Проблема, связанная с алгоритмом Backpropagation в нейронной сети
У меня возникли проблемы с пониманием алгоритма backpropagation. Я много читал и много искал, но я не понимаю, почему моя нейронная сеть не работает. Я хочу подтвердить, что я делаю каждую часть правильно.
Вот моя нейронная сеть, когда она инициализируется, и когда установлена первая строка входов [1, 1] и выход [0] (как вы можете видеть, я пытаюсь выполнить XOR Neural Network):
![My Neural Network]()
У меня 3 слоя: вход, скрытый и выходной. Первый слой (вход) и скрытый слой содержат 2 нейрона, в которых есть по 2 синапса. Последний слой (вывод) содержит один нейрон с 2 синапсами.
Синапс содержит вес и его предыдущую дельта (в начале это 0). Выход, подключенный к синапсу, можно найти с помощью источникаNeuron, связанного с синапсом или в массиве входов, если нет источникаNeuron (как во входном слое).
Класс Layer.java содержит список нейронов. В моей NeuralNetwork.java, я инициализирую Нейронную сеть, затем зациклирую в своем учебном наборе. На каждой итерации я заменяю входы и выходные значения и набираю поезд по моему алгоритму BackPropagation Algorithm, и алгоритм запускает определенное количество времени (эпоха 1000 раз на данный момент) для текущего набора.
Используемая активация - сигмоида.
Набор для обучения И набор проверки (вход 1, вход 2, выход):
1,1,0
0,1,1
1,0,1
0,0,0
Вот моя реализация Neuron.java:
public class Neuron {
private IActivation activation;
private ArrayList<Synapse> synapses; // Inputs
private double output; // Output
private double errorToPropagate;
public Neuron(IActivation activation) {
this.activation = activation;
this.synapses = new ArrayList<Synapse>();
this.output = 0;
this.errorToPropagate = 0;
}
public void updateOutput(double[] inputs) {
double sumWeights = this.calculateSumWeights(inputs);
this.output = this.activation.activate(sumWeights);
}
public double calculateSumWeights(double[] inputs) {
double sumWeights = 0;
int index = 0;
for (Synapse synapse : this.getSynapses()) {
if (inputs != null) {
sumWeights += synapse.getWeight() * inputs[index];
} else {
sumWeights += synapse.getWeight() * synapse.getSourceNeuron().getOutput();
}
index++;
}
return sumWeights;
}
public double getDerivative() {
return this.activation.derivative(this.output);
}
[...]
}
Synapse.java содержит:
public Synapse(Neuron sourceNeuron) {
this.sourceNeuron = sourceNeuron;
Random r = new Random();
this.weight = (-0.5) + (0.5 - (-0.5)) * r.nextDouble();
this.delta = 0;
}
[... getter and setter ...]
Метод train в моем классе BackpropagationStrategy.java запускает цикл while и останавливается после 1000 раз (эпоха) одной строкой набора тренировок. Это выглядит так:
this.forwardPropagation(neuralNetwork, inputs);
this.backwardPropagation(neuralNetwork, expectedOutput);
this.updateWeights(neuralNetwork);
Вот вся реализация вышеперечисленных методов (learningRate = 0.45 и momentum = 0.9):
public void forwardPropagation(NeuralNetwork neuralNetwork, double[] inputs) {
for (Layer layer : neuralNetwork.getLayers()) {
for (Neuron neuron : layer.getNeurons()) {
if (layer.isInput()) {
neuron.updateOutput(inputs);
} else {
neuron.updateOutput(null);
}
}
}
}
public void backwardPropagation(NeuralNetwork neuralNetwork, double realOutput) {
Layer lastLayer = null;
// Loop à travers les hidden layers et le output layer uniquement
ArrayList<Layer> layers = neuralNetwork.getLayers();
for (int i = layers.size() - 1; i > 0; i--) {
Layer layer = layers.get(i);
for (Neuron neuron : layer.getNeurons()) {
double errorToPropagate = neuron.getDerivative();
// Output layer
if (layer.isOutput()) {
errorToPropagate *= (realOutput - neuron.getOutput());
}
// Hidden layers
else {
double sumFromLastLayer = 0;
for (Neuron lastLayerNeuron : lastLayer.getNeurons()) {
for (Synapse synapse : lastLayerNeuron.getSynapses()) {
if (synapse.getSourceNeuron() == neuron) {
sumFromLastLayer += (synapse.getWeight() * lastLayerNeuron.getErrorToPropagate());
break;
}
}
}
errorToPropagate *= sumFromLastLayer;
}
neuron.setErrorToPropagate(errorToPropagate);
}
lastLayer = layer;
}
}
public void updateWeights(NeuralNetwork neuralNetwork) {
for (int i = neuralNetwork.getLayers().size() - 1; i > 0; i--) {
Layer layer = neuralNetwork.getLayers().get(i);
for (Neuron neuron : layer.getNeurons()) {
for (Synapse synapse : neuron.getSynapses()) {
double delta = this.learningRate * neuron.getError() * synapse.getSourceNeuron().getOutput();
synapse.setWeight(synapse.getWeight() + delta + this.momentum * synapse.getDelta());
synapse.setDelta(delta);
}
}
}
}
Для набора проверки я запускаю только это:
this.forwardPropagation(neuralNetwork, inputs);
И затем проверьте вывод нейрона в моем выходном слое.
Я сделал что-то не так? Нужны некоторые объяснения...
Вот мои результаты после 1000 эпохи:
Real: 0.0
Current: 0.025012156926937503
Real: 1.0
Current: 0.022566830709341495
Real: 1.0
Current: 0.02768416343491415
Real: 0.0
Current: 0.024903432706154027
Почему синапсы во входном слое не обновляются? Всюду он записывается только для обновления скрытых и выходных уровней.
Как вы можете видеть, это совершенно неправильно! Он не переходит к 1.0 только к первому выходному набору (0.0).
ОБНОВЛЕНИЕ 1
Вот одна итерация по сети с этим набором: [1.0,1.0,0.0]. Вот результат для метода прямого распространения:
=== Input Layer
== Neuron #1
= Synapse #1
Weight: -0.19283583155573614
Input: 1.0
= Synapse #2
Weight: 0.04023817185601586
Input: 1.0
Sum: -0.15259765969972028
Output: 0.461924442180935
== Neuron #2
= Synapse #1
Weight: -0.3281099260608612
Input: 1.0
= Synapse #2
Weight: -0.4388250065958519
Input: 1.0
Sum: -0.7669349326567131
Output: 0.31714251453174147
=== Hidden Layer
== Neuron #1
= Synapse #1
Weight: 0.16703288052854093
Input: 0.461924442180935
= Synapse #2
Weight: 0.31683996162148054
Input: 0.31714251453174147
Sum: 0.17763999229679783
Output: 0.5442935820534444
== Neuron #2
= Synapse #1
Weight: -0.45330313978424686
Input: 0.461924442180935
= Synapse #2
Weight: 0.3287014377113835
Input: 0.31714251453174147
Sum: -0.10514659949771789
Output: 0.47373754172497556
=== Output Layer
== Neuron #1
= Synapse #1
Weight: 0.08643751629154495
Input: 0.5442935820534444
= Synapse #2
Weight: -0.29715579267218695
Input: 0.47373754172497556
Sum: -0.09372646936373039
Output: 0.47658552081912403
Обновление 2
У меня, вероятно, есть проблема смещения. Я рассмотрю это с помощью этого ответа: Роль смещения в нейронных сетях. Он не переключается обратно на следующий набор данных, поэтому...
Ответы
Ответ 1
Наконец-то я нашел проблему. Для XOR я не нуждался в каких-либо смещениях, и он сходился к ожидаемым значениям. Я получаю точно результат, когда вы завершаете окончательный вывод. Нужно было тренироваться, затем проверять, а затем тренироваться до тех пор, пока нейронная сеть не будет удовлетворительной. Я тренировал каждый набор до удовлетворения, но не ВСЕ, заданные снова и снова.
// Initialize the Neural Network
algorithm.initialize(this.numberOfInputs);
int index = 0;
double errorRate = 0;
// Loop until satisfaction or after some iterations
do {
// Train the Neural Network
algorithm.train(this.trainingDataSets, this.numberOfInputs);
// Validate the Neural Network and return the error rate
errorRate = algorithm.run(this.validationDataSets, this.numberOfInputs);
index++;
} while (errorRate > minErrorRate && index < numberOfTrainValidateIteration);
С реальными данными мне нужно смещение, потому что выходы начали расходиться. Вот как я добавил смещение:
В классе Neuron.java я добавил синапс смещения с весом и выходом 1.0. Я суммирую его со всеми другими синапсами, а затем помещаю его в свою функцию активации.
public class Neuron implements Serializable {
[...]
private Synapse bias;
public Neuron(IActivation activation) {
[...]
this.bias = new Synapse(this);
this.bias.setWeight(0.5); // Set initial weight OR keep the random number already set
}
public void updateOutput(double[] inputs) {
double sumWeights = this.calculateSumWeights(inputs);
this.output = this.activation.activate(sumWeights + this.bias.getWeight() * 1.0);
}
[...]
В BackPropagationStrategy.java я изменяю вес и дельта каждого смещения в методе updateWeights, который я переименовал updateWeightsAndBias.
public class BackPropagationStrategy implements IStrategy, Serializable {
[...]
public void updateWeightsAndBias(NeuralNetwork neuralNetwork, double[] inputs) {
for (int i = neuralNetwork.getLayers().size() - 1; i >= 0; i--) {
Layer layer = neuralNetwork.getLayers().get(i);
for (Neuron neuron : layer.getNeurons()) {
[...]
Synapse bias = neuron.getBias();
double delta = learning * 1.0;
bias.setWeight(bias.getWeight() + delta + this.momentum * bias.getDelta());
bias.setDelta(delta);
}
}
}
[...]
С реальными данными Сеть сходится. В настоящее время обрезка заключается в том, чтобы найти идеальные переменные (если возможно), скорость обучения, импульс, частоту ошибок, количество нейронов, количество скрытых слоев и т.д.