Reti neurali e neuroevoluzione · guida interattiva

Un'auto che impara a guidare senza che nessuno le insegni: sensori, una rete di 82 numeri e la selezione naturale

Nel video Neural Network Driving un'auto gira su una pista mentre, sotto, un grafo di nodi e archi si accende a ogni curva: è una rete neurale che legge sette distanze e decide sterzo e gas. Nessuno le ha mostrato come si guida. Cento auto con pesi casuali provano, quasi tutte sbattono, le poche che vanno più lontano si riproducono con qualche errore di copia, e dopo qualche decina di generazioni una completa il giro. Questa guida smonta il progetto nei suoi pezzi, dal modello fisico dell'auto al ciclo evolutivo, e ti fa toccare ciascuno: alla fine c'è una pista vera in cui far evolvere una popolazione nel browser.

1 · Il mondo: pista, auto, collisioni

Prima della rete serve un mondo in cui guidare, e deve dare lo stesso risultato ogni volta che lo fai ripartire

Il progetto ha tre blocchi e un ciclo che gira a ogni frame: il mondo produce le letture dei sensori, la rete le trasforma in comandi, i comandi muovono l'auto nel mondo. L'addestramento sta fuori da questo ciclo: non guida mai, sceglie soltanto quali pesi mettere nella rete.

Conviene costruire i blocchi in quest'ordine, perché ognuno produce qualcosa di visibile prima del successivo, e il primo è il mondo.

mondo pista, auto, sensori → 8 numeri in [0, 1]
↓ ogni frame
rete 82 parametri (72 pesi e 10 bias): 8 → 6 → 4
↓
comandi sterzo, gas, freno, freno a mano → nuova posizione
↺ torna al mondo · fuori dal ciclo: l'evoluzione sceglie i pesi

Il game loop: separare il tempo della fisica da quello del disegno

Un gioco ha due ritmi. Il disegno va alla frequenza dello schermo, 60 volte al secondo se va bene, meno se il computer è lento. La fisica non può dipendere da questo: se il passo di integrazione cambiasse con il frame rate, la stessa auto con gli stessi comandi farebbe traiettorie diverse su due computer, e confrontare due auto diventerebbe impossibile. Perciò update(dt) gira con un dt fisso (per esempio 1/60 s) tante volte quante servono, e render() disegna lo stato quando può.

Il vantaggio non è solo la riproducibilità: in addestramento si possono fare cento passi di fisica per ogni frame disegnato, o non disegnare affatto, e la simulazione va cento volte più veloce del tempo reale.

La pista come geometria

La pista è una linea centrale (una polilinea o una spline chiusa) più una larghezza: spostando ogni punto della centrale verso l'interno e verso l'esterno di metà larghezza si ottengono i due bordi, come sequenze di segmenti. Tutto ciò che il mondo deve sapere sulla pista sta in questi segmenti: contro di essi si calcolano le collisioni e le distanze dei sensori.

Lungo la centrale si mettono dei checkpoint ordinati: sono il metro con cui, nella sezione 4, si misurerà quanto un'auto è arrivata lontano. I cordoli bianco-rossi nelle curve sono solo decorazione.

La fisica dell'auto: il modello a bicicletta

Non serve una simulazione realistica delle quattro ruote. Il modello cinematico a bicicletta riduce l'auto a due ruote in linea, distanti L (il passo): la posteriore spinge, l'anteriore sterza di un angolo δ. Lo stato è posizione, orientamento (heading) e velocità.

A ogni passo l'auto avanza di v·dt nella direzione dell'heading, e l'heading ruota di (v / L)·tan(δ)·dt: a parità di sterzo il raggio della curva resta lo stesso, ma più vai veloce più in fretta ruoti, e da ferma l'auto non ruota affatto.

Calcolo · un passo del modello a bicicletta

avanzamento=v · dt
nella direzione dell'heading
rotazione dell'heading=v L · tan δ · dt
L è il passo; da ferma (v = 0) la rotazione è nulla

