16. Procesamiento Lenguaje Natural (NLP) III#

16.1. Representación o Vectorización del Texto#

¿Qué es la Representación del texto?

La representación del texto implica convertir el texto en un vector numérico para que los algoritmos de Machine Learning puedan entender sus atributos. En Procesamiento del Lenguaje Natural (PLN), este proceso se conoce como Representación de Texto o Vectorización de Texto. La vectorización es el proceso de convertir datos textuales o categóricos en vectores numéricos. Al convertir los datos en formatos numéricos, se puede entrenar el modelo con mayor precisión.
¿Por qué necesitamos representar las palabras como vectores numéricos?
  • Para cuantificar la semántica de las palabras.
  • Para capturar su significado contextualizado. Hay palabras que, fuera de su contexto, pueden tener significados completamente diferentes.
  • Porque los modelos no pueden procesar texto directamente.
Queremos una representación que capture el significado de una palabra de manera similar a como lo haría una persona. Es importante definir cuál es la unidad básica que queremos analizar: pueden ser palabras, frases dentro de un párrafo o párrafos dentro de un documento. Podemos, por ejemplo, representar una frase como un único vector numérico.

16.1.1. Término de Frecuencia (Term Frequency – Inverse Document Frequency)#

¿Qué significa el Término de Frecuencia(TF-IDF)?

TF-IDF< puede definirse como el cálculo de cuán relevante es una palabra en una serie o corpus respecto a un texto. La relevancia aumenta proporcionalmente al número de veces que una palabra aparece en el texto, pero se compensa por la frecuencia de la palabra en el corpus (conjunto de datos).

16.1.1.1. Ejemplo para explicar TF-IDF#

En este ejemplo, calculamos primero la frecuencia de cada término en cada documento (TF), luego la frecuencia de documento inversa (IDF) para cada término y finalmente multiplicamos TF por IDF para obtener los valores de TF-IDF. Esto nos da una medida más equilibrada de la importancia de cada término en cada documento, considerando tanto la frecuencia del término en el documento como su rareza en el corpus total.

Supongamos que tenemos los siguientes tres documentos:

  • Documento 1: “Me gusta caminar por el bosque”

  • Documento 2: “Caminar por el bosque es sanador”

  • Documento 3: “Me gusta el bosque”

Paso 1: Calcular la Frecuencia de Término (TF)

Término

Documento 1

Documento 2

Documento 3

“Me”

1/5 = 0.2

0

1/4 = 0.25

“gusta”

1/5 = 0.2

0

0

“caminar”

1/5 = 0.2

1/5 = 0.2

0

“por”

1/5 = 0.2

1/5 = 0.2

0

“el”

0

0

1/4 = 0.25

“bosque”

1/5 = 0.2

1/5 = 0.2

1/4 = 0.25

“es”

0

1/5 = 0.2

0

“sanador”

0

1/5 = 0.2

0

Paso 2: Calcular la Frecuencia de Documento Inversa (IDF)
  • N = 3 (número total de documentos)

Término

IDF

“Me”

(\log(3/2) \approx 0.176)

“gusta”

(\log(3/2) \approx 0.176)

“caminar”

(\log(3/2) \approx 0.176)

“por”

(\log(3/2) \approx 0.176)

“el”

(\log(3/1) = \log(3) \approx 0.477)

“bosque”

(\log(3/2) \approx 0.176)

“es”

(\log(3/1) = \log(3) \approx 0.477)

“sanador”

(\log(3/1) = \log(3) \approx 0.477)

Paso 3: Calcular TF-IDF

Término

Documento 1

Documento 2

Documento 3

“Me”

(0.2 \cdot 0.176 \approx 0.035)

(0 \cdot 0.176 = 0)

(0.25 \cdot 0.176 \approx 0.044)

“gusta”

(0.2 \cdot 0.176 \approx 0.035)

(0 \cdot 0.176 = 0)

(0 \cdot 0 = 0)

“caminar”

(0.2 \cdot 0.176 \approx 0.035)

(0.2 \cdot 0.176 \approx 0.035)

(0 \cdot 0 = 0)

“por”

(0.2 \cdot 0.176 \approx 0.035)

(0.2 \cdot 0.176 \approx 0.035)

(0 \cdot 0 = 0)

“el”

(0 \cdot 0.477 = 0)

