Vi skal nå gjøre det mulig for agenten å lære gjennom å utforske labyrinten på egenhånd. Målet er å lære en policy som er optimal med tanke på målet vårt: Fra en gitt posisjon i labyrinten, finn den beste handlingen, altså retningen å gå i, for å nå målet raskest mulig.
Vi skriver
Merk: Vi tar med alle rutene i labyrinten i
Mengden av alle handlinger (actions) kaller vi for
Q-funksjonen er en funksjon
For å være litt mer konkret: La oss si at agenten er i posisjon
Siden vi har endelig mange tilstander og handlinger så kan vi representere Q-funksjonen som en tabell. La oss kalle denne Q-tabellen.
For eksempel, hvis labyrinten vår har 3 rader og 3 kolonner, så ser Q-tabellen slik ut før agenten har lært noe:
|
|
||||
|---|---|---|---|---|
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 | |
| 0.0 | 0.0 | 0.0 | 0.0 |
I tabellen ovenfor har vi tilstanden (agentens posisjon) i kolonnen helt til venstre og Q-verdiene til de forskjellige handlingene vi kan utføre i denne posisjonen til høyre for tilstanden. For eksempel så er
Vi skal nå opprette en slik "tom" Q-tabell.
1) Lag en funksjon generate_q_table(n_row, n_col) i filen learning.py. Parameterene n_row og n_col er henholdsvis antall rader og kolonner i labyrinten (begge heltall). Returner et oppslagsverk (dictionary) med nøkkelverdier (col, row) for alle mulige posisjoner i labyrinten (inkludert veggruter og målruter). For hver nøkkelverdi (col, row) skal vi ha verdien {"left": 0.0, "right": 0.0, "up": 0.0, "down": 0.0}. Vi har med andre ord laget et oppslagsverk av oppslagsverk for å lagre Q-tabellen.
Q-verdien som korresponderer til å gå til venstre når agenten er i posisjon q_table[(1,2)]["left"]. For å få tak i Q-verdien for å gå ned når agenten er i posisjon q_table[(1,0)]["down"], og så videre.
Spørsmål: Hvis vi har en labyrint med
💡 Hint
- For å iterere over alle par
(col, row)hvor0 <= col < n_colog0 <= row < n_rowkan du benytte to nøstede for-løkker... - ... eller du kan benytte
itertools.productsammen medrange(prøv å kjør eksempelet nedenfor).
from itertools import product
for pos in product(range(6), range(4)):
print(pos)📋 Test implementasjonen din: Lagre filen test_generate_q_table.py i samme mappe som de andre filene og kjør den for å teste koden din. Hvis du får All test passed! kan du gå videre.
2) I funksjonen app_started i filen main.py, lag en variabel app.q_table = generate_q_table(n_row, n_col) (etter at app.maze er definert) hvor n_row = len(app.maze) og n_col = len(app.maze[0]).
For at agenten skal kunne lære noe må vi bestemme hva som skal belønnes og hva som skal straffes (negativ belønning). Dette gjøres ved hjelp av en funksjon kalt belønningsfunksjonen (reward function)
For enhver tilstand
Lag en funksjon reward_function(agent_pos, direction, maze) i filen learning.py som tar in følgende tre parametere:
agent_pos: en tupel(col, row)som gir oss agentens posisjon i labyrinten,direction: en streng som kan ta verdiene"left","right","up"eller"down"som sier hvilken posisjon vi skal gå, ogmaze: en 2D-liste som representerer labyrinten.
Hvis naboruten til agent_pos i retning direction er en åpen rute skal funksjonen returnere -0.1. Hvis naboruten er en veggrute skal funksjonen returnere -1.0, og hvis naboruten er en målrute skal funksjonen returnere 1.0.
💡 Hint
- Bruk funksjonen
coord_in_directionfra filenmaze.pyfor å finne koordinatene i retningdirectionfraagent_pos. - Returner belønningsverdi basert på verdien av
mazefor den nye posisjonen. Bruk gjerne funksjoneneis_wallogis_goalframaze.pyfor å avgjøre hva belønningen skal være.
📋 Test implementasjonen din: Lagre filen test_reward_function.py i samme mappe som de andre filene og kjør den for å teste koden din. Hvis du får All test passed! kan du gå videre.
La
På denne måten lar vi agenten utforske tilfeldige retninger slik at den kan lære mer. Samtidig lar vi agenten også bruke tidligere erfaring noen ganger. Senere skal vi la
2.c.1) Finn optimal retning basert på Q-tabellen
Lag en funksjon get_policy_direction(agent_pos, q_table) i filen learning.py. Denne funksjonen tar inn to parametere: en tupel av heltall agent_pos som angir agentens posisjon og q_table som representerer en Q-tabell. Funksjonen skal returnere en streng ("left", "right", "up" eller "down") som angir retningen (handlingen) som korresponderer til høyest Q-verdi i angitt posisjon (tilstand). Med andre ord, funksjonen skal returnere agent_pos.
💡 Hint
- Verdien til
q_table[agent_pos]er et oppslagsverk med nøkkelverdier"left","right","up"og"down". Returner nøkkelverdien som peker på den høyeste verdien. - En mulighet er å bruke parameteren
keyfor funksjonenmax(se eksempelet nedenfor).
foo = {"a": 0.0, "b": 1.0, "c": 0.5, "d": -1.0}
max_key = max(foo, key=foo.get)
print(max_key)- Hva skjer hvis du skriver
max(foo)i eksempelt ovenfor? Hvorfor gir ikke det resultatet vi ønsker?
📋 Test implementasjonen din: Lagre filen test_get_policy_direction.py i samme mappe som de andre filene og kjør den for å teste koden din. Hvis du får All test passed! kan du gå videre.
2.c.2) Velg tilfeldig retning
Lag en funksjon decide_direction(agent_pos, q_table, epsilon) i filen learning.py. Funksjonen skal ta inn tre parametere: en tupel av heltall agent_pos som angir agentens posisjon, et oppslagsverk q_table som representerer en Q-tabell, og et flyttall epsilon mellom 0.0 og 1.0. Funksjonen skal returnere en streng ("left", "right", "up" eller "down") som angir retningen (handlingen) agenten skal gå. Retningen som returneres skal enten være tilfeldig (med sannsynlighet epsilon) eller den som har høyest Q-verdi (med sannsynlighet 1 - epsilon).
💡 Hint
- Koden nedenfor skriver ut
Hellomed sannsynlighet0.8og skriver utWorldmed sannsynlighet0.2.
import random
epsilon = 0.8
if random.random() < epsilon:
# Runs with probability epsilon
print("Hello")
else:
# Runs with probability 1 - epsilon
print("World")-
For å velge et tilfeldig element fra en liste kan du bruke funksjonen
random.choice. For eksempelrandom.choice(["Hello", "Hey", "Howdy"])returnerer en av strengene"Hello","Hey"eller"Howdy"med lik sannsynlighet (uniformt fordelt). -
Bruk funksjonen
get_policy_directionfra forrige deloppgave for å finne den beste retningen i følge Q-tabellen.
2.c.3) Lag nye variabel
I funksjonen app_started i filen main.py lag en ny variabel app.epsilon med verdien 0.5.
2.c.4) La agenten utforske
I slutten av funksjonen timer_fired i filen main.py, implementer følgende:
- Lag en variabel
directionmed returverdien du får ved å kalledecide_direction(fralearning.py) med parametreneapp.agent_pos,app.q_tableogapp.epsilon. - Hvis ruten i retning
directionfraapp.agent_posikke er en veggrute, flytt agenten dit.
💡 Hint
Oppdater app.agent_pos ved å bruke funksjonen move_agent fra maze.py.
2.c.5) Resett agenten
Vi skal nå resette agenten når den når en målrute. I slutten av funksjonen timer_fired i filen main.py, implementer følgende: Hvis agenten havner på en målrute, sett variabelen app.agent_pos til posisjonen til en tilfeldig åpen rute.
💡 Hint
- Bruk funksjonen
is_goalframaze.pyfor å sjekke om agenten har havnet på en målrute. - Bruk funksjonen
get_random_positionframaze.pyfor å finne en tilfeldig åpen rute.
📋 Test implementasjonen din så langt: Hvis du nå kjører main.py skal agenten gå tilfeldig rundt i labyrinten, og hvis agenten når en målrute skal agenten settes på en tilfeldig åpen rute. Siden vi enda ikke har implementert oppdateringssteget for Q-tabellen (selve læringen) så ser vi ingen forbedringen av agentens evner til å finne målruten.
Hitill har vi kun en tom Q-tabell app.q_table hvor alle Q-verdier er lik 0.0. Det er nå vi skal implementere selve Q-læringsalgoritmen som tillater agenten å lære via å oppdatere Q-tabellen hver gang agenten har utført en handling.
La
La oss prøve å forstå litt bedre hva denne regelen egentlig gjør:
- Pilen som peker til venstre betyr "erstatt
$Q(s_t, a_t)$ med det som står til høyre". -
$\alpha$ er et tall mellom$0$ og$1$ og kalles læringsraten (learning rate). Dette tallet sier noe om hvor mye den gamle Q-verdien påvirke den oppdaterte Q-verdien i forhold til ny erfaring. -
$R(s_t, a_t)$ er belønningen vi mottar for å gjøre handling$a_t$ i posisjon$s_t$ . -
$\gamma$ er et tall mellom$0$ og$1$ som sier noe om hvor mye vi skal ta framtidige Q-verdier i betraktning. -
$\max_a Q(s_{t+1}, a)$ er den høyeste Q-verdien for posisjon$s_{t+1}$ som er posisjonen vi kommer til hvis vi utfører handling$a_t$ is posisjon$s_t$ . For eksempel, hvis agenten er i posisjon$s_t = (4,2)$ og$a_t = \text{venstre}$ så er$s_{t+1} = (3,2)$ og$\max_{a\in\mathcal{A}} Q(s_{t+1}, a)$ er det største av tallene$Q((3,2), \text{venstre})$ ,$Q((3,2), \text{høyre})$ ,$Q((3,2), \text{opp})$ ,$Q((3,2), \text{ned})$ .
Spørsmål: Hva blir formelen for å oppdatere
Spørsmål: Kan du prøve å forklare hva
Spørsmål: Hva blir formelen for å oppdatere
2.d.1) Finn
Lag en funksjon get_optimal_future_q_value(q_table, agent_pos, direction, maze) i learning.py. Denne funksjonen skal returnere verdien av q_table som representerer en Q-tabell, en tupel av heltall agent_pos som angir agentens posisjon (altså direction som angir hvilken retning agenten går i (altså maze som en 2D-liste av heltall.
💡 Hint
- Bruk
move_agentframaze.pyfor å finne neste posisjon$s_{t+1}$ . - Hvis du kaller
$s_{t+1}$ fornext_position, brukmaxpå listenq_table[next_position].values()for å finne den høyeste Q-verdien (dict.values()returnerer verdiene i et oppslagsverk som en liste).
📋 Test implementasjonen din: Lagre filen test_get_optimal_future_q_value.py i samme mappe som de andre filene og kjør den for å teste koden din. Hvis du får All test passed! kan du gå videre.
2.d.2) Lag nye variabler
Vi skal nå lage varialer for læringsraten app_started i main.py opprett variablene app.learning_rate med verdien 0.8 og variabelen app.discount_factor med verdien 0.5.
2.d.3) Oppdater Q-verdi
Vi skal nå oppdatere Q-verdien når agenten går et steg. I funksjonen timer_fired i main.py rett under linjen direction = decide_direction(...) (og før linjen der du oppdaterer app.agent_pos), oppdater Q-tabellen ved å bruke regelen under.
💡 Hint
- Verdien
$Q(s_t, a_t)$ kan du lese fraapp.q_table[app.agent_pos][direction]. - Verdien
$R(s_t, a_t)$ kan du finne ved å bruke funksjonenreward_functionfralearning.py. - Verdien
$\max_{a\in\mathcal{A}} Q(s_{t+1}, a)$ kan du finne ved å bruke funksjonenget_optimal_future_q_valuefralearning.py. - Husk at
$\alpha$ er lagret iapp.learning_rateog$\gamma$ eller lagret iapp.discount_factor. - Husk å faktisk oppdatere
$Q(s_t, a_t)$ etter å ha regnet ut den nye Q-verdien.
Hvis du kjører main.py skal agenten nå kunne lære via å oppdatere Q-tabellen. Men siden
Vi skal nå oppdatere verdien av app.epsilon) slik at
2.e.1) Lag ny variabel
I funksjonen app_started i filen main.py, lag en ny variabel app.episodes_trained med verdi 0.
2.e.2) Tell episoder og oppdater
I funksjonen timer_fired i filen main.py gjør følgende når agenten havner på en målrute (i tillegg til å resette agentens posisjon):
- Øk verdien av
app.episodes_trainedmed1. - Sett verdien av
app.epsilontil$\frac{1}{2^{1+N/10}}$ hvor$N$ erapp.episodes_trained.
Se figuren nedenfor som illustrerer hvordan verdien av
Spørsmål: Hvordan påvirker denne måten å oppdatere epsilon på valgene til agenten?
Når du nå kjører main.py skal agenten gradvis lære seg å løse labyrinten. Over tid vil agenten nesten alltid benytte Q-tabellen til å velge retning. Etter rundt 20-30 episoder bør agent være rimelig flink til å finne mål.
Dersom agenten ikke ser ut til å lære, prøv følgende:
- Lag en mindre labyrint (for eksempel 5x5 ruter).
- Lag tekst over labyrinten som viser verdien av forskjellige variabler.
- Se nøye over koden din for å forsikre deg om at alt er rett.
- Spør om hjelp.
Hvis alt fungerer som det skal, trykk her for å gå til listen over forslag til utvidelser.
