神经网络为何沉寂多年?一文体会深度学习的巨人之力——反向传播算法

本文深入探讨了模型优化的核心——梯度下降算法,包括其基本原理、方向和步长的确定。同时,介绍了反向传播算法如何利用链式法则有效地计算复杂神经网络的梯度,从而实现模型参数的更新。通过实例解析,展示了PyTorch中如何利用AutoGrad模块进行自动微分和反向传播,为理解和实现深度学习模型的训练提供了清晰的路径。
摘要由CSDN通过智能技术生成

书接上回,上篇文章我们了解了模型的损失函数,通过损失函数能衡量模型预测的准确性,我们的目标就是最小化损失函数,也称之为优化,以使得预测值和真实值尽可能的接近。本文就和大家一起学习一下关于模型优化的相关问题。

推荐阅读,以便新老朋友丝滑入戏

1. 一文参透神经网络及其实现,揭开深度学习的神秘面纱

2. 由回归到分类,继续探寻神经网络的美妙

3. 一窥深度学习黑箱,拼接深层神经网络架构

4. 建模之后又要做些什么?如何考察模型优劣?

图片

文章大纲

图片

当我们得到一个模型的损失函数之后,那么目标只有一个,就是找到使得损失函数最小的参数值。对于任何一个仅靠人力难以解决的问题,一般都要得到其数学化的表示,以期望利用现代计算机去做无脑的计算推理,来得到一个超出预期的结果,损失函数就是我们的数学表示。

1. 梯度下降算法

具体来看损失函数loss,其本质就是将神经网络中的权重w和偏差b作为输入,并输出一个数值来反映这些权重和偏差质量的好坏,以此来衡量模型的性能。当我们不断的训练神经网络时,损失函数得到的值也会相应的变化,直到得到一个很小的损失。将这个过程用一个简单的函数来表示,如二次函数的形式,其简单图像如

图片

对这个函数来说,我们求其最小值就是求导 f ′ ( x ) = d y d x f'(x)=\frac {dy} {dx} f(x)=dxdy ,并令导数为0,可以看出,这个过程就是微积分。这是凸函数的情况,有确定的唯一最小值。那么当损失函数变得复杂时,我们没有办法通过一次求导就得到其最小值。如

图片

对于这个非凸函数,有多个最小值,一般常使用拉格朗日变换转换为凸函数。有局部最小值也是我们训练模型结果时好时坏的原因之一,当在训练过程中陷入局部最优解时,这样训练的结果就不会很好。对于这种情况最优化损失函数,我们可以想到的是使用迭代,即让函数值向着最小值的地方一次次迭代前进。这就引出了两个问题,一是向哪个方向迭代,二是一次迭代多少,即距离。

1.1 “下山”问题

一个经典的问题场景是“下山”,
在这里插入图片描述

图源 https://www.3blue1brown.com/lessons/backpropagation

假设我们此时在山顶⛰️,我们想要用最短的时间下山,将这个由山顶到山脚的过程细化,即每一步都想要朝着能下去最快的那个方向移动,等等,下降最快的方向,我们学过的导数的定义不就是变化最快吗?

当确定了朝哪里走之后,就要开始迈步子了🏃‍♂️,这一步迈多大呢?我们知道,每一点的导数是不同的,也就是说只要移动,那么在移动后的点的下山最快的方向就一定会发生变化,因此如果我们步子迈的太大,就会增加错过最好方向的误差,迈的太小,则会很久才能到山下,也就是收敛的很慢,因此这一步要试出一个最佳的长度。OK,总结一下,以下山的过程来考虑我们的损失函数,即为损失函数经过迭代一步步收敛到全域最小值,每迭代一次的方向就是导数的方向,如图

图片

图源 https://www.3blue1brown.com/lessons/backpropagation

当斜率为正时,向左移动,当斜率为负时,向右移动,这样就能使得函数总是向着函数值变小的方向移动↗️。而梯度和导数大致是一个意思,只是梯度更强调于方向,是一个矢量。函数的梯度(gradient)指示的是上升最快的方向,即函数增加最快的方向,那么其反方向就是下降最快的方向,而我们需要的就是这个下降最快的方向。

