求解神经网络模型参数的大致过程
神经网络代价函数
首先我们回顾一下神经基本结构:
假设神经网络的训练样本有 m 个,每个包含一组输入 x 和一组输出信号 y(在神经网格中,有时候将输出表示为h
Θ
\Theta
Θ,都是表示输出),L 表示神经网络层数,Sl 表示每层的 neuron 个数(SL 表示输出层神经元个数), SL 代表最后一层输出单元的个数。(大写L表示一共有几层,小写l表示l=1~L)
将神经网络的分类定义为两种情况:二类分类和多类分类,
二类分类:SL= 0,y=0 or 1表示哪一类;
K 类分类:SL= K,yi表示分到第 i 类;(K > 2)
我们之前对于逻辑回归函数来说,求解参数要先写出他的代价函数,通过代价函数来求解最小值,逻辑回归代价函数:
同理,对于神经网络来说,他也有代价函数:
因为对于神经网络来说,他的输出变量会有很多,是个维度为K的变量,所以代价函数会更复杂一点。但是跟逻辑回归函数还是一样的,我们希望通过代价函数来观察算法预测的结果与真实情况的误差有多大,唯一不同的是,对于每一行特征,我们都会给出K 个预测,基本上我们可以利用循环,对每一行特征都预测 K 个不同结果,然后在利用循环在 K 个预测中选择可能性最高的一个,将其与 y 中的实际数据进行比较。
正则化的那一项只是排除了每一层参数的
θ
\theta
θ0之后,每一层的
θ
\theta
θ矩阵的和。
反向传播
跟逻辑回归一样,需要求解代价函数的最小值,我们需要求代价函数的梯度:
为了计算代价函数的偏导数 ,我们采用一种反向传播算法,也
就是首先计算最后一层的误差,然后再一层一层反向求出各层的误差,直到倒数第二层。
以一个例子来说明反向传播算法:
这是一个4层的神经网络:
假设训练集只有一个实例(x,y),对于该神经网络,对于输入x,可以根据前向传播得到最后的输出:
我们从最后一层的误差开始算,
δ
j
l
\delta_j^l
δjl表示第l层第j个结点的误差。(由于假设只有一个实例,所以下标j可忽略)
Sigmoid函数有一个很好的性质,即
f
′
(
x
)
=
f
(
x
)
(
1
−
f
(
x
)
)
f'(x)=f(x)(1-f(x))
f′(x)=f(x)(1−f(x)),所以上式中
g
′
(
z
)
=
g
(
z
)
(
1
−
g
(
z
)
)
g'(z)=g(z)(1-g(z))
g′(z)=g(z)(1−g(z))
因为第一层是输入变量,不存在误差。我们有了所有的误差的表达式后,便可以计算代价函数的偏导数了,可由证明得代价函数的偏导数可表示为:(不考虑正则化的情况下)
l 代表目前所计算的是第几层
j 代表目前计算层中的激活单元的下标,也将是下一层的第 j 个输入变量的下标。
i 代表下一层中误差单元的下标,是受到权重矩阵中第 i 行影响的下一层中的误差单元的下标。
如果我们考虑正则化处理,并且我们的训练集是一个特征矩阵而非向量。在上面的特殊情况中,我们需要计算每一层的误差单元来计算代价函数的偏导数。在更为一般的情况中,我们同样需要计算每一层的误差单元,但是我们需要为整个训练集计算误差单元,此时的误差单元也是一个矩阵,我们用
Δ
i
l
j
\Delta^l_ij
Δilj来表示这个误差矩阵。第 l 层的第 i 个激活单元受到第 j个参数影响而导致的误差。
我们的算法步骤如下:
即首先用正向传播方法计算出每一层的激活单元,利用训练集的结果与神经网络预测的结果求出最后一层的误差,然后利用该误差运用反向传播法计算出直至第二层的所有误差。
接着就可以利用这个结果求解出神经网络的代价函数的梯度:
参数求解
在 matlab 中,如果我们要使用 fminunc函数 这样的优化算法来根据我们求出的梯度从而求解出权重矩阵。
在上面的函数进行求解
Θ
\Theta
Θ,传递的参数为向量,而我们上次的计算过程中,传递的梯度和
θ
\theta
θ都是矩阵形式,所以我们需要将矩阵转化为向量传递,求解完再向量转化为矩阵。以下面一个例子解释向量和矩阵的转化:
向量和矩阵进行转化在其他场合也有十分重要的作用。
梯度检验
当我们对一个较为复杂的模型使用反向传播算法时,虽然求解效果好,但是由于该算法具有一定的复杂度,所以过程中难免会有一些疏忽,或者由于其他的原因可能会存在一些不容易察觉的错误,意味着,虽然代价看上去在不断减小,但最终的结果可能并不是最优解。为了避免这样的问题,我们采取一种叫做梯度的数值检验(Numerical Gradient Checking)方法。这种方法的思想是通过估计梯度值来检验我们计算的导数值是否真的是我们要求的。
对梯度的估计采用的方法是在代价函数上沿着切线的方向选择离两个非常近的点然后计算两个点的平均值用以估计梯度。即对于某个特定的
θ
\theta
θ,我们计算出在
θ
\theta
θ-
ϵ
\epsilon
ϵ 处和
θ
\theta
θ+
ϵ
\epsilon
ϵ 的代价值(
ϵ
\epsilon
ϵ 是一个非常小的值,通常选取 0.001),然后求两个代价的平均,用以估计在
θ
\theta
θ处的代价值。
当
θ
\theta
θ是一个向量时:
将求出来的梯度估计值与反向传播计算出来的梯度进行比较:
根据上面的反向传播算法,计算出的偏导数存储在矩阵
Δ
i
l
j
\Delta^l _i j
Δilj 中。检验时,我们要将
θ
\theta
θ矩阵展开成为向量,同时我们针对每一个
θ
\theta
θ都计算一个近似的梯度值,将这些值存储于一个近似梯度矩阵中,最终将得出的这个矩阵同
Δ
i
l
j
\Delta^l _i j
Δilj 进行比较。当两者接近的时候说明我们的反向传播算法计算过程是正确的,就可以将计算出来的梯度结果运用到其他地方开始接下来的工作。
注:当进行梯度检验完之后,一定要关掉梯度检验算法,因为该算法占据资源,运行较慢,如果在后面的每次迭代中都运行一次梯度检查,那么将会十分占据资源影响效率。所以一定要确保梯度检验完,开始训练分类器开始之前禁用梯度检查算法。
随机初始化
我们上面提到的优化算法fminunc/fming算法,在求解参数的时候都需要初始化参数,到目前为止我们都是初始所有参数为 0,这样的初始方法对于逻辑回归来说是可行的。
但是对于神经网络来说是不可行的。如果我们令所有的初始参数都为 0,那么根据正向传播算法,这将意味着我们第二层的所有激活单元都会有相同的值。计算反向传播算法,算出来的每一层的又会有相同的值,如此一来,每次更新后,输入到每层的各个单元值相同。同理,如果我们初始所有的参数都为一个非 0 的数,结果也是一样的。
所以我们对于神经网络,采用的是随机初始化参数:
将所有
θ
\theta
θ参数随机初始化在[-
ϵ
\epsilon
ϵ,
ϵ
\epsilon
ϵ]范围内。(注:此处的
ϵ
\epsilon
ϵ与之前梯度检测的
ϵ
\epsilon
ϵ值并不相同,只是都是表示一个数值比较小的数。)
总体实现过程
根据以上的介绍,我们可以大概总结一下使用神经网络的过程:
一、选择网络结构:第一件要做的事是选择网络结构,即决定选择多少层以及决定每层分别有多少个单元。第一层的单元数即我们训练集的特征数量。最后一层的单元数是我们训练集的结果的类的数量。如果隐藏层数大于 1,确保每个隐藏层的单元个数相同(一般和输入特征维度相匹配,或相等或是2~3倍),通常情况下隐藏层单元的个数越多越好。我们真正要决定的是隐藏层的层数和每个中间层的单元数。
二、训练神经网络:
- 参数的随机初始化
- 利用正向传播方法计算所有的 h θ (x)
- 编写计算代价函数 J 的代码
- 利用反向传播方法计算所有偏导数
- 利用数值检验方法检验这些偏导数,之后关掉梯度检查算法。
- 使用梯度下降算法或者优化算法来最小化代价函数求得参数theta值。
注:由于神经网络中的代价函数J为非凸函数,所以求出来的值有可能是局部最小值。
在现实生活中,目前最经常采用以下策略避免局部最小,从而接近全局最小:
a.以多组不同参数值初始化多个神经网络,取经过训练后误差最小的值为最终参数。相当于从多个起始点开始搜索。
b.采用“模拟退火”技术[Aarts and Korst,1989]跳出局部最小值;
c.遗传算法(genetic algorithms)逼近全局最小,[Goldberg ,19891]。
目前这个问题还属于不断改进阶段
一些重要的编程代码:
求解代价函数和梯度的函数:
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);
J = 0;
Theta1_grad = zeros(size(Theta1));
Theta2_grad = zeros(size(Theta2));
%使用前向传播:
a1=[ones(m,1) X]; %给X增加一行1
% X=[ones(m,1) X];
z2=a1*Theta1'; %相乘的时候要注意两者的维度关系,得到z为5000*25
a2=sigmoid(z2);
a2=[ones(size(a2,1),1) a2];
z3=a2*Theta2'; %5000*10
a3=sigmoid(z3);
h=eye(num_labels);
y=h(y,:); %将y也变成5000*10;
J=-1/m*(sum(sum(y.*log(a3)+(1-y).*log(1-a3))));
%正则化:!!!!注意括号方向
J=J+lambda/(2*m)*(sum(sum(Theta1(:,2:end).^2))+sum(sum(Theta2(:,2:end).^2))); %两个sum求矩阵里所有元素求和
%使用反向传播:
%只有实现了前向传播和代价函数之后才能使用反向传播算法
delta3=a3-y;
delta2=delta3*Theta2;
delat2=delta2(:,2:end).*sigmoidGradient(z2);
%!!!!注意是sigmoidGradient而不是sigmoid,sigmoidGradient是编写的sigmoid的导数
%初始化:
Delta1=zeros(size(Theta1));
Delta2=zeros(size(Theta2));
Delta1=Delta1+delat2'*a1;
Delta2=Delta2+delta3'*a2;
%正则化梯度:
Theta1_grad=Delta1/m;
Theta2_grad=Delta2/m;
Theta1_grad(:,2:end)=Theta1_grad(:,2:end)+ (lambda/m)*Theta1(:,2:end);
Theta2_grad(:,2:end)=Theta2_grad(:,2:end)+ (lambda/m)*Theta2(:,2:end);
% Unroll gradients
grad = [Theta1_grad(:) ; Theta2_grad(:)]; %方便后面使用
end
随机初始化theta参数函数:
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
W = zeros(L_out, 1 + L_in);
ep=0.12;
W=rand(L_out,L_in+1)*2*ep-ep;
end
sigmoid函数的导数:
function g = sigmoidGradient(z)
%SIGMOIDGRADIENT returns the gradient of the sigmoid function
% %求g(z)的导数
% g=sigmoid(z).*(1-sigmoid(z));
g=sigmoid(z).*(1-sigmoid(z));%由g函数的性质所决定的
end
梯度检查函数:
实用提示:在进行梯度检查时,使用输入单元和隐藏单元相对较少的小型神经网络,从而获得相对较少的参数,效率要高得多。每个维度都需要对成本函数进行两次评估,这可能是昂贵的。在checkNNGradients函数中,我们的代码创建了一个小的随机模型和数据集,它与computeNumericalGradient一起用于梯度检查。此外。在您确信您的梯度计算是正确的之后,您应该在运行您的学习算法之前关闭梯度检查。
注:梯度检查适用于任何函数计算成本和梯度。具体来说,你可以使用相同的computeNumericalGradient.m函数来检查你对其他练习的梯度折法是否正确(例如,逻辑回归的成本函数)。
function checkNNGradients(lambda)
%CHECKNNGRADIENTS Creates a small neural network to check the
%backpropagation gradients
% CHECKNNGRADIENTS(lambda) Creates a small neural network to check the
% backpropagation gradients, it will output the analytical gradients
% produced by your backprop code and the numerical gradients (computed
% using computeNumericalGradient). These two gradient computations should
% result in very similar values.
%
if ~exist('lambda', 'var') || isempty(lambda)
lambda = 0;
end
input_layer_size = 3;
hidden_layer_size = 5;
num_labels = 3;
m = 5;
% We generate some 'random' test data
Theta1 = debugInitializeWeights(hidden_layer_size, input_layer_size);
Theta2 = debugInitializeWeights(num_labels, hidden_layer_size);
% Reusing debugInitializeWeights to generate X
X = debugInitializeWeights(m, input_layer_size - 1);
y = 1 + mod(1:m, num_labels)';
% Unroll parameters
nn_params = [Theta1(:) ; Theta2(:)];
% Short hand for cost function
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);
% Visually examine the two gradient computations. The two columns
% you get should be very similar.
disp([numgrad grad]);
fprintf(['The above two columns you get should be very similar.\n' ...
'(Left-Your Numerical Gradient, Right-Analytical Gradient)\n\n']);
% Evaluate the norm of the difference between two solutions.
% If you have a correct implementation, and assuming you used EPSILON = 0.0001
% in computeNumericalGradient.m, then diff below should be less than 1e-9
diff = norm(numgrad-grad)/norm(numgrad+grad); %得出一个范数 matlab默认的是2范数
% diff = abs(numgrad-grad); %绝对值的话会得出好多个差值
fprintf(['If your backpropagation implementation is correct, then \n' ...
'the relative difference will be small (less than 1e-9). \n' ...
'\nRelative Difference: %g\n'], diff);
end
function numgrad = computeNumericalGradient(J, theta)
%COMPUTENUMERICALGRADIENT Computes the gradient using "finite differences"
%and gives us a numerical estimate of the gradient.
% numgrad = COMPUTENUMERICALGRADIENT(J, theta) computes the numerical
% gradient of the function J around theta. Calling y = J(theta) should
% return the function value at theta.
% Notes: The following code implements numerical gradient checking, and
% returns the numerical gradient.It sets numgrad(i) to (a numerical
% approximation of) the partial derivative of J with respect to the
% i-th input argument, evaluated at theta. (i.e., numgrad(i) should
% be the (approximately) the partial derivative of J with respect
% to theta(i).)
%
numgrad = zeros(size(theta));
perturb = zeros(size(theta));
e = 1e-4;
for p = 1:numel(theta) %numel返回数组元素个数
% Set perturbation vector
perturb(p) = e;
loss1 = J(theta - perturb);
loss2 = J(theta + perturb);
% Compute Numerical Gradient
numgrad(p) = (loss2 - loss1) / (2*e);
perturb(p) = 0;
end
end
整个过程的主函数:
%% Machine Learning Online Class - Exercise 4 Neural Network Learning
%% Initialization
clear ; close all; clc
%% Setup the parameters you will use for this exercise
input_layer_size = 400; % 20x20 Input Images of Digits
hidden_layer_size = 25; % 25 hidden units
num_labels = 10; % 10 labels, from 1 to 10
% (note that we have mapped "0" to label 10)
%% =========== Part 1: Loading and Visualizing Data =============
load('ex4data1.mat');
m = size(X, 1);
% Randomly select 100 data points to display
sel = randperm(size(X, 1));
sel = sel(1:100);
displayData(X(sel, :)); %随机展示几幅图
%% ================ Part 2: Loading Parameters ================
% Load the weights into variables Theta1 and Theta2
load('ex4weights.mat');
% Unroll parameters
nn_params = [Theta1(:) ; Theta2(:)];
%% ================ Part 3: Compute Cost (Feedforward) ================
lambda = 0;
J = nnCostFunction(nn_params, input_layer_size, hidden_layer_size, ...
num_labels, X, y, lambda);
%% =============== Part 4: Implement Regularization ===============
% Weight regularization parameter (we set this to 1 here).
lambda = 1;
J = nnCostFunction(nn_params, input_layer_size, hidden_layer_size, ...
num_labels, X, y, lambda);
%% ================ Part 5: Sigmoid Gradient ================
g = sigmoidGradient([-1 -0.5 0 0.5 1]);
%% ================ Part 6: Initializing Pameters ================
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(:)];
%% =============== Part 7: Implement Backpropagation ===============
% Check gradients by running checkNNGradients
checkNNGradients;
fprintf('\nProgram paused. Press enter to continue.\n');
pause;
%% =============== Part 8: Implement Regularization ===============
fprintf('\nChecking Backpropagation (w/ Regularization) ... \n')
% Check gradients by running checkNNGradients
lambda = 3;
checkNNGradients(lambda);
% Also output the costFunction debugging values
debug_J = nnCostFunction(nn_params, input_layer_size, ...
hidden_layer_size, num_labels, X, y, lambda);
%% =================== Part 8: Training NN ===================
fprintf('\nTraining Neural Network... \n')
options = optimset('MaxIter', 400);
% You should also try different values of lambda
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, cost] = 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));
fprintf('Program paused. Press enter to continue.\n');
pause;
%% ================= Part 9: Visualize Weights =================
% You can now "visualize" what the neural network is learning by
% displaying the hidden units to see what features they are capturing in
% the data.
fprintf('\nVisualizing Neural Network... \n')
displayData(Theta1(:, 2:end));
fprintf('\nProgram paused. Press enter to continue.\n');
pause;
%% ================= Part 10: Implement Predict =================
pred = predict(Theta1, Theta2, X);
fprintf('\nTraining Set Accuracy: %f\n', mean(double(pred == y)) * 100);
注:学习吴恩达coursera机器学习课程的笔记。