Napište AI, abyste vyhráli na Pongu od nuly pomocí Reinforcement Learning

Existuje obrovský rozdíl mezi čtením o Reinforcement Learning a jeho skutečnou implementací.

V tomto příspěvku implementujete Neuronovou síť pro učení zesílení a uvidíte, že se stále více a více dozví, jak se konečně stane dost dobrým na to, abyste porazili počítač v Pongu! V tělocvičně OpenAI si můžete hrát s dalšími podobnými hrami Atari.

Na konci tohoto příspěvku budete moci provádět následující akce:

  • Napište neuronovou síť od nuly.
  • Proveďte přechod na politiku pomocí učení o posílení.
  • Postavte AI pro Pong, který dokáže porazit počítač v méně než 250 řádcích Pythonu.
  • Používejte tělocvičnu OpenAI.

Prameny

Kód a myšlenka jsou pevně založeny na blogu Andrej Karpathy. Účelem kódu me_pong.py je snazší sledovat verzi pong.py, kterou napsal Dr. Karpathy.

Předpoklady a čtení na pozadí

Chcete-li pokračovat, musíte znát následující:

  • Základní Python
  • Návrh neuronové sítě a backpropogation
  • Kalkul a lineární algebra

Pokud chcete hlouběji ponořit do materiálu, který máte po ruce, přečtěte si blogový příspěvek, na kterém je vše založeno. Tento příspěvek je zamýšlen jako jednodušší úvod do tohoto materiálu.

Skvělý! Začněme.

Založit

  1. Otevřete kód a pokračujte dále.
  2. Postupujte podle pokynů pro instalaci OpenAI Gym. Možná budete muset nejprve nainstalovat cmake.
  3. Spustit pip install -e. [Atari]
  4. Vraťte se do kořenového adresáře tohoto úložiště a ve svém oblíbeném editoru otevřete nový soubor s názvem my_pong.py.
  5. Pojďme k řešení problémů. Tady je problém:

Problém

Dostáváme následující:

  • Posloupnost obrázků (rámců) představujících každý snímek hry Pong.
  • Indikace, kdy jsme vyhráli nebo prohráli hru.
  • Agent soupeře, který je tradičním počítačovým hráčem Pong.
  • Agent, kterého ovládáme, můžeme říci, že se v každém snímku můžeme pohybovat nahoru nebo dolů.

Můžeme použít tyto kousky k tréninku našeho agenta, aby porazil počítač? Můžeme navíc učinit naše řešení natolik obecným, že může být znovu použito k vítězství ve hrách, které nejsou pong?

Řešení

Opravdu můžeme! Andrej to dělá tím, že buduje neuronovou síť, která přijímá každý obraz a vydává příkaz našemu AI, aby se pohyboval nahoru nebo dolů.

Architektura Andrejova řešení z jeho blogu.

Můžeme to trochu rozdělit do následujících kroků:

Naše neuronová síť, založená převážně na Andrejově řešení, bude dělat následující:

  1. Pořiďte obrázky ze hry a předzpracovejte je (odstraňte barvu, pozadí, downsample atd.).
  2. Použijte neuronovou síť k výpočtu pravděpodobnosti posunu nahoru.
  3. Ukázka z této distribuce pravděpodobnosti a řekněte agentovi, aby se pohyboval nahoru nebo dolů.
  4. Pokud kolo skončilo (vynechal jste míč nebo soupeř vynechal míč), zjistěte, zda jste vyhráli nebo prohráli.
  5. Když epizoda skončila (někdo dostal 21 bodů), předejte výsledek algoritmem backpropagation, abyste vypočítali gradient pro naše váhy.
  6. Po dokončení 10 epizod sčítejte gradient a posuňte závaží ve směru gradientu.
  7. Tento postup opakujte, dokud naše váhy nejsou naladěny do bodu, kdy můžeme porazit počítač. To je v podstatě to! Pojďme se podívat, jak to náš kód dosáhne.

Ok, teď, když jsme popsali problém a jeho řešení, pojďme psát nějaký kód!

Kód

Nyní budeme následovat kód v me_pong.py. Udržujte jej prosím otevřený a přečtěte si ho! Kód začíná zde:

def main ():

Inicializace

Nejprve pomocí OpenAI Gym vytvoříme herní prostředí a získáme náš první obrázek hry.

