使用TensorFlow.js在Node.js中进行机器学习

7,425 阅读9分钟

TensorFlow.js是流行开源库的新版本,它为JavaScript带来了深度学习。 开发人员现在可以使用高级库API来定义,训练和运行机器学习模型。

开发人员利用预训练的模型,仅用几行JavaScript即可轻松执行复杂的任务,例如视觉识别,生成音乐或检测人体姿势。

从Web浏览器的前端库开始,最近的更新增加了对Node.js的实验性支持。 这使得TensorFlow.js可以在后端JavaScript应用程序中使用,而不必使用Python。

阅读有关该库的信息,我想通过一个简单的任务对其进行测试...

使用TensorFlow.js通过Node.js进行图像识别

不幸的是,大多数文档和示例代码都是使用浏览器中的库。 Node.js尚未扩展用于简化加载和使用预训练模型的项目程序。 完成这项工作的确使我花了很多时间阅读该库的Typescript源文件。 👎

但是,经过几天的夜以继日的学习,我完成了! 欢呼! 🤩

在深入研究代码之前,让我们先概述一下不同的TensorFlow库。

TensorFlow

TensorFlow是一个用于机器学习应用程序的开源软件库。 TensorFlow可用于实现神经网络和其他深度学习算法。

TensorFlow于2015年11月由Google发布,最初是一个Python库。 它使用基于CPU或GPU的计算来训练和评估机器学习模型。 该库最初旨在在具有昂贵GPU的高性能服务器上运行。

最近的更新已将软件扩展到如移动设备和Web浏览器环境中运行。

TensorFlow Lite

Tensorflow Lite是移动和嵌入式设备库的轻量级版本,于2017年5月发布。该版本随附了一系列针对视觉识别任务的全新预训练深度学习模型,称为MobileNetMobileNet模型旨在在资源受限的环境(例如移动设备)中高效运行。

TensorFlow.js

Tensorflow Lite之后,TensorFlow.js于2018年3月发布。该版本的库旨在在浏览器中运行,建立在一个名为deeplearn.js的早期项目上。 WebGL提供对库的GPU访问。 开发人员使用JavaScript API训练,加载和运行模型。

TensorFlow.js最近通过tfjs-node的扩展库在Node.js上运行。

Node.js扩展是一个Alpha版本,仍处于积极开发中

将已有模型引入Tensorflow.js

可以使用TensorFlow.js库执行现有的TensorFlowKeras模型。 模型需要在执行之前使用此工具转换为新格式。 Github提供了用于图像分类,姿势检测和k近邻的预训练和转换模型。

在nodejs中使用tensorflow.js

安装tensorflow库

TensorFlow.jsNPM库中进行安装.

@tensorflow/tfjs - 核心TensorFlow.js包

@tensorflow/tfjs-node - TensorFlow.js的Node.js 扩展

@tensorflow/tfjs-node-gpu - TensorFlow.js 的GPU支持的nodejs扩展

npm install @tensorflow/tfjs @tensorflow/tfjs-node
// or...
npm install @tensorflow/tfjs @tensorflow/tfjs-node-gpu

两个Node.js扩展都使用本机依赖项,这些依赖项将根据需要进行编译。

加载tensorflow库

TensorFlow的JavaScript API可供用户使用。 Node.js支持的扩展模块不会公开其他API。

const tf = require('@tensorflow/tfjs')
// Load the binding (CPU computation)
require('@tensorflow/tfjs-node')
// Or load the binding (GPU computation)
require('@tensorflow/tfjs-node-gpu')

加载tensorflow模型

TensorFlow.js提供了一个NPM库(tfjs-models),可轻松加载用于图像分类,姿势检测和k近邻的预训练和转换后的模型。

用于图像分类的MobileNet模型是经过训练1000个不同类别的深度神经网络。

在项目的自述文件中,以下示例代码用于加载模型。

import * as mobilenet from '@tensorflow-models/mobilenet';

// Load the model.
const model = await mobilenet.load();

我遇到的第一个挑战是,这在Node.js上不起作用。

Error: browserHTTPRequest is not supported outside the web browser.

查看源代码,mobilenet库是基础tf.Model类的包装。 调用load()方法时,它将自动从外部HTTP地址下载正确的模型文件,并实例化TensorFlow模型。

Node.js扩展尚不支持HTTP请求以动态检索模型。 相反,必须从文件系统手动加载模型。

阅读了库的源代码后,我设法创建了一种解决方法……

从文件系统加载模型

