Conceptos Fundamentales de Python para Ciencia de Datos y PLN

Trabajando con Librerías en Python

1. Importación de Librerías

  • import math: importa todo el módulo → math.pi
  • import math as mt: usa un alias → mt.pi
  • from math import pi: importa solo un elemento → pi
  • from math import *: importa todos los elementos sin prefijo (⚠️ no recomendable)

2. Instalación de Librerías en Colab

  • Se utiliza !pip install nombre_librería
    • Ejemplo: !pip install ipdb

3. Lectura y Escritura de Archivos

  • Abrir archivo: open('archivo.txt', 'modo')
  • Modos:
    • ‘r’: lectura
    • ‘w’: escritura
    • a’: añadir
    • ‘r+’: lectura y escritura
  • Escribir: f.write("texto")
  • Leer todo: contenido = f.read()
  • Leer línea a línea: for line in f:
  • No olvides f.close()

4. Uso de Archivos con Google Drive

  1. Montar Drive:
    from google.colab import drive
    drive.mount('/content/drive')
  2. Definir ruta:
    MYDRIVE = "/content/drive/MyDrive/..."
  3. Leer archivo desde Drive:
    f = open(MYDRIVE + 'archivo.csv', 'r')
  4. Ver contenido del directorio
!ls /content/drive/MyDrive/pln/pln/ -1

5. Serialización con Pickle

Pickle es un módulo que permite convertir objetos de Python a un formato binario para guardarlos y recuperarlos más tarde (serialización y deserialización).

  • Serialización de una lista:
mylistserialized = pickle.dumps(mylist)
  • Recuperar una lista
mylistrecovered = pickle.loads(mylistserialized)
  • Guardar objetos:
    import pickle
    with open('archivo.pkl', 'wb') as f:
        pickle.dump(objeto, f)
pickled_file = open(filename, 'wb')
pickle.dump(mylist, pickled_file)
  • Leer objetos:
    with open('archivo.pkl', 'rb') as f:
        objeto = pickle.load(f)
pickled_file = open(filename, 'rb')
mylistrecovered = pickle.load(pickled_file)
  • Puedes guardar listas, diccionarios, múltiples variables, etc.
import pickle

1. Lista

frutas = [«manzana», «pera», «naranja»]

2. Diccionario

datos_personales = {     «nombre»: «Laura»,     «edad»: 25,     «email»: «laura@example.com» }

3. Múltiples variables combinadas

paquete = {     «frutas»: frutas,     «datos»: datos_personales,     «año»: 2025 }

Guardar en un archivo pickle

with open(«archivo.pkl», «wb») as f:     pickle.dump(paquete, f)

with open(«archivo.pkl», «rb») as f:     datos_recuperados = pickle.load(f)

leer el archivo y recuperar los datos

print(datos_recuperados[«frutas»])          # [‘manzana’, ‘pera’, ‘naranja’] print(datos_recuperados[«datos»][«nombre»])   # Laura print(datos_recuperados[«año»])             # 2025

6. Archivos MATLAB (.mat)

  • Librería: from scipy.io import savemat, loadmat
  • Guardar:
    savemat("archivo.mat", {"clave": datos})
  • Leer:
    python
    CopiarEditar
    datos = loadmat("archivo.mat")

7. Creación de Librerías Propias

  • Crea un .py con funciones.
  • Ejemplo:
    def mi_funcion(): return "Hola"
  • Importar en Colab:
    from google.colab import drive
    from importlib.machinery import SourceFileLoader
    mi_lib = SourceFileLoader('nombre', ruta).load_module()

8. Creación de Paquetes (Packages)

  • Estructura de carpetas con __init__.py para que Python lo reconozca como paquete.
  • Ejemplo:
    mipackage/
      __init__.py
      modulo.py
  • Importar:
    from mipackage import modulo

9. Programación Orientada a Objetos (POO)

Clases y Objetos

class Persona:
    def __init__(self, nombre):
        self.nombre = nombre

Métodos:

  • __init__: constructor
  • Métodos propios: def mostrar(self): ...

Atributos:

  • Públicos: self.nombre
  • Puedes calcular edad con datetime

Ejemplo de clase HUMAN:

Atributos: nombre, apellido, fecha_nacimiento, estado_civil, hijos

Métodos: get_age(), add_child(), change_civil_status(), etc.

10. Herencia

