You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ML-For-Beginners/translations/cs/8-Reinforcement/2-Gym
leestott e4050807fb
🌐 Update translations via Co-op Translator
2 weeks ago
..
solution 🌐 Update translations via Co-op Translator 2 weeks ago
README.md 🌐 Update translations via Co-op Translator 2 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 2 weeks ago
notebook.ipynb 🌐 Update translations via Co-op Translator 2 weeks ago

README.md

CartPole Bruslení

Problém, který jsme řešili v předchozí lekci, se může zdát jako hračka, která nemá skutečné využití v reálných situacích. To však není pravda, protože mnoho reálných problémů má podobný scénář například hraní šachů nebo Go. Jsou podobné, protože také máme hrací desku s danými pravidly a diskrétní stav.

Kvíz před lekcí

Úvod

V této lekci použijeme stejné principy Q-Learningu na problém s kontinuálním stavem, tj. stavem, který je definován jedním nebo více reálnými čísly. Budeme se zabývat následujícím problémem:

Problém: Pokud chce Petr utéct vlkovi, musí se naučit pohybovat rychleji. Uvidíme, jak se Petr může naučit bruslit, konkrétně udržovat rovnováhu, pomocí Q-Learningu.

Velký útěk!

Petr a jeho přátelé jsou kreativní, aby unikli vlkovi! Obrázek od Jen Looper

Použijeme zjednodušenou verzi udržování rovnováhy známou jako problém CartPole. Ve světě CartPole máme horizontální jezdec, který se může pohybovat doleva nebo doprava, a cílem je udržet vertikální tyč na vrcholu jezdce.

Předpoklady

V této lekci budeme používat knihovnu OpenAI Gym k simulaci různých prostředí. Kód této lekce můžete spustit lokálně (např. z Visual Studio Code), v takovém případě se simulace otevře v novém okně. Při spuštění kódu online může být nutné provést některé úpravy kódu, jak je popsáno zde.

OpenAI Gym

V předchozí lekci byly pravidla hry a stav definovány třídou Board, kterou jsme si sami vytvořili. Zde použijeme speciální simulační prostředí, které bude simulovat fyziku za udržováním rovnováhy tyče. Jedno z nejpopulárnějších simulačních prostředí pro trénování algoritmů posilovaného učení se nazývá Gym, které spravuje OpenAI. Pomocí tohoto gymu můžeme vytvořit různá prostředí od simulace CartPole až po hry Atari.

Poznámka: Další prostředí dostupná v OpenAI Gym si můžete prohlédnout zde.

Nejprve nainstalujeme gym a importujeme potřebné knihovny (blok kódu 1):

import sys
!{sys.executable} -m pip install gym 

import gym
import matplotlib.pyplot as plt
import numpy as np
import random

Cvičení inicializace prostředí CartPole

Pro práci s problémem udržování rovnováhy CartPole musíme inicializovat odpovídající prostředí. Každé prostředí je spojeno s:

  • Prostor pozorování, který definuje strukturu informací, které získáváme z prostředí. U problému CartPole získáváme polohu tyče, rychlost a některé další hodnoty.

  • Prostor akcí, který definuje možné akce. V našem případě je prostor akcí diskrétní a skládá se ze dvou akcí doleva a doprava. (blok kódu 2)

  1. Pro inicializaci napište následující kód:

    env = gym.make("CartPole-v1")
    print(env.action_space)
    print(env.observation_space)
    print(env.action_space.sample())
    

