Addestrare un agente a padroneggiare Tic-Tac-Toe attraverso il gioco autonomo | di Sébastien Gilbert | Settembre 2023


Sorprendentemente, un agente software non si stanca mai del gioco.

Ah! scuola elementare! Questo è stato il periodo in cui abbiamo acquisito competenze preziose, come l'alfabetizzazione, l'aritmetica e il gioco ottimale del tris.

fotografato da Solstizio Hannan SU Unsplash

Giocare una partita a tris con il tuo amico senza essere scoperto dall'insegnante è un'arte. Bisogna passare discretamente il foglio di gioco sotto la scrivania dando l'impressione di essere attenti all'argomento trattato. Il divertimento probabilmente riguardava più l'operazione sotto copertura che il gioco stesso.

Non possiamo insegnare l'arte di evitare di essere scoperti in classe a un agente software, ma possiamo addestrare un agente a padroneggiare il gioco?

Nel mio post precedente, abbiamo studiato un agente che impara il gioco Somma a100 attraverso il gioco personale. È stato un gioco semplice che ci ha permesso di visualizzare il valore dello stato, il che ci ha aiutato a creare un'intuizione su come l'agente apprende il gioco. Con il tris affrontiamo uno spazio statale molto più ampio.

Puoi trovare il codice Python in questo deposito. Lo script che esegue la formazione è learn_tictactoe.sh:

#!/bin/bash
dichiarare -i NUMERO_DI_GIOCHI=30000
dichiarare -i NUMBER_OF_EPOCHS=5

esporta PYTHONPATH='./'

preelaborazione python/generate_positions_expectations.py \
--outputDirectory=./learn_tictactoe/output_tictactoe_generate_positions_expectations_level0 \
--game=tictactoe \
--numberOfGames=$NUMBER_OF_GAMES \
--gamma=0,95 \
--randomSeed=1 \
--agentArchitecture=Nessuno \
--agentFilepath=Nessuno \
--opponentArchitecture=Nessuno \
--opponentFilepath=Nessuno \
--epsilons="[1.0]" \
--temperatura=0

dataset_filepath="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level0/dataset.csv"

python treno/train_agent.py \
$dataset_percorsofile \
--outputDirectory="./learn_tictactoe/output_tictactoe_train_agent_level1" \
--game=tictactoe \
--randomSeed=0 \
--validationRatio=0.2 \
--dimensione batch=64 \
--architecture=Sant'Andrea_1024 \
--dropoutRatio=0,5 \
--Tassoapprendimento=0.0001 \
--weightDecay=0.00001 \
--numberOfEpochs=$NUMBER_OF_EPOCHS \
--startingNeuralNetworkFilepath=Nessuno

per il livello in {1..16}
Fare
dataset_filepath="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}/dataset.csv"
preelaborazione python/generate_positions_expectations.py \
--outputDirectory="./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${livello}" \
--game=tictactoe \
--numberOfGames=$NUMBER_OF_GAMES \
--gamma=0,95 \
--randomSeed=0 \
--agentArchitecture=Sant'Andrea_1024 \
--agentFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth" \
--opponentArchitecture=Sant'Andrea_1024 \
--opponentFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth" \
--epsilons="[0.5, 0.5, 0.1]" \
--temperatura=0

dichiarare -i livello_successivo=$((livello + 1))
python treno/train_agent.py \
"./learn_tictactoe/output_tictactoe_generate_positions_expectations_level${level}/dataset.csv" \
--outputDirectory="./learn_tictactoe/output_tictactoe_train_agent_level${next_level}" \
--game=tictactoe \
--randomSeed=0 \
--validationRatio=0.2 \
--dimensione batch=64 \
--architecture=Sant'Andrea_1024 \
--dropoutRatio=0,5 \
--Tassoapprendimento=0.0001 \
--weightDecay=0.00001 \
--numberOfEpochs=$NUMBER_OF_EPOCHS \
--startingNeuralNetworkFilepath="./learn_tictactoe/output_tictactoe_train_agent_level${level}/SaintAndre_1024.pth"

Fatto

Lo script esegue un ciclo delle chiamate a due programmi:

L'apprendimento della partita da parte dell'agente procede attraverso un ciclo di generazione delle partite e di allenamento per prevedere l'esito della partita dallo stato del gioco:

Figura 1: Il ciclo di generazione della corrispondenza e addestramento della rete neurale. Immagine dell'autore.

Il ciclo inizia con la simulazione di partite tra giocatori casuali, cioè giocatori che scelgono casualmente dalla lista delle azioni legali in un dato stato del gioco.

Perché stiamo generando partite giocate in modo casuale?

Questo progetto riguarda l'apprendimento tramite il gioco autonomo, quindi non possiamo fornire all'agente alcuna informazione a priori al riguardo come giocare. Nel primo ciclo, poiché l'agente non ha idea delle mosse buone o cattive, le partite devono essere generate in modo casuale.

La Figura 2 mostra un esempio di una partita tra giocatori casuali:

Figura 2: Esempio di una partita di tris giocata in modo casuale. Immagine dell'autore.

Quale lezione possiamo imparare osservando questa partita? Dal punto di vista del giocatore "X", possiamo supporre che questo sia un esempio di gioco scadente poiché si è concluso con una sconfitta. Non sappiamo quale/i mossa/i sia/i responsabile/i della sconfitta, quindi lo supponiamo tutte le decisioni realizzati dal giocatore "X" erano pessimi. Se alcune decisioni fossero buone, scommetteremo sulle statistiche (altre simulazioni potrebbero attraversare uno stato simile) per rettificare il valore dello stato previsto.

All'ultima azione del giocatore 'X' viene assegnato un valore di -1. Le altre azioni ricevono un valore negativo scontato che decade geometricamente di un fattore γ (gamma) ∈ [0, 1] man mano che si torna indietro verso la prima mossa.

