scikit-learn

ScikitLearn

Tutorial ScikitLearn: Preprocesamiento de textos para NLP

Scikit-Learn: Preprocesamiento de textos para NLP en Python con NLTK. Aprende tokenización, normalización y eliminación de stop words para preparar textos en modelos de aprendizaje automático.

Aprende ScikitLearn GRATIS y certifícate

Tokenización y segmentación de texto

La tokenización es un paso fundamental en el preprocesamiento de textos para aplicaciones de Procesamiento del Lenguaje Natural (NLP). Consiste en dividir un texto en unidades más pequeñas llamadas tokens, que pueden ser palabras, subpalabras o caracteres. Esta segmentación permite transformar los datos textuales en formatos manejables para los algoritmos de aprendizaje automático.

En Scikit Learn, la tokenización se realiza principalmente a través de las clases CountVectorizer y TfidfVectorizer. Estas clases convierten textos en representaciones numéricas y ofrecen mecanismos integrados para tokenizar y normalizar el texto. Por defecto, utilizan un analizador basado en expresiones regulares que divide el texto en tokens basándose en patrones predefinidos.

El parámetro clave para controlar el proceso de tokenización es token_pattern. Este parámetro acepta una expresión regular que define cómo se segmentará el texto. Por ejemplo, el patrón por defecto es r'(?u)\\b\\w\\w+\\b', que captura secuencias de caracteres alfanuméricos de al menos dos caracteres de longitud.

Si se requiere un control más preciso sobre la tokenización, es posible proporcionar una función personalizada al parámetro tokenizer. Esta función debe tomar una cadena de texto y devolver una lista de tokens. A continuación se muestra un ejemplo de cómo utilizar una función personalizada para tokenizar el texto:

from sklearn.feature_extraction.text import CountVectorizer

def mi_tokenizador(texto):
    # Convertir a minúsculas
    texto = texto.lower()
    # Dividir por espacios en blanco
    tokens = texto.split()
    # Filtrar tokens no alfanuméricos
    tokens = [token for token in tokens if token.isalnum()]
    return tokens

corpus = [
    "El rápido zorro marrón salta sobre el perro perezoso.",
    "Un vaso de agua con hielo, por favor.",
    "¡Bienvenidos al mundo del procesamiento de lenguaje!"
]

vectorizador = CountVectorizer(tokenizer=mi_tokenizador, token_pattern=None)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida: 

En este ejemplo, la función mi_tokenizador convierte el texto a minúsculas y lo divide en tokens basándose en espacios en blanco. Luego filtra los tokens para mantener solo aquellos que son alfanuméricos. Al pasar esta función personalizada al CountVectorizer, se controla exactamente cómo se realiza la tokenización.

Además de tokenizer, Scikit Learn permite modificar el proceso de preprocesamiento a través del parámetro preprocessor. Este acepta una función que toma el texto original y devuelve el texto modificado antes de la tokenización. Por ejemplo, para eliminar signos de puntuación o normalizar acentos:

import unicodedata
import re
from sklearn.feature_extraction.text import CountVectorizer

def mi_preprocesador(texto):
    # Eliminar acentos
    texto = unicodedata.normalize('NFD', texto).encode('ascii', 'ignore').decode('utf-8')
    # Eliminar signos de puntuación
    texto = re.sub(r'[^\w\s]', '', texto)
    return texto

vectorizador = CountVectorizer(tokenizer=mi_tokenizador, preprocessor=mi_preprocesador)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida: 

Aquí, mi_preprocesador elimina los acentos y los signos de puntuación antes de la tokenización. Esto es útil para normalizar el texto y reducir la variabilidad causada por diferentes formas de escribir las mismas palabras.

Para tareas más avanzadas, se puede utilizar un analizador personalizado a través del parámetro analyzer. Un analizador es una función que combina el preprocesamiento, la tokenización y el filtrado de tokens en un solo paso. Al definir un analizador personalizado, se obtiene un control total sobre el proceso de transformación del texto:

def mi_analizador(texto):
    # Preprocesamiento: eliminar acentos y signos de puntuación
    texto = mi_preprocesador(texto)
    # Tokenización: dividir por espacios en blanco
    tokens = texto.split()
    # Filtrado: eliminar tokens cortos
    tokens = [token for token in tokens if len(token) > 2]
    return tokens

vectorizador = CountVectorizer(analyzer=mi_analizador)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida: 

En este código, mi_analizador incorpora el preprocesamiento, la tokenización y el filtrado de tokens. Esto permite crear un proceso de segmentación de texto totalmente adaptado a las necesidades específicas del problema.

Es importante considerar el equilibrio entre la complejidad del proceso de tokenización y el rendimiento del modelo. Una tokenización más detallada puede capturar matices del lenguaje, pero también puede aumentar significativamente el tamaño del vocabulario y los recursos computacionales necesarios.

En ciertos casos, puede ser beneficioso utilizar técnicas como la tokenización mediante n-gramas, que considera secuencias de n tokens consecutivos. Esto se configura en Scikit Learn usando los parámetros ngram_range y analyzer. Por ejemplo, para incluir unigramas, bigramas y trigramas:

vectorizador = CountVectorizer(ngram_range=(1, 3))
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

