我在MATLAB中实现了反向传播算法,但在训练过程中遇到了问题。训练初期,所有输出都趋向于1。我已经对输入数据进行了归一化处理(除了用于生成二进制目标向量的期望类别),将其归一化为[0, 1]区间。我参考了《人工智能:现代方法》一书中Norvig等人的实现方法。
我已经将伪代码与我的代码进行了对比(并研究了该算法一段时间),但未能发现错误。我使用MATLAB的时间并不长,所以在需要时会参考文档。
我还尝试了不同数量的隐藏层节点和不同的学习率(ALPHA
)。
目标数据编码如下:例如,当目标是分类为2
时,目标向量将是[0,1,0]
;如果是1
,则为[1, 0, 0]
,依此类推。我还尝试了为目标使用不同的值,例如(对于类别1
)使用[0.5, 0, 0]
。
我注意到一些权重超过了1
,导致网络值过大。
%拓扑常量NUM_HIDDEN = 8+1;%写成n+1以明确使用偏置NUM_OUT = 3;%训练常量ALPHA = 0.01;TARG_ERR = 0.01;MAX_EPOCH = 50000;%读取并归一化数据文件。X = normdata(dlmread('iris.data'));X = shuffle(X);%X_test = normdata(dlmread('iris2.data'));%epocherrors = fopen('epocherrors.txt', 'w');%权重矩阵。%特征构成size(X, 2)-1,但大小为(X, 2)以便添加偏置。w_IH = rand(size(X, 2), NUM_HIDDEN)-(0.5*rand(size(X, 2), NUM_HIDDEN)); w_HO = rand(NUM_HIDDEN+1, NUM_OUT)-(0.5*rand(NUM_HIDDEN+1, NUM_OUT));%+1为偏置%层网络net_H = zeros(NUM_HIDDEN, 1);net_O = zeros(NUM_OUT, 1);%层输出out_H = zeros(NUM_HIDDEN, 1);out_O = zeros(NUM_OUT, 1);%层误差d_H = zeros(NUM_HIDDEN, 1);d_O = zeros(NUM_OUT, 1);%控制变量error = inf;epoch = 0;%运行算法。while error > TARG_ERR && epoch < MAX_EPOCH for n=1:size(X, 1) x = [X(n, 1:size(X, 2)-1) 1]';%为隐藏层添加偏置并转置为列向量。 o = X(n, size(X, 2)); %前向传播。 net_H = w_IH'*x;%转置的w。 out_H = [sigmoid(net_H); 1]; %为输出添加1作为偏置 net_O = w_HO'*out_H; out_O = sigmoid(net_O); %再次,转置的w。 %计算输出误差。 d_O = ((targetVec(o, NUM_OUT)-out_O) .* (out_O .* (1-out_O))); %计算隐藏层误差。 for i=1:size(w_HO, 1); delta_weight = 0; for j=1:size(w_HO, 2) delta_weight = delta_weight + d_O(j)*w_HO(i, j); end d_H(i) = (out_H(i)*(1-out_H(i)))*delta_weight; end %更新隐藏层-输出层权重 for i=1:size(w_HO, 1) for j=1:size(w_HO, 2) w_HO(i, j) = w_HO(i, j) + (ALPHA*out_H(i)*d_O(j)); end end %更新输入层-隐藏层权重。 for i=1:size(w_IH, 1) for j=1:size(w_IH, 2) w_IH(i, j) = w_IH(i, j) + (ALPHA*x(i)*d_H(j)); end end out_O o %out_H %w_IH %w_HO %d_O %d_H end endfunction outs = sigmoid(nets) outs = zeros(size(nets, 1), 1); for i=1:size(nets, 1) if nets(i) < -45 outs(i) = 0; elseif nets(i) > 45 outs(i) = 1; else outs(i) = 1/1+exp(-nets(i)); end endend
回答:
经过讨论,发现问题出在sigmoid函数中:
function outs = sigmoid(nets)%... outs(i) = 1/1+exp(-nets(i)); % 缺少括号!!!!!!%...end
应该改为:
function outs = sigmoid(nets)%... outs(i) = 1/(1+exp(-nets(i)));%...end
由于缺少括号,有时sigmoid函数的输出会大于1。这导致梯度计算不正确(因为它不是该函数的梯度)。这使得梯度为负值。这导致输出层的误差大部分时间方向错误。修复后(并正确维护错误变量 – 这在您的代码中似乎缺失),一切似乎都正常工作。
除此之外,该代码还有两个主要问题:
1) 没有偏置。没有偏置,每个神经元只能表示通过原点的直线。如果数据被归一化(即值在0到1之间),某些配置是不可分的。
2) 缺乏对高梯度值的防护(我之前回答中的第一点)。