简述

前面介绍了人工神经网络的表示,了解了如何利用神经网络预测结果。但我们还不知道如何训练神经网络,确定轴突的权值。

即将介绍的反向传播(backpropagation)算法,就是实现这个目的的。

符号约定

代价函数

回顾一下正规化的逻辑回归的代价函数:

在神经网络中,代价是相对输出层的全部节点而言的,因此代价函数更复杂一些:

可以看到,正规化的部分也更加复杂,遍历了全部权值(除去偏移量)。

反向传播算法

目标

思路

类似梯度下降法,给定一个初值后,计算出所有节点的计算值和激活值,然后根据代价函数的变化不断调整参数值(权值),最终不断逼近最优结果,使代价函数值最小。

推导

为了实现上述思路,我们必须首先计算代价函数的偏导数:

这个偏导并不好求,为了方便推导,我们假设只有一个样本(,可忽略代价函数中的外部求和),并舍弃正规化部分,然后分为两种情况来求。

情况1 隐层输出层

我们知道:

另外,输出层即第层。

所以:

其中:

综上:

情况2 隐层/输入层隐层

因为,所以可以将输入层和隐层同样对待。

其中后两部分偏导很容易根据前面所得类推出来:

第一部分偏导是不好求解的,或者说是没法直接求解的,我们可以得到一个递推式:

因为该层的激活值与下一层各节点都有关,链式法则求导时需一一求导,所以有上式中的求和。

递推式中第一部分是递推项,后两部分同样易求:

所以,递推式为:

为了简化表达式,定义第层第个节点的误差:

可知,情况1的误差为:

则最终的代价函数的偏导为:

我们发现,引入误差后,这个公式可以通用于情况1情况2

可以看出,当前层的代价函数偏导,需要依赖于后一层的计算结果。这也是为什么这个算法的名称叫做“反向传播算法”。

总结算法公式

算法过程

bp

有了上述推导,我们描述一下算法具体的操作流程:

  1. 输入:输入样本数据,初始化权值参数(建议随机生成较小的数)。
  2. 前馈:计算各层()各节点的计算值()和激活值()。
  3. 输出层误差:计算输出层误差(公式见前文)。
  4. 反向传播误差:计算各层()的误差(公式见前文)。
  5. 输出:得到代价函数的梯度(参考前文偏导计算公式)。

反向传播算法帮助我们得到了代价函数的梯度,我们就可以借助梯度下降法训练神经网络了。

为学习速率。

Octave/MATLAB代码

以3层神经网络(输入层、隐层、输出层各一)为例。

代码实现中,考虑了正规化,避免出现过拟合问题。

前馈阶段

逐层计算各节点值和激活值。

a1 = X;
z2 = [ones(m, 1), a1] * Theta1';
a2 = sigmoid(z2);
z3 = [ones(m, 1), a2] * Theta2';
a3 = sigmoid(z3);

代价函数

正规化部分需注意代价函数不惩罚偏移参数,即(代码表示为Theta(:,1))。

J = 1 / m * sum((-log(a3) .* Y - log(1 .- a3) .* (1 - Y))(:)) + ... # 代价部分
 lambda / 2 / m * (sum((Theta1(:, 2:end) .^ 2)(:)) + sum((Theta2(:, 2:end) .^ 2)(:))); 
 # 正规化部分,lambda为正规参数,需除去偏移参数Theta*(:,1)

反向传播

输出层误差和梯度计算,反向传播计算隐层误差和梯度。

仍需注意正规化时排除偏移参数,另外注意为激活值补一个偏移量


function g = sigmoid(z)
    g = 1.0 ./ (1.0 + exp(-z));
end

function g = sigmoidGradient(z)
    g = sigmoid(z) .* (1 - sigmoid(z));
end

delta3 = a3 - Y;

Theta2_grad = 1 / m * delta3' * [ones(m, 1), a2] + ...
  lambda / m * [zeros(K, 1), Theta2(:, 2:end)]; # 正规化部分

delta2 = (delta3 * Theta2 .* sigmoidGradient([ones(m, 1), z2]));
delta2 = delta2(:, 2:end); # 反向计算多一个偏移参数误差,除去

Theta1_grad = 1 / m *  delta2' * [ones(m, 1), a1] + ...
  lambda / m * [zeros(H, 1), Theta1(:, 2:end)]; # 正规化部分