如何使用JavaScript实现GPU加速神经网络

根据 GitHub Octoverse 2017 报告,JavaScript 是过去一年中 GitHub 最流行的编程语言。根据 pull requests 的数量,JavaScript 的体量与 Python、Java 以及 Go 语言的总和相当。

JavaScript 已经征服了 Web,并在服务器、移动电话、桌面和其他平台上取得了进展。

与此同时,GPU 加速的使用已经远远超出了计算机图形学的范围,它现在已经成为机器学习的一个组成部分。

训练深层神经网络是一个计算密集型过程,深度神经网络在机器智能的许多重要领域得到了当前最优结果。

本文着眼于这些趋势的持续融合,并概述了将 GPU 加速的神经网络引入 JavaScript 的一些项目。

概述

本文列出的所有项目都是正被社区积极维护的,它们在 GitHub 上有着数千 stars,并且通过 NPM 或 CDN 进行分发。

它们都是通过 WebGL 在浏览器中实现 GPU 加速的,如果没有合适的显卡,则返回到 CPU 模式。

本概述不包含旨在运行现有模型(尤其是使用 Python 训练的模型)的库。

最后,有 4 个项目被列入清单。

尽管 deeplearn.js 的特征集是面向神经网络的,但是它也可被看作是一个通用的机器学习框架。Propel 是一个用于科学计算的库,提供自动微分功能。gpu.js 提供了在 GPU 上运行 JavaScript 函数的便捷方式。Brain.js 是一个较老的神经网络库的延续,它使用 gpu.js 来完成硬件加速。

Deeplearn.js

Deeplearn.js 是以上四个项目中最流行的,被描述为「用于机器智能的硬件加速 JavaScript 库」。它由 Google Brain 团队和一个超过 50 位贡献者的社区共同支持。两位主要作者是 Daniel Smilkov 和 Nikhil Thorat.

  1. import * as dl from 'deeplearn'

  2. const xs = inputXs.as4D(-1, IMAGE_HEIGHT, IMAGE_WIDTH, 1)

  3. const conv1Weights = dl.variable(

  4.    dl.randomNormal([FILTER_HEIGHT, FILTER_WIDTH, 1, NUMBER_FILTERS], 0, 0.1) as dl.Tensor4D)

  5. const layer1 = dl.tidy(() => {

  6.    return xs.conv2d(conv1Weights, 1, 'same')

  7.        .relu()

  8.        .maxPool([2, 2], STRIDES, PADDING)

  9. })

deeplearn.js 中卷积层的定义

