梯度下降法python
A. 一文搞懂梯度下降&反向传播
如果把神经网络模型比作一个黑箱,把模型参数比作黑箱上面一个个小旋钮,那么根据通用近似理论(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就会报错。结果证明,我们自己动手实现的算法是正确的。
反向传播是遵循链式法则的,它将前向传播的输出作为输入,输入作为输出,通过递进的方式将求导这个动作从后向前传递回各层。神经网络参数的求导需要进行矩阵微积分计算,根据这些导数的反方向来调节参数,就可以让模型的输出误差的优化到最小值。
欢迎关注和点赞,你的鼓励将是我创作的动力
B. Python实现 近端梯度下降法、BCD、ADMM解LASSO问题
近端梯度下降法、BCD、ADMM解LASSO问题
数据处理部分,我们先简单介绍三种方法:近端梯度下降法、BCD块坐标下降法、ADMM交替方向乘子法。它们分别应用于不同场景的优化问题,特别是LASSO问题。
近端梯度下降法(Proximal Gradient Descent)的核心在于其能够处理具有非光滑部分的优化问题,尤其在LASSO问题中,其L1范数惩罚项导致目标函数不光滑。近端梯度下降法结合了梯度下降和proximal操作,以实现对非光滑部分的处理,从而求解最优解。在LASSO问题中,通过迭代更新参数,近端梯度下降法可以有效寻找到最优解。
接着,我们转向BCD块坐标下降法。这种方法通过将优化问题分解为多个子问题,分别在不同坐标上进行优化。对于LASSO问题,BCD通过交替地优化不同变量,从而逐步逼近全局最优解。推导过程见文章《BCD算法解LASSO问题》[1]。
最后,我们介绍ADMM交替方向乘子法。ADMM是一种用于分解复杂优化问题的算法,将原问题分解为多个子问题并交替求解。在LASSO问题中,ADMM通过引入乘子来处理约束条件,进而实现对目标函数的优化。推导过程可参照文章《ADMM算法解Fused LASSO问题》[2]。
总之,这三种方法在解决LASSO问题时各具优势,近端梯度下降法擅长处理非光滑问题,BCD块坐标下降法通过分解问题进行优化,而ADMM则利用分解和乘子策略有效处理约束问题。它们在实际应用中各有侧重,可根据具体问题选择适用的方法。