机器学习之五:神经网络反向传播算法
Posted Fordestiny
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机器学习之五:神经网络反向传播算法相关的知识,希望对你有一定的参考价值。
一、逻辑回归的局限
在逻辑回归一节中,使用逻辑回归的多分类,实现了识别20*20的图片上的数字。
但所使用的是一个一阶的模型,并没有使用多项式,为什么?
可以设想一下,在原有400个特征的数据样本中,增加二次、三次、四次多项式,会是什么情形?
很显然,训练样本的特征数量将会拔高多个数量级,而且,更重要的,要在一个式子中拟合这么多的特征,其难度是非常大的,可能无法收敛到一个比较理想的状态。
也就是说,逻辑回归没法提供很复杂的模型。
因为其本质上是一个线性的分类器,擅长解决的是线性可分的问题。
那么非线性可分问题,要怎么解决?
解决思路
如果有一种方法,将非线性可分问题先进行特征提取,变为接近线性可分,那么再应用一次逻辑回归,是否就能解决非线性问题了?
这便是神经网络的思想。
二、神经网络
1、结构
神经网络的结构,如下图所示
上面是一个最简单的模型,分为三层:输入层、隐藏层、输出层。
其中,隐藏层可以是多层结构,通过扩展隐藏层的结构,可以构建更得杂的模型,例如下面的模型:
每一层的输出,皆是下一层的输出,层层连接而成,形成一个网络。
网络中的节点,称为神经元。每个神经元,其实就是进行一次类似逻辑回归的运算(之所以说是"类似",是因为可以使用逻辑回归,也有别的算法代替,但可以使用逻逻回归来理解它的运算机理)。
根据上面前言中的分析,显然,隐藏层是进行特征的提取,而输出层,其实就是进行逻辑回归。
为何说隐藏层是进行特征提取?
为方便理解,这里假设所有神经元执行逻辑回归。
一次逻辑回归,可以将平面一分为二。神经网络中,执行的是 N 多个逻辑回归,那么可以将平面切割为 N 多个区域,这些区域最后由输出层进行综合后做为结果。
如果只关注输出层,那么这些前面切割出来的区域,其实可以当作是一种特征,是一种更高级的特征,由原始样本提取出来的。这就是特征的提取。
2、计算原理
2.1 前向传播,计算输出
下面求解当一个样本从输入层输入时,如何得到最终结果。
假设每个神经元,都执行逻辑回归的计算,则第 \(i\) 层网络的输出为:\[a^{(i)} = g(z^{(i)}) = g(\Theta^Ta^{(i-1)}) \tag{1}\]
以如下三层网络为例:
各层的输入输出如下:
Input layer:
\[
a^{(1)} = x
\]
Hidden layer:
\[
\begin{split}
z^{(2)} &= \Theta^{(1)}a^{(1)} \a^{(2)} &= g(z^{(2)})
\end{split}
\]
Output layer:
\[ \begin{split} z^{(3)} &= \Theta^{(2)}a^{(2)} \a^{(3)} &= g(z^{(2)}) \end{split} \]
即整个网络的最终结果为:
\[ h_\theta(x) = a^{(3)} \]
上述流程:以上一层的输出,作为下一层的输入,一层一层叠加运算后,得到最终的输出,这个计算方法,称为“前向传播”
2.2 反向传播,求theta矩阵
训练算法的目的是“求取使得误差函数最小化的参数矩阵”,用梯度下降法处理最小化误差,需要计算误差函数J、以及J对theta的偏导数。
2.2.1 误差函数J
\[ J(\Theta) = -\frac{1}{m} \sum_{i=1}^{m}\sum_{k=1}^{K}[y_k^{(i)}log(h_\Theta(x^{(i)}))_k + (1-y_k^{(i)})log(1-h_\theta(x^{(i)}))_k] + \frac{\lambda}{2m}\sum_{l=1}^{L-1}\sum_{i=1}^{S_l}\sum_{j=1}^{S_l+1}(\Theta_{ji}^{(l)})^2 \tag{2} \]
其中 \(K\) 为输出层的单元数,即类别数。在计算误差的时候,需要将每一类都计算进去。后面的正则项是整个神经网络中所有的参数 \(\Theta\) 的值之和。
2.2.2 J对theta偏导数
这里先给结果,后面再做推导:
\[ \frac{\partial}{\partial\Theta_{ij}^{(l)}}J(\Theta) = \frac{1}{m}\sum_{t=1}^{m}\delta_i^{(t)(l+1)}a_j^{(t)(l)} + \frac{\lambda}{m}\sum_{l=1}^{L-1}\sum_{i=1}^{S_l}\sum_{j=1}^{S_l+1}(\Theta_{ji}^{(l)}) \tag{3} \]
其中
\[ \begin{cases} \delta^{(L)} &=& a^{(L)}-y \\\delta^{(l)} &=& \delta^{(l+1)}*(\Theta^{(l+1)})^T*g'(z^{(l)}) \\\delta^{(0)} &=& 0 \\end{cases} \tag{4} \]
上述公式描述的内容
第 \(l\) 层的误差,可以通过第 \(l+1\) 层的误差计算出来,而最后一层的误差,就是系统通过前向传播计算出的值与样本 \(Y\) 值的差。
也就是说,从输出层开始,各层误差能通过一层一层反向迭代的方式得到,确定误差之后,偏导数便也随之计算出来,进而可进行模型的调整。这就是,“反向传播算法”
而反向传播的内容,其实是误差。
关于误差的直观理解:
输出层的误差,即为系统的总误差;
中间层的误差,即为每一层对总误差的贡献值(所以,\(\theta\) 矩阵,在前向传播中,是特征权重,而在反向传播中,就是误差权重);
而输入层,其输出即为原始数据,即无误差。
2.2.3 反向传播算法的推导过程
(1) 第一部分,推导偏导数
上面给出了反向传播的结论,以下进行推导。
矩阵形式计算第 \(l\) 层的偏导数:
\[ \begin{split} \frac{\partial J(\Theta)}{\partial\Theta^{(l)}} &= \frac{\partial J(\Theta)}{\partial z^{(l+1)}} * \frac{\partial z^{(l+1)}}{\partial \Theta^{(l)}} \\&= \frac{\partial J(\Theta)}{\partial z^{(l+1)}} * \frac{\partial (\Theta^{(l)}*a^{(l)})}{\partial \Theta^{(l)}} \\&= \frac{\partial J(\Theta)}{\partial z^{(l+1)}} * a^{(l)} \end{split} \tag{5} \]
令 \[\delta^{(l)} = \frac{\partial J(\Theta)}{\partial z^{(l)}} \tag{6}\]
则有
\[ \begin{split} \frac{\partial J(\Theta)}{\partial\Theta^{(l)}} &=& \frac{\partial J(\Theta)}{\partial z^{(l+1)}} * a^{(l)} \\&=& \delta^{(l+1)} * a^{(l)} \\end{split} \tag{7} \]
(2) 第二部分,推导误差delta
上面推导过程中,有这个式子:
\[ \delta^{(l)} = \frac{\partial J(\Theta)}{\partial z^{(l)}} \]
表示了什么意思?下面分别从输出层、及中间层来推导、解释这个式子。
输出层
因误差函数如下(这里省略掉正则项)
\[ J(\Theta) = -\frac{1}{m} \sum_{i=1}^{m}\sum_{k=1}^{K}[y_k^{(i)}log(h_\Theta(x^{(i)}))_k + (1-y_k^{(i)})log(1-h_\theta(x^{(i)}))_k] \]
此式表达的是总误差,那么,对于输出层的每个神经元的误差,可用矩阵表示为:
\[ C = - [ylog(h_\Theta(x)) + (1-y)log(1-h_\theta(x))] \tag{8} \]
故输出层的误差为:
\[ \begin{split} \delta^{(L)} &=& \frac{\partial J(\Theta)}{\partial z^{(L)}} = \frac{\partial C}{\partial z^{(L)}} \\&=& \frac{\partial }{\partial z^{(L)}} [ylog(h_\Theta(x)) + (1-y)log(1-h_\theta(x))] \\&=& -\frac{y}{g(z^{(L)})}g'(z^{(L)}) - \frac{1-y}{1-g(z^{(L)})}(-g'(z^{(L)})) \\&=& \frac{g(z^{(L)})-y}{g(z^{(L)})(1-g(z^{(L)}))}(g'(z^{(L)})) \\&=& g(z^{(L)})-y \\&=& a^{(L)}-y \end{split} \tag{9} \]
这个结果,有点意思了,表示出输层的 \(\delta\) 值,就是系统输出值与样本 \(Y\) 值的差。所以,我们称 \(\delta\) 为神经系统各层结构的各个神经元的误差。
中间层误差推导
对于第 \(l\) 层
\[ \begin{split} \delta^{(l)} &=& \frac{\partial J(\Theta)}{\partial z^{(l)}} \\&=& \frac{\partial J(\Theta)}{\partial z^{(l+1)}} * \frac{\partial z^{(l+1)}}{\partial z^{(l)}} \\&=& \delta^{(l+1)} * \frac{\partial [(\Theta^{(l+1)})^T*g(z^{(l)})]}{\partial z^{(l)}} \\&=& \delta^{(l+1)} * (\Theta^{(l+1)})^T*g'(z^{(l)}) \\end{split} \tag{10}\]
即第 \(l\) 层的误差,能用第 \(l+1\) 层的误差计算得到,与先前所定的结论完全一致。
这就是反向传播的所有推导的内容。
三、程序实现
例子来源于,吴恩达的机器学习编程题。样本与逻辑回归中的多分类的数字识别相同。
1、计算损失函数、及梯度
function [J grad] = nnCostFunction(nn_params, ...
input_layer_size, ...
hidden_layer_size, ...
num_labels, ...
X, y, lambda)
Theta1 = reshape(nn_params(1:hidden_layer_size * (input_layer_size + 1)), ...
hidden_layer_size, (input_layer_size + 1));
Theta2 = reshape(nn_params((1 + (hidden_layer_size * (input_layer_size + 1))):end), ...
num_labels, (hidden_layer_size + 1));
% Setup some useful variables
m = size(X, 1);
% You need to return the following variables correctly
J = 0;
Theta1_grad = zeros(size(Theta1));
Theta2_grad = zeros(size(Theta2));
% ------ 前向传播计算输出 ------
% input layer
a1 = [ones(m, 1) X]; %add +1 to X;
% hidden layer
a2 = sigmoid(a1 * Theta1');
a2 = [ones(m, 1) a2];
% output layer
a3 = sigmoid(a2 * Theta2');
% ------ 样本的Y值 ------
% [1 0 0 0 0 0 0 0 0 0] -- the value is 1
% [0 1 0 0 0 0 0 0 0 0] -- the value is 2
Y = zeros(m,num_labels);
for i = 1 : m
Y(i,y(i)) = 1;
end
% ------ 损失函数J ------
J = (sum(sum(-Y .* log(a3))) - sum(sum((1-Y) .* log(1-a3)))) / m ;
% remove theta0
t1 = Theta1(:,2:end);
t2 = Theta2(:,2:end);
regularize = lambda / 2 / m * (sum(sum(t1.^2)) + sum(sum(t2.^2)));
J = J + regularize;
% ------ 反向传播计算各层误差 ------
delta3 = a3 - Y;
delta2 = delta3 * Theta2 .* a2 .* (1 - a2);
delta2 = delta2(:,2:end);
% ------ 计算梯度 ------
Theta1_grad = ( delta2' * a1 + [zeros(size(t1,1),1) t1] * lambda) / m;
Theta2_grad = ( delta3' * a2 + [zeros(size(t2,1),1) t2] * lambda) / m;
% Unroll gradients
grad = [Theta1_grad(:) ; Theta2_grad(:)];
end
2、前向传播及计算delta中,需要用到sigmoid函数及其导数
2.1 sigmoid函数
function g = sigmoid(z)
g = 1.0 ./ (1.0 + exp(-z));
end
2.2 sigmoid函数的导数
function g = sigmoidGradient(z)
g = sigmoid(z) .* (1 - sigmoid(z));
end
3、训练过程
3.1、随机初始化theta参数矩阵
initial_Theta1 = randInitializeWeights(input_layer_size, hidden_layer_size);
initial_Theta2 = randInitializeWeights(hidden_layer_size, num_labels);
% Unroll parameters
initial_nn_params = [initial_Theta1(:) ; initial_Theta2(:)];
逻辑回归中,theta矩阵可以初始化为同一个值,如全0或全1。但神经网络中却不行。
原因在于:神经网络中,神经元是以全连接的形式组织起来的,即n-1层的任意一个节点,都与第n层所有节点相连接。
若是初始化时theta矩阵初始化为同一个值,同一个层的每一个神经元都进行相同的运算,多个神经元进行相同的运算,这对于数据的拟合没有任何用处,只是浪费资源,造成冗余。此为对称现象。
随机初始化参数的实现如下:
function W = randInitializeWeights(L_in, L_out)
W = zeros(L_out, 1 + L_in);
epsilon_init = 0.12;
W = rand(L_out, 1 + L_in) * 2 * epsilon_init - epsilon_init;
end
3.2、初始化参数
options = optimset('MaxIter', 100);
% 正则项参数
lambda = 1;
% 损失函数
costFunction = @(p) nnCostFunction(p, ...
input_layer_size, ...
hidden_layer_size, ...
num_labels, X, y, lambda);
% 梯度下降计算参数
[nn_params, cost] = fmincg(costFunction, initial_nn_params, options);
% 获取两层神经网络的参数
Theta1 = reshape(nn_params(1:hidden_layer_size * (input_layer_size + 1)), ...
hidden_layer_size, (input_layer_size + 1));
Theta2 = reshape(nn_params((1 + (hidden_layer_size * (input_layer_size + 1))):end), ...
num_labels, (hidden_layer_size + 1));
4、预测
pred = predict(Theta1, Theta2, X);
fprintf('\nTraining Set Accuracy: %f\n', mean(double(pred == y)) * 100);
可以看到,其预测结果,比逻辑回归准确率高接近3个点。
原因在于:神经网络所能构建的模型,比逻辑回归更为复杂,其对数据的拟合能力也更强。
predict函数,使用训练得到的参数矩阵,前向传播计算得到结果即为输出层,输出层表示一个输入样本经过经神网络计算之后,其可能属于各个分类的概率值。与逻辑回归类似,取最大值即为最终的结果。
function p = predict(Theta1, Theta2, X)
m = size(X, 1);
num_labels = size(Theta2, 1);
p = zeros(size(X, 1), 1);
h1 = sigmoid([ones(m, 1) X] * Theta1');
h2 = sigmoid([ones(m, 1) h1] * Theta2');
[dummy, p] = max(h2, [], 2);
end
以上是关于机器学习之五:神经网络反向传播算法的主要内容,如果未能解决你的问题,请参考以下文章