前言:

前面已经通过采集拿到了图片,并且也手动对图片做了标注。接下来就要通过 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).div(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

<script src="script.js"></script>
<input type="file" onchange="predict(this.files[0])">
<br>

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)
.div(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)

用 Tensorflow.js 做了一个动漫分类的功能(二)的更多相关文章

  1. TensorFlow.js之根据数据拟合曲线

    这篇文章中,我们将使用TensorFlow.js来根据数据拟合曲线.即使用多项式产生数据然后再改变其中某些数据(点),然后我们会训练模型来找到用于产生这些数据的多项式的系数.简单的说,就是给一些在二维 ...

  2. 关于最近在做的一个js全屏轮播插件

    最近去面试了,对方要求我在一个星期内用原生的js代码写一个全屏轮播的插件,第一想法就是跟照片轮播很相似,只是照片轮播是有定义一个宽高度大小已经确定了的容器用来存储所有照片,然后将照片全部左浮动,利用m ...

  3. 做了一个图片等比缩放的js

    做了一个图片等比缩放的js 芋头 发布在view:8447   今天改了一下博客的主题,发现博客主题在ie6下变样了,后来发现是因为某篇文章里的某个图片太大了撑开了容器,导致样式错位,前几天公司需求里 ...

  4. 4-13 Webpacker-React.js; 用React做一个下拉表格的功能: <详解>

    Rails5.1增加了Webpacker: Webpacker essentially is the decisions made by the Rails team and bundled up i ...

  5. 用 JS 做一个数独游戏(二)

    用 JS 做一个数独游戏(二) 在 上一篇博客 中,我们通过 Node 运行了我们的 JavaScript 代码,在控制台中打印出来生成好的数独终盘.为了让我们的数独游戏能有良好的体验,这篇博客将会为 ...

  6. 用 JS 做一个数独游戏(一)

    用 JS 做一个数独游戏(一) 数独的棋盘由 9x9 的方格组成,每一行的数字包含 1 ~ 9 九个数字,并且每一列包含 1 ~ 9 这 9 个不重复的数字,另外,整个棋盘分为 9 个 3x3 的块, ...

  7. 用js给闺女做了一个加减乘除的html

    下班回家用二十分钟给闺女做了一个加减乘除的页面,顺便记录下代码,时间仓促,后期再来修改吧 目录结构 -yq --menu.html --yq.html --yq50.html --yq70.html ...

  8. 用js,css3 做的一个球

    用css3属性很容易做一个立方体,但是要做一个球体,会相对复杂些 原理是:球可以看做是由无数个圆圈构成,然后就可以用圆圈来做球, 下面的例子是我做的一个小球,由72个圆圈组成.如果把每个圆圈的背景颜色 ...

  9. JS 做时钟

    今天,给大家分享一个用JS做的时钟. <!DOCTYPE html><html> <head> <meta charset="utf-8" ...

  10. JS一般般的网页重构可以使用Node.js做些什么(转)

    一.非计算机背景前端如何快速了解Node.js? 做前端的应该都听过Node.js,偏开发背景的童鞋应该都玩过. 对于一些没有计算机背景的,工作内容以静态页面呈现为主的前端,可能并未把玩过Node.j ...

随机推荐

  1. 性能_1 Jmeter脚本编写

    一.万能法 先把项目启动 打开项目接口文档,接口文档: 一般是开发 特别注意事项:当你的接口请求参数为json格式时,一定要写请求头,请求头中一定要有 Content-Type: applicatio ...

  2. CentOS 8 部署 ELK 8.7真的是方便呀

    之前装过一次 ELK 7.7,相比之下装 8.7可方便太多了~ CentOS版本 CentOS-8.5.2111-x86_64-dvd1 JAVA ELK会自己使用内置版本的JDK ElasticSe ...

  3. 2022-12-25:etcd可以完全替代zookeeper,原因是k8s用的etcd,不用担心不成熟。请问etcd部署在k3s中,yaml如何写?

    2022-12-25:etcd可以完全替代zookeeper,原因是k8s用的etcd,不用担心不成熟.请问etcd部署在k3s中,yaml如何写? 答案2022-12-25: 用户名:root 密码 ...

  4. 2021-08-11:按要求补齐数组。给定一个已排序的正整数数组 nums,和一个正整数 n 。从 [1, n] 区间内选取任意个数字补充到 nums 中,使得 [1, n] 区间内的任何数字都可以用

    2021-08-11:按要求补齐数组.给定一个已排序的正整数数组 nums,和一个正整数 n .从 [1, n] 区间内选取任意个数字补充到 nums 中,使得 [1, n] 区间内的任何数字都可以用 ...

  5. 2021-09-08:每一个项目都有三个数,[a,b,c]表示这个项目a和b乐队参演,花费为c。每一个乐队可能在多个项目里都出现了,但是只能被挑一次。nums是可以挑选的项目数量,所以一定会有nums

    2021-09-08:每一个项目都有三个数,[a,b,c]表示这个项目a和b乐队参演,花费为c.每一个乐队可能在多个项目里都出现了,但是只能被挑一次.nums是可以挑选的项目数量,所以一定会有nums ...

  6. WPF入门教程系列二十四——DataGrid使用示例(1)

    WPF入门教程系列二--Application介绍 WPF入门教程系列三--Application介绍(续) WPF入门教程系列四--Dispatcher介绍 WPF入门教程系列五--Window 介 ...

  7. Redash 可视化BI系统部署安装及简单使用

    这篇文章主要为介绍一下Redash的使用和安装 概览 Redash 主要使用的语言为 Python 和 TypeScript 这个安装主要是基于Docker 来安装的,官网教程基本没有不是基于Dock ...

  8. vst实例(2) 创建VST

    前面我们知道,创建一个虚拟树,应该首先告知VST节点数据的大小(即nodedatasize),其实在创建树结构时,这一点并不是必须的,而是如果你需要让VST的每一个节点能指向一定的数据,从而在执行树的 ...

  9. 一分钟学一个 Linux 命令 - cd

    前言 大家好,我是 god23bin.欢迎来到这个系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天让我们从 cd 命令开始,掌握在 Linux 系统中切换目录的技巧. 什么是 cd 命令 ...

  10. 2023-06-03:redis中pipeline有什么好处,为什么要用 pipeline?

    2023-06-03:redis中pipeline有什么好处,为什么要用 pipeline? 答案2023-06-03: Redis客户端执行一条命令通常包括以下四个阶段: 1.发送命令:客户端将要执 ...