Exploración de poesía mediante machine learning: generación de embeddings, clustering y clasificación emocional usando textos de César Vallejo y otros poetas traducidos al inglés.
Nota: Aunque este proyecto se describe en español, los datasets y modelos se entrenan con poemas en inglés, debido a la mayor disponibilidad de recursos NLP en ese idioma.
Este proyecto explora la relación entre el significado semántico y emocional de la poesía a través de modelos de embeddings modernos.
Combina dos enfoques de aprendizaje:
- Aprendizaje no supervisado: Agrupamiento (clustering) de poemas por estilo o tono.
- Aprendizaje supervisado: Clasificación de poemas por emoción o tema.
Se busca responder:
“¿Puede un modelo de lenguaje percibir la emoción detrás de un poema, como lo hace un lector humano?”
Cómo se presentarán los modelos a emplear en este repositorio
---
title: Flujo de Trabajo
---
%%{init: {
'look':'handDrawn',
'theme':'default',
'flowchart': {
'layoutDirection': "TD"
}
}
}%%
flowchart
A[Poemas originales<br>Vallejo + otros] --> B[Preprocesamiento<br>Limpieza y Tokenización]
%% Representación expandida
B --> S2[FeatureUnion]
subgraph FeatureUnion["Unión de Características"]
S2 -->|CountVect| S21[CountVectorizer]
S2 -->|TF-IDF| S22[TfidfVectorizer]
S2 -->|DictVect| S23[DictVectorizer]
end
%% unión de features
S21 --> S3[ToDense]
S22 --> S3[ToDense]
S23 --> S3[ToDense]
S3 --> S4[Normalize]
%% Paso opcional de reducción dimensional
S4 --> D[Reducción Dimensional<br>PCA / t-SNE / UMAP]
D --> E[Clustering<br>KMeans / GMM / DBSCAN / Agglomerative]
%% Ramas no supervisadas
subgraph Clustering["Análisis no supervisado"]
E[Clustering<br>KMeans / GMM / DBSCAN / Agglomerative]
F[Modelado de Tópicos<br>LDA]
G[Similitud<br>Coseno / Correlación]
end
S4 --> F
S4 --> G
E --> H1[Gráficas 2D/3D<br>t-SNE/UMAP + labels]
F --> H2[Análisis de temas]
G --> H2
E --> H2[Emociones emergentes]
%% Rama supervisada detallada
S4 --> S5[StackingClassifier<br>OneVsRest]
subgraph Stacking["Análisis supervisado"]
S5 --> S51[MultinomialNB]
S5 --> S52[ComplementNB]
S51 --> S6[LogisticRegression<br>final estimator]
S52 --> S6[LogisticRegression<br>final estimator]
end
S6 --> E2[Predicción de emoción o<br>tono poético]
%% Subgraph de integración
subgraph Integracion["Integración de Resultados"]
E2 --> J[Resultados supervisados]
H2 --> J
H1 --> J
end
J --> K[Interpretación final<br>Emoción + Temas emergentes]
%% === Estilos de subgraphs con colores ===
style FeatureUnion fill:#FFE0B2,stroke:#EF6C00,stroke-width:2px %% naranja claro
style Stacking fill:#BBDEFB,stroke:#1565C0,stroke-width:2px %% azul claro
style Clustering fill:#F1F3F4,stroke:#9AA0A6,stroke-width:1px %% gris claro
style Integracion fill:#E8F5E9,stroke:#2E7D32,stroke-width:2px %% verde claro
Nota: UMAP y t-SNE son reducción de dimensionalidad, no clustering.
Elaborado con: Mermaid - Flowchart
Nota: Aunque este proyecto se describe en español, los datasets y modelos se entrenan con poemas en inglés, debido a la mayor disponibilidad de recursos NLP en ese idioma.
📘 Ejemplo poético: Los Heraldos Negros
“Hay golpes en la vida, tan fuertes... ¡Yo no sé!
Golpes como del odio de Dios; como si ante ellos,
la resaca de todo lo sufrido
se empozara en el alma... ¡Yo no sé!”
En la versión inglesa:
“There are blows in life, so powerful... I don't know!
Blows as from God's hatred; as if before them,
the backlash of everything suffered
were to dam up in the soul... I don't know!”
El dataset combina poemas en dominio público y textos etiquetados a partir de fuentes abiertas (HuggingFace / Kaggle).
Cuando no hay etiquetas manuales, se aplican modelos de Análisis de Sentimientos (sentiment analysis) como punto de partida.
kaggle:
Fundación BBVA
Para analizar la poesía desde una perspectiva computacional, los textos deben transformarse en representaciones numéricas.
Con ello se puede aplicar embeddings y algoritmos de clustering o classification.
Esta sección describe cómo se generan las primeras representaciones usando enfoques clásicos de bag-of-words: CountVectorizer, TF-IDF Vectorizer y DictVectorizer, antes de generar embeddings más complejos.
El CountVectorizer convierte cada poema en un vector basado en la frecuencia de aparición de cada término.
Sea un corpus con
Para un documento
donde
Cada poema queda representado como un vector:
Consideremos el verso inicial de César Vallejo:
“Hay golpes en la vida, tan fuertes... ¡Yo no sé!”
Después de normalizar, eliminar signos y stopwords, el texto puede quedar así:
tokens = ["golpes", "vida", "tan", "fuertes"]Dependiendo del idioma y la lista de stopwords usada, pueden quedar entre 3 y 6 términos relevantes. Ese número es el que se utiliza como denominador en el cálculo de la frecuencia (TF).
Si el vocabulario relevante es (se considera tan como stopword)
["golpes", "vida", "fuertes"]entonces:
Cada palabra aparece una vez.
El TF-IDF (Term Frequency–Inverse Document Frequency) pondera la frecuencia de los términos por su rareza en el conjunto de poemas.
Así, las palabras comunes reciben menos peso y las más singulares destacan en la representación.
donde
Por tanto:
Supongamos un corpus de tres versos de César Vallejo:
from typing import Dict
verso: Dict[int: str] = {
1: "Hay golpes en la vida, tan fuertes... ¡Yo no sé!"
2: "Golpes como del odio de Dios;"
3: "Son las caídas hondas de los Cristos del alma."
}Si el término "golpes" aparece en 2 de 3 documentos, y "vida" solo en uno:
Dado que cada palabra aparece una vez y el poema tiene 6 términos relevantes (según el preprocesamiento elegido):
Entonces:
Por tanto, el vector TF-IDF sería:
El DictVectorizer permite convertir diccionarios de frecuencias o características personalizadas en vectores numéricos.
Es útil cuando cada poema ya fue transformado en una estructura tipo diccionario, por ejemplo:
from sklearn.feature_extraction import DictVectorizer
from typing import Dict
verso: Dict[str, int] = [
{"golpes": 2, "vida": 1},
{"odio": 1, "dios": 1, "golpes": 1}
]
vectorizer = DictVectorizer()
X = vectorizer.fit_transform(verso)El resultado es una matriz dispersa con dimensiones iguales al vocabulario global. Cada columna representa una palabra y cada fila un verso.
El DictVectorizer es particularmente útil si antes aplicas una limpieza o un conteo personalizado (por ejemplo, solo de sustantivos o adjetivos).
- CountVectorizer: solo cuenta ocurrencias: útil para observar repeticiones léxicas.
- TF-IDF Vectorizar: valora la relevancia semántica de los términos únicos o pocos frecuentes.
- DictVectorizer: traduce diccionarios personalizados en vectores, útil para features lingüísticas.
En poesía, donde cada palabra tiene un peso emocional y simbólico, TF-IDF refleja mejor la singularidad expresiva de cada poema, esas que, como en Vallejo, "duelen en el alma y pesan en la historia".
Una vez que los poemas han sido transformados en vectores (por ejemplo, con TF-IDF Vectorizer), se puede medir qué tan cercos semánticamente están dos versos o poemas.
La medida más utilizada para esto es la similitud del coseno:
Tomemos 2 versos de César Vallejo:
from typing import Dict
verso: Dict[int: str] = {
1: "Hay golpes en la vida, tan fuertes... ¡Yo no sé!"
2: "Golpes como del odio de Dios;"
}A partir del preprocesamiento y cálculo TF-IDF previo, se tiene:
Y para el segundo verso, aplicando el mismo procedimiento:
El vocabulario común es:
["golpes", "vida", "dios"]Siendo su representación tridimensional de los dos versos de Los Heraldos Negros en el espacio de embeddings TF-IDF. El vector azul claro -dodgerblue- corresponde al primer verso (“Hay golpes en la vida, tan fuertes... ¡Yo no sé!”) y el naranja al segundo (“Golpes como del odio de Dios;”).
Esta visualización permite observar cómo las diferencias en el peso semántico y frecuencia de términos alteran la dirección y magnitud de los vectores en el espacio.
- Producto punto:
- Norma de cada vector:
- Similitud del coseno:
-
La similitud de 0.845 indica una fuerte afinidad semántica entre ambos versos: ambos giran en torno al concepto de golpe, vida y el dolor divino.
-
En términos poéticos, se podría decir que ambos fragmentos vibran en la misma frecuencia emocional, aunque sus palabras difieran.
“Así, el vector no mide rimas, sino resonancias del alma.” 💫
Las ramificaciones son poemas que comparten similitudes con varios grupos → quedan como “puentes” o “brazos”.
Los nudos o concentraciones (zonas densas) son grupos de poemas con vocabulario/emoción muy parecida.
El hecho de que se vean como filamentos o bacterias es porque UMAP estira el espacio para mostrar continuidad entre regiones.
Si en el corpus hay poemas con temas/emociones muy conectados (por ejemplo, dolor ↔ muerte ↔ desesperanza en Vallejo), UMAP los hilvana en curvas continuas.
Si fueran más disjuntos (ej. poemas amorosos vs poemas políticos), verías islas separadas, no ramificaciones.
En poesía esto es natural: los temas no son rígidos, sino que fluyen de uno a otro. El gráfico refleja precisamente esa transición semántica difusa.
Fue generado en gitignore.io con los filtros python, macos, windows y consumido mediante su API como archivo crudo desde la terminal:
curl -L https://www.toptal.com/developers/gitignore/api/python,macos,windows > .gitignore-
Hubert Ronald - Trabajo Inicial - HubertRonald
-
Ve también la lista de contribuyentes que participaron en este proyecto.
El código fuente de este proyecto se distribuye bajo licencia MIT - ver la LICENCIA archivo (en inglés) para más detalle.
Los textos poéticos utilizados (como los de César Vallejo) provienen de fuentes de dominio público o traducciones disponibles con fines educativos.
En caso de utilizar materiales con derechos reservados, estos se emplean únicamente para fines de investigación, análisis lingüístico y demostración académica, sin fines comerciales.

