Cómo ajustar a Distilbert para la clasificación de emociones

Los equipos de atención al cliente se ahogaron con el abrumador volumen de consultas de clientes en cada compañía en la que he trabajado. ¿Has tenido experiencias similares?

¿Qué pasaría si te dijera que podrías usar AI para automáticamente? identificar, clasificar por categorías, e incluso resolver los problemas más comunes?

Al ajustar un modelo de transformador como Bert, puede construir un sistema automatizado que etiquete los boletos por tipo de problema y los enruta al equipo correcto.

En este tutorial, te mostraré cómo ajustar un modelo de transformador para la clasificación de emociones en cinco pasos:

  1. Configure su entorno: Prepare su conjunto de datos e instale las bibliotecas necesarias.
  2. Cargar y preprocesar datos: Analizar archivos de texto y organizar sus datos.
  3. Afinar Distilbert: Modelo de trenes para clasificar las emociones utilizando su conjunto de datos.
  4. Evaluar el rendimiento: Use métricas como precisión, puntaje F1 y matrices de confusión para medir el rendimiento del modelo.
  5. Interpretar predicciones: Visualice y comprenda las predicciones utilizando SHAP (explicaciones aditivas de Shapley).

Al final, tendrá un modelo ajustado que clasifica las emociones de las entradas de texto con alta precisión, y también aprenderá cómo interpretar estas predicciones usando SHAP.

Este mismo enfoque se puede aplicar a los casos de uso del mundo real más allá de la clasificación de emociones, como la automatización de atención al cliente, el análisis de sentimientos, la moderación de contenido y más.

¡Vamos a sumergirnos!

Elegir el modelo de transformador correcto

Al seleccionar un modelo de transformador para Clasificación de textoaquí hay un desglose rápido de los modelos más comunes:

  • Bert: Ideal para tareas generales de la PNL, pero computacionalmente caro tanto para capacitación como para inferencia.
  • Distilbert: 60% más rápido que Bert mientras conserva el 97% de sus capacidades, lo que lo hace ideal para aplicaciones en tiempo real.
  • Roberta: Una versión más robusta de Bert, pero requiere más recursos.
  • XLM-Roberta: Una variante multilingüe de Roberta entrenada en 100 idiomas. Es perfecto para tareas multilingües, pero es bastante intensiva en recursos.

Para este tutorial, elegí ajustar a Distilbert porque ofrece el mejor equilibrio entre rendimiento y eficiencia.

Paso 1: Configuración e instalación de dependencias

Asegúrese de tener las bibliotecas requeridas instaladas:

!pip install datasets transformers torch scikit-learn shap

Paso 2: Datos de carga y preprocesamiento

Yo usé el Conjunto de datos de emociones para PNL por Praveen Govi, disponible en Kaggle y con licencia para uso comercial. Contiene texto etiquetado con emociones. Los datos vienen en tres .txt Archivos: tren, validación, y prueba.

Cada línea contiene una oración y su etiqueta de emoción correspondiente, separada por un punto y coma:

text; emotion
"i didnt feel humiliated"; "sadness"
"i am feeling grouchy"; "anger"
"im updating my blog because i feel shitty"; "sadness"

Analizar el conjunto de datos en un Pandas DataFrame

Cargamos el conjunto de datos:

def parse_emotion_file(file_path):
"""
    Parses a text file with each line in the format: {text; emotion}
    and returns a pandas DataFrame with 'text' and 'emotion' columns.

    Args:
    - file_path (str): Path to the .txt file to be parsed

    Returns:
    - df (pd.DataFrame): DataFrame containing 'text' and 'emotion' columns
    """
    texts = []
    emotions = []
   
    with open(file_path, 'r', encoding='utf-8') as file:
        for line in file:
            try:
                # Split each line by the semicolon separator
                text, emotion = line.strip().split(';')
               
                # append text and emotion to separate lists
                texts.append(text)
                emotions.append(emotion)
            except ValueError:
                continue
   
    return pd.DataFrame({'text': texts, 'emotion': emotions})

# Parse text files and store as Pandas DataFrames
train_df = parse_emotion_file("train.txt")
val_df = parse_emotion_file("val.txt")
test_df = parse_emotion_file("test.txt")

Comprender la distribución de la etiqueta

Este conjunto de datos contiene Ejemplos de entrenamiento de 16k y 2k ejemplos para la validación y las pruebas. Aquí está el desglose de distribución de la etiqueta:

Imagen del autor.

El gráfico de barras anterior muestra que el conjunto de datos es desequilibrado, con la mayoría de las etiquetas de muestras como alegría y tristeza.

Para un modelo de producción ajustado, consideraría experimentar con diferentes técnicas de muestreo para superar este problema de desequilibrio de clase y mejorar el rendimiento del modelo.

