Моя простая нейронная сеть работает лучше без предвзятости. У меня проблемы с интерпретацией смещения на скрытом слое
Быстрое исправление: моя сеть лучше учится с помощью столбца смещения на входе, раньше я сталкивался с глупой ошибкой, из-за которой казалось, что смещение вредно, но на самом деле оно фактически повышает производительность по сравнению с отсутствием смещения
У меня есть нейронная сеть, реализованная с моим собственным классом матрицы. Для справки, я использовал эту же сеть, чтобы получить ~94% правильного решения проблемы классификации рукописных цифр MNIST. Рассматривая проблему с игрушками (XOR Gate), я понял, что мое представление смещения неверно, и если я его исправлю, у меня будет гораздо лучшая производительность. Также эта сеть использует стохастический мини-пакетный градиентный спуск.
Я разработал множество функций и возможностей в своих классах, чтобы они были максимально удобочитаемыми. Моя сеть хорошо учится с колонкой смещения, вставленной в матрицу ввода. Мне нужно найти способ реализовать это и на скрытом слое. Я хочу реализовать его в виде столбца на входе и в скрытом слое.
В следующем коде inputBatch содержит входы, в которые уже вставлен столбец 1. Например, проблемы ввода XOR становятся
{1, 0, 0}
{1, 0, 1}
{1, 1, 0}
{1, 1, 1}
Для этой проблемы предположим, что я использую 16 скрытых узлов. Мои выводы отформатированы так:
{0}
{1}
{1}
{0}
Поскольку мои входные данные представляют собой матрицу 4x3, моя матрица inputToHiddenWeights будет иметь размер 3x16. Это приводит к скрытому слою 4x16, к которому я поэлементно применяю сигмовидную функцию. Затем я умножаю его на матрицу hiddenToOutputWeight, которая является матрицей 16x1. Результатом этого является конечный результат, представляющий собой вектор 4x1, в котором я снова и снова применяю сигмовидную функцию. На этом теперь заканчивается прямой проход.
После просмотра этой ссылки: https://stats.stackexchange.com/questions/124651/how-do-i-incorporate-the-biases-in-my-feed-forward-neural-network
Меня убеждают, что мое входное представление правильное, я добавил столбец из них, и это пока дало мне приличную производительность, хотя я знаю, что у меня более высокая производительность, ожидая, если я смогу исправить смещение в других слоях. В конце ссылки выше пользователь добавляет дополнительный столбец единиц к скрытому слою после применения сигмоида и перед умножением на HiddenToOutputWeights (он не включает никаких шагов после добавления столбца к скрытому слою). Если бы я сделал это, это означало бы, что 1-й: вычислить скрытый слой как обычно, 2-й: добавить столбец 1 с результатом, 3-й: изменить мой HiddenToOutputWeights на размер 17x1.
Это все хорошо и даже хорошо (и даже работает для прямого прохода). Но мои проблемы возникают во время обратного распространения. Я могу сойти со всех, кроме последних шагов. Вы увидите весь код ниже, но я хочу выделить последние две операции
this.hiddenToOutputWeights = hiddenToOutputWeights.add(deltaOutput.add(momentumOutput));
this.inputToHiddenWeights = inputToHiddenWeights.add(deltaHidden.add(momentumHidden));
В этой операции я не могу добавить deltaHidden и momentumHidden, так как deltaHidden является матрицей 3x17, а momentumHidden (и inputToHiddenWeights) имеют размер 3x16.
Моя проблема: добавление дополнительного столбца одного в скрытом слое, как видно по ссылке выше, работает для прямого распространения, но когда я добираюсь до backprop, добавленный столбец останавливает меня от выполнения моих матричных операций. Как я могу это исправить? Спасибо всем. Есть вопросы, дайте мне знать.
Вот мой код прямого распространения:
private void forwardPropagation(Matrix inputBatch, Matrix desiredLabels, boolean isTraining) {
// compute the hiddenActivation by multiplying across
// the inputToHiddenWeights and then apply the sigmoid function
// this is the transfer from the input neurons to the first hidden layer
this.hiddenActivation = (inputBatch.matrixMult(this.inputToHiddenWeights)).sigmoid();
// compute the outputActivation (the final prediction) by multing the hiddenActivation
// with the hiddenToOutput weights and then apply the sigmoid function
// this is the transfer from the middle hidden layer to the 3rd and final output layer
this.outputActivation = (hiddenActivation.matrixMult(this.hiddenToOutputWeights)).sigmoid();
// Compute error (desired - actual)
// this is used in backprop
this.labelError = desiredLabels.sub(outputActivation);
// no reason for separating forward/backward prop other than I like how it looks
if(isTraining) {
backPropagation(inputBatch);
}
И вот мой код backProp:
private void backPropagation(Matrix inputBatch) {
// compute slope at output and hidden layers
Matrix slopeOutput = outputActivation.sigmoidPrime();
Matrix slopeHidden = hiddenActivation.sigmoidPrime();
// compute error signals of the output and hidden layers. This is an intermediate calculation of the delta rule,
// and because the calculation for the hidden layer use parts of the calculation already done for the output layer,
// we can't just calculate the delta rule all at once.
// Note: Transposes represent accumulated dot products
Matrix errorSignalOutput = labelError.scalarMultAcrossMatricies(slopeOutput);
Matrix errorSignalHidden = (errorSignalOutput.matrixMult(hiddenToOutputWeights.transpose())).scalarMultAcrossMatricies(slopeHidden);
// calculate momentum by grabbing hold of the previous iterations deltas and multiplying it by the momentum factor which is a number from 0 to 1
this.momentumOutput = deltaOutput.scalarMult(this.momentumFactor);
this.momentumHidden = deltaHidden.scalarMult(this.momentumFactor);
// compute delta for outputToHidden weights and then inputToHidden weights.
// save the deltas so we can add a momentum term
this.deltaOutput = ((hiddenActivation.transpose()).matrixMult(errorSignalOutput)).scalarMult(LEARNING_RATE);
this.deltaHidden = ((inputBatch.transpose()).matrixMult(errorSignalHidden)).scalarMult(LEARNING_RATE);
// update weight at both output and hidden layers SIMULTANEOUSLY else you risk messing up the final result do to
// meddling with intermediate calculations.
this.hiddenToOutputWeights = hiddenToOutputWeights.add(deltaOutput.add(momentumOutput));
this.inputToHiddenWeights = inputToHiddenWeights.add(deltaHidden.add(momentumHidden));
this.stepOfIteration++;
}
Скорее всего: наличие только одного столбца смещения на входе по-прежнему позволяет сети сходиться к решению. У меня точность 94% с использованием только смещения на входе и больше нигде. Я хочу найти решение этой проблемы, и у меня есть предвзятость к скрытому слою.