XOR: por qué necesitamos capas ocultas
25 min de lectura
Con la lección anterior, sobre funciones de activación y no linealidad, la neurona quedó terminada: una suma ponderada, y encima una curva y con derivada aprovechable —sigmoide, tangente hiperbólica o ReLU— en lugar del escalón que ocupaba ese sitio al principio. Terminada, y con un límite que ninguna de las tres candidatas toca: la neurona contesta según de qué lado de un hiperplano haya caído la entrada. Eso mueve la pregunta de sitio. Ya no es cuál asignar a , sino una pregunta sobre los datos: qué repartos de ejemplos caben dentro de un hiperplano, y si hay alguno que no cabe en ninguno.
Cuatro frases alcanzan para encontrarlo. Toma está bien, está mal, no está bien y no está mal, y clasifícalas: la primera y la cuarta hablan bien, la segunda y la tercera hablan mal. Las cuatro se distinguen con dos datos —si aparece no, y si la palabra que valora es bien o mal—, de modo que dos coordenadas las describen enteras. Ahora mira qué hace no. No aporta un signo propio: le da la vuelta al de la palabra que le sigue. Y un peso es un signo fijo.
Esas dos coordenadas caben en un dibujo, que es el de la lección sobre la neurona artificial: cada frase es un punto del plano, su color es lo que dice, y la neurona busca una recta que deje un color a cada lado. El segundo botón del explorable carga exactamente esos cuatro puntos.
Los cuatro puntos son las esquinas de un cuadrado y cada clase ocupa dos esquinas opuestas. Una recta parte el plano en dos mitades, y no hay forma de meter dos esquinas opuestas en la misma mitad sin llevarse por delante alguna de las otras dos. Eso lo enseña el explorable, y no lo demuestra: que un procedimiento no encuentre una recta deja abierto que la recta exista y él no dé con ella. Hay que descartarlas todas a la vez.
Cuatro frases que ningún hiperplano separa
Fijemos las coordenadas. Sea cuando la frase contiene no y cuando no lo contiene, y sea cuando la palabra que valora es bien y cuando es mal. La etiqueta vale si la frase habla bien y si habla mal, que son los dos valores que entrega el escalón.
| frase | ||
|---|---|---|
| está bien | ||
| está mal | ||
| no está bien | ||
| no está mal |
La etiqueta vale exactamente cuando las dos coordenadas difieren. Esa función tiene nombre desde mucho antes que las redes: es el o exclusivo (exclusive or, XOR), y sus dos clases son las dos diagonales del cuadrado.
Supongamos que existen y con los que la neurona acierta las cuatro. Acertar las dos positivas es en las dos, y acertar las dos negativas es en las dos. Sumemos cada pareja por separado, con :
y para la otra diagonal, con los mismos pesos,
Los pesos se cancelan en las dos, y las dos sumas dan el mismo número. Pero la primera suma dos cantidades , luego ; y la segunda suma dos cantidades , luego . Un mismo no puede cumplir las dos, así que la neurona supuesta no existe.
Merece la pena leer esa cancelación en el dibujo, porque dice de dónde sale. Los dos segmentos —el que une las dos frases positivas y el que une las dos negativas— tienen el mismo punto medio, el origen. Y es afín, así que su valor en el punto medio de un segmento es la media de sus valores en los extremos. El origen tendría que caer del lado positivo por estar entre dos positivas, y del negativo por estar entre dos negativas.
La versión general: dos segmentos que se cruzan lo impiden siempre
Nada de la cuenta anterior depende de que las coordenadas fueran ni de que los cuatro puntos formaran un cuadrado. Sean dos ejemplos de una clase y dos de la otra, y supongamos que el segmento que une los primeros corta al que une los segundos en un punto . Cortarse significa que ese punto se escribe de las dos maneras,
con . Una función afín reparte esas combinaciones, porque los pesos suman y el sesgo sobrevive intacto:
Si la neurona acierta, y son , y entonces —una media de los dos con pesos no negativos— también lo es. Por el otro camino, y son y tiene que ser . El mismo punto con dos signos incompatibles.
Con esto, el resultado no cambia si prefieres codificar las frases con y en lugar de y : los cuatro puntos son entonces las esquinas del cuadrado unidad, los dos segmentos se cruzan en y la contradicción aparece igual.
Y ninguna activación lo arregla
La demostración usó el escalón en un sitio concreto: para leer «acierta» como o . Es lo único que hay que revisar. Sea estrictamente creciente —la sigmoide y la tangente hiperbólica lo son— y sea el umbral con el que decidas leer su salida como un sí. Al ser creciente es invertible en su recorrido, de modo que equivale a , y el conjunto de las frases que reciben un sí es
que es el semiplano de la misma neurona con el sesgo . La demostración se aplica sin tocar una letra.
ReLU no es estrictamente creciente y tampoco escapa. Con , pedir es pedir , otro semiplano; con se cumple para toda entrada y la neurona contesta que sí a las cuatro frases, incluidas las dos que hablan mal. Semiplano o plano entero, y ninguna de las dos cosas parte las diagonales.
Una capa es varias neuronas a la vez
Lo que falla no es la neurona: es que sólo haya una. Pon dos mirando la misma entrada y una tercera mirando lo que entregan esas dos. Quedan tres columnas, y cada una tiene nombre. La capa de entrada es y no calcula nada —se dibuja como una columna de círculos por costumbre, pero ahí no hay neuronas—; la capa oculta son las dos primeras neuronas, oculta porque su salida no es la respuesta final y nadie la lee desde fuera; y la capa de salida es la tercera, la que contesta.
Con varias neuronas en un nivel, el vector de pesos de cada una pasa a ser una fila. La neurona de la capa guarda sus pesos en la fila de y su sesgo en , y lo que en la lección sobre la neurona artificial eran los escalares y son ahora las coordenadas y . Es el mismo objeto apilado; por dentro no ha cambiado nada. Con , la capa oculta calcula
con y , y con aplicada coordenada a coordenada. La capa de salida hace lo mismo sobre :
con y . Una capa de una sola neurona es una matriz de una sola fila, y escribirla así cuesta un par de subíndices ahora y ahorra tener dos notaciones después.
Dos rectas paralelas separan lo que una sola no puede
Estos pesos resuelven las cuatro frases, con el escalón en en las dos capas:
Los he elegido yo a mano, igual que los ocho pesos de la neurona de reseñas de la lección sobre la neurona artificial, porque todavía no hay nada en el curso que los busque solo. Y están elegidos para que se lean. Las dos filas de son idénticas, así que las dos neuronas ocultas miran la misma dirección y sólo las separa el sesgo: la primera pregunta si y la segunda si . Son dos rectas paralelas, y la clase positiva es la franja que queda entre ellas. Una franja es la intersección de dos semiplanos, y decidir esa intersección es lo que hace la neurona de salida: pide un sí a la primera y un no a la segunda.
| frase | ||||||
|---|---|---|---|---|---|---|
| está bien | ||||||
| está mal | ||||||
| no está bien | ||||||
| no está mal |
La columna de es la que tiene el resultado. Cuatro puntos de entrada han salido convertidos en tres —, y —, y la clase positiva es justo el de en medio. En esas coordenadas nuevas ya hay una recta que deja una clase a cada lado, y la neurona de salida no hace nada que no supiera hacer una neurona suelta. La capa oculta no ha añadido capacidad de decidir: ha cambiado el sitio desde el que se decide.
Ese es el giro que conviene guardar, porque el bloque anterior es entero sobre lo mismo. Allí las representaciones las elegía una persona —una dimensión por entrada del vocabulario, después cuentas, después vectores densos— y cada elección era una decisión escrita a mano. Una capa oculta es una representación más, con una diferencia: sus coordenadas salen de unos pesos, y a unos pesos se les puede pedir que se ajusten solos. Una red neuronal con al menos una capa oculta tiene nombre propio, perceptrón multicapa (multilayer perceptron, MLP), y es el que da título a este bloque.
Todas las rectas de una malla, y después la red
La celda hace dos cosas. Primero prueba a lo bruto una malla de rectas, para ver dónde está el techo de aciertos; después monta la red de dos capas con los pesos de arriba y la pasa por las cuatro frases. Ejecútala y compara los dos bloques de salida.
np.random.seed(0)
# --- Las cuatro frases, con sus dos coordenadas y su etiqueta.
frases = [
("está bien", (-1.0, 1.0), 1),
("está mal", (-1.0, -1.0), 0),
("no está bien", ( 1.0, 1.0), 0),
("no está mal", ( 1.0, -1.0), 1),
]
X = np.array([coords for _, coords, _ in frases]) # (4, 2): una fila por frase
y = np.array([etiqueta for _, _, etiqueta in frases])
print("X tiene forma", X.shape, " etiquetas:", y)
print()
# 1. Fuerza bruta: 41 valores por parámetro, todas las combinaciones.
g = np.linspace(-3.0, 3.0, 41)
G1, G2, GB = np.meshgrid(g, g, g, indexing="ij")
rectas = np.stack([G1.ravel(), G2.ravel(), GB.ravel()], axis=1) # (41^3, 3)
Z = rectas[:, :2] @ X.T + rectas[:, 2:3] # una z por recta y por frase
aciertos = ((Z >= 0).astype(int) == y).sum(axis=1)
print("rectas probadas: ", len(rectas))
print("aciertos máximos: ", aciertos.max(), "de 4")
print("rectas con 3 aciertos:", int((aciertos == 3).sum()))
print("rectas con 4 aciertos:", int((aciertos == 4).sum()))
print()
# 2. La red: dos neuronas ocultas y una de salida, con los pesos de la lección.
W1 = np.array([[1.0, 1.0],
[1.0, 1.0]])
b1 = np.array([1.0, -1.0])
W2 = np.array([[1.0, -1.0]])
b2 = np.array([-0.5])
def escalon(z):
return (z >= 0).astype(float)
print("frase".ljust(14), "z(1)".rjust(12), "h(1)".rjust(9), "z(2)".rjust(6), " y_hat", " y")
correctos = 0
for texto, coords, etiqueta in frases:
x = np.array(coords)
z1 = W1 @ x + b1
h1 = escalon(z1)
z2 = W2 @ h1 + b2
y_hat = int(escalon(z2)[0])
correctos += int(y_hat == etiqueta)
print(texto.ljust(14),
("(%+.0f, %+.0f)" % (z1[0], z1[1])).rjust(12),
("(%.0f, %.0f)" % (h1[0], h1[1])).rjust(9),
("%+.1f" % z2[0]).rjust(6),
"%6d" % y_hat, "%2d" % etiqueta)
print()
print("aciertos:", correctos, "de", len(frases))
print()
# 3. Las dos frases positivas caen en el mismo punto de la capa oculta.
h_bien = escalon(W1 @ np.array([-1.0, 1.0]) + b1)
h_mal = escalon(W1 @ np.array([1.0, -1.0]) + b1)
print("h(1) de «está bien» =", h_bien)
print("h(1) de «no está mal» =", h_mal)
print("¿el mismo vector?", bool(np.array_equal(h_bien, h_mal)))
La primera ejecución descarga el intérprete de Python (~15 MB). Después queda en la caché del navegador.
La malla prueba rectas, ninguna acierta las cuatro frases y se quedan en tres. Esa cifra tiene un límite que conviene decir: una malla deja fuera todas las rectas que no probó, que son casi todas, así que por sí sola no demuestra nada —para eso está la cuenta de más arriba, y por eso va antes—. Lo que aporta es el techo. Tres aciertos no es un resultado a medio camino que se arregle afinando la búsqueda; es el máximo, y el máximo se toca por todas partes.
La red acierta las cuatro. La tabla de la celda es la misma de arriba, ahora calculada, y en ella vale en las dos frases positivas y en las dos negativas, con la misma holgura por los dos lados. Y la última comprobación imprime lo que hace la capa oculta: está bien y no está mal, que ocupan esquinas opuestas del cuadrado de entrada, salen las dos como . Empezaron lo más lejos que podían estar y terminaron en el mismo sitio.
Comprueba tu intuición
Cuatro preguntas: qué demuestra la imposibilidad, si la activación tiene algo que ver, una preactivación de salida a mano y una salida de NumPy.
¿Cuál de estas afirmaciones demuestra que ninguna neurona sola acierta las cuatro frases?
Cambias el escalón por la sigmoide y lees la salida así: la frase habla bien cuando . Con esa lectura existe alguna elección de y que acierta las cuatro frases.
Con los pesos que fija la lección, ¿cuánto vale la preactivación de salida para la frase no está bien, es decir para ?
Se acepta un margen de ±0.01.
Esta es la capa oculta de la lección aplicada a no está mal. ¿Qué imprime?
import numpy as np
W1 = np.array([[1., 1.],
[1., 1.]])
b1 = np.array([1., -1.])
x = np.array([1., -1.])
print((W1 @ x + b1 >= 0).astype(float))
Los cuatro números de y los tres sesgos los escribí yo mirando el cuadrado, y esa deuda es ya la segunda vez que aparece. Ahora, además, cuesta más de pagar. Con una neurona sola se podía al menos mirar cada peso y decir de qué palabra opinaba; los pesos de una capa oculta no opinan sobre la respuesta, opinan sobre unas coordenadas intermedias que nadie ve, y su acierto o su fallo sólo se nota dos capas más allá. Repartir la responsabilidad entre capas es lo que ocupa el resto del bloque.
Antes de eso hay un problema más pequeño y más urgente, y es de escritura. La red de esta lección cabe en una tabla porque tiene tres neuronas; con un vocabulario de cincuenta mil entradas y un par de cientos de neuronas ocultas, recorrerla neurona a neurona no cabe en ninguna página ni en ningún bucle que merezca la pena ejecutar. Hace falta la forma en la que una capa entera es un producto de matrices, y un montón de ejemplos entra de una vez por el mismo producto: es la siguiente lección, sobre el forward pass.
Para profundizar1 fuente · 1 libro
De dónde sale lo de esta lección, y dónde seguir si quieres más. Nada de aquí hace falta para continuar el curso.
- Deep Learning, cap. 6: Deep Feedforward Networks
Su §6.1 hace este mismo XOR con la misma red de dos neuronas ocultas y ReLU en vez del escalón: da los pesos a mano, igual que aquí, y deja entrenarlos para después. Enseña que el modelo lineal falla, pero no lo demuestra en general.