Risolvere l’esercizio di Reinforcement Learning sulle piste da corsa con il controllo Monte Carlo fuori politica

Risolvere l'esercizio di Reinforcement Learning con controllo Monte Carlo fuori politica sulle piste da corsa

Immagine generata da Midjourney con una sottoscrizione a pagamento, che rispetta i termini commerciali generali [1].

Introduzione

Nella sezione Controllo Monte Carlo off-policy del libro Reinforcement Learning: An Introduction 2nd Edition (pagina 112), l’autore ci ha lasciato con un interessante esercizio: utilizzare il metodo Monte Carlo off-policy con ponderazione dell’importanza per trovare il modo più veloce di guidare su entrambe le piste. Questo esercizio è completo e ci chiede di considerare e costruire quasi ogni componente di un compito di apprendimento per rinforzo, come l’ambiente, l’agente, la ricompensa, le azioni, le condizioni di terminazione e l’algoritmo. Risolvere questo esercizio è divertente e ci aiuta a costruire una solida comprensione dell’interazione tra algoritmo e ambiente, dell’importanza di una corretta definizione del compito episodico e di come l’inizializzazione del valore influisce sull’esito dell’addestramento. Attraverso questo post, spero di condividere la mia comprensione e soluzione a questo esercizio con tutti coloro interessati all’apprendimento per rinforzo.

Requisiti dell’esercizio

Come accennato in precedenza, questo esercizio ci chiede di trovare una politica che permetta a una macchina da corsa di guidare dalla linea di partenza alla linea del traguardo il più velocemente possibile senza finire nella ghiaia o fuori pista. Dopo aver letto attentamente le descrizioni dell’esercizio, ho elencato alcuni punti chiave che sono essenziali per completare questo compito:

  • Rappresentazione della mappa: le mappe in questo contesto sono in realtà matrici 2D con (indice_riga, indice_colonna) come coordinate. Il valore di ogni cella rappresenta lo stato di quella cella; ad esempio, possiamo usare 0 per descrivere la ghiaia, 1 per la superficie della pista, 0.4 per la regione di partenza e 0.8 per la linea del traguardo. Qualsiasi indice di riga e colonna al di fuori della matrice può essere considerato fuori dai limiti.
  • Rappresentazione dell’auto: possiamo utilizzare direttamente le coordinate della matrice per rappresentare la posizione dell’auto;
  • Velocità e controllo: lo spazio delle velocità è discreto e consiste in velocità orizzontali e verticali che possono essere rappresentate come una tupla (velocità_riga, velocità_colonna). Il limite di velocità su entrambi gli assi è (-5, 5) e viene incrementato di +1, 0 e -1 su ciascun asse in ogni passo; quindi, ci sono un totale di 9 azioni possibili in ogni passo. Letteralmente, la velocità non può essere zero tranne alla linea di partenza, e la velocità verticale, o velocità di riga, non può essere negativa in quanto non vogliamo che la nostra auto torni alla linea di partenza.
  • Ricompensa ed episodio: la ricompensa per ogni passo prima di attraversare la linea del traguardo è -1. Quando l’auto esce dalla pista, verrà riposizionata in una delle celle di partenza. L’episodio termina SOLTANTO quando l’auto attraversa con successo la linea del traguardo.
  • Stati di partenza: scegliamo casualmente una cella di partenza per l’auto dalla linea di partenza; la velocità iniziale dell’auto è (0, 0) secondo la descrizione dell’esercizio.
  • Sfida di accelerazione zero: l’autore propone una piccola sfida di accelerazione zero in cui, ad ogni passo temporale, con una probabilità del 0,1, l’azione non avrà effetto e l’auto manterrà la sua velocità precedente. Possiamo implementare questa sfida nell’addestramento anziché aggiungere la funzionalità all’ambiente.

La soluzione all’esercizio è divisa in due post; in questo post, ci concentreremo sulla creazione di un ambiente per una pista di corsa. La struttura dei file di questo esercizio è la seguente:

|-- race_track_env|  |-- maps|  |  |-- build_tracks.py // questo file viene utilizzato per generare le mappe della pista|  |  |-- track_a.npy // dati della pista a|  |  |-- track_b.npy // dati della pista b|  |-- race_track.py // ambiente della pista di corsa|-- exercise_5_12_racetrack.py // la soluzione a questo esercizio

E le librerie utilizzate in questa implementazione sono le seguenti:

python==3.9.16numpy==1.24.3matplotlib==3.7.1pygame==2.5.0

Creazione delle mappe della pista

Possiamo rappresentare le mappe della pista come matrici 2D con valori diversi che indicano lo stato della pista. Voglio essere fedele all’esercizio, quindi sto cercando di costruire le stesse mappe mostrate nel libro assegnando manualmente i valori della matrice. Le mappe verranno salvate come file .npy separati in modo che l’ambiente possa leggere il file durante l’addestramento anziché generarle durante l’esecuzione.

Le due mappe appaiono come segue; le celle chiare sono ghiaia, le celle scure sono superfici di pista, le celle verdastre rappresentano la linea del traguardo e le celle azzurrine rappresentano la linea di partenza.

Figura 1 mappe della pista con la rappresentazione a matrice 2D. Fonte: Immagine dell'autore

Creazione di un ambiente simile a Gym

Con le mappe della pista pronte, procediamo ora alla creazione di un ambiente simile a Gym con cui l’algoritmo può interagire. Il gymnasium, precedentemente il OpenAI gym ora appartenente alla Farama Foundation, fornisce un’interfaccia semplice per testare gli algoritmi di RL; utilizzeremo il pacchetto gymnasium per creare l’ambiente della pista di gara. Il nostro ambiente includerà i seguenti componenti/caratteristiche:

  • env.nS: la forma dello spazio di osservazione, che è (num_rows, num_cols, row_speed, col_speed) in questo esempio. Il numero di righe e colonne varia tra le mappe, ma lo spazio di velocità è costante su tutte le piste. Per la velocità di riga, poiché non vogliamo che l’auto torni alla linea di partenza, le osservazioni di velocità di riga consistono in [-4, -3, -2, -1, 0] (valore negativo perché ci aspettiamo che l’auto si muova verso l’alto nella mappa), cinque spazi in totale; la velocità delle colonne non ha tale limite, quindi le osservazioni vanno da -4 a 4, nove spazi in totale. Pertanto, la forma dello spazio di osservazione nell’esempio della pista di gara è (num_rows, num_cols, 5, 9).
  • env.nA: il numero di azioni possibili. Ci sono 9 azioni possibili nella nostra implementazione; creeremo anche un dizionario nella classe dell’ambiente per mappare l’azione intera alla rappresentazione della tupla (row_speed, col_speed) per controllare l’agente.
  • env.reset(): la funzione che riporta l’auto in una delle celle di partenza quando l’episodio finisce o l’auto esce dalla pista;
  • env.step(action): la funzione step consente all’algoritmo di interagire con l’ambiente prendendo un’azione e restituendo una tupla di (next_state, reward, is_terminated, is_truncated).
  • Funzioni di controllo dello stato: ci sono due funzioni private che aiutano a verificare se l’auto ha lasciato la pista o attraversato la linea del traguardo;
  • Funzioni di rendering: la funzione di rendering è essenziale per l’ambiente personalizzato dal mio punto di vista; controllo sempre se l’ambiente è stato costruito correttamente rendendo lo spazio di gioco e i comportamenti dell’agente, che è più semplice che semplicemente fissare le informazioni di registrazione.

Con queste caratteristiche in mente, possiamo iniziare a costruire l’ambiente della pista di gara.

Verifica dell’ambiente

Fino ad ora, con tutto il necessario per l’ambiente della pista di gara pronto, possiamo testare/verificare la nostra configurazione dell’ambiente.

Prima di tutto, rendiamo visibile il gioco per verificare se ogni componente funziona correttamente:

Figura 2 Agenti che guidano su entrambe le piste con una politica casuale. Fonte: Gif dell’autore

Poi disattiviamo l’opzione di rendering per far funzionare l’ambiente in background per verificare se la traiettoria termina correttamente:

// Traccia a|   Osservazione   | ricompensa | Terminato | Ricompensa totale ||  (1, 16, 0, 3)  |   -1   |    True    |    -4984     ||  (2, 16, 0, 2)  |   -1   |    True    |    -23376    ||  (3, 16, 0, 3)  |   -1   |    True    |    -14101    ||  (1, 16, 0, 3)  |   -1   |    True    |    -46467    |// Traccia b|   Osservazione    | ricompensa | Terminato | Ricompensa totale ||  (1, 31, -2, 2)  |   -1   |    True    |    -3567     ||  (0, 31, -4, 4)  |   -1   |    True    |    -682      ||  (2, 31, -2, 1)  |   -1   |    True    |    -1387     ||  (3, 31, -1, 3)  |   -1   |    True    |    -2336     |

Algoritmo di controllo Monte Carlo off-policy

Con l’ambiente pronto, possiamo prepararci per implementare l’algoritmo di controllo MC off-policy con l’algoritmo di campionamento ponderato di importanza, come mostrato di seguito:

Figura 3 Algoritmo di controllo Monte Carlo off-policy. Fonte: Algoritmo scritto in LaTeX dall'autore.

I metodi Monte Carlo risolvono il problema dell’apprendimento per rinforzo mediando i ritorni campionati. Durante l’addestramento, il metodo genera una traiettoria basata su una politica data e apprende dalla fine di ogni episodio. La differenza tra l’apprendimento on-policy e off-policy è che i metodi on-policy utilizzano una sola politica per prendere decisioni e miglioramenti, mentre i metodi off-policy utilizzano politiche separate per generare dati e migliorare la politica. Secondo l’autore del libro, quasi tutti i metodi off-policy utilizzano il campionamento di importanza per stimare i valori attesi in base a una distribuzione data campioni da un’altra. [2]

Le prossime sezioni spiegheranno i trucchi della politica soft e del campionamento ponderato dell’importanza che appaiono nell’algoritmo sopra e quanto sia essenziale una corretta inizializzazione del valore per questo compito.

Politica target e politica di comportamento

Il metodo off-policy di questo algoritmo utilizza due politiche:

  • Politica target: la politica in fase di apprendimento. In questo algoritmo, la politica target è avida e deterministica, il che significa che la probabilità dell’azione avida A al tempo t è 1.0, mentre la probabilità di tutte le altre azioni è uguale a 0.0. La politica target segue l’Iterazione della Politica Generalizzata (GPI) che migliora ad ogni passo.
  • Politica di comportamento: la politica utilizzata per generare il comportamento. La politica di comportamento in questo algoritmo è definita come politica soft, il che significa che tutte le azioni in uno stato dato hanno una probabilità diversa da zero. Qui utilizzeremo la politica ε-greedy come politica di comportamento.

Nella politica soft, la probabilità di un’azione è:

Dovremmo anche restituire questa probabilità quando generiamo azioni per il campionamento di importanza. Il codice per la politica di comportamento appare come segue:

Campionamento ponderato dell’importanza

Stimiamo la probabilità relativa della traiettoria generata dalla politica target rispetto alla traiettoria della politica di comportamento, e l’equazione per tale probabilità è:

La funzione valore per il campionamento ponderato dell’importanza è:

Possiamo riformulare l’equazione per adattarla al compito episodico con la forma di aggiornamenti incrementali:

Ci sono molti ottimi esempi su come derivare l’equazione sopra, quindi non perderemo tempo a derivarla nuovamente qui. Tuttavia, voglio anche spiegare i trucchi interessanti sulle ultime righe dell’algoritmo:

Figura 4 Il trucco nell'algoritmo di controllo Monte Carlo off-policy. Fonte: Immagine dell'autore

Il trucco si basa sulla configurazione che la politica target qui è deterministica. Se l’azione presa in un passo di tempo differisce da quella proveniente dalla politica target, allora la probabilità di quell’azione rispetto alla politica target è 0.0; quindi, tutti i passaggi successivi non aggiornano più la funzione valore stato-azione della traiettoria. Al contrario, se l’azione in quel passo di tempo è la stessa della politica target, allora la probabilità è 1.0 e l’aggiornamento del valore dell’azione continua.

