Хорошо, думаю, я мог бы решить проблему.Обычно различия в градиентах составляют <1e-4, хотя у меня есть по крайней мере один, который равен 6e-4.Кто-нибудь знает, если это все еще приемлемо? </p>
Чтобы получить этот результат, я переписал код и не связывая матрицы весов (я не уверен, что это всегда приведет к сбою проверки производной).Я также добавил ошибки, поскольку они не слишком усложняли ситуацию.
Что-то еще, что я понял при отладке, это то, что действительно легко допустить ошибку в коде.Например, мне потребовалось некоторое время, чтобы поймать:
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'
.Таким образом, я мог бы просто воссоздать W2, посмотрев на W1.Однако, поскольку значения $ \ theta $ изменяются эпсилоном, это фактически изменяет обе матрицы одновременно.Правильное решение - просто передать W1
в качестве отдельного параметра, одновременно уменьшая $ \ theta $.
Обновление 2
Хорошо, вот чтоЯ получаю пост слишком поздно ночью.Хотя первое обновление действительно приводит к тому, что все проходит правильно, это не правильное решение.
Я думаю, что правильное решение - это фактически рассчитать градиенты для W1 и W2, а затем установить окончательный градиент для W1в grad_W1 в grad_W2.Аргумент махания рукой состоит в том, что, поскольку весовая матрица действует как для кодирования, так и для декодирования, на ее веса должны влиять оба градиента.Однако я еще не обдумал фактические теоретические последствия этого.
Если я выполню это, используя свою собственную проверку производной, она преодолеет порог 10e-4.Это намного лучше, чем раньше, с производной проверкой minFunc, но все же хуже, чем если бы я не связывал веса.