Chiarezza sui concetti di fine-tuning, transfer learning e prompt engineering

Chiarezza sui concetti di fine-tuning, transfer learning e prompt engineering

Condividi con i tuoi amici...

Il fine-tuning è una tecnica specifica di transfer learning che implica l’addestramento di alcuni o tutti i livelli di un modello pre-addestrato con un nuovo dataset, spesso usando un learning rate più basso. Dopo aver inizializzato i pesi dell’intera rete con quelli pre-addestrati, si addestrano i pesi di uno o più strati, inclusi spesso gli strati finali o tutti gli strati.

Il transfer learning è una tecnica in cui un modello addestrato su un compito viene riutilizzato come punto di partenza per un nuovo compito correlato.

Il fine-tuning è quindi una forma di transfer learning, ma non tutti i metodi di transfer learning richiedono il fine-tuning. Infatti se, per esempio, scorporiamo alcuni strati della rete preaddestrata senza aggiornare i pesi per utilizzarla stiamo facendo transfer learning ma non fine-tuning.

Il transfer learning è un concetto più ampio che si riferisce alla pratica di riutilizzare un modello addestrato su un dataset (o compito) come punto di partenza per un altro compito o dataset. Può coinvolgere sia l’uso di caratteristiche apprese da strati intermedi che l’uso diretto di una rete pre-addestrata come base. Può comportare il congelamento di tutti o parte dei pesi del modello pre-addestrato e l’aggiunta di nuovi strati per adattarsi al nuovo compito. Copre una gamma di strategie che includono la feature extraction, il fine-tuning, e molte altre tecniche intermedie.

Nel documento How Deeply to Fine-Tune a Convolutional Neural Network: A Case Study Using a Histopathology Dataset vengono spiegate quattro principali tecniche di transfer learning, tre delle quali applicano il fine-tuning:

Il documento applica queste considerazioni ad una rete CNN. Per approfondire l’architettura di una rete CNN puoi leggere l’articolo: Le reti neurali convoluzionali, un modo per vedere bene al minimo costo

  1. Feature Extraction (Estrazione delle Caratteristiche):
    Questa tecnica prevede l’uso di un modello pre-addestrato per estrarre caratteristiche utili dai dati senza modificare i pesi della rete.

Procedura:

  • Congelare tutti i pesi del modello pre-addestrato.
  • Passare i nuovi dati attraverso il modello fino all’output di uno dei livelli intermedi.
  • Utilizzare queste caratteristiche come input per un nuovo classificatore che sarà addestrato.

Questa tecnica è utile quando le caratteristiche apprese dal modello originale sono sufficientemente generali da essere applicate direttamente al nuovo compito.

  1. Fine-Tuning dell’intera Rete:
    Consiste nel prendere un modello pre-addestrato e addestrarlo ulteriormente su nuovi dati. In questo caso, tutti i pesi della rete vengono aggiornati.

Procedura:

  • Inizializzare il modello con i pesi pre-addestrati.
  • Addestrare il modello sui nuovi dati con un learning rate più basso per non sovrascrivere completamente i vecchi pesi.

Questo metodo è utile quando si dispone di un dataset sufficientemente grande per addestrare nuovamente tutta la rete e quando il nuovo compito è abbastanza differente da richiedere una maggiore specializzazione del modello.

  1. Training del Classificatore Finale (Fine-tuning degli ultimi strati):
    In questa strategia, solo gli ultimi strati della rete neurale (i livelli completamente connessi) vengono addestrati nuovamente, mentre i primi strati rimangono congelati.

Procedura:

  • Congelare i pesi degli strati iniziali/middle della rete.
  • Aggiungere uno o più strati completamente connessi alla fine della rete.
  • Addestrare solo questi nuovi strati con il dataset specifico.

Questa tecnica è particolarmente utile quando il nuovo compito è molto simile al compito originale, ma richiede solo una piccola modifica nel modo in cui vengono combinate le caratteristiche.

4.Training a Blocchi (Blocco per Blocco Fine-tuning):
Questo metodo prevede il fine-tuning di diversi blocchi della rete neurale pre-addestrata. Tipicamente i vari blocchi rappresentano insiemi di strati che processano caratteristiche a diverse granulometrie.