Permite extender clases:

class Estudiante(HUMAN):
    def __init__(...):
        super().__init__(...)

Se pueden definir nuevas clases como Teacher, Student, Course basadas en HUMAN o PROFESSIONAL.

NumPy y Matplotlib: Resumen

NumPy

import numpy as np

Introducción

  • NumPy es el paquete clave para computación científica en Python.
  • Usa arrays n-dimensionales (ndarray) para representar datos de forma eficiente.
  • Permite broadcasting, álgebra lineal, generación de datos aleatorios y operaciones vectorizadas.

Creación de Arrays

  • np.array([1, 2, 3]) crea un array 1D.
  • np.ones
  • .shape devuelve su forma (dimensiones).
  • .dtype indica el tipo de dato.
  • Métodos Útiles:
    • np.arange(inicio, fin, paso)
    • np.linspace(inicio, fin, num)
    • .tolist(): convierte array a lista
    • .ravel(): aplanar array
    • .reshape(): cambiar dimensiones
    • .flatten()

flatten() always returns a copy of the original vector, whereas ravel() and shape() returns a view of the original array whenever possible.

Dimensiones y Manipulación

  • np.newaxis: añade dimensión (útil para transformar vectores columna/fila).
  • np.squeeze(): elimina dimensiones de tamaño 1.
  • Arrays pueden tener más de 2 dimensiones (tensores).
  • np.random.randn(m, n): matriz de números aleatorios con distribución normal estándar.

Indexación y Slicing

  • Igual que listas: a[0], a[1:3], a[:,1], etc.
  • .where(condición): devuelve índices donde se cumple la condición.
  • x[y > 5]: acceso condicional.

Concatenación de Arrays

  • np.hstack(): concatena horizontalmente.
  • np.vstack(): concatena verticalmente.
  • .T: transpuesta del array.

Operaciones con Arrays

  • Element-wise: +, -, *, /, **
  • Operaciones columna/fila automáticamente por broadcasting.
  • Producto Escalar: np.dot(a, b) o a @ b

Multiplicación de Matrices y Transpuestas

  • np.dot(): producto de matrices.
  • np.matmul(): igual que dot para 2D.
  • .T, np.transpose() o np.ndarray.T: transposición.

Inversas y Pseudo-inversas

  • Inversa: np.linalg.inv(A)
  • Pseudo-inversa (Moore–Penrose): np.linalg.pinv(A)
  • Verificación: A @ A⁺ ≈ I
    print(M2@M2I)

Generación Útil de Arrays

  • np.linspace(a, b, n): n puntos entre a y b.
  • np.logspace(0,10,10) # 10 elements equally spaced in the 10^0,10^10 range
  • np.arange(start, stop, step)

Guardar Datos con Pickle

  • Serialización de Objetos:
    import pickle
    with open('archivo.pkl', 'wb') as f:
        pickle.dump(datos, f)
  • Carga de Objetos:
    with open('archivo.pkl', 'rb') as f:
        datos = pickle.load(f)

Puntos Clave para Repasar

  • ¿Qué devuelven .shape, .dtype, .T, .squeeze(), .reshape()?
  • ¿Cómo se concatena un array? ¿Qué diferencia hay entre ravel() y reshape(-1)?
  • ¿Qué función se usa para: crear distribuciones, guardar arrays, visualizar datos?
  • ¿Qué hace np.dot()? ¿Y np.matmul()?
  • ¿Qué devuelve np.where(x > 5)?

Introducción a Pandas: Resumen

¿Qué es Pandas?

Pandas es una librería de Python para el análisis de datos. Permite trabajar con:

  • Series: estructuras unidimensionales (como arrays con índice).
  • DataFrames: tablas de datos con filas y columnas etiquetadas.

1. Carga Inicial de Librerías y Archivos

import pandas as pd
from google.colab import drive
drive.mount("/content/drive")
  • Se usa pd.read_csv("ruta") para leer ficheros .csv.

2. Exploración Básica de Datos

print(data.shape)       # filas y columnas
print(data.columns)     # nombres de columnas
print(data.head())      # primeras filas
simple_data.tail(10) # `.tail()` shows the last 10 entries

3. Operaciones con Columnas

  • Renombrar Columnas:
    data.columns = ["new1", "new2", ...]
  • Crear Columna Nueva:
    data["new_col"] = data["col1"] + data["col2"]

