DPO: la derivación completa

DPO: la derivación completa

38 min de lectura

Con el aprendizaje por refuerzo con retroalimentación humana (reinforcement learning from human feedback, RLHF), el bloque tiene montada la cadena con que se entrenó InstructGPT, y la cadena tiene tres eslabones. Las comparaciones de los anotadores entrenan un modelo de recompensa, rϕr_\phi. La política escribe respuestas, rϕr_\phi las puntúa, y la política sube la puntuación media pagando cada nat que se aleja de la referencia. En ningún punto una comparación toca la política: lo que dijeron los anotadores le llega a través de rϕr_\phi, y de las respuestas que salgan en cada sorteo.

En 2023, Rafailov y sus coautores mostraron que el eslabón del medio se puede quitar sin cambiar el objetivo, y pusieron la idea en el subtítulo de su artículo: «tu modelo de lenguaje es, en secreto, un modelo de recompensa». El método se llama optimización directa de preferencias (direct preference optimization, DPO), y al año siguiente Meta lo prefirió a PPO (proximal policy optimization), el algoritmo de RLHF de InstructGPT, para ajustar Llama 3 con preferencias. Esta lección lo deriva entero, y el paso del que depende todo es una cancelación que el bloque dejó preparada dos lecciones atrás.

El objetivo de RLHF, la recompensa media menos β\beta veces la distancia a la referencia, tiene un óptimo que se puede escribir. Es la referencia, con la probabilidad de cada respuesta multiplicada por un número que crece exponencialmente con su recompensa, y todo dividido por lo que haga falta para que vuelva a sumar 1. Si la referencia escribe dos respuestas por igual y una tiene un punto más de recompensa, con β=0.5\beta = 0.5 la política óptima escribe esa unas siete veces más.

Leída al revés, la fórmula dice algo más útil: cada política es la óptima de alguna recompensa. La que mide, en unidades de β\beta, cuánto multiplica la política la probabilidad de cada respuesta respecto de la referencia. Una política que escribe una respuesta el doble que la referencia le da βln⁡2\beta \ln 2 de recompensa; una que la escribe la mitad, −βln⁡2-\beta \ln 2. Así que la política lleva dentro un modelo de recompensa, y se puede entrenar como se entrenaba aquél, con comparaciones. Al entrenarlo, se entrena ella.

La política óptima tiene fórmula

Fijemos una instrucción xx y una recompensa r(x,y)r(x, y) cualquiera, y escribamos el objetivo de RLHF como sumas sobre las respuestas, con rr en el lugar de rϕr_\phi. Las dos sumas llevan el mismo peso, πθ(y∣x)\pi_\theta(y \mid x), así que se pueden juntar, y r/βr/\beta entra en el logaritmo como log⁡er/β\log e^{r/\beta}:

J(θ)=∑yπθ(y∣x) r(x,y)−β∑yπθ(y∣x)log⁡πθ(y∣x)πref(y∣x)=−β∑yπθ(y∣x)[log⁡πθ(y∣x)πref(y∣x)−r(x,y)β]=−β∑yπθ(y∣x)log⁡πθ(y∣x)πref(y∣x) er(x,y)/β.\begin{aligned} J(\theta) &= \sum_{y} \pi_\theta(y \mid x)\, r(x, y) - \beta \sum_{y} \pi_\theta(y \mid x) \log \frac{\pi_\theta(y \mid x)}{\pi_{\text{ref}}(y \mid x)} \\ &= -\beta \sum_{y} \pi_\theta(y \mid x) \left[\log \frac{\pi_\theta(y \mid x)}{\pi_{\text{ref}}(y \mid x)} - \frac{r(x, y)}{\beta}\right] \\ &= -\beta \sum_{y} \pi_\theta(y \mid x) \log \frac{\pi_\theta(y \mid x)}{\pi_{\text{ref}}(y \mid x)\, e^{r(x, y)/\beta}}. \end{aligned}

El denominador de la última línea tiene la forma de una distribución sobre respuestas, pero no suma

  1. Llamemos Z(x)Z(x) a lo que suma, la función de partición (partition function), y π⋆\pi^{\star} a lo que queda al dividir por ella:
