LLM Course documentation

O instruire completă

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

O instruire completă

Ask a Question Open In Colab Open In Studio Lab

Acum vom vedea cum să obținem aceleași rezultate ca în secțiunea anterioară, dar fără să folosim clasa Trainer. Din nou, presupunem că ați parcurs deja procesarea datelor din secțiunea 2. Iată un scurt rezumat al tot ceea ce veți avea nevoie:

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)

Pregătirea pentru antrenament

Înainte de a scrie efectiv bucla de antrenament, va trebui să definim câteva obiecte. Primele sunt încărcătoarele de date (dataloaders) pe care le vom folosi pentru a itera pe batch-uri. Dar, înainte de a putea defini acele dataloaders, trebuie să aplicăm un pic de postprocesare dataset-urilor noastre tokenized_datasets, pentru a ne ocupa de câteva lucruri pe care Trainer le făcea automat pentru noi. Mai exact, trebuie să:

  • Eliminăm coloanele care corespund valorilor pe care modelul nu le așteaptă (cum ar fi coloanele sentence1 și sentence2).
  • Redenumim coloana label în labels (pentru că modelul se așteaptă ca argumentul să se numească labels).
  • Setăm formatul dataset-urilor astfel încât să returneze tensori PyTorch în loc de liste.

tokenized_datasets are câte o metodă pentru fiecare dintre acești pași:

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

Putem apoi să verificăm că rezultatul are doar coloanele pe care modelul le va accepta:

["attention_mask", "input_ids", "labels", "token_type_ids"]

Acum, după ce am terminat acest pas, putem defini foarte ușor dataloader-urile noastre:

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
)

Pentru a verifica rapid că nu există nicio eroare în procesarea datelor, putem inspecta un batch astfel:

for batch in train_dataloader:
    break
{k: v.shape for k, v in batch.items()}
{'attention_mask': torch.Size([8, 65]),
 'input_ids': torch.Size([8, 65]),
 'labels': torch.Size([8]),
 'token_type_ids': torch.Size([8, 65])}

Observați că formele reale ar putea fi ușor diferite pentru voi, pentru că am setat shuffle=True în dataloader-ul nostru de antrenament și pentru că împachetăm (padding) la lungimea maximă în interiorul batch-ului.

Acum că am terminat complet procesarea datelor (un obiectiv satisfăcător, dar uneori greu de atins pentru orice practician ML), să trecem la model. Îl instanțiem exact ca în secțiunea anterioară:

from transformers import AutoModelForSequenceClassification

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

Pentru a ne asigura că totul va decurge fără probleme în timpul antrenamentului, trecem batch-ul nostru prin model:

outputs = model(**batch)
print(outputs.loss, outputs.logits.shape)
tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2])

Toate modelele 🤗 Transformers vor returna pierderea (loss) când labels sunt furnizate, și, de asemenea, obținem logits (două pentru fiecare intrare în batch, deci un tensor de mărimea 8 x 2).

Suntem aproape gata să scriem bucla de antrenament! Ne mai lipsesc două lucruri: un optimizer și un scheduler pentru rata de învățare. Pentru că încercăm să reproducem ceea ce făcea Trainer, vom folosi aceleași valori implicite. Optimizer-ul folosit de Trainer este AdamW, care este același cu Adam, dar cu o abordare particulară pentru regularizarea weight decay (vedeți lucrarea “Decoupled Weight Decay Regularization” de Ilya Loshchilov și Frank Hutter):

from transformers import AdamW

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

În final, scheduler-ul pentru rata de învățare folosit implicit este doar o descreștere liniară de la valoarea maximă (5e-5) la 0. Pentru a-l defini corect, trebuie să știm numărul de pași de antrenament pe care îi vom face, care este numărul de epoci dorit înmulțit cu numărul de batch-uri de antrenament (care este lungimea dataloader-ului nostru de antrenament). Trainer folosește trei epoci implicit, așa că vom urma acest exemplu:

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,
)
print(num_training_steps)
1377

Bucla de antrenament

Încă un lucru: vom dori să folosim GPU-ul dacă avem acces la unul (pe un CPU, antrenamentul poate dura câteva ore în loc de câteva minute). Pentru asta, definim un device pe care vom pune modelul și batch-urile noastre:

import torch

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

Acum suntem gata de antrenament! Pentru a ne face o idee despre momentul în care se va termina antrenamentul, adăugăm o bară de progres peste numărul de pași de antrenament, folosind biblioteca tqdm:

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)

