JavaScript 深度学习 - Hello World
作者: 发布于:

为了更好的阅读体验,欢迎语雀搜索 【JavaScript 深度学习】 关注

1. 前言

2017年的双十一为了解决运营图片审核任务繁重的问题,我们发起了素材智能审核项目。在这个项目中,我们基于深度学习拿到了很好的项目结果,至今已经审核数千万张图片。

后续我们也尝试做一个 JavaScript 版本 TensorFlow - Tens.js,不过发现很多问题比较难解决。好在后续 TensorFlow 官方发布了 JavaScript 版本,并在近期发布 2.0 版本。

最近几年深度学习在人工智能领域取得了非凡进展,在很多任务中远远超过人类的表现,我相信深度学习后续会在更多的工作场景中被广泛使用。随着 TensorFlow.js 的发布,我们的学习成本进一步降低,我认为深度学习的工程化、大众化是接下来的必然趋势。

2. 介绍

本系列不需要深度学习基础,会避免使用数学符号,会通过代码示例来介绍概念。

代码示例使用 JavaScript,使用 TensorFlow.js(浏览器/Node.js)框架。

3. Hello World

几乎所有深度学习相关的教程都会以 Minist 项目为例,Mnist 是机器学习一个经典的数据集,包含 60000 张训练图片和 10000 张测试图片,在本项目中我们会基于这些图片数据通过深度学习训练出一个图像识别的模型。

图像目前也是深度学习最有优势的场景,通过该项目,我们也可以理解TensorFlow.js 的基础用法和深度学习的一些核心概念。

简单来看,通过深度学习解决一个问题一般抽象为下面的流程

3.1. 数据预处理

对于浏览器环境来说,数据处理与其他环境相比还是比较麻烦。 这次 Mnist 我们使用单张雪碧图来存储,对应 label 使用二进制进行存储。我们可以直接从 Google 的 url 中获取。

3.1.1. 图片数据预处理

图片数据的处理在浏览器场景非常有用,无论是图片数据集处理、模型预测时的上传图片处理都经常用到

const MNIST_IMAGES_SPRITE_PATH =
    'https://storage.googleapis.com/learnjs-data/model-builder/mnist_images.png';
const MNIST_LABELS_PATH =
    'https://storage.googleapis.com/learnjs-data/model-builder/mnist_labels_uint8';

在浏览器将图片转换成二进制数据,需要

const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.crossOrigin = '';
// 图片加载后,使用 canvas drawImage,然后 getImageData 获取二进制数据
img.onload = () => {
  img.width = img.naturalWidth;
  img.height = img.naturalHeight;
  ctx.drawImage(img, 0, i * chunkSize, img.width, chunkSize, 0, 0, img.width,chunkSize);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
}
img.src = MNIST_IMAGES_SPRITE_PATH;

3.1.2. ArrayBuffer & DataView

在 Tensorflow.js 的使用中,会经常使用 ArrayBuffer 类型,比如Canvas、Fetch API、File API。

// Canvas Uint8ClampedArray
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const uint8ClampedArray = imageData.data; 

// Fetch ArrayBuffer
fetch(url)
.then(function(response){
  return response.arrayBuffer()
})
.then(function(arrayBuffer){
  // ...
});

// File ArrayBuffer
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
  const arrayBuffer = reader.result;
  // ···
};

3.1.3. 测试数据与验证数据

如果用全量数据来训练模型,模型很容易和数据集过度拟合,并不一定能很好的应对未来产生的新数据。为了解决这个问题,一般我们会将数据分为多份,分别用来做训练和验证使用。

  this.datasetLabels = new Uint8Array(await labelsResponse.arrayBuffer());

  // Slice the the images and labels into train and test sets.
  this.trainImages =
      this.datasetImages.slice(0, IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
  this.testImages = this.datasetImages.slice(IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
  this.trainLabels =
      this.datasetLabels.slice(0, NUM_CLASSES * NUM_TRAIN_ELEMENTS);
  this.testLabels =
      this.datasetLabels.slice(NUM_CLASSES * NUM_TRAIN_ELEMENTS);

3.2. 构建模型

深度学习本质上是构建了一个多层网络模型,不能层会来实现 特征提取、关联学习的工作,通过 TensorFlow 我们可以很简单的构建一个自己的深度网络

function createDenseModel() {
  const model = tf.sequential();
  model.add(tf.layers.flatten({inputShape: [IMAGE_H, IMAGE_W, 1]}));
  model.add(tf.layers.dense({units: 42, activation: 'relu'}));
  model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
  return model;
}
// 下一章会来介绍更多细节

3.3. 训练模型

模型编译后,便可进行训练,目前 TensorFlow.js 也提供 tfjs-vis 方便将训练过程可视化

model.compile({
  optimizer,
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy'],
});

await model.fit(trainData.xs, trainData.labels, {
  batchSize,
  validationSplit,
  epochs: trainEpochs
});

4. 线上示例

线上 demo
官方示例代码

5. 后续

下一周会来介绍 TensorFlow.js 中的核心概念

6. 参考

tfjs-examples

Python 深度学习