A parità di sterzo δ il raggio resta lo stesso, ma la rotazione per passo cresce con v.

Gas e freno cambiano v; un attrito la riduce ogni passo. Il freno a mano riduce l'aderenza posteriore: l'heading continua a ruotare mentre la velocità si conserva più a lungo nella direzione vecchia, e l'auto derapa lasciando le scie.

Laboratorio: sterzo, velocità e raggio di curva

matematica reale

Muovi sterzo e velocità: la traiettoria dei prossimi due secondi cambia forma, e il raggio della curva segue una formula che vale la pena conoscere.

raggio di curva R = L / tan δ
velocità angolare v / R
gradi percorsi in 2 s

Modello cinematico puro: niente massa, niente slittamento. È esattamente l'approssimazione usata nel progetto; il freno a mano si aggiunge sopra come riduzione dell'aderenza.

Collisioni: toccare il bordo è la morte

L'auto è un rettangolo (o, per semplicità, un cerchio) e a ogni passo si controlla se interseca uno dei segmenti del bordo. Se sì, l'auto è eliminata: si ferma dov'è, non consuma più calcolo e la sua corsa è finita. Nella sezione 4 questa è la «morte» che l'evoluzione impara a evitare: un'auto che sbatte presto ha percorso poca pista, e la sua fitness è bassa.

Da ricordare

Un dettaglio che semplifica tutto: le auto della popolazione non collidono tra loro. Sono copie della stessa pista sovrapposte, una per auto, non tante auto nella stessa gara.

Mappa concettuale · il mondo: pista, auto, collisioni
  • Il mondo della simulazione
    • gira con Game loop a passo fisso
      • garantisce stessa traiettoria su ogni computer
      • permette cento passi di fisica per frame disegnato
    • descrive la pista con Linea centrale + larghezza
      • produce due bordi, come segmenti
      • porta dei checkpoint ordinati misurano la fitness, sezione 4
    • muove l'auto con Modello a bicicletta
      • ha per stato posizione, heading, velocità
      • fa ruotare di più chi va più veloce
    • controlla le Collisioni
      • se tocca il bordo auto eliminata
      • tra auto nessuna collisione
Passo fisso
La fisica avanza sempre dello stesso dt, indipendentemente dal frame rate: stessi comandi, stessa traiettoria, su qualunque computer.
update(1/60) × n, poi render()
Linea centrale
Polilinea chiusa che definisce la pista; i bordi sono la centrale spostata di metà larghezza da ogni lato.
bordo = centrale ± w/2 · normale
Modello a bicicletta
Auto ridotta a due ruote in linea: la posteriore spinge, l'anteriore sterza; il raggio dipende solo da sterzo e passo, la velocità di rotazione cresce con velocità e sterzo.
θ̇ = (v/L)·tan δ
Checkpoint
Punti ordinati lungo la centrale; contarli è il modo di misurare il progresso di un'auto.
fitness = superati + frazione
2 · I sensori

La rete non vede la pista: vede sette numeri tra zero e uno

Una rete neurale non può ricevere «la pista»: riceve un vettore di numeri. Il modo più semplice di darle degli occhi è il raycasting: dal muso dell'auto partono sette raggi, a −65°, −20°, −10°, 0°, +10°, +20° e +65° rispetto alla direzione di marcia, e per ciascuno si misura la distanza al primo bordo incontrato.

Sette raggi sono pochi ma bastano: i due a ±65° dicono quanto spazio c'è ai lati, quello a 0° quanto è lontana la curva, quelli intermedi da che parte gira.

La distanza si trova con un'intersezione segmento-segmento: il raggio è un segmento dal muso alla portata massima dmax, e lo si confronta con ogni segmento del bordo tenendo l'intersezione più vicina.

Poi si normalizza: la lettura è:

Calcolo · la lettura normalizzata di un raggio

