DerivativeCheck因minFunc而失败

时间:2011-07-06 18:28:31

标签: matlab machine-learning mathematical-optimization

我正在尝试使用minFunc训练单层自动编码器,虽然成本函数似乎减少,但启用时,DerivativeCheck失败。我正在使用的代码尽可能接近教科书值,但非常简化。

我正在使用的损失函数是平方误差:

$ J(W; x)= \ frac {1} {2} || a ^ {l} - x || ^ 2 $

$ a ^ {l} $等于$ \ sigma(W ^ {T} x)$,其中$ \ sigma $是sigmoid函数。因此,梯度应为:

$ \ delta =(a ^ {l} - x)* a ^ {l}(1 - a ^ {l})$

$ \ nabla_ {W} = \ delta(a ^ {l-1})^ T $

请注意,为了简化事情,我完全放弃了偏见。虽然这会导致性能不佳,但它不应该影响梯度检查,因为我只关注权重矩阵。另外,我已经绑定了编码器和解码器矩阵,因此实际上只有一个权重矩阵。

我用于丢失函数的代码是(编辑:我已经将我的循环矢量化并清理了一些代码):

% loss function passed to minFunc
function [ loss, grad ] = calcLoss(theta, X, nHidden)
  [nInstances, nVars] = size(X);

  % we get the variables a single vector, so need to roll it into a weight matrix
  W = reshape(theta(1:nVars*nHidden), nVars, nHidden);
  Wp = W; % tied weight matrix

  % encode each example (nInstances)
  hidden = sigmoid(X*W);

  % decode each sample (nInstances)
  output = sigmoid(hidden*Wp);

  % loss function: sum(-0.5.*(x - output).^2)
  % derivative of loss: -(x - output)*f'(o)
  % if f is sigmoid, then f'(o) = output.*(1-output)
  diff = X - output;
  error = -diff .* output .* (1 - output);
  dW = hidden*error';

  loss = 0.5*sum(diff(:).^2, 2) ./ nInstances;

  % need to unroll gradient matrix back into a single vector
  grad = dW(:) ./ nInstances;
end

下面是我用来运行优化器的代码(一次,因为所有训练样本的运行时相当长):

examples = 5000;
fprintf('loading data..\n');
images = readMNIST('train-images-idx3-ubyte', examples) / 255.0;

data = images(:, :, 1:examples);

% each row is a different training sample
X = reshape(data, examples, 784);

% initialize weight matrix with random values
% W: (R^{784} -> R^{10}), W': (R^{10} -> R^{784})
numHidden = 10; % NOTE: this is extremely small to speed up DerivativeCheck
numVisible = 784;
low = -4*sqrt(6./(numHidden + numVisible));
high = 4*sqrt(6./(numHidden + numVisible));
W = low + (high-low)*rand(numVisible, numHidden);

% run optimization
options = {};
options.Display = 'iter';
options.GradObj = 'on';
options.MaxIter = 10;
mfopts.MaxFunEvals = ceil(options.MaxIter * 2.5);
options.DerivativeCheck = 'on';
options.Method = 'lbfgs';    
[ x, f, exitFlag, output] = minFunc(@calcLoss, W(:), options, X, numHidden);

我在DerivitiveCheck上得到的结果通常小于0,但大于0.1。我使用批量梯度下降尝试了类似的代码,并获得了稍微好一点的结果(有些是<0.0001,但肯定不是全部)。

我不确定我的数学或代码是否犯了错误。任何帮助将不胜感激!

更新

我在代码中发现了一个小错字(下面的代码中没有出现),导致性能异常糟糕。不幸的是,我的成绩仍然不尽如人意。例如,两个渐变之间的比较:

calculate     check
0.0379        0.0383
0.0413        0.0409
0.0339        0.0342
0.0281        0.0282
0.0322        0.0320

差异高达0.04,我假设仍然失败。

1 个答案:

答案 0 :(得分:2)

好的,我想我可能已经解决了这个问题。通常,梯度的差异<1。 1e-4,虽然我确实至少有一个是6e-4。有谁知道这是否仍然可以接受?

为了得到这个结果,我重写了代码而没有绑定权重矩阵(我不确定这样做是否会导致派生检查失败)。我也包含了偏见,因为它们并没有使事情变得太复杂。

我在调试时意识到的是,真的很容易在代码中出错。例如,我花了一段时间来抓住:

grad_W1 = error_h*X';

而不是:

grad_W1  = X*error_h';

虽然这两行之间的差异只是grad_W1的转置,但由于需要将参数打包/解包到单个向量中,因此Matlab无法抱怨grad_W1是错误的维度。

