一文搞懂梯度下降&反向传播

Python039

一文搞懂梯度下降&反向传播,第1张

如果把神经网络模型比作一个黑箱,把模型参数比作黑箱上面一个个小旋钮,那么根据通用近似理论(universal approximation theorem),只要黑箱上的旋钮数量足够多,而且每个旋钮都被调节到合适的位置,那这个模型就可以实现近乎任意功能(可以逼近任意的数学模型)。

显然,这些旋钮(参数)不是由人工调节的,所谓的机器学习,就是通过程序来自动调节这些参数。神经网络不仅参数众多(少则十几万,多则上亿),而且网络是由线性层和非线性层交替叠加而成,上层参数的变化会对下层的输出产生非线性的影响,因此,早期的神经网络流派一度无法往多层方向发展,因为他们找不到能用于任意多层网络的、简洁的自动调节参数的方法。

直到上世纪80年代,祖师爷辛顿发明了反向传播算法,用输出误差的均方差(就是loss值)一层一层递进地反馈到各层神经网络,用梯度下降法来调节每层网络的参数。至此,神经网络才得以开始它的深度之旅。

本文用python自己动手实现梯度下降和反向传播算法。 请点击这里 到Github上查看源码。

梯度下降法是一种将输出误差反馈到神经网络并自动调节参数的方法,它通过计算输出误差的loss值( J )对参数 W导数,并沿着导数的反方向来调节 W ,经过多次这样的操作,就能将输出误差减小到最小值,即曲线的最低点。

虽然Tensorflow、Pytorch这些框架都实现了自动求导的功能,但为了彻底理解参数调节的过程,还是有必要自己动手实现梯度下降和反向传播算法。我相信你和我一样,已经忘了之前学的微积分知识,因此,到可汗学院复习下 Calculus

和 Multivariable Calculus 是个不错的方法,或是拜读 这篇关于神经网络矩阵微积分的文章 。

Figure2是求导的基本公式,其中最重要的是 Chain Rule ,它通过引入中间变量,将“ y x 求导”的过程转换为“ y 对中间变量 u 求导,再乘以 u x 求导”,这样就将一个复杂的函数链求导简化为多个简单函数求导。

如果你不想涉及这些求导的细节,可以跳过具体的计算,领会其思想就好。

对于神经网络模型: Linear ->ReLu ->Linear ->MSE(Loss function) 来说,反向传播就是根据链式法则对 求导,用输出误差的均方差(MSE)对模型的输出求导,并将导数传回上一层神经网络,用于它们来对 w b x (上上层的输出)求导,再将 x 的导数传回到它的上一层神经网络,由此将输出误差的均方差通过递进的方式反馈到各神经网络层。

对于 求导的第一步是为这个函数链引入中间变量:

接着第二步是对各中间变量求导,最后才是将这些导数乘起来。

首先,反向传播的起点是对loss function求导,即 。 :

mse_grad()之所以用unsqueeze(-1)给导数增加一个维度,是为了让导数的shape和tensor shape保持一致。

linear层的反向传播是对 求导,它也是一个函数链,也要先对中间变量求导再将所有导数相乘:

这些中间变量的导数分别是:

对向量 求导,指的是对向量所有的标量求偏导( ),即: ,这个横向量也称为y的梯度。

这里 ,是一个向量,因此, 求导,指的是y的所有标量(y_1, y_2, ..., y_n)对向量x求偏导,即:

这个矩阵称为雅克比矩阵,它是个对角矩阵,因为 ,因此 。

同理, 。

因此,所有中间导数相乘的结果:

lin_grad() 中的inp.g、w.g和b.g分别是求 的导数,以inp.g为例,它等于 ,且需要乘以前面各层的导数,即 outp.g @ w.t() ,之所以要用点积运算符(@)而不是标量相乘,是为了让它的导数shape和tensor shape保持一致。同理,w.g和b.g也是根据相同逻辑来计算的。

ReLu层的求导相对来说就简单多了,当输入 <= 0时,导数为0,当输入 >0时,导数为1。

求导运算终于结束了,接下来就是验证我们的反向传播是否正确。验证方法是将forward_backward()计算的导数和Pytorch自动微分得到的导数相比较,如果它们相近,就认为我们的反向传播算法是正确的。

首先,将计算好的参数导数保存到w1g、b1g、w2g和b2g中,再用Pytorch的自动微分来求w11、b11、w22和b22的导数。

最后,用np.allclose()来比较导数间的差异,如果有任何一个导数不相近,assert就会报错。结果证明,我们自己动手实现的算法是正确的。

反向传播是遵循链式法则的,它将前向传播的输出作为输入,输入作为输出,通过递进的方式将求导这个动作从后向前传递回各层。神经网络参数的求导需要进行矩阵微积分计算,根据这些导数的反方向来调节参数,就可以让模型的输出误差的优化到最小值。

欢迎关注和点赞,你的鼓励将是我创作的动力

梯度下降算法是一种最优化算法。

基本原理是:通过不断迭代调整参数来使得损失函数的值达到最小。每次迭代都会根据当前的参数来计算损失函数的梯度,然后沿着梯度的反方向调整参数,使得损失函数的值变小。

具体来说,每次迭代都会计算出当前参数下损失函数对每个参数的偏导数,这些偏导数构成了损失函数的梯度。然后按照如下公式来调整参数:

θ = θ - α * ∇θ J(θ)

