XOR: por qué necesitamos capas ocultas

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 φ\varphi 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 φ\varphi, 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.

Pulsa «No separable» y deja la recta trabajando: no llega a pararse, y se queda roja y a trazos porque siempre tiene algún punto del lado que no le toca. Arrastra después una de las cuatro esquinas hacia el centro y verás que encuentra sitio y se pone verde. Lo imposible no era el número de puntos, sino dónde estaban.

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 x1=+1x_1 = +1 cuando la frase contiene no y x1=1x_1 = -1 cuando no lo contiene, y sea x2=+1x_2 = +1 cuando la palabra que valora es bien y x2=1x_2 = -1 cuando es mal. La etiqueta yy vale 11 si la frase habla bien y 00 si habla mal, que son los dos valores que entrega el escalón.

frasex\mathbf{x}yy
está bien(1,+1)(-1,\, +1)^{\top}11
está mal(1,1)(-1,\, -1)^{\top}00
no está bien(+1,+1)(+1,\, +1)^{\top}00
no está mal(+1,1)(+1,\, -1)^{\top}11

La etiqueta vale 11 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 wR2\mathbf{w} \in \mathbb{R}^{2} y bRb \in \mathbb{R} con los que la neurona escaloˊn(wx+b)\text{escalón}\left(\mathbf{w}^{\top}\mathbf{x} + b\right) acierta las cuatro. Acertar las dos positivas es z0z \geq 0 en las dos, y acertar las dos negativas es z<0z < 0 en las dos. Sumemos cada pareja por separado, con z(x)=w1x1+w2x2+bz(\mathbf{x}) = w_1 x_1 + w_2 x_2 + b:

z(1,+1)+z(+1,1)=(w1+w2+b)+(w1w2+b)=2b,z(-1, +1) + z(+1, -1) = \left(-w_1 + w_2 + b\right) + \left(w_1 - w_2 + b\right) = 2b,

y para la otra diagonal, con los mismos pesos,

z(1,1)+z(+1,+1)=(w1w2+b)+(w1+w2+b)=2b.z(-1, -1) + z(+1, +1) = \left(-w_1 - w_2 + b\right) + \left(w_1 + w_2 + b\right) = 2b.

Los pesos se cancelan en las dos, y las dos sumas dan el mismo número. Pero la primera suma dos cantidades 0\geq 0, luego 2b02b \geq 0; y la segunda suma dos cantidades <0< 0, luego 2b<02b < 0. Un mismo bb 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 zz 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 ±1\pm 1 ni de que los cuatro puntos formaran un cuadrado. Sean p1,p2\mathbf{p}_1, \mathbf{p}_2 dos ejemplos de una clase y n1,n2\mathbf{n}_1, \mathbf{n}_2 dos de la otra, y supongamos que el segmento que une los primeros corta al que une los segundos en un punto q\mathbf{q}. Cortarse significa que ese punto se escribe de las dos maneras,

q=λp1+(1λ)p2=μn1+(1μ)n2,\mathbf{q} = \lambda \mathbf{p}_1 + (1 - \lambda)\, \mathbf{p}_2 = \mu \mathbf{n}_1 + (1 - \mu)\, \mathbf{n}_2,

con λ,μ[0,1]\lambda, \mu \in [0, 1]. Una función afín reparte esas combinaciones, porque los pesos suman 11 y el sesgo sobrevive intacto:

z(λu+(1λ)v)=λ(wu+b)+(1λ)(wv+b)=λz(u)+(1λ)z(v).z\left(\lambda \mathbf{u} + (1 - \lambda)\mathbf{v}\right) = \lambda \left(\mathbf{w}^{\top}\mathbf{u} + b\right) + (1 - \lambda)\left(\mathbf{w}^{\top}\mathbf{v} + b\right) = \lambda\, z(\mathbf{u}) + (1 - \lambda)\, z(\mathbf{v}).

