[Aprendizaje de la serie Huggingface] Ajuste de un modelo de preentrenamiento

Procesando los datos

Cargar un conjunto de datos desde el Hub

Podemos usar el siguiente código para entrenar un clasificador de secuencia.

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()

Obviamente, es imposible obtener buenos resultados con dos oraciones, por lo que necesitamos un conjunto de datos más grande.

  • Huggingface también guarda una gran cantidad de conjuntos de datos, que se pueden load_datadescargar a través de

    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
        })
    })
    
    • Podemos acceder a cada par de oraciones indexando

      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 desea conocer el significado de cada parte del conjunto de datos, puede featuresverlo a través del atributo

      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)}
      

Preprocesar un conjunto de datos

El tokenizador puede procesar directamente datos emparejados, que es BERTlo que se espera.

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_idsSe utiliza para distinguir la primera oración de la segunda, que puede no estar disponible en todos los tokenizadores. Sólo se devuelven si el modelo sabe qué hacer con ellos, ya que los ha visto durante el preentrenamiento.
    • Aquí BERTtoken_type_dis se usa en el entrenamiento previo.
    • Si decodificamos, encontraremos que el formato es [CLS] oración1 [SEP] oración2 [SEP]

Podemos dejar que el tokenizador procese una lista de pares de oraciones dándole una lista de la primera oración y una lista de la segunda oración.

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

Esto devuelve un diccionario y podemos agregar este resultado devuelto al conjunto de datos original.

def tokenize_function(example):
    return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
  • Usamos lotes=True cuando llamamos a map, de modo que la función se aplicará a múltiples elementos del conjunto de datos al mismo tiempo, en lugar de aplicar a cada elemento por separado (más rápido).
  • También podemos cambiar los campos existentes si la función de preprocesamiento devuelve un nuevo valor para una clave existente en el conjunto de datos.

Relleno dinámico

La función responsable de poner muestras en un lote es collate function(es un parámetro de DataLoader, que convierte muestras en tensor de pytorch de forma predeterminada y las conecta)

Aplazamos deliberadamente el relleno, aplicándolo solo en cada lote y evitamos entradas demasiado largas con mucho relleno.

  • Esto hará que el entrenamiento sea más rápido.
  • Pero habrá problemas con el TPU, el TPU prefiere una forma fija

Para hacer esto en la práctica, tenemos que definir una función de clasificación que aplicará la cantidad correcta de relleno a los elementos del conjunto de datos que queremos agrupar.

from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
samples = tokenized_datasets["train"][:8]
batch = data_collator(samples)
  • Es necesario proporcionar un token durante la inicialización, para que pueda saber qué token llenar, ya sea a la izquierda o a la derecha.

Ajustar un modelo con la API Trainer

Transformers proporciona una clase de Entrenador para ayudar a ajustar el modelo de preentrenamiento en sus propios datos. Cuando finaliza el procesamiento de datos, solo quedan unos pocos pasos para definir el Entrenador.

Resumimos las operaciones anteriores.

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)

Capacitación

Antes de definir el entrenador, es necesario definir la clase TrainingArguments, que incluye todos los parámetros. Solo necesitamos proporcionar la ubicación donde se guardará el modelo, y se pueden entrenar bien otros parámetros manteniendo el valor predeterminado.

from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")

definir un modelo

from transformers import AutoModelForSequenceClassification

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

Definir un entrenador

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,
)
  • Cuando pasamos el tokenizador, el data_collator aquí tendrá de forma predeterminada lo que definimos anteriormente, por lo que podemos omitir este paso.

tren

trainer.train()
  • generará una pérdida de entrenamiento cada 500 pasos, pero no nos dirá qué tan bueno es el modelo:
    • No permitió que el capacitador evaluara durante el entrenamiento (estableciendo la estrategia_evaluación en pasos/época)
    • No se proporciona compute_metrics()para calcular una métrica durante la evaluación, solo devuelve la pérdida, lo cual no es intuitivo.

Evaluación

Podemos usar Trainer.predict() para hacer que nuestro modelo prediga

predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
>(408, 2) (408,)
  • El resultado es una tupla con tres campos (predicciones, label_ids, métricas).
  • Aquí las métricas solo tienen pérdida y algo de tiempo de ejecución, si definimos nuestra propia función Compute_metrics() y la pasamos a Trainer , este campo también contendrá el resultado de Compute_metrics()
  • Las predicciones son logits para cada elemento del conjunto de datos.

Para comparar nuestra etiqueta predicha con la verdadera, necesitamos el índice del valor máximo en el segundo eje:

import numpy as np

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

Para construir Compute_metraic(), podemos usar la biblioteca Evaluar.

import evaluate

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

Finalmente juntando todo obtenemos la función Compute_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)

En este punto podemos definir un nuevo Entrenador.

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,
)
  • Tenga en cuenta que la evaluación se realizará en cada época. Esta vez, generará la pérdida de validación y las métricas al final de cada época además de la pérdida de entrenamiento.

El proceso detrás de Trainer

Un breve resumen del procesamiento de datos.

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)

preparación antes del entrenamiento

Necesitamos realizar algún procesamiento en tokenized_datasets, específicamente:

  • sentence1Elimine las columnas (como las columnas y sentence2) que correspondan a valores que el modelo no espera .
  • Cambie el nombre de la columna labela labels(como el modelo espera que sea el parámetro labels).
  • Formatee el conjunto de datos para que devuelva tensores de PyTorch en lugar de listas.
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

Cargar cargador de datos

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
)

modelo de carga

from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
  • El modelo está cargado en la gpu.

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

optimizador de carga

from transformers import AdamW

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

programador de carga

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,
)

bucle de entrenamiento

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)

evaluar bucle

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()En realidad, este indicador acumula todos los resultados cuando utilizamos el método del ciclo de pronóstico batch. Una vez que hayamos acumulado todo batch, podemos metric.compute()obtener el resultado final usando

Entrenamiento en circuito con Accelerate: un entrenamiento completo - Curso Hugging Face

Supongo que te gusta

Origin blog.csdn.net/qq_52852138/article/details/128997766
Recomendado
Clasificación