Al ajustar ngram_range a (1, 3), el CountVectorizer incluye unigramas, bigramas y trigramas en la representación del texto. Esto permite capturar dependencias entre palabras que un enfoque de unigramas podría pasar por alto.

Alternativamente, para realizar una tokenización a nivel de caracteres, se puede establecer el parámetro analyzer a 'char'. Esta técnica es útil para ciertos tipos de análisis lingüísticos, como la detección de errores ortográficos o el análisis morfológico:

vectorizador = CountVectorizer(analyzer='char', ngram_range=(2, 4))
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

En este ejemplo, se generan secuencias de caracteres (bigramas hasta tetragramas). Esto crea una representación basada en patrones de caracteres que puede ser útil para modelos que analizan estructuras sublexicales.

Al trabajar con idiomas que no utilizan espacios para separar palabras, como el chino o el japonés, la segmentación de texto requiere herramientas especializadas. Aunque Scikit Learn no proporciona soporte directo para estos idiomas, es posible integrar su pipeline con bibliotecas externas y luego incorporar los resultados al proceso de vectorización.

Finalmente, es fundamental asegurar que el proceso de tokenización sea consistente entre el conjunto de entrenamiento y el de prueba. Las mismas funciones de preprocesamiento y tokenización deben aplicarse a todos los datos para evitar discrepancias que puedan afectar negativamente el rendimiento del modelo.

Normalización: minúsculas, lematización y stemming

La normalización es un paso esencial en el preprocesamiento de textos para Procesamiento del Lenguaje Natural (NLP). Su propósito es transformar las palabras a una forma común para reducir la variabilidad léxica y facilitar el análisis. Las técnicas más habituales incluyen la conversión a minúsculas, la lematización y el stemming.

La conversión a minúsculas es una forma sencilla de normalizar el texto. Al transformar todas las letras a minúsculas, se evita que las diferencias de capitalización afecten al análisis. En Scikit-Learn, los vectorizadores como CountVectorizer y TfidfVectorizer aplican esta transformación por defecto mediante el parámetro lowercase=True.

from sklearn.feature_extraction.text import CountVectorizer

corpus = [
    "El rápido Zorro marrón salta sobre el Perro perezoso.",
    "Un Vaso de Agua con Hielo, por favor.",
    "¡Bienvenidos al Mundo del Procesamiento de Lenguaje!"
]

vectorizador = CountVectorizer()
X = vectorizador.fit_transform(corpus)
print(vectorizador.get_feature_names_out())

En este ejemplo, las palabras "Zorro" y "zorro" se consideran iguales gracias a la conversión a minúsculas. Sin embargo, a veces puede ser necesario desactivar esta opción. Para mantener la capitalización original, se puede establecer lowercase=False:

vectorizador = CountVectorizer(lowercase=False)
X = vectorizador.fit_transform(corpus)
print(vectorizador.get_feature_names_out())

Salida:

La lematización es una técnica más sofisticada que reduce las palabras a su forma base o lema. Por ejemplo, las palabras "corriendo", "corrí" y "corre" se reducen al lema "correr". Esto ayuda a agrupar diferentes formas de la misma palabra y mejorar la consistencia del análisis. Para realizar lematización en Scikit-Learn, se requiere integrar una herramienta externa como NLTK o spaCy y crear un preprocesador o tokenizador personalizado.

A continuación se muestra cómo utilizar spaCy para lematizar el texto antes de vectorizarlo:

import spacy
from sklearn.feature_extraction.text import CountVectorizer

# Cargar el modelo de spaCy para español
nlp = spacy.load('es_core_news_sm')

def lematizador(texto):
    doc = nlp(texto)
    tokens_lematizados = [token.lemma_ for token in doc if not token.is_punct]
    return tokens_lematizados

vectorizador = CountVectorizer(tokenizer=lematizador)
X = vectorizador.fit_transform(corpus)
print(vectorizador.get_feature_names_out())

Salida:

En este código, la función lematizador procesa el texto con spaCy y extrae los lemas de cada token, excluyendo los signos de puntuación. Al pasar esta función al parámetro tokenizer de CountVectorizer, se asegura que la lematización se aplique durante el proceso de vectorización.

El stemming es otra técnica de normalización que reduce las palabras a su raíz o stem, que puede no ser una palabra válida en el idioma. Por ejemplo, "corriendo", "corrí" y "corre" podrían reducirse a "corr". Aunque el stemming es menos preciso que la lematización, es más sencillo y ligero computacionalmente. Para aplicar stemming en Scikit-Learn, se puede integrar NLTK y crear un tokenizador personalizado.

Aquí se muestra cómo utilizar el SnowballStemmer de NLTK para realizar stemming en español:

from nltk.stem.snowball import SnowballStemmer
from sklearn.feature_extraction.text import CountVectorizer
import nltk

# Descargar recursos necesarios de NLTK si no se han descargado previamente
nltk.download('punkt')

stemmer = SnowballStemmer('spanish')

def stemmizador(texto):
    tokens = nltk.word_tokenize(texto, language='spanish')
    stems = [stemmer.stem(token) for token in tokens if token.isalnum()]
    return stems

