Oviedo_Alexander_Proyecto_Integrador_S7.docx
Procesamiento de Lenguaje Natural · Semana 7 · 10 de agosto de 2026
← volver descargar
Lo entregué en Word con formato APA. Aquí lo puedes leer sin descargar nada, aunque los márgenes y la tipografía originales sólo están en el archivo.

Predicción y generación de texto con redes neuronales recurrentes LSTM:

proyecto integrador sobre el corpus de Don Quijote de la Mancha

Alexander Oviedo Fadul

Maria Fernanda Ruiz Paipilla

Neheman Samir Jaller Cerchiaro

William David Obando Lopez

Grupo 7

Especialización en Inteligencia Artificial

Corporación Universitaria Minuto de Dios (UNIMINUTO)

NRC-8774 — Procesamiento Natural del Lenguaje

Semana 7 — Proyecto integrador

Docente: Nathalia Orozco Morales

17 de agosto de 2026

Introducción

Escribir un texto que suene a Cervantes es fácil para una persona que haya leído el Quijote; para una máquina, no tanto. Este proyecto integrador aborda ese problema con la herramienta que corresponde a la Semana 7 del curso: una red neuronal recurrente de tipo LSTM que aprende a predecir el siguiente elemento de una secuencia y, encadenando esas predicciones, genera texto nuevo.

El punto de partida no fue una hoja en blanco. La docente compartió en la sesión sincrónica un cuaderno de referencia con la arquitectura exacta que pide la actividad, y pidió algo concreto: que lo tomáramos como insumo, lo enriqueciéramos y lo hiciéramos más complejo, aumentando las épocas y demostrando la mejora con métricas (Orozco Morales, 2026). Eso fue lo que hicimos. El corpus de 60 filas del ejemplo de clase pasó a un texto de 403.247 caracteres descargado directamente de Project Gutenberg; las 30 épocas del insumo pasaron a 30; y donde había un modelo, ahora hay dos.

Ese segundo modelo responde a un detalle del enunciado que conviene no pasar por alto. La actividad pide, textualmente, predecir caracteres de un texto, mientras que el insumo de clase trabaja a nivel de palabra. En lugar de elegir una u otra lectura, implementamos las dos con la misma arquitectura solicitada —LSTM de 128 unidades internas seguida de una capa Dense con softmax— y las comparamos. La comparación terminó siendo una de las partes más instructivas del trabajo.

El documento recorre primero los fundamentos teóricos de las redes recurrentes y de la variante LSTM, describe después la metodología y explica el código bloque por bloque, presenta los resultados reales de los dos entrenamientos con sus métricas época a época, discute el efecto de la temperatura sobre el texto generado y cierra con las conclusiones y las limitaciones que encontramos. El cuaderno ejecutable, con todas las salidas, está enlazado en el anexo.

Desarrollo

Marco teórico

De las redes feedforward a las recurrentes

Una red feedforward procesa cada entrada de forma independiente: no guarda rastro de lo que vio antes. Para clasificar una imagen eso basta, pero para el lenguaje es un problema serio, porque el significado de una palabra depende de las que la preceden. Las redes recurrentes resuelven esa carencia introduciendo un estado oculto que se realimenta en cada paso de la secuencia, de modo que la salida en el instante t depende tanto de la entrada actual como de todo lo procesado hasta ese momento (Srinivasa-Desikan, 2018).

El mecanismo tiene un costo. Al entrenar mediante retropropagación a través del tiempo, el gradiente se multiplica repetidamente por los mismos pesos y tiende a desvanecerse; en la práctica, una RNN simple olvida lo que ocurrió veinte o treinta pasos atrás. Hochreiter y Schmidhuber (1997) propusieron la LSTM justamente para eso.

Arquitectura de una red neuronal recurrente

Esta es la primera de las dos preguntas orientadoras del enunciado. Una RNN se compone de una celda que se aplica de forma repetida sobre la secuencia. En cada paso recibe el elemento actual y el estado oculto anterior, y produce un nuevo estado: h(t) = f(Wx·x(t) + Wh·h(t−1) + b). Los pesos Wx y Wh son los mismos en todos los pasos, lo que permite procesar secuencias de cualquier longitud con un número fijo de parámetros.

