Алгоритм обратного распространения нейронной сети получает на XOR Training PAttern

Обзор

Итак, я пытаюсь понять механизм нейронных сетей. Я все еще не полностью понимаю математику за ней, но я думаю, что понимаю, как ее реализовать. В настоящее время у меня есть нейронная сеть, которая может изучать шаблоны обучения AND, OR и NOR. Однако я не могу заставить его реализовать шаблон XOR. Моя нейронная сеть вперед состоит из 2 входов, 3 скрытых и 1 вывода. Весы и предубеждения произвольно устанавливаются между -0,5 и 0,5, а выходы генерируются с помощью сигмоидальной функции активации

Алгоритм

До сих пор я предполагаю, что допустил ошибку в моем алгоритме обучения, который описан ниже:

  • Для каждого нейрона в выходном слое укажите error значение desiredOutput - actualOutput --go на шаг 3
  • Для каждого нейрона в скрытом или входном слое (работа назад) укажите значение error, которое является суммой всех forward connection weights * the errorGradient of the neuron at the other end of the connection --go до шага 3
  • Для каждого нейрона, используя предоставленное значение error, создайте error gradient, равный output * (1-output) * error. - перейти к шагу 4
  • Для каждого нейрона отрегулируйте смещение равным current bias + LEARNING_RATE * errorGradient. Затем настройте каждый вес обратного соединения равным current weight + LEARNING_RATE * output of neuron at other end of connection * this neuron errorGradient

Я тренирую свою нейронную сеть онлайн, так что это проходит после каждого учебного примера.

код

Это основной код, который запускает нейронную сеть:

private void simulate(double maximumError) {

    int errorRepeatCount = 0;
    double prevError = 0;

    double error; // summed squares of errors
    int trialCount = 0;

    do {

        error = 0;

        // loop through each training set
        for(int index = 0; index < Parameters.INPUT_TRAINING_SET.length; index++) {

            double[] currentInput = Parameters.INPUT_TRAINING_SET[index];
            double[] expectedOutput = Parameters.OUTPUT_TRAINING_SET[index];
            double[] output = getOutput(currentInput);

            train(expectedOutput);

            // Subtracts the expected and actual outputs, gets the average of those outputs, and then squares it.
            error += Math.pow(getAverage(subtractArray(output, expectedOutput)), 2); 



        }

    } while(error > maximumError);

Теперь функция train():

public void train(double[] expected) {

    layers.outputLayer().calculateErrors(expected);

    for(int i = Parameters.NUM_HIDDEN_LAYERS; i >= 0; i--) {
        layers.allLayers[i].calculateErrors();
    }

}

Функция выходного уровня calculateErrors():

public void calculateErrors(double[] expectedOutput) {

    for(int i = 0; i < numNeurons; i++) {

        Neuron neuron = neurons[i];
        double error = expectedOutput[i] - neuron.getOutput();
        neuron.train(error);

    }

}

Обычный (скрытый и входной) уровень calculateErrors():

public void calculateErrors() {

    for(int i = 0; i < neurons.length; i++) {

        Neuron neuron = neurons[i];

        double error = 0;

        for(Connection connection : neuron.forwardConnections) {

            error += connection.output.errorGradient * connection.weight;

        }

        neuron.train(error);

    }

}

Полный класс нейрона:

package neuralNet.layers.neurons;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import neuralNet.Parameters;
import neuralNet.layers.NeuronLayer;

public class Neuron {

private double output, bias;
public List<Connection> forwardConnections = new ArrayList<Connection>(); // Forward = layer closer to input -> layer closer to output
public List<Connection> backwardConnections = new ArrayList<Connection>(); // Backward = layer closer to output -> layer closer to input

public double errorGradient;
public Neuron() {

    Random random = new Random();
    bias = random.nextDouble() - 0.5;

}

public void addConnections(NeuronLayer prevLayer) {

    // This is true for input layers. They create their connections differently. (See InputLayer class)
    if(prevLayer == null) return;

    for(Neuron neuron : prevLayer.neurons) {

        Connection.createConnection(neuron, this);

    }

}

public void calcOutput() {

    output = bias;

    for(Connection connection : backwardConnections) {

        connection.input.calcOutput();
        output += connection.input.getOutput() * connection.weight;

    }

    output = sigmoid(output);

}

private double sigmoid(double output) {
    return 1 / (1 + Math.exp(-1*output));
}

public double getOutput() {
    return output;
}

public void train(double error) {

    this.errorGradient = output * (1-output) * error;

    bias += Parameters.LEARNING_RATE * errorGradient;

    for(Connection connection : backwardConnections) {

        // for clarification: connection.input refers to a neuron that outputs to this neuron
        connection.weight += Parameters.LEARNING_RATE * connection.input.getOutput() * errorGradient;

    }

}

}

Результаты

Когда я тренируюсь для AND, OR или NOR, сеть обычно может сходиться в течение примерно 1000 эпох, однако, когда я тренируюсь с XOR, выходы фиксируются и никогда не сходятся. Итак, что я делаю неправильно? Любые идеи?

Edit

Следуя советам других, я начал и реализовал свою нейронную сеть без классов... и это работает. Я все еще не уверен, где моя проблема лежит в приведенном выше коде, но там где-то там.

Ответ 1

Комментарий LiKao для упрощения моей реализации и избавления от объектно-ориентированных аспектов решает мою проблему. Недостаток алгоритма, описанный выше, неизвестен, однако теперь у меня есть рабочая нейронная сеть, которая намного меньше.

Не стесняйтесь продолжать рассказывать о проблеме с моей предыдущей реализацией, так как другие могут иметь такую ​​же проблему в будущем.

Ответ 2

Это удивительно, потому что вы используете достаточно большую сеть (едва), чтобы изучить XOR. Ваш алгоритм выглядит правильно, поэтому я не знаю, что происходит. Это может помочь узнать, как вы генерируете свои учебные данные: вы просто повторяете образцы (1,0,1),(1,1,0),(0,1,1),(0,0,0) или что-то в этом роде снова и снова? Возможно, проблема в том, что стохастический градиентный спуск заставляет вас прыгать вокруг стабилизирующих минимумов. Вы могли бы попробовать некоторые вещи, чтобы исправить это: возможно, случайный образец из ваших учебных примеров, а не повторять их (если это то, что вы делаете). Или, альтернативно, вы можете изменить свой алгоритм обучения:

в настоящее время у вас есть что-то эквивалентное:

weight(epoch) = weight(epoch - 1) + deltaWeight(epoch)
deltaWeight(epoch) = mu * errorGradient(epoch)

где mu - скорость обучения

Один из вариантов: очень медленно уменьшает значение mu.

Альтернативой было бы изменить ваше определение deltaWeight, чтобы включить "импульс"

deltaWeight(epoch) = mu * errorGradient(epoch) + alpha * deltaWeight(epoch -1)

где alpha - параметр импульса (между 0 и 1).

Визуально, вы можете думать о градиентном спуске, пытаясь найти минимальную точку изогнутой поверхности, поместив объект на эту поверхность, а затем шаг за шагом перемещая этот объект в небольших количествах, в которых когда-либо направляемое наклонно вниз, в зависимости от того, где он в настоящее время находится. Проблема в том, что вы действительно не делаете градиентный спуск: вместо этого вы делаете стохастический градиентный спуск, когда вы двигаетесь в направлении, беря выборку из набора векторов тренировки и перемещаясь в том, что когда-либо было похоже на образец образца. В среднем по всем данным обучения стохастический градиентный спуск должен работать, но это не гарантируется, потому что вы можете попасть в ситуацию, когда вы прыгаете вперед и назад, не делая прогресса. Медленно уменьшая скорость обучения, вы каждый раз делаете все меньше и меньше шагов, поэтому не можете застревать в бесконечном цикле.

С другой стороны, импульс делает алгоритм чем-то похожим на катящийся резиновый шар. Поскольку роль мяча имеет тенденцию идти в нисходящем направлении, но она также имеет тенденцию продолжать двигаться в том направлении, в котором она шла раньше, и если она когда-либо находится на участке, где нисходящий склон находится в одном направлении на некоторое время, он будет ускорить. Таким образом, мяч будет перепрыгивать через некоторые локальные минимумы, и он будет более устойчивым к тому, чтобы двигаться вперед и назад по цели, потому что это означает работу против силы импульса.


Имея некоторый код и думая об этом еще немного, довольно ясно, что ваша проблема заключается в обучении ранних слоев. Функции, которые вы успешно изучили, являются линейно разделяемыми, поэтому было бы разумно, что только один уровень будет правильно изучен. Я согласен с LiKao в отношении стратегий реализации в целом, хотя ваш подход должен работать. Мое предложение о том, как отладить это, заключается в том, как выглядит прогрессия весов на соединениях между входным уровнем и выходным уровнем.

Вы должны опубликовать оставшуюся реализацию Neuron.

Ответ 3

Я столкнулся с той же проблемой совсем недавно. Наконец, я нашел решение, как написать код, решающий XOR с алгоритмом MLP.

Проблема XOR кажется легкой задачей для изучения, но она не для MLP, потому что она не является линейно разделяемой. Поэтому даже если ваш MLP в порядке (я имею в виду, что в вашем коде нет ошибки), вам нужно найти хорошие параметры, чтобы узнать проблему XOR.

Два скрытых и один выходной нейрон в порядке. Главное, что вы должны установить:

  • хотя у вас всего 4 учебных образца, вам нужно пройти обучение на пару тысяч лет.
  • Если вы используете сигмовидные скрытые слои, но линейный вывод, сеть будет быстрее сходиться

Вот подробное описание и пример кода: http://freeconnection.blogspot.hu/2012/09/solving-xor-with-mlp.html

Ответ 4

Маленький намек - если выход вашего NN, похоже, дрейфует к 0,5, тогда все ОК!

Алгоритм, использующий только скорость обучения и предвзятость, слишком просто, чтобы быстро изучить XOR. Вы можете либо увеличить количество эпох, либо изменить алгоритм.

Моя рекомендация - использовать импульс:

  • 1000 эпох
  • learningRate = 0.3
  • импульс = 0,8
  • веса, взятые из [0,1]
  • отклонённая форма [-0.5, 0.5]

И какой-то критический псевдокод (при условии продолжения работы в прямом и обратном направлении):

for every edge:
    previous_edge_weight_change = -1 * learningRate * edge_source_neuron_value * edge_target_neuron_delta + previous_edge_weight * momentum

    edge_weight += previous_edge_weight_change

for every neuron:
    previous_neuron_bias_change = -1 * learningRate * neuron_delta + previous_neuron_bias_change * momentum

    bias += previous_neuron_bias_change

Ответ 5

Я предлагаю вам создать сетку (скажем, от [-5, -5] до [5,5] с шагом, равным 0,5), узнать свой MLP на XOR и применить его к сетке. По цвету вы могли видеть какую-то границу. Если вы сделаете это на каждой итерации, вы увидите эволюцию границы и сможете контролировать обучение.

Ответ 6

Прошло некоторое время с тех пор, как я в последний раз реализовал Нейронную сеть, но я думаю, что ваша ошибка в строках:

bias += Parameters.LEARNING_RATE * errorGradient;

и

connection.weight += Parameters.LEARNING_RATE * connection.input.getOutput() * errorGradient;

Первая из этих строк не должна быть вообще. Смещение лучше всего моделируется как вход нейрона, который фиксирован в 1. Это поможет сделать ваш код намного проще и чище, потому что вам не придется рассматривать отклонение каким-либо особым образом.

Другим моментом является то, что я считаю, что знак в обоих этих выражениях неверен. Подумайте об этом так:

  • Ваш градиент указывает на направление крутого подъема, поэтому, если вы пойдете в этом направлении, ваша ошибка станет больше.

  • То, что вы здесь делаете, добавляет что-то к весам, если ошибка уже положительная, т.е. вы делаете ее более позитивной. Если он отрицательный, вы вычитаете someting, т.е. Делаете его более отрицательным.

Если я не пропущу что-то о вашем определении ошибки или вычислении градиента, вы должны изменить эти строки на:

bias -= Parameters.LEARNING_RATE * errorGradient;

и

connection.weight -= Parameters.LEARNING_RATE * connection.input.getOutput() * errorGradient;

У меня была аналогичная ошибка в одной из моих ранних реализаций, и это привело к точному поведению, то есть привело к сети, которая училась в простых случаях, но не более того, когда данные обучения стали более сложными.

Ответ 7

Я немного ржавый в нейронных сетях, но я думаю, что возникла проблема с внедрением XOR с одним персептроном: в основном нейрон способен разделять две группы решений по прямой, но одна прямая линия не является достаточный для задачи XOR...

Здесь должен быть ответ!

Ответ 8

Я не вижу ничего плохого в коде, но у меня была аналогичная проблема с тем, что моя сеть не сходилась для XOR, поэтому я решил опубликовать свою рабочую конфигурацию.

3 входных нейрона (один из которых является фиксированным смещением 1,0)
3 скрытых нейрона
1 выходной нейрон

Весы, случайно выбранные между -0,5 и 0,5.
Функция активации сигмоида.

Уровень обучения = 0.2
Momentum = 0,4
Эпохи = 50 000

Конвергенция 10/10 раз.

Одна из ошибок, которые я делал, не связана с подключением входа смещения к выходному нейрону, и это означало бы, что для той же конфигурации он только конвертировал 2 из 10 раз, а остальные восемь раз терпел неудачу, потому что 1 и 1 выдавали 0,5.

Другая ошибка заключалась в том, что не хватало эпох. Если бы я делал только 1000, то на каждый тестовый случай выходы составляли около 0,5. С эпохами >= 8000 и 2000 раз для каждого тестового примера, он начал выглядеть так, как будто он работает (но только при использовании импульса).

При выполнении 50000 эпох неважно, использовался ли импульс или нет.

Еще одна вещь, которую я пробовал, заключалась в том, чтобы не применять сигмоидную функцию к выходному выводу нейронов (что, по моему мнению, было тем, что было предложено более ранним сообщением), но это разрушило сеть, потому что выходная (1 выходная) часть ошибки теперь уравнение может быть отрицательным, поскольку весы были обновлены таким образом, чтобы ошибка увеличивалась.