Torna al catalogo
Season 55 17 Episodi 1h 4m 2026

PyTorch Fundamentals

v2.11 — Edizione 2026. Un corso audio completo sulla creazione di modelli di deep learning utilizzando PyTorch versione 2.11. Copre Tensors, Autograd, Neural Networks, Optimizers, DataLoaders e il compilatore PyTorch.

Framework AI/ML Python Core Scienza dei Dati
PyTorch Fundamentals
In Riproduzione
Click play to start
0:00
0:00
1
L'identità principale di PyTorch
Scopri lo scopo fondamentale di PyTorch e cosa lo distingue dalle tradizionali librerie matematiche. Questo episodio spiega il ruolo dei Tensors, di Autograd e dell'accelerazione GPU nel deep learning moderno.
3m 40s
2
Comprendere i Tensors di PyTorch
Immergiti nei Tensors, la struttura dati fondamentale di PyTorch. Scopri come collegano i dati grezzi alle Neural Networks e condividono la memoria in modo fluido con gli array Numpy.
4m 08s
3
Operazioni sui Tensors e memoria
Impara a manipolare i Tensors in modo efficiente. Questo episodio copre le operazioni aritmetiche, la concatenazione, i trasferimenti tra dispositivi e le implicazioni di memoria delle operazioni in-place.
3m 43s
4
La magia di Autograd
Scopri il motore che rende possibile il deep learning in PyTorch. Impara come Autograd traccia dinamicamente le operazioni e calcola automaticamente derivate complesse.
3m 45s
5
Controllare il tracciamento dei gradienti
Scopri come disabilitare il tracciamento dei gradienti di PyTorch per risparmiare memoria e velocizzare i calcoli. Essenziale per eseguire l'inferenza e congelare i parametri del modello.
3m 59s
6
Datasets e gestione dei dati
Impara a separare l'elaborazione dei dati dall'architettura del modello utilizzando la classe Dataset di PyTorch. Esploriamo il lazy loading e le strutture di dataset personalizzate.
3m 41s
7
DataLoaders e Batching
Sprigiona tutta la velocità del tuo hardware avvolgendo i Datasets nei DataLoaders. Impara a eseguire il batching, lo shuffling e il multiprocessing dei tuoi flussi di dati.
3m 54s
8
Trasformazioni dei dati
Scopri come pre-elaborare i dati grezzi al volo prima che raggiungano la tua Neural Network. Trattiamo i transforms di torchvision come ToTensor e le funzioni Lambda personalizzate.
3m 58s
9
Progettare reti con nn.Module
Esplora lo schema strutturale di ogni Neural Network in PyTorch. Impara a creare sottoclassi di nn.Module, definire i layer nell'inizializzazione e instradare i dati nel forward pass.
4m 07s
10
Layer Linear e attivazioni
Guarda all'interno della Neural Network. Analizziamo il modulo nn.Linear e spieghiamo perché le funzioni di attivazione non lineari come ReLU sono matematicamente essenziali.
4m 10s
11
Il contenitore nn.Sequential
Semplifica il tuo codice PyTorch usando il contenitore nn.Sequential. Impara a incastrare i layer in modo pulito e a ispezionare i parametri del tuo modello.
3m 38s
12
Comprendere le Loss Functions
Prima che un'AI possa imparare, deve misurare i propri errori. Ci immergiamo nelle Loss Functions di PyTorch, confrontando CrossEntropyLoss per la classificazione e MSELoss per la regressione.
3m 32s
13
Optimizers e Gradient Descent
Esplora come l'Optimizer aggiorna i pesi del modello per ridurre l'errore. Impara la fondamentale danza in tre passaggi di zero_grad(), backward() e step().
3m 36s
15
Validazione e inferenza
Valuta il tuo modello in modo oggettivo. Impara a passare la tua rete in modalità di valutazione, congelare i gradienti ed estrarre previsioni accurate su dati mai visti.
3m 52s
16
Salvare e caricare i modelli
Non perdere i progressi guadagnati a fatica! Discutiamo i modi più sicuri per serializzare i pesi del tuo modello usando state_dict e ricaricarli in modo sicuro.
3m 27s
17
Aumentare la velocità con torch.compile
Sblocca la funzionalità distintiva di PyTorch 2.0. Scopri come il decoratore torch.compile esegue la JIT-compilation del tuo codice Python in kernel ottimizzati per enormi incrementi di velocità.
3m 20s
18
Compilatori e Graph Breaks
Immergiti sotto il cofano del compilatore PyTorch. Esploriamo i graph breaks, il flusso di controllo dinamico e perché torch.compile ha successo dove i sistemi legacy hanno fallito.
4m 00s

Episodi

1

L'identità principale di PyTorch

3m 40s

Scopri lo scopo fondamentale di PyTorch e cosa lo distingue dalle tradizionali librerie matematiche. Questo episodio spiega il ruolo dei Tensors, di Autograd e dell'accelerazione GPU nel deep learning moderno.

Download
Ciao, sono Alex di DEV STORIES DOT EU. PyTorch Fundamentals, episodio 1 di 18. Scrivi un modello matematico complesso in Python, ma quando fai scale up, manda completamente in blocco il processore. Devi far girare quei calcoli su hardware parallelo e calcolare continuamente tutte le loro derivate, ma riscrivere tutto in un linguaggio di basso livello richiederebbe settimane. Questo problema viene risolto dall'identità principale di PyTorch. Quando guardi PyTorch per la prima volta, spesso ti sembra esattamente come NumPy. Crei array, moltiplichi matrici e manipoli numeri. Questa somiglianza visiva causa molta confusione all'inizio. Le persone danno per scontato che PyTorch sia solo un'altra libreria matematica standard. Non lo è. Mentre le librerie matematiche standard sono costruite per il calcolo numerico CPU-bound, PyTorch è progettato da zero per sfruttare l'hardware parallelo e costruire computational graph dinamici. Il mattoncino fondamentale di questo framework è il tensore. Un tensore è essenzialmente un array multidimensionale. Se hai una griglia di numeri che rappresenta un'immagine, un'onda sonora o un blocco di testo, la salvi in un tensore. La differenza fondamentale tra un array standard e un tensore PyTorch è dove quei dati possono vivere ed essere eseguiti. I tensori possono spostarsi senza problemi dalla memoria di sistema del tuo computer a una Graphics Processing Unit. Prendi un'enorme moltiplicazione di matrici. Hai due griglie che contengono milioni di numeri. Se chiedi a una CPU standard di moltiplicarle, elabora i calcoli in modo sequenziale o in batch molto piccoli. Il processo fa fatica e si blocca. Dato che i tensori sono progettati esplicitamente per l'accelerazione hardware, puoi inviare quegli stessi identici dati a una GPU. La GPU contiene migliaia di piccoli core progettati per eseguire operazioni matematiche in simultanea. Un'enorme computazione che richiede minuti su una CPU finisce all'istante su una GPU. PyTorch fa da ponte, traducendo il tuo codice Python standard in istruzioni per quell'hardware parallelo. Un hardware veloce è solo metà dei requisiti per il machine learning. Il training di una rete neurale richiede calcolo continuo. Devi sapere esattamente come modificare una singola variabile cambia il tuo output finale, il che significa calcolare costantemente i gradienti. Farlo a mano per un modello con miliardi di parametri è impossibile. Questo ci porta al secondo pilastro di PyTorch, che è Autograd. Autograd è un engine di differenziazione automatica. Quando esegui operazioni matematiche sui tensori, PyTorch non si limita a calcolare il numero finale. Costruisce silenziosamente una mappa in background. Registra ogni addizione, moltiplicazione e trasformazione dei dati in un computational graph dinamico. Quando arrivi alla fine del tuo calcolo, chiedi semplicemente al framework di calcolare i gradienti. PyTorch cammina all'indietro attraverso quel graph invisibile, applicando automaticamente la chain rule del calcolo differenziale. Ricevi le derivate esatte per ogni singolo parametro nel tuo modello senza scrivere tu stesso alcun codice di calcolo. Dato che questo graph viene costruito dinamicamente al volo, si adatta al tuo codice. Se un loop Python standard o un if-statement cambia il flusso dei tuoi dati, il graph si adatta immediatamente. La vera potenza di PyTorch non è solo che gira velocemente o fa calcoli. Ti dà la velocità di esecuzione di un supercomputer e il rigore matematico di un engine di calcolo automatizzato, il tutto completamente nascosto dietro a del Python leggibile e ordinario. Se vuoi aiutare a far continuare questi episodi, puoi cercare DevStoriesEU su Patreon e supportare lo show. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
2