si=min(di dmax, 1)
un numero tra 0 (muro addosso) e 1 (nessun bordo entro la portata)

L'ottavo input è la velocità in avanti, anch'essa divisa per la velocità massima. Normalizzare non è un vezzo: se un input valesse 300 e un altro 0,4, i pesi della rete dovrebbero compensare scale diverse, e l'evoluzione farebbe molta più fatica a trovarli.

Laboratorio: i sette raggi

matematica reale

Sposta e ruota l'auto nel tratto di pista: i raggi si accorciano dove trovano il bordo e le otto letture in basso sono esattamente il vettore che entra nella rete.

Portata massima 160 px. L'intersezione è quella reale tra il raggio e i segmenti dei due bordi; la pista è un tratto curvo costruito con lo stesso metodo della sezione 1 (centrale + larghezza).

Mappa concettuale · i sensori
  • Gli otto input della rete
    • sette arrivano dal Raycasting
      • lancia 7 raggi da −65° a +65°
      • misura distanza al primo bordo
      • si trova con intersezione segmento-segmento
    • poi li porta in [0, 1] con la Normalizzazione
      • si calcola come min(d / d_max, 1)
      • evita pesi che compensano scale diverse
    • l'ottavo è la Velocità in avanti
      • si divide per la velocità massima
    • vanno in ingresso alla Rete neurale sezione 3
Raycasting
Lanciare un raggio da un punto in una direzione e misurare la distanza al primo ostacolo.
7 raggi da −65° a +65°
Intersezione segmento-segmento
Formula chiusa che dice se e dove due segmenti si incrociano; per ogni raggio si tiene il bordo più vicino.
parametri t, u in [0, 1]
Normalizzazione
Portare ogni input in [0, 1] dividendo per il massimo possibile, così i pesi non devono compensare scale diverse.
s = min(d / d_max, 1)
VelF
Velocità in avanti normalizzata, ottavo input: senza, la rete non saprebbe se frenare.
v / v_max
3 · La rete neurale

Una funzione pura: otto numeri entrano, quattro comandi escono, e tutto il sapere sta in 82 numeri

Spogliata della mistica, la rete di questo progetto è una funzione: prende il vettore degli otto input della sezione 2 e restituisce quattro numeri, da interpretare come comandi. Non ha memoria (è feed-forward: i segnali vanno solo in avanti) e non impara mentre guida. Tutto ciò che «sa» è scritto nei suoi pesi, e cambiare i pesi cambia il modo di guidare. È per questo che l'addestramento può stare fuori dal ciclo di gioco: il suo unico compito è trovare 82 numeri buoni.

Il singolo neurone è un rilevatore

Un neurone fa una somma pesata dei suoi input, aggiunge un bias e passa il risultato in una funzione di attivazione.

L'intuizione

L'intuizione che aiuta: un neurone è un piccolo rilevatore. Se i pesi verso i raggi di sinistra sono negativi e grandi, il neurone si accende quando quei raggi leggono valori bassi, cioè quando il muro è vicino a sinistra. Sei neuroni nascosti sono sei rilevatori del genere, e i quattro neuroni di uscita combinano i rilevatori in comandi: «muro vicino a sinistra» e «curva lontana davanti» diventano «sterza un po' a destra, tieni il gas».

Il forward pass e le attivazioni

Il calcolo procede strato per strato: si moltiplica la matrice dei pesi W per il vettore degli input, si somma il vettore dei bias b, si applica l'attivazione σ elemento per elemento, e il risultato è l'input dello strato successivo.

Per lo strato nascosto e per l'uscita si usa la tanh (tangente iperbolica), che schiaccia qualunque numero in [−1, 1]: è l'intervallo naturale per uno sterzo, dove −0,98 significa «quasi tutto a sinistra» e 0 «dritto». Negli strati nascosti va bene anche la ReLU (Rectified Linear Unit, max(0, x)).