Si la neurona acierta, z(p1)z(\mathbf{p}_1) y z(p2)z(\mathbf{p}_2) son 0\geq 0, y entonces z(q)z(\mathbf{q}) —una media de los dos con pesos no negativos— también lo es. Por el otro camino, z(n1)z(\mathbf{n}_1) y z(n2)z(\mathbf{n}_2) son <0< 0 y z(q)z(\mathbf{q}) tiene que ser <0< 0. El mismo punto con dos signos incompatibles.

Con esto, el resultado no cambia si prefieres codificar las frases con 00 y 11 en lugar de 1-1 y +1+1: los cuatro puntos son entonces las esquinas del cuadrado unidad, los dos segmentos se cruzan en (1/2,1/2)(1/2,\, 1/2) 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 z0z \geq 0 o z<0z < 0. Es lo único que hay que revisar. Sea φ\varphi estrictamente creciente —la sigmoide y la tangente hiperbólica lo son— y sea θ\theta el umbral con el que decidas leer su salida como un sí. Al ser creciente es invertible en su recorrido, de modo que φ(z)θ\varphi(z) \geq \theta equivale a zφ1(θ)z \geq \varphi^{-1}(\theta), y el conjunto de las frases que reciben un sí es

{xR2  :  wx+bφ1(θ)0},\left\{\, \mathbf{x} \in \mathbb{R}^{2} \;:\; \mathbf{w}^{\top}\mathbf{x} + b - \varphi^{-1}(\theta) \geq 0 \,\right\},

que es el semiplano de la misma neurona con el sesgo bφ1(θ)b - \varphi^{-1}(\theta). La demostración se aplica sin tocar una letra.

ReLU no es estrictamente creciente y tampoco escapa. Con θ>0\theta > 0, pedir ReLU(z)θ\text{ReLU}(z) \geq \theta es pedir zθz \geq \theta, otro semiplano; con θ0\theta \leq 0 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 x\mathbf{x} 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.

Tres columnas de círculos unidas por flechas. La primera, con el rótulo capa de entrada, tiene dos círculos de trazo discontinuo etiquetados x1 y x2. La segunda, la capa oculta, tiene dos círculos de trazo verde continuo, y cada uno recibe una flecha de los dos círculos de la entrada. La tercera, la capa de salida, tiene un solo círculo verde que recibe una flecha de cada neurona oculta. El haz de flechas de la izquierda lleva el rótulo W(1), b(1) y el de la derecha W(2), b(2).
Cada flecha es un peso y cada círculo verde es una neurona con su sesgo. Este dibujo es la forma que tendrán todas las redes del bloque: sólo cambian cuántos círculos hay por columna y cuántas columnas hay.

Con varias neuronas en un nivel, el vector de pesos de cada una pasa a ser una fila. La neurona jj de la capa ll guarda sus pesos en la fila jj de W(l)\mathbf{W}^{(l)} y su sesgo en bj(l)b^{(l)}_j, y lo que en la lección sobre la neurona artificial eran los escalares zz y aa son ahora las coordenadas zj(l)z^{(l)}_j y hj(l)h^{(l)}_j. Es el mismo objeto apilado; por dentro no ha cambiado nada. Con h(0)=x\mathbf{h}^{(0)} = \mathbf{x}, la capa oculta calcula

z(1)=W(1)x+b(1),h(1)=φ(z(1)),\mathbf{z}^{(1)} = \mathbf{W}^{(1)}\mathbf{x} + \mathbf{b}^{(1)}, \qquad \mathbf{h}^{(1)} = \varphi\left(\mathbf{z}^{(1)}\right),

con W(1)R2×2\mathbf{W}^{(1)} \in \mathbb{R}^{2 \times 2} y b(1),z(1),h(1)R2\mathbf{b}^{(1)}, \mathbf{z}^{(1)}, \mathbf{h}^{(1)} \in \mathbb{R}^{2}, y con φ\varphi aplicada coordenada a coordenada. La capa de salida hace lo mismo sobre h(1)\mathbf{h}^{(1)}:

z(2)=W(2)h(1)+b(2),y^=φ(z1(2)),\mathbf{z}^{(2)} = \mathbf{W}^{(2)}\mathbf{h}^{(1)} + \mathbf{b}^{(2)}, \qquad \hat{y} = \varphi\left(z^{(2)}_1\right),

