Aprendizaje en contexto: el prompt como programa
27 min de lectura
Hasta aquí, en este bloque, aprender ha querido decir una sola cosa: mover . La lección sobre el entrenamiento lo hacía un paso de gradiente cada vez, y la de las leyes de escala le puso precio al recorrido entero y concluyó que, para lo que costó, al mini-GPT no le sobra tamaño: le falta texto. Los modelos grandes hacen otra cosa que también parece aprender, y en la que no se mueve. Escribe en el prompt unos cuantos casos resueltos de algo que nunca se les enseñó, añade uno sin resolver, y lo resuelven. La primera lección del bloque prometió explicar cómo llega a eso un modelo que sólo aprendió a continuar texto, y esta lección cumple la promesa.
Escribe dos listas con las mismas cuatro palabras a la izquierda de los dos puntos y dos tareas distintas a la derecha, y termina las dos con el mismo caso sin resolver:
| una lista | la otra |
|---|---|
| hijo: hijos | hijo: hija |
| amigo: amigos | amigo: amiga |
| niño: niños | niño: niña |
| bueno: buenos | bueno: buena |
| hermano: | hermano: |
Tú has completado la primera con hermanos y la segunda con hermana antes de terminar de leer, y un modelo grande hace lo mismo. Nada en su entrenamiento le pidió poner palabras en plural ni en femenino, y el caso es idéntico en las dos columnas. Sólo cambia lo que hay a la derecha de los dos puntos en las líneas de arriba, y con eso cambia la función que el modelo calcula sobre hermano.
Mira el modelo como una función de un solo argumento, el texto que le das, con fijo dentro. Los ejemplos forman parte de ese argumento y hacen de programa: dicen qué hay que hacer con el caso que viene detrás. Ningún peso guarda el plural en un sitio y el femenino en otro. Lo que el modelo tiene es lo que aprendió al predecir su texto, y en el texto hay listas, glosarios, ejercicios resueltos: documentos que repiten una misma operación caso tras caso. Continuar bien uno de ellos exige averiguar qué operación sigue.
Una tarea es una condición, no un peso
Llamemos ejemplo a un caso resuelto escrito en el prompt, una secuencia de tokens como hijo: hijos con su salto de línea detrás; son los ejemplos en orden, es el caso sin resolver con el que acaba el prompt (hermano:) e la respuesta que se le pide ( hermanos, con su espacio). Los tres son secuencias de tokens: este sin subíndice es un caso entero, no el token de las lecciones anteriores, y es la letra del artículo de GPT-3, no la de top-k. Lo que el modelo le da a una respuesta sale de la regla de la cadena de la primera lección, con el prompt delante:
un término por cada token de la respuesta, y todos salen, gracias a la máscara, de una sola pasada por el prompt seguido de .
Un modelo hace aprendizaje en contexto (in-context learning, ICL) con una tarea cuando, con fijo, los ejemplos le suben la probabilidad de la respuesta que esa tarea pide hasta que es la más probable, sin que nadie lo haya entrenado para esa tarea. El artículo de GPT-3 lo midió sin ejemplos, con uno y con cuantos cupieran en su ventana de 2 048 tokens (entre 10 y 100, según la tarea), y los llamó zero-shot, one-shot y few-shot. Aprender, aquí, es cambiar el argumento de , no sus parámetros.
Con dos tareas que reciben los mismos casos, como las dos columnas, la prueba es la diferencia entre los logaritmos de sus dos respuestas: si el modelo lee la tarea en los ejemplos, sube con en una lista y baja en la otra. Dos filas que se separan.
Predecir el texto obliga a reconocer la tarea
Para ver por qué pasa, dejemos la red un momento y preguntemos qué haría el texto. Llamemos a la distribución de la que sale el texto de entrenamiento: lo que el texto es, frente a lo que el modelo cree, . Supongamos que el texto está hecho de documentos y que cada uno sigue una tarea : se elige una con probabilidad , y después se escriben sus casos resueltos uno tras otro, independientes entre sí una vez fijada la tarea. Una lista de plurales es un documento; un capítulo de Marianela, otro, con su propia tarea. Ningún texto real se escribe así, y lo que sigue vale en la medida en que el texto se le parezca. (Es una versión reducida del modelo de Xie y sus coautores; ellos llaman a la tarea, y aquí tiene dueño.)
Con ese modelo, la probabilidad de la respuesta, leídos los ejemplos y el caso, se reparte entre las tareas:
Es la regla de la cadena aplicada al par y sumada sobre , y en el primer factor los ejemplos desaparecen porque, fijada la tarea, no dicen nada más sobre la respuesta. Así que lo único que hacen es mover el segundo factor: cuánto cree el texto, a la vista del prompt, en cada tarea. La regla de Bayes lo da, con una constante que es la misma para todas:
La constante se va al comparar dos tareas, y . Tomando logaritmos,
que se lee término a término, y cada término dice una cosa.
Cada ejemplo suma uno. Si los ejemplos son de verdad de , cada término vale, en promedio, cero o más: es la desigualdad de la lección sobre la perplejidad, según la cual ninguna distribución paga menos por un texto que aquella de la que sale. La suma crece con , y el cociente, como su exponencial: unos pocos ejemplos que distingan bien dejan a una sola tarea con casi toda la probabilidad, y la respuesta es la suya.
Lo que distingue es lo que difiere. En las dos columnas del principio, las palabras de la izquierda son las mismas, y las dos tareas las reciben igual, así que su parte de cada término se cancela y el término del caso vale cero. Queda la derecha de los dos puntos, y ahí cada ejemplo pesa mucho: hijo: hija es casi imposible en una lista de plurales. El programa entero está en las respuestas.
Y una tarea que el texto no tiene no aparece. El primer término no depende de : si , vale , y ninguna suma finita lo levanta. Los ejemplos no crean tareas. Eligen entre las que el texto ya traía, y una tarea rara cuesta más ejemplos que una frecuente.
Lo que la red necesita para imitarlo
Falta la red. Entrenar es bajar la pérdida sobre el texto, y en cualquier contexto la pérdida media más baja posible es la que paga el propio texto. Para la respuesta de un documento,
con las dos esperanzas sobre los documentos del texto, y con igualdad sólo cuando y coinciden en esos contextos: es otra vez la desigualdad de la perplejidad. Una red que se acercara a ese mínimo calcularía la suma sobre tareas sin que nadie la hubiera escrito: reconocería la tarea en los ejemplos y la aplicaría al caso. La pérdida no menciona tareas en ningún sitio. El aprendizaje en contexto sale de predecir bien un texto que las contiene.
La desigualdad dice también qué le hace falta a la red para acercarse a ese mínimo, y son tres cosas. Que su texto tenga las tareas, para que no sea cero en lo que leyó: GPT-3 leyó 300 000 millones de tokens de páginas web y de libros, con sus listas y sus glosarios, y el mini-GPT, una novela. Parámetros para guardar muchas tareas y distinguirlas: en el artículo de GPT-3, lo que aportan los ejemplos crece con el tamaño, del modelo de 1 300 millones de parámetros al de 175 000 millones. Y leer lo bastante atrás, porque los ejemplos quedan decenas de tokens antes del caso. Son los ejes y de las leyes de escala, más la ventana, y en los tres el mini-GPT está lejos.
La derivación da el mecanismo mínimo, no el techo. Min y sus coautores cambiaron al azar las respuestas de los ejemplos en tareas de clasificación, y los modelos grandes apenas empeoraron: los ejemplos servían sobre todo para mostrar el formato y el tipo de caso, que es lo que predice un modelo que reconoce. Pero Wei y los suyos encontraron que los más grandes sí siguen ejemplos que contradicen lo que traen aprendido, y eso ya no es elegir entre tareas conocidas.
Lo que el mini-GPT hace con las dos listas
Las dos celdas corren sobre el mini-GPT, el checkpoint de la lección sobre el entrenamiento, y no lo entrenan: sólo lo leen. La primera escribe las dos columnas del principio con de 0 a 4 y pregunta, con la regla de la cadena de arriba, cuánto da el modelo a hermanos y a hermana detrás de hermano:. Después mira qué escribe él, eligiendo siempre la favorita.
import numpy as np
from pyodide.http import open_url
exec(open_url("/courses/llm-agents/bpe.py").read()) # codificar, decodificar
exec(open_url("/courses/llm-agents/minigpt.py").read()) # MiniGPT, softmax
F = [tuple(par) for par in json.load(open_url("/courses/llm-agents/bpe-merges.json"))]
modelo = MiniGPT.cargar(open_url("/courses/llm-agents/minigpt.json").read())
def log_p(prompt, y):
"""log p(y | prompt) según el mini-GPT: una pasada, un término por token de y."""
a, b = codificar(prompt, F), codificar(y, F)
Z = modelo.adelante(np.array([a + b]))[0][0] # (len(a) + len(b), 512)
logP = Z - Z.max(axis=-1, keepdims=True)
logP -= np.log(np.exp(logP).sum(axis=-1, keepdims=True))
return sum(logP[len(a) - 1 + j, v] for j, v in enumerate(b)) # la fila anterior a cada token
listas = {"plural": ["hijo: hijos", "amigo: amigos", "niño: niños", "bueno: buenos"],
"femenino": ["hijo: hija", "amigo: amiga", "niño: niña", "bueno: buena"]}
x = "hermano:" # el caso, al final del prompt
print(" K lista log p(hermanos) log p(hermana) diferencia")
for K in range(5):
for nombre, lista in listas.items():
prompt = "".join(e + "\n" for e in lista[:K]) + x # K ejemplos, uno por línea
a, b = log_p(prompt, " hermanos"), log_p(prompt, " hermana")
print("%2d %-9s %12.2f %15.2f %+11.2f" % (K, nombre, a, b, a - b))
print()
for nombre, lista in listas.items():
prompt = codificar("".join(e + "\n" for e in lista) + x, F)
q = softmax(modelo.adelante(np.array([prompt]))[0][0, -1])
sigue = modelo.generar(prompt, 12, temperatura=0)[len(prompt):]
print("tras la lista %s, la favorita es %r (%.2f) y escribe %r"
% (nombre, decodificar([int(q.argmax())], F), q.max(), decodificar(sigue, F)))
La primera ejecución descarga el intérprete de Python (~15 MB). Después queda en la caché del navegador y se reutiliza en todas las lecciones.
Con las dos listas son el mismo prompt, y hermana gana por nats: la novela la usa mucho más. Con ejemplos, la diferencia sube en las dos listas, también en la del femenino, donde tendría que bajar, y nunca pasa de cero. Lo que distingue una tarea de otra es la separación entre las dos filas, y es de nats con un ejemplo y de con cuatro: los ejemplos no se acumulan, que era lo primero que pedía la derivación. Las dos respuestas, además, son casi imposibles para el modelo, entre y nats. Lo que espera está en las dos últimas líneas: un salto de línea, con la mitad de la probabilidad, y después un diálogo.
La segunda celda busca dos de las tres cosas que la red necesita: qué tarea tiene el texto del mini-GPT para unos dos puntos, y hasta dónde lee él. Para lo segundo toma 64 tramos de 32 tokens del texto reservado y le da cada uno dos veces seguidas. La primera vez, la pérdida del token dice cuánto le sirve tener tokens delante; la segunda, cuánto le sirve haber leído ya, 32 posiciones atrás, el tramo entero.
import re
from collections import Counter
texto = quitar_cabecera(open_url("/courses/llm-agents/corpus.txt").read())
tras = Counter(texto[i + 1:i + 3] for i, c in enumerate(texto) if c == ":")
lista = sum(re.fullmatch(r"\w+: \w+", linea) is not None for linea in texto.split("\n"))
print("dos puntos en la novela: %d; detrás, un párrafo nuevo %d veces y unas comillas %d"
% (sum(tras.values()), tras["\n\n"], tras[" «"]))
print("líneas con la forma «palabra: palabra»: %d" % lista)
ids = np.array(codificar(texto, F))
res = ids[-(len(ids) // 10):] # el texto reservado, el último 10 %
H, rng = 32, np.random.default_rng(0)
S = np.stack([res[i:i + H] for i in rng.integers(0, len(res) - H, size=64)]) # (64, 32)
W = np.concatenate([S, S], axis=1) # (64, 64): cada tramo, dos veces
Z = modelo.adelante(W[:, :-1])[0]
logP = Z - Z.max(axis=-1, keepdims=True)
logP -= np.log(np.exp(logP).sum(axis=-1, keepdims=True))
nats = -np.take_along_axis(logP, W[:, 1:, None], axis=-1)[..., 0].mean(axis=0) # (63,), por fila
print()
print("token j del tramo 2-4 5-8 9-16 17-32")
for vez, desde in [("la primera vez", 0), ("la segunda vez", H)]:
# el token j lo predice la fila j - 1 la primera vez, y la fila H + j - 1 la segunda
print("%-22s" % vez + "".join(" %6.2f" % nats[desde + a - 2:desde + b - 1].mean()
for a, b in [(2, 4), (5, 8), (9, 16), (17, 32)]))
La primera ejecución descarga el intérprete de Python (~15 MB). Después queda en la caché del navegador y se reutiliza en todas las lecciones.
De los 204 dos puntos de la novela, 95 abren un párrafo nuevo, que en Marianela es un diálogo, y 40 unas comillas; ninguna de sus líneas tiene la forma palabra: palabra. En el texto del mini-GPT, una lista de plurales tiene probabilidad cero, y la tarea que hay detrás de unos dos puntos es que alguien hable. Eso es lo que hizo en la primera celda: reconocer la única tarea que conoce.
La tabla dice lo otro. La primera vez, la pérdida baja de a mientras el token tiene hasta siete delante, y de ahí en adelante se queda en y : lo que el mini-GPT lee más allá de unos ocho tokens no le sirve para predecir, y los ejemplos de la primera celda quedan, casi todos, más atrás. Kaplan midió la misma curva en ventanas de 1 024 tokens, y en sus modelos bajaba como una ley de potencia hasta el final. La segunda vez, el mini-GPT paga lo mismo, y nats, por tokens que tiene escritos 32 posiciones atrás. Copiar lo que acaba de leer es el aprendizaje en contexto más elemental que hay, un solo ejemplo de la tarea «repite», y no lo hace. Olsson y sus coautores encontraron el mecanismo que lo hace, dos cabezas de atención en capas distintas que completan A B … A con B, y el mini-GPT tiene las dos capas que hacen falta. No lo aprendió, y su texto apenas se lo pedía: en ventanas de 64 tokens de una novela, lo que acaba de pasar casi nunca vuelve a pasar igual.
Comprueba tu intuición
Cuatro preguntas: una cuenta con el cociente de la derivación, una tarea que el texto no tiene, lo que miden las celdas y lo que le falta al mini-GPT.
Dos tareas reciben los mismos casos. A priori, en el texto, la primera tiene probabilidad y la segunda , y los ejemplos del prompt son de la segunda: cada uno suma nats a su favor en el logaritmo del cociente de la lección. ¿Cuántos ejemplos hacen falta, como mínimo, para que la segunda sea la tarea más probable?
Se acepta un margen de ±0.
En el modelo del texto de la lección, ¿qué hacen unos ejemplos impecables de una tarea que el texto de entrenamiento no contiene nunca, con ?
Marca lo que muestran las dos celdas sobre el mini-GPT.
Marca todas las opciones correctas. Se corrige todo o nada: no hay puntuación parcial.
Según el argumento de la lección, ¿qué cambio haría más probable que un mini-GPT distinguiera el plural del femenino a partir de los ejemplos?
El mini-GPT no lee tareas en su prompt, pero su forma es la de GPT-3: texto que entra, texto que sale y fijo dentro. Lo que esta lección le ha añadido es para qué sirve esa forma: con los pesos quietos, el prompt es el único sitio por el que se le puede decir algo a un modelo, y buena parte de lo que queda de curso se escribe ahí.
Antes de envolver ese modelo en nada, falta escribirlo una vez como función y congelarlo: el
checkpoint, el tokenizador y lo que las lecciones del bloque le han ido añadiendo, detrás de una
sola firma, modelo(prompt) -> texto, que los bloques siguientes puedan llamar sin abrirla. Es
la lección siguiente, el proyecto del bloque: el modelo
hecho función.
Para profundizar5 fuentes · 5 papers
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.
- Language Models are Few-Shot Learners
El artículo de GPT-3 y del nombre. La sección 2 define zero-shot, one-shot y few-shot, y la figura 1.2 enseña que lo que aportan los ejemplos crece con el tamaño del modelo.
- An Explanation of In-context Learning as Implicit Bayesian Inference
El modelo del texto de esta lección, con cadenas de Markov ocultas en lugar de ejemplos independientes, y un corpus sintético en el que Transformers pequeños sí aprenden en contexto.
- In-context Learning and Induction Heads
El mecanismo que copia lo ya leído, dos cabezas en capas distintas, y el momento del entrenamiento en que aparece. La prueba de la segunda celda es la suya, con tokens al azar.
- Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?
Cambian al azar las respuestas de los ejemplos y los modelos apenas empeoran: lo que cuenta es el formato y el tipo de caso. Lo que predice un modelo que reconoce la tarea en vez de aprenderla.
- Larger language models do in-context learning differently
La otra cara: los modelos más grandes sí siguen ejemplos que contradicen lo que traen aprendido. Hasta dónde llega reconocer, y dónde empieza algo más.