Implementarlo a mano con due cicli e un Float32Array, senza librerie, è la parte più didattica del progetto: la formula sotto diventa una decina di righe. Lo stesso calcolo, con la sigmoide e la ReLU al posto della tanh e una rete che impara con il gradiente invece che per evoluzione, è nel capitolo 7 della guida sul machine learning.

Calcolo · il forward pass della rete 8 → 6 → 4

h=tanh(W1 · x + b1)
strato nascosto: sei neuroni
y=tanh(W2 · h + b2)
uscita: quattro comandi

x ∈ ℝ8 · h ∈ ℝ6 · y ∈ ℝ4

La topologia: 8 → 6 → 4, e perché si contano i parametri

Il video mostra una rete con circa cinque strati nascosti, per effetto scenico. Per girare in pista ne basta uno con sei neuroni, e la scelta ha due vantaggi: ognuno dei sei neuroni resta leggibile nel grafo della UI (User Interface, interfaccia utente), e il numero di parametri resta piccolo. Contarli è un'abitudine da prendere subito:

Calcolo · i parametri della rete

primo strato=8 × 6 + 6 = 54
48 pesi più 6 bias
secondo strato=6 × 4 + 4 = 28
24 pesi più 4 bias
totale=54 + 28 = 82
cioè (8 × 6 + 6) + (6 × 4 + 4)

Cioè pesi più bias di ogni strato.

Ottantadue numeri da trovare sono un problema che l'evoluzione risolve in minuti; con cinque strati da trenta neuroni sarebbero migliaia, e servirebbero ore.

Dagli output ai comandi, e i pesi come genoma

I quattro output vanno interpretati. Lo sterzo è continuo: il valore in [−1, 1] moltiplicato per l'angolo massimo. Gas, freno e freno a mano sono attivi se il rispettivo output supera una soglia, per esempio 0. È una convenzione arbitraria, e va scritta una volta sola nel codice che traduce output in comandi, così che rete ed evoluzione non la conoscano.

L'ultimo passo prepara la sezione 4: tutti i pesi e i bias vengono appiattiti in un unico vettore di 82 numeri, il genoma. L'evoluzione lavorerà su quel vettore, senza sapere che dentro c'è una rete; la rete lo rilegge nella sua forma a matrici quando deve calcolare.

Laboratorio: la rete che guida, con i suoi pesi

matematica reale

Gli otto input sono quelli del laboratorio dei sensori (modificali lì e torna qui). Genera pesi casuali e osserva come cambiano attivazioni e comandi; il grafo è quello del video: archi verdi per pesi positivi, rossi per negativi, spessore proporzionale al modulo, nodi riempiti in base all'attivazione.

Steering
Acceleration
Brake
Handbrake
parametri82

Forward pass reale su una rete 8 → 6 → 4 con tanh, pesi iniziali uniformi in [−1, 1]. Con pesi casuali i comandi sono arbitrari: è esattamente lo stato di un'auto della generazione 0.

Mappa concettuale · la rete neurale
  • La rete 8 → 6 → 4
    • è fatta di Neuroni
      • fanno somma pesata + bias
      • poi applicano una funzione di attivazione
      • sono come rilevatori
    • calcola con il Forward pass
      • strato per strato W · x + b, poi attivazione
      • usa tanh, numeri in [−1, 1]
    • si misura con 82 parametri
      • sono pesi più bias di ogni strato
      • pochi, quindi neuroni leggibili, ricerca in minuti
    • produce i Comandi
      • sterzo continuo, in [−1, 1]
      • gas e freni attivi oltre una soglia
    • appiattita diventa il Genoma
      • è un vettore di 82 numeri
      • lo evolve l'addestramento sezione 4
