El entrenamiento de principio a fin
En resumen: Entrenar una red neuronal es un ciclo sobre épocas: mezclar, lotes, una pasada hacia adelante, retropropagación y un paso, hasta que el error de validación deja de bajar.
Problema
La red dice −0,23 días
La página de cobro debería decir «llega en 2 días». El tiempo de entrega depende de la distancia, de si el pedido se hace después de las 16:00 (entonces sale al día siguiente) y un poco del peso. Una recta de la regresión lineal no sigue esta curva: en la ciudad es más o menos un día, hasta 200 km crece rápido, luego cada vez más despacio.
La red de la lección de retropropagación puede seguir una curva. Aquí está, con 4 neuronas ocultas y pesos aleatorios, antes de entrenar:
var rng = new Rng(42);var net = new Net(4, 3);net.Randomize(rng, 1.0);Console.WriteLine($"error (MSE): {net.Loss(trainX, trainY):F4}");foreach (int k in new[] { 0, 12, 23 }) Console.WriteLine($"{train[k][0]} km: la red dice {net.Predict(trainX[k]):F2}, " + $"real {trainY[k]:F1} días");error (MSE): 7.18555 km: la red dice -0.23, real 1.0 días150 km: la red dice -0.17, real 2.3 días520 km: la red dice 0.18, real 3.0 díasUn pedido a 5 km llega en −0,23 días. En la lección de retropropagación viste un paso para un ejemplo. ¿Cuántos pasos necesitas, con qué ejemplos, en qué orden, y cuándo paras?
La idea
El ciclo de entrenamiento
Entrenar es un ciclo que sigue girando hasta que la red deja de mejorar:
- Pesos iniciales: números aleatorios pequeños de un generador con semilla.
- Época: una pasada por todos los ejemplos de entrenamiento. Antes de cada época los ejemplos se mezclan, para que la red no aprenda su orden.
- Lote: unos cuantos ejemplos a la vez. Cada uno recibe una pasada hacia adelante y un error, luego una pasada hacia atrás. Las pendientes se promedian y la red da un paso.
- Comprobación: cada 100 épocas el programa imprime el error en el entrenamiento y en la validación.
Este es el ciclo de entrenamiento. Los números que imprime, dibujados como un gráfico, son la curva del error (loss curve). Con ella puedes saber si la red está aprendiendo, si la tasa es buena y cuándo parar.
Los datos son 32 pedidos de la tienda: 24 para entrenar y 8 para la validación. Con los pedidos de validación la red nunca da un paso. Son la medida para pedidos nuevos.
Míralo
Los datos y la red
Los pedidos, escritos en el código:
// pedido: km, después de las 16:00 (1 o 0), kg, días hasta la entregadouble[][] train =[ [5, 0, 1.2, 1.0], [12, 1, 3.5, 1.6], [18, 0, 0.8, 1.0], [25, 0, 6.0, 1.2], [28, 1, 2.1, 1.8], [35, 0, 4.4, 1.3], [48, 1, 0.5, 2.2], [60, 0, 9.0, 2.5], [75, 0, 1.7, 1.6], [90, 1, 2.9, 2.6], [110, 0, 5.5, 1.9], [130, 1, 0.9, 2.7], [150, 0, 3.1, 2.3], [170, 0, 8.2, 3.3], [185, 1, 1.4, 3.1], [195, 0, 2.6, 2.3], [210, 0, 4.8, 2.6], [240, 1, 0.6, 3.3], [280, 0, 8.6, 3.6], [320, 0, 1.9, 2.7], [360, 1, 3.3, 3.6], [410, 0, 5.0, 2.8], [460, 1, 2.2, 3.6], [520, 0, 0.7, 3.0],];// validación: pedidos que la red no ve durante el entrenamientodouble[][] valid =[ [8, 1, 2.4, 1.6], [22, 0, 8.8, 1.0], [40, 0, 1.1, 1.4], [95, 1, 7.9, 2.5], [160, 0, 2.0, 2.3], [205, 1, 3.9, 3.1], [300, 0, 9.4, 2.6], [480, 1, 4.6, 3.6],];Las características se llevan a escalas parecidas: kilómetros en cientos, peso en decenas de kilogramos. Si no, 520 km taparían todo lo demás:
// características en escalas parecidas: cientos de km, 0 o 1, decenas de kgstatic double[][] Inputs(double[][] orders) => orders.Select(o => new[] { o[0] / 100, o[1], o[2] / 10 }).ToArray();
static double[] Targets(double[][] orders) => orders.Select(o => o[3]).ToArray();El azar viene de nuestro propio generador con semilla. Una misma semilla da los mismos números en cualquier computadora y en los cuatro lenguajes, así que la lección imprime lo mismo en todas partes. El número 48271 y la división por 2³¹ − 1 son un generador antiguo y bien probado (Park–Miller). El producto se queda por debajo de 2⁵³, así que es exacto incluso en JavaScript:
class Rng(long seed){ long state = seed;
// el siguiente número entre 0 y 1; una misma semilla da los mismos números public double Next() { state = state * 48271 % 2147483647; return (double)state / 2147483647; }}new Random(42) en C# también tiene semilla. Aquí el generador es el nuestro, así que es el mismo que en los otros tres lenguajes.La red es la de la lección de retropropagación, metida en una clase: 3 entradas, 4 neuronas ocultas con sigmoide y una salida sin activación, porque los días no son una probabilidad. Step hace una pasada hacia adelante y otra hacia atrás por cada pedido del lote, suma las pendientes y mueve cada peso una vez, según la pendiente media:
class Net{ public readonly double[][] W1; // entrada → capa oculta public readonly double[] B1; public readonly double[] W2; // capa oculta → salida public double B2; readonly double[] h; // las salidas de la capa oculta del último predict
public Net(int hidden, int inputs) { W1 = new double[hidden][]; for (int j = 0; j < hidden; j++) W1[j] = new double[inputs]; B1 = new double[hidden]; W2 = new double[hidden]; h = new double[hidden]; }
// pesos aleatorios de -scale a scale; los sesgos siguen en 0 public void Randomize(Rng rng, double scale) { foreach (var row in W1) for (int i = 0; i < row.Length; i++) row[i] = (rng.Next() * 2 - 1) * scale; for (int j = 0; j < W2.Length; j++) W2[j] = (rng.Next() * 2 - 1) * scale; }
static double Sigmoid(double z) => 1 / (1 + Math.Exp(-z));
public double Predict(double[] x) { double y = B2; for (int j = 0; j < W2.Length; j++) { double z = B1[j]; for (int i = 0; i < x.Length; i++) z += W1[j][i] * x[i]; h[j] = Sigmoid(z); y += W2[j] * h[j]; } return y; }
public double Loss(double[][] xs, double[] ys) { double total = 0; for (int k = 0; k < xs.Length; k++) { double e = Predict(xs[k]) - ys[k]; total += e * e; } return total / xs.Length; }
// un paso: la pendiente media sobre los ejemplos del lote public void Step(double[][] xs, double[] ys, int[] batch, double rate) { int hidden = W2.Length, inputs = xs[0].Length; var gw1 = new double[hidden, inputs]; double[] gb1 = new double[hidden], gw2 = new double[hidden]; double gb2 = 0; foreach (int k in batch) { double d = 2 * (Predict(xs[k]) - ys[k]); gb2 += d; for (int j = 0; j < hidden; j++) { gw2[j] += d * h[j]; double dz = d * W2[j] * h[j] * (1 - h[j]); gb1[j] += dz; for (int i = 0; i < inputs; i++) gw1[j, i] += dz * xs[k][i]; } } int n = batch.Length; B2 -= rate * gb2 / n; for (int j = 0; j < hidden; j++) { W2[j] -= rate * gw2[j] / n; B1[j] -= rate * gb1[j] / n; for (int i = 0; i < inputs; i++) W1[j][i] -= rate * gw1[j, i] / n; } }}La mezcla es Fisher–Yates: de atrás hacia adelante cada lugar se intercambia con un lugar al azar anterior a él.
// Fisher–Yates: todos los órdenes son igual de probablesstatic void Shuffle(int[] order, Rng rng){ for (int i = order.Length - 1; i > 0; i--) { int k = (int)(rng.Next() * (i + 1)); (order[i], order[k]) = (order[k], order[i]); }}Míralo
El ciclo y la curva del error
var rng = new Rng(42);var net = new Net(4, 3);net.Randomize(rng, 1.0);int size = 8;double rate = 0.05;int[] order = Enumerable.Range(0, trainX.Length).ToArray();Console.WriteLine("época entrenamiento validación");for (int epoch = 1; epoch <= 1000; epoch++){ Shuffle(order, rng); for (int start = 0; start < order.Length; start += size) { int end = Math.Min(start + size, order.Length); int[] batch = order[start..end]; net.Step(trainX, trainY, batch, rate); } if (epoch % 100 == 0) { double t = net.Loss(trainX, trainY); double v = net.Loss(validX, validY); Console.WriteLine($"{epoch,5}{t,15:F4}{v,12:F4}"); }}Paso a paso, en palabras
- Semilla 42: cada ejecución empieza con los mismos pesos aleatorios. Antes del entrenamiento el error es 7,19, muy por encima del gráfico.
- Una época es una pasada por los 24 pedidos de entrenamiento. Primero se mezclan, con el mismo generador.
- Un lote de 8 pedidos: hacia adelante, error, hacia atrás, un paso. Tres pasos por época, 3000 en todo el entrenamiento.
- Época 100: entrenamiento 0,1365, validación 0,0756. Con los pedidos de validación nunca se dio un paso.
- La validación es más baja en la época 200: 0,0705. Después sube, mientras el entrenamiento sigue bajando.
- Época 1000: entrenamiento 0,0381, validación 0,2371. La red se aprende de memoria tres paquetes pesados que llegaron tarde por casualidad.
- La mejor red está cerca de la época 200, no de la 1000. Se para cuando la validación deja de bajar.
El ciclo completo: 1000 épocas, lotes de 8, tasa 0,05:
var rng = new Rng(42);var net = new Net(4, 3);net.Randomize(rng, 1.0);int size = 8;double rate = 0.05;int[] order = Enumerable.Range(0, trainX.Length).ToArray();Console.WriteLine("época entrenamiento validación");for (int epoch = 1; epoch <= 1000; epoch++){ Shuffle(order, rng); for (int start = 0; start < order.Length; start += size) { int end = Math.Min(start + size, order.Length); int[] batch = order[start..end]; net.Step(trainX, trainY, batch, rate); } if (epoch % 100 == 0) { double t = net.Loss(trainX, trainY); double v = net.Loss(validX, validY); Console.WriteLine($"{epoch,5}{t,15:F4}{v,12:F4}"); }}época entrenamiento validación 100 0.1365 0.0756 200 0.1041 0.0705 300 0.0826 0.0776 400 0.0667 0.1048 500 0.0556 0.1350 600 0.0486 0.1718 700 0.0443 0.1967 800 0.0417 0.2202 900 0.0396 0.2243 1000 0.0381 0.2371El entrenamiento baja de 7,19 a 0,0381. La validación baja hasta la época 200 y luego sube: en la época 1000 es 0,2371, más de tres veces peor que la mejor. La red sigue ajustándose a los pedidos de entrenamiento, incluido lo que hay en ellos de casualidad. Volveremos a eso al final de la lección.
Pruébalo
Predice la salida
Si el lote es de 10 y hay 24 ejemplos, ¿cuántos pasos da el entrenamiento en 1000 épocas?
int examples = 24, size = 10, steps = 0;for (int epoch = 1; epoch <= 1000; epoch++) for (int start = 0; start < examples; start += size) steps++;Console.WriteLine($"pasos: {steps}");24 ejemplos en grupos de 10 son tres lotes: 10, 10 y 4. El último es más pequeño, pero es un paso como los demás, así que son 3 por época y 3000 en total. En Step la pendiente media se calcula sobre el número de ejemplos del lote, así que el lote pequeño también da un paso de tamaño normal.
La idea
Lotes: todos, uno por uno o 8
¿Cuántos ejemplos deben entrar en un paso? Aquí está la misma red durante 40 épocas con un lote de 24 (todos a la vez), 8 y 1. Después de cada paso el programa mide el error sobre los 24 pedidos y cuenta cuántas veces el paso lo subió:
foreach (int size in new[] { 24, 8, 1 }){ var rng = new Rng(42); var net = new Net(4, 3); net.Randomize(rng, 1.0); int[] order = Enumerable.Range(0, trainX.Length).ToArray(); int steps = 0, up = 0; double before = net.Loss(trainX, trainY); for (int epoch = 1; epoch <= 40; epoch++) { Shuffle(order, rng); for (int start = 0; start < order.Length; start += size) { int end = Math.Min(start + size, order.Length); net.Step(trainX, trainY, order[start..end], 0.05); double after = net.Loss(trainX, trainY); steps++; if (after > before) up++; before = after; } } Console.WriteLine($"lote {size,2}: {steps,3} pasos, {up,3} arriba, error {before:F4}");}lote 24: 40 pasos, 0 arriba, error 0.2913lote 8: 120 pasos, 21 arriba, error 0.1840lote 1: 960 pasos, 495 arriba, error 0.1238- Todos a la vez: la pendiente es la pendiente exacta de todo el error, así que ningún paso lo sube. Pero hay un solo paso por época, y tras 40 épocas la red es la que está más lejos.
- Un ejemplo por vez: 24 pasos por época, así que en 40 épocas llega más abajo. Pero la pendiente de un ejemplo es una estimación burda de la pendiente de todos: 495 de 960 pasos suben el error.
- Un lote de 8: tres pasos por época y solo 21 de 120 hacia arriba.
Esto se llama minilote (mini-batch): un paso según la pendiente media de unos cuantos ejemplos. El lote es el compromiso que usa casi todo el mundo. En una tarjeta gráfica los ejemplos de un lote se calculan a la vez, así que en proyectos reales los lotes son de decenas o cientos de ejemplos.
Pruébalo
Completa
Las pendientes del lote se suman, y el paso va según la pendiente media. ¿Entre qué divides?
int n = ;
B2 -= rate * gb2 / n;Entre el número de ejemplos del lote: batch.Length. Sin la división, un lote de 24 da un paso 24 veces mayor que un lote de 1, y cambiar el lote cambia también la tasa de aprendizaje.
La idea
Una mala tasa, un mal comienzo
La curva te dice qué anda mal. Las tres tasas de la lección del descenso del gradiente, pero en una red. El error en las épocas 50, 100 y 200:
foreach (double rate in new[] { 0.005, 0.05, 2.0 }){ var rng = new Rng(42); var net = new Net(4, 3); net.Randomize(rng, 1.0); int[] order = Enumerable.Range(0, trainX.Length).ToArray(); Console.Write($"tasa {rate:F3}:"); for (int epoch = 1; epoch <= 200; epoch++) { TrainEpoch(net, trainX, trainY, order, 8, rate, rng); double loss = net.Loss(trainX, trainY); if (double.IsNaN(loss) || loss > 1e6) { Console.Write($" se disparó en la época {epoch}"); break; } if (epoch is 50 or 100 or 200) Console.Write($" {loss:F4}"); } Console.WriteLine();}tasa 0.005: 0.3892 0.3201 0.2472tasa 0.050: 0.1698 0.1365 0.1041tasa 2.000: se disparó en la época 2Con 0,005 la red aprende, pero despacio: en la época 200 (0,2472) está peor que 0,05 en la época 50 (0,1698). La curva baja con suavidad y no deja de bajar, lo que significa que puedes subir la tasa. Con 2 cada paso se pasa del fondo y la siguiente pendiente es aún más empinada. El error supera el millón ya en la segunda época, y después llegan infinito y NaN. Por eso el programa lo comprueba y se detiene.
El comienzo también importa:
var rng = new Rng(42);var zeros = new Net(4, 3); // sin Randomize: todos los pesos son 0int[] order = Enumerable.Range(0, trainX.Length).ToArray();for (int epoch = 1; epoch <= 200; epoch++) TrainEpoch(zeros, trainX, trainY, order, 8, 0.05, rng);Console.WriteLine($"ceros: error {zeros.Loss(trainX, trainY):F4}");for (int j = 0; j < zeros.W1.Length; j++) Console.WriteLine($" neurona {j + 1}: " + string.Join(" ", zeros.W1[j].Select(x => x.ToString("F2"))));
foreach (double scale in new double[] { 8, 1 }){ rng = new Rng(42); var net = new Net(4, 3); net.Randomize(rng, scale); double first = net.Loss(trainX, trainY); order = Enumerable.Range(0, trainX.Length).ToArray(); for (int epoch = 1; epoch <= 200; epoch++) TrainEpoch(net, trainX, trainY, order, 8, 0.05, rng); Console.WriteLine($"pesos ×{scale}: al principio {first:F4}, tras 200 épocas {net.Loss(trainX, trainY):F4}");}ceros: error 0.1127 neurona 1: 0.99 0.47 0.19 neurona 2: 0.99 0.47 0.19 neurona 3: 0.99 0.47 0.19 neurona 4: 0.99 0.47 0.19pesos ×8: al principio 33.9817, tras 200 épocas 0.2971pesos ×1: al principio 7.1855, tras 200 épocas 0.1041- Ceros. Las cuatro neuronas ocultas empiezan idénticas, reciben pendientes idénticas y dan pasos idénticos. Tras 200 épocas sus pesos son iguales hasta el último dígito: la red tiene una neurona oculta, repetida cuatro veces. Los pesos iniciales aleatorios existen para que las neuronas se vuelvan distintas.
- Pesos ×8. Las sumas en la sigmoide son grandes, así que para la mayoría de los pedidos vale casi 0 o casi 1, donde es plana. Una curva plana significa una pendiente casi nula y pasos diminutos: tras 200 épocas el error es casi tres veces mayor que con pesos ×1.
Pruébalo
Encuentra el error
Un colega escribe el ciclo a su manera y mezcla él mismo los arreglos en lugar de los índices. El error se queda por encima de 0,6 y no baja, por muchas épocas que ejecute. ¿En qué línea está el error?
Elige la línea con el error.
Las dos mezclas toman números aleatorios distintos del generador, así que después los pedidos y las respuestas ya no están en el mismo orden. La red aprende, por ejemplo, un pedido a 5 km con la respuesta de un pedido a 410 km. Lo mejor que puede hacer es decir el promedio para todos, y el error se queda alrededor de la dispersión de las respuestas. La solución es como en la lección: mezclar los índices (order) y tomar el pedido y la respuesta con el mismo índice.
La idea
Cuándo parar
¿Qué se aprende exactamente de memoria la red? Entre los pedidos de entrenamiento hay tres paquetes pesados (9,0 kg, 8,2 kg y 8,6 kg) que llegaron un día entero tarde por casualidad. Aquí están los tres paquetes pesados de la validación, que no llegaron tarde:
var rng = new Rng(42);var net = new Net(4, 3);net.Randomize(rng, 1.0);int[] order = Enumerable.Range(0, trainX.Length).ToArray();for (int epoch = 1; epoch <= 1000; epoch++){ TrainEpoch(net, trainX, trainY, order, 8, 0.05, rng); if (epoch is 200 or 1000) { Console.WriteLine($"época {epoch}:"); foreach (int k in new[] { 1, 3, 6 }) // los paquetes pesados de la validación Console.WriteLine($" {valid[k][0]} km, {valid[k][2]:F1} kg: " + $"{net.Predict(validX[k]):F2} (real {validY[k]:F1})"); }}época 200: 22 km, 8.8 kg: 1.31 (real 1.0) 95 km, 7.9 kg: 2.53 (real 2.5) 300 km, 9.4 kg: 3.13 (real 2.6)época 1000: 22 km, 8.8 kg: 1.84 (real 1.0) 95 km, 7.9 kg: 3.09 (real 2.5) 300 km, 9.4 kg: 3.38 (real 2.6)En la época 200 las predicciones están cerca de la realidad. En la época 1000 la red ha aprendido «lo pesado viaja más lento»: el paquete a 22 km recibe 1,84 días en lugar de 1,0. Una regla que en los datos es solo casualidad es sobreajuste: el entrenamiento mejora, y los pedidos nuevos empeoran.
Por eso paras cuando la validación deja de bajar. Esto es la parada temprana (early stopping). El programa comprueba la validación cada 10 épocas y guarda una instantánea (una copia) de la mejor red. Si no hay mejora durante 100 épocas, se detiene y devuelve la instantánea:
// una copia de los pesos: una instantánea de la red en este momentopublic Net Copy(){ var c = new Net(W2.Length, W1[0].Length); for (int j = 0; j < W2.Length; j++) { W1[j].CopyTo(c.W1[j], 0); c.B1[j] = B1[j]; c.W2[j] = W2[j]; } c.B2 = B2; return c;}var rng = new Rng(42);var net = new Net(4, 3);net.Randomize(rng, 1.0);int[] order = Enumerable.Range(0, trainX.Length).ToArray();// revisar cada 10 épocas; parar tras 100 sin mejoradouble best = double.MaxValue;int bestEpoch = 0, epoch = 0;var bestNet = net.Copy();while (epoch < 1000 && epoch - bestEpoch < 100){ epoch++; TrainEpoch(net, trainX, trainY, order, 8, 0.05, rng); if (epoch % 10 == 0) { double loss = net.Loss(validX, validY); if (loss < best) { best = loss; bestEpoch = epoch; bestNet = net.Copy(); } }}Console.WriteLine($"validación más baja: {best:F4} en la época {bestEpoch}");Console.WriteLine($"me detuve en la época {epoch}");Console.WriteLine($"conservo la red de la época {bestEpoch}: {bestNet.Loss(validX, validY):F4}");validación más baja: 0.0667 en la época 230me detuve en la época 330conservo la red de la época 230: 0.0667La mejor validación es 0,0667 en la época 230, mejor que cualquier punto de la curva de arriba, porque la comprobación es más frecuente. El entrenamiento se detiene en la época 330 en lugar de la 1000, y la red conservada es la de la época 230.
La validación decidió cuándo parar, así que ya no es una medida honesta para pedidos nuevos. La nota final se toma sobre una tercera parte de los datos, el conjunto de pruebas, que no participó en ninguna decisión.
La idea
Cuándo perjudica
- Una red para todo. 32 pedidos y tres características son pocos datos: una recta con unas cuantas características o una fórmula más simple suele bastar, es más fácil de explicar y no aprende de memoria. Una red compensa cuando las reglas son demasiadas para escribirlas y hay suficientes ejemplos.
- Una sola ejecución. Otra semilla da otros pesos iniciales y otra curva. Una ejecución puede ser suerte. Compara los cambios con varias semillas y mira el promedio.
- Un conjunto de validación pequeño. Ocho pedidos son una medida ruidosa: un pedido raro mueve la curva. Cuantos menos datos tienes, con más cuidado lees un solo punto.
- La parada temprana no arregla los datos. Los tres retrasos por casualidad siguen ahí. Es mejor encontrarlos y quitarlos o corregirlos, y si el peso de verdad no influye, no darlo como característica.
- El costo. Aquí el entrenamiento tarda menos de un segundo. Las redes reales entrenan durante horas o semanas en muchas tarjetas gráficas. Los clústeres de entrenamiento y poner un modelo en producción son infraestructura y se enseñan en el curso de DevOps ZeroToOps.
Más precisamente, para curiosos
El paso como fórmula. El lote tiene B ejemplos, y η es la tasa. Para cada peso w:
w ← w − η · (1/B) · Σ ∂L/∂w
En palabras: suma la pendiente del error respecto a w para cada ejemplo del lote, divide entre B, multiplica por la tasa y resta. ∂L/∂w es la pendiente que calcula la retropropagación para un peso. Cuando B es todos los ejemplos, es el descenso del gradiente corriente. Con B = 1 se llama estocástico (SGD), y con lotes es SGD por minilotes.
Por qué 2 · (y − t). El error de un ejemplo es (y − t)². Su pendiente respecto a y es 2 · (y − t): si la salida está 0,5 por encima de la realidad, la pendiente es 1, y si está 0,5 por debajo, es −1. Ahí empieza la retropropagación en Step.
Las herramientas reales. La práctica del aprendizaje automático es sobre todo en Python. En PyTorch el mismo ciclo es: DataLoader(..., batch_size=8, shuffle=True) da los lotes, optimizer.zero_grad() reinicia las pendientes (nuestros arreglos nuevos al inicio del paso), loss.backward() es la retropropagación, y optimizer.step() mueve los pesos. La semilla es torch.manual_seed(42). En C# lo mismo lo hacen ML.NET o TorchSharp, en Java DJL o Tribuo, y en JavaScript TensorFlow.js, mientras que ONNX Runtime ejecuta un modelo ya entrenado. El ciclo es el mismo, solo que las matrices se calculan en una tarjeta gráfica.
Pasos más inteligentes. Casi nadie usa el paso básico de la lección. El momentum recuerda la dirección de los últimos pasos y atraviesa con más suavidad el ruido de los lotes. Adam lleva una tasa propia para cada peso. A menudo la tasa se baja con el tiempo (un learning rate schedule): pasos grandes al principio, pequeños al final.
Los pesos iniciales. Aquí los pesos aleatorios van de −1 a 1. En redes más grandes el rango se calcula a partir del número de entradas de la neurona, por ejemplo ±1/√n (Xavier, He), para que las sumas no sean demasiado grandes ya en la primera capa.
Los mismos números en cuatro lenguajes. Las sumas y multiplicaciones van aquí en el mismo orden, así que el resultado es idéntico bit a bit. No se promete que exp sea idéntica hasta el último bit en cada lenguaje, por eso los programas imprimen 4 decimales. La salida está comprobada y es la misma en Java, C#, Python y JavaScript.
:F4 escribe el número según la cultura del sistema. En una computadora con cultura española verás 0,1365, mientras que la comprobación de la lección se ejecuta con la cultura invariante y un punto.Ejercicio
Parada temprana con tres tasas
Resuelve la tarea en tu propio editor.
Toma la parada temprana de la lección y ejecútala para las tasas 0,02, 0,05 y 0,2, cada vez con una red nueva de la semilla 42. Para cada tasa imprime la validación más baja, su época y la época en que se detiene el entrenamiento. No hace falta guardar una copia de la red.
tasa 0.02: validación más baja 0.0683 en la época 470, me detuve en la época 570tasa 0.05: validación más baja 0.0667 en la época 230, me detuve en la época 330tasa 0.20: validación más baja 0.0628 en la época 50, me detuve en la época 150Con 0,2 la red alcanza su validación más baja en 50 épocas y se detiene en la 150. Con 0,02 necesita 470. La tasa mayor llega antes al fondo, pero también empieza a subir antes. Por eso la validación se comprueba a menudo, no solo al final.
Más tareas
- Seis neuronas. Ejecuta el ciclo principal con 6 neuronas ocultas. ¿Sube la validación antes?
- Sin el peso. Quita el peso de las características (2 entradas en lugar de 3) y ejecuta el ciclo principal. ¿Qué pasa con la validación en la época 1000?
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Sobreajuste: el modelo que memoriza · La neurona · Capas · Retropropagación
- Continúa en: Los dígitos del recibo
- Ejercicios sobre el tema: Una red neuronal por dentro