Suggerimenti su PyTorch per aumentare la tua produttività

Suggerimenti su PyTorch per migliorare produttività

 

Introduzione

 

Hai mai trascorso ore di debug di un modello di apprendimento automatico ma non riesci a trovare una ragione per cui l’accuratezza non migliora? Hai mai avuto la sensazione che tutto dovrebbe funzionare perfettamente ma per qualche motivo misterioso non ottieni risultati esemplari?

Bene, non più. Esplorare PyTorch come principiante può essere spaventoso. In questo articolo, esplorerai flussi di lavoro provati e testati che sicuramente miglioreranno i tuoi risultati e aumenteranno le prestazioni del tuo modello.

 

1. Sovradattamento su un singolo batch

 

Hai mai addestrato un modello per ore su un grande set di dati solo per scoprire che la perdita non diminuisce e l’accuratezza si stabilizza? Beh, prima di tutto, fai un controllo di sanità.

Può richiedere molto tempo addestrare e valutare su un grande set di dati ed è più facile prima risolvere i problemi dei modelli su un piccolo sottoinsieme dei dati. Una volta che siamo sicuri che il modello funzioni, possiamo facilmente scalare l’addestramento all’intero set di dati.

Invece di addestrare sull’intero set di dati, addestrare sempre su un singolo batch per un controllo di sanità.

batch = next(iter(train_dataloader)) # Ottieni un singolo batch

# Per tutte le epoche, continua ad addestrare sul singolo batch.
for epoch in range(num_epochs):
    inputs, targets = batch    
    predictions = model.train(inputs)

 

Considera il frammento di codice sopra riportato. Supponiamo di avere già un caricatore di dati di addestramento e un modello. Invece di iterare sull’intero set di dati, possiamo facilmente recuperare il primo batch del set di dati. Possiamo quindi addestrare sul singolo batch per verificare se il modello può imparare i pattern e la varianza in questa piccola porzione dei dati.

Se la perdita diminuisce a un valore molto piccolo, sappiamo che il modello può sovrapporre questi dati e possiamo essere certi che sta imparando in breve tempo. Possiamo quindi addestrare questo sull’intero set di dati semplicemente modificando una singola riga come segue:

# Per tutte le epoche, iterare su tutti i batch di dati.
for epoch in range(num_epochs):
    for batch in iter(dataloader):
        inputs, targets = batch    
        predictions = model.train(inputs)

 

Se il modello può sovrapporre un singolo batch, dovrebbe essere in grado di imparare i pattern nel set di dati completo. Questo metodo di sovrapposizione del batch facilita il debug. Se il modello non può nemmeno sovrapporre un singolo batch, possiamo essere certi che ci sia un problema con l’implementazione del modello e non con il set di dati.

 

2. Normalizza e mescola i dati

 

Per i set di dati in cui la sequenza dei dati non è importante, è utile mescolare i dati. Ad esempio, per i compiti di classificazione delle immagini, il modello si adatterà meglio ai dati se vengono fornite immagini di diverse classi all’interno di un singolo batch. Passando i dati nella stessa sequenza, rischiamo che il modello impari i pattern in base alla sequenza dei dati passati, invece di imparare la varianza intrinseca dei dati. Pertanto, è meglio passare dati mescolati. Per fare ciò, possiamo semplicemente utilizzare l’oggetto DataLoader fornito da PyTorch e impostare shuffle su True.

from torch.utils.data import DataLoader

dataset = # Caricamento dati
dataloder = DataLoader(dataset, shuffle=True)

 

Inoltre, è importante normalizzare i dati quando si utilizzano modelli di apprendimento automatico. È essenziale quando c’è una grande varianza nei nostri dati e un parametro particolare ha valori più alti rispetto a tutti gli altri attributi nel set di dati. Questo può causare il dominio di uno dei parametri su tutti gli altri, con conseguente riduzione dell’accuratezza. Vogliamo che tutti i parametri di input ricadano nello stesso intervallo ed è meglio avere una media di 0 e una varianza di 1,0. Per fare ciò, dobbiamo trasformare il nostro set di dati. Conoscendo la media e la varianza del set di dati, possiamo semplicemente utilizzare la funzione torchvision.transforms.Normalize.

import torchvision.transforms as transforms

image_transforms = transforms.Compose([
    transforms.ToTensor(),
    # Normalizza i valori nei nostri dati
    transforms.Normalize(mean=(0.5,), std=(0.5))
])

 

Possiamo passare la media e la deviazione standard per canale nella funzione transforms.Normalize e convertirà automaticamente i dati con una media di 0 e una deviazione standard di 1.

 

3. Clipping del Gradiente

 

Il problema dell’esplosione del gradiente è noto nelle reti neurali ricorrenti (RNN) e nelle reti neurali LSTM. Tuttavia, non è limitato solo a queste architetture. Qualsiasi modello con strati profondi può soffrire di gradienti esplosivi. Il backpropagation su gradienti elevati può portare alla divergenza invece di una diminuzione graduale della perdita.

Considera il frammento di codice qui di seguito.

for epoch in range(num_epochs):
    for batch in iter(train_dataloader):
        inputs, targets = batch
        predictions = model(inputs)
     
     
        optimizer.zero_grad() # Rimuove tutti i gradienti precedenti
        loss = criterion(targets, predictions)
        loss.backward() # Calcola i gradienti per i pesi del modello
     
        # Clip dei gradienti dei pesi del modello a un valore max_norm specificato.
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1)
     
        # Ottimizza i pesi del modello DOPO IL CLIPPING
        optimizer.step()

 

Per risolvere il problema dell’esplosione del gradiente, utilizziamo la tecnica del clipping del gradiente che limita i valori del gradiente entro un intervallo specificato. Ad esempio, se utilizziamo 1 come valore di clipping o norma come sopra, tutti i gradienti saranno limitati nell’intervallo [-1, 1]. Se abbiamo un valore di gradiente esplosivo di 50, verrà limitato a 1. Pertanto, il clipping del gradiente risolve il problema dell’esplosione del gradiente consentendo un’ottimizzazione lenta del modello verso la convergenza.

 

4. Modalità di Allenamento / Valutazione

 

Questa singola riga di codice aumenterà sicuramente l’accuratezza dei tuoi modelli di test. Quasi sempre, un modello di deep learning utilizzerà livelli di dropout e normalizzazione. Questi sono necessari solo per un addestramento stabile e per garantire che il modello non sia né sovradattato né diverga a causa della varianza nei dati. Livelli come BatchNorm e Dropout offrono una regolarizzazione per i parametri del modello durante l’allenamento. Tuttavia, una volta addestrati, non sono necessari. La modifica di un modello in modalità di valutazione disabilita i livelli necessari solo per l’allenamento e tutti i parametri del modello vengono utilizzati per la predizione.

Per una migliore comprensione, considera il seguente frammento di codice.

for epoch in range(num_epochs):
    
    # Utilizza la modalità di allenamento durante l'iterazione sul dataset di allenamento
    model.train()
    for batch in iter(train_dataloader):
            # Codice di addestramento e ottimizzazione della perdita
    
    # Utilizza la modalità di valutazione durante il calcolo dell'accuratezza sul dataset di convalida
    model.eval()
    for batch in iter(val_dataloader):
            # Solo predizioni e calcolo della perdita. Nessun backpropagation
            # Nessun passaggio dell'ottimizzatore quindi possiamo omettere i livelli non richiesti.

 

Quando valutiamo, non è necessario ottimizzare i parametri del modello. Non calcoliamo alcun gradiente durante le fasi di convalida. Per una valutazione migliore, possiamo quindi omettere il Dropout e gli altri livelli di normalizzazione. Ad esempio, abiliterà tutti i parametri del modello invece di solo un subset dei pesi come nel livello Dropout. Ciò aumenterà notevolmente l’accuratezza del modello poiché sarai in grado di utilizzare l’intero modello.

 

5. Utilizzare Module e ModuleList

 

Di solito, il modello PyTorch eredita dalla classe di base torch.nn.Module. Come indicato nella documentazione:

I sottomoduli assegnati in questo modo saranno registrati e i loro parametri saranno convertiti anche quando si chiama to(), ecc.

Ciò che la classe di base del modulo consente è di registrare ogni livello all’interno del modello. Possiamo quindi utilizzare model.to() e funzioni simili come model.train() e model.eval() e saranno applicate a ogni livello all’interno del modello. Se non lo facciamo, non cambieremo il dispositivo o la modalità di allenamento per ogni livello contenuto nel modello. Dovrai farlo manualmente. La classe di base del modulo effettuerà automaticamente le conversioni per te una volta che utilizzi una funzione semplicemente sull’oggetto del modello.

Inoltre, alcuni modelli contengono livelli sequenziali simili che possono essere facilmente inizializzati utilizzando un ciclo for e contenuti all’interno di una lista. Ciò semplifica il codice. Tuttavia, causa lo stesso problema descritto sopra, poiché i moduli all’interno di una semplice lista Python non vengono registrati automaticamente all’interno del modello. Dovremmo utilizzare ModuleList per contenere livelli sequenziali simili all’interno di un modello.

import torch
import torch.nn as nn


# Eredita dalla classe di base del modulo
class Model(nn.Module):
      def __init__(self, input_size, output_size):
            # Inizializza la classe genitore del modulo
            super().__init__()

             self.dense_layers = nn.ModuleList()

            # Aggiunge 5 livelli lineari e li contiene all'interno di una ModuleList
            for i in range(5):
                self.dense_layers.append(
                    nn.Linear(input_size, 512)
                )

            self.output_layer = nn.Linear(512, output_size)

    def forward(self, x):

            # Semplifica la propagazione in avanti.
            # Invece di ripetere una singola riga per ogni livello, utilizza un ciclo
            for layer in range(len(self.dense_layers)):
            x = layer(x)

            return self.output_layer(x)

 

Il frammento di codice sopra mostra il modo corretto di creare il modello e i sottolivelli con il modello. L’uso di Module e ModuleList aiuta a evitare errori inaspettati durante l’addestramento e la valutazione del modello.

 

Conclusione

 

I metodi sopra menzionati sono le migliori pratiche per il framework di apprendimento automatico PyTorch. Sono ampiamente utilizzati e sono raccomandati dalla documentazione di PyTorch. Utilizzare tali metodi dovrebbe essere il modo principale di flusso del codice di apprendimento automatico e sicuramente migliorerà i tuoi risultati.     Muhammad Arham è un ingegnere di apprendimento profondo che lavora in Computer Vision e Natural Language Processing. Ha lavorato sulla distribuzione e l’ottimizzazione di diverse applicazioni AI generative che hanno raggiunto i vertici globali presso Vyro.AI. È interessato alla costruzione e all’ottimizzazione di modelli di apprendimento automatico per sistemi intelligenti e crede nel miglioramento continuo.