deeplearn.js教程 - 介绍

介绍

deeplearn.js是用于机器智能的开源WebGL加速JavaScript库。 deeplearn.js将高性能的机器学习构建块带到您的指尖,让您可以在浏览器中训练神经网络或在推断模式(inference mode)下运行预先训练的模型。 它提供了一个用于构建可微数据流图的API,以及可以直接使用的一组数学函数。

您可以在这里找到补充本教程的代码。

运行:

./scripts/watch-demo demos/intro/intro.ts

然后访问http://localhost:8080/demos/intro/

或者直接点击这里观看我们的演示。

此文档将使用TypeScript代码示例。 对于vanilla JavaScript,您可能需要删除某些TypeScript类型的注释或定义。

核心概念

NDArrays

deeplearn.js中的中心数据单元是NDArray。 一个NDArray由一组浮点值组成,它们是一个任意数量的数组。 NDArray有一个shape属性来定义它们的形状。 该库为低级NDArrays提供糖类:ScalarArray1DArray2DArray3DArray4D

使用2x3矩阵的示例:

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

NDArray可以在GPU上存储数据作为WebGLTexture,其中每个像素存储一个浮点值,或者作为一个vanilla JavaScript TypedArray在CPU上存储数据。 大多数时候,用户不应该考虑存储,因为它是一个实现上的细节。

如果NDArray数据存储在CPU上,则首次调用GPU数学运算时,数据将自动上传到纹理。 如果在GPU驻留的NDArray上调用NDArray.getValues(),库将把纹理下载到CPU并删除纹理。

NDArrayMath

该库提供了一个NDArrayMath基类,它定义了一组对NDArray进行操作的数学函数。

NDArrayMathGPU

当使用NDArrayMathGPU实现时,这些数学运算将着色器程序排列在GPU上执行。 与NDArrayMathCPU不同,这些操作并不阻止,但用户可以通过在NDArray上调用get()getValues()来同步cpu和gpu,如下所述。

这些着色器从NGArray拥有的WebGLTextures中读取和写入。 当接入数学运算时,纹理可以停留在GPU内存中(未在操作之间下载到CPU),这对于性能至关重要。

采取两个矩阵之间的均方差的示例(有关math.scopekeep,以及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]));

  // 非阻塞数学调用。
  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);

  // 阻止调用实际从平均值读取值。
  // 等待直到GPU返回值之前完成执行操作。
  // average是一个标量,所以我们使用.get()
  console.log(average.get());
});

注意:NDArray.get()NDArray.getValues()正在阻止调用。 执行被链接的数学函数后无需注册回调,只需调用getValues()来同步CPU和GPU。

math.scope()

当使用数学运算时,必须将它们包装在一个math.scope()函数闭包中,如上面的例子所示。 此范围内的数学运算结果将被放置在范围的末尾,除非它们是范围中返回的值。

两个函数传递给函数闭包,keep()track()

keep()确保在范围结束时,传递给保留的NDArray将不会自动清除。

track()跟踪可以在范围内直接构造的任何NDArray。 当范围结束时,任何手动跟踪的NDArray将被清理。 所有math.method()函数的结果以及许多其他核心库函数的结果都会自动清除,因此您不必手动跟踪它们。

const math = new NDArrayMathGPU();

let output;

// 您必须拥有一个外部范围,但不用担心,如果没有该库,则会导致错误。
math.scope((keep, track) => {
  // 正确:默认情况下,数学不会跟踪直接构造的NDArray。
  // 您可以在NDArray上调用track(),以便在范围结束时进行跟踪和清理。
  const a = track(Scalar.new(2));

  // 错误:这是纹理泄漏!
  // 数学不知道b,所以它不能跟踪它。 当范围结束时,GPU驻留的NDArray不会被清理,即使b超出范围。 
  // 确保您在创建的NDArrays上调用track()。
  // scope. Make sure you call track() on NDArrays you create.
  const b = Scalar.new(2);

  // 正确:默认情况下,数学跟踪数学函数的所有输出。
  const c = math.neg(math.exp(a));

  // 正确:d由父范围跟踪。
  const d = math.scope(() => {
    // 正确:当内部范围结束时,e将被清理。
    const e = track(Scalar.new(3));

    // 正确:
    // 这个数学功能的结果已经被跟踪。
    // 由于它是该范围的返回值,它将不会被内部范围清理。
    // 结果将在父范围内自动跟踪。
    return math.elementWiseMul(e, e);
  });

  // 正确但是请注意:math.tanh的输出将被自动跟踪,但是我们可以在其上调用keep(),以便在范围结束时保留它。
  // 这意味着如果您稍后调用output.dispose()时不小心,可能会引入纹理内存泄漏。 
  // 一个更好的方法是将此值作为范围的返回值返回,以便在父作用域中进行跟踪。
  output = keep(math.tanh(d));
});