确定了方向,我们再来看步子要迈多大🏃,这也是一个超参数,要通过我们不断的调整,最后得到一个最合适的值,使得损失函数又快又好地收敛,这个步子也被称为步长,也叫**“学习率”(learning rate),一般用η表示。一般来讲,当就要迭代到一个最小值时,步长就会越小,以此来避免越过最小值点。当然了,在模型训练中还有很多的调整学习率的trick,比如warmup**,在刚开始设置一个较小的学习率,让模型先了解数据,在随着训练的进行,逐渐增大学习率。这里只是简单介绍,具体可以参考论文《Cyclical Learning Rates for Training Neural Networks》

https://ieeexplore.ieee.org/abstract/document/7926641

1.2 移动过程

有了方向和步长,接下来就是让我们的点动起来,假设一个点A(9,9),向量b(3,3),向量大小为 3 2 3\sqrt {2} 32 ,按照刚才的推导,需要让A点沿着向量b的反方向移动 3 2 3\sqrt {2} 32 个单位,如图

图片

在图中可以看出,绿色的点就是在向量的反方向上移动 3 2 3\sqrt {2} 32 个单位后得到的新的点A’。A点和A’点有什么关系呢,我们发现移动后的点就是在A点坐标的基础上,横纵坐标都减去3,也就是减去向量的坐标。这么一来我们就可以将该规律做一个推广,对于任意的一个点w,梯度向量为 ∂ L ∂ w \frac{\partial L}{\partial \boldsymbol{w}} wL,其迭代过程就是
w ( t + 1 ) = w ( t ) − ∂ L ∂ w \boldsymbol{w}_{(t+1)}=\boldsymbol{w}_{(t)}-\frac{\partial L}{\partial \boldsymbol{w}} w(t+1)=w(t)wL
再将我们的学习率η加上,则变为
w ( t + 1 ) = w ( t ) − η ∂ L ∂ w \boldsymbol{w}_{(t+1)}=\boldsymbol{w}_{(t)}-η\frac{\partial L}{\partial \boldsymbol{w}} w(t+1)=w(t)ηwL
η 这个就是常见的梯度下降的迭代公式,可以看出式子的核心就是求梯度,一旦求出梯度,再利用η控制下降的距离,利用负号控制下降的方向,一步一步迭代,直到损失函数收敛。

图片

图源 https://www.3blue1brown.com/lessons/backpropagation

梯度向量每一项的大小都是在告诉我们,损失函数对于每个参数有多敏感,这个过程就是神经网络学习的核心。

2. 如何求解梯度

2.1 尝试求导

有了梯度下降的公式,明白了我们要如何迭代,下一步就是去考虑怎么实现。还是利用简单的神经网络来看,如图

图片 我们只关注图中红框内的部分,现在要做的就是对损失函数求导,得到当前参数情况下的梯度。在求导之前还是先解释一下图中的变量的含义。z是我们之前提到过的输入x和权重w加权求和的结果,即
z = w 0 x 0 + w 1 x 1 + w 2 x 2 z={w}_{0}{{x}_{0}+w}_{1}{x}_{1}+{w}_{2}{x}_{2} z=w0x0+w1x1+w2x2
将这个结果作为输入传递给隐藏层,隐藏层的激活函数的sigmoid函数,经过该函数做一个非线性映射,得到我们的预测值 y ^ \hat{y} y^
y ^ = s i g m o i d ( z ) = 1 1 + e − z \hat{y} =sigmoid(z)=\frac {1} {1+{e}^{-z}} y^=sigmoid(z)=1+ez1