Dále jsme nastavili spoustu parametrů na základě Andrejova blogu. Nebudeme se starat o jejich vyladění, ale uvědomte si, že tím pravděpodobně dosáhnete lepšího výkonu. Parametry, které použijeme, jsou:

  • batch_size: kolik kol hrajeme před aktualizací hmotností naší sítě.
  • gama: diskontní faktor, který používáme pro diskontování účinku starých akcí na konečný výsledek. (Nedělejte si s tím starosti)
  • decay_rate: Parametr použitý v algoritmu RMSProp. (Nedělejte si s tím starosti)
  • num_hidden_layer_neurons: Kolik neuronů je v naší skryté vrstvě.
  • learning_rate: Míra, kterou se z našich výsledků poučíme pro výpočet nových hmotností. Vyšší míra znamená, že více reagujeme na výsledky, a nižší míra znamená, že na každý výsledek nereagujeme tak silně.

Poté v naší neuronové síti nastavíme počitadla, počáteční hodnoty a počáteční váhy.

Hmotnosti jsou uloženy v matricích. Vrstva 1 naší neuronové sítě je matice 200 x 6400 představující váhy pro naši skrytou vrstvu. Pro vrstvu 1 představuje prvek w1_ij hmotnost neuronu i pro vstupní pixel j ve vrstvě 1.

Vrstva 2 je matice 200 x 1 představující hmotnosti výstupu skryté vrstvy na našem konečném výstupu. Pro vrstvu 2 představuje prvek w2_i váhy, které klademe na aktivaci neuronu i ve skryté vrstvě.

Momentálně inicializujeme váhy každé vrstvy náhodnými čísly. Vydělíme druhou odmocninou počtu rozměrů, abychom normalizovali naše váhy.

Dále jsme nastavili počáteční parametry pro RMSProp (způsob aktualizace hmotností, o kterém se budeme bavit později). Nedělejte si starosti s pochopením toho, co vidíte níže. Hlavně jsem to vyvedl sem, abychom mohli pokračovat v hlavním bloku kódu.

V epizodě musíme shromáždit spoustu pozorování a mezilehlých hodnot a pomocí nich vypočítat gradient na konci na základě výsledku. Níže jsou uvedena pole, ve kterých shromažďujeme všechny tyto informace.

Dobře, všichni jsme s nastavením hotovi! Pokud jste postupovali, mělo by to vypadat takto:

Phew. Nyní pro zábavu část!

Zjistit, jak se pohybovat

Těžiště našeho algoritmu bude žít ve smyčce, kde se neustále pohybujeme a poté se učíme na základě výsledků tohoto pohybu. Prozatím vše vložíme do bloku, ale ve skutečnosti můžete nastavit zastavení procesu, který proces zastaví.

Prvním krokem k našemu algoritmu je zpracování obrazu hry, kterou nám OpenAI Gym prošlo. Opravdu se nestaráme o celý obraz - jen o určité detaily. Děláme to níže:

Pojďme se ponořit do preprocess_observations a uvidíme, jak převedeme obrázek OpenAI Gym, který nám dá něco, co můžeme použít k trénování naší neuronové sítě. Základní kroky jsou:

  1. Ořízněte obrázek (staráme se jen o části s informacemi, které nás zajímají).
  2. Převzorkujte obrázek.
  3. Převeďte obrázek na černobílý (barva pro nás není zvlášť důležitá).
  4. Odstraňte pozadí.
  5. Převeďte z matice hodnot 80 x 80 na matici 6400 x 1 (matici vyrovnejte, aby se dala snadněji používat).
  6. Uložte pouze rozdíl mezi aktuálním rámcem a předchozím rámcem, pokud předchozí rámeček známe (staráme se pouze o to, co se změnilo).

Nyní, když jsme předběžně zpracovali pozorování, pojďme dále posílat pozorování přes naši neuronovou síť, abychom vygenerovali pravděpodobnost, že řekneme naší AI, aby se posunula nahoru. Zde jsou kroky, které podnikneme:

Jak přesně to, že aplikuje síťové_příslušenství, přijímá pozorování a váhy a generuje pravděpodobnost stoupání? Toto je jen dopředný průchod neuronovou sítí. Podívejme se na níže uvedený kód, kde jsou další informace:

Jak vidíte, není to vůbec mnoho kroků! Pojďme krok za krokem:

  1. Vypočítejte hodnoty nezpracovaných skrytých vrstev jednoduchým vyhledáním tečkového součinu hmotností [1] (hmotností vrstvy 1) a pozorovací matice. Pokud si pamatujete, závaží [1] je matice 200 x 6400 a matice observ_matrix je matice 6400 x 1. Tečkový produkt nám poskytne matici o rozměrech 200 x 1. Máme 200 neuronů, takže každý řádek představuje výstup jednoho neuronu.
  2. Dále na tyto hodnoty skryté vrstvy aplikujeme nelineární prahování - v tomto případě pouze jednoduchou ReLU. Na vysoké úrovni to představuje nelinearity, díky nimž je naše síť schopna počítat nelineární funkce, nikoli pouze jednoduché lineární funkce.
  3. Tyto hodnoty aktivace skryté vrstvy používáme k výpočtu hodnot výstupní vrstvy. Děje se to jednoduchým tečkovým produktem skrytých_hodnot (200 x 1) a hmotností [2] (1 x 200), který dává jednu hodnotu (1 x 1).
  4. Nakonec na tuto výstupní hodnotu aplikujeme sigmoidní funkci, takže je mezi 0 a 1, a proto je platná pravděpodobnost (pravděpodobnost vzestupu).

Vraťme se k hlavnímu algoritmu a pokračujeme dál. Nyní, když jsme získali pravděpodobnost, že půjdeme nahoru, musíme nyní zaznamenat výsledky pro pozdější učení a zvolit akci, která řekne naší AI, aby implementovala:

Vybíráme akci tak, že převrátíme imaginární minci, která dopadne „vzhůru“ s pravděpodobností na vyšší pravděpodobnost a dolů s 1 - na vyšší pravděpodobnost. Pokud to dopadne, zvolíme, abychom řekli naší AI, aby šla nahoru, a pokud ne, řekneme jí, aby šla dolů. My také

Poté jsme akci předali OpenAI Gym prostřednictvím env.step (action).

Dobře, pokryli jsme první polovinu řešení! Víme, jaké kroky sdělit naší AI, aby přijala. Pokud jste postupovali společně, měl by váš kód vypadat takto:

Nyní, když jsme udělali náš krok, je čas začít se učit, a tak určíme správné váhy v naší neuronové síti!

Učení se

Učení je o tom, jak vidět výsledek akce (tj. Zda jsme vyhráli kolo) a podle toho změnit naše váhy. Prvním krokem k učení je položení následující otázky:

  • Jak ovlivní změna pravděpodobnosti výstupu (vzestupu) můj výsledek vítězství v kole?

Matematicky je to jen derivát našeho výsledku s ohledem na výstupy naší konečné vrstvy. Pokud L je hodnota našeho výsledku pro nás af je funkce, která nám dává aktivaci naší konečné vrstvy, je tato derivace pouze ∂L / ∂f.

V kontextu binární klasifikace (tj. Stačí říct AI jednu ze dvou akcí, nahoru nebo dolů), se tato derivace ukáže jako

Všimněte si, že σ ve výše uvedené rovnici představuje sigmoidní funkci. Přečtěte si sekci Klasifikace atributů, kde najdete další informace o tom, jak získáme výše uvedený derivát. Níže to zjednodušujeme:

∂L / ∂f = true_label (0 nebo 1) - predikovaný_label (0 nebo 1)

Po jedné akci (posunutím pádla nahoru nebo dolů) opravdu nemáme představu o tom, zda se jednalo o správnou akci. Budeme tedy podvádět a považovat akci, kterou skončíme, vzorkování z naší pravděpodobnosti za správnou akci.

Naše predikce pro toto kolo bude pravděpodobnost stoupání, kterou jsme vypočítali. Pomocí toho máme, že ∂L / ∂f lze vypočítat pomocí

Skvělý! Máme gradient na akci.

Dalším krokem je zjistit, jak se učíme po skončení epizody (tj. Když my nebo náš soupeř chybí míč a někdo dostane bod). Děláme to tak, že vypočítáme gradient politiky na konci každé epizody. Intuice je taková, že pokud jsme vyhráli kolo, chtěli bychom, aby naše síť vytvořila více akcí, které vedly k vítězství. Pokud ztratíme, zkusíme vygenerovat méně z těchto akcí.

OpenAI Gym nám poskytuje šikovnou hotovou proměnnou, která nám sdělí, kdy epizoda skončí (tj. Minul jsme míč nebo náš soupeř minul míč). Když si všimneme, že jsme hotovi, první věcí, kterou uděláme, je sestavení všech našich pozorování a výpočtů gradientu pro epizodu. To nám umožňuje aplikovat naše poznatky na všechny akce v epizodě.