vectorizador = CountVectorizer(tokenizer=stemmizador)
X = vectorizador.fit_transform(corpus)
print(vectorizador.get_feature_names_out())

En este ejemplo, la función stemmizador tokeniza el texto utilizando word_tokenize de NLTK y aplica el stemmer a cada token alfanumérico. Al proporcionar esta función al CountVectorizer, se incorpora el stemming al proceso de vectorización.

Es importante destacar que la elección entre lematización y stemming depende de las necesidades específicas del proyecto. La lematización preserva mejor el significado semántico, mientras que el stemming es más rápido pero menos preciso. Ambas técnicas ayudan a reducir la dimensionalidad del vocabulario y mejorar la generalización de los modelos.

Además, es posible combinar estas técnicas con otros pasos de preprocesamiento. Por ejemplo, se puede crear un preprocesador personalizado que convierta el texto a minúsculas, elimine acentos y luego aplique lematización:

import unicodedata

def preprocesador_personalizado(texto):
    # Convertir a minúsculas
    texto = texto.lower()
    # Eliminar acentos
    texto = unicodedata.normalize('NFD', texto).encode('ascii', 'ignore').decode('utf-8')
    return texto

def lematizador(texto):
    texto = preprocesador_personalizado(texto)
    doc = nlp(texto)
    tokens_lematizados = [token.lemma_ for token in doc if not token.is_punct]
    return tokens_lematizados

vectorizador = CountVectorizer(tokenizer=lematizador)
X = vectorizador.fit_transform(corpus)
print(vectorizador.get_feature_names_out())

Salida:

En este código, el preprocesador_personalizado realiza la conversión a minúsculas y elimina los acentos para normalizar el texto. Luego, el lematizador aplica la lematización sobre el texto preprocesado.

Al utilizar estos enfoques, es fundamental recordar que las funciones personalizadas pueden afectar al rendimiento del vectorizador. El parámetro n_jobs de CountVectorizer puede ajustarse para realizar el procesamiento en paralelo y mejorar la eficiencia.

vectorizador = CountVectorizer(tokenizer=lematizador, n_jobs=-1)

La normalización del texto mediante minúsculas, lematización y stemming es esencial para crear modelos de NLP robustos y efectivos. Al reducir la variabilidad léxica, se mejora la calidad de las características extraídas y, por ende, el desempeño de los algoritmos de aprendizaje automático.

Eliminación de stop words y caracteres especiales

La eliminación de stop words y caracteres especiales es un paso crucial en el preprocesamiento de textos para Procesamiento del Lenguaje Natural (NLP). Las stop words son palabras muy frecuentes que suelen aportar poco valor semántico al análisis, como artículos, preposiciones y conjunciones. Los caracteres especiales, por otro lado, pueden introducir ruido en los datos y afectar negativamente al rendimiento de los algoritmos.

En Scikit-Learn, es posible eliminar las stop words de forma eficiente utilizando los parámetros integrados en las clases CountVectorizer y TfidfVectorizer. Estas clases disponen del parámetro stop_words, que permite especificar una lista de palabras a omitir durante el proceso de vectorización.

Por defecto, Scikit-Learn ofrece una lista de stop words para el idioma inglés. Sin embargo, al trabajar con textos en español, es necesario proporcionar una lista personalizada. A continuación, se muestra cómo eliminar las stop words en español utilizando una lista predefinida:

from sklearn.feature_extraction.text import CountVectorizer

corpus = [
    "El rápido zorro marrón salta sobre el perro perezoso.",
    "Un vaso de agua con hielo, por favor.",
    "Bienvenidos al mundo del procesamiento de lenguaje natural."
]

# Lista de stop words en español
stop_words_es = [
    "el", "la", "los", "las", "un", "una", "unos", "unas", "de", "del",
    "con", "y", "o", "a", "por", "para", "en", "al", "lo", "le", "les",
    "su", "sus", "es", "son", "que"
]

vectorizador = CountVectorizer(stop_words=stop_words_es)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

En este ejemplo, el parámetro stop_words recibe una lista de palabras comunes en español que serán excluidas durante la vectorización. Esto ayuda a reducir el vocabulario y a centrar el análisis en términos más relevantes.

También es posible utilizar listas de stop words proporcionadas por bibliotecas como NLTK, que ofrece colecciones estándar para varios idiomas. Para integrarlas, se puede proceder de la siguiente manera:

import nltk
from sklearn.feature_extraction.text import CountVectorizer

# Descargar las stop words en español de NLTK
nltk.download('stopwords')
from nltk.corpus import stopwords

stop_words_es = stopwords.words('spanish')

vectorizador = CountVectorizer(stop_words=stop_words_es)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

Aquí se utiliza la lista de stop words en español de NLTK, que es más completa y está actualizada. Esto garantiza una mejor eliminación de palabras irrelevantes.

Además de las stop words, los caracteres especiales pueden interferir en el análisis textual. Para eliminarlos, se puede crear un preprocesador personalizado que limpie el texto antes de la tokenización. Un ejemplo de preprocesador que elimina caracteres especiales utilizando expresiones regulares es el siguiente:

import re
from sklearn.feature_extraction.text import CountVectorizer