Paso 3: Tokenización y preprocesamiento de datos

A continuación, cargué en Distilbert’s Tokenizer:

from transformers import AutoTokenizer

# Define the model path for DistilBERT
model_name = "distilbert-base-uncased"

# Load the tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name)

Luego, lo usé para tokenizar los datos de texto y transformar las etiquetas en ID numéricos:

# Tokenize data
def preprocess_function(df, label2id):
    """
    Tokenizes text data and transforms labels into numerical IDs.

    Args:
        df (dict or pandas.Series): A dictionary-like object containing "text" and "emotion" fields.
        label2id (dict): A mapping from emotion labels to numerical IDs.

    Returns:
        dict: A dictionary containing:
              - "input_ids": Encoded token sequences
              - "attention_mask": Mask to indicate padding tokens
              - "label": Numerical labels for classification

    Example usage:
        train_dataset = train_dataset.map(lambda x: preprocess_function(x, tokenizer, label2id), batched=True)
    """
    tokenized_inputs = tokenizer(
        df["text"],
        padding="longest",
        truncation=True,
        max_length=512,
        return_tensors="pt"
    )

    tokenized_inputs["label"] = [label2id.get(emotion, -1) for emotion in df["emotion"]]
    return tokenized_inputs
   
# Convert the DataFrames to HuggingFace Dataset format
train_dataset = Dataset.from_pandas(train_df)

# Apply the 'preprocess_function' to tokenize text data and transform labels
train_dataset = train_dataset.map(lambda x: preprocess_function(x, label2id), batched=True)

Paso 4: Modelo de ajuste fino

A continuación, cargué un modelo Distilbert previamente entrenado con un cabezal de clasificación para nuestro texto de clasificación de texto. También especifiqué cómo se ven las etiquetas para este conjunto de datos:

# Get the unique emotion labels from the 'emotion' column in the training DataFrame
labels = train_df["emotion"].unique()

# Create label-to-id and id-to-label mappings
label2id = {label: idx for idx, label in enumerate(labels)}
id2label = {idx: label for idx, label in enumerate(labels)}

# Initialize model
model = AutoModelForSequenceClassification.from_pretrained(
    model_name,
    num_labels=len(labels),
    id2label=id2label,
    label2id=label2id
)

El modelo Distilbert previamente entrenado para la clasificación consiste en Cinco capas más un cabezal de clasificación.

Para evitar el sobreajuste, yo congelamiento las primeras cuatro capasPreservar el conocimiento aprendido durante la pre-entrenamiento. Esto permite que el modelo retenga la comprensión del lenguaje general mientras solo ajusta la quinta capa y el jefe de clasificación para adaptarse a mi conjunto de datos. Así es como hice esto:

# freeze base model parameters
for name, param in model.base_model.named_parameters():
    param.requires_grad = False

# keep classifier trainable
for name, param in model.base_model.named_parameters():
    if "transformer.layer.5" in name or "classifier" in name:
        param.requires_grad = True

Definición de métricas

Dado el desequilibrio de la etiqueta, pensé que la precisión puede no ser la métrica más apropiada, por lo que elegí incluir otras métricas adecuadas para problemas de clasificación como precisión, recuperación, puntaje F1 y puntaje AUC.

También utilicé promedios “ponderados” para puntaje F1, precisión y recuerdo para abordar el problema de desequilibrio de clase. Este parámetro garantiza que todas las clases contribuyan proporcionalmente a la métrica y eviten que cualquier clase única domine los resultados:

def compute_metrics(p):
    """
    Computes accuracy, F1 score, precision, and recall metrics for multiclass classification.

    Args:
    p (tuple): Tuple containing predictions and labels.

    Returns:
    dict: Dictionary with accuracy, F1 score, precision, and recall metrics, using weighted averaging
          to account for class imbalance in multiclass classification tasks.
    """
    logits, labels = p
   
    # Convert logits to probabilities using softmax (PyTorch)
    softmax = torch.nn.Softmax(dim=1)
    probs = softmax(torch.tensor(logits))
   
    # Convert logits to predicted class labels
    preds = probs.argmax(axis=1)

    return {
        "accuracy": accuracy_score(labels, preds),  # Accuracy metric
        "f1_score": f1_score(labels, preds, average="weighted"),  # F1 score with weighted average for imbalanced data
        "precision": precision_score(labels, preds, average="weighted"),  # Precision score with weighted average
        "recall": recall_score(labels, preds, average="weighted"),  # Recall score with weighted average
        "auc_score": roc_auc_score(labels, probs, average="macro", multi_class="ovr")
    }

Establezcamos el proceso de capacitación:

# Define hyperparameters
lr = 2e-5
batch_size = 16
num_epochs = 3
weight_decay = 0.01

