Modelli di AI più efficienti
L’efficienza dei modelli di intelligenza artificiale (AI) è un aspetto cruciale per l’applicazione pratica e la scalabilità delle tecnologie AI. Diversi metodi e tecniche possono essere impiegati per migliorare l’efficienza dei modelli AI, ognuno con i propri vantaggi e sfide. Esaminiamo ciascuno di questi punti in dettaglio.
La compressione dei modelli di AI si riferisce a tecniche utilizzate per ridurre la dimensione dei modelli senza comprometterne significativamente le prestazioni. Questo processo può comportare la rimozione di ridondanze nei parametri del modello, rendendo il modello più leggero e più veloce da eseguire.
Ecco alcune tecniche usate per rendere più efficienti i modelli
Pruning
Il pruning consiste nel rimuovere i pesi meno significativi di una rete neurale. Questo processo riduce il numero di connessioni nel modello, mantenendo solo quelle più rilevanti.
Ricerca dell’Architettura Neurale (Neural Architecture Search, NAS)
La NAS è un processo automatizzato per progettare architetture di rete neurale ottimali. Utilizza algoritmi di ricerca per esplorare diverse combinazioni di architetture e trovare quella che offre le migliori prestazioni date le risorse computazionali disponibili.
Addestramento Distribuito
L’addestramento distribuito suddivide il processo di addestramento di un modello AI su più unità computazionali (CPU, GPU, TPU) per accelerare il processo.
Parallelismo dei Dati
Divide i dati di addestramento in batch e li distribuisce tra più processori per l’elaborazione parallela.
Parallelismo dei Modelli
Suddivide un modello AI in componenti più piccoli che possono essere eseguiti in parallelo su diversi processori.
Fine-tuning sul Dispositivo (On-Device Fine-tuning)
Il fine-tuning sul dispositivo si riferisce all’ottimizzazione di un modello AI direttamente sul dispositivo finale (es. smartphone, edge devices) utilizzando un sottoinsieme di dati locali.
Esaminiamo nel dettaglio i primi due: Pruning e Neural Architecture Search, NAS
Pruning
Il pruning è un processo iterativo che richiede attento monitoraggio per assicurarsi che le prestazioni del modello non siano compromesse in modo significativo.
L’obiettivo è trovare un equilibrio tra riduzione dei pesi e il mantenimento dell’accuratezza.

Ogni tecnica di pruning ha i suoi vantaggi e sfide, quindi è importante scegliere quella più adatta alle esigenze specifiche del modello e dell’applicazione.
Individuare i pesi più significativi
Per isolare i pesi più significativi e rimuovere quelli meno significativi (pruning) in una rete neurale, si possono seguire diversi approcci. Di seguito viene descritto un processo generale, insieme ad alcune delle tecniche comuni utilizzate:
Si addestra il modello neurale sulla tua base di dati fino a ottenere prestazioni soddisfacenti.
Si calcola l’importanza di ogni peso nella rete. Ci sono diversi modi per misurare questa importanza, come la magnitudine del peso o l’impatto sull’errore di addestramento.
Si rimuovono i pesi meno significativi in base alla valutazione. Questo può essere fatto impostando questi pesi a zero o rimuovendoli completamente.
Si riaddestra il modello dopo il pruning per recuperare eventuali perdite di precisione dovute alla rimozione dei pesi.
Si ripetono i passaggi di valutazione, pruning e riaddestramento fino a raggiungere un buon compromesso tra la dimensione del modello e le sue prestazioni.


Un modo per stabilire l’importanza dei pesi é semplicemente quello di considerare il loro valore numerico assoluto |w|. Più è grande e maggiore sarà l’impatto che avrà sulle previsioni della rete neurale.
Viene calcolato il valore assoluto di ogni peso nella matrice dei pesi. Il risultato è una nuova matrice della stessa dimensione contenente i valori assoluti dei pesi originali.
Si confronta ogni valore assoluto con la soglia di pruning. Il risultato è una matrice booleana della stessa dimensione, dove ogni elemento è TRUE se il valore assoluto del peso corrispondente è maggiore della soglia e FALSE altrimenti.
Si moltiplica elemento per elemento i pesi originali per la matrice booleana ottenuta. Questo ha l’effetto di mantenere i pesi con valore assoluto maggiore della soglia (poiché True è equivalente a 1) e impostare a zero i pesi con valore assoluto inferiore alla soglia (poiché False è equivalente a 0).
Esempio


