deeplearnjs一个最简单的应用

结合 deeplearnjs 和之前的一篇神经网络的文章《小白成长记之神经网络》,来看看二者各自在最简单情况下的一次应用,以此继续寻找一些学习线索。

通过一些样本 X,去预测一个一元二次方程:y = ax^2 + bx + c,实际上就是通过样本数据的拟合真实曲线的过程,通过不断的调整方程系数,并最终确定一组最优系数 a, b, c。

怎么动手看起来似乎有点麻烦。如果将上述方程改写成一个二元一次方程:y = ax1 + bx2 + c。这个问题就变成了一个简单的线性规划问题。

在神经网络模型下,上述问题就是一个单层网络,且该层只有一个神经节点(节点偏移量为 c),且该层的激活函数是一个线性函数。在两个变量 x1, x2 及其权重系数 a, b 下,预测出 y 值。

一个最简单的线性规划的网络.png

现在看看 deeplearnjs 框架下,是如何编程实现的——实际上来自于它的教程

import * as dl from 'deeplearn';

/**
 * We want to learn the coefficients that give correct solutions to the
 * following quadratic equation:
 *      y = a * x^2 + b * x + c
 * In other words we want to learn values for:
 *      a
 *      b
 *      c
 * Such that this function produces 'desired outputs' for y when provided
 * with x. We will provide some examples of xs and ys to allow this model
 * to learn what we mean by desired outputs and then use it to produce new
 * values of y that fit the curve implied by our example.
 */


// Step 1. Set up variables, these are the things we want the model
// to learn in order to do prediction accurately. We will initialize
// them with random values.
const a = dl.variable(dl.scalar(Math.random()));
const b = dl.variable(dl.scalar(Math.random()));
const c = dl.variable(dl.scalar(Math.random()));

// 注:dl.scalar, dl.mul, dl.square 等等方法生成(或返回)的都是一个 Tensor
// dl.variable 创建了一个变量,实质上 Variable 类继承自 Tensor 类

// Step 2. Create an optimizer, we will use this later
const learningRate = 0.01;
const optimizer = dl.train.sgd(learningRate);

// Step 3. Write our training process functions.

/*
 * This function represents our 'model'. Given an input 'x' it will try and predict
 * the appropriate output 'y'.
 *
 * This could be as complicated a 'neural net' as we would like, but we can just
 * directly model the quadratic equation we are trying to model.
 *
 * It is also sometimes referred to as the 'forward' step of our training process.
 * Though we will use the same function for predictions later.
 *
 *
 * @return number predicted y value
 */
function predict(input) { // y = a * x ^ 2 + b * x + c
  return dl.tidy(() => {
    const x = dl.scalar(input);
    const ax2 = a.mul(x.square());
    const bx = b.mul(x);
    const y = ax2.add(bx).add(c);
    return y;
  });
}

/*
 * This will tell us how good the 'prediction' is given what we actually expected.
 *
 * prediction is a tensor with our predicted y value.
 * actual number is a number with the y value the model should have predicted.
 */
function loss(prediction, actual) {
  // Having a good error metric is key for training a machine learning model
  const error = dl.scalar(actual).sub(prediction).square();
  return error;
}

/*
 * This will iteratively train our model. We test how well it is doing
 * after numIterations by calculating the mean error over all the given
 * samples after our training.
 *
 * xs - training data x values
 * ys — training data y values
 */
async function train(xs, ys, numIterations, done) {
  let currentIteration = 0;

  for (let iter = 0; iter < numIterations; iter++) {
    for (let i = 0; i < xs.length; i++) {
      // Minimize is where the magic happens, we must return a
      // numerical estimate (i.e. loss) of how well we are doing using the
      // current state of the variables we created at the start.

      // This optimizer does the 'backward' step of our training data
      // updating variables defined previously in order to minimize the
      // loss.
      optimizer.minimize(() => {
        // Feed the examples into the model
        const pred = predict(xs[i]);
        const predLoss = loss(pred, ys[i]);

        return predLoss;
      });
    }

    // Use dl.nextFrame to not block the browser.
    await dl.nextFrame();
  }

  done();
}
/*
 * This function compare expected results with the predicted results from
 * our model.
 */