def preprocesador(texto):
    # Eliminar caracteres especiales y dígitos
    texto_limpio = re.sub(r'[^a-zA-ZñÑáéíóúÁÉÍÓÚüÜ\s]', '', texto)
    # Convertir a minúsculas
    texto_limpio = texto_limpio.lower()
    return texto_limpio

vectorizador = CountVectorizer(preprocessor=preprocesador, stop_words=stop_words_es)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

La función preprocesador elimina todos los caracteres que no sean letras o espacios, incluyendo tildes y diéresis. Al pasar esta función al parámetro preprocessor, se asegura que la limpieza se aplique antes de la tokenización.

Para un control más granular, se puede definir un tokenizador personalizado que, además de eliminar caracteres especiales, filtre tokens específicos. Por ejemplo:

def tokenizador(texto):
    # Aplicar el preprocesador personalizado
    texto_limpio = preprocesador(texto)
    # Dividir en tokens
    tokens = texto_limpio.split()
    # Filtrar tokens que no sean alfabéticos
    tokens = [token for token in tokens if token.isalpha()]
    return tokens

vectorizador = CountVectorizer(tokenizer=tokenizador, stop_words=stop_words_es, preprocessor=None)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Al establecer preprocessor=None, se evita el preprocesamiento interno de CountVectorizer, permitiendo utilizar exclusivamente las funciones personalizadas.

Es posible que ciertos proyectos requieran preservar algunos caracteres especiales o stop words por su relevancia en el contexto. En tales casos, se pueden ajustar las expresiones regulares o modificar las listas de stop words adecuadamente.

Otra opción para manejar los caracteres especiales es utilizar el parámetro token_pattern de CountVectorizer. Este parámetro define una expresión regular para identificar los tokens durante la tokenización. Por ejemplo:

# Definir un patrón para tokens que solo incluyan letras y tildes
patron_token = r"(?u)\b[a-zA-ZáéíóúñüÁÉÍÓÚÑÜ]+\b"

vectorizador = CountVectorizer(token_pattern=patron_token, stop_words=stop_words_es)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

El patrón patron_token captura palabras que consisten únicamente en letras, incluyendo caracteres con tildes y diéresis, y excluye otros símbolos o dígitos.

Para integrar técnicas más avanzadas, se puede utilizar spaCy para filtrar tokens según propiedades lingüísticas, como su categoría gramatical. Por ejemplo, para eliminar stop words y caracteres especiales utilizando spaCy:

import spacy
from sklearn.feature_extraction.text import CountVectorizer

# Cargar el modelo de spaCy para español
nlp = spacy.load('es_core_news_sm')

def tokenizador_spacy(texto):
    doc = nlp(texto)
    tokens = [
        token.lemma_ for token in doc
        if not token.is_punct and not token.is_stop and token.is_alpha
    ]
    return tokens

vectorizador = CountVectorizer(tokenizer=tokenizador_spacy)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

La función tokenizador_spacy procesa el texto con spaCy, eliminando signos de puntuación (is_punct), stop words (is_stop) y manteniendo solo tokens alfabéticos (is_alpha). Además, utiliza los lemas de las palabras para una mejor normalización.

En algunos casos, puede ser útil eliminar o reemplazar ciertos caracteres especiales específicos, como emojis o símbolos técnicos. Esto se puede lograr ampliando la lógica del preprocesador. Por ejemplo:

def preprocesador_avanzado(texto):
    # Eliminar emojis y símbolos técnicos
    texto = re.sub(r'[\U00010000-\U0010ffff]', '', texto)
    # Eliminar otros caracteres especiales
    texto = re.sub(r'[^a-zA-ZñÑáéíóúÁÉÍÓÚüÜ\s]', '', texto)
    # Convertir a minúsculas
    texto = texto.lower()
    return texto

vectorizador = CountVectorizer(preprocessor=preprocesador_avanzado, stop_words=stop_words_es)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Aquí, se utiliza una expresión regular para eliminar emojis y otros símbolos fuera del plano básico Unicode, asegurando una limpieza más exhaustiva del texto.

Es importante señalar que la eliminación de stop words y caracteres especiales debe adaptarse al contexto del análisis. En ciertos dominios, algunas stop words pueden ser significativas, o los caracteres especiales pueden contener información relevante. Por ello, es recomendable ajustar las listas y los patrones de acuerdo a las necesidades específicas del proyecto.

Además, al trabajar con textos que incluyen contracciones o expresiones idiomáticas, se debe tener cuidado para no eliminar información valiosa. Técnicas adicionales de normalización y expansión de contracciones pueden ser necesarias.

Por último, es conveniente integrar estos procesos en un pipeline de Scikit-Learn para mantener un flujo de trabajo ordenado y reproducible. Esto facilita la experimentación y la implementación de modelos en producción.

from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('vectorizacion', CountVectorizer(
        preprocessor=preprocesador,
        stop_words=stop_words_es
    )),
    # Aquí se pueden agregar más pasos al pipeline, como un modelo de clasificación
], memory = None)

X = pipeline.fit_transform(corpus)
print(pipeline.named_steps['vectorizacion'].get_feature_names_out())

La utilización de pipelines permite encadenar múltiples transformaciones y modelos, asegurando que el preprocesamiento se aplique de manera consistente a todos los datos.