Observați că partea principală a buclei de antrenament arată foarte asemănător cu cea din introducere. Nu am cerut niciun raport, așa că această buclă de antrenament nu ne va spune nimic despre performanța modelului. Pentru a avea feedback, trebuie să adăugăm o buclă de evaluare.

Bucla de evaluare

Ca și înainte, vom folosi o metrică oferită de biblioteca 🤗 Evaluate. Am văzut deja metoda metric.compute(), dar metricle pot de fapt să acumuleze batch-uri pentru noi în timp ce parcurgem bucla de predicție, cu metoda add_batch(). Odată ce am acumulat toate batch-urile, putem obține rezultatul final cu metric.compute(). Iată cum să implementăm toate acestea într-o buclă de evaluare:

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()
{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535}

Din nou, rezultatele voastre vor fi ușor diferite din cauza aleatorietății în inițializarea layer-ului final (model head) și a amestecării datelor, dar ar trebui să fie în aceeași zonă valorică.

✏️ Încercați! Modificați bucla de antrenament anterioară pentru a vă rafina modelul pe dataset-ul SST-2.

Îmbunătățiți circuitul de antrenament cu 🤗 Accelerate

Bucla de antrenament pe care am definit-o anterior funcționează bine pe un singur CPU sau GPU. Dar, folosind biblioteca 🤗 Accelerate, cu doar câteva ajustări putem activa antrenarea distribuită pe mai multe GPU-uri sau TPU-uri. Pornind de la crearea dataloader-urilor de antrenament și validare, iată cum arată bucla noastră manuală de antrenament:

from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

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

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

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)

Iar aici sunt modificările:

+ from accelerate import Accelerator
  from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler

+ accelerator = Accelerator()

  model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
  optimizer = AdamW(model.parameters(), lr=3e-5)

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

+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+     train_dataloader, eval_dataloader, model, optimizer
+ )

  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
  )

  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()
+         accelerator.backward(loss)

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

Prima linie de adăugat este linia de import. A doua linie instanțiază un obiect Accelerator care va examina mediul și va inițializa setarea distribuită corespunzătoare. 🤗 Accelerate se ocupă de plasarea pe device pentru voi, așa că puteți elimina liniile care pun modelul pe device (sau, dacă preferați, le puteți schimba să folosească accelerator.device în loc de device).

Apoi, partea principală a muncii este făcută în linia care trimite dataloaders, modelul și optimizer-ul la accelerator.prepare(). Aceasta va împacheta acele obiecte în containerul potrivit pentru a vă asigura că antrenarea distribuită funcționează corespunzător. Restul modificărilor constau în eliminarea liniei care mută batch-ul pe device (din nou, dacă doriți să o păstrați puteți doar să o schimbați să folosească accelerator.device) și înlocuirea loss.backward() cu accelerator.backward(loss).

⚠️ Pentru a beneficia de creșterea vitezei oferită de Cloud TPU-uri, vă recomandăm să împachetați mostrele la o lungime fixă folosind argumentele `padding="max_length"` și `max_length` ale tokenizer-ului.

Dacă vreți să copiați și să lipiți pentru a vă juca, iată cum arată bucla completă de antrenament cu 🤗 Accelerate:

from accelerate import Accelerator
from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler

accelerator = Accelerator()

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)

train_dl, eval_dl, model, optimizer = accelerator.prepare(
    train_dataloader, eval_dataloader, model, optimizer
)

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

progress_bar = tqdm(range(num_training_steps))

model.train()
for epoch in range(num_epochs):
    for batch in train_dl:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

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

Plasând acest cod într-un fișier train.py îl face rulabil pe orice tip de configurare distribuită. Pentru a-l încerca în configurarea voastră distribuită, rulați comanda:

accelerate config

care vă va cere să răspundeți la câteva întrebări și vă va crea un fișier de configurare folosit de comanda:

accelerate launch train.py

care va porni antrenarea distribuită.

Dacă vreți să încercați asta într-un Notebook (de exemplu, pentru a-l testa cu TPU-uri pe Colab), doar lipiți codul într-o funcție training_function() și rulați într-o celulă finală:

from accelerate import notebook_launcher

notebook_launcher(training_function)

Puteți găsi mai multe exemple în repo-ul 🤗 Accelerate.

< > Update on GitHub