javascript - programar - redes neuronales ejemplos
Cómo entrenar correctamente mi red neuronal (1)
Estoy tratando de enseñar a una red neuronal a decidir dónde ir en función de su nivel de vida ingresado. La red neuronal siempre recibirá tres entradas [x, y, life]
. Si life => 0.2
, debe mostrar el ángulo de [x, y]
a (1, 1)
. Si la life < 0.2
, debe mostrar el ángulo de [x, y]
a (0, 0)
.
Como las entradas y salidas de las neuronas deben estar entre 0
y 1
, 2 *Math.PI
el ángulo entre 2 *Math.PI
Aquí está el código:
var network = new synaptic.Architect.Perceptron(3,4,1);
for(var i = 0; i < 50000; i++){
var x = Math.random();
var y = Math.random();
var angle1 = angleToPoint(x, y, 0, 0) / (2 * Math.PI);
var angle2 = angleToPoint(x, y, 1, 1) / (2 * Math.PI);
for(var j = 0; j < 100; j++){
network.activate([x,y,j/100]);
if(j < 20){
network.propagate(0.3, [angle1]);
} else {
network.propagate(0.3, [angle2]);
}
}
}
Pruébalo aquí: jsfiddle
Entonces, cuando ingreso la siguiente entrada [0, 1, 0.19]
, espero que la red neuronal emita algo cercano a [0.75]
( 1.5PI / 2PI
). Pero mis resultados son completamente inconsistentes y no muestran ninguna correlación con ninguna entrada dada en absoluto.
¿Qué error estoy cometiendo en la enseñanza de mi red neuronal?
He logrado enseñar una red neuronal a la salida
1
cuando la entrada[a, b, c]
conc => 0.2
y0
cuando la entrada[a, b, c]
conc < 0.2
. También me las he arreglado para enseñarle a emitir un ángulo a una determinada ubicación en función de la entrada[x, y]
, sin embargo, parece que no puedo combinarlos .
Según lo solicitado, he escrito un código que utiliza 2 redes neuronales para obtener el resultado deseado. La primera red neuronal convierte el nivel de vida a 0 o 1, y la segunda red neuronal genera un ángulo que depende del 0 o 1 que obtuvo de la primera red neuronal. Este es el código:
// This network outputs 1 when life => 0.2, otherwise 0
var network1 = new synaptic.Architect.Perceptron(3,3,1);
// This network outputs the angle to a certain point based on life
var network2 = new synaptic.Architect.Perceptron(3,3,1);
for (var i = 0; i < 50000; i++){
var x = Math.random();
var y = Math.random();
var angle1 = angleToPoint(x, y, 0, 0) / (2 * Math.PI);
var angle2 = angleToPoint(x, y, 1, 1) / (2 * Math.PI);
for(var j = 0; j < 100; j++){
network1.activate([x,y,j/100]);
if(j < 20){
network1.propagate(0.1, [0]);
} else {
network1.propagate(0.1, [1]);
}
network2.activate([x,y,0]);
network2.propagate(0.1, [angle1]);
network2.activate([x,y,1]);
network2.propagate(0.1, [angle2]);
}
}
Pruébalo aquí: jsfiddle
Como puedes ver en este ejemplo. Se las arregla para alcanzar la salida deseada bastante cerca, al agregar más iteraciones se acercará aún más.
Observaciones
Distribución sesgada muestreada como conjunto de entrenamiento
Su conjunto de entrenamiento está eligiendo el parámetro de
life
interiorfor(var j = 0; j < 100; j++)
, que está muy orientado haciaj>20
y, en consecuencia,life>0.2
. Cuenta con 4 veces más datos de entrenamiento para ese subconjunto, lo que hace que su función de entrenamiento tenga prioridad.Datos de entrenamiento no barajados
Estás entrenando secuencialmente contra el parámetro de
life
, que puede ser perjudicial. Su red terminará prestando más atención a las grandesj
s, ya que es la razón más reciente para las propagaciones de la red. Debes barajar tu conjunto de entrenamiento para evitar este sesgo.Esto se acumulará con el punto anterior, porque nuevamente estás prestando más atención a algunos subconjuntos de valores de
life
.Debes medir tu rendimiento de entrenamiento también
Tu red, a pesar de las observaciones anteriores, no era realmente tan mala. Tu error de entrenamiento no fue tan grande como tus pruebas. Esta discrepancia generalmente significa que está entrenando y probando en diferentes distribuciones de muestras.
Se podría decir que tiene dos clases de puntos de datos: los que tienen una
life>0.2
y los otros no. Pero debido a que introdujo una discontinuidad en la funciónangleToPoint
, le recomiendo que se separe en tres clases: mantenga una clase para toda lalife<0.2
(porque la función se comporta de manera continua) y divida lalife>0.2
en "arriba (1,1)" y "abajo (1,1)".Complejidad de la red
Podrías entrenar exitosamente una red para cada tarea por separado. Ahora quieres apilarlos . Este es el objetivo del aprendizaje profundo : cada capa se basa en los conceptos percibidos por la capa anterior, lo que aumenta la complejidad de los conceptos que puede aprender.
Entonces, en lugar de usar 20 nodos en una sola capa, recomiendo que uses 2 capas de 10 nodos. Esto coincide con la jerarquía de clases que mencioné en el punto anterior.
El código
Al ejecutar este código tuve un error de entrenamiento / prueba de 0.0004
/ 0.0002
.
var network = new synaptic.Architect.Perceptron(3,10,10,1);
var trainer = new synaptic.Trainer(network);
var trainingSet = [];
for(var i = 0; i < 50000; i++){
// 1st category: above vector (1,1), measure against (1,1)
var x = getRandom(0.0, 1.0);
var y = getRandom(x, 1.0);
var z = getRandom(0.2, 1);
var angle = angleToPoint(x, y, 1, 1) / (2 * Math.PI);
trainingSet.push({input: [x,y,z], output: [angle]});
// 2nd category: below vector (1,1), measure against (1,1)
var x = getRandom(0.0, 1.0);
var y = getRandom(0.0, x);
var z = getRandom(0.2, 1);
var angle = angleToPoint(x, y, 1, 1) / (2 * Math.PI);
trainingSet.push({input: [x,y,z], output: [angle]});
// 3rd category: above/below vector (1,1), measure against (0,0)
var x = getRandom(0.0, 1.0);
var y = getRandom(0.0, 1.0);
var z = getRandom(0.0, 0.2);
var angle = angleToPoint(x, y, 0, 0) / (2 * Math.PI);
trainingSet.push({input: [x,y,z], output: [angle]});
}
trainer.train(trainingSet, {
rate: 0.1,
error: 0.0001,
iterations: 50,
shuffle: true,
log: 1,
cost: synaptic.Trainer.cost.MSE
});
testSet = [
{input: [0,1,0.25], output: [angleToPoint(0, 1, 1, 1) / (2 * Math.PI)]},
{input: [1,0,0.35], output: [angleToPoint(1, 0, 1, 1) / (2 * Math.PI)]},
{input: [0,1,0.10], output: [angleToPoint(0, 1, 0, 0) / (2 * Math.PI)]},
{input: [1,0,0.15], output: [angleToPoint(1, 0, 0, 0) / (2 * Math.PI)]}
];
$(''html'').append(''<p>Train:</p> '' + JSON.stringify(trainer.test(trainingSet)));
$(''html'').append(''<p>Tests:</p> '' + JSON.stringify(trainer.test(testSet)));
$(''html'').append(''<p>1st:</p> '')
$(''html'').append(''<p>Expect:</p> '' + angleToPoint(0, 1, 1, 1) / (2 * Math.PI));
$(''html'').append(''<p>Received: </p> '' + network.activate([0, 1, 0.25]));
$(''html'').append(''<p>2nd:</p> '')
$(''html'').append(''<p>Expect:</p> '' + angleToPoint(1, 0, 1, 1) / (2 * Math.PI));
$(''html'').append(''<p>Received: </p> '' + network.activate([1, 0, 0.25]));
$(''html'').append(''<p>3rd:</p> '')
$(''html'').append(''<p>Expect:</p> '' + angleToPoint(0, 1, 0, 0) / (2 * Math.PI));
$(''html'').append(''<p>Received: </p> '' + network.activate([0, 1, 0.15]));
$(''html'').append(''<p>4th:</p> '')
$(''html'').append(''<p>Expect:</p> '' + angleToPoint(1, 0, 0, 0) / (2 * Math.PI));
$(''html'').append(''<p>Received: </p> '' + network.activate([1, 0, 0.15]));
function angleToPoint(x1, y1, x2, y2){
var angle = Math.atan2(y2 - y1, x2 - x1);
if(angle < 0){
angle += 2 * Math.PI;
}
return angle;
}
function getRandom (min, max) {
return Math.random() * (max - min) + min;
}
Más observaciones
Como mencioné en los comentarios y en el chat, no hay tal cosa como "ángulo entre (x, y) y (0,0)", porque la noción de ángulo entre vectores se toma generalmente como la diferencia entre sus direcciones y (0,0)
no tiene dirección.
Su función angleToPoint(p1, p2)
devuelve en su lugar la dirección de (p1-p2). Para p2 = (0,0)
, eso significa que el ángulo entre p1 y el eje x
bien. Pero para p1 = (1,1)
y p2 = (1,0)
no retornará 45 grados. Para p1 = p2, es indefinido en lugar de cero.