Manejo de signos de puntuación y espacios en blanco

En el preprocesamiento de textos para Procesamiento del Lenguaje Natural (NLP), el tratamiento adecuado de los signos de puntuación y los espacios en blanco es fundamental para mejorar la calidad de las características extraídas y, por ende, el rendimiento de los modelos. Los signos de puntuación pueden influir en el significado de las frases y su presencia o ausencia puede afectar el proceso de tokenización y análisis.

En Scikit-Learn, el CountVectorizer y el TfidfVectorizer manejan los signos de puntuación y los espacios en blanco de acuerdo con el patrón de tokens definido en el parámetro token_pattern. Por defecto, este patrón es r"(?u)\b\w\w+\b", el cual captura secuencias de caracteres alfanuméricos de al menos dos caracteres, separadas por límites de palabra. Esto implica que los signos de puntuación y ciertos caracteres especiales son ignorados durante la tokenización.

Para personalizar cómo se manejan los signos de puntuación, es posible modificar el token_pattern o proporcionar una función personalizada al parámetro tokenizer. Por ejemplo, si se desea incluir palabras de un solo carácter o tokens que contengan signos de puntuación internos (como "e-mail" o "vis-à-vis"), se puede ajustar el patrón de la siguiente manera:

from sklearn.feature_extraction.text import CountVectorizer

corpus = [
    "¡Hola! ¿Cómo estás?",
    "En el e-mail, se mencionó el proyecto vis-à-vis.",
    "La temperatura es de 20°C en Nueva York."
]

patron_personalizado = r"(?u)\b\w[\w\-]+\b"

vectorizador = CountVectorizer(token_pattern=patron_personalizado)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

En este ejemplo, el patrón patron_personalizado permite capturar tokens que incluyen guiones internos, lo cual es útil para palabras compuestas o términos técnicos. Al ajustar el token_pattern, se controla cómo se segmenta el texto y cómo se manejan los signos de puntuación específicos.

Si se requiere un control más preciso sobre los signos de puntuación, se puede utilizar un preprocesador personalizado para eliminar o transformar los caracteres deseados antes de la tokenización. Por ejemplo, para eliminar todos los signos de puntuación excepto los símbolos de interrogación y exclamación, se puede hacer lo siguiente:

import re

def preprocesador_puntuacion(texto):
    # Mantener símbolos de interrogación y exclamación
    texto = re.sub(r"[^\w\s!?¡¿]", "", texto)
    return texto

vectorizador = CountVectorizer(preprocessor=preprocesador_puntuacion)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida: 

La función preprocesador_puntuacion utiliza una expresión regular para eliminar todos los caracteres que no sean alfanuméricos, espacios en blanco o signos de interrogación y exclamación. De esta manera, se preservan elementos que pueden ser importantes para el análisis semántico o la detección de emociones.

El manejo de los espacios en blanco también puede afectar la tokenización. Por defecto, los espacios múltiples son tratados como separadores simples, pero en algunos casos, los espacios adicionales pueden tener significado (como en formatos de texto preformateado). Si se necesita preservar los espacios en blanco o normalizarlos, se puede utilizar un preprocesador para ajustar el texto antes de la tokenización.

Por ejemplo, para reemplazar múltiples espacios consecutivos por un único espacio, se puede implementar la siguiente función:

def preprocesador_espacios(texto):
    # Reemplazar múltiples espacios por uno solo
    texto = re.sub(r"\s+", " ", texto)
    return texto.strip()

vectorizador = CountVectorizer(preprocessor=preprocesador_espacios)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida: 

Esta función preprocesador_espacios normaliza los espacios en blanco, lo cual puede ser útil cuando el texto original contiene espacios irregulares debido a errores de formato o extracción de datos.

En casos donde los signos de puntuación tienen un valor analítico, como en el análisis de sentimientos donde las exclamaciones o interrogaciones pueden indicar énfasis o emociones, es conveniente convertir estos signos en tokens separados. A continuación, se muestra cómo tokenizar los signos de puntuación específicos:

def tokenizador_con_puntuacion(texto):
    # Dividir incluyendo signos de exclamación e interrogación como tokens
    tokens = re.findall(r"\w+|[!?¡¿]", texto)
    return tokens

vectorizador = CountVectorizer(tokenizer=tokenizador_con_puntuacion)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

El tokenizador_con_puntuacion utiliza una expresión regular para separar palabras y signos de exclamación e interrogación, convirtiendo estos últimos en tokens individuales. Esto permite que el modelo aprenda patrones asociados con la presencia de dichos signos.

Además, es posible que se desee normalizar ciertos signos de puntuación, reemplazándolos por equivalentes o eliminándolos. Por ejemplo, convertir comillas tipográficas en comillas simples o dobles estándar:

def normalizar_puntuacion(texto):
    # Reemplazar comillas tipográficas por comillas estándar
    texto = texto.replace("“", '"').replace("”", '"').replace("‘", "'").replace("’", "'")
    return texto

vectorizador = CountVectorizer(preprocessor=normalizar_puntuacion)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

La función normalizar_puntuacion unifica el uso de comillas, lo cual puede ser importante para mantener la coherencia en el análisis de textos provenientes de diferentes fuentes.

