29 KiB
Проблем са клизањем на шипци
Проблем који смо решавали у претходној лекцији можда изгледа као играчка, без стварне примене у реалним сценаријима. Међутим, то није случај, јер многи проблеми из стварног живота деле сличан сценарио – укључујући играње шаха или игре Го. Они су слични јер такође имамо таблу са одређеним правилима и дискретно стање.
Квиз пре предавања
Увод
У овој лекцији применићемо исте принципе Q-учења на проблем са континуалним стањем, односно стањем које је дефинисано једним или више реалних бројева. Бавићемо се следећим проблемом:
Проблем: Ако Петар жели да побегне од вука, мора бити у стању да се креће брже. Видећемо како Петар може научити да клиза, конкретно, како да одржава равнотежу, користећи Q-учење.
Петар и његови пријатељи постају креативни како би побегли од вука! Слика: Џен Лупер
Користићемо поједностављену верзију одржавања равнотеже познату као проблем CartPole. У свету CartPole-а имамо хоризонтални клизач који се може кретати лево или десно, а циљ је одржати вертикалну шипку у равнотежи на врху клизача.
Предуслови
У овој лекцији користићемо библиотеку OpenAI Gym за симулацију различитих окружења. Код ове лекције можете покренути локално (нпр. из Visual Studio Code-а), у ком случају ће се симулација отворити у новом прозору. Када покрећете код онлајн, можда ћете морати да направите неке измене у коду, као што је описано овде.
OpenAI Gym
У претходној лекцији, правила игре и стање су били дефинисани унутар класе Board
коју смо сами креирали. Овде ћемо користити специјално симулационо окружење, које ће симулирати физику иза равнотеже шипке. Једно од најпопуларнијих симулационих окружења за тренирање алгоритама за појачано учење је Gym, који одржава OpenAI. Коришћењем овог Gym-а можемо креирати различита окружења, од симулације CartPole-а до Atari игара.
Напомена: Остала окружења доступна у OpenAI Gym-у можете видети овде.
Прво, инсталирајмо Gym и увезимо потребне библиотеке (блок кода 1):
import sys
!{sys.executable} -m pip install gym
import gym
import matplotlib.pyplot as plt
import numpy as np
import random
Вежба – иницијализација окружења CartPole
Да бисмо радили са проблемом равнотеже шипке, потребно је да иницијализујемо одговарајуће окружење. Свако окружење је повезано са:
-
Простором посматрања који дефинише структуру информација које добијамо из окружења. За проблем CartPole-а, добијамо позицију шипке, брзину и неке друге вредности.
-
Простором акција који дефинише могуће акције. У нашем случају, простор акција је дискретан и састоји се од две акције – лево и десно. (блок кода 2)
-
Да бисмо иницијализовали окружење, укуцајте следећи код:
env = gym.make("CartPole-v1") print(env.action_space) print(env.observation_space) print(env.action_space.sample())
Да бисмо видели како окружење функционише, покренимо кратку симулацију од 100 корака. На сваком кораку, задајемо једну од акција које треба предузети – у овој симулацији насумично бирамо акцију из action_space
.
-
Покрените код испод и видите шта се дешава.
✅ Запамтите да је пожељно покренути овај код на локалној Python инсталацији! (блок кода 3)
env.reset() for i in range(100): env.render() env.step(env.action_space.sample()) env.close()
Требало би да видите нешто слично овој слици:
-
Током симулације, потребно је да добијемо посматрања како бисмо одлучили како да делујемо. У ствари, функција
step
враћа тренутна посматрања, функцију награде и заставицуdone
која указује да ли има смисла наставити симулацију или не: (блок кода 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()
У излазу у бележници требало би да видите нешто овако:
[ 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
Вектор посматрања који се враћа на сваком кораку симулације садржи следеће вредности:
- Позиција колица
- Брзина колица
- Угао шипке
- Брзина ротације шипке
-
Добијте минималну и максималну вредност ових бројева: (блок кода 5)
print(env.observation_space.low) print(env.observation_space.high)
Такође можете приметити да је вредност награде на сваком кораку симулације увек 1. То је зато што је наш циљ да преживимо што је дуже могуће, односно да одржимо шипку у разумно вертикалном положају што дуже.
✅ У ствари, симулација CartPole-а се сматра решеном ако успемо да добијемо просечну награду од 195 током 100 узастопних покушаја.
Дискретизација стања
У Q-учењу, потребно је изградити Q-табелу која дефинише шта радити у сваком стању. Да бисмо то урадили, стање мора бити дискретно, прецизније, мора садржати коначан број дискретних вредности. Због тога је потребно некако дискретизовати наша посматрања, мапирајући их на коначан скуп стања.
Постоји неколико начина да се то уради:
- Подела на интервале. Ако знамо интервал одређене вредности, можемо поделити тај интервал на одређени број интервала, а затим заменити вредност бројем интервала коме припада. Ово се може урадити помоћу numpy методе
digitize
. У овом случају, тачно ћемо знати величину стања, јер ће зависити од броја интервала које изаберемо за дигитализацију.
✅ Можемо користити линеарну интерполацију да доведемо вредности у неки коначан интервал (рецимо, од -20 до 20), а затим претворити бројеве у целе бројеве заокруживањем. Ово нам даје нешто мању контролу над величином стања, посебно ако не знамо тачне опсеге улазних вредности. На пример, у нашем случају 2 од 4 вредности немају горње/доње границе, што може резултирати бесконачним бројем стања.
У нашем примеру, користићемо други приступ. Како ћете касније приметити, упркос неодређеним горњим/доњим границама, те вредности ретко прелазе одређене коначне интервале, тако да ће та стања са екстремним вредностима бити веома ретка.
-
Ево функције која ће узети посматрање из нашег модела и произвести четворку целих бројева: (блок кода 6)
def discretize(x): return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
-
Такође истражимо други метод дискретизације користећи интервале: (блок кода 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))
-
Сада покренимо кратку симулацију и посматрајмо те дискретне вредности окружења. Слободно испробајте и
discretize
иdiscretize_bins
и видите да ли постоји разлика.✅
discretize_bins
враћа број интервала, који почиње од 0. Дакле, за вредности улазне променљиве око 0 враћа број из средине интервала (10). Уdiscretize
, нисмо се бринули о опсегу излазних вредности, дозвољавајући им да буду негативне, тако да вредности стања нису померене, а 0 одговара 0. (блок кода 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()
✅ Раскоментаришите линију која почиње са
env.render
ако желите да видите како се окружење извршава. У супротном, можете га извршити у позадини, што је брже. Ово "невидљиво" извршавање ћемо користити током процеса Q-учења.
Структура Q-табеле
У претходној лекцији, стање је било једноставан пар бројева од 0 до 8, и стога је било згодно представити Q-табелу помоћу numpy тензора облика 8x8x2. Ако користимо дискретизацију интервала, величина нашег векторског стања је такође позната, тако да можемо користити исти приступ и представити стање низом облика 20x20x10x10x2 (овде је 2 димензија простора акција, а прве димензије одговарају броју интервала које смо изабрали за сваку од параметара у простору посматрања).
Међутим, понекад прецизне димензије простора посматрања нису познате. У случају функције discretize
, никада не можемо бити сигурни да наше стање остаје унутар одређених граница, јер неке од оригиналних вредности нису ограничене. Због тога ћемо користити мало другачији приступ и представити Q-табелу помоћу речника.
-
Користите пар (state, action) као кључ речника, а вредност би одговарала вредности уноса Q-табеле. (блок кода 9)
Q = {} actions = (0,1) def qvalues(state): return [Q.get((state,a),0) for a in actions]
Овде такође дефинишемо функцију
qvalues()
, која враћа листу вредности Q-табеле за дато стање које одговарају свим могућим акцијама. Ако унос није присутан у Q-табели, вратићемо 0 као подразумевану вредност.
Почнимо са Q-учењем
Сада смо спремни да научимо Петра како да одржава равнотежу!
-
Прво, подесимо неке хиперпараметре: (блок кода 10)
# hyperparameters alpha = 0.3 gamma = 0.9 epsilon = 0.90
Овде је
alpha
стопа учења која дефинише у којој мери треба да прилагодимо тренутне вредности Q-табеле на сваком кораку. У претходној лекцији почели смо са 1, а затим смањилиalpha
на ниже вредности током тренинга. У овом примеру ћемо га држати константним ради једноставности, а ви можете експериментисати са прилагођавањем вредностиalpha
касније.gamma
је фактор попуста који показује у којој мери треба да приоритет дамо будућој награди у односу на тренутну награду.epsilon
је фактор истраживања/експлоатације који одређује да ли треба да преферирамо истраживање у односу на експлоатацију или обрнуто. У нашем алгоритму, уepsilon
проценту случајева изабраћемо следећу акцију према вредностима Q-табеле, а у преосталом броју случајева извршићемо насумичну акцију. Ово ће нам омогућити да истражимо области претраживачког простора које раније нисмо видели.✅ У смислу равнотеже – одабир насумичне акције (истраживање) деловао би као насумичан ударац у погрешном смеру, а шипка би морала да научи како да поврати равнотежу из тих "грешака".
Побољшање алгоритма
Можемо направити два побољшања нашег алгоритма из претходне лекције:
-
Израчунајте просечну кумулативну награду, током одређеног броја симулација. Штампаћемо напредак сваких 5000 итерација и просечити нашу кумулативну награду током тог периода. То значи да ако добијемо више од 195 поена – можемо сматрати да је проблем решен, и то са још већим квалитетом него што је потребно.
-
Израчунајте максимални просечни кумулативни резултат,
Qmax
, и сачуваћемо Q-табелу која одговара том резултату. Када покренете тренинг, приметићете да понекад просечни кумулативни резултат почиње да опада, и желимо да задржимо вредности Q-табеле које одговарају најбољем моделу посматраном током тренинга.
-
Сакупите све кумулативне награде у свакој симулацији у вектору
rewards
за даље графичко приказивање. (блок кода 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=[]
Шта можете приметити из ових резултата:
-
Близу нашег циља. Веома смо близу постизања циља од 195 кумулативних награда током 100+ узастопних покушаја симулације, или смо га можда већ постигли! Чак и ако добијемо мање бројеве, и даље не знамо, јер просечимо преко 5000 покушаја, а само 100 покушаја је потребно према формалним критеријумима.
-
Награда почиње да опада. Понекад награда почиње да опада, што значи да можемо "уништавати" већ научене вредности у Q-табели са онима које погоршавају ситуацију.
Ово запажање је јасније видљиво ако графички прикажемо напредак тренинга.
Графички приказ напретка тренинга
Током тренинга, сакупили смо вредност кумулативне награде у свакој од итерација у вектору rewards
. Ево како изгледа када га графички прикажемо у односу на број итерација:
plt.plot(rewards)
Из овог графика није могуће закључити ништа, јер због природе стохастичког процеса тренинга дужина сесија тренинга варира у великој мери. Да бисмо овај график учинили смисленијим, можемо израчунати покретни просек током серије експеримената, рецимо 100. Ово се може згодно урадити помоћу np.convolve
: (блок кода 12)
def running_average(x,window):
return np.convolve(x,np.ones(window)/window,mode='valid')
plt.plot(running_average(rewards,100))
Променљиви хиперпараметри
Да би учење било стабилније, има смисла прилагодити неке од наших хиперпараметара током тренинга. Конкретно:
-
За стопу учења,
alpha
, можемо почети са вредностима блиским 1, а затим постепено смањивати параметар. Временом ћемо добијати добре вредности вероватноће у Q-табели, и стога бисмо их требали благо прилагођавати, а не потпуно преписивати новим вредностима. -
Повећајте epsilon. Можда ћ
Задатак 1: Испробајте различите вредности хиперпараметара и проверите да ли можете постићи већи кумулативни наградни резултат. Да ли добијате резултат изнад 195? Задатак 2: Да бисте формално решили проблем, потребно је да постигнете просечну награду од 195 током 100 узастопних покретања. Мерите то током тренинга и уверите се да сте формално решили проблем!
Гледање резултата у пракси
Било би занимљиво видети како се обучени модел понаша. Покренимо симулацију и пратимо исту стратегију избора акција као током тренинга, узоркујући према расподели вероватноће у Q-табели: (блок кода 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()
Требало би да видите нешто овако:
🚀Изазов
Задатак 3: Овде смо користили финалну копију Q-табеле, која можда није најбоља. Запамтите да смо сачували најбоље перформантну Q-табелу у променљиву
Qbest
! Пробајте исти пример са најбољом Q-табелом тако што ћете копиратиQbest
уQ
и проверите да ли примећујете разлику.
Задатак 4: Овде нисмо бирали најбољу акцију у сваком кораку, већ смо узорковали према одговарајућој расподели вероватноће. Да ли би имало више смисла увек бирати најбољу акцију, са највишом вредношћу у Q-табели? Ово се може урадити коришћењем функције
np.argmax
да се пронађе број акције који одговара највишој вредности у Q-табели. Имплементирајте ову стратегију и проверите да ли побољшава балансирање.
Квиз након предавања
Задатак
Закључак
Сада смо научили како да обучимо агенте да постигну добре резултате само пружањем функције награде која дефинише жељено стање игре и давањем могућности да интелигентно истражују простор претраге. Успешно смо применили Q-Learning алгоритам у случајевима дискретних и континуалних окружења, али са дискретним акцијама.
Важно је такође проучити ситуације у којима је простор акција такође континуалан, као и када је простор посматрања много сложенији, попут слике са екрана Atari игре. У тим проблемима често је потребно користити моћније технике машинског учења, као што су неуронске мреже, како би се постигли добри резултати. Те напредније теме биће предмет нашег наредног, напреднијег курса о вештачкој интелигенцији.
Одрицање од одговорности:
Овај документ је преведен коришћењем услуге за превођење помоћу вештачке интелигенције Co-op Translator. Иако настојимо да обезбедимо тачност, молимо вас да имате у виду да аутоматски преводи могу садржати грешке или нетачности. Оригинални документ на изворном језику треба сматрати ауторитативним извором. За критичне информације препоручује се професионални превод од стране људи. Не сносимо одговорност за било каква погрешна тумачења или неспоразуме који могу произаћи из коришћења овог превода.