Discesa del gradiente, il motore del machine learning

Il gradiente è stato citato una volta sola nella nostra conversazione con l’AI ma vediamolo un attimo perché ci servirà nei prossimi discorsi. Intanto cos’è il machine learning che abbiamo tirato fuori nel titolo? Ci si riferisce con questo a un’ampia varietà di metodi statistici in grado di imparare da un insieme predisposto di dati per poi essere in grado di fornire risposte su nuovi dati senza bisogno di ulteriori istruzioni. La performance di un sistema di machine learning dipende da quello che ha appreso nei dati usati per l’addestramento e dalla sua architettura. Ambedue le cose sono soggette a grande variabilità.

Ma come fa ad apprendere? A prescindere dall’architettura specifica un sistema del genere contiene un’enorme quantità di parametri e un certo numero di iperparametri. I parametri vengono calcolati attraverso l’addestramento mentre gli iperparametri definiscono l’architettura e le sue regolazioni. Le dimensioni e la topologia di una rete neurale, la velocità dei processi di convergenza verso la soluzione, il valore di regolarizzazione dei disturbi derivanti da rumore statistico, le dimensioni degli insiemi di frasi (batch) somministrate sono esempi di iperparametri. Possiamo pensarli come manopole e interruttori che gli ingegneri possono manovrare per modulare il comportamento della macchina, magari per renderla un po’ più creativa o un po’ più prudente. Bisogna però chiarire il ruolo degli ingegneri: smanettare con i parametri di una rete neurale non è come progettare un ponte o un motore. Sì, nelle costruzioni e nelle macchine esistono aspetti affidati più all’empirìa che alla teoria, ma nel machine learning vale l’artigiano quanto l’ingegnere. Non vi sono pietre o metalli da modellare e accoppiare ma solo bit, in ultima istanza. Vale a dire che siamo completamente all’interno del dominio dello scientific computing, che è squisitamente numerico, e di conseguenza, oggi, squisitamente digitale. È un luogo paradossale: il titolo di un classico della programmazione numerica scientifica è Numerical recipes — The art of scientific computing (Press et al, Cambridge, 2007). Si intende qui l’arte dell’artigiano, ove lo scienziato si fa anche artigiano il cuoco che smette di aggiungere l’ingrediente alla zuppa quando questa “riempie l’occhio”, il muratore che giudica pronto l’impasto quando “la betoniera canta”, l’informatico che ritocca il “freno del gradiente”. Non basta sapere la teoria, occorre fiuto e disposizione all’imprevedibile. I numeri, perfetti nella teoria, si fanno capricciosi quando costretti negli angusti recinti del digitale: i bit in un computer e in tutte le sue parti sono sempre in numero finito. Quando i calcoli si reiterano migliaia o miliardi di volte, in un attimo minime approssimazioni devastano i risultati. Tutto questo valeva prima del machine learning, vale ora a maggior ragione.

I circa 39000 Large Language Models (per linitarsi a questi!) in circolazione hanno decine o centinaia di miliardi di parametri. Pare (dati ufficiosi) che GPT 4.0 abbia 1800 miliardi di parametri, anche se suddivisi in 16 diversi sottosistemi da 111 miliardi di parametri l’uno. Tutti questi numeri devono essere aggiustati durante il processo di apprendimento sulla base di decine di migliaia di miliardi di parole, parti di parole, codici. Una mostruosità.

Abbiamo detto degli iperparametri ma i parametri dove stanno? Per avere un’idea vediamo un esempio semplice di rete neurale.

Immagine tratta da Analysis of KDD-Cup’99, NSL-KDD and UNSW-NB15 Datasets using Deep Learning in IoT, disponibile con licenza Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International.