La LSTM sustituye esa celda por una estructura con memoria explícita y tres puertas. La puerta de olvido decide qué parte del estado anterior se descarta; la de entrada, qué información nueva se incorpora; y la de salida, qué se expone al resto de la red. Gracias a ese diseño, el gradiente puede fluir por la celda de memoria sin desvanecerse, y la red aprende dependencias considerablemente más largas (Goodfellow et al., 2016).

En nuestro caso la salida de la LSTM son 128 valores numéricos —las 128 unidades internas que exige la actividad—. Conviene insistir en algo que la docente subrayó en clase: esos 128 valores no son 128 palabras memorizadas, sino una representación comprimida de todo lo que la red ha leído de la secuencia. La capa Dense proyecta esa representación a una puntuación por cada elemento del vocabulario, y softmax convierte esas puntuaciones en una distribución de probabilidad que suma uno.

Aplicaciones de las redes neuronales recurrentes

La segunda pregunta orientadora admite una respuesta corta: cualquier problema en el que el orden importe. Traducción automática, reconocimiento y síntesis de voz, análisis de sentimiento, etiquetado morfosintáctico, reconocimiento de entidades nombradas, predicción de series temporales, detección de anomalías en lecturas de sensores y, por supuesto, generación y autocompletado de texto (Zhou, 2022).

Vale la pena señalar los casos que usamos a diario sin reparar en ellos: la sugerencia de continuación cuando escribimos un correo, el autocompletado de un editor de código, la corrección predictiva del teclado del teléfono. Todos ellos descienden conceptualmente del mismo problema que resolvemos aquí: dada una secuencia parcial, ¿cuál es el elemento más probable a continuación?

Softmax, temperatura y token OOV

Softmax entrega la distribución de probabilidad, pero elegir siempre la opción más probable produce un texto que se repite en bucle. La temperatura corrige eso dividiendo los logaritmos de las probabilidades por un factor T antes de muestrear. Con T menor que uno la distribución se concentra y el texto se vuelve conservador; con T mayor que uno se aplana y aparecen opciones poco frecuentes, a costa de la coherencia. Es el mismo mando de creatividad que llevan los modelos generativos actuales (Campesato, 2021).

El token OOV, por su parte, es una pieza pequeña pero indispensable. Al declarar Tokenizer(oov_token="<OOV>") se reserva un índice para las palabras que el modelo nunca vio. Sin él, una palabra desconocida generaría una secuencia vacía y rompería la inferencia; con él, el modelo la representa de forma consistente y la ejecución continúa.

Metodología

El trabajo siguió el esquema de pipeline replicable que la docente insistió en clase: una línea base que funciona, y sobre ella las mejoras, documentando cada paso para que otro pueda reproducirlo. Las fases fueron cinco.

Fase 1. Construcción del corpus

El insumo de clase obliga a subir un CSV con el botón de Colab cada vez que se reinicia el entorno, lo que rompe la reproducibilidad. Optamos por descargar el texto de El ingenioso hidalgo don Quijote de la Mancha desde Project Gutenberg, que es dominio público (Cervantes, 1605/2004), recortarlo de forma determinista entre la primera línea de la novela y el pie de licencia, y limitarlo a un volumen manejable. Como respaldo, el mini-corpus de la docente quedó embebido en el propio cuaderno como constante de Python: si no hay red, el notebook sigue funcionando.

Al revisar ese mini-corpus encontramos algo que merece mención. El archivo tiene 60 filas, pero en realidad contiene 30 frases: la segunda mitad repite la primera añadiendo siempre la misma coletilla sobre el caballero y su escudero. Esa duplicación explica en buena medida por qué en la demostración de clase el modelo tendía a producir cadenas del tipo «su su su y y y»: estaba aprendiendo el relleno más que el estilo. Conservamos únicamente las 30 frases únicas.

El corpus final quedó en 403.247 caracteres y 73.974 palabras, con 12.479 formas distintas. Frente a los 1.354 tokens del ejemplo de clase, el factor de enriquecimiento es de aproximadamente 55 veces, por encima del «aliméntenlo por diez» que pidió la docente.

Fase 2. Preprocesamiento

Para el modelo de palabras se tokenizó con la clase Tokenizer de Keras, limitando el vocabulario a las 6.000 formas más frecuentes y reservando el token OOV. El dataset se construyó con ventanas deslizantes de 20 palabras de contexto y la palabra siguiente como etiqueta.