Comprendere i Tensors di PyTorch

4m 08s

Immergiti nei Tensors, la struttura dati fondamentale di PyTorch. Scopri come collegano i dati grezzi alle Neural Networks e condividono la memoria in modo fluido con gli array Numpy.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 2 di 18. Ogni immagine, onda sonora e documento di testo che passi a una rete neurale alla fine si trasforma nella stessa identica struttura dati. Se è tutto solo una griglia di numeri, potresti chiederti perché abbiamo bisogno di un oggetto specializzato invece di affidarci ai normali array di programmazione. La risposta sta nel capire i tensor di PyTorch. Un tensor è una struttura dati specializzata che assomiglia e si comporta molto come un array o una matrice. In PyTorch, i tensor sono la valuta universale. Contengono i tuoi input grezzi, gli output generati dal tuo modello e i parametri interni della rete neurale stessa. Spesso si pensa che i tensor siano completamente identici agli array di NumPy. In effetti si somigliano, e condividono molti degli stessi comportamenti. La differenza fondamentale sta in quello che i tensor sbloccano. Mentre un array standard risiede nella memoria principale del tuo sistema e gira sul tuo processore centrale, un tensor è progettato per essere spostato facilmente su una graphics processing unit, o GPU, per una massiccia accelerazione hardware. I tensor contengono anche l'infrastruttura integrata necessaria per il gradient tracking, che permette alle reti neurali di imparare. Puoi inizializzare un tensor in diversi modi. La strada più diretta è passare i dati grezzi, come una normale list di Python di numeri, direttamente nel costruttore del tensor. Puoi anche creare un nuovo tensor basandoti su uno esistente. Quando lo fai, il nuovo tensor eredita automaticamente le proprietà dell'originale, il che significa che avrà le stesse dimensioni e lo stesso data type, a meno che tu non faccia un override esplicito. In alternativa, se ti serve solo un placeholder, puoi definire una shape, che è una semplice collezione di numeri che rappresenta le dimensioni che vuoi, e chiedere a PyTorch di generare un tensor riempito con numeri casuali, tutti uno o tutti zeri, basato su quella shape. Una volta che hai un tensor, controllerai spesso tre attributi principali. Il primo è la shape, che ti dice la dimensione esatta del tensor lungo ogni dimensione. Il secondo è il data type, che indica il tipo di numeri memorizzati al suo interno, come float a 32 bit o integer. Il terzo è l'attributo device. Questo ti dice dove risiede fisicamente il tensor in questo momento, che sia sulla CPU o su una GPU specifica. Devi tenerne traccia perché PyTorch richiede che i tensor si trovino sullo stesso device prima di poter interagire. I tensor e gli array standard spesso devono lavorare insieme, il che ci porta al bridge di NumPy. I tensor che risiedono sulla CPU possono effettivamente condividere la loro memoria sottostante con un array di NumPy. Mettiamo che tu carichi una fotografia ad alta risoluzione usando una libreria standard di image processing di Python. Quell'immagine viene caricata nella memoria del tuo sistema come un array standard di NumPy. Puoi passare quell'array a PyTorch usando una funzione dedicata che crea un tensor da NumPy. PyTorch non duplica i dati dei pixel sottostanti in un nuovo blocco di memoria. Semplicemente, crea un wrapper con la sua interfaccia tensor attorno all'indirizzo di memoria esistente. Cambiare un valore nel tensor cambia immediatamente il valore nell'array di NumPy, e viceversa. Questa conversione zero-copy fa risparmiare sia memoria che tempo di elaborazione. Quando hai finito di passare i dati attraverso il tuo modello e devi restituire i risultati a un tool di visualizzazione standard, chiami un singolo metodo sul tensor per esporlo di nuovo come array di NumPy, usando esattamente la stessa memoria condivisa. La vera potenza di un tensor non è solo memorizzare una griglia di numeri, ma portare con sé il contesto hardware specifico e la struttura di memoria necessari per far passare i dati grezzi attraverso una rete neurale senza intoppi. Grazie per aver ascoltato, happy coding a tutti!
3

Operazioni sui Tensors e memoria

3m 43s

Impara a manipolare i Tensors in modo efficiente. Questo episodio copre le operazioni aritmetiche, la concatenazione, i trasferimenti tra dispositivi e le implicazioni di memoria delle operazioni in-place.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 3 di 18. Un singolo underscore nel tuo codice può farti risparmiare gigabyte di memoria, ma potrebbe anche compromettere silenziosamente l'intera rete neurale. Sapere quando usare quell'underscore dipende dal capire le operazioni sui tensor e la memoria. Considera uno scenario pratico. Hai tre feature vector separati che rappresentano dati di testo, audio e immagini. Vuoi combinarli e moltiplicarli per una matrice di pesi. Di default, PyTorch crea i tensor sulla CPU. Ma per la matematica matriciale pesante, vuoi usare un acceleratore hardware. Puoi verificare se una GPU è disponibile usando i controlli integrati del framework. Se lo è, sposti i tuoi tensor chiamando il metodo to su di essi. Passi il nome del device di destinazione, come la string cuda, a questo metodo. PyTorch quindi copia il tensor dalla RAM di sistema alla memoria dedicata della tua scheda video. Con i tuoi tensor sull'hardware giusto, devi combinare i tre feature vector separati in uno solo. Lo fai usando la funzione concatenate, comunemente scritta come cat. Le passi una lista dei tuoi tensor e specifichi una dimensione. Se li combini lungo la dimensione delle colonne, i tuoi tre tensor stretti vengono uniti fianco a fianco per formare un unico tensor più largo. Ora hai un input unificato che risiede nella memoria della GPU. PyTorch gestisce oltre cento operazioni diverse, ma l'aritmetica è la base. Per elaborare il tuo feature vector combinato, devi moltiplicarlo per una matrice di pesi. Puoi usare il metodo matmul, o semplicemente usare il simbolo at come comoda abbreviazione. Questo esegue una vera moltiplicazione di matrici matematica, calcolando i dot product di righe e colonne, e restituisce un tensor completamente nuovo che contiene i risultati. A volte invece ti serve la matematica element-wise. Supponi di voler applicare una binary mask al tuo tensor, forzando certi valori a zero. Per questo, usi il metodo mul, o l'operatore standard asterisco. Questo non fa una moltiplicazione di matrici. Moltiplica semplicemente il primo elemento del tensor A per il primo elemento del tensor B, il secondo per il secondo, e così via. Ogni volta che esegui operazioni come la moltiplicazione di matrici o l'addizione element-wise, PyTorch alloca nuova memoria per il risultato. Quando operi su milioni di parametri, questo consuma rapidamente la tua memoria hardware disponibile. È qui che devi prestare attenzione. PyTorch fornisce operazioni in-place per gestire il memory overhead. Qualsiasi operazione che termina con un underscore opera in-place. Se usi il metodo standard add, ottieni un nuovo tensor. Se usi il metodo add con un underscore, PyTorch sovrascrive direttamente i valori all'interno del tensor esistente. Il memory footprint rimane esattamente lo stesso. Sebbene le operazioni in-place siano molto efficienti per la memoria, sono anche pericolose. Quando sovrascrivi un tensor, cancelli i suoi valori precedenti. Le reti neurali si basano su un record completo degli stati passati per calcolare le derivate durante la fase di apprendimento. Se sovrascrivi un tensor usando un'operazione in-place, distruggi la computation history di cui il sistema ha bisogno per aggiornare il modello. Riserva le operazioni in-place per la formattazione dei dati prima che entrino nel tuo modello, e attieniti alle operazioni standard durante il training per mantenere intatta la tua computation history. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
4

La magia di Autograd

3m 45s