(0 \cdot 0.477 = 0)

(0.25 \cdot 0.477 \approx 0.119)

“bosque”

(0.2 \cdot 0.176 \approx 0.035)

(0.2 \cdot 0.176 \approx 0.035)

(0.25 \cdot 0.176 \approx 0.044)

“es”

(0 \cdot 0.477 = 0)

(0.2 \cdot 0.477 \approx 0.095)

(0 \cdot 0 = 0)

“sanador”

(0 \cdot 0.477 = 0)

(0.2 \cdot 0.477 \approx 0.095)

(0 \cdot 0 = 0)

16.1.1.2. Ejemplo de código para explicar TF-IDF#

El código que se presenta a continuación realiza lo siguiente:
  • Tokenización y conteo de palabras: Divide cada documento en palabras y cuenta la frecuencia de cada término en cada documento.
  • Calcular TF (Frecuencia de Término): Calcula la frecuencia de cada término en cada documento.
  • Calcular IDF (Frecuencia de Documento Inversa): Calcula la frecuencia inversa de los documentos para cada término.
  • Calcular TF-IDF: Multiplica los valores de TF por los valores de IDF para obtener los valores de TF-IDF.
import math
from collections import Counter

# Documentos de ejemplo
doc1 = "Me gusta caminar por el bosque"
doc2 = "Caminar por el bosque es sanador"
doc3 = "Me gusta el bosque"

# Lista de documentos
docs = [doc1, doc2, doc3]

# Tokenización y conteo de palabras
tokenized_docs = [doc.lower().split() for doc in docs]
term_counts = [Counter(doc) for doc in tokenized_docs]

# Calcular TF (Frecuencia de Término)
def compute_tf(term_counts):
    tfs = []
    for doc_counts in term_counts:
        doc_len = sum(doc_counts.values())
        tf = {term: count / doc_len for term, count in doc_counts.items()}
        tfs.append(tf)
    return tfs

# Calcular IDF (Frecuencia de Documento Inversa)
def compute_idf(term_counts):
    N = len(term_counts)
    all_terms = set(term for doc_counts in term_counts for term in doc_counts)
    idf = {}
    for term in all_terms:
        doc_freq = sum(1 for doc_counts in term_counts if term in doc_counts)
        idf[term] = math.log(N / doc_freq)
    return idf

# Calcular TF-IDF
def compute_tfidf(tfs, idf):
    tfidf = []
    for tf in tfs:
        tfidf_doc = {term: tf_val * idf[term] for term, tf_val in tf.items()}
        tfidf.append(tfidf_doc)
    return tfidf

# Ejecutar las funciones
tfs = compute_tf(term_counts)
idf = compute_idf(term_counts)
tfidf = compute_tfidf(tfs, idf)

# Imprimir resultados
print("TF:")
for i, tf in enumerate(tfs):
    print(f"Documento {i+1}: {tf}")

print("\nIDF:")
print(idf)

print("\nTF-IDF:")
for i, tfidf_doc in enumerate(tfidf):
    print(f"Documento {i+1}: {tfidf_doc}")
TF:
Documento 1: {'me': 0.16666666666666666, 'gusta': 0.16666666666666666, 'caminar': 0.16666666666666666, 'por': 0.16666666666666666, 'el': 0.16666666666666666, 'bosque': 0.16666666666666666}
Documento 2: {'caminar': 0.16666666666666666, 'por': 0.16666666666666666, 'el': 0.16666666666666666, 'bosque': 0.16666666666666666, 'es': 0.16666666666666666, 'sanador': 0.16666666666666666}
Documento 3: {'me': 0.25, 'gusta': 0.25, 'el': 0.25, 'bosque': 0.25}

IDF:
{'por': 0.4054651081081644, 'gusta': 0.4054651081081644, 'caminar': 0.4054651081081644, 'el': 0.0, 'es': 1.0986122886681098, 'sanador': 1.0986122886681098, 'me': 0.4054651081081644, 'bosque': 0.0}

TF-IDF:
Documento 1: {'me': 0.06757751801802739, 'gusta': 0.06757751801802739, 'caminar': 0.06757751801802739, 'por': 0.06757751801802739, 'el': 0.0, 'bosque': 0.0}
Documento 2: {'caminar': 0.06757751801802739, 'por': 0.06757751801802739, 'el': 0.0, 'bosque': 0.0, 'es': 0.1831020481113516, 'sanador': 0.1831020481113516}
Documento 3: {'me': 0.1013662770270411, 'gusta': 0.1013662770270411, 'el': 0.0, 'bosque': 0.0}