Aquí tomamos una decisión de diseño que vale la pena justificar, porque nos costó un intento fallido. La aproximación intuitiva —generar prefijos crecientes y codificar la etiqueta con one-hot— es inviable en cuanto el vocabulario crece: con varios miles de clases y decenas de miles de ejemplos, la matriz de salida no cabe en memoria. Usar etiquetas enteras con sparse_categorical_crossentropy da exactamente el mismo resultado matemático consumiendo varios órdenes de magnitud menos memoria.

Para el modelo de caracteres se usó una ventana de 60 caracteres con paso 3 sobre un subconjunto del corpus, y un vocabulario de 42 símbolos del español.

Fase 3. Arquitectura

Ambos modelos comparten la estructura que exige el enunciado: una capa Embedding que convierte los índices en vectores densos, una capa LSTM de 128 unidades internas y una capa Dense con activación softmax del tamaño del vocabulario. El optimizador fue Adam y la función de pérdida, entropía cruzada categórica dispersa.

Fase 4. Entrenamiento con seguimiento por época

El punto 4 de la actividad pide entregar la salida de un entrenamiento completo con los textos generados época a época. Lo resolvimos con un callback propio que, al terminar cada época, imprime las cuatro métricas y genera texto con cuatro temperaturas distintas. También se incorporaron ReduceLROnPlateau, para bajar la tasa de aprendizaje cuando la pérdida de validación se estanca, y ModelCheckpoint, para conservar el mejor modelo.

Fase 5. Evaluación y análisis

Se compararon las curvas de entrenamiento y validación de ambos modelos, se midió el efecto de las temperaturas 0.3, 0.7, 1.0 y 1.2 sobre el texto generado y se contrastaron los dos niveles de tokenización. Todas las cifras de este informe proceden del archivo de resultados que produce el propio cuaderno; ninguna se transcribió a mano.

Explicación de las líneas que integran el código

El enunciado pide explicar las líneas que integran el código. Recorremos los bloques en el mismo orden en que aparecen en el cuaderno.

Configuración y reproducibilidad

SEMILLA = 42
np.random.seed(SEMILLA)
tf.random.set_seed(SEMILLA)

Fijar las semillas de NumPy y de TensorFlow hace que la inicialización de pesos, el barajado de los lotes y el muestreo con temperatura sean reproducibles. Sin esto, dos ejecuciones del mismo cuaderno darían textos distintos y la comparación entre modelos perdería sentido.

Carga del corpus desde internet

peticion = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(peticion, timeout=timeout) as respuesta:
crudo = respuesta.read().decode("utf-8", errors="ignore")
inicio = crudo.find("En un lugar de la Mancha, de cuyo nombre")
fin = crudo.find("*** END OF THE PROJECT GUTENBERG")
return crudo[inicio:fin]

La cabecera User-Agent evita que el servidor rechace la petición. El recorte por marcadores de texto es determinista: siempre devuelve el mismo fragmento, independientemente de cambios menores en la cabecera del archivo. Todo el bloque va dentro de un try/except que prueba dos URL y, si ambas fallan, continúa con el corpus embebido en lugar de abortar.

Tokenización

tokenizer = Tokenizer(num_words=VOCAB_MAX, oov_token="<OOV>")
tokenizer.fit_on_texts([corpus])
tokens = tokenizer.texts_to_sequences([corpus])[0]

fit_on_texts recorre el corpus, cuenta frecuencias y asigna a cada palabra un índice entero, empezando por la más frecuente. num_words limita el vocabulario efectivo, lo que reduce el tamaño de la capa de salida y acelera el entrenamiento. texts_to_sequences traduce el texto a la lista de enteros con la que trabajará la red.

Construcción del dataset con ventanas deslizantes

for i in range(SEQ_LEN, len(tokens)):
X.append(tokens[i - SEQ_LEN:i]) # contexto
y.append(tokens[i]) # palabra siguiente

Cada ejemplo es una ventana de longitud fija y su continuación. Al desplazar la ventana de uno en uno se obtienen tantos ejemplos como tokens tenga el corpus, y el modelo ve cada palabra en múltiples contextos. La división en entrenamiento y validación es temporal, no aleatoria: el último quince por ciento del corpus se reserva para validar, de modo que el modelo no valide sobre fragmentos que ya vio entremezclados.

Definición del modelo