Procedura:

  • Congelare tutti i blocchi tranne uno.
  • Addestrare il modello con il blocco scelto liberato (non congelato).
  • Successivamente, liberare altri blocchi e ripetere il processo di addestramento.

Questa tecnica è spesso usata quando si sperimentano prestazioni subottimali con le altre tecniche menzionate, permettendo di trovare il giusto equilibrio tra caratteristiche apprese e caratteristiche adattate al nuovo compito.

Se i compiti originali e nuovi sono molto simili, verranno probabilmente utilizzate tecniche che congelano meno strati. Se i compiti sono molto differenti, sarà necessario un fine-tuning più esteso. Alcune tecniche di fine-tuning richiedono maggiori capacità computazionali rispetto ad altre. È necessario bilanciare tra l’accuratezza desiderata e le risorse disponibili.

Tabella di sintesi

Clicca sulla tabella per ingrandirla

Passaggi per il Fine-Tuning

  1. Scelta del Modello Pre-Addestrato:
    Seleziona un modello di rete neurale che è stato pre-addestrato su un grande dataset (per esempio, ImageNet). Modelli comuni includono VGG16, VGG19, ResNet, Inception, e altri.
  2. Congelamento dei Pesi:
    Congelare (bloccare) i pesi di alcuni o tutti i livelli della rete pre-addestrata. Il “congelamento” significa che durante l’addestramento, i pesi di questi livelli non saranno aggiornati. Spesso, solo gli ultimi strati della rete sono addestrati nuovamente, mentre gli strati precedenti, che contengono caratteristiche generali come bordi e texture, rimangono congelati.
     for layer in model.layers:
         layer.trainable = False
  1. Modifica della Struttura della Rete:
    Aggiungere uno o più nuovi strati (tipicamente strati densi/fully connected) alla fine della rete per adattarla al nuovo compito, il che potrebbe includere la modifica del numero di neuroni nell’ultimo strato per riflettere il numero di classi nel nuovo dataset.
     from tensorflow.keras.layers import Dense
     from tensorflow.keras.models import Sequential

     model = Sequential([
         base_model,
         Dense(256, activation='relu'),
         Dense(num_classes, activation='softmax')
     ])
  1. Compilazione del Modello:
    Compilare il modello con una funzione di perdita appropriata per il problema (ad esempio, ‘categorical_crossentropy’ per la classificazione), un ottimizzatore (ad esempio, Adam o SGD) e metriche da monitorare (ad esempio, ‘accuracy’).
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
  1. Addestramento del Modello:
    Addestrare il modello sui dati specifici del nuovo compito. Il numero di epoche e il tasso di apprendimento possono essere regolati per ottimizzare le prestazioni.
 history = model.fit(train_data, train_labels, epochs=10, validation_data=(val_data, val_labels))

Considerazioni importanti

È bene utilizzare un learning rate minore durante il fine-tuning rispetto a quello usato per il training originale per evitare di distruggere le feature generali apprese. Ricorda che cos’è il learning rate:

Va considerato con attenzione quale parte del modello congelare e quale parte lasciare addestrabile; gli strati superiori contengono caratteristiche generali, mentre gli strati profondi contengono caratteristiche più specifiche. Infine va utilizzato un set di validazione per monitorare le prestazioni del modello durante l’addestramento e fare aggiustamenti di iperparametri se necessario.

Ricordiamo che il processo di ri-addestramento di una rete neurale può portare alla catastrophic forgetting, dove l’apprendimento di nuovi compiti degrada la performance sui compiti precedentemente appresi. Se vuoi approfodindire questo aspetto leggi l’articolo

Catastrophic forgetting: quando imparare cose nuove a volte ci fa dimenticare quelle vecchie

Esempio di codice Python che realizza il Fine-tuning degli ultimi strati

import tensorflow as tf
from tensorflow.keras.applications import VGG16
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras.optimizers import Adam