其中 θ 是参数, J(θ) 是损失函数, α 是学习率, ∇θ J(θ) 是损失函数关于 θ 的梯度。

这样不断迭代调整参数,直到损失函数达到最小值,或者迭代次数达到预定值为止。

梯度下降算法在很多机器学习算法中都有应用,如线性回归、逻辑回归、神经网络等。

从零开始用Python构建神经网络

动机:为了更加深入的理解深度学习,我们将使用 python 语言从头搭建一个神经网络,而不是使用像 Tensorflow 那样的封装好的框架。我认为理解神经网络的内部工作原理,对数据科学家来说至关重要。

这篇文章的内容是我的所学,希望也能对你有所帮助。

神经网络是什么?

介绍神经网络的文章大多数都会将它和大脑进行类比。如果你没有深入研究过大脑与神经网络的类比,那么将神经网络解释为一种将给定输入映射为期望输出的数学关系会更容易理解。

神经网络包括以下组成部分

? 一个输入层,x

? 任意数量的隐藏层

? 一个输出层,?

? 每层之间有一组权值和偏置,W and b

? 为隐藏层选择一种激活函数,σ。在教程中我们使用 Sigmoid 激活函数

下图展示了 2 层神经网络的结构(注意:我们在计算网络层数时通常排除输入层)

2 层神经网络的结构

用 Python 可以很容易的构建神经网络类

训练神经网络

这个网络的输出 ? 为:

你可能会注意到,在上面的等式中,输出 ? 是 W 和 b 函数。

因此 W 和 b 的值影响预测的准确率. 所以根据输入数据对 W 和 b 调优的过程就被成为训练神经网络。

每步训练迭代包含以下两个部分:

? 计算预测结果 ?,这一步称为前向传播

? 更新 W 和 b,,这一步成为反向传播

下面的顺序图展示了这个过程:

前向传播

正如我们在上图中看到的,前向传播只是简单的计算。对于一个基本的 2 层网络来说,它的输出是这样的:

我们在 NeuralNetwork 类中增加一个计算前向传播的函数。为了简单起见我们假设偏置 b 为0:

但是我们还需要一个方法来评估预测结果的好坏(即预测值和真实值的误差)。这就要用到损失函数。

损失函数

常用的损失函数有很多种,根据模型的需求来选择。在本教程中,我们使用误差平方和作为损失函数。

误差平方和是求每个预测值和真实值之间的误差再求和,这个误差是他们的差值求平方以便我们观察误差的绝对值。

训练的目标是找到一组 W 和 b,使得损失函数最好小,也即预测值和真实值之间的距离最小。

反向传播

我们已经度量出了预测的误差(损失),现在需要找到一种方法来传播误差,并以此更新权值和偏置。

为了知道如何适当的调整权值和偏置,我们需要知道损失函数对权值 W 和偏置 b 的导数。

回想微积分中的概念,函数的导数就是函数的斜率。

梯度下降法

如果我们已经求出了导数,我们就可以通过增加或减少导数值来更新权值 W 和偏置 b(参考上图)。这种方式被称为梯度下降法。

但是我们不能直接计算损失函数对权值和偏置的导数,因为在损失函数的等式中并没有显式的包含他们。因此,我们需要运用链式求导发在来帮助计算导数。

链式法则用于计算损失函数对 W 和 b 的导数。注意,为了简单起见。我们只展示了假设网络只有 1 层的偏导数。

这虽然很简陋,但是我们依然能得到想要的结果—损失函数对权值 W 的导数(斜率),因此我们可以相应的调整权值。

现在我们将反向传播算法的函数添加到 Python 代码中

为了更深入的理解微积分原理和反向传播中的链式求导法则,我强烈推荐 3Blue1Brown 的如下教程:

Youtube:https://youtu.be/tIeHLnjs5U8

整合并完成一个实例

既然我们已经有了包括前向传播和反向传播的完整 Python 代码,那么就将其应用到一个例子上看看它是如何工作的吧。

神经网络可以通过学习得到函数的权重。而我们仅靠观察是不太可能得到函数的权重的。

让我们训练神经网络进行 1500 次迭代,看看会发生什么。 注意观察下面每次迭代的损失函数,我们可以清楚地看到损失函数单调递减到最小值。这与我们之前介绍的梯度下降法一致。

让我们看看经过 1500 次迭代后的神经网络的最终预测结果:

经过 1500 次迭代训练后的预测结果

我们成功了!我们应用前向和方向传播算法成功的训练了神经网络并且预测结果收敛于真实值。

注意预测值和真实值之间存在细微的误差是允许的。这样可以防止模型过拟合并且使得神经网络对于未知数据有着更强的泛化能力。

下一步是什么?

幸运的是我们的学习之旅还没有结束,仍然有很多关于神经网络和深度学习的内容需要学习。例如:

? 除了 Sigmoid 以外,还可以用哪些激活函数

? 在训练网络的时候应用学习率

? 在面对图像分类任务的时候使用卷积神经网络

我很快会写更多关于这个主题的内容,敬请期待!

最后的想法

我自己也从零开始写了很多神经网络的代码

虽然可以使用诸如 Tensorflow 和 Keras 这样的深度学习框架方便的搭建深层网络而不需要完全理解其内部工作原理。但是我觉得对于有追求的数据科学家来说,理解内部原理是非常有益的。

这种练习对我自己来说已成成为重要的时间投入,希望也能对你有所帮助