modelo_palabra = Sequential([
Embedding(input_dim=vocab_size, output_dim=128),
LSTM(128),
Dense(vocab_size, activation="softmax"),
])
modelo_palabra.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"])

Embedding aprende un vector de 128 dimensiones por palabra; palabras que aparecen en contextos parecidos acaban con vectores parecidos, y eso es lo que permite generalizar. LSTM(128) es el requisito central de la actividad: 128 celdas internas que mantienen el estado de la secuencia. Dense con softmax produce la probabilidad de cada palabra del vocabulario. La pérdida sparse_categorical_crossentropy acepta la etiqueta como entero, lo que evita la matriz one-hot mencionada antes.

Muestreo con temperatura y top-k

for indice in PROHIBIDOS: # 0 = padding, 1 = <OOV>
p[indice] = 0.0
p = p / p.sum()
logits = np.log(p + 1e-12) / max(temperatura, 1e-6)
p = np.exp(logits - logits.max())
p /= p.sum()
indices = np.argpartition(p, -k)[-k:]
return int(np.random.choice(indices, p=p[indices] / p[indices].sum()))

Se pasa a logaritmos, se divide por la temperatura y se vuelve al dominio de probabilidades. El término 1e-12 evita el logaritmo de cero y la resta del máximo previene el desbordamiento de la exponencial. Después, top-k restringe el muestreo a las k opciones más probables: sin ese filtro, la larga cola de palabras improbables introduce demasiado ruido.

Las dos primeras líneas merecen una aclaración, porque son una decisión nuestra y no del enunciado. Como el vocabulario del modelo está acotado a las formas más frecuentes, el token <OOV> tiene una probabilidad apreciable y el muestreo lo elegía con frecuencia, dejando el texto salpicado de marcadores que no son palabras. Anularlo en el momento de generar —junto con el relleno del padding— no modifica el modelo ni sus métricas: es una restricción de inferencia, del mismo tipo que el top-k. Las cifras de pérdida y precisión que se reportan más adelante se calculan sobre el vocabulario completo, sin esta exclusión.

Callback de generación por época

class GeneracionPorEpoca(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs=None):
for temperatura in TEMPERATURAS:
texto = generar_palabras(self.semilla, temperatura=temperatura)

Keras invoca on_epoch_end al cerrar cada época, con las métricas en el diccionario logs. El callback las imprime y genera texto con cada temperatura, guardando además todo en una lista que después se exporta a JSON. Este bloque es el que materializa el requisito de mostrar la evolución época a época.

Un detalle de rendimiento: dentro del bucle de generación se llama a modelo(entrada, training=False) en lugar de modelo.predict(...). predict está pensado para lotes grandes y arrastra una sobrecarga considerable en llamadas sueltas; en un bucle que hace cientos de predicciones por época, la diferencia se nota.

Ventajas y desventajas de este tipo de código

Ventajas

  • Es transparente. Cada etapa —tokenizar, ventanear, entrenar, muestrear— está a la vista y se puede inspeccionar por separado. Frente a una API cerrada, aquí se entiende exactamente qué hace el modelo.

  • Es liviano. Los dos modelos juntos rondan los 1.8 millones de parámetros y se entrenan en una sesión de clase sin GPU dedicada, algo impensable con un transformer preentrenado.

  • Es reproducible y portátil. Con las semillas fijas y el corpus descargado desde una URL pública, el cuaderno da los mismos resultados en Colab y en local, sin depender de archivos que haya que subir a mano.

  • Es modular. Cambiar LSTM(128) por GRU(128), ajustar la ventana de contexto o sustituir el corpus son modificaciones de una línea, lo que facilita experimentar.

Desventajas

  • El entrenamiento es secuencial por naturaleza: la LSTM debe procesar el paso t antes del t+1, así que no se paraleliza como un transformer. Es la limitación que motivó la arquitectura de atención.

  • La memoria efectiva es corta. Con una ventana de 20 palabras, el modelo no puede referirse a algo mencionado dos párrafos antes; genera frases locales plausibles, no discurso con hilo argumental.

  • La capa de salida crece con el vocabulario. En el modelo de palabras, la Dense final concentra la mayor parte de los parámetros, y ampliar el corpus encarece el modelo de forma directa.

  • No inventa palabras nuevas: el modelo de nivel palabra solo puede emitir formas que estén en su vocabulario, y cualquier otra queda reducida al token OOV.

  • Es sensible a la calidad del corpus. Como se vio con el mini-corpus duplicado de clase, un sesgo en los datos aparece de inmediato en el texto generado. El preprocesamiento no es un trámite.

