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:
const rng = new Rng(42);const net = new Net(4, 3);net.randomize(rng, 1.0);console.log(`error (MSE): ${net.loss(trainX, trainY).toFixed(4)}`);for (const k of [0, 12, 23]) console.log(`${TRAIN[k][0]} km: la red dice ${net.predict(trainX[k]).toFixed(2)}, ` + `real ${trainY[k].toFixed(1)} 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 entregaconst 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 entrenamientoconst 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 kgconst inputs = (orders) => orders.map(([km, late, kg]) => [km / 100, late, kg / 10]);const targets = (orders) => orders.map((order) => order[3]);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 { constructor(seed) { this.state = seed; }
// el siguiente número entre 0 y 1; una misma semilla da los mismos números next() { this.state = (this.state * 48271) % 2147483647; return this.state / 2147483647; }}Math.random() en JavaScript no tiene semilla: cada ejecución da otros números. Por eso el generador es el nuestro.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:
const sigmoid = (z) => 1 / (1 + Math.exp(-z));
class Net { constructor(hidden, inputs) { this.w1 = Array.from({ length: hidden }, () => new Array(inputs).fill(0)); // entrada → capa oculta this.b1 = new Array(hidden).fill(0); this.w2 = new Array(hidden).fill(0); // capa oculta → salida this.b2 = 0; this.h = new Array(hidden).fill(0); // las salidas de la capa oculta del último predict }
// pesos aleatorios de -scale a scale; los sesgos siguen en 0 randomize(rng, scale) { for (const row of this.w1) for (let i = 0; i < row.length; i++) row[i] = (rng.next() * 2 - 1) * scale; for (let j = 0; j < this.w2.length; j++) this.w2[j] = (rng.next() * 2 - 1) * scale; }
predict(x) { let y = this.b2; for (let j = 0; j < this.w1.length; j++) { let z = this.b1[j]; for (let i = 0; i < x.length; i++) z += this.w1[j][i] * x[i]; this.h[j] = sigmoid(z); y += this.w2[j] * this.h[j]; } return y; }
loss(xs, ys) { let total = 0; for (let k = 0; k < xs.length; k++) { const e = this.predict(xs[k]) - ys[k]; total += e * e; } return total / xs.length; }
// un paso: la pendiente media sobre los ejemplos del lote step(xs, ys, batch, rate) { const hidden = this.w2.length, inputs = xs[0].length; const gw1 = Array.from({ length: hidden }, () => new Array(inputs).fill(0)); const gb1 = new Array(hidden).fill(0), gw2 = new Array(hidden).fill(0); let gb2 = 0; for (const k of batch) { const d = 2 * (this.predict(xs[k]) - ys[k]); gb2 += d; for (let j = 0; j < hidden; j++) { gw2[j] += d * this.h[j]; const dz = d * this.w2[j] * this.h[j] * (1 - this.h[j]); gb1[j] += dz; for (let i = 0; i < inputs; i++) gw1[j][i] += dz * xs[k][i]; } } const n = batch.length; this.b2 -= (rate * gb2) / n; for (let j = 0; j < hidden; j++) { this.w2[j] -= (rate * gw2[j]) / n; this.b1[j] -= (rate * gb1[j]) / n; for (let i = 0; i < inputs; i++) this.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 probablesfunction shuffle(order, rng) { for (let i = order.length - 1; i > 0; i--) { const k = Math.floor(rng.next() * (i + 1)); [order[i], order[k]] = [order[k], order[i]]; }}Míralo
El ciclo y la curva del error
const rng = new Rng(42);const net = new Net(4, 3);net.randomize(rng, 1.0);const size = 8;const rate = 0.05;const order = [...trainX.keys()];console.log("época entrenamiento validación");for (let epoch = 1; epoch <= 1000; epoch++) { shuffle(order, rng); for (let start = 0; start < order.length; start += size) { const batch = order.slice(start, start + size); net.step(trainX, trainY, batch, rate); } if (epoch % 100 === 0) { const t = net.loss(trainX, trainY).toFixed(4); const v = net.loss(validX, validY).toFixed(4); const e = String(epoch).padStart(5); console.log(e + t.padStart(15) + v.padStart(12)); }}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:
const rng = new Rng(42);const net = new Net(4, 3);net.randomize(rng, 1.0);const size = 8;const rate = 0.05;const order = [...trainX.keys()];console.log("época entrenamiento validación");for (let epoch = 1; epoch <= 1000; epoch++) { shuffle(order, rng); for (let start = 0; start < order.length; start += size) { const batch = order.slice(start, start + size); net.step(trainX, trainY, batch, rate); } if (epoch % 100 === 0) { const t = net.loss(trainX, trainY).toFixed(4); const v = net.loss(validX, validY).toFixed(4); const e = String(epoch).padStart(5); console.log(e + t.padStart(15) + v.padStart(12)); }}é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?
const examples = 24, size = 10;let steps = 0;for (let epoch = 1; epoch <= 1000; epoch++) for (let start = 0; start < examples; start += size) steps++;console.log(`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ó:
for (const size of [24, 8, 1]) { const rng = new Rng(42); const net = new Net(4, 3); net.randomize(rng, 1.0); const order = [...trainX.keys()]; let steps = 0, up = 0; let before = net.loss(trainX, trainY); for (let epoch = 1; epoch <= 40; epoch++) { shuffle(order, rng); for (let start = 0; start < order.length; start += size) { net.step(trainX, trainY, order.slice(start, start + size), 0.05); const after = net.loss(trainX, trainY); steps++; if (after > before) up++; before = after; } } const s = String(size).padStart(2), n = String(steps).padStart(3), u = String(up).padStart(3); console.log(`lote ${s}: ${n} pasos, ${u} arriba, error ${before.toFixed(4)}`);}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?
const n = ;
this.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:
for (const rate of [0.005, 0.05, 2.0]) { const rng = new Rng(42); const net = new Net(4, 3); net.randomize(rng, 1.0); const order = [...trainX.keys()]; let line = `tasa ${rate.toFixed(3)}:`; for (let epoch = 1; epoch <= 200; epoch++) { trainEpoch(net, trainX, trainY, order, 8, rate, rng); const loss = net.loss(trainX, trainY); if (Number.isNaN(loss) || loss > 1e6) { line += ` se disparó en la época ${epoch}`; break; } if ([50, 100, 200].includes(epoch)) line += ` ${loss.toFixed(4)}`; } console.log(line);}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:
let rng = new Rng(42);const zeros = new Net(4, 3); // sin randomize: todos los pesos son 0let order = [...trainX.keys()];for (let epoch = 1; epoch <= 200; epoch++) trainEpoch(zeros, trainX, trainY, order, 8, 0.05, rng);console.log(`ceros: error ${zeros.loss(trainX, trainY).toFixed(4)}`);zeros.w1.forEach((row, j) => console.log(` neurona ${j + 1}: ` + row.map((x) => x.toFixed(2)).join(" ")));
for (const scale of [8, 1]) { rng = new Rng(42); const net = new Net(4, 3); net.randomize(rng, scale); const first = net.loss(trainX, trainY); order = [...trainX.keys()]; for (let epoch = 1; epoch <= 200; epoch++) trainEpoch(net, trainX, trainY, order, 8, 0.05, rng); const last = net.loss(trainX, trainY); console.log(`pesos ×${scale}: al principio ${first.toFixed(4)}, tras 200 épocas ${last.toFixed(4)}`);}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:
const rng = new Rng(42);const net = new Net(4, 3);net.randomize(rng, 1.0);const order = [...trainX.keys()];for (let epoch = 1; epoch <= 1000; epoch++) { trainEpoch(net, trainX, trainY, order, 8, 0.05, rng); if (epoch === 200 || epoch === 1000) { console.log(`época ${epoch}:`); for (const k of [1, 3, 6]) { // los paquetes pesados de la validación const [km, , kg, days] = VALID[k]; const y = net.predict(validX[k]).toFixed(2); console.log(` ${km} km, ${kg.toFixed(1)} kg: ${y} (real ${days.toFixed(1)})`); } }}é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 momentocopy() { const c = new Net(this.w2.length, this.w1[0].length); c.w1 = this.w1.map((row) => [...row]); c.b1 = [...this.b1]; c.w2 = [...this.w2]; c.b2 = this.b2; return c;}const rng = new Rng(42);const net = new Net(4, 3);net.randomize(rng, 1.0);const order = [...trainX.keys()];// revisar cada 10 épocas; parar tras 100 sin mejoralet best = Infinity, bestEpoch = 0, epoch = 0;let bestNet = net.copy();while (epoch < 1000 && epoch - bestEpoch < 100) { epoch++; trainEpoch(net, trainX, trainY, order, 8, 0.05, rng); if (epoch % 10 === 0) { const valid = net.loss(validX, validY); if (valid < best) { best = valid; bestEpoch = epoch; bestNet = net.copy(); } }}console.log(`validación más baja: ${best.toFixed(4)} en la época ${bestEpoch}`);console.log(`me detuve en la época ${epoch}`);console.log(`conservo la red de la época ${bestEpoch}: ${bestNet.loss(validX, validY).toFixed(4)}`);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.
toFixed(4) siempre escribe un punto, sea cual sea el idioma del sistema.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