Un algoritmo di machine learning non contiene la soluzione del problema: contiene un modello con dei parametri, una misura dell'errore e un procedimento che muove i parametri per ridurre l'errore sui dati. Questa guida segue il percorso della Machine Learning Specialization di Andrew Ng (DeepLearning.AI e Stanford Online), riscritto a modo nostro: si parte da una retta che impara a prevedere il prezzo di una casa, si passa alla classificazione, alle reti neurali e agli alberi, e si arriva a clustering, rilevamento di anomalie, sistemi di raccomandazione e apprendimento per rinforzo. Ogni idea arriva prima come intuizione, poi come meccanismo con i numeri, e quasi sempre con un laboratorio in cui muovere i parametri con le mani e vedere che cosa succede all'errore.
Parte I
Un programma di filtro antispam scritto a mano è un elenco di regole: «se contiene "vinci", è spam». Dopo la decima regola smette di funzionare. Che cosa fa di diverso un programma che impara?
Arthur Samuel, nel 1959, definì il machine learning come «il campo di studio che dà ai computer la capacità di imparare senza essere esplicitamente programmati». Il suo programma di dama giocò decine di migliaia di partite contro se stesso e, tenendo conto di quali posizioni portavano a vincere, finì per battere il suo autore. La differenza sta in chi scrive la regola. Nel programma tradizionale la scrive il programmatore.
In un programma che impara il programmatore scrive una forma di regola con dei numeri lasciati liberi (i parametri), un modo di misurare quanto la regola sbaglia sugli esempi, e un procedimento che muove i numeri per sbagliare meno. I numeri giusti li trovano i dati. Tutta la guida è lo svolgimento di questa frase: cambiano la forma del modello, la misura dell'errore e il tipo di dati, ma il ciclo è sempre quello.
I due grandi tipi di machine learning si distinguono per che cosa contengono i dati. Nell'apprendimento supervisionato ogni esempio è una coppia: un input x e la risposta giusta y, scritta da qualcuno. Email → spam o no; audio → trascrizione; immagine di un pezzo → difettoso o no; dati di una casa → prezzo di vendita. Il modello vede migliaia di coppie e impara a produrre y da x, così da farlo poi su x mai visti. È il tipo che produce più valore economico oggi, ed è la Parte I e II di questa guida.
Dentro il supervisionato ci sono due famiglie: la regressione, quando y è un numero tra infiniti possibili (un prezzo, una temperatura), e la classificazione, quando y è una tra poche categorie (spam/non spam, gatto/cane/cavallo, benigno/maligno).
Nell'apprendimento non supervisionato i dati hanno solo gli x: nessuno ha scritto la risposta, e il compito è trovare una struttura. Raggruppare clienti simili senza sapere in anticipo quali gruppi esistono (clustering, capitolo 11); segnalare la transazione che non somiglia a nessun'altra (rilevamento di anomalie, capitolo 12); comprimere molte variabili in poche (riduzione di dimensionalità).
Un terzo tipo, l'apprendimento per rinforzo (capitolo 14), non ha né le risposte né solo gli x: ha un agente che agisce e riceve ricompense, e deve scoprire da solo quali azioni le producono.
| Tipo | Che cosa c'è nei dati | Che cosa impara | Esempi | Capitoli |
|---|---|---|---|---|
| Supervisionato · regressione | x e un numero y | a prevedere il numero | prezzo di una casa, domanda di energia | 2–4 |
| Supervisionato · classificazione | x e una categoria y | a scegliere la categoria | spam, diagnosi, riconoscimento di oggetti | 5–10 |
| Non supervisionato | solo x | una struttura nascosta | segmenti di clienti, anomalie, compressione | 11–13 |
| Per rinforzo | stati, azioni, ricompense | una strategia | robot, giochi, atterraggi | 14 |
Per tutta la Parte I useremo un dataset minuscolo, abbastanza piccolo da seguire ogni numero: la superficie di alcune case vendute, in metri quadri, e il prezzo a cui sono state vendute, in migliaia di euro. È un problema di regressione supervisionata: abbiamo x (la superficie) e y (il prezzo), e vogliamo una funzione che, data la superficie di una casa nuova, ne stimi il prezzo.
| esempio i | superficie x(i) (m²) | prezzo y(i) (k€) |
|---|---|---|
| 1 | 50 | 150 |
| 2 | 65 | 185 |
| 3 | 80 | 210 |
| 4 | 95 | 265 |
| 5 | 110 | 290 |
| 6 | 130 | 350 |
I dati di addestramento si chiamano training set; il loro numero è m (qui 6). Il singolo esempio è x(i), y(i), con l'indice tra parentesi (non è una potenza). La funzione che il modello produce si chiama f, oppure ŷ («y cappello»): la stima, da non confondere con il valore vero y. La variabile di ingresso x si chiama anche feature (caratteristica), quella di uscita y target (obiettivo) o etichetta.
In sintesi · capitolo 1
Guardando i sei punti del dataset, un essere umano traccerebbe «a occhio» una retta che li attraversa e la userebbe per stimare il prezzo di una casa da 100 m². Come si traduce quell'«a occhio» in qualcosa che un programma può fare?
Prima si fissa la forma: una retta. Una retta è f(x) = w · x + b, dove w è la pendenza (quanti k€ in più per ogni m² in più) e b l'intercetta (il prezzo «di base» a superficie zero). w e b sono i parametri: i due numeri liberi del modello. Scegliere la retta significa scegliere w e b. Il modello si chiama regressione lineare a una variabile (o univariata), ed è il più semplice modello supervisionato: tutto ciò che impareremo dopo è una variazione di questo.
Poi serve un modo di dire quanto una retta è sbagliata, con un numero, perché solo un numero si può minimizzare. Per ogni esempio la retta stima ŷ(i) = w·x(i) + b, e il vero prezzo è y(i): la differenza ŷ(i) − y(i) è l'errore (o residuo) su quell'esempio.
Si eleva al quadrato, per due motivi: così gli errori positivi e negativi non si cancellano, e un errore grande pesa più di due piccoli (sbagliare di 40 costa quattro volte sbagliare di 20). Si sommano i quadrati su tutti gli m esempi, e si divide per 2m: dividere per m dà la media, così il costo non cresce solo perché i dati sono di più; il 2 è una comodità, perché semplifica la derivata del capitolo 3 senza cambiare dove sta il minimo.
I sei punti sono il dataset del capitolo 1. Muovi w e b: le linee verticali sono gli errori su ogni esempio, e il costo J è la media dei loro quadrati, diviso 2. Prova a portarlo più in basso che puoi a mano.
Il minimo esatto per questi dati è w ≈ 2,50 e b ≈ 21,2, con J ≈ 19,4: nessuna retta può fare meglio, perché i punti non sono perfettamente allineati. Trovarlo a mano è faticoso già con due parametri; con mille è impossibile. Il capitolo 3 fa fare al computer esattamente quello che stai facendo tu, ma con una bussola.
Il costo come paesaggio. Per ogni coppia (w, b) c'è un valore di J: se si disegna J al variare di w e b si ottiene una superficie a forma di ciotola, con un solo punto più basso. Per la regressione lineare con l'errore quadratico è sempre così (la funzione è convessa): non ci sono avvallamenti secondari in cui restare bloccati.
Tagliando la ciotola a quote fisse si ottengono le curve di livello, ellissi concentriche attorno al minimo: le useremo nel capitolo 4 per capire perché conviene scalare le feature.
In sintesi · capitolo 2
Autodiagnosi
Sei in una valle nebbiosa e devi raggiungere il punto più basso. Non vedi la valle: senti solo la pendenza del terreno sotto i piedi. Che cosa fai?
Fai un passo in discesa, nella direzione in cui il terreno scende di più; ti fermi, risenti la pendenza, fai un altro passo. È la discesa del gradiente (gradient descent), il procedimento che trova i parametri in quasi tutto il machine learning, dalle rette alle reti neurali con miliardi di pesi.
La «pendenza del terreno» ha un nome matematico: la derivata del costo rispetto a un parametro, ∂J/∂w, che dice di quanto cambierebbe J se aumentassi w di pochissimo. Positiva: salendo con w il costo sale, quindi w va diminuito. Negativa: va aumentato. Grande: siamo su un pendio ripido, lontani dal fondo. Vicina a zero: siamo quasi sul fondo.
Il gradiente è l'insieme delle derivate rispetto a tutti i parametri: la direzione di massima salita, e il suo opposto è la direzione di massima discesa.
Le derivate per la regressione lineare si calcolano a mano, ed è qui che il 2 nel denominatore di J ripaga:
Formula · le derivate della regressione lineare
Leggile così: la derivata rispetto a b è l'errore medio (se in media stimiamo troppo alto, b scende); quella rispetto a w è l'errore medio pesato per x (gli esempi con x grande spostano di più la pendenza). Questa versione, che a ogni passo usa tutti gli m esempi, si chiama batch gradient descent.
Troppo piccolo, e servono migliaia di passi minuscoli. Troppo grande, e il passo scavalca il fondo atterrando più in alto di prima, poi scavalca di nuovo dall'altra parte: il costo oscilla e diverge. La cosa notevole è che con α fisso il passo si accorcia da solo avvicinandosi al minimo, perché la derivata diminuisce: non serve ridurre α durante la discesa.
Il laboratorio mostra tutte e tre le situazioni sulla ciotola del nostro dataset, con b tenuto fermo al suo valore ottimo per poter disegnare J in due dimensioni.
La curva è il costo del dataset al variare di w, con b fissato a 21,2. Scegli il punto di partenza, il learning rate e il numero di passi, e segui la pallina: ogni passo è w ← w − α·∂J/∂w, con la derivata calcolata davvero sui sei esempi.
Nota la scala di α: con x intorno a 100 m², la derivata ∂J/∂w contiene x² ≈ 10.000, quindi i passi sensati sono dell'ordine di 10⁻⁴. È il primo segnale di un problema che il capitolo 4 risolve con il feature scaling: la scala giusta di α dipende dalla scala dei dati.
In sintesi · capitolo 3
Autodiagnosi
Una casa non ha solo la superficie: ha il numero di stanze, il piano, l'età. Come si estende la retta a quattro input? E perché, appena lo si fa, la discesa del gradiente comincia a comportarsi male?
La forma resta lineare: ogni feature ha il suo peso, f(x) = w₁x₁ + w₂x₂ + w₃x₃ + w₄x₄ + b. Si scrive in modo compatto raccogliendo le feature in un vettore x = [x₁, x₂, x₃, x₄] e i pesi in un vettore w: f(x) = w · x + b, dove il punto è il prodotto scalare (si moltiplicano le componenti a coppie e si sommano).
Ogni peso ha un significato leggibile: w₂ = 15 dice «ogni stanza in più vale 15 k€, a parità del resto»; un w₄ negativo dice che l'età abbassa il prezzo.
Il modello si chiama regressione lineare multipla (non «multivariata», che indica più output).
Vettorizzazione. Il prodotto scalare si può scrivere come un ciclo for su n feature, oppure come np.dot(w, x) + b. La seconda forma non è solo più corta: NumPy la esegue con istruzioni del processore che moltiplicano molte coppie in parallelo e sommano in blocco, e con n grande la differenza è di decine di volte.
La stessa idea vale per il gradiente: i Σ su tutti gli esempi diventano prodotti tra matrici, ed è così che il training di una rete neurale su GPU diventa possibile. Le derivate della discesa del gradiente restano quelle del capitolo 3, una per ogni wj: ∂J/∂wj = 1/m · Σ (f(x(i)) − y(i)) · xj(i).
La superficie va da 50 a 130, il numero di stanze da 1 a 5. Perché questo dovrebbe disturbare la discesa del gradiente?
Perché il peso di una feature grande deve essere piccolo e viceversa: se x₁ (superficie) vale 100 e x₂ (stanze) vale 3, un w₁ = 2 sposta il prezzo di 200 mentre un w₂ = 2 lo sposta di 6. La ciotola del costo diventa allora una valle stretta e lunga: ripidissima lungo w₁ (basta un filo per cambiare molto il prezzo), piattissima lungo w₂.
La discesa del gradiente, che segue la pendenza locale, rimbalza da una parete all'altra della valle stretta facendo passi minuscoli lungo l'asse lungo: serve un α piccolo per non divergere su w₁, e con quell'α lungo w₂ si impiega un'eternità.
Il rimedio è portare tutte le feature su una scala simile, tipicamente intorno a [−1, 1].
Formule · tre modi di scalare
La normalizzazione per media sulle case del capitolo 1. Le superfici sono 50, 65, 80, 95, 110 e 130 m². Servono tre numeri, tutti calcolati sul training set: il minimo (50), il massimo (130) e la media vera μ. Il denominatore è l'ampiezza dell'intervallo, max − min.
Calcolo · media e ampiezza delle superfici
Ogni superficie diventa x′ (la colonna centrale è lo scarto x − μ, quella a destra il valore scalato x′):
| superficie x (m²) | scarto dalla media | valore scalato |
|---|---|---|
| 50 | −38,3 | −0,479 |
| 65 | −23,3 | −0,292 |
| 80 | −8,3 | −0,104 |
| 95 | 6,7 | 0,083 |
| 110 | 21,7 | 0,271 |
| 130 | 41,7 | 0,521 |
Che cosa si nota. La media delle x′ è esattamente 0: sottrarre μ sposta il centro dei dati sullo zero. I valori vanno da −0,479 a 0,521, un intervallo largo esattamente 1, perché si è diviso proprio per l'ampiezza max − min; non è quindi [−1, 1] pieno, ma un tratto largo 1 che sta dentro [−1, 1] e che è simmetrico solo se i dati lo sono.
Qui la casa più piccola è più vicina alla media di quella più grande (38 m² contro 42), e infatti −0,479 è più vicino a zero di 0,521.
Se al posto di μ si usasse il punto medio tra minimo e massimo, si otterrebbe un [−0,5, 0,5] perfetto, ma la media delle x′ non sarebbe più zero: per questo serve la media vera e non bastano minimo e massimo.
Calcolo · il punto medio al posto della media
Qualunque scaling si scelga, va applicato con gli stessi numeri del training set (μ, minimo e massimo, oppure μ e σ) anche ai dati nuovi al momento della previsione: il modello ha imparato sulle feature scalate e si aspetta quelle.
Una casa nuova di 150 m² diventa:
Calcolo · una casa nuova
Anche se supera il massimo del training set: non si ricalcolano minimo e massimo con la casa nuova.
Curve di livello del costo per un dataset con due feature, superficie (50–130) e stanze (1–5), al variare di w₁ e w₂ (b fissato). Attiva lo scaling e guarda la valle diventare una ciotola rotonda; poi confronta quanti passi servono alla discesa per arrivare al centro.
Il cursore di α è relativo: lo stesso valore viene convertito nella scala sensata per ciascun caso (nel caso grezzo i passi utili sono mille volte più piccoli), così da confrontare la forma delle due discese. Il centro è il minimo di J; i cerchi sono le curve di livello.
Il grafico da guardare sempre è J in funzione del numero di iterazioni (la curva di apprendimento): se la discesa funziona, scende a ogni passo e poi si appiattisce. Se sale, anche solo a tratti, α è troppo grande (o c'è un errore nel codice: con α abbastanza piccolo J deve scendere a ogni iterazione).
Un metodo pratico per scegliere α: provare valori distanziati di circa tre volte, 0,001, 0,003, 0,01, 0,03, 0,1…, scegliere il più grande che fa ancora scendere J in modo regolare, o poco meno. Quando J scende di meno di una soglia (per esempio 0,001) in un'iterazione si può dichiarare la convergenza, ma guardare il grafico è più affidabile di qualsiasi soglia.
Il modello è lineare nei parametri, non necessariamente nei dati: nessuno vieta di costruire nuove feature dalle vecchie. Se hai la larghezza e la profondità del lotto, la feature che conta è probabilmente l'area, larghezza × profondità: creala e dalla al modello.
Se la relazione tra superficie e prezzo non è una retta ma si piega, aggiungi x² e x³ come feature: f(x) = w₁x + w₂x² + w₃x³ + b è ancora una regressione lineare (nei w), ma disegna una curva. È la regressione polinomiale, e qui lo scaling diventa indispensabile: se x va fino a 130, x² arriva a 17.000 e x³ a due milioni.
Scegliere le feature giuste, con la conoscenza del problema, è spesso ciò che fa la differenza tra un modello mediocre e uno buono; nel capitolo 7 vedremo che le reti neurali imparano da sole feature intermedie, ed è il motivo del loro successo.
In sintesi · capitolo 4
Autodiagnosi
Un tumore è maligno (1) o benigno (0), e conosciamo la sua dimensione. Perché non usare la retta del capitolo 2, con una soglia a 0,5?
Perché la retta non sa che y è una categoria. Funziona finché i dati sono «comodi», ma basta aggiungere un esempio di tumore molto grande, chiaramente maligno, perché la retta si inclini per inseguirlo, sposti la soglia a destra e cominci a classificare come benigni tumori che prima classificava bene.
Inoltre produce valori sotto 0 e sopra 1, che come «probabilità di essere maligno» non significano nulla. Serve un modello che produca sempre un numero tra 0 e 1 e che non si lasci trascinare dagli esempi estremi.
La regressione logistica (che, nonostante il nome, è un algoritmo di classificazione) prende la stessa combinazione lineare z = w·x + b e la passa attraverso la funzione sigmoide, g(z) = 1 / (1 + e−z): una S che vale 0,5 per z = 0, tende a 1 per z grande e a 0 per z molto negativo. L'uscita f(x) = g(w·x + b) si legge come la probabilità che y sia 1 dato x: f = 0,7 vuol dire «70% maligno».
Per decidere si confronta con una soglia, di solito 0,5: f ≥ 0,5 equivale a z ≥ 0, cioè w·x + b ≥ 0. L'insieme dei punti in cui w·x + b = 0 è il confine di decisione (decision boundary): una retta se le feature sono due, un piano se tre; e con feature polinomiali (x₁², x₂²…) diventa un cerchio o una forma qualsiasi, come nel capitolo 4.
Se si usa l'errore quadratico con la sigmoide dentro, la ciotola del costo smette di essere una ciotola: diventa un terreno pieno di avvallamenti locali, e la discesa del gradiente si ferma nel primo che trova.
Serve una misura dell'errore pensata per le probabilità. Per un singolo esempio, la perdita (loss) della regressione logistica è −log(f(x)) se y = 1 e −log(1 − f(x)) se y = 0. Leggila:
Calcolo · la perdita quando il vero valore è 1
Più il modello è sicuro della cosa sbagliata, più la perdita cresce, senza limite. Le due righe si scrivono in una sola, perché y vale 0 o 1: L = −y·log(f) − (1 − y)·log(1 − f). Il costo J è la media delle perdite sugli m esempi, e con questa scelta torna a essere convesso: un solo minimo. (Nel capitolo 2 della guida sul training degli LLM la stessa funzione, chiamata cross-entropy, è la loss con cui si addestrano i modelli linguistici.)
Il colpo di scena è nella discesa del gradiente: le derivate hanno la stessa identica forma della regressione lineare:
Formula · il gradiente della regressione logistica
Cambia solo che cosa è f: w·x + b prima, g(w·x + b) ora. Tutto il resto (vettorizzazione, scaling, controllo della curva di J, scelta di α) si trasferisce senza modifiche.
Dieci tumori su una sola feature, la dimensione (cm): i punti in alto sono maligni (y = 1), quelli in basso benigni (y = 0). Muovi w e b: la curva è f(x) = g(w·x + b), la linea tratteggiata il confine f = 0,5, e il costo è la media delle perdite logistiche. Prova a far passare la soglia nel punto giusto, poi rendi la S più ripida.
Il confine di decisione è x = −b/w: cambiando b la S trasla, cambiando w si irrigidisce. Con dati perfettamente separabili la discesa del gradiente continuerebbe a crescere w all'infinito (una S sempre più verticale abbassa sempre un po' il costo): è uno dei motivi per cui, nel capitolo 6, si aggiunge la regolarizzazione.
In sintesi · capitolo 5
Autodiagnosi
Un polinomio di grado 5 passa esattamente per tutti e sei i punti del dataset: costo zero. È il modello migliore?
No, e si vede appena arriva una casa nuova: la curva che si contorce per toccare ogni punto del training set stima prezzi assurdi tra un punto e l'altro. Ha imparato il rumore dei sei esempi, non la relazione tra superficie e prezzo.
Si dice che il modello ha overfit (si è sovradattato) o che ha alta varianza: cambiando di poco i dati di training, la curva cambierebbe moltissimo.
All'estremo opposto c'è l'underfit, o alto bias: una retta su dati che si piegano ha un pregiudizio (la relazione è lineare) che i dati non confermano, e sbaglia sia sul training set sia sui dati nuovi. Il modello che generalizza sta nel mezzo: va bene sui dati di training e quasi altrettanto bene su quelli mai visti. Lo stesso accade nella classificazione: un confine di decisione che si avvolge attorno a ogni singolo esempio ha overfit.
Più dati: con molti esempi un modello flessibile non riesce più a inseguire il rumore, perché il rumore si media; è il rimedio migliore quando si può. Meno feature: usare solo quelle che contano (feature selection), a costo di buttare informazione. Regolarizzazione: tenere tutte le feature ma impedire ai parametri di diventare grandi, che è ciò che permette alla curva di contorcersi.
L'idea è aggiungere al costo un termine che cresce con la grandezza dei pesi:
Formula · il costo con la regolarizzazione
Il primo pezzo chiede di adattarsi ai dati, il secondo di tenere i w piccoli; λ (lambda, il parametro di regolarizzazione) decide il compromesso.
λ = 0: nessuna tassa, si torna all'overfitting. λ enorme: tutti i w vengono schiacciati a zero e resta f = b, una retta orizzontale, underfitting. Nel mezzo c'è il valore giusto, e il capitolo 9 spiega come sceglierlo con i dati. Per convenzione b non si regolarizza: non cambia la forma della curva, solo la sua altezza.
Nella discesa del gradiente il termine aggiunge λ/m · wj alla derivata, e la regola di aggiornamento diventa:
Formula · l'aggiornamento con la regolarizzazione
A ogni passo ogni peso viene prima moltiplicato per un numero appena sotto 1 (per esempio 0,9998) e poi aggiornato come sempre. La regolarizzazione è letteralmente un restringimento continuo dei pesi, che il termine dei dati contrasta solo dove serve davvero. Per la regressione logistica è identico: stessa tassa, stesso restringimento.
Dodici case generate da una relazione curva più rumore; otto sono di training (pieni), quattro di test (vuoti), mai usate per adattare il modello. Scegli il grado del polinomio e λ: il modello è ottimizzato esattamente (con la regolarizzazione), e i due errori dicono se sta imparando il problema o il rumore.
Le feature polinomiali sono scalate con lo z-score prima dell'adattamento, come richiede il capitolo 4; il grafico mostra la curva nelle unità originali. L'errore di test è la misura che conta: è ciò che il modello farà sulle case che non ha mai visto.
In sintesi · capitolo 6
Autodiagnosi
Parte II
Nel capitolo 4 le feature utili (l'area del lotto, x²) le abbiamo costruite noi. Per riconoscere un volto in una foto di un milione di pixel, quali feature costruiresti a mano? E se fosse il modello a inventarle?
Prendi il problema di prevedere se una maglietta sarà un best seller a partire da prezzo, costo di spedizione, spesa in marketing e qualità del tessuto. Un esperto ragionerebbe per passi intermedi: prezzo e spedizione determinano quanto è conveniente; il marketing quanto è conosciuta; prezzo e tessuto la qualità percepita; e da queste tre si decide.
Una rete neurale fa esattamente questo, con una differenza: le tre grandezze intermedie non le definisce nessuno. Il modello ha tre «unità» che ricevono tutte e quattro le feature e imparano, durante il training, quali combinazioni calcolare perché la previsione finale funzioni. Noi, dopo, possiamo guardarle e dar loro un nome; la rete non ne ha bisogno.
Ogni unità, o neurone, è una regressione logistica del capitolo 5: prende i suoi input, calcola z = w·x + b, applica una funzione di attivazione g (per ora la sigmoide) e produce un numero a, l'attivazione.
Un gruppo di neuroni che ricevono gli stessi input è uno strato (layer); l'uscita di uno strato è l'input del successivo. Lo strato finale produce la previsione; quelli in mezzo si chiamano nascosti (hidden), perché il training set non dice che cosa debbano calcolare: contiene solo gli x e le y, e i valori intermedi li decide la rete.
In una rete per il riconoscimento dei volti, i neuroni dei primi strati finiscono per reagire a bordi e linee corte, quelli successivi a occhi e nasi, gli ultimi a forme di volto intere: una gerarchia di feature che nessuno ha programmato.
La notazione mette tra parentesi quadre l'indice dello strato: a[1] è il vettore delle attivazioni dello strato 1, a2[1] quella del suo secondo neurone, W[1] e b[1] i suoi parametri; l'input x si può chiamare a[0]. Lo strato l calcola, per ciascun neurone j:
Formula · un neurone di uno strato
Fare questo calcolo strato dopo strato, dall'input all'uscita, si chiama forward propagation (propagazione in avanti) ed è ciò che la rete fa in inferenza, quando è già addestrata e deve rispondere. Tutti i neuroni di uno strato si calcolano insieme con un prodotto tra matrici:
Formula · un intero strato, in un colpo solo
È la vettorizzazione del capitolo 4, ed è il motivo per cui le reti girano bene su GPU.
Un solo neurone logistico traccia un confine di decisione lineare. Due strati possono tracciare confini curvi, chiusi, a pezzi: ogni neurone nascosto divide il piano con la sua retta, e il neurone di uscita combina le regioni.
Il laboratorio allena davvero una rete piccola su dati che nessuna retta può separare; guarda nascere il confine e le rette dei singoli neuroni nascosti.
Sessanta punti in due classi, i positivi in mezzo: nessuna retta li separa. La rete ha 2 input, uno strato nascosto (scegli quanti neuroni, con attivazione ReLU) e un neurone di uscita sigmoide; viene addestrata con la discesa del gradiente sulla perdita logistica, con le derivate calcolate dalla regola della catena (backpropagation). Le linee sottili sono i confini dei neuroni nascosti (dove la loro z vale 0); lo sfondo è la probabilità prevista.
Con 1 neurone nascosto la rete può al massimo tracciare una retta; con 2 chiude una striscia; con 4 o più circonda i positivi. Ogni «Nuovi pesi casuali» parte da un'inizializzazione diversa: a volte la discesa si ferma in una soluzione peggiore, perché con gli strati nascosti il costo non è più una ciotola convessa. Il training vero usa le stesse formule, con milioni di parametri e la libreria (TensorFlow, PyTorch) che calcola le derivate al posto nostro.
In sintesi · capitolo 7
Autodiagnosi
In TensorFlow addestrare una rete sono tre righe: model = Sequential([...]), model.compile(loss=...), model.fit(X, Y, epochs=100). Che cosa fanno davvero quelle tre righe?
Sono le tre mosse della regressione logistica. La prima definisce il modello: quanti strati, quanti neuroni, quali attivazioni, cioè la funzione f(x) con i suoi parametri. La seconda sceglie il costo: per una classificazione binaria la binary cross-entropy, che è esattamente la perdita logistica del capitolo 5 (−y log f − (1−y) log(1−f)) mediata sugli esempi; per una regressione l'errore quadratico. La terza minimizza il costo con la discesa del gradiente per il numero di epoche indicato (un'epoca = un passaggio su tutto il training set).
La libreria calcola le derivate rispetto a tutti i parametri con la backpropagation, la regola della catena applicata all'indietro strato per strato; è la stessa cosa che il laboratorio del capitolo 7 fa a mano per una rete piccola, e la guida sul training degli LLM la spiega passo per passo.
Quando il gradiente non c'è, perché l'unico segnale è un punteggio che arriva alla fine di una prova e non è derivabile rispetto ai pesi, si può farne a meno: la guida Neural Network Driving allena una rete piccola come quella del capitolo 7 per evoluzione, facendo competere molte copie e ricombinando i pesi delle migliori.
# 1 · il modello: la forma di f(x) e i suoi parametri
model = Sequential([Dense(units=25, activation='relu'),
Dense(units=15, activation='relu'),
Dense(units=1, activation='sigmoid')])
# 2 · il costo: la perdita logistica, qui chiamata binary cross-entropy
model.compile(loss=BinaryCrossentropy(), optimizer=Adam(learning_rate=1e-3))
# 3 · la discesa: 100 passaggi sul training set
model.fit(X, Y, epochs=100)
Tre attivazioni coprono quasi tutto. La sigmoide, tra 0 e 1, va bene quando l'uscita è una probabilità: il neurone finale di una classificazione binaria. La lineare (g(z) = z, cioè nessuna attivazione) serve quando l'uscita è un numero qualsiasi, positivo o negativo: una regressione.
La ReLU (Rectified Linear Unit, g(z) = max(0, z)) è la scelta di default per gli strati nascosti, per due ragioni: è più veloce da calcolare, e soprattutto non si «appiattisce» a destra come la sigmoide, che per z grande ha derivata quasi zero e rende la discesa del gradiente lentissima. Per uscite che devono essere non negative (un prezzo) va bene la ReLU anche in uscita.
Se tutti i neuroni avessero attivazione lineare, la rete intera, per quanti strati abbia, calcolerebbe una funzione lineare di x: una combinazione lineare di combinazioni lineari è ancora una combinazione lineare, e si torna alla regressione del capitolo 4. Gli strati nascosti valgono qualcosa solo con una g che piega: è la ReLU a permettere al laboratorio del capitolo 7 di tracciare confini curvi, a pezzi di retta.
Riconoscere le cifre scritte a mano ha dieci risposte possibili, non due. Come si generalizza la sigmoide?
Con la softmax. Lo strato di uscita ha un neurone per classe, ciascuno con la sua zj = wj·a + bj; la softmax trasforma le N z in probabilità che sommano a 1:
Formula · softmax
La perdita è la cross-entropy: −log ay, il logaritmo negativo della probabilità data alla classe vera; se la classe vera era la 3 e il modello le dà:
Calcolo · la cross-entropy se la classe vera era la 3
A differenza delle altre attivazioni, qui ogni uscita dipende da tutte le z: alzare una abbassa le altre.
Un dettaglio pratico che conta: calcolare ez per z grandi e poi il logaritmo produce errori di arrotondamento. Le librerie permettono di far uscire dalla rete le z grezze (attivazione lineare) e di dire alla loss di applicare la softmax internamente (from_logits=True), riorganizzando i conti in modo più stabile. Il risultato è lo stesso, i numeri sono più precisi.
Da non confondere con la classificazione multi-label: se una foto può contenere insieme un'auto, un pedone e un autobus, le risposte sono tre sì/no indipendenti, e si usano tre sigmoidi, non una softmax.
Quattro cifre possibili, quattro z in uscita dalla rete. Muovi le z e scegli quale cifra era quella vera: le barre sono la softmax, il numero in basso la perdita.
Adam (Adaptive Moment Estimation) è la discesa del gradiente con un learning rate per parametro, che si adatta da solo: se un parametro continua a muoversi nella stessa direzione passo dopo passo, il suo α cresce (si può andare più in fretta); se oscilla avanti e indietro, il suo α si riduce. In pratica è più robusto della discesa semplice alla scelta di α iniziale ed è l'ottimizzatore di default per quasi tutto; si parte di solito da 10⁻³.
Negli strati convoluzionali, infine, ogni neurone guarda solo una finestra dell'input (un pezzo di immagine, un tratto di segnale) invece che tutto: meno parametri, meno overfitting, e la capacità di riconoscere un motivo ovunque compaia. Sono il mattone delle reti per immagini (CNN, Convolutional Neural Network); qui basta sapere che esistono e perché.
In sintesi · capitolo 8
Autodiagnosi
Il modello della casa sbaglia di 60 k€ su case nuove. Potresti raccogliere più dati, aggiungere feature, toglierne, cambiare λ, cambiare rete. Ciascuna scelta costa settimane: quale fare?
La risposta sbagliata è «proviamole tutte». Quella giusta è misurare due numeri che dicono di che male soffre il modello, perché i rimedi per i due mali sono opposti. Per misurarli servono dati che il modello non ha usato per imparare: si divide il dataset in training set (per esempio il 60%), con cui si trovano i parametri, cross-validation set (20%, detto anche validation o dev set), con cui si scelgono grado del polinomio, λ, architettura della rete, e test set (20%), toccato una volta sola alla fine per stimare quanto il modello finale sbaglierà nel mondo.
Il test set non si usa per nessuna scelta: nel momento in cui lo si usa per scegliere, la sua stima diventa ottimista, perché si è scelto per fare bene su di lui.
Il capitolo 6 ha dato i nomi: alto bias (underfitting) e alta varianza (overfitting). Due numeri li distinguono. Jtrain alto significa che il modello non riesce nemmeno a imparare gli esempi che vede: alto bias. Jtrain basso ma Jcv molto più alto significa che li ha imparati ma non generalizza: alta varianza. Possono anche coesistere, in parti diverse dello spazio: Jtrain alto e Jcv ancora più alto.
«Alto» rispetto a che cosa? Rispetto a una baseline: le prestazioni umane sullo stesso compito, un modello concorrente, o una stima di quanto il rumore dei dati renda impossibile scendere.
Se un umano trascrive l'audio con il 10,6% di errore, un Jtrain del 10,8% non è alto bias: è vicino al limite.
Da qui la tabella delle decisioni, che è il cuore pratico di tutto il corso. Alta varianza: più dati di training, meno feature, λ più grande. Alto bias: più feature o feature polinomiali, λ più piccolo, un modello più grande.
Notare che più dati non cura l'alto bias: se la retta non riesce a seguire i dati che ha, dargliene altri non la fa piegare.
| Sintomo | Diagnosi | Rimedi che aiutano | Rimedi inutili |
|---|---|---|---|
| Jtrain alto (rispetto alla baseline), Jcv ≈ Jtrain | Alto bias | feature in più o polinomiali · λ più piccolo · rete più grande | più dati |
| Jtrain basso, Jcv ≫ Jtrain | Alta varianza | più dati · meno feature · λ più grande | un modello ancora più grande (senza regolarizzazione) |
| Jtrain alto e Jcv ancora più alto | entrambi | prima il bias (modello più capace), poi la varianza (dati, λ) | — |
Le curve di apprendimento rendono la diagnosi visiva: Jtrain e Jcv in funzione del numero di esempi m. Con pochi esempi Jtrain è quasi zero (facile adattarsi a due punti) e Jcv altissimo; crescendo m, Jtrain sale e Jcv scende.
Con alto bias le due curve si avvicinano presto e si appiattiscono alte: aggiungere dati non serve. Con alta varianza resta un divario largo tra le due che si chiude lentamente: qui i dati servono eccome. Il laboratorio le calcola davvero, adattando un polinomio a dataset sempre più grandi.
Dati generati da una relazione curva più rumore. Per ogni m da 3 a 60 si adatta il modello scelto ai primi m esempi (media su più estrazioni) e si misurano Jtrain e Jcv su 200 esempi mai visti. La linea tratteggiata è il rumore irriducibile, la baseline.
Con le reti neurali il compromesso tra bias e varianza si allenta. Una rete grande, con una regolarizzazione scelta bene, fa almeno bene quanto una piccola: il costo è solo computazionale.
Da qui una ricetta: se Jtrain è alto rispetto alla baseline, rete più grande (più strati o più neuroni), finché Jtrain non scende; poi, se Jcv è alto, più dati, e si ricomincia. La regolarizzazione in una rete è la stessa tassa λ·Σw² del capitolo 6, applicata ai pesi di ogni strato (kernel_regularizer=L2(0.01) in TensorFlow).
Un progetto reale gira in un ciclo: si sceglie l'architettura (modello, dati, iperparametri), si addestra, si fanno le diagnosi (bias/varianza e analisi degli errori), si decide che cosa cambiare, e si ricomincia. L'analisi degli errori è il complemento umano della diagnosi numerica: si prendono 100 esempi del cross-validation set sbagliati dal modello, si leggono uno per uno e si contano per categoria.
Se su 100 spam non riconosciuti 21 sono farmaci, 18 sono email con errori di ortografia voluti, 7 phishing e 3 hanno il testo dentro un'immagine, è chiaro dove investire (farmaci, ortografia) e dove no (immagini: anche risolvendolo del tutto si guadagna il 3%).
Se gli errori sono migliaia se ne campiona un centinaio: l'analisi manuale è lenta ma è lo strumento che evita mesi di lavoro su problemi irrilevanti.
Aggiungere dati con criterio. Non «più dati di tutto», ma più dati del tipo che l'analisi degli errori indica.
Due strumenti: la data augmentation, cioè creare esempi nuovi trasformando quelli esistenti in modo realistico (ruotare, ingrandire, distorcere una lettera scritta a mano; aggiungere rumore di fondo a un audio: distorsioni che il modello incontrerà davvero, non rumore casuale sui pixel), e la sintesi, cioè generare dati artificiali da zero (testo reso con font diversi su sfondi fotografati, per il riconoscimento di scritte).
Negli ultimi anni l'attenzione si è spostata dal modello ai dati (data-centric AI): spesso il miglioramento più grande viene da dati migliori, non da un algoritmo diverso.
Transfer learning. Se di dati ne hai pochi, puoi partire da una rete addestrata da altri su un compito vicino con milioni di esempi (riconoscere oggetti generici) e riaddestrare solo l'ultimo strato sui tuoi dati, o tutta la rete con un learning rate piccolo (fine-tuning).
Funziona perché i primi strati hanno imparato feature generali (bordi, forme) che servono anche a te; richiede che l'input sia dello stesso tipo (immagini con immagini, audio con audio). È il modo in cui, su scala enorme, si addestrano gli assistenti linguistici a partire da un modello pre-addestrato.
Una malattia rara colpisce lo 0,5% dei pazienti. Un «modello» che dice sempre «sano» ha il 99,5% di accuratezza. Come si smaschera?
Con due misure che l'accuratezza nasconde. Dividendo le previsioni in veri positivi (TP), falsi positivi (FP), falsi negativi (FN) e veri negativi (TN), la precisione è TP/(TP+FP): di quelli che ho detto malati, quanti lo erano; il recall è TP/(TP+FN): dei malati veri, quanti ne ho trovati. Il modello «sempre sano» ha recall zero, e il trucco è scoperto.
Le due misure tirano in direzioni opposte, e la leva è la soglia sulla probabilità: alzarla sopra 0,5 (dire «malato» solo se molto sicuri) aumenta la precisione e abbassa il recall; abbassarla fa il contrario. La scelta dipende dai costi: una malattia curabile con un trattamento pesante chiede alta precisione; una malattia grave da non lasciar passare chiede alto recall.
Per confrontare modelli con un solo numero si usa l'F1, la media armonica di precisione e recall, che punisce i valori sbilanciati:
Calcolo · F1 con precisione 0,02 e recall 1
Duecento pazienti, dieci malati; per ciascuno il modello ha dato una probabilità. Muovi la soglia e guarda la matrice di confusione, precisione, recall e F1 ricalcolati sui dati.
Le probabilità dei pazienti sono fisse (generate con un seme): i malati tendono ad avere punteggi alti ma non tutti, e qualche sano ha punteggi alti. È la situazione reale: nessuna soglia è perfetta, e l'accuratezza da sola (riportata per confronto) resta alta anche con scelte pessime.
In sintesi · capitolo 9
Autodiagnosi
Devi riconoscere se un animale è un gatto da tre indizi: forma delle orecchie, forma del muso, presenza dei baffi. Quale domanda fai per prima?
Un albero di decisione è esattamente questo: un nodo radice con una domanda su una feature, rami per le risposte, altri nodi con altre domande, e in fondo le foglie con la previsione. Per classificare un esempio nuovo si parte dalla radice e si seguono le risposte.
Il problema dell'apprendimento è costruire l'albero dai dati: quale feature mettere in ogni nodo, e quando fermarsi. Il criterio è la purezza: la domanda migliore è quella che divide gli esempi in gruppi il più possibile omogenei (tutti gatti da una parte, nessun gatto dall'altra). Per misurarla serve un numero.
L'entropia misura il disordine di un gruppo. Se p₁ è la frazione di gatti nel gruppo:
Formula · entropia di un gruppo
Vale 0 se il gruppo è tutto gatti o tutto non-gatti (ordine perfetto), vale 1 (il massimo) se è metà e metà.
Calcolo · entropia di un gruppo di 6 esempi
Il logaritmo in base 2 fa sì che l'entropia si misuri in bit, ma la base non cambia quale split è migliore.
Una domanda divide il gruppo in due sottogruppi, sinistro e destro, ciascuno con la sua entropia. Il guadagno di informazione (information gain) è di quanto l'entropia scende grazie alla domanda:
Formula · guadagno di informazione
La media è pesata perché un sottogruppo purissimo ma con un solo esempio conta poco. L'algoritmo calcola il guadagno per ogni feature disponibile, sceglie la più alta, e ripete ricorsivamente su ciascun ramo con gli esempi che vi sono finiti, finché un criterio di arresto non lo ferma: il nodo è puro, l'albero ha raggiunto la profondità massima, il guadagno è sotto una soglia, o gli esempi nel nodo sono troppo pochi.
Questi criteri sono il controllo dell'overfitting: un albero lasciato crescere fino a foglie con un esempio ciascuna ha imparato il training set a memoria.
Dieci animali, tre feature binarie, cinque gatti. Scegli la feature per il nodo radice: vedi come si dividono gli esempi, le entropie dei due rami e il guadagno. Poi guarda quale avrebbe scelto l'algoritmo.
Se una feature ha tre valori (orecchie a punta, flosce, ovali) si può aprire un nodo a tre rami, oppure, più comodo per gli algoritmi che vogliono domande sì/no, trasformarla in tre feature binarie con il one-hot encoding: «a punta?», «flosce?», «ovali?», di cui una sola vale 1. Lo stesso trucco serve alle reti neurali, che vogliono numeri in ingresso.
Una feature continua (il peso dell'animale) diventa una domanda con soglia, «peso ≤ 9 kg?»: l'algoritmo prova come soglia tutti i punti a metà tra due valori consecutivi presenti nei dati e tiene quella con il guadagno più alto.
Infine un albero di regressione prevede un numero: le foglie contengono la media dei y degli esempi finiti lì, e al posto dell'entropia si usa la varianza dei y, scegliendo lo split che la riduce di più, con la stessa media pesata.
Un albero singolo è fragile: basta cambiare un esempio del training set perché la domanda alla radice cambi e con lei l'intero albero. Come lo si rende stabile?
Allenandone tanti, diversi, e facendoli votare: un ensemble. La diversità si ottiene con il campionamento con reinserimento: da m esempi se ne estraggono m a caso, rimettendo ogni volta l'estratto nell'urna, così alcuni compaiono due o tre volte e altri mai; ogni albero è allenato su un campione diverso. Con B alberi (64, 100, 128: oltre il guadagno si appiattisce) si ha il bagged decision tree.
La random forest aggiunge un secondo elemento di diversità: a ogni nodo l'albero può scegliere solo tra un sottoinsieme casuale di k feature, tipicamente k ≈ √n, così gli alberi non finiscono tutti per usare la stessa feature forte alla radice. Il voto di molti alberi un po' diversi riduce la varianza senza aumentare il bias.
Il boosting cambia il modo di scegliere i campioni: invece che a caso, dopo ogni albero si guardano gli esempi che l'ensemble sbaglia ancora e si rende più probabile estrarli per l'albero successivo, che quindi si concentra sui casi difficili, come uno studente che ripassa solo gli esercizi sbagliati.
XGBoost (eXtreme Gradient Boosting) è l'implementazione che domina le competizioni su dati tabellari: veloce, con regolarizzazione e criteri di arresto buoni per default, e un'interfaccia di due righe (XGBClassifier().fit(X, y)).
Gli alberi (e XGBoost in particolare) vincono sui dati tabellari, quelli che stanno in una tabella con colonne eterogenee: sono veloci da addestrare, poco sensibili alla scala delle feature, e un albero piccolo si può leggere. Le reti vincono sui dati non strutturati (immagini, audio, testo), dove le feature vanno inventate dal modello, si combinano bene tra loro e con il transfer learning, ma sono lente da addestrare e difficili da interpretare. Nel dubbio su una tabella, prima XGBoost.
In sintesi · capitolo 10
Autodiagnosi
Parte III
Hai le misure di mille clienti (quanto spendono, quanto spesso) e vuoi dividerli in tre gruppi per tre campagne. Nessuno ti ha detto quali sono i gruppi: come li trovi?
Con il primo algoritmo non supervisionato della guida, k-means. L'idea è di una semplicità disarmante, e si capisce guardandola girare. Si scelgono K punti a caso, i centroidi, uno per gruppo.
Poi si ripetono due mosse. Assegna: ogni esempio viene assegnato al centroide più vicino. Sposta: ogni centroide si trasferisce nel baricentro (la media) degli esempi che gli sono stati assegnati. Si ripete finché i centroidi smettono di muoversi.
Dopo poche iterazioni i centroidi si sono sistemati nel mezzo dei gruppi, anche se all'inizio erano in posizioni assurde; e funziona anche quando i gruppi non sono ben separati (le taglie di una maglietta su un continuo di altezze e pesi: S, M, L sono un k-means con K = 3).
Dietro le due mosse c'è una funzione di costo, come sempre: la distorsione, la media delle distanze al quadrato tra ogni esempio e il centroide a cui è assegnato:
Formula · la distorsione
La mossa «assegna» la minimizza rispetto alle assegnazioni tenendo fermi i centroidi; la mossa «sposta» la minimizza rispetto ai centroidi tenendo ferme le assegnazioni. Per questo J non può mai salire da un'iterazione all'altra, e se sale c'è un errore nel codice. Se un centroide resta senza punti, lo si elimina o lo si reinizializza.
Il risultato dipende da dove partono i centroidi: inizializzazioni diverse portano a minimi locali diversi, alcuni chiaramente peggiori (due centroidi che si dividono un gruppo e un terzo che ne copre due). La pratica standard: scegliere i centroidi iniziali tra gli esempi stessi (K punti a caso del dataset), eseguire k-means 50–1.000 volte con inizializzazioni diverse, e tenere la soluzione con la distorsione J più bassa. Costa poco e toglie quasi sempre i minimi cattivi.
Non c'è una risposta «giusta» dai dati: i dati non dicono quanti cluster hanno, e J scende sempre aumentando K (con K = m ogni punto è il proprio centroide e J = 0), quindi minimizzare J su K non ha senso.
Il metodo del gomito disegna J in funzione di K e cerca il punto in cui smette di scendere in fretta; spesso il gomito non c'è. Il criterio migliore è lo scopo a valle: tre taglie o cinque? Dipende da quanto costa produrne cinque e quanto meglio vestono. K si sceglie per come verranno usati i cluster, non per una statistica.
Novanta punti in tre gruppi (non etichettati). Scegli K, poi alterna «Assegna» e «Sposta» e guarda i centroidi (le croci) trovare i gruppi; la distorsione J non sale mai. «Nuova partenza» pesca altri centroidi iniziali: a volte si finisce in un minimo peggiore.
In sintesi · capitolo 11
Autodiagnosi
Hai i dati di mille motori d'aereo appena prodotti: vibrazione, temperatura, consumo. Quasi tutti sono buoni; non sai quali no, e i difettosi sono pochi e tutti diversi. Come li segnali?
Non con la classificazione: per addestrare un classificatore servirebbero molti esempi di difetti, e di ogni tipo di difetto, mentre i difetti futuri saranno diversi da quelli passati.
Il rilevamento di anomalie rovescia il problema: impara com'è fatto un motore normale, con un modello di probabilità p(x) stimato sugli esempi, e segnala come anomalo tutto ciò che il modello ritiene molto improbabile, p(x) < ε. Non serve sapere che forma avrà il prossimo difetto: basta che non somigli al normale.
Lo stesso schema vale per le frodi (comportamenti anomali di un account), il monitoraggio dei server, i controlli di qualità.
Il modello più semplice per «com'è fatto il normale» è la distribuzione gaussiana (o normale), la campana: ha un centro μ (la media) e una larghezza σ (la deviazione standard), e la probabilità di un valore x decresce con il quadrato della sua distanza dal centro, misurata in σ:
Formula · la gaussiana
Stimare μ e σ dai dati è un calcolo diretto: μ è la media dei valori, σ² la media dei quadrati degli scarti. Con n feature si stima una gaussiana per ciascuna e si moltiplicano:
Formula · più feature
È un'ipotesi di indipendenza che quasi mai è vera, e che in pratica funziona lo stesso: un motore con vibrazione un po' alta e temperatura un po' alta ha una p piccola per effetto del prodotto, anche se nessuna delle due da sola sarebbe anomala.
Scegliere ε, e valutare. Anche se il training è non supervisionato (solo esempi normali), per scegliere la soglia servono alcuni esempi anomali etichettati, pochi: si mettono nel cross-validation set insieme a molti normali, si prova una gamma di ε e si tiene quello con la F1 migliore (le anomalie sono rare: precisione e recall, non accuratezza, come nel capitolo 9). Il test set, se i difetti bastano, funziona uguale.
Una regola di massima sulla scelta tra anomalie e classificazione: con pochissimi positivi (0–20) e difetti di tipo sempre nuovo, rilevamento di anomalie; con molti positivi e difetti che si ripetono, un classificatore supervisionato.
Le feature contano più dell'algoritmo. Se una feature ha una distribuzione molto lontana dalla campana (una coda lunga a destra), una trasformazione la rende più gaussiana: log(x), log(x + c), √x, x1/3; si prova a occhio con l'istogramma.
E quando un'anomalia sfugge perché le sue feature prese una per una sono normali, si costruisce una feature che la cattura: un server con CPU normale e traffico normale ma CPU/traffico altissimo (un processo bloccato in un ciclo) si vede solo con il rapporto. È lo stesso lavoro del capitolo 4, con lo stesso effetto decisivo.
Trecento misure di vibrazione di motori buoni, più sette motori etichettati come difettosi (in rosso) usati solo per scegliere la soglia. La campana è stimata dai dati; sposta ε e guarda quali punti finiscono sotto la soglia, e come cambiano precisione e recall sui sette difettosi.
In sintesi · capitolo 12
Autodiagnosi
Hai una tabella utenti × film con i voti da 0 a 5, piena di buchi. Come riempi i buchi, cioè come prevedi quanto piacerebbe a Alice un film che non ha mai visto?
Se dei film conoscessi delle feature (quanto è romantico, quanto è d'azione), il problema sarebbe una regressione lineare per ogni utente: i parametri w(j), b(j) di Alice si imparano dai voti che ha dato, e il voto previsto per il film i è w(j)·x(i) + b(j). Il costo è l'errore quadratico sui soli voti esistenti (r(i,j) = 1), con la regolarizzazione.
Ma le feature dei film, nella realtà, nessuno le ha scritte. E qui c'è un'idea bella: se conoscessi i gusti degli utenti (i loro w), potresti dedurre le feature di ogni film dai voti ricevuti, con la stessa regressione al contrario.
Non conosciamo né gli uni né le altre: il filtro collaborativo (collaborative filtering) li impara insieme, minimizzando un unico costo rispetto a tutti i w(j), b(j) e x(i) contemporaneamente, con la discesa del gradiente. Funziona perché gli utenti collaborano senza saperlo: i voti di molti su un film lo descrivono, e i voti di uno su molti film lo descrivono.
Voti binari. Spesso non ci sono stelle ma segnali sì/no: ha cliccato, ha comprato, ha guardato fino in fondo. Lo stesso modello passa da regressione a classificazione come nel capitolo 5: la previsione diventa g(w·x + b) con la sigmoide e il costo diventa la perdita logistica.
Un utente nuovo, senza voti, finirebbe con w = 0 e previsioni tutte a zero stelle; sottraendo da ogni film la sua media dei voti prima del training, e risommandola dopo, le previsioni per chi non ha votato nulla diventano «la media degli altri», che è ragionevole, e la discesa del gradiente converge meglio.
In TensorFlow il costo si scrive in poche righe e le derivate le calcola tf.GradientTape: è la discesa del gradiente «a mano», senza strati Dense, perché il modello non è una rete.
Il filtro collaborativo usa solo i voti. Ma di utenti e film si sa altro: età, paese, genere; anno, attori, durata. Il filtro basato sul contenuto usa due reti neurali: una trasforma le feature dell'utente in un vettore vu, l'altra le feature del film in un vettore vm della stessa lunghezza, e il voto previsto è il prodotto scalare vu·vm. Le due reti si addestrano insieme sui voti esistenti: è la stessa architettura «a due torri» che si usa per il retrieval nei sistemi di ricerca.
Il vantaggio sul collaborativo è che funziona anche per un film nuovo senza voti (ha pure lui le feature) e per un utente nuovo.
Con milioni di film non si calcola il prodotto per tutti a ogni richiesta: si fa retrieval e ranking, prima si pescano poche centinaia di candidati plausibili (i film simili agli ultimi visti, i più popolari nel genere preferito, con i vm precalcolati), poi si ordinano solo quelli con il modello completo.
Un sistema di raccomandazione ottimizza ciò che gli si chiede: se gli si chiede «massimizza il tempo di permanenza» imparerà a proporre contenuti che arrabbiano o danno dipendenza; se «massimizza il profitto per l'azienda» proporrà i prodotti con il margine più alto, non i migliori per l'utente. Decidere la funzione obiettivo è una scelta che riguarda le persone che useranno il sistema, e va dichiarata.
PCA, per guardare i dati. Chiude il capitolo l'analisi delle componenti principali (Principal Component Analysis): dato un dataset con 50 feature, trova i pochi assi (2 o 3) lungo i quali i dati variano di più e li proietta su quelli, perdendo meno informazione possibile. Serve soprattutto per visualizzare: un grafico a due dimensioni di dati che ne avevano cinquanta.
Non è un passo di pre-elaborazione raccomandato per il training (le reti e gli alberi gestiscono bene molte feature), ma è lo strumento per vedere se i cluster del capitolo 11 esistono davvero.
Quattro utenti, cinque film, voti da 0 a 5 con i buchi (—). Il modello impara insieme i gusti w(j) di ogni utente e le feature x(i) di ogni film (due numeri per film) con la discesa del gradiente sul costo qui sopra. Premi «Allena» e guarda i buchi riempirsi; i voti esistenti vengono ricostruiti, quelli mancanti previsti.
Con così pochi voti il modello ha molta libertà: la regolarizzazione (λ = 0,1) lo tiene a bada. Le feature dei film imparate sono due numeri senza nome: guarda nella tabella quali film finiscono con feature simili, e confrontalo con chi li ha votati allo stesso modo.
In sintesi · capitolo 13
Autodiagnosi
Devi far atterrare un elicottero autonomo. Non esiste un dataset di «stato → comando giusto»: nessuno sa scriverlo. Che cosa si può dare al programma, allora?
Una ricompensa (reward): un numero che dice quanto bene sta andando. Vola bene, +1; si schianta, −1000.
È come addestrare un cane: non gli si spiega il movimento, gli si dice «bravo» o «no», e lui scopre che cosa fare.
Nell'apprendimento per rinforzo (reinforcement learning, RL) un agente si trova in uno stato s, sceglie un'azione a, finisce in un nuovo stato s′ e riceve una ricompensa R; il compito è imparare una policy π, una regola che a ogni stato associa l'azione da fare, tale da massimizzare la ricompensa accumulata.
La definizione si applica a tutto: robot, giochi (le partite a scacchi e Go dei programmi campioni), controllo industriale, trading, e, su scala enorme, l'ultima fase di addestramento degli assistenti linguistici.
L'esempio del corso è un rover su una striscia di sei caselle. Nella casella 1 c'è una roccia molto interessante (ricompensa 100), nella 6 una meno interessante (40), nelle altre nulla; il rover parte dalla 4 e a ogni passo può andare a sinistra o a destra; raggiunta una delle due estremità la missione finisce (stato terminale). Che cosa deve fare? La roccia da 100 è lontana tre passi, quella da 40 due.
Per rispondere serve il ritorno (return): la somma delle ricompense future, ma con le più lontane che valgono meno:
Formula · il ritorno
Dove γ (gamma) è il fattore di sconto, un numero appena sotto 1 (0,9, 0,99). Il ritorno dice che un euro domani vale meno di un euro oggi, e rende l'agente un po' impaziente:
Calcolo · il rover dalla casella 4, con sconto 0,5
Conviene la roccia grande. Con γ più piccolo vince la roccia vicina. Lo sconto serve anche a far convergere la somma quando il compito non ha fine, e con ricompense negative spinge a rimandare il male il più possibile.
Lo strumento centrale è la funzione stato-azione Q(s, a): il ritorno che si ottiene partendo dallo stato s, facendo una volta l'azione a, e poi comportandosi nel modo migliore possibile. Se conosci Q, la policy ottima è immediata: in ogni stato scegli l'azione con Q più alta, π(s) = argmaxa Q(s, a). Nel rover, Q(4, sinistra) = 12,5 e Q(4, destra) = 10 → sinistra.
Come si calcola Q senza enumerare tutti i futuri? Con l'equazione di Bellman, che è una definizione ricorsiva:
Formula · l'equazione di Bellman
Negli stati terminali Q(s, a) = R(s). L'equazione lega il valore di ogni stato a quello dei suoi vicini; applicarla ripetutamente (si parte da Q = 0 e si aggiorna ogni cella con la formula finché i valori non cambiano più) fa convergere Q al valore vero. Il laboratorio lo fa sotto i tuoi occhi.
Ambienti stocastici. Il rover reale, comandato «sinistra», va a sinistra con probabilità 0,9 e scivola a destra con 0,1. Allora la ricompensa accumulata è casuale, l'obiettivo diventa il ritorno atteso (la media su tanti tentativi) e Bellman prende una media sui possibili s′:
Formula · Bellman con la media
Il resto non cambia.
Sei caselle, ricompense 100 a sinistra e 40 a destra. Ogni pressione di «Un passo di Bellman» ricalcola Q(s, sinistra) e Q(s, destra) per tutte le caselle con l'equazione; le frecce indicano la policy ottima corrente (argmax). Cambia γ e guarda dove cambia la decisione del rover.
Un modulo lunare non ha sei caselle: ha posizione, velocità, angolo, velocità angolare, tutti numeri reali. Dove si mette la tabella Q?
Da nessuna parte: si sostituisce con una rete neurale che, dato lo stato s (il vettore dei numeri) e un'azione, stima Q(s, a); in pratica la rete riceve s e produce quattro uscite, una per azione (niente, sinistra, motore principale, destra). È il Deep Q-Network (DQN).
L'addestramento usa Bellman come fonte di esempi: si fa agire il lander (anche male), si registrano le quadruple (s, a, R, s′), e per ciascuna si costruisce un esempio di training con input s e target y = R + γ·maxa′ Q(s′, a′), dove Q sul lato destro è calcolata dalla rete attuale. Si addestra la rete su questi esempi con l'errore quadratico, si aggiorna, si ripete. È una regressione in cui i target cambiano man mano che la rete migliora, e converge verso la Q vera.
Tre accorgimenti rendono la ricetta stabile. L'ε-greedy: durante la raccolta, con probabilità 1 − ε si fa l'azione che la rete attuale ritiene migliore, con probabilità ε (per esempio 0,05, o alta all'inizio e decrescente) un'azione a caso, altrimenti la rete non scopre mai che un'azione che oggi stima male è in realtà buona (è l'esplorazione, lo stesso dilemma degli assistenti linguistici addestrati con RL).
Il replay buffer con i mini-batch: si tengono le ultime 10.000 quadruple e a ogni passo si addestra su un campione di 1.000, non su tutto, per velocità.
Gli aggiornamenti morbidi (soft update): invece di sostituire la rete di colpo con quella appena addestrata, la si muove di poco verso di essa:
Formula · aggiornamento morbido
Così i target non saltano a ogni passo.
L'RL è l'area più promettente e meno matura del corso: funziona benissimo nei simulatori e nei giochi, molto meno nel mondo fisico (robot veri, con sensori rumorosi e conseguenze costose), e il divario simulazione-realtà è un problema di ricerca aperto. Nelle applicazioni industriali di oggi l'apprendimento supervisionato e quello non supervisionato pesano molto di più. Ma le idee, ritorno, policy, esplorazione, sono quelle che oggi insegnano ai modelli linguistici a ragionare.
In sintesi · capitolo 14
Autodiagnosi
Ogni algoritmo della guida risponde alle stesse tre domande: che cosa c'è nei dati, che forma ha il modello e che cosa minimizza. Le tre colonne centrali della tabella sono il motivo per cui, imparata la regressione lineare, il resto si riconosce.
| Algoritmo | Dati | Modello | Costo / criterio | Come si ottimizza | Quando usarlo |
|---|---|---|---|---|---|
| Regressione lineare | x, y numero | w·x + b | errore quadratico medio | discesa del gradiente | prevedere una quantità |
| Regressione logistica | x, y ∈ {0,1} | g(w·x + b) | perdita logistica | discesa del gradiente | sì/no, con probabilità |
| Rete neurale | x, y | strati di neuroni, ReLU, softmax | cross-entropy / quadratico | backpropagation + Adam | immagini, audio, testo; feature da imparare |
| Alberi, random forest, XGBoost | x tabellari, y | domande sì/no in sequenza, votate | entropia / varianza | scelta greedy dello split | dati tabellari |
| k-means | solo x | K centroidi | distorsione | assegna / sposta | segmentare, raggruppare |
| Rilevamento di anomalie | x normali (+ poche anomalie per ε) | gaussiana per feature | p(x) < ε, F1 per ε | stima di μ, σ | difetti rari e sempre nuovi |
| Filtro collaborativo | voti utente × item con buchi | w(j)·x(i) + b(j) | quadratico sui voti presenti + λ | discesa del gradiente su w e x insieme | raccomandare |
| Apprendimento per rinforzo | stati, azioni, ricompense | Q(s, a), tabella o rete | Bellman: R + γ·max Q | iterazione di Bellman / DQN | decidere in sequenza |
Il corso si chiude con un consiglio che vale più degli algoritmi: la maggior parte dei progetti fallisce non perché manca il modello giusto ma perché manca la diagnosi (capitolo 9). Prima di cambiare algoritmo, misura dove sbagli e perché.