Resultados

Modelo a nivel de palabra

El modelo se entrenó durante 30 épocas sobre 73.959 ejemplos, con 1.673.584 parámetros entrenables y un vocabulario efectivo de 6.000 palabras. La pérdida de entrenamiento bajó de 6.4233 a 4.1717 y la precisión subió de 0.0636 a 0.2047. En validación, la pérdida partió de 5.9385, alcanzó su mínimo de 5.5484 en la época 9 y terminó en 5.7540.

Tabla 1

Evolución de las métricas del modelo a nivel de palabra

Época loss val_loss accuracy val_accuracy
1 6.4233 5.9385 0.0636 0.0964
4 5.6167 5.6355 0.1057 0.1227
7 5.1894 5.5561 0.1466 0.1365
10 4.8504 5.5556 0.1668 0.1411
13 4.5216 5.6032 0.1839 0.1412
16 4.3494 5.6510 0.1947 0.1431
19 4.2591 5.6924 0.2000 0.1433
22 4.2118 5.7201 0.2034 0.1420
25 4.1879 5.7385 0.2038 0.1419
28 4.1761 5.7495 0.2041 0.1415
30 4.1717 5.7540 0.2047 0.1411

Nota. Se muestra una de cada 3 épocas del entrenamiento completo de 30. El mínimo de val_loss se alcanzó en la época 9. Elaboración propia.

Figura 1

Curvas de pérdida y precisión del modelo a nivel de palabra

Figura del informe

Nota. Entrenamiento en línea continua y validación en línea discontinua. Elaboración propia.

La progresión del texto generado es más elocuente que las cifras. Con la semilla «el caballero andante se lanzó a la aventura» y temperatura 0.7, estos fueron los resultados en tres momentos del entrenamiento:

Época 1 (val_loss = 5.9385):

el caballero andante se lanzó a la aventura a en la la bien y la caballero don se lo que que que no — — la es a bien en

Época 15 (val_loss = 5.6424):

el caballero andante se lanzó a la aventura que no le han contado y con una manera que vuestra merced señor caballero —dijo don quijote— que no la suerte —dijo

Época 30 (val_loss = 5.7540):

el caballero andante se lanzó a la aventura por ella no hay dos y aun las aventuras no se había estado en el suelo con la cabeza y los que

Modelo a nivel de carácter

El modelo de caracteres trabajó con un vocabulario de apenas 42 símbolos y 33.140 ejemplos, con 89.194 parámetros. Tras 28 épocas, la pérdida de entrenamiento pasó de 2.5678 a 1.2034 y la precisión de 0.2605 a 0.6258; el mínimo de val_loss (1.6562) se alcanzó en la época 20.

Conviene aclarar que la precisión de este modelo no es comparable con la del anterior: acertar un carácter entre unas decenas es mucho más fácil que acertar una palabra entre miles. Lo interesante es cualitativo. En las primeras épocas el modelo produce secuencias de letras sin espacios coherentes; hacia el final ya respeta la longitud típica de las palabras españolas y produce formas ortográficamente plausibles, aunque no siempre existentes.

Época 1 (val_loss = 2.2017):

en un lugar de la mancha, de cuyo nombre no quiero acordarme lanpandesledesansara dorio y daro as mue noltanisare coda son lestan enadido arado antiniado da y ejua ponada el lre as antidas lina los as

Época 28 (val_loss = 1.6637):

en un lugar de la mancha, de cuyo nombre no quiero acordarme potrieron todas dejar que había hacer a peder de la obres, y, por él herros, lo de caballero en blanta, su algún trapiez al caballero en la

Figura 2

Curvas de pérdida y precisión del modelo a nivel de carácter

Figura del informe

Nota. Entrenamiento en línea continua y validación en línea discontinua. Elaboración propia.

Efecto de la temperatura

Con el modelo de palabras ya entrenado, generamos cuarenta palabras a partir de la misma semilla variando únicamente la temperatura. El contraste es inmediato.

Tabla 2

Texto generado según la temperatura de muestreo

