In questo articolo spiegheremo cos'è un Modello di intelligenza artificiale, utilizzando un linguaggio semplice e…

Esempio pratico di un semplice Modello AI
In questo articolo spiegheremo come creare un semplice Modello AI con PyTorch, un framework open-source per il machine learning e il deep learning, rilasciato da Facebook AI Research nel 2017 e ampiamente utilizzato per costruire, addestrare e testare modelli di intelligenza artificiale, in particolare reti neurali.
Pre requisiti
Se il vostro interesse è solo quello di comprendere il funzionamento di un semplice Modello AI, allora l’unico prerequisito per la comprensione di questo articolo è la conoscenza di base di Python e dei Modelli AI, se non l’avete, potete leggere l’articolo Cos’è un Modello di intelligenza artificiale?.
Se invece volete creare voi stessi il Modello AI spiegato in queto articolo ed eseguire il codice di esempio mostrato, è necessario avere installato Python e PyTorch, se non si hanno seguite queste istruzioni:
- Installa Python
Scarica Python dal sito ufficiale: https://www.python.org/downloads/
Leggi la guida ufficiale per l’installazione: https://docs.python.org/3/using/index.html - Installa pip
Solitamente è incluso con Python, verifica digitando: pip –version nel terminale
Leggi la documentazione sull’installazione: https://pip.pypa.io/en/stable/installation/ - Installa PyTorch
Puoi installarlo con il comando standard: pip install torch torchvision torchaudio
Oppure usa il configuratore ufficiale (per CPU o GPU): https://pytorch.org/get-started/locally/ - Installa un Editor di codice
Se vuoi modificare il codice, hai bisogno anche di un editor, come ad esempio Visual Studio Code e la sua estensione Python per VS Code.
Esempio pratico di un semplice Modello AI
Lo scopo del nostro Modello di esempio sarà quello di prendere in input un numero (da 0 a 1) e predire il doppio di quel numero.
L’articolo che spiega l’esempio si struttura in 5 passi:
- Definizione del modello
- Addestramento del modello
- Salvataggio del modello su file
- Caricamento del modello
- Uso del modello per fare previsioni
Iniziamo dal primo punto.
1. Definizione del modello
In questa prima parte costruiremo una “scatola di apprendimento”: una struttura iniziale vuota, con una formula lineare che imparerà a funzionare bene man mano che gli diamo esempi.
# Importa il framework PyTorch e il modulo per le reti neurali. import torch import torch.nn as nn # Definizione di un modello molto semplice: y = wx + b class SimpleModel(nn.Module): def __init__(self): super().__init__() # Costruisce un layer lineare # che prende un numero e restituisce un numero self.linear = nn.Linear(1, 1) # 1 input, 1 output def forward(self, x): return self.linear(x)
Spiegazione non tecnica:
Prima di tutto cerchiamo di comprendere, con parole semplici e non tecniche, cosa vogliamo fare con questo codice e perché.
Immagina di volere insegnare ad una persona a “prendere un numero e restituire il suo doppio”, ma invece di dirglielo direttamente, vogliamo che lo scopra da solo facendo prove e errori, osservando esempi (input e output). La persona in questo caso è il nostro modello di intelligenza artificiale e per farlo funzionare, dobbiamo prima dargli una “struttura mentale”, una formula molto semplice del tipo:
Output = (Numero in ingresso × Peso) + Bias
In termini di codice è quello che abbiamo fatto in questa riga:
self.linear = nn.Linear(1, 1)
Qui stiamo infatti dicendo: “costruisci una scatola che prende 1 numero in ingresso e restituisce 1 numero in uscita, applicando una formula matematica con 2 valori che il Modello deve imparare: il peso e il bias.”.
Nel contesto dell’IA, peso e bias sono parametri appresi dal modello. Il peso determina quanto un input influisce sull’output, mentre il bias permette al modello di adattarsi meglio spostando il risultato. Insieme, definiscono la funzione che trasforma l’input in output. Vengono ottimizzati durante l’addestramento per ridurre l’errore tra previsioni e valori reali.
Questa riga crea esattamente quella formula (y = x ⋅ w + b ), ma incapsulata in un oggetto già pronto di PyTorch: nn.Linear, e crea un peso (w) e un bias (b) che inizialmente saranno casuali. Inizialmente infatti il modello non sa nulla, poi gli daremo degli esempi e lui imparerà da solo qual è il peso e qual è il bias giusto per far funzionare la formula. In questo modo, potrà imitare il comportamento corretto (es. raddoppiare un numero).
Quindi, riassumendo:
- dobbiamo costruire una scatola che prende un numero e produce un altro numero (in questo caso il suo doppio);
- all’inizio la scatola non sa cosa fare: è vuota, ma ha spazio per imparare due cose: peso e bias;
- con gli esempi che gli daremo in seguito (es. 1→2, 0.5→1), il modello aggiusterà da solo il suo comportamento;
- questa “scatola” la costruiamo in Python creando un modello, cioè una classe con una formula dentro.
Spiegazione tecnica
Vediamo adesso gli stessi concetti ma espressi in termini più tecnici.
In questa prima parte di codice viene creato un layer lineare, che applica cioè una trasformazione lineare di questo tipo:
y = x ⋅ w + b
dove:
- x è l’input (in questo caso un singolo numero);
- w è un peso (appreso durante l’addestramento);
- b è il bias (anch’esso appreso durante l’addestramento);
- y è l’output (anch’esso un singolo numero).
La formula “y = x ⋅ w + b” non viene intuita o inventata dal modello ma è imposta dal programmatore, nel momento in cui utilizza nn.Linear nel codice. Per maggiori informazioni sulla formula per la trasformazione lineare si rimanda alla documentazione ufficiale della classe Linear.
Quello che il modello impara da sé, durante l’addestramento, è quale valore usare per il peso (weight) e quale valore usare per il bias. Questi due valori, detti parametri, all’inizio sono casuali, ma poi vengono corretti passo dopo passo durante l’addestramento, in modo da far avvicinare l’output al valore desiderato (2x in questo caso), mentre il modello guarda degli esempi dove viene mostrato il numero in input e il numero di output atteso.
Immagina che il modello sia una persona che ha ricevuto questa formula:
output = input × ?? + ??
Il suo compito non è quindi inventare la formula, ma scoprire quali numeri (peso e bias) mettere al posto dei ?? per farla funzionare bene.
La classe SimpleModel, all’interno della quale troviamo la nostra riga di codice, serve a impacchettare tutto insieme in un oggetto che possiamo addestrare, salvare e usare per fare previsioni.
2. Addestramento del modello
Questa è la parte in cui il modello impara davvero guardando esempi e correggendo se stesso.
# Dati di esempio: dato x, y dovrà essere uguale a 2x x_train = torch.tensor([[0.0], [0.5], [1.0]]) y_train = torch.tensor([[0.0], [1.0], [2.0]]) model = SimpleModel() loss_fn = nn.MSELoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.1) # Addestramento per 1000 iterazioni for epoch in range(1000): y_pred = model(x_train) loss = loss_fn(y_pred, y_train) optimizer.zero_grad() loss.backward() optimizer.step()
In questa parte di codice:
x_train = torch.tensor([[1.0], [2.0], [3.0], [4.0]]) y_train = torch.tensor([[2.0], [4.0], [6.0], [8.0]])
Stiamo dicendo:
- quando l’input è 1.0, l’output corretto è 2.0;
- quando l’input è 2.0, l’output corretto è 4.0;
- quando l’input è 3.0, l’output corretto è 6.0;
- e così via…
Il modello inizia con dei valori casuali (per peso e bias) e cerca, passo dopo passo, di capire come modificarli per dare risposte più corrette.
model = SimpleModel()
La riga sopra crea un’istanza del modello (la scatola con la formula y = x*w + b).
criterion = nn.MSELoss()
MSELoss() è la funzione di perdita (loss function). Nel nostro caso usiamo Mean Squared Error (MSE), che misura quanto sono distanti le previsioni dal risultato corretto. Se il modello dice “3.5” ma il valore giusto è “4”, l’errore è (3.5 – 4)² = 0.25
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
L’ottimizzatore è lo strumento che aggiusta i pesi e bias per ridurre l’errore.
- SGD significa Stochastic Gradient Descent, ed è un metodo classico di ottimizzazione;
- lr=0.01 significa learning rate, ovvero quanto “velocemente” il modello cambia i suoi parametri.
L’addestramento vero e proprio (ciclo di epoche) è questo:
for epoch in range(1000): y_pred = model(x_train) # 1. Fai una previsione loss = criterion(y_pred, y_train) # 2. Calcola l'errore optimizer.zero_grad() # 3. Azzeri i gradienti loss.backward() # 4. Calcoli i nuovi gradienti optimizer.step() # 5. Aggiorni i pesi e bias
Spiegazione passo per passo:
previsione: il modello applica la formula attuale (output = input * weight + bias) agli input di addestramento:
y_pred = model(x_train) # 1. Fai una previsione
errore (loss): confronta l’output del modello con i risultati reali; più l’errore è alto, peggio sta andando il modello:
loss = criterion(y_pred, y_train) # 2. Calcola l'errore
azzeramento gradienti: necessario per non sommare i gradienti tra una iterazione e l’altra:
optimizer.zero_grad() # 3. Azzeri i gradienti
backpropagation (loss.backward()): calcola come devono cambiare peso e bias per migliorare:
loss.backward() # 4. Calcoli i nuovi gradienti
ottimizzazione (optimizer.step()): applica le modifiche ai valori interni del modello:
optimizer.step() # 5. Aggiorni i pesi e bias
Dopo 1000 giri (epoche), il modello avrà imparato:
- che il miglior peso da usare è circa 2;
- che il bias ideale è circa 0.
Quindi ora è capace di applicare la regola del raddoppio, pur senza che nessuno gliel’abbia detta esplicitamente.
3. Salvataggio del modello su file
Una volta che il modello ha imparato (cioè è stato addestrato), vogliamo salvare ciò che ha imparato su disco, per poterlo riutilizzare in futuro senza doverlo addestrare di nuovo; per farlo utilizziamo questo codice:
# Salva pesi e bias nel file esempio_modello.pth torch.save(model.state_dict(), "esempio_modello.pth")
Cosa viene salvato?
Nel file “esempio_modello.pth” vengono salvati i pesi (weight) e il bias che il modello ha imparato durante l’addestramento. Questi valori sono contenuti nei parametri del modello. Il file conterrà quindi il dizionario Python serializzato con i pesi della rete in formato binario.
La riga che segue serve per ottenere ottenere un dizionario Python con tutti i valori appresi dal modello (pesi e bias):
model.state_dict()
il file verrà salvato, nella stessa cartella in cui si trova lo script Python che esegue il codice, con questa istruzione:
torch.save(...)
Ricorda: dentro il file non c’è il codice del Modello, ma solo i numeri che ha imparato (pesi e bias).
In questo modo riusciamo a:
- salvare modelli addestrati da usare più avanti;
- evitare di doverli riaddestrare ogni volta;
- condividerli con altri (che possono caricarli nel loro codice).
4. Caricamento del modello
Adesso vogliamo ripristinare il modello che abbiamo salvato in precedenza nel file “esempio_modello.pth”, per poterlo usare subito, senza doverlo addestrare di nuovo.
# Crea una nuova istanza e carica i pesi loaded_model = SimpleModel() loaded_model.load_state_dict(torch.load("esempio_modello.pth")) loaded_model.eval() # modalità di inferenza
La prima riga ricrea la struttura del modello:
model = SimpleModel()
poi legge il dizionario dei pesi dal file “esempio_modello.pth” e li carica nel modello:
model.load_state_dict(torch.load("esempio_modello.pth"))
La riga che segue imposta il modello in modalità valutazione (evaluation mode). La modalità valutazione (eval()) disattiva comportamenti speciali usati solo durante l’addestramento, come Dropout o BatchNorm, rendendo le previsioni più stabili e accurate:
model.eval()
5. Uso del modello per fare previsioni
Dopo aver creato, addestrato e caricato il modello, ora vogliamo usarlo, dandogli un nuovo input per vedere qual è l’output previsto.
x_test = torch.tensor([[0.75]]) with torch.no_grad(): y_test = loaded_model(x_test) print(y_test.item()) # dovrebbe essere circa 1.5
Con la prima riga creiamo un nuovo input (il numero 0.75) da dare poi al nostro modello.
Si tratta di un tensore 2D, cioè una matrice 1×1 con una riga e una colonna che contiene solo il numero 0.75. Anche se abbiamo un solo numero, il modello si aspetta sempre i dati come se arrivassero in gruppo, come una lista di numeri.
In questo caso, stiamo chiedendo al modello: “Secondo te, se l’input è 0.75, qual è l’output?”
x_test = torch.tensor([[0.75]])
La riga successiva disattiva il tracciamento dei gradienti, che serve solo durante l’addestramento. I gradienti sono dei numeri che misurano quanto aumenta o diminuisce l’errore se cambiamo leggermente i parametri. Indicano quanto cambiare peso e bias per migliorare le previsioni. Servono solo durante l’addestramento, non quando si fanno previsioni.
È utile disattivare il tracciamento dei gradienti per risparmiare memoria e calcoli, e evitare modifiche accidentali ai parametri del modello. Quando fai previsioni (inferenza), è buona pratica usare sempre torch.no_grad().
with torch.no_grad():
Adesso usiamo il modello addestrato (loaded_model) per fare la previsione:
y_test = loaded_model(x_test)
Per calcolare il risultato a partire dal nostro input, il modello applica i pesi e bias che ha imparato durante il nostro addestramento. Se ha imparato correttamente la funzione output = input × 2, allora il risultato sarà: 0.75 × 2 = 1.5
print(y_test.item())
Come detto prima, y_test è un tensore PyTorch contenente un solo valore, mentre .item() estrae quel numero e lo converte in un valore Python standard (float), che può essere stampato facilmente.
Il risultato stampato sarà qualcosa tipo 1.4899, che è molto vicino a 1.5, a causa di leggere approssimazioni numeriche.
Riassumendo, con queste istruzioni, abbiamo:
- creato un input numerico (x_test);
- usato il modello addestrato per fare una previsione;
- disattivato il calcolo dei gradienti per efficienza e sicurezza;
- stampato il risultato come numero reale.
Vedere il contenuto del file
Abbiamo detto che il file esempio_modello.pht è un file binario che contiene i parametri appresi (pesi e bias) durante l’addestramento. È possibile vedere il suo contenuto interno in formato leggibile con questo codice:
state_dict = torch.load() print(state_dict)
La riga seguente carica i pesi salvati nel nostro file:
state_dict = torch.load("esempio_modello.pth")
E poi li stampa in un formato leggibile:
print(state_dict)
L’Output (semplificato) dovrebbe essere simile al seguente:
OrderedDict([ ('linear.weight', tensor([[2.0000]])), ('linear.bias', tensor([8.3447e-07])) ])
Questo significa che il modello ha imparato che:
- peso (w) ≈ 2.0
- bias (b) ≈ 0,00000083447
- quindi: y = 2x + 8.3447e-07
Se volete creare il vostro Modello basato su questo semplice esempio, create un file Python (e.g. “semplice_modelloAI.py”), copiate al suo interno il codice formattato degli esempi (quello a colori), salvate il file ed eseguitelo con Python.
Se il numero che gli fornite in input è 0.75 (come nell’esempio) dovreste ricevere due righe di output simili a queste:
1.4999996423721313 OrderedDict({'linear.weight': tensor([[2.0000]]), 'linear.bias': tensor([8.3447e-07])})
La prima riga contiene l’output calcolato dal Modello (il doppio del numero in input 0.75) e la seconda riga vi mostra il contenuto del file del Modello.