Feed-forward
Rete in cui i segnali vanno solo dall'input all'output, senza cicli né memoria: a input uguali, output uguali.
funzione pura di 8 numeri
Bias
Termine costante sommato alla somma pesata; sposta la soglia a cui il neurone si accende.
W·x + b
tanh
Tangente iperbolica: schiaccia qualunque numero in [−1, 1]; l'intervallo naturale di uno sterzo.
tanh(3) ≈ 0.995
Forward pass
Propagazione in avanti: matrice × vettore, più bias, attivazione, strato dopo strato.
h = σ(W h_prev + b)
Genoma
Tutti i pesi e i bias appiattiti in un unico vettore; l'unità su cui lavora l'evoluzione.
Float32Array(82)
4 · L'addestramento: neuroevoluzione

Non esiste un dataset di guida corretta: molte auto provano, le migliori si riproducono con errori di copia

Il modo standard di addestrare una rete, l'apprendimento supervisionato con la discesa del gradiente, ha bisogno di due cose che qui mancano. Servirebbero esempi di «input → output giusto», e nessuno ha un archivio di letture dei sensori con lo sterzo corretto accanto. E servirebbe un errore derivabile rispetto ai pesi; invece l'unico segnale disponibile è «quanto sei arrivato lontano», arriva alla fine della corsa e dipende dai pesi attraverso centinaia di passi di fisica e collisioni: non c'è un gradiente da seguire.

L'apprendimento per rinforzo (RL, Reinforcement Learning) risolve questo con tecniche come PPO (Proximal Policy Optimization); la neuroevoluzione lo risolve rinunciando al gradiente del tutto, e per un problema con 82 parametri è più semplice e spesso altrettanto efficace.

Come funziona la discesa a cui qui si rinuncia, passo per passo su una retta, lo mostra il capitolo 3 della guida sul machine learning.

La fitness: un numero per auto, e il modo in cui viene imbrogliato

Una popolazione di N auto (da 50 a 200) parte con genomi casuali, la generazione 0. Ogni auto guida finché non sbatte o non scade il tempo, e alla fine riceve una fitness: i checkpoint superati più la frazione di strada verso il successivo. È il numero che dice «questa guida meglio di quella». Serve un timeout di mancato progresso, altrimenti un'auto ferma o che gira in tondo resta in gara fino alla scadenza, sprecando calcolo senza fare progressi.

Diffida della tua metrica

E serve diffidare della propria metrica: se la fitness premiasse la distanza percorsa invece dei checkpoint, un'auto che fa avanti e indietro nel primo rettilineo vincerebbe. È il reward hacking: la popolazione ottimizza esattamente ciò che misuri, non ciò che volevi.

Selezione, crossover, mutazione

Finita la valutazione, si ordina per fitness e si costruisce la generazione successiva. L'elitismo copia intatti i migliori (per esempio i primi 2 o 3), così il campione non si perde mai. Gli altri posti si riempiono con figli: due genitori scelti con un torneo (pesca k a caso, tieni il migliore) o con la roulette (probabilità proporzionale alla fitness), combinati per crossover uniforme (ogni gene preso a caso da uno dei due) o a un punto (prima parte da A, resto da B), e infine mutati: a ogni gene, con probabilità p, si somma rumore gaussiano di ampiezza (deviazione standard) σ.

Tre ruoli

La mutazione è la sorgente di novità, il crossover ricombina ciò che già funziona, la selezione decide che cosa sopravvive. Ridurre σ col passare delle generazioni fa esplorare all'inizio e rifinire alla fine.

Laboratorio: crossover e mutazione su due genomi

matematica reale

Due genitori di 82 geni. Scegli il tipo di crossover, la probabilità e l'ampiezza della mutazione, e genera un figlio: ogni cella dice da chi viene e se è stata mutata.

gene dal genitore Agene dal genitore Bmutato
geni da A / da B
geni mutati
distanza dal genitore più vicino

Il ciclo delle generazioni, dal vivo

Valuta, ordina, seleziona, incrocia, muta, ripeti. Si ferma quando un'auto completa il giro, quando la fitness migliore non cresce più da molte generazioni, o a un massimo prefissato.

