Week 5主要讲了神经网络的反向传播算法(Backpropagation Algorithm)。如上一节所讲,前馈传播算法是根据输入层向量x,各层权重矩阵 Θ \Theta Θ ,以及激活函数逐层从左向右计算,最终得到输出层结果。反向传播算法则是为了找出使损失函数 J ( Θ ) J(\Theta) J(Θ)最小化的最佳 Θ \Theta Θ ,BP为优化函数(如梯度下降、其它高级优化算法)提供梯度值,通过从右向左来逐层求偏导,得到每层的参数梯度,用 δ ( l ) δ^{(l)} δ(l)来保存每一层求偏导的部分结果,以便后面可以接着借用这部分结果来求下一层的结果,用 D i , j ( l ) D_{i, j}^{(l)} Di,j(l)来保存偏导结果。
选择神经网络的结构。
训练神经网络的时候,需要先随机化权重
Θ
\Theta
Θ,不能初始化为0,不然BP的时候所有节点会重复地更新为同一个值。还要注意用gradient checking来保证梯度下降算法或其它高级算法的正确性,因为算法的复杂性,有时候一些bug会导致表面上梯度是下降的,但是结果会有很大误差。
Cost Function
正则化逻辑回归的损失函数为
神经网络的损失函数为
其中L为神经网络的总层数,
s
l
s_l
sl为第l层不包括bias unit的单元数量,K为输出层的units/classes数量。
- 前一项的两个求和表示计算输出层每个单元的逻辑回归损失之和
- 后一项的三个求和表示计算整个神经网络中的 Θ \Theta Θ的各项平方之和。在第l层中 s l + 1 s_{l+1} sl+1是当前 Θ \Theta Θ的行数(不包括bias unit), s l s_l sl是当前 Θ \Theta Θ的列数(包括bias unit)。
Backpropagation Algorithm
反向传播算法是为了得到
m
i
n
Θ
J
(
Θ
)
min_{\Theta}J(\Theta)
minΘJ(Θ)
所以我们用损失函数对
Θ
\Theta
Θ求偏导(partial derivative)
∂
∂
Θ
i
,
j
(
l
)
J
(
Θ
)
\frac{∂}{∂\Theta_{i,j}^{(l)}}J(\Theta)
∂Θi,j(l)∂J(Θ)
考虑只有一个分类(K=1)的简单情况,
J
(
Θ
)
J(\Theta)
J(Θ)可以简化为
J
(
Θ
)
=
c
o
s
t
(
t
)
=
y
(
t
)
l
o
g
(
h
Θ
(
x
(
t
)
)
)
+
(
1
−
y
(
t
)
)
l
o
g
(
1
−
h
Θ
(
x
(
t
)
)
)
J(\Theta) = cost(t) = y^{(t)}log(h_\Theta(x^{(t)})) + (1-y^{(t)})log(1-h_\Theta(x^{(t)}))
J(Θ)=cost(t)=y(t)log(hΘ(x(t)))+(1−y(t))log(1−hΘ(x(t)))
δ
j
(
l
)
δ_j^{(l)}
δj(l)可以看做是
a
j
(
l
)
a_j^{(l)}
aj(l)处的误差"error",具体推导可见link,定义
δ
(
l
)
δ^{(l)}
δ(l)为
δ
j
(
l
)
=
∂
∂
z
j
(
l
)
J
(
Θ
)
δ_j^{(l)} = \frac{∂}{∂z_j^{(l)}}J(\Theta)
δj(l)=∂zj(l)∂J(Θ)
δ
(
l
)
δ^{(l)}
δ(l)的大小与对应那一层的
a
(
l
)
a^{(l)}
a(l)的大小一样,
Δ
(
l
)
Δ^{(l)}
Δ(l)的大小与对应那一层的
Θ
(
l
)
\Theta^{(l)}
Θ(l)的大小一样,通过
Δ
(
l
)
Δ^{(l)}
Δ(l)来更新
Θ
(
l
)
\Theta^{(l)}
Θ(l),找到使损失函数最小的
Θ
\Theta
Θ。BP算法的计算过程:
训练集{(
x
(
1
)
x^{(1)}
x(1),
y
(
1
)
y^{(1)}
y(1))…(
x
(
m
)
x^{(m)}
x(m),
y
(
m
)
y^{(m)}
y(m))},初始化
Δ
i
,
j
(
l
)
:
=
0
Δ_{i,j}^{(l)}:=0
Δi,j(l):=0
对训练样本t = 1 to m:
- Set a ( 1 ) : = x ( t ) a^{(1)} := x^{(t)} a(1):=x(t)
- 使用前馈算法计算后面每层的结果 a ( l ) a^{(l)} a(l)
- 计算
δ
(
l
)
δ^{(l)}
δ(l):
δ ( L ) = a ( L ) − y ( t ) δ^{(L)} = a^{(L)} - y^{(t)} δ(L)=a(L)−y(t), l = L l = L l=L
δ ( l ) = ( ( Θ ( l ) ) T δ ( l + 1 ) . ∗ a ( l ) . ∗ ( 1 − a ( l ) ) δ^{(l)} = ((\Theta^{(l)})^Tδ^{(l + 1)} .* a^{(l)} .* (1 - a^{(l)}) δ(l)=((Θ(l))Tδ(l+1).∗a(l).∗(1−a(l)), l = L − 1 , L − 2 , . . . , 2 l = L - 1, L - 2, ..., 2 l=L−1,L−2,...,2 - 计算
Δ
i
,
j
(
l
)
Δ_{i,j}^{(l)}
Δi,j(l):
Δ i , j ( l ) : = Δ i , j ( l ) + a j ( l ) δ ( l + 1 ) Δ_{i,j}^{(l)}:=Δ_{i,j}^{(l)} + a_j^{(l)}δ^{(l + 1)} Δi,j(l):=Δi,j(l)+aj(l)δ(l+1)
vectorization Δ ( l ) : = Δ ( l ) + δ ( l + 1 ) ( a j ( l ) ) T Δ^{(l)}:=Δ^{(l)} + δ^{(l + 1)}(a_j^{(l)})^T Δ(l):=Δ(l)+δ(l+1)(aj(l))T - 计算
D
i
,
j
(
l
)
D_{i,j}^{(l)}
Di,j(l):
D i , j ( l ) : = 1 m ( Δ i , j ( l ) + λ Θ i , j ( l ) ) D_{i,j}^{(l)}:=\frac{1}{m}(Δ_{i,j}^{(l)} + λ\Theta_{i,j}^{(l)}) Di,j(l):=m1(Δi,j(l)+λΘi,j(l)), j ≠ 0 j ≠ 0 j̸=0
D i , j ( l ) : = 1 m Δ i , j ( l ) D_{i,j}^{(l)}:=\frac{1}{m}Δ_{i,j}^{(l)} Di,j(l):=m1Δi,j(l), j = 0 j = 0 j=0
Unrolling Parameters
为了能使用优化函数,如fminunc(),需要将unroll所有元素将它们放进一个vector中。如Theta1是个25x401的矩阵,Theta2是个10x26的矩阵,unroll后是一个10285x1的vector。
thetaVector = [ Theta1(:); Theta2(:); Theta3(:); ]
deltaVector = [ D1(:); D2(:); D3(:) ]
// if the dimensions of Theta1 is 10x11, Theta2 is 10x11 and Theta3 is 1x11
Theta1 = reshape(thetaVector(1:110),10,11)
Theta2 = reshape(thetaVector(111:220),10,11)
Theta3 = reshape(thetaVector(221:231),1,11)
Gradient Checking
Gradient checking可以保证算法的正确性,通过检查gradApprox ≈ deltaVector来确认算法是否正确。一旦确认BP算法的正确性,就不需要继续计算gradApprox了,因为gradient checking远比BP耗时。
epsilon = 1e-4;
for i = 1:n,
thetaPlus = theta;
thetaPlus(i) += epsilon;
thetaMinus = theta;
thetaMinus(i) -= epsilon;
gradApprox(i) = (J(thetaPlus) - J(thetaMinus))/(2*epsilon)
end;
Random Initialization
将每个
Θ
i
,
j
(
l
)
\Theta_{i,j}^{(l)}
Θi,j(l)初始化为
[
−
ϵ
,
ϵ
]
[−ϵ,ϵ]
[−ϵ,ϵ]之间的随机值,此处代码中的epsilon与gradient checking中的epsilon无关。
// If the dimensions of Theta1 is 10x11, Theta2 is 10x11 and Theta3 is 1x11.
// rand(x,y) is just a function in octave that will initialize a matrix of random real numbers between 0 and 1.
Theta1 = rand(10,11) * (2 * INIT_EPSILON) - INIT_EPSILON;
Theta2 = rand(10,11) * (2 * INIT_EPSILON) - INIT_EPSILON;
Theta3 = rand(1,11) * (2 * INIT_EPSILON) - INIT_EPSILON;
Exercise 4: 实现反向传播的神经网络 - Matlab
用反向传播算法实现神经网络,来识别手写数字。
1. Neural Networks
构建的神经网络结构与ex3中一致,3层神经网络,输入层特征数量为400(不包括bias unit),输出层类别数量为10,隐藏层单元数为25。注意unroll parameters。
1.1 Feedforward and Cost Function
非正则化的神经网络损失函数为
在nnCostFunction.m文件中,实现非正则化的损失函数。因为没有正则化,lambda设为0。对ex3中给出的Theta1和Theta2,我们可以测试这个损失函数的正确性。eye(num_labels)(:,y)的解释参考link。sum(x)表示列求和,sum(x,2)表示行求和,sum(x(
:
:
:))表示矩阵求和。
% feedforward propagation
X = [ones(m, 1), X]; % 5000x401
a_super_2 = sigmoid(Theta1 * X'); % 25x5000=(25x401)*(401x5000)
a_super_2 = [ones(1, m); a_super_2]; % 26x5000
a_super_3 = sigmoid(Theta2 * a_super_2); % 10x5000=(10x26)*(26*5000)
% non-regularized cost function
I = eye(num_labels); % 10x10
y = I(:,y); % y:5000x1 -> 10x5000
tempJ = (y .* log(a_super_3) - (y - 1) .* log(1 - a_super_3)) / (-m);
J = sum(tempJ(:));
1.2 Regularized Cost Function
正则化的神经网络损失函数为
在nnCostFunction.m文件中,基于上面非正则化的损失函数基础上,加上正则化的项,即可得到正则化的损失函数。
% regularized cost function
tempTheta1 = Theta1.^2;
tempTheta1 = tempTheta1(:,2:end);
tempTheta2 = Theta2.^2;
tempTheta2 = tempTheta2(:,2:end);
J = J + lambda / (2 * m) * (sum(tempTheta1(:)) + sum(tempTheta2(:)));
2. Backpropagation
2.1 Sigmoid Gradient
sigmoid函数求导有如下特征,在sigmoidGradient.m文件中完成对sigmoid的求导。
function g = sigmoidGradient(z)
%SIGMOIDGRADIENT returns the gradient of the sigmoid function
%evaluated at z
% g = SIGMOIDGRADIENT(z) computes the gradient of the sigmoid function
% evaluated at z. This should work regardless if z is a matrix or a
% vector. In particular, if z is a vector or matrix, you should return
% the gradient for each element.
g = zeros(size(z));
% ====================== YOUR CODE HERE ======================
% Instructions: Compute the gradient of the sigmoid function evaluated at
% each value of z (z can be a matrix, vector or scalar).
g = sigmoid(z) .* (1 - sigmoid(z));
% =============================================================
end
2.2 Random Initialization
在randInitializeWeights.m文件中,完成对参数的随机初始化。
function W = randInitializeWeights(L_in, L_out)
%RANDINITIALIZEWEIGHTS Randomly initialize the weights of a layer with L_in
%incoming connections and L_out outgoing connections
% W = RANDINITIALIZEWEIGHTS(L_in, L_out) randomly initializes the weights
% of a layer with L_in incoming connections and L_out outgoing
% connections.
%
% Note that W should be set to a matrix of size(L_out, 1 + L_in) as
% the first column of W handles the "bias" terms
%
% You need to return the following variables correctly
W = zeros(L_out, 1 + L_in);
% ====================== YOUR CODE HERE ======================
% Instructions: Initialize W randomly so that we break the symmetry while
% training the neural network.
%
% Note: The first column of W corresponds to the parameters for the bias unit
%
epsilon_init = 0.12;
W = rand(L_out, 1 + L_in) * 2 * epsilon_init - epsilon_init;
% =========================================================================
end
2.3 Backpropagation
BP需要从右到左计算输出层和隐藏层的"error term"
δ
j
(
l
)
δ_j^{(l)}
δj(l),计算方式如下图所示。一共有m=5000个样本,对每个样本,都要进行1-4的操作,最后将得到的gradients的和除以m就是整个神经网络损失函数的gradients。
δ
(
l
)
δ^{(l)}
δ(l)的大小与对应那一层的
a
(
l
)
a^{(l)}
a(l)的大小一样,
Δ
(
l
)
Δ^{(l)}
Δ(l)的大小与对应那一层的
Θ
(
l
)
\Theta^{(l)}
Θ(l)的大小一样。
delta_super_3 = a_super_3 - y; % 10x5000
delta_super_2 = Theta2(:,2:end)' * delta_super_3 .* sigmoidGradient(Theta1 * X'); % 25x5000
Delta_super_2 = delta_super_3 * a_super_2'; % 10x26=(10x5000)*(5000x26)
Delta_super_1 = delta_super_2 * X; % 25x401=(25x5000)*(5000x401)
% gradient for non-regularized BP
Theta1_grad = Delta_super_1 / m;
Theta2_grad = Delta_super_2 / m;
2.4 Gradient checking
Gradient checking可以用于各种算法的检验。此处不需要自己填写代码,比较numerical gradient和我们得到的gradient是否在一个很小的误差内,此处的 ϵ = 1 0 − 4 ϵ = 10^{-4} ϵ=10−4。在确认了算法正确性后,训练参数前记得关闭gradient checking。
2.5 Regularized Neural Network
在2.3的基础上,给gradient增加上正则化项,就可以得到正则化的gradient,注意不能正则化
Θ
(
l
)
\Theta^{(l)}
Θ(l)的第一列,因为那部分是用于bias unit的,也就是说
Θ
i
,
j
(
l
)
\Theta_{i,j}^{(l)}
Θi,j(l)中,我们需要用到的部分i从1开始,j从0开始。在nnCostFunction.m文件中加上正则化后的gradients。
% regularized gradients
Theta1(:,1) = 0;
Theta2(:,1) = 0;
Theta1_grad = Theta1_grad + lambda / m * Theta1;
Theta2_grad = Theta2_grad + lambda / m * Theta2;
在nnCostFunction.m文件中,我们间断地补充了几次代码,主要进行了三步操作,完整的代码如下。
function [J grad] = nnCostFunction(nn_params, ...
input_layer_size, ...
hidden_layer_size, ...
num_labels, ...
X, y, lambda)
%NNCOSTFUNCTION Implements the neural network cost function for a two layer
%neural network which performs classification
% [J grad] = NNCOSTFUNCTON(nn_params, hidden_layer_size, num_labels, ...
% X, y, lambda) computes the cost and gradient of the neural network. The
% parameters for the neural network are "unrolled" into the vector
% nn_params and need to be converted back into the weight matrices.
%
% The returned parameter grad should be a "unrolled" vector of the
% partial derivatives of the neural network.
%
% Reshape nn_params back into the parameters Theta1 and Theta2, the weight matrices
% for our 2 layer neural network
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));
% ====================== YOUR CODE HERE ======================
% Instructions: You should complete the code by working through the
% following parts.
%
% Part 1: Feedforward the neural network and return the cost in the
% variable J. After implementing Part 1, you can verify that your
% cost function computation is correct by verifying the cost
% computed in ex4.m
% feedforward propagation
X = [ones(m, 1), X]; % 5000x401
a_super_2 = sigmoid(Theta1 * X'); % 25x5000=(25x401)*(401x5000)
a_super_2 = [ones(1, m); a_super_2]; % 26x5000
a_super_3 = sigmoid(Theta2 * a_super_2); % 10x5000=(10x26)*(26*5000)
% non-regularized cost function
I = eye(num_labels); % 10x10
y = I(:,y); % y:5000x1 -> 10x5000
tempJ = (y .* log(a_super_3) - (y - 1) .* log(1 - a_super_3)) / (-m);
J = sum(tempJ(:));
% regularized cost function
tempTheta1 = Theta1.^2;
tempTheta1 = tempTheta1(:,2:end);
tempTheta2 = Theta2.^2;
tempTheta2 = tempTheta2(:,2:end);
J = J + lambda / (2 * m) * (sum(tempTheta1(:)) + sum(tempTheta2(:)));
% Part 2: Implement the backpropagation algorithm to compute the gradients
% Theta1_grad and Theta2_grad. You should return the partial derivatives of
% the cost function with respect to Theta1 and Theta2 in Theta1_grad and
% Theta2_grad, respectively. After implementing Part 2, you can check
% that your implementation is correct by running checkNNGradients
%
% Note: The vector y passed into the function is a vector of labels
% containing values from 1..K. You need to map this vector into a
% binary vector of 1's and 0's to be used with the neural network
% cost function.
%
% Hint: We recommend implementing backpropagation using a for-loop
% over the training examples if you are implementing it for the
% first time.
delta_super_3 = a_super_3 - y; % 10x5000
delta_super_2 = Theta2(:,2:end)' * delta_super_3 .* sigmoidGradient(Theta1 * X'); % 25x5000
Delta_super_2 = delta_super_3 * a_super_2'; % 10x26=(10x5000)*(5000x26)
Delta_super_1 = delta_super_2 * X; % 25x401=(25x5000)*(5000x401)
% gradient for non-regularized BP
Theta1_grad = Delta_super_1 / m;
Theta2_grad = Delta_super_2 / m;
% Part 3: Implement regularization with the cost function and gradients.
%
% Hint: You can implement this around the code for
% backpropagation. That is, you can compute the gradients for
% the regularization separately and then add them to Theta1_grad
% and Theta2_grad from Part 2.
%
% regularized gradients
Theta1(:,1) = 0;
Theta2(:,1) = 0;
Theta1_grad = Theta1_grad + lambda / m * Theta1;
Theta2_grad = Theta2_grad + lambda / m * Theta2;
% -------------------------------------------------------------
% =========================================================================
% Unroll gradients
grad = [Theta1_grad(:) ; Theta2_grad(:)];
end
2.6 Learning Parameters Using fmincg
用fmincg训练参数,注意参数是unroll了的。
options = optimset('MaxIter', 50);
lambda = 1;
% Create "short hand" for the cost function to be minimized
costFunction = @(p) nnCostFunction(p, input_layer_size, hidden_layer_size, num_labels, X, y, lambda);
% Now, costFunction is a function that takes in only one argument (the
% neural network parameters)
[nn_params, ~] = fmincg(costFunction, initial_nn_params, options);
% Obtain Theta1 and Theta2 back from nn_params
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));
pred = predict(Theta1, Theta2, X);
fprintf('\nTraining Set Accuracy: %f\n', mean(double(pred == y)) * 100);
3. Visualizing the Hidden Layer
我们可以通过可视化隐藏层来看神经网络学习的是什么东西。
Ex4全部代码已上传Github。