Para esta actividad, he seleccionado tres descripciones breves de películas de ciencia ficción. Este texto es apropiado porque comparte términos técnicos (lexical overlap) pero difiere en el contexto narrativo, lo que permite evaluar la efectividad de la similitud de coseno.
Doc 1: Un viaje interestelar para salvar la humanidad a través de un agujero de gusano.
Doc 2: Una misión interestelar en una nave espacial para colonizar un nuevo planeta.
Doc 3: Un detective persigue replicantes en una ciudad futurista y oscura.
corpus_raw <- data.frame(
doc_id = c("Doc_1", "Doc_2", "Doc_3"),
text = c(
"Un viaje interestelar para salvar la humanidad a través de un agujero de gusano.",
"Una misión interestelar en una nave espacial para colonizar un nuevo planeta.",
"Un detective persigue replicantes en una ciudad futurista y oscura."
),
stringsAsFactors = FALSE
)Realizamos limpieza: minúsculas, eliminación de puntuación, stopwords y stemming para reducir la varianza léxica.
stop_es <- data.frame(word = stopwords("es"))
corpus_clean <- corpus_raw %>%
unnest_tokens(word, text) %>%
anti_join(stop_es) %>%
mutate(word = wordStem(word, language = "spanish")) %>%
group_by(doc_id) %>%
summarise(text_proc = paste(word, collapse = " "))
kable(corpus_clean) %>% kable_styling(full_width = F)| doc_id | text_proc |
|---|---|
| Doc_1 | viaj interestel salv human traves agujer gusan |
| Doc_2 | mision interestel nav espacial coloniz nuev planet |
| Doc_3 | detectiv persig replic ciud futur oscur |
Construimos la Matriz de Documento-Término (DTM) basada en frecuencias absolutas.
dtm_bow <- corpus_clean %>%
unnest_tokens(word, text_proc) %>%
count(doc_id, word) %>%
pivot_wider(names_from = word, values_from = n, values_fill = 0)
# Vocabulario aprendido
vocabulario <- colnames(dtm_bow)[-1]
print(paste("Vocabulario:", paste(vocabulario, collapse = ", ")))## [1] "Vocabulario: agujer, gusan, human, interestel, salv, traves, viaj, coloniz, espacial, mision, nav, nuev, planet, ciud, detectiv, futur, oscur, persig, replic"
| doc_id | agujer | gusan | human | interestel | salv | traves | viaj | coloniz | espacial | mision | nav | nuev | planet | ciud | detectiv | futur | oscur | persig | replic |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Doc_1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Doc_2 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| Doc_3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
Interpretación: La matriz es dispersa (muchos ceros) debido a que los documentos no comparten todas las palabras del vocabulario global.
La importancia de un término se calcula mediante el producto de su frecuencia local y su rareza global:
Frecuencia de Término (TF): Mide la importancia local de una palabra dentro de un documento específico. La forma más común de calcularla es la frecuencia relativa:
\[TF(t, d) = \frac{f_{t,d}}{\sum_{k \in d} f_{k,d}}\]
Frecuencia Inversa de Documento (IDF): Mide la importancia global de una palabra en todo el corpus (conjunto de documentos), penalizando las palabras que aparecen con demasiada frecuencia en todos lados.
\[IDF(t, D) = \log \left( \frac{N}{|\{d \in D : t \in d\}|} \right)\]
Peso Final TF-IDF: Es el producto de ambas medidas, logrando un balance entre la relevancia local y la rareza global.
\[TF\text{-}IDF(t, d, D) = TF(t, d) \cdot IDF(t, D)\]
Calculamos el peso TF-IDF para reequilibrar la importancia de los términos.
dtm_tfidf <- corpus_clean %>%
unnest_tokens(word, text_proc) %>%
count(doc_id, word) %>%
bind_tf_idf(word, doc_id, n) %>%
select(doc_id, word, tf_idf) %>%
pivot_wider(names_from = word, values_from = tf_idf, values_fill = 0)
kable(dtm_tfidf, caption = "Matriz TF-IDF") %>% kable_styling()| doc_id | agujer | gusan | human | interestel | salv | traves | viaj | coloniz | espacial | mision | nav | nuev | planet | ciud | detectiv | futur | oscur | persig | replic |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Doc_1 | 0.1569446 | 0.1569446 | 0.1569446 | 0.0579236 | 0.1569446 | 0.1569446 | 0.1569446 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| Doc_2 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0579236 | 0.0000000 | 0.0000000 | 0.0000000 | 0.1569446 | 0.1569446 | 0.1569446 | 0.1569446 | 0.1569446 | 0.1569446 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| Doc_3 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.0000000 | 0.183102 | 0.183102 | 0.183102 | 0.183102 | 0.183102 | 0.183102 |
Comparación: TF-IDF asigna valores menores a términos comunes en el corpus (como “interestelar”) y mayor peso a términos únicos de cada documento.
Se utiliza para cuantificar qué tan parecidos son dos documentos interpretándolos como vectores en un espacio multidimensional. En lugar de medir la distancia euclidiana, medimos el ángulo entre ellos.
Para medir la similitud entre dos documentos representados como vectores (\(\mathbf{A}\) y \(\mathbf{B}\)), calculamos el coseno del ángulo que forman en el espacio vectorial:
\[\text{sim}(\mathbf{A}, \mathbf{B}) = \cos(\theta) = \frac{\mathbf{A} \cdot \mathbf{B}}{\|\mathbf{A}\| \|\mathbf{B}\|}\]
De forma expandida para el cálculo computacional:
\[\text{sim}(\mathbf{A}, \mathbf{B}) = \frac{\sum_{i=1}^{n} A_i B_i}{\sqrt{\sum_{i=1}^{n} A_i^2} \sqrt{\sum_{i=1}^{n} B_i^2}}\]
Medimos el ángulo entre los vectores de los documentos.
calc_coseno <- function(m) {
mat <- as.matrix(m[,-1])
sim <- as.matrix(proxy::simil(mat, method = "cosine"))
diag(sim) <- 1
return(sim)
}
sim_bow <- calc_coseno(dtm_bow)
sim_tfidf <- calc_coseno(dtm_tfidf)
print("Similitud BoW (Doc 1 vs Doc 2):")## [1] "Similitud BoW (Doc 1 vs Doc 2):"
## [1] 0.1428571
Par más similar: Doc 1 y Doc 2 (comparten el concepto de misión/viaje interestelar).
Par menos similar: Doc 2 y Doc 3 (no hay solapamiento léxico significativo).
Seleccionamos tres tokens: “viaj”, “planet”, “detectiv”.
Viaj: [1, 0, 0]
Planet: [0, 1, 0]
Detectiv: [0, 0, 1]
Reflexión: Las representaciones One-hot son inadecuadas para la similitud semántica porque todos los vectores son ortogonales entre sí; la distancia entre cualquier par de palabras distintas es siempre la misma, ignorando si son sinónimos o están relacionadas.
La vectorización permite tratar el lenguaje como puntos en un espacio multidimensional, facilitando comparaciones matemáticas mediante el álgebra lineal. El esquema TF-IDF es fundamental porque penaliza términos globales que no ayudan a distinguir documentos. Sin embargo, al ser métodos puramente léxicos, no capturan el significado profundo; por ejemplo, “nave” y “cohete” serían tratados como términos totalmente distintos a pesar de su cercanía semántica.
Para garantizar que este análisis pueda ser ejecutado independientemente y genere los mismos resultados, se reportan las especificaciones técnicas del entorno.
set.seed(2026)A continuación, se detallan las versiones de los paquetes instalados en el sistema al momento de la compilación:
Información del Sistema: R version 4.4.2 (2024-10-31 ucrt)