En cuanto al manejo de los espacios en blanco en contextos multilingües o con sistemas de escritura sin separación clara entre palabras (como el chino o el japonés), se requiere una segmentación más avanzada. Aunque Scikit-Learn no proporciona directamente herramientas para estos idiomas, se puede integrar con bibliotecas externas y adaptar el preprocesamiento.

Cuando se trabaja con textos que incluyen saltos de línea, tabulaciones u otros caracteres de espacio en blanco, es posible que se necesite consolidarlos o eliminarlos según el caso. Por ejemplo, para eliminar todos los espacios en blanco excepto los espacios simples:

def limpiar_espacios(texto):
    # Reemplazar tabulaciones y saltos de línea por espacios
    texto = texto.replace("\n", " ").replace("\t", " ")
    # Reemplazar múltiples espacios por uno solo
    texto = re.sub(r"\s+", " ", texto)
    return texto.strip()

vectorizador = CountVectorizer(preprocessor=limpiar_espacios)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

La función limpiar_espacios limpia el texto de espacios en blanco extras, lo cual es útil para textos que han sido extraídos de fuentes con formatos variados.

En algunos contextos avanzados, es necesario conservar el contexto proporcionado por los signos de puntuación. Por ejemplo, al analizar entidades nombradas en un texto, los puntos y comas pueden indicar límites de frases o cláusulas que son relevantes. En tales casos, se puede optar por tokenizadores más sofisticados que preserven esta información, como los proporcionados por bibliotecas especializadas como spaCy o NLTK.

Sin embargo, si se desea continuar utilizando Scikit-Learn y aprovechar funcionalidades avanzadas, se puede crear un análisis personalizado que incorpore estas bibliotecas. A continuación, se muestra un ejemplo utilizando spaCy para conservar los signos de puntuación como tokens:

import spacy

# Cargar el modelo de spaCy para español
nlp = spacy.load('es_core_news_sm')

def analizador_spacy(texto):
    doc = nlp(texto)
    tokens = [token.text for token in doc]
    return tokens

vectorizador = CountVectorizer(tokenizer=analizador_spacy, token_pattern=None)
X = vectorizador.fit_transform(corpus)

print(vectorizador.get_feature_names_out())

Salida:

En este caso, al establecer token_pattern=None, se desactiva el tokenizador interno de CountVectorizer, permitiendo que el analizador_spacy maneje completamente la tokenización, incluyendo los signos de puntuación como tokens separados. Esto proporciona un mayor control y precisión en el procesamiento del texto.

Es importante considerar que el manejo de los signos de puntuación y los espacios en blanco debe adaptarse a los objetivos específicos del análisis. En algunos modelos, puede ser beneficioso eliminar completamente la puntuación para reducir el ruido, mientras que en otros, preservarla puede aportar información valiosa.

Finalmente, al diseñar el pipeline de preprocesamiento, es recomendable probar diferentes configuraciones y evaluar el impacto en el rendimiento del modelo. La flexibilidad que ofrece Scikit-Learn permite ajustar el manejo de signos de puntuación y espacios en blanco para optimizar los resultados en proyectos de Procesamiento del Lenguaje Natural.

Construcción de pipelines de preprocesamiento de textos

La construcción de pipelines en Scikit-Learn permite organizar y automatizar el proceso de preprocesamiento de textos para aplicaciones de Procesamiento del Lenguaje Natural (NLP). Utilizando la clase Pipeline, es posible encadenar múltiples transformaciones y modelos, asegurando que el flujo de datos sea consistente y reproducible.

Dado que el preprocesamiento de textos a menudo implica una serie de pasos, como la normalización, la vectorización y la selección de características, los pipelines simplifican este flujo al integrarlos en una estructura coherente. A continuación, se muestra cómo construir un pipeline que incorpore varias etapas de preprocesamiento y un modelo de clasificación.

Supongamos que se dispone de un conjunto de datos de mensajes de correos electrónicos etiquetados como "spam" o "no spam". El objetivo es construir un modelo que sea capaz de clasificar nuevos correos electrónicos basándose en su contenido. Para ello, se puede crear un pipeline que realice los siguientes pasos:

  1. Preprocesamiento del texto: Limpieza y normalización.
  2. Vectorización: Conversión del texto en una representación numérica.
  3. Clasificación: Entrenamiento de un modelo predictivo.

Primero, se importan las clases necesarias:

from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression

Se define un preprocesador personalizado para limpiar el texto, eliminando números y convirtiendo todo a minúsculas:

import re

def preprocesar_texto(texto):
    texto = texto.lower()
    texto = re.sub(r'\d+', '', texto)
    texto = re.sub(r'[^\w\s]', '', texto)
    return texto

A continuación, se crea el pipeline utilizando la clase Pipeline, especificando los pasos y sus respectivos parámetros:

pipeline = Pipeline([
    ('vectorizador', CountVectorizer(preprocessor=preprocesar_texto, stop_words='spanish')),
    ('clasificador', LogisticRegression(solver='liblinear'))
], memory = None)

En este pipeline, el vectorizador utiliza CountVectorizer con el preprocesador personalizado y elimina las stop words en español. El clasificador es una regresión logística que utilizará la representación numérica generada por el vectorizador.