π⋆(y∣x)=πref(y∣x) er(x,y)/βZ(x),Z(x)=∑yπref(y∣x) er(x,y)/β.\pi^{\star}(y \mid x) = \frac{\pi_{\text{ref}}(y \mid x)\, e^{r(x, y)/\beta}}{Z(x)}, \qquad Z(x) = \sum_{y} \pi_{\text{ref}}(y \mid x)\, e^{r(x, y)/\beta}.

Con eso, el denominador es Z(x) π⋆(y∣x)Z(x)\, \pi^{\star}(y \mid x), y el logaritmo del cociente se parte en dos. log⁡Z(x)\log Z(x) no depende de yy, y las probabilidades de πθ\pi_\theta suman 1, así que sale de la suma tal cual:

J(θ)=−β∑yπθ(y∣x)log⁡πθ(y∣x)π⋆(y∣x)+βlog⁡Z(x)=−β DKL(πθ(⋅∣x) ∥ π⋆(⋅∣x))+βlog⁡Z(x).\begin{aligned} J(\theta) &= -\beta \sum_{y} \pi_\theta(y \mid x) \log \frac{\pi_\theta(y \mid x)}{\pi^{\star}(y \mid x)} + \beta \log Z(x) \\ &= -\beta\, \mathbb{D}_{\text{KL}}\big(\pi_\theta(\cdot \mid x) \,\|\, \pi^{\star}(\cdot \mid x)\big) + \beta \log Z(x). \end{aligned}

El segundo término no depende de θ\theta. El primero es −β-\beta por una divergencia de Kullback–Leibler (KL), que nunca es negativa y vale cero sólo si las dos distribuciones coinciden: la desigualdad de la lección sobre la perplejidad, otra vez. Así que JJ nunca pasa de βlog⁡Z(x)\beta \log Z(x), y llega a ese valor exactamente cuando πθ(⋅∣x)=π⋆(⋅∣x)\pi_\theta(\cdot \mid x) = \pi^{\star}(\cdot \mid x). Para un modelo capaz de dar a cada instrucción la distribución que quiera, como el de la lección sobre el ajuste supervisado, π⋆\pi^{\star} es el óptimo.

La política óptima es la referencia repesada por er/βe^{r/\beta} y renormalizada. Con β\beta grande casi no se separa de πref\pi_{\text{ref}}; con β\beta cerca de cero, toda la masa se va a las respuestas de mayor recompensa, como la que repetía la segunda celda de la lección anterior. El ascenso de RLHF buscaba esto paso a paso, y aquí está escrito.

Escrito no quiere decir utilizable. Z(x)Z(x) es una suma sobre todas las respuestas posibles a xx, y para las de 16 tokens del mini-GPT eso son 51216512^{16}, unos 2×10432 \times 10^{43} términos. Sin ella, la fórmula no da la probabilidad de ninguna respuesta, y no hay forma de sortear de π⋆\pi^{\star}.

De dónde sale la fórmula, si no se adivina

La reescritura de arriba demuestra que π⋆\pi^{\star} es el máximo, pero hay que conocer la fórmula para hacerla. Así se encuentra. Tratemos cada probabilidad de la política como una variable libre, y fijémonos en dos respuestas cualesquiera, yAy_A e yBy_B, con recompensas rA=r(x,yA)r_A = r(x, y_A) y rB=r(x,yB)r_B = r(x, y_B). Pasemos una cantidad pequeña ε\varepsilon de probabilidad de yBy_B a yAy_A, sin tocar las demás, de modo que todo sigue sumando 1. De todos los términos de JJ, sólo cambian los de esas dos respuestas:

f(ε)=(πθ(yA∣x)+ε)[rA−βlog⁡πθ(yA∣x)+επref(yA∣x)]+(πθ(yB∣x)−ε)[rB−βlog⁡πθ(yB∣x)−επref(yB∣x)].\begin{aligned} f(\varepsilon) &= \big(\pi_\theta(y_A \mid x) + \varepsilon\big)\left[r_A - \beta \log \frac{\pi_\theta(y_A \mid x) + \varepsilon}{\pi_{\text{ref}}(y_A \mid x)}\right] \\ &\quad + \big(\pi_\theta(y_B \mid x) - \varepsilon\big)\left[r_B - \beta \log \frac{\pi_\theta(y_B \mid x) - \varepsilon}{\pi_{\text{ref}}(y_B \mid x)}\right]. \end{aligned}

