我正在尝试构建一个由三层组成的简单神经网络,以解决二元分类问题。前两层各有八个神经元(加上偏置单元)。我使用的是fminunc
。这是我的成本函数:
1 function [jVal, gradient] = cost2(thetaVec, X, y)2 Theta1 = reshape(thetaVec(1:72),8, 9); % my weights used for 3 Theta2 = reshape(thetaVec(73:81),1, 9); %forward propagation4 Delta1 = 0; %Delta is divided in Delta1 and Delta2 for simplicity but 5 Delta2 = 0; %they're combined to eventually calculate the gradient6 jVal = 0; %the value of the costfunction7 m = length(y);8 for i = 1:m9 a1 = X(i, :); %X size: 3x9, a1 size: 1x910 z2 = Theta1 * a1';11 a2 = 1 ./(1 + exp(-z2)); %a2 size: 8x112 a2 = [ones(columns(a2), 1) a2']; % bias unit added to a2: a2 size: 1x913 z3 = Theta2 * a2';14 a3 = 1 ./(1 + exp(-z3)); %a3 = h(x(i)) size: 1x115 jVal += (-1/m) * (y(i) * log(a3) + (1 - y(i)) * log(1 - a3));16 delta3 = a3 - y(i); %delta3 size: 1x117 delta2 = Theta2' * delta3 .* a2 .* (1 - a2); %delta2 size: 9x918 Delta2 += delta3 * a2'; %I use Delta1 and Delta2 as accumulators19 Delta1 += delta2 * a1'; %size Delta2: 9x1, size Delta1: 9x120 endfor21 jVal = jVal/m; %avarage of jVal22 Delta = [Delta1;Delta2]; %Deltas are combined. Size Delta: 18x123 gradient = (1/m) * Delta;% size gradient: 18x124 endfunction
我的主程序如下:
%the values of the vector from which I derive my weights are chosen randomly INIT_EPSILON = 0.1; %between thi interval Theta1 = rand(8, 9) * (2*INIT_EPSILON) - INIT_EPSILON; Theta2 = rand(1, 9) * (2*INIT_EPSILON) - INIT_EPSILON; thetaVec = [ Theta1(:); Theta2(:)]; options = optimset('GradObj', 'on', 'MaxIter', 10000); [optTheta, functionVal, exitFlag] = fminunc(@(t) cost2(t, X, y), thetaVec, options)
梯度应该是一个9×9的矩阵,但实际上它是一个18×1的矩阵,所以我无法使用fminunc
。实际上,我尝试了多次修改成本函数中的反向传播部分以获得9×9的梯度(特别是修改了delta2
)。然而,这从未奏效,输出结果是:
optTheta = %a vector of various valuesfunctionVal = 0.71681 %or a similar valueexitFlag = 1
所以,即使exitflag为1,它也没有收敛。我哪里做错了?
回答:
你当前的代码如下:
delta3 = a3 - y(i); % (1×1)delta2 = Theta2' * delta3 .* a2 .* (1 - a2); % (9×1).*(1×9) = (9×9)Delta2 += delta3 * a2'; % (9×9)* (9×1) = (9×1)Delta1 += delta2 * a1'; % (9×9)* (n×1) = (9×1)
我认为应该改成这样:
delta3 = a3 - y(i); % (1×1)delta2 = Theta2 * delta3 .* a2 .* (1 - a2); % (1×9).*(1×9) = (1×9)Delta2 += delta3 * a2'; % (9×9)* (9×1) = (9×1)Delta1 += delta2.' * a1; % (9×1)* (1×9) = (9×9)
然后在每一步中丢弃Delta1中偏置的梯度,最终得到一个(8×9)的矩阵作为你的Delta1组件。(你可能需要先转置Delta1,我没有仔细跟踪你的转置操作)。
最后,在末尾的垂直拼接步骤的整个目的是将你的矩阵“压缩”回单列长向量形式,以便它们遵循与输入“thetaVec”相同的“规格”,因此你需要将你的(8×9)和(1×9)的Delta1和Delta2对象“压缩”,即:
[Delta1(:) ; Delta2(:)]
更新
继续在上面的评论中讨论。
考虑Delta1和Delta2是什么。这是对应于Theta1和Theta2中元素的总误差(在所有观测值上)。换句话说,Delta1应该与Theta1大小相同,Delta2应该与Theta2大小相同。现在你已经在代码中包含了矩阵大小,你可以立即看到这不是这种情况。
此外,由于每次迭代都会添加到这些矩阵中,每次迭代的结果应该是正确大小的Delta1和Delta2,以便你可以添加每次迭代贡献的误差,以获得所有迭代的总误差(每个参数)。
另外,考虑delta2和delta3是什么。这些也是误差,但它们不是指参数中的误差,而是指节点中的误差。换句话说,它们显示了每一层中每个节点对最终误差的贡献/责任。因此,它们的大小需要是一个与相应层中节点数量相同的向量。你已经可以看到delta2的大小为9×9是没有意义的!
所以算法的逻辑是这样的。获取节点对误差的贡献,并将其反向传播到前面的节点和它们之间的参数。
例如,将每个delta3(在这种情况下只有一个节点)与第二层中的每个节点相乘,以找出错误是如何在Theta2的每个参数元素上分布的。
同样,为了获得delta2,我们考虑了delta3中的误差,并通过参数乘法向后分配到第二层中的每个节点。然后在此基础上,我们乘以梯度(因为这定义了该误差将向前传播的程度/速率)。
现在我们已经处理了第三层及其与第二层的交互,我们继续处理第二层及其与第一层的交互。因此,同样,将每个delta2(第二层有9个节点,因此delta2应该有9个元素)与第一层中的每个节点相乘。这给出了一个9×9的矩阵,反映了第一层中的每个节点通过参数Theta1如何导致第二层中每个节点的错误。然而,因为我们不关心对第二层“偏置”节点的贡献,我们从Delta1中删除这部分,这使我们得到一个8×9的矩阵,与Theta1的大小完全相同。
理论上你也可以反向传播delta2以找到delta1,但由于我们不需要它,我们跳过了这一步。毕竟,我们真正关心的是参数中的误差,而不是节点中的误差。(即我们只关心每一步中节点的误差,因为我们需要它们来从前一层获取参数中的误差)。