Es posible ajustar los hiperparámetros de cada paso utilizando un diccionario de parámetros. Por ejemplo, para realizar una búsqueda de los mejores hiperparámetros:

from sklearn.model_selection import GridSearchCV

parametros = {
    'vectorizador__ngram_range': [(1, 1), (1, 2)],
    'clasificador__C': [0.1, 1, 10]
}

grid_search = GridSearchCV(pipeline, parametros, cv=5)

Aquí, se está buscando el mejor rango de n-gramas para el vectorizador y el valor de regularización C para el clasificador.

Una vez configurado el pipeline y los parámetros, se puede entrenar el modelo con los datos de entrenamiento:

# Supongamos que X_train contiene los textos y y_train las etiquetas
grid_search.fit(X_train, y_train)

Tras el entrenamiento, es posible predecir las etiquetas de nuevos textos:

# Predicción sobre datos de prueba
predicciones = grid_search.predict(X_test)

Para mejorar el proceso de preprocesamiento, se puede incorporar un tokenizador personalizado, como una función de lematización con spaCy:

import spacy

nlp = spacy.load('es_core_news_sm')

def tokenizar_lematizar(texto):
    documento = nlp(texto)
    tokens = [token.lemma_ for token in documento if not token.is_stop and not token.is_punct]
    return tokens

Se actualiza el pipeline para utilizar este tokenizador:

pipeline = Pipeline([
    ('vectorizador', CountVectorizer(tokenizer=tokenizar_lematizar)),
    ('clasificador', LogisticRegression(solver='liblinear'))
], memory = None)

Al utilizar el CountVectorizer con el tokenizador personalizado, se aplicará la lematización a cada texto antes de la vectorización.

Es posible combinar múltiples transformaciones utilizando la clase FeatureUnion o ColumnTransformer cuando se tienen diferentes tipos de datos. Por ejemplo, si se dispone de datos numéricos adicionales:

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler

transformador = ColumnTransformer([
    ('texto', CountVectorizer(tokenizer=tokenizar_lematizar), 'texto'),
    ('numerico', StandardScaler(), ['longitud', 'palabras_clave'])
])

El pipeline se ajusta para incluir el transformador de columnas:

pipeline = Pipeline([
    ('preprocesamiento', transformador),
    ('clasificador', LogisticRegression(solver='liblinear'))
], memory = None)

En este ejemplo, el ColumnTransformer aplica el CountVectorizer a la columna de texto y el StandardScaler a las características numéricas, combinando ambas para el entrenamiento del clasificador.

Es importante destacar que los nombres asignados a cada paso en el pipeline permiten acceder y ajustar sus parámetros fácilmente. Además, utilizar pipelines facilita la reproducibilidad y el mantenimiento del código, ya que todas las transformaciones y el modelo están encapsulados en una estructura coherente.

Para evaluar el rendimiento del modelo, se pueden utilizar métricas como la matriz de confusión, la precisión o el F1-score:

from sklearn.metrics import classification_report, confusion_matrix

# Evaluación del modelo
print(classification_report(y_test, predicciones))
print(confusion_matrix(y_test, predicciones))

Al utilizar pipelines, también es sencillo implementar técnicas de validación cruzada, lo que permite obtener estimaciones más fiables del rendimiento del modelo.

Para preservar el modelo entrenado y reutilizarlo en el futuro, se puede guardar el pipeline completo utilizando joblib:

import joblib

# Guardar el pipeline entrenado
joblib.dump(grid_search.best_estimator_, 'modelo_spam_pipeline.joblib')

# Cargar el pipeline entrenado
modelo_cargado = joblib.load('modelo_spam_pipeline.joblib')

De esta manera, el pipeline conserva tanto los pasos de preprocesamiento como el modelo entrenado, listo para hacer predicciones sobre nuevos datos.

Si se requiere implementar el modelo en un entorno de producción, el uso de pipelines simplifica la integración, ya que se necesita llamar únicamente al método predict del pipeline cargado:

# Predicción de un nuevo correo electrónico
nuevo_correo = ["Gana dinero rápido con este truco increíble"]
prediccion = modelo_cargado.predict(nuevo_correo)
print(prediccion)

La construcción de pipelines también permite experimentar con diferentes modelos y técnicas de preprocesamiento sin modificar significativamente el código. Por ejemplo, se puede sustituir el clasificador por un árbol de decisión:

from sklearn.tree import DecisionTreeClassifier

pipeline = Pipeline([
    ('vectorizador', CountVectorizer(tokenizer=tokenizar_lematizar)),
    ('clasificador', DecisionTreeClassifier())
], memory = None)

O incorporar el uso de TfidfVectorizer en lugar de CountVectorizer:

from sklearn.feature_extraction.text import TfidfVectorizer

pipeline = Pipeline([
    ('vectorizador', TfidfVectorizer(tokenizer=tokenizar_lematizar)),
    ('clasificador', LogisticRegression(solver='liblinear'))
], memory = None)

Los pipelines ofrecen flexibilidad y eficiencia en el desarrollo de modelos de NLP al permitir un diseño modular y escalable. Además, al integrar todos los pasos en un solo objeto, se reduce la posibilidad de errores y se mejora la consistencia en el manejo de los datos.