16.1.2. One-hot-encoding#

¿Qué significa One-hot-encoding?

En la codificación one-hot<, cada palabra se representa como un vector binario donde solo un bit está configurado en 1 (encendido), y todos los demás están en 0 (apagado). La longitud del vector es igual al tamaño del vocabulario, y cada palabra tiene un índice único en este vector. Este método es simple e intuitivo, pero no captura ninguna relación semántica entre las palabras. Se utiliza comúnmente como una representación básica en modelos de aprendizaje automático.

16.1.2.1. Ejemplo para explicar One-hot-encoding#

Esta representación muestra cómo cada palabra se convierte en un vector binario único. Sin embargo, como se mencionó, esta técnica no captura ninguna relación semántica entre las palabras.
Supongamos que tenemos los siguientes dos documentos:
  • Documento 1: "Me gusta caminar por el bosque"
  • Documento 2: "Caminar por el bosque es sanador"
Paso 1: Creamos un vocabulario a partir de todas las palabras únicas en ambos documentos:
  • Vocabulario: ["Me", "gusta", "caminar", "por", "el", "bosque", "es", "sanador"]
Paso 2: representamos cada palabra como un vector binario en el que solo un bit está configurado en 1 y todos los demás en 0. Cada palabra tiene un índice único en este vector.

Vocabulario y sus vectores one-hot:

  • “Me”: [1, 0, 0, 0, 0, 0, 0, 0]

  • “gusta”: [0, 1, 0, 0, 0, 0, 0, 0]

  • “caminar”: [0, 0, 1, 0, 0, 0, 0, 0]

  • “por”: [0, 0, 0, 1, 0, 0, 0, 0]

  • “el”: [0, 0, 0, 0, 1, 0, 0, 0]

  • “bosque”: [0, 0, 0, 0, 0, 1, 0, 0]

  • “es”: [0, 0, 0, 0, 0, 0, 1, 0]

  • “sanador”: [0, 0, 0, 0, 0, 0, 0, 1]

Paso 2:: Para representar los documentos usando la codificación one-hot, cada palabra en el documento se sustituye por su vector one-hot correspondiente.

Representación one-hot Documento 1: "Me gusta caminar por el bosque"
  • “Me”: [1, 0, 0, 0, 0, 0, 0, 0]

  • “gusta”: [0, 1, 0, 0, 0, 0, 0, 0]

  • “caminar”: [0, 0, 1, 0, 0, 0, 0, 0]

  • “por”: [0, 0, 0, 1, 0, 0, 0, 0]

  • “el”: [0, 0, 0, 0, 1, 0, 0, 0]

  • “bosque”: [0, 0, 0, 0, 0, 1, 0, 0]

Representación one-hot Documento 2: "Caminar por el bosque es sanador"
  • “caminar”: [0, 0, 1, 0, 0, 0, 0, 0]

  • “por”: [0, 0, 0, 1, 0, 0, 0, 0]

  • “el”: [0, 0, 0, 0, 1, 0, 0, 0]

  • “bosque”: [0, 0, 0, 0, 0, 1, 0, 0]

  • “es”: [0, 0, 0, 0, 0, 0, 1, 0]

  • “sanador”: [0, 0, 0, 0, 0, 0, 0, 1]

16.1.2.2. Ejemplo de código para explicar One-hot-encoding#

El código siguiente proporciona una representación one-hot para los documentos dados, donde cada palabra se convierte en un vector binario con un solo bit configurado en 1 y todos los demás en 0, según su índice en el vocabulario.
  1. Documentos de ejemplo: Se definen dos documentos como cadenas de texto.

  2. Crear el vocabulario:
  • Se crea una lista de todas las palabras únicas en los documentos, convertidas a minúsculas y ordenadas alfabéticamente.
  • Se crea un diccionario que asigna un índice a cada palabra del vocabulario.
  1. Crear la representación one-hot para cada palabra: La función one_hot_vector toma una palabra, el tamaño del vocabulario y el diccionario word_to_index como entrada y devuelve un vector one-hot para esa palabra.

  2. Representar los documentos usando la codificación one-hot: La función document_to_one_hot toma un documento, el tamaño del vocabulario y el diccionario word_to_index como entrada y devuelve una lista de vectores one-hot para cada palabra en el documento.

  3. Imprimir los resultados: Se imprimen el vocabulario, los índices de las palabras y la representación one-hot para cada documento.