Figura 3: valori target per gli stati del gioco. Immagine dell'autore.

Gli stati delle partite che hanno portato a una vittoria ricevono valori scontati positivi simili. Agli stati estratti dai sorteggi viene assegnato un valore pari a zero. L'agente assume il punto di vista sia del primo che del secondo giocatore.

Il gioco si afferma come tensori

Abbiamo bisogno di una rappresentazione tensore per lo stato del gioco. Utilizzeremo un tensore [2x3x3] in cui la prima dimensione rappresenta i canali (0 per 'X' e 1 per 'O'), e le altre due dimensioni sono le righe e le colonne. L'occupazione di un quadrato (riga, colonna) viene codificata come 1 nella voce (canale, riga, colonna).

Figura 4: La rappresentazione dello stato del gioco mediante un tensore [2x3x3]. Immagine dell'autore.

Le coppie di (tensore di stato, valore target) ottenute dalla generazione delle corrispondenze costituiscono il set di dati su cui la rete neurale si addestrerà in ogni round. Il set di dati viene creato all'inizio del ciclo, sfruttando l'apprendimento avvenuto nei cicli precedenti. Mentre il primo round genera giocate puramente casuali, i successivi generano partite via via più realistiche.

Iniettare casualità nel gioco

Il primo round di generazione della partita si oppone ai giocatori casuali. I turni successivi oppongono l'agente a se stesso (da qui “self-play”). L'agente è dotato di una rete neurale di regressione addestrata a prevedere l'esito della partita, che gli consente di scegliere l'azione legale che produce il valore atteso più elevato. Per promuovere la diversità, l'agente sceglie le azioni in base a un algoritmo epsilon-greedy: con probabilità (1-ε), viene scelta l'azione migliore; in caso contrario, viene scelta un'azione casuale.

La Figura 5 mostra l'evoluzione delle perdite di validazione lungo cinque epoche per un massimo di 17 cicli di addestramento:

Figura 5: Evoluzione della perdita di convalida per vari numeri di cicli di formazione. Immagine dell'autore.

Possiamo vedere che i primi cicli di addestramento mostrano una rapida diminuzione della perdita di convalida, quindi sembra esserci un plateau attorno a una perdita di errore quadratico medio di 0,2. Questa tendenza mostra che la rete neurale di regressione dell'agente migliora nel prevedere l'esito di una partita giocata contro se stessa, a partire da un dato stato del gioco. Poiché le azioni di entrambi i giocatori non sono deterministiche, esiste un limite alla prevedibilità del risultato della partita. Ciò spiega perché la perdita di convalida smette di migliorare dopo alcuni round.

Miglioramento di round in round

Con il gioco Somma a100, potremmo rappresentare lo stato su una griglia 1D. Tuttavia, con tic-tac-toe, non possiamo visualizzare direttamente l'evoluzione del valore dello stato. Una cosa che possiamo fare per misurare il miglioramento è confrontare l'agente con la versione precedente di se stesso e osservare la differenza tra vincite e perdite.

Utilizzando ε = 0,5 per la prima azione di entrambi i giocatori e ε = 0,1 per il resto della partita, eseguendo 1000 partite per confronto, questo è ciò che otteniamo:

Figura 6: Confronto dell'agente con la versione precedente. Immagine dell'autore.

Il numero di vittorie ha superato il numero di sconfitte (mostrando un miglioramento) fino a 10 turni di allenamento. Successivamente, l'agente non è migliorato di round in round.

È ora di vedere come gioca a tris il nostro agente!

Una caratteristica utile di una rete neurale di regressione è la possibilità di visualizzare la valutazione dell'agente su ogni mossa legale. Facciamo un gioco contro l'agente, mostrando come giudica le sue opzioni.

Riproduzione manuale

L'agente inizia, suonando 'X':

Figura 7: Una partita giocata contro l'agente, con le valutazioni dell'azione. Immagine dell'autore.

È così che vieni brutalmente schiacciato da una macchina da tris senz'anima!

Non appena ho messo la "O" nella casella (1, 0), il rendimento atteso è aumentato da 0,142 a 0,419 e il mio destino è stato segnato.

Vediamo come si comporta quando l'agente gioca per secondo:

Figura 8: Una partita giocata contro l'agente, con valutazioni dell'azione. Immagine dell'autore.

Non è caduto nella trappola e la partita è stata un pareggio.

Partite contro un giocatore casuale

Se noi simulare un gran numero di partite contro un giocatore casuale, questo è ciò che otteniamo:

Figura 9: Risultati di 1000 partite contro un giocatore casuale. Immagine dell'autore.

Su 1000 partite (l'agente ha giocato per primo in metà partite), l'agente ha vinto 950 partite, non ha perso nessuna e ci sono stati 50 pareggi. Questa non è la prova che il nostro agente stia giocando in modo ottimale, ma sicuramente ha raggiunto un livello di gioco discreto.

Come seguito a Formare un agente per padroneggiare un gioco semplice attraverso il gioco autonomo dove il gioco era facile da decifrare e lo spazio degli stati era piccolo, abbiamo usato la stessa tecnica per padroneggiare il tris. Sebbene questo sia ancora un problema giocattolo, lo spazio degli stati del tris è abbastanza grande da richiedere la rete neurale di regressione dell'agente per trovare modelli nei tensori degli stati. Questi modelli consentono la generalizzazione per tensori di stato invisibili.

Il codice è disponibile in questo deposito. Provatelo e fatemi sapere cosa ne pensate!



Collegamento alla fonte

lascia un commento

L'indirizzo email non verrà pubblicato. I campi richiesti sono contrassegnati *

Puoi utilizzare questi tag e attributi HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

it_ITItalian