4. Operaciones con .apply()

Permite aplicar funciones a columnas:

data["edad"] = data["birth_year"].apply(lambda x: funcion())

⚠️ **Nota:** Cuidado con el tipo de dato (string vs int), puede provocar errores.

5. Selección de Datos (iloc y loc)

iloc → por posición (índice numérico):

data.iloc[0]         # primera fila
data.iloc[:, 1]      # segunda columna (todas las filas)

loc → por nombre:

data.loc[0, "columna"]

6. Filtrado Condicional

data[data["valor"] > 300]         # casas con valor > 300
data[(cond1) & (cond2)]          # múltiples condiciones

7. Manejo de Valores Nulos

data.isnull()         # detecta nulos
data.dropna()         # elimina filas con nulos
data.fillna(valor)    # reemplaza nulos

8. Guardar Datos

data.to_csv(ruta_o_archivo)

Procesamiento de Lenguaje Natural (PLN)

1. Introducción al PLN

  • En PLN (Procesamiento de Lenguaje Natural) se trabajan textos en forma de datos para analizarlos con algoritmos de aprendizaje automático.
  • Se usan técnicas para limpiar y transformar el texto en vectores comprensibles por los algoritmos.
  • Ejemplos de aplicación: motores de búsqueda, asistentes conversacionales, análisis de sentimientos, detección de spam, etc.
  • El objetivo es convertir texto no estructurado en datos útiles para modelos de aprendizaje automático.

2. Pipeline del Procesamiento de Texto

El texto pasa por una serie de etapas:

  1. Tokenización (separa en palabras o frases)
  2. Homogeneización (normaliza formato)
  3. Limpieza (elimina símbolos innecesarios)
  4. Vectorización (transforma a números)
  5. Modelo de aprendizaje (usa los datos para entrenar un algoritmo)

3. Acceso a Corpus con NLTK

3.1 El Objeto CorpusReader

  • Se usa para explorar los corpus de texto que ofrece nltk.
  • Ejemplo: movie_reviews contiene reseñas etiquetadas como positivas o negativas.
  • Métodos útiles:
    • movie_reviews.fileids() → lista de documentos.
    • .categories() → categorías disponibles.
    • .raw(fileid) → texto sin procesar.
    • .words(fileid) → texto tokenizado en palabras.
    • .split(',') hacer frases usando punto como separador (Nota: esto no es un método estándar de CorpusReader).
from nltk.corpus import movie_reviews
nltk.download('movie_reviews')
movie_reviews

4. Preprocesamiento del Corpus

4.1 Tokenización

  • Separar el texto en unidades (tokens): palabras, signos de puntuación, etc.
  • Es útil para analizar el contenido de un texto palabra por palabra.
  • **Token**: unidad mínima del texto (palabras, signos, números).
  • Ejemplo en NLTK: nltk.word_tokenize().
nltk.download('punkt_tab').
  • Se analiza cómo se separan frases y palabras.
# Ejemplo de tokenización usando NLTK:
nltk.download('punkt_tab')
from nltk.tokenize import sent_tokenize

los signos de puntuación se convierten también en «tokens»

Procesamos el texto número 8

raw_text = movie_reviews.raw(doc_ids[7])

Dividimos el texto en frases

frases = sent_tokenize(raw_text)

Tokenizamos todas las frases

frases_tokenizadas = [word_tokenize(frase) for frase in frases]

Mostramos los tokens de las frases indicadas

print(‘Estos son los tokens de las frases número 3, 4 y 7:’) print(frases_tokenizadas[2]) print(frases_tokenizadas[3]) print(frases_tokenizadas[6])

Se retienen sólo los tokens de más de un caracter de largo:

frases_tokenizadas_filtradas = [[token for token in frase if len(token)>1] for frase in frases_tokenizadas]

Mostramos los tokens de las frases indicadas, tras el filtrado

print(‘\nEstos son los tokens de las frases número 3, 4 y 7 tras el filtrado:’) print(frases_tokenizadas_filtradas[2]) print(frases_tokenizadas_filtradas[3]) print(frases_tokenizadas_filtradas[6])

4.2 Homogeneización

  • Normaliza el texto para reducir variaciones irrelevantes.
  • Pasos comunes:
    • Convertir a minúsculas
    • Eliminar signos de puntuación
    • Eliminar palabras vacías (stop words)
    • **Stemming** y **Lematización**

