Capítulo 2 · La regla

La red neuronal, desde cero

La regla de nuestro autómata es una red neuronal de dos capas y 8 320 números. Este capítulo la arma pieza por pieza, desde una sola neurona hasta cómo aprende a través de 96 pasos de tiempo. No necesitas saber nada de redes neuronales: cada pieza tiene un ejemplo que puedes mover.

1

Qué tiene que hacer la red

Olvida por un momento la lagartija. Mira una sola célula. En cada paso tiene que responder una pregunta: "con lo que veo a mi alrededor, ¿cuánto cambio cada uno de mis 16 números?"

  • Lo que entra: lo que la célula percibe de su vecindario de 3×3. Son 48 números: para cada uno de sus 16 canales, el valor propio y cuánto cambia ese canal hacia la derecha y hacia abajo. Esto se explica en la sección 6.
  • Lo que sale: 16 números, uno por canal: cuánto sumarle a cada uno. Positivo sube, negativo baja, cero lo deja igual.

O sea, la regla es una función que recibe 48 números y devuelve 16. Una red neuronal es una manera de escribir una función así con miles de "perillas" ajustables, los parámetros, que no eliges tú: se ajustan solos durante el entrenamiento hasta que la función hace lo que quieres.

regla : 48 números → 16 números
2

Una neurona

La pieza básica es la neurona. Hace tres cosas:

  1. Multiplica cada entrada por un peso. El peso dice cuánto le importa esa entrada y en qué sentido: positivo empuja hacia arriba, negativo hacia abajo, cero la ignora.
  2. Suma todo y le agrega un número fijo, el sesgo, que corre el resultado.
  3. Pasa la suma por una función de activación. La nuestra es ReLU: si la suma es negativa devuelve 0, si no la deja igual.
salida = ReLU( w₁·x₁ + w₂·x₂ + w₃·x₃ + b )

Una neurona con 3 entradas: mueve las perillas

salida

Prueba a dejar la suma negativa: la neurona se "apaga" y devuelve 0. Ese apagado es lo que la hace útil, como verás en la sección siguiente.

Una forma de pensar una neurona: es un detector. Con los pesos adecuados se enciende solo en una situación concreta, por ejemplo "hay piel verde a mi izquierda y yo todavía estoy vacía". Nadie le dice qué detectar: lo descubre el entrenamiento.

3

ReLU, y por qué hace falta

ReLU (rectified linear unit) es la función más simple que no es una recta:

ReLU(z) = máx(0, z)

Parece un detalle, pero sin ella la red no serviría. Sin activación, una capa es una multiplicación por una matriz, W₁·p, y dos capas seguidas son W₂·(W₁·p) = (W₂·W₁)·p: otra matriz. Apilar cien capas lineales da lo mismo que una sola. La red solo podría aprender reglas del tipo "el cambio es proporcional a lo que veo".

zReLU(z) apagada: 0pasa igual

Con ReLU cada neurona oculta funciona como un interruptor: aporta solo cuando su situación ocurre. Combinando 128 interruptores, la red puede armar reglas del tipo "si estoy en el borde de la cola y el canal 7 sube hacia la derecha, entonces vuélvete verde y aumenta el canal 9". Eso es justo lo que necesita una célula.

4

Capas: muchas neuronas a la vez

Una capa es un grupo de neuronas que miran las mismas entradas, cada una con sus propios pesos. Si pones los pesos de las 128 neuronas como filas de una tabla, tienes una matriz W₁ de 128 filas × 48 columnas, y calcular la capa entera es una multiplicación de matriz por vector:

h = ReLU( W₁ · p + b₁ )     (128×48)·(48) + (128) → 128 números
dx = W₂ · h     (16×128)·(128) → 16 números

Las dos capas de la regla

p48 números lo que percibe W₁, b₁ h = ReLU(…)128 neuronas W₂ dx16 números cuánto cambiar

Contemos las perillas:

PiezaFormaParámetrosQué hace
W₁128 × 486 144cada neurona oculta decide cuánto le importa cada cosa que la célula percibe
b₁128128el sesgo de cada neurona oculta: qué tan fácil se enciende
W₂16 × 1282 048cada neurona encendida empuja los 16 canales en su propia dirección
Total8 320todo lo que se aprende

La segunda capa no tiene ReLU ni sesgo. No tiene ReLU porque el cambio tiene que poder ser negativo: a veces una célula necesita bajar un canal. Y no tiene sesgo porque un sesgo sería un cambio que todas las células aplican siempre, incluso sin percibir nada, y lo natural es que "nada alrededor" signifique "no cambies". La página de decisiones lo detalla.

8 320 es poquísimo

