Heurystyczne przeszukiwanie grafów gier dwuosobowych Wykład Informatyka Studia InŜynierskie Teoria gier w dziedzinie SI Liczba graczy jednoosobowe, dwuosobowe oraz wieloosobowe Suma wypłat gry o sumie zerowej (zyski i straty uczestników bilansują się) gry o sumie niezerowej (wygrane i przegrane nie muszą się bilansować) Dostępna wiedza gry z pełną informacją (precyzyjna wiedza o sytuacji i celach przeciwnika) gry z niepełną informacją (brak wiedzy na temat przeciwnika, czynnik losowy źródłem niepewności) 1
Gry dwuosobowe - przeszukiwanie heurystyczne Dwóch przeciwników posiadających pełną informację stanie gry i wszystkich moŝliwych ruchach Jeden gracz nosi nazwę Max, bo: maksymalizuje rezultat końcowy kaŝdy wzrost wartości oznacza poprawę dla tego gracza i równowaŝną stratę dla przeciwnika Drugi gracz nosi nazwę Min, bo: imalizuje rezultat końcowy kaŝdy spadek wartości oznacza poprawę dla tego gracza i równowaŝną stratę dla przeciwnika o Zasada - 5 7 9 5 9 7 7 9 3 5 2 3 9 4 1 7 2
Min- wynik! 5 7 9 5 9 7 7 9 3 5 2 3 9 4 1 7 wywołanie: Algorytm - result = MinMax(s, MAXDEPTH, MAX) int MinMax(state s, int depth, int type) { if( is_teral_node(s) depth==0 ) return(eval(s)); if( type == MAX ){ best = - ; for(child=1; child<=numofsucc(s); child++) { val = MinMax(Succ(s,child), depth-1, MIN); if( val > best ) best = val; } //endfor } else { // type == MIN best = ; for(child=1; child<=numofsucc(s); child++) { val = MinMax(Succ(s,child), depth-1, MAX); if( val < best ) best = val; } //endfor } return best; } 3
Przykład - - gra NIM - korzeń MAX 7-1 6-1 -1-1 5-2 -1 4-3 5-1-1 1 4-2-1-1 1 3-2-2 3-3-1-1 MAX MIN MAX Ocena: mniejsza wartość wygrana MIN większa wartość wygrana MAX 4-1-1-1 1-1 1 3-2-1-1 2-2-2-1 3-1-1-1-11 2-2-1-1-1-1 2-1-1-1-1-11 bo wygrał MAX bo wygrał MIN MIN MAX MIN Przykład - - gra NIM - korzeń MIN 1 7 MIN 6-11 1 5-2 1 4-3 5-1-1-1 4-2-1 1-1 3-2-2 1 3-3-1 MAX MIN Ocena: mniejsza wartość wygrana MIN większa wartość wygrana MAX 4-1-1-1 -1 1-1 3-2-1-1 2-2-2-1 3-1-1-1-1-1 2-2-1-1-1 1 2-1-1-1-1-1-1 bo wygrał MIN bo wygrał MAX MAX MIN MAX 4
Zasada nega (y) 5 7 y = - - -5-7 = (x) - -9-5 -9 - -7 9 5 9 7 x = - (z) 7 9 3 5 2 3 9 4 1 7 z Algorytm neg- wywołanie: result = NegMax(s, MAXDEPTH) int NegMax(state s, int depth) { if( is_teral_node(s) depth==0 ) return(eval(s,depth)); best = - ; for(child=1; child<=numofsucc(s); child++) { val = -NegMax(Succ(s,child), depth-1); if( val > best ) best = val; } //endfor return best; } Funkcja heurystycznej oceny stanu musi uwzględniać, kto wykonywałby ruch w ocenianym stanie. JeŜeli gracz MAX to ocena jest w postaci prostej, jeśli gracz MIN - w postaci zanegowanej. 5
Przykład nega- - gra NIM - ruch MIN 7-1 -MIN 6-1 1 5-2 1 4-3 1 5-1-1 1 4-2-1-1 1 3-2-2-1 3-3-1 MAX -MIN Ocena: MIN wartość zanegowana MAX wartość prosta 4-1-1-1-1 3-2-1-1 1 2-2-2-1-1 3-1-1-1-11-1 2-2-1-1-1 2-1-1-1-1-1-1 przegrał MIN wartość zaneg. przegrał MAX wartość prosta MAX -MIN MAX Przykład nega- - gra NIM - ruch MAX 7-1 MAX 6-1 1 5-2 1 4-3 1 5-1-1 1 4-2-1-1 1 3-2-2-1 3-3-1 -MIN MAX Ocena: MIN wartość zanegowana MAX wartość prosta 4-1-1-1-1 3-2-1-1 1 2-2-2-1-1-1 3-1-1-1-11 2-2-1-1-1 2-1-1-1-1-1-1 przegrał MAX wartość prosta przegrał MIN wartość zanegowana -MIN MAX -MIN 6
Kółko i krzyŝyk - heurystyka Funkcja oceny heurystycznej stanu gry - róŝnica liczby moŝliwych wygranych gracza X i gracza O Gracz X ma 6 moŝliwych wygranych Gracz O ma 5 moŝliwych wygranych E(n) = 6-5 = 1 Gracz X ma 4 moŝliwych wygranych Gracz O ma 6 moŝliwych wygranych E(n) = 4-6 = -2 Gracz X ma 5 moŝliwych wygranych Gracz O ma 4 moŝliwych wygranych E(n) = 5-4 = 1 Kółko i krzyŝyk - przykład gry(1) 1 Ruch MAXa 1-1 -2 6-5=1 5-5=0 6-5=1 5-5=0 4-5=-1 5-4=1 6-4=2. głębokość=2 5-6=-1 5-5=0 5-6=-1 6-6=0 4-6=-2 7
. głębokość=2 Kółko i krzyŝyk - przykład gry(2) 1 1 4-2=2 3-2=1 5-2=3 3-2=1 4-2=2 3-2=1 0 Ruch MAXa 4-3=1 3-3=0 5-3=2 3-3=0 4-3=1 4-3=1 1 0 4-2=2 4-2=2 5-2=3 3-2=1 4-2=2 4-2=2 4-3=1 4-3=1 3-3=0 Kółko i krzyŝyk - przykład gry(3) 1 1 - Ruch MAXa 2-1=1 3-1=2 2-1=1 3-1=2-2-1=1 2-1=1 2-1=1 - - - - 3-2=1 2-2=0 3-2=1-2-1=1 3-1=2 3-1=2. głębokość=2-2-2=0 2-2=0 3-2=1
Algorytm - (lub nega) Przyjmując określony branching factor (b) oraz stałą głębokość przeszukiwania (d) ZłoŜoność pamięciowa O(bd) ZłoŜoność czasowa O(b d ) Czy moŝna ten wynik poprawić? Tak! Branch&bound Ograniczenie dolne - odcięcieα 5... 5 3 odcięcie α... 5 7 3 9 Analiza lewego poddrzewa pokazała, Ŝe MAX ma ruch o wartości 5. Po sprawdzeniu lewego liścia środkowego poddrzewa widać, Ŝe wartość drugiego ruchu będzie mniejsza lub równa 3 (w stanie tym decyduje MIN!). Analiza pozostałych ruchów MINa nie ma zatem sensu, gdyŝ decyzja MAXa w korzeniu grafu nie moŝe juŝ ulec zmianie niezaleŝnie od ich wartości. 9
Ograniczenie górne - odcięcieβ 6... 6 9 odcięcie β... 6 3 9 2 Analiza lewego poddrzewa pokazała, Ŝe MIN ma ruch o wartości 6. Po sprawdzeniu lewego liścia środkowego poddrzewa widać, Ŝe wartość drugiego ruchu MINa będzie większa lub równa 9 (w stanie tym decyduje MAX!). Analiza pozostałych ruchów MAXa nie ma zatem sensu, gdyŝ decyzja MINa w korzeniu grafu nie moŝe juŝ ulec zmianie niezaleŝnie od ich wartości. Odcięcia α-β: przykład 7 9 3 5 2 3 9 4 1 7 10
Odcięcia α-β 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 7 9 3 5 2 3 9 4 1 7 11
Odcięcia α-β 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 9 7 9 3 5 2 3 9 4 1 7 12
Odcięcia α-β 9 9 β 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 9 9 β 7 9 3 5 2 3 9 4 1 7 13
Odcięcia α-β 9 9 β 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 9 5 9 β 7 9 3 5 2 3 9 4 1 7 14
Odcięcia α-β 9 5 9 5 β 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 9 5 9 5 β 5 7 9 3 5 2 3 9 4 1 7 15
Odcięcia α-β 9 5 9 5 β 5 α 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 9 5 9 5 β 5 5 α 7 9 3 5 2 3 9 4 1 7 16
Odcięcia α-β 9 5 9 5 β 5 5 α 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 9 5 9 5 β 5 5 α 7 9 3 5 2 3 9 4 1 7 17
Odcięcia α-β 5 5 α 9 5 9 5 β 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 5 5 α 9 5 9 5 β 7 9 3 5 2 3 9 4 1 7 1
Odcięcia α-β 5 5 α 9 5 9 5 β α 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 5 5 α 9 5 9 5 β α 7 9 3 5 2 3 9 4 1 7 19
Odcięcia α-β 5 5 α 9 5 9 5 β α 7 9 3 5 2 3 9 4 1 7 Odcięcia α-β 5 5 α 9 5 9 5 β α 7 9 3 5 2 3 9 4 1 7 20
Mechanizm odcięć alfa-beta Dwa ograniczenia: α dolne ograniczenie dla wierzchołków Max (najwyŝsza wartość jaką dotychczas osiągnął gracz Max) β górne ograniczenie dla wierzchołków Min (najniŝsza wartość jaką dotychczas osiągnął gracz Min) Wartość ograniczenia α ustalana jest w wierzchołku Max, a wartość ograniczenia β - w wierzchołku Min Odcięcie α wykonywane jest w wierzchołku Min, a odcięcie β - w wierzchołku Max Kiedy tylko zachodzi warunek α β, nie ma potrzeby analizowania dalszych następników danego stanu Algorytm AlfaBeta (zapis -) wywołanie: result = AlphaBeta(s, MAXDEPTH, -,, MAX) int AlphaBeta(state s,int depth,int alpha,int beta,int type) { if( is_teral_node(s) depth == 0 ) return(eval(s)); if( type == MAX){ for(child=1; child<=numofsucc(s); child++) { val = AlphaBeta(Succ(s,child),depth-1,alpha,beta,MIN); alpha = (val, alpha); if( alpha >= beta ) return beta; //cutoff } //endfor return alpha; } else { // type == MIN for(child=1; child<=numofsucc(s); child++) { val = AlphaBeta(Succ(s,child),depth-1,alpha,beta,MAX); beta = (val, beta); if( alpha >= beta ) return alpha; //cutoff } //endfor return beta; } } 21
Odcięcia α-β C 3 3 = 3 2 = 3 3 3 = 3 0 A 3 D 0 E 0 α 2 5 = 0 = 2 B 5 0 2 β 2 2 α 2 3 5 9 0 5 3 2 1 1 7 A ma próg β = 3 (A nie będzie większe niŝ 3) B odcięcie dla β, bo 5 > 3 C ma próg α = 3 (C nie będzie mniejsze niŝ 3) D odcięcie dla α, bo 0 < 3 E odcięcie dla α, bo 2 < 3 C ma wartość 3 Sformułowanie neg- dla AlfaBeta Sformułowanie - wymaga przemiennych wywołań rekurencyjnych dwóch graczy (raz dla gracza MAX, dwa dla gracza MIN, itd.) Sformułowanie neg- opiera się tylko na graczu MAX (jedna funkcja rekurencyjna) Przy wyjściu z rekurencji negujemy zwracaną wartość Czy to wystarczy? Nie! Przy zagnieŝdŝeniu rekurencyjnym w wersji neg- negujemy ograniczenia i zamieniamy miejscami 22
Algorytm AlfaBeta (zapis neg-) wywołanie: result = AlphaBeta(s, MAXDEPTH, -, ) int AlphaBeta(state s, int depth, int alpha, int beta) { if( is_teral_node(s) depth==0 ) return(eval(s,depth)); for(child=1; child<=numofsucc(s); child++) { val = -AlphaBeta(Succ(s,child),depth-1,-beta,-alpha); if( val > alpha ) alpha = val; // alpha=(val,alpha); if( alpha >= beta ) return beta; // cutoff } //endfor return alpha; } Cechy algorytmu AlfaBeta ŚcieŜka krytyczna (ang. principal variation) ścieŝka w grafie przeszukiwania od korzenia do najlepszego liścia Wartości zwracane: w wersji : ze względu na gracza w korzeniu w wersji neg-: ze względu na tego czyj jest ruch w liściu Bardzo zawikłany kod ewentualny błędy pozostają długo ukryte (problemy moŝna zauwaŝyć tylko wtedy, gdy niepoprawne wartości zostaną przepropagowane do korzenia grafu) Efektywność algorytmu zaleŝy w ogromnym stopniu od kolejności następników i występowania odcięć 23
Analiza algorytmu AlfaBeta Sytuacja idealna jeśli odcięcie ma się pojawić, to powinno wystąpić jak najszybciej, czyli najlepiej zaraz po sprawdzeniu pierwszego następnika 5 5 5 3 5 7 3 9 odcięcie α 5 9 5 7 9 3? zmieniona kolejność Odcięcia α-β (mniej odcięć!) = = = 9 3 = 9 9 = 5 5 3? 5 = 9 = 5 = 9 5 α? 7 3 9 3 9 5 2 4 1 7 zmieniona kolejność 24
ZłoŜoność algorytmu AlfaBeta Dla danej głębokości (d) i stałego braching factor (b) Najlepszy przypadek: O(b d/2 ) Najgorszy przypadek: brak odcięć (czyli jak MinMax) Średni przypadek: O((b/log b) d ) Słabości algorytmu AlfaBeta Efekt horyzontu (ang. horizont effect) Niewidoczny spadek wartości stanu tuŝ za wyznaczoną głębokością przeszukiwania Występuje we wszystkich odmianach algorytmu Wykrywanie stanów naraŝonych na wystąpienie efektu horyzontu i prowadzenia przeszukiwania za tym stanami - problem otwarty 25
Rozszerzenia algorytmu AlfaBeta Doskonalenie funkcji oceny stanu (funkcji heurystycznej) Modyfikacje sposobu przeszukiwania grafu zastosowanie pamięci (np. tablica przejść) porządkowanie następników manipulowanie zakresem α-β zmienna głębokość przeszukiwania przeszukiwanie eksploracyjne Rozwiązania sprzętowe (np. obliczenia równoległe) Wariant fail-soft algorytmu AlfaBeta Klasyczna postać algorytmu wartości zwracane zawsze z przedziału [α, β] Wariant fail-soft algorytmu AlfaBeta [Fishburn 1] zwraca dowolne wartości niezaleŝnie od początkowego zakresu [α, β] Wariant fail-soft algorytmu AlfaBeta stanowi podstawę do wszelkich jego modyfikacji, wykorzystujących manipulacje zakresem [α, β] 26
Algorytm AlfaBeta fail-soft (zapis neg-) wywołanie: result = AlphaBetaFS(s, MAXDEPTH, -, ) int AlphaBetaFS(state s, int depth, int alpha, int beta) { if( is_teral_node(s) depth==0 ) return(eval(s)); best = - ; for(child=1; child<=numofsucc(s); child++) { val = -AlphaBetaFS(Succ(s,child),depth-1,-beta,-alpha); if( val > best ) best = val; if( best >= beta ) break; // cutoff if( best > alpha) alpha = best; } //endfor return best; } Znaczenie zakresu α-β ZałóŜmy, Ŝe dla wierzchołka n o faktycznej wartości f procedura AlfaBetaFS(n, α, β) zwraca wartość g. MoŜemy wyróŝnić trzy następujące sytuacje: α < g < β (sukces) g jest równe faktycznej wartości f g α (failing low) g jest górnym ograniczeniem dla f (oznaczane jako f + ), tzn. f g g β (failing high) - g jest dolnym ograniczeniem dla f (oznaczane jako f ), tzn. f g 27
Zakresy α-β: sytuacja failing low (g α) f + =41 α = -1, β = f + =41 f + =36 f + =41 f + =36 f + =41 f + =12 f + =34 f + =36 41 5 12 90 101 0 20 30 34 0 36 35 50 36 25 3 Zakresy α-β: failing low Wywołanie procedury alfa-beta dla wierzchołka n z parametrami AlfaBetaFS(n, -1, ) (wszystkie liście mają wartości mniejsze) spowoduje: we wszystkich wierzchołkach MIN wystąpienie odcięćα, bo wartości wszystkich następników są g α= -1 we wszystkich wierzchołkach MAX brak jakichkolwiek cięćβ, bo wartości wszystkich następników są g < β = Otrzymane drzewo przeszukiwania będzie zawierać po jednym potomku dla kaŝdego wierzchołka MIN i wszystkie potomne dla kaŝdego wierzchołka MAX. 2
Failing low dlaczego górne ograniczenie? all... one all............ one............ Zakresy α-β: sytuacja failing high (g β ) f - =5 α = -, β = - +1 f - =5 f - =5 f - =0 f - =5 f - =0 41 5 12 90 101 0 20 30 34 0 36 35 50 36 25 3 29
Zakresy α-β: failing high Wywołanie procedury alfa-beta dla wierzchołka n z parametrami AlfaBetaFS(n, -, - +1) (wszystkie liście mają wartości większe) spowoduje: we wszystkich wierzchołkach MAX wystąpienie odcięć β, bo wartości wszystkich następników są g β = - +1 we wszystkich wierzchołkach MIN brak jakichkolwiek cięćα, bo wartości wszystkich następników są g > α = - Otrzymane drzewo przeszukiwania będzie zawierać po jednym potomku dla kaŝdego wierzchołka MAX i wszystkie potomne dla kaŝdego wierzchołka MIN. Failing high dlaczego dolne ograniczenie? one all... one all............ 30
AlfaBeta jako przeszukiwanie w głąb Jak określić właściwą głębokość przeszukiwania? Czym moŝna przeszukiwać do wierzchołków teralnych? Najczęściej nie! (Zbyt duŝa przestrzeń) Przeszukiwanie do ustalonej głębokości: Niewłaściwa kolejność następników (ruchów) moŝe doprowadzić do ogromnego grafu przeszukiwania Co w sytuacji, gdy głębokość jest za mała? Co w sytuacji, gdy głębokość jest za duŝa? Iteracyjne pogłębianie int iterative_deepening(state s) { depth = 0; { depth++; } value = AlfaBeta(s, depth, -, ); if( rescources_up() ) break; // stop } while( depth < MAXDEPTH ) return(value); 31
Iteracyjne pogłębianie Zalety Osiąganie maksymalnej moŝliwej głębokości przeszukiwania przy aktualnie dostępnych zasobach (obliczenia w systemach czasu rzeczywistego!) Gwarancja znalezienia najlepszego rozwiązania do określonej głębokości przeszukiwania Wady Wielokrotne przeszukiwanie tych samych obszarów przestrzeni stanów Iteracyjne pogłębianie Potencjalne korzyści z poprzednich iteracji Przed przejściem do przeszukiwania na głębokości d+1 moŝna uporządkować ruchy na podstawie wyników uzyskanych dla głębokości d W większości gier słuszne jest załoŝenie, iŝ najlepszy pierwszy ruch w przeszukiwaniu na głębokość d stanowi dobre przybliŝenie najlepszego ruchu w przeszukiwaniu na głębokość d+1 Wzrasta prawdopodobieństwo wybrania właściwego pierwszego ruchu im bliŝej ostatniej (najkosztowniejszej) iteracji 32
Iteracyjne pogłębianie: zmiana kolejności A B C 15 23 12 B A C 30 17 19 głębokość d głębokość d+1 Badania eksperymentalne wykazały, iŝ koszty wielokrotnego przeszukiwania przestrzeni stanów są niewspółmiernie niskie w stosunku do zysków wynikających z porządkowania ruchów w wierzchołkach grafu. Sterowanie zakresem α-β Metody manipulowania zakresem odcięć Aspiration Search [Slate&Atkin 77] Metody z imalnym zakresem * NegaScout (PVS) [Reinefeld 3] * Rodzina algorytmów MTD [Plaat 96] 33
Zakresy α-β: Aspiration Search Tradycyjny zakres przeszukiwania (, ) Co w sytuacji, gdy jesteśmy w pewnym stopniu przewidzieć rezultat przeszukiwania? Przeszukiwanie z zakresem (v, v + ), gdzie: v spodziewany rezultat zakładane odchylenie od tej wartości ( >0) Mniejszy zakres alfa-beta oznacza więcej odcięć i mniejszy graf przeszukiwania Kiedy przewidywania się nie sprawdziły (failing-low lub failing-high), konieczność powtórzenia przeszukiwania z większym zakresem Zakresy α-β: Algorytm Aspiration Search int IDAspirationSearch(state s, deviation ) { guess = 0; for(depth=1;!resources_up(); depth++) { alpha = guess- ; beta = guess+ ; score = AlphaBetaFS(s, depth, alpha, beta); if( score >= beta ) { // failing high alpha = score; beta = ; score = AlphaBetaFS(s, depth, alpha, beta); } else } if( score <= alpha ) { // failing low alpha = - ; beta = score; score = AlphaBetaFS(s, depth, alpha, beta); } guess = score; } //endfor return(guess); 34
Zakresy α-β: Własności Aspiration Search Nietrywialny problem wyboru początkowej wartości zakresu alfa-beta (wartości guess oraz ) Iteracyjne pogłębianie automatyzuje proces manipulowania zakresem α-β wartość korzenia wyznaczona w poprzedniej iteracji jest najlepszym przybliŝeniem środka przedziału w następnej iteracji Podwójna kontrola przebiegu przeszukiwania: sterowanie głębokością oraz manipulowanie przedziałem alfa-beta Jeśli nie Aspiration Search, to co? AspirationSearch nie moŝe korzystać ze zbyt małego zakresu α-β, bo koszty powtórzeń przeszukiwania byłyby zbyt duŝe! AspirationSearch ogranicza się tylko do manipulacji zakresem w korzeniu grafu, dlaczego nie robić tego w kaŝdym wierzchołku grafu? Bardziej efektywne metody operują na imalnym zakresie α-β, tj. β = α+1 ( algorytmy NegaScout oraz MTD(f) ), ale wymagają dodatkowej pamięci do porządkowania ruchów 35