RLHF: mejorar la recompensa sin alejarse
32 min de lectura
Con el modelo de recompensa ya entrenado y congelado, , el bloque tiene con qué puntuar respuestas y una política que mejorar, la del ajuste supervisado. Lo que cambia ahora es de dónde salen las respuestas con que se entrena. En el ajuste las escribía alguien y el modelo aprendía a imitarlas; aquí las escribe el propio modelo, las puntúa, y el modelo aprende de lo que reciben. Eso es el aprendizaje por refuerzo: aprender de lo que pasa después de actuar, no de un ejemplo de cómo actuar.
El método completo se llama aprendizaje por refuerzo con retroalimentación humana (reinforcement learning from human feedback, RLHF), porque la recompensa sale de comparaciones de personas, y tiene un riesgo conocido desde sus primeros experimentos. En uno de ellos, un brazo robótico debía aprender a agarrar un objeto a partir de comparaciones de personas que lo veían por una cámara. Aprendió a poner la mano entre la cámara y el objeto, donde parecía que lo agarraba. Esta lección escribe el objetivo que RLHF optimiza, que lleva dentro el freno contra eso, y el gradiente que lo sube.
Dos dificultades separan la idea del algoritmo. La primera es el sorteo, que no deja pasar ningún gradiente: un cambio pequeño de deja el token sorteado igual o lo cambia por otro, y la recompensa salta con él. Lo que sí cambia poco a poco es la probabilidad de cada respuesta. De ahí sale el método: sortear unas cuantas, puntuarlas, y hacer más probables las que puntúan por encima de la media y menos las de debajo.
La segunda dificultad es la propia recompensa. El modelo de la lección anterior acertaba con rivales nuevos en las preguntas que conocía y echaba una moneda al aire en las que no. Una política que sólo persigue su número hace lo que hizo el brazo: encuentra dónde se equivoca la recompensa a su favor. Por eso RLHF cobra la distancia: cada respuesta paga por lo improbable que la encontraba el modelo de partida, y la política sólo se aleja de él donde lo que gana compensa lo que paga. Es el freno que el ajuste supervisado no tenía, y mide la distancia en las distribuciones, no en los pesos.
El objetivo: recompensa menos distancia
Fijemos los objetos. es la política que se entrena, y empieza siendo el modelo del ajuste supervisado. es ese mismo modelo congelado, la referencia contra la que se mide la distancia. es el modelo de recompensa, congelado también: no se mueve en toda la lección, y por eso los dos conjuntos de parámetros llevan letras distintas.
La distancia es la divergencia de Kullback–Leibler (KL) de la política a la referencia: la media, sobre las respuestas de la política, del logaritmo del cociente de sus probabilidades,
Nunca es negativa, y vale cero sólo si las dos políticas coinciden: es la desigualdad de la lección sobre la perplejidad, con en el papel de las frecuencias y en el del modelo. No es simétrica, y la asimetría es lo que interesa. La media lleva los pesos de , así que una respuesta cuesta mucho cuando la política la escribe a menudo y la referencia casi nunca. Dejar de escribir algo que la referencia sí escribiría no se cobra por sí mismo: esa respuesta entra en la media con peso cero.
Una respuesta es una secuencia de tokens, y la regla de la cadena escribe su probabilidad como un producto con un factor por token, así que el logaritmo del cociente es una suma. Escribamos para el factor de , y lo mismo para la referencia:
Cada respuesta sorteada de la política da así una estimación de la KL, sumada token a token, cuya media es la KL exacta. Una estimación sola puede salir negativa, si la respuesta es de las que la referencia prefiere, aunque su media nunca lo sea. Y hay un caso que la celda va a encontrar. Si la política escribe siempre la misma respuesta , la media tiene un único término y la KL vale : lo improbable que era esa respuesta, contado en nats.
El objetivo de RLHF suma la recompensa media de la política y le resta la KL multiplicada por ,
para una instrucción . Lo que se maximiza es su media sobre las instrucciones del entrenamiento, y todo lo que sigue vale instrucción a instrucción. es el precio de un nat en unidades de recompensa. Con , la política persigue la recompensa sin mirar dónde; con muy grande, no se mueve de la referencia. En medio hay un trato: la política se aleja sólo donde gana más recompensa de la que paga en KL.
El gradiente de una media sobre un sorteo
Empecemos por la recompensa. Como suma sobre todas las respuestas posibles, su media es , y sólo aparece en las probabilidades. La derivada del logaritmo da , y con ella la suma vuelve a ser una media sobre las respuestas de la política:
Es el truco de la derivada logarítmica (log-derivative trick), y el estimador que sale de él se llama REINFORCE desde que Williams lo publicó en 1992. La derivada de una media es ahora la media de algo que se calcula sobre una respuesta, y una media se estima sorteando. Con respuestas de la política,
Nada en esa expresión atraviesa el sorteo. La recompensa de cada respuesta es un número, y es un gradiente que ya conoces: con el signo cambiado, el de la pérdida del ajuste supervisado para el par . Un paso de REINFORCE es un paso de ajuste supervisado sobre las respuestas que la política acaba de escribir, con la pérdida de cada una multiplicada por su recompensa. El ajuste imitaba respuestas ajenas, todas por igual; éste imita las propias, más cuanto mejor puntúan.
Lo que se puede restar sin cambiar nada
Hay una media que vale cero, y es la que hace práctico el estimador:
Restar a todas las recompensas un número que dependa de y no de deja, por tanto, el gradiente igual en media. Es el de la lección anterior en otro papel: allí no cambiaba ninguna comparación, aquí no cambia el gradiente esperado. Lo que sí cambia es cuánto se equivoca un batch. Con una recompensa que nunca es negativa, como la de la celda, cada respuesta sorteada se empuja hacia arriba, también las peores. Restando la recompensa media, las de encima suben y las de debajo bajan. Esa resta es la línea base (baseline).
La celda usa la media de su propio batch, que incluye a la respuesta a la que se le resta, y eso no rompe la cuenta. La diferencia entre una recompensa y la media de las es veces su diferencia con la media de las otras , que no depende de ella, y un factor fijo no cambia la dirección.
La KL, descontada de cada recompensa
Falta el término de la KL, que también es una media sobre las respuestas de la política, con una diferencia: lo que se promedia depende de . La regla del producto da dos sumas, y la segunda es la que acaba de valer cero:
con abreviando la de la definición. Juntando las tres piezas,
La KL entra como un descuento en la recompensa de cada respuesta: veces su logaritmo del cociente, la suma token a token de antes, tratada como un número más. Una respuesta que la referencia encontraba mucho menos probable que la política llega al estimador rebajada. InstructGPT se entrenó con esta recompensa descontada, pero no con este estimador tal cual. Usaba PPO (proximal policy optimization), que aprovecha cada batch de respuestas en varios pasos y recorta en ellos cuánto puede cambiar el cociente entre la política nueva y la que las escribió; está en las lecturas.
Una recompensa que cuenta letras, en el mini-GPT
Las tres celdas corren sobre el mini-GPT, tu mini-GPT del bloque 1 cargado desde el checkpoint, sin la plantilla: la instrucción es el principio de una frase de la novela, La Nela, y la respuesta, los 16 tokens que la continúan. La referencia es ese mismo modelo, congelado. En RLHF es el modelo ajustado; aquí no puede serlo, por la razón de la lección anterior: el ajustado olvidó la novela y sólo sabe escribir frases de colores.
La política tampoco es la red entera. He congelado el mini-GPT, y la política es él más un vector de 512 números, uno por entrada, que se suma a sus logits en cada posición: es proporcional a . Con la red entera, un paso cuesta en el navegador unas tres veces más, y en mis pruebas ocho respuestas por paso no bastaban para mover sus 136 000 pesos sin que la política se alejara por puro ruido, con el freno puesto y sin ganar recompensa. Con , el estimador es el mismo y cabe en una línea: en cada token, el one-hot del token escrito menos el softmax.
La recompensa de la celda no sale de ninguna comparación. Cuenta las letras a de la respuesta, y está ahí porque se equivoca a la vista. Un modelo de recompensa de verdad tiene sesgos que nadie ha elegido, como premiar las respuestas largas, y la política los encuentra antes que nadie. Éste tiene uno solo, exagerado, para que se vea qué hace la política con él. La primera celda prepara el muestreo, ocho respuestas a la vez con la caché de claves y valores, y mide la referencia: con la política es .
from pyodide.http import open_url
exec(open_url("/courses/llm-agents/modelo.py").read()) # red, F, codificar, softmax, layer_norm
def avanzar_lote(ids, cache):
"""avanzar() de modelo.py con un eje más, el del batch: una fila nueva por respuesta."""
p, L, h, d = red.p, red.cfg["n_capas"], red.cfg["h"], red.cfg["d_model"]
B, d_k, t = len(ids), d // h, cache[0][0].shape[2] # t: posiciones ya guardadas
H = p["E"][ids] + p["P"][t] # (B, d)
for l in range(L):
Hn, _ = layer_norm(H, p[f"{l}.ln1_g"], p[f"{l}.ln1_b"])
q, k, v = (u.reshape(B, h, 1, d_k) for u in np.split(Hn @ p[f"{l}.Wqkv"], 3, axis=1))
cache[l] = K, V = np.concatenate([cache[l][0], k], 2), np.concatenate([cache[l][1], v], 2)
a = softmax(q @ K.transpose(0, 1, 3, 2) / np.sqrt(d_k)) # (B, h, 1, t + 1)
H = H + (a @ V).reshape(B, d) @ p[f"{l}.Wo"]
H = H + red.ffn(H[:, None], l)[0][:, 0]
return layer_norm(H, p["lnf_g"], p["lnf_b"])[0] @ p["E"].T
x = codificar("La Nela", F) # la instrucción: dos tokens
def responder(theta, rng, B=8, n=16):
"""B respuestas de la política: su recompensa, log pi_theta/pi_ref y el gradiente de log pi_theta."""
Z, (_, capas, _, _) = red.adelante(np.tile(x, (B, 1))) # el prefill, las B a la vez
cache = [(K, V) for (_, _, _, K, V, _, _), _ in capas]
z, Y, k, G = Z[:, -1], np.zeros((B, n), int), np.zeros(B), np.zeros((B, 512))
filas = np.arange(B)
for j in range(n):
if j:
z = avanzar_lote(Y[:, j - 1], cache)
p_ref, p = softmax(z), softmax(z + theta) # pi_ref y pi_theta, en este token
Y[:, j] = v = [rng.choice(512, p=fila) for fila in p] # temperatura 1, sin corte
k += np.log(p[filas, v] / p_ref[filas, v]) # el cociente, token a token
G -= p # one-hot menos softmax,
G[filas, v] += 1 # token a token
r = np.array([decodificar(list(y), F).count("a") for y in Y]) # la recompensa: cuántas «a»
return Y, r, k, G
def evaluar(theta):
"""La recompensa media y la KL estimada sobre 32 respuestas nuevas, y tres de ellas."""
Y, r, k, _ = responder(theta, np.random.default_rng(1), B=32)
print("32 respuestas nuevas: recompensa %.2f KL %.2f" % (r.mean(), k.mean()))
for y in Y[:3]:
print(" ", repr(decodificar(list(y), F)))
evaluar(np.zeros(512)) # theta = 0: la política es pi_ref
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.
El mini-GPT escribe de media letras a en 16 tokens, y la KL sale exactamente cero, porque con cada cociente vale 1. La segunda celda es el estimador de la lección: treinta pasos de ascenso por gradiente sobre , ocho respuestas por paso, la línea base y el descuento de la KL. La ejecuta con , sin freno.
def reinforce(beta, pasos=30, eta=1.0, semilla=0):
"""Ascenso por gradiente sobre theta: el estimador de la lección, con 8 respuestas por paso."""
rng, theta = np.random.default_rng(semilla), np.zeros(512)
for s in range(1, pasos + 1):
_, r, k, G = responder(theta, rng)
R = r - beta * k # la KL, descontada a cada respuesta
theta += eta * ((R - R.mean())[:, None] * G).mean(axis=0) # la línea base: la media del batch
if s % 5 == 0:
print("paso %2d recompensa %5.2f KL %6.2f" % (s, r.mean(), k.mean()))
return theta
theta = reinforce(beta=0) # sin freno
evaluar(theta)
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.
La recompensa y la KL suben juntas: letras con nats en el paso 10, y con en el 15, donde se quedan. Las 32 respuestas de la evaluación son la misma, a a a a a a a a a a a a a a a a, el token a dieciséis veces. Ni siquiera es la de más recompensa posible, porque aba repetido daría el doble. Es la primera que encontró el ascenso, y de ahí ya no sale: ocho respuestas iguales tienen la misma recompensa, la línea base las deja en cero y el gradiente con ellas.
Su KL, nats, es la del caso de una sola respuesta. La referencia la escribiría con probabilidad , del orden de . Esto es reward hacking: la recompensa ha pasado de a , y lo que debía medir, que la respuesta fuera buena, se ha perdido por el camino. La tercera celda repite el entrenamiento con , de modo que cada nat que la política se aleja le cuesta media letra.
evaluar(reinforce(beta=0.5))
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 el freno, la recompensa sube a , más del doble de la de partida, por nats de KL. Las respuestas siguen siendo novela, ahora con más a que antes: a la triste bajar a Florentina, a. La política las ha buscado donde la referencia también las pone, en la, en a la, en las palabras que las llevan.
La cuenta del objetivo dice por qué. Con , la respuesta de la segunda celda vale , mucho menos que quedarse en la referencia, que vale , y que lo que logra la política entrenada, . El freno no prohíbe aquella respuesta: la hace salir cara, y con un lo bastante pequeño volvería a compensar.
Comprueba tu intuición
Cinco preguntas, la última con los números de las celdas.
La recompensa de una respuesta depende de los tokens que la política ha sorteado. ¿Por qué no se calcula el gradiente de la recompensa media con una vuelta hacia atrás a través del sorteo, como el de una pérdida?
En el estimador se resta a la recompensa de cada respuesta una línea base. Marca las afirmaciones ciertas.
Marca todas las opciones correctas. Se corrige todo o nada: no hay puntuación parcial.
Una política escribe siempre la misma respuesta, a la que la referencia da una probabilidad de una entre un millón. ¿Cuánto vale la KL de la política a la referencia, en nats?
Se acepta un margen de ±0.01.
Si fuera la red entera, ¿qué sería un paso de este estimador visto desde el ajuste supervisado?
En la segunda celda, la respuesta que escribe la política sin freno tiene recompensa 16 y una KL de 73.3 nats, y la referencia consigue una recompensa media de 3.47 con KL cero. ¿Por debajo de qué vale más en el objetivo esa respuesta repetida que quedarse en la referencia?
Se acepta un margen de ±0.002.
RLHF funciona, y es caro. Necesita un modelo de recompensa entrenado aparte, sortear respuestas en cada paso y tener a la vez en memoria la política, la referencia y el modelo de recompensa, y estimar un gradiente con unas pocas respuestas es tan ruidoso que esta lección ha tenido que congelar la red para que ocho por paso bastaran. Y lo que se optimiza no es la recompensa sino un trato entre ella y la KL, cuyo óptimo esta lección no ha buscado: el ascenso sólo se acerca a él paso a paso.
La lección siguiente, sobre la optimización directa de preferencias (direct preference optimization, DPO), lo busca. El objetivo de esta lección tiene una solución exacta, escrita con la referencia y la recompensa, y esa solución, leída al revés, convierte la política en su propio modelo de recompensa: con ella, las comparaciones entrenan la política directamente, sin modelo de recompensa y sin sorteos.
Para profundizar5 fuentes · 4 papers, 1 artículo
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.
- Simple statistical gradient-following algorithms for connectionist reinforcement learning
El artículo que da nombre a REINFORCE: el gradiente de una recompensa media como la media de la recompensa por el gradiente del logaritmo, y la línea base que no lo cambia, para redes que deciden al azar.
- Fine-Tuning Language Models from Human Preferences
RLHF sobre GPT-2. Su ecuación 2 es la recompensa con la KL descontada de esta lección, y su tabla 10 es la segunda celda en un modelo de verdad: sin la KL, «Love this ride instantly instantly instantly».
- Proximal Policy Optimization Algorithms
El paso que da InstructGPT en lugar del estimador de la lección: aprovecha cada batch de respuestas para varios pasos y recorta el cociente entre la política nueva y la que las escribió, para que ninguno la aleje demasiado.
- Scaling Laws for Reward Model Overoptimization
El reward hacking, medido: optimizan contra un modelo de recompensa y puntúan con otro, mayor, que hace de recompensa verdadera. La recompensa optimizada no deja de subir; la verdadera sube, toca techo y baja a medida que crece la KL.
- Specification gaming: the flip side of AI ingenuity
Unos sesenta sistemas que cumplen la letra de su recompensa y no su intención, el brazo que engaña a la cámara entre ellos. La celda de las «a» es uno más, hecho a propósito para que se vea.