con W(2)R1×2\mathbf{W}^{(2)} \in \mathbb{R}^{1 \times 2} y b(2)R1\mathbf{b}^{(2)} \in \mathbb{R}^{1}. 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 φ\varphi en las dos capas:

W(1)=(1111),b(1)=(11),W(2)=(11),b(2)=(12).\mathbf{W}^{(1)} = \begin{pmatrix} 1 & 1 \\ 1 & 1 \end{pmatrix}, \quad \mathbf{b}^{(1)} = \begin{pmatrix} 1 \\ -1 \end{pmatrix}, \quad \mathbf{W}^{(2)} = \begin{pmatrix} 1 & -1 \end{pmatrix}, \quad \mathbf{b}^{(2)} = \begin{pmatrix} -\tfrac{1}{2} \end{pmatrix}.

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 W(1)\mathbf{W}^{(1)} 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 x1+x21x_1 + x_2 \geq -1 y la segunda si x1+x21x_1 + x_2 \geq 1. 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: z1(2)=h1(1)h2(1)1/2z^{(2)}_1 = h^{(1)}_1 - h^{(1)}_2 - 1/2 pide un sí a la primera y un no a la segunda.

El plano de las dos coordenadas. Dos rectas paralelas de pendiente negativa, x1 + x2 = 1 y x1 + x2 = menos 1, dejan entre ellas una franja sombreada en verde que cruza el dibujo de la esquina superior izquierda a la inferior derecha. Los dos puntos verdes, en menos 1 coma 1 y en 1 coma menos 1, caen dentro de la franja; los dos puntos huecos, en menos 1 coma menos 1 y en 1 coma 1, caen fuera, uno a cada lado. Cada región lleva escrito el vector que la capa oculta le asigna: 1 coma 0 en la franja, 0 coma 0 abajo a la izquierda y 1 coma 1 arriba a la derecha.
Las dos neuronas ocultas dibujan las dos rectas, y la clase positiva es lo que queda en medio. En cada región está el vector que la capa oculta le asigna: los dos puntos verdes caen en la misma, así que salen de ella con el mismo (1, 0) aunque hayan entrado por esquinas opuestas.
frasex1+x2x_1 + x_2z(1)\mathbf{z}^{(1)}h(1)\mathbf{h}^{(1)}z1(2)z^{(2)}_1y^\hat{y}yy
está bien00(1,1)(1,\, -1)^{\top}(1,0)(1,\, 0)^{\top}+0.5+0.51111
está mal2-2(1,3)(-1,\, -3)^{\top}(0,0)(0,\, 0)^{\top}0.5-0.50000
no está bien22(3,1)(3,\, 1)^{\top}(1,1)(1,\, 1)^{\top}0.5-0.50000
no está mal00(1,1)(1,\, -1)^{\top}(1,0)(1,\, 0)^{\top}+0.5+0.51111

La columna de h(1)\mathbf{h}^{(1)} es la que tiene el resultado. Cuatro puntos de entrada han salido convertidos en tres —(0,0)(0,\, 0)^{\top}, (1,0)(1,\, 0)^{\top} y (1,1)(1,\, 1)^{\top}—, 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.

import numpy as np

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

La primera ejecución descarga el intérprete de Python (~15 MB). Después queda en la caché del navegador.

La malla prueba 413=6892141^3 = 68\,921 rectas, ninguna acierta las cuatro frases y 1712317\,123 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 z1(2)z^{(2)}_1 vale +0.5+0.5 en las dos frases positivas y 0.5-0.5 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 (1,0)(1,\, 0)^{\top}. 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 σ(z)0.5\sigma(z) \geq 0.5. Con esa lectura existe alguna elección de w\mathbf{w} y bb que acierta las cuatro frases.

Con los pesos que fija la lección, ¿cuánto vale la preactivación de salida z1(2)z^{(2)}_1 para la frase no está bien, es decir para x=(1,1)\mathbf{x} = (1,\, 1)^{\top}?

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 W(1)\mathbf{W}^{(1)} 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
    libroGoodfellow, Bengio y Courville, 2016deeplearningbook.orgEN

    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.