¿Qué es defaultdict?

defaultdict es una subclase de dict que proporciona valores por defecto para claves no existentes. Esto significa que, si intentas acceder a una clave que no existe en el diccionario, defaultdict creará automáticamente un valor por defecto utilizando una función que proporcionas cuando creas la instancia del defaultdict.
from collections import defaultdict

# Documentos de ejemplo
doc1 = "Me gusta caminar por el bosque"
doc2 = "Caminar por el bosque es sanador"

# Crear una lista de documentos
docs = [doc1, doc2]

# Paso 1: Crear el vocabulario
vocabulario = sorted(set(word.lower() for doc in docs for word in doc.split()))

# Crear un diccionario que asigne un índice a cada palabra del vocabulario
word_to_index = {word: idx for idx, word in enumerate(vocabulario)}

# Paso 2: Crear la representación one-hot para cada palabra
def one_hot_vector(word, vocab_size, word_to_index):
    vector = [0] * vocab_size
    index = word_to_index[word]
    vector[index] = 1
    return vector

# Representar los documentos usando la codificación one-hot
def document_to_one_hot(doc, vocab_size, word_to_index):
    words = doc.lower().split()
    one_hot_vectors = [one_hot_vector(word, vocab_size, word_to_index) for word in words]
    return one_hot_vectors

# Tamaño del vocabulario
vocab_size = len(vocabulario)

# Representación one-hot para cada documento
one_hot_doc1 = document_to_one_hot(doc1, vocab_size, word_to_index)
one_hot_doc2 = document_to_one_hot(doc2, vocab_size, word_to_index)

# Imprimir los resultados
print("Vocabulario:", vocabulario)
print("Índices de las palabras:", word_to_index)

print("\nRepresentación one-hot Documento 1:")
for word, vector in zip(doc1.split(), one_hot_doc1):
    print(f"{word}: {vector}")

print("\nRepresentación one-hot Documento 2:")
for word, vector in zip(doc2.split(), one_hot_doc2):
    print(f"{word}: {vector}")
Vocabulario: ['bosque', 'caminar', 'el', 'es', 'gusta', 'me', 'por', 'sanador']
Índices de las palabras: {'bosque': 0, 'caminar': 1, 'el': 2, 'es': 3, 'gusta': 4, 'me': 5, 'por': 6, 'sanador': 7}

Representación one-hot Documento 1:
Me: [0, 0, 0, 0, 0, 1, 0, 0]
gusta: [0, 0, 0, 0, 1, 0, 0, 0]
caminar: [0, 1, 0, 0, 0, 0, 0, 0]
por: [0, 0, 0, 0, 0, 0, 1, 0]
el: [0, 0, 1, 0, 0, 0, 0, 0]
bosque: [1, 0, 0, 0, 0, 0, 0, 0]

Representación one-hot Documento 2:
Caminar: [0, 1, 0, 0, 0, 0, 0, 0]
por: [0, 0, 0, 0, 0, 0, 1, 0]
el: [0, 0, 1, 0, 0, 0, 0, 0]
bosque: [1, 0, 0, 0, 0, 0, 0, 0]
es: [0, 0, 0, 1, 0, 0, 0, 0]
sanador: [0, 0, 0, 0, 0, 0, 0, 1]

16.1.3. Bag of Words#

¿Qué significa Bag of words (BoW)?

En la Bag Words o Bolsa de Palabras (BoW) cada documento se representa como un vector donde cada elemento corresponde al conteo de una palabra en el documento. El orden de las palabras se ignora, y solo sus frecuencias importan. Este método preserva más información que la codificación one-hot, pero aún carece de significado semántico y no considera la importancia relativa de las palabras.

16.1.3.1. Ejemplo para explicar Bag of Words#

En esta representación, se ignora el orden de las palabras y solo se consideran sus frecuencias. Aunque este método preserva más información que la codificación one-hot, no captura las relaciones semánticas entre las palabras ni la importancia relativa de cada una en el contexto.
Supongamos que tenemos los siguientes dos documentos:
  • Documento 1: "Me gusta caminar por el bosque"
  • Documento 2: "Caminar por el bosque es sanador"