Il laboratorio qui sotto è il progetto intero in miniatura: una pista chiusa costruita come nella sezione 1, auto con il modello a bicicletta, cinque raggi, una rete 6 → 5 → 2 (sterzo e gas) e il ciclo evolutivo completo. Le prime generazioni sbattono quasi subito; guarda la fitness migliore e quella media salire, e la traiettoria del campione allungarsi.

Laboratorio: una popolazione che impara a girare

simulazione

Avanza di una o dieci generazioni. In pista vedi le traiettorie dell'ultima generazione (il campione in evidenza); sotto, la fitness migliore e media per generazione.

generazione0
fitness migliore—
fitness media—
vive a fine corsa—

Generazione 0: pesi casuali. Premi «Avanza» per valutare la popolazione.

Rete 6 → 5 → 2 (47 parametri), cinque raggi, 900 passi di fisica per auto con timeout di progresso, fitness = checkpoint superati + frazione, torneo di 3, crossover uniforme, elitismo. Seed fisso per generazione: stessi parametri, stessa storia. È una versione ridotta del progetto, non il progetto.

Generalizzazione: memorizzare una pista non è saper guidare

Una rete allenata su una sola pista può aver imparato quella pista, non la guida: una sequenza di riflessi che funziona perché le curve arrivano sempre nello stesso ordine. La prova è metterla su un tracciato mai visto.

Per ottenere un pilota e non un memorizzatore si allena su più piste, o con partenze variate (posizione e orientamento iniziali casuali), e si testa su una pista tenuta fuori. È la stessa distinzione tra training set e test set del machine learning classico, e vale anche qui dove non c'è un dataset. Il campione, infine, si salva: genoma e topologia in JSON (JavaScript Object Notation), da ricaricare per la modalità replay, quella del video, con una sola auto a velocità normale.

Mappa concettuale · l'addestramento: neuroevoluzione
  • Neuroevoluzione
    • nasce perché non c'è né dataset né gradiente
      • mancano esempi «input → output giusto»
      • manca un errore derivabile
    • valuta ogni auto con la Fitness
      • è fatta di checkpoint superati + frazione
      • si protegge con il timeout di progresso
      • rischia il reward hacking
    • costruisce la generazione con Selezione, crossover, mutazione
      • l'elitismo copia intatti i migliori
      • torneo o roulette scelgono i genitori
      • la mutazione è la sorgente di novità
    • si verifica con la Generalizzazione
      • si allena su più piste o partenze variate
      • si testa su una pista tenuta fuori
Neuroevoluzione
Addestrare una rete con un algoritmo evolutivo sui suoi pesi invece che con il gradiente: valuta, seleziona, ricombina, muta.
nessun dataset, nessuna derivata
Fitness
Numero che misura quanto un individuo ha fatto bene; qui checkpoint superati più frazione verso il successivo.
con timeout di progresso
Reward hacking
La popolazione ottimizza la metrica alla lettera e trova comportamenti che la massimizzano senza fare ciò che volevi.
avanti e indietro nel rettilineo
Elitismo
I migliori passano intatti alla generazione successiva: il campione non si perde mai.
élite = 2–3
Torneo
Selezione dei genitori: pesca k individui a caso e tieni il migliore.
k = 3
Crossover
Ricombinazione di due genomi: uniforme (gene per gene) o a un punto (prima parte da A, resto da B).
figlio[i] = caso ? A[i] : B[i]
Mutazione
Rumore gaussiano di ampiezza σ sommato a ogni gene con probabilità p; la sorgente di novità.
σ decrescente nel tempo
NEAT
NeuroEvolution of Augmenting Topologies: evolve anche la struttura della rete, non solo i pesi. Estensione facoltativa.
Stanley e Miikkulainen, 2002
5 · Dal video al repo

La UI deve far vedere il ragionamento della rete, e il repo deve poter essere ripercorso da chi impara

