简述
前面介绍了人工神经网络的表示,了解了如何利用神经网络预测结果。但我们还不知道如何训练神经网络,确定轴突的权值。
即将介绍的反向传播(backpropagation)算法,就是实现这个目的的。
符号约定
$$
\begin{align*}
z_i^{(j)} =& \text{第$j$层的第$i$个节点(神经元)的“计算值”} \newline
a_i^{(j)} =& \text{第$j$层的第$i$个节点(神经元)的“激活值”} \newline
\Theta^{(l)}_{i,j} =& \text{映射第$l$层到第$l+1$层的权值矩阵的第$i$行第$j$列的分量} \newline
L =& \text{神经网络总层数(包括输入层、隐层和输出层)} \newline
s_l =& \text{第$l$层节点(神经元)个数,不包括偏移量节点。} \newline
K =& \text{输出节点个数} \newline
h_{\theta}(x)_k =& \text{第$k$个预测输出结果} \newline
x^{(i)} =& \text{第$i$个样本特征向量} \newline
x^{(i)}_k =& \text{第$i$个样本的第$k$个特征值} \newline
y^{(i)} =& \text{第$i$个样本实际结果向量} \newline
y^{(i)}_k =& \text{第$i$个样本结果向量的第$k$个分量} \newline
\end{align*}
$$
代价函数
回顾一下正规化的逻辑回归的代价函数:
$$
J(\theta) = - \frac{1}{m} \sum_{i=1}^m \large[ y^{(i)}\ \log (h_\theta (x^{(i)})) + (1 - y^{(i)})\ \log (1 - h_\theta(x^{(i)}))\large] + \frac{\lambda}{2m}\sum_{j=1}^n \theta_j^2
$$
在神经网络中,代价是相对输出层的全部节点而言的,因此代价函数更复杂一些:
$$
\begin{gather*}\large J(\Theta) = - \frac{1}{m} \sum_{i=1}^m \sum_{k=1}^K \left[y^{(i)}_k \log ((h_\Theta (x^{(i)}))_k) + (1 - y^{(i)}_k)\log (1 - (h_\Theta(x^{(i)}))_k)\right] + \frac{\lambda}{2m}\sum_{l=1}^{L-1} \sum_{i=1}^{s_l} \sum_{j=1}^{s_{l+1}} ( \Theta_{j,i}^{(l)})^2\end{gather*}
$$
可以看到,正规化的部分也更加复杂,遍历了全部权值(除去偏移量)。
反向传播算法
目标
求 $ \min_\Theta J(\Theta) $
思路
类似梯度下降法,给定一个初值后,计算出所有节点的计算值和激活值,然后根据代价函数的变化不断调整参数值(权值),最终不断逼近最优结果,使代价函数值最小。
推导
为了实现上述思路,我们必须首先计算代价函数的偏导数:
$$
\dfrac{\partial}{\partial \Theta_{i,j}^{(l)}}J(\Theta)
$$
这个偏导并不好求,为了方便推导,我们假设只有一个样本($m=1$
,可忽略代价函数中的外部求和),并舍弃正规化部分,然后分为两种情况来求。
情况1 隐层$\rightarrow$
输出层
我们知道:
$$
h_\Theta(x) = a^{(j+1)} = g(z^{(j+1)})
$$
$$
z^{(j)} = \Theta^{(j-1)}a^{(j-1)}
$$
另外,输出层即第$L$
层。
所以:
$$
\dfrac{\partial}{\partial \Theta_{i,j}^{(L)}}J(\Theta)
= \dfrac{\partial J(\Theta)}{\partial h_{\Theta}(x)_i} \dfrac{\partial h_{\Theta}(x)_i}{\partial z_i^{(L)}} \dfrac{\partial z_i^{(L)}}{\partial \Theta_{i,j}^{(L)}}
= \dfrac{\partial J(\Theta)}{\partial a_i^{(L)}} \dfrac{\partial a_i^{(L)}}{\partial z_i^{(L)}} \dfrac{\partial z_i^{(L)}}{\partial \Theta_{i,j}^{(L)}}
$$
其中:
$$
\dfrac{\partial J(\Theta)}{\partial a_i^{(L)}} = \dfrac{a_i^{(L)} - y_i}{(1 - a_i^{(L)})a_i^{(L)}}
$$
$$
\dfrac{\partial a_i^{(L)}}{\partial z_i^{(L)}} = \dfrac{\partial g(z_i^{(L)})}{\partial z_i^{(L)}} = \dfrac{e^{z_i^{(L)}}}{(e^{z_i^{(L)}}+1)^2} = a_i^{(L)} (1 - a_i^{(L)})
$$
$$
\dfrac{\partial z_i^{(L)}}{\partial \Theta_{i,j}^{(L)}} = \dfrac{\partial ( \sum_{k=0}^{s_{L-1}} \Theta_{i,k}^{(L)} a_k^{(L-1)})}{\partial \Theta_{i,j}^{(L)}} = a_j^{(L-1)}
$$
综上:
$$
\begin{split}
\dfrac{\partial}{\partial \Theta_{i,j}^{(L)}}J(\Theta)
=& \dfrac{\partial J(\Theta)}{\partial a_i^{(L)}} \dfrac{\partial a_i^{(L)}}{\partial z_i^{(L)}} \dfrac{\partial z_i^{(L)}}{\partial \Theta_{i,j}^{(L)}} \newline
=& \dfrac{a_i^{(L)} - y_i}{(1 - a_i^{(L)})a_i^{(L)}} a_i^{(L)} (1 - a_i^{(L)}) a_j^{(L-1)} \newline
=& (a_i^{(L)} - y_i)a_j^{(L-1)}
\end{split}
$$
情况2 隐层/输入层$\rightarrow$
隐层
因为$ a^{(1)}=x $
,所以可以将输入层和隐层同样对待。
$$
\dfrac{\partial}{\partial \Theta_{i,j}^{(l)}}J(\Theta)
=\dfrac{\partial J(\Theta)}{\partial a_i^{(l)}} \dfrac{\partial a_i^{(l)}}{\partial z_i^{(l)}} \dfrac{\partial z_i^{(l)}}{\partial \Theta_{i,j}^{(l)}}\ (l = 1, 2, ..., L-1)
$$
其中后两部分偏导很容易根据前面所得类推出来:
$$
\dfrac{\partial a_i^{(l)}}{\partial z_i^{(l)}} = \dfrac{e^{z_i^{(l)}}}{(e^{z_i^{(l)}}+1)^2} = a_i^{(l)} (1 - a_i^{(l)})
$$
$$
\dfrac{\partial z_i^{(l)}}{\partial \Theta_{i,j}^{(l)}} = a_j^{(l-1)}
$$
第一部分偏导是不好求解的,或者说是没法直接求解的,我们可以得到一个递推式:
$$
\dfrac{\partial J(\Theta)}{\partial a_i^{(l)}}
= \sum_{k=1}^{s_{l+1}} \Bigg[\dfrac{\partial J(\Theta)}{\partial a_k^{(l+1)}} \dfrac{\partial a_k^{(l+1)}}{\partial z_k^{(l+1)}} \dfrac{\partial z_k^{(l+1)}}{\partial a_i^{(l)}}\Bigg]
$$
因为该层的激活值与下一层各节点都有关,链式法则求导时需一一求导,所以有上式中的求和。
递推式中第一部分是递推项,后两部分同样易求:
$$
\dfrac{\partial a_k^{(l+1)}}{\partial z_{k}^{(l+1)}} = \dfrac{e^{z_{k}^{(l+1)}}}{(e^{z_{k}^{(l+1)}}+1)^2} = a_k^{(l+1)} (1 - a_k^{(l+1)})
$$
$$
\dfrac{\partial z_k^{(l+1)}}{\partial a_i^{(l)}} = \dfrac{\partial ( \sum_{j=0}^{s_l} \Theta_{k,j}^{(l+1)} a_j^{(l)})}{\partial a_i^{(l)}} = \Theta_{k,i}^{(l+1)}
$$
所以,递推式为:
$$
\begin{split}
\dfrac{\partial J(\Theta)}{\partial a_i^{(l)}}
=& \sum_{k=1}^{s_{l+1}} \Bigg[\dfrac{\partial J(\Theta)}{\partial a_k^{(l+1)}} \dfrac{\partial a_k^{(l+1)}}{\partial z_k^{(l+1)}} \dfrac{\partial z_k^{(l+1)}}{\partial a_i^{(l)}}\Bigg] \newline
=& \sum_{k=1}^{s_{l+1}} \Bigg[ \dfrac{\partial J(\Theta)}{\partial a_k^{(l+1)}} \dfrac{\partial a_k^{(l+1)}}{\partial z_k^{(l+1)}} \Theta_{k,i}^{(l+1)} \Bigg] \newline
=& \sum_{k=1}^{s_{l+1}} \Bigg[ \dfrac{\partial J(\Theta)}{\partial a_k^{(l+1)}} a_k^{(l+1)} (1 - a_k^{(l+1)}) \Theta_{k,i}^{(l+1)} \Bigg]
\end{split}
$$
为了简化表达式,定义第$l$
层第$i$
个节点的误差:
$$
\begin{split}
\delta^{(l)}_i
=& \dfrac{\partial J(\Theta)}{\partial a_i^{(l)}} \dfrac{\partial a_i^{(l)}}{\partial z_i^{(l)}} \newline
=& \dfrac{\partial J(\Theta)}{\partial a_i^{(l)}} a_i^{(l)} (1 - a_i^{(l)}) \newline
=& \sum_{k=1}^{s_{l+1}} \Bigg[ \dfrac{\partial J(\Theta)}{\partial a_k^{(l+1)}} \dfrac{\partial a_k^{(l+1)}}{\partial z_k^{(l+1)}} \Theta_{k,i}^{(l+1)} \Bigg] a_i^{(l)} (1 - a_i^{(l)}) \newline
=& \sum_{k=1}^{s_{l+1}} \Big[\delta^{(l+1)}_k \Theta_{k,i}^{(l+1)} \Big] a_i^{(l)} (1 - a_i^{(l)})
\end{split}
$$
可知,情况1的误差为:
$$
\begin{split}
\delta^{(L)}_i
=& \dfrac{\partial J(\Theta)}{\partial a_i^{(L)}} \dfrac{\partial a_i^{(L)}}{\partial z_i^{(L)}} \newline
=& \dfrac{a_i^{(L)} - y_i}{(1 - a_i^{(L)})a_i^{(L)}} a_i^{(L)} (1 - a_i^{(L)}) \newline
=& a_i^{(L)} - y_i
\end{split}
$$
则最终的代价函数的偏导为:
$$
\begin{split}
\dfrac{\partial}{\partial \Theta_{i,j}^{(l)}}J(\Theta)
=& \dfrac{\partial J(\Theta)}{\partial a_i^{(l)}} \dfrac{\partial a_i^{(l)}}{\partial z_i^{(l)}} \dfrac{\partial z_i^{(l)}}{\partial \Theta_{i,j}^{(l)}} \newline
=& \delta^{(l)}_i \dfrac{\partial z_i^{(l)}}{\partial \Theta_{i,j}^{(l)}} \newline
=& \delta^{(l)}_i a_j^{(l-1)}
\end{split}
$$
我们发现,引入误差$\delta^{(l)}_i$
后,这个公式可以通用于情况1和情况2。
可以看出,当前层的代价函数偏导,需要依赖于后一层的计算结果。这也是为什么这个算法的名称叫做“反向传播算法”。
总结算法公式
$$
\delta^{(L)}_i = a_i^{(L)} - y_i
$$
$$
\delta^{(l)}_i = \sum_{k=1}^{s_{l+1}} \Big[\delta^{(l+1)}_k \Theta_{k,i}^{(l+1)} \Big] a_i^{(l)} (1 - a_i^{(l)})
$$
$$
\dfrac{\partial}{\partial \Theta_{i,j}^{(l)}}J(\Theta) = \delta^{(l)}_i a_j^{(l-1)}
$$
算法过程
有了上述推导,我们描述一下算法具体的操作流程:
- 输入:输入样本数据,初始化权值参数(建议随机生成较小的数)。
- 前馈:计算各层(
$l=2, 3, ..., L$
)各节点的计算值($z^{(l)}=\Theta^{(l-1)}a^{(l-1)}$
)和激活值($a^{(l)}=g(z^{(l)})$
)。
- 输出层误差:计算输出层误差
$\delta^{(L)}$
(公式见前文)。
- 反向传播误差:计算各层(
$l=L-1, L-2, ..., 2$
)的误差$\delta^{(l)}$
(公式见前文)。
- 输出:得到代价函数的梯度
$\nabla J(\Theta)$
(参考前文偏导计算公式)。
反向传播算法帮助我们得到了代价函数的梯度,我们就可以借助梯度下降法训练神经网络了。
$$
\Theta := \Theta - \eta \nabla J(\Theta)
$$
$\eta$
为学习速率。
Octave/MATLAB代码
以3层神经网络(输入层、隐层、输出层各一)为例。
- X为大小为
$样本数 * 特征数$
的样本特征矩阵
- Y为大小为
$样本数 * 输出节点数$
的样本类别(结果)矩阵
- Theta1为
$输入层\rightarrow隐层$
的权值矩阵
- Theta2为
$隐层\rightarrow输出层$
的权值矩阵
- m为样本数
- K为输出层节点数
- H为隐层节点数
- sigmoid函数即逻辑函数(S型函数,Sigmoid函数)
- sigmoidGradient函数即Sigmoid函数的导函数
代码实现中,考虑了正规化,避免出现过拟合问题。
前馈阶段
逐层计算各节点值和激活值。
a1 = X;
z2 = [ones(m, 1), a1] * Theta1';
a2 = sigmoid(z2);
z3 = [ones(m, 1), a2] * Theta2';
a3 = sigmoid(z3);
代价函数
正规化部分需注意代价函数不惩罚偏移参数,即$\Theta_{i,0}$
(代码表示为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)
反向传播
输出层误差和$\Theta^{(2)}$
梯度计算,反向传播计算隐层误差和$\Theta^{(1)}$
梯度。
仍需注意正规化时排除偏移参数,另外注意为激活值补一个偏移量$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)]; # 正规化部分