T Texto generado (modelo a nivel de palabra)
0.3 el caballero andante se lanzó a la aventura que la pierda el galeote— que no le había de la mancha que me ha de la vida de la mancha que no me ha de la mancha que no me ha de ser que no me ha de ser
0.7 el caballero andante se lanzó a la aventura que era bien a su tierra con él y así se le sucedió a buscar y a su parte con la cabeza que él le había leído en mucha priesa que en facilidad adelante sino lo que le dio por
1.0 el caballero andante se lanzó a la aventura que era un camino que le pusieron un poco la espada al día por las razones a sus aventuras le dijo — no sea bien sino que él te faltan yo a él se había dado luego en la causa
1.2 el caballero andante se lanzó a la aventura no tengo menester lo mejor lo que más no le hizo de la espada — bien se pierde el galeote— del mundo porque no es nada mas aquí es menester que le han sucedido el día está el otro rey

Nota. Semilla común: «el caballero andante se lanzó a la aventura». Muestreo con top-k = 30. Elaboración propia.

Figura 3

Distribución de probabilidad de las diez palabras candidatas según la temperatura

Figura del informe

Nota. Contexto: «en un lugar de la mancha de cuyo». A menor temperatura, la probabilidad se concentra en las primeras candidatas. Elaboración propia.

Comparación entre los dos niveles de tokenización

Tabla 3

Comparación de los modelos a nivel de palabra y de carácter

Criterio Nivel palabra Nivel carácter
Tamaño del vocabulario 6.000 42
Contexto de entrada 20 palabras 60 caracteres
Ejemplos de entrenamiento 73.959 33.140
Parámetros 1.673.584 89.194
Épocas 30 28
val_loss final 5.7540 1.6637
val_accuracy final 0.1411 0.4957

Nota. Los valores de pérdida no son comparables entre columnas: el espacio de salida tiene tamaños muy distintos. Elaboración propia.

Discusión

El resultado más útil del ejercicio no fue el texto generado, sino lo que revelan las dos curvas al mirarlas juntas. La analogía que usó la docente en clase —cuántas veces hay que leer un libro para poder responder preguntas sobre él— funciona bien como intuición, pero tiene un límite que los datos muestran con claridad: a partir de la época 9 la pérdida de entrenamiento sigue bajando mientras la de validación deja de mejorar. El modelo está memorizando, no aprendiendo. Aumentar las épocas sin vigilar la validación no mejora nada; solo consume tiempo, y por eso conviene conservar el punto de control del mejor modelo en lugar del último.

El segundo hallazgo tiene que ver con los datos. Al revisar el mini-corpus de clase encontramos que la mitad de sus filas repite la primera mitad añadiendo siempre la misma coletilla. Eso ofrece una explicación plausible de por qué en la demostración de la sesión el modelo tendía a producir cadenas repetitivas: una construcción que aparece en la mitad exacta de las muestras recibe un peso enorme durante el entrenamiento. No lo verificamos con un experimento controlado —habría requerido entrenar dos veces variando solo esa condición—, pero la relación es lo bastante directa como para justificar la limpieza que aplicamos. Un modelo bien diseñado sobre datos sesgados reproduce el sesgo con toda fidelidad.

Sobre la temperatura, la conclusión práctica es que no existe un valor universalmente óptimo. Para autocompletar una frase, donde importa acertar, conviene un valor bajo. Para generar variantes de un texto, un valor medio o alto amplía el repertorio. Lo que sí quedó claro es que por encima de 1.2 el texto empieza a desarmarse a mitad de frase.

La comparación entre niveles de tokenización arrojó una diferencia de fondo. El modelo de caracteres debe gastar buena parte de su capacidad en aprender ortografía —dónde termina una palabra, qué letras pueden ir juntas— antes de ocuparse del significado; el de palabras arranca con ese problema resuelto, pero paga el precio de un vocabulario enorme en la salida y de no poder producir ninguna forma que no haya visto. Para un corpus de este tamaño, el nivel de palabra da resultados legibles antes; con corpus mucho mayores, la balanza puede inclinarse hacia unidades intermedias como las subpalabras que usan los modelos actuales.

Conviene ser honestos sobre el alcance. Con un corpus de este volumen y una ventana de 20 palabras, el modelo no captura dependencias largas: produce fragmentos plausibles, no discurso con hilo. Resolver eso exige mecanismos de atención, que es precisamente el contenido de la semana siguiente. Visto así, este proyecto funciona como el escalón previo: entender qué problema resuelve la recurrencia y dónde se queda corta explica por qué apareció el transformer.