# Set up training arguments for fine-tuning models
training_args = TrainingArguments(
    output_dir="./results",
    evaluation_strategy="steps",
    eval_steps=500,
    learning_rate=lr,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    num_train_epochs=num_epochs,
    weight_decay=weight_decay,
    logging_dir="./logs",
    logging_steps=500,
    load_best_model_at_end=True,
    metric_for_best_model="eval_f1_score",
    greater_is_better=True,
)

# Initialize the Trainer with the model, arguments, and datasets
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=val_dataset,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)

# Train the model
print(f"Training {model_name}...")
trainer.train()

Paso 5: Evaluación del rendimiento del modelo

Después del entrenamiento, evalué el rendimiento del modelo en el conjunto de pruebas:

# Generate predictions on the test dataset with fine-tuned model
predictions_finetuned_model = trainer.predict(test_dataset)
preds_finetuned = predictions_finetuned_model.predictions.argmax(axis=1)

# Compute evaluation metrics (accuracy, precision, recall, and F1 score)
eval_results_finetuned_model = compute_metrics((predictions_finetuned_model.predictions, test_dataset["label"]))

Así es como lo hizo el modelo Distilbert ajustado en el conjunto de pruebas en comparación con el modelo base previamente capacitado:

Gráfico de radar del modelo Distilbert sintonizado. Imagen del autor.

Antes de ajustar, el modelo previamente entrenado funcionó mal en nuestro conjunto de datos, porque no ha visto las etiquetas de emoción específicas antes. Era esencialmente adivinando al azar, como se refleja en una puntuación AUC de 0.5 que no indica que no sea mejor que el azar.

Después del ajuste, el modelo significativamente mejorado en todas las métricaslogrando una precisión del 83% en la identificación correcta de emociones. Esto demuestra que el modelo ha aprendido con éxito patrones significativos en los datos, incluso con solo 16k muestras de entrenamiento.

¡Eso es increíble!

Paso 6: Interpretar predicciones con SHAP

Probé el modelo ajustado en tres oraciones y aquí están las emociones que predijo:

  1. La idea de hablar frente a una gran multitud hace que mi corazón se acelere, y empiezo a sentirme abrumado por la ansiedad. ” → Miedo 😱
  2. “¡No puedo creer lo irrespetuosos que fueron! Trabajé muy duro en este proyecto, y simplemente lo descartaron sin siquiera escuchar. ¡Es irritante! “ → enojo 😡
  3. “¡Me encanta este nuevo teléfono! La calidad de la cámara es increíble, la batería dura todo el día, y es muy rápida. No podría estar más feliz con mi compra, y se la recomiendo a cualquiera que busque un teléfono nuevo “. → Alegría 😀

Impresionante, ¿verdad?

Quería entender cómo el modelo hizo sus predicciones, usé usando Bandear (Explicaciones aditivas de Shapley) Para visualizar la importancia de la característica.

Comencé creando un explicador:

# Build a pipeline object for predictions
preds = pipeline(
    "text-classification",
    model=model_finetuned,
    tokenizer=tokenizer,
    return_all_scores=True,
)

# Create an explainer
explainer = shap.Explainer(preds)

Luego, calculé los valores de Shap usando el explicador:

# Compute SHAP values using explainer
shap_values = explainer(example_texts)

# Make SHAP text plot
shap.plots.text(shap_values)

El cuadro a continuación visualiza cómo cada palabra en el texto de entrada contribuye a la salida del modelo utilizando valores de forma:

Totación de texto de bandeja. Imagen del autor.

En este caso, la trama muestra que la “ansiedad” es el factor más importante para predecir el “miedo” como la emoción.

La trama de texto SHAP es una forma agradable, intuitiva e interactiva de comprender las predicciones descomponiendo cuánto influye cada palabra en la predicción final.

Resumen

¡Has aprendido con éxito a ajustar a Distilbert para la clasificación de emociones de los datos de texto! (Puedes ver el modelo en la cara abrazada aquí).

Los modelos de transformadores se pueden ajustar para muchas aplicaciones del mundo real, que incluyen:

  • Etiquetado de boletos de servicio al cliente (como se discutió en la introducción),
  • Marcar riesgos para la salud mental en conversaciones basadas en texto,
  • Detección del sentimiento en las revisiones de productos.

El ajuste fino es una forma efectiva y eficiente de adaptar los potentes modelos previamente capacitados a tareas específicas con un conjunto de datos relativamente pequeño.

¿Qué taladrarás a continuación?


¿Quieres construir tus habilidades de IA?

👉🏻 Corro el AI Weekender y Escriba publicaciones semanales de blog sobre ciencia de datos, proyectos de fin de semana de IA, asesoramiento profesional para profesionales en datos.


Recursos

  • Cuaderno de jupyter [HERE]
  • Tarjeta modelo en la cara abrazada [HERE]