ScikitLearn
Tutorial ScikitLearn: Representación de texto y extracción de características
Scikit Learn: Representación de texto y extracción de características. Descubre cómo usar bolsa de palabras, n-gramas y TF-IDF en procesamiento del lenguaje natural para mejorar tus modelos de NLP.
Aprende ScikitLearn GRATIS y certifícateModelo de bolsa de palabras (Bag-of-Words)
El Modelo de Bolsa de Palabras (Bag-of-Words) es una representación simplificada del texto utilizada en el procesamiento del lenguaje natural (NLP) y en la recuperación de información. En este modelo, se crea un vocabulario a partir de un conjunto de documentos y se representa cada documento como una frecuencia de las palabras que contiene, ignorando el orden y la gramática.
Para aplicar el modelo de bolsa de palabras en Scikit Learn, se utiliza la clase CountVectorizer
, que convierte una colección de documentos de texto en una matriz de conteos de tokens. A continuación, se presenta un ejemplo práctico:
from sklearn.feature_extraction.text import CountVectorizer
# Lista de documentos de ejemplo
documentos = [
"El gato se sentó en la estera",
"El perro se tumbó en el sofá",
"El gato y el perro son amigos"
]
# Crear una instancia de CountVectorizer
vectorizador = CountVectorizer()
# Ajustar y transformar los documentos en una matriz de conteos
matriz_conteos = vectorizador.fit_transform(documentos)
# Obtener el vocabulario extraído
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario:", vocabulario)
# Convertir la matriz de conteos a un array denso
print("Matriz de conteos:\n", matriz_conteos.toarray())
Salida:
Este código genera un vocabulario único de todas las palabras presentes en los documentos y una matriz donde cada fila representa un documento y cada columna una palabra del vocabulario. Los valores en la matriz indican cuántas veces aparece cada palabra en cada documento.
El uso del modelo de bolsa de palabras es fundamental para transformar datos textuales en una representación numérica que los algoritmos de aprendizaje automático puedan procesar. Sin embargo, es importante tener en cuenta que este modelo no captura la semántica ni el orden de las palabras, lo que puede ser una limitación en ciertas aplicaciones.
Es posible personalizar el CountVectorizer
para mejorar la calidad de la representación. Por ejemplo, se puede eliminar las palabras vacías (stop words) que no aportan significado significativo:
from nltk.corpus import stopwords
# Obtener las stop words en español
stop_words_espanol = stopwords.words('spanish')
# Crear el vectorizador con las stop words en español
vectorizador = CountVectorizer(stop_words=stop_words_espanol)
matriz_conteos = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario sin stop words:", vocabulario)
print("Matriz de conteos:\n", matriz_conteos.toarray())
Salida:
Además, se pueden ajustar parámetros como max_df
y min_df
para ignorar palabras que aparecen en un porcentaje demasiado alto o bajo de documentos, lo cual ayuda a reducir el ruido en los datos:
# Ignorar palabras muy frecuentes o muy infrecuentes
# Ajustar max_df y min_df con valores enteros
vectorizador = CountVectorizer(stop_words=stop_words_espanol, max_df=2, min_df=1)
matriz_conteos = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario refinado con valores enteros:", vocabulario)
Es habitual integrar el modelo de bolsa de palabras en un pipeline de procesamiento para combinarlo con etapas adicionales, como preprocesamiento o clasificación:
from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import MultinomialNB
# Crear un pipeline con CountVectorizer y un clasificador
pipeline = Pipeline([
('vectorizador', CountVectorizer()),
('clasificador', MultinomialNB())
], memory = None)
# Datos de entrenamiento y etiquetas
X_train = [
"El gato come pescado",
"El perro ladra fuerte",
"El canario canta bonito"
]
y_train = ['felino', 'canino', 'ave']
# Entrenar el modelo
pipeline.fit(X_train, y_train)
# Realizar predicciones sobre nuevos documentos
X_test = ["El gato maúlla", "El perro corre", "El ave vuela"]
predicciones = pipeline.predict(X_test)
print("Predicciones:", predicciones)
En este ejemplo, el clasificador Naive Bayes se entrena con los vectores generados por el modelo de bolsa de palabras para predecir la categoría de nuevos textos.
Una consideración importante es el manejo de la dimensionalidad. El vocabulario puede crecer rápidamente con datasets grandes, lo que genera matrices muy esparsas. Para mitigar esto, se puede limitar el número de características usando el parámetro max_features
:
# Limitar el vocabulario a las 10 palabras más frecuentes
vectorizador = CountVectorizer(max_features=10)
matriz_conteos = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario limitado:", vocabulario)
El modelo de bolsa de palabras es la base para muchas técnicas en NLP y sirve como punto de partida para métodos más avanzados. Aunque sencillo, es una herramienta poderosa para convertir texto en datos numéricos y permite aplicar algoritmos de clasificación, clustering y otras técnicas de aprendizaje automático.
Es recomendable combinar el modelo de bolsa de palabras con técnicas adicionales de preprocesamiento, como la lemmatización o el stemming, y considerar el uso de n-gramas para capturar más contexto en los datos textuales. Estas mejoras pueden conducir a modelos más precisos y representativos del lenguaje natural.
N-gramas y su relevancia en NLP
En el procesamiento del lenguaje natural (NLP), los n-gramas son secuencias contiguas de n elementos de un texto o discurso. Estos elementos pueden ser fonemas, sílabas, letras, palabras o incluso caracteres. Los n-gramas son esenciales para capturar contextos y patrones lingüísticos que las palabras individuales no pueden representar por sí solas.
Al utilizar n-gramas de palabras, se pueden modelar dependencias locales y frases comunes en el lenguaje. Por ejemplo, los bigramas (n = 2) y los trigramas (n = 3) permiten conservar información sobre parejas o tríos de palabras consecutivas, lo que es especialmente útil en tareas como análisis de sentimiento, traducción automática y modelado del lenguaje.
En Scikit-Learn, la clase CountVectorizer
admite la generación de n-gramas mediante el parámetro ngram_range
. Este parámetro acepta una tupla que indica el mínimo y máximo tamaño de n-gramas a extraer. A continuación se muestra un ejemplo práctico:
from sklearn.feature_extraction.text import CountVectorizer
# Lista de documentos de ejemplo
documentos = [
"El rápido zorro marrón salta sobre el perro perezoso",
"Un salto pérfido realizó el zorro astuto",
"Los zorros son animales ágiles y rápidos"
]
# Crear una instancia de CountVectorizer con n-gramas
vectorizador = CountVectorizer(ngram_range=(1, 2))
# Ajustar y transformar los documentos en una matriz de conteos de n-gramas
matriz_ngramas = vectorizador.fit_transform(documentos)
# Obtener el vocabulario de n-gramas extraído
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario de n-gramas:", vocabulario)
# Convertir la matriz a un array denso para visualizarla
print("Matriz de n-gramas:\n", matriz_ngramas.toarray())
En este ejemplo, el vectorizador extrae tanto unigramas (palabras individuales) como bigramas (pares de palabras consecutivas). Esto enriquece la representación del texto al incluir información sobre la secuencia de palabras, lo cual es crucial para capturar significados que no son evidentes con unigramas.
Al inspeccionar el vocabulario generado, se observan n-gramas como "rápido zorro"
, "zorro marrón"
o "sobre el"
, que aportan contexto adicional. La matriz de n-gramas resultante representa la frecuencia de cada n-grama en los documentos, facilitando el análisis y la modelización.
Es importante ajustar adecuadamente el rango de n-gramas según la naturaleza del problema y el tamaño del corpus. Utilizar n-gramas de mayor tamaño puede capturar contextos más amplios pero también aumenta la dimensionalidad y puede introducir ruido. Por ejemplo:
- Un ngram_range de
(1, 1)
extrae solo unigramas. - Un ngram_range de
(2, 2)
extrae solo bigramas. - Un ngram_range de
(1, 3)
extrae unigramas, bigramas y trigramas.
A continuación, se muestra cómo extraer trigramas utilizando CountVectorizer
:
# Extraer unigramas, bigramas y trigramas
vectorizador = CountVectorizer(ngram_range=(1, 3))
matriz_ngramas = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario con trigramas:", vocabulario)
La utilización de n-gramas es especialmente útil en tareas donde la combinación de palabras altera el significado. Por ejemplo, las expresiones idiomáticas o nombres propios se representan mejor como n-gramas que como palabras aisladas.
Además de CountVectorizer
, Scikit-Learn ofrece TfidfVectorizer
, que también admite n-gramas y combina la frecuencia de término con la frecuencia inversa de documento para ponderar la importancia de los n-gramas:
from sklearn.feature_extraction.text import TfidfVectorizer
# Crear una instancia de TfidfVectorizer con n-gramas
vectorizador = TfidfVectorizer(ngram_range=(1, 2))
matriz_tfidf = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario TF-IDF con n-gramas:", vocabulario)
Al utilizar TF-IDF con n-gramas, se considera no solo la frecuencia de los n-gramas en un documento, sino también su rareza en el corpus completo, lo que ayuda a destacar las frases más significativas.
Sin embargo, es fundamental tener en cuenta el compromiso entre la riqueza de la representación y la complejidad computacional. A medida que se incrementa el valor de n, el número de n-gramas posibles crece exponencialmente, lo que puede conducir a matrices extremadamente grandes y esparsas. Para mitigar este efecto, se pueden aplicar técnicas como:
- Filtrado por frecuencia mínima o máxima mediante los parámetros
min_df
ymax_df
para excluir n-gramas muy raros o demasiado comunes. - Limitación del número de características con
max_features
para considerar solo los n-gramas más frecuentes. - Utilización de Hashing Trick con
HashingVectorizer
para manejar grandes vocabularios de forma más eficiente.
Por ejemplo, para eliminar n-gramas poco informativos:
# Filtrar n-gramas con baja frecuencia
vectorizador = CountVectorizer(ngram_range=(1, 2), min_df=2)
matriz_ngramas = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario filtrado:", vocabulario)
Los n-gramas también son fundamentales en modelos avanzados como los modelos de lenguaje y en algoritmos de predicción de texto. Al capturar secuencias específicas, se mejora la capacidad del modelo para entender y generar lenguaje de manera más natural y coherente.
Es importante destacar que el uso de n-gramas puede introducir colinealidad y aumentar la posibilidad de sobreajuste, especialmente en conjuntos de datos pequeños. Por ello, es recomendable combinar su uso con técnicas de reducción de dimensionalidad o regularización para obtener modelos más generalizables.
En resumen, los n-gramas enriquecen la representación textual al considerar combinaciones de palabras, lo que permite a los algoritmos de aprendizaje automático capturar patrones más complejos y mejorar el rendimiento en diversas tareas de NLP. Su implementación en Scikit-Learn es sencilla y flexible, lo que facilita su integración en pipelines de procesamiento de texto.
Vectorización TF-IDF
La Vectorización TF-IDF (Term Frequency - Inverse Document Frequency) es una técnica esencial en el procesamiento del lenguaje natural que convierte textos en representaciones numéricas, ponderando la importancia de las palabras en función de su frecuencia en un documento y su rareza en el conjunto de documentos. Esta metodología mejora la relevancia de las características al destacar términos significativos y atenuar el impacto de las palabras comunes.
El enfoque TF-IDF combina dos componentes fundamentales:
- Frecuencia de Término (TF): mide cuántas veces aparece una palabra en un documento específico.
- Frecuencia Inversa de Documento (IDF): evalúa la rareza de una palabra en el corpus total de documentos.
Multiplicando estos dos valores, se obtiene una medida que resalta las palabras que son frecuentes en un documento pero infrecuentes en el corpus, proporcionando una representación más informativa que el simple conteo de palabras.
En Scikit-Learn, la clase TfidfVectorizer
facilita la transformación de una colección de documentos de texto en una matriz TF-IDF. A continuación, se muestra un ejemplo práctico que ilustra su uso:
from sklearn.feature_extraction.text import TfidfVectorizer
# Documentos de ejemplo
documentos = [
"El gato juega en el jardín",
"El perro juega en el parque",
"El gato y el perro duermen juntos en casa"
]
# Crear una instancia de TfidfVectorizer
vectorizador = TfidfVectorizer()
# Ajustar el vectorizador y transformar los documentos
matriz_tfidf = vectorizador.fit_transform(documentos)
# Obtener el vocabulario generado
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario:", vocabulario)
# Mostrar la matriz TF-IDF resultante
print("Matriz TF-IDF:\n", matriz_tfidf.toarray())
En este código, se crea un vocabulario de términos a partir de los documentos proporcionados. La matriz resultante representa la importancia de cada palabra en cada documento según el cálculo TF-IDF. Esto permite capturar la relevancia de las palabras más allá de su simple aparición.
Es posible personalizar el comportamiento de TfidfVectorizer
mediante diversos parámetros. Por ejemplo, para eliminar palabras vacías (stop words) y ajustar el rango de n-gramas:
stop_words_espanol = stopwords.words('spanish')
# Eliminar palabras vacías y considerar bigramas
vectorizador = TfidfVectorizer(stop_words=stop_words_espanol, ngram_range=(1, 2))
# Ajustar y transformar
matriz_tfidf = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario modificado:", vocabulario)
Salida:
Al incluir bigramas y excluir palabras vacías, se mejora la calidad de la representación al considerar frases clave y eliminar términos poco informativos.
Otro aspecto importante es el manejo de la normalización y la suavización en el cálculo de TF-IDF. Por defecto, TfidfVectorizer
aplica una normalización L2 y utiliza la suavización sublinear TF scaling. Estos comportamientos se pueden ajustar según las necesidades del análisis:
# Desactivar la normalización y modificar la suavización
vectorizador = TfidfVectorizer(norm=None, sublinear_tf=True)
# Ajustar y transformar
matriz_tfidf = vectorizador.fit_transform(documentos)
print("Matriz TF-IDF sin normalización:\n", matriz_tfidf.toarray())
La normalización puede ser relevante al utilizar algoritmos que asumen vectores normalizados. Al desactivarla, se conservan los valores originales de TF-IDF, lo que puede ser útil en ciertos contextos.
Además, es posible limitar el tamaño del vocabulario para reducir la dimensionalidad y enfocarse en los términos más relevantes:
# Limitar el vocabulario a las 5 palabras más frecuentes
vectorizador = TfidfVectorizer(max_features=5)
# Ajustar y transformar
matriz_tfidf = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario limitado:", vocabulario)
Salida:
Este enfoque ayuda a manejar grandes conjuntos de datos al centrarse en las características más significativas.
Para ilustrar cómo utilizar la matriz TF-IDF en un modelo de clasificación, consideremos el siguiente ejemplo con el algoritmo de Naive Bayes multinomial:
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
# Datos de entrenamiento y etiquetas
X_train = [
"El jugador anota un gol",
"El científico publica un estudio",
"El atleta gana una medalla",
"La investigadora descubre una teoría"
]
y_train = ['deportes', 'ciencia', 'deportes', 'ciencia']
# Crear un pipeline con TfidfVectorizer y el clasificador
pipeline = Pipeline([
('vectorizador', TfidfVectorizer()),
('clasificador', MultinomialNB())
], memory = None)
# Entrenar el modelo
pipeline.fit(X_train, y_train)
# Datos de prueba
X_test = ["Un nuevo récord en atletismo", "Un avance en biología molecular"]
# Realizar predicciones
predicciones = pipeline.predict(X_test)
print("Predicciones:", predicciones)
En este caso, el pipeline combina la vectorización TF-IDF con el clasificador, permitiendo predecir la categoría de textos nuevos basándose en la importancia de las palabras. La utilización de TF-IDF mejora la precisión del modelo al considerar tanto la frecuencia de términos como su relevancia global.
Cabe destacar que TfidfVectorizer
ofrece diversos parámetros para el preprocesamiento del texto, como el uso de un analizador personalizado o la aplicación de funciones de tokenización y lematización:
import nltk
from nltk.stem import SnowballStemmer
# Descargar recursos de NLTK (solo la primera vez)
# nltk.download('punkt')
# Función de tokenización personalizada
def tokenizador(texto):
stemmer = SnowballStemmer('spanish')
tokens = nltk.word_tokenize(texto, language='spanish')
stems = [stemmer.stem(token) for token in tokens]
return stems
# Crear el vectorizador con el tokenizador personalizado
vectorizador = TfidfVectorizer(tokenizer=tokenizador)
# Ajustar y transformar
matriz_tfidf = vectorizador.fit_transform(documentos)
vocabulario = vectorizador.get_feature_names_out()
print("Vocabulario con stemming:", vocabulario)
Al aplicar stemming o lemmatización, se reducen las palabras a su raíz, lo que permite agrupar términos similares y reducir la dimensionalidad del espacio de características.
Es posible también ajustar los parámetros de IDF para controlar cómo se calcula la frecuencia inversa de documento. Por ejemplo, se puede desactivar el suavizado de IDF:
# Desactivar el suavizado de IDF
vectorizador = TfidfVectorizer(smooth_idf=False)
# Ajustar y transformar
matriz_tfidf = vectorizador.fit_transform(documentos)
print("Matriz TF-IDF sin suavizado de IDF:\n", matriz_tfidf.toarray())
Esta opción puede ser útil para experimentar con diferentes configuraciones y entender su impacto en el modelo.
La Vectorización TF-IDF es particularmente efectiva en escenarios donde se busca identificar términos discriminantes en grandes colecciones de texto, como en búsquedas de información, filtrado de spam y análisis de sentimientos. Al ponderar adecuadamente las palabras, se consigue una representación más rica y significativa del lenguaje natural.
Por último, es importante considerar la sparsidad de la matriz TF-IDF. Dado que muchos términos solo aparecen en unos pocos documentos, la matriz resultante suele ser sparse. Scikit-Learn maneja eficientemente este tipo de matrices, pero es recomendable prestar atención al rendimiento en conjuntos de datos muy extensos.
Uso de Hashing Vectorizer para grandes conjuntos de datos
El Hashing Vectorizer es una herramienta de Scikit-Learn diseñada para convertir colecciones de documentos de texto en matrices de características de manera eficiente y escalable. A diferencia de los métodos tradicionales como CountVectorizer
o TfidfVectorizer
, el HashingVectorizer
utiliza una función de hashing para asignar de forma determinista los tokens individuales a índices en una matriz de características de tamaño fijo, lo que permite manejar grandes conjuntos de datos sin almacenar un vocabulario en memoria.
La principal ventaja del HashingVectorizer
es su capacidad para procesar grandes volúmenes de texto de forma rápida y con bajo consumo de memoria. Al no requerir construir un vocabulario completo, es especialmente útil en situaciones donde el tamaño del corpus es demasiado grande para almacenarlo en memoria o cuando se trabaja en entornos distribuidos.
A continuación, se presenta un ejemplo práctico de cómo utilizar HashingVectorizer
en Scikit-Learn:
from sklearn.feature_extraction.text import HashingVectorizer
# Colección de documentos de ejemplo
documentos = [
"El zorro rápido salta sobre el perro perezoso",
"El perro perezoso no es tan rápido como el zorro",
"El zorro y el perro compiten en velocidad"
]
# Crear una instancia de HashingVectorizer
vectorizador = HashingVectorizer(n_features=20, alternate_sign=False)
# Transformar los documentos en una matriz de características
matriz_hashing = vectorizador.transform(documentos)
# Mostrar la matriz resultante
print("Matriz de características (densa):\n", matriz_hashing.toarray())
En este ejemplo, el HashingVectorizer
transforma los documentos en una matriz de características de tamaño fijo, especificado por el parámetro n_features
. Al establecer alternate_sign=False
, se asegura que los valores sean únicamente positivos, lo cual es importante para ciertos algoritmos de aprendizaje automático, como el Naive Bayes multinomial.
Es importante destacar que, debido al uso de funciones de hashing, el HashingVectorizer
no permite recuperar el vocabulario original ni invertir el proceso para obtener los tokens a partir de los índices. Esto limita la interpretabilidad de los modelos entrenados con estas características. Sin embargo, este inconveniente se compensa con el ahorro significativo de memoria y tiempo de procesamiento.
El HashingVectorizer
es especialmente útil en flujos de procesamiento donde se requiere escalar a grandes cantidades de datos o realizar aprendizaje en línea. Por ejemplo, en tareas de clasificación de documentos de streaming o análisis de redes sociales en tiempo real.
A continuación, se muestra cómo integrar el HashingVectorizer
en un pipeline de Scikit-Learn para realizar una clasificación de texto:
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import Pipeline
# Crear el pipeline con HashingVectorizer y un clasificador lineal estocástico
pipeline = Pipeline([
('vectorizador', HashingVectorizer(n_features=2**18, alternate_sign=False)),
('clasificador', SGDClassifier(loss='log_loss', random_state=42))
], memory = None)
# Datos de entrenamiento y etiquetas
X_train = [
"Este es un documento de spam con contenido irrelevante",
"Importante actualización sobre tu cuenta bancaria",
"Oferta exclusiva para ganar dinero rápido",
"Reunión de trabajo programada para mañana"
]
y_train = ['spam', 'no_spam', 'spam', 'no_spam']
# Entrenar el modelo
pipeline.fit(X_train, y_train)
# Datos de prueba
X_test = ["Gana dinero desde casa fácilmente", "Agenda de la conferencia anual"]
# Realizar predicciones
predicciones = pipeline.predict(X_test)
print("Predicciones:", predicciones)
En este código, se utiliza el SGDClassifier
con la pérdida 'log_loss'
, que es adecuada para clasificación binaria. El clasificador de descenso de gradiente estocástico es eficiente para grandes conjuntos de datos y complementa al HashingVectorizer
en escenarios de gran escala.
Se debe tener en cuenta que la elección del número de características, definida por n_features
, es crucial. Un valor demasiado pequeño puede provocar colisiones de hash, donde diferentes tokens se asignan al mismo índice, lo que introduce ruido en los datos. Por otro lado, un valor demasiado grande aumenta la dimensionalidad y puede afectar el rendimiento computacional. Es común utilizar potencias de 2 para n_features
por motivos de eficiencia y distribución uniforme de los hashes.
El HashingVectorizer
también admite parámetros para controlar el proceso de tokenización y preprocesamiento. Por ejemplo, se pueden especificar una función de preprocesamiento personalizada o un analizador:
import re
def preprocesado_personalizado(texto):
# Convertir a minúsculas y eliminar dígitos
texto = texto.lower()
texto = re.sub(r'\d+', '', texto)
return texto
# HashingVectorizer con función de preprocesado personalizada
vectorizador = HashingVectorizer(
n_features=2**16,
preprocessor=preprocesado_personalizado,
stop_words='spanish',
alternate_sign=False
)
Al aplicar una función de preprocesamiento personalizada, se puede mejorar la calidad de las características generadas al normalizar el texto y eliminar elementos no deseados. Además, al especificar stop_words='spanish'
, se eliminan las palabras vacías del idioma español, reduciendo el ruido en los datos.
Otra ventaja del HashingVectorizer
es su capacidad para ser utilizado en entornos distribuidos o con paralelismo. Debido a que no mantiene un estado interno dependiente de los datos de entrenamiento, puede ser aplicado en arquitecturas de procesamiento en paralelo sin necesidad de sincronización del vocabulario.
Sin embargo, dado que no se puede acceder al vocabulario, se pierden algunas funcionalidades como la posibilidad de interpretar las características o realizar una selección de características basada en la importancia de los términos. Para mitigar este inconveniente, se puede utilizar el FeatureHasher
en combinación con técnicas que permitan aproximar la interpretabilidad.
Por ejemplo, en casos donde la interpretabilidad es crucial, se puede optar por una estrategia híbrida, utilizando primero un subconjunto de los datos para construir un vocabulario limitado con CountVectorizer
o TfidfVectorizer
, y luego aplicar el HashingVectorizer
para procesar el conjunto completo. Sin embargo, esto supone un compromiso entre eficiencia y capacidad interpretativa.
El uso del HashingVectorizer
también es apropiado en escenarios de aprendizaje en línea, donde los datos llegan en forma de flujo continuo. A continuación, se presenta un ejemplo con el partial_fit
de Scikit-Learn:
from sklearn.linear_model import SGDClassifier
from sklearn.feature_extraction.text import HashingVectorizer
# Simulación de un flujo de datos entrante
lotes_de_datos = [
["Mensaje de spam número uno", "Correo legítimo sobre reunión", "Otra oferta de spam"],
["Actualización de seguridad importante", "Gana premios ahora mismo", "Evento corporativo anual"],
["Descuento exclusivo en productos", "Informe financiero trimestral"]
]
# Inicializar el vectorizador y el clasificador
vectorizador = HashingVectorizer(n_features=2**17, alternate_sign=False)
clasificador = SGDClassifier(loss='log_loss', random_state=42)
for lote in lotes_de_datos:
# Supongamos que tenemos etiquetas para cada lote
y_lote = ['spam', 'no_spam', 'spam'] if len(lote) == 3 else ['spam', 'no_spam']
# Transformar el lote actual
X_lote = vectorizador.transform(lote)
# Entrenar o actualizar el modelo con partial_fit
clases = ['spam', 'no_spam']
clasificador.partial_fit(X_lote, y_lote, classes=clases)
# Opcional: realizar predicciones sobre el lote actual
predicciones = clasificador.predict(X_lote)
print("Predicciones del lote:", predicciones)
Salida:
En este ejemplo, el HashingVectorizer
permite procesar cada lote de datos sin necesidad de mantener un vocabulario global. El SGDClassifier
se actualiza incrementalmante con partial_fit
, lo que es adecuado para escenarios de aprendizaje continuo o cuando los datos no pueden cargarse en memoria simultáneamente.
Es importante recordar que, al utilizar el HashingVectorizer
, debemos ser conscientes de las limitaciones en cuanto a interpretabilidad y posibles colisiones de hash. Sin embargo, sus ventajas en términos de eficiencia y escalabilidad lo convierten en una opción destacada para el procesamiento de texto en grandes volúmenes de datos.
El HashingVectorizer
también se integra bien con otros componentes de Scikit-Learn, permitiendo construir pipelines complejos. Por ejemplo, se puede combinar con técnicas de reducción de dimensionalidad para mitigar el efecto de la alta dimensionalidad:
from sklearn.decomposition import TruncatedSVD
from sklearn.pipeline import Pipeline
# Pipeline con HashingVectorizer y TruncatedSVD
pipeline = Pipeline([
('vectorizador', HashingVectorizer(n_features=2**15, alternate_sign=False)),
('reductor_dim', TruncatedSVD(n_components=100)),
('clasificador', SGDClassifier(loss='log_loss', random_state=42))
], memory = None)
# Datos de entrenamiento y etiquetas
X_train = [...] # Datos de entrenamiento (lista de textos)
y_train = [...] # Etiquetas correspondientes
# Entrenar el modelo
pipeline.fit(X_train, y_train)
En este caso, el TruncatedSVD
actúa como un reducción de dimensionalidad para matrices dispersas, ayudando a mejorar el rendimiento y la generalización del modelo al reducir el número de características.
Al trabajar con el HashingVectorizer
, es recomendable realizar experimentos y validaciones para asegurarse de que el tamaño de la matriz de características y otros parámetros están ajustados adecuadamente al problema específico. La selección cuidadosa de hyperparámetros es clave para obtener buenos resultados en modelos que manejan grandes volúmenes de datos textuales.
Selección y reducción de características textuales
En el procesamiento de lenguaje natural, la representación de textos mediante técnicas como Bag-of-Words o TF-IDF genera matrices con miles o incluso millones de características. Este alto nivel de dimensionalidad puede llevar a problemas como el sobreajuste o altos costos computacionales. Por ello, es esencial aplicar métodos de selección y reducción de características para mejorar el rendimiento de los modelos y hacerlos más manejables.
La selección de características implica elegir un subconjunto de las características originales que sean más informativas para la tarea en cuestión. Una técnica común es la selección univariada, que evalúa cada característica de forma individual en función de una prueba estadística. En el contexto de clasificación de texto, el método SelectKBest
con la prueba chi-cuadrado es particularmente útil:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.pipeline import Pipeline
from sklearn.naive_bayes import MultinomialNB
# Conjunto de documentos y etiquetas
documentos = [
"El gato persigue al ratón",
"El perro ladra al cartero",
"El ratón come queso",
"El cartero entrega cartas"
]
y = [0, 1, 0, 1] # 0: acción de gato/ratón, 1: acción de perro/cartero
# Crear un pipeline con vectorización TF-IDF y selección de características
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('seleccion', SelectKBest(score_func=chi2, k=5)),
('clasificador', MultinomialNB())
], memory = None)
# Entrenar el modelo
pipeline.fit(documentos, y)
# Predecir nuevas muestras
nuevos_documentos = ["El gato juega con el perro", "El cartero teme al perro"]
predicciones = pipeline.predict(nuevos_documentos)
print("Predicciones:", predicciones)
En este ejemplo, SelectKBest
selecciona las 5 características más relevantes según la prueba chi-cuadrado, reduciendo la dimensionalidad y potencialmente mejorando la generalización del modelo.
Además de los métodos univariados, existen técnicas de selección basadas en modelos que consideran todas las características simultáneamente. Los modelos con regularización L1 pueden realizar sparse learning, lo que induce a que los coeficientes de ciertas características sean exactamente cero, efectivamente seleccionando las más importantes. El uso de SelectFromModel
junto con un clasificador como LogisticRegression
con penalización L1 es una estrategia eficaz:
from sklearn.linear_model import LogisticRegression
from sklearn.feature_selection import SelectFromModel
# Pipeline con TF-IDF, selección basada en modelo y regresión logística
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('seleccion', SelectFromModel(LogisticRegression(penalty='l1', solver='liblinear', C=0.1))),
('clasificador', LogisticRegression())
], memory = None)
# Entrenar el modelo
pipeline.fit(documentos, y)
# Predecir nuevas muestras
predicciones = pipeline.predict(nuevos_documentos)
print("Predicciones con selección L1:", predicciones)
Aquí, SelectFromModel
selecciona características cuyos coeficientes no son cero tras el ajuste del modelo con regularización L1, permitiendo una interpretabilidad mejorada y un modelo más simple.
Por otro lado, la reducción de dimensionalidad transforma las características originales en un nuevo espacio de menor dimensión, preservando la mayor cantidad posible de información. Una técnica ampliamente utilizada es la Descomposición en Valores Singulares Truncada (TruncatedSVD
), que es especialmente adecuada para matrices dispersas como las obtenidas de TF-IDF. Este método permite realizar Análisis Semántico Latente (LSA), capturando relaciones subyacentes entre términos:
from sklearn.decomposition import TruncatedSVD
# Pipeline con TF-IDF, reducción de dimensionalidad y clasificación
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('reductor', TruncatedSVD(n_components=2)),
('clasificador', MultinomialNB())
], memory = None)
# Entrenar el modelo
pipeline.fit(documentos, y)
# Transformar nuevos documentos y predecir
predicciones = pipeline.predict(nuevos_documentos)
print("Predicciones con TruncatedSVD:", predicciones)
Al reducir las características a solo 2 componentes, TruncatedSVD
compacta la información esencial, lo que puede mejorar la eficiencia computacional y ayudar a visualizar los datos en espacios de menor dimensión.
Es importante distinguir entre selección y reducción de características. Mientras que la selección elige un subconjunto de las características existentes, la reducción genera nuevas características que son combinaciones de las originales. La elección entre una u otra depende del objetivo específico y de las características de los datos.
Otra técnica de reducción es utilizar la Análisis de Componentes Independientes (FastICA
), que busca representar los datos como combinaciones lineales de componentes estadísticamente independientes:
from sklearn.decomposition import FastICA
# Pipeline con TF-IDF, FastICA y clasificación
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('ica', FastICA(n_components=2, random_state=42)),
('clasificador', MultinomialNB())
], memory = None)
# Entrenar el modelo
pipeline.fit(documentos, y)
# Predecir con el modelo entrenado
predicciones = pipeline.predict(nuevos_documentos)
print("Predicciones con FastICA:", predicciones)
Aunque menos común que TruncatedSVD
, FastICA
puede ser útil en ciertos casos donde se busca revelar estructuras ocultas en los datos.
Una práctica habitual es combinar múltiples técnicas para optimizar el rendimiento del modelo. Por ejemplo, aplicar una selección de características seguida de una reducción de dimensionalidad:
# Pipeline combinado
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('seleccion', SelectKBest(chi2, k=10)),
('reductor', TruncatedSVD(n_components=2)),
('clasificador', MultinomialNB())
], memory = None)
# Entrenar y predecir
pipeline.fit(documentos, y)
predicciones = pipeline.predict(nuevos_documentos)
print("Predicciones con técnicas combinadas:", predicciones)
Esta combinación puede aprovechar las ventajas de ambas técnicas, filtrando primero las características menos relevantes y luego reduciendo la dimensionalidad de las restantes para mejorar la eficiencia.
La interpretación de resultados es un aspecto crucial al aplicar estas técnicas. Mientras que la selección de características mantiene las variables originales, facilitando su interpretación, la reducción de dimensionalidad transforma las variables, lo que puede dificultar la comprensión directa de los componentes resultantes.
Al trabajar con textos en múltiples idiomas o dominios, es posible que ciertas características sean relevantes en un contexto pero no en otro. Por ello, adaptar los métodos de selección y reducción a las peculiaridades del conjunto de datos es fundamental para obtener modelos robustos y generalizables.
Además, es recomendable evaluar el impacto de estas técnicas mediante validación cruzada y analizar métricas como la precisión, la recuperación o el F1-score. De esta forma, se puede determinar si la simplificación del modelo contribuye a un mejor desempeño o si, por el contrario, se pierde información valiosa.
Finalmente, herramientas como el Análisis Discriminante Lineal (LinearDiscriminantAnalysis
) también pueden ser útiles, especialmente cuando se dispone de etiquetas y se busca maximizar la separación entre clases:
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
# Pipeline con TF-IDF, LDA y clasificación
pipeline = Pipeline([
('tfidf', TfidfVectorizer()),
('lda', LDA(n_components=1)),
('clasificador', MultinomialNB())
], memory = None)
# Entrenar y predecir
pipeline.fit(documentos, y)
predicciones = pipeline.predict(nuevos_documentos)
print("Predicciones con LDA:", predicciones)
El uso de LDA
como método supervisado de reducción de dimensionalidad puede mejorar la capacidad predictiva al enfocar la transformación en maximizar la separabilidad de las clases.
En conclusión, la selección y reducción de características textuales son procesos esenciales para manejar la alta dimensionalidad inherente al procesamiento del lenguaje natural. La elección de la técnica adecuada depende del problema específico, y su correcta aplicación puede mejorar significativamente el rendimiento y la interpretabilidad de los modelos.
Ejercicios de esta lección Representación de texto y extracción de características
Evalúa tus conocimientos de esta lección Representación de texto y extracción de características con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Todas las lecciones de ScikitLearn
Accede a todas las lecciones de ScikitLearn y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Aprendizaje Automático
Introducción Y Entorno
Introducción E Instalación
Introducción Y Entorno
Introducción Al Preprocesamiento De Datos
Preprocesamiento De Datos
Identificación Y Tratamiento De Valores Faltantes
Preprocesamiento De Datos
Escalado De Datos
Preprocesamiento De Datos
Normalización De Datos
Preprocesamiento De Datos
Codificación De Variables Categóricas
Preprocesamiento De Datos
Ingeniería De Características
Preprocesamiento De Datos
Selección De Características
Preprocesamiento De Datos
Extracción De Características
Preprocesamiento De Datos
Particionamiento De Datos
Preprocesamiento De Datos
Preprocesamiento De Datos Desbalanceados
Preprocesamiento De Datos
Introducción A La Regresión
Regresión
Regresión Lineal
Regresión
Regresión Knn Kneighborsregressor
Regresión
Regresión Svm Con Svr
Regresión
Regresión Con Árboles Decisiontreeregressor
Regresión
Regresión Con Algoritmos De Conjunto
Regresión
Introducción A La Clasificación
Clasificación
Clasificación Con Regresión Logística
Clasificación
Clasificación Knn Kneighborsclassifier
Clasificación
Clasificación Svm Con Svc
Clasificación
Clasificación Con Árboles Decisiontreeclassifier
Clasificación
Clasificación Con Algoritmos De Conjunto
Clasificación
Reducción De La Dimensionalidad Con Pca
Aprendizaje No Supervisado
Clustering Con Kmeans
Aprendizaje No Supervisado
Clustering Jerárquico
Aprendizaje No Supervisado
Clustering De Densidad Con Dbscan
Aprendizaje No Supervisado
Preprocesamiento De Textos Para Nlp
Nlp
Representación De Texto Y Extracción De Características
Nlp
Clasificación De Texto Con Scikit Learn
Nlp
Análisis De Sentimiento
Nlp
Técnicas Avanzadas De Extracción De Características
Nlp
Introducción Al Análisis De Series Temporales
Series Temporales
Preprocesamiento De Datos De Series Temporales
Series Temporales
Ingeniería De Características Para Series Temporales
Series Temporales
Transformación Y Escalado De Series Temporales
Series Temporales
Validación Y Evaluación De Modelos En Series Temporales
Series Temporales
Validación Y Evaluación De Modelos
Validación De Modelos
Técnicas De Validación Cruzada
Validación De Modelos
Métricas De Regresión
Validación De Modelos
Métricas De Clasificación
Validación De Modelos
Ajuste De Hiperparámetros
Validación De Modelos
Introducción A Pipelines
Pipelines Y Despliegue
Creación De Pipelines Básicos
Pipelines Y Despliegue
Preprocesamiento De Datos Con Pipelines
Pipelines Y Despliegue
Pipelines Y Validación Cruzada
Pipelines Y Despliegue
Pipelines Con Columntransformer
Pipelines Y Despliegue
Exportar E Importar Pipelines
Pipelines Y Despliegue
Objetivos de aprendizaje de esta lección
- Entender el modelo de bolsa de palabras y su implementación con
CountVectorizer
. - Explorar el uso de n-gramas y su relevancia en el procesamiento del lenguaje natural.
- Aplicar la vectorización TF-IDF para ponderar la importancia de términos en documentos.
- Utilizar
HashingVectorizer
para manejar grandes conjuntos de datos textuales eficientemente. - Implementar técnicas de selección y reducción de características textuales para mejorar modelos.
- Construir pipelines de procesamiento de texto combinando múltiples técnicas con Scikit-Learn.
- Mejorar la eficiencia y precisión de modelos de NLP mediante la correcta representación del texto.