deeplearn.js 是仿照 TensorFlow 用 TypeScript 写成的。deeplearn.js 支持由 Google Brain 主要开源项目提供的一个功能子集。API 基本上拥有 3 个部分(API 地址:http://www.deeplearnjs.org/docs/api/index.html)。

第一部分包括用来创建、初始化以及变换张量的函数(http://www.deeplearnjs.org/docs/api/index.html#Tensors-Creation),用类似数组的结构来保存数据。

第二部分提供了在张量上执行的操作(http://www.deeplearnjs.org/docs/api/index.html#Operations-Arithmetic),包括基本的数学运算、规约(reduction)、正则化以及卷积。对循环神经网络的支持目前还处于初级阶段,但是已包括 LSTM 单元的堆叠(http://www.deeplearnjs.org/docs/api/index.html#dl.multiRNNCell)。

API 的第三部分围绕模型训练展开。所有流行优化器,从随机梯度下降到 Adam 都包含在其中。不过,目前 reference 中提及的损失函数只有交叉熵损失函数。

API 其他部分用来进行环境设置和资源管理。

可以通过 headless-gl(https://github.com/stackgl/headless-gl0)在 node.js 中实现 GPU 加速的实验(参见 issue #49,https://github.com/PAIR-code/deeplearnjs/issues/49)。

项目网站有很多优秀的 demo(http://www.deeplearnjs.org/index.html#demos),包括使用循环神经网络进行钢琴演奏、用来构建模型的可视化界面,以及基于 SqueezeNet(一个使用较少参数的图像分类器)的 webcam 应用。

PropelJS

PropelJS 被描述为「可微分编程的 JavaScript」。这份工作由主要作者 Ryan Dahl 和 Bert Belder 以及其他 11 位贡献者完成。

  1. import * as pr from "propel"

  2. export async function train(maxSteps = 0) {

  3.  const ds = pr.dataset("mnist/train").batch(128).repeat(100)

  4.  const exp = await pr.experiment("exp001")

  5.  for (const batchPromise of ds) {

  6.    const { images, labels } = await batchPromise

  7.    exp.sgd({ lr: 0.01 }, (params) =>

  8.      images.rescale([0, 255], [-1, 1])

  9.        .linear("L1", params, 200).relu()

  10.        .linear("L2", params, 100).relu()

  11.        .linear("L3", params, 10)

  12.        .softmaxLoss(labels))

  13.    if (maxSteps && exp.step >= maxSteps) break

  14.  }

  15. }

在 MNIST 数据集上使用 Propel 训练一个三层的前馈神经网络。

自动微分(AD)是这个项目的核心,它使得我们无需手动指定导数。给定一个由支持的张量运算定义的函数 f(x),它的梯度函数可以使用 grad(http://propelml.org/docs/#grad)得到。多变量的情况可以使用 multigrad 完成(http://propelml.org/docs/#multigrad)。

除了自动微分之外,目前尚不清楚该项目的方向。虽然网站上提到其目标是成为「类似 numpy 的基础架构」,但该项目目前仍在开发中,并且包含与神经网络(http://propelml.org/docs/#conv2d)和计算机视觉(http://propelml.org/docs/#imread)相关的功能。npy 文件的内容可以通过 load 函数(http://propelml.org/docs/#load)进行解析,并作为张量使用。

在浏览器环境中,PropelJS 利用了 deeplearn.js 中的 WebGL 功能。对于节点中的 GPU 加速,该项目则使用了 TensorFlow 的 C API。

gpu.js

虽然我的大部分经验是使用 CUDA 而不是 WebGL,但我可以证明 GPU 编程的耗时性。因此,当我遇到 gpu.js 时,我感到非常意外。该项目在 GitHub 上拥有约 5700 个 stars,在知名度方面与 deeplearn .js 相当,共有 18 位贡献者。Robert Plummer 是主要作者。

  1. import GPU from 'gpu.js'

  2. const gpu = new GPU()

  3. const multiplyMatrix = gpu.createKernel(function(a, b) {

  4.  var sum = 0;

  5.  for (var i = 0; i < 512; i++) {

  6.    sum += a[this.thread.y][i] * b[i][this.thread.x];

  7.  }

  8.  return sum;

  9. }).setOutput([512, 512])

使用 gpu.js 进行矩阵乘法运算,相当于 GPU 编程中的 Hello World!

在当前语境中,内核是在 GPU 而不是 CPU 上执行的函数。使用 gpu.js,内核可以用 JavaScript 的子集(https://github.com/gpujs/gpu.js#creating-and-running-functions)编写。然后编译代码并在 GPU 上运行。几周前,gpu.js 支持基于 OpenCL 的 Node.JS(https://github.com/mikeseven/node-opencl/issues/55)。

数字和最多具有三维的数组被用作输入和输出。除了基本的数学运算之外,gpu.js 还支持局部变量、循环和 if/else 语句。

为了实现代码重用并允许更多模块化设计,你们可以注册自定义函数 ( https://github.com/gpujs/gpu.js#adding-custom-functions #),然后从内核代码中使用。

在内核的 JavaScript 定义中,this 对象提供线程标识符,并存储在实际内核里是常量、在外部是动态变量的值。

该项目专门研究加速 JavaScript 函数,并不试图提供神经网络框架。为此,我们可以求助一个依赖 gpu.js 的库。

Brain.js

Brain.js 继承自 harthur/brain(https://github.com/harthur/brain),一个可以回溯至 2010 年的 repo。

  1. import brain from 'brain.js'

  2. const network = new brain.recurrent.RNN()

  3. const data = [

  4.    {input: [0, 0], output: [0]},

  5.    {input: [0, 1], output: [1]},

  6.    {input: [1, 0], output: [1]},

  7.    {input: [1, 1], output: [0]}

  8. ]

  9. network.train(data)

共有近 30 人对这两个 repo 做出了贡献。

对 GPU 加速神经网络的支持基于 GPU.js,这可以算得上该项目近期最重要的进展了。

除了前馈网络之外,Brain.js 还包括三种重要 RNN 类型的实现(https://github.com/BrainJS/brain.js#neural-network-types):经典 Elman 网络、LSTM,以及具备门控循环单元的近期网络。

该 repo 包含的 demo 处于早期阶段。源代码中还有另外两个演示 ( https://github.com/BrainJS/brain.js/tree/develop/examples),其中一个 demo 涉及检测用 ASCII 码绘制的字符。

针对机器学习的加速 JavaScript 库有很多有趣的应用。

在线课程可以将与机器学习或 GPU 计算相关的练习直接集成到 web 应用程序中。学生不必跨不同的操作系统和软件版本去设置单独的开发环境。

许多基于神经网络的 demo 可以更容易地部署,并且不再需要服务器端 API。

对机器学习感兴趣的 JavaScript 开发者可以充分利用他们的专业技能,在集成问题上花费更少的时间。

此外,客户端上的可用计算资源应该被更好地利用。毕竟,并非所有的显卡都一直用于虚拟现实和挖矿。

需要说清楚,我现在并不主张将本文中提到的库用于任务关键型神经网络。Python 生态系统仍然是大多数应用程序的首选。

然而,过去 12 个月取得的进展确实令人鼓舞。一年前既没有 deeplearn.js,也没有 Propel。彼时 gpu.js repo 中的活动水平相对较低,Brain.js 也不支持 GPU 加速。

随着时间的推移,这些项目将在某些方面与已建立的框架发生竞争,并催生出 JavaScript 完美适合的全新应用。

原文链接:

https://towardsdatascience.com/gpu-accelerated-neural-networks-in-javascript-195d6f8e69ef

工程JavaScriptGPU实现神经网络
1
路雪
路雪

机器之心编辑

返回顶部