从0开始学习 Deep Learning - 02手写纯JS的深度学习玩耍 Chrome 恐龙游戏(二)

152 阅读4分钟

上一篇文章中已经展示了最终的效果,这篇文章将讲解如何基于线性回归以及神经网络进行整体的框架设计,在此之前需要对机器学习常见的一些概念做一些说明。

前置说明

以下内容基于线性回归算法模型

我们知道机器学习的本质是数学,机器学习的推理基于数学公式,但是如何确定算什么场景用什么样的公式,这个就涉及到算法的选择,也就是模型的选择。

训练模型的本质就是不停的依据入参和出参,不停地得出合理的模型参数。最简单也是最基础的一个模型就是线性回归 y = f(x) = weight * x + bias。在训练的过程中 x 和 y 是已知的,那我们需要求解的是 weight 和 bias。

这里有几个概念要捋一捋,对于 y = f(x); 在数学里 x 被称为自变量,y 被称为因变量;在机器学习领域,x 被称为特征,y 被称为标签;

那么问题来了,如何根据已知的入参和出参得出合理的 weight 和 bias,这里就涉及到损失函数的定义和梯度下降算法(这里不展开讲解,可以查阅本系列的第一篇文章)。

小结

大致流程是进行训练前先初始化 weight 和 bias,然后在每个迭代用线性回归算法得出 label 也就是 f(x),通过和预期的 label 值对比,差值为 loss_function 的输出值,再通过梯度下降算法依次算出 weight 和 bias 的值,每次梯度下降的幅度由学习率决定 learning_rate。所以下降幅度过快会导致过拟合,下降幅度过慢会导致欠拟合。

神经网络

从前面内容可以看出我们这里只针对一个节点 neuron 的处理,也就是通过一个神经元来实现一个函数收敛,早期的机器学习以神经元为切入点,产生了简单的参数调整和拟合算法,重点是如何用梯度下降来修正权重,达到不断逼近最优解的目的。后面基于多个神经元的神经网络诞生了。

隐藏层的数量称为网络的深度(Depth),把每一层的神经元个数称为宽度(Width)

在用神经网络时,我们会遇到如何训练的问题,单个节点的输出依赖前一层的输出,也就是

w0 就是 bias

那么每个节点是如何在训练过程中确定 weight 和 bias 呢?这里就涉及到向前传播和向后传播了

向前传播:指将数据输入神经网络中,每个隐藏层的神经元都接收网络输入,通过激活函数进行处理,然后进入下一层或者输出的过程。在前面linear_regression的例子中,predict方法实际上就是一个简单的前向传播方法。

向后传播:指对网络中的所有权重都计算损失函数的梯度,这个梯度会在优化算法中用来更新权值以最小化损失函数。

简而言之就是向前传播负责计算 label,向后传播负责通过向前传播得出的label和实际情况的损失,更新权值。

在生物上的神经网络每个 neuron 传递给下一层的是信号,而不是值,所以向前传播过程中每个 neuron 的输出值需要做激活函数,变为一种刺激信号,这个转换过程叫做激活函数

一些公式

有了上述概念,就可以进行公式的推导了。

单个 neuron 的 output 值计算

private activate(neuron: INeuron, inputs: Array<number>) {
    const { weights, bias } = neuron;
    const ret = weights.reduce(
        (prev, weight, index) => prev + weight * inputs[index],
        bias
    );

    return ret;
}

neuron 传递给下一层信号-激活函数

常见的激活函数 sigmoid

private sigmoid(activation: number) {
  return 1 / (1 + Math.exp(-activation));
}

下一章就开始讲解自建的 NN 全链路是如何实现,如何将上述的思路变为工程代码。