Projekt 4 — Nim
Nejnovější verze Pythonu, kterou bys měla v tomto kurzu používat, je Python 3.12.
Napiš AI, která se pomocí posilovaného učení sama naučí hrát Nim.
$ python play.py
Playing training game 1
Playing training game 2
Playing training game 3
...
Playing training game 9999
Playing training game 10000
Done training
Piles:
Pile 0: 1
Pile 1: 3
Pile 2: 5
Pile 3: 7
AI's Turn
AI chose to take 1 from pile 2.
Kdy úlohu dokončit
Jak získat pomoc
- Ptej se prostřednictvím Ed!
- Ptej se v kterékoli komunitě CS50!
Východiska
Ve hře Nim začínáme s několika hromádkami objektů. Hráči se střídají: hráč na tahu odebere libovolný nezáporný počet objektů z jedné neprázdné hromádky. Kdo odebere poslední objekt, prohraje.
Poznámka překladatele: Původní anglické zadání zde uvádí „any non-negative number“, tedy nezáporný počet, což zahrnuje i nulu. Jde o nepřesnost původního slovního popisu: platný tah hry Nim odebírá kladný počet objektů, alespoň 1, z jedné neprázdné hromádky. Překlad původní věty výše je zachován; tato poznámka objasňuje pravidlo a nemění dodaný kód ani ostatní požadavky projektu.
Dokážeš si představit jednoduchou strategii: pokud zbývá jediná hromádka se třemi objekty a jsi na tahu, nejlépe odebereš dva a necháš protivníkovi třetí, poslední. Při více hromádkách se strategie podstatně komplikuje. V projektu vytvoříme AI, která se strategii naučí posilovaným učením. Opakovaným hraním proti sobě a učením ze zkušenosti časem zjistí, které akce provádět a kterým se vyhnout.
Konkrétně použijeme Q-learning. Pro každou dvojici (state, action) se učíme číselnou hodnotu odměny. Akce vedoucí k prohře má odměnu −1, akce vedoucí k prohře protivníka odměnu 1 a akce pokračující ve hře okamžitou odměnu 0, ale také určitou budoucí odměnu.
Jak stavy a akce vyjádříme v Pythonu? Stav Nimu jsou aktuální velikosti všech hromádek. Například [1, 1, 3, 5] znamená 1 objekt v hromádce 0, 1 v hromádce 1, 3 v hromádce 2 a 5 v hromádce 3. Akce je dvojice celých čísel (i, j) znamenající odebrání j objektů z hromádky i. Akce (3, 5) tedy odebere 5 objektů z hromádky 3. Použitím na [1, 1, 3, 5] dostaneme [1, 1, 3, 0], stejný stav s nyní prázdnou hromádkou 3.
Základní vzorec Q-learningu je níže. Kdykoli ve stavu s provedeme akci a, můžeme upravit Q(s, a) podle něj:
Q(s, a) <- Q(s, a) + alpha * (nový odhad hodnoty - starý odhad hodnoty)
alpha je rychlost učení, tedy míra, v níž nové informace oceňujeme vůči dosavadním. new value estimate je součet odměny za aktuální akci a odhadu všech budoucích odměn hráče. old value estimate je dosavadní hodnota Q(s, a). Používáním vzorce po každé nové akci se AI postupně učí, které akce jsou v jednotlivých stavech lepší.
Začínáme
- Stáhni dodaný kód z https://cdn.cs50.net/ai/2023/x/projects/4/nim.zip a rozbal jej.
Porozumění zadání
Nejprve otevři nim.py. Obsahuje třídy Nim a NimAI a funkce train a play. Nim, train i play už jsou implementované, zatímco v NimAI musíš doplnit několik funkcí.
Třída Nim definuje pravidla hry. V __init__ si všimni seznamu hromádek, aktuálního hráče 0 nebo 1 a vítěze, pokud existuje. available_actions vrací množinu dostupných akcí ve stavu. Například Nim.available_actions([2, 1, 0, 0]) vrátí {(0, 1), (1, 1), (0, 2)}, protože lze odebrat 1 či 2 objekty z hromádky 0 nebo 1 objekt z hromádky 1.
Další funkce určují průběh hry: other_player určí protivníka daného hráče, switch_player předá tah protivníkovi a move provede akci v aktuálním stavu a předá tah.
Třída NimAI definuje AI učící se Nim. V __init__ začíná prázdným slovníkem self.q. self.q uchovává naučené Q hodnoty mapováním dvojic (state, action) na čísla. Ačkoli state obvykle představujeme seznamem, seznam nemůže být klíčem slovníku Pythonu. Při získávání a nastavování self.q proto použijeme stav jako tuple.
Chceme-li například Q hodnotu pro stav [0, 0, 0, 2] a akci (3, 2) nastavit na -1, napíšeme:
self.q[(0, 0, 0, 2), (3, 2)] = -1
Každý objekt NimAI má také hodnoty alpha a epsilon, používané pro Q-learning a volbu akce.
Hotová funkce update přijímá stav old_state, provedenou akci action, výsledný stav new_state a okamžitou odměnu reward. Provede Q-learning: získá aktuální Q hodnotu stavu a akce voláním get_q_value, určí nejlepší možné budoucí odměny pomocí best_future_reward a z obou upraví Q hodnotu pomocí update_q_value. Tyto tři funkce musíš implementovat.
Poslední nedokončenou funkcí je choose_action, která vybere akci pro daný stav, buď hladově, nebo epsilon-hladovým algoritmem.
Třídy Nim a NimAI využívají funkce train a play. train spustí n simulovaných her proti sobě a vrátí natrénovanou AI. play natrénovanou AI přijme a umožní člověku hrát proti ní.
Specifikace
V nim.py dokonči get_q_value, update_q_value, best_future_reward a choose_action. V každé funkci můžeš předpokládat, že state je seznam celých čísel a action dvojice celých čísel (i, j), hromádky i a počtu objektů j.
get_q_value přijme state a action a vrátí odpovídající Q hodnotu dvojice stav–akce.
- Q hodnoty uchovává slovník
self.q. Klíčeself.qmají tvar(state, action), kdestateje tuple velikostí všech hromádek v pořadí aactiontuple(i, j)označující hromádku a počet. - Pokud dvojice nemá ve
self.qQ hodnotu, vrať0.
update_q_value přijme stav state, akci action, dosavadní Q hodnotu old_q, aktuální odměnu reward a odhad budoucí odměny future_rewards. Podle vzorce Q-learningu upraví Q hodnotu této dvojice.
- Vzorec Q-learningu připomínáme:
Q(s, a) <- old value estimate + alpha * (new value estimate - old value estimate). alphaje rychlost učení přiřazená objektuNimAI.- Starý odhad je současná Q hodnota dvojice stav–akce. Nový odhad má být součtem aktuální odměny a odhadované budoucí odměny.
best_future_reward přijme state a podle údajů self.q vrátí nejlepší možnou odměnu z dostupných akcí v tomto stavu.
- U akce, která pro daný stav dosud není v
self.q, předpokládej Q hodnotu 0. - Pokud nejsou dostupné žádné akce, vrať 0.
choose_action přijme state a volitelný příznak epsilon, zda má použít epsilon-hladový algoritmus, a vrátí dostupnou akci v daném stavu.
- Je-li
epsilonFalse, funkce má vybrat hladově nejlepší dostupnou akci, tedy nejvyšší Q hodnotu; neznámá Q hodnota je 0. - Je-li
epsilonTrue, použij epsilon-hladový algoritmus: s pravděpodobnostíself.epsilonvyber náhodnou dostupnou akci, jinak nejlepší dostupnou. - Má-li stejnou Q hodnotu více akcí, lze vrátit kteroukoli z nich.
V nim.py neupravuj nic kromě funkcí určených zadáním. Můžeš přidat pomocné funkce a importy standardní knihovny Pythonu, případně numpy či pandas, pokud je znáš, ale žádné jiné moduly třetích stran. Pro vlastní testování můžeš upravit play.py.
Tipy
- Je-li
lstseznam, volánítuple(lst)převedelstna tuple.
Testování
Chceš-li vyhodnotit správnost kódu, můžeš po nastavení check50 spustit následující příkaz. Není to povinné: při odevzdání podle kroků na konci zadání proběhnou stejné testy na našem serveru. V každém případě kód také sama zkompiluj a otestuj!
check50 ai50/projects/2024/x/nim
Následujícím příkazem vyhodnotíš styl kódu pomocí style50.
style50 nim.py
Pamatuj, že kromě standardní knihovny Pythonu nesmíš importovat žádné moduly mimo ty, které jsou zde výslovně povoleny. Jinak nebude fungovat check50 a ani submit50 nebude moci práci ohodnotit, protože používá check50. Pokud se to stane, patrně jsi importovala zakázaný modul nebo nepovoleně upravila dodaný kód. Existují nástroje, které některé projekty velmi zjednodušují, ale to zde není cílem: učíš se principům na nižší úrovni. Pokud zde jejich použití nepovolujeme, použít je nemůžeš.
Jak odevzdat
- Otevři tento odkaz, přihlas se účtem GitHub a klikni na Authorize cs50. Pak zaškrtni souhlas s přístupem učitelů k odevzdaným pracím a klikni na Join course.
- Nainstaluj Git a případně nainstaluj
submit50. - Pokud máš nainstalované
submit50, spusťsubmit50 ai50/projects/2024/x/nimJinak pomocí Gitu odešli práci do
https://github.com/me50/USERNAME.git, kdeUSERNAMEje tvoje jméno na GitHubu, na větevai50/projects/2024/x/nim.
Při přímém odevzdání přes Git místo submit50 nepřikládej soubory ani složky mimo ty, které máš podle zadání upravit. Neodesílej tedy celý adresář!
Práce by měla být ohodnocena do pěti minut. Svůj postup pak můžeš sledovat na https://cs50.me/cs50ai!