Es recomendable seguir buenas prácticas al construir pipelines, como:

  • Nombrar claramente cada paso para facilitar su identificación.
  • Validar los datos después de cada transformación si es necesario.
  • Utilizar funciones personalizadas para etapas específicas del preprocesamiento.
  • Documentar las transformaciones aplicadas para mantener la transparencia del modelo.

En conclusión, la utilización de pipelines en Scikit-Learn es fundamental para construir flujos de preprocesamiento de textos eficientes y robustos, lo que resulta esencial en proyectos avanzados de Procesamiento del Lenguaje Natural.

Aprende ScikitLearn GRATIS online

Ejercicios de esta lección Preprocesamiento de textos para NLP

Evalúa tus conocimientos de esta lección Preprocesamiento de textos para NLP 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

scikit-learn

Introducción Y Entorno

Introducción E Instalación

scikit-learn

Introducción Y Entorno

Introducción Al Preprocesamiento De Datos

scikit-learn

Preprocesamiento De Datos

Identificación Y Tratamiento De Valores Faltantes

scikit-learn

Preprocesamiento De Datos

Escalado De Datos

scikit-learn

Preprocesamiento De Datos

Normalización De Datos

scikit-learn

Preprocesamiento De Datos

Codificación De Variables Categóricas

scikit-learn

Preprocesamiento De Datos

Ingeniería De Características

scikit-learn

Preprocesamiento De Datos

Selección De Características

scikit-learn

Preprocesamiento De Datos

Extracción De Características

scikit-learn

Preprocesamiento De Datos

Particionamiento De Datos

scikit-learn

Preprocesamiento De Datos

Preprocesamiento De Datos Desbalanceados

scikit-learn

Preprocesamiento De Datos

Introducción A La Regresión

scikit-learn

Regresión

Regresión Lineal

scikit-learn

Regresión

Regresión Knn Kneighborsregressor

scikit-learn

Regresión

Regresión Svm Con Svr

scikit-learn

Regresión

Regresión Con Árboles Decisiontreeregressor

scikit-learn

Regresión

Regresión Con Algoritmos De Conjunto

scikit-learn

Regresión

Introducción A La Clasificación

scikit-learn

Clasificación

Clasificación Con Regresión Logística

scikit-learn

Clasificación

Clasificación Knn Kneighborsclassifier

scikit-learn

Clasificación

Clasificación Svm Con Svc

scikit-learn

Clasificación

Clasificación Con Árboles Decisiontreeclassifier

scikit-learn

Clasificación

Clasificación Con Algoritmos De Conjunto

scikit-learn

Clasificación

Reducción De La Dimensionalidad Con Pca

scikit-learn

Aprendizaje No Supervisado

Clustering Con Kmeans

scikit-learn

Aprendizaje No Supervisado

Clustering Jerárquico

scikit-learn

Aprendizaje No Supervisado

Clustering De Densidad Con Dbscan

scikit-learn

Aprendizaje No Supervisado

Preprocesamiento De Textos Para Nlp

scikit-learn

Nlp

Representación De Texto Y Extracción De Características

scikit-learn

Nlp

Clasificación De Texto Con Scikit Learn

scikit-learn

Nlp

Análisis De Sentimiento

scikit-learn

Nlp

Técnicas Avanzadas De Extracción De Características

scikit-learn

Nlp

Introducción Al Análisis De Series Temporales

scikit-learn

Series Temporales

Preprocesamiento De Datos De Series Temporales

scikit-learn

Series Temporales

Ingeniería De Características Para Series Temporales

scikit-learn

Series Temporales

Transformación Y Escalado De Series Temporales

scikit-learn

Series Temporales

Validación Y Evaluación De Modelos En Series Temporales

scikit-learn

Series Temporales

Validación Y Evaluación De Modelos

scikit-learn

Validación De Modelos

Técnicas De Validación Cruzada

scikit-learn

Validación De Modelos

Métricas De Regresión

scikit-learn

Validación De Modelos

Métricas De Clasificación

scikit-learn

Validación De Modelos

Ajuste De Hiperparámetros

scikit-learn

Validación De Modelos

Introducción A Pipelines

scikit-learn

Pipelines Y Despliegue

Creación De Pipelines Básicos

scikit-learn

Pipelines Y Despliegue

Preprocesamiento De Datos Con Pipelines

scikit-learn

Pipelines Y Despliegue

Pipelines Y Validación Cruzada

scikit-learn

Pipelines Y Despliegue

Pipelines Con Columntransformer

scikit-learn

Pipelines Y Despliegue

Exportar E Importar Pipelines

scikit-learn

Pipelines Y Despliegue

Accede GRATIS a ScikitLearn y certifícate

Objetivos de aprendizaje de esta lección

  • Comprender la tokenización y segmentación de texto utilizando Scikit-Learn.
  • Aplicar técnicas de normalización como conversión a minúsculas, lematización y stemming.
  • Implementar la eliminación de stop words y caracteres especiales en textos.
  • Manejar signos de puntuación y espacios en blanco durante el preprocesamiento.
  • Construir pipelines de preprocesamiento de textos eficientes con Scikit-Learn.