专栏首页机器之心业界 | 谷歌开源DeepLearn.js:可实现硬件加速的机器学习JavaScript库

业界 | 谷歌开源DeepLearn.js:可实现硬件加速的机器学习JavaScript库

选自GitHub

机器之心编译

参与:蒋思源、路雪

deeplearn.js 是一个可用于机器智能并加速 WebGL 的开源 JavaScript 库。deeplearn.js 提供高效的机器学习构建模块,使我们能够在浏览器中训练神经网络或在推断模式中运行预训练模型。它提供构建可微数据流图的 API,以及一系列可直接使用的数学函数。

本文档中,我们使用 TypeScript 代码示例。对于 vanilla JavaScript,你可能需要移除 TypeScript 语法,如 const、let 或其他类型定义。

核心概念

NDArrays

deeplearn.js 的核心数据单元是 NDArray。NDArray 包括一系列浮点值,它们将构建为任意维数的数组。NDArray 具备一个用来定义形状的 shape 属性。该库为低秩 NDArray 提供糖子类(sugar subclasses):Scalar、Array1D、Array2D、Array3D 和 Array4D。

2x3 矩阵的用法示例:

const shape = [2, 3];  // 2 rows, 3 columns
const a = Array2D.new(shape, [1.0, 2.0, 3.0, 10.0, 20.0, 30.0]);

NDArray 可作为 WebGLTexture 在 GPU 上储存数据,每一个像素储存一个浮点值;或者作为 vanilla JavaScript TypedArray 在 CPU 上储存数据。大多数时候,用户不应思考储存问题,因为这只是一个实现细节。

如果 NDArray 数据储存在 CPU 上,那么 GPU 数学操作第一次被调用时,该数据将被自动上传至一个 texture。如果你在 GPU 常驻内存的 NDArray 上调用 NDArray.getValues(),则该库将下载该 texture 至 CPU,然后将其删除。

NDArrayMath

该库提供一个 NDArrayMath 基类,其为定义在 NDArray 上运行的一系列数学函数。

NDArrayMathGPU

当使用 NDArrayMathGPU 实现时,这些数学运算对将在 GPU 上执行的着色器程序(shader program)排序。和 NDArrayMathCPU 中不同,这些运算不会阻塞,但用户可以通过在 NDArray 上调用 get() 或 getValues() 使 cpu 和 gpu 同步化,详见下文。

这些着色器从 NDArray 上的 WebGLTexture 中读取和写入。当连接数学运算时,纹理可以停留在 GPU 内存中(不必下载至运算之间的 CPU),这对性能来说非常关键。

以两个矩阵间的均方差为例(有关 math.scope、keep 和 track 的更多细节,详见下文):

const math = new NDArrayMathGPU();

math.scope((keep, track) => {
  const a = track(Array2D.new([2, 2], [1.0, 2.0, 3.0, 4.0]));
  const b = track(Array2D.new([2, 2], [0.0, 2.0, 4.0, 6.0]));

  // Non-blocking math calls.
  const diff = math.sub(a, b);
  const squaredDiff = math.elementWiseMul(diff, diff);
  const sum = math.sum(squaredDiff);
  const size = Scalar.new(a.size);
  const average = math.divide(sum, size);

  // Blocking call to actually read the values from average. Waits until the
  // GPU has finished executing the operations before returning values.
  console.log(average.get());  // average is a Scalar so we use .get()
});

注:NDArray.get() 和 NDArray.getValues() 是阻塞调用。因此在执行一系列数学函数之后,无需寄存回调函数,只需调用 getValues() 来使 CPU 和 GPU 同步化。

小技巧:避免在 GPU 数学运算之间调用 get() 或 getValues(),除非你在进行调试。因为这会强制下载 texture,然后后续的 NDArrayMathGPU 调用将不得不重新下载数据至新的 texture 中。

math.scope()

当我们进行数学运算时,我们需要像如上所示的案例那样将它们封装到 math.scope() 函数的闭包中。该数学运算的结果将在作用域的端点处得到配置,除非该函数在作用域内返回函数值。

有两种函数可以传递到函数闭包中:keep() 和 track()。

  • keep() 确保了 NDArray 将得到传递并保留,它不会在作用域范围结束后被自动清除。
  • track() 追踪了我们在作用域内直接构建的 NDArray。当作用域结束时,任何手动追踪的 NDArray 都将会被清除。math.method() 函数的结果和其它核心库函数的结果一样将会被自动清除,所以我们也不必手动追踪它们。
const math = new NDArrayMathGPU();

let output;