# Caricare il modello pre-addestrato VGG16 senza il top (strati finali di classificazione)
base_model = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3))

# Congelare i pesi dei primi livelli
for layer in base_model.layers:
    layer.trainable = False

# Aggiungere nuovi strati in cima al modello base: prendo l'output di base_model e lo metto in input al pooling
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)  # Aggiungere un layer denso con 1024 neuroni
predictions = Dense(10, activation='softmax')(x)  # Aggiungere il layer di output per 10 classi

# Creare il nuovo modello
model = Model(inputs=base_model.input, outputs=predictions)

# Compilare il modello
model.compile(optimizer=Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'])

# Stampare il sommario del modello
model.summary()

# Caricare i dati (esempio con CIFAR-10)
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical

(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train = tf.image.resize(x_train, (224, 224)) / 255.0
x_test = tf.image.resize(x_test, (224, 224)) / 255.0
y_train = to_categorical(y_train, 10)
y_test = to_categorical(y_test, 10)

# Addestrare il modello
model.fit(x_train, y_train, epochs=10, batch_size=32, validation_data=(x_test, y_test))

Spiegazione del Codice

Carichiamo il modello VGG16 pre-addestrato su ImageNet senza gli strati finali di classificazione (include_top=False).

Congeliamo i pesi dei primi livelli del modello base impostando layer.trainable = False.

Aggiungiamo nuovi strati densi in cima al modello base. In questo caso, aggiungiamo un livello di pooling globale, seguito da un livello denso con 1024 neuroni e un livello di output con 10 neuroni (per la classificazione su 10 classi).

Creiamo un nuovo modello con il modello base come input e i nuovi strati come output. Compiliamo il modello con l’ottimizzatore Adam e la funzione di perdita categorical_crossentropy.

Carichiamo il dataset CIFAR-10 e ridimensioniamo le immagini a 224×224 pixel per adattarle all’input del modello VGG16. Normalizziamo i valori dei pixel tra 0 e 1 e convertiamo le etichette in una codifica one-hot.

Addestriamo il modello sui dati di addestramento e valutiamo le prestazioni sui dati di test.

Si sarebbero potuti innestare i nuovi strati nel seguente modo:

model = Sequential([
    base_model,
    GlobalAveragePooling2D(),
    Dense(1024, activation='relu'),
    Dense(10, activation='softmax')
])

Distinzione tra fine-tuning e prompt-engineering

Benchè diverse fonti usino il termine fine-tuning per indicare anche la personalizzazione di un modello preaddestrato mediante la fornitura di esempi di contesto, in realtà ciò avviene impropriamente, perché in questo caso è più giusto affermare che si tratta di un’operazione di prompt-engineering.

Il prompt-engineering è l’arte di formulare richieste (prompts) in modo tale da ottenere risposte desiderate dal modello senza ulteriori riaddestramenti. Si tratta di scrivere prompt chiari e dettagliati che guidino il modello a generare la risposta desiderata.

Il prompt può essere modificato iterativamente per affinare le risposte.

Inoltre è possibile fornire al modello un’ampia documentazione in grado di creare un contesto utile per elaborare la miglior risposta.

In tutti questi casi, non avviene alcun aggiornamento dei pesi del modello preaddestrato, ma grazie alle tecniche di self-attention il modello è in grado di generare riposte attinenenti.

Per approfondire tali tecniche leggi l’articolo La self-attention delle reti transformer

In sintesi se l’aggiornamento dei compiti del modello avviene modificando i suoi pesi allora stiamo facendo fine-tuning. D’altra parte, se i pesi non vengono modificati ma si fornisce contesto o istruzioni supplementari al modello nell’ambito di una conversazione o mediante un message system stiamo facendo prompt-engineering.

Tecniche di Ottimizzazione dei Modelli di Linguaggio (LLM)

├── Ottimizzazione tramite Fine-Tuning
├── Raccolta di dataset specifici
├── Riadattamento del modello
├── Validazione e testing

├── Ottimizzazione tramite Prompt-Engineering
├── Formulazione dei prompt
├── Iterazione e miglioramento dei prompt
├── Valutazione delle risposte del modello