Abychom viděli, jak prostředí funguje, spusťme krátkou simulaci na 100 kroků. V každém kroku poskytujeme jednu z akcí, které mají být provedeny v této simulaci náhodně vybíráme akci z action_space.

  1. Spusťte níže uvedený kód a podívejte se, k čemu vede.

    Pamatujte, že je preferováno spustit tento kód na lokální instalaci Pythonu! (blok kódu 3)

    env.reset()
    
    for i in range(100):
       env.render()
       env.step(env.action_space.sample())
    env.close()
    

    Měli byste vidět něco podobného tomuto obrázku:

    nevyvážený CartPole

  2. Během simulace potřebujeme získat pozorování, abychom rozhodli, jak jednat. Ve skutečnosti funkce step vrací aktuální pozorování, funkci odměny a příznak done, který označuje, zda má smysl pokračovat v simulaci nebo ne: (blok kódu 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 byste měli vidět něco podobného tomuto výstupu:

    [ 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 pozorování, který je vrácen při každém kroku simulace, obsahuje následující hodnoty:

    • Poloha vozíku
    • Rychlost vozíku
    • Úhel tyče
    • Rychlost otáčení tyče
  3. Získejte minimální a maximální hodnotu těchto čísel: (blok kódu 5)

    print(env.observation_space.low)
    print(env.observation_space.high)
    

    Můžete si také všimnout, že hodnota odměny při každém kroku simulace je vždy 1. To je proto, že naším cílem je přežít co nejdéle, tj. udržet tyč v přiměřeně vertikální poloze po co nejdelší dobu.

    Ve skutečnosti je simulace CartPole považována za vyřešenou, pokud se nám podaří získat průměrnou odměnu 195 během 100 po sobě jdoucích pokusů.

Diskretizace stavu

V Q-Learningu potřebujeme vytvořit Q-Tabulku, která definuje, co dělat v každém stavu. Abychom toho dosáhli, musí být stav diskrétní, přesněji řečeno, musí obsahovat konečný počet diskrétních hodnot. Proto musíme nějak diskretizovat naše pozorování, mapovat je na konečnou množinu stavů.

Existuje několik způsobů, jak to udělat:

  • Rozdělení na intervaly. Pokud známe interval určité hodnoty, můžeme tento interval rozdělit na několik intervalů a poté nahradit hodnotu číslem intervalu, do kterého patří. To lze provést pomocí metody numpy digitize. V tomto případě budeme přesně znát velikost stavu, protože bude záviset na počtu intervalů, které vybereme pro digitalizaci.

Můžeme použít lineární interpolaci k přivedení hodnot na nějaký konečný interval (např. od -20 do 20) a poté převést čísla na celá čísla zaokrouhlením. To nám dává o něco menší kontrolu nad velikostí stavu, zejména pokud neznáme přesné rozsahy vstupních hodnot. Například v našem případě 2 ze 4 hodnot nemají horní/dolní hranice svých hodnot, což může vést k nekonečnému počtu stavů.

V našem příkladu použijeme druhý přístup. Jak si možná později všimnete, navzdory nedefinovaným horním/dolním hranicím tyto hodnoty zřídka nabývají hodnot mimo určité konečné intervaly, takže tyto stavy s extrémními hodnotami budou velmi vzácné.

  1. Zde je funkce, která vezme pozorování z našeho modelu a vytvoří čtveřici 4 celých čísel: (blok kódu 6)

    def discretize(x):
        return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int))
    
  2. Prozkoumejme také jinou metodu diskretizace pomocí intervalů: (blok kódu 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))
    
  3. Nyní spusťme krátkou simulaci a pozorujme tyto diskrétní hodnoty prostředí. Vyzkoušejte discretize i discretize_bins a podívejte se, zda je mezi nimi rozdíl.

    discretize_bins vrací číslo intervalu, které je 0-based. Takže pro hodnoty vstupní proměnné kolem 0 vrací číslo ze středu intervalu (10). U discretize jsme se nestarali o rozsah výstupních hodnot, což umožňuje, aby byly negativní, takže hodnoty stavu nejsou posunuté a 0 odpovídá 0. (blok kódu 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 řádek začínající env.render, pokud chcete vidět, jak prostředí funguje. Jinak jej můžete spustit na pozadí, což je rychlejší. Tento "neviditelný" způsob provádění použijeme během procesu Q-Learningu.

Struktura Q-Tabulky

V naší předchozí lekci byl stav jednoduchý pár čísel od 0 do 8, a proto bylo pohodlné reprezentovat Q-Tabulku pomocí numpy tenzoru s tvarem 8x8x2. Pokud použijeme diskretizaci pomocí intervalů, velikost našeho stavového vektoru je také známá, takže můžeme použít stejný přístup a reprezentovat stav pomocí pole tvaru 20x20x10x10x2 (zde 2 je dimenze prostoru akcí a první dimenze odpovídají počtu intervalů, které jsme vybrali pro každou z hodnot v prostoru pozorování).

Nicméně někdy přesné rozměry prostoru pozorování nejsou známé. V případě funkce discretize si nikdy nemůžeme být jisti, že náš stav zůstane v určitých mezích, protože některé z původních hodnot nejsou omezené. Proto použijeme mírně odlišný přístup a reprezentujeme Q-Tabulku pomocí slovníku.

  1. Použijte dvojici (state,action) jako klíč slovníku a hodnota by odpovídala hodnotě položky Q-Tabulky. (blok kódu 9)

    Q = {}
    actions = (0,1)
    
    def qvalues(state):
        return [Q.get((state,a),0) for a in actions]
    

    Zde také definujeme funkci qvalues(), která vrací seznam hodnot Q-Tabulky pro daný stav, který odpovídá všem možným akcím. Pokud položka není přítomna v Q-Tabulce, vrátíme 0 jako výchozí hodnotu.

Začněme Q-Learning

Nyní jsme připraveni naučit Petra udržovat rovnováhu!

  1. Nejprve nastavme některé hyperparametry: (blok kódu 10)

    # hyperparameters
    alpha = 0.3
    gamma = 0.9
    epsilon = 0.90
    

    Zde alpha je rychlost učení, která určuje, do jaké míry bychom měli upravit aktuální hodnoty Q-Tabulky při každém kroku. V předchozí lekci jsme začali s hodnotou 1 a poté jsme alpha snížili na nižší hodnoty během tréninku. V tomto příkladu ji ponecháme konstantní jen pro jednoduchost, a můžete experimentovat s úpravou hodnot alpha později.

    gamma je faktor diskontování, který ukazuje, do jaké míry bychom měli upřednostňovat budoucí odměnu před aktuální odměnou.

    epsilon je faktor průzkumu/využití, který určuje, zda bychom měli preferovat průzkum před využitím nebo naopak. V našem algoritmu budeme v epsilon procentech případů vybírat další akci podle hodnot Q-Tabulky a ve zbývajícím počtu případů provedeme náhodnou akci. To nám umožní prozkoumat oblasti prostoru hledání, které jsme dosud neviděli.

    Z hlediska udržování rovnováhy výběr náhodné akce (průzkum) by působil jako náhodný úder špatným směrem a tyč by se musela naučit, jak obnovit rovnováhu z těchto "chyb".

Vylepšení algoritmu

Můžeme také provést dvě vylepšení našeho algoritmu z předchozí lekce:

  • Výpočet průměrné kumulativní odměny během několika simulací. Pokrok budeme tisknout každých 5000 iterací a průměrnou kumulativní odměnu za toto období zprůměrujeme. To znamená, že pokud získáme více než 195 bodů, můžeme problém považovat za vyřešený, a to s ještě vyšší kvalitou, než je požadováno.

  • Výpočet maximálního průměrného kumulativního výsledku, Qmax, a uložíme Q-Tabulku odpovídající tomuto výsledku. Když spustíte trénink, všimnete si, že někdy průměrný kumulativní výsledek začne klesat, a chceme si uchovat hodnoty Q-Tabulky, které odpovídají nejlepšímu modelu pozorovanému během tréninku.

  1. Sbírejte všechny kumulativní odměny při každé simulaci do vektoru rewards pro další vykreslení. (blok kódu 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=[]
    

Co si můžete všimnout z těchto výsledků:

  • Blízko našeho cíle. Jsme velmi blízko dosažení cíle získat 195 kumulativních odměn během 100+ po sobě jdoucích běhů simulace, nebo jsme toho možná již dosáhli! I když získáme menší čísla, stále to nevíme, protože průměrujeme přes 5000 běhů a pouze 100 běhů je požadováno v rámci formálních kritérií.

  • Odměna začíná klesat. Někdy odměna začne klesat, což znamená, že můžeme "zničit" již naučené hodnoty v Q-Tabulce těmi, které situaci zhoršují.

Toto pozorování je jasněji viditelné, pokud vykreslíme průběh tréninku.

Vykreslení průběhu tréninku

Během tréninku jsme sbírali hodnotu kumulativní odměny při každé iteraci do vektoru rewards. Takto to vypadá, když to vykreslíme proti číslu iterace:

plt.plot(rewards)

surový průběh

Z tohoto grafu není možné nic říct, protože kvůli povaze stochastického procesu tréninku se délka tréninkových sezení značně liší. Aby měl tento graf větší smysl, můžeme vypočítat běžný průměr přes sérii experimentů, řekněme 100. To lze pohodlně provést pomocí np.convolve: (blok kódu 12)

def running_average(x,window):
    return np.convolve(x,np.ones(window)/window,mode='valid')

plt.plot(running_average(rewards,100))

průběh tréninku

Úprava hyperparametrů

Aby bylo učení stabilnější, má smysl upravit některé z našich hyperparametrů během tréninku. Konkrétně:

  • Pro rychlost učení, alpha, můžeme začít s hodnotami blízkými 1 a poté tento parametr postupně snižovat. S časem budeme získávat dobré pravděpodobnostní hodnoty v Q-Tabulce, a proto bychom je měli upravovat mírně, a ne zcela přepisovat novými hodnotami.

  • Zvýšení epsilon. Můžeme chtít epsilon pomalu zvyšovat, aby se méně prozkoumávalo a více využívalo. Pravděpodobně má smysl začít s nižší hodnotou epsilon a postupně ji zvýšit téměř na 1.

Úkol 1: Experimentujte s hodnotami hyperparametrů a zjistěte, zda můžete dosáhnout vyššího kumulativního odměny. Dosahujete více než 195? Úkol 2: Aby bylo možné problém formálně vyřešit, je potřeba dosáhnout průměrné odměny 195 během 100 po sobě jdoucích běhů. Měřte to během tréninku a ujistěte se, že jste problém formálně vyřešili!

Vidět výsledek v akci

Bylo by zajímavé skutečně vidět, jak se naučený model chová. Spusťme simulaci a použijme stejnou strategii výběru akcí jako během tréninku, tedy vzorkování podle pravděpodobnostního rozdělení v Q-Tabulce: (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()

Měli byste vidět něco podobného:

balancující cartpole


🚀Výzva

Úkol 3: Zde jsme používali finální verzi Q-Tabulky, která nemusí být ta nejlepší. Pamatujte, že jsme uložili nejlépe fungující Q-Tabulku do proměnné Qbest! Vyzkoušejte stejný příklad s nejlépe fungující Q-Tabulkou tím, že zkopírujete Qbest do Q, a sledujte, zda zaznamenáte rozdíl.

Úkol 4: Zde jsme na každém kroku nevybírali nejlepší akci, ale spíše vzorkovali podle odpovídajícího pravděpodobnostního rozdělení. Mělo by větší smysl vždy vybírat nejlepší akci s nejvyšší hodnotou v Q-Tabulce? To lze provést pomocí funkce np.argmax, která zjistí číslo akce odpovídající nejvyšší hodnotě v Q-Tabulce. Implementujte tuto strategii a sledujte, zda zlepší balancování.

Kvíz po přednášce

Zadání

Vytrénujte Mountain Car

Závěr

Nyní jsme se naučili, jak trénovat agenty, aby dosáhli dobrých výsledků pouze tím, že jim poskytneme funkci odměny, která definuje požadovaný stav hry, a dáme jim příležitost inteligentně prozkoumat prostor hledání. Úspěšně jsme aplikovali algoritmus Q-Learning v případech diskrétních i spojitých prostředí, ale s diskrétními akcemi.

Je také důležité studovat situace, kdy je stav akcí spojitý a kdy je prostor pozorování mnohem složitější, například obraz z obrazovky hry Atari. V těchto problémech často potřebujeme použít výkonnější techniky strojového učení, jako jsou neuronové sítě, abychom dosáhli dobrých výsledků. Tyto pokročilejší témata jsou předmětem našeho nadcházejícího pokročilého kurzu AI.


Prohlášení:
Tento dokument byl přeložen pomocí služby pro automatický překlad Co-op Translator. Ačkoli se snažíme o přesnost, mějte na paměti, že automatické překlady mohou obsahovat chyby nebo nepřesnosti. Původní dokument v jeho původním jazyce by měl být považován za autoritativní zdroj. Pro důležité informace doporučujeme profesionální lidský překlad. Neodpovídáme za žádná nedorozumění nebo nesprávné interpretace vyplývající z použití tohoto překladu.