在上一个练习中,您实现了神经网络的前馈传播,并使用它来预测我们提供的权重手写数字。在本练习中,您将实现反向传播算法来学习神经网络的参数。
复制ex4data1文件到D:\Machine Learning\ex4目录下,在当前目录下建立displayData.m文件,用来可视化数据。具体实现参照前一次作业的函数实现。
Octave中的操作:
我们已经为您提供了一套我们已经培训过的网络参数(Θ(1),Θ(2)),这些存储在ex4weights文件中,将该文件复制到当前目录下。
Octave中的操作:
将该数据集加载,将Theta1和Theta2矩阵上下结合成为一个新矩阵。
先编写前向传播的成本函数,在当前目录下建立nnCostFunction文件,该函数中也实现了后向传播函数的相关算法,并返回相应的梯度:
function [J grad] = nnCostFunction(nn_params, ...
input_layer_size, ...
hidden_layer_size, ...
num_labels, ...
X, y, lambda)
%返回grad 和J
%第一个参数就是合并的矩阵,第二个参数值为400,第三个参数值为25,第四个参数值为10,最后一个代表λ
Theta1 = reshape(nn_params(1:hidden_layer_size * (input_layer_size + 1)), ...
hidden_layer_size, (input_layer_size + 1));
%将Theta1矩阵还原成20*401
Theta2 = reshape(nn_params((1 + (hidden_layer_size * (input_layer_size + 1))):end), ...
num_labels, (hidden_layer_size + 1));
%将Theta1矩阵还原成10*26
m = size(X, 1);
J = 0;
Theta1_grad = zeros(size(Theta1));
Theta2_grad = zeros(size(Theta2));
delta_2 = zeros(hidden_layer_size, 1);
%25*1
delta_3 = zeros(num_labels, 1);
%10*1
delta_1_sum = zeros(size(Theta1));
%25*1
delta_2_sum = zeros(size(Theta2));
%10*1
X = [ones(m,1) X];
%加上常数项
for i = 1: m,
H = sigmoid(Theta2 * [ones(1,1) ; sigmoid(Theta1 * X(i, :)')]);
%运用前向传播算法,算出一个计算后的y值,即第i行y值,这里共5000行
yi = zeros(num_labels, 1);
%10*1
yi(y(i)) = 1;
%原始标签(变量y中)是1,2,…,10,为了训练神经网络,我们需要将标签重新编码为只包含值0或1的向量。
%如果y(i)是数字5,那么对应的yi应该是一个10维向量,y5=1,其他元素等于0。
J_i = sum(log(H) .* ( -yi) - (1 - yi) .* log(1 - H));
J = J + J_i;
delta_3 = H - yi;
delta_2 = (Theta2(:,2:end)' * delta_3) .* sigmoidGradient(Theta1 * X(i, :)');
%δ矩阵
delta_1_sum = delta_1_sum + delta_2 * X(i, :);
delta_2_sum = delta_2_sum + delta_3 * [ones(1,1) ; sigmoid(Theta1 * X(i, :)')]';
%Theta2(:,2:end)'矩阵去掉了常数列,因此得到的只有25行,求得∆(l)
end;
J = J / m;
%乘以系数m分之一,最终得到非正规化的成本函数值
Theta1_grad = delta_1_sum ./ m + lambda * Theta1 ./ m;
Theta1_grad(:,1) = delta_1_sum(:,1) ./ m;
Theta2_grad = delta_2_sum ./ m + lambda * Theta2 ./ m;
Theta2_grad(:,1) = delta_2_sum(:,1) ./ m;
%求得两个Theta矩阵对应的梯度矩阵
Theta1_i = Theta1(:,2:(input_layer_size + 1));
Theta1_i = Theta1_i .^ 2;
Theta2_i = Theta2(:,2:(hidden_layer_size + 1));
Theta2_i = Theta2_i .^ 2;
J = J + lambda / 2 / m * (sum(sum(Theta1_i)) + sum(sum(Theta2_i)));
%求得正规化的成本函数值
grad = [Theta1_grad(:) ; Theta2_grad(:)];
end
function g = sigmoidGradient(z)
g = (1 ./ (exp(-z) + 1)) .* (1 - 1 ./ (exp(-z) + 1));
end
在当前目录下建立randInitializeWeights.m文件,以初始化Θ的权重:
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;
%随机初始一个尺寸为 L_out×1 + L_in 的参数矩阵,我们通常初始epsilon_init为正负之间的随机值
end
近似于该点处代价函数的导数:
在当前目录下建立checkNNGradients.m文件,完成坡度检查工作,主要根据相对差异的大小来判断检查nnCostFunction中反向传播算法是否正确。在该文件中需要调用debugInitializeWeights函数和computeNumericalGradient函数,首先完成这两个函数的实现。
在当前目录下建立debugInitializeWeights.m文件,它将创建一个小的神经网络和数据集,用于检查渐变。如果您的反向传播实现是正确的,您应该看到一个小于1e-9的相对差异:
function W = debugInitializeWeights(fan_out, fan_in)
%随机产生一个数据矩阵fan_out*fan_in
W = zeros(fan_out, 1 + fan_in);
W = reshape(sin(1:numel(W)), size(W)) / 10;
%矩阵元素值很小,值为sinx/10,numel(W)计算矩阵元素个数。
end
在当前目录下建立computeNumericalGradient.m文件,它主要用来实现上面的两个数学表达式,计算出公式产生的梯度;
function numgrad = computeNumericalGradient(J, theta)
%返回numel(theta)*1的矩阵,每一个元素对应相应Θ的相对差异的大小
numgrad = zeros(size(theta));
perturb = zeros(size(theta));
e = 1e-4;
for p = 1:numel(theta)
perturb(p) = e;
loss1 = J(theta - perturb);
loss2 = J(theta + perturb);
numgrad(p) = (loss2 - loss1) / (2*e);
perturb(p) = 0;
end
end
checkNNGradients.m文件:
function checkNNGradients(lambda)
%参数就是λ
if ~exist('lambda', 'var') || isempty(lambda)
lambda = 0;
end
%如果没有设置λ,就设置它为0
input_layer_size = 3;
hidden_layer_size = 5;
num_labels = 3;
m = 5;
Theta1 = debugInitializeWeights(hidden_layer_size, input_layer_size);
Theta2 = debugInitializeWeights(num_labels, hidden_layer_size);
X = debugInitializeWeights(m, input_layer_size - 1);
%debugInitializeWeights主要作用就是随机产生一些数据来组成相应的矩阵,用于检查渐变
y = 1 + mod(1:m, num_labels)';
nn_params = [Theta1(:) ; Theta2(:)];
costFunc = @(p) nnCostFunction(p, input_layer_size, hidden_layer_size, ...
num_labels, X, y, lambda);
[cost, grad] = costFunc(nn_params);
numgrad = computeNumericalGradient(costFunc, nn_params);
%产生数字梯度的矩阵,由公式计算出来的
disp([numgrad grad]);
%输出二者组合形成的矩阵,来看公式计算出来的梯度和nnCostFunction计算出来的梯度有什么不同
%应该是近乎等于
diff = norm(numgrad-grad)/norm(numgrad+grad);
end
Octave中的操作:
可以看到用公式计算出的梯度与反向传播算法计算出的梯度基本一致,说明反向传播算法正确。
使用fmincg学习一个好的设置参数。如果您的实现是正确的,您应该看到报告的训练准确率约为95.3%(由于随机初始化,这可能会有大约1%的变化)。通过对神经网络进行多次迭代训练,可以获得更高的训练精度。我们鼓励您尝试训练神经网络进行更多迭代(例如,将MaxIter设置为400),并改变正则化参数λ。有了正确的学习设置,就有可能使神经网络完全适合训练集。
同时还可以可视化隐藏层:displayData(Theta1(:, 2:end));
由于用到预测函数,在当前目录下建立predict.m文件和fmincg.m文件