Derivando cada producto, la derivada del logaritmo deja un −β-\beta en los dos corchetes, que se cancelan en la resta:

f′(0)=[rA−βlog⁡πθ(yA∣x)πref(yA∣x)]−[rB−βlog⁡πθ(yB∣x)πref(yB∣x)].f'(0) = \left[r_A - \beta \log \frac{\pi_\theta(y_A \mid x)}{\pi_{\text{ref}}(y_A \mid x)}\right] - \left[r_B - \beta \log \frac{\pi_\theta(y_B \mid x)}{\pi_{\text{ref}}(y_B \mid x)}\right].

En un máximo ningún traslado pequeño puede ganar nada, así que f′(0)=0f'(0) = 0 para cualquier par de respuestas con probabilidad positiva. Lo que hay entre corchetes, la recompensa de una respuesta menos β\beta por su logaritmo del cociente, vale lo mismo para todas: en el óptimo, cada respuesta rinde igual. Despejando, πθ(y∣x)\pi_\theta(y \mid x) es πref(y∣x) er(x,y)/β\pi_{\text{ref}}(y \mid x)\, e^{r(x, y)/\beta} por un mismo número para todas las respuestas, y el que hace que sumen 1 es 1/Z(x)1/Z(x). Es π⋆\pi^{\star}.

La segunda derivada, f′′(ε)=−β/(πθ(yA∣x)+ε)−β/(πθ(yB∣x)−ε)f''(\varepsilon) = -\beta/\big(\pi_\theta(y_A \mid x) + \varepsilon\big) - \beta/\big(\pi_\theta(y_B \mid x) - \varepsilon\big), es negativa: cada traslado recorre una curva cóncava, y su punto plano es un máximo. Y el valor común de los corchetes resulta ser βlog⁡Z(x)\beta \log Z(x), que es la ecuación de la sección siguiente vista desde aquí.

La recompensa, escrita con la política

La fórmula sirve leída al revés. Tomemos logaritmos en la definición de π⋆\pi^{\star} y despejemos la recompensa:

r(x,y)=βlog⁡π⋆(y∣x)πref(y∣x)+βlog⁡Z(x).r(x, y) = \beta \log \frac{\pi^{\star}(y \mid x)}{\pi_{\text{ref}}(y \mid x)} + \beta \log Z(x).

Una recompensa decide su política óptima, y la política óptima devuelve la recompensa salvo βlog⁡Z(x)\beta \log Z(x), un número que depende de la instrucción y no de la respuesta. Es un κ(x)\kappa(x), la constante por instrucción que la lección sobre el modelo de recompensa encontró en Bradley–Terry. Recompensa y política óptima son, dadas la referencia y β\beta, la misma información escrita de dos maneras.

Así que cualquier política πθ\pi_\theta es la óptima de una recompensa, y de todas las que se diferencian de ella en una constante por instrucción. Llamemos recompensa implícita (implicit reward) de πθ\pi_\theta a

rθ(x,y)=βlog⁡πθ(y∣x)πref(y∣x),r_\theta(x, y) = \beta \log \frac{\pi_\theta(y \mid x)}{\pi_{\text{ref}}(y \mid x)},

con el subíndice de los parámetros que la calculan, como rϕr_\phi: es un modelo de recompensa cuyos parámetros son los de la política. Vale cero en todas las respuestas cuando la política es la referencia, y es positiva en las que la política escribe más que la referencia.

Ahora, Bradley–Terry. Sustituyamos la recompensa despejada en la probabilidad de que un anotador prefiera ywy_w a yly_l:

