Martin Malý zveřejnil sérii svých starších článků o tzv. Vězňově dilematu. Rád jsem si tuto úlohu připomněl. O co jde?
Představte si, že jste uvězněn spolu s vaším komplicem, jste držen každý zvlášť a jste zvlášť vyslýcháni. Máte možnost vypovídat proti tomu druhému nebo mlčet. Policie na vás skoro nic nemá a pokud se oba svorně rozhodnete mlčet, dostanete oba tak akorát podmínku. Pokud však pomůžete vašeho kolegu usvědčit, půjde on sedět na deset let a vy vyváznete bez trestu. Totéž platí i v opačném případě. A pokud se rozhodnete oba vypovídat proti tomu druhému, půjdete sedět na pět let oba. Jak se rozhodnete?
já / protihráč | podraz | spolupráce |
---|---|---|
podraz | 1 | 10 |
spolupráce | -10 | 6 |
Hra na toto téma spočívá v tom, že se proti sobě postaví dva algoritmy na předem neznámý počet kol a mají hrát tak, aby maximalizovaly svůj zisk při nějakém bodovém ohodnocení jednotlivých tahů.
Martin problém popisuje ve vztahu ke komunitám, do hry vnesl šum a mutování algoritmů a celé jeho dílo určitě stojí za přečtení. Mně problém přijde zajímavý z jiného pohledu.
Martin použil v simulacích tyto algoritmy:
- Kavka. Algoritmus, který nikdy nepodrazí, jedině když dojde k nějakému šumu, tak je jeho krok vnímán jako podraz. (Beránčí algoritmus)
- Podrazák. Vždy podrazí.
- Rozmar. Algoritmus, který se mezi spoluprací a podrazem rozhoduje zcela náhodně.
- Dobrý. Algoritmus se rozhoduje náhodně. Spolupracuje, ale v jednom případu ze čtyř podrazí.
- Špatný. Rozhoduje se náhodně, většinou podrazí, v jednom ze čtyř případů spolupracuje.
- TFT. Algoritmus začne spoluprací a pak opakuje poslední krok soupeře – podrazí za podraz, spolupracuje při spolupráci. (Čistící algoritmus)
- TF2T. Algoritmus, který začíná spoluprací a podrazí jen tehdy, když byl dvakrát za sebou podražen.
- Velký pes. Algoritmus, který začne spoluprací. Pokud v minulé hře oba spolupracovali, spolupracuje dál, pokud byl podražen, podrazí. Pokud v minulé hře podrazil a podraz se mu vyplatil (=soupeř spolupracoval), bude dál podrážet. Pokud podráží oba, nabídne spolupráci. (Pragmatický algoritmus)
- Malý pes. Chová se stejně jako velký pes, ale začíná podrazem.
Naprogramovat by se daly takhle:
<?php // true je spolupráce, false je podraz function kavka($history) { return true; } function podrazak($history) { return false; } function rozmar($history) { return (mt_rand(0, 1) == 0); } function dobry($history) { return (mt_rand(0, 3) != 0); } function spatny($history) { return (mt_rand(0, 3) == 0); } function tft($history) { return (!$history || array_pop($history)); } function tf2t($history) { return (count($history) < 2 || array_pop($history) || array_pop($history)); } function velky_pes($history) { $return = true; foreach ($history as $play) { if (!$play) { $return = !$return; } } return $return; } function maly_pes($history) { $return = false; foreach ($history as $play) { if (!$play) { $return = !$return; } } return $return; } ?>
Všechny algoritmy jsou dost primitivní – do historie se buď vůbec nedívají nebo se dívají jen jeden, maximálně dva kroky zpět. Pro Martinovy účely to úplně stačí a závěry z toho vyvodil zajímavé. Ale já jsem si řekl – jak by vypadal algoritmus, který by chtěl maximalizovat svůj zisk s využitím všech dostupných znalostí? Pokud by věděl, kdo proti němu stojí, tak má situaci docela jednoduchou:
<?php function optimalni($history, $opponent) { switch ($opponent) { case 'kavka': return false; case 'podrazak': return false; case 'rozmar': return false; case 'dobry': return false; case 'spatny': return false; case 'tft': return true; case 'tf2t': return (count($history) % 2 != 0); case 'velky_pes': return true; case 'maly_pes': return (bool)$history; } return true; } ?>
Netriviální protihráči:
Pokud by se hra hrála na předem známý počet kol, tak by se také vyplatilo v posledním kole každého podrazit.
A teď řekněme, že nevíme, kdo proti nám stojí – jak z předešlých tahů odvodit, kdo to je? Kvůli náhodným algoritmům je to ještě o něco těžší – nikdy nemůžeme mít jistotu. Vyplatí se zariskovat a v jednom tahu nabídnout spolupráci jen proto, abych zjistil, kdo proti mně stojí v příštím kole? Vyplatí se na to vůbec sestavovat pravidla nebo by bylo lepší vyzkoušet všechny možnosti a optimální strategii vypočítat? Já jsem sestavil pravidla začínající podrazem a spoluprací, náhodné algoritmy jsem nezohlednil:
<?php function ucici($history) { $opening = array(false, true); // v prvních dvou kolech vyzkoušíme reakce na podraz a spolupráci if (count($history) < count($opening)) { return $opening[count($history)]; } if (!$history[0]) { // podraz v prvním kole: Podrazák nebo Malý pes return $history[1]; // když v druhém kole spolupracoval, je to Malý pes a vyplatí se spolupracovat, jinak je to Podrazák } if (!$history[1]) { // podraz v druhém kole: TFT nebo Velký pes if (count($history) < 3) { return false; // ve třetím kole musíme Velkého psa naučit, že se mu nevyplatí podrážet } return true; } // TF2T nebo Kavka if (count($history) < 4) { return false; // ve třetím kole se vyplatí podrazit, ve čtvrtém vyzkoušíme, jestli to je TF2T } if (count($history) < 5) { return true; // v pátém kole TF2T naznačíme, že s ním můžeme spolupracovat, zisk z Kavky oželíme } if (!$history[4]) { // když v pátém kole podrazil, je to TF2T return (count($history) % 2 == 0); // v lichých kolech se vyplatí spolupracovat } return false; // jinak to je Kavka } ?>
Při jiném zahájení by pravidla samozřejmě vypadala jinak. Algoritmus není tak dobrý jako optimální (který ovšem podvádí) a v některých případech ani jako některé primitivní algoritmy, s velkým počtem kol se jim ale přibližuje. V součtu proti všem drtivě vyhrává:
zisk | Kavka | Podrazák | TFT | TF2T | Velký pes | Malý pes | celkem |
---|---|---|---|---|---|---|---|
Kavka | 1200 | -2000 | 1200 | 1200 | 1200 | -2000 | 800 |
Podrazák | 2000 | 200 | 218 | 236 | 1100 | 1100 | 4854 |
TFT | 1200 | 178 | 1200 | 1200 | 1200 | 46 | 5024 |
TF2T | 1200 | 156 | 1200 | 1200 | 1200 | -450 | 4506 |
Velký pes | 1200 | -900 | 1200 | 1200 | 1200 | 1158 | 5058 |
Malý pes | 2000 | -900 | 86 | 550 | 1198 | 1190 | 4124 |
Optimální | 2000 | 200 | 1200 | 1600 | 1200 | 1190 | 7390 |
Učící | 1984 | 178 | 1152 | 1576 | 1166 | 1190 | 7246 |
A co kdybych nevěděl, kdo proti mně stojí, ale ani bych nevěděl, jaké algoritmy existují? Co kdybych vypustil stádo divokých algoritmů do arény a nechal přežít ten nejlepší? Jak by takový algoritmus vypadal?
Pokud s tím někdo chce experimentovat, zde je program, který jsem použil pro testování algoritmů:
<?php function veznovo_dilema($algos, $prices) { $profits = array_fill_keys($algos, array_fill_keys($algos, 0)); foreach ($algos as $home) { foreach ($algos as $away) { $home_history = array(); $away_history = array(); for ($i = 0; $i < 100; $i++) { $home_play = $home($away_history, $away); $away_play = $away($home_history, $home); $home_history[] = $home_play; $away_history[] = $away_play; $profits[$home][$away] += $prices[$away_play][$home_play]; $profits[$away][$home] += $prices[$home_play][$away_play]; } } } return $profits; } $algos = array('kavka', 'podrazak', 'tft', 'tf2t', 'velky_pes', 'maly_pes', 'optimalni', 'ucici'); $prices = array( false => array(false => 1, true => -10), true => array(false => 10, true => 6), ); foreach (veznovo_dilema($algos, $prices) as $algo => $val) { echo "$algo\t" . implode("\t", $val) . "\t" . array_sum($val) . "\n"; } ?>
Ve skutečném světě taky pracujeme s delší historií, pamatujeme si křivdy z minula a při svém rozhodování je chtě nechtě zohledňujeme. Co se algoritmizuje těžko, je první dojem – pokud se nám někdo nezdá, tak se k němu chováme jinak, než pokud je nám sympatický.
Diskuse je zrušena z důvodu spamu.