java - Problemas para entender el algoritmo de contrapropagación en la red neuronal
algorithm artificial-intelligence (1)
Finalmente encontré el problema. Para el XOR, no necesitaba ningún sesgo y estaba convergiendo a los valores esperados. Obtuve exactamente la salida cuando redondeas la salida final. Lo que se necesitaba era entrenar y luego validar, luego entrenar nuevamente hasta que la Red Neural fuera satisfactoria. Entrené cada set hasta la satisfacción, pero no el ENTERO una y otra vez.
// 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);
Con los datos reales, necesito un sesgo porque las salidas comenzaron a divergir. Así es como agregué el sesgo:
En la clase Neuron.java , agregué una sinapsis de sesgo con un peso y una salida de 1.0. Lo sumo con todas las otras sinapsis y luego lo puse en mi función de activación.
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);
}
[...]
En BackPropagationStrategy.java , cambio el peso y el delta de cada sesgo en el método updateWeights al que cambié el nombre de 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);
}
}
}
[...]
Con los datos reales, la red está convergiendo. Ahora es un trabajo de poda encontrar el combo de variables perfecto (si es posible) de velocidad de aprendizaje, momento, tasa de error, cantidad de neuronas, cantidad de capas ocultas, etc.
Tengo problemas para entender el algoritmo de propagación hacia atrás. Leí mucho y busqué mucho, pero no puedo entender por qué mi red neuronal no funciona. Quiero confirmar que estoy haciendo cada parte de la manera correcta.
Aquí está mi red neuronal cuando se inicializa y cuando se establece la primera línea de entradas [1, 1] y la salida [0] (como puede ver, estoy tratando de hacer la red neuronal XOR):
Tengo 3 capas: entrada, oculta y salida. La primera capa (entrada) y la capa oculta contienen 2 neuronas en las que hay 2 sinapsis cada una. La última capa (salida) contiene una neurona con 2 sinapsis también.
Una sinapsis contiene un peso y su delta anterior (al principio, es 0). La salida conectada a la sinapsis se puede encontrar con el sourceNeuron asociado a la sinapsis o en la matriz de entradas si no hay ningún sourceNeuron (como en la capa de entrada).
La clase Layer.java contiene una lista de neuronas. En mi NeuralNetwork.java , inicializo la red neuronal y luego hago un bucle en mi conjunto de entrenamiento. En cada iteración, sustituyo las entradas y los valores de salida y el tren de llamadas en mi algoritmo de BackPropagation y el algoritmo se ejecuta cierto número de tiempo (época de 1000 veces por ahora) para el conjunto actual.
La función de activación que uso es el sigmoide.
El conjunto de entrenamiento Y el conjunto de validación es (entrada1, entrada2, salida):
1,1,0
0,1,1
1,0,1
0,0,0
Aquí está mi implementación de 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);
}
[...]
}
El Synapse.java contiene:
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 ...]
El método del tren en mi clase BackpropagationStrategy.java ejecuta un bucle while y se detiene 1000 veces (época) con una línea del conjunto de entrenamiento. Se parece a esto:
this.forwardPropagation(neuralNetwork, inputs);
this.backwardPropagation(neuralNetwork, expectedOutput);
this.updateWeights(neuralNetwork);
Aquí está toda la implementación de los métodos anteriores (learningRate = 0.45 y 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);
}
}
}
}
Para el conjunto de validación, solo ejecuto esto:
this.forwardPropagation(neuralNetwork, inputs);
Y luego verifica la salida de la neurona en mi capa de salida.
¿Hice algo mal? Necesito algunas explicaciones ...
Aquí están mis resultados después de 1000 épocas:
Real: 0.0
Current: 0.025012156926937503
Real: 1.0
Current: 0.022566830709341495
Real: 1.0
Current: 0.02768416343491415
Real: 0.0
Current: 0.024903432706154027
¿Por qué las sinapsis en la capa de entrada no se actualizan? En todas partes está escrito solo para actualizar las capas ocultas y de salida.
Como puedes ver, está totalmente equivocado! No va al 1.0 solo a la salida del primer conjunto de trenes (0.0).
ACTUALIZACIÓN 1
Aquí hay una iteración sobre la red con este conjunto: [1.0,1.0.0.0]. Aquí está el resultado para el método de propagación hacia adelante:
=== 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
Actualización 2
Probablemente tengo un problema de sesgo. Lo analizaré con la ayuda de esta respuesta: Role of Bias in Neural Networks . No vuelve al siguiente conjunto de datos, así que ...