En 04-04 aprendiste a convertir secuencias en ventanas deslizantes para predecir «el siguiente valor», y quedó prometido que ese mismo truco serviría para generar texto. Es la hora. En este segundo proyecto del módulo construirás un generador de borradores de descripciones de producto para los vendedores de TecnoMarket: un modelo que, carácter a carácter, aprende el estilo de las fichas de producto y propone texto nuevo. Además de las fases habituales del proyecto, esta lección introduce la única pieza técnica nueva que necesitas: el muestreo con temperatura, que controla el equilibrio entre texto seguro-pero-repetitivo y texto creativo-pero-caótico.
Contenido
- Enunciado del proyecto y enfoque carácter a carácter
- Fase 1: el corpus de descripciones
- Fase 2: vectorización a nivel de carácter y ventanas de secuencias
- Fase 3: modelo Embedding + LSTM
- Fase 4: entrenamiento
- Fase 5: muestreo con temperatura
- Fase 6: generación iterativa y resultados típicos
- Límites del enfoque y uso responsable
Enunciado del proyecto y enfoque carácter a carácter
Contexto de negocio. Los vendedores de TecnoMarket tardan en redactar las descripciones de sus artículos y muchas quedan vacías o telegráficas. Queremos un asistente que genere un borrador con el estilo de la casa, que el vendedor edita y aprueba. El humano siempre firma el texto final.
¿Por qué carácter a carácter? Es la formulación más simple y didáctica de la generación: el modelo ve una secuencia de caracteres y predice cuál viene después. Es exactamente el problema de 04-04 (predecir el siguiente paso de una serie) cambiando números por caracteres: mismas ventanas deslizantes, misma cabeza de clasificación que en 04-03. Con vocabularios de ~50 símbolos no hay que gestionar palabras desconocidas y el modelo cabe en cualquier máquina. El precio — coherencia limitada a frases cortas — lo analizaremos al final.
Fase 1: el corpus de descripciones
Para el prototipo usamos un corpus sintético de descripciones ficticias de TecnoMarket. En un caso real usarías el histórico de fichas aprobadas; aquí generamos material de estilo homogéneo (también puedes practicar con cualquier texto público en español, pero el corpus propio mantiene el hilo del proyecto):
import numpy as np
import tensorflow as tf
tf.random.set_seed(42) # 06-04: reproducibilidad
productos = ["auriculares inalambricos", "teclado mecanico", "monitor curvo",
"robot aspirador", "freidora de aire", "camara de seguridad",
"altavoz bluetooth", "cafetera espresso", "raton gaming",
"lampara inteligente", "bascula digital", "ventilador de torre"]
adjetivos = ["compacto", "potente", "silencioso", "elegante", "versatil",
"ergonomico", "resistente", "ligero"]
ventajas = ["bateria de larga duracion", "conexion estable", "diseno moderno",
"facil instalacion", "bajo consumo", "gran capacidad",
"control desde el movil", "materiales de primera calidad"]
cierres = ["ideal para el hogar.", "perfecto para tu dia a dia.",
"la mejor opcion calidad precio.", "envio en 24 horas."]
rng = np.random.default_rng(42)
frases = []
for _ in range(3000):
p, a = rng.choice(productos), rng.choice(adjetivos)
v1, v2 = rng.choice(ventajas, size=2, replace=False)
c = rng.choice(cierres)
frases.append(f"{p} {a} con {v1} y {v2}. {c}\n")
corpus = "".join(frases)
print(len(corpus)) # ~300 000 caracteres
print(corpus[:200])Observa dos decisiones: el corpus está en minúsculas y sin tildes (vocabulario pequeño y limpio) y cada descripción termina en \n, que el modelo aprenderá como señal de «fin de descripción». Unos 300 000 caracteres son pocos para generación de calidad, pero suficientes para que el modelo aprenda el estilo — calibra expectativas en consecuencia.
Fase 2: vectorización a nivel de carácter y ventanas de secuencias
En 04-03 usamos TextVectorization a nivel de palabra; aquí bajamos al carácter con StringLookup, y aplicamos las ventanas deslizantes de 04-04 con tf.data (06-01):
vocab = sorted(set(corpus))
print(len(vocab)) # ~45 símbolos: letras, dígitos, espacio, puntuación, \n
char_a_id = tf.keras.layers.StringLookup(vocabulary=vocab, mask_token=None)
id_a_char = tf.keras.layers.StringLookup(vocabulary=vocab, invert=True,
mask_token=None)
ids = char_a_id(tf.strings.unicode_split(corpus, "UTF-8"))
LONG = 80 # longitud de ventana: ~una descripción completa
ds = tf.data.Dataset.from_tensor_slices(ids)
ds = ds.batch(LONG + 1, drop_remainder=True) # trozos de 81 caracteres
def entrada_objetivo(trozo):
return trozo[:-1], trozo[1:] # entrada: 80 chars; objetivo: los mismos desplazados 1
BATCH = 64
train_ds = (ds.map(entrada_objetivo)
.shuffle(10_000)
.batch(BATCH, drop_remainder=True)
.prefetch(tf.data.AUTOTUNE))La clave está en entrada_objetivo: para la entrada "teclado mecanic", el objetivo es "eclado mecanico" — en cada posición temporal el modelo aprende a predecir el carácter siguiente. Es la misma idea de ventana→siguiente-paso de 04-04, pero entrenando las 80 posiciones de la ventana a la vez, lo que multiplica la señal de aprendizaje por ejemplo.
Fase 3: modelo Embedding + LSTM
La arquitectura reutiliza las piezas de 04-02 y 04-03: un Embedding (aquí de caracteres, no de palabras) y una LSTM con return_sequences=True, porque necesitamos una predicción en cada paso temporal, no solo al final:
from tensorflow.keras import layers, models
VOCAB = len(char_a_id.get_vocabulary()) # incluye el token [UNK]
modelo = models.Sequential([
layers.Embedding(VOCAB, 64), # cada char -> vector de 64
layers.LSTM(256, return_sequences=True), # estado por cada posición
layers.Dropout(0.2), # regularización (05-04)
layers.Dense(VOCAB), # logits: puntuación por char
])
modelo.compile(
optimizer="adam",
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
)
modelo.build(input_shape=(None, None))
modelo.summary() # ~0.4 M de parámetrosDos detalles importantes:
- La capa final no lleva softmax (
from_logits=Trueen la pérdida): en la fase de muestreo manipularemos los logits directamente para aplicar la temperatura. - La pérdida es la CCE de siempre (02-04) aplicada por paso temporal: Keras promedia la entropía cruzada de las 80 predicciones de cada ventana.
Fase 4: entrenamiento
callbacks = [
tf.keras.callbacks.ModelCheckpoint("models/generador_mejor.keras",
monitor="loss", save_best_only=True),
tf.keras.callbacks.EarlyStopping(monitor="loss", patience=3,
restore_best_weights=True),
]
historia = modelo.fit(train_ds, epochs=30, callbacks=callbacks)Guía de lectura de la pérdida (con vocabulario de ~45 símbolos, la pérdida inicial ronda ln(45) ≈ 3.8 — predicción aleatoria):
| Pérdida | Qué genera el modelo |
|---|---|
| ~3.8 | Sopa de caracteres aleatorios |
| ~2.0 | Pseudo-palabras pronunciables, espacios en su sitio |
| ~1.2 | Palabras reales del corpus, sintaxis aproximada |
| < 0.8 | Frases con la estructura de las descripciones |
Con este corpus sintético (muy regular) la pérdida baja rápido, hacia 0.5-0.7 en 15-25 épocas y pocos minutos de CPU. Con un corpus real, más variado, esperarías valores más altos y más tiempo. No monitorizamos validación aquí: en generación de prototipo nos interesa capturar el estilo del corpus, y la evaluación decisiva será cualitativa (fase 6).
Fase 5: muestreo con temperatura
Entrenado el modelo, ¿cómo elegimos el siguiente carácter a partir de sus logits? Aquí aparece el concepto central de la lección.
- Greedy (voraz): elegir siempre el carácter más probable. Determinista y «seguro», pero cae en bucles:
con bateria de larga duracion y bateria de larga duracion y .... - Muestreo con temperatura: dividir los logits entre una temperatura
Tantes del softmax y muestrear de la distribución resultante:
def siguiente_char(logits, temperatura):
logits = logits / temperatura
id_muestra = tf.random.categorical(logits[tf.newaxis, :], num_samples=1)
return int(id_muestra[0, 0])Efecto de T sobre la distribución:
| Temperatura | Efecto | Salida típica |
|---|---|---|
| T → 0 | Casi greedy: gana siempre el más probable | Correcta pero repetitiva, frases clonadas del corpus |
| T = 0.5 | Conservadora: reparte poco | Buen compromiso por defecto |
| T = 1.0 | Distribución tal cual la aprendió el modelo | Variada, algún tropiezo |
| T = 1.5 | Aplana la distribución: los improbables ganan opciones | Inventa palabras: freidora de aire vertatil con conexo estible |
La temperatura no cambia el modelo: cambia cuánto riesgo aceptas al muestrear. Para borradores comerciales, temperaturas bajas-medias (0.4-0.8) son lo razonable.
Fase 6: generación iterativa y resultados típicos
Generar es iterar: predecir un carácter, añadirlo a la secuencia, volver a predecir. Versión didáctica (re-procesa toda la secuencia en cada paso; suficiente para un prototipo):
def generar(semilla, n_chars=150, temperatura=0.5):
ids_gen = char_a_id(tf.strings.unicode_split(semilla, "UTF-8"))
ids_gen = list(ids_gen.numpy())
for _ in range(n_chars):
entrada = tf.constant([ids_gen[-LONG:]]) # última ventana
logits = modelo(entrada)[0, -1, :] # logits del ultimo paso
ids_gen.append(siguiente_char(logits, temperatura))
chars = id_a_char(tf.constant(ids_gen))
return tf.strings.reduce_join(chars).numpy().decode("utf-8")
print(generar("robot aspirador ", temperatura=0.5))Salidas típicas reales de un modelo así entrenado (las tuyas variarán):
- T=0.2:
robot aspirador silencioso con bateria de larga duracion y facil instalacion. ideal para el hogar.— impecable, pero casi calcada del corpus. - T=0.7:
robot aspirador compacto con control desde el movil y bajo consumo. la mejor opcion calidad precio.— combina piezas de forma nueva y correcta: el punto dulce. - T=1.4:
robot aspirador ergonimo con gron combacidad y conexion estoble. envio en 24 hores.— creatividad desbocada: errores ortográficos y sintaxis rota.
Entrega. Igual que en 07-01, el generador se empaqueta con su preprocesado (el StringLookup viaja con el modelo si lo integras en un modelo de inferencia, 06-05) y se serviría tras un endpoint FastAPI (POST /borrador con producto y temperatura) que devuelve 3 borradores para que el vendedor elija y edite.
Límites del enfoque y uso responsable
Sé honesto con lo que has construido:
- Coherencia corta: una LSTM de caracteres mantiene el hilo unas decenas de caracteres; en textos largos se contradice o divaga. Nuestro corpus de frases cortas encaja justo dentro de ese límite — por eso funciona.
- No sabe de hechos: puede escribir «bateria de larga duracion» para un producto sin batería. Genera estilo, no verdad.
- Los sistemas comerciales de generación usan transformers/LLMs basados en la atención que viste en 05-05: contexto de miles de tokens y conocimiento general. El mecanismo de muestreo con temperatura que has aprendido aquí es exactamente el mismo que usan ellos — este proyecto te ha enseñado el motor en pequeño.
- Uso responsable en TecnoMarket: el modelo produce borradores; un humano revisa, corrige la ficha técnica y aprueba antes de publicar. Es la misma filosofía de la cola de revisión de 03-04: automatizar lo mecánico, supervisar lo que llega al cliente.
Errores Comunes y Consejos
- Olvidar
return_sequences=True: la LSTM devolvería solo el último estado y las formas no cuadrarían con el objetivo de 80 posiciones. Repasa 04-02 si el error de formas te despista. - Poner softmax en la última capa y además
from_logits=True: doble softmax silencioso que degrada el entrenamiento. Elige uno de los dos; aquí, logits. - Evaluar la generación solo por la pérdida: una pérdida baja con un corpus repetitivo puede significar pura memorización. Genera y lee: la inspección cualitativa es parte de la evaluación.
- Usar temperatura alta «para más creatividad» en producción: en textos comerciales, las palabras inventadas destruyen la confianza del cliente. Empieza en 0.5 y ajusta con ejemplos delante.
- Ventanas más largas que las descripciones sin motivo: alargan el entrenamiento sin mejorar un corpus de frases cortas. Ajusta
LONGal texto real.
Ejercicios
- Entrena el mismo modelo con
LSTM(128)y conLSTM(512)y compara pérdida final, tiempo por época y calidad de tres muestras a T=0.7. ¿Dónde están los rendimientos decrecientes? - Añade al corpus un 10 % de descripciones con un formato nuevo (por ejemplo, empezando por «oferta: »). Reentrena y comprueba con qué frecuencia y fidelidad el modelo genera ese formato.
- Implementa
generar_lote(semilla, k, temperatura)que devuelvakborradores distintos y descarte los que contengan palabras fuera de un diccionario del corpus (control de calidad automático previo a la revisión humana).
Soluciones
- Con 128 unidades la pérdida se queda típicamente 0.1-0.2 por encima y aparecen más tropiezos ortográficos; con 512 la pérdida mejora poco (el corpus es simple) y el tiempo por época se multiplica por ~3. Con este corpus, 256 es el punto dulce: más capacidad no aporta porque no hay más complejidad que aprender.
- Basta añadir
frases.append(f"oferta: {p} {a} con {v1}. {c}\n")dentro de un bucle adicional (~300 frases). Tras reentrenar, semillas que empiecen por"oferta: "completan el formato de forma consistente; sin esa semilla, el patrón aparece espontáneamente en torno al 10 % de las muestras — el modelo reproduce las frecuencias del corpus. - Construye el diccionario con
palabras_validas = set(corpus.split()); genera en bucle congenerar, divide cada borrador con.split()y acepta solo siall(p.strip('.') in palabras_validas for p in borrador.split()). Devuelve loskprimeros aceptados (con un tope de intentos). Este filtro barato elimina la mayoría de artefactos de temperaturas altas antes de que los vea el vendedor.
Conclusión
Segundo proyecto entregado: un generador de borradores que aprende el estilo de TecnoMarket carácter a carácter, con las ventanas deslizantes de 04-04, el tándem Embedding+LSTM de 04-03 y un muestreo con temperatura que ahora sabes leer y ajustar. Igual de valioso es lo que sabes que no hace: coherencia larga y veracidad quedan para los transformers de 05-05, y todo texto generado pasa por revisión humana. En la próxima lección cambiamos de nuevo de problema y cumplimos la promesa de 05-02: usar el error de reconstrucción de un autoencoder para detectar transacciones fraudulentas en TecnoMarket — nuestro primer proyecto con datos desbalanceados de verdad.
Curso de Deep Learning
Módulo 1: Introducción a Deep Learning
- ¿Qué es Deep Learning?
- Historia y evolución del Deep Learning
- Aplicaciones de Deep Learning
- Conceptos básicos de redes neuronales
- Preparación del entorno de trabajo
Módulo 2: Fundamentos de Redes Neuronales
- Perceptrón y Perceptrón Multicapa
- Función de activación
- Propagación hacia adelante y hacia atrás
- Optimización y función de pérdida
- Tu primera red neuronal completa
Módulo 3: Redes Neuronales Convolucionales (CNN)
- Introducción a las CNN
- Capas convolucionales y de pooling
- Arquitecturas populares de CNN
- Aplicaciones de CNN en reconocimiento de imágenes
Módulo 4: Redes Neuronales Recurrentes (RNN)
- Introducción a las RNN
- LSTM y GRU
- Aplicaciones de RNN en procesamiento del lenguaje natural
- Secuencias y series temporales
Módulo 5: Técnicas Avanzadas en Deep Learning
- Redes Generativas Adversariales (GAN)
- Autoencoders
- Transfer Learning
- Regularización y técnicas de mejora
- Mecanismos de atención y Transformers
Módulo 6: Herramientas y Frameworks
- Introducción a TensorFlow
- Introducción a PyTorch
- Comparación de frameworks
- Entornos de desarrollo y recursos adicionales
- Guardar, cargar y desplegar modelos
Módulo 7: Proyectos Prácticos
- Clasificación de imágenes con CNN
- Generación de texto con RNN
- Detección de anomalías con Autoencoders
- Creación de una GAN para generación de imágenes
- Fine-tuning de un modelo preentrenado
