Salta al contenuto
Note per Studenti Esercizio - Keras e PyTorch a confronto

Esercizio - Keras e PyTorch a confronto

Questa pagina non ha ancora la versione ripasso: qui sotto c'è il testo completo.

In questa pagina 4

Testo (laboratorio sulle reti neurali, seconda parte). La stessa rete per MNIST (Flatten, strato denso da 512512 con ReLU, dropout 0,20{,}2, uscita da 1010) si scrive in Keras e in PyTorch. Si chiede di:

  1. contare i parametri nei due framework;
  2. spiegare le differenze nella definizione del modello, della loss e dell'ottimizzatore;
  3. spiegare perché il primo ciclo di addestramento in PyTorch del laboratorio (un solo passo per epoca) impara molto meno del secondo (con DataLoader) e quante iterazioni per epoca fa ciascuno;
  4. scrivere la sottoclasse nn.Module equivalente.

Teoria usata: Reti neurali - neuroni e funzioni di attivazioneUn neurone calcola $\hat y=g(w_0+w^\top x)$: somma pesata degli ingressi più bias, poi una funzione di attivazione $g$ non lineare (Perceptron a soglia, sigmoide, tanh, ReLU e varianti). Senza non linearità ogni rete è equivalente a un solo modello lineare. Una rete feed-forward impila strati di neuroni: $a^{(k)}=g(W^{(k)}a^{(k-1)}+b^{(k)})$; con uno strato nascosto è già un approssimatore universale, ma più strati rappresentano funzioni complesse con molti meno neuroni e imparano feature gerarchiche (bordi, parti, oggetti). Lo strato di uscita e la loss si scelgono dal compito: lineare+MSE (regressione), sigmoide+cross-entropy binaria, softmax+cross-entropy (multiclasse). Il numero di parametri di uno strato denso è $n_{in}n_{out}+n_{out}$. Nel lab (Keras, MNIST) una rete 784-512-10 ha 407 050 parametri e supera il 98% di accuratezza.Reti neurali - neuroni e funzioni di attivazione →, Addestramento delle reti neurali - backpropagation e ottimizzatoriAddestrare una rete significa minimizzare la loss empirica $J(W)=\frac1n\sum_i\mathcal L(f(x^{(i)};W),y^{(i)})$ con la discesa del gradiente $W\leftarrow W-\eta,\partial J/\partial W$; in pratica a mini-batch (SGD). Il gradiente di tutti i pesi si ottiene con la backpropagation, cioè la regola della catena applicata all'indietro: $\delta^{(L)}=\partial J/\partial a^{(L)}\odot g'(z^{(L)})$, $\delta^{(l)}=(W^{(l+1)\top}\delta^{(l+1)})\odot g'(z^{(l)})$, $\partial J/\partial W^{(l)}=\delta^{(l)}a^{(l-1)\top}$ (con sigmoide e cross-entropy $\delta=\hat y-y$). Per far funzionare reti profonde: attivazioni ReLU, inizializzazione di Xavier o He (varianza $2/(n_{in}+n_{out})$ e $2/n_{in}$), batch normalization, ottimizzatori con momento o adattivi (Momentum, AdaGrad, RMSProp, Adam con $\beta_1=0{,}9$, $\beta_2=0{,}999$, lr $10^{-3}$) e un learning rate che varia nel tempo (a gradini, coseno). Si addestra tenendo d'occhio la loss di training e di validazione.Addestramento delle reti neurali - backpropagation e ottimizzatori →.

1. Parametri

Identici: in Keras Dense(512) dopo Flatten() su ingressi 28×2828\times28 ha 784⋅512+512=401 920784\cdot512+512=401\,920 parametri e Dense(10) ne ha 5 1305\,130; in PyTorch Linear(784, 512) e Linear(512, 10) hanno gli stessi pesi e bias. Totale 407 050407\,050 in entrambi (ReLU, Flatten e Dropout non hanno parametri).

2. Differenze di scrittura

aspetto Keras PyTorch
dimensione di ingresso dichiarata una volta (Input(shape=(28, 28))), le altre dimensioni si deducono si scrivono sia ingresso sia uscita di ogni strato (Linear(in_features=784, out_features=512))
attivazione argomento del layer (Dense(512, activation="relu")) strato separato (ReLU())
softmax finale activation="softmax" nell'ultimo strato assente: la rete restituisce i logits
loss e ottimizzatore parte della rete: model.compile(loss, optimizer, metrics) oggetti separati: CrossEntropyLoss() e Adam(model.parameters())
loss di classificazione sparse_categorical_crossentropy, applicata alle probabilità CrossEntropyLoss applica già la softmax e vuole etichette intere (tipo long)
dati array NumPy tensori (torch.tensor), suddivisione e mini-batch gestiti a mano
addestramento una chiamata: model.fit(...) ciclo scritto a mano

Errore tipico: mettere una softmax nel modello PyTorch e poi usare CrossEntropyLoss applica la softmax due volte.

3. I due cicli di addestramento

Primo ciclo del laboratorio. Per ogni epoca: optimizer.zero_grad(), forward sull'intero training (tutti i 54 00054\,000 campioni), loss, backward(), optimizer.step(). Una sola iterazione per epoca: in cinque epoche 55 aggiornamenti dei pesi in tutto. È una discesa del gradiente a batch completo.

Secondo ciclo. Con DataLoader(batch_size=32, shuffle=True): ⌈54 000/32⌉=1 688\lceil54\,000/32\rceil=1\,688 iterazioni per epoca (il laboratorio stampa 1 6881\,688 batch di training; ⌈6 000/32⌉=188\lceil6\,000/32\rceil=188 di validazione), quindi 5⋅1 688=8 4405\cdot1\,688=8\,440 aggiornamenti: una discesa a mini-batch, come fa fit di Keras con batch_size=32. Moltissimi più passi, quindi molto più apprendimento a parità di epoche. È il motivo per cui nel primo ciclo la loss scende lentamente.

Nel ciclo si ricordano: model.train() per attivare il dropout e model.eval() con torch.no_grad() per la valutazione; zero_grad() a ogni passo perché PyTorch somma i gradienti; shuffle=True solo sul training.

4. Il modello come classe

python
import torch.nn as nn

class MioModello(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear1 = nn.Linear(784, 512)
        self.relu = nn.ReLU()
        self.dropout = nn.Dropout(0.2)
        self.linear2 = nn.Linear(512, 10)

    def forward(self, x):                 # equivalente di call() in Keras
        x = self.flatten(x)
        x = self.relu(self.linear1(x))
        x = self.dropout(x)
        return self.linear2(x)            # logits

__init__ dichiara gli strati (e quindi i parametri), forward descrive il percorso dei dati (in Keras call). Salvataggio: il laboratorio salva l'intero oggetto con torch.save(model, "m.pth") e lo ricarica con torch.load("m.pth", weights_only=False); è più sicuro salvare solo i pesi, torch.save(model.state_dict(), "m.pth") e model.load_state_dict(torch.load("m.pth", weights_only=True)), perché ricaricare un oggetto intero esegue codice contenuto nel file (in Keras model.save e load_model).

Conclusione del laboratorio. Keras è più semplice, PyTorch richiede più codice ma rende ogni passo visibile ed è più flessibile e comodo da debuggare: per questo è molto usato nella ricerca.

Lezioni in cui compare

Teoria collegata