I nodi della rete emulano i neuroni biologici. Il primo strato a sinistra costituisce l’input. In ciascuno strato interno, i nodi emettono segnali verso gli strati successivi a destra se la somma delle intensità dei segnali ricevuti dagli strati precedenti a sinistra supera una certa soglia. Lo strato di destra rappresenta l’output. Se gli strati intermedi sono molti la rete neurale è “profonda”: il deep learning si realizza con reti neurali profonde. I segnali che giungono da sinistra a ciascun nodo sono caratterizzati da un peso. I pesi sono i parametri di cui dicevamo. All’inizio dell’apprendimento sono fissati in modo casuale, poi durante il processo vengono iterativamente adattati in modo che l’output sia quello giusto. La “conoscenza” di una rete risiede nell’insieme dei suoi pesi. Finito l’addestramento questa rimane congelata, fino ad un nuovo eventuale addestramento. Non è il tuo caso caro lettore, la tua si aggiorna di continuo, se ti dai fare!

Come al solito la faccenda è assai più complicata. Ad esempio, gli elementi di quelle strane matrici che formano il transformer, di cui ragionavamo con l’AI, sono anch’essi parametri da determinare con l’apprendimento. I sistemi di AI hanno architetture molto varie e sono dotati di combinazioni complicate di varie tipologie di reti. In ogni caso la totalità dei parametri che vi compaiono viene determinata attraverso l’addestramento.

Malgrado tale soverchiante complessità, lo schema di base dell’addestramento è semplice. Il processo è iterativo: fissato un certo tipo di compito, si nutre la rete con dati per i quali è nota la risposta, partendo da valori casuali dei parametri. Poi si confrontano i risultati con le risposte note valutando una cosiddetta funzione di costo che esprime la distanza fra risultati stimati e reali. La funzione di costo è un numero il cui valore dipende dalla posizione nello spazio dei parametri. Idealmente la funzione di costo vale zero quando i risultati stimati eguagliano quelli veri ma le dimensioni (mostruose) del problema e le imperfezioni nei dati rendono impossibile un calcolo diretto. Occorre andare per tentativi. Il processo consiste nel muoversi nello spazio dei parametri in passi successivi, ogni volta in modo da ridurre il valore della funzione di costo.

Vale la metafora dell’esploratore bendato in montagna. Costui si trova in vetta a un monte e deve tornare a valle. Poiché non vede è costretto a lavorare localmente, valutando a tasto la direzione di massima pendenza. Ripete la ricerca ad ogni passo seguendo la linea di massima pendenza che serpeggia giù dal monte. Deve fare passi piccoli se vuole essere sicuro di seguire fedelmente la linea, se poi procede troppo lentamente può aumentare la lunghezza dei passi ma — immaginandolo capace di passi da sette leghe… — potrebbe mancare il minimo. E comunque ci sono sempre rischi, perché non vedere è un bel problema e la macchina per sua natura non vede: se il terreno è carsico potrebbe finire in fondo a una dolina ed esserne felice, credendo di avere raggiunto la meta.

Figura generata dal sottoscritto in Python. Il grafico è costituito dalla somma di tre funzioni \frac{\sin(x)\sin(y)}{xy} variamente traslate e scalate.

La figura è largamente imperfetta ma dovrebbe dare l’idea del compito dell’esploratore bendato in uno spazio di sole due dimensioni (parametri). È evidente che il percorso dipende dalla partenza e che si può porre il problema di quale sia la soluzione giusta, perché costui potrebbe finire senza rendersene conto nella dolina (minimo locale) ben lontana dal fondovalle. O potrebbe, con la premura d’andar veloce, saltare a piè pari la soluzione e finire da un’altra parte. O, di converso, potrebbe metterci una vita con passetti troppo timidi. Tutte cose rimediabili o da aggiustare nel contesto ma che richiedono accorgimenti. Il mestiere che si diceva.

La direzione di massima pendenza può essere calcolata in ciascun punto e prende il nome di gradiente. Direzione e valore del gradiente servono ad aggiornare i parametri ad ogni passo. Come al solito, l’abbiamo fatta molto semplice ma i concetti essenziali ci dovrebbero essere.


Inter nos: per fare prima, lo scheletro del codice del grafico me lo sono fatto fare da ChatGPT, poi l’ho aggiustato…

2 pensieri riguardo “Discesa del gradiente, il motore del machine learning”

Lascia un commento