用 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-p8080

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(() => {
    //把图片转成tensorconst imgt = tf.node.decodeImage(newUint8Array(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. 运行执行文件

noderun.js

2. 调用模型

2.1. 全局安装 parcel

npminstall i parcel

2.2. 创建页面 index.html

<scriptsrc="script.js"></script><inputtype="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';

exportfunctionimg2x(imgEl){
    return tf.tidy(() => {
        const input = tf.browser.fromPixels(imgEl)
            .toFloat()
            .sub(255 / 2)
            .div(255 / 2)
            .reshape([1, 224, 224, 3]);
        return input;
    });
}

exportfunctionfile2img(f) {
    returnnewPromise(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. 打包项目并运行

parcelindex.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)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值