结合 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后记
通过一个简单的线性规划的例子,其实可以发现很多可以优化、发散、深化的线索。这本身就是深度学习算法的乐趣所在~
网友评论