Inizializzazione dei pesi

Un’adeguata inizializzazione dei pesi svolge un ruolo importante nel risolvere con successo questo esercizio. Iniziamo dando un’occhiata alle ricompense su entrambe le piste da una politica casuale:

// Pista a|   Osservazione   | ricompensa | Terminato | Ricompensa totale ||  (1, 16, 0, 3)  |   -1   |    Vero    |    -4984     ||  (2, 16, 0, 2)  |   -1   |    Vero    |    -23376    ||  (3, 16, 0, 3)  |   -1   |    Vero    |    -14101    ||  (1, 16, 0, 3)  |   -1   |    Vero    |    -46467    |// Pista b|   Osservazione    | ricompensa | Terminato | Ricompensa totale ||  (1, 31, -2, 2)  |   -1   |    Vero    |     -3567    ||  (0, 31, -4, 4)  |   -1   |    Vero    |     -682     ||  (2, 31, -2, 1)  |   -1   |    Vero    |     -1387    ||  (3, 31, -1, 3)  |   -1   |    Vero    |     -2336    |

La ricompensa ad ogni passo di tempo prima di attraversare la linea del traguardo è -1. Nella fase iniziale dell’addestramento, la ricompensa sarà un grande valore negativo; se inizializziamo il valore stato-azione a 0 o a valori casuali da una distribuzione normale, ad esempio con una media di 0 e una varianza di 1, il valore potrebbe essere considerato troppo ottimistico e potrebbe richiedere molto tempo al programma per trovare una politica ottimale.

Dopo vari test, ho scoperto che una distribuzione normale con una media di -500 e una varianza di 1 può essere un valore efficace per una convergenza più rapida; ti incoraggio a provare altri valori e sicuramente potrai trovare un valore iniziale migliore di questo.

Risolvere il problema

Con tutte le considerazioni sopra in mente, possiamo programmare la funzione di controllo Monte Carlo off-policy e usarla per risolvere l’esercizio della pista di corsa. Aggiungeremo anche la sfida di accelerazione zero menzionata nella descrizione dell’esercizio.

Quindi addestriamo le politiche per 1.000.000 di episodi senza/con sfida di accelerazione zero su entrambe le piste e le valutiamo con il seguente codice:

Plotting del record di addestramento, scopriamo che la politica converge intorno al 10.000° episodio su entrambe le piste senza considerare l’accelerazione zero; l’aggiunta dell’accelerazione zero rende la convergenza più lenta e instabile prima del 100.000° episodio, ma converge comunque successivamente.

Figura 5 Storia delle ricompense di addestramento della pista A. Fonte: Immagine dell'autore
Figura 6 Storia delle ricompense di addestramento della pista B. Fonte: Immagine dell'autore

Risultati

Infine, possiamo chiedere agli agenti di giocare al gioco con le politiche addestrate e plottare le possibili traiettorie per verificare ulteriormente la correttezza delle politiche.

Figura 7 Agenti che guidano su entrambe le piste basandosi sulle politiche addestrate. Fonte: Gif dell’autore

Traiettorie campione per la pista a:

Figura 8 Traiettorie campione ottimali per la pista A. Fonte: Immagine dell'autore

Traiettorie campione per la pista b:

Figura 9 Traiettorie campione ottimali per la pista B. Fonte: Immagine dell'autore

Fino ad ora, abbiamo risolto l’esercizio della pista da corsa. Questa implementazione potrebbe ancora avere alcuni problemi e sei invitato a segnalarli e discutere una soluzione migliore nei commenti. Grazie per aver letto!

Riferimenti

[1] Termini di servizio Midjourney: https://docs.midjourney.com/docs/terms-of-service

[2] Sutton, Richard S., e Andrew G. Barto. Reinforcement learning: Un’introduzione. MIT Press, 2018.

Il mio repository GitHub di questo esercizio: [Link]; ti prego di controllare la sezione “esercizio”.