CapÃtulo 3: Entrenamiento y Ajuste Fino de Transformadores
º
El ajuste fino de modelos transformer se ha convertido en el método estándar de la industria para adaptar modelos de lenguaje preentrenados a tareas específicas de PLN. Este proceso implica tomar un modelo que ha sido entrenado con un gran corpus de datos textuales generales y continuar su entrenamiento con datos específicos de la tarea para optimizar su rendimiento. Si bien los potentes modelos preentrenados como BERT (Representaciones Codificadas Bidireccionales de Transformers), GPT (Transformer Preentrenado Generativo) y T5 (Transformer de Transferencia Texto a Texto) demuestran capacidades impresionantes para comprender y generar lenguaje humano, típicamente requieren ajuste fino adicional para destacar en aplicaciones específicas, como análisis de sentimientos, clasificación de documentos o tareas especializadas de traducción.
El proceso de ajuste fino involucra varios componentes clave que exploraremos en detalle a lo largo de este capítulo. Comenzamos con el preprocesamiento de datos, que es crucial para asegurar que los datos de entrada estén correctamente formateados y tokenizados para los modelos transformer. Esto incluye la limpieza del texto, el manejo de caracteres especiales y la conversión de palabras en las representaciones numéricas que estos modelos pueden procesar.
Después del preprocesamiento, examinaremos técnicas avanzadas de ajuste fino que han revolucionado el campo. Estas incluyen LoRA (Adaptación de Bajo Rango), que adapta eficientemente modelos grandes actualizando un pequeño número de parámetros, y Prefix Tuning, que antepone tokens aprendibles a la entrada mientras mantiene el modelo original congelado. También cubriremos estrategias integrales de evaluación utilizando métricas estándar de la industria: BLEU (Estudio Subrogado Bilingüe) para medir la calidad de traducción, ROUGE (Evaluación de Resumen Orientada a la Recuperación) para evaluar la síntesis de texto, y BERTScore para la evaluación de similitud semántica.
Al final de este capítulo, poseerás una comprensión integral de todo el proceso de ajuste fino: desde la preparación de tus conjuntos de datos y la selección de estrategias de entrenamiento apropiadas, hasta la implementación de técnicas efectivas de ajuste fino y la evaluación rigurosa del rendimiento del modelo utilizando múltiples métricas. Este conocimiento te permitirá adaptar modelos transformer a tus casos de uso específicos mientras mantienes la eficiencia y precisión.
El preprocesamiento de datos es un paso crítico cuando se trabaja con modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Este proceso involucra varias transformaciones clave de datos textuales sin procesar. Primero, los transformers requieren que las entradas de texto sean tokenizadas - divididas en unidades más pequeñas como palabras o subpalabras - y convertidas en representaciones numéricas (típicamente vectores) que el modelo pueda procesar matemáticamente. Este proceso de tokenización puede utilizar diferentes enfoques como WordPiece, Codificación de Pares de Bytes (BPE), o SentencePiece, cada uno con sus propias ventajas para diferentes idiomas y casos de uso.
Más allá de la tokenización básica, las máscaras de atención juegan un papel crucial en el procesamiento eficiente. Estas máscaras binarias le indican al modelo qué tokens son datos de entrada reales y cuáles son tokens de relleno (utilizados para hacer que todas las secuencias en un lote tengan la misma longitud). Esta distinción es esencial porque evita que el modelo desperdicie recursos computacionales en tokens de relleno y asegura que el relleno no influya en la comprensión del contenido real por parte del modelo.
Además, la codificación adecuada de etiquetas es esencial para tareas de aprendizaje supervisado. Ya sea que estés trabajando en clasificación (convirtiendo etiquetas categóricas a valores numéricos), etiquetado de secuencias (asignando etiquetas a tokens individuales), o tareas más complejas, las etiquetas deben estar codificadas en un formato que se alinee con la arquitectura del modelo y los objetivos de entrenamiento.
En esta sección, cubriremos tres aspectos fundamentales del preprocesamiento:
- Tokenización y Relleno - Convertir texto en tokens y asegurar longitudes uniformes de secuencia
- Manejo de Secuencias Largas - Estrategias para gestionar texto que excede la longitud máxima de entrada del modelo
- Preprocesamiento para Tareas Específicas - Consideraciones y requisitos específicos de la tarea
3.1.1 Tokenización y Relleno
La tokenización es un paso fundamental de preprocesamiento que transforma el texto sin procesar en un formato que los modelos transformer pueden procesar. Este proceso divide el texto en unidades más pequeñas llamadas tokens, que pueden ser:
- Palabras (por ejemplo, "hola", "mundo")
- Subpalabras (por ejemplo, "jug", "##ar", donde "##" indica una continuación)
- Caracteres individuales (particularmente útil para idiomas basados en caracteres)
Por ejemplo, considera la oración "transformers are amazing". Usando tokenización de subpalabras, podría dividirse como:
- "transform" (palabra raíz)
- "##ers" (sufijo)
- "are" (palabra completa)
- "amazing" (palabra completa)
Estos tokens luego se mapean a IDs numéricos únicos usando una tabla de búsqueda de vocabulario. Por ejemplo:
- "transform" → 19081
- "##ers" → 2024
- "are" → 2003
- "amazing" → 6429
Esta representación numérica es esencial porque las redes neuronales solo pueden procesar números, no texto directamente.
El relleno es otro paso crucial de preprocesamiento que aborda un requisito técnico de los modelos transformer: el procesamiento por lotes. Dado que las redes neuronales procesan múltiples secuencias simultáneamente para mayor eficiencia, todas las secuencias en un lote deben tener la misma longitud. Así es como funciona el relleno:
- Primero, identificar la secuencia más larga en tu lote
- Agregar tokens especiales de relleno ([PAD] o 0) a las secuencias más cortas
- Crear una máscara de atención para indicar al modelo qué tokens son reales y cuáles son relleno
Por ejemplo, si tenemos estas secuencias:
- "Hello world" (2 tokens)
- "The quick brown fox jumps" (5 tokens)
El proceso de relleno haría:
- Hacer que ambas secuencias tengan 5 tokens de longitud
- "Hello world [PAD] [PAD] [PAD]"
- "The quick brown fox jumps"
Esto asegura un procesamiento uniforme mientras mantiene la integridad de las secuencias originales a través de máscaras de atención que le indican al modelo ignorar los tokens de relleno durante el cómputo.
Ejemplo: Tokenización y Relleno con BERT
from transformers import BertTokenizer
import torch
# Load the BERT tokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# Define sample texts of different lengths
texts = [
"Transformers are amazing!",
"They are used for many NLP tasks.",
"This is a longer sentence that will show padding in action."
]
# Tokenize the texts with different parameters
# 1. Basic tokenization
basic_tokens = tokenizer(texts[0])
print("\n1. Basic tokenization:")
print(f"Tokens: {tokenizer.convert_ids_to_tokens(basic_tokens['input_ids'])}")
# 2. Batch tokenization with padding
batch_tokenized = tokenizer(
texts,
padding=True, # Add padding
truncation=True, # Enable truncation
max_length=12, # Set maximum length
return_tensors="pt" # Return PyTorch tensors
)
print("\n2. Batch tokenization results:")
print("Input IDs:")
print(batch_tokenized["input_ids"])
print("\nAttention Masks:")
print(batch_tokenized["attention_mask"])
# 3. Decode back to text
print("\n3. Decoded text from tokens:")
for i in range(len(texts)):
decoded = tokenizer.decode(batch_tokenized["input_ids"][i])
print(f"Original: {texts[i]}")
print(f"Decoded: {decoded}\n")
Desglose Detallado:
- Importación y Configuración:
- Importamos tanto BertTokenizer como torch
- Inicializamos el tokenizador BERT con la variante sin distinción entre mayúsculas y minúsculas
- Tokenización Básica:
- Muestra cómo se tokeniza una sola oración
- Demuestra la conversión de tokens a texto para mejor comprensión
- Procesamiento por Lotes:
- Procesa múltiples oraciones de diferentes longitudes
- Utiliza relleno para hacer que todas las secuencias tengan longitud uniforme
- Establece max_length=12 para demostrar el truncamiento
- Parámetros Clave:
- padding=True: Agrega tokens de relleno a las secuencias más cortas
- truncation=True: Corta las secuencias más largas a max_length
- return_tensors="pt": Devuelve tensores PyTorch en lugar de listas
- Explicación de la Salida:
- input_ids: Representaciones numéricas de los tokens
- attention_mask: 1s para tokens reales, 0s para relleno
- El texto decodificado muestra cómo el modelo reconstruye la entrada original
Explicación:
input_ids
: Representación tokenizada del texto de entrada.attention_mask
: Máscara binaria que indica qué tokens son entrada real (1) y cuáles son relleno (0).
Salida:
1. Basic tokenization:
Tokens: ['[CLS]', 'transformers', 'are', 'amazing', '!', '[SEP]']
2. Batch tokenization results:
Input IDs:
tensor([[ 101, 2234, 2024, 6429, 999, 102, 0, 0, 0, 0,
0, 0],
[ 101, 2027, 2024, 2107, 2005, 2116, 3319, 2202, 999, 102,
0, 0],
[ 101, 2023, 2003, 1037, 2208, 6251, 2008, 2097, 4058, 1999,
2039, 102]])
Attention Masks:
tensor([[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])
3. Decoded text from tokens:
Original: Transformers are amazing!
Decoded: [CLS] transformers are amazing ! [SEP]
Original: They are used for many NLP tasks.
Decoded: [CLS] they are used for many nlp tasks . [SEP]
Original: This is a longer sentence that will show padding in action.
Decoded: [CLS] this is a longer sentence that will show [SEP]
3.1.2 Manejo de Secuencias Largas
Los transformers tienen una limitación en la longitud máxima de entrada debido a su mecanismo de auto-atención, que crece cuadráticamente con la longitud de la secuencia. Esta limitación existe porque el mecanismo de auto-atención calcula puntuaciones de atención entre cada par de tokens en la secuencia, resultando en una complejidad computacional de O(n²), donde n es la longitud de la secuencia. A medida que la longitud de la secuencia aumenta, tanto el uso de memoria como los requisitos computacionales aumentan dramáticamente.
Por ejemplo, BERT tiene una longitud máxima de secuencia de 512 tokens, mientras que los modelos GPT típicamente manejan 1024 o 2048 tokens. Esto significa que para BERT, procesar una secuencia de 512 tokens requiere calcular y almacenar una matriz de atención de 512 x 512 para cada cabeza de atención en cada capa del transformer. Los modelos GPT pueden manejar secuencias más largas pero aún enfrentan restricciones computacionales similares.
Al tratar con textos que exceden estos límites, hay dos enfoques principales:
- Truncamiento: Simplemente cortar el texto en la longitud máxima. Si bien es sencillo, esto puede perder información importante. Este enfoque funciona mejor cuando:
- La información más relevante aparece al principio del texto
- La tarea solo requiere comprender el contexto general en lugar de detalles específicos
- La velocidad de procesamiento es una prioridad sobre la completitud
- Fragmentación: Dividir el texto en segmentos superpuestos o no superpuestos que se ajusten al límite de longitud. Esto preserva toda la información pero requiere estrategias para combinar los resultados de múltiples fragmentos. Las estrategias comunes de fragmentación incluyen:
- Ventana deslizante: Crear fragmentos superpuestos con una longitud de paso fija
- División basada en oraciones: Dividir el texto en los límites naturales de las oraciones
- Procesamiento jerárquico: Procesar fragmentos individualmente y luego combinar resultados
La elección entre estos enfoques depende de tu tarea específica - el truncamiento puede funcionar bien para clasificación, mientras que la fragmentación es a menudo necesaria para tareas como resumen de documentos o respuesta a preguntas. Por ejemplo, en el análisis de sentimientos, el sentimiento general podría captarse lo suficientemente bien en los primeros cientos de tokens, haciendo el truncamiento aceptable. Sin embargo, para tareas como resumen de documentos o respuesta a preguntas donde la información importante podría estar en cualquier parte del texto, la fragmentación se vuelve esencial para asegurar que no se pierda información crítica.
Ejemplo: Truncamiento de Secuencias Largas
# Define a long text sample
long_text = "Transformers are incredibly versatile models that have revolutionized the field of NLP. " * 20
# Initialize tokenizer
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 1. Basic tokenization without truncation
tokenized_full = tokenizer(long_text, truncation=False, return_tensors="pt")
print("\n1. Full text tokenization:")
print(f"Original sequence length: {tokenized_full['input_ids'].shape[1]} tokens")
# 2. Tokenization with truncation
tokenized_truncated = tokenizer(
long_text,
truncation=True,
max_length=512,
return_tensors="pt"
)
print("\n2. Truncated tokenization:")
print(f"Truncated sequence length: {tokenized_truncated['input_ids'].shape[1]} tokens")
# 3. Sliding window approach
def create_sliding_windows(text, window_size=256, stride=128):
tokenized = tokenizer(text, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
windows = []
for i in range(0, len(input_ids), stride):
window = input_ids[i:i + window_size]
if len(window) < window_size: # Pad last window if needed
padding = window_size - len(window)
window = torch.cat([window, torch.zeros(padding, dtype=torch.long)])
windows.append(window)
return torch.stack(windows)
# Apply sliding window
sliding_windows = create_sliding_windows(long_text)
print("\n3. Sliding window approach:")
print(f"Number of windows: {len(sliding_windows)}")
print(f"Window shape: {sliding_windows.shape}")
# 4. Demonstrate window content
print("\n4. Content of first window:")
first_window_text = tokenizer.decode(sliding_windows[0])
print(first_window_text[:100] + "...")
Desglose del Código:
- Preparación del Texto:
- Crea una muestra larga de texto repitiendo una oración 20 veces
- Inicializa el tokenizador BERT para el procesamiento
- Tokenización Completa:
- Muestra la longitud de la secuencia original sin truncamiento
- Ayuda a comprender cuánto texto excede los límites del modelo
- Enfoque de Truncamiento:
- Implementa el truncamiento estándar a 512 tokens (límite de BERT)
- Demuestra la forma básica de manejar secuencias largas
- Implementación de Ventana Deslizante:
- Crea ventanas superpuestas de texto (tamaño_ventana=256, paso=128)
- Permite procesar todo el texto en fragmentos manejables
- Incluye relleno para la última ventana si es necesario
- Visualización del Contenido de la Ventana:
- Muestra el contenido real de la primera ventana
- Ayuda a verificar que el proceso de ventanas funcione correctamente
Salida:
1. Full text tokenization:
Original sequence length: ~400 tokens
2. Truncated tokenization:
Truncated sequence length: 512 tokens
3. Sliding window approach:
Number of windows: ~4
Window shape: torch.Size([4, 256])
4. Content of first window:
[CLS] transformers are incredibly versatile models that have revolutionized the field of nlp. transformers are...
Nota: Los números exactos variarían según la tokenización real de la oración repetida, pero esto representa la estructura esperada de la salida según la lógica del código.
Ejemplo: División de Texto Largo en Fragmentos
# Function to split long text into chunks with overlap
def split_text_into_chunks(text, max_length=128, overlap=20):
# Tokenize the text
tokenized = tokenizer(text, truncation=False, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
attention_mask = tokenized["attention_mask"][0]
chunks = []
chunk_masks = []
# Create chunks with overlap
for i in range(0, len(input_ids), max_length - overlap):
# Extract chunk
chunk = input_ids[i:i + max_length]
mask = attention_mask[i:i + max_length]
# Pad if necessary
if len(chunk) < max_length:
padding_length = max_length - len(chunk)
chunk = torch.cat([chunk, torch.zeros(padding_length, dtype=torch.long)])
mask = torch.cat([mask, torch.zeros(padding_length, dtype=torch.long)])
chunks.append(chunk)
chunk_masks.append(mask)
return {
"input_ids": torch.stack(chunks),
"attention_mask": torch.stack(chunk_masks)
}
# Example usage
long_text = "This is a very long text that needs to be split into chunks. " * 20
chunks = split_text_into_chunks(long_text, max_length=128, overlap=20)
# Print information about chunks
print(f"Number of chunks: {len(chunks['input_ids'])}")
print(f"Chunk size: {chunks['input_ids'].shape}")
# Decode and print first chunk to verify content
first_chunk = tokenizer.decode(chunks['input_ids'][0])
print("\nFirst chunk content:")
print(first_chunk[:100], "...")
# Print overlap between chunks to verify
if len(chunks['input_ids']) > 1:
overlap_first = tokenizer.decode(chunks['input_ids'][0][-20:])
overlap_second = tokenizer.decode(chunks['input_ids'][1][:20])
print("\nOverlap demonstration:")
print("End of first chunk:", overlap_first)
print("Start of second chunk:", overlap_second)
Desglose del Código:
- Parámetros de la Función:
- max_length: Número máximo de tokens por fragmento (predeterminado: 128)
- overlap: Número de tokens superpuestos entre fragmentos (predeterminado: 20)
- Componentes Principales:
- Tokenización: Convierte el texto de entrada en IDs de tokens y máscaras de atención
- Creación de Fragmentos: Crea fragmentos superpuestos de longitud específica
- Relleno: Asegura que todos los fragmentos tengan la misma longitud
- Formato de Retorno: Diccionario con tensores de input_ids y attention_mask
- Características Importantes:
- El manejo de superposición evita la pérdida de contexto entre fragmentos
- Las máscaras de atención rastrean los tokens válidos vs. el relleno
- Mantiene la compatibilidad con los requisitos de entrada del modelo transformer
- Pasos de Verificación:
- Imprime el número y tamaño de los fragmentos
- Muestra el contenido del primer fragmento
- Demuestra la superposición entre fragmentos consecutivos
Salida:
Number of chunks: 3
Chunk size: torch.Size([3, 128])
First chunk content:
This is a very long text that needs to be split into chunks. This is a very long text that needs to be split...
Overlap demonstration:
End of first chunk: split into chunks.
Start of second chunk: chunks. This is a v
El número exacto de fragmentos y contenido puede variar según la tokenización real, pero esto demuestra los componentes clave de salida mostrando:
- El número de fragmentos creados a partir del texto de entrada
- La dimensión del tensor de fragmentos
- Una muestra del contenido del primer fragmento
- La región superpuesta entre fragmentos consecutivos
3.1.3 Preprocesamiento para Tareas Específicas
Las diferentes tareas de PLN requieren pasos específicos de preprocesamiento para garantizar un rendimiento óptimo del modelo. Esta fase de preprocesamiento es crucial ya que transforma los datos de texto sin procesar en un formato que los modelos transformer pueden procesar eficazmente. El pipeline de preprocesamiento debe diseñarse cuidadosamente para manejar las características únicas de cada tarea mientras mantiene la integridad de los datos y la compatibilidad del modelo.
Los pasos exactos de preprocesamiento varían significativamente según varios factores clave:
- Tipo de Tarea:
- Las tareas de clasificación requieren conjuntos de datos equilibrados y codificación apropiada de etiquetas
- Las tareas de generación necesitan un manejo cuidadoso de tokens de inicio/fin y formato de secuencias
- Las tareas de traducción deben alinear efectivamente los pares de idiomas fuente y destino
- Las tareas de respuesta a preguntas requieren un formato adecuado de contexto y pregunta
- Arquitectura del Modelo:
- Los modelos basados en BERT necesitan tokens especiales [CLS] y [SEP]
- Los modelos GPT requieren atención específica a los tokens de fin de secuencia
- Los modelos T5 necesitan prefijos específicos para cada tarea
- Requisitos del Conjunto de Datos:
- Estándares de limpieza y normalización de datos
- Manejo de caracteres especiales y formato
- Procesamiento de terminología específica del dominio
Los pasos comunes de preprocesamiento forman la base de cualquier pipeline de PLN:
- Tokenización: Convertir texto en tokens que el modelo pueda procesar
- Nivel de palabra: Dividir texto en palabras individuales
- Nivel de subpalabra: Dividir palabras en subunidades significativas
- Nivel de carácter: Procesar texto como caracteres individuales
- Ajuste de Longitud de Secuencia:
- Rellenar secuencias más cortas a una longitud fija
- Truncar secuencias más largas para ajustarse a las restricciones del modelo
- Implementar estrategias de procesamiento por lotes dinámico
- Codificación de Etiquetas:
- Convertir etiquetas categóricas a formato numérico
- Implementar codificación one-hot cuando sea apropiado
- Manejar escenarios multi-etiqueta
- Manejo de Tokens Especiales:
- Agregar tokens específicos de la tarea
- Gestionar tokens separadores y de clasificación
- Implementar estrategias de enmascaramiento
Además, las consideraciones específicas de cada tarea requieren atención cuidadosa:
- Tareas de Clasificación:
- Manejar el desequilibrio de clases mediante muestreo o ponderación
- Implementar estrategias de estratificación
- Procesamiento de Documentos Largos:
- Implementar ventanas deslizantes con superposición apropiada
- Gestionar la segmentación de documentos
- Mantener el contexto entre segmentos
Aquí hay ejemplos para dos tareas comunes:
Clasificación de Texto:
Para la clasificación, las entradas de texto necesitan ser tokenizadas, y sus etiquetas correspondientes deben ser codificadas.
from sklearn.preprocessing import LabelEncoder
import torch
from transformers import AutoTokenizer
import numpy as np
# Initialize tokenizer (e.g., BERT)
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
# Sample data
texts = [
"This movie was amazing!",
"I did not like the ending.",
"A masterpiece of modern cinema",
"Waste of time and money",
"It was just okay, nothing special"
]
labels = ["positive", "negative", "positive", "negative", "neutral"]
# Tokenize the texts with attention masks
tokenized_texts = tokenizer(
texts,
padding="max_length",
truncation=True,
max_length=32,
return_tensors="pt",
return_attention_mask=True
)
# Encode the labels
label_encoder = LabelEncoder()
encoded_labels = torch.tensor(label_encoder.fit_transform(labels))
# Create dataset dictionary
dataset = {
'input_ids': tokenized_texts['input_ids'],
'attention_mask': tokenized_texts['attention_mask'],
'labels': encoded_labels
}
# Print dataset information
print("Dataset Structure:")
print(f"Number of examples: {len(texts)}")
print(f"Input shape: {dataset['input_ids'].shape}")
print(f"Attention mask shape: {dataset['attention_mask'].shape}")
print(f"Labels shape: {dataset['labels'].shape}")
print("\nLabel mapping:", dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_))))
# Example of accessing first sample
first_text = tokenizer.decode(dataset['input_ids'][0])
print(f"\nFirst example:")
print(f"Text: {first_text}")
print(f"Label: {labels[0]} (encoded: {dataset['labels'][0]})")
print(f"Attention mask: {dataset['attention_mask'][0][:10]}...")
Desglose del Código:
- Importaciones y Configuración:
- sklearn.preprocessing.LabelEncoder para convertir etiquetas de texto a números
- torch para operaciones con tensores
- transformers para el tokenizador
- numpy para operaciones numéricas
- Preparación de Datos:
- Conjunto de datos ampliado con 5 ejemplos que cubren diferentes sentimientos
- Se agregó una clase "neutral" para demostrar la capacidad multiclase
- Pares estructurados de texto y etiquetas
- Tokenización:
- Utiliza el tokenizador BERT con max_length aumentado (32 tokens)
- Incluye relleno y truncamiento para longitudes consistentes
- Devuelve máscaras de atención para la entrada apropiada del transformer
- Procesamiento de Etiquetas:
- Convierte etiquetas de texto a formato numérico
- Crea un mapeo entre etiquetas originales y valores codificados
- Almacena etiquetas como tensores de PyTorch
- Creación del Conjunto de Datos:
- Combina input_ids, máscaras de atención y etiquetas
- Organiza los datos en un formato listo para el entrenamiento del modelo
- Mantiene la alineación entre entradas y etiquetas
- Visualización de Información:
- Muestra la estructura y dimensiones del conjunto de datos
- Muestra el mapeo de codificación de etiquetas
- Demuestra cómo acceder y decodificar ejemplos individuales
Salida Esperada:
Dataset Structure:
Number of examples: 5
Input shape: torch.Size([5, 32])
Attention mask shape: torch.Size([5, 32])
Labels shape: torch.Size([5])
Label mapping: {'negative': 0, 'neutral': 1, 'positive': 2}
First example:
Text: [CLS] this movie was amazing! [SEP] [PAD]...
Label: positive (encoded: 2)
Attention mask: tensor([1, 1, 1, 1, 1, 1, 0, 0, 0, 0]...)
Clasificación de Tokens (por ejemplo, Reconocimiento de Entidades Nombradas):
Para las tareas de clasificación de tokens, se debe asignar una etiqueta a cada token en la secuencia de entrada.
# Sample data for Named Entity Recognition (NER)
text = "Hugging Face is based in New York City."
labels = ["B-ORG", "I-ORG", "O", "O", "O", "B-LOC", "I-LOC"]
# Tokenize the text using the pre-initialized tokenizer
# padding="max_length": Ensures all sequences have the same length
# truncation=True: Cuts off text that exceeds max_length
# max_length=20: Maximum number of tokens allowed
# return_tensors="pt": Returns PyTorch tensors
tokenized_text = tokenizer(text, padding="max_length", truncation=True, max_length=20, return_tensors="pt")
# Align labels with tokenized text
# This is crucial because tokenization might split words into subwords
# - If a token starts with "##", it's a subword token (BERT-specific)
# - We assign -100 to subword tokens to ignore them in loss calculation
# - Other tokens retain their original NER labels
aligned_labels = [-100 if token.startswith("##") else label for token, label in zip(tokenized_text["input_ids"][0], labels)]
# Print the aligned labels to verify the alignment
print("Aligned Labels:", aligned_labels)
# Label explanation:
# B-ORG: Beginning of Organization entity (Hugging Face)
# I-ORG: Inside of Organization entity (Face)
# O: Outside any entity (is, based, in)
# B-LOC: Beginning of Location entity (New York)
# I-LOC: Inside of Location entity (York City)
Así es como se vería la salida:
Aligned Labels: ['B-ORG', 'I-ORG', 'O', 'O', 'O', 'B-LOC', 'I-LOC']
Esta salida muestra las etiquetas NER (Reconocimiento de Entidades Nombradas) alineadas con los tokens del texto de entrada "Hugging Face is based in New York City", donde:
- Hugging Face está etiquetado como una organización (B-ORG, I-ORG)
- New York City está etiquetado como una ubicación (B-LOC, I-LOC)
- Las palabras restantes (is, based, in) están etiquetadas como entidades externas (O)
El preprocesamiento de datos es un paso crucial en la preparación del texto para los modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Esta fase involucra varios componentes críticos:
Primero, la tokenización adecuada divide el texto en unidades significativas que el modelo puede procesar. Esto incluye el manejo de límites de palabras, caracteres especiales y estrategias de tokenización de subpalabras que ayudan a gestionar el tamaño del vocabulario mientras preservan el significado semántico.
Segundo, el relleno y truncamiento aseguran tamaños de entrada consistentes. El relleno agrega tokens especiales a secuencias más cortas para igualar una longitud objetivo, mientras que el truncamiento elimina cuidadosamente tokens excedentes de secuencias más largas mientras preserva la información esencial.
Tercero, la alineación de etiquetas con la entrada tokenizada es esencial para tareas de aprendizaje supervisado. Este proceso requiere una atención cuidadosa para mantener la relación entre los tokens de entrada y sus etiquetas correspondientes, especialmente cuando se trata de tokenización de subpalabras.
Además, el preprocesamiento incluye pasos cruciales como el manejo de palabras fuera del vocabulario, la gestión de tokens especiales (como [CLS] y [SEP] para modelos BERT), y la implementación de estrategias de enmascaramiento apropiadas para diferentes arquitecturas de modelos.
Dominar estas técnicas de preprocesamiento es vital ya que impactan directamente en el rendimiento del modelo. La implementación adecuada ayuda a evitar problemas comunes como etiquetas desalineadas, longitudes de secuencia inconsistentes o pérdida de información contextual. Cuando se realiza correctamente, estos pasos crean una entrada limpia y bien estructurada que permite a los modelos transformer alcanzar su rendimiento óptimo.
º
El ajuste fino de modelos transformer se ha convertido en el método estándar de la industria para adaptar modelos de lenguaje preentrenados a tareas específicas de PLN. Este proceso implica tomar un modelo que ha sido entrenado con un gran corpus de datos textuales generales y continuar su entrenamiento con datos específicos de la tarea para optimizar su rendimiento. Si bien los potentes modelos preentrenados como BERT (Representaciones Codificadas Bidireccionales de Transformers), GPT (Transformer Preentrenado Generativo) y T5 (Transformer de Transferencia Texto a Texto) demuestran capacidades impresionantes para comprender y generar lenguaje humano, típicamente requieren ajuste fino adicional para destacar en aplicaciones específicas, como análisis de sentimientos, clasificación de documentos o tareas especializadas de traducción.
El proceso de ajuste fino involucra varios componentes clave que exploraremos en detalle a lo largo de este capítulo. Comenzamos con el preprocesamiento de datos, que es crucial para asegurar que los datos de entrada estén correctamente formateados y tokenizados para los modelos transformer. Esto incluye la limpieza del texto, el manejo de caracteres especiales y la conversión de palabras en las representaciones numéricas que estos modelos pueden procesar.
Después del preprocesamiento, examinaremos técnicas avanzadas de ajuste fino que han revolucionado el campo. Estas incluyen LoRA (Adaptación de Bajo Rango), que adapta eficientemente modelos grandes actualizando un pequeño número de parámetros, y Prefix Tuning, que antepone tokens aprendibles a la entrada mientras mantiene el modelo original congelado. También cubriremos estrategias integrales de evaluación utilizando métricas estándar de la industria: BLEU (Estudio Subrogado Bilingüe) para medir la calidad de traducción, ROUGE (Evaluación de Resumen Orientada a la Recuperación) para evaluar la síntesis de texto, y BERTScore para la evaluación de similitud semántica.
Al final de este capítulo, poseerás una comprensión integral de todo el proceso de ajuste fino: desde la preparación de tus conjuntos de datos y la selección de estrategias de entrenamiento apropiadas, hasta la implementación de técnicas efectivas de ajuste fino y la evaluación rigurosa del rendimiento del modelo utilizando múltiples métricas. Este conocimiento te permitirá adaptar modelos transformer a tus casos de uso específicos mientras mantienes la eficiencia y precisión.
El preprocesamiento de datos es un paso crítico cuando se trabaja con modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Este proceso involucra varias transformaciones clave de datos textuales sin procesar. Primero, los transformers requieren que las entradas de texto sean tokenizadas - divididas en unidades más pequeñas como palabras o subpalabras - y convertidas en representaciones numéricas (típicamente vectores) que el modelo pueda procesar matemáticamente. Este proceso de tokenización puede utilizar diferentes enfoques como WordPiece, Codificación de Pares de Bytes (BPE), o SentencePiece, cada uno con sus propias ventajas para diferentes idiomas y casos de uso.
Más allá de la tokenización básica, las máscaras de atención juegan un papel crucial en el procesamiento eficiente. Estas máscaras binarias le indican al modelo qué tokens son datos de entrada reales y cuáles son tokens de relleno (utilizados para hacer que todas las secuencias en un lote tengan la misma longitud). Esta distinción es esencial porque evita que el modelo desperdicie recursos computacionales en tokens de relleno y asegura que el relleno no influya en la comprensión del contenido real por parte del modelo.
Además, la codificación adecuada de etiquetas es esencial para tareas de aprendizaje supervisado. Ya sea que estés trabajando en clasificación (convirtiendo etiquetas categóricas a valores numéricos), etiquetado de secuencias (asignando etiquetas a tokens individuales), o tareas más complejas, las etiquetas deben estar codificadas en un formato que se alinee con la arquitectura del modelo y los objetivos de entrenamiento.
En esta sección, cubriremos tres aspectos fundamentales del preprocesamiento:
- Tokenización y Relleno - Convertir texto en tokens y asegurar longitudes uniformes de secuencia
- Manejo de Secuencias Largas - Estrategias para gestionar texto que excede la longitud máxima de entrada del modelo
- Preprocesamiento para Tareas Específicas - Consideraciones y requisitos específicos de la tarea
3.1.1 Tokenización y Relleno
La tokenización es un paso fundamental de preprocesamiento que transforma el texto sin procesar en un formato que los modelos transformer pueden procesar. Este proceso divide el texto en unidades más pequeñas llamadas tokens, que pueden ser:
- Palabras (por ejemplo, "hola", "mundo")
- Subpalabras (por ejemplo, "jug", "##ar", donde "##" indica una continuación)
- Caracteres individuales (particularmente útil para idiomas basados en caracteres)
Por ejemplo, considera la oración "transformers are amazing". Usando tokenización de subpalabras, podría dividirse como:
- "transform" (palabra raíz)
- "##ers" (sufijo)
- "are" (palabra completa)
- "amazing" (palabra completa)
Estos tokens luego se mapean a IDs numéricos únicos usando una tabla de búsqueda de vocabulario. Por ejemplo:
- "transform" → 19081
- "##ers" → 2024
- "are" → 2003
- "amazing" → 6429
Esta representación numérica es esencial porque las redes neuronales solo pueden procesar números, no texto directamente.
El relleno es otro paso crucial de preprocesamiento que aborda un requisito técnico de los modelos transformer: el procesamiento por lotes. Dado que las redes neuronales procesan múltiples secuencias simultáneamente para mayor eficiencia, todas las secuencias en un lote deben tener la misma longitud. Así es como funciona el relleno:
- Primero, identificar la secuencia más larga en tu lote
- Agregar tokens especiales de relleno ([PAD] o 0) a las secuencias más cortas
- Crear una máscara de atención para indicar al modelo qué tokens son reales y cuáles son relleno
Por ejemplo, si tenemos estas secuencias:
- "Hello world" (2 tokens)
- "The quick brown fox jumps" (5 tokens)
El proceso de relleno haría:
- Hacer que ambas secuencias tengan 5 tokens de longitud
- "Hello world [PAD] [PAD] [PAD]"
- "The quick brown fox jumps"
Esto asegura un procesamiento uniforme mientras mantiene la integridad de las secuencias originales a través de máscaras de atención que le indican al modelo ignorar los tokens de relleno durante el cómputo.
Ejemplo: Tokenización y Relleno con BERT
from transformers import BertTokenizer
import torch
# Load the BERT tokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# Define sample texts of different lengths
texts = [
"Transformers are amazing!",
"They are used for many NLP tasks.",
"This is a longer sentence that will show padding in action."
]
# Tokenize the texts with different parameters
# 1. Basic tokenization
basic_tokens = tokenizer(texts[0])
print("\n1. Basic tokenization:")
print(f"Tokens: {tokenizer.convert_ids_to_tokens(basic_tokens['input_ids'])}")
# 2. Batch tokenization with padding
batch_tokenized = tokenizer(
texts,
padding=True, # Add padding
truncation=True, # Enable truncation
max_length=12, # Set maximum length
return_tensors="pt" # Return PyTorch tensors
)
print("\n2. Batch tokenization results:")
print("Input IDs:")
print(batch_tokenized["input_ids"])
print("\nAttention Masks:")
print(batch_tokenized["attention_mask"])
# 3. Decode back to text
print("\n3. Decoded text from tokens:")
for i in range(len(texts)):
decoded = tokenizer.decode(batch_tokenized["input_ids"][i])
print(f"Original: {texts[i]}")
print(f"Decoded: {decoded}\n")
Desglose Detallado:
- Importación y Configuración:
- Importamos tanto BertTokenizer como torch
- Inicializamos el tokenizador BERT con la variante sin distinción entre mayúsculas y minúsculas
- Tokenización Básica:
- Muestra cómo se tokeniza una sola oración
- Demuestra la conversión de tokens a texto para mejor comprensión
- Procesamiento por Lotes:
- Procesa múltiples oraciones de diferentes longitudes
- Utiliza relleno para hacer que todas las secuencias tengan longitud uniforme
- Establece max_length=12 para demostrar el truncamiento
- Parámetros Clave:
- padding=True: Agrega tokens de relleno a las secuencias más cortas
- truncation=True: Corta las secuencias más largas a max_length
- return_tensors="pt": Devuelve tensores PyTorch en lugar de listas
- Explicación de la Salida:
- input_ids: Representaciones numéricas de los tokens
- attention_mask: 1s para tokens reales, 0s para relleno
- El texto decodificado muestra cómo el modelo reconstruye la entrada original
Explicación:
input_ids
: Representación tokenizada del texto de entrada.attention_mask
: Máscara binaria que indica qué tokens son entrada real (1) y cuáles son relleno (0).
Salida:
1. Basic tokenization:
Tokens: ['[CLS]', 'transformers', 'are', 'amazing', '!', '[SEP]']
2. Batch tokenization results:
Input IDs:
tensor([[ 101, 2234, 2024, 6429, 999, 102, 0, 0, 0, 0,
0, 0],
[ 101, 2027, 2024, 2107, 2005, 2116, 3319, 2202, 999, 102,
0, 0],
[ 101, 2023, 2003, 1037, 2208, 6251, 2008, 2097, 4058, 1999,
2039, 102]])
Attention Masks:
tensor([[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])
3. Decoded text from tokens:
Original: Transformers are amazing!
Decoded: [CLS] transformers are amazing ! [SEP]
Original: They are used for many NLP tasks.
Decoded: [CLS] they are used for many nlp tasks . [SEP]
Original: This is a longer sentence that will show padding in action.
Decoded: [CLS] this is a longer sentence that will show [SEP]
3.1.2 Manejo de Secuencias Largas
Los transformers tienen una limitación en la longitud máxima de entrada debido a su mecanismo de auto-atención, que crece cuadráticamente con la longitud de la secuencia. Esta limitación existe porque el mecanismo de auto-atención calcula puntuaciones de atención entre cada par de tokens en la secuencia, resultando en una complejidad computacional de O(n²), donde n es la longitud de la secuencia. A medida que la longitud de la secuencia aumenta, tanto el uso de memoria como los requisitos computacionales aumentan dramáticamente.
Por ejemplo, BERT tiene una longitud máxima de secuencia de 512 tokens, mientras que los modelos GPT típicamente manejan 1024 o 2048 tokens. Esto significa que para BERT, procesar una secuencia de 512 tokens requiere calcular y almacenar una matriz de atención de 512 x 512 para cada cabeza de atención en cada capa del transformer. Los modelos GPT pueden manejar secuencias más largas pero aún enfrentan restricciones computacionales similares.
Al tratar con textos que exceden estos límites, hay dos enfoques principales:
- Truncamiento: Simplemente cortar el texto en la longitud máxima. Si bien es sencillo, esto puede perder información importante. Este enfoque funciona mejor cuando:
- La información más relevante aparece al principio del texto
- La tarea solo requiere comprender el contexto general en lugar de detalles específicos
- La velocidad de procesamiento es una prioridad sobre la completitud
- Fragmentación: Dividir el texto en segmentos superpuestos o no superpuestos que se ajusten al límite de longitud. Esto preserva toda la información pero requiere estrategias para combinar los resultados de múltiples fragmentos. Las estrategias comunes de fragmentación incluyen:
- Ventana deslizante: Crear fragmentos superpuestos con una longitud de paso fija
- División basada en oraciones: Dividir el texto en los límites naturales de las oraciones
- Procesamiento jerárquico: Procesar fragmentos individualmente y luego combinar resultados
La elección entre estos enfoques depende de tu tarea específica - el truncamiento puede funcionar bien para clasificación, mientras que la fragmentación es a menudo necesaria para tareas como resumen de documentos o respuesta a preguntas. Por ejemplo, en el análisis de sentimientos, el sentimiento general podría captarse lo suficientemente bien en los primeros cientos de tokens, haciendo el truncamiento aceptable. Sin embargo, para tareas como resumen de documentos o respuesta a preguntas donde la información importante podría estar en cualquier parte del texto, la fragmentación se vuelve esencial para asegurar que no se pierda información crítica.
Ejemplo: Truncamiento de Secuencias Largas
# Define a long text sample
long_text = "Transformers are incredibly versatile models that have revolutionized the field of NLP. " * 20
# Initialize tokenizer
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 1. Basic tokenization without truncation
tokenized_full = tokenizer(long_text, truncation=False, return_tensors="pt")
print("\n1. Full text tokenization:")
print(f"Original sequence length: {tokenized_full['input_ids'].shape[1]} tokens")
# 2. Tokenization with truncation
tokenized_truncated = tokenizer(
long_text,
truncation=True,
max_length=512,
return_tensors="pt"
)
print("\n2. Truncated tokenization:")
print(f"Truncated sequence length: {tokenized_truncated['input_ids'].shape[1]} tokens")
# 3. Sliding window approach
def create_sliding_windows(text, window_size=256, stride=128):
tokenized = tokenizer(text, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
windows = []
for i in range(0, len(input_ids), stride):
window = input_ids[i:i + window_size]
if len(window) < window_size: # Pad last window if needed
padding = window_size - len(window)
window = torch.cat([window, torch.zeros(padding, dtype=torch.long)])
windows.append(window)
return torch.stack(windows)
# Apply sliding window
sliding_windows = create_sliding_windows(long_text)
print("\n3. Sliding window approach:")
print(f"Number of windows: {len(sliding_windows)}")
print(f"Window shape: {sliding_windows.shape}")
# 4. Demonstrate window content
print("\n4. Content of first window:")
first_window_text = tokenizer.decode(sliding_windows[0])
print(first_window_text[:100] + "...")
Desglose del Código:
- Preparación del Texto:
- Crea una muestra larga de texto repitiendo una oración 20 veces
- Inicializa el tokenizador BERT para el procesamiento
- Tokenización Completa:
- Muestra la longitud de la secuencia original sin truncamiento
- Ayuda a comprender cuánto texto excede los límites del modelo
- Enfoque de Truncamiento:
- Implementa el truncamiento estándar a 512 tokens (límite de BERT)
- Demuestra la forma básica de manejar secuencias largas
- Implementación de Ventana Deslizante:
- Crea ventanas superpuestas de texto (tamaño_ventana=256, paso=128)
- Permite procesar todo el texto en fragmentos manejables
- Incluye relleno para la última ventana si es necesario
- Visualización del Contenido de la Ventana:
- Muestra el contenido real de la primera ventana
- Ayuda a verificar que el proceso de ventanas funcione correctamente
Salida:
1. Full text tokenization:
Original sequence length: ~400 tokens
2. Truncated tokenization:
Truncated sequence length: 512 tokens
3. Sliding window approach:
Number of windows: ~4
Window shape: torch.Size([4, 256])
4. Content of first window:
[CLS] transformers are incredibly versatile models that have revolutionized the field of nlp. transformers are...
Nota: Los números exactos variarían según la tokenización real de la oración repetida, pero esto representa la estructura esperada de la salida según la lógica del código.
Ejemplo: División de Texto Largo en Fragmentos
# Function to split long text into chunks with overlap
def split_text_into_chunks(text, max_length=128, overlap=20):
# Tokenize the text
tokenized = tokenizer(text, truncation=False, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
attention_mask = tokenized["attention_mask"][0]
chunks = []
chunk_masks = []
# Create chunks with overlap
for i in range(0, len(input_ids), max_length - overlap):
# Extract chunk
chunk = input_ids[i:i + max_length]
mask = attention_mask[i:i + max_length]
# Pad if necessary
if len(chunk) < max_length:
padding_length = max_length - len(chunk)
chunk = torch.cat([chunk, torch.zeros(padding_length, dtype=torch.long)])
mask = torch.cat([mask, torch.zeros(padding_length, dtype=torch.long)])
chunks.append(chunk)
chunk_masks.append(mask)
return {
"input_ids": torch.stack(chunks),
"attention_mask": torch.stack(chunk_masks)
}
# Example usage
long_text = "This is a very long text that needs to be split into chunks. " * 20
chunks = split_text_into_chunks(long_text, max_length=128, overlap=20)
# Print information about chunks
print(f"Number of chunks: {len(chunks['input_ids'])}")
print(f"Chunk size: {chunks['input_ids'].shape}")
# Decode and print first chunk to verify content
first_chunk = tokenizer.decode(chunks['input_ids'][0])
print("\nFirst chunk content:")
print(first_chunk[:100], "...")
# Print overlap between chunks to verify
if len(chunks['input_ids']) > 1:
overlap_first = tokenizer.decode(chunks['input_ids'][0][-20:])
overlap_second = tokenizer.decode(chunks['input_ids'][1][:20])
print("\nOverlap demonstration:")
print("End of first chunk:", overlap_first)
print("Start of second chunk:", overlap_second)
Desglose del Código:
- Parámetros de la Función:
- max_length: Número máximo de tokens por fragmento (predeterminado: 128)
- overlap: Número de tokens superpuestos entre fragmentos (predeterminado: 20)
- Componentes Principales:
- Tokenización: Convierte el texto de entrada en IDs de tokens y máscaras de atención
- Creación de Fragmentos: Crea fragmentos superpuestos de longitud específica
- Relleno: Asegura que todos los fragmentos tengan la misma longitud
- Formato de Retorno: Diccionario con tensores de input_ids y attention_mask
- Características Importantes:
- El manejo de superposición evita la pérdida de contexto entre fragmentos
- Las máscaras de atención rastrean los tokens válidos vs. el relleno
- Mantiene la compatibilidad con los requisitos de entrada del modelo transformer
- Pasos de Verificación:
- Imprime el número y tamaño de los fragmentos
- Muestra el contenido del primer fragmento
- Demuestra la superposición entre fragmentos consecutivos
Salida:
Number of chunks: 3
Chunk size: torch.Size([3, 128])
First chunk content:
This is a very long text that needs to be split into chunks. This is a very long text that needs to be split...
Overlap demonstration:
End of first chunk: split into chunks.
Start of second chunk: chunks. This is a v
El número exacto de fragmentos y contenido puede variar según la tokenización real, pero esto demuestra los componentes clave de salida mostrando:
- El número de fragmentos creados a partir del texto de entrada
- La dimensión del tensor de fragmentos
- Una muestra del contenido del primer fragmento
- La región superpuesta entre fragmentos consecutivos
3.1.3 Preprocesamiento para Tareas Específicas
Las diferentes tareas de PLN requieren pasos específicos de preprocesamiento para garantizar un rendimiento óptimo del modelo. Esta fase de preprocesamiento es crucial ya que transforma los datos de texto sin procesar en un formato que los modelos transformer pueden procesar eficazmente. El pipeline de preprocesamiento debe diseñarse cuidadosamente para manejar las características únicas de cada tarea mientras mantiene la integridad de los datos y la compatibilidad del modelo.
Los pasos exactos de preprocesamiento varían significativamente según varios factores clave:
- Tipo de Tarea:
- Las tareas de clasificación requieren conjuntos de datos equilibrados y codificación apropiada de etiquetas
- Las tareas de generación necesitan un manejo cuidadoso de tokens de inicio/fin y formato de secuencias
- Las tareas de traducción deben alinear efectivamente los pares de idiomas fuente y destino
- Las tareas de respuesta a preguntas requieren un formato adecuado de contexto y pregunta
- Arquitectura del Modelo:
- Los modelos basados en BERT necesitan tokens especiales [CLS] y [SEP]
- Los modelos GPT requieren atención específica a los tokens de fin de secuencia
- Los modelos T5 necesitan prefijos específicos para cada tarea
- Requisitos del Conjunto de Datos:
- Estándares de limpieza y normalización de datos
- Manejo de caracteres especiales y formato
- Procesamiento de terminología específica del dominio
Los pasos comunes de preprocesamiento forman la base de cualquier pipeline de PLN:
- Tokenización: Convertir texto en tokens que el modelo pueda procesar
- Nivel de palabra: Dividir texto en palabras individuales
- Nivel de subpalabra: Dividir palabras en subunidades significativas
- Nivel de carácter: Procesar texto como caracteres individuales
- Ajuste de Longitud de Secuencia:
- Rellenar secuencias más cortas a una longitud fija
- Truncar secuencias más largas para ajustarse a las restricciones del modelo
- Implementar estrategias de procesamiento por lotes dinámico
- Codificación de Etiquetas:
- Convertir etiquetas categóricas a formato numérico
- Implementar codificación one-hot cuando sea apropiado
- Manejar escenarios multi-etiqueta
- Manejo de Tokens Especiales:
- Agregar tokens específicos de la tarea
- Gestionar tokens separadores y de clasificación
- Implementar estrategias de enmascaramiento
Además, las consideraciones específicas de cada tarea requieren atención cuidadosa:
- Tareas de Clasificación:
- Manejar el desequilibrio de clases mediante muestreo o ponderación
- Implementar estrategias de estratificación
- Procesamiento de Documentos Largos:
- Implementar ventanas deslizantes con superposición apropiada
- Gestionar la segmentación de documentos
- Mantener el contexto entre segmentos
Aquí hay ejemplos para dos tareas comunes:
Clasificación de Texto:
Para la clasificación, las entradas de texto necesitan ser tokenizadas, y sus etiquetas correspondientes deben ser codificadas.
from sklearn.preprocessing import LabelEncoder
import torch
from transformers import AutoTokenizer
import numpy as np
# Initialize tokenizer (e.g., BERT)
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
# Sample data
texts = [
"This movie was amazing!",
"I did not like the ending.",
"A masterpiece of modern cinema",
"Waste of time and money",
"It was just okay, nothing special"
]
labels = ["positive", "negative", "positive", "negative", "neutral"]
# Tokenize the texts with attention masks
tokenized_texts = tokenizer(
texts,
padding="max_length",
truncation=True,
max_length=32,
return_tensors="pt",
return_attention_mask=True
)
# Encode the labels
label_encoder = LabelEncoder()
encoded_labels = torch.tensor(label_encoder.fit_transform(labels))
# Create dataset dictionary
dataset = {
'input_ids': tokenized_texts['input_ids'],
'attention_mask': tokenized_texts['attention_mask'],
'labels': encoded_labels
}
# Print dataset information
print("Dataset Structure:")
print(f"Number of examples: {len(texts)}")
print(f"Input shape: {dataset['input_ids'].shape}")
print(f"Attention mask shape: {dataset['attention_mask'].shape}")
print(f"Labels shape: {dataset['labels'].shape}")
print("\nLabel mapping:", dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_))))
# Example of accessing first sample
first_text = tokenizer.decode(dataset['input_ids'][0])
print(f"\nFirst example:")
print(f"Text: {first_text}")
print(f"Label: {labels[0]} (encoded: {dataset['labels'][0]})")
print(f"Attention mask: {dataset['attention_mask'][0][:10]}...")
Desglose del Código:
- Importaciones y Configuración:
- sklearn.preprocessing.LabelEncoder para convertir etiquetas de texto a números
- torch para operaciones con tensores
- transformers para el tokenizador
- numpy para operaciones numéricas
- Preparación de Datos:
- Conjunto de datos ampliado con 5 ejemplos que cubren diferentes sentimientos
- Se agregó una clase "neutral" para demostrar la capacidad multiclase
- Pares estructurados de texto y etiquetas
- Tokenización:
- Utiliza el tokenizador BERT con max_length aumentado (32 tokens)
- Incluye relleno y truncamiento para longitudes consistentes
- Devuelve máscaras de atención para la entrada apropiada del transformer
- Procesamiento de Etiquetas:
- Convierte etiquetas de texto a formato numérico
- Crea un mapeo entre etiquetas originales y valores codificados
- Almacena etiquetas como tensores de PyTorch
- Creación del Conjunto de Datos:
- Combina input_ids, máscaras de atención y etiquetas
- Organiza los datos en un formato listo para el entrenamiento del modelo
- Mantiene la alineación entre entradas y etiquetas
- Visualización de Información:
- Muestra la estructura y dimensiones del conjunto de datos
- Muestra el mapeo de codificación de etiquetas
- Demuestra cómo acceder y decodificar ejemplos individuales
Salida Esperada:
Dataset Structure:
Number of examples: 5
Input shape: torch.Size([5, 32])
Attention mask shape: torch.Size([5, 32])
Labels shape: torch.Size([5])
Label mapping: {'negative': 0, 'neutral': 1, 'positive': 2}
First example:
Text: [CLS] this movie was amazing! [SEP] [PAD]...
Label: positive (encoded: 2)
Attention mask: tensor([1, 1, 1, 1, 1, 1, 0, 0, 0, 0]...)
Clasificación de Tokens (por ejemplo, Reconocimiento de Entidades Nombradas):
Para las tareas de clasificación de tokens, se debe asignar una etiqueta a cada token en la secuencia de entrada.
# Sample data for Named Entity Recognition (NER)
text = "Hugging Face is based in New York City."
labels = ["B-ORG", "I-ORG", "O", "O", "O", "B-LOC", "I-LOC"]
# Tokenize the text using the pre-initialized tokenizer
# padding="max_length": Ensures all sequences have the same length
# truncation=True: Cuts off text that exceeds max_length
# max_length=20: Maximum number of tokens allowed
# return_tensors="pt": Returns PyTorch tensors
tokenized_text = tokenizer(text, padding="max_length", truncation=True, max_length=20, return_tensors="pt")
# Align labels with tokenized text
# This is crucial because tokenization might split words into subwords
# - If a token starts with "##", it's a subword token (BERT-specific)
# - We assign -100 to subword tokens to ignore them in loss calculation
# - Other tokens retain their original NER labels
aligned_labels = [-100 if token.startswith("##") else label for token, label in zip(tokenized_text["input_ids"][0], labels)]
# Print the aligned labels to verify the alignment
print("Aligned Labels:", aligned_labels)
# Label explanation:
# B-ORG: Beginning of Organization entity (Hugging Face)
# I-ORG: Inside of Organization entity (Face)
# O: Outside any entity (is, based, in)
# B-LOC: Beginning of Location entity (New York)
# I-LOC: Inside of Location entity (York City)
Así es como se vería la salida:
Aligned Labels: ['B-ORG', 'I-ORG', 'O', 'O', 'O', 'B-LOC', 'I-LOC']
Esta salida muestra las etiquetas NER (Reconocimiento de Entidades Nombradas) alineadas con los tokens del texto de entrada "Hugging Face is based in New York City", donde:
- Hugging Face está etiquetado como una organización (B-ORG, I-ORG)
- New York City está etiquetado como una ubicación (B-LOC, I-LOC)
- Las palabras restantes (is, based, in) están etiquetadas como entidades externas (O)
El preprocesamiento de datos es un paso crucial en la preparación del texto para los modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Esta fase involucra varios componentes críticos:
Primero, la tokenización adecuada divide el texto en unidades significativas que el modelo puede procesar. Esto incluye el manejo de límites de palabras, caracteres especiales y estrategias de tokenización de subpalabras que ayudan a gestionar el tamaño del vocabulario mientras preservan el significado semántico.
Segundo, el relleno y truncamiento aseguran tamaños de entrada consistentes. El relleno agrega tokens especiales a secuencias más cortas para igualar una longitud objetivo, mientras que el truncamiento elimina cuidadosamente tokens excedentes de secuencias más largas mientras preserva la información esencial.
Tercero, la alineación de etiquetas con la entrada tokenizada es esencial para tareas de aprendizaje supervisado. Este proceso requiere una atención cuidadosa para mantener la relación entre los tokens de entrada y sus etiquetas correspondientes, especialmente cuando se trata de tokenización de subpalabras.
Además, el preprocesamiento incluye pasos cruciales como el manejo de palabras fuera del vocabulario, la gestión de tokens especiales (como [CLS] y [SEP] para modelos BERT), y la implementación de estrategias de enmascaramiento apropiadas para diferentes arquitecturas de modelos.
Dominar estas técnicas de preprocesamiento es vital ya que impactan directamente en el rendimiento del modelo. La implementación adecuada ayuda a evitar problemas comunes como etiquetas desalineadas, longitudes de secuencia inconsistentes o pérdida de información contextual. Cuando se realiza correctamente, estos pasos crean una entrada limpia y bien estructurada que permite a los modelos transformer alcanzar su rendimiento óptimo.
º
El ajuste fino de modelos transformer se ha convertido en el método estándar de la industria para adaptar modelos de lenguaje preentrenados a tareas específicas de PLN. Este proceso implica tomar un modelo que ha sido entrenado con un gran corpus de datos textuales generales y continuar su entrenamiento con datos específicos de la tarea para optimizar su rendimiento. Si bien los potentes modelos preentrenados como BERT (Representaciones Codificadas Bidireccionales de Transformers), GPT (Transformer Preentrenado Generativo) y T5 (Transformer de Transferencia Texto a Texto) demuestran capacidades impresionantes para comprender y generar lenguaje humano, típicamente requieren ajuste fino adicional para destacar en aplicaciones específicas, como análisis de sentimientos, clasificación de documentos o tareas especializadas de traducción.
El proceso de ajuste fino involucra varios componentes clave que exploraremos en detalle a lo largo de este capítulo. Comenzamos con el preprocesamiento de datos, que es crucial para asegurar que los datos de entrada estén correctamente formateados y tokenizados para los modelos transformer. Esto incluye la limpieza del texto, el manejo de caracteres especiales y la conversión de palabras en las representaciones numéricas que estos modelos pueden procesar.
Después del preprocesamiento, examinaremos técnicas avanzadas de ajuste fino que han revolucionado el campo. Estas incluyen LoRA (Adaptación de Bajo Rango), que adapta eficientemente modelos grandes actualizando un pequeño número de parámetros, y Prefix Tuning, que antepone tokens aprendibles a la entrada mientras mantiene el modelo original congelado. También cubriremos estrategias integrales de evaluación utilizando métricas estándar de la industria: BLEU (Estudio Subrogado Bilingüe) para medir la calidad de traducción, ROUGE (Evaluación de Resumen Orientada a la Recuperación) para evaluar la síntesis de texto, y BERTScore para la evaluación de similitud semántica.
Al final de este capítulo, poseerás una comprensión integral de todo el proceso de ajuste fino: desde la preparación de tus conjuntos de datos y la selección de estrategias de entrenamiento apropiadas, hasta la implementación de técnicas efectivas de ajuste fino y la evaluación rigurosa del rendimiento del modelo utilizando múltiples métricas. Este conocimiento te permitirá adaptar modelos transformer a tus casos de uso específicos mientras mantienes la eficiencia y precisión.
El preprocesamiento de datos es un paso crítico cuando se trabaja con modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Este proceso involucra varias transformaciones clave de datos textuales sin procesar. Primero, los transformers requieren que las entradas de texto sean tokenizadas - divididas en unidades más pequeñas como palabras o subpalabras - y convertidas en representaciones numéricas (típicamente vectores) que el modelo pueda procesar matemáticamente. Este proceso de tokenización puede utilizar diferentes enfoques como WordPiece, Codificación de Pares de Bytes (BPE), o SentencePiece, cada uno con sus propias ventajas para diferentes idiomas y casos de uso.
Más allá de la tokenización básica, las máscaras de atención juegan un papel crucial en el procesamiento eficiente. Estas máscaras binarias le indican al modelo qué tokens son datos de entrada reales y cuáles son tokens de relleno (utilizados para hacer que todas las secuencias en un lote tengan la misma longitud). Esta distinción es esencial porque evita que el modelo desperdicie recursos computacionales en tokens de relleno y asegura que el relleno no influya en la comprensión del contenido real por parte del modelo.
Además, la codificación adecuada de etiquetas es esencial para tareas de aprendizaje supervisado. Ya sea que estés trabajando en clasificación (convirtiendo etiquetas categóricas a valores numéricos), etiquetado de secuencias (asignando etiquetas a tokens individuales), o tareas más complejas, las etiquetas deben estar codificadas en un formato que se alinee con la arquitectura del modelo y los objetivos de entrenamiento.
En esta sección, cubriremos tres aspectos fundamentales del preprocesamiento:
- Tokenización y Relleno - Convertir texto en tokens y asegurar longitudes uniformes de secuencia
- Manejo de Secuencias Largas - Estrategias para gestionar texto que excede la longitud máxima de entrada del modelo
- Preprocesamiento para Tareas Específicas - Consideraciones y requisitos específicos de la tarea
3.1.1 Tokenización y Relleno
La tokenización es un paso fundamental de preprocesamiento que transforma el texto sin procesar en un formato que los modelos transformer pueden procesar. Este proceso divide el texto en unidades más pequeñas llamadas tokens, que pueden ser:
- Palabras (por ejemplo, "hola", "mundo")
- Subpalabras (por ejemplo, "jug", "##ar", donde "##" indica una continuación)
- Caracteres individuales (particularmente útil para idiomas basados en caracteres)
Por ejemplo, considera la oración "transformers are amazing". Usando tokenización de subpalabras, podría dividirse como:
- "transform" (palabra raíz)
- "##ers" (sufijo)
- "are" (palabra completa)
- "amazing" (palabra completa)
Estos tokens luego se mapean a IDs numéricos únicos usando una tabla de búsqueda de vocabulario. Por ejemplo:
- "transform" → 19081
- "##ers" → 2024
- "are" → 2003
- "amazing" → 6429
Esta representación numérica es esencial porque las redes neuronales solo pueden procesar números, no texto directamente.
El relleno es otro paso crucial de preprocesamiento que aborda un requisito técnico de los modelos transformer: el procesamiento por lotes. Dado que las redes neuronales procesan múltiples secuencias simultáneamente para mayor eficiencia, todas las secuencias en un lote deben tener la misma longitud. Así es como funciona el relleno:
- Primero, identificar la secuencia más larga en tu lote
- Agregar tokens especiales de relleno ([PAD] o 0) a las secuencias más cortas
- Crear una máscara de atención para indicar al modelo qué tokens son reales y cuáles son relleno
Por ejemplo, si tenemos estas secuencias:
- "Hello world" (2 tokens)
- "The quick brown fox jumps" (5 tokens)
El proceso de relleno haría:
- Hacer que ambas secuencias tengan 5 tokens de longitud
- "Hello world [PAD] [PAD] [PAD]"
- "The quick brown fox jumps"
Esto asegura un procesamiento uniforme mientras mantiene la integridad de las secuencias originales a través de máscaras de atención que le indican al modelo ignorar los tokens de relleno durante el cómputo.
Ejemplo: Tokenización y Relleno con BERT
from transformers import BertTokenizer
import torch
# Load the BERT tokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# Define sample texts of different lengths
texts = [
"Transformers are amazing!",
"They are used for many NLP tasks.",
"This is a longer sentence that will show padding in action."
]
# Tokenize the texts with different parameters
# 1. Basic tokenization
basic_tokens = tokenizer(texts[0])
print("\n1. Basic tokenization:")
print(f"Tokens: {tokenizer.convert_ids_to_tokens(basic_tokens['input_ids'])}")
# 2. Batch tokenization with padding
batch_tokenized = tokenizer(
texts,
padding=True, # Add padding
truncation=True, # Enable truncation
max_length=12, # Set maximum length
return_tensors="pt" # Return PyTorch tensors
)
print("\n2. Batch tokenization results:")
print("Input IDs:")
print(batch_tokenized["input_ids"])
print("\nAttention Masks:")
print(batch_tokenized["attention_mask"])
# 3. Decode back to text
print("\n3. Decoded text from tokens:")
for i in range(len(texts)):
decoded = tokenizer.decode(batch_tokenized["input_ids"][i])
print(f"Original: {texts[i]}")
print(f"Decoded: {decoded}\n")
Desglose Detallado:
- Importación y Configuración:
- Importamos tanto BertTokenizer como torch
- Inicializamos el tokenizador BERT con la variante sin distinción entre mayúsculas y minúsculas
- Tokenización Básica:
- Muestra cómo se tokeniza una sola oración
- Demuestra la conversión de tokens a texto para mejor comprensión
- Procesamiento por Lotes:
- Procesa múltiples oraciones de diferentes longitudes
- Utiliza relleno para hacer que todas las secuencias tengan longitud uniforme
- Establece max_length=12 para demostrar el truncamiento
- Parámetros Clave:
- padding=True: Agrega tokens de relleno a las secuencias más cortas
- truncation=True: Corta las secuencias más largas a max_length
- return_tensors="pt": Devuelve tensores PyTorch en lugar de listas
- Explicación de la Salida:
- input_ids: Representaciones numéricas de los tokens
- attention_mask: 1s para tokens reales, 0s para relleno
- El texto decodificado muestra cómo el modelo reconstruye la entrada original
Explicación:
input_ids
: Representación tokenizada del texto de entrada.attention_mask
: Máscara binaria que indica qué tokens son entrada real (1) y cuáles son relleno (0).
Salida:
1. Basic tokenization:
Tokens: ['[CLS]', 'transformers', 'are', 'amazing', '!', '[SEP]']
2. Batch tokenization results:
Input IDs:
tensor([[ 101, 2234, 2024, 6429, 999, 102, 0, 0, 0, 0,
0, 0],
[ 101, 2027, 2024, 2107, 2005, 2116, 3319, 2202, 999, 102,
0, 0],
[ 101, 2023, 2003, 1037, 2208, 6251, 2008, 2097, 4058, 1999,
2039, 102]])
Attention Masks:
tensor([[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])
3. Decoded text from tokens:
Original: Transformers are amazing!
Decoded: [CLS] transformers are amazing ! [SEP]
Original: They are used for many NLP tasks.
Decoded: [CLS] they are used for many nlp tasks . [SEP]
Original: This is a longer sentence that will show padding in action.
Decoded: [CLS] this is a longer sentence that will show [SEP]
3.1.2 Manejo de Secuencias Largas
Los transformers tienen una limitación en la longitud máxima de entrada debido a su mecanismo de auto-atención, que crece cuadráticamente con la longitud de la secuencia. Esta limitación existe porque el mecanismo de auto-atención calcula puntuaciones de atención entre cada par de tokens en la secuencia, resultando en una complejidad computacional de O(n²), donde n es la longitud de la secuencia. A medida que la longitud de la secuencia aumenta, tanto el uso de memoria como los requisitos computacionales aumentan dramáticamente.
Por ejemplo, BERT tiene una longitud máxima de secuencia de 512 tokens, mientras que los modelos GPT típicamente manejan 1024 o 2048 tokens. Esto significa que para BERT, procesar una secuencia de 512 tokens requiere calcular y almacenar una matriz de atención de 512 x 512 para cada cabeza de atención en cada capa del transformer. Los modelos GPT pueden manejar secuencias más largas pero aún enfrentan restricciones computacionales similares.
Al tratar con textos que exceden estos límites, hay dos enfoques principales:
- Truncamiento: Simplemente cortar el texto en la longitud máxima. Si bien es sencillo, esto puede perder información importante. Este enfoque funciona mejor cuando:
- La información más relevante aparece al principio del texto
- La tarea solo requiere comprender el contexto general en lugar de detalles específicos
- La velocidad de procesamiento es una prioridad sobre la completitud
- Fragmentación: Dividir el texto en segmentos superpuestos o no superpuestos que se ajusten al límite de longitud. Esto preserva toda la información pero requiere estrategias para combinar los resultados de múltiples fragmentos. Las estrategias comunes de fragmentación incluyen:
- Ventana deslizante: Crear fragmentos superpuestos con una longitud de paso fija
- División basada en oraciones: Dividir el texto en los límites naturales de las oraciones
- Procesamiento jerárquico: Procesar fragmentos individualmente y luego combinar resultados
La elección entre estos enfoques depende de tu tarea específica - el truncamiento puede funcionar bien para clasificación, mientras que la fragmentación es a menudo necesaria para tareas como resumen de documentos o respuesta a preguntas. Por ejemplo, en el análisis de sentimientos, el sentimiento general podría captarse lo suficientemente bien en los primeros cientos de tokens, haciendo el truncamiento aceptable. Sin embargo, para tareas como resumen de documentos o respuesta a preguntas donde la información importante podría estar en cualquier parte del texto, la fragmentación se vuelve esencial para asegurar que no se pierda información crítica.
Ejemplo: Truncamiento de Secuencias Largas
# Define a long text sample
long_text = "Transformers are incredibly versatile models that have revolutionized the field of NLP. " * 20
# Initialize tokenizer
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 1. Basic tokenization without truncation
tokenized_full = tokenizer(long_text, truncation=False, return_tensors="pt")
print("\n1. Full text tokenization:")
print(f"Original sequence length: {tokenized_full['input_ids'].shape[1]} tokens")
# 2. Tokenization with truncation
tokenized_truncated = tokenizer(
long_text,
truncation=True,
max_length=512,
return_tensors="pt"
)
print("\n2. Truncated tokenization:")
print(f"Truncated sequence length: {tokenized_truncated['input_ids'].shape[1]} tokens")
# 3. Sliding window approach
def create_sliding_windows(text, window_size=256, stride=128):
tokenized = tokenizer(text, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
windows = []
for i in range(0, len(input_ids), stride):
window = input_ids[i:i + window_size]
if len(window) < window_size: # Pad last window if needed
padding = window_size - len(window)
window = torch.cat([window, torch.zeros(padding, dtype=torch.long)])
windows.append(window)
return torch.stack(windows)
# Apply sliding window
sliding_windows = create_sliding_windows(long_text)
print("\n3. Sliding window approach:")
print(f"Number of windows: {len(sliding_windows)}")
print(f"Window shape: {sliding_windows.shape}")
# 4. Demonstrate window content
print("\n4. Content of first window:")
first_window_text = tokenizer.decode(sliding_windows[0])
print(first_window_text[:100] + "...")
Desglose del Código:
- Preparación del Texto:
- Crea una muestra larga de texto repitiendo una oración 20 veces
- Inicializa el tokenizador BERT para el procesamiento
- Tokenización Completa:
- Muestra la longitud de la secuencia original sin truncamiento
- Ayuda a comprender cuánto texto excede los límites del modelo
- Enfoque de Truncamiento:
- Implementa el truncamiento estándar a 512 tokens (límite de BERT)
- Demuestra la forma básica de manejar secuencias largas
- Implementación de Ventana Deslizante:
- Crea ventanas superpuestas de texto (tamaño_ventana=256, paso=128)
- Permite procesar todo el texto en fragmentos manejables
- Incluye relleno para la última ventana si es necesario
- Visualización del Contenido de la Ventana:
- Muestra el contenido real de la primera ventana
- Ayuda a verificar que el proceso de ventanas funcione correctamente
Salida:
1. Full text tokenization:
Original sequence length: ~400 tokens
2. Truncated tokenization:
Truncated sequence length: 512 tokens
3. Sliding window approach:
Number of windows: ~4
Window shape: torch.Size([4, 256])
4. Content of first window:
[CLS] transformers are incredibly versatile models that have revolutionized the field of nlp. transformers are...
Nota: Los números exactos variarían según la tokenización real de la oración repetida, pero esto representa la estructura esperada de la salida según la lógica del código.
Ejemplo: División de Texto Largo en Fragmentos
# Function to split long text into chunks with overlap
def split_text_into_chunks(text, max_length=128, overlap=20):
# Tokenize the text
tokenized = tokenizer(text, truncation=False, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
attention_mask = tokenized["attention_mask"][0]
chunks = []
chunk_masks = []
# Create chunks with overlap
for i in range(0, len(input_ids), max_length - overlap):
# Extract chunk
chunk = input_ids[i:i + max_length]
mask = attention_mask[i:i + max_length]
# Pad if necessary
if len(chunk) < max_length:
padding_length = max_length - len(chunk)
chunk = torch.cat([chunk, torch.zeros(padding_length, dtype=torch.long)])
mask = torch.cat([mask, torch.zeros(padding_length, dtype=torch.long)])
chunks.append(chunk)
chunk_masks.append(mask)
return {
"input_ids": torch.stack(chunks),
"attention_mask": torch.stack(chunk_masks)
}
# Example usage
long_text = "This is a very long text that needs to be split into chunks. " * 20
chunks = split_text_into_chunks(long_text, max_length=128, overlap=20)
# Print information about chunks
print(f"Number of chunks: {len(chunks['input_ids'])}")
print(f"Chunk size: {chunks['input_ids'].shape}")
# Decode and print first chunk to verify content
first_chunk = tokenizer.decode(chunks['input_ids'][0])
print("\nFirst chunk content:")
print(first_chunk[:100], "...")
# Print overlap between chunks to verify
if len(chunks['input_ids']) > 1:
overlap_first = tokenizer.decode(chunks['input_ids'][0][-20:])
overlap_second = tokenizer.decode(chunks['input_ids'][1][:20])
print("\nOverlap demonstration:")
print("End of first chunk:", overlap_first)
print("Start of second chunk:", overlap_second)
Desglose del Código:
- Parámetros de la Función:
- max_length: Número máximo de tokens por fragmento (predeterminado: 128)
- overlap: Número de tokens superpuestos entre fragmentos (predeterminado: 20)
- Componentes Principales:
- Tokenización: Convierte el texto de entrada en IDs de tokens y máscaras de atención
- Creación de Fragmentos: Crea fragmentos superpuestos de longitud específica
- Relleno: Asegura que todos los fragmentos tengan la misma longitud
- Formato de Retorno: Diccionario con tensores de input_ids y attention_mask
- Características Importantes:
- El manejo de superposición evita la pérdida de contexto entre fragmentos
- Las máscaras de atención rastrean los tokens válidos vs. el relleno
- Mantiene la compatibilidad con los requisitos de entrada del modelo transformer
- Pasos de Verificación:
- Imprime el número y tamaño de los fragmentos
- Muestra el contenido del primer fragmento
- Demuestra la superposición entre fragmentos consecutivos
Salida:
Number of chunks: 3
Chunk size: torch.Size([3, 128])
First chunk content:
This is a very long text that needs to be split into chunks. This is a very long text that needs to be split...
Overlap demonstration:
End of first chunk: split into chunks.
Start of second chunk: chunks. This is a v
El número exacto de fragmentos y contenido puede variar según la tokenización real, pero esto demuestra los componentes clave de salida mostrando:
- El número de fragmentos creados a partir del texto de entrada
- La dimensión del tensor de fragmentos
- Una muestra del contenido del primer fragmento
- La región superpuesta entre fragmentos consecutivos
3.1.3 Preprocesamiento para Tareas Específicas
Las diferentes tareas de PLN requieren pasos específicos de preprocesamiento para garantizar un rendimiento óptimo del modelo. Esta fase de preprocesamiento es crucial ya que transforma los datos de texto sin procesar en un formato que los modelos transformer pueden procesar eficazmente. El pipeline de preprocesamiento debe diseñarse cuidadosamente para manejar las características únicas de cada tarea mientras mantiene la integridad de los datos y la compatibilidad del modelo.
Los pasos exactos de preprocesamiento varían significativamente según varios factores clave:
- Tipo de Tarea:
- Las tareas de clasificación requieren conjuntos de datos equilibrados y codificación apropiada de etiquetas
- Las tareas de generación necesitan un manejo cuidadoso de tokens de inicio/fin y formato de secuencias
- Las tareas de traducción deben alinear efectivamente los pares de idiomas fuente y destino
- Las tareas de respuesta a preguntas requieren un formato adecuado de contexto y pregunta
- Arquitectura del Modelo:
- Los modelos basados en BERT necesitan tokens especiales [CLS] y [SEP]
- Los modelos GPT requieren atención específica a los tokens de fin de secuencia
- Los modelos T5 necesitan prefijos específicos para cada tarea
- Requisitos del Conjunto de Datos:
- Estándares de limpieza y normalización de datos
- Manejo de caracteres especiales y formato
- Procesamiento de terminología específica del dominio
Los pasos comunes de preprocesamiento forman la base de cualquier pipeline de PLN:
- Tokenización: Convertir texto en tokens que el modelo pueda procesar
- Nivel de palabra: Dividir texto en palabras individuales
- Nivel de subpalabra: Dividir palabras en subunidades significativas
- Nivel de carácter: Procesar texto como caracteres individuales
- Ajuste de Longitud de Secuencia:
- Rellenar secuencias más cortas a una longitud fija
- Truncar secuencias más largas para ajustarse a las restricciones del modelo
- Implementar estrategias de procesamiento por lotes dinámico
- Codificación de Etiquetas:
- Convertir etiquetas categóricas a formato numérico
- Implementar codificación one-hot cuando sea apropiado
- Manejar escenarios multi-etiqueta
- Manejo de Tokens Especiales:
- Agregar tokens específicos de la tarea
- Gestionar tokens separadores y de clasificación
- Implementar estrategias de enmascaramiento
Además, las consideraciones específicas de cada tarea requieren atención cuidadosa:
- Tareas de Clasificación:
- Manejar el desequilibrio de clases mediante muestreo o ponderación
- Implementar estrategias de estratificación
- Procesamiento de Documentos Largos:
- Implementar ventanas deslizantes con superposición apropiada
- Gestionar la segmentación de documentos
- Mantener el contexto entre segmentos
Aquí hay ejemplos para dos tareas comunes:
Clasificación de Texto:
Para la clasificación, las entradas de texto necesitan ser tokenizadas, y sus etiquetas correspondientes deben ser codificadas.
from sklearn.preprocessing import LabelEncoder
import torch
from transformers import AutoTokenizer
import numpy as np
# Initialize tokenizer (e.g., BERT)
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
# Sample data
texts = [
"This movie was amazing!",
"I did not like the ending.",
"A masterpiece of modern cinema",
"Waste of time and money",
"It was just okay, nothing special"
]
labels = ["positive", "negative", "positive", "negative", "neutral"]
# Tokenize the texts with attention masks
tokenized_texts = tokenizer(
texts,
padding="max_length",
truncation=True,
max_length=32,
return_tensors="pt",
return_attention_mask=True
)
# Encode the labels
label_encoder = LabelEncoder()
encoded_labels = torch.tensor(label_encoder.fit_transform(labels))
# Create dataset dictionary
dataset = {
'input_ids': tokenized_texts['input_ids'],
'attention_mask': tokenized_texts['attention_mask'],
'labels': encoded_labels
}
# Print dataset information
print("Dataset Structure:")
print(f"Number of examples: {len(texts)}")
print(f"Input shape: {dataset['input_ids'].shape}")
print(f"Attention mask shape: {dataset['attention_mask'].shape}")
print(f"Labels shape: {dataset['labels'].shape}")
print("\nLabel mapping:", dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_))))
# Example of accessing first sample
first_text = tokenizer.decode(dataset['input_ids'][0])
print(f"\nFirst example:")
print(f"Text: {first_text}")
print(f"Label: {labels[0]} (encoded: {dataset['labels'][0]})")
print(f"Attention mask: {dataset['attention_mask'][0][:10]}...")
Desglose del Código:
- Importaciones y Configuración:
- sklearn.preprocessing.LabelEncoder para convertir etiquetas de texto a números
- torch para operaciones con tensores
- transformers para el tokenizador
- numpy para operaciones numéricas
- Preparación de Datos:
- Conjunto de datos ampliado con 5 ejemplos que cubren diferentes sentimientos
- Se agregó una clase "neutral" para demostrar la capacidad multiclase
- Pares estructurados de texto y etiquetas
- Tokenización:
- Utiliza el tokenizador BERT con max_length aumentado (32 tokens)
- Incluye relleno y truncamiento para longitudes consistentes
- Devuelve máscaras de atención para la entrada apropiada del transformer
- Procesamiento de Etiquetas:
- Convierte etiquetas de texto a formato numérico
- Crea un mapeo entre etiquetas originales y valores codificados
- Almacena etiquetas como tensores de PyTorch
- Creación del Conjunto de Datos:
- Combina input_ids, máscaras de atención y etiquetas
- Organiza los datos en un formato listo para el entrenamiento del modelo
- Mantiene la alineación entre entradas y etiquetas
- Visualización de Información:
- Muestra la estructura y dimensiones del conjunto de datos
- Muestra el mapeo de codificación de etiquetas
- Demuestra cómo acceder y decodificar ejemplos individuales
Salida Esperada:
Dataset Structure:
Number of examples: 5
Input shape: torch.Size([5, 32])
Attention mask shape: torch.Size([5, 32])
Labels shape: torch.Size([5])
Label mapping: {'negative': 0, 'neutral': 1, 'positive': 2}
First example:
Text: [CLS] this movie was amazing! [SEP] [PAD]...
Label: positive (encoded: 2)
Attention mask: tensor([1, 1, 1, 1, 1, 1, 0, 0, 0, 0]...)
Clasificación de Tokens (por ejemplo, Reconocimiento de Entidades Nombradas):
Para las tareas de clasificación de tokens, se debe asignar una etiqueta a cada token en la secuencia de entrada.
# Sample data for Named Entity Recognition (NER)
text = "Hugging Face is based in New York City."
labels = ["B-ORG", "I-ORG", "O", "O", "O", "B-LOC", "I-LOC"]
# Tokenize the text using the pre-initialized tokenizer
# padding="max_length": Ensures all sequences have the same length
# truncation=True: Cuts off text that exceeds max_length
# max_length=20: Maximum number of tokens allowed
# return_tensors="pt": Returns PyTorch tensors
tokenized_text = tokenizer(text, padding="max_length", truncation=True, max_length=20, return_tensors="pt")
# Align labels with tokenized text
# This is crucial because tokenization might split words into subwords
# - If a token starts with "##", it's a subword token (BERT-specific)
# - We assign -100 to subword tokens to ignore them in loss calculation
# - Other tokens retain their original NER labels
aligned_labels = [-100 if token.startswith("##") else label for token, label in zip(tokenized_text["input_ids"][0], labels)]
# Print the aligned labels to verify the alignment
print("Aligned Labels:", aligned_labels)
# Label explanation:
# B-ORG: Beginning of Organization entity (Hugging Face)
# I-ORG: Inside of Organization entity (Face)
# O: Outside any entity (is, based, in)
# B-LOC: Beginning of Location entity (New York)
# I-LOC: Inside of Location entity (York City)
Así es como se vería la salida:
Aligned Labels: ['B-ORG', 'I-ORG', 'O', 'O', 'O', 'B-LOC', 'I-LOC']
Esta salida muestra las etiquetas NER (Reconocimiento de Entidades Nombradas) alineadas con los tokens del texto de entrada "Hugging Face is based in New York City", donde:
- Hugging Face está etiquetado como una organización (B-ORG, I-ORG)
- New York City está etiquetado como una ubicación (B-LOC, I-LOC)
- Las palabras restantes (is, based, in) están etiquetadas como entidades externas (O)
El preprocesamiento de datos es un paso crucial en la preparación del texto para los modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Esta fase involucra varios componentes críticos:
Primero, la tokenización adecuada divide el texto en unidades significativas que el modelo puede procesar. Esto incluye el manejo de límites de palabras, caracteres especiales y estrategias de tokenización de subpalabras que ayudan a gestionar el tamaño del vocabulario mientras preservan el significado semántico.
Segundo, el relleno y truncamiento aseguran tamaños de entrada consistentes. El relleno agrega tokens especiales a secuencias más cortas para igualar una longitud objetivo, mientras que el truncamiento elimina cuidadosamente tokens excedentes de secuencias más largas mientras preserva la información esencial.
Tercero, la alineación de etiquetas con la entrada tokenizada es esencial para tareas de aprendizaje supervisado. Este proceso requiere una atención cuidadosa para mantener la relación entre los tokens de entrada y sus etiquetas correspondientes, especialmente cuando se trata de tokenización de subpalabras.
Además, el preprocesamiento incluye pasos cruciales como el manejo de palabras fuera del vocabulario, la gestión de tokens especiales (como [CLS] y [SEP] para modelos BERT), y la implementación de estrategias de enmascaramiento apropiadas para diferentes arquitecturas de modelos.
Dominar estas técnicas de preprocesamiento es vital ya que impactan directamente en el rendimiento del modelo. La implementación adecuada ayuda a evitar problemas comunes como etiquetas desalineadas, longitudes de secuencia inconsistentes o pérdida de información contextual. Cuando se realiza correctamente, estos pasos crean una entrada limpia y bien estructurada que permite a los modelos transformer alcanzar su rendimiento óptimo.
º
El ajuste fino de modelos transformer se ha convertido en el método estándar de la industria para adaptar modelos de lenguaje preentrenados a tareas específicas de PLN. Este proceso implica tomar un modelo que ha sido entrenado con un gran corpus de datos textuales generales y continuar su entrenamiento con datos específicos de la tarea para optimizar su rendimiento. Si bien los potentes modelos preentrenados como BERT (Representaciones Codificadas Bidireccionales de Transformers), GPT (Transformer Preentrenado Generativo) y T5 (Transformer de Transferencia Texto a Texto) demuestran capacidades impresionantes para comprender y generar lenguaje humano, típicamente requieren ajuste fino adicional para destacar en aplicaciones específicas, como análisis de sentimientos, clasificación de documentos o tareas especializadas de traducción.
El proceso de ajuste fino involucra varios componentes clave que exploraremos en detalle a lo largo de este capítulo. Comenzamos con el preprocesamiento de datos, que es crucial para asegurar que los datos de entrada estén correctamente formateados y tokenizados para los modelos transformer. Esto incluye la limpieza del texto, el manejo de caracteres especiales y la conversión de palabras en las representaciones numéricas que estos modelos pueden procesar.
Después del preprocesamiento, examinaremos técnicas avanzadas de ajuste fino que han revolucionado el campo. Estas incluyen LoRA (Adaptación de Bajo Rango), que adapta eficientemente modelos grandes actualizando un pequeño número de parámetros, y Prefix Tuning, que antepone tokens aprendibles a la entrada mientras mantiene el modelo original congelado. También cubriremos estrategias integrales de evaluación utilizando métricas estándar de la industria: BLEU (Estudio Subrogado Bilingüe) para medir la calidad de traducción, ROUGE (Evaluación de Resumen Orientada a la Recuperación) para evaluar la síntesis de texto, y BERTScore para la evaluación de similitud semántica.
Al final de este capítulo, poseerás una comprensión integral de todo el proceso de ajuste fino: desde la preparación de tus conjuntos de datos y la selección de estrategias de entrenamiento apropiadas, hasta la implementación de técnicas efectivas de ajuste fino y la evaluación rigurosa del rendimiento del modelo utilizando múltiples métricas. Este conocimiento te permitirá adaptar modelos transformer a tus casos de uso específicos mientras mantienes la eficiencia y precisión.
El preprocesamiento de datos es un paso crítico cuando se trabaja con modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Este proceso involucra varias transformaciones clave de datos textuales sin procesar. Primero, los transformers requieren que las entradas de texto sean tokenizadas - divididas en unidades más pequeñas como palabras o subpalabras - y convertidas en representaciones numéricas (típicamente vectores) que el modelo pueda procesar matemáticamente. Este proceso de tokenización puede utilizar diferentes enfoques como WordPiece, Codificación de Pares de Bytes (BPE), o SentencePiece, cada uno con sus propias ventajas para diferentes idiomas y casos de uso.
Más allá de la tokenización básica, las máscaras de atención juegan un papel crucial en el procesamiento eficiente. Estas máscaras binarias le indican al modelo qué tokens son datos de entrada reales y cuáles son tokens de relleno (utilizados para hacer que todas las secuencias en un lote tengan la misma longitud). Esta distinción es esencial porque evita que el modelo desperdicie recursos computacionales en tokens de relleno y asegura que el relleno no influya en la comprensión del contenido real por parte del modelo.
Además, la codificación adecuada de etiquetas es esencial para tareas de aprendizaje supervisado. Ya sea que estés trabajando en clasificación (convirtiendo etiquetas categóricas a valores numéricos), etiquetado de secuencias (asignando etiquetas a tokens individuales), o tareas más complejas, las etiquetas deben estar codificadas en un formato que se alinee con la arquitectura del modelo y los objetivos de entrenamiento.
En esta sección, cubriremos tres aspectos fundamentales del preprocesamiento:
- Tokenización y Relleno - Convertir texto en tokens y asegurar longitudes uniformes de secuencia
- Manejo de Secuencias Largas - Estrategias para gestionar texto que excede la longitud máxima de entrada del modelo
- Preprocesamiento para Tareas Específicas - Consideraciones y requisitos específicos de la tarea
3.1.1 Tokenización y Relleno
La tokenización es un paso fundamental de preprocesamiento que transforma el texto sin procesar en un formato que los modelos transformer pueden procesar. Este proceso divide el texto en unidades más pequeñas llamadas tokens, que pueden ser:
- Palabras (por ejemplo, "hola", "mundo")
- Subpalabras (por ejemplo, "jug", "##ar", donde "##" indica una continuación)
- Caracteres individuales (particularmente útil para idiomas basados en caracteres)
Por ejemplo, considera la oración "transformers are amazing". Usando tokenización de subpalabras, podría dividirse como:
- "transform" (palabra raíz)
- "##ers" (sufijo)
- "are" (palabra completa)
- "amazing" (palabra completa)
Estos tokens luego se mapean a IDs numéricos únicos usando una tabla de búsqueda de vocabulario. Por ejemplo:
- "transform" → 19081
- "##ers" → 2024
- "are" → 2003
- "amazing" → 6429
Esta representación numérica es esencial porque las redes neuronales solo pueden procesar números, no texto directamente.
El relleno es otro paso crucial de preprocesamiento que aborda un requisito técnico de los modelos transformer: el procesamiento por lotes. Dado que las redes neuronales procesan múltiples secuencias simultáneamente para mayor eficiencia, todas las secuencias en un lote deben tener la misma longitud. Así es como funciona el relleno:
- Primero, identificar la secuencia más larga en tu lote
- Agregar tokens especiales de relleno ([PAD] o 0) a las secuencias más cortas
- Crear una máscara de atención para indicar al modelo qué tokens son reales y cuáles son relleno
Por ejemplo, si tenemos estas secuencias:
- "Hello world" (2 tokens)
- "The quick brown fox jumps" (5 tokens)
El proceso de relleno haría:
- Hacer que ambas secuencias tengan 5 tokens de longitud
- "Hello world [PAD] [PAD] [PAD]"
- "The quick brown fox jumps"
Esto asegura un procesamiento uniforme mientras mantiene la integridad de las secuencias originales a través de máscaras de atención que le indican al modelo ignorar los tokens de relleno durante el cómputo.
Ejemplo: Tokenización y Relleno con BERT
from transformers import BertTokenizer
import torch
# Load the BERT tokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
# Define sample texts of different lengths
texts = [
"Transformers are amazing!",
"They are used for many NLP tasks.",
"This is a longer sentence that will show padding in action."
]
# Tokenize the texts with different parameters
# 1. Basic tokenization
basic_tokens = tokenizer(texts[0])
print("\n1. Basic tokenization:")
print(f"Tokens: {tokenizer.convert_ids_to_tokens(basic_tokens['input_ids'])}")
# 2. Batch tokenization with padding
batch_tokenized = tokenizer(
texts,
padding=True, # Add padding
truncation=True, # Enable truncation
max_length=12, # Set maximum length
return_tensors="pt" # Return PyTorch tensors
)
print("\n2. Batch tokenization results:")
print("Input IDs:")
print(batch_tokenized["input_ids"])
print("\nAttention Masks:")
print(batch_tokenized["attention_mask"])
# 3. Decode back to text
print("\n3. Decoded text from tokens:")
for i in range(len(texts)):
decoded = tokenizer.decode(batch_tokenized["input_ids"][i])
print(f"Original: {texts[i]}")
print(f"Decoded: {decoded}\n")
Desglose Detallado:
- Importación y Configuración:
- Importamos tanto BertTokenizer como torch
- Inicializamos el tokenizador BERT con la variante sin distinción entre mayúsculas y minúsculas
- Tokenización Básica:
- Muestra cómo se tokeniza una sola oración
- Demuestra la conversión de tokens a texto para mejor comprensión
- Procesamiento por Lotes:
- Procesa múltiples oraciones de diferentes longitudes
- Utiliza relleno para hacer que todas las secuencias tengan longitud uniforme
- Establece max_length=12 para demostrar el truncamiento
- Parámetros Clave:
- padding=True: Agrega tokens de relleno a las secuencias más cortas
- truncation=True: Corta las secuencias más largas a max_length
- return_tensors="pt": Devuelve tensores PyTorch en lugar de listas
- Explicación de la Salida:
- input_ids: Representaciones numéricas de los tokens
- attention_mask: 1s para tokens reales, 0s para relleno
- El texto decodificado muestra cómo el modelo reconstruye la entrada original
Explicación:
input_ids
: Representación tokenizada del texto de entrada.attention_mask
: Máscara binaria que indica qué tokens son entrada real (1) y cuáles son relleno (0).
Salida:
1. Basic tokenization:
Tokens: ['[CLS]', 'transformers', 'are', 'amazing', '!', '[SEP]']
2. Batch tokenization results:
Input IDs:
tensor([[ 101, 2234, 2024, 6429, 999, 102, 0, 0, 0, 0,
0, 0],
[ 101, 2027, 2024, 2107, 2005, 2116, 3319, 2202, 999, 102,
0, 0],
[ 101, 2023, 2003, 1037, 2208, 6251, 2008, 2097, 4058, 1999,
2039, 102]])
Attention Masks:
tensor([[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])
3. Decoded text from tokens:
Original: Transformers are amazing!
Decoded: [CLS] transformers are amazing ! [SEP]
Original: They are used for many NLP tasks.
Decoded: [CLS] they are used for many nlp tasks . [SEP]
Original: This is a longer sentence that will show padding in action.
Decoded: [CLS] this is a longer sentence that will show [SEP]
3.1.2 Manejo de Secuencias Largas
Los transformers tienen una limitación en la longitud máxima de entrada debido a su mecanismo de auto-atención, que crece cuadráticamente con la longitud de la secuencia. Esta limitación existe porque el mecanismo de auto-atención calcula puntuaciones de atención entre cada par de tokens en la secuencia, resultando en una complejidad computacional de O(n²), donde n es la longitud de la secuencia. A medida que la longitud de la secuencia aumenta, tanto el uso de memoria como los requisitos computacionales aumentan dramáticamente.
Por ejemplo, BERT tiene una longitud máxima de secuencia de 512 tokens, mientras que los modelos GPT típicamente manejan 1024 o 2048 tokens. Esto significa que para BERT, procesar una secuencia de 512 tokens requiere calcular y almacenar una matriz de atención de 512 x 512 para cada cabeza de atención en cada capa del transformer. Los modelos GPT pueden manejar secuencias más largas pero aún enfrentan restricciones computacionales similares.
Al tratar con textos que exceden estos límites, hay dos enfoques principales:
- Truncamiento: Simplemente cortar el texto en la longitud máxima. Si bien es sencillo, esto puede perder información importante. Este enfoque funciona mejor cuando:
- La información más relevante aparece al principio del texto
- La tarea solo requiere comprender el contexto general en lugar de detalles específicos
- La velocidad de procesamiento es una prioridad sobre la completitud
- Fragmentación: Dividir el texto en segmentos superpuestos o no superpuestos que se ajusten al límite de longitud. Esto preserva toda la información pero requiere estrategias para combinar los resultados de múltiples fragmentos. Las estrategias comunes de fragmentación incluyen:
- Ventana deslizante: Crear fragmentos superpuestos con una longitud de paso fija
- División basada en oraciones: Dividir el texto en los límites naturales de las oraciones
- Procesamiento jerárquico: Procesar fragmentos individualmente y luego combinar resultados
La elección entre estos enfoques depende de tu tarea específica - el truncamiento puede funcionar bien para clasificación, mientras que la fragmentación es a menudo necesaria para tareas como resumen de documentos o respuesta a preguntas. Por ejemplo, en el análisis de sentimientos, el sentimiento general podría captarse lo suficientemente bien en los primeros cientos de tokens, haciendo el truncamiento aceptable. Sin embargo, para tareas como resumen de documentos o respuesta a preguntas donde la información importante podría estar en cualquier parte del texto, la fragmentación se vuelve esencial para asegurar que no se pierda información crítica.
Ejemplo: Truncamiento de Secuencias Largas
# Define a long text sample
long_text = "Transformers are incredibly versatile models that have revolutionized the field of NLP. " * 20
# Initialize tokenizer
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 1. Basic tokenization without truncation
tokenized_full = tokenizer(long_text, truncation=False, return_tensors="pt")
print("\n1. Full text tokenization:")
print(f"Original sequence length: {tokenized_full['input_ids'].shape[1]} tokens")
# 2. Tokenization with truncation
tokenized_truncated = tokenizer(
long_text,
truncation=True,
max_length=512,
return_tensors="pt"
)
print("\n2. Truncated tokenization:")
print(f"Truncated sequence length: {tokenized_truncated['input_ids'].shape[1]} tokens")
# 3. Sliding window approach
def create_sliding_windows(text, window_size=256, stride=128):
tokenized = tokenizer(text, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
windows = []
for i in range(0, len(input_ids), stride):
window = input_ids[i:i + window_size]
if len(window) < window_size: # Pad last window if needed
padding = window_size - len(window)
window = torch.cat([window, torch.zeros(padding, dtype=torch.long)])
windows.append(window)
return torch.stack(windows)
# Apply sliding window
sliding_windows = create_sliding_windows(long_text)
print("\n3. Sliding window approach:")
print(f"Number of windows: {len(sliding_windows)}")
print(f"Window shape: {sliding_windows.shape}")
# 4. Demonstrate window content
print("\n4. Content of first window:")
first_window_text = tokenizer.decode(sliding_windows[0])
print(first_window_text[:100] + "...")
Desglose del Código:
- Preparación del Texto:
- Crea una muestra larga de texto repitiendo una oración 20 veces
- Inicializa el tokenizador BERT para el procesamiento
- Tokenización Completa:
- Muestra la longitud de la secuencia original sin truncamiento
- Ayuda a comprender cuánto texto excede los límites del modelo
- Enfoque de Truncamiento:
- Implementa el truncamiento estándar a 512 tokens (límite de BERT)
- Demuestra la forma básica de manejar secuencias largas
- Implementación de Ventana Deslizante:
- Crea ventanas superpuestas de texto (tamaño_ventana=256, paso=128)
- Permite procesar todo el texto en fragmentos manejables
- Incluye relleno para la última ventana si es necesario
- Visualización del Contenido de la Ventana:
- Muestra el contenido real de la primera ventana
- Ayuda a verificar que el proceso de ventanas funcione correctamente
Salida:
1. Full text tokenization:
Original sequence length: ~400 tokens
2. Truncated tokenization:
Truncated sequence length: 512 tokens
3. Sliding window approach:
Number of windows: ~4
Window shape: torch.Size([4, 256])
4. Content of first window:
[CLS] transformers are incredibly versatile models that have revolutionized the field of nlp. transformers are...
Nota: Los números exactos variarían según la tokenización real de la oración repetida, pero esto representa la estructura esperada de la salida según la lógica del código.
Ejemplo: División de Texto Largo en Fragmentos
# Function to split long text into chunks with overlap
def split_text_into_chunks(text, max_length=128, overlap=20):
# Tokenize the text
tokenized = tokenizer(text, truncation=False, return_tensors="pt")
input_ids = tokenized["input_ids"][0]
attention_mask = tokenized["attention_mask"][0]
chunks = []
chunk_masks = []
# Create chunks with overlap
for i in range(0, len(input_ids), max_length - overlap):
# Extract chunk
chunk = input_ids[i:i + max_length]
mask = attention_mask[i:i + max_length]
# Pad if necessary
if len(chunk) < max_length:
padding_length = max_length - len(chunk)
chunk = torch.cat([chunk, torch.zeros(padding_length, dtype=torch.long)])
mask = torch.cat([mask, torch.zeros(padding_length, dtype=torch.long)])
chunks.append(chunk)
chunk_masks.append(mask)
return {
"input_ids": torch.stack(chunks),
"attention_mask": torch.stack(chunk_masks)
}
# Example usage
long_text = "This is a very long text that needs to be split into chunks. " * 20
chunks = split_text_into_chunks(long_text, max_length=128, overlap=20)
# Print information about chunks
print(f"Number of chunks: {len(chunks['input_ids'])}")
print(f"Chunk size: {chunks['input_ids'].shape}")
# Decode and print first chunk to verify content
first_chunk = tokenizer.decode(chunks['input_ids'][0])
print("\nFirst chunk content:")
print(first_chunk[:100], "...")
# Print overlap between chunks to verify
if len(chunks['input_ids']) > 1:
overlap_first = tokenizer.decode(chunks['input_ids'][0][-20:])
overlap_second = tokenizer.decode(chunks['input_ids'][1][:20])
print("\nOverlap demonstration:")
print("End of first chunk:", overlap_first)
print("Start of second chunk:", overlap_second)
Desglose del Código:
- Parámetros de la Función:
- max_length: Número máximo de tokens por fragmento (predeterminado: 128)
- overlap: Número de tokens superpuestos entre fragmentos (predeterminado: 20)
- Componentes Principales:
- Tokenización: Convierte el texto de entrada en IDs de tokens y máscaras de atención
- Creación de Fragmentos: Crea fragmentos superpuestos de longitud específica
- Relleno: Asegura que todos los fragmentos tengan la misma longitud
- Formato de Retorno: Diccionario con tensores de input_ids y attention_mask
- Características Importantes:
- El manejo de superposición evita la pérdida de contexto entre fragmentos
- Las máscaras de atención rastrean los tokens válidos vs. el relleno
- Mantiene la compatibilidad con los requisitos de entrada del modelo transformer
- Pasos de Verificación:
- Imprime el número y tamaño de los fragmentos
- Muestra el contenido del primer fragmento
- Demuestra la superposición entre fragmentos consecutivos
Salida:
Number of chunks: 3
Chunk size: torch.Size([3, 128])
First chunk content:
This is a very long text that needs to be split into chunks. This is a very long text that needs to be split...
Overlap demonstration:
End of first chunk: split into chunks.
Start of second chunk: chunks. This is a v
El número exacto de fragmentos y contenido puede variar según la tokenización real, pero esto demuestra los componentes clave de salida mostrando:
- El número de fragmentos creados a partir del texto de entrada
- La dimensión del tensor de fragmentos
- Una muestra del contenido del primer fragmento
- La región superpuesta entre fragmentos consecutivos
3.1.3 Preprocesamiento para Tareas Específicas
Las diferentes tareas de PLN requieren pasos específicos de preprocesamiento para garantizar un rendimiento óptimo del modelo. Esta fase de preprocesamiento es crucial ya que transforma los datos de texto sin procesar en un formato que los modelos transformer pueden procesar eficazmente. El pipeline de preprocesamiento debe diseñarse cuidadosamente para manejar las características únicas de cada tarea mientras mantiene la integridad de los datos y la compatibilidad del modelo.
Los pasos exactos de preprocesamiento varían significativamente según varios factores clave:
- Tipo de Tarea:
- Las tareas de clasificación requieren conjuntos de datos equilibrados y codificación apropiada de etiquetas
- Las tareas de generación necesitan un manejo cuidadoso de tokens de inicio/fin y formato de secuencias
- Las tareas de traducción deben alinear efectivamente los pares de idiomas fuente y destino
- Las tareas de respuesta a preguntas requieren un formato adecuado de contexto y pregunta
- Arquitectura del Modelo:
- Los modelos basados en BERT necesitan tokens especiales [CLS] y [SEP]
- Los modelos GPT requieren atención específica a los tokens de fin de secuencia
- Los modelos T5 necesitan prefijos específicos para cada tarea
- Requisitos del Conjunto de Datos:
- Estándares de limpieza y normalización de datos
- Manejo de caracteres especiales y formato
- Procesamiento de terminología específica del dominio
Los pasos comunes de preprocesamiento forman la base de cualquier pipeline de PLN:
- Tokenización: Convertir texto en tokens que el modelo pueda procesar
- Nivel de palabra: Dividir texto en palabras individuales
- Nivel de subpalabra: Dividir palabras en subunidades significativas
- Nivel de carácter: Procesar texto como caracteres individuales
- Ajuste de Longitud de Secuencia:
- Rellenar secuencias más cortas a una longitud fija
- Truncar secuencias más largas para ajustarse a las restricciones del modelo
- Implementar estrategias de procesamiento por lotes dinámico
- Codificación de Etiquetas:
- Convertir etiquetas categóricas a formato numérico
- Implementar codificación one-hot cuando sea apropiado
- Manejar escenarios multi-etiqueta
- Manejo de Tokens Especiales:
- Agregar tokens específicos de la tarea
- Gestionar tokens separadores y de clasificación
- Implementar estrategias de enmascaramiento
Además, las consideraciones específicas de cada tarea requieren atención cuidadosa:
- Tareas de Clasificación:
- Manejar el desequilibrio de clases mediante muestreo o ponderación
- Implementar estrategias de estratificación
- Procesamiento de Documentos Largos:
- Implementar ventanas deslizantes con superposición apropiada
- Gestionar la segmentación de documentos
- Mantener el contexto entre segmentos
Aquí hay ejemplos para dos tareas comunes:
Clasificación de Texto:
Para la clasificación, las entradas de texto necesitan ser tokenizadas, y sus etiquetas correspondientes deben ser codificadas.
from sklearn.preprocessing import LabelEncoder
import torch
from transformers import AutoTokenizer
import numpy as np
# Initialize tokenizer (e.g., BERT)
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
# Sample data
texts = [
"This movie was amazing!",
"I did not like the ending.",
"A masterpiece of modern cinema",
"Waste of time and money",
"It was just okay, nothing special"
]
labels = ["positive", "negative", "positive", "negative", "neutral"]
# Tokenize the texts with attention masks
tokenized_texts = tokenizer(
texts,
padding="max_length",
truncation=True,
max_length=32,
return_tensors="pt",
return_attention_mask=True
)
# Encode the labels
label_encoder = LabelEncoder()
encoded_labels = torch.tensor(label_encoder.fit_transform(labels))
# Create dataset dictionary
dataset = {
'input_ids': tokenized_texts['input_ids'],
'attention_mask': tokenized_texts['attention_mask'],
'labels': encoded_labels
}
# Print dataset information
print("Dataset Structure:")
print(f"Number of examples: {len(texts)}")
print(f"Input shape: {dataset['input_ids'].shape}")
print(f"Attention mask shape: {dataset['attention_mask'].shape}")
print(f"Labels shape: {dataset['labels'].shape}")
print("\nLabel mapping:", dict(zip(label_encoder.classes_, label_encoder.transform(label_encoder.classes_))))
# Example of accessing first sample
first_text = tokenizer.decode(dataset['input_ids'][0])
print(f"\nFirst example:")
print(f"Text: {first_text}")
print(f"Label: {labels[0]} (encoded: {dataset['labels'][0]})")
print(f"Attention mask: {dataset['attention_mask'][0][:10]}...")
Desglose del Código:
- Importaciones y Configuración:
- sklearn.preprocessing.LabelEncoder para convertir etiquetas de texto a números
- torch para operaciones con tensores
- transformers para el tokenizador
- numpy para operaciones numéricas
- Preparación de Datos:
- Conjunto de datos ampliado con 5 ejemplos que cubren diferentes sentimientos
- Se agregó una clase "neutral" para demostrar la capacidad multiclase
- Pares estructurados de texto y etiquetas
- Tokenización:
- Utiliza el tokenizador BERT con max_length aumentado (32 tokens)
- Incluye relleno y truncamiento para longitudes consistentes
- Devuelve máscaras de atención para la entrada apropiada del transformer
- Procesamiento de Etiquetas:
- Convierte etiquetas de texto a formato numérico
- Crea un mapeo entre etiquetas originales y valores codificados
- Almacena etiquetas como tensores de PyTorch
- Creación del Conjunto de Datos:
- Combina input_ids, máscaras de atención y etiquetas
- Organiza los datos en un formato listo para el entrenamiento del modelo
- Mantiene la alineación entre entradas y etiquetas
- Visualización de Información:
- Muestra la estructura y dimensiones del conjunto de datos
- Muestra el mapeo de codificación de etiquetas
- Demuestra cómo acceder y decodificar ejemplos individuales
Salida Esperada:
Dataset Structure:
Number of examples: 5
Input shape: torch.Size([5, 32])
Attention mask shape: torch.Size([5, 32])
Labels shape: torch.Size([5])
Label mapping: {'negative': 0, 'neutral': 1, 'positive': 2}
First example:
Text: [CLS] this movie was amazing! [SEP] [PAD]...
Label: positive (encoded: 2)
Attention mask: tensor([1, 1, 1, 1, 1, 1, 0, 0, 0, 0]...)
Clasificación de Tokens (por ejemplo, Reconocimiento de Entidades Nombradas):
Para las tareas de clasificación de tokens, se debe asignar una etiqueta a cada token en la secuencia de entrada.
# Sample data for Named Entity Recognition (NER)
text = "Hugging Face is based in New York City."
labels = ["B-ORG", "I-ORG", "O", "O", "O", "B-LOC", "I-LOC"]
# Tokenize the text using the pre-initialized tokenizer
# padding="max_length": Ensures all sequences have the same length
# truncation=True: Cuts off text that exceeds max_length
# max_length=20: Maximum number of tokens allowed
# return_tensors="pt": Returns PyTorch tensors
tokenized_text = tokenizer(text, padding="max_length", truncation=True, max_length=20, return_tensors="pt")
# Align labels with tokenized text
# This is crucial because tokenization might split words into subwords
# - If a token starts with "##", it's a subword token (BERT-specific)
# - We assign -100 to subword tokens to ignore them in loss calculation
# - Other tokens retain their original NER labels
aligned_labels = [-100 if token.startswith("##") else label for token, label in zip(tokenized_text["input_ids"][0], labels)]
# Print the aligned labels to verify the alignment
print("Aligned Labels:", aligned_labels)
# Label explanation:
# B-ORG: Beginning of Organization entity (Hugging Face)
# I-ORG: Inside of Organization entity (Face)
# O: Outside any entity (is, based, in)
# B-LOC: Beginning of Location entity (New York)
# I-LOC: Inside of Location entity (York City)
Así es como se vería la salida:
Aligned Labels: ['B-ORG', 'I-ORG', 'O', 'O', 'O', 'B-LOC', 'I-LOC']
Esta salida muestra las etiquetas NER (Reconocimiento de Entidades Nombradas) alineadas con los tokens del texto de entrada "Hugging Face is based in New York City", donde:
- Hugging Face está etiquetado como una organización (B-ORG, I-ORG)
- New York City está etiquetado como una ubicación (B-LOC, I-LOC)
- Las palabras restantes (is, based, in) están etiquetadas como entidades externas (O)
El preprocesamiento de datos es un paso crucial en la preparación del texto para los modelos transformer, sirviendo como base para el entrenamiento y despliegue exitoso del modelo. Esta fase involucra varios componentes críticos:
Primero, la tokenización adecuada divide el texto en unidades significativas que el modelo puede procesar. Esto incluye el manejo de límites de palabras, caracteres especiales y estrategias de tokenización de subpalabras que ayudan a gestionar el tamaño del vocabulario mientras preservan el significado semántico.
Segundo, el relleno y truncamiento aseguran tamaños de entrada consistentes. El relleno agrega tokens especiales a secuencias más cortas para igualar una longitud objetivo, mientras que el truncamiento elimina cuidadosamente tokens excedentes de secuencias más largas mientras preserva la información esencial.
Tercero, la alineación de etiquetas con la entrada tokenizada es esencial para tareas de aprendizaje supervisado. Este proceso requiere una atención cuidadosa para mantener la relación entre los tokens de entrada y sus etiquetas correspondientes, especialmente cuando se trata de tokenización de subpalabras.
Además, el preprocesamiento incluye pasos cruciales como el manejo de palabras fuera del vocabulario, la gestión de tokens especiales (como [CLS] y [SEP] para modelos BERT), y la implementación de estrategias de enmascaramiento apropiadas para diferentes arquitecturas de modelos.
Dominar estas técnicas de preprocesamiento es vital ya que impactan directamente en el rendimiento del modelo. La implementación adecuada ayuda a evitar problemas comunes como etiquetas desalineadas, longitudes de secuencia inconsistentes o pérdida de información contextual. Cuando se realiza correctamente, estos pasos crean una entrada limpia y bien estructurada que permite a los modelos transformer alcanzar su rendimiento óptimo.