如果你喜欢本文的话,不妨点赞、分享或者关注公共号「数据搬瓦工」,上面会同步更新文章。
如何有错误之处,还望大家指点,我将及时修改。
本文重点讲解神经网络的「反向传播算法(BP)」
首先定义一些变量:
L = 神经网络的层次数
= 第 l 层包含的单元数(不包括偏置单元)
K = 输出层的单元数
m = 样本数
代价函数
代价函数 J(θ) 度量的就是这个神经网络对训练数据的拟合情况。
神经网络的激励函数(activation function) 与逻辑回归模型的假设函数都是 Sigmoid 函数。从某种意义上来说,神经网络的代价函数是逻辑回归模型的代价函数的泛化(generalization)。
下图分别是逻辑回归模型与神经网络的代价函数(都经过正则化)。可以看出来两者相似,不同之处在于「多输出节点会产生一个输出向量」以及「神经网络具有多层结构」。
下面解释神经网络的代价函数,式子里有两个多重求和,将双重求和称为第一部分,三重求和称为第二部分;其中单个输出的代价值与逻辑回归模型相同 。
式子第一部分解释:上一章节讲到「多输出节点会产生一个输出向量」,故此需要先求一个样本产生的所有输出结果的代价之和(即 );然后再求所有样本的所有输出结果的代价之和;
式子第二部分解释:属于代价函数正则化,增加对参数惩罚项。三重求和的实际意思就是求除了偏置单元的参数 之外的其他所有参数平方和。
需注意:对于神经网络代价函数 是一个非凸函数,因此理论上是会停留在局部最小值的位置。实际上,梯度下降算法和其他一些高级优化方法理论上都会收敛于局部最小值。但一般来讲,这个问题其实并不是什么要紧的事,尽管我们不能保证这些优化算法一定会得到全局最优值。因为通常来讲,像梯度下降这类的算法在最小化代价函数 的过程中虽然通常得到一个很小的局部最小值,但是还是表现得很不错的。
反向传播算法 Backpropagation Algorithm
反向传播算法是神经网络中求解一组最优的 让代价函数取最小值(即 )的算法。
具体求解方法:反向传播算法通过对每个参数 计算偏导数 ,然后再像逻辑回归与线性回归使用梯度下降算法一样调整参数值。
在下图所示的神经网络中且以一个样本计算过程为例,反向传播算法过程:
- 计算最后一层(即输出层)的所有节点误差(error),输出层的误差直接预测值减去真实值,即 ;
- 计算倒数第二层(即第三层)的所有节点误差,隐藏层的误差计算公式与输出层不同,套用公式得 。
- 计算前一层的所有节点误差,套用公式得 ;
- 输入层的误差不需要计算;
- 计算偏导数,公式为 ;
- 按层计算偏导数,就有
概述,一个样本的反向传播算法过程:从输出层开始计算每一个神经元的误差,然后计算前一层的每个神经元的误差,一直逐层递减计算每层每个神经元的误差,直至输入层为止,但不计算输入层。接着,每层 的神经元与它后一层 的误差组合加到对应的偏导数中。
更一般地,当有大量的训练样本时:
其中, 代表第 层, 代表第 个样本, 代表某层中的第 个神经元。
反向传播算法实现(伪代码)
以三层神经网络为例,给出反向传播算法实现:
% 以三层神经网络为例,反向传播算法实现
% Theta1,Theta2都是参数矩阵;X是样本数据,X是m*n矩阵;y是真实值,y是m*1的向量
Theta1_grad = zeros(size(Theta1)); %Theta1的偏导数
Theta2_grad = zeros(size(Theta2)); %Theta2的偏导数
delta3 = zeros(size(Theta2,1),1); %反向传播,输出层的误差
delta2 = zeros(size(Theta1)); %反向传播,隐藏层的误差;输入层不计算误差
for i = 1:m %m为训练样本数,利用for遍历
% 先执行向前传播,求第 i 个样本的预测值
a1 = X(i,:)'; %取第i个训练样本
z2 = Theta1 * a1; %对第i个训练样本向前传播得到输出h(x),即为a3
a2 = sigmoid(z2);
a2 = [1; a2]; %补偏置单元
z3 = Theta2 * a2;
a3 = sigmoid(z3);
% 反向传播,计算偏导数
temp_y = zeros(size(Theta2,1), 1); %将原先真实值y(i)修改为神经网络多输出的真实值向量
temp_y(y(i)) = 1;
delta3 = a3 .- temp_y; %反向传播,计算得偏导数
delta2 = Theta2' * delta3 .*[1;sigmoidGradient(z2)]; %sigmoidGradient(z)是g'(z)
delta2 = delta2(2:end);
Theta2_grad = Theta2_grad + delta3 * a2';
Theta1_grad = Theta1_grad + delta2 * a1';
end
Theta2_grad = 1/m * Theta2_grad + lambda/m * Theta2; %正则化,修正梯度值
Theta2_grad(:,1) = Theta2_grad(:,1) - lambda/m * Theta2(:,1); %由于不惩罚偏执单元对应的列,所以把他减掉
Theta1_grad = 1/m * Theta1_grad + lambda/m * Theta1; %同理修改Theta1_grad
Theta1_grad(:,1) = Theta1_grad(:,1) - lambda/m * Theta1(:,1);
梯度检验
反向传播有很多细节,这些细节有点复杂有一个不幸的消息是, 它们有很多细节会导致一些 BUG。如果你用梯度下降来计算,你会发现表面上它可以工作。实际上,代价函数 虽然每次迭代都在下降。但是可能你的代码仍然有很多 BUG。 所以,表面上 在减小,但是你可能最后得到的结果实际上有很大的误差。你这时候可能知道,有一些小的 BUG 导致这种不好的算法性能表现。所以,怎么办呢?有一个办法叫「梯度检验(Gradient Checking)」,它能减少这种错误的概率。
总而言之,梯度检验是用于检验反向传播算法内部是否正确。
梯度检验原理:当计算偏导数时,可采用双边导数计算偏导数的近似值。如图中蓝线代表真实的导数,红线代表双边导数。当 很小时,双边导数约等于精确计算的偏导数。
梯度检验实现:运用反向传播算法计算偏导数的同时运用双边导数计算偏导数的近似值,通过对比两个值是否约等于来判断反向传播算法是否正确。
一般 check 公式的值如果大于 取值时,就需要检查一下是否存在 bug。
梯度检验方法使用提醒:当你的反向传播算法代码通过「梯度检验」后,要先关闭「梯度检验」再运行反向传播算法。因为「梯度检验」时间复杂度高,不关闭的话会使算法运行变慢。
参数初始化
将所有参数初始化为零不适用于神经网络。 因为当所有初始为零时,当我们进行向前传播时,所有激活单元的值都相同;当进行反向传播时,所有神经元的误差也相同,最后不管怎么使用反向传播算法调整参数,所有参数依旧相等。
具体参数需要随机初始化的原因:https://web.stanford.edu/class/ee373b/nninitialization.pdf
我们可以使用以下方法随机初始化参数矩阵 的值:
其中 rand() 的参数为矩阵的行列数;同时, 建议取值是 , 分别是 。
总结
最后提醒一下,第一次编写向前传播算法和反向传播算法代码,推荐使用 for 循环遍历每个样本来实现算法,因为有助于了解算法的过程。虽然有更高级算法不需要使用 for 循环来实现向前传播算法和反向传播算法。
参考
- Coursera. 机器学期, 吴恩达
</div>