有了预测值和真实值就可以得到衡量模型优劣的损失函数,这里我们还是使用交叉熵损失函数,即  loss  = − ∑ i = 1 m ( y i ∗ ln ⁡ ( y ^ i ) + ( 1 − y i ) ∗ ln ⁡ ( 1 − y ^ i ) ) = − ∑ i = 1 m ( y i ∗ ln ⁡ ( 1 1 + e − X i w ) + ( 1 − y i ) ∗ ln ⁡ ( 1 − 1 1 + e − X i w ) ) \begin{aligned} \text { loss } &=-\sum_{i=1}^{m}\left(y_{i} * \ln \left(\hat{y}_i\right)+\left(1-y_{i}\right) * \ln \left(1-\hat{y}_i\right)\right) \\ &=-\sum_{i=1}^{m}\left(y_{i} * \ln \left(\frac{1}{1+e^{-\boldsymbol{X}_{i} \boldsymbol{w}}}\right)+\left(1-y_{i}\right) * \ln \left(1-\frac{1}{1+e^{-\boldsymbol{X}_{i} \boldsymbol{w}}}\right)\right) \end{aligned}  loss =i=1m(yiln(y^i)+(1yi)ln(1y^i))=i=1m(yiln(1+eXiw1)+(1yi)ln(11+eXiw1)) 从输入到最后得到损失函数的这个过程也被称为前向传播,得到损失函数后要对loss进行优化,采用梯度下降法即对权重w进行求导 ∂  Loss  ∂ w \frac{\partial \text { Loss }}{\partial w} w Loss  ,可以预见仅仅是简单的两层神经网络且参数量很小的情况下,求导已经是一个很复杂的任务,更何况当神经网络叠加很多层。这种求导的困难一直限制着神经网络的发展,自1943年提出的人工神经网络, 直到1986年Hinton老爷子提出来大名鼎鼎**反向传播算法(Backpropagation algorithm)**才使得神经网络有了巨大的发展,又随着硬件算力的提升,近十年AI得以变得炙手可热。

2.2 膜拜大佬

图片

这张照片拍摄于18年深度学习三巨头共同获得图灵奖的时刻,整个深度学习领域的四位绝顶高手自左向右分别是Yann LeCunGeoffrey HintonYoshua Bengio吴恩达老师。

其中Yann LeCun是卷积神经网络的创立者,被称为“机器学习界的果蝇”的经典数据集Mnist也是由其所创;Hinton老爷子被称为“神经网络之父”、“深度学习鼻祖”;Yoshua Bengio自然语言处理的重要奠基人,也是深度学习的圣经–花书的作者之一;吴恩达老师更是不必多言,其机器学习课程带领无数学生入门。

专门写这一段是因为笔者非常景仰这些大牛,借助深度学习的巨人之力,常怀敬畏之心,常守慎独之境。当然了,还有非常多的牛人,都是耳熟能详的名字,下面这张图可以参考

图片

图源 https://zhuanlan.zhihu.com/p/35338693

大家如果感兴趣可以参考于知乎上的文章《 深度学习&自然语言处理领域的牛人、贡献及关系网 》https://zhuanlan.zhihu.com/p/35338693

2.3 反向传播算法

回到正题,反向传播算法提出的基础就是高等数学中学过的求导的链式法则。当y为u的函数,u为v的函数,v为x的函数时, d y d x = d y d u d u d v d v d x \frac {dy} {dx}=\frac {dy} {du}\frac {du} {dv}\frac {dv} {dx} dxdy=dudydvdudxdv

  						【⚠️公式预警⚠️】

