440 likes | 643 Views
Aleksander Sadikov Kako se računalnik spopade z igrami?. Pregled vsebine. predstavitve stanja iskalni algoritmi in njihove razširitve zanimivejši programi in njihove lastnosti. Predstavitev stanja. 8x8 tabela 0x88 predstavitev bitboard predstavitev. Predstavitev pozicije.
E N D
Pregled vsebine • predstavitve stanja • iskalni algoritmi in njihove razširitve • zanimivejši programi in njihove lastnosti
Predstavitev stanja • 8x8 tabela • 0x88 predstavitev • bitboard predstavitev
Predstavitev pozicije • vsak program za igranje iger potrebuje nekakšno podatkovno strukturo za predstavitev stanja igre • le-ta mora omogočati naslednje operacije: • generiranje vseh naslednikov dane pozicije • izvedba poteze, undo poteze (potrebno tudi med iskanjem) • ocenitev pozicije • ker se te operacije izvajajo zelo velikokrat v notranjih zankah programov je zelo pomembno, da so čimbolj učinkovite glede na časovno porabo • posebni primeri, dodatna informacija (ponavljanje potez, 50-move rule, itd.)
8x8 tabela • vsako polje v tabeli vsebuje vrednost, ki predstavlja tip figure, ki stoji na tem polju (empty je posebna vrednost), npr.: wP, wN, ..., bK • prednosti: • preprostost • lahko izračunljiva materialna vrednost pozicije • možne poteze je relativno lahko poiskati; kodo pa otežujejo in upočasnjujejo preverjanja robov • 10x10 tabela s specialno vrednostjo “rob” nekoliko olajša preverjanje robov
0x88 predstavitev • vsakemu polju šahovnice priredimo enobajtno vrednost, zgornji 4 biti so vrstica, spodnji 4 pa stolpec • levo od polja i je polje i-1, desno polje i+1, navzgor polje i+16, navzdol pa polje i-16, itd. • nato šahovnico predstavimo s 128 polj veliko tabelo • samo polja z zgornjimi številkami so veljavna polja • prednost: zelo hitro testiranje, če je polje i na katerega se premikamo veljaven del šahovnice – legalen je, če in samo če velja i & 0x88 == 0(0x08 za stolpec in 0x80 za vrstico)
Bitboard predstavitev • inverzna ideja: namesto tabele vseh polj šahovnice, na katerih so različni tipi figur imamo tabelo različnih tipov figur, za vsako pa 64 bitno število, ki pove na katerih poljih so figure tega tipa • ogromna prednost te predstavitve je da lahko na teh 64 bitnih številih zelo hitro izvajamo določene operacije (Boolean bitwise operations) • to v bistvu pomeni, da lahko marsikaj izračunamo paralelno na vseh 64 poljih naenkrat
Bitboards • za spodnjo pozicijo je polje wP, ki predstavlja bele kmete takšno:
Bitboards • polja, ki jih zasedajo bele figure se izračunajo takole: wOcc = wP | wN | wB | wR | wQ | wK • analogno se izračunajo tudi polja, ki jih zasedajo črne figure; vsa zasedena polja pa so potem: allOcc = wOcc | bOcc • vsa polja, na katera se beli kmetje lahko premaknejo z enojnim premikom nazvgor so tako: single_pawn_moves = (wP << 8) & ~allOcc
Hitrost predstavitev pozicij • zakaj tako velik poudarek na hitrosti? • zaradi velikokratnega izvajanja • ker hitrost predstavitve vpliva na vse glavne ostale elemente programa: • generator potez (paralelnost bitboardov) • iskalni algoritem (hiter move in unmove) • evaluacijsko funkcijo (izračun posameznih elementov in posredno na količino in s tem kvaliteto evaluacijske funkcije)
Iskalni algoritmi in njihove razširitve • minimax in negamax • alfa-beta • aspiration search • best-first search SSS*, DUAL* • conspiracy numbers search
drevo igre (game tree) vozlišča so pozicije, povezave so akcije koren je začetna pozicija identične pozicije se narišejo večkrat (drevo, ne graf) 3 tipi vozlišč: notranja na sodih nivojih notranja na lihih nivojih listi označeni z izidom Predstavitev možnih akcij (potez)
Ovrednotenje drevesa igre (pozicije) • vrednotenje začnemo pri notranjih vozliščih, katerega vsi nasledniki so listi – igra se bo končala po naslednji potezi • igralec na potezi izbere najboljšo možno potezo (zmago, remi, poraz) • ko ovrednotimo vsa vozlišča tik nad listi, postopek rekurzivno ponavljamo navzgor po drevesu • zadostujejo že tri ocene: zmaga, remi, poraz; več ocen se uporablja izključno zato, ker so te ocene približki • algoritem zadostuje za popolno igranje igre, če le imamo na voljo zadosti računskega časa
Drevesa igre in AND/OR grafi • na drevo igre lahko gledamo kot na AND/OR graf • v drevesih se izmenjujejo nivoji, kjer je enkrat na potezi igralec 1 in enkrat igralec 2 • če želimo najti zmago za igralca 1, ki je na potezi v začetni poziciji (koren drevesa), potem moramo poiskati nekega takšnega naslednika, ki je označen kot “zmaga” zanj (OR vozlišče) • to pa pomeni, da vse poteze igralca 2, ki je na potezi v “vozlišču nasledniku”, vodijo k njegovemu porazu (AND vozlišče)
Parcialna drevesa igre • v realnosti drevesa igre ne moremo razviti do konca, ampak gradnjo drevesa ustavimo glede na določen kriterij (fiksna globina, čas) • notranja vozlišča na zadnjem nivoju spremenimo v liste • liste tako dobljenega drevesa označimo z vrednostmi ocenitvene funkcije, ki so približek za prave izide • analogno kot pri popolnih drevesih ocenimo takšno drevo; pri tem se igralec 1 trudi doseči pozicije s čim višjo vrednostjo ocenitvene funkcije, igralec 2 pa s čim nižjo vrednostjo
Princip minimax in glavna varianta • če oba igralca igrata tako, kot smo opisali, potem igralec 1 vedno izbira maksimum možnih naslednikov danega vozlišča, igralec 2 pa ravno nasprotno, minimum • na podlagi tega lahko točno določimo katero pot skozi drevo (in do katerega lista) bosta igralca izbrala • tej poti rečemo glavna varianta (principal variation) • princip minimax lahko povzamemo kot: • zgradi parcialno drevo igre začenši v trenutni poziciji • poišči glavno varianto • naredi potezo, ki vodi do prvega vozlišča na glavni varianti
Iskanje v širino, globino • ocenjevanje dreves iger, kot smo ga opisali, predstavlja iskanje v širino (obdelujemo nivo za nivojem) • slabost: potrebno je v spominu imeti celotno drevo igre • ocenjujemo lahko tudi z iskanjem v globino (post-order obhod drevesa) – pri tem je v spominu potrebno imeti samo trenutno pot • dodatna prednost iskanja v globino je, da lahko pridobljeno informacijo o drevesu sproti uporabljamo in se tako izognemo iskanju v nepomembnem delu drevesa
Minimax in negamax • minimax princip alternirajoče uporablja iskanje maksimuma in minimuma, ker ločuje med svojim in nasprotnikovim pogledom na pozicijo • to razlikovanje lahko odstranimo in vedno uporabljamo pogled s strani igralca, ki je na potezi • to naredimo tako, da negiramo vrednost, kot jo vidi nasprotnik in vedno iščemo samo maksimum • tako namesto minimuma iščemo maksimum negirane vrednosti, npr. min(5, 7, 2) = -max(-5, -7, -2)
Analiza negamax algoritma • kritična je časovna zahtevnost, ki je neposredno odvisna od števila obiskanih vozlišč v drevesu igre • pri analizi privzamemo: • število naslednikov je vedno b (branching factor) • globina drevesa d je fiksna • ni predčasnih zaključkov igre • razvitih vozlišč je:1 + b + b2 + b3 + ... + bd = bd ·(1-1/bd) / (1-1/b) ≈ bd • eksponentna rast • b je odvisen od pravil igre; z nekaterimi rešitvami ga je možno zmanjšati
Iterative deepening • kako izberemo globino iskanja d? • prvotni programi so preprosto privzeli neko konstantno globino d (časovno iskanje precej varira) • bolj realna omejitev je čas, ki ga imamo na voljo • obstaja dobra stran eksponencialne rasti časovne zahtevnosti – omogoča uporabo tehnike iterative deepening depth = 0; while (still time left) { depth++; m = negamax(pos, depth); } make move m; • večino časa porabimo za zadnjo globino d • predhodne iteracije omogočajo sortiranje potez
Alfa-Beta, posplošitev rezanja • če je vrnjena vrednost boljša od kateregakoli bratskega vozlišča sodo število nivojev višje v drevesu, lahko takoj naredimo rez • najmanjšo vrednost takšnega bratskega vozlišča zato podamo v klicu iskalne funkcije kot parameter beta • za lihe nivoje uporabimo parameter alfa, ki hrani najvišjo vrednost vozlišča na lihem nivoju • vrednosti alfa in beta se zamenjata analogno kot se zamenjata lihost in sodost nivojev, ko napredujemo v večjo globino
Aspiration search • kako izberemo začetne vrednosti za alfo in beto? • varna opcija je alfa = −∞, beta = +∞ • če uporabljamo iterative deepening, zaradi predhodnih iskanj dobimo predstavo o glavni varianti – in za alfo in beto lahko izberemo (v - ε, v + ε) • klic alfabeta(pos, depth, v - ε, v + ε) lahko povzroči: • vrnjena vrednost je znotraj intervala (v - ε, v + ε); O.K. • vrnjena vrednost je manjša od v - ε; fail low • vrnjena vrednost je večja od v + ε; fail high • če pride do neuspeha, moramo iskanje ponoviti z ustrezno popravljenimi vrednostmi v in ε • v splošnem je aspiration search majhno izboljšanje • Principal Variation Search: beta = alfa + 1
Analiza alfa-beta algoritma • analiza najboljšega možnega primera • vsako vozlišče na globini d-1 bo preiskalo samo enega naslednika na globini d, izjema je vozlišče ležeče na glavni varianti • na globini d-2 nobeno vozlišče ne reže, ker so vsi otroci vrnili vrednosti “večje od beta”, kar se na tem nivoju prelevi v “manjše od alfa”; sam alfa (zdaj beta) pa je ostal nespremenjen • torej, alterniramo med maksimalno in nič rezi, kar pomeni, da število vozlišč raste enkrat s faktorjem b in drugič s faktorjem ≈ 1 (glavna varianta) • preiščemo torej približno bd/2 = sqrt(b)d vozlišč – kar pomeni, da v enakem času lahko iščemo dvakrat globlje
Transpozicijske tabele • velikokrat imamo opravka s pozicijami, ki smo jih že videli (drugačen vrstni red potez, iterative deepening) • če imamo shranjeno vrednost te pozicije jo lahko ponovno uporabimo namesto nadaljnjega iskanja • vseh videnih pozicij ne moremo shraniti v pomnilniku – zato uporabimo hash tabele struct { long checksum; int depth; enum {exact, low_bound, up_bound} entry_type; double eval; } hashtable[HASH_TABLE_SIZE] • pred iskanjem pogledamo v tabelo, če nas tam slučajno čaka vrednost z ustrezno globino (večjo ali enako trenutni) • po iskanju dobljeno vrednost shranimo v tabelo • shranjevanje vrednosti pozicij blizu listov nima smisla
Alfa-Beta in vrstni red potez • optimistična analiza algoritma je predvidevala, da alfa-beta reže kadar je le možno • pogoj za to je, da dobre poteze preiščemo pred slabimi – pravzaprav zadostuje, da je med prvimi preiskana vsaj ena najboljših možnih potez • informacija, ki jo lahko uporabimo za sortiranje potez: • vrednosti v transpozicijski tabeli, ki jih shrani iterative deep. • poznavanje igre (za šah npr.: jemanja, centralizacija) • killer heuristic: dobra poteza v vozlišču bratu je lahko dobra tudi tukaj (če je možna); hranimo 1-2 killer potezi • v praksi je najboljša poteza preiskana prva v > 95% • enhanced transposition cutoff • precej dela lahko opravi že generator potez
Dodatne hevristike • null move heuristics • dodatno rezanje delov drevesa igre • ideja: spustimo lastno potezo, če je pozicija po dveh zaporednih nasprotnikovih potezah še zmeraj dobra (> beta) imamo rez • takšno rezanje je lahko nevarno (zugzwang) • history heuristics • nadgradnja killer hevristike • killer = shortterm memory, history = longterm memory • v posebni tabeli shranjujemo dobre poteze, ki so v preteklosti povzročile rez
Selective extensions • kdaj se pri iskanju ustavimo in vozlišče razglasimo za list ter pokličemo ocenjevalno funkcijo? • že Shannon (1950) je nakazal dve možni strategiji: • brute-force v popolni širini do fiksne globine • selektivno rezanje, ki razvija samo nekatera vozlišča; spet do fiksne globine (ampak precej globlje!) • problemi: efekt horizonta, spregled dobrih potez • danes programi uporabljajo mešanico obeh strategij (selective extensions): • iskanje v popolni širini do določene globine • od tam naprej pa nekatera vozlišča dodatno razvijamo
Kdaj razvijati naprej? • nadaljnje razvijanje vozlišča ima smisel v 2 primerih: • ko pričakujemo, da je ocenjevalna funkcija v danem vozlišču netočna • ko je trenutna pot v drevesu zelo pomembna (GV in okolica) • quiescence search • poskrbi, da ocenjujemo samo “stabilne” pozicije • ponavadi se ocenjevalna funkcija kliče samo znotraj quiescence search rutine • relativno učinkovito orožje proti efektu horizonta • singular extensions • previdnost zaradi kombinatorične eksplozije
Best-first search, SSS* in DUAL* • Stockman, 1979 • SSS* postopoma izboljšuje zgornjo mejo za pravo minimax vrednost pozicije (drevesa), začne pri +∞ • v vsakem koraku poskuša razviti tisto vozlišče, ki naj bi najbolj zmanjšalo zgornjo mejo • za to vzdržuje seznam vozlišč kandidatov, ki je sortiran • seznam je prostorsko potraten in njegovo vzdrževanje predstavlja velik časoven overhead • v teoriji boljši od α-β, v praksi ne • Schaeffer et al., 1994: α-β + TT = SSS* • DUAL* izboljšuje spodnjo mejo, začne pri −∞
Conspiracy Number Search • ideja: vozlišča razvijamo (selektivno) tako dolgo, da presežemo prag c, ki pove, koliko listov mora spremeniti svojo vrednost, da se bo spremenila vrednost korena • ne potrebuje znanja odvisnega od domene
Zanimivejši programi • Deep Blue • Logistello • MACH
Deep Blue • dobil dvoboj proti Kasparovu s 3,5 – 2,5 (1997) • razvoj že od leta 1980 (ChipTest, Deep Thought) • glavna značilnost: brute brute-force • sposoben je bil iskati 30-40 polpotez globoko • dosegel je hitrosti okoli 100-200M vozlišč na sekundo • paralelen računalnik, chess-specific hardware chips • 30 RS/6000 SP procesorjev, 120-135MHz, 1GB RAM • 480 šahovskih čipov, po 16 na procesor • uporablja quiscence search, iterative deepening, TT, alfa-beta algoritem, nima null-move hevristike
Deep Blue, search • en master, 29 delavcev, 480 pomožnih delavcev • zelo selektivno iskanje
Deep Blue, ocenjevalna funkcija • ocenjevalna funkcija je vsota 8000 členov • pozna statične (material, BPair) in dinamične uteži (king_safety, passed_pawn, pawn_structure) • avtomatska analiza ocenjevalne funkcije: • iskanje “šumnih” členov – tisti, za katere je bilo praktično vseeno kakšna utež jim je bila dana • avtomatski tuning delov ocenjevalne funkcije (pawn_shelter) z uporabo “comparison training” metodologije; Deep Blue je igral proti samemu sebi
Deep Blue vs. Deep (Fritz, Junior) • Bahrain, oktober 2002: Kramnik – Deep Fritz 4:4 • New York, januar 2003: Kasparov – Deep Junior 3:3 • Fritz in Junior sta napisana za navadne PCje, Deep so večprocesorske verzije • 8 procesorjev, 1.6 GHz, 8 GB RAM • hitrosti teh novih programov so ≈ 2M vozlišč/sekundo • avtorji: napredek je predvsem v ocenjevalni funkciji • Kasparov, Mig Greengard: današnji računalniki igrajo kot 2450-2550 ELO igralci, s perfektno taktično igro
Logistello • avtor je Michael Buro • program za igranje Othella • premagal svetovnega prvaka 6-0 • uporablja sistem GLEM za polavtomatsko konstrukcijo členov ocenjevalne funkcije • ekspert poda osnovne člene • gradnja logičnih kombinacij teh členov po določenih pravilih • osnovni členi so resnično zelo osnovni
Logistello • Buro je dal velik poudarek učinkoviti implementaciji • Logistello je kreiral in se naučil več kot 1,000,000 členov ocenjevalne funkcije • to presega ocenjene človeške sposobnosti, ki znašajo okoli 10,000 – 100,000 vzorcev • podobni poskusi kreiranja novih konceptov (členov) so bili tudi za druge igre, vendar z manjšim uspehom • razlog je verjetno v implementaciji in ne naravi igre
MACH • program za šah • uporablja nasvete ekspertov v obliki podatkovne baze velemojstrskih partij • v fazi učenja v bazi išče pogosto ponavljajoče vzorce, katerim določi uteži • igranje je realizirano kot simbioza sistema MACH in klasičnega šahovskega sistema • MACH v dani pozicije išče vzorce in na njihovi podlagi ocenjuje podobnost pozicije • ko najde podobno pozicijo lahko sugerira poteze ali celo plane • s tem usmerja iskanje klasičnega šahovskega programa • izhod iz otvoritvene knjige
Diskusija Laboratorij za umetno inteligenco e-mail: aleksander.sadikov@fri.uni-lj.si