**Stemming**: recorta palabras a su raíz (más agresivo).

**Lematización**: convierte a la forma base del diccionario (más preciso).

4.3 Preprocesamiento de Texto con spaCy

  • **spaCy** es una biblioteca potente para procesamiento en tiempo real.
  • Elementos clave:
    • Doc, Token, Span → objetos básicos de análisis.
    • Se puede analizar POS tags, detectar stop words, puntuación, etc.
  • Instalación: !pip install spacy y descarga del modelo.
# Descargamos el modelo para inglés
!python -m spacy download en_core_web_sm

Cargamos el modelo

nlp = spacy.load(«en_core_web_sm»)

El **acceso a las palabras del vocabulario** se hace a través del atributo .vocab.strings

lista_vocab = list(nlp.vocab.strings)
print(type(nlp.vocab.strings))
print(type(lista_vocab))
print("\nEl tamaño del diccionario es de {} 'palabras' de todo tipo, algunos ejemplos:".format(len(lista_vocab)))

Las 20 primeras palabras y las 20 en posición 1000 (muchas son alfanuméricas)

print(lista_vocab[:20]) print(lista_vocab[1000:1020]) print(lista_vocab[10000:10020])

Suprimimos las palabras no alfabéticas

alpha_words = [word for word in nlp.vocab.strings if word.isalpha()] no_alpha_words = [word for word in nlp.vocab.strings if not word.isalpha()]

**NOTA:** el formato de entrada a spaCy siempre es un texto plano.

sentence = 'Japan’s economy, struggling to emerge from the pandemic, is sagging under rising food and energy costs and a weak yen'
print("Esta es la frase de partida, tipo `string`:\n")
print(sentence)
print(type(sentence))

doc_tokenized = nlp(sentence) print(«\nAunque al hacer un print se nos muestra el texto, vemos que no es un string habitual…\n») print(doc_tokenized) print(type(doc_tokenized))

doc_tokenized es un objeto indexado e iterable, compuesto por objetos tipo token.

print(doc_tokenized[0])
print(type(doc_tokenized[0]))

Los atributos del tipo is_ nos serán especialmente útiles, por ejemplo is_digit para reconocer números, is_alpha para reconocer tokens alfanuméricos, is_stop para detectar las stop words, etc. En el siguiente bucle imprimimos algunas de las propiedades de dichos tokens determinadas por el pipeline que hemos cargado, incluyendo el **POS tag** («Part Of Speech»):

Con spacy.explain() podemos obtener una descripción de los distintos tags …

En lo referente a **stop words**, spaCy incluye una amplia lista (326 elementos en idioma inglés) que podemos personalizar para nuestra propia aplicación si es necesario.

spacy_stopwords = nlp.Defaults.stop_words

Printing the total number of stop words:

print(‘Number of stop words: %d’ % len(spacy_stopwords))

Printing first ten stop words:

print(‘First ten stop words: %s’ % list(spacy_stopwords)[:20])

Añadir una palabra al conjunto de stop words

nlp.Defaults.stop_words.add(«my_new_stopword») print(‘Number of stop words: %d’ % len(nlp.Defaults.stop_words))

Quitar una palabra del conjunto de stop words

nlp.Defaults.stop_words.remove(«my_new_stopword») print(‘Number of stop words: %d’ % len(nlp.Defaults.stop_words))

4.4 spaCy para Idioma Español

  • spaCy ofrece modelos entrenados con textos en español.
  • Instalación: es_core_news_sm.
!python -m spacy download es_core_news_sm
nlp = spacy.load("es_core_news_sm")
lista_vocab = list(nlp.vocab.strings)
print("El tamaño del diccionario es de {} palabras. Las primeras 10 tras la posición 20000 son \n".format(len(lista_vocab)))
print(lista_vocab[20000:20010])

Printing the total number of stop words:

print(‘\nHay un total de {0} stop words en el modelo’.format(len(spacy.lang.es.stop_words.STOP_WORDS)))

  • Permite analizar texto, obtener lemas, y clasificar palabras por tipo (sustantivo, verbo, etc.).

4.5 Uso de spaCy para Normalización

  • Se implementa una función para eliminar puntuación, números y stop words.
  • Se lematiza todo el texto, quedando solo palabras útiles.