如果手动创建了MobileNet类,则无需调用模块的load方法,而是可以使用本地文件系统路径覆盖包含模型的HTTP地址的自动生成的路径变量。 完成此操作后,在类实例上调用load方法将触发文件系统加载器类,而不是尝试使用基于浏览器的HTTP加载器。

const path = "mobilenet/model.json"
const mn = new mobilenet.MobileNet(1, 1);
mn.path = `file://${path}`
await mn.load()

太棒了,它有效!

但是,模型文件从何而来?

MobileNet模型

TensorFlow.js的模型包含两种文件类型,一种是存储在JSON中的模型配置文件,另一种是二进制格式的模型权重。 通常将模型权重分割为多个文件,以供浏览器更好地缓存。

查看MobileNet模型的自动加载的代码,可以从该地址处的公共存储中检索模型配置和权重片段。

https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v${version}_${alpha}_${size}/

URL中的模板参数是指此处列出的模型版本。 该页面还显示了每个版本的分类准确性结果。

根据源代码,使用tensorflow-models / mobilenet库只能加载MobileNet v1模型。

HTTP检索代码从该位置加载model.json文件,然后以递归方式获取所有引用的模型权重分片。 这些文件的格式为groupX-shard1of1

手动下载模型

通过检索模型配置文件,解析引用的权重文件并手动下载每个权重文件,可以将所有模型文件保存到文件系统中。

我想使用1.0的alpha值和224像素的图像大小的MobileNet V1模块。 这为我提供了以下用于模型配置文件的URL。

https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/model.json

在本地下载此文件后,我可以使用jq工具来解析所有文件名称。

$ cat model.json | jq -r ".weightsManifest[].paths[0]"
group1-shard1of1
group2-shard1of1
group3-shard1of1
...

使用sed工具,我可以为这些名称加上HTTP URL前缀,为每个文件生成的URL。

$ cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//'
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group1-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group2-shard1of1
https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_1.0_224/group3-shard1of1
...

然后,使用 parallelcurl命令,可以将所有这些文件下载到本地目录中。

cat model.json | jq -r ".weightsManifest[].paths[0]" | sed 's/^/https:\/\/storage.googleapis.com\/tfjs-models\/tfjs\/mobilenet_v1_1.0_224\//' |  parallel curl -O

图片分类 TensorFlow.js提供了此示例代码,以演示图像的返回分类。

const img = document.getElementById('img');

// Classify the image.
const predictions = await model.classify(img)   

由于缺少DOM,因此不适用于Node.js。

classify方法接受许多DOM元素(画布,视频,图像),并将自动从这些元素检索图像字节并将其转换为tf.Tensor3D类,该类用作模型的输入。 或者,可以直接传递tf.Tensor3D输入。

我发现与其尝试使用外部包来模拟Node.js中的DOM元素,还不如手动构造tf.Tensor3D

从图像生成Tensor3D 读取用于将DOM元素转换为Tensor3D类的方法的源代码,以下输入参数用于生成Tensor3D类。

const values = new Int32Array(image.height * image.width * numChannels);
// fill pixels with pixel channel bytes from image
const outShape = [image.height, image.width, numChannels];
const input = tf.tensor3d(values, outShape, 'int32'); 

pixel是类型为(Int32Array)的2D数组,其中包含每个像素的通道值的顺序列表。 numChannels是每个像素的通道值数量。

为JPEG创建输入值

jpeg-js库是Node.js的纯JavaScript JPEG编码器和解码器。 使用此库,可以提取每个像素的RGB值。

const pixel = jpeg.decode(buffer,true);

这将为每个像素(宽度*高度)返回一个具有四个通道值(RGBA)的Uint8ArrayMobileNet模型仅使用三个颜色通道(RGB)进行分类,而忽略了alpha通道。 此代码将四通道数组转换为正确的三通道版本。

const numChannels = 3;
const numPixels = image.width * image.height;
const values = new Int32Array(numPixels * numChannels);

for (let i = 0; i < numPixels; i++) {
  for (let channel = 0; channel < numChannels; ++channel) {
    values[i * numChannels + channel] = pixels[i * 4 + channel];
  }
}

MobileNet模型输入要求

使用的MobileNet模型将宽度和高度为224像素的图像分类。对于三个通道像素值的每个,输入张量必须包含介于-1和1之间的浮点值。

分类前,需要重新调整不同尺寸图像的输入值的大小。另外,来自JPEG解码器的像素值在0-255的范围内,而不是在-1到1的范围内。在分类之前,这些值也需要转换。

