Capas
En resumen: Las capas conectan neuronas una tras otra: una capa oculta permite que la red aprenda reglas que una sola recta no puede dibujar, como XOR.
Problema
La regla que una recta no puede dibujar
El equipo antifraude quiere una regla nueva: un pago es sospechoso cuando el país de la tarjeta y el país de la conexión (según la dirección IP) no coinciden. Una tarjeta búlgara desde una IP extranjera: alguien usa una tarjeta robada en el extranjero. Una tarjeta extranjera desde una IP búlgara: una tarjeta robada usada aquí. Una tarjeta búlgara desde Bulgaria y una tarjeta extranjera desde el extranjero (un cliente que vive allí) están bien.
Hay dos características, de 0 o 1 cada una, donde 1 significa extranjero. Hay cuatro casos, y (1, 0) y (0, 1) son sospechosos. El programa prueba 15 000 neuronas: cada peso de −6 a 6 en pasos de 0,5 y un sesgo de −5,75 a 5,75. Para cada una cuenta cuántos casos acierta con un umbral de 0,5:
const cases = [[0, 0], [1, 0], [0, 1], [1, 1]];const labels = [0, 1, 1, 0]; // 1 = sospechosolet tried = 0;let best = 0;let bestW = [0, 0];let bestB = 0;for (let i = -12; i <= 12; i++) { for (let j = -12; j <= 12; j++) { for (let k = -12; k <= 11; k++) { const w = [i / 2, j / 2]; const b = k / 2 + 0.25; tried++; let right = 0; cases.forEach((x, n) => { const guess = neuron(x, w, b) >= 0.5 ? 1 : 0; if (guess === labels[n]) right++; }); if (right > best) { best = right; bestW = w; bestB = b; } } }}console.log(`neuronas probadas: ${tried}, la mejor neurona: ${best} de 4`);console.log(`w = [${bestW[0].toFixed(2)}, ${bestW[1].toFixed(2)}], b = ${bestB.toFixed(2)}`);cases.forEach((x, n) => { const p = neuron(x, bestW, bestB); const right = (p >= 0.5 ? 1 : 0) === labels[n]; console.log(`${x[0]} ${x[1]}: p = ${p.toFixed(3)} ${right ? "correcto" : "incorrecto"}`);});neuronas probadas: 15000, la mejor neurona: 3 de 4w = [-6.00, 0.50], b = -0.250 0: p = 0.438 correcto1 0: p = 0.002 incorrecto0 1: p = 0.562 correcto1 1: p = 0.003 correctoLa mejor acierta 3 de 4, y llama «ok» a una tarjeta extranjera desde una IP búlgara. Ninguna de las 15 000 acierta los cuatro. Y no es porque la cuadrícula de pesos sea demasiado gruesa: la próxima tarjeta muestra por qué.
La idea
Por qué una recta no basta
Dibuja los casos como puntos en un cuadrado: la tarjeta es el eje horizontal, la conexión es el vertical. Los sospechosos (1, 0) y (0, 1) están en dos esquinas opuestas, y los otros dos están bien.
La neurona dice «sí» cuando z está por encima de cero. La frontera z = 0, es decir, w₁·tarjeta + w₂·IP + b = 0, es una recta. A un lado la neurona dice «sí», al otro «no». Y una recta que deja las dos esquinas sospechosas de un lado deja al menos una de las otras dos con ellas. Pruébalo con un lápiz: no funciona.
La regresión logística de la lección sobre clasificación tiene la misma frontera, así que tampoco puede. Hacen falta dos rectas.
La idea
Dos neuronas en el medio
Entre la entrada y la salida ponemos dos neuronas. Cada una traza su propia recta:
- h1: «al menos uno es del extranjero». Pesos 6 y 6, sesgo −3: z está por encima de cero en cuanto al menos una entrada es 1.
- h2: «los dos son del extranjero». Pesos 6 y 6, sesgo −9: z solo está por encima de cero cuando las dos entradas son 1.
La salida lee h1 y h2 con pesos 10 y −10 y un sesgo de −5: «al menos uno, pero no los dos». La zona sospechosa es la franja entre las dos rectas.
Las neuronas de en medio son una capa oculta. Oculta, porque nadie les da una respuesta correcta: la entrada viene de los datos, la salida tiene una etiqueta, y lo que calculan las de en medio lo decide la red misma. Aquí los pesos están elegidos a mano para que signifiquen algo que puedas decir con palabras. Los aprendidos casi nunca se pueden decir con palabras tan fácilmente.
Calcular de izquierda a derecha, capa por capa, es la pasada hacia adelante:
const W = [[6, 6], [6, 6]];const B = [-3, -9];const V = [10, -10];const C = -5;
function forward(x) { // al menos uno es del extranjero const h1 = neuron(x, W[0], B[0]); // los dos son del extranjero const h2 = neuron(x, W[1], B[1]); const h = [h1, h2]; // la capa oculta return neuron(h, V, C);}Míralo
La pasada hacia adelante, número por número
Un caso: una tarjeta extranjera desde una IP búlgara, (1, 0). Cada paso imprime su z y su salida:
const x = [1, 0]; // una tarjeta extranjera, una IP de Bulgariaconst z1 = weightedSum(x, W[0], B[0]);const h1 = sigmoid(z1);console.log(`h1: z = ${z1.toFixed(2)}, h1 = ${h1.toFixed(4)}`);const z2 = weightedSum(x, W[1], B[1]);const h2 = sigmoid(z2);console.log(`h2: z = ${z2.toFixed(2)}, h2 = ${h2.toFixed(4)}`);const h = [h1, h2];const z = weightedSum(h, V, C);const p = sigmoid(z);console.log(`salida: z = ${z.toFixed(2)}, p = ${p.toFixed(4)}`);h1: z = 3.00, h1 = 0.9526h2: z = -3.00, h2 = 0.0474salida: z = 4.05, p = 0.9829h1: 6 × 1 + 6 × 0 − 3 = 3, y la sigmoide da 0,9526. h2: 6 × 1 + 6 × 0 − 9 = −3, es decir, 0,0474. La salida: 10 × 0,9526 − 10 × 0,0474 − 5 = 4,05, y la sigmoide da 0,9829. Sospechoso.
Los cuatro casos con forward:
const cases = [[0, 0], [1, 0], [0, 1], [1, 1]];const fmt = (v, d, n = 0) => v.toFixed(d).padStart(n);console.log("tarj. IP p");for (const x of cases) { const p = forward(x); const verdict = p >= 0.5 ? "sospechoso" : "ok"; console.log(`${fmt(x[0], 0, 5)} ${fmt(x[1], 0, 3)} ${fmt(p, 4, 7)} ${verdict}`);}tarj. IP p 0 0 0.0107 ok 1 0 0.9829 sospechoso 0 1 0.9829 sospechoso 1 1 0.0107 okCuatro de cuatro. Los mismos pesos, la misma sigmoide, solo una capa más.
Míralo
Dos rectas y la señal a través de la red
const W = [[6, 6], [6, 6]];const B = [-3, -9];const V = [10, -10];const C = -5;
function forward(x) { // al menos uno es del extranjero const h1 = neuron(x, W[0], B[0]); // los dos son del extranjero const h2 = neuron(x, W[1], B[1]); const h = [h1, h2]; // la capa oculta return neuron(h, V, C);}Paso a paso, en palabras
- Cuatro casos: tarjeta e IP, 1 significa extranjero. (1, 0) y (0, 1) son sospechosos.
- La mejor neurona sola traza una recta y acierta 3 de 4: (1, 0) queda del lado equivocado.
- La capa oculta: dos rectas, h1 «al menos uno» y h2 «los dos». Lo sospechoso está entre ellas.
- La pasada hacia adelante para una tarjeta extranjera desde una IP búlgara: x = (1, 0).
- h1: 6 × 1 + 6 × 0 − 3 = 3,00, σ da 0,9526. h2: 6 × 1 + 6 × 0 − 9 = −3,00, σ da 0,0474.
- La salida lee h: 10 × 0,9526 − 10 × 0,0474 − 5 = 4,05, σ da 0,9829.
- 0,9829 está por encima de 0,5: sospechoso.
- Los cuatro casos por la misma red: 0,0107; 0,9829; 0,9829; 0,0107. Cuatro de cuatro.
Arriba están los cuatro casos. Una recta siempre deja uno mal. Las dos rectas de la capa oculta cierran una franja, y la salida dice «sospechoso» solo dentro de ella.
Pruébalo
Predice la salida
La misma regla con ReLU en lugar de la sigmoide: h1 = ReLU(tarjeta + IP), h2 = ReLU(tarjeta + IP − 1), salida h1 − 2·h2. ¿Qué imprimirá para los cuatro casos?
const cases = [[0, 0], [1, 0], [0, 1], [1, 1]];const out = cases.map((x) => { const h1 = relu(x[0] + x[1]); const h2 = relu(x[0] + x[1] - 1); return h1 - 2 * h2;});console.log(out.join(" "));(0, 0): 0 − 0 = 0. (1, 0) y (0, 1): h1 = 1, h2 = ReLU(0) = 0, la salida es 1. (1, 1): h1 = 2, h2 = 1, la salida es 2 − 2 = 0. Con ReLU la regla sale exacta, sin redondeo. Sin h2 el último número sería 2.
Pruébalo
Encuentra el error
Un colega escribe forward por su cuenta. La red responde como una sola neurona: tres de cuatro. ¿En qué línea está el error?
Elige la línea con el error.
La salida lee la entrada x, no la capa oculta h. Las neuronas ocultas se calculan y no se usan, y la red se reduce a una neurona con su única recta. La salida tiene que recibir h.
Pruébalo
Completa
¿Qué recibe la neurona de salida?
const h = [h1, h2];
return neuron(, V, C);La salida lee h: las salidas de la capa oculta. Cada capa recibe las salidas de la anterior.
La idea
Cuándo ayuda una capa oculta y cuándo perjudica
- Ayuda cuando las características se influyen entre sí. «Tarjeta extranjera» sola no significa nada, y «IP extranjera» tampoco. La combinación sí. Una capa oculta aprende esas combinaciones sin que se lo digan.
- Si conoces la combinación, escríbela. La característica «tarjeta ≠ conexión» es una línea de código. Con ella basta una neurona o un
if. Una red que aprende algo que podrías escribir es más lenta, necesita datos y se equivoca más. - Más neuronas son más pesos. Aquí hay 9 números. Una capa oculta de 100 neuronas sobre 20 características son 2201 números. Más pesos significa más datos, o la red se aprende todo de memoria.
- Cada capa cuesta en cada pago. La pasada hacia adelante recorre todos los pesos en cada solicitud. Con una red grande y muchos pagos eso es tiempo y dinero.
- Las neuronas ocultas no tienen por qué significar algo. Aquí sí, porque los pesos están elegidos a mano. Las aprendidas suelen ser una mezcla que no se puede expresar con palabras.
Más precisamente, para curiosos
Con fórmulas. La capa oculta es h = σ(W·x + b): W es una tabla (una matriz) de 2 × 2 pesos, b guarda los dos sesgos, y σ se aplica a cada elemento. La salida es p = σ(v·h + c). La pasada hacia adelante son estas dos líneas una tras otra. Por eso en las bibliotecas reales una capa es una matriz por un vector.
Aprendizaje profundo. Una red con muchas capas ocultas se llama profunda. Cada capa se apoya en la anterior: las primeras captan combinaciones simples, las siguientes combinan esas.
Cuántas neuronas bastan. Se ha demostrado que una capa oculta con suficientes neuronas puede aproximar cualquier función continua (en una región acotada) tan bien como quieras. Pero «suficientes» puede ser mucho, y nadie dice cómo encontrar los pesos. Por eso en la práctica hay varias capas.
Un poco de historia. En 1969 Minsky y Papert demostraron que una sola capa (un perceptrón) no puede aprender justamente esta regla (XOR, «lo uno o lo otro»). El interés por las redes neuronales cayó durante años. La solución con una capa oculta se conocía, pero no cómo aprender sus pesos. Eso llegó con la retropropagación.
Ejercicio
Una capa para todos
Resuélvelo en tu editor.
El forward de la lección sabe que hay dos neuronas ocultas. Escribe un layer(x, w, b) general: recibe las entradas, una tabla de pesos (una fila por neurona) y los sesgos, y devuelve un arreglo con la salida de cada neurona. Después construye la misma red como dos capas: la oculta con los pesos de la lección y la de salida con una sola neurona (pesos 10 y −10, sesgo −5). Pasa los cuatro casos por layer(layer(x, w1, b1), w2, b2).
tarj. IP p 0 0 0.0107 ok 1 0 0.9829 sospechoso 0 1 0.9829 sospechoso 1 1 0.0107 okLa misma tabla que en la lección: layer es la parte general, y la red son dos líneas de pesos.
Más tareas
- Una tercera neurona oculta. Añade a la capa oculta una neurona con pesos 0 y 0 y un sesgo de 0 (y un peso de 0 para ella en la salida). La salida no debe cambiar.
- ReLU en la capa oculta. Dale a
layerla activación como parámetro y construye la red de «Predice la salida».
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Funciones flecha · Qué es el aprendizaje automático · Clasificación: fraude o no · La neurona
- Continúa en: Retropropagación · El entrenamiento de principio a fin · Los dígitos del recibo
- Ejercicios sobre el tema: Una red neuronal por dentro