Aleksander Sadikov
Kako se računalnikspopade 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
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
Bitboards
&
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
Predstavitev možnih akcij (potez)
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
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)
Negamax algoritem
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, plitvo rezanje
Alfa-Beta, globoko rezanje
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
Alfa-Beta algoritem
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 tabelestruct {
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