更多技术细节:当WebGL纹理超出JavaScript范围时,它们不会被浏览器的垃圾收集机制自动清理。 这意味着当你完成一个GPU驻留的NDArray,它必须在一段时间后手动放置。 如果您在完成NDArray后忘记手动调用ndarray.dispose(),那么您将会引入纹理内存泄漏,从而导致严重的性能问题。 如果使用math.scope(),则由math.method()创建的任何NDArray和通过范围返回结果的任何其他方法将自动被清除。

如果要进行手动内存管理,而不使用math.scope(),则可以使用safeMode = false构造NDArrayMath对象。 这是不推荐的,但对于NDArrayMathCPU是有用的,因为CPU驻留的内存将被JavaScript垃圾回收器自动清理。

NDArrayMathCPU

当使用CPU实现时,这些数学运算被阻塞,并使用vanilla JavaScript立即在底层TypedArray上执行。

训练

deeplearn.js中的可微数据流图使用延迟执行模型,就像在TensorFlow中一样。 用户构建Graph,然后通过FeedEntry提供输入NDArray来对其进行训练或推断。

注意:NDArrayMath和NDArrays足以推断模式。 如果你想训练,你只需要一个图形(Graph)。

图形(Graph)和张量(Tensor)

Graph对象是构建数据流图的核心类。Graph对象实际上并不包含NDArray数据,只能在操作之间进行连接。

Graph类具有可操作的操作作为顶级成员函数。 当您调用Graph方法来添加操作时,您将返回一个仅保存连接和形状信息的Tensor对象。

一个将输入与变量相乘的示例图:

const g = new Graph();

// 占位符是输入容器。
// 这是在我们执行图形(graph)时我们将为我们传送输入NDArray的容器。
const inputShape = [3];
const inputTensor = g.placeholder('input', inputShape);

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

// 变量是容纳可以从培训中更新的值的容器。
// 这里我们随机初始化乘数变量。
const multiplier = g.variable('multiplier', Array2D.randNormal([1, 3]));

// 最高级图形(graph)方法采用Tensor并返回Tensor。
const outputTensor = g.matmul(multiplier, inputTensor);
const costTensor = g.meanSquaredCost(outputTensor, labelTensor);

// Tensor,像NDArray,有一个shape属性。
console.log(outputTensor.shape);

会话(Session)和 FeedEntry

会话对象用于驱动Graph的执行。 FeedEntry(类似于TensorFlow的feed_dict)为运行提供数据,从给定的NDArray向Tensor提供值。

关于批处理的一个快速注意事项:deeplearn.js尚未实现批处理作为操作的外部维度。 这意味着每个最高级图形(Graph)操作以及数学函数都可以在单个示例中进行操作。 但是,批处理是很重要的,因此权重更新是根据批次的平均梯度进行操作的。deeplearn.js通过在训练FeedEntry中使用InputProvider来模拟批处理,以直接提供输入,而不是NDArrayInputProvider将在批处理中调用每个项目。 我们提供InMemoryShuffledInputProviderBuilder来混洗一组输入并保持它们同步。

用上面的Graph对象进行训练:

const learningRate = .00001;
const batchSize = 3;
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([4.0]),
  Array1D.new([40.0]),
  Array1D.new([400.0])
];

// 混合输入和标签,并保持它们相互同步。
const shuffledInputProviderBuilder =
  new InCPUMemoryShuffledInputProviderBuilder([inputs, labels]);
const [inputProvider, labelProvider] =
  shuffledInputProviderBuilder.getInputProviders();

// 将张量映射到InputProviders。
const feedEntries: FeedEntry[] = [
  {tensor: inputTensor, data: inputProvider},
  {tensor: labelTensor, data: labelProvider}
];

const NUM_BATCHES = 10;
for (let i = 0; i < NUM_BATCHES; i++) {
  // 在会话中包装session.train,以便自动清除成本。
  math.scope(() => {
    // 训练需要一个成本张量来最小化。
    // 训练一批。返回平均成本作为标量。
    const cost = session.train(
        costTensor, feedEntries, batchSize, optimizer, CostReduction.MEAN);

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

训练后,我们可以通过Graph推断:

// 在会话中包含session.eval,以便中间值被自动清理。
math.scope((keep, track) => {
  const testInput = track(Array1D.new([0.1, 0.2, 0.3]));

  // session.eval可以将NDArray作为输入数据。
  const testFeedEntries: FeedEntry[] = [
    {tensor: inputTensor, data: testInput}
  ];

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

  console.log('---inference output---');
  console.log('shape: ' + testOutput.shape);
  console.log('value: ' + testOutput.get(0));
});

// 清理训练数据。
inputs.forEach(input => input.dispose());
labels.forEach(label => label.dispose());
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容