0 序言
在
本文将对模型细节进行讨论补充,定性分析激活函数的使用,阐述网络中数据维度情况;记录自己在BP算法学习过程中的一些思考(纯属发散,可跳过. 如有不正确之处欢迎讨论);给出深度神经网络BP算法代码实现(文末)及结果.
1 激活函数的位置
上文最后小节(3.3)介绍了结合Softmax和Cross Entropy Loss Function的神经网络反向传播输出层梯度计算. 在该网络中,输出层(第
层)在Feedforward过程仍使用了激活函数
,得到
,再对
做softmax得到
.
代表Sigmoid函数,常见激活函数还包括Relu(族)、Softplus、Tanh等,下表给出了几种经典激活函数及对应求导结果.表1 典型激活函数及对应导数
可见,实际上对于输出层,我们进行了2种操作:
和
.
的作用是将网络输出映射至(0, 1),便于划分阈值,处理二分类问题;对于多分类问题,则可以通过Softmax将输出归一化至(0, 1),得到类似概率分布的结果进行判断. 因此实际上这两种操作(activation, softmax)并不需要对输出层同时使用.
如果采用其他的激活函数,甚至有可能导致网络无法输出全部范围的值(如Relu),或是激活函数的导数在-1和1附近变得非常小,发生“梯度消失”.[Stanford CS231]示例中输出层未接激活函数,直接做Softmax.
2 Further discussion on Softmax + Cross Entropy
在一些文章中,将Softmax也称为激活函数,对输出层进行softmax激活. 在不接其他激活函数的情况下,上篇文章3.3节中递推起始层对权重矩阵和偏置向量梯度计算没有了对
的导数
,因此下面两式(上篇文章中(3.16),(3.17))中没有了
部分.
即:
可以看出,使用Softmax和交叉熵,可以减少输出层的
,从而略微减轻"梯度消失".
另一方面,由于真实标签
采用one-hot编码,仅有1个分量为1,因此在一些文章和代码demo中梯度计算项会出现
的记法,其实是式(2.2)中真实类别对应的元素项,从向量角度的计算为(
是真实类别
):
3 还是数据数量的问题:向量→矩阵→平均梯度
把输出层做Softmax激活的网络的BP算法递推重写如下:
观察第2组式子,
是一个
维矩阵,即
维向量. 这个向量是由1个输入数据
经过各层正向传播得到的向量
,再逐层反向递推得到,从而计算出每一层的
和
.
注意,上述过程中,输入1个数据
,对应产生1组梯度
,这正是随机梯度下降(SGD)算法的数据情况. 那么在使用Batch GD或Mini-batch GD进行优化的情况下,输入为
个数据组成的
维矩阵,输出为
个对应
组成的
维矩阵. 下面我们分析权重矩阵
和偏置向量
的计算. 首先看
,以第
层为例,输入1个数据时:
输入
个数据时,输出变为
,因此应计算
个梯度(这里特指
的梯度)
,形成以每列为一个数据对应梯度组成的矩阵:
最终我们需要的梯度
是向量,但得到了
个梯度,因此需要将所有向量求平均,作为该次iteration的梯度. 示意代码如下:
# y_label即使没有做one-hot来与y对齐行数,np中的Broadcast机制也能保证正确计算
Delta = y - y_label
delta = np.sum(Delta, axis = 1, keepdims=True) / M
再看
:
在
个数据输入的情况下,对于输出层的梯度,式(3.3)应写为:
式(3.3)中向量相乘维度为:
;
式(3.4)中矩阵相乘维度为:
.
两者结果维数完全相同,但(3.4)式的结果其实对应着
个(3.3)结果中矩阵之和,定性理解见图1. 读者也可以结合矩阵相乘的定义,用元素乘累加的方式定量表述两式,对比一下很容易理解,作为小练习吧:).图1 梯度矩阵与输出层矩阵相乘示意
从图1可看出,在网络一次输入
个数据时,误差对权重的梯度矩阵变成了每次输入1个数据计算得到的
个梯度矩阵之和。因此也需将计算结果求平均,示意代码如下:
dW_L = np.dot(Delta, ys[-2].T) / M # ys是存放各层激活值的list对于隐藏层
的梯度在
个数据输入时的情况,只是
的计算式从
变为
,均为
的向量,因此从向量到矩阵,再到梯度平均的过程和输出层分析完全相同.
4 一些思考和自言自语(可直接跳过)
4.1 DNN
从线性到非线性的飞跃:激活函数.
4.2 正向传播
这里需注意:也许是人们的成长经历养成的视觉和思维习惯,使得我们在第一眼看见神经网络的结构图时,潜意识里会更加关注圆圈,认为圆圈(神经元)比线条(权重)更加重要(我的第一感觉就是这样). 但是这在神经网络这个scenario,其实最重要的是
和
,请牢记!我们所做的所有思维体操,都围绕着这两个家伙展开. 而代表着神经元的圆圈,其实只是
和
的byproduct. 3blue1brown中打了个很巧妙的比喻:神经元neurons就是装
和
结果的容器.
4.3 Loss Function衡量真实值与网络输出值之间的差异. 包含了特征提取、模式识别、信号稀疏表示、空间投影等多个信息技术领域的原理与思想:样本的数据空间由一组广义基(区别于该空间的向量空间基)张成,这组广义基并不一定相互正交,也可以是low coherence的. 于是,广义基的基数(Cardinality)可以大于该数据空间的向量空间的基的数量.
对于特征提取、模式识别,广义基可以理解为目标空间所包含的所有特征组成的集合,如:各种形状、局部器官、纹理、层次、颜色等,具有很好的直观意义,便于理解和解释.
对于信号稀疏表示(sparse reprensentation),广义基可以理解为该空间所对应的超完备字典/正交级联字典中的所有原子,或者DFT, DWT, DCT, wavelet, Harr小波基, Doubechies的D4, D8小波基, chirplet/curvelet/bandlet/Contourlet基, Gabor基等等对应的各种基向量.
有了广义基,输入数据(信号)即可在广义基张成的空间上进行投影,保留最重要的分量,使得逼近信号和原信号的误差最小,得到数据的稀疏表示. 如果将傅里叶分析作为鼻祖,信号表示中的各种基的寻找已有至少两个世纪的研究和积累. 这种方式得到的广义基具有更明确的自然和物理意义,有很强的可解释性.
对于经训练得到的冗余字典,已经具有了一定的“自适应”特点,这和深度神经网络的训练的本质非常相似,都是通过数据(信号)驱动,去得到尽可能匹配数据空间的模型或者框架,这也是“智能”开始的标志. 前文所述的技术——深度神经网络更往前走了一步:几乎不通过任何结构化/先验的框架(神经元模型当然是必须有的),完全“自组织”地生成网络结构,最终数据(信号)通过这个网络可得到连续/离散的输出. 其实也相当于数据(信号)在一个高维空间上对其所有广义基的投影,而由于空间结构的约束,投影结果维度就是网络输出层维数. 最为精妙的地方在于:这种“自组织”的广义基没有了训练基、框架、线性等因素的限制,能够更好地匹配训练数据对应的空间.
另一方面,没有了诸多约束条件,使得得到的网络结构/广义基难以描述和解释,这是目前深度学习理论的短板,也是很多学者在不断探索的领域. 笔者认为结合信号表述、空间投影理论,也许能够一定程度上解决深度学习理论的可解释性问题.
明白了上面小节,可以继续来思考误差的定义:真实值/原始信号与预测值/经过广义基线性组合的逼近信号 之间的差异. 怎么衡量差异?最直观的想法就是两点之间距离,进而演进到向量的距离的定义,或者从“熵增”的角度来度量等等. 这些度量方式抽象出来,都包含3点性质:正定性、齐次性、三角不等式. 由此可以进一步将距离的定义推广至赋范空间. 如
范数:
图2 lp范数示意图2给出了常见的几种
范数在三维空间下的图像.
时,将
范数形式作为正则化项可以增加稀疏性. 2范数也就是常用的欧式距离. 实际中,
一般取0,1,2,因为这几种范数的计算比
简单. 但应注意,不同的
对应不同几何意义,有兴趣的同学可查阅相关资料.
降低误差直至达到可接受的值. 最好的结果:找到最小值;其次是找到局部最小值;再其次是小于一个设定的误差阈值. 无论要达到哪个目标,总归是要降低误差,而误差又是的函数. 这个函数的输入是
. 函数
的系数是待训练的数据
.
4.4 梯度下降法要使误差逐渐降低,可以有无数种方式,但要“最效率”地达到这个目标,最直观的想法就是“怎么不走弯路就怎么走”. 用生活中最直观的例子:下山. An intuitive idea is:迈步子之前先看看,然后朝着垂直下降最快的方向走. 从理论上可以证明(李宏毅教授DL课程),这种直观而又朴素的想法是非常正确的. 这里隐隐地包含了“奥卡姆剃刀原理”,在生活中我们常常可以用这种方法论来帮助自己.
回到正题,刚才说到方向,但请别忘了步子大小,这也是个非常重要的因素!还是下山的例子(文章标题上方配图),如果发现这里有个悬崖,嗯,下降的方向完美:垂直于地面,但是咱们绝不能一步就从悬崖跳到地面!这个方向确实是最好的,可还是只能一点一点往下走,比方说利用工具固定在悬崖边上,一步一步挪。当然,在实际中我们也绝对不会这么干:遇上这种情况,我们会找一条次优的、可行的路径平安下山;而在数学上,“从悬崖直接往下跳”这种操作表明该点不可导,已经不符合梯度下降要处处可导的前提了. 退一步想想,即使可导,也不能在一个点上飞快地下降一大段,因为也许在你前进的这一段路径中,又出现了下降更快(梯度更大)的方向.
4.5 关于
和
的初始值 不能为零矩阵
,
可以为零向量
,否则网络更新参数计算梯度时,梯度为零,无法更新.
也不能在初始化时每层矩阵都用全部相同数值,否则可能导致各隐藏层的输入输出都相同,神经网络“退化”成了一根导线/一个低秩(
)矩阵/一个线性函数
,失去了我们所希望的非线性特质.
5 代码与结果
实现了Fully Connected Neural Network的BP算法及训练过程. 模型层数、神经元个数、隐藏层激活函数类型可设置(3种经典激活:sigmoid, relu, tanh),方便调参对比结果. 输出层使用softmax激活,Cross Entropy损失函数.
一些结果:网络4层:隐藏层神经元个数:25, 25. 3分类,BGD Optimizer, iterations = 25000.图3 BGD, Sigmoid激活图4 BGD, Relu激活图5 BGD, Tanh激活
对比图3-图5,Sigmoid激活函数训练时间居中,但是达到训练次数时仍欠拟合,性能54.0%;Relu激活函数时间最短,性能较好99.33%;Tanh函数训练时间最长,效果与Relu接近98.83%.网络4层:隐藏层神经元个数:25, 25. 3分类,MBGD Optimizer, mini-batch-size =20, epochs = 10000.图6 MBGD, Sigmoid激活
对比图3、图6,采用小批量梯度下降后,sigmoid激活函数也可以较好地拟合(99.16%),但训练时间随着计算次数增加而增加.图7 MBGD, Relu激活图8 MBGD, Tanh激活
观察图7,图8,Relu和Tanh激活函数收敛速度明显快于Sigmoid(图6),两者性能相当(99.67%). Tanh函数训练时间最长,且在该实验参数条件下,已经开始出现过拟合.
6 总结
本系列是笔者学习DL的一些思考和记录. 可以说,所有想学深度学习的同学都有矩阵、概率和一定的泛函、随机过程基础. 对于很多经典算法理论,如LR、NN、SVM、kNN、决策树PCA、Manifold等都有掌握. 为了迅速上手,也许一上来就开始看CNN、RNN,LSTM, resNet,甚至Attention、HAN、Transformer、BERT、GPT2.0这些近几年出现的最新模型和方法.
相信我,如果不是科班ML出身,又决心把DL扎扎实实学好的话,还是会一步一步反向回溯每个知识点,最终来到知识的源头——DNN和BP的. 那么,就正向地从BP开始吧。毕竟DNN的第一步也是正向传播嘛,哈哈.
同时,从理论到代码还有很长的路要走,每次操作的对象是什么?输入、输出是什么?数据在每层是怎么流动的?矩阵还是向量还是标量?它们的的维度又是多少?写代码的时候如果把这些问题都想明白了,才算真的吃透了深度神经网络,迈出了DL的第一步.
参考了前辈、大神们的代码,自己也写了一个,并做了尽量详尽的注释,欢迎交流指正.“万丈高楼平地起”——此为自勉.
声明:本文为原创文章,欢迎转载。转载时请注明出处:观海云远:深度学习之反向传播算法(3)——全连接神经网络BP算法思考及Python实现zhuanlan.zhihu.com