Ciò che rende il video memorabile non è l'auto che gira: è il grafo sotto, che si accende a ogni curva. La UI (User Interface, interfaccia utente) ha quel compito: rendere visibile il forward pass. Nodi in colonne, uno strato per colonna; archi tra tutti i nodi di strati adiacenti, con il colore che dice il segno del peso (verde positivo, rosso negativo) e lo spessore il suo modulo; il riempimento di ogni nodo che segue l'attivazione corrente, frame dopo frame; le etichette con nome e valore degli input a sinistra (−65° 0.77, VelF 0.53) e dei comandi a destra (Steering −0.98).

Sono 72 archi e 18 nodi da ridisegnare 60 volte al secondo, e con più raggi o più neuroni diventano centinaia: meglio un canvas dedicato che un SVG (Scalable Vector Graphics) con un elemento per ciascuno.

Accanto, il pannello di addestramento: generazione, auto vive, fitness migliore, il grafico migliore/media, la velocità di simulazione (x1, x10, massima senza disegno), e i controlli per cambiare popolazione e mutazione al volo.

Una sola fisica: il nodo tra notebook Python e UI nel browser

Se l'addestramento vive in un notebook Python (NumPy rende la formula della sezione 3 una riga) e la UI in TypeScript con Canvas, la simulazione rischia di esistere due volte, e un'auto allenata in Python guiderebbe diversamente nel browser.

