Lección 2 de 10 · El problema y los datos · 16 min
Los datos
De dónde salen los prompts, cuántos hay en cada paso y quién etiqueta
Notebook del episodio
El código de esta lección, para correrlo y modificarlo en Google Colab, gratis y con tu cuenta. Para guardar tus cambios: Archivo → Guardar una copia en Drive.
InstructGPT. Episodio dos: los datos. Antes de cualquier pérdida o gradiente, el paper tuvo que decidir qué pedidos usar, quién escribe las respuestas y quién las juzga. Esas decisiones definen a quién se alinea el modelo. Hoy las leemos una por una, con los números de las tablas del paper.
Tres datasets, tres usos
- SFT: prompt + respuesta escrita por una persona
- RM: prompt + K respuestas ordenadas por una persona
- PPO: solo prompts, sin ninguna etiqueta humana
- Todo sale de dos fuentes: etiquetadores y usuarios de la API
De una misma colección de prompts salen tres datasets. El de SFT, supervised fine-tuning: cada prompt viene con la respuesta que escribió una persona, la demostración. El del reward model: cada prompt viene con varias respuestas del modelo, ordenadas por una persona. El de PPO, proximal policy optimization: solo prompts. Ahí no hace falta ninguna etiqueta, porque el que juzga es el reward model. Y los prompts tienen dos orígenes: los que escribieron los propios etiquetadores, y los que mandaron usuarios a la API de OpenAI, application programming interface: la puerta por la que los clientes usan el modelo.
Parte 1 · De dónde salen los prompts
Un problema de huevo y gallina: para juntar pedidos tipo instrucción hace falta un modelo que acepte instrucciones.
El arranque
Así arrancaron. A los usuarios de GPT tres no se les ocurría mandar instrucciones, porque el modelo no las seguía. Así que los etiquetadores escribieron los primeros prompts. Con eso entrenaron, de forma supervisada, un primer InstructGPT. Lo pusieron en beta en el Playground, la interfaz web de la API, a principios de dos mil veintiuno. Los usuarios del Playground le mandaron pedidos, con un aviso de que podían usarse para entrenar. Y de esos pedidos salen casi todos los datasets del paper. A este arranque se le dice bootstrapping.
Tres tipos de prompt escritos a mano
- Plain: una tarea cualquiera, con diversidad
- Few-shot: una instrucción y varios pares pregunta/respuesta
- User-based: casos de uso pedidos en la lista de espera de la API
A los etiquetadores les pidieron tres tipos de prompt. Plain, simple: inventar una tarea cualquiera, cuidando que haya variedad. Few-shot: una instrucción, más varios pares de pregunta y respuesta para esa instrucción. El ejemplo del paper: dá el sentimiento de un tweet, con respuestas positivo o negativo. Y user-based: la gente que pedía acceso a la API contaba para qué lo quería, y los etiquetadores escribieron prompts para esos casos de uso, reescritos para que no se pueda identificar a ninguna empresa.
Un prompt few-shot rinde K ejemplos
El paper describe una forma de multiplicar los few-shot, y es fácil de escribir. Tenemos K pares de pregunta y respuesta para la misma instrucción, sintéticos. Para cada par i, tomamos los otros K menos uno. Los ponemos en el contexto, con su respuesta. Y al final va la pregunta i, sin respuesta: el objetivo es su respuesta. Cuatro pares, cuatro ejemplos. Mirá el tercero: la instrucción, los otros tres pares completos, y qué lindo día, flecha, sin respuesta. El objetivo es positivo. Con K pares, K ejemplos distintos.
instr = "Dá el sentimiento de la frase."
pares = [("Me gustó el libro.", "Positivo"), # K pares
("El tren llegó tarde.", "Negativo"), # pregunta/respuesta
("Qué lindo día.", "Positivo"),
("Perdí el paraguas.", "Negativo")]
ejemplos = []
for i, (q, r) in enumerate(pares):
otros = [p for j, p in enumerate(pares) if j != i] # los K−1
prompt = instr + "\n"
prompt += "".join(f"{a} → {b}\n" for a, b in otros) # en contexto
prompt += q + " →"
ejemplos.append((prompt, r)) # objetivo: r
print("K =", len(pares), "pares →", len(ejemplos), "ejemplos")
prompt, r = ejemplos[2]
print(prompt)
print("objetivo:", r)
K = 4 pares → 4 ejemplos
Dá el sentimiento de la frase.
Me gustó el libro. → Positivo
El tren llegó tarde. → Negativo
Perdí el paraguas. → Negativo
Qué lindo día. →
objetivo: Positivo
K pares → K ejemplos
: pregunta · : respuesta
: concatenar texto
Lo mismo en notación. Llamamos q sub j a la pregunta del par j, y r sub j a su respuesta; j va de uno a K. De ahí salen K ejemplos. En el dibujo, el contexto del ejemplo tres: los pares uno, dos y cuatro entran; el tres, apagado, queda afuera, porque es la pregunta. El ejemplo i, x sub i, es la instrucción, concatenada con todos los pares menos el i, concatenada con la pregunta i. Las dos barras verticales quieren decir concatenar texto. Y el objetivo, y sub i, es la respuesta i. En el dibujo: todo el prompt queda tachado, y solo la respuesta entra en la pérdida. Eso es la máscara del SFT, que vemos en el episodio tres.
Limpiar los prompts de la API
Los prompts de la API pasan por tres filtros que el paper describe en una frase. Acá, con prompts sintéticos. Primero, deduplicar con una heurística: si un prompt comparte un comienzo largo con otro ya visto, se descarta. Segundo, un tope de prompts por usuario. El paper usa doscientos; acá, tres. Tercero, dividir en train y test por usuario, no por prompt. Y contamos cuántos usuarios quedan en los dos lados. De cuarenta y ocho prompts quedan diecinueve. El usuario c, que mandaba siempre la misma plantilla, pasa de siete a uno. Los que mandaban mucho quedan en tres. Y ningún usuario del test aparece en train: cero.
vistos, por_usuario, limpios = set(), Counter(), []
for usuario, p in prompts:
if p[:LARGO] in vistos: # mismo comienzo largo: duplicado
continue
if por_usuario[usuario] >= TOPE: # tope de prompts por usuario
continue
vistos.add(p[:LARGO])
por_usuario[usuario] += 1
limpios.append((usuario, p))
usuarios = sorted(por_usuario)
random.shuffle(usuarios)
test = set(usuarios[:2]) # se divide por usuario, no por prompt
train = [(u, p) for u, p in limpios if u not in test]
print("prompts:", len(prompts), "→ limpios:", len(limpios))
antes = Counter(u for u, _ in prompts)
print("antes: ", " ".join(f"{u}{antes[u]}" for u in sorted(antes)))
print("después:", " ".join(f"{u}{por_usuario[u]}" for u in sorted(antes)))
print("test:", sorted(test), "| en ambos:",
len({u for u, _ in train} & test))
prompts: 48 → limpios: 19
antes: a8 b6 c7 d9 e5 f6 g3 h4
después: a3 b3 c1 d3 e3 f3 g2 h1
test: ['b', 'c'] | en ambos: 0
Tres reglas, en símbolos
: los primeros caracteres
: prompts que aporta el usuario
ningún usuario de test aparece en train
Las tres reglas en símbolos. Pref sub L de p son los primeros L caracteres del prompt p. Si dos prompts empiezan igual, el segundo se descarta. En el dibujo, cuántos prompts mandó cada usuario sintético antes de filtrar. n sub u es cuántos prompts aporta el usuario u: como mucho doscientos. En el dibujo, después de deduplicar y de aplicar el tope: nadie domina. U train y U test son los conjuntos de usuarios de cada parte. Su intersección, el símbolo de la U invertida, tiene que ser vacía. En el dibujo, b y c van enteros a test. Un usuario manda pedidos parecidos: si estuviera en los dos lados, el test premiaría memorizarlo.
Cuidados con los datos de la API
- Solo el Playground, nunca tráfico de producción
- Aviso a los usuarios de que sus prompts podían usarse
- Filtro de información personal (PII) en train
- Más del 96 % en inglés
Cuatro cuidados que el paper declara. Solo usan prompts del Playground, no de clientes que usan la API en producción. Cada vez que alguien pasaba a un modelo InstructGPT, aparecía un aviso de que sus prompts podían usarse para entrenar. Filtran del split de train los prompts con información personal, PII. El paper no dice con qué método. Y más del noventa y seis por ciento está en inglés.
Parte 2 · Qué piden los usuarios
Con los prompts de la API, los etiquetadores marcaron a qué tipo de tarea pertenece cada uno.
Tabla 1: tipos de tarea en el dataset del RM
La tabla uno del paper, sobre los prompts del dataset del reward model. Casi la mitad, cuarenta y cinco coma seis por ciento, es generation: escribir algo nuevo. Después, open QA, question answering: preguntas abiertas, doce coma cuatro; brainstorming, once coma dos; chat, ocho coma cuatro; reescribir, seis coma seis. Las tareas clásicas de los benchmarks de lenguaje, como clasificar, extraer o responder sobre un texto dado, son las tres más chicas, abajo del cuatro por ciento cada una. La gente le pide al modelo que cree, no que clasifique.
- Generation: 45,6 %
- Open QA: 12,4 %
- Brainstorm: 11,2 %
- Chat: 8,4 %
- Rewrite: 6,6 %
- Summariz.: 4,2 %
- Classif.: 3,5 %
- Other: 3,5 %
- Closed QA: 2,6 %
- Extract: 1,9 %
Parte 3 · Cuántos datos hay en cada paso
La tabla seis del paper dice cuántos prompts hay en cada dataset y de dónde vienen. Son menos de lo que uno imagina.
La tabla 6, sumada
Copiamos la tabla seis tal cual: para cada dataset, la cantidad de prompts por split, train o validación, y por origen, etiquetador o cliente. Sumamos los de train. Y calculamos qué fracción viene de clientes de la API. SFT: doce mil setecientos veinticinco prompts de train, y solo el once por ciento de clientes; el resto lo escribieron los etiquetadores. Reward model: treinta y tres mil doscientos siete, ochenta por ciento de clientes. PPO: treinta y un mil ciento cuarenta y cuatro, todos de clientes.
tabla6 = { # (split, origen): prompts
"SFT": {("train", "etiquetador"): 11_295, ("train", "cliente"): 1_430,
("valid", "etiquetador"): 1_550, ("valid", "cliente"): 103},
"RM": {("train", "etiquetador"): 6_623, ("train", "cliente"): 26_584,
("valid", "etiquetador"): 3_488, ("valid", "cliente"): 14_399},
"PPO": {("train", "cliente"): 31_144, ("valid", "cliente"): 16_185},
}
for nombre, t in tabla6.items():
train = sum(n for (s, o), n in t.items() if s == "train")
cliente = t.get(("train", "cliente"), 0)
print(f"{nombre:4} train {train:6,} de clientes {cliente / train:4.0%}")
SFT train 12,725 de clientes 11%
RM train 33,207 de clientes 80%
PPO train 31,144 de clientes 100%
Prompts de train por dataset y origen
En barras. El SFT es casi todo de etiquetadores: once mil doscientos noventa y cinco. De clientes, apenas mil cuatrocientos treinta. Por las plantillas few-shot, que se multiplican. El reward model, al revés: seis mil seiscientos veintitrés de etiquetadores, y veintiséis mil quinientos ochenta y cuatro de clientes. Y cada prompt trae entre cuatro y nueve respuestas ordenadas: en el episodio cuatro vemos que eso multiplica los pares. PPO, treinta y un mil prompts de clientes, sin etiquetas.
- SFT · etiquetador: 11.295
- SFT · cliente: 1.430
- RM · etiquetador: 6.623
- RM · cliente: 26.584
- PPO · cliente: 31.144
Parte 4 · Quiénes etiquetan
Unas cuarenta personas, contratadas por Upwork y Scale AI, escriben y juzgan todo. Cómo se las eligió importa.
Los cuatro criterios de selección
- Acuerdo con los investigadores marcando contenido sensible
- Acuerdo con los investigadores ordenando respuestas
- Demostraciones para prompts sensibles: puntaje de 1 a 7
- Autoevaluación: con qué temas o grupos se sienten cómodos
El paper quería gente sensible a las preferencias de distintos grupos y buena detectando respuestas dañinas. Por eso hizo un test con cuatro criterios. Primero, el acuerdo con los investigadores al marcar qué es contenido sensible. Segundo, el acuerdo al ordenar respuestas por calidad. Tercero, escribir demostraciones para prompts delicados, puntuadas en una escala de uno a siete. Y cuarto, una autoevaluación: para qué temas o grupos culturales se sienten capaces de identificar lenguaje sensible. Por razones legales no podían elegir por datos demográficos.
Los cortes del test
Los tres primeros criterios tienen cortes numéricos. Setenta y cinco por ciento de acuerdo marcando contenido sensible. Setenta y cinco por ciento ordenando. Y seis sobre siete en las demostraciones. Entra quien pasa todo. Los candidatos son inventados. A y D entran. B falla por ordenar: setenta por ciento. C escribe bien pero no llega a seis. E falla dos. Ojo con el código: el paper dice que eran cortes blandos, y que la elección final fue subjetiva, por el cuarto criterio. Esto es la versión dura de una regla blanda.
def pasa(sensible, ranking, demo):
return [sensible >= 0.75, # acuerdo marcando contenido sensible
ranking >= 0.75, # acuerdo ordenando respuestas
demo >= 6] # demostraciones sensibles, de 1 a 7
for nombre, notas in candidatos.items():
ok = pasa(*notas)
marca = "".join("✓" if x else "✗" for x in ok)
s, r, d = notas
print(f"{nombre} {s:.2f} {r:.2f} {d:.1f} {marca}",
"entra" if all(ok) else "")
A 0.81 0.78 6.4 ✓✓✓ entra
B 0.92 0.70 6.1 ✓✗✓
C 0.77 0.83 5.6 ✓✓✗
D 0.88 0.79 6.5 ✓✓✓ entra
E 0.66 0.74 6.2 ✗✗✓
¿Cuánto coinciden dos personas?
¿Y qué significa acuerdo entre etiquetadores? Con rankings sintéticos: dos personas ordenan las mismas cuatro respuestas, cada una con su ruido. Un ranking de K respuestas se parte en pares. K sobre dos es la cantidad de pares: K por K menos uno, sobre dos. Con cuatro, seis. s sub i j vale uno si la respuesta i va antes que la j. Comparamos ese valor entre la persona a y la b. Y promediamos sobre los pares. En el primer prompt coinciden en tres de seis pares. En dos mil prompts, el acuerdo da setenta y uno por ciento. Elegimos el ruido para quedar cerca del número del paper.
Cada línea, su término:
def pares(orden)→ —pos[i] < pos[j]→ — cada par: quién va primerox == y→ — ¿las dos personas coinciden?np.mean(iguales)→ — promedio sobre los pares
def pares(orden): # ranking → ¿i antes que j? para cada par
pos = {y: k for k, y in enumerate(orden)}
return [pos[i] < pos[j] for i, j in combinations(range(K), 2)]
acuerdos = []
for q in calidad: # dos personas ordenan las mismas K
a, b = pares(etiquetador(q)), pares(etiquetador(q))
iguales = [x == y for x, y in zip(a, b)]
acuerdos.append(np.mean(iguales))
if len(acuerdos) == 1:
print("pares:", len(iguales), "| coinciden:", [int(v) for v in iguales])
print(f"acuerdo promedio: {np.mean(acuerdos):.1%}")
pares: 6 | coinciden: [1, 1, 0, 1, 0, 0]
acuerdo promedio: 71.0%
El acuerdo, con números
primer prompt del snippet
train · held-out · investigadores en Stiennon 2020
El primer prompt del snippet, par por par: coinciden en el uno dos, el uno tres y el dos tres; en los otros tres, no. Tres sobre seis: cero coma cinco. En el dibujo, uno o cero por par, y el promedio. Los números del paper. Los etiquetadores de entrenamiento coinciden entre sí el setenta y dos coma seis por ciento de las veces, más o menos uno coma cinco. Un grupo aparte, que no produjo datos de entrenamiento, los held-out, setenta y siete coma tres. En el trabajo anterior de resúmenes, de Stiennon, entre investigadores: setenta y tres. En una de cada cuatro comparaciones dos personas no coinciden: un techo para cualquier reward model.
Qué se les pidió priorizar
- En entrenamiento: primero que sea helpful
- En la evaluación final: primero honest y harmless
- Inferir la intención; saltear lo que no se entiende
- Quien etiqueta no es quien escribió el prompt
Una decisión que el paper marca como difícil. Durante el entrenamiento, a los etiquetadores se les pidió priorizar que la respuesta ayude al usuario, por encima de la veracidad y de no dañar. En la evaluación final, al revés: primero veraz e inofensiva, porque, dicen, es lo que de verdad les importa. Tenían que inferir la intención, y saltear los pedidos muy confusos. Y una limitación admitida: quien etiqueta no es quien escribió el prompt, y puede entender otra cosa.
Ouyang et al., 2022 — Training language models to follow instructions with human feedback · arXiv:2203.02155
~40 · «we hired a team of about 40 contractors on Upwork and through ScaleAI»
El dato para quedarse. Unas cuarenta personas. Ellas escribieron las demostraciones, ordenaron las respuestas y juzgaron los resultados. El paper lo dice explícitamente: el método alinea el modelo a las preferencias de un grupo específico, sobre todo etiquetadores e investigadores, y no a una noción más amplia de valores humanos. Lo discutimos en el episodio ocho.
Resumiendo
- Prompts: primero escritos a mano, después del Playground
- Few-shot: K pares → K ejemplos; dedupe, tope y split por usuario
- Tabla 1: 45,6 % generation; clasificar y extraer, < 4 %
- Train: SFT 12.725 · RM 33.207 · PPO 31.144 prompts
- ~40 etiquetadores; acuerdo entre ellos 72,6 %
- Próximo: paso 1, el SFT y sus 16 epochs
Papers del episodio
¿Qué tal esta lección?
Sé el primero en calificarla
¿Terminaste la lección?
Márcala para sumar +50 XP y mantener tu racha.
Tutor IA
Contexto: Los datos
Conectando con el tutor…