Ir al contenido
wannacode

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:

double[][] cases = {{0, 0}, {1, 0}, {0, 1}, {1, 1}};
int[] labels = {0, 1, 1, 0}; // 1 = sospechoso
int tried = 0, best = 0;
double[] bestW = {0, 0};
double bestB = 0;
for (int i = -12; i <= 12; i++) {
for (int j = -12; j <= 12; j++) {
for (int k = -12; k <= 11; k++) {
double[] w = {i / 2.0, j / 2.0};
double b = k / 2.0 + 0.25;
tried++;
int right = 0;
for (int n = 0; n < 4; n++) {
int guess = neuron(cases[n], w, b) >= 0.5 ? 1 : 0;
if (guess == labels[n]) right++;
}
if (right > best) {
best = right;
bestW = w;
bestB = b;
}
}
}
}
System.out.printf("neuronas probadas: %d, la mejor neurona: %d de 4%n", tried, best);
System.out.printf("w = [%.2f, %.2f], b = %.2f%n", bestW[0], bestW[1], bestB);
for (int n = 0; n < 4; n++) {
double p = neuron(cases[n], bestW, bestB);
boolean right = (p >= 0.5 ? 1 : 0) == labels[n];
System.out.printf("%.0f %.0f: p = %.3f %s%n", cases[n][0], cases[n][1], p,
right ? "correcto" : "incorrecto");
}
Salida
neuronas probadas: 15000, la mejor neurona: 3 de 4
w = [-6.00, 0.50], b = -0.25
0 0: p = 0.438 correcto
1 0: p = 0.002 incorrecto
0 1: p = 0.562 correcto
1 1: p = 0.003 correcto

La 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:

static final double[][] W = {{6, 6}, {6, 6}};
static final double[] B = {-3, -9};
static final double[] V = {10, -10};
static final double C = -5;
static double forward(double[] x) {
// al menos uno es del extranjero
double h1 = neuron(x, W[0], B[0]);
// los dos son del extranjero
double h2 = neuron(x, W[1], B[1]);
double[] 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:

double[] x = {1, 0}; // una tarjeta extranjera, una IP de Bulgaria
double z1 = weightedSum(x, W[0], B[0]);
double h1 = sigmoid(z1);
System.out.printf("h1: z = %.2f, h1 = %.4f%n", z1, h1);
double z2 = weightedSum(x, W[1], B[1]);
double h2 = sigmoid(z2);
System.out.printf("h2: z = %.2f, h2 = %.4f%n", z2, h2);
double[] h = {h1, h2};
double z = weightedSum(h, V, C);
double p = sigmoid(z);
System.out.printf("salida: z = %.2f, p = %.4f%n", z, p);
Salida
h1: z = 3.00, h1 = 0.9526
h2: z = -3.00, h2 = 0.0474
salida: z = 4.05, p = 0.9829

h1: 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:

double[][] cases = {{0, 0}, {1, 0}, {0, 1}, {1, 1}};
System.out.println("tarj. IP p");
for (double[] x : cases) {
double p = forward(x);
String verdict = p >= 0.5 ? "sospechoso" : "ok";
System.out.printf("%5.0f %3.0f %7.4f %s%n", x[0], x[1], p, verdict);
}
Salida
tarj. IP p
0 0 0.0107 ok
1 0 0.9829 sospechoso
0 1 0.9829 sospechoso
1 1 0.0107 ok

Cuatro 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

static final double[][] W = {{6, 6}, {6, 6}};
static final double[] B = {-3, -9};
static final double[] V = {10, -10};
static final double C = -5;
static double forward(double[] x) {
// al menos uno es del extranjero
double h1 = neuron(x, W[0], B[0]);
// los dos son del extranjero
double h2 = neuron(x, W[1], B[1]);
double[] h = {h1, h2}; // la capa oculta
return neuron(h, V, C);
}
Paso a paso, en palabras
  1. Cuatro casos: tarjeta e IP, 1 significa extranjero. (1, 0) y (0, 1) son sospechosos.
  2. La mejor neurona sola traza una recta y acierta 3 de 4: (1, 0) queda del lado equivocado.
  3. La capa oculta: dos rectas, h1 «al menos uno» y h2 «los dos». Lo sospechoso está entre ellas.
  4. La pasada hacia adelante para una tarjeta extranjera desde una IP búlgara: x = (1, 0).
  5. h1: 6 × 1 + 6 × 0 − 3 = 3,00, σ da 0,9526. h2: 6 × 1 + 6 × 0 − 9 = −3,00, σ da 0,0474.
  6. La salida lee h: 10 × 0,9526 − 10 × 0,0474 − 5 = 4,05, σ da 0,9829.
  7. 0,9829 está por encima de 0,5: sospechoso.
  8. 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?

double[][] cases = {{0, 0}, {1, 0}, {0, 1}, {1, 1}};
double[] out = new double[4];
for (int n = 0; n < 4; n++) {
double[] x = cases[n];
double h1 = relu(x[0] + x[1]);
double h2 = relu(x[0] + x[1] - 1);
out[n] = h1 - 2 * h2;
}
System.out.printf("%.0f %.0f %.0f %.0f%n", out[0], out[1], out[2], out[3]);
Respuesta

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.

Pruébalo

Completa

¿Qué recibe la neurona de salida?

double[] h = {h1, h2};
return neuron(, V, C);

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).

Salida
tarj. IP p
0 0 0.0107 ok
1 0 0.9829 sospechoso
0 1 0.9829 sospechoso
1 1 0.0107 ok

La 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 layer la activación como parámetro y construye la red de «Predice la salida».

Test

Ponte a prueba

1. ¿Por qué una neurona sola no puede aprender que «la tarjeta y la conexión son de países distintos»?
2. ¿Qué es una capa oculta?
3. ¿Qué es la pasada hacia adelante?
4. La capa oculta tiene dos neuronas con sigmoide, y la salida es una sigmoide. ¿Cuántos números aprende la red?
5. Sabes que lo sospechoso es que los dos países sean distintos. ¿Qué es lo más sensato?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·