function test(xs, ys) {
  dl.tidy(() => {
    const predictedYs = xs.map(predict);
    console.log('Expected', ys);
    console.log('Got', predictedYs.map((p) => p.dataSync()[0]));
  })
}

// 样本数据
const data = {
  xs: [0, 1, 2, 3],
  ys: [1.1, 5.9, 16.8, 33.9]
};

// Lets see how it does before training.
console.log('Before training: using random coefficients')
test(data.xs, data.ys);
train(data.xs, data.ys, 50, () => {
  console.log(
      `After training: a=${a.dataSync()}, b=${b.dataSync()}, c=${c.dataSync()}`)
  test(data.xs, data.ys);
  console.log('Start to predict the output of 4 through the trained model:', predict(4).dataSync()[0])
});
// Huzzah we have trained a simple machine learning model!

我们来看看代码中几个重要的过程。

变量初始化

我们并不关心变量的初始值,因而用了一个随机的标量,作为变量的初始值。变量初始化后,整个模型就完成来初始化——这一初始化,是迭代的起点,必不可少。因为随机的初始化,故此时的模型与“真实”的模型有着非常大的差异。

可以通过打印初始系数,和训练后最终的系数做对比:

a.print(); b.print(); c.print();

预测 predict

输入经过模型产生输出,这个输出就是一个预测。所有的数据无论是训练集、测试集还是验证集,走的都是同样一个模型。

代码中predict方法实际上就调用了这个隐藏着的模型,而模型在每一个样本接受训练时都会做细微调整,模型所有的变动都存放在 CPU 或 GPU 的内存中。这也是为什么系数 a、b、c 被声明成 const 量,而实际上它的值是一直在变化的。

训练好了模型,就可以拿来输入新数据,预测新输出了。

console.log('Start to predict the output of 4 through the trained model:', predict(4).dataSync()[0])

训练 train

训练过程其实很简单,因为模型已经初始化好了,训练只不过将样本数据交给机器,让它循环迭代而已。在每一轮迭代中,都会产生一个损失值。代码中的损失仍然是一个张量:

const error = dl.scalar(actual).sub(prediction).square();

通过损失反向回溯,从而不断对权重系数进行调整。这个过程就是交给优化器optimizer做的。此处暂时不必追究优化器是如何办到的。

梯度下降 Gradient Descent

4 个样本,依次经过 50 次迭代训练,最终能训练出一个比较满意(损失较小)的结果,这是为什么?梯度下降 起到了什么作用?

首先我们要知道,梯度下降一般有这些变体,它们彼此容易相互推导。上例中用的是随机梯度下降,后边本文介绍最基本款“批量梯度下降”。

  • 批量梯度下降
  • 随机梯度下降 sdl.train.sgd
  • 小批量梯度下降

批量梯度下降算法

取神经网络模型中一个节点,其拓扑结构就是简单的多输入对单输出。

单层神经网络-单个节点.png

取任意一个样本 x ,输入到网络后,得到一个输出值的 hw 。变量 x 包含有多个特征属性值:x1, x2, ..., xj,每个特征属性都有一个权重系数与之对应——于是进一步将该计算准确描述为一个加权运算:

image.png

假设该节点有一个已知真实值y,那么在该节点的损失就是:

image.png

实际中,对一个问题我们往往有非常多个样本,比如m个样本,任意一个样本x(i)都应该经过这样的运算,得到损失。最后所有m个样本的总损失就是:

image.png

因为这是整个训练集的总损失,它从整体上描述了模型的拟合度。如果找到了它的一个最小值,那么也就找到了最小损失。最小值问题,我们这里可以求导数。

将权重系数w作为参数,同样它包含若干个分量 w1, w2, ..., wj, 对w求导意味着求每个分量的偏导:

image.png

要想取到极小值,导数应该趋向于0。但是趋向于0并不是一步就能做到的,需要在反复迭代中调整权重系数。那么每次权重系数w怎样的变化,才有助于做到这一点呢?事实上,只需这样更新:

image.png

后记

通过一个简单的线性规划的例子,其实可以发现很多可以优化、发散、深化的线索。这本身就是深度学习算法的乐趣所在~

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

推荐阅读更多精彩内容