使用链式法则就可以把复杂函数关系的求导转换为由最内层向最外层逐步求导,像上面求解损失函数的问题,将其复杂的函数关系拆解开,转换为 ∂  loss  ∂ w = ∂ l ( y ^ ) ∂ y ^ ∗ ∂ y ^ ( z ) ∂ z ∗ ∂ z ( w ) ∂ w \frac{\partial \text { loss }}{\partial w}=\frac{\partial l(\hat{y})}{\partial \hat{y}} * \frac{\partial \hat{y}(z)}{\partial z} * \frac{\partial z(w)}{\partial w} w loss =y^l(y^)zy^(z)wz(w) 对第一部分求导可得
∂ l ( y ^ ) ∂ y ^ = ∂ ( − ∑ i = 1 m ( y i ∗ ln ⁡ ( y ^ i ) + ( 1 − y i ) ∗ ln ⁡ ( 1 − y ^ i ) ) ) ∂ y ^ = ∑ i = 1 m ∂ ( − ( y i ∗ ln ⁡ ( y ^ i ) + ( 1 − y i ) ∗ ln ⁡ ( 1 − y ^ i ) ) ) ∂ y ^ = ∑ i = 1 m − ( y i ∗ 1 y i ^ + ( 1 − y i ) ∗ 1 1 − y i ^ ∗ ( − 1 ) ) = ∑ i = 1 m − ( y i y i ^ + y i − 1 1 − y i ^ ) = ∑ i = 1 m − ( y ( 1 − y i ^ ) + ( y i − 1 ) y i ^ y i ^ ( 1 − y i ^ ) ) = ∑ i = 1 m − ( y i − y i y i ^ + y i y i ^ − y i ^ y i ^ ( 1 − y i ^ ) ) = ∑ i = 1 m y i ^ − y i y i ^ ( 1 − y i ^ ) \begin{aligned} \frac{\partial l(\hat{y})}{\partial \hat{y}} &=\frac{\partial\left(-\sum_{i=1}^{m}\left(y_{i} * \ln \left(\hat{y}_{i}\right)+\left(1-y_{i}\right) * \ln \left(1-\hat{y}_{i}\right)\right)\right)}{\partial \hat{y}} \\ &=\sum_{i=1}^{m} \frac{\partial\left(-\left(y_{i} * \ln \left(\hat{y}_{i}\right)+\left(1-y_{i}\right) * \ln \left(1-\hat{y}_{i}\right)\right)\right)}{\partial \hat{y}}\\ &=\sum_{i=1}^{m}{-\left(y_{i} * \frac{1}{\hat{y_{i}}}+(1-y_{i}) * \frac{1}{1-\hat{y_{i}}} *(-1)\right)} \\ &=\sum_{i=1}^{m}-\left(\frac{y_{i}}{\hat{y_{i}}}+\frac{y_{i}-1}{1-\hat{y_{i}}}\right) \\ &=\sum_{i=1}^{m}-\left(\frac{y(1-\hat{y_{i}})+(y_{i}-1) \hat{y_{i}}}{\hat{y_{i}}(1-\hat{y_{i}})}\right) \\ &=\sum_{i=1}^{m}-\left(\frac{y_{i}-y_{i} \hat{y_{i}}+y_{i} \hat{y_{i}}-\hat{y_{i}}}{\hat{y_{i}}(1-\hat{y_{i}})}\right) \\ &=\sum_{i=1}^{m}\frac{\hat{y_{i}}-y_{i}}{\hat{y_{i}}(1-\hat{y_{i}})} \end{aligned} y^l(y^)=y^(i=1m(yiln(y^i)+(1yi)ln(1y^i)))=i=1my^((yiln(y^i)+(1yi)ln(1y^i)))=i=1m(yiyi^1+(1yi)1yi^1(1))=i=1m(yi^yi+1yi^yi1)=i=1m(yi^(1yi^)y(1yi^)+(yi1)yi^)=i=1m(yi^(1yi^)yiyiyi^+yiyi^yi^)=i=1myi^(1yi^)yi^yi
第二部分, y ^ \hat{y} y^在我们的神经网络图中是指激活函数sigmoid,而sigmoid函数的求导结果非常有意思,一起来看一下 ∂ y ^ ( z ) ∂ z = ∂ 1 1 + e − z ∂ z = − ( 1 + e − z ) − 2 ∗ e − z ∗ ( − 1 ) = e − z ( 1 + e − z ) 2 = 1 + e − z − 1 ( 1 + e − z ) 2 = 1 + e − z ( 1 + e − z ) 2 − 1 ( 1 + e − z ) 2 = 1 ( 1 + e − z ) − 1 ( 1 + e − z ) 2 = 1 ( 1 + e − z ) ( 1 − 1 ( 1 + e − z ) ) = y ^ ( 1 − y ^ ) \begin{aligned} \frac{\partial \hat{y}(z)}{\partial z} &=\frac{\partial \frac{1}{1+e^{-z}}}{\partial z} \\\\ &=-\left(1+e^{-z}\right)^{-2} * e^{-z} *(-1) \\\\ &=\frac{e^{-z}}{\left(1+e^{-z}\right)^{2}} \\\\ &=\frac{1+e^{-z}-1}{\left(1+e^{-z}\right)^{2}} \\\\ &=\frac{1+e^{-z}}{\left(1+e^{-z}\right)^{2}}-\frac{1}{\left(1+e^{-z}\right)^{2}} \\\\ &=\frac{1}{\left(1+e^{-z}\right)}-\frac{1}{\left(1+e^{-z}\right)^{2}} \\\\ &=\frac{1}{\left(1+e^{-z}\right)}\left(1-\frac{1}{\left(1+e^{-z}\right)}\right) \\\\ &=\hat{y}(1-\hat{y}) \end{aligned} zy^(z)=z1+ez1=(1+ez)2ez(1)=(1+ez)2ez=(1+ez)21+ez1=(1+ez)21+ez(1+ez)21=(1+ez)1(1+ez)21=(1+ez)1(1(1+ez)1)=y^(1y^) 第三部分 X = [ x 0 , x 1 , x 2 ] , w = [ w 0 , w 1 , w 2 ] X=\left[x_{0}, x_{1}, x_{2}\right], \quad w=\left[w_{0}, w_{1}, w_{2}\right] \quad X=[x0,x1,x2],w=[w0,w1,w2] z = X w z=X w z=Xw ,所以对其求导可得 将以上三个部分组合,可得到 ∂ z ( w ) ∂ w = ∂ X w ∂ w = X \begin{aligned} \frac{\partial z(w)}{\partial w} &=\frac{\partial \boldsymbol{X} \boldsymbol{w}}{\partial w} \\ &=\boldsymbol{X} \end{aligned} wz(w)=wXw=X