P(yw≻yl∣x)=σ(r(x,yw)−r(x,yl))=σ(βlog⁡π⋆(yw∣x)πref(yw∣x)+βlog⁡Z(x)−βlog⁡π⋆(yl∣x)πref(yl∣x)−βlog⁡Z(x))=σ(βlog⁡π⋆(yw∣x)πref(yw∣x)−βlog⁡π⋆(yl∣x)πref(yl∣x)).\begin{aligned} P(y_w \succ y_l \mid x) &= \sigma\big(r(x, y_w) - r(x, y_l)\big) \\ &= \sigma\bigg(\beta \log \frac{\pi^{\star}(y_w \mid x)}{\pi_{\text{ref}}(y_w \mid x)} + \beta \log Z(x) \\ &\qquad\quad - \beta \log \frac{\pi^{\star}(y_l \mid x)}{\pi_{\text{ref}}(y_l \mid x)} - \beta \log Z(x)\bigg) \\ &= \sigma\left(\beta \log \frac{\pi^{\star}(y_w \mid x)}{\pi_{\text{ref}}(y_w \mid x)} - \beta \log \frac{\pi^{\star}(y_l \mid x)}{\pi_{\text{ref}}(y_l \mid x)}\right). \end{aligned}

βlog⁡Z(x)\beta \log Z(x) entra sumando con la preferida y restando con la rechazada, y se va. Lo que nadie podía calcular ha desaparecido, y no por suerte: las dos respuestas de una comparación contestan a la misma instrucción, y Z(x)Z(x) es la constante por instrucción que no cambia ninguna comparación. Aquella libertad, que en Bradley–Terry parecía un defecto del modelo, es lo que hace posible esta lección.

La probabilidad de cada comparación queda escrita sin ninguna recompensa. La pérdida de DPO es menos su logaritmo, en media, con πθ\pi_\theta en el lugar de π⋆\pi^{\star}: la pérdida del modelo de recompensa, LR\mathcal{L}_{\text{R}}, con rθr_\theta en el lugar de rϕr_\phi:

LDPO(θ)=E(x,yw,yl)∼ppref[−log⁡σ(rθ(x,yw)−rθ(x,yl))]=E(x,yw,yl)∼ppref[−log⁡σ(βlog⁡πθ(yw∣x)πref(yw∣x)−βlog⁡πθ(yl∣x)πref(yl∣x))].\begin{aligned} \mathcal{L}_{\text{DPO}}(\theta) &= \mathbb{E}_{(x, y_w, y_l) \sim p_{\text{pref}}}\left[-\log \sigma\big(r_\theta(x, y_w) - r_\theta(x, y_l)\big)\right] \\ &= \mathbb{E}_{(x, y_w, y_l) \sim p_{\text{pref}}}\left[-\log \sigma\left(\beta \log \frac{\pi_\theta(y_w \mid x)}{\pi_{\text{ref}}(y_w \mid x)} - \beta \log \frac{\pi_\theta(y_l \mid x)}{\pi_{\text{ref}}(y_l \mid x)}\right)\right]. \end{aligned}

Minimizarla es ajustar a las comparaciones un modelo de recompensa con la forma rθr_\theta. Si la política puede ser cualquier distribución, esa forma alcanza cualquier recompensa salvo una constante por instrucción, que LR\mathcal{L}_{\text{R}} no ve; y la política óptima de la recompensa ajustada es, por construcción, πθ\pi_\theta. Las dos fases de RLHF quedan en una, y no hay nada que sortear: cada término lee el logaritmo de la probabilidad de la preferida y de la rechazada, en la política, con una ida sobre cada una, y en la referencia, que se calcula una vez antes de empezar.

El gradiente: cuánto se equivoca todavía la recompensa implícita

Del margen, sólo la política depende de θ\theta, así que ∇θrθ(x,y)=β ∇θlog⁡πθ(y∣x)\nabla_\theta r_\theta(x, y) = \beta\, \nabla_\theta \log \pi_\theta(y \mid x). Con la derivada de −log⁡σ-\log \sigma de la lección sobre el modelo de recompensa, la de cada comparación es

