|
3 weeks ago | |
---|---|---|
.. | ||
solution | 3 weeks ago | |
README.md | 3 weeks ago | |
assignment.md | 3 weeks ago | |
notebook.ipynb | 3 weeks ago |
README.md
Predpoklady
V tejto lekcii budeme používať knižnicu OpenAI Gym na simuláciu rôznych prostredí. Kód z tejto lekcie môžete spustiť lokálne (napr. vo Visual Studio Code), v takom prípade sa simulácia otvorí v novom okne. Pri spúšťaní kódu online môže byť potrebné upraviť kód, ako je popísané tu.
OpenAI Gym
V predchádzajúcej lekcii boli pravidlá hry a stav definované triedou Board
, ktorú sme si sami vytvorili. Tu použijeme špeciálne simulačné prostredie, ktoré bude simulovať fyziku za balansujúcou tyčou. Jedným z najpopulárnejších simulačných prostredí na trénovanie algoritmov posilneného učenia je Gym, ktorý spravuje OpenAI. Pomocou Gym môžeme vytvárať rôzne prostredia, od simulácie CartPole až po hry Atari.
Poznámka: Ďalšie prostredia dostupné v OpenAI Gym si môžete pozrieť tu.
Najskôr nainštalujme Gym a importujme potrebné knižnice (kódový blok 1):
import sys
!{sys.executable} -m pip install gym
import gym
import matplotlib.pyplot as plt
import numpy as np
import random
Cvičenie - inicializácia prostredia CartPole
Aby sme mohli pracovať s problémom balansovania CartPole, musíme inicializovať príslušné prostredie. Každé prostredie je spojené s:
-
Priestorom pozorovaní, ktorý definuje štruktúru informácií, ktoré dostávame z prostredia. Pri probléme CartPole dostávame polohu tyče, rýchlosť a ďalšie hodnoty.
-
Priestorom akcií, ktorý definuje možné akcie. V našom prípade je priestor akcií diskrétny a pozostáva z dvoch akcií - vľavo a vpravo. (kódový blok 2)
-
Na inicializáciu zadajte nasledujúci kód:
env = gym.make("CartPole-v1") print(env.action_space) print(env.observation_space) print(env.action_space.sample())
Aby sme videli, ako prostredie funguje, spustime krátku simuláciu na 100 krokov. Pri každom kroku poskytneme jednu z akcií, ktoré sa majú vykonať - v tejto simulácii náhodne vyberáme akciu z action_space
.
-
Spustite nasledujúci kód a pozrite sa, čo sa stane.
✅ Pamätajte, že je preferované spustiť tento kód na lokálnej inštalácii Pythonu! (kódový blok 3)
env.reset() for i in range(100): env.render() env.step(env.action_space.sample()) env.close()
Mali by ste vidieť niečo podobné ako na tomto obrázku:
-
Počas simulácie musíme získavať pozorovania, aby sme sa rozhodli, ako konať. V skutočnosti funkcia
step
vracia aktuálne pozorovania, funkciu odmeny a príznakdone
, ktorý indikuje, či má zmysel pokračovať v simulácii alebo nie: (kódový blok 4)env.reset() done = False while not done: env.render() obs, rew, done, info = env.step(env.action_space.sample()) print(f"{obs} -> {rew}") env.close()
V notebooku by ste mali vidieť niečo podobné ako toto:
[ 0.03403272 -0.24301182 0.02669811 0.2895829 ] -> 1.0 [ 0.02917248 -0.04828055 0.03248977 0.00543839] -> 1.0 [ 0.02820687 0.14636075 0.03259854 -0.27681916] -> 1.0 [ 0.03113408 0.34100283 0.02706215 -0.55904489] -> 1.0 [ 0.03795414 0.53573468 0.01588125 -0.84308041] -> 1.0 ... [ 0.17299878 0.15868546 -0.20754175 -0.55975453] -> 1.0 [ 0.17617249 0.35602306 -0.21873684 -0.90998894] -> 1.0
Vektor pozorovaní, ktorý sa vracia pri každom kroku simulácie, obsahuje nasledujúce hodnoty:
- Poloha vozíka
- Rýchlosť vozíka
- Uhol tyče
- Rýchlosť rotácie tyče
-
Získajte minimálnu a maximálnu hodnotu týchto čísel: (kódový blok 5)
print(env.observation_space.low) print(env.observation_space.high)
Môžete si tiež všimnúť, že hodnota odmeny pri každom kroku simulácie je vždy 1. Je to preto, že naším cieľom je prežiť čo najdlhšie, t. j. udržať tyč v primerane vertikálnej polohe čo najdlhšie.
✅ V skutočnosti sa simulácia CartPole považuje za vyriešenú, ak sa nám podarí dosiahnuť priemernú odmenu 195 počas 100 po sebe nasledujúcich pokusov.
Diskretizácia stavu
Pri Q-Learningu musíme vytvoriť Q-Tabuľku, ktorá definuje, čo robiť v každom stave. Aby sme to mohli urobiť, potrebujeme, aby bol stav diskrétny, presnejšie, aby obsahoval konečný počet diskrétnych hodnôt. Preto musíme nejako diskretizovať naše pozorovania, mapovať ich na konečnú množinu stavov.
Existuje niekoľko spôsobov, ako to urobiť:
- Rozdelenie na intervaly. Ak poznáme interval určitej hodnoty, môžeme tento interval rozdeliť na niekoľko intervalov a potom nahradiť hodnotu číslom intervalu, do ktorého patrí. To sa dá urobiť pomocou metódy numpy
digitize
. V tomto prípade budeme presne vedieť veľkosť stavu, pretože bude závisieť od počtu intervalov, ktoré vyberieme na digitalizáciu.
✅ Môžeme použiť lineárnu interpoláciu na privedenie hodnôt do určitého konečného intervalu (napríklad od -20 do 20) a potom previesť čísla na celé čísla zaokrúhlením. To nám dáva o niečo menšiu kontrolu nad veľkosťou stavu, najmä ak nepoznáme presné rozsahy vstupných hodnôt. Napríklad v našom prípade 2 zo 4 hodnôt nemajú horné/dolné hranice svojich hodnôt, čo môže viesť k nekonečnému počtu stavov.
V našom príklade použijeme druhý prístup. Ako si neskôr všimnete, napriek nedefinovaným horným/dolným hraniciam tieto hodnoty zriedka nadobúdajú hodnoty mimo určitých konečných intervalov, takže stavy s extrémnymi hodnotami budú veľmi zriedkavé.
-
Tu je funkcia, ktorá vezme pozorovanie z nášho modelu a vytvorí z neho štvoricu 4 celých čísel: (kódový blok 6)
def discretize(x): return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
-
Preskúmajme aj ďalšiu metódu diskretizácie pomocou intervalov: (kódový blok 7)
def create_bins(i,num): return np.arange(num+1)*(i[1]-i[0])/num+i[0] print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10)) ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter nbins = [20,20,10,10] # number of bins for each parameter bins = [create_bins(ints[i],nbins[i]) for i in range(4)] def discretize_bins(x): return tuple(np.digitize(x[i],bins[i]) for i in range(4))
-
Teraz spustime krátku simuláciu a pozorujme tieto diskrétne hodnoty prostredia. Skúste použiť
discretize
ajdiscretize_bins
a zistite, či je medzi nimi rozdiel.✅
discretize_bins
vracia číslo intervalu, ktoré je 0-based. Takže pre hodnoty vstupnej premennej okolo 0 vracia číslo zo stredu intervalu (10). Pridiscretize
sme sa nestarali o rozsah výstupných hodnôt, umožnili sme im byť záporné, takže hodnoty stavu nie sú posunuté a 0 zodpovedá 0. (kódový blok 8)env.reset() done = False while not done: #env.render() obs, rew, done, info = env.step(env.action_space.sample()) #print(discretize_bins(obs)) print(discretize(obs)) env.close()
✅ Odkomentujte riadok začínajúci
env.render
, ak chcete vidieť, ako sa prostredie vykonáva. Inak ho môžete spustiť na pozadí, čo je rýchlejšie. Tento "neviditeľný" spôsob vykonávania použijeme počas procesu Q-Learningu.
Štruktúra Q-Tabuľky
V našej predchádzajúcej lekcii bol stav jednoduchou dvojicou čísel od 0 do 8, a preto bolo pohodlné reprezentovať Q-Tabuľku pomocou numpy tenzora s tvarom 8x8x2. Ak použijeme diskretizáciu pomocou intervalov, veľkosť nášho stavového vektora je tiež známa, takže môžeme použiť rovnaký prístup a reprezentovať stav pomocou poľa s tvarom 20x20x10x10x2 (tu 2 je dimenzia priestoru akcií a prvé dimenzie zodpovedajú počtu intervalov, ktoré sme vybrali na použitie pre každú z parametrov v priestore pozorovaní).
Avšak niekedy presné dimenzie priestoru pozorovaní nie sú známe. V prípade funkcie discretize
si nikdy nemôžeme byť istí, že náš stav zostane v určitých hraniciach, pretože niektoré z pôvodných hodnôt nie sú ohraničené. Preto použijeme trochu iný prístup a reprezentujeme Q-Tabuľku pomocou slovníka.
-
Použite dvojicu (state,action) ako kľúč slovníka a hodnota by zodpovedala hodnote záznamu Q-Tabuľky. (kódový blok 9)
Q = {} actions = (0,1) def qvalues(state): return [Q.get((state,a),0) for a in actions]
Tu tiež definujeme funkciu
qvalues()
, ktorá vracia zoznam hodnôt Q-Tabuľky pre daný stav, ktorý zodpovedá všetkým možným akciám. Ak záznam nie je prítomný v Q-Tabuľke, vrátime 0 ako predvolenú hodnotu.
Začnime Q-Learning
Teraz sme pripravení naučiť Petra balansovať!
-
Najskôr nastavme niektoré hyperparametre: (kódový blok 10)
# hyperparameters alpha = 0.3 gamma = 0.9 epsilon = 0.90
Tu
alpha
je rýchlosť učenia, ktorá určuje, do akej miery by sme mali upraviť aktuálne hodnoty Q-Tabuľky pri každom kroku. V predchádzajúcej lekcii sme začali s hodnotou 1 a potom sme znižovalialpha
na nižšie hodnoty počas tréningu. V tomto príklade ju ponecháme konštantnú len pre jednoduchosť a môžete experimentovať s úpravou hodnôtalpha
neskôr.gamma
je faktor diskontovania, ktorý ukazuje, do akej miery by sme mali uprednostniť budúcu odmenu pred aktuálnou odmenou.epsilon
je faktor prieskumu/využitia, ktorý určuje, či by sme mali uprednostniť prieskum pred využitím alebo naopak. V našom algoritme vepsilon
percentách prípadov vyberieme ďalšiu akciu podľa hodnôt Q-Tabuľky a v zostávajúcom počte prípadov vykonáme náhodnú akciu. To nám umožní preskúmať oblasti vyhľadávacieho priestoru, ktoré sme nikdy predtým nevideli.✅ Z hľadiska balansovania - výber náhodnej akcie (prieskum) by pôsobil ako náhodný úder nesprávnym smerom a tyč by sa musela naučiť, ako obnoviť rovnováhu z týchto "chýb".
Zlepšenie algoritmu
Môžeme tiež urobiť dve vylepšenia nášho algoritmu z predchádzajúcej lekcie:
-
Vypočítať priemernú kumulatívnu odmenu počas niekoľkých simulácií. Pokrok budeme tlačiť každých 5000 iterácií a priemernú kumulatívnu odmenu vypočítame za toto obdobie. To znamená, že ak získame viac ako 195 bodov, môžeme problém považovať za vyriešený, dokonca s vyššou kvalitou, než je požadovaná.
-
Vypočítať maximálny priemerný kumulatívny výsledok,
Qmax
, a uložíme Q-Tabuľku zodpovedajúcu tomuto výsledku. Keď spustíte tréning, všimnete si, že niekedy priemerný kumulatívny výsledok začne klesať, a chceme si ponechať hodnoty Q-Tabuľky, ktoré zodpovedajú najlepšiemu modelu pozorovanému počas tréningu.
-
Zbierajte všetky kumulatívne odmeny pri každej simulácii do vektora
rewards
na ďalšie vykreslenie. (kódový blok 11)def probs(v,eps=1e-4): v = v-v.min()+eps v = v/v.sum() return v Qmax = 0 cum_rewards = [] rewards = [] for epoch in range(100000): obs = env.reset() done = False cum_reward=0 # == do the simulation == while not done: s = discretize(obs) if random.random()<epsilon: # exploitation - chose the action according to Q-Table probabilities v = probs(np.array(qvalues(s))) a = random.choices(actions,weights=v)[0] else: # exploration - randomly chose the action a = np.random.randint(env.action_space.n) obs, rew, done, info = env.step(a) cum_reward+=rew ns = discretize(obs) Q[(s,a)] = (1 - alpha) * Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns))) cum_rewards.append(cum_reward) rewards.append(cum_reward) # == Periodically print results and calculate average reward == if epoch%5000==0: print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}") if np.average(cum_rewards) > Qmax: Qmax = np.average(cum_rewards) Qbest = Q cum_rewards=[]
Čo si môžete všimnúť z týchto výsledkov:
-
Blízko nášho cieľa. Sme veľmi blízko k dosiahnutiu cieľa získania 195 kumulatívnych odmien počas 100+ po sebe nasledujúcich simulácií, alebo sme ho možno už dosiahli! Aj keď získame menšie čísla, stále to nevieme, pretože priemerujeme cez 5000 pokusov a formálne kritérium vyžaduje iba 100 pokusov.
-
Odmena začína klesať. Niekedy odmena začne klesať, čo znamená, že môžeme "zničiť" už naučené hodnoty v Q-Tabuľke tými, ktoré situáciu zhoršujú.
Toto pozorovanie je jasnejšie viditeľné, ak vykreslíme pokrok tréningu.
Vykreslenie pokroku tréningu
Počas tréningu sme zbierali hodnotu kumulatívnej odmeny pri každej iterácii do vektora rewards
. Takto to vyzerá, keď to vykreslíme proti číslu iterácie:
plt.plot(rewards)
Z tohto grafu nie je možné nič povedať, pretože kvôli povahe stochastického tréningového procesu sa dĺžka tréningových relácií veľmi líši. Aby sme tento graf urobili zrozumiteľnejším, môžeme vypočítať bežiaci priemer cez sériu experimentov, povedzme 100. To sa dá pohodlne urobiť pomocou np.convolve
: (kódový blok 12)
def running_average(x,window):
return np.convolve(x,np.ones(window)/window,mode='valid')
plt.plot(running_average(rewards,100))
Zmena hyperparametrov
Aby bolo učenie stabilnejšie, má zmysel upraviť niektoré z našich hyperparametrov počas tréningu. Konkrétne:
-
Pre rýchlosť učenia,
alpha
, môžeme začať s hodnotami blízkymi 1 a potom postupne znižovať parameter. S časom budeme získavať dobré pravdepodobnostné hodnoty v Q-Tabuľke, a preto by sme ich mali upravovať mierne, a nie úplne prepisovať novými hodnotami. -
Zvýšiť epsilon. Môžeme chcieť pomaly zvyšovať
epsilon
, aby sme menej skúmali a viac využívali. Pravdepodobne má zmysel začať s nižšou hodnotouepsilon
a postupne ju zvýšiť takmer na 1.
Úloha 1: Skúste experimentovať s hodnotami hyperparametrov a zistite, či dokážete dosiahnuť vyššiu kumulatívnu odmenu. Dosahujete viac ako 195? Úloha 2: Na formálne vyriešenie problému je potrebné dosiahnuť priemernú odmenu 195 počas 100 po sebe idúcich spustení. Merajte to počas tréningu a uistite sa, že ste problém formálne vyriešili!
Vidieť výsledok v akcii
Bolo by zaujímavé vidieť, ako sa vyškolený model správa. Spustime simuláciu a použime rovnakú stratégiu výberu akcií ako počas tréningu, pričom vzorkujeme podľa pravdepodobnostného rozdelenia v Q-Tabuľke: (blok kódu 13)
obs = env.reset()
done = False
while not done:
s = discretize(obs)
env.render()
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
obs,_,done,_ = env.step(a)
env.close()
Mali by ste vidieť niečo takéto:
🚀Výzva
Úloha 3: Tu sme používali finálnu verziu Q-Tabuľky, ktorá nemusí byť najlepšia. Pamätajte, že sme uložili najlepšie fungujúcu Q-Tabuľku do premennej
Qbest
! Skúste ten istý príklad s najlepšie fungujúcou Q-Tabuľkou tak, že skopírujeteQbest
doQ
a sledujte, či si všimnete rozdiel.
Úloha 4: Tu sme nevyberali najlepšiu akciu v každom kroku, ale namiesto toho vzorkovali podľa zodpovedajúceho pravdepodobnostného rozdelenia. Malo by väčší zmysel vždy vybrať najlepšiu akciu s najvyššou hodnotou v Q-Tabuľke? To sa dá urobiť pomocou funkcie
np.argmax
, ktorá nájde číslo akcie zodpovedajúce najvyššej hodnote v Q-Tabuľke. Implementujte túto stratégiu a sledujte, či zlepší balansovanie.
Kvíz po prednáške
Zadanie
Záver
Teraz sme sa naučili, ako trénovať agentov na dosiahnutie dobrých výsledkov len tým, že im poskytneme funkciu odmeny, ktorá definuje požadovaný stav hry, a dáme im príležitosť inteligentne preskúmať priestor možností. Úspešne sme aplikovali algoritmus Q-Learning v prípadoch diskrétnych a spojitých prostredí, ale s diskrétnymi akciami.
Je dôležité študovať aj situácie, kde je stav akcií spojitý a kde je priestor pozorovaní oveľa zložitejší, napríklad obrazovka z hry Atari. Pri takýchto problémoch často potrebujeme použiť výkonnejšie techniky strojového učenia, ako sú neurónové siete, aby sme dosiahli dobré výsledky. Tieto pokročilejšie témy sú predmetom nášho nadchádzajúceho pokročilého kurzu AI.
Upozornenie:
Tento dokument bol preložený pomocou služby AI prekladu Co-op Translator. Hoci sa snažíme o presnosť, prosím, berte na vedomie, že automatizované preklady môžu obsahovať chyby alebo nepresnosti. Pôvodný dokument v jeho pôvodnom jazyku by mal byť považovaný za autoritatívny zdroj. Pre kritické informácie sa odporúča profesionálny ľudský preklad. Nie sme zodpovední za akékoľvek nedorozumenia alebo nesprávne interpretácie vyplývajúce z použitia tohto prekladu.