print('Volvemos a cargar el modelo para inglés a fin de procesar el corpus de películas:')
nlp = spacy.load("en_core_web_sm")

def normalize_Spacy(text):     text2 = nlp(text)     # Ejercicio: interpretar el sentido de esta list comprehension:     normalizedtext = [w.lemma.lower() for w in text2 if not w.is_stop     and not w.is_punct and (w.is_alpha or w.is_digit)]     return normalized_text

corpus_prec = [] for fileid in movie_reviews.fileids():     text = movie_reviews.raw(fileid)     text_preproc = normalize_Spacy(text)     corpus_prec.append(text_preproc)

4.6 Conteo de Palabras Frecuentes

  • Se usa collections.Counter para encontrar las palabras más comunes.
from collections import Counter
all_normalized_words = [w for d in corpus_prec for w in d]
words_freq = Counter(all_normalized_words)
  • Ejercicio Final: Contar los **20 Adjetivos** más frecuentes.
    • Se filtra por POS tags (ADJ).
    • Se compara con el vocabulario del modelo.
# We make a list with all the words in the movies corpus, in lower case
all_words = [w.lower() for d in corpus_prec for w in d]
print("\nHay un total de {} palabras en el corpus.".format(len(all_words)))

We select the alpha words in the vocabulary, in lower case and eliminating possible duplicates

vocab_list_alpha_words = list(set([word.lower() for word in nlp.vocab.strings if word.isalpha()])) print(«\nHay un total de {} palabras alfanuméricas en el vocabulario.».format(len(vocab_list_alpha_words))) print(vocab_list_alpha_words[0:100])

We select the words in the vocabulary that are adjectives

adjectives_list = [word for word in vocab_list_alphawords if nlp(word)[0].tag == «JJ»] print(«\nHay un total de {} adjetivos en el vocabulario.».format(len(adjectives_list))) print(adjectives_list[:100])

We filter the words in the corpus, retaining only the adjectives:

adjectives_in_corpus = [word for word in all_words if word in adjectives_list] print(«\nHay un total de {} adjetivos en el corpus, algunos de ellos son:».format(len(adjectives_in_corpus))) print(adjectives_in_corpus[:100])

Now we count the adjectives in the corpus and print the most common ones

print(«\nEsta es la lista de los 20 adjetivos más comunes, así como su conteo en el corpus:») adjectives_frequency = Counter(adjectives_in_corpus) most_common_adjectives = adjectives_frequency.most_common(20) for word, freq in most_common_adjectives[:20]:     print(word, freq)

Representación Vectorial de Corpus

1. Preparación del Corpus

Antes de representar documentos como vectores, es necesario limpiar y normalizar los textos. El corpus final es una lista de listas, donde cada sublista contiene los tokens (palabras lematizadas, sin stopwords ni signos) de un documento.

import nltk
from nltk.corpus import inaugural
nltk.download('inaugural')

first_id = list(inaugural.fileids())[0] print(«Este es el identificador del primer documento: {}».format(first_id)) print(«\nEste es el texto de dicho documento:\n») text = inaugural.raw(first_id)

Descargamos el modelo spaCy y procesamos este texto

!python -m spacy download en_core_web_sm import spacy

Cargamos el modelo

nlp = spacy.load(«en_core_web_sm»)

Procesamos el texto

text_spacy = nlp(text) print(type(text_spacy))

2. Bag of Words (BoW)

2.1. Generación del Diccionario con Gensim

  • Se utiliza **gensim** para crear un diccionario del corpus preprocesado:
    from gensim import corpora
    diccionario = corpora.Dictionary(corpus_proc)
!pip install gensim
import gensim
n_docs = len(corpus_prec)

Create dictionary of tokens: the input is the preprocessed corpus

D = gensim.corpora.Dictionary(corpus_prec) # Nos devuelve cada elemento como string n_tokens = len(D)

Información del Diccionario:

  • token2id: mapea tokens a IDs.
  • cfs: frecuencia total del token en el corpus.
  • dfs: número de documentos en que aparece el token.
diccionario.token2id["president"]
diccionario.cfs[id]
diccionario.dfs[id]

