Saltar al contenido
1/30Capítulo 1 de 30

El perceptrón desde cero: qué calcula una neurona

Crea un perceptrón en Python puro, mira cómo falla con XOR y descubre por qué su teorema promete convergencia, no rapidez.

En esta página

Hay una cinta transportadora en una fábrica. Las piezas avanzan por ella y alguien tiene que decidir cuáles se envían y cuáles vuelven atrás. De cada pieza se miden dos números: su anchura en milímetros y su peso en gramos. Esa es toda la información disponible.

La forma obvia de automatizarlo es escribir la regla. Aceptar si la anchura está por debajo de 22 milímetros. Funciona hasta que el proveedor cambia la aleación y los pesos se desplazan. Así que añades una cláusula. Luego se renegocia la tolerancia y añades otra. Seis meses después la función tiene cuarenta líneas, nadie recuerda por qué existe la línea 19 y la persona que la escribió se ha ido.

La otra forma es el tema de este curso. No escribes la regla. Escribes la forma de la regla — una plantilla con huecos — y dejas que los ejemplos decidan qué va en esos huecos. Esa inversión es todo el aprendizaje automático, y en este capítulo la plantilla es tan pequeña como puede ser una plantilla: dos números y un umbral.

Al final habrás escrito un perceptrón en unas veinte líneas de Python, lo habrás visto acertar, lo habrás visto fallar y habrás entendido ambas cosas. El archivo que escribes aquí no es un juguete que se tira en el siguiente capítulo: es el primer commit de un repositorio que acaba, dentro de veintinueve capítulos, como un agent con un bucle de herramientas y un modelo de permisos.

Un perceptrón toma las mediciones, multiplica cada una por un número que controla, las suma, añade un número más y mira el signo.

Escribe las mediciones de una pieza como un vector x=(x1,x2)\mathbf{x} = (x_1, x_2) — anchura y peso. El perceptrón mantiene un vector de pesos w=(w1,w2)\mathbf{w} = (w_1, w_2) y un sesgo bb. Su puntuación es

s(x)=wx+b=w1x1+w2x2+bs(\mathbf{x}) = \mathbf{w} \cdot \mathbf{x} + b = w_1 x_1 + w_2 x_2 + b

y su respuesta es el signo de esa puntuación: aceptar si s(x)0s(\mathbf{x}) \geq 0, rechazar en caso contrario.

Ese es todo el modelo. Todo lo que el perceptrón sabrá alguna vez sobre la fábrica vive en tres números.

Merece la pena detenerse en la geometría, porque es la imagen que seguirá funcionando durante los próximos veintinueve capítulos, incluso cuando las ecuaciones dejen de caber en una línea. El conjunto de puntos donde s(x)=0s(\mathbf{x}) = 0 — donde el perceptrón está exactamente indeciso — es una línea recta en el plano. A un lado la puntuación es positiva y todo se acepta; al otro es negativa y todo se rechaza. Para un perceptrón, aprender significa mover esa línea.

Dos hechos sobre esa línea se deducen directamente del álgebra, y ambos importan más adelante:

  • w\mathbf{w} es perpendicular a ella. El vector de pesos no está tendido a lo largo de la frontera, sino que apunta a través de ella, hacia el lado aceptado.
  • bb la desplaza sin girarla. Sin sesgo, la línea estaría obligada a pasar por el origen, lo que para una fábrica que mide milímetros y gramos sería una restricción absurda: significaría que una pieza de anchura cero y peso cero está exactamente sobre la valla.

La regla de aprendizaje, y por qué no necesita cálculo

Enlace a la sección: La regla de aprendizaje, y por qué no necesita cálculo

El perceptrón empieza sin saber nada: w=(0,0)\mathbf{w} = (0, 0) y b=0b = 0. Todas las puntuaciones son cero, así que lo acepta todo.

Ahora muéstrale un ejemplo cada vez. Etiqueta las piezas aceptadas como y=+1y = +1 y las rechazadas como y=1y = -1. Para cada ejemplo, haz una pregunta: ¿salió bien el signo? La forma compacta de escribir esa pregunta es comprobar si ys(x)y \cdot s(\mathbf{x}) es positivo: si la etiqueta y la puntuación coinciden en signo, su producto es positivo; si discrepan, es negativo.