TensorFlow.js有一些库方法可以简化此过程,幸运的是,tfjs-models / mobilenet库可以自动处理此问题! 👍

开发人员可以将int32类型和不同维度的Tensor3D输入传递给分类方法,然后在分类之前将输入转换为正确的格式。这意味着超级🕺🕺🕺。

预测

Tensorflow中的MobileNet模型经过训练,可以识别ImageNet数据集中前1000个类别中的实体。模型输出这些实体中的每个实体在被分类图像中的概率。

可以在此文件中找到正在使用的模型的训练有素的课程的完整列表。

tfjs-models / mobilenet库在MobileNet类上公开了一个分类方法,以从图像输入中返回概率最高的前X个类。

const predictions = await mn_model.classify(input, 10);

predictions是X类和概率形式的数组,格式如下。

{
  className: 'panda',
  probability: 0.9993536472320557
}

例子

研究了如何在Node.js上使用TensorFlow.js库和MobileNet模型之后,此脚本将对作为命令行参数给出的图像进行分类。

源代码 将此脚本文件和程序包描述符保存到本地文件。

//package.json
{
  "name": "tf-js",
  "version": "1.0.0",
  "main": "script.js",
  "license": "MIT",
  "dependencies": {
    "@tensorflow-models/mobilenet": "^0.2.2",
    "@tensorflow/tfjs": "^0.12.3",
    "@tensorflow/tfjs-node": "^0.1.9",
    "jpeg-js": "^0.3.4"
  }
}
//script.js
const tf = require('@tensorflow/tfjs')
const mobilenet = require('@tensorflow-models/mobilenet');
require('@tensorflow/tfjs-node')

const fs = require('fs');
const jpeg = require('jpeg-js');

const NUMBER_OF_CHANNELS = 3

const readImage = path => {
  const buf = fs.readFileSync(path)
  const pixels = jpeg.decode(buf, true)
  return pixels
}

const imageByteArray = (image, numChannels) => {
  const pixels = image.data
  const numPixels = image.width * image.height;
  const values = new Int32Array(numPixels * numChannels);

  for (let i = 0; i < numPixels; i++) {
    for (let channel = 0; channel < numChannels; ++channel) {
      values[i * numChannels + channel] = pixels[i * 4 + channel];
    }
  }

  return values
}

const imageToInput = (image, numChannels) => {
  const values = imageByteArray(image, numChannels)
  const outShape = [image.height, image.width, numChannels];
  const input = tf.tensor3d(values, outShape, 'int32');

  return input
}

const loadModel = async path => {
  const mn = new mobilenet.MobileNet(1, 1);
   mn.path = `file://${path}`
  await mn.load()
  return mn
}

const classify = async (model, path) => {
  const image = readImage(path)
  const input = imageToInput(image, NUMBER_OF_CHANNELS)

  const  mn_model = await loadModel(model)
  const predictions = await mn_model.classify(input)

  console.log('classification results:', predictions)
}

if (process.argv.length !== 4) throw new Error('incorrect arguments: node script.js <MODEL> <IMAGE_FILE>')

classify(process.argv[2], process.argv[3])

测试

  • 按照上述说明将模型文件下载到mobilenet目录。

  • 使用NPM安装项目依赖项

npm install

  • 下载样本JPEG文件进行分类 wget http://bit.ly/2JYSal9 -O panda.jpg

  • 使用模型文件运行脚本,然后输入图像作为参数。 node script.js mobilenet/model.json panda.jpg 如果一切正常,则应将以下输出打印到控制台。
classification results: [ {
    className: 'giant panda, panda, panda bear, coon bear',
    probability: 0.9993536472320557
} ]

图片正确分类为包含可能性为99.93%的熊猫! 🐼🐼🐼

结论

TensorFlow.jsJavaScript开发人员带来了深度学习的力量。 在TensorFlow.js库中使用经过预训练的模型,可以轻松地以最少的工作量和代码来扩展具有复杂机器学习任务的JavaScript应用程序。

TensorFlow.js已作为基于浏览器的库发布,现已扩展为可在Node.js上运行,尽管并非所有工具和实用程序都支持新的运行时。 经过几天的研究,我可以将库与MobileNet模型一起使用,以便对本地文件中的图像进行视觉识别。

让它在Node.js运行时中运行意味着我现在继续下一步…使它在无服务器功能内运行! 很快回来阅读有关TensorFlow.js的下一次冒险。 👋