Paso 1: creamos un vocabulario a partir de todas las palabras únicas en ambos documentos:
  • Vocabulario: ["Me", "gusta", "caminar", "por", "el", "bosque", "es", "sanador"]
Paso 2: representamos cada documento como un vector de conteos basado en este vocabulario.
Para el Documento 1:
  • “Me” aparece 1 vez

  • “gusta” aparece 1 vez

  • “caminar” aparece 1 vez

  • “por” aparece 1 vez

  • “el” aparece 1 vez

  • “bosque” aparece 1 vez

  • “es” aparece 0 veces

  • “sanador” aparece 0 veces

Vector para Documento 1: [1, 1, 1, 1, 1, 1, 0, 0]

Para el Documento 2:
  • “Me” aparece 0 vez

  • “gusta” aparece 0 vez

  • “caminar” aparece 1 vez

  • “por” aparece 1 vez

  • “el” aparece 1 vez

  • “bosque” aparece 1 vez

  • “es” aparece 1 vez

  • “sanador” aparece 1 vez

Vector para Documento 2: [0, 0, 1, 1, 1, 1, 1, 1]

Paso 3: la representación BoW para los documentos sería:
  • Documento 1: [1, 1, 1, 1, 1, 1, 0, 0]

  • Documento 2: [0, 0, 1, 1, 1, 1, 1, 1]

16.1.3.2. Ejemplo de código para explicar Bag of Words#

El código siguiente proporciona una representación de Bolsa de Palabras (BoW) para los documentos dados, donde cada documento se convierte en un vector que cuenta la frecuencia de aparición de cada palabra del vocabulario en el documento:
  1. Documentos de ejemplo: Se definen dos documentos como cadenas de texto.

  2. Crear el vocabulario: Se crea una lista de todas las palabras únicas en los documentos, convertidas a minúsculas y ordenadas alfabéticamente.

  3. Vectorizar cada documento:
  • La función `vectorize` toma un documento y el vocabulario como entrada y devuelve un vector de conteos para ese documento.
  • Para cada palabra en el vocabulario, cuenta cuántas veces aparece en el documento y construye un vector de estos conteos.
  1. Imprimir la representación BoW:
  • Se imprimen el vocabulario y los vectores de conteos para ambos documentos.
  • Se construye y se imprime la representación BoW para los documentos.
# Documentos de ejemplo
doc1 = "Me gusta caminar por el bosque"
doc2 = "Caminar por el bosque es sanador"

# Crear una lista de documentos
docs = [doc1, doc2]

# Paso 1: Crear el vocabulario
vocabulario = sorted(set(word.lower() for doc in docs for word in doc.split()))

# Paso 2: Representar cada documento como un vector de conteos
def vectorize(doc, vocabulario):
    vector = [0] * len(vocabulario)
    word_count = Counter(doc.lower().split())
    for i, word in enumerate(vocabulario):
        vector[i] = word_count[word]
    return vector

# Vectorizar documentos
vector_doc1 = vectorize(doc1, vocabulario)
vector_doc2 = vectorize(doc2, vocabulario)

# Paso 3: Imprimir la representación BoW para los documentos
print("Vocabulario:", vocabulario)
print("Vector para Documento 1:", vector_doc1)
print("Vector para Documento 2:", vector_doc2)

# Representación BoW
bow_representation = [vector_doc1, vector_doc2]
print("\nRepresentación BoW:")
for i, vector in enumerate(bow_representation):
    print(f"Documento {i+1}: {vector}")
Vocabulario: ['bosque', 'caminar', 'el', 'es', 'gusta', 'me', 'por', 'sanador']
Vector para Documento 1: [1, 1, 1, 0, 1, 1, 1, 0]
Vector para Documento 2: [1, 1, 1, 1, 0, 0, 1, 1]

Representación BoW:
Documento 1: [1, 1, 1, 0, 1, 1, 1, 0]
Documento 2: [1, 1, 1, 1, 0, 0, 1, 1]

Para tener en cuenta

Una bolsa de palabras trata a todas las palabras por igual y sólo se preocupa por la frecuencia de palabras únicas en las frases. El TF-IDF da importancia a las palabras de un documento teniendo en cuenta tanto la frecuencia como la unicidad.