Le opzioni sono tre. A, la consigliata: mondo, rete ed evoluzione in Python; un piccolo server FastAPI manda lo stato via WebSocket a ogni frame (auto, punti d'impatto dei raggi, pesi e attivazioni, generazione, fitness) e riceve i comandi (tasti, pausa, velocità); il browser disegna e basta.

B: tutto in TypeScript, con il kernel Jupyter di Deno che importa gli stessi moduli della UI; un solo linguaggio, ma senza NumPy. C: fisica riscritta in TypeScript e genoma esportato in JSON (JavaScript Object Notation); UI pubblicabile come pagina statica, ma due fisiche da tenere allineate con test di equivalenza: sconsigliata.

In tutti i casi il contratto WebSocket va fissato prima del codice, perché è il confine oltre il quale Python e UI evolvono separati.

opzione A · consigliata Python + UI che disegna
NumPy · FastAPI · WebSocket · React + Canvas
una sola fisica; notebook classico; esercita Python
due ambienti da avviare, un protocollo da definire
opzione B Tutto TypeScript
kernel Jupyter di Deno importa i moduli .ts della UI
un solo linguaggio, una sola installazione
niente NumPy né ecosistema Python di machine learning
opzione C · sconsigliata Fisica riscritta in TypeScript
genoma esportato in JSON e caricato dal browser
UI statica, pubblicabile ovunque
due fisiche da tenere allineate con test di equivalenza

Un corso fatto di tag git

Il repo è pensato per essere ripercorso: la storia git diventa il corso. Sette tappe (pista e auto manuale; sensori; rete casuale; grafo della rete; neuroevoluzione; campione e replay; estensioni) diventano sette coppie di tag: step-NN-start contiene boilerplate, commenti TODO(step-NN) che rimandano alla teoria, test rossi, e la soluzione della tappa precedente, così chi si blocca non resta indietro; step-NN-solution è la tappa risolta, da confrontare con la propria.

Il test è la definizione di «tappa completata», e per questo tutto deve essere deterministico: seed fisso per ogni generatore casuale, passo di fisica fisso.

I tag però non si modificano, e un bug nella tappa 1 andrebbe corretto a mano in tutte le successive. La soluzione è un solo branch solution con la logica nei moduli e blocchi # BEGIN SOLUTION / # END SOLUTION; uno script (tools/make_steps.py) sostituisce i blocchi con stub e rigenera tutti i tag; i marcatori riprendono la convenzione che nbgrader usa per i notebook.

I notebook stessi non contengono logica: importano da sim/ e sperimentano, sono abbinati a file .py con Jupytext (diff leggibili) e ripuliti dagli output con nbstripout; la CI (Continuous Integration) li esegue da zero e verifica che sugli starter i test della tappa corrente falliscano e quelli precedenti passino.

Da ricordare

Si parte scrivendo la soluzione completa, una tappa per commit; gli starter si ricavano dopo, per sottrazione.

Python · un TODO ben scritto
def forward(self, x: np.ndarray) -> np.ndarray:
    # TODO(step-03): calcola lo strato nascosto h = tanh(W1 @ x + b1)
    # e poi l'output y = tanh(W2 @ h + b2). Formula: sezione 3.
    # Verifica: pytest tests/test_step03.py
    raise NotImplementedError
Mappa concettuale · dal video al repo
  • Dal video al repo
    • mostra il ragionamento con la UI: il grafo della rete
      • colore segno del peso
      • spessore modulo del peso
      • riempimento attivazione del nodo
    • tiene una sola Fisica
      • opzione consigliata Python + browser che disegna
      • si fissa prima del codice il contratto WebSocket
    • trasforma la storia git in un Corso a tag
      • ogni tappa ha step-NN-start e step-NN-solution
      • si generano da un solo branch solution
    • richiede Determinismo
      • seed fisso e passo di fisica fisso
Grafo della rete
Visualizzazione del forward pass: colore dell'arco = segno del peso, spessore = modulo, riempimento del nodo = attivazione.
canvas dedicato, 60 fps
Contratto WebSocket
Lo schema dei messaggi tra simulazione e browser, fissato prima del codice: stato a ogni frame in una direzione, comandi nell'altra.
server → browser: auto, raggi, pesi, attivazioni
Tag a coppie
step-NN-start (TODO e test rossi, con la soluzione della tappa precedente) e step-NN-solution.
git diff step-03-start step-03-solution
BEGIN / END SOLUTION
Marcatori nel branch soluzione da cui uno script genera gli starter, così un bug si corregge in un posto solo.
convenzione nbgrader
Jupytext
Notebook abbinato a un file .py leggibile: diff puliti, logica nei moduli, notebook che sperimenta.
+ nbstripout per gli output
Riepilogo

I pezzi del progetto, uno per riga

PezzoChe cosa faLa formula o la regolaL'errore tipico
Game loopSepara fisica e disegno; accelera l'addestramentoupdate(dt fisso) × n, poi render()dt legato al frame rate: traiettorie diverse su computer diversi
Modello a biciclettaFisica dell'auto con due ruote in lineaθ̇ = (v / L)·tan δ; R = L / tan δfar curvare l'auto da ferma, o rotazione indipendente dalla velocità
SensoriSette raggi + velocità → otto numeri in [0, 1]s = min(d / d_max, 1)input non normalizzati: pesi che devono compensare scale diverse
Rete 8 → 6 → 4Funzione pura da input a comandi; 82 parametrih = tanh(W₁x + b₁), y = tanh(W₂h + b₂)rete troppo grande per effetto scenico: migliaia di parametri da evolvere
GenomaPesi e bias appiattiti in un vettoreFloat32Array(82)far conoscere all'evoluzione la struttura della rete
FitnessQuanto l'auto è arrivata lontanocheckpoint + frazione, con timeout di progressoreward hacking: premiare la distanza e ottenere auto che vanno avanti e indietro
Selezione e riproduzioneCostruisce la generazione successivaélite intatta; torneo; crossover; mutazione gaussiana (p, σ)σ costante: esplora sempre e non rifinisce mai; niente élite: si perde il campione
GeneralizzazioneDistingue un pilota da un memorizzatorepiù piste o partenze variate; test su pista mai vistadichiarare «impara a guidare» avendo visto una sola pista
UIRende visibile il forward passcolore = segno, spessore = modulo, riempimento = attivazioneSVG con centinaia di elementi a 60 fps
Repo a tappeLa storia git è il corsobranch solution + marcatori + script che rigenera i tag; test per tappa; seed fissitag modificati a mano; notebook con stato nascosto; due fisiche