2.2. Gestión del Vocabulario

  • Se pueden aplicar filtros al diccionario:
    • **no_below**: se queda con los **tokens que están contenidos en al menos** no_below **documentos**.
    • **no_above** (fracción del tamaño total del corpus, no un número absoluto): retiene los tokens que no están en más de una fracción no_above de los documentos.
    • **keep_n**: se queda exactamente con los keep_n tokens más relevantes, **eliminando los más y menos frecuentes**.
    • **keep_tokens**: listado de **tokens que deben permanecer en el diccionario** después de ser filtrados.
    • Ejemplo: diccionario.filter_extremes(no_below=10, no_above=0.7)
  • Esto reduce ruido eliminando palabras poco frecuentes o demasiado comunes.

2.3. Visualización del Diccionario

  • Se pueden analizar manualmente cuántos documentos contienen ciertas palabras: diccionario.dfs[diccionario.token2id["president"]]
  • También se puede representar gráficamente cuántos documentos contiene cada palabra (doc_freq).

Para la visualización necesitaremos los métodos de la clase Dictionary de gensim: .dfs() (calcula en cuántos documentos del corpus aparece cada palabra del vocabulario) y .cfs() (calcula cuántas veces aparece cada palabra del vocabulario en todo el corpus).

3. Vectorización de Documentos

  • Cada documento se convierte en una **bolsa de palabras (BoW)**: una lista de tuplas (id_palabra, frecuencia).
    corpus_bow = [diccionario.doc2bow(doc) for doc in corpus_proc]
doc0 = corpus_prec[0]
bow0 = D.doc2bow(doc0)
  • Ejemplo de documento vectorizado: [(0, 2), (1, 1), (2, 1)] # palabra 0 aparece 2 veces, la 1 una vez, etc.

4. Representación TF-IDF

**Frecuencia de Término (TF)**: número de veces que una palabra w dada ocurre en un documento, dividido por el número total de palabras en dicho documento.

**Frecuencia de Documento Inversa (IDF)**: medida de cuánta información proporciona la palabra w, es decir, si es común o rara en todos los documentos del corpus D.

  • TF-IDF da más peso a palabras importantes:

A diferencia de la vectorización BoW, para el **TF-IDF tenemos que obtener la vectorización conjuntamente con todo el corpus**. No obstante, una vez que hemos calculado el BoW para todos los documentos, aprender el modelo TF-IDF es sencillo usando la función TfidfModel de **Gensim**.

  • En gensim se implementa así:
    from gensim.models import TfidfModel
    modelo_tfidf = TfidfModel(corpus_bow)
    corpus_tfidf = modelo_tfidf[corpus_bow]

5. Limitaciones de BoW y TF-IDF

Las **representaciones vectoriales** que acabamos de ver son sencillas de comprender y aplicar y ofrecen mucha **flexibilidad** para manejo de información textual. De hecho, se han utilizado con gran éxito en problemas de **predicción y clasificación de documentos**.

Sin embargo, sufren de algunas **deficiencias** que tenemos que tener en cuenta:

  • **Vocabulario**: **requiere un diseño cuidadoso**, más específicamente para gestionar su tamaño, lo que afecta a la dispersión de las representaciones de los documentos.
  • **Dispersión**: Las **representaciones dispersas son más difíciles de modelar** tanto por razones computacionales como por razones de información, en las que el reto es que los modelos aprovechen la poca información común en un espacio de representación tan grande.
  • **Significado**: Al **descartar el orden de las palabras se ignora el contexto** y, a su vez, el significado de las palabras del documento (**semántica**). El contexto y el significado pueden ofrecer mucho al modelo, que si se modela podría diferenciar entre las mismas palabras dispuestas de manera diferente («esto es interesante» vs «es esto interesante?»), sinónimos («bicicleta vieja» vs «bicicleta usada»), y mucho más.

6. Uso de Representaciones Vectoriales en Aprendizaje Automático

6.1. Codificación Dispersa

Vamos a usar aquí las librerías de **Sklearn**, tras convertir nuestra representación vectorial dispersa en **numpy arrays** «densos». Para ello, **Gensim** nos incluye dos funciones: corpus2dense, corpus2csc

**Importante**: note que hace falta conocer el número de documentos y el número de elementos en el diccionario.

Una vez convertidos a **Numpy array denso** o **matriz dispersa CSC de Scipy**, ambas parametrizaciones tienen el mismo formato y se pueden tratar de la misma forma. Incluso las librerías que pueden trabajar con datos en formato disperso, suelen requerir formato CSC de Scipy, no trabajan directamente con el formato de lista de tuplas que hemos visto.

