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

Python016

一文搞懂梯度下降&反向传播,第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就会报错。结果证明,我们自己动手实现的算法是正确的。

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

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

下面是函数实现的代码部分:

clc

clear all

close all

%% 加载神经网络的训练样本 测试样本每列一个样本 输入P 输出T,T是标签

%样本数据就是前面问题描述中列出的数据

%epochs是计算时根据输出误差返回调整神经元权值和阀值的次数

load data

% 初始隐层神经元个数

hiddennum=31

% 输入向量的最大值和最小值

threshold=[0 10 10 10 10 10 10 10 10 10 10 10 10 10 10 1]

inputnum=size(P,1)% 输入层神经元个数

outputnum=size(T,1)% 输出层神经元个数

w1num=inputnum*hiddennum% 输入层到隐层的权值个数

w2num=outputnum*hiddennum% 隐层到输出层的权值个数

N=w1num+hiddennum+w2num+outputnum%待优化的变量的个数

%% 定义遗传算法参数

NIND=40%个体数目

MAXGEN=50%最大遗传代数

PRECI=10%变量的二进制位数

GGAP=0.95%代沟

px=0.7%交叉概率

pm=0.01%变异概率

trace=zeros(N+1,MAXGEN)%寻优结果的初始值

FieldD=[repmat(PRECI,1,N)repmat([-0.50.5],1,N)repmat([1011],1,N)]%区域描述器

Chrom=crtbp(NIND,PRECI*N)%初始种群

%% 优化

gen=0%代计数器

X=bs2rv(Chrom,FieldD)%计算初始种群的十进制转换

ObjV=Objfun(X,P,T,hiddennum,P_test,T_test)%计算目标函数值

while gen

BP算法实现步骤(软件):

1)初始化

2)输入训练样本对,计算各层输出

3)计算网络输出误差

4)计算各层误差信号

5)调整各层权值

6)检查网络总误差是否达到精度要求

满足,则训练结束;不满足,则返回步骤2)

3、多层感知器(基于BP算法)的主要能力:

1)非线性映射:足够多样本->学习训练

能学习和存储大量输入-输出模式映射关系。只要能提供足够多的样本模式对供BP网络进行学习训练,它便能完成由n维输入空间到m维输出空间的非线性映射。

2)泛化:输入新样本(训练时未有)->完成正确的输入、输出映射

3)容错:个别样本误差不能左右对权矩阵的调整

4、标准BP算法的缺陷:

1)易形成局部极小(属贪婪算法,局部最优)而得不到全局最优;

2)训练次数多使得学习效率低下,收敛速度慢(需做大量运算);

3)隐节点的选取缺乏理论支持;

4)训练时学习新样本有遗忘旧样本趋势。

注3:改进算法—增加动量项、自适应调整学习速率(这个似乎不错)及引入陡度因子