Dále se chceme učit takovým způsobem, aby akce učiněné na konci epizody více ovlivňovaly naše učení než akce prováděné na začátku. Tomu se říká diskontování.

Přemýšlejte o tom tímto způsobem - pokud jste se posunuli nahoru v prvním snímku epizody, pravděpodobně to mělo jen velmi malý dopad na to, zda vyhrajete nebo ne. Nicméně, blíže ke konci epizody, vaše akce pravděpodobně mají mnohem větší účinek, protože určují, zda vaše pádlo dosáhne míče a jak vaše pádlo zasáhne míč.

Budeme brát tuto váhu v úvahu tím, že diskontujeme naše odměny tak, že odměny za dřívější snímky jsou mnohem nižší než odměny za pozdější snímky. Poté budeme konečně používat pro výpočet gradientu backpropagation (tj. Směr, kterým musíme pohybovat, abychom zlepšili).

Podívejme se trochu na to, jak se vypočítá gradient zásad pro epizodu. Toto je jedna z nejdůležitějších částí Výztužného učení, protože to je to, jak náš agent přijde na to, jak se postupem času zlepšit.

Nejprve si přečtěte tento výňatek z backpropagation, pokud jste tak ještě neučinili, z vynikající knihy Michaela Nielsena o Deep Learning.

Jak uvidíte v tomto výňatku, existují čtyři základní rovnice backpropogation, technika výpočtu gradientu pro naše váhy.

Čtyři základní rovnice backpropagation. Zdroj: Michael Nielsen

Naším cílem je najít ∂C / ∂w1 (BP4), derivát nákladové funkce s ohledem na hmotnosti první vrstvy, a ∂C / ∂w2, derivát nákladové funkce s ohledem na hmotnosti druhé vrstvy. Tyto přechody nám pomohou pochopit, jakým směrem posuneme naše závaží pro co největší zlepšení.

Začneme tím, že začneme s ∂C / ∂w2. Pokud je ^ 1 aktivací skryté vrstvy (vrstva 2), vidíme, že vzorec je:

To je přesně to, co zde děláme:

Dále musíme vypočítat ∂C / ∂w1. Vzorec pro to je:

a také víme, že a ^ l1 jsou jen naše pozorovací hodnoty.

Takže vše, co nyní potřebujeme, je δ ^ l2. Jakmile to máme, můžeme vypočítat ∂C / ∂w1 a vrátit se. Děláme to jen níže:

Pokud jste postupovali společně, měla by vaše funkce vypadat takto:

S tím jsme dokončili backpropagation a vypočítali naše gradienty!

Po dokončení epizod batch_size konečně aktualizujeme naše váhy pro naši neuronovou síť a implementujeme naše poznatky.

Pro aktualizaci vah jednoduše použijeme RMSProp, algoritmus pro aktualizaci vah, který zde popsal Sebastian Reuder.

Implementujeme to níže:

Toto je krok, který vyladí naše váhy a umožňuje nám v průběhu času se zlepšovat.

To je v podstatě to! Celkově by to mělo vypadat takto.

Právě jste kódovali plnou neuronovou síť pro hraní tenisu! Odkomentujte env.render () a spusťte jej po dobu 3–4 dní, abyste viděli, že konečně porazil počítač! Aby bylo možné výsledky zviditelnit, když vyhrajete, budete muset udělat nějaké moření, jak je to provedeno v řešení Andrej Karpathy.

Výkon

Podle příspěvku na blogu by měl tento algoritmus trvat asi 3 dny tréninku na Macbooku, než začne bít počítač.

Zvažte vyladění parametrů nebo použití konvolučních neuronových sítí k dalšímu zvýšení výkonu.

Další čtení

Chcete-li získat další základní informace o neuronových sítích a posilování učení, existuje několik skvělých zdrojů, jak se dozvědět více (v Udacity pracuji jako ředitel programů strojového učení):

  • Původní blogový příspěvek Andrej Karpathy
  • Bezplatný kurz hloubky učení společnosti Udacity od společnosti Google
  • Zdarma vzdělávací kurz Udacity od Georgia Tech
  • Kniha Deep Learning Book od Michaela Neilsena
  • Učební kniha Suttona a Bartola o posílení