// You must have an outer scope, but don't worry, the library will throw an
// error if you don't have one.
math.scope((keep, track) => {
  // CORRECT: By default, math wont track NDArrays that are constructed
  // directly. You can call track() on the NDArray for it to get tracked and
  // cleaned up at the end of the scope.
  const a = track(Scalar.new(2));

  // INCORRECT: This is a texture leak!!
  // math doesn't know about b, so it can't track it. When the scope ends, the
  // GPU-resident NDArray will not get cleaned up, even though b goes out of
  // scope. Make sure you call track() on NDArrays you create.
  const b = Scalar.new(2);

  // CORRECT: By default, math tracks all outputs of math functions.
  const c = math.neg(math.exp(a));

  // CORRECT: d is tracked by the parent scope.
  const d = math.scope(() => {
    // CORRECT: e will get cleaned up when this inner scope ends.
    const e = track(Scalar.new(3));

    // CORRECT: The result of this math function is tracked. Since it is the
    // return value of this scope, it will not get cleaned up with this inner
    // scope. However, the result will be tracked automatically in the parent
    // scope.
    return math.elementWiseMul(e, e);
  });

  // CORRECT, BUT BE CAREFUL: The output of math.tanh will be tracked
  // automatically, however we can call keep() on it so that it will be kept
  // when the scope ends. That means if you are not careful about calling
  // output.dispose() some time later, you might introduce a texture memory
  // leak. A better way to do this would be to return this value as a return
  // value of a scope so that it gets tracked in a parent scope.
  output = keep(math.tanh(d));
});

技术细节:当 WebGL textures 在 JavaScript 的作用范围之外时,它们因为浏览器的碎片回收机制而不会被自动清除。这就意味着当我们使用 GPU 常驻内存完成了 NDArray 时,它随后需要手动地配置。如果我们完成 NDArray 时忘了手动调用 ndarray.dispose(),那就会引起 texture 内存渗漏,这将会导致十分严重的性能问题。如果我们使用 math.scope(),任何由 math.method() 创建的 NDArray 或其它通过作用域返回函数值方法创建的 NDArray 都会被自动清除。

如果我们想不使用 math.scope(),并且手动配置内存,那么我们可以令 safeMode = false 来构建 NDArrayMath 对象。这种方法我们并不推荐,但是因为 CPU 常驻内存可以通过 JavaScript 碎片回收器自动清除,所以它对 NDArrayMathCPU 十分有用。

NDArrayMathCPU

当我们使用 CPU 实现模型时,这些数学运算是封闭的并且可以通过 vanilla JavaScript 在底层 TypedArray 上立即执行。

训练

在 deeplearn.js 中的可微数据流图使用的是延迟执行模型,这一点就和 TensorFlow 一样。用户可以通过 FeedEntrys 提供的输入 NDArray 构建一个计算图,然后再在上面进行训练或推断。

注意:NDArrayMath 和 NDArrays 对于推断模式来说是足够的,如果我们希望进行训练,只需要一个图就行。

图和张量

Graph 对象是构建数据流图的核心类别,Graph 对象实际上并不保留 NDArray 数据,它只是在运算中构建连接。

Graph 类像顶层成员函数(member function)一样有可微分运算。当我们调用一个图方法来添加运算时,我们就会获得一个 Tensor 对象,它仅仅保持连通性和形状信息。

下面是一个将输入和变量做乘积的计算图示例:

const g = new Graph();

// Placeholders are input containers. This is the container for where we will
// feed an input NDArray when we execute the graph.
const inputShape = [3];
const inputTensor = g.placeholder('input', inputShape);

const labelShape = [1];
const inputTensor = g.placeholder('label', labelShape);

// Variables are containers that hold a value that can be updated from training.
// Here we initialize the multiplier variable randomly.
const multiplier = g.variable('multiplier', Array2D.randNormal([1, 3]));

// Top level graph methods take Tensors and return Tensors.
const outputTensor = g.matmul(multiplier, inputTensor);
const costTensor = g.meanSquaredCost(outputTensor, labelTensor);

// Tensors, like NDArrays, have a shape attribute.
console.log(outputTensor.shape);

Session 和 FeedEntry

Session 对象是驱动执行计算图的方法,FeedEntry 对象(和 TensorFlow 中的 feed_dict 类似)将提供运行所需的数据,并从给定的 NDArray 中馈送一个值给 Tensor 对象。

批处理简单的注释:deeplearn.js 并没有执行批处理作为运算的外部维度(outer dimension)。这就意味着每一个顶层图运算就像数学函数那样在单个样本上运算。然而,批处理十分重要,以至于权重的更新依赖于每一个批量的梯度均值。deeplearn.js 在训练 FeedEntry 时通过使用 InputerProvider 模拟批处理来提供输入向量,而不是直接使用 NDArray。因此,每一个批量中的每一项都会调用 InputerProvider。我们同样提供了 InMemoryShuffledInputProviderBuilder 来清洗一系列输入并保持它们的同步性。

通过上面的 Graph 对象训练:

const learningRate = .001;
const batchSize = 2;

const math = new NDArrayMathGPU();
const session = new Session(g, math);
const optimizer = new SGDOptimizer(learningRate);

const inputs: Array1D[] = [
  Array1D.new([1.0, 2.0, 3.0]),
  Array1D.new([10.0, 20.0, 30.0]),
  Array1D.new([100.0, 200.0, 300.0])
];

