Fine-tune y despliega clasificadores de texto con
Fine-tunea un modelo transformer pre-entrenado para clasificacion de texto usando Hugging Face Trainer, tokeniza datasets, evalua metricas y despliega para inferencia
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Fine-tune y despliega clasificadores de texto con Hugging Face Transformers
Los modelos transformer pre-entrenados como BERT y DistilBERT alcanzan resultados modern en tareas de clasificacion de texto. La libreria transformers de Hugging Face proporciona la API Trainer para fine-tunear estos modelos en datasets personalizados con codigo minimo. A continuacion: cargar un dataset, tokenizar, fine-tunear, evaluar y desplegar un clasificador de texto.
Cuando Usar Esto
-
For alternatives, see Complete Guide to OpenAI API Mastery.
-
Analisis de sentimiento en reviews de clientes o redes sociales
-
Deteccion de spam, clasificacion de temas o reconocimiento de intenciones
-
Cualquier tarea de clasificacion de texto donde un modelo fine-tuneado supera enfoques basados en prompts
Requisitos Previos
- Python 3.10+
- Paquetes
transformers,datasets,evaluate - Se recomienda una GPU (pero CPU funciona para datasets pequenos)
Solucion
1. Instalar dependencias
pip install transformers datasets evaluate torch accelerate
2. Cargar y preparar el dataset
from datasets import load_dataset, DatasetDict
# Cargar un dataset de analisis de sentimiento
dataset = load_dataset("imdb")
# Subset para entrenamiento mas rapido (opcional)
train_dataset = dataset["train"].shuffle(seed=42).select(range(2000))
test_dataset = dataset["test"].shuffle(seed=42).select(range(500))
dataset = DatasetDict({
"train": train_dataset,
"test": test_dataset,
})
print(dataset)
# DatasetDict({
# 'train': Dataset({features: ['text', 'label'], num_rows: 2000}),
# 'test': Dataset({features: ['text', 'label'], num_rows: 500}),
# })
3. Tokenizar el dataset
from transformers import AutoTokenizer
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
def tokenize_function(examples):
return tokenizer(
examples["text"],
padding="max_length",
truncation=True,
max_length=256,
)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
# Establecer formato para PyTorch
tokenized_datasets.set_format(
type="torch",
columns=["input_ids", "attention_mask", "label"],
)
4. Fine-tunear con Trainer API
from transformers import (
AutoModelForSequenceClassification,
TrainingArguments,
Trainer,
)
import evaluate
# Cargar modelo pre-entrenado con cabeza de clasificacion
model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=2,
)
# Definir argumentos de entrenamiento
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
warmup_steps=100,
weight_decay=0.01,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
metric_for_best_model="f1",
logging_dir="./logs",
logging_steps=50,
)
# Cargar metricas de evaluacion
f1_metric = evaluate.load("f1")
accuracy_metric = evaluate.load("accuracy")
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = logits.argmax(axis=-1)
return {
"f1": f1_metric.compute(predictions=predictions, references=labels)["f1"],
"accuracy": accuracy_metric.compute(predictions=predictions, references=labels)["accuracy"],
}
# Crear Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["test"],
compute_metrics=compute_metrics,
)
# Entrenar
trainer.train()
5. Evaluar el modelo
eval_results = trainer.evaluate()
print(f"F1 Score: {eval_results['eval_f1']:.4f}")
print(f"Accuracy: {eval_results['eval_accuracy']:.4f}")
6. Guardar y desplegar
# Guardar el modelo fine-tuneado y el tokenizer
model_dir = "./sentiment-classifier"
trainer.save_model(model_dir)
tokenizer.save_pretrained(model_dir)
# Cargar para inferencia
from transformers import pipeline
classifier = pipeline(
"text-classification",
model=model_dir,
tokenizer=model_dir,
device=0 if torch.cuda.is_available() else -1,
)
# Predecir
result = classifier("This movie was absolutely fantastic!")
print(result)
# [{'label': 'LABEL_1', 'score': 0.9998}]
7. Servicio de inferencia batch
from typing import List
import torch
class TextClassificationService:
def __init__(self, model_dir: str):
self.classifier = pipeline(
"text-classification",
model=model_dir,
tokenizer=model_dir,
device=0 if torch.cuda.is_available() else -1,
)
self.label_map = {0: "negative", 1: "positive"}
def classify(self, texts: List[str]) -> List[dict]:
"""Classify a batch of texts.
Args:
texts: List of input texts.
Returns:
List of {label, score, text} dicts.
"""
results = self.classifier(texts, batch_size=32)
return [
{
"label": self.label_map.get(int(r["label"].split("_")[-1]), r["label"]),
"score": r["score"],
"text": text[:100],
}
for r, text in zip(results, texts)
]
# Uso
service = TextClassificationService("./sentiment-classifier")
predictions = service.classify([
"Great product, highly recommend!",
"Terrible experience, would not buy again.",
"It's okay, nothing special.",
])
for p in predictions:
print(f"{p['label']}: {p['score']:.3f} — {p['text']}")
Como Funciona
- Tokenizacion convierte texto en IDs de tokens que el modelo entiende.
padding="max_length"asegura longitud de secuencia uniforme, ytruncation=Truecorta textos mas largos quemax_length. AutoModelForSequenceClassificationcarga un modelo pre-entrenado con una nueva cabeza de clasificacion encima. Las capas base retienen sus pesos pre-entrenados; solo la cabeza se inicializa aleatoriamente.Trainermaneja el loop de entrenamiento: forward pass, computo de loss, backpropagation, pasos del optimizer, scheduling de learning rate y checkpointing. Tambien soporta entrenamiento distribuido y precision mixta.compute_metricscorre despues de cada epoch de evaluacion, calculando F1 y accuracy desde los logits del modelo y las etiquetas verdaderas.pipelineenvuelve el modelo y tokenizer en una sola funcion de inferencia, manejando tokenizacion, inferencia del modelo y post-procesamiento (softmax, mapeo de etiquetas).
Variantes
Clasificacion multi-clase
# Para 3+ clases (ej. clasificacion de temas)
num_labels = 5 # deportes, politica, tech, salud, entretenimiento
model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=num_labels,
)
# Usar F1 macro para multi-clase desbalanceado
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = logits.argmax(axis=-1)
return {
"f1_macro": f1_metric.compute(
predictions=predictions, references=labels, average="macro"
)["f1"],
}
Dataset personalizado desde CSV
from datasets import Dataset
# Cargar desde CSV
dataset = Dataset.from_csv("reviews.csv") # columnas: text, label
# O desde una lista de dicts
data = [
{"text": "Great product!", "label": 1},
{"text": "Worst purchase ever.", "label": 0},
]
dataset = Dataset.from_list(data)
Exportacion ONNX para inferencia mas rapida
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from optimum.onnxruntime import ORTModelForSequenceClassification
# Exportar a ONNX
model = AutoModelForSequenceClassification.from_pretrained("./sentiment-classifier")
ort_model = ORTModelForSequenceClassification.from_pretrained(
"./sentiment-classifier",
export=True,
)
ort_model.save_pretrained("./sentiment-classifier-onnx")
# Usar pipeline ONNX (2-4x mas rapido en CPU)
classifier = pipeline(
"text-classification",
model=ort_model,
tokenizer="./sentiment-classifier",
)
Early stopping
from transformers import EarlyStoppingCallback
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["test"],
compute_metrics=compute_metrics,
callbacks=[EarlyStoppingCallback(early_stopping_patience=3)],
)
Mejores Practicas
- Empieza con DistilBERT — es 40% mas pequeno que BERT con 95% del rendimiento para la mayoria de tareas de clasificacion
- Usa
max_length=256o512— secuencias mas largas mejoran precision pero aumentan el tiempo de entrenamiento cuadraticamente - Monitorea F1 de validacion, no solo loss — la loss puede decrecer mientras F1 se estanca o cae
- Guarda el mejor modelo —
load_best_model_at_end=Truemantiene el checkpoint con la mejor metrica
Errores Comunes
- No barajar el dataset — los modelos pueden aprender patrones dependientes del orden desde datos no barajados
- Usar batch size demasiado grande — causa errores de out-of-memory; reduce el batch size o usa gradient accumulation
- Olvidar establecer
num_labels— por defecto es 2; tareas multi-clase necesitan el conteo correcto - No mapear etiquetas correctamente —
LABEL_0,LABEL_1son nombres por defecto; mapealos a etiquetas legibles
FAQ
Q: Cuanto toma el fine-tuning? A: En una sola GPU (ej. T4), fine-tunear DistilBERT en 2.000 ejemplos por 3 epochs toma ~5-10 minutos. En CPU, espera 30-60 minutos.
Q: Puedo usar esto para texto no-ingles?
A: Si. Usa un modelo multilingue como distilbert-base-multilingual-cased o modelos especificos del idioma desde el Hub de Hugging Face.
Q: Cuantos ejemplos necesito? A: 500-1.000 ejemplos por clase es un buen punto de partida. Con menos, considera few-shot learning con LLMs.
Q: Debo fine-tunear o usar un LLM con prompts? A: Los modelos fine-tuneados son mas rapidos, baratos y a menudo mas precisos para tareas de clasificacion especificas. Los LLMs son mejores para escenarios zero-shot o few-shot.
¿Esta solución está lista para producción?
Sí. Los ejemplos de código arriba muestran implementaciones probadas. Adapta el manejo de errores y la configuración a tu entorno específico antes de desplegar.
¿Cuáles son las características de rendimiento?
El rendimiento depende de tu volumen de datos e infraestructura. Las soluciones mostradas priorizan claridad. Para escenarios de alto throughput, añade caching, batching y connection pooling según sea necesario.
¿Cómo depuro problemas con este enfoque?
Empieza con el ejemplo mínimo de arriba. Añade logging en cada paso. Prueba con entradas pequeñas primero, luego escala. Usa el debugger de tu lenguaje para revisar los edge cases.
Recursos Relacionados
Compose LCEL Chains in LangChain for Multi-Step LLM
Build composable LLM pipelines with LangChain Expression Language (LCEL) using pipes, parallel execution, and custom runnable components
Recipea Local RAG Pipeline with ChromaDB and Sentence Transformers
Implement retrieval-augmented generation locally with ChromaDB, sentence-transformers embeddings, and LLM generation without external API dependencies
RecipeStructured JSON Output from OpenAI Function Calling
Use OpenAI function calling and structured outputs to get reliable JSON from LLMs with Pydantic validation and error handling
RecipeBuild Stateful AI Agents with LangGraph State Machines
Create multi-step AI agents with LangGraph using state machines, conditional edges, tool calling, and human-in-the-loop checkpoints for production workflows
GuideComplete Guide to OpenAI API Mastery
Master the OpenAI API in production. Covers chat completions, streaming, function calling, structured outputs, embeddings, fine-tuning, batch API, assistants API, rate limits, error handling, and cost optimization patterns.