Un modelo de lenguaje moderno tiene miles de millones de parámetros, y una red simple que aprende a jugar al gato puede tener unos 700. Esta regla cabe en 33 KB. Toda la "inteligencia" que hace crecer y sanar la lagartija está en esos 8 320 números.

5

La misma red en todas las células

Hay una sola red, y las 5 184 células de la cuadrícula de 72×72 la usan, cada una con lo que percibe. Se le dice compartir pesos: es lo mismo que hace una red convolucional. De hecho, aplicar una red densa a cada píxel por separado es exactamente una convolución 1×1, y así lo implementa el artículo original.

¿Por qué no una red que mire la cuadrícula entera? Hagamos la cuenta. El estado completo tiene 72 × 72 × 16 = 82 944 números. Una sola capa densa que lea todos y produzca un cambio para todos necesitaría 82 944 × 82 944 ≈ 6 880 millones de pesos, más de 800 000 veces lo que tiene nuestra regla. Y además sería otra cosa: un jefe que ve todo, justo lo que no queremos.

Compartir pesos te da tres cosas

Pocos parámetros: 8 320 sin importar el tamaño de la cuadrícula. Localidad: cada célula decide con información de su vecindario, como en biología. Igualdad de reglas: la lagartija puede crecer en cualquier lugar de la cuadrícula, porque ninguna posición tiene una regla especial.

6

Cómo percibe una célula

Antes de la red hay un paso fijo, que no se aprende: la percepción. Para cada uno de sus 16 canales, la célula calcula tres números mirando su bloque de 3×3:

  1. Valor: lo que tiene ella misma (filtro identidad).
  2. Gradiente x: cuánto más tienen las vecinas de la derecha que las de la izquierda (filtro Sobel x).
  3. Gradiente y: cuánto más tienen las vecinas de abajo que las de arriba (filtro Sobel y).

16 canales × 3 filtros = 48 números: la entrada de la red. Cada filtro es una plantilla de 3×3 que se multiplica casilla por casilla con el vecindario y se suma, que es lo que hace una convolución. Los de Sobel ponderan doble la fila o columna del medio y se dividen por 8, para que una rampa que sube 1 por casilla dé un gradiente de exactamente 1:

identidad
000
010
000
Sobel x (÷8)
−101
−202
−101
Sobel y (÷8)
−1−2−1
000
121

Percepción de un canal: haz clic en las casillas de la izquierda para cambiarlas

Ejemplo
el canal (clic: 0 → ½ → 1)
valor
gradiente x
gradiente y

Ámbar = positivo, azul verdoso = negativo. Pasa el mouse o toca una casilla de los mapas para ver la cuenta de esa célula. Fuera de la cuadrícula se cuenta como 0.

¿Por qué gradientes? Porque dicen hacia dónde hay algo. Con el valor solo, una célula vacía al lado de la lagartija y una vacía lejos de todo serían iguales. Con el gradiente, la primera "siente" que a su izquierda hay mucho alfa y puede decidir crecer. Es lo mismo que hace una bacteria que nada hacia donde hay más azúcar: sigue un gradiente químico. Los 12 canales ocultos sirven justamente para que las células se inventen sus propias sustancias químicas y sigan sus gradientes.

Decisión del artículo

Los filtros son fijos y no se aprenden. Se podría usar una convolución 3×3 aprendida, pero el artículo eligió estos por simplicidad y porque tienen un sentido físico. Además permiten el experimento 4: si giras los filtros Sobel, la lagartija crece girada sin reentrenar nada.

7

Cómo aprende: pérdida y descenso de gradiente

Al principio los 8 320 parámetros son casi aleatorios y la regla no hace nada útil. Para mejorarla hacen falta tres cosas.

1. Medir qué tan mal lo hace: la pérdida

Hacemos crecer el autómata desde la semilla, lo comparamos con la lagartija objetivo y calculamos el error cuadrático medio: para cada uno de los 72 × 72 × 4 números visibles (color y alfa), la diferencia con el objetivo al cuadrado, y el promedio de todo. Si es 0, la imagen es idéntica.

pérdida = promedio de ( xvisible − objetivo )²

Los 12 canales ocultos no entran en la pérdida. A la red no le importa qué valores tengan: son libres de usarse como quiera para coordinar a las células.

2. Saber hacia dónde mover cada perilla: el gradiente

Para cada parámetro, la pregunta es: "si lo subo un poquito, ¿la pérdida sube o baja, y cuánto?". La respuesta es la derivada de la pérdida respecto a ese parámetro. El conjunto de las 8 320 derivadas es el gradiente. PyTorch lo calcula solo con perdida.backward(), usando la regla de la cadena hacia atrás desde la pérdida: es la retropropagación (backpropagation).

3. Dar un paso: el descenso de gradiente

Se mueve cada parámetro un poquito en contra de su derivada, que es la dirección en que la pérdida baja:

parámetro ← parámetro − tasa · derivada

Descenso de gradiente con un solo parámetro

Con tasa chica baja lento pero seguro. Cerca de 1 rebota de lado a lado. Sobre 1 se aleja cada vez más: diverge. Elegir la tasa es elegir el tamaño del paso.

Con 8 320 parámetros es lo mismo, pero en un paisaje de 8 320 dimensiones. Nuestro entrenamiento repite esto 4 000 veces.

8

Aprender a través del tiempo

Aquí está lo especial de este problema. La pérdida no se mide después de usar la red una vez, sino después de usarla entre 64 y 96 veces seguidas: la semilla pasa por la red, el resultado vuelve a pasar por la red, y así hasta que la lagartija debería estar crecida.

La misma red, aplicada una y otra vez

el gradiente vuelve por los 96 pasos (retropropagación a través del tiempo)

El resultado final depende de los pesos a través de cada uno de los 96 pasos, así que el gradiente tiene que recorrerlos todos hacia atrás. Esto se llama retropropagación a través del tiempo y es lo mismo que pasa en una red recurrente (RNN). Aquí la "memoria" de la red recurrente es la cuadrícula completa.

Tiene dos costos:

  • Memoria: para volver hacia atrás, PyTorch guarda todos los estados intermedios de los 96 pasos. Con lotes de 8 cuadrículas, cada entrenamiento ocupa alrededor de 1,5 GB de RAM (4 GB en la primera versión, que calculaba la red también en las células muertas).
  • Gradientes inestables: un número multiplicado 96 veces por algo un poco mayor que 1 explota, y por algo un poco menor que 1 se desvanece. Por eso se normaliza el gradiente, como se ve en la sección siguiente.

¿Y por qué un número de pasos al azar entre 64 y 96, y no siempre 80? Para que la red no aprenda "en el paso 80 exacto, verse como la lagartija". Tiene que verse bien en cualquier momento de ese rango, lo que ya la empuja a llegar a la forma y quedarse un rato en ella.

9

Adam, normalización e inicio en cero

Adam: un descenso de gradiente con inercia

En vez del descenso de gradiente simple se usa Adam, el optimizador más común. Hace dos mejoras: recuerda la dirección de los pasos anteriores, como una pelota con inercia que no se frena en cada bache, y ajusta el tamaño del paso de cada parámetro según cuánto ha variado su gradiente. La tasa empieza en 0,002 y en el paso 2 000 baja a 0,0002, para afinar al final con pasos más pequeños.

Normalizar el gradiente

Antes de cada paso de Adam, el gradiente de cada tensor de pesos (W₁, b₁, W₂) se divide por su largo. Así el largo del gradiente siempre es 1 y lo único que importa es su dirección. Si en un paso el gradiente explota a través de los 96 pasos de tiempo, no arrastra a la red a cualquier lado: el tamaño del paso lo sigue controlando la tasa de aprendizaje.

# entrenar.py
perdida.backward()
for p in modelo.parameters():
    p.grad /= p.grad.norm() + 1e-8     # + 1e-8 para no dividir por cero
optimizador.step()

La última capa empieza en cero

Normalmente los pesos parten al azar. Aquí W₂ parte en cero, así que al principio dx = 0 para todas las células: la regla es "no cambies nada" y la semilla se queda quieta. Es un punto de partida tranquilo. Con pesos al azar, desde el primer paso la cuadrícula se llenaría de ruido, y el entrenamiento empezaría peleando contra el caos.

nn.init.zeros_(self.capa2.weight)   # al principio la regla es "no cambies nada"

Que la salida sea un cambio y no el estado nuevo también ayuda: quedarse quieto es tan fácil como producir ceros. Se le dice actualización residual, la misma idea de las redes ResNet.

10

La red completa, en una figura

Lo que hace cada célula en cada paso

vecindario3×3 × 16 canales percibirfijo · 48 capa 1ReLU · 128 capa 2dx · 16 + el estado propio de la célula (16) lo único que se aprende: 8 320 números nuevoestado

En código PyTorch, la regla completa cabe en una clase de veinte líneas (nca.py, clase AutomataNeuronal):

self.capa1 = nn.Linear(16 * 3, 128)
self.capa2 = nn.Linear(128, 16, bias=False)
nn.init.zeros_(self.capa2.weight)

# en cada paso, para cada célula:
p = percibir(x)                              # 48 números
dx = self.capa2(torch.relu(self.capa1(p)))   # 16 números
x = x + dx                                   # (solo la mitad de las células, al azar)

Lo que falta, la parte de "solo la mitad al azar" y "las células sin vecinas vivas mueren", está en el capítulo siguiente, donde se ve un paso completo célula por célula, con los números reales de la red entrenada.