Si la respuesta es sí, no cambies nada. Si la respuesta es no, da un empujón:

ww+yx,bb+y\mathbf{w} \leftarrow \mathbf{w} + y\,\mathbf{x}, \qquad b \leftarrow b + y

Ese es todo el algoritmo, y merece la pena entender por qué es el empujón correcto en vez de memorizarlo. Supón que una pieza debería haber sido aceptada (y=+1y = +1) y la puntuación salió negativa. Sumar x\mathbf{x} a w\mathbf{w} cambia la puntuación de esa misma pieza en

(w+x)xwx=xx=x2(\mathbf{w} + \mathbf{x}) \cdot \mathbf{x} - \mathbf{w} \cdot \mathbf{x} = \mathbf{x} \cdot \mathbf{x} = \lVert \mathbf{x} \rVert^2

que es un número positivo. La puntuación de la pieza en la que acaba de equivocarse sube, que era la dirección en la que tenía que ir. La regla no es una heurística que alguien se inventó; es el cambio más pequeño que mejora de forma demostrable el caso que tiene delante. Por supuesto puede romper otro caso distinto, y por eso vuelves a dar otra vuelta.

Fíjate en lo que falta. No hay ninguna derivada en ninguna parte. No es un descuido, y es la primera idea realmente importante del curso.

Lo que querrías derivar es el error: el recuento de piezas mal clasificadas. Pero ese recuento es una escalera: se queda plano en 4 mientras empujas la línea, y luego baja a 3 en el instante en que la línea cruza un punto. Su derivada es cero casi en todas partes e indefinida en los escalones. El cálculo no tiene de dónde agarrarse. La regla del perceptrón lo rodea al no pedir ninguna pendiente: solo pregunta «¿bien o mal?» y se mueve en una dirección que puede justificar geométricamente.

Eso es una solución real, y también es un callejón sin salida. En el capítulo 2 querremos una pérdida que venga de algún sitio en vez de haber sido elegida, en el capítulo 4 un modelo que informe de cuánta seguridad tiene, y en el capítulo 5 algo con más de una capa; y nada de eso está al alcance de una regla que solo sabe «mal». Recuperar una pendiente utilizable es lo que fuerza los dos capítulos siguientes. Pero el perceptrón puede hacer algo que ninguno de sus sucesores puede: aprender sin cálculo en absoluto.

Python puro, sin NumPy. Listas y un bucle. NumPy llega en el siguiente capítulo, cuando la aritmética deja de caber en un bucle que querrías leer; introducirlo ahora escondería la aritmética detrás de una biblioteca justo en el momento en que quieres verla.

perceptron.pyPYTHON
def score(w, b, x):
    return w[0] * x[0] + w[1] * x[1] + b


def predict(w, b, x):
    return 1 if score(w, b, x) >= 0 else -1


def train(data, epochs=200):
    """Returns (w, b, epoch_it_converged) — or None for the epoch if it never did."""
    w, b = [0.0, 0.0], 0.0
    for epoch in range(epochs):
        mistakes = 0
        for x, y in data:
            if y * score(w, b, x) <= 0:          
                w[0] += y * x[0]                 
                w[1] += y * x[1]                 
                b += y                           
                mistakes += 1
        if mistakes == 0:
            return w, b, epoch + 1
    return w, b, None

Las cuatro líneas resaltadas son el algoritmo. Todo lo demás es contabilidad.

Y la cinta, con ocho piezas medidas al pasar: cuatro que se enviaron y cuatro que volvieron atrás:

belt.pyPYTHON
BELT = [
    ((18.0, 47.0), +1), ((19.5, 52.0), +1), ((20.2, 49.0), +1), ((21.0, 55.0), +1),
    ((24.0, 61.0), -1), ((25.5, 66.0), -1), ((23.0, 70.0), -1), ((26.0, 58.0), -1),
]

w, b, epoch = train(BELT, epochs=200)
print(epoch, w, b)

Estas ocho piezas son separables por una línea recta: todas las piezas aceptadas están por debajo de 22 mm y todas las rechazadas están en 23 mm o más. Una valla vertical en 22 milímetros hace el trabajo. Así que el perceptrón debería encontrarla.

Ejecútalo:

TEXT
None [-142.1, -13.0] 54.0

Doscientas épocas, 454 correcciones, y no ha convergido. Los pesos son grandes y tienen el signo equivocado. Algo va mal; salvo que no va mal nada, y la razón es lo más útil de este capítulo.

El teorema de convergencia, y el número que te da de verdad

Enlace a la sección: El teorema de convergencia, y el número que te da de verdad

El perceptrón tiene una garantía, demostrada por Novikoff en 1962.1 Si los datos pueden separarse por una línea, el algoritmo hace como máximo

(Rγ)2\left(\frac{R}{\gamma}\right)^2

correcciones antes de dejar de hacer ninguna, donde RR es el radio de los datos, la longitud del vector de ejemplo más largo, y γ\gamma es el margen: la distancia desde el hiperplano separador hasta el punto más cercano en el espacio aumentado donde el sesgo es una tercera coordenada. Por eso centrar los datos lo cambia mientras que la distancia en milímetros no.

La garantía es incondicional y no menciona épocas, tasas de aprendizaje ni suerte. Tampoco menciona el tiempo, y esa omisión es el punto.

Mete nuestros números. Medidos directamente a partir de las ocho piezas, con el sesgo incorporado como una característica constante:

radio RRmargen γ\gammacota (R/γ)2(R/\gamma)^2correcciones realmente hechas
milímetros y gramos en bruto73,690,0452.633.55029.870
tras restar la media12,820,9891681

El teorema nunca se violó. Ejecuta la versión en bruto durante el tiempo suficiente y converge: en la época 11.976, después de 29.870 correcciones, cómodamente dentro de su cota de 2.633.550. Y esa brecha es precisamente el punto: el teorema acota el peor caso, no el caso típico. Simplemente necesitaba sesenta veces más épocas de las que cualquiera estaría dispuesto a esperar.

La segunda fila son las mismas ocho piezas, las mismas veinte líneas de código, con tres líneas añadidas para restar la anchura media y el peso medio de cada medición. Eso es todo. Ese es todo el cambio. Mueve la nube de puntos para que atraviese el origen en lugar de flotar en (22, 57), y el efecto sobre la cota es un factor de quince mil, porque ambos términos mejoran a la vez: RR cae de 74 a 13 porque los puntos ya no se miden desde un origen lejano, y γ\gamma sube de 0,045 a 0,989 porque el margen se mide contra un vector de pesos que ya no tiene que cargar con un sesgo enorme para alcanzar los datos.

belt.py (centred)PYTHON
mean_w = sum(x[0] for x, _ in BELT) / len(BELT)   # 22.15
mean_g = sum(x[1] for x, _ in BELT) / len(BELT)   # 57.25
CENTRED = [(((x[0] - mean_w), (x[1] - mean_g)), y) for x, y in BELT]

w, b, epoch = train(CENTRED, epochs=200)
print(epoch, w, b)
TEXT
2 [-4.15, -10.25] 1.0

Convergió en dos épocas, habiéndose corregido exactamente una vez.

Aquí hay una lección real, y no es «acuérdate de normalizar tus entradas», aunque deberías hacerlo. Es que una garantía sobre si un algoritmo termina no te dice nada sobre si tú estarás allí cuando lo haga, y que la brecha entre ambas cosas suele ser geometría. Esta es la primera aparición de un patrón que volverás a encontrar en el capítulo 6 con la inicialización, en el capítulo 10 con los calendarios de tasa de aprendizaje y en el capítulo 13 con la cuantización: las matemáticas dicen que la cosa es posible, y la ingeniería decide si es práctica. Un curso que solo te enseña el teorema te entrega un modelo que entrena durante tres días y te echa la culpa.

Ahora el fallo que puso fin a la primera era de las redes neuronales, y cabe en cuatro filas.

Olvida la fábrica. Toma dos entradas que son 0 o 1, y pide que la respuesta sea +1+1 cuando exactamente una de ellas sea 1:

x1x_1x2x_2yy
001-1
01+1+1
10+1+1
111-1

