[Apprentissage de la série Huggingface] Affiner un modèle de pré-formation

Traitement des données

Charger un ensemble de données depuis le Hub

Nous pouvons utiliser le code suivant pour former un classificateur de séquence

import torch
from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification

# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
    "I've been waiting for a HuggingFace course my whole life.",
    "This course is amazing!",
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")

# This is new
batch["labels"] = torch.tensor([1, 1])

optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()

Évidemment, il est impossible d’obtenir de bons résultats avec deux phrases, nous avons donc besoin d’un ensemble de données plus important.

  • huggingface enregistre également de nombreux ensembles de données, qui peuvent être load_datatéléchargés via

    from datasets import load_dataset
    
    raw_datasets = load_dataset("glue", "mrpc") # GLUE benchmark 中的 MRPC数据集
    raw_datasets
    > DatasetDict({
          
          
        train: Dataset({
          
          
            features: ['sentence1', 'sentence2', 'label', 'idx'],
            num_rows: 3668
        })
        validation: Dataset({
          
          
            features: ['sentence1', 'sentence2', 'label', 'idx'],
            num_rows: 408
        })
        test: Dataset({
          
          
            features: ['sentence1', 'sentence2', 'label', 'idx'],
            num_rows: 1725
        })
    })
    
    • Nous pouvons accéder à chaque paire de phrases en indexant

      raw_train_dataset = raw_datasets["train"]
      raw_train_dataset[0]
      
      >{
              
              'idx': 0,
       'label': 1,
       'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
       'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}
      
    • Si vous souhaitez connaître la signification de chaque partie de l'ensemble de données, vous pouvez featuresla visualiser via l'attribut

      raw_train_dataset.features
      {
              
              'sentence1': Value(dtype='string', id=None),
       'sentence2': Value(dtype='string', id=None),
       'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
       'idx': Value(dtype='int32', id=None)}
      

Prétraiter un ensemble de données

Le tokenizer peut traiter directement les données appariées, ce qui BERTest espéré

inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
>{
    
     
  'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
  'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
  'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
  • token_type_idsIl est utilisé pour distinguer la première phrase de la deuxième phrase, qui peut ne pas être disponible dans tous les tokenizer. Ils ne sont restitués que si le modèle sait quoi en faire, puisqu'il les a vus lors du pré-entraînement.
    • Ici, BERTtoken_type_dis est utilisé en pré-formation
    • Si nous décodons, nous constaterons que le format est [CLS] phrase1 [SEP] phrase2 [SEP]

Nous pouvons laisser le tokenizer traiter une liste de paires de phrases en lui donnant une liste de la première phrase et une liste de la deuxième phrase.

tokenized_dataset = tokenizer(
    raw_datasets["train"]["sentence1"],
    raw_datasets["train"]["sentence2"],
    padding=True,
    truncation=True,
)

Cela renvoie un dictionnaire, et nous pouvons ajouter ce résultat renvoyé à l'ensemble de données d'origine

def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
  • Nous utilisons batched=True lors de l'appel de map, afin que la fonction soit appliquée à plusieurs éléments de l'ensemble de données en même temps, au lieu de s'appliquer à chaque élément séparément (plus rapide)
  • Nous pouvons également modifier les champs existants si la fonction de prétraitement renvoie une nouvelle valeur pour une clé existante dans l'ensemble de données.

Rembourrage dynamique

La fonction chargée de mettre les échantillons dans un lot est collate function(c'est un paramètre de DataLoader, qui convertit les échantillons en tenseur pytorch par défaut et les connecte)

Nous différons délibérément le remplissage, en l'appliquant uniquement dans chaque lot, et évitons les entrées trop longues avec beaucoup de remplissage.

  • Cela rendra la formation plus rapide
  • Mais il y aura des problèmes sur le TPU, le TPU préfère une forme fixe

Pour ce faire en pratique, nous devons définir une fonction d'assemblage qui appliquera la quantité correcte de remplissage aux éléments de l'ensemble de données que nous souhaitons regrouper.

from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
samples = tokenized_datasets["train"][:8]
batch = data_collator(samples)
  • Un tokenize doit être donné lors de l'initialisation, afin qu'il sache quel token remplir, s'il doit remplir à gauche ou à droite

Affiner un modèle avec l'API Trainer

Transformers fournit une classe Trainer pour aider à affiner le modèle de pré-formation sur ses propres données. Une fois le traitement des données terminé, il n'y a que quelques étapes pour définir le Trainer.

Nous résumons les opérations précédentes

from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)


tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

Entraînement

Avant de définir le formateur, vous devez définir la classe TrainingArguments, qui inclut tous les paramètres. Il nous suffit de fournir l'emplacement où le modèle doit être enregistré, et d'autres paramètres peuvent être bien entraînés en conservant la valeur par défaut.

from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")

définir un modèle

from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

Définir un formateur

from transformers import Trainer

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)
  • Lorsque nous passons le tokenizer, le data_collator ici prendra par défaut ce que nous avons défini ci-dessus, nous pouvons donc omettre cette étape

former

trainer.train()
  • affichera une perte d'entraînement toutes les 500 étapes, mais ne nous dira pas à quel point le modèle est bon :
    • N'a pas laissé le formateur évaluer pendant la formation (en définissant évaluation_strategy sur steps/epoch)
    • Aucun moyen n'est fourni compute_metrics()pour calculer une métrique lors de l'évaluation, juste une perte de retour, ce qui n'est pas intuitif

Évaluation

Nous pouvons utiliser Trainer.predict() pour faire prédire notre modèle

predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
>(408, 2) (408,)
  • La sortie est un tuple avec trois champs (prédictions, label_ids, métriques)
  • Ici, les métriques n'ont que des pertes et du temps d'exécution, si nous définissons notre propre fonction calculate_metrics() et la transmettons à Trainer , ce champ contiendra également le résultat de computation_metrics()
  • les prédictions sont des logits pour chaque élément de l'ensemble de données

Pour comparer notre prédiction à la vraie étiquette, nous avons besoin de l'indice de la valeur maximale sur le deuxième axe :

import numpy as np

preds = np.argmax(predictions.predictions, axis=-1)

Pour construire computing_metraic(), nous pouvons utiliser la bibliothèque Evaluate

import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
> {
    
    'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}

Finalement, en mettant le tout ensemble, nous obtenons la fonction calculate_metrics()

def compute_metrics(eval_preds):
    metric = evaluate.load("glue", "mrpc")
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)

À ce stade, nous pouvons définir un nouveau formateur

training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
  • Notez que l’évaluation sera effectuée à chaque époque. Cette fois, il affichera la perte de validation et les métriques à la fin de chaque époque en plus de la perte de formation.

Le processus derrière Trainer

Un bref résumé du traitement des données

from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding

raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)


