# Clasificación de tokens

La clasificación de tokens asigna una etiqueta a los tokens individuales de una oración. Una de las tareas de clasificación de tokens más comunes es el reconocimiento de entidades nombradas (NER). El NER intenta encontrar una etiqueta para cada entidad en una oración, como una persona, un lugar o una organización.

Esta guía le mostrará cómo:

1. Ajusta [DistilBERT](https://huggingface.co/distilbert/distilbert-base-uncased) en el conjunto de datos [WNUT 17](https://huggingface.co/datasets/flaitenberger/wnut_17) para detectar nuevas entidades.
2. Usa tu modelo afinado para la inferencia.

Para ver todas las arquitecturas y puntos de control compatibles con esta tarea, recomendamos consultar la [página de la tarea](https://huggingface.co/tasks/token-classification).

Antes de comenzar, asegúrate de tener instaladas todas las bibliotecas necesarias:

```bash
pip install transformers datasets evaluate seqeval
```

Te animamos a iniciar sesión en tu cuenta de Hugging Face para que puedas subir y compartir tu modelo con la comunidad. Cuando se te solicite, introduce tu token para iniciar sesión:

```py
>>> from huggingface_hub import notebook_login

>>> notebook_login()
```

## Cargar el conjunto de datos WNUT 17

Comience cargando el conjunto de datos WNUT 17 desde la biblioteca 🤗 Datasets:

```py
>>> from datasets import load_dataset

>>> wnut = load_dataset("flaitenberger/wnut_17")
```

A continuación, echa un vistazo a un ejemplo:

```py
>>> wnut["train"][0]
{'id': '0',
 'ner_tags': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 8, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0],
 'tokens': ['@paulwalk', 'It', "'s", 'the', 'view', 'from', 'where', 'I', "'m", 'living', 'for', 'two', 'weeks', '.', 'Empire', 'State', 'Building', '=', 'ESB', '.', 'Pretty', 'bad', 'storm', 'here', 'last', 'evening', '.']
}
```

Cada número en `ner_tags` representa una entidad. Convierta los números a sus nombres de etiqueta para descubrir de qué entidades se trata:

```py
>>> label_list = wnut["train"].features[f"ner_tags"].feature.names
>>> label_list
[
    "O",
    "B-corporation",
    "I-corporation",
    "B-creative-work",
    "I-creative-work",
    "B-group",
    "I-group",
    "B-location",
    "I-location",
    "B-person",
    "I-person",
    "B-product",
    "I-product",
]
```

La letra que antecede a cada `ner_tag` indica la posición del token dentro de la entidad:

- `B-` indica el inicio de una entidad.
- `I-` indica que un token está contenido dentro de la misma entidad (por ejemplo, el token `State` es parte de una entidad como `Empire State Building`).
- `0` indica que el token no corresponde a ninguna entidad.

## Preprocesar

El siguiente paso es cargar un tokenizador de DistilBERT para preprocesar el campo `tokens`:

```py
>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased")
```

Como vio en el campo `tokens` del ejemplo anterior, parece que la entrada ya ha sido tokenizada. Sin embargo, la entrada en realidad aún no ha sido tokenizada y deberá establecer `is_split_into_words=True` para tokenizar las palabras en subpalabras. Por ejemplo:

```py
>>> example = wnut["train"][0]
>>> tokenized_input = tokenizer(example["tokens"], is_split_into_words=True)
>>> tokens = tokenizer.convert_ids_to_tokens(tokenized_input["input_ids"])
>>> tokens
['[CLS]', '@', 'paul', '##walk', 'it', "'", 's', 'the', 'view', 'from', 'where', 'i', "'", 'm', 'living', 'for', 'two', 'weeks', '.', 'empire', 'state', 'building', '=', 'es', '##b', '.', 'pretty', 'bad', 'storm', 'here', 'last', 'evening', '.', '[SEP]']
```

Sin embargo, esto añade algunos tokens especiales `[CLS]` y `[SEP]` y la tokenización de subpalabras crea una desalineación entre la entrada y las etiquetas. Una sola palabra que corresponde a una sola etiqueta ahora puede dividirse en dos subpalabras. Deberás realinear los tokens y las etiquetas mediante:

1. Mapear todos los tokens a su palabra correspondiente con el método [word_ids](/docs/transformers/main/es/main_classes/tokenizer#transformers.BatchEncoding.word_ids).
2. Asignar la etiqueta `-100` a los tokens especiales `[CLS]` y `[SEP]` para que sean ignorados por la función de pérdida de PyTorch (ver [CrossEntropyLoss](https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html)).
3. Solo etiquetar el primer token de una palabra dada. Asignar `-100` a los demás subtokens de la misma palabra.

A continuación se muestra cómo crear una función para realinear los tokens y las etiquetas, y truncar las secuencias para que no superen la longitud de entrada máxima de DistilBERT:

```py
>>> def tokenize_and_align_labels(examples):
...     tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)

...     labels = []
...     for i, label in enumerate(examples[f"ner_tags"]):
...         word_ids = tokenized_inputs.word_ids(batch_index=i)  # Map tokens to their respective word.
...         previous_word_idx = None
...         label_ids = []
...         for word_idx in word_ids:  # Set the special tokens to -100.
...             if word_idx is None:
...                 label_ids.append(-100)
...             elif word_idx != previous_word_idx:  # Only label the first token of a given word.
...                 label_ids.append(label[word_idx])
...             else:
...                 label_ids.append(-100)
...             previous_word_idx = word_idx
...         labels.append(label_ids)

...     tokenized_inputs["labels"] = labels
...     return tokenized_inputs
```

Para aplicar la función de preprocesamiento a todo el conjunto de datos, utiliza la función [map](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.map) de 🤗 Datasets. Puedes acelerar la función `map` estableciendo `batched=True` para procesar varios elementos del conjunto de datos a la vez:

```py
>>> tokenized_wnut = wnut.map(tokenize_and_align_labels, batched=True)
```

Ahora crea un lote de ejemplos usando [DataCollatorWithPadding](/docs/transformers/main/es/main_classes/data_collator#transformers.DataCollatorWithPadding). Es más eficiente *rellenar dinámicamente* las oraciones hasta la longitud más larga dentro de un lote durante la recopilación, en lugar de rellenar todo el conjunto de datos hasta la longitud máxima.

```py
>>> from transformers import DataCollatorForTokenClassification

>>> data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)
```

## Evaluar

Incluir una métrica durante el entrenamiento suele ser útil para evaluar el rendimiento de tu modelo. Puedes cargar rápidamente un método de evaluación con la biblioteca 🤗 [Evaluate](https://huggingface.co/docs/evaluate/index). Para esta tarea, carga el marco [seqeval](https://huggingface.co/spaces/evaluate-metric/seqeval) (consulta el [quick tour](https://huggingface.co/docs/evaluate/a_quick_tour) de 🤗 Evaluate para saber más sobre cómo cargar y calcular una métrica). Seqeval produce varios puntajes: precisión, recall, F1 y exactitud.

```py
>>> import evaluate

>>> seqeval = evaluate.load("seqeval")
```

Obtén primero las etiquetas NER y, a continuación, crea una función que pase tus predicciones verdaderas y tus etiquetas verdaderas a [compute](https://huggingface.co/docs/evaluate/main/en/package_reference/main_classes#evaluate.EvaluationModule.compute) para calcular las puntuaciones:

```py
>>> import numpy as np

>>> labels = [label_list[i] for i in example[f"ner_tags"]]

>>> def compute_metrics(p):
...     predictions, labels = p
...     predictions = np.argmax(predictions, axis=2)

...     true_predictions = [
...         [label_list[p] for (p, l) in zip(prediction, label) if l != -100]
...         for prediction, label in zip(predictions, labels)
...     ]
...     true_labels = [
...         [label_list[l] for (p, l) in zip(prediction, label) if l != -100]
...         for prediction, label in zip(predictions, labels)
...     ]

...     results = seqeval.compute(predictions=true_predictions, references=true_labels)
...     return {
...         "precision": results["overall_precision"],
...         "recall": results["overall_recall"],
...         "f1": results["overall_f1"],
...         "accuracy": results["overall_accuracy"],
...     }
```

Tu función `compute_metrics` ya está lista y volverás a ella cuando configures el entrenamiento.

## Entrenar

Antes de comenzar a entrenar tu modelo, crea un mapa de los ids esperados a sus etiquetas con `id2label` y `label2id`:

```py
>>> id2label = {
...     0: "O",
...     1: "B-corporation",
...     2: "I-corporation",
...     3: "B-creative-work",
...     4: "I-creative-work",
...     5: "B-group",
...     6: "I-group",
...     7: "B-location",
...     8: "I-location",
...     9: "B-person",
...     10: "I-person",
...     11: "B-product",
...     12: "I-product",
... }
>>> label2id = {
...     "O": 0,
...     "B-corporation": 1,
...     "I-corporation": 2,
...     "B-creative-work": 3,
...     "I-creative-work": 4,
...     "B-group": 5,
...     "I-group": 6,
...     "B-location": 7,
...     "I-location": 8,
...     "B-person": 9,
...     "I-person": 10,
...     "B-product": 11,
...     "I-product": 12,
... }
```

Si no estás familiarizado con el ajuste fino de un modelo con [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer), ¡echa un vistazo al tutorial básico [aquí](../training)!

¡Ya estás listo para comenzar a entrenar tu modelo! Carga DistilBERT con [AutoModelForTokenClassification](/docs/transformers/main/es/model_doc/auto#transformers.AutoModelForTokenClassification), junto con el número de etiquetas esperadas y los mapeos de etiquetas:

```py
>>> from transformers import AutoModelForTokenClassification, TrainingArguments, Trainer

>>> model = AutoModelForTokenClassification.from_pretrained(
...     "distilbert/distilbert-base-uncased", num_labels=13, id2label=id2label, label2id=label2id
... )
```

En este punto, solo quedan tres pasos:

1. Define los hiperparámetros de entrenamiento en [TrainingArguments](/docs/transformers/main/es/main_classes/trainer#transformers.TrainingArguments). El único parámetro obligatorio es `output_dir`, que especifica dónde se guardará el modelo. Subirás este modelo al Hub configurando `push_to_hub=True` (debes iniciar sesión en Hugging Face para subir tu modelo). Al final de cada epoch, [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer) evaluará las puntuaciones de seqeval y guardará el checkpoint de entrenamiento.
2. Pasa los argumentos de entrenamiento a [Trainer](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer) junto con el modelo, el conjunto de datos, el tokenizador, el data collator y la función `compute_metrics`.
3. Llama a [train()](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer.train) para ajustar tu modelo.

```py
>>> training_args = TrainingArguments(
...     output_dir="my_awesome_wnut_model",
...     learning_rate=2e-5,
...     per_device_train_batch_size=16,
...     per_device_eval_batch_size=16,
...     num_train_epochs=2,
...     weight_decay=0.01,
...     eval_strategy="epoch",
...     save_strategy="epoch",
...     load_best_model_at_end=True,
...     push_to_hub=True,
... )

>>> trainer = Trainer(
...     model=model,
...     args=training_args,
...     train_dataset=tokenized_wnut["train"],
...     eval_dataset=tokenized_wnut["test"],
...     processing_class=tokenizer,
...     data_collator=data_collator,
...     compute_metrics=compute_metrics,
... )

>>> trainer.train()
```

Una vez completado el entrenamiento, comparte tu modelo en el Hub con el método [push_to_hub()](/docs/transformers/main/es/main_classes/trainer#transformers.Trainer.push_to_hub) para que todo el mundo pueda usarlo:

```py
>>> trainer.push_to_hub()
```

Para un ejemplo más detallado sobre cómo ajustar un modelo para la clasificación de tokens, consulte el [cuaderno de PyTorch](https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/token_classification.ipynb) correspondiente.

## Inferencia

Genial, ahora que has afinado un modelo, ¡puedes usarlo para inferencia!

Guarda el texto sobre el que deseas ejecutar la inferencia:

```py
>>> text = "The Golden State Warriors are an American professional basketball team based in San Francisco."
```

La forma más sencilla de probar tu modelo afinado para inferencia es usarlo en un [pipeline()](/docs/transformers/main/es/main_classes/pipelines#transformers.pipeline). Crea una instancia de `pipeline` para NER con tu modelo y pásale tu texto:

```py
>>> from transformers import pipeline

>>> classifier = pipeline("ner", model="stevhliu/my_awesome_wnut_model")
>>> classifier(text)
[{'entity': 'B-location',
  'score': 0.42658573,
  'index': 2,
  'word': 'golden',
  'start': 4,
  'end': 10},
 {'entity': 'I-location',
  'score': 0.35856336,
  'index': 3,
  'word': 'state',
  'start': 11,
  'end': 16},
 {'entity': 'B-group',
  'score': 0.3064001,
  'index': 4,
  'word': 'warriors',
  'start': 17,
  'end': 25},
 {'entity': 'B-location',
  'score': 0.65523505,
  'index': 13,
  'word': 'san',
  'start': 80,
  'end': 83},
 {'entity': 'B-location',
  'score': 0.4668663,
  'index': 14,
  'word': 'francisco',
  'start': 84,
  'end': 93}]
```

También puedes replicar manualmente los resultados de `pipeline` si lo deseas:

Tokeniza el texto y devuelve tensores de PyTorch:

```py
>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")
>>> inputs = tokenizer(text, return_tensors="pt")
```

Pasa tus entradas al modelo y devuelve los `logits`:

```py
>>> from transformers import AutoModelForTokenClassification

>>> model = AutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model")
>>> with torch.no_grad():
...     logits = model(**inputs).logits
```

Obtén la clase con la probabilidad más alta y usa el mapeo `id2label` del modelo para convertirla en una etiqueta de texto:

```py
>>> predictions = torch.argmax(logits, dim=2)
>>> predicted_token_class = [model.config.id2label[t.item()] for t in predictions[0]]
>>> predicted_token_class
['O',
 'O',
 'B-location',
 'I-location',
 'B-group',
 'O',
 'O',
 'O',
 'O',
 'O',
 'O',
 'O',
 'O',
 'B-location',
 'B-location',
 'O',
 'O']
```