Esto es XOR: o exclusivo. Antes de seguir leyendo, dibuja los cuatro puntos en papel: tres esquinas de un cuadrado unidad y la cuarta. Marca las dos esquinas diagonales (0,1)(0,1) y (1,0)(1,0) como aceptar, y (0,0)(0,0) y (1,1)(1,1) como rechazar. Ahora dibuja una línea recta con los dos puntos aceptados a un lado y los dos rechazados al otro.

No puedes. No es que sea difícil, ni que necesites un algoritmo más listo; es que la línea no existe. Tres líneas de álgebra muestran por qué. Si un perceptrón acertara las cuatro, al leer las cuatro filas en orden tendríamos

b<0,w2+b0,w1+b0,w1+w2+b<0b < 0, \qquad w_2 + b \geq 0, \qquad w_1 + b \geq 0, \qquad w_1 + w_2 + b < 0

Suma las dos desigualdades del medio: w1+w2+2b0w_1 + w_2 + 2b \geq 0, así que w1+w22bw_1 + w_2 \geq -2b. La última dice w1+w2<bw_1 + w_2 < -b. Juntas: 2bw1+w2<b-2b \leq w_1 + w_2 < -b, lo que requiere 2b<b-2b < -b, lo que requiere b>0b > 0. Y la primera desigualdad dice b<0b < 0. No existe tal bb, así que no existen tales pesos. Ningún perceptrón, con ningún número posible, clasifica XOR.

Ejecútalo de todos modos, porque ver fallar a un algoritmo vale más que que te digan que fallará:

TEXT
     100 epochs -> converged=None  w=[0.0, 0.0] b=0.0  correct=2/4
   1,000 epochs -> converged=None  w=[0.0, 0.0] b=0.0  correct=2/4
 100,000 epochs -> converged=None  w=[0.0, 0.0] b=0.0  correct=2/4

No diverge, y tampoco se agita cerca de una respuesta decente. Cicla: recorre un pequeño bucle por el espacio de pesos y vuelve exactamente a donde empezó, para siempre, acertando dos de cuatro, que es lo que conseguirías adivinando. Cien mil épocas y cien son indistinguibles, porque el algoritmo no está haciendo un progreso que una ejecución más larga pueda terminar. Compáralo con la cinta, que parecía atascada en 200 épocas y en realidad estaba avanzando con esfuerzo hacia una respuesta real. Desde fuera, los dos se parecen durante los primeros segundos. Distinguirlos sin el teorema es imposible, lo que es un argumento más para conocer el teorema.

En 1969 Marvin Minsky y Seymour Papert publicaron Perceptrons, un estudio matemático con extensión de libro sobre exactamente lo que este modelo puede y no puede representar.2 XOR es su resultado más citado, y la cita suele usarse como acusación: que el libro mató la investigación en redes neuronales durante quince años por rivalidad o despecho.

Las matemáticas del libro son correctas, y son más interesantes que el ejemplo de XOR. A Minsky y Papert no les interesaba principalmente si un único perceptrón podía hacer XOR; les interesaba qué ocurre cuando a los perceptrones se les dan campos receptivos limitados — cada unidad ve solo parte de la entrada — y demostraron que ciertas propiedades globales de una imagen, como si una figura está conectada, no pueden computarse de esa manera sin importar cuántas unidades uses. Es un resultado genuinamente profundo sobre la localidad, y no tiene nada que ver con la historia popular.

La historia popular también se equivoca en la historia. Minsky y Papert hablan explícitamente de perceptrones multicapa y dicen que la cuestión de su potencia está abierta: sospechaban que extender la teoría sería «estéril», lo cual es una predicción, no una prueba, y era incorrecta. Lo que faltaba en 1969 no era la idea de apilar capas; era una forma de entrenar una pila. La regla del perceptrón no puede hacerlo: necesita saber cuánto se equivoca cada unidad, y para una unidad enterrada en medio no hay etiqueta con la que comparar. Esa brecha permaneció abierta hasta que backpropagation se popularizó en 1986,3 y cerrarla es lo que hace el capítulo 5.

Así que el resumen honesto es este. El libro demostró una limitación real de un modelo real. El colapso de la financiación del campo en los setenta tuvo muchas causas, una de las cuales fue que las promesas hechas para los perceptrones a principios de los sesenta habían sido desmesuradas. Y el obstáculo técnico era resoluble, pero nadie tenía todavía la herramienta.