Pruning basato su gradienti
I gradienti dei pesi indicano come la funzione di perdita cambierebbe rispetto ai cambiamenti nei pesi. Un piccolo gradiente suggerisce che modifiche a quel peso avranno un effetto minore sull’errore complessivo, indicando che il peso potrebbe essere meno importante.
Mentre il valore assoluto dei pesi è statico (cioè, non cambia a meno che il modello non venga addestrato ulteriormente), i gradienti sono dinamici e dipendono dalla fase attuale dell’addestramento. Questo può fornire una visione più aggiornata dell’importanza dei pesi.
Procedimento
Si calcolano i gradienti dei pesi rispetto alla funzione di perdita.
Si rimuovono i pesi con gradienti piccoli.
Si riaddestra il modello.
Pruning strutturato
Con questa tecnica si rimuovono intere unità strutturali (come canali in una convoluzione, neuroni in un layer) invece di singoli pesi. Questo può portare a modelli più efficienti dal punto di vista computazionale.
Procedimento
Si valuta l’importanza di interi neuroni o filtri.
Si rimuovono i neuroni o filtri meno significativi.
Si riaddestra il modello.
Esempio di Implementazione in Python con TensorFlow/Keras
import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.optimizers import Adam
# Carica il dataset MNIST
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Definisci un modello semplice
model = Sequential([
Flatten(input_shape=(28, 28)),
Dense(128, activation='relu'),
Dense(10, activation='softmax')
])
# Compila e addestra il modello
model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))
# Funzione di pruning basata sulla magnitudine
def prune_weights(model, pruning_threshold=0.01):
for layer in model.layers:
if isinstance(layer, Dense):
weights, biases = layer.get_weights()
new_weights = weights * (abs(weights) > pruning_threshold)
layer.set_weights([new_weights, biases])
return model
# Applica il pruning al modello
pruned_model = prune_weights(model, pruning_threshold=0.01)
# Riaddestra il modello pruned
pruned_model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
pruned_model.fit(x_train, y_train, epochs=3, validation_data=(x_test, y_test))
# Valuta il modello pruned
pruned_model.evaluate(x_test, y_test)
Note sul codice
Tensorflow è la libreria principale per costruire e addestrare modelli di deep learning.
Mnist dal modulo tensorflow.keras.datasets è il dataset di immagini di cifre scritte a mano.
Sequential dal modulo tensorflow.keras.models è il modello sequenziale per impilare i layer in sequenza.
Dense e Flatten dal modulo tensorflow.keras.layers sono i layer densi (fully connected) e layer per appiattire i dati.
Adam dal modulo tensorflow.keras.optimizers è l’ottimizzatore Adam.
Le righe che creano l’architettura del modello sono le seguenti:
model = Sequential([
Flatten(input_shape=(28, 28)),
Dense(128, activation='relu'),
Dense(10, activation='softmax')
])
Sequential crea un modello sequenziale.
Flatten(input_shape=(28, 28)) appiattisce l’input 28×28 in un vettore di 784 elementi.
Dense(128, activation=’relu’) definisce il layer denso con 128 neuroni e la funzione di attivazione ReLU.
Dense(10, activation=’softmax’) defnisce il layer denso con 10 neuroni (uno per ogni cifra) e la funzione di attivazione softmax per la classificazione.
La compilazione e l’addestramento del modello sono realizzati da queste istruzioni:
model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))
model.compile configura il modello per l’addestramento.
optimizer=Adam() è l’ottimizzatore Adam.
loss=’sparse_categorical_crossentropy’ imposta la funzione di perdita “sparse categorical crossentropy”.
metrics=[‘accuracy’] misura la precisione durante l’addestramento e la valutazione.
model.fit è l’istruzione che addestra il modello.
x_train, y_train sono i dati di addestramento.
epochs=5 definisce il numero di epoche (iterazioni sull’intero dataset).
validation_data=(x_test, y_test) sono i dati di validazione per valutare il modello ad ogni epoca.
Questa è la funzione di pruning basata sull’ampiezza:
def prune_weights(model, pruning_threshold=0.01):
for layer in model.layers:
if isinstance(layer, Dense):
weights, biases = layer.get_weights()
new_weights = weights * (abs(weights) > pruning_threshold)
layer.set_weights([new_weights, biases])
return model
for layer in model.layers itera attraverso i layer del modello.
if isinstance(layer, Dense) serve per controllare se il layer è di tipo Dense.
weights, biases = layer.get_weights() ottiene i pesi e i bias del layer.
new_weights = weights * (abs(weights) > pruning_threshold) imposta a zero i pesi con ampiezza inferiore alla soglia pruning_threshold.
layer.set_weights([new_weights, biases]) aggiorna i pesi del layer con quelli pruned.
return model restituisce il modello con i pesi pruned.
pruned_model = prune_weights(model, pruning_threshold=0.01) applica la funzione di pruning al modello originale con una soglia di 0.01.
Queste istruzioni si occupano del riaddestramento del modello pruned:
pruned_model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
pruned_model.fit(x_train, y_train, epochs=3, validation_data=(x_test, y_test))
pruned_model.compile ricompila il modello pruned con gli stessi parametri del modello originale.
pruned_model.fit riaddestra il modello pruned sui dati di addestramento per 3 epoche, utilizzando i dati di validazione.
Infine pruned_model.evaluate(x_test, y_test) valuta le prestazioni del modello pruned sui dati di test, restituendo la perdita e la precisione.
Nel codice fornito, l’ampiezza dei pesi viene calcolata all’interno della funzione prune_weights. L’ampiezze di un peso è calcolata utilizzando il valore assoluto del peso stesso. Questo viene fatto nella linea:
new_weights = weights * (abs(weights) > pruning_threshold)
Ecco come funziona questa linea in dettaglio:
abs(weights) calcola il valore assoluto di ogni peso nella matrice dei pesi weights. Il risultato è una nuova matrice della stessa dimensione contenente i valori assoluti dei pesi originali.
abs(weights) > pruning_threshold confronta ogni valore assoluto con la soglia di pruning pruning_threshold. Il risultato è una matrice booleana della stessa dimensione, dove ogni elemento è True se il valore assoluto del peso corrispondente è maggiore della soglia e False altrimenti.
weights * (abs(weights) > pruning_threshold) moltiplica elemento per elemento i pesi originali per la matrice booleana ottenuta. Questo ha l’effetto di mantenere i pesi con valore assoluto maggiore della soglia (poiché True è equivalente a 1) e impostare a zero i pesi con valore assoluto inferiore alla soglia (poiché False è equivalente a 0).
La scelta tra l’uso dell’ampiezza dei pesi e del gradiente dei pesi per determinare la loro importanza dipende dall’approccio e dall’obiettivo specifico del pruning. Entrambi i metodi hanno il loro fondamento teorico e pratico, e possono essere utilizzati in diverse situazioni.
Gradiente dei Pesi
Per implementare il pruning basato sui gradienti, si deve tenere traccia dei gradienti dei pesi durante l’addestramento. Ecco un esempio di come potrebbe essere implementato in TensorFlow/Keras:
import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.optimizers import Adam
# Carica il dataset MNIST
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
# Definisci un modello semplice
model = Sequential([
Flatten(input_shape=(28, 28)),
Dense(128, activation='relu'),
Dense(10, activation='softmax')
])
# Compila e addestra il modello
model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test))
# Funzione di pruning basata sui gradienti
def prune_weights_by_gradient(model, pruning_threshold=0.001):
# Calcola i gradienti dei pesi rispetto alla perdita
with tf.GradientTape() as tape:
logits = model(x_train[:100]) # usa un sottoinsieme dei dati per calcolare i gradienti
loss_value = tf.keras.losses.sparse_categorical_crossentropy(y_train[:100], logits)
grads = tape.gradient(loss_value, model.trainable_weights)
# Itera attraverso i layer e i loro gradienti
for layer, grad in zip(model.layers, grads):
if isinstance(layer, Dense):
weights, biases = layer.get_weights()
# Calcola la nuova matrice dei pesi mantenendo solo i pesi con gradienti superiori alla soglia
new_weights = weights * (abs(grad) > pruning_threshold)
layer.set_weights([new_weights, biases])
return model
# Applica il pruning al modello
pruned_model = prune_weights_by_gradient(model, pruning_threshold=0.001)
# Riaddestra il modello pruned
pruned_model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
pruned_model.fit(x_train, y_train, epochs=3, validation_data=(x_test, y_test))
# Valuta il modello pruned
pruned_model.evaluate(x_test, y_test)
Ricerca dell’Architettura Neurale (Neural Architecture Search, NAS)
La Neural Architecture Search (NAS) è un campo emergente nell’apprendimento automatico che si concentra sull’automatizzazione del processo di progettazione delle architetture delle reti neurali. Il processo di NAS cerca di trovare le configurazioni ottimali delle reti neurali, spesso superando le architetture progettate manualmente, riducendo allo stesso tempo la necessità di esperienza e conoscenza approfondita da parte dei progettisti.
Il processo di NAS può essere suddiviso in tre componenti principali:
Spazio di Ricerca (Search Space)
Definisce l’insieme di tutte le possibili architetture che l’algoritmo di ricerca può esplorare.
Può includere diverse configurazioni di layer (convoluzionali, ricorrenti, fully connected), funzioni di attivazione, connessioni tra layer, ecc.
Algoritmo di Ricerca (Search Algorithm)
Gli algoritmi di ricerca comuni includono la ricerca casuale, l’ottimizzazione bayesiana, gli algoritmi genetici, le reti neurali a rinforzo, e la ricerca a gradiente.
Strategia di Valutazione (Evaluation Strategy)
Tipicamente implica l’addestramento delle architetture candidate e la valutazione delle loro prestazioni su un set di validazione. A causa dell’alto costo computazionale, si possono utilizzare tecniche di stima surrogata o apprendimento di meta-modelli per predire le prestazioni senza un addestramento completo.
Algoritmi di NAS
Ricerca Casuale (Random Search)
Seleziona casualmente le architetture dallo spazio di ricerca.
Semplice ma spesso inefficiente perché non sfrutta informazioni sulle architetture valutate.
Ottimizzazione Bayesiana (Bayesian Optimization)
Modella una funzione di utilità che predice le prestazioni delle architetture.
Utilizza l’ottimizzazione bayesiana per scegliere le architetture più promettenti da valutare.
Algoritmi Genetici (Genetic Algorithms)
Utilizza meccanismi ispirati alla selezione naturale, come mutazione, crossover e selezione.
Evoluzione delle architetture attraverso generazioni successive. Utilizza un agente che apprende a progettare architetture tramite un processo di prova e errore. L’agente riceve ricompense basate sulle prestazioni delle architetture generate.
Ricerca a Gradiente (Gradient-Based Search)
Utilizza gradienti per ottimizzare le architetture.
Approccio innovativo che applica tecniche di backpropagation agli iperparametri architetturali.
Esempio di Implementazione con NAS
Un esempio concreto di NAS è l’uso di AutoML frameworks come AutoKeras, che automatizzano il processo di NAS. Ecco un esempio di utilizzo di AutoKeras:
import tensorflow as tf
from tensorflow.keras.datasets import mnist
import autokeras as ak
# Carica il dataset MNIST
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.astype('float32') / 255.0
x_test = x_test.astype('float32') / 255.0
# Definisci un modello di AutoKeras per la classificazione di immagini
clf = ak.ImageClassifier(
overwrite=True,
max_trials=10 # Numero massimo di architetture da provare
)
# Addestra il modello
clf.fit(x_train, y_train, epochs=5)
# Valuta il modello
accuracy = clf.evaluate(x_test, y_test)
print(f'Accuracy: {accuracy}')
Autokeras
AutoKeras è una libreria open source di AutoML (Machine Learning automatico) sviluppata per facilitare l’implementazione e l’uso di reti neurali complesse senza la necessità di una profonda conoscenza dei dettagli tecnici del deep learning. È costruita sopra Keras e TensorFlow, offrendo un’interfaccia user-friendly per la creazione automatica e l’ottimizzazione di modelli di machine learning.
AutoKeras è in grado di eseguire una ricerca automatica dell’architettura (Neural Architecture Search, NAS) per trovare la miglior struttura della rete neurale per un determinato problema. Questo significa che il sistema può sperimentare con diverse combinazioni di strati, funzioni di attivazione e altri iperparametri per ottimizzare le prestazioni del modello.
La libreria automatizza anche il preprocessamento dei dati, gestendo operazioni come la normalizzazione, la gestione dei valori mancanti e la codifica delle etichette. AutoKeras supporta una vasta gamma di tipi di dati, inclusi immagini, testo, e dati tabulari. Ciò permette di applicare tecniche di deep learning a diversi domini con facilità.
Gli utenti forniscono i dati di input, che possono essere immagini, testo o dati tabulari. AutoKeras gestisce il preprocessamento necessario per rendere i dati pronti per l’addestramento.
L’utente definisce il problema da risolvere, che può essere una classificazione, una regressione o un problema di clustering. AutoKeras seleziona automaticamente l’approccio di machine learning più appropriato. AutoKeras esegue una ricerca automatica per trovare la miglior architettura del modello. Utilizza tecniche di ottimizzazione per addestrare il modello e migliorare le sue prestazioni iterativamente.
Dopo l’addestramento, AutoKeras valuta il modello sui dati di test e può essere utilizzato per fare previsioni sui nuovi dati.