const labels: Array1D[] = [
  Array1D.new([2.0, 6.0, 12.0]),
  Array1D.new([20.0, 60.0, 120.0]),
  Array1D.new([200.0, 600.0, 1200.0])
];

// Shuffles inputs and labels and keeps them mutually in sync.
const shuffledInputProviderBuilder =
   new InCPUMemoryShuffledInputProviderBuilder([inputs, labels]);
const [inputProvider, labelProvider] =
   shuffledInputProviderBuilder.getInputProviders();

// Maps tensors to InputProviders.
const feedEntries: FeedEntry[] = [
  {tensor: inputTensor, data: inputProvider},
  {tensor: labelTensor, data: labelProvider}
];

// Wrap session.train in a scope so the cost gets cleaned up automatically.
math.scope(() => {
  // Train takes a cost tensor to minimize. Trains one batch. Returns the
  // average cost as a Scalar.
  const cost = session.train(
      costTensor, feedEntries, batchSize, optimizer, CostReduction.MEAN);

  console.log('last average cost: ' + cost.get());
});

在训练后,我们就可以通过图进行推断:

// Wrap session.eval in a scope so the intermediate values get cleaned up
// automatically.
math.scope((keep, track) => {
  const testInput = track(Array1D.new([1.0, 2.0, 3.0]));

  // session.eval can take NDArrays as input data.
  const testFeedEntries: FeedEntry[] = [
    {tensor: inputTensor, data: testInput}
  ];

  const testOutput = session.eval(outputTensor, testFeedEntries);

  console.log('inference output:');
  console.log(testOutput.shape);
  console.log(testOutput.getValues());
});
  • 详情请查看文档教程:https://pair-code.github.io/deeplearnjs/docs/tutorials/
  • 原文地址:https://pair-code.github.io/deeplearnjs/docs/tutorials/intro.html

本文为机器之心编译,转载请联系本公众号获得授权。

本文分享自微信公众号 - 机器之心(almosthuman2014)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-08-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 机器学习不神秘!手把手教你用R语言打造文本分类器

    简单安装几个R软件包,你就直接在自己电脑上打造出一个文本分类器,用进行机器来评估人类写作。 本文是一篇极简的上手教程,它想用清晰明了的步骤告诉读者,机器学习不...

    AI科技大本营
  • 除了 Python ,这些语言写的机器学习项目也很牛

    Python 由于本身的易用优势和强大的工具库储备,成为了在人工智能及其它相关科学领域中最常用的语言之一。尤其是在机器学习,已然是各大项目最偏爱的语言。 其实除...

    AI科技大本营
  • 详解计算机视觉五大技术:图像分类、对象检测、目标跟踪、语义分割和实例分割

    译者 | 王柯凝 【 AI 科技大本营导读】目前,计算机视觉是深度学习领域最热门的研究领域之一。计算机视觉实际上是一个跨领域的交叉学科,包括计算机科学(图形、算...

    AI科技大本营
  • Google VS 亚马逊 VS 微软,机器学习服务选谁好?

    译者 | reason_W 编辑 | Just 对大多数企业来说,机器学习听起来就像航天技术一样,属于花费不菲又“高大上”的技术。如果你是想构建一个 Netfl...

    AI科技大本营
  • AI Insight:春节福利——成为神人的五条建议

    【AI100 导读】今天是大年三十,AI100 祝大家鸡年大吉!春节是个重要的日子,全世界有五分之一的智人在这一天满怀对未来的憧憬,喜气洋洋的迎接新年。因此 A...

    AI科技大本营
  • AI实践精选:艺术家如何应用RNN(循环神经网络)创作AI化的艺术作品

    文章导读:这篇文章不是为了全面深入的介绍循环神经网络(recurrent neural networks),而是为那些没有任何机器学习(machine lear...

    AI科技大本营
  • 如何跨越人工智能技术与产品的鸿沟?

    【AI100导读】:“鸿沟理论”指的就是高科技产品在市场营销过程中遭遇的最大障碍:高科技企业的早期市场和主流市场之间存在着一条巨大的“鸿沟”,能否顺利跨越鸿沟并...

    AI科技大本营
  • AI Insight:李开复的AI未来简史——AI红利三段论

    去年对我影响比较大的一本书是尤瓦尔·赫拉利的《人类简史》,所以知道他的新书《未来简史》即将上市,我还是比较关注的。不过,这本书还没有上市,我倒是听到了另一位著名...

    AI科技大本营
  • 重读经典 | 亚马逊“一键下单”的背后——个性化推荐系统的发展历程

    一般都认为,亚马逊的成功要归功于它鼎鼎大名的“一键下单”功能,但“一键下单”的背后,还需要一个成单量极高的智能推荐系统,来驱动消费者不停地在亚马逊上“买!买!买...

    AI科技大本营
  • 实战 | 手把手教你用苹果CoreML实现iPhone的目标识别

    在WWDC 2017上,苹果首次公布了机器学习方面的动作。iOS系统早已支持Machine Learning 和 Computer Vision ,但这次苹果提...

    AI科技大本营

扫码关注云+社区

领取腾讯云代金券