Conclusiones

Se implementó y entrenó la arquitectura solicitada —red neuronal recurrente LSTM con 128 unidades internas seguida de una capa Dense con activación softmax— en dos variantes, a nivel de palabra y a nivel de carácter, sobre un corpus de 403.247 caracteres del Quijote descargado en línea. Ambas generan texto con sentido reconocible del castellano cervantino.

El seguimiento época a época mostró una mejora sostenida: en el modelo de palabras la pérdida de entrenamiento cayó de 6.4233 a 4.1717 y la precisión pasó de 0.0636 a 0.2047 en 30 épocas. Ahora bien, el mejor punto de generalización no fue el final sino la época 9, donde la pérdida de validación tocó su mínimo. Más épocas no equivale automáticamente a mejor modelo.

El enriquecimiento del corpus, siguiendo la indicación de la docente, amplió el vocabulario del modelo a 6.000 formas frente a las poco más de doscientas del ejemplo de clase, y con ello el repertorio de continuaciones posibles. También detectamos y eliminamos la duplicación que arrastraba el mini-corpus original, un ajuste de preprocesamiento que no cambia la arquitectura pero sí lo que el modelo aprende.

La temperatura se confirmó como el parámetro más visible del muestreo. En este corpus, el rango 0.7 a 0.8 ofreció el mejor equilibrio entre coherencia y variedad; por debajo el texto se vuelve repetitivo y por encima de 1.2 pierde estructura.

Finalmente, el pipeline quedó documentado y es replicable de principio a fin: el cuaderno descarga su propio corpus, fija las semillas, entrena, evalúa y exporta los resultados a un archivo con el que se construyó este informe. Ese cierre del circuito —que las cifras del documento provengan directamente de la ejecución y no de una transcripción manual— nos parece tan relevante como el modelo mismo, porque es lo que permite que otro equipo verifique lo que aquí se afirma.

Referencias

Arumugam, R., y Shanmugamani, R. (2018). TensorBoard visualization. En Hands-on natural language processing with Python: A practical guide to applying deep learning architectures to your NLP applications (pp. 224-236). Packt Publishing.

Campesato, O. (2021). Transformer, BERT, and GPT. En Natural language processing fundamentals for developers (pp. 247-287). Mercury Learning and Information.

Cervantes Saavedra, M. de. (2004). El ingenioso hidalgo don Quijote de la Mancha [Libro electrónico núm. 2000]. Project Gutenberg. https://www.gutenberg.org/ebooks/2000 (Obra original publicada en 1605)

Ganegedara, T. (2018). Defining inputs in TensorFlow. En Natural language processing with TensorFlow: Teach language to machines using Python's deep learning library (pp. 37-152). Packt Publishing.

Goodfellow, I., Bengio, Y., y Courville, A. (2016). Deep learning. MIT Press. https://www.deeplearningbook.org/

Hochreiter, S., y Schmidhuber, J. (1997). Long short-term memory. Neural Computation, 9(8), 1735-1780. https://doi.org/10.1162/neco.1997.9.8.1735

Orozco Morales, N. (2026). Semana 7: Redes neuronales recurrentes, LSTM y generación de texto [Cuaderno de clase y sesión sincrónica]. NRC-8774 Procesamiento Natural del Lenguaje, Corporación Universitaria Minuto de Dios.

Srinivasa-Desikan, B. (2018). Deep learning for text. En Natural language processing and computational linguistics: A practical guide to text analysis with Python, Gensim, spaCy and Keras (pp. 224-236). Packt Publishing.

Zhou, Y. (2022). Natural language processing with improved deep learning neural networks. Scientific Programming, 2022, 1-8.

Anexo. Cuaderno ejecutable

El cuaderno con el código completo, las salidas de los dos entrenamientos época a época, las figuras y las comparaciones de temperatura está disponible en Google Colab:

<<PEGAR AQUÍ LA URL DE GOOGLE COLAB>>

El cuaderno es autónomo: descarga el corpus desde Project Gutenberg y lleva embebido el mini-corpus de clase como respaldo, de modo que no requiere subir ningún archivo al entorno de ejecución. Basta con abrirlo y ejecutar todas las celdas en orden.

Archivo asociado: Oviedo_Alexander_Proyecto_Integrador_S7.ipynb. Entorno verificado: TensorFlow con Keras 3, ejecución en CPU.