Scopri il motore che rende possibile il deep learning in PyTorch. Impara come Autograd traccia dinamicamente le operazioni e calcola automaticamente derivate complesse.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 4 di 18. Addestrare una rete neurale significa calcolare la derivata dell'errore rispetto a milioni di parametri. Farlo a mano richiederebbe pagine di calcoli e continue riscritture ogni volta che cambi l'architettura del tuo modello. PyTorch risolve questo problema tracciando le tue operazioni matematiche in background, un concetto noto come la magia di Autograd. Autograd è il motore di differenziazione integrato di PyTorch. Calcola automaticamente i gradienti per qualsiasi grafo computazionale. Per capire come funziona, immagina una trasformazione lineare standard. Hai un tensore di input che contiene i tuoi dati, una matrice di weight e un vettore di bias. L'obiettivo è calcolare un output, confrontarlo con il valore target effettivo e calcolare l'errore, o loss. I tuoi dati di input sono fissi, quindi non hai bisogno delle loro derivate. Ma i tensori di weight e bias devono essere aggiornati in seguito, il che significa che hai assolutamente bisogno dei loro gradienti. Puoi segnalarlo a PyTorch impostando un flag chiamato requires grad su true quando crei quei tensori di parametri. Questo indica al motore di autograd di iniziare a monitorarli. Quando esegui delle operazioni su questi tensori monitorati, come moltiplicare l'input per i weight, aggiungere il bias e calcolare la loss finale, PyTorch fa due cose contemporaneamente. Calcola il risultato numerico effettivo e, allo stesso tempo, costruisce un Directed Acyclic Graph, o DAG. In questo grafo, i tuoi tensori di partenza sono le foglie, e le operazioni matematiche che hai applicato sono le radici. Ogni nuovo tensore creato da un'operazione ha un attributo che memorizza un riferimento alla funzione che lo ha creato. Questo indica ad autograd esattamente come calcolare la derivata per quello specifico step matematico. Questo grafo non è una struttura statica definita all'inizio del tuo script. PyTorch costruisce il DAG dinamicamente da zero durante ogni singola iterazione. Quando esegui un forward pass, viene costruito un grafo completamente nuovo al volo. Questa esecuzione dinamica significa che la tua rete può cambiare il suo comportamento a ogni step. Puoi usare il normale control flow di Python, come gli if o i cicli, e il motore traccerà in modo pulito il percorso che i dati hanno effettivamente fatto durante quella specifica run. Una volta che il tuo forward pass produce il tensore di loss finale, attivi il calcolo del gradiente chiamando il metodo backward su quella loss. Autograd attraversa immediatamente il grafo a ritroso. Usa la chain rule per calcolare le derivate della loss rispetto a ogni tensore che ha requires grad impostato su true. Poi prende questi valori calcolati e li salva nell'attributo grad dei tuoi tensori di weight e bias. I calcoli complessi vengono completamente astratti. Ci sono momenti in cui vuoi solo far passare i dati attraverso il modello senza calcolare i gradienti, ad esempio quando valuti un modello addestrato. Tracciare la history richiede memoria e calcoli extra. Puoi impedire ad autograd di costruire del tutto il grafo wrappando il tuo blocco di codice nel context manager torch no grad. Questo interrompe temporaneamente il tracking ed esegue i calcoli molto più velocemente. Il vero potere di autograd è che trasforma codice Python arbitrario in una struttura matematica completamente differenziabile, senza che tu debba mai scrivere a mano le formule delle derivate. Grazie per l'ascolto. Statemi bene, tutti.
5

Controllare il tracciamento dei gradienti

3m 59s

Scopri come disabilitare il tracciamento dei gradienti di PyTorch per risparmiare memoria e velocizzare i calcoli. Essenziale per eseguire l'inferenza e congelare i parametri del modello.

Download
Ciao, sono Alex di DEV STORIES DOT EU. PyTorch Fundamentals, episodio 5 di 18. Se esegui un modello appena addestrato su un grande batch di immagini di test senza modificare un'impostazione specifica, la tua applicazione finirà per bloccarsi a causa di un errore di out-of-memory. Anche se stai solo facendo prediction, il modello sta segretamente accumulando memoria per ricordare ogni operazione matematica che esegue fino al crash del sistema. Controllare il Gradient Tracking è il modo per evitare questo problema. Di default, i tensor di PyTorch sono progettati per imparare. Se un tensor ha il suo gradient requirement impostato su true, PyTorch traccia ogni operazione eseguita su di esso. Costruisce un computation graph in background, collegando input, pesi e output in modo da poter calcolare i gradienti in seguito durante la backpropagation. Questo tracking engine è fantastico per il training, ma richiede un sacco di overhead. Una volta completato il training, le tue priorità cambiano. Mettiamo che tu abbia appena finito di addestrare un image classifier e debba eseguire delle prediction su un milione di nuove immagini. Non hai più bisogno di aggiornare i pesi del modello. Ti serve solo il forward pass. Se lasci attivo il meccanismo di tracking, PyTorch crea un computation graph enorme e inutile per quel milione di immagini, bruciando la tua RAM e rallentando i tuoi cicli di calcolo. Per evitare questo, hai a disposizione due tool principali. Il primo è un context manager chiamato torch dot no grad. Lo usi per wrappare interi blocchi di codice. Quando inserisci il tuo forward pass all'interno di un blocco no grad, stai dicendo a PyTorch di spegnere temporaneamente il tracking engine. Qualsiasi operazione eseguita all'interno di quel blocco non verrà registrata. Anche se i tensor di input vengono normalmente tracciati, gli output creati all'interno del blocco avranno i loro gradient requirement impostati su false. Questo è il tuo tool per eseguire evaluation, testing o prediction in blocco. Disattiva il graph per tutto ciò che si trova all'interno del suo scope. Il secondo tool è il metodo detach. Mentre no grad gestisce blocchi di codice, detach gestisce singoli tensor. Chiamare detach su un tensor restituisce un nuovo tensor che condivide esattamente gli stessi dati sottostanti dell'originale, ma è completamente disconnesso dal computation graph. Non ha history. Spesso si fa confusione su quando usare l'uno o l'altro. Usa il context manager torch dot no grad quando vuoi silenziare il tracking per una sequenza di operazioni, come il passaggio dal training all'inference. Usa il metodo detach quando stai costruendo attivamente un computation graph durante il training, ma devi estrarre un tensor specifico da quel graph. Un use case comune per detach è quando devi passare un tensor a una diversa libreria Python, come NumPy, che non comprende i computation graph di PyTorch. Prima fai il detach del tensor, rimuovendo il bagaglio del tracking, e poi passi i numeri grezzi. Disabilitare il Gradient Tracking è anche una tecnica fondamentale per il freezing dei parametri. Se stai facendo il fine-tuning di un modello pre-trained enorme, probabilmente non vuoi fare il training di tutto quanto da zero. Puoi fare un loop sui layer di base del modello e impostare i loro gradient requirement a false. PyTorch smette completamente di fare il tracking su di loro. Durante il backward pass, quei layer frozen non calcoleranno i gradienti e non si aggiorneranno, risparmiando enormi quantità di memoria e velocizzando notevolmente il tuo processo di fine-tuning. Il Gradient Tracking è un macchinario industriale pesante progettato rigorosamente per il learning. Ogni volta che un tensor non ha bisogno di imparare, spegni il macchinario per recuperare la tua memoria e velocità. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a costruire!
6

Datasets e gestione dei dati

3m 41s