没想到经过链式法则求得的导数最后是这么简单的一个式子,这里面X是我们输入, 是模型输出的预测值,y是真实值,经过前向传播之后,所有的数据都是已知,因此可以很方便的求得导数。同样的,如果层数增加,这些重复的计算过程就派上了用场,不必再对所有的链式法则小部分一个个求解,而是利用已经做过的工作。

上面的推导便是整个神经网络的反向传播,训练开始随机选择一些权重,经过反向传播可以求出当前参数下损失函数的梯度,让权重参数减去梯度向量就相当于做了一次迭代,然后循环往复,直到函数收敛。同样的还是上面的图,再看一次就变得清晰明朗了

图片

图源 https://www.3blue1brown.com/lessons/backpropagation

  1. Pytorch中的实现

3.1 AutoGrad模块

上面的结果虽然看起来已经很简单,但是在巨量的参数下,逐步求导仍是人力所不能及,考虑到都是简单的求导运算,如此简单重复的运算交由计算机处理那就再好不过了,那么在计算机中如何去实现呢?这就不得不提深度学习框架了,在pytorch中有一个梯度计算工具,也是pytorch框架的核心——AutoGrad模块(自动微分)

pytorch中的张量tensor具有可微分性,好像之前版本并不是这样,在创建张量的时候,将属性requires_grad设为True,则张量就可以做微分运算。官方解释为

图片

还有grad_fn属性,用来存储Tensor微分函数,即存储计算关系,比如一个函数为 y = x 2 y=x^2 y=x2 ,其中x的requires_grad为True,则y由x运算得到,其requires_grad也为True。此时查看y的grad_fn属性,

图片

grad_fn中就记录了x经过平方运算得到y,这种存储了x的运算关系也称为“回溯机制”,在该机制的作用下,所有的运算关系形成一个计算图。像反向传播这种算法,其计算图是非常大的,这也方便了微分运算。pytorch中的计算图是动态的,也就是说计算图是在计算过程中自动生成,这种动态的方式极大的增加了pytorch的灵活性,而早些版本的Tensorflow是静态计算图,也就是在运算之前先构建好计算图,运算过程中不能随意修改,二者相比后者就显得有些迟滞。

3.2 backward()

介绍了pytorch的自动微分机制,再来考虑实现反向传播就比较简单了,无非就是记录函数关系,生成计算图,在计算图的基础上反向传播记录的函数关系,直到传播到计算图中的叶节点(输入x),进而求导。

图片

在pytorch中计算图只可以利用一次,也就是说只能进行一次反向传播,再次运行会报错

图片

3.3 with torch.no_grad()

前面提到的在反向传播的过程中,有非常多的函数关系,因此如果全部都生成计算图会产生很大的内存消耗,因此就有了一个停止跟踪计算图的方法with torch.no_grad() 这个方法在评估模型的时候非常有必要,模型在训练阶段生成计算图是为了反向传播求梯度,但是在评估阶段我们仅仅是为了测试模型,使用该方法会让反向传播过程只是测试,而不修改参数。当然很重要的一点也是为了节省显存,否则就要看到out of memory的报错了。

更多关于AutoGrad mechanics请参考官方文档

https://pytorch.org/docs/stable/notes/autograd.html?highlight=grad_fn

图片

以上便是本文的全部内容了,希望能给到你一点点的帮助🐞。

分享知识,记录生活,一起进步。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值