Muestrear la siguiente palabra
Usa random.choices() para elegir la siguiente palabra de una distribución de probabilidad ponderada por probabilidades de bigramas.
- Usar random.choices(población, pesos) para realizar una selección aleatoria ponderada
- Comprender cómo los pesos influyen en la probabilidad de cada resultado
- Configurar una semilla aleatoria para resultados reproducibles
- Muestrear de una tabla de bigramas para elegir la siguiente palabra dada la palabra actual
El motor de la generación de texto
La generación de texto es, en esencia, un problema de muestreo. Dada una palabra actual, necesitas elegir la siguiente palabra de una distribución de posibilidades, algunas palabras son probables, otras raras, pero todas son posibles. random.choices() hace exactamente esto.
Las celdas siguientes reutilizan las funciones load_corpus, tokenize, build_bigrams y normalize_bigrams de las lecciones 01 a 06. Cada página de lección inicia una sesión de Python nueva, así que ejecuta primero esta celda de configuración para reconstruir el modelo de bigramas:
import csv
import string
import random
from collections import defaultdict
with open("slm-corpus.csv", newline="") as f:
reader = csv.DictReader(f)
texts = [row["text"] for row in reader]
def load_corpus(path):
with open(path, newline="") as f:
reader = csv.DictReader(f)
return [row["text"] for row in reader]
def tokenize(text):
text = text.lower()
for char in string.punctuation:
text = text.replace(char, " ")
return text.split()
def build_bigrams(tokens):
bigrams = defaultdict(lambda: defaultdict(int))
for i in range(len(tokens) - 1):
bigrams[tokens[i]][tokens[i + 1]] += 1
return dict(bigrams)
def normalize_bigrams(bigrams):
normalized = {}
for word, followers in bigrams.items():
if not followers:
continue
total = sum(followers.values())
normalized[word] = {w: c / total for w, c in followers.items()}
return normalized
model = normalize_bigrams(build_bigrams(tokenize(" ".join(texts))))Conceptos clave
Fundamentos de random.choices()
random.choices() elige uno o más elementos de una lista, ponderados por sus probabilidades:
import random
words = ["cat", "dog", "bird"]
weights = [0.5, 0.3, 0.2] # probabilities must sum to 1
# Pick one word
result = random.choices(words, weights=weights, k=1)
print(result[0]) # e.g. 'cat'El parámetro k controla cuántos elementos elegir. Para la generación de texto, eliges una palabra a la vez.
Muestreo repetido
Para ver la distribución en acción, muestrea muchas veces:
import random
words = ["cat", "dog", "bird"]
weights = [0.5, 0.3, 0.2]
counts = {w: 0 for w in words}
for _ in range(1000):
pick = random.choices(words, weights=weights, k=1)[0]
counts[pick] += 1
print(counts)
# e.g. {'cat': 502, 'dog': 298, 'bird': 200}Con 1000 muestras, “cat” debería aparecer aproximadamente 500 veces (50 %), “dog” unas 300 veces (30 %) y “bird” unas 200 veces (20 %).
Muestrear del modelo de bigramas
Dada una palabra actual, busca sus seguidores en el modelo normalizado y muestrea:
def sample_next(model, current_word):
if current_word not in model:
return None # no followers known
followers = model[current_word]
words = list(followers.keys())
weights = list(followers.values())
return random.choices(words, weights=weights, k=1)[0]
# Example
current = "the"
next_word = sample_next(model, current)
print(f"After '{current}' comes '{next_word}'")Si la palabra actual no está en el modelo (no tiene seguidores conocidos), devuelve None. Quien llama debe manejar esto, ya sea detener la generación o elegir una palabra aleatoria para continuar.
Reproducibilidad con semillas
random.choices() usa el estado aleatorio global de Python. Configurar una semilla hace que la salida sea reproducible, útil para depurar y probar:
random.seed(42)
print(sample_next(model, "the")) # always the same word with seed 42
random.seed(99)
print(sample_next(model, "the")) # might be differentManejar el caso límite: sin seguidores
Algunas palabras solo aparecen al final del corpus y no tienen seguidores conocidos. Cuando sample_next devuelve None, tienes opciones:
- Detener la generación, la opción más conservadora
- Reiniciar desde una palabra aleatoria, mantiene la salida en marcha
- Reiniciar desde una palabra común, elige de las N palabras más frecuentes
La opción 3 suele producir los mejores resultados:
import random
top_words = ["the", "and", "to", "of", "a"]
def sample_next_or_restart(model, current_word):
result = sample_next(model, current_word)
if result is None:
return random.choice(top_words) # restart
return resultInténtalo
Carga el modelo de bigramas normalizado y muestrea la siguiente palabra 10 veces después de “the”:
random.seed(42)
tokens = tokenize(" ".join(load_corpus("slm-corpus.csv")))
model = normalize_bigrams(build_bigrams(tokens))
for _ in range(10):
next_word = sample_next(model, "the")
print(f"the → {next_word}")¿Qué tan consistentes son los resultados? Prueba a cambiar la semilla, ¿obtienes palabras diferentes?
Conclusiones clave
random.choices(población, pesos, k=1)realiza una selección aleatoria ponderada- Los pesos deben sumar 1.0 para una interpretación correcta de la probabilidad
random.seed()hace que la salida sea reproducible para depurar- Maneja los seguidores faltantes reiniciando desde una palabra común
Reto de práctica
Escribe una función sample_n(model, palabra, n) que devuelva una lista de n siguientes palabras muestreadas para una palabra actual dada. Úsala para ver la distribución de seguidores de “the”:
def sample_n(model, word, n=100):
results = []
for _ in range(n):
results.append(sample_next(model, word))
from collections import Counter
return Counter(results).most_common()1. ¿Qué hace random.choices() para el muestreo de palabras?
2. ¿Por qué usar pesos en lugar de probabilidades iguales para muestrear?
3. ¿Qué ocurre si muestreas con pesos=[0.5, 0.3, 0.2]?