我还包括了我自己的衍生检查,它给出了与minFunc相比略有不同的答案(我的衍生检查给出的差异均低于1e-4)。

<强> fwdprop.m:

function [ hidden, output ] = fwdprop(W1, bias1, W2, bias2, X)
  hidden = sigmoid(bsxfun(@plus, W1'*X, bias1));
  output = sigmoid(bsxfun(@plus, W2'*hidden, bias2));
 end

<强> calcLoss.m:

function [ loss, grad ] = calcLoss(theta, X, nHidden)
  [nVars, nInstances] = size(X);
  [W1, bias1, W2, bias2] = unpackParams(theta, nVars, nHidden);
  [hidden, output] = fwdprop(W1, bias1, W2, bias2, X);
  err = output - X;
  delta_o = err .* output .* (1.0 - output);
  delta_h = W2*delta_o .* hidden .* (1.0 - hidden);

  grad_W1 = X*delta_h';
  grad_bias1 = sum(delta_h, 2);
  grad_W2 = hidden*delta_o';
  grad_bias2 = sum(delta_o, 2);

  loss = 0.5*sum(err(:).^2);
  grad = packParams(grad_W1, grad_bias1, grad_W2, grad_bias2);
end

<强> unpackParams.m:

function [ W1, bias1, W2, bias2 ] = unpackParams(params, nVisible, nHidden)
  mSize = nVisible*nHidden;

  W1 = reshape(params(1:mSize), nVisible, nHidden);
  offset = mSize;    

  bias1 = params(offset+1:offset+nHidden);
  offset = offset + nHidden;

  W2 = reshape(params(offset+1:offset+mSize), nHidden, nVisible);
  offset = offset + mSize;

  bias2 = params(offset+1:end);
end

<强> packParams.m

function [ params ] = packParams(W1, bias1, W2, bias2)
  params = [W1(:); bias1; W2(:); bias2(:)];
end

<强> checkDeriv.m:

function [check] = checkDeriv(X, theta, nHidden, epsilon)
  [nVars, nInstances] = size(X);

  [W1, bias1, W2, bias2] = unpackParams(theta, nVars, nHidden);
  [hidden, output] = fwdprop(W1, bias1, W2, bias2, X);
  err = output - X;
  delta_o = err .* output .* (1.0 - output);
  delta_h = W2*delta_o .* hidden .* (1.0 - hidden);

  grad_W1 = X*delta_h';
  grad_bias1 = sum(delta_h, 2);
  grad_W2 = hidden*delta_o';
  grad_bias2 = sum(delta_o, 2);

  check = zeros(size(theta, 1), 2);
  grad = packParams(grad_W1, grad_bias1, grad_W2, grad_bias2);
  for i = 1:size(theta, 1)
      Jplus = calcHalfDeriv(X, theta(:), i, nHidden, epsilon);
      Jminus = calcHalfDeriv(X, theta(:), i, nHidden, -epsilon);

      calcGrad = (Jplus - Jminus)/(2*epsilon);
      check(i, :) = [calcGrad grad(i)];
  end
end

<强> checkHalfDeriv.m:

function [ loss ] = calcHalfDeriv(X, theta, i, nHidden, epsilon)
  theta(i) = theta(i) + epsilon;

  [nVisible, nInstances] = size(X);
  [W1, bias1, W2, bias2] = unpackParams(theta, nVisible, nHidden);
  [hidden, output] = fwdprop(W1, bias1, W2, bias2, X);

  err = output - X;
  loss = 0.5*sum(err(:).^2);
end

<强>更新

好的,我也想出了为什么绑重物会导致问题。我想从[ W1; bias1; bias2 ]开始追溯到W2 = W1'。这样我就可以通过查看W1来重新创建W2。但是,由于epsilon更改了$ \ theta $的值,因此实际上同时更改了两个矩阵。正确的解决方案是简单地将W1作为单独的参数传递,同时减少$ \ theta $。

更新2

好的,这是我晚上发布的内容。虽然第一次更新确实导致事情正确通过,但这不是正确的解决方案。

我认为正确的做法是实际计算W1和W2的梯度,然后将W1的最终梯度设置为grad_W1到grad_W2。挥手的论点是,由于权重矩阵对编码和解码起作用,其权重必须受两个梯度的影响。然而,我还没有想到这个的实际理论分支。

如果我使用自己的衍生检查来运行它,它会通过10e-4阈值。使用minFunc的衍生检查确实比以前好多了,但是如果我没有加权,那就更糟了。