En el caso del TF-IDF denso, aparecen rellenas con ceros las posiciones vacías.

El formato disperso CSC de Scipy tiene estructura propia, según sea una fila del BoW o una parte del mismo:

  • Tanto BoW como TF-IDF generan vectores con muchos ceros. Se usan matrices dispersas para optimizar memoria:
    from scipy.sparse import csr_matrix
    X_bow = csr_matrix(corpus_bow)

6.2. Cálculo de Distancias

Una vez que tenemos la representación vectorial de nuestros documentos (cada una de las filas de la matriz BoW o TF-IDF), muchos de nuestros modelos de ML necesitarán **calcular similitudes entre ellos**, como puede ser un algoritmo **K-NN para regresión** o un **K-means para agrupamiento**. Para ello, cuando trabajamos con características BoW o TF-IDF, donde la magnitud de los vectores no es tan decisiva, suele utilizarse como métrica la **similitud del coseno**.

Podríamos suponer que cuando una palabra (por ejemplo, ciencia) aparece con más frecuencia en el documento 1 que en el 2, ese documento 1 está más relacionado con el tema de la ciencia. Sin embargo, también podría darse el caso de que estemos trabajando con documentos de longitudes desiguales (artículos de Wikipedia, por ejemplo). En ese caso, es probable que la ciencia aparezca más en el documento 1 sólo porque es mucho más largo que el documento 2. La **similitud del coseno corrige estos sesgos**.

Por esta razón, cuando se trabaja con documentos codificados con BoW o TF-IDF, se tiende a utilizar la similitud del coseno (**coseno del ángulo que forman ambos vectores**). Si v₁ y v₂ son dos vectores TF-IDF, la similitud del coseno se calcula como sigue:

Permite comparar documentos con independencia de su longitud.

Otro aspecto que tenemos que tener en cuenta es que **la distancia coseno no es formalmente una «distancia» como tal** (no cumple la desigualdad triangular) e **implementaciones por defecto** de nuestros modelos de aprendizaje como el K-NN o K-means de sklearn usan la **distancia euclídea por defecto** y no permiten incluir la distancia coseno.

Para solventar este problema tenemos dos opciones:

  • **Usar otras implementaciones** como K-means de NLTK que nos permite usar la distancia coseno. Aunque el problema de esta implementación es que **no nos deja trabajar con matrices sparse**.
  • **Reescalar nuestros datos para poder utilizar implementaciones basadas en la distancia euclídea**… (luego explicaremos en qué consiste esta aproximación).

Sin embargo, cuando nuestros **vectores** x **e** y **están normalizados** (||x||₂=*x*ᴾ*x*=1), sus distancias euclídeas ||xy||₂ y coseno d(*x*,*y*) están relacionadas por la siguiente igualdad:

||x−y||₂ = xᴾ x + yᴾ y − 2xᴾ y = 2(1 − xᴾ y) = 2d(x,y)

En este caso, usar la distancia euclídea nos va a dar los mismos resultados que la distancia coseno.

Nótese que esta normalización es parecida al cálculo TF que compensa en la representación BoW la longitud de los documentos.

Así que comencemos normalizando nuestros datos para que cada vector tenga norma unidad.

7. Clustering con KMeans

7.1 y 7.2. Clustering con BoW y TF-IDF

  • Se aplica KMeans para agrupar documentos similares:
    from sklearn.cluster import KMeans
    modelo_kmeans = KMeans(n_clusters=3)
    modelo_kmeans.fit(X_tfidf) # o X_bow
  • Se analiza qué documentos hay en cada grupo y se inspeccionan los **centroides** para interpretar cada clúster.

7.3. Entrenamiento de KMeans

  • Se entrena con fit() y se obtiene el grupo de cada documento con .predict() o .labels_.

No podemos usar las librerías de K-means o K-NN de sklearn con representaciones BoW o TF-IDF porque **solo permiten usar la distancia euclídea**.

Sin embargo, cuando nuestros **vectores** x **e** y **están normalizados** (||x||₂=*x*ᴾ*x*=1), sus distancias euclídeas ||xy||₂ y coseno d(*x*,*y*) están relacionadas por la siguiente igualdad:

7.4 y 7.5. Resultados del Clustering

  • Se representan los resultados, mostrando qué documentos hay en cada grupo y qué palabras son más representativas de cada clúster.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.