El perceptrón tiene sesenta y ocho años y acabas de escribir uno. Merece la pena ser preciso sobre qué partes de él siguen en la máquina con la que terminarás este curso, porque la respuesta es: más de lo que imaginas.

Sigue aquí. La forma — multiplicar por pesos, sumar, añadir un sesgo, aplicar una función no lineal al resultado — es exactamente la forma de una unidad en cada red neuronal de este curso, incluidas las que hay dentro de un bloque transformer en el capítulo 9. La regla de actualizar al equivocarse es stochastic gradient descent disfrazado: es precisamente lo que obtienes al aplicar el método del capítulo 3 a una función de pérdida concreta. Entrenar de forma incremental — un puñado de ejemplos cada vez en lugar de todo el conjunto de datos de una vez — sigue siendo la forma en que se entrenan los modelos hoy a cualquier escala. El capítulo 3 mide dónde se sitúa realmente ese compromiso.

Desapareció. El propio umbral: reemplazado en el capítulo 4 por una función que produce una probabilidad en vez de un veredicto, porque «rechazar» y «rechazar, pero estuvo cerca» son piezas de información distintas y el signo tira esa diferencia. La capa única, reemplazada en el capítulo 5. Y las características elegidas a mano: alguien escogió anchura y peso para esta cinta, y esa elección hizo más trabajo que el algoritmo. El capítulo 8 es donde el modelo empieza a escoger las suyas.

El perceptrón se atascó en dos cosas a la vez, y resulta que son la misma cosa.

No puede representar XOR, porque una línea no basta. Arreglarlo significa apilar capas: una primera capa que dobla el espacio, una segunda que dibuja la línea en el espacio doblado. Ese es el capítulo 5.

Pero no puedes entrenar una pila con la regla del perceptrón, porque solo sabe «mal», y una unidad en medio de una red no tiene una etiqueta propia sobre la que equivocarse. Para entrenar una pila necesitas saber cuánto se equivoca, y en qué dirección, para cada peso: necesitas una pendiente. Y la función de error del perceptrón, la escalera, no tiene ninguna.

Así que antes de la pila tiene que haber una función de pérdida con una derivada utilizable. Y tampoco una elegida porque sea cómoda de derivar: una que venga de algún sitio, que diga algo verdadero sobre los datos y cuyo gradiente salga de ese significado en lugar de estar diseñado al revés para parecer ordenado.

Ese es el capítulo 2, y empieza planteando una pregunta que el perceptrón nunca tuvo que responder: no «¿esta pieza es buena?», sino «¿qué probabilidad tienen estas lecturas, si esta es la verdad?»


También merece la pena leer junto a este capítulo el artículo original de Rosenblatt, The Perceptron: A Probabilistic Model for Information Storage and Organization in the Brain (Psychological Review 65(6), 1958), que es más legible de lo que su reputación sugiere; McCulloch y Pitts, A Logical Calculus of the Ideas Immanent in Nervous Activity (Bulletin of Mathematical Biophysics 5, 1943), el artículo que modeló por primera vez una neurona como un umbral sobre una suma ponderada; la sección sobre el perceptrón de A Course in Machine Learning, de Hal Daumé III, que deriva la misma actualización con un énfasis distinto; y los capítulos 2 y 3 de Mathematics for Machine Learning, de Deisenroth, Faisal y Ong, para el álgebra lineal, si el recuadro de arriba te dejó con ganas de más de lo que ofrecía.

  1. Novikoff, A. B. J. On convergence proofs for perceptrons. Proceedings of the Symposium on the Mathematical Theory of Automata, vol. 12, pp. 615–622 (Polytechnic Institute of Brooklyn, 1962). La formulación y la demostración originales de la cota de errores utilizada arriba.

  2. Minsky, M. and Papert, S. Perceptrons: An Introduction to Computational Geometry (MIT Press, 1969; edición ampliada de 1988). El resultado de XOR es elemental; los resultados sustanciales tratan sobre predicados con orden limitado y conectividad.

  3. Rumelhart, D. E., Hinton, G. E. and Williams, R. J. Learning representations by back-propagating errors. Nature 323, pp. 533–536 (1986).

¿Listo para dejar que elija LIA?

Crea con todos los modelos de IA en un mismo sitio. Empieza gratis hoy.