def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

préparation avant l'entraînement

Nous devons effectuer certains traitements sur tokenized_datasets, en particulier :

  • sentence1Supprimez les colonnes (telles que les colonnes et sentence2) qui correspondent aux valeurs que le modèle n'attend pas .
  • Renommez le nom de la colonne labelen labels(car le modèle s'attend à ce que le paramètre soit labels).
  • Formatez l'ensemble de données afin qu'il renvoie des tenseurs PyTorch au lieu de listes.
tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")
tokenized_datasets["train"].column_names

Charger le chargeur de données

from torch.utils.data import DataLoader

train_dataloader = DataLoader(
    tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator
)

modèle de charge

from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
  • Le modèle est chargé sur le GPU

    import torch
    
    device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
    model.to(device)
    

optimiseur de charge

from transformers import AdamW

optimizer = AdamW(model.parameters(), lr=5e-5)

planificateur de chargement

from transformers import get_scheduler

num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

boucle d'entraînement

from tqdm.auto import tqdm

progress_bar = tqdm(range(num_training_steps))  # 进度条

model.train()
for epoch in range(num_epochs):
    for batch in train_dataloader:
        batch = {
    
    k: v.to(device) for k, v in batch.items()}
        outputs = model(**batch)
        loss = outputs.loss
        loss.backward()

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        progress_bar.update(1)

évaluer la boucle

import evaluate

metric = evaluate.load("glue", "mrpc")
model.eval()
for batch in eval_dataloader:
    batch = {
    
    k: v.to(device) for k, v in batch.items()}
    with torch.no_grad():
        outputs = model(**batch)

    logits = outputs.logits
    predictions = torch.argmax(logits, dim=-1)
    metric.add_batch(predictions=predictions, references=batch["labels"])

metric.compute()
  • add_batch()Cet indicateur cumule en fait pour nous tous les résultats lorsque nous utilisons la méthode de la boucle de prévision batch. Une fois que nous avons tout accumulé batch, nous pouvons metric.compute()obtenir le résultat final en utilisant

Entraînement en circuit avec Accelerate : un entraînement complet - Cours Hugging Face

Je suppose que tu aimes

Origine blog.csdn.net/qq_52852138/article/details/128997766
conseillé
Classement