Buckets:
| # Un addestramento completo | |
| <DocNotebookDropdown | |
| classNames="absolute z-10 right-0 top-0" | |
| options={[ | |
| {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/it/chapter3/section4.ipynb"}, | |
| {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/it/chapter3/section4.ipynb"}, | |
| ]} /> | |
| <Youtube id="Dh9CL8fyG80"/> | |
| Ora vedremo come ottenere gli stessi risultati della sezione precedente senza utilizzare la classe `Trainer`. Ancora una volta, aver compiuto il processing dei dati spiegato nella sezione 2 è un prerequisito. Ecco un riassunto di tutto ciò di cui avrete bisogno: | |
| ```py | |
| 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) | |
| ``` | |
| ### Preparazione all'addestramento | |
| Prima di cominciare a scrivere il nostro ciclo di addestramento, dobbiamo definire alcuni oggetti. Per prima cosa, i dataloaders (caricatori di dati) che useremo per iterare sulle batch. Ma prima di poter definire i dataloaders, dobbiamo applicare un po' di postprocessing ai nostri `tokenized_datasets`, per compiere alcune operazione che `Trainer` gestiva in automatico per noi. Nello specifico dobbiamo: | |
| - Rimuovere le colonne corrispondente a valori che il modello non si aspetta (come ad esempio le colonne `sentence1` e `sentence2 `). | |
| - Rinominare la colonna `label` a `labels` (perché il modello si aspetta questo nome). | |
| - Fissare il formato dei datasets in modo che restituiscano tensori Pytorch invece di liste. | |
| L'oggetto `tokenized_datasets` ha un metodo per ciascuno di questi punti: | |
| ```py | |
| 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 | |
| ``` | |
| Possiamo poi controllare che il risultato ha solo solo colonne che saranno accettate dal nostro modello: | |
| ```python | |
| ["attention_mask", "input_ids", "labels", "token_type_ids"] | |
| ``` | |
| Ora che questo è fatto, possiamo finalmente definire i dataloaders in maniera semplice: | |
| ```py | |
| 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 | |
| ) | |
| ``` | |
| Per controllare velocemente che non ci sono errori nel processing dei dati, possiamo ispezionare una batch in questo modo: | |
| ```py | |
| for batch in train_dataloader: | |
| break | |
| {k: v.shape for k, v in batch.items()} | |
| ``` | |
| ```python out | |
| {'attention_mask': torch.Size([8, 65]), | |
| 'input_ids': torch.Size([8, 65]), | |
| 'labels': torch.Size([8]), | |
| 'token_type_ids': torch.Size([8, 65])} | |
| ``` | |
| È importante sottolineare che i valori di shape (forma) potrebbero essere leggermente diversi per voi, poiché abbiamo fissato `shuffle=True` (rimescolamento attivo) per i dataloader di apprendimento, e stiamo applicando padding alla lunghezza massima all'interno della batch. | |
| Ora che il preprocessing dei dati è completato (uno scopo soddisfacente ma elusivo per qualunque praticante di ML), focalizziamoci sul modello. Lo istanziamo esattamente come avevamo fatto nella sezione precedente: | |
| ```py | |
| from transformers import AutoModelForSequenceClassification | |
| model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) | |
| ``` | |
| Per assicurarci che tutto andrà bene durante l'addestramento, passiamo la batch al modello: | |
| ```py | |
| outputs = model(**batch) | |
| print(outputs.loss, outputs.logits.shape) | |
| ``` | |
| ```python out | |
| tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2]) | |
| ``` | |
| Tutti i modelli 🤗 Transformers restituiscono il valore obiettivo quando vengono fornite loro le `labels`, e anche i logits (due per ciascun input della batch, quindi un tensore di dimensioni 8 x 2). | |
| Siamo quasi pronti a scrivere il ciclo di addestramento! Mancano solo due cose: un ottimizzatore e un learning rate scheduler. Poiché stiamo tentando di replicare a mano ciò che viene fatto dal `Trainer`, utilizzeremo gli stessi valori di default. L'ottimizzatore utilizzato dal `Trainer` è `AdamW`, che è lo stesso di Adam ma con una variazione per quanto riguarda la regolarizzazione del decadimento dei pesi (rif. ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) di Ilya Loshchilov e Frank Hutter): | |
| ```py | |
| from torch.optim import AdamW | |
| optimizer = AdamW(model.parameters(), lr=5e-5) | |
| ``` | |
| Infine, il learning rate scheduler usato di default è solo un decadimento lineare dal valore massimo (5e-5) fino a 0. Per definirlo correttamente, dobbiamo sapere il numero di iterazioni per l'addestramento, che è dato dal numero di epoche che vogliamo eseguire moltiplicato per il numero di batch per l'addestramento (ovverosia la lunghezza del dataloader). Il `Trainer` usa 3 epoche di default, quindi: | |
| ```py | |
| 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) | |
| ``` | |
| ```python out | |
| 1377 | |
| ``` | |
| ### Il ciclo di addestramento | |
| Un'ultima cosa: se si ha accesso ad una GPU è consigliato usarla (su una CPU, l'addestramento potrebbe richiedere svariate ore invece di un paio di minuti). Per usare la GPU, definiamo un `device` su cui spostare il modello e le batch: | |
| ```py | |
| import torch | |
| device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") | |
| model.to(device) | |
| device | |
| ``` | |
| ```python out | |
| device(type='cuda') | |
| ``` | |
| Siamo pronti per l'addestramento! Per avere un'intuizione di quando sarà finito, aggiungiamo una barra di progresso sul numero di iterazioni di addestramento, usando la libreria `tqdm`: | |
| ```py | |
| 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) | |
| ``` | |
| Potete vedere che il nocciolo del ciclo di addestramento è molto simile a quello nell'introduzione. Non abbiamo chiesto nessun report, quindi il ciclo non ci informerà su come si sta comportando il modello. Dobbiamo aggiungere un ciclo di valutazione per quello. | |
| ### Il ciclo di valutazione | |
| Come fatto in precedenza, utilizzeremo una metrica fornita dalla libreria 🤗 Datasets. Abbiamo già visto il metodo `metric.compute()`, ma le metriche possono automaticamente accumulare le batch nel ciclo di predizione col metodo `add_batch()`. Una volta accumulate tutte le batch, possiamo ottenere il risultato finale con `metric.compute()`. Ecco come implementare tutto ciò in un ciclo di valutazione: | |
| ```py | |
| from datasets import load_metric | |
| metric = load_metric("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() | |
| ``` | |
| ```python out | |
| {'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} | |
| ``` | |
| Ancora una volta, i vostri risultati potrebbero essere leggermente diversi a causa della casualità nell'inizializzazione della testa del modello e del ricombinamento dei dati, ma dovrebbero essere nello stesso ordine di grandezza. | |
| > [!TIP] | |
| > ✏️ **Prova tu!** Modifica il ciclo di addestramento precedente per affinare il modello sul dataset SST-2. | |
| ### Potenzia il tuo ciclo di addestramento con 🤗 Accelerate | |
| <Youtube id="s7dy8QRgjJ0" /> | |
| Il ciclo di addestramento che abbiamo definito prima funziona bene per una sola CPU o GPU. Ma grazie alla libreria [🤗 Accelerate](https://github.com/huggingface/accelerate), con alcuni aggiustamenti possiamo attivare l'addestramento distribuito su svariate GPU o TPU. Partendo dalla creazione dei dataloaders di addestramento e validazione, ecco l'aspetto del nostro ciclo di addestramento manuale: | |
| ```py | |
| from torch.optim import AdamW | |
| from transformers import 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) | |
| ``` | |
| Ecco i cambiamenti necessari: | |
| ```diff | |
| + from accelerate import Accelerator | |
| from torch.optim import AdamW | |
| from transformers import 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 di tutto bisogna inserire la linea di importazione. La seconda linea istanzia un oggetto di tipo `Accelerator` che controllerà e inizializzerà il corretto ambiente distribuito. 🤗 Accelerate gestice il posizionamento sui dispositivi per voi, quindi potete togliere le linee che spostavano il modello sul dispositivo (o, se preferite, cambiare in modo da usare `acceleratore.device` invece di `device`). | |
| Dopodiché la maggior parte del lavoro è fatta dalla linea che invia i dataloaders, il modello e gli ottimizzatori a `accelerator.prepare()`. Ciò serve a incapsulare queli oggetti nei contenitori appropriati per far sì che l'addestramento distribuito funzioni correttamente. I cambiamenti rimanenti sono la rimozione della linea che sposta la batch sul `device` (dispositivo) (di nuovo, se volete tenerlo potete semplicemente cambiarlo con `accelerator.device`) e lo scambio di `loss.backward()` con `accelerator.backward(loss)`. | |
| > [!TIP] | |
| > ⚠️ Per poter beneficiare dell'accelerazione offerta da Cloud TPUs, è raccomandabile applicare padding ad una lunghezza fissa tramite gli argomenti `padding="max_length"` e `max_length` del tokenizer. | |
| Se volete copiare e incollare il codice per giocarci, ecco un ciclo di addestramento completo che usa 🤗 Accelerate: | |
| ```py | |
| from accelerate import Accelerator | |
| from torch.optim import AdamW | |
| from transformers import 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) | |
| ``` | |
| Mettere questo codice in uno script `train.py` lo renderà eseguibile su qualsiasi ambiente distribuito. Per provarlo nel vostro ambiente distribuito, eseguite: | |
| ```bash | |
| accelerate config | |
| ``` | |
| che vi chiederà di rispondere ad alcune domande e inserirà le vostre risposte in un documento di configurazione usato dal comando: | |
| ``` | |
| accelerate launch train.py | |
| ``` | |
| che eseguirà l'addestramento distribuito. | |
| Se volete provarlo in un Notebook (ad esempio, per testarlo con le TPUs su Colab), incollate il codice in una `training_function()` ed eseguite l'ultima cella con: | |
| ```python | |
| from accelerate import notebook_launcher | |
| notebook_launcher(training_function) | |
| ``` | |
| Potete trovare altri esempi nella [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples). | |
| <EditOnGithub source="https://github.com/huggingface/course/blob/main/chapters/it/chapter3/4.mdx" /> |
Xet Storage Details
- Size:
- 14.1 kB
- Xet hash:
- 5eeb3acbe7a2d96b0c9f2637ef7055d2acb49faa2789907dfe93144068571add
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.