∇θ[−log⁡σ(rθ(x,yw)−rθ(x,yl))]=−β σ(rθ(x,yl)−rθ(x,yw))(∇θlog⁡πθ(yw∣x)−∇θlog⁡πθ(yl∣x)).\begin{aligned} &\nabla_\theta\left[-\log \sigma\big(r_\theta(x, y_w) - r_\theta(x, y_l)\big)\right] \\ &\qquad = -\beta\, \sigma\big(r_\theta(x, y_l) - r_\theta(x, y_w)\big)\left(\nabla_\theta \log \pi_\theta(y_w \mid x) - \nabla_\theta \log \pi_\theta(y_l \mid x)\right). \end{aligned}

∇θlog⁡πθ(y∣x)\nabla_\theta \log \pi_\theta(y \mid x) es, con el signo cambiado, el gradiente de la pérdida del ajuste supervisado para el par (x,y)(x, y). Un paso de descenso es un paso de ajuste sobre la preferida y otro al revés sobre la rechazada: sube la una y baja la otra. Lo mismo hacía el estimador de la lección anterior, con respuestas que la política acababa de escribir; éstas estaban escritas antes de empezar.

El factor que lo multiplica, σ(rθ(x,yl)−rθ(x,yw))\sigma\big(r_\theta(x, y_l) - r_\theta(x, y_w)\big), es el mismo que en el modelo de recompensa: la probabilidad que la recompensa implícita da todavía al orden que el anotador no eligió. Mide cuánto se equivoca aún. Al empezar, la política es la referencia, las dos recompensas implícitas valen cero, y cada comparación cuesta ln⁡2\ln 2 con factor 1/21/2. Una comparación ya ordenada por un margen de 3 tiene un factor de 0.0470.047: se apaga, y deja de mover la política. Una que está al revés empuja con un factor cercano a 1. Sin el factor, el paso subiría la preferida y bajaría la rechazada siempre, y el artículo de DPO muestra que así el modelo degenera.

Y β\beta está dentro del margen. Para un mismo alejamiento de la referencia, una β\beta grande da un margen grande, y la comparación se apaga cerca de la referencia; con β\beta pequeña, la política tiene que alejarse mucho más antes de que se apague. Es el freno de la KL, y la pérdida no tiene ningún término de KL: el freno está en cómo se escribe la recompensa.

Una comparación es un punto: cuánto ha subido la política la preferida respecto de la referencia, en horizontal, y la rechazada, en vertical. Mira dónde se apaga el factor y por qué no cambia nada al sumar lo mismo a los dos. Después mueve β: la zona apagada se acerca a la diagonal, o se sale del plano.

Dos concesiones. La equivalencia con RLHF es entre óptimos, y supone una política que puede ser cualquier distribución. Y la pérdida sólo habla de las respuestas de las comparaciones: el freno actúa sobre ellas, mientras que la KL de RLHF se mide sobre las respuestas que escribe la propia política. Lo que DPO haga con las demás lo deciden los pesos, como en el ajuste supervisado.

DPO en el mini-GPT

Las dos celdas corren sobre el mini-GPT, tu mini-GPT del bloque 1 cargado desde el checkpoint, con la política de la lección anterior: el mini-GPT congelado más un vector θ\theta de 512 números, uno por entrada, sumado a sus logits, y la misma instrucción, La Nela. La referencia es el mini-GPT sin θ\theta. Lo que cambia es de dónde aprende.

Aquí las respuestas se sortean una vez, de la referencia, antes de entrenar: 64 continuaciones de 16 tokens, como las que un modelo ajustado escribe para que alguien las compare. El anotador es un programa que sigue a Bradley–Terry con la recompensa de la lección anterior, las letras a: entre dos respuestas, prefiere la de más letras con probabilidad σ\sigma de la diferencia. Compara cada respuesta con las otras 63, como los anotadores de InstructGPT ordenaban varias respuestas a una instrucción y de cada orden salían todas sus comparaciones.

La primera celda prepara eso, y guarda además la distribución de la referencia en cada token de cada respuesta. Con ella, πθ\pi_\theta sale para cualquier θ\theta sin volver a pasar por la red: es esa distribución con cada entrada vv multiplicada por eθve^{\theta_v}, y renormalizada.

import numpy as np
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, y la distribución de la referencia en cada uno de sus tokens."""
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, P = Z[:, -1], np.zeros((B, n), int), np.zeros((B, n, 512))
for j in range(n):
if j:
z = avanzar_lote(Y[:, j - 1], cache)
P[:, j] = softmax(z) # pi_ref en este token
q = P[:, j] * np.exp(theta) # pi_theta, sin normalizar
Y[:, j] = [rng.choice(512, p=fila / fila.sum()) for fila in q]
return Y, P

letras = lambda Y: np.array([decodificar(list(y), F).count("a") for y in Y])
# Los datos: 64 respuestas de la referencia, y un anotador que compara cada una con todas las demás.
rng = np.random.default_rng(0)
Y, P = responder(np.zeros(512), rng, B=64) # se sortean aquí, una vez
r = letras(Y)
i, j = np.triu_indices(64, 1) # los 2016 pares
gana_i = rng.random(len(i)) < 1 / (1 + np.exp(-(r[i] - r[j]))) # Bradley-Terry, con r = las letras a
w, l = np.where(gana_i, i, j), np.where(gana_i, j, i) # la preferida y la rechazada
print("%d respuestas, %.2f letras a de media; %d comparaciones" % (len(Y), r.mean(), len(w)))
print("el anotador prefirió la de más letras en el %.0f %%, la de menos en el %.0f %%; en el %.0f %% empataban"
% (100 * np.mean(r[w] > r[l]), 100 * np.mean(r[w] < r[l]), 100 * np.mean(r[w] == r[l])))
print("una comparación:", repr(decodificar(list(Y[w[0]]), F)), "≻", repr(decodificar(list(Y[l[0]]), F)))
numpy

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.

Las 64 respuestas tienen 3.283.28 letras a de media. En las 2016 comparaciones, el anotador prefirió la de más letras en el 69 % y la de menos en el 13 %; en el 18 % restante empataban, y echó una moneda al aire. Es un anotador con ruido, como los de verdad. La segunda celda es DPO: cien pasos de descenso por gradiente sobre θ\theta con la pérdida de la lección, con β=0.5\beta = 0.5 y con β=0.1\beta = 0.1. Mira que el bucle no llama a responder: sólo lee las respuestas guardadas y las comparaciones. Después sí sortea 32 respuestas nuevas de cada política, para medirla como en la lección anterior.

El paso depende de β\beta. La recompensa implícita es β\beta por el logaritmo del cociente, así que con β\beta pequeña θ\theta tiene que llegar más lejos, y el gradiente, que lleva β\beta delante, empuja menos. Con un paso proporcional a 1/β21/\beta^{2}, las dos ejecuciones recorren el mismo camino en recompensa implícita.

# Necesita la celda anterior. Entrenar no sortea nada: lee Y, P y las comparaciones.
def log_cociente(theta, Y, P):
"""log pi_theta(y|x) - log pi_ref(y|x) de cada respuesta, y su gradiente respecto de theta."""
S = P @ np.exp(theta) # (B, n): lo que renormaliza cada token
k = (theta[Y] - np.log(S)).sum(axis=1)
cuenta = np.stack([np.bincount(y, minlength=512) for y in Y])
return k, cuenta - np.exp(theta) * np.einsum("bj,bjv->bv", 1 / S, P) # one-hot menos softmax

def dpo(beta, pasos=100):
"""Descenso por gradiente sobre theta con la pérdida de DPO, y nada más."""
theta, eta = np.zeros(512), 2.5 / beta**2 # beta pequeña: más lejos, menos empuje
for s in range(pasos + 1):
k, G = log_cociente(theta, Y, P)
margen = beta * (k[w] - k[l]) # r_theta(x, y_w) - r_theta(x, y_l)
factor = 1 / (1 + np.exp(margen)) # sigma(-margen)
if s % 25 == 0: # (+ 0: que el cero no salga -0.00)
print("paso %3d pérdida %.3f margen %.2f factor %.2f"
% (s, np.logaddexp(0, -margen).mean(), margen.mean().round(2) + 0, factor.mean()))
if s < pasos:
c = np.bincount(w, factor, 64) - np.bincount(l, factor, 64) # + preferidas, - rechazadas
theta += eta * beta * (c @ G) / len(w)
d = r[w] - r[l]
print("factor al final: %.2f si la preferida tenía tres letras más o más, %.2f si tenía menos"
% (factor[d >= 3].mean(), factor[d < 0].mean()))
return theta

for beta in (0.5, 0.1):
print("\nbeta = %g" % beta)
theta = dpo(beta)
rt = beta * log_cociente(theta, Y, P)[0] # la recompensa implícita de las 64
print("recompensa implícita media, según sus letras a:",
" ".join("%d: %.1f" % (n, rt[r == n].mean()) for n in range(1, 7)))
Yn, Pn = responder(theta, np.random.default_rng(1), B=32) # ahora sí, sólo para medir
print("32 respuestas nuevas: recompensa %.2f KL %.2f"
% (letras(Yn).mean(), log_cociente(theta, Yn, Pn)[0].mean()))
print(" ", repr(decodificar(list(Yn[0]), F)))
numpy

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 pérdida arranca en ln⁡2=0.693\ln 2 = 0.693, como decía la sección del gradiente: con θ=0\theta = 0 la política es la referencia y todo margen vale cero. Baja hasta 0.4330.433 y ahí se queda. No puede bajar más porque los datos tienen ruido, y el factor lo enseña: al final vale 0.030.03 en las comparaciones que la preferida ganaba por tres letras o más, que se han apagado, y 0.740.74 en las que el anotador eligió la de menos letras, que siguen empujando al revés. El entrenamiento se para donde los dos empujes se compensan.

Lo que ha aprendido la recompensa implícita está en la línea de las letras. Con β=0.5\beta = 0.5, las respuestas con una a tienen de media −3.4-3.4, las de dos −2.4-2.4, y así hasta 1.91.9 con seis: un punto por letra. Es la recompensa del anotador, en su unidad, y nadie se la ha dado a la política: la ha sacado de las comparaciones, como la habría sacado un modelo de recompensa. El nivel no significa nada, porque es el de siempre, una constante por instrucción. Con β=0.1\beta = 0.1 los valores son otros, de −6.1-6.1 a −0.7-0.7, y el punto por letra es el mismo.

Las dos políticas, en cambio, no se parecen. Con β=0.5\beta = 0.5, las respuestas nuevas tienen 5.755.75 letras a de media, frente a las 3.283.28 de las respuestas de la referencia, a 3.013.01 nats de ella, y siguen siendo novela. Con β=0.1\beta = 0.1 tienen 12.1212.12, a 32.9432.94 nats: a la De a la a la día a la a mano la. Es la fórmula despejada: la misma recompensa entre una β\beta cinco veces menor da un logaritmo del cociente cinco veces mayor. La pérdida no tiene ningún término de KL, y β\beta frena igual.

La lección anterior llegó, con β=0.5\beta = 0.5 y REINFORCE, a 7.447.44 letras y 5.485.48 nats. Los dos métodos persiguen el mismo óptimo y no llegan al mismo sitio, por las dos concesiones de arriba: 512 números no son cualquier política, y DPO sólo ha visto 64 respuestas de la referencia, mientras REINFORCE aprendía de las que escribía.

Comprueba tu intuición

Cinco preguntas y un desafío: la pérdida de DPO y su gradiente, para un batch de comparaciones.

La política óptima de RLHF tiene fórmula, π⋆(y∣x)=πref(y∣x) er(x,y)/β/Z(x)\pi^{\star}(y \mid x) = \pi_{\text{ref}}(y \mid x)\, e^{r(x, y)/\beta} / Z(x). ¿Por qué no se calcula y se sortea de ella, sin entrenar nada?

Al sustituir la recompensa despejada en Bradley–Terry, el término βlog⁡Z(x)\beta \log Z(x) desaparece. Marca las afirmaciones ciertas.

Marca todas las opciones correctas. Se corrige todo o nada: no hay puntuación parcial.

Una comparación con β=0.1\beta = 0.1: la política hace la preferida e3e^{3} veces más probable que la referencia, y la rechazada e2e^{2} veces menos. ¿Cuánto vale el factor de esa comparación en el gradiente?

Se acepta un margen de ±0.002.

La pérdida de DPO no tiene ningún término de KL. ¿Qué frena a la política para que no se aleje de la referencia?

En la segunda celda, con β=0.5\beta = 0.5, las 16 respuestas con cuatro letras a, más que la media, ganaron el 70 % de sus comparaciones. Su recompensa implícita media es −0.4-0.4: la política entrenada las escribe con menos probabilidad que la referencia. ¿Cómo puede ser?

Escribe dpo(logp_w, logp_l, ref_w, ref_l, beta): la pérdida de DPO media de un batch de comparaciones y su gradiente. Los cuatro primeros argumentos son arrays con un número por comparación: log⁡πθ(yw∣x)\log \pi_\theta(y_w \mid x), log⁡πθ(yl∣x)\log \pi_\theta(y_l \mid x) y las dos de la referencia. Devuelve (perdida, g_w, g_l), donde g_w[i] es la derivada de la pérdida media respecto de logp_w[i], y g_l[i] respecto de logp_l[i]. Con márgenes de miles de nats no puede desbordar ni dar nan.

La primera comprobación descarga el intérprete de Python (~15 MB); después queda en la caché del navegador. Este desafío se resuelve mejor con un teclado físico: en el móvil puedes leerlo y volver luego.


Con DPO, aprender de preferencias cabe en una pérdida: comparaciones, una referencia congelada y descenso por gradiente, como el ajuste supervisado. Todo lo que la política aprende está en esas comparaciones, y la celda las ha tenido fáciles. Su anotador era un programa que contaba letras, conocía la recompensa verdadera y comparó 2016 pares sin cansarse. Con anotadores de verdad, cada comparación cuesta tiempo y dinero, dos de ellos discrepan algo más de una vez de cada cuatro, y lo que prefieren es lo que el modelo aprende a hacer.

La lección siguiente, sobre de dónde salen las preferencias, mira esa fuente: anotadores humanos, una lista de principios escrita para que un modelo critique y corrija sus propias respuestas, y modelos que juzgan a otros. Y qué quiere decir, con cada una, que un modelo esté «alineado».

¿Te ha sido útil?
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.

  • Direct Preference Optimization: Your Language Model is Secretly a Reward Model
    paperRafailov, Sharma, Mitchell, Ermon, Manning y Finn, 2023NeurIPS 2023 · arXiv:2305.18290EN

    El artículo de DPO. Sus apéndices A.1, A.2 y A.4 son las tres derivaciones de esta lección, y su sección 4 lee el gradiente igual. Muestra además que sin el factor el modelo degenera.

  • RL with KL penalties is better viewed as Bayesian inference
    paperKorbak, Perez y Buckley, 2022Findings of EMNLP 2022 · arXiv:2205.11275EN

    La política óptima de esta lección leída como una posterior bayesiana: la referencia es la distribución a priori y la exponencial de la recompensa, la verosimilitud. La KL es lo que impide que todo acabe en una sola respuesta.

  • A General Theoretical Paradigm to Understand Learning from Human Preferences
    paperAzar, Rowland, Piot, Guo, Calandriello, Valko y Munos, 2023AISTATS 2024 · arXiv:2310.12036EN

    Dónde falla el freno de DPO: si las comparaciones de un par salen siempre iguales, el margen que las explica es infinito y la β deja de frenar. Su sección 4.2 lo deriva, y la 5 propone IPO, que fija un margen finito.

  • The Llama 3 Herd of Models
    paperGrattafiori, Dubey, Jauhri, Pandey, Kadian y otros, 2024arXiv:2407.21783EN

    DPO a escala: su sección 4.1.4 cuenta por qué Meta ajustó Llama 3 con DPO y no con PPO (menos cálculo y mejores resultados en obedecer instrucciones), y los dos cambios que hicieron a la pérdida.

  • Is DPO Superior to PPO for LLM Alignment? A Comprehensive Study
    paperXu, Fu, Gao, Ye, Liu, Mei, Wang, Yu y Wu, 2024ICML 2024 · arXiv:2404.10719EN

    El contrapunto: con PPO bien ajustado, RLHF gana a DPO en todas sus pruebas. Una de las razones es la de la celda: DPO sólo ve las respuestas de las comparaciones, y la política puede ir a parar a otras que nadie ha juzgado.