用Tensorflow。js做了一个动漫分类的功能(二)
前言:
前面已经通过采集拿到了图片,并且也手动对图片做了标注。接下来就要通过 Tensorflow.js 基于 mobileNet 训练模型,最后就可以实现在采集中对图片进行自动分类了。
这种功能在应用场景里就比较多了,比如图标素材站点,用户通过上传一个图标,系统会自动匹配出相似的图标,还有二手平台,用户通过上传闲置物品图片,平台自动给出分类等,这些也都是前期对海量图片进行了标注训练而得到一个损失率极低的模型。下面就通过简单的代码实现一个小的动漫分类。
环境:
Node
Http-Server
Parcel
Tensorflow
编码:
1. 训练模型
1.1. 创建项目,安装依赖包 npm install @tensorflow/tfjs --legacy-peer-deps npm install @tensorflow/tfjs-node-gpu --legacy-peer-deps
1.2. 全局安装 Http-Server npm install i http-server
1.3. 下载 mobileNet 模型文件 (网上有下载)
1.4. 根目录下启动 Http 服务 (开启跨域),用于 mobileNet 和训练结果的模型可访问 http-server --cors -p 8080
1.5. 创建训练执行脚本 run.js const tf = require("@tensorflow/tfjs-node-gpu"); const getData = require("./data"); const TRAIN_PATH = "./动漫分类/train"; const OUT_PUT = "output"; const MOBILENET_URL = "http://127.0.0.1:8080/data/mobilenet/web_model/model.json"; (async () => { const { ds, classes } = await getData(TRAIN_PATH, OUT_PUT); console.log(ds, classes); //引入别人训练好的模型 const mobilenet = await tf.loadLayersModel(MOBILENET_URL); //查看模型结构 mobilenet.summary(); const model = tf.sequential(); //截断模型,复用了86个层 for (let i = 0; i < 86; ++i) { const layer = mobilenet.layers[i]; layer.trainable = false; model.add(layer); } //降维,摊平数据 model.add(tf.layers.flatten()); //设置全连接层 model.add(tf.layers.dense({ units: 10, activation: "relu"//设置激活函数,用于处理非线性问题 })); model.add(tf.layers.dense({ units: classes.length, activation: "softmax"//用于多分类问题 })); //设置损失函数,优化器 model.compile({ loss: "sparseCategoricalCrossentropy", optimizer: tf.train.adam(), metrics:["acc"] }); //训练模型 await model.fitDataset(ds, { epochs: 20 }); //保存模型 await model.save(`file://${process.cwd()}/${OUT_PUT}`); })();
1.6. 创建图片与 Tensor 转换库 data.js const fs = require("fs"); const tf = require("@tensorflow/tfjs-node-gpu"); const img2x = (imgPath) => { const buffer = fs.readFileSync(imgPath); //清除数据 return tf.tidy(() => { //把图片转成tensor const imgt = tf.node.decodeImage(new Uint8Array(buffer), 3); //调整图片大小 const imgResize = tf.image.resizeBilinear(imgt, [224, 224]); //归一化 return imgResize.toFloat().sub(255 / 2).p(255 / 2).reshape([1, 224, 224, 3]); }); } const getData = async (traindir, output) => { let classes = fs.readdirSync(traindir, "utf-8"); fs.writeFileSync(`./${output}/classes.json`, JSON.stringify(classes)); const data = []; classes.forEach((dir, dirIndex) => { fs.readdirSync(`${traindir}/${dir}`) .filter(n => n.match(/jpg$/)) .slice(0, 1000) .forEach(filename => { const imgPath = `${traindir}/${dir}/${filename}`; data.push({ imgPath, dirIndex }); }); }); console.log(data); //打乱训练顺序,提高准确度 tf.util.shuffle(data); const ds = tf.data.generator(function* () { const count = data.length; const batchSize = 32; for (let start = 0; start < count; start += batchSize) { const end = Math.min(start + batchSize, count); console.log("当前批次", start); yield tf.tidy(() => { const inputs = []; const labels = []; for (let j = start; j < end; ++j) { const { imgPath, dirIndex } = data[j]; const x = img2x(imgPath); inputs.push(x); labels.push(dirIndex); } const xs = tf.concat(inputs); const ys = tf.tensor(labels); return { xs, ys }; }); } }); return { ds, classes }; } module.exports = getData;
1.7. 运行执行文件 node run.js
2. 调用模型
2.1. 全局安装 parcel npm install i parcel
2.2. 创建页面 index.html
2.3. 创建模型调用预测脚本 script.js import * as tf from "@tensorflow/tfjs"; import { img2x, file2img } from "./utils"; const MODEL_PATH = "http://127.0.0.1:8080/t7"; const CLASSES = ["假面骑士","奥特曼","海贼王","火影忍者","龙珠"]; window.onload = async () => { const model = await tf.loadLayersModel(MODEL_PATH + "/output/model.json"); window.predict = async (file) => { const img = await file2img(file); document.body.appendChild(img); const pred = tf.tidy(() => { const x = img2x(img); return model.predict(x); }); const index = pred.argMax(1).dataSync()[0]; console.log(pred.argMax(1).dataSync()); let predictStr = ""; if (typeof CLASSES[index] == "undefined") { predictStr = BRAND_CLASSES[index]; } else { predictStr = CLASSES[index]; } setTimeout(() => { alert(`预测结果:${predictStr}`); }, 0); }; };
2.4. 创建图片 tensor 格式转换库 utils.js import * as tf from "@tensorflow/tfjs"; export function img2x(imgEl){ return tf.tidy(() => { const input = tf.browser.fromPixels(imgEl) .toFloat() .sub(255 / 2) .p(255 / 2) .reshape([1, 224, 224, 3]); return input; }); } export function file2img(f) { return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(f); reader.onload = (e) => { const img = document.createElement("img"); img.src = e.target.result; img.width = 224; img.height = 224; img.onload = () => resolve(img); }; }); }
2.5. 打包项目并运行 parcel index.html
2.6. 运行效果
注意:
1. 模型训练过程报错
Input to reshape is a tensor with 50176 values, but the requested shape has 150528
1.1. 原因
张量 reshape 不对,实际输入元素个数与所需矩阵元素个数不一致,就是采集过来的图片有多种图片格式,而不同格式的通道不同 (jpg3 通道,png4 通道,灰色图片 1 通道),在将图片转换 tensor 时与代码里的张量形状不匹配。
1.2. 解决方法
一种方法是删除灰色或 png 图片,其二是修改代码 tf.node.decodeImage (new Uint8Array (buffer), 3)
不要看见别人发光,就觉得自己暗淡,否定自己的选择看到读书群分享千万不要看见别人发光,就觉得自己暗淡!很有感触。讲到生活中人和人的节奏不一样!有人三分钟泡面,有人一小时煲汤。你选择了你要的方式就坚定走下去,别胡思乱想!阳光少女,心
帽爷头条创作挑战赛九十四岁的帽爷,老伴走的早,虽然膝下四男双女,儿孙满堂一大片,可他老人家偏偏一直坚守着独守一人过日子。就这样不但不拖累家人,还时不时的给小字辈们点补贴,那日子过得就是
静下心来读书,它会一点一滴地滋养你1hr为什么要读书?很多时候,我们之所以陷于痛苦,烦恼不断,根源就是读书太少,囿于有限的学识和认知,没有足够的智慧来解决生活中的难题。读一书,增一智。阅读,才是最好的疗心良药,是真
想日常生活中我们是如何进行思考的?想你想对了什么,想错了什么,英尼基海斯著,姚瑞元译,人民邮电出版社,2022年11月。我们大部分思考过程是没有意识的当我们说某人不能一边走路一边嚼口香糖时,这其实是一个由来已久的侮
让自己保持优秀的5个习惯树立有意义的目标比起着急赶路,更重要的是先做好充足的准备,制定好缜密的计划。一个目标清晰的人,在前进的路上往往不会迷失方向。试试给自己设定一个有意义且实际的目标吧。找准目标,踏踏实
五十岁以后,越能忍的人,往往越有福!年过半百之后,很多该经历的事情我们也都经历过了。慢慢的看人看事都渐渐通透,过去的激进也逐渐转变成了平和。以前无法放下的事情,现在也渐渐的看开放下了。这就是来自于心灵深处的成长,非常
男人老了的标准是什么?1老男人最显著的特点就是贪财怕死。活了几十年了,经历过太多的事情,老男人明白了,只有钱和健康是最靠得住的。他知道钱能够解决90以上的问题,他也知道,只有健康的身体才能够让他享受生活
父子俩的第56场马拉松,打卡完毕!来源央视新闻今天(19日)上午,深圳马拉松赛场上,一对父子的身影吸引了很多人的目光。来自浙江金华的罗书坚用三轮手推车推着儿子小柏,完成他们的第56场马拉松旅程。约21公里的半程马拉
中美芯片公司科技战,美国限制芯片出口!华盛顿最新的出口限制禁止美国人支持目标中国公司的开发或生产芯片受影响公司的几位高管曾在美国半导体行业工作,并在返回中国之前获得了公民身份美国对中国芯片公司的新限制可能会使目标公司的
连州市卫健局开展院前急救医疗质评工作日前,连州市卫生健康局组织120急救指挥中心,对市区4家急救网络医院开展院前急救医疗质量考核工作,进一步提升院前急救综合服务能力,促进连州市院前急救医疗工作的同质化发展。本次考核主
新视野丨博爱卫生站助力农村医疗服务石榴云新疆日报记者于熙基层医疗网底,事关群众福祉,人民安康。新春伊始,中共中央国务院发布关于做好2023年全面推进乡村振兴重点工作的意见,提出推进医疗卫生资源县域统筹,加强乡村两级