Impara a separare l'elaborazione dei dati dall'architettura del modello utilizzando la classe Dataset di PyTorch. Esploriamo il lazy loading e le strutture di dataset personalizzate.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 6 di 18. Il tuo modello è valido tanto quanto i dati che gli dai in pasto, ma cosa fai quando il tuo dataset pesa un terabyte e la tua macchina ha solo sedici gigabyte di RAM? La risposta sta nel modo in cui fai il fetch di quei dati. Questo episodio parla di Dataset e Data Handling. La logica di data processing può diventare incasinata molto in fretta. Se mischi il codice di lettura, decoding e formattazione dei file direttamente nel training loop del tuo modello, il tuo progetto diventa fragile e difficile da mantenere. PyTorch ti incoraggia a disaccoppiare queste logiche. Vuoi che la data preparation sia completamente separata dal tuo algoritmo di training. Per ottenere questo risultato, PyTorch fornisce una primitiva chiamata Dataset, che si trova nel modulo torch punto utils punto data. La classe Dataset fa da wrapper standardizzato per i tuoi dati grezzi. Per gestire i tuoi file specifici, crei una classe custom che eredita da questa primitiva. Quando costruisci un dataset custom, devi implementare tre metodi specifici. Questi sono init, len e getitem. Il metodo init gira esattamente una volta quando crei l'oggetto dataset. È qui che configuri le tue directory e i tuoi path. Un errore frequente che fanno i principianti è cercare di caricare tutti i dati veri e propri in memoria proprio qui. Non farlo. Se hai cinquantamila immagini ad alta risoluzione, leggerle tutte in memoria durante l'inizializzazione farà crashare immediatamente la tua macchina. Invece, usa init per caricare un indice leggero. Per esempio, potresti leggere un file CSV che contiene i nomi dei file delle immagini in una colonna e le loro corrispondenti label di testo in un'altra. Stai solo costruendo la mappa, non stai tenendo il territorio. Il prossimo è il metodo len. Questo restituisce semplicemente il numero totale di sample nel tuo dataset. Se il tuo file CSV ha cinquantamila righe, questo metodo restituisce il numero cinquantamila. Il sistema si affida a questo per conoscere i limiti assoluti dei tuoi dati disponibili, in modo da non richiedere un indice che non esiste. Il lavoro pesante avviene nel metodo getitem. Questa funzione è progettata per caricare e restituire un singolo sample a uno specifico indice richiesto. Quando il sistema ha bisogno del sample numero quarantadue, chiama getitem e gli passa quel numero. Il tuo codice cerca la riga quarantadue nel CSV che hai caricato prima. Legge la string del file path da quella riga. Poi, e solo allora, accede al disco, trova il file e decodifica i pixel veri e propri dell'immagine in memoria. Prende la label da quella stessa riga del CSV, e restituisce l'immagine e la label insieme come una tupla. Questa tecnica si chiama lazy loading. Consumi memoria solo per lo specifico pezzo di dato di cui hai bisogno, nell'esatto momento in cui sei pronto a processarlo. Isolando questa logica all'interno del metodo getitem, il tuo codice di training non ha mai bisogno di sapere se i dati provengono da un hard drive locale, da un network stream o da un database complesso. Richiede semplicemente un indice e riceve un output standardizzato. Separare il meccanismo di come viene fatto il fetch dei dati da come vengono consumati è il fondamento di un codice di machine learning scalabile. Se trovi utili questi episodi e vuoi supportare lo show, puoi cercare DevStoriesEU su Patreon. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
7

DataLoaders e Batching

3m 54s

Sprigiona tutta la velocità del tuo hardware avvolgendo i Datasets nei DataLoaders. Impara a eseguire il batching, lo shuffling e il multiprocessing dei tuoi flussi di dati.

Download
Ciao, sono Alex di DEV STORIES DOT EU. PyTorch Fundamentals, episodio 7 di 18. Le GPU sono incredibilmente veloci, ma resteranno completamente inattive se la tua CPU non riesce a fornirgli i dati abbastanza in fretta. Spesso i training loop hanno un bottleneck non sui calcoli matematici, ma sul caricamento del set di file successivo dal disco. La soluzione è separare il recupero dei dati dall'esecuzione del modello usando DataLoader e batching. È facile confondere i ruoli di un Dataset e di un DataLoader. Un Dataset ha esattamente un compito: recuperare un singolo item e la sua label. Non sa nulla del processo di training in generale. Il DataLoader è un wrapper attorno a quel Dataset. Agisce da manager, responsabile di organizzare questi singoli item in gruppi, randomizzare il loro ordine e usare processi multipli per caricarli in modo efficiente. Durante il training, i modelli raramente guardano un solo data point alla volta. Aggiornano i loro pesi interni in base a un gruppo di item valutati simultaneamente, noto come minibatch. Questo approccio rende il processo di training più stabile e sfrutta al massimo la potenza di calcolo parallelo dell'hardware. Per creare un minibatch manualmente, dovresti scrivere un loop per estrarre i singoli sample, impilarli in una struttura a tensor più grande e gestire gli edge case, come quando l'ultimo batch è più piccolo degli altri. Il DataLoader gestisce tutto questo automaticamente. Inizializzi un DataLoader passandogli il tuo oggetto Dataset e un parametro chiamato batch size. Se imposti il batch size a 64, il DataLoader estrarrà 64 item distinti dal Dataset, li consoliderà in un singolo tensor e te li servirà tutti in una volta. Nel tuo codice, il DataLoader si comporta come uno standard iterable di Python. Ci fai un loop. Ogni volta che il loop va avanti, il DataLoader fa lo yield del prossimo batch completo di dati e del corrispondente batch di label. Passi anche un parametro shuffle. Se una rete neurale elabora i dati di training sempre nell'esatta stessa sequenza, potrebbe memorizzare quella specifica sequenza invece di imparare le vere feature. Impostare shuffle a true dice al DataLoader di randomizzare l'ordine degli item del dataset all'inizio di ogni epoch. Una volta che è stato fatto lo yield di ogni batch e il dataset è esaurito, il loop finisce. La volta successiva che iteri sul DataLoader, genererà una sequenza randomizzata completamente nuova. Questa è la parte che conta. Il DataLoader accetta anche un parametro per il numero di processi worker. Quando usi più worker, il DataLoader avvia dei processi CPU in background per recuperare i dati. Immagina di dare in pasto quelle 64 immagini a una rete neurale. Mentre la tua GPU è occupata a calcolare i gradienti per il batch corrente, i worker CPU in background stanno simultaneamente leggendo, decodificando e impilando le successive 64 immagini. Nel momento in cui la GPU finisce il suo step matematico corrente, il batch di dati successivo sta già aspettando in memoria. La GPU non rimane mai a secco. Un training loop ad alte prestazioni isola la realtà lenta e imprevedibile delle operazioni su disco dalla realtà veloce e strutturata del model training. Il DataLoader fornisce questo isolamento, trasformando una collezione di file standalone in una pipeline continua e parallelizzata di minibatch. Questo è tutto per questo episodio. Grazie per aver ascoltato, e continua a sviluppare!
8

Trasformazioni dei dati

3m 58s

Scopri come pre-elaborare i dati grezzi al volo prima che raggiungano la tua Neural Network. Trattiamo i transforms di torchvision come ToTensor e le funzioni Lambda personalizzate.

Download
Ciao, sono Alex di DEV STORIES DOT EU. PyTorch Fundamentals, episodio 8 di 18. Le reti neurali calcolano solo numeri, ma i tuoi dati del mondo reale di solito sono una raccolta disordinata di file immagine raw e categorie di testo. Se scrivi dei loop manuali per convertire ogni singola immagine e label prima di iniziare il training, il tuo codice diventerà rapidamente un disastro fragile e illeggibile. Le Data Transformation sono il meccanismo che risolve questo problema, convertendo automaticamente i tuoi dati raw in un formato pronto per il modello esattamente quando serve. Raramente i dati arrivano già pronti per il machine learning. Devi manipolarli in uno specifico formato tensor prima di passarli alla tua rete. PyTorch gestisce questo aspetto in modo pulito applicando le transform al volo durante il processo di data loading. Quando inizializzi un dataset, soprattutto in librerie come torchvision, definisci queste modifiche usando due argomenti specifici. Usi l'argomento transform esclusivamente per le tue input feature, come le tue immagini raw. Usi l'argomento target transform esclusivamente per le tue label. È fondamentale mantenere separati questi due elementi, poiché operano in modo indipendente su due metà diverse dei tuoi dati. Diamo prima un'occhiata alle input feature. Supponiamo che tu abbia un dataset di immagini PIL raw. Una rete neurale non può leggere direttamente un oggetto immagine PIL. Per risolvere questo problema, passi una transform integrata di torchvision chiamata ToTensor nell'argomento transform. Quando il dataset carica un'immagine, ToTensor esegue automaticamente due passaggi. Primo, converte l'immagine PIL in un float tensor di PyTorch. Secondo, scala i valori di intensità dei pixel. I pixel delle immagini raw generalmente vanno da zero a duecentocinquantacinque. L'operazione ToTensor normalizza questi valori in un range floating-point compreso tra zero e uno. Il dataset applica questa operazione rigorosamente man mano che ogni immagine viene recuperata. Questo copre gli input, ma che dire degli output? Le label del tuo dataset potrebbero essere semplici interi che rappresentano diverse categorie. Ad esempio, il numero tre potrebbe indicare un cane. Ma per calcolare la loss durante il training, il tuo modello spesso richiede che quelle label siano vettori one-hot encoded, anziché singoli interi. Questo significa che hai bisogno di un array in cui tutti i valori sono zero, tranne l'indice che rappresenta la classe corretta, che è impostato a uno. Per gestire una logica custom come questa, PyTorch offre le Lambda transform. Una Lambda transform incapsula qualsiasi funzione definita dall'utente, in modo che possa essere applicata durante il data loading. Scrivi una breve funzione che prende la tua label intera come input. All'interno di quella funzione, crei un tensor di zeri che corrisponde al numero totale di categorie nel tuo dataset. Poi, usi un'operazione interna di PyTorch per fare lo scatter di un valore pari a uno nell'indice specifico che corrisponde alla tua label intera. Passi questa funzione custom in una Lambda transform, e poi la assegni all'argomento target transform del tuo dataset. Questo crea una pipeline altamente efficiente. Un worker thread preleva un singolo record raw dal tuo disco. L'immagine incontra l'argomento transform, passa attraverso ToTensor, ed emerge come un float tensor normalizzato. Contemporaneamente, la categoria intera incontra l'argomento target transform, esegue la tua funzione Lambda custom, e si trasforma in un vettore one-hot encoded. Entrambi i pezzi sono ora formattati matematicamente e passati direttamente al tuo modello. Il vero potere di questa architettura è la separation of concerns. Attaccando queste Data Transformation direttamente alla definizione del dataset, il tuo vero e proprio training loop rimane completamente cieco alla realtà disordinata dei tuoi file raw. Questo è tutto per questo episodio. Grazie per aver ascoltato, e continua a sviluppare!
9

Progettare reti con nn.Module

4m 07s

Esplora lo schema strutturale di ogni Neural Network in PyTorch. Impara a creare sottoclassi di nn.Module, definire i layer nell'inizializzazione e instradare i dati nel forward pass.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 9 di 18. Ogni rete neurale in PyTorch, da un semplice classificatore di immagini a un enorme language model, condivide esattamente lo stesso schema di base. Se non capisci come questo schema organizza dati e logica, finirai per scontrarti con il framework a ogni passo. Progettare le reti con nn.Module è il modo per padroneggiare questa struttura. nn.Module è la classe base per tutti i componenti delle reti neurali in PyTorch. Funge da contenitore universale. Quando costruisci un custom model, crei una classe che eredita da nn.Module. Questa ereditarietà dà automaticamente alla tua classe la capacità di tracciare i propri parametri, calcolare i gradienti e integrarsi fluidamente con il resto dell'ecosistema PyTorch. Permette anche architetture annidate. Puoi inserire moduli all'interno di altri moduli, creando un albero di layer che il modulo padre traccia e gestisce come un'unica unità. Immagina di impostare lo scheletro vuoto di un classificatore di immagini nuovo di zecca. Costruire questo scheletro richiede la definizione di due metodi specifici: il metodo initialize e il metodo forward. PyTorch impone una rigorosa separazione delle responsabilità tra queste due fasi. Il primo è il metodo initialize. Pensalo come il tuo inventario. Quando la classe viene istanziata, questo metodo viene eseguito esattamente una volta. Lo usi per dichiarare tutti i singoli layer e le operazioni matematiche di cui il tuo model avrà alla fine bisogno. Qui non stai elaborando nessun dato reale. Stai semplicemente prendendo dei componenti strutturali dallo scaffale, configurando le loro shape di input e output, e salvandoli come variabili interne alla tua classe. Poi c'è il metodo forward. Questa è la tua catena di montaggio attiva. Il metodo forward prende un tensor di input e stabilisce esattamente come viaggia attraverso l'inventario che hai appena dichiarato. Scrivi la sequenza di operazioni passo dopo passo. Prendi il tensor dell'immagine di input, lo passi a un'operazione di flattening, dai in pasto quel risultato a una serie di dense layer, e infine restituisci le prediction di output. Ogni custom model deve definire questo metodo forward per stabilire il data flow. Questo ci porta a una trappola comune. Poiché hai scritto esplicitamente la logica del data flow all'interno di un metodo chiamato forward, l'istinto naturale è quello di passare i tuoi dati chiamando model punto forward. Non farlo. Devi chiamare il model direttamente come se fosse una normale funzione, passando il tuo input direttamente all'oggetto model istanziato. Dietro le quinte, eseguire direttamente l'oggetto model attiva diversi hook critici in background di cui PyTorch ha bisogno per gestire lo stato della rete. Chiamare direttamente il metodo forward bypassa questi hook e causerà comportamenti imprevisti durante il tuo training loop. Una volta definita la tua classe e creato un oggetto model, hai una rete funzionante. Tuttavia, di default, PyTorch crea questo oggetto e tutti i suoi pesi interni nella memoria della CPU del tuo sistema. Per fare training a velocità realistiche, devi inviare questa architettura a un acceleratore. Puoi farlo verificando se è disponibile una GPU CUDA o un chip specializzato come l'MPS di Apple, e assegnando quel target hardware a una variabile device. Poi, chiami il metodo to sul tuo model, passando quella variabile device. Questo singolo comando sposta immediatamente tutti i parametri inizializzati del model fuori dalla memoria standard e nella memoria ad alta velocità del tuo acceleratore hardware. Il tratto distintivo di nn.Module è il modo in cui impone un confine architetturale netto tra i componenti statici che il tuo model possiede in memoria, e il percorso dinamico che i tuoi dati seguono per essere elaborati. Grazie per l'ascolto. Statemi bene, tutti.
10

Layer Linear e attivazioni

4m 10s

Guarda all'interno della Neural Network. Analizziamo il modulo nn.Linear e spieghiamo perché le funzioni di attivazione non lineari come ReLU sono matematicamente essenziali.

Download
Ciao, sono Alex di DEV STORIES DOT EU. PyTorch Fundamentals, episodio 10 di 18. Se impili cento layer di una rete neurale senza uno specifico trucco matematico, l'intera rete collassa matematicamente in una singola linea retta. La colpevole è l'algebra lineare, e la soluzione richiede di capire i Linear Layer e le Activation. Una rete neurale è fondamentalmente una sequenza di operazioni matematiche sui tensor. L'operazione più comune è il Linear Layer, definito in PyTorch come nn.Linear. Questo modulo applica una trasformazione affine ai dati in input. Contiene due tensor interni che impara nel tempo: i pesi e i bias. Quando i dati ci passano attraverso, il layer moltiplica l'input per la matrice dei pesi e aggiunge il bias. Prendi un'immagine standard in scala di grigi di 28 per 28 pixel. Prima che un Linear Layer possa elaborarla, devi fare il flatten della griglia bidimensionale in un array unidimensionale di 784 numeri. Passi quell'array di 784 valori a un layer nn.Linear configurato per avere in output 512 feature. Dietro le quinte, PyTorch crea una matrice di pesi che mappa i 784 input a 512 output. Moltiplica i valori dei tuoi pixel per questi pesi, li somma, aggiunge un termine di bias per shiftare il risultato, e restituisce in output 512 nuovi numeri. Durante il training, PyTorch aggiorna continuamente questi pesi e bias. Formano la vera e propria memoria del tuo modello. Potresti pensare che una deep neural network sia solo una lunga sequenza di questi Linear Layer impilati uno dopo l'altro. Questa è la parte che conta. Se metti in chain più operazioni nn.Linear insieme senza niente in mezzo, la matematica si semplifica. La matrice A moltiplicata per la matrice B è semplicemente un'altra matrice, C. Impilare dieci Linear Layer ha esattamente la stessa capacità matematica di calcolare un singolo Linear Layer. La tua deep network si riduce a un'equazione piatta e lineare, completamente incapace di imparare pattern complessi del mondo reale. Per fermare questo collasso matematico, introduci una non-linearità subito dopo il Linear Layer. Queste si chiamano activation function. L'activation più usata in PyTorch è nn.ReLU, che sta per Rectified Linear Unit. Dopo che il Linear Layer ha calcolato i suoi 512 output, passi quel tensor direttamente in una funzione ReLU. La logica della ReLU è brutalmente semplice. Guarda ogni numero nel tensor. Se un numero è minore di zero, la ReLU lo cambia esattamente in zero. Se un numero è zero o positivo, la ReLU lo lascia completamente intatto. Quella singola piega sullo zero distrugge la linearità. Impedisce al Linear Layer successivo di fondersi matematicamente con quello precedente. Forzando i valori negativi a zero, la ReLU crea anche rappresentazioni sparse. Questo significa che solo uno specifico sottoinsieme di neuroni si attiva per un dato input, rendendo la rete altamente efficiente. Il flusso dei dati è coerente. La tua immagine, dopo il flatten, entra nel Linear Layer, viene trasformata dai pesi e dai bias, e poi arriva all'activation ReLU dove gli output negativi vengono eliminati. A questo punto, puoi passare tranquillamente questo tensor attivato in un secondo Linear Layer per estrarre pattern più profondi e astratti. Un Linear Layer determina quanta importanza matematica dare a ciascun input, ma l'activation function dà alla rete la vera geometria necessaria per imparare le forme imprevedibili dei dati reali. Grazie per aver passato qualche minuto con me. Alla prossima, stammi bene.
11

Il contenitore nn.Sequential

3m 38s

Semplifica il tuo codice PyTorch usando il contenitore nn.Sequential. Impara a incastrare i layer in modo pulito e a ispezionare i parametri del tuo modello.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 11 di 18. Scrivere metodi forward custom per ogni rete neurale diventa rapidamente noioso quando devi semplicemente impilare layer standard. Non devi sempre fare il routing manuale dei dati da una funzione all'altra. A volte ti basta incastrare i layer tra loro come mattoncini LEGO. Ed è esattamente quello che fa il container nn.Sequential. Il container nn.Sequential è una pipeline ordinata di moduli di rete neurale. Quando passi dei dati a questo container, i dati fluiscono attraverso i moduli interni nell'esatta sequenza in cui sono stati aggiunti. Pensa di dover assemblare un Multilayer Perceptron standard a tre layer. Normalmente, definiresti i tuoi layer Linear e le funzioni di attivazione in un metodo di inizializzazione, per poi scrivere un metodo forward custom. In quel metodo forward, prenderesti esplicitamente l'input, lo passeresti al layer uno, lo incapsuleresti in un'attivazione ReLU, passeresti il risultato al layer due, applicheresti un'altra ReLU e lo daresti in pasto al layer finale. Con Sequential, bypassi completamente il metodo forward. Istanzi il container e gli passi i tuoi moduli direttamente come argomenti. Fornisci un modulo Linear, seguito da un modulo ReLU, un secondo modulo Linear, un'altra ReLU e un modulo Linear finale. PyTorch gestisce automaticamente il routing dei dati. L'output del primo modulo diventa all'istante l'input del secondo, procedendo automaticamente lungo la chain. Questo container è molto efficiente, ma ha un limite invalicabile. Serve strettamente per un flusso di dati lineare e diretto. Non può gestire architetture complesse che richiedono branching, input multipli o skip connection. Se stai costruendo qualcosa come una Residual Network, in cui i dati bypassano certi layer e vengono sommati di nuovo più avanti, Sequential non funzionerà. Per qualsiasi topologia non lineare, devi comunque scrivere un modulo custom con un metodo forward dedicato. Una volta concatenati i tuoi layer, spesso hai bisogno di ispezionare ciò che hai appena costruito. Ogni layer nel tuo container Sequential è una sottoclasse di nn.Module, il che significa che PyTorch registra e traccia automaticamente tutto lo state sottostante. Per visualizzare questo state, usi il metodo named_parameters. Chiamare named_parameters sul tuo modello ti fornisce un iteratore su tutti i pesi e i bias al suo interno. Ogni elemento che restituisce è una semplice coppia: il nome del parametro e il tensor del parametro stesso. Dato che hai usato un container Sequential senza nominare esplicitamente i tuoi layer, PyTorch genera dei nomi numerici basati sul loro indice. Vedrai nomi come zero dot weight per i pesi del primo layer Linear, o zero dot bias per i suoi termini di bias. Il tensor associato contiene i valori numerici effettivi, la shape della matrice e indica se richiede il calcolo del gradiente. Iterare su named_parameters è il modo standard per verificare la tua architettura. Puoi stampare rapidamente la size di ogni matrice dei pesi per confermare che le tue dimensioni di input e output siano perfettamente allineate lungo l'intera chain, prima ancora di iniziare a far passare dati reali nel sistema. La vera potenza del container Sequential, combinata con il tracking dei parametri, è che PyTorch si fa carico di tutto il lavoro per la gestione dello state e il routing dei dati, lasciandoti concentrare interamente sulla shape della tua rete. Per questo episodio è tutto. Alla prossima!
12

Comprendere le Loss Functions

3m 32s

Prima che un'AI possa imparare, deve misurare i propri errori. Ci immergiamo nelle Loss Functions di PyTorch, confrontando CrossEntropyLoss per la classificazione e MSELoss per la regressione.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 12 di 18. Per insegnare a una rete neurale a dare la risposta giusta, devi prima misurare rigorosamente quanto si sbaglia. Se non riesci a quantificare l'errore, il tuo modello non può imparare. Questo ci porta a capire le Loss Function. Quando una rete non addestrata elabora dei dati, il suo output è essenzialmente un'ipotesi. Una loss function valuta questa ipotesi. Misura il grado di differenza tra il risultato prodotto dal modello e la verità assoluta del valore target. L'output di una loss function è sempre un singolo numero scalare. L'intero processo di training esiste per spingere quel singolo numero il più vicino possibile allo zero. Dato che diversi task di machine learning hanno definizioni diverse di errore, PyTorch fornisce diverse loss function. Se stai costruendo un modello di regressione per prevedere un valore continuo, come la temperatura di domani, misuri la distanza tra la tua ipotesi e la temperatura reale. Per fare questo, usi il Mean Square Error, che in PyTorch si chiama nn.MSELoss. Ma la classificazione è diversa. Supponi di avere un modello che categorizza immagini di abbigliamento in dieci categorie di moda. Il modello guarda l'immagine di un cappotto e restituisce dieci punteggi grezzi, uno per ogni possibile categoria. Questi punteggi grezzi, non normalizzati, si chiamano logit. La risposta vera è solo un singolo numero intero, che rappresenta la classe corretta. Non puoi semplicemente sottrarre un indice di classe da un punteggio grezzo. Hai invece bisogno di una funzione che penalizzi il modello se assegna punteggi bassi alla classe corretta e punteggi alti alle classi sbagliate. Per la classificazione, lo strumento standard è nn.CrossEntropyLoss. Inizializzi la tua loss function, le passi i dieci logit grezzi del tuo modello insieme alla label intera corretta, e lei ti restituisce la tua penalità scalare. Questa è la parte che conta. C'è una trappola enorme qui per gli sviluppatori. In molti libri di machine learning, una rete di classificazione termina con un layer softmax. Softmax forza i logit grezzi in una distribuzione di probabilità ordinata in cui tutti i punteggi sommati danno esattamente uno. Per questo motivo, spesso gli sviluppatori aggiungono manualmente un'operazione softmax proprio alla fine del loro modello PyTorch. Se stai usando nn.CrossEntropyLoss, farlo è un errore. In PyTorch, nn.CrossEntropyLoss applica automaticamente una funzione LogSoftmax internamente prima di calcolare la negative log likelihood. È progettata per accettare direttamente i logit grezzi, non normalizzati. Se il tuo modello restituisce probabilità perché hai già applicato softmax, passarle a nn.CrossEntropyLoss significa che stai applicando la matematica due volte. Questo comprime i tuoi gradienti, rallenta drasticamente il training e rovina la capacità del tuo modello di imparare in modo efficace. La regola da ricordare è che la tua rete neurale dovrebbe semplicemente restituire in output numeri grezzi. Mantieni gli output del tuo modello grezzi, passali direttamente a nn.CrossEntropyLoss e lascia che PyTorch faccia il lavoro pesante di trasformare quei logit in una penalità significativa. Grazie per l'ascolto, buon coding a tutti!
13

Optimizers e Gradient Descent

3m 36s

Esplora come l'Optimizer aggiorna i pesi del modello per ridurre l'errore. Impara la fondamentale danza in tre passaggi di zero_grad(), backward() e step().

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 13 di 18. Il bug più comune nel training con PyTorch non è avere dati pessimi o un'architettura sbagliata. È dimenticarsi di pulire i vecchi calcoli matematici, facendo perdere il controllo alla tua rete. Oggi parliamo di Optimizer e Gradient Descent, che gestiscono esattamente come il tuo modello impara dai propri errori. Il tuo modello fa una prediction, e tu calcoli la loss per vedere quanto si è sbagliato. Ora devi regolare i weights interni della rete neurale per rendere la prediction successiva leggermente più accurata. Questo processo di aggiustamento dei parametri per minimizzare la loss si chiama optimization. L'optimizer è l'algoritmo specifico che governa come cambiano quei weights. Per configurare un optimizer, devi passargli due cose. Primo, gli passi un iterable che contiene i parametri del modello che vuoi fargli regolare. Secondo, gli fornisci un learning rate. Il learning rate è un hyperparameter fondamentale che controlla l'entità delle modifiche applicate ai weights. Se il learning rate è troppo piccolo, l'optimizer fa passi microscopici, rendendo il training dolorosamente lento. Se il learning rate è troppo grande, l'optimizer supera i valori ottimali, portando a un comportamento folle e imprevedibile. Un algoritmo standard per questo task è lo Stochastic Gradient Descent, o SGD. Valuta la pendenza della tua loss function e fa un passo nella direzione opposta per scendere verso l'errore più basso possibile. Una volta inizializzato il tuo optimizer SGD con i tuoi parametri e il learning rate, gli update effettivi avvengono in una rigorosa sequenza di tre step. Lo step uno è fare tabula rasa. Chiami il comando zero grad sull'optimizer. È qui che si annida quel bug comune. PyTorch accumula i gradienti di default. Quando calcola nuovi gradienti, non sovrascrive quelli vecchi; aggiunge semplicemente i nuovi numeri ai totali esistenti. Se salti questo step di zero grad, i calcoli del tuo batch attuale vengono corrotti dai numeri residui del batch precedente. Azzera sempre i gradienti prima di fare qualsiasi altra cosa. Lo step due è calcolare i nuovi gradienti. Prendi il valore della loss calcolata e chiami il comando backward su di esso. Questo innesca la backpropagation. PyTorch viaggia all'indietro attraverso l'architettura della tua rete. Calcola la derivata della loss rispetto a ogni singolo parametro. In sostanza, capisce esattamente quanto ogni singolo weight ha contribuito all'errore complessivo. Questi gradienti calcolati vengono salvati direttamente dentro gli oggetti parametro. Lo step tre è applicare la correzione. Chiami il comando step sull'optimizer. L'optimizer guarda i gradienti salvati in ciascun parametro durante il backward pass. Moltiplica quei gradienti per il learning rate per capire la dimensione esatta dell'aggiustamento, e poi aggiorna i weights effettivi in memoria. Questo ciclo si ripete per ogni batch. Azzera i gradienti, calcola la backward loss, chiama step sull'optimizer. Il dettaglio critico da ricordare è che l'optimizer aggiorna solo i parametri che gli sono stati passati esplicitamente durante il setup. Se devi fare il freeze di un layer nella tua rete, escludi semplicemente i suoi parametri quando inizializzi l'optimizer, e quei weights rimarranno permanentemente fissi. A proposito, se vuoi supportare lo show, puoi cercare DevStoriesEU su Patreon. Grazie per l'ascolto. Statemi bene, tutti.
15

Validazione e inferenza

3m 52s

Valuta il tuo modello in modo oggettivo. Impara a passare la tua rete in modalità di valutazione, congelare i gradienti ed estrarre previsioni accurate su dati mai visti.

Download
Ciao, sono Alex di DEV STORIES DOT EU. PyTorch Fundamentals, episodio 15 di 18. Il tuo model potrebbe funzionare perfettamente sui suoi training data, ma l'unica vera prova per un'AI è come gestisce l'ignoto. Se ti affidi esclusivamente al feedback che riceve l'optimizer, potresti semplicemente costruire un memory bank molto costoso. Per verificare se il tuo model generalizza effettivamente al mondo reale, hai bisogno di validation e inference. Durante la fase di training, osservi la training loss. Quel numero esiste per guidare l'optimizer interno. Forza il model ad aggiustare i suoi weights finché l'errore matematico non si riduce. Ma una training loss bassa non significa che tu abbia un buon model. Significa semplicemente che il model è molto bravo a rispondere alle domande che ha già visto. La validation accuracy è la metrica completamente separata che dice agli umani se il model è in grado di fare predictions corrette su dati completamente nuovi. Per ottenere questa metrica, devi eseguire un validation loop su un test dataset dedicato. Prima di dare in pasto un singolo test data alla network, devi cambiare lo stato del model. Lo fai chiamando il metodo eval sul tuo oggetto model. Chiamare eval fa passare la network in evaluation mode. Certi layer interni si comportano in modo diverso durante il training rispetto a quando sono in inference. Chiamare eval li forza a bloccare il loro comportamento, in modo che le tue predictions rimangano coerenti. Se salti questo passaggio, i tuoi test results saranno fondamentalmente inaffidabili. Questo copre il model state. Successivamente, devi controllare l'engine stesso disattivando il gradient tracking. Lo fai racchiudendo il tuo validation code all'interno di un context manager no grad. Durante il training, PyTorch costruisce costantemente un computational graph in memoria, salvando la history di ogni operazione in modo da poter calcolare i gradients in seguito. In un validation loop, hai completamente finito con il training. Non vuoi aggiornare i weights. Il blocco no grad dice a PyTorch di smettere di tracciare la history. Questa è la parte che conta. Disabilitare il tracking impedisce aggiornamenti accidentali al tuo model, ma libera anche un'enorme quantità di memoria e velocizza drasticamente la computation. All'interno di quel blocco no grad, la logica vera e propria è semplice. Iteri sul tuo test dataset in batch. Per ogni batch, passi gli input data attraverso il model. Il model calcola il forward pass e restituisce le sue raw predictions. Se stai facendo classification, il model non restituisce una text label pulita. Invece, restituisce una lista di numerical scores per ogni singola categoria che conosce. Per scoprire quale categoria ha effettivamente scelto il model, hai bisogno della funzione argmax. Argmax esamina la lista dei raw scores e trova il numero più alto. Quindi restituisce la index position di quello score più alto. Quell'index è la tua class prediction scelta. Una volta che hai le model predictions, le confronti direttamente con le true labels fornite dal test dataset. Conti esattamente quante predictions corrispondono alle true labels. Tieni un running total di questi match corretti attraverso tutti i batch. Quando il loop finisce, dividi il numero totale di correct predictions per il numero totale di elementi nel test dataset. Il risultato è la tua percentuale di accuracy finale. Il training loop forza il tuo model ad adattarsi ai dati storici, ma i rigidi vincoli del validation loop dimostrano se quel model è effettivamente utile. Grazie per l'ascolto. Statemi bene, tutti.
16

Salvare e caricare i modelli

3m 27s

Non perdere i progressi guadagnati a fatica! Discutiamo i modi più sicuri per serializzare i pesi del tuo modello usando state_dict e ricaricarli in modo sicuro.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 16 di 18. Addestrare un classificatore di immagini per cinquanta epoche può richiedere settimane e bruciare migliaia di dollari in compute. Eppure, nel momento in cui il tuo script Python termina l'esecuzione, tutti quei pattern faticosamente acquisiti scompaiono completamente dalla memoria. Per proteggere questo investimento, hai bisogno di un modo per salvare i tuoi progressi su disco. Questo è esattamente ciò che il salvataggio e il caricamento dei model risolvono. All'interno di ogni model PyTorch c'è un dizionario interno chiamato state dict. Questo dizionario mappa ogni layer della tua rete ai suoi corrispondenti tensori dei parametri. Contiene i weights e i bias effettivi che il tuo model ha imparato durante il training. La struttura del model è solo codice, ma lo state dict è l'intelligenza. Per salvare il tuo model, estrai questo dizionario e lo scrivi su un file. Lo fai usando una funzione chiamata torch punto save. Le passi due cose. Primo, lo state dict del tuo model. Secondo, il file path dove vuoi salvarlo, che tradizionalmente usa un'estensione punto pth. In una sola riga di codice, la tua sessione di training di cinquanta epoche viene salvata in modo sicuro sul tuo hard drive come un singolo file contenente nient'altro che dati raw dei tensori. Potresti vedere online degli esempi che saltano completamente lo state dict e passano direttamente l'oggetto model a torch punto save. Non farlo. Salvare l'intero model si basa pesantemente sulla serializzazione pickle di Python. Questo lega il file salvato all'esatta struttura delle directory e alle definizioni delle classi presenti al momento della creazione del file. Se in seguito fai refactoring del tuo codice o sposti un file, il model fallirà il load. Attenersi allo state dict è molto più sicuro e significativamente più robusto, perché stai salvando solo i dati, non il codice. Quando è il momento di fare il deploy del tuo classificatore per l'inferenza in produzione, devi invertire il processo. Dato che hai salvato solo i weights, PyTorch ha bisogno di sapere com'è fatta la struttura della rete. Inizi istanziando una versione completamente vuota della classe del tuo model. Questo ti dà la shell architetturale. Successivamente, chiami torch punto load e gli passi il tuo file path per rileggere il dizionario in memoria. Quando chiami torch punto load, c'è una best practice moderna e cruciale che devi seguire. Passa sempre l'argomento weights only impostato su true. I file pickle di Python possono contenere codice eseguibile arbitrario. Se scarichi un model pre-addestrato da internet e fai il load alla cieca, potrebbe eseguire script malevoli sulla tua macchina. Impostare weights only su true restringe il loader a deserializzare solo i tensori standard di PyTorch, mantenendo il tuo sistema al sicuro. Infine, con il tuo model vuoto pronto e il tuo dizionario sicuro caricato, chiami load state dict sul model e gli passi il dizionario. PyTorch mappa i weights caricati ai layer corrispondenti nella shell vuota. Il tuo model ora è completamente ripristinato e pronto per fare prediction. Non affidare mai il tuo investimento di training a un fragile oggetto serializzato; separa sempre l'architettura nel tuo codice dai parametri appresi sul tuo disco. Grazie per averci fatto compagnia. Spero tu abbia imparato qualcosa di nuovo.
17

Aumentare la velocità con torch.compile

3m 20s

Sblocca la funzionalità distintiva di PyTorch 2.0. Scopri come il decoratore torch.compile esegue la JIT-compilation del tuo codice Python in kernel ottimizzati per enormi incrementi di velocità.

Download
Ciao, sono Alex di DEV STORIES DOT EU. PyTorch Fundamentals, episodio 17 di 18. Passi settimane a perfezionare l'architettura di un modello per ottenere un aumento di velocità del cinque percento. Ma spesso, il vero bottleneck non è la tua matematica. È Python stesso, e i continui e inefficienti trasferimenti di dati tra la tua memoria e la GPU. Risolvere questo problema non richiede di riscrivere la tua codebase. Oggi vediamo come mettere il turbo alla velocità con torch dot compile. Introdotta in PyTorch 2.0, questa feature sposta il tuo codice dall'esecuzione standard a un workflow altamente ottimizzato. È comune pensare che velocizzare PyTorch per la produzione significhi scrivere kernel C plus plus custom o cambiare radicalmente la tua architettura. Non è così. Non cambi nulla all'interno del tuo modello. Fai semplicemente il wrap del tuo modello esistente in una singola function call. Per capire perché questo crea un enorme salto di performance, devi guardare a come gira normalmente PyTorch. Il PyTorch standard opera in eager mode. Esegue esattamente quello che gli dici di fare, esattamente quando lo chiedi, un'operazione alla volta. Se il tuo codice dice alla GPU di sommare due tensori, moltiplicare il risultato per un altro tensore e applicare una activation function, l'eager mode li tratta come tre eventi isolati. Per ogni step, la GPU legge i dati dalla sua main memory, fa i calcoli e scrive indietro il risultato intermedio. La memory bandwidth della GPU è limitata. Questo continuo trasferimento di dati richiede molto più tempo dei calcoli effettivi. Quando passi il tuo modello attraverso la funzione compile, PyTorch cambia tattica. Usa un tool interno chiamato TorchDynamo per catturare le tue operazioni in un computation graph prima di eseguirle. Guardando la sequenza più ampia, trova le inefficienze. Poi, usa un backend compiler per generare una nuova versione delle tue operazioni, pesantemente ottimizzata. La tecnica principale che usa è la kernel fusion. Invece di leggere e scrivere in memoria tre volte separate, il codice compilato unisce questi step. La GPU legge i dati una volta sola, li tiene nei suoi registri interni più veloci, esegue l'addizione, la moltiplicazione e l'activation di fila, e poi scrive il risultato finale una volta sola. L'overhead di Python scompare, e il memory bottleneck viene bypassato. L'implementazione è semplice. Istanzi il tuo modello come al solito. Poi, chiami torch dot compile, gli passi il tuo modello, e assegni l'output a una nuova variabile. Fai passare i tuoi dati attraverso questa versione compilata. Se TorchDynamo incontra un costrutto Python oscuro che non può ottimizzare in modo sicuro, non rompe il tuo programma. Semplicemente lascia quella piccola sezione in standard eager mode e compila il resto. Quando fai il benchmark di tutto questo, fai attenzione alla prima run. Il primo pass richiede molto più tempo perché la compilazione vera e propria avviene esattamente quando arrivano i primi dati. Ma al secondo pass, l'inference time crolla drasticamente. Non devi più scegliere tra la flessibilità dell'eager mode durante lo sviluppo e la velocità pura di un compiled backend in produzione. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
18

Compilatori e Graph Breaks

4m 00s

Immergiti sotto il cofano del compilatore PyTorch. Esploriamo i graph breaks, il flusso di controllo dinamico e perché torch.compile ha successo dove i sistemi legacy hanno fallito.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Fondamenti di PyTorch, episodio 18 di 18. I vecchi compilatori AI richiedevano execution path perfettamente prevedibili, fallendo clamorosamente se includevi costrutti Python complessi e dinamici nel tuo modello. Modificavi una singola istruzione condizionale e l'intero processo di compilazione andava in crash. PyTorch due punto zero gestisce esattamente lo stesso codice arbitrario senza fare una piega. Il motore alla base di questa flessibilità si basa su come il compilatore gestisce i graph break. Se hai lavorato con il vecchio tool di compilazione, TorchScript, sai che richiedeva strutture di codice rigide. TorchScript si basava su uno static typing rigoroso e su un'esecuzione prevedibile. Se il tuo modello presentava un control flow altamente dinamico, si basava su dizionari Python standard o richiamava librerie esterne non tensoriali, TorchScript spesso lo rifiutava. Gli ingegneri dovevano spesso riscrivere parti significative dell'architettura del loro modello solo per accontentare il compilatore. PyTorch due punto zero affronta la cosa in modo completamente diverso. Invece di richiedere codice statico in anticipo, il compilatore nativo analizza dinamicamente la tua esecuzione Python. Cattura tutte le operazioni matematiche che può ottimizzare in modo sicuro e le impacchetta in un computational graph altamente efficiente. Inevitabilmente, il compilatore incontrerà del codice che non riesce a mappare facilmente in una struttura a grafo ottimizzata. Quando incontra questa logica imprevedibile, innesca un graph break. Un graph break non è un errore, e non è un crash. È semplicemente un meccanismo di fallback. Significa che il compilatore restituisce elegantemente il controllo alla eager execution standard di PyTorch per quello specifico segmento di codice. Immagina una funzione in cui esegui una serie di pesanti moltiplicazioni di matrici, seguite da un if-statement in Python che controlla il valore medio di un tensore per decidere l'operazione successiva. Quella condizione è data-dependent. L'execution path è completamente sconosciuto fino al momento esatto in cui i valori del tensore vengono calcolati a runtime. Quando fai il trace di questa funzione con il compilatore, lui analizza il flusso. Prende le moltiplicazioni di matrici che avvengono prima della condizione e le compila in un sub-graph veloce e ottimizzato. Poi, incontra l'insidioso if-statement. Dato che non può prevederne il risultato, crea un graph break. Il compilatore lascia che il Python standard esegua la condizione in eager mode. Una volta valutata la condizione e scelto il percorso, il compilatore riprende il controllo, prendendo le operazioni rimanenti e compilandole in un secondo sub-graph ottimizzato. Il sistema segmenta automaticamente il tuo codice. Ottieni isole compilate di calcoli veloci separate da ponti Python standard. Il tuo modello continua a girare senza interruzioni. Questa è la parte che conta. Probabilmente vedrai nei log delle performance dei graph break nella tua architettura. Anche se in genere vuoi minimizzarli per spremere la massima velocità di esecuzione, esistono puramente come rete di sicurezza. Garantiscono che il tuo codice produca sempre il risultato matematico corretto, anche se non è possibile fondere ogni singola riga in un unico kernel. Il principale cambio di design nella moderna compilazione di PyTorch è dare priorità a un'esecuzione fluida rispetto all'ottimizzazione totale, assicurando che il motore si adatti alla tua logica arbitraria, anziché costringere la tua logica ad adattarsi al motore. Questo conclude la nostra serie su PyTorch. Ti incoraggio vivamente a esplorare la documentazione ufficiale, a provare questi tool di compilazione hands-on e a visitare DEV STORIES DOT EU per suggerire argomenti per le prossime serie. Vorrei prendermi un momento per ringraziarti per l'ascolto: ci aiuta tantissimo. Alla prossima!