Svarog - język do AI

4

Witam wszystkich.
Jakieś dwadzieścia lat temu odkryłem nowy algorytm planowania oparty na tzw. zmiennych ukrytych. Pięć lat temu napisałem pierwszą implementację - język Perkun (www.perkun.org). Niedawno napisałem nowy, trochę lepszy język o nazwie Svarog. Bardzo chciałbym porozmawiać o tym algorytmie. Nie mam z kim. Próbowałem już pisać o Perkunie na forum "Oceny i recenzje", ale bez większego odzewu.
Proponuję ściągnąć i zainstalować sobie Svaroga - https://www.perkun.org/resources/targz/svarog-0.0.1.tar.gz. Następnie należy wejść do katalogu examples i wstukać np. make test1. Działa też make test2 i make test3. Wszystkie one wykorzystują specyfikację Svaroga o nazwie example1_dorban.svarog. Różnią się tylko danymi wysyłanymi na wejście. Te dane np. dla testu 1 opowiadają taką historię:

Jest sobie bohater Dorban, są trzy miasta (Wyzima, Shadizar i Novigrad). W jednym z tych miast jest wampir. Również w jednym z miast jest drugi bohater Pregor, który może pomóc Dorbanowi pokonać wampira. W teście 1 Dorban od razu spotyka wampira w Wyzimie, ale nie atakuje go, idzie do Novigradu. Tam nikogo nie ma, więc Dorban spodziewa się, że Pregor musi być w Shadizarze. Idzie tam, prosi go o pomoc i następnie obaj idą do Wyzimy (tam gdzie jest wampir). Atakują wampira, z sukcesem. Atakują drugi raz - tym razem przegrywają i Pregor się odłącza. W związku z tym Dorban ponownie prosi go o pomoc. Koniec historii.

Najważniejsze jest to, że Svarog korzysta ze zmiennych ukrytych. W danym przykładzie są dwie (where_is_vampire i where_is_pregor). Odkryłem formułę w jaki sposób interpretować daną obserwację, tj. jak konstruować tzw. belief (rozkład prawdopodobieństwa nad zbiorem możliwych wartości zmiennych ukrytych). Zasadnicza cecha mojego algorytmu to to, że konstruując drzewo gry wykorzystuje tę formułę, tj. wyobraża sobie "teraz wierzę w X1", "pójdę do Novigradu", "zobaczę tam wampira/Pregora bądź nie". Zależnie od obserwacji zinterpretuję ją tak a tak. Wtedy będę wierzył w X2.

Postaram się porządnie matematycznie opisać sam algorytm, już zresztą zacząłem. Różni się od minimaxu tym, że jest tylko jeden agent (nie dwóch), że świat jest stochastyczny i że niektóre zmienne nie są obserwowalne. To tzw. hidden variables (zmienne ukryte).

1

Masz ode mnie plusika za nazwę języka i wykorzystanych w nim obiektów :)

Ale to co napisałeś, brzmi po prostu jak opis automatu stanowego, no może kilku automatów stanowych działających równolegle wedle zasad programowania agentowego. Co jest w tym szczególnie rewolucynego?

Bo istnieją już np. podobne do opisanych systemy zdolne pisać proste opowiadania o bohaterach (agentach), którzy posiadają własne stany psychiczne i teorie na temat innych bohaterów, oraz podejmują decyzje w oparciu o to, czego wcześniej doświadczyli ze strony innych bohaterów.

0

Dzięki za plusa i za odpowiedź. To prawda, że mamy do czynienia z automatem stanowym. Dla każdej kombinacji danych wejściowych mamy pewien stan widoczny (visible state). Ale rzeczywisty stan świata zależy jeszcze od wartości zmiennych ukrytych. Taka kombinacja wartości zmiennych ukrytych to stan (state). Każdy stan widoczny ma jednocześnie listę stanów. Rewolucyjne jest to, że Svarog utrzymuje belief nad taką listą, to znaczy po każdej konkretnej akcji A wykonanej w widocznym stanie VS1 i z beliefem B1 (który jest pewną funkcją, rozkładem) dla każdego nowego stanu widocznego VS2 otrzymujemy nowy belief B2=B(VS1,B1,A,VS2). I tą "sztuczkę" przeprowadzam również planując w drzewie gry, czyli gdy wyobrażam sobie, że wykonam akcję A i otrzymam jako rezultat stan widoczny VS2, to planując następny krok zakładam już w co będę wierzył PO ZINTERPRETOWANIU VS2. Czyli do kolejnej iteracji algorytmu, na kolejnym węźle drzewa gry wchodzę już z beliefem B(VS1,B1,A,VS2)!

Powiedzmy, że jestem w Wyzimie i nie widzę nikogo. Odpowiada to wejściu "none place_Wyzima false false false" (mniejsza teraz o znaczenie poszczególnych wartości). Wówczas belief będzie równy:

where_is_vampire=>place_Novigrad where_is_pregor=>place_Novigrad has_dorban_won_a_fight=>none where_is_dorban=>place_Wyzima can_dorban_see_pregor=>false can_dorban_see_vampire=>false is_pregor_following_dorban=>false 0.250000
where_is_vampire=>place_Novigrad where_is_pregor=>place_Shadizar has_dorban_won_a_fight=>none where_is_dorban=>place_Wyzima can_dorban_see_pregor=>false can_dorban_see_vampire=>false is_pregor_following_dorban=>false 0.250000
where_is_vampire=>place_Shadizar where_is_pregor=>place_Novigrad has_dorban_won_a_fight=>none where_is_dorban=>place_Wyzima can_dorban_see_pregor=>false can_dorban_see_vampire=>false is_pregor_following_dorban=>false 0.250000
where_is_vampire=>place_Shadizar where_is_pregor=>place_Shadizar has_dorban_won_a_fight=>none where_is_dorban=>place_Wyzima can_dorban_see_pregor=>false can_dorban_see_vampire=>false is_pregor_following_dorban=>false 0.250000

Po ludzku mówiąc są cztery możliwości - Pregor i wampir są w Novigradzie, albo wampir w Novigradzie a pregor w Shadizarze, albo wampir w Shadizarze a pregor w Novigradzie, albo obaj są w Shadizarze. Na pewno żadnego nie ma w Wyzimie, bo Dorban by ich widział, a nie widzi. Wykonując kolejną akcję (idź do Novigradu) nie mamy gwarancji, że spotkamy tam Pregora lub wampira. Ale idziemy tam, bo wiemy, że jest szansa na spotkanie tam Pregora. Zależnie od tego czy zobaczymy tam Pregora i wampira będziemy już w Novigradzie wiedzieli na pewno gdzie kto jest (bo miast jest tylko trzy). Załóżmy, że zobaczymy w Novigradzie Pregora a nie zobaczymy wampira. Wówczas będziemy już WIEDZIELI, że wampir jest w Shadizarze (dzięki mojej formule interpretacji obserwacji) ale nie rzucimy się od razu do Shadizaru, tylko najpierw poprosimy Pregora o pomoc. Dlatego, że sam Dorban ma przeciwko wampirowi marne szanse, ale z pomocą Pregora ma już duże szanse.

Najważniejsze jest to, że planując, w każdym węźle drzewa gry konstruujemy nowy belief dla zakładanego rezultatu. Dzięki temu Svarog jest w stanie przeprowadzać eksperymenty, nawet poświęcać bezpośredni zysk dla wartości oczekiwanej zysku w przyszłości (bo w powyższym przykładzie podróż do Novigradu jest takim eksperymentem). Eksperyment daje nam pewną obserwowalną wiedzę (visible state), którą następnie zinterpretujemy i będziemy wiedzieć, być może, więcej.

Mam nadzieję, że wyjaśniłem. Mojemu przydługiemu wywodowi odpowiada w kodzie (w pliku src/optimizer.cc) funkcja float optimizer::get_payoff_expected_value_for_consequences(const belief & b1, int n, const action & a)

Jest w niej mianowicie (w linii 439) polecenie

populate_belief_for_consequence(b1, a, **i, b2); 

I to jest właśnie miejsce, w którym konstruujemy nowy belief (b2) dla danych wejściowych: poprzedniego beliefu (b1), akcji (a) i zakładanego stanu widocznego (**i). Dalej w drzewie gry, w liniach 442 i 444 wykorzystujemy już b2, nie b1! Czyli np, idę do Novigradu, zobaczę tam wampira, ale nie Pregora, będę wtedy myślał, że Pregor jest w Shadizarze, a wampir oczywiście w Novigradzie!

1

Nie wiem czy dobrze zrozumiałem problem, ale mi to wygląda na powiązane z teorią gier i grami z niepełną informacją. Czy Svarog ma ułatwiać tworzenie strategii dla takich gier?

1

Tak, właśnie tak jest. Od tego powinienem zacząć. To algorytm optymalizacji, który w warunkach gry z niepełną informacją maksymalizuje pewną funkcję zwaną "payoff" (funkcję odpłaty).

W liniach 80-88 pliku examples/example1_dorban.svarog jest zdefiniowana funkcja odpłaty:

	payoff "payoff for winning a fight":100.0 {
		requires initial value has_dorban_won_a_fight == true;
	}
	payoff "payoff for loosing a fight":0.0 {
		requires initial value has_dorban_won_a_fight == false;
	}
	payoff "neutral payoff":50.0 {
		requires initial value has_dorban_won_a_fight == none;
	}

Algorytm ją maksymalizuje, czyli Dorban lubi wygrywać z wampirem (100 punktów), nie lubi przegrywać (0 punktów) i jest neutralny wobec wartości "none" (50 punktów). W Perkunie trzeba było funkcję odpłaty zdefiniować dla wszystkich możliwości, dlatego Perkun jest trochę słabszy od Svaroga.

Oprócz funkcji odpłaty ważny jest model, czyli opis jak działa świat. Znowu w Perkunie trzeba było jawnie podać model dla wszystkich możliwości. W Svarogu jest trochę wygodniej.

1

Rozumiem, że:

  • definiujemy w Svarogu pewien model działania świata na zasadzie niedeterministycznego automatu (tj. STAN x ZDARZENIE -> NOWY_STAN, gdzie NOWY_STAN jest tak naprawdę zmienną losową {nowyStan z prawdopodobieństwem P1}, {nowyStan2 z prawdopodobieństwem P2} etc.) .
  • prawdopodobieństwa zależą od stanu ukrytego
  • funkcja wypłaty ("odpłaty" tak średnio brzmi jak dla mnie) może zależeć ukrytego stanu

Co jest efektem działania programu w Svarogu?

1

Chyba nie do końca rozumiem o co chodzi. Ale z terminologi której używasz wnioskuje, że zrobiłeś coś w stylu ukrytego modelu markova?

0
  1. Ja bym to opisał tak:
    STAN x AKCJA AGENTA -> rozkład NOWYCH STANÓW

  2. prawdopodobieństwa zależą od stanu ukrytego, jak najbardziej

  3. funkcja wypłaty NIE ZALEŻY od ukrytego stanu, zależy wyłącznie od zmiennych wejściowych (obserwowalnych). To była świadoma decyzja.

  4. Efektem działania programu w Svarogu w trybie interaktywnym jest optymalizator, który steruje agentem (wybiera wartości zmiennych wyjściowych) tak, by zmaksymalizować wartość oczekiwaną funkcji wypłaty biorąc pod uwagę wartości zmiennych wejściowych.

Można korzystać ze SVAROGA we własnych programach C++ jako z biblioteki. To trochę analogiczne do mojej gry PerkunWars, która tworzy dodatkowe procesy dla każdego NPC i w każdym takim procesie odpala interpreter Perkuna, który komunikuje się z rodzicem przez pipe'y.

Od razu zastrzegam, że nie mam dowodu na poprawność algorytmu. Nie mam nawet porządnego matematycznego opisu. Pracuję nad tym.

0
part napisał(a):

Chyba nie do końca rozumiem o co chodzi. Ale z terminologi której używasz wnioskuje, że zrobiłeś coś w stylu ukrytego modelu markova?

Mam wrażenie, że to coś podobnego, ale chyba nie to samo. U mnie zmienne wejściowe są zmiennymi stanu świata, tak samo jak zmienne ukryte. Obserwacja jest częścią rzeczywistości a nie jakąś zmienną losową skorelowaną z rzeczywistością. U mnie istnieje naturalne odwzorowanie ze zbioru stanów świata do zbioru obserwacji (stanów widocznych).

Łatwiej to prześledzić w Perkunie, np. model jest zapisywany jako ciąg instrukcji:
set(INITIAL_STATE,ACTION,TERMINAL_STATE,PROBABILITY);
Przy czym zarówno INITIAL_STATE jak i TERMINAL_STATE zawiera zmienne wejściowe (z wartościami) i zmienne ukryte (z wartościami). Np.

set({alpha=>true,beta=>false},{gamma=>false},{alpha=>false,beta=>true},0.3);

Przy czym beta może być zmienną ukrytą a alpha zmienną wejściową. Gamma oczywiście musi być zmienną wyjściową. Powyższa instrukcja mówi, że w razie akcji agenta {gamma=>false} prawdopodobieństwo przeskoku ze stanu {alpha=>true,beta=>false} do stanu {alpha=>false,beta=>true} wynosi 0.3. Dla mojego modelu nie ma znaczenia co agent widzi a czego nie widzi. Ważne tylko co robi.

W modelach Markowa o ile je rozumiem mamy jakąś ukrytą rzeczywistość i coś w rodzaju skorelowanej z nią obserwacji. W moich modelach agent widzi część rzeczywistości.

1

Nie lubię takich udziwnionych przypadków. Z tego co rozumiem problem polega na tym:
1. Masz parę dróg do przeszukania i każda ma jakieś tam prawdopodobieństwo, że jest właściwa
2. Za każdym razem kiedy przeczesujesz drogę musisz zaktualizować prawdopodobieństwa

Czyli to taki udziwniony Monty Hall? W obu przypadkach można użyć prostej sieci bayesowskiej np. Infer.Net

Może prościej - odnieś swój program do problemu Monty Halla.

Zawodnik stoi przed trzema zasłoniętymi bramkami. Za jedną z nich (za którą – wie to tylko prowadzący program) jest nagroda (umieszczana całkowicie losowo). Gracz wybiera jedną z bramek. Prowadzący program odsłania inną bramkę (co istotne – anonsując, że jest to bramka pusta), po czym proponuje graczowi zmianę wyboru. (zerżnięte z wiki)

Jak twój NPC się zachowa?

1

Ja bardzo lubię. Dzięki za interesujący przypadek. Nie znałem tego paradoksu. Rozwiązałem go w Perkunie. Proponuję ściągnąć i rozpakować montyhall.zip. Do tego trzeba też niestety Perkuna, nie mam rozwiązania w Svarogu. Zip zawiera trzy pliki:

montyhall.perkun - szkielet generujący generator w Prologu
montyhall_generator.prolog - generator w Prologu uzupełniony o pewne fakty
montyhall_generated.perkun - ostateczna specyfikacja w Perkunie

Odpalmy:

perkun montyhall_generated.perkun

Odpowiedź brzmi:

loop with depth 3
I expect the values of the variables: x_is_empty reward first_attempt 
perkun> 

Wstukajmy "none false true" (czyli nie wiadomo co jest puste z możliwych bramek a,b,c, poza tym nie ma nagrody i to pierwsze podejście). Odpowiedź:

belief:
solution=a x_is_empty=none reward=false first_attempt=true 0.333333
solution=b x_is_empty=none reward=false first_attempt=true 0.333333
solution=c x_is_empty=none reward=false first_attempt=true 0.333333
optimal action:
guess=a 
perkun> 

Czyli Perkun wybiera a. Teraz podajmy informację, że któraś z bramek jest pusta. Np. b:
"b false false".
Odpowiedź:

belief:
solution=a x_is_empty=b reward=false first_attempt=false 0.333333
solution=b x_is_empty=b reward=false first_attempt=false 0.00000
solution=c x_is_empty=b reward=false first_attempt=false 0.666667
optimal action:
guess=c 

Zadziwiające. Oczywiście Perkun zachowa się logicznie (choć wbrew intuicji) i oceni, że bramka c ma dwa razy większe szanse być właściwym rozwiązaniem niż bramka a. I ZMIENI SWOJĄ DECYZJĘ.

1

No dobra, ale czy w tym przypadku twoja metoda nie staje się równoznaczna z siecią zaimplementowaną w infer.net? Nie chce udowadniać, że twój język nie działa. Chce zrozumieć czym Twój algorytm różni się od tych już istniejących.

Weźmy twój przykład - w systemie bayesowskim każde miasto miałoby przypisane - prawdopodobieństwo, że w mieście jest wampir i prawdopodobieństwo, że mieście jest Pregor. Twój NPC cytując Ciebie "wierzył by", że w mieście z największym przypisanym prawdopodobieństwem jest wampir.
Po wizycie w mieście aktualizowałbyś te prawdopodobieństwa (na podstawie obserwacji) i dokonywał dalszego wyboru "wiary".

Zakładam, że tak działa twój system, popraw mnie jeżeli się mylę. Kwestią jest czym się różni od już istniejących?

0
part napisał(a):

No dobra, ale czy w tym przypadku twoja metoda nie staje się równoznaczna z siecią zaimplementowaną w infer.net? Nie chce udowadniać, że twój język nie działa. Chce zrozumieć czym Twój algorytm różni się od tych już istniejących.

Weźmy twój przykład - w systemie bayesowskim każde miasto miałoby przypisane - prawdopodobieństwo, że w mieście jest wampir i prawdopodobieństwo, że mieście jest Pregor. Twój NPC cytując Ciebie "wierzył by", że w mieście z największym przypisanym prawdopodobieństwem jest wampir.
Po wizycie w mieście aktualizowałbyś te prawdopodobieństwa (na podstawie obserwacji) i dokonywał dalszego wyboru "wiary".

Zakładam, że tak działa twój system, popraw mnie jeżeli się mylę. Kwestią jest czym się różni od już istniejących?

Nie wiem jak działa infer.net. Muszę się dokształcić.
Podejrzewam, że istniejące algorytmy nie stosują mojej metody propagowania beliefu przez drzewo gry. Nie znam wszystkich istniejących algorytmów, tak tylko "strzelam".

Może tak. Jest sobie moja funkcja void optimizer::populate_belief_for_consequence(const belief & b1, const action & a, const visible_state & vs, belief & target). I ona jest używana w dwóch miejscach - w komendzie loop (plik src/command_loop.cc) i w samym algorytmie planowania (funkcja float optimizer::get_payoff_expected_value_for_consequences(const belief & b1, int n, const action & a)). W komendzie loop za każdą iteracją dopasowujemy wiarę do obserwacji. A w planowaniu, w trakcie budowania drzewa gry wykorzystujemy identyczną metodę do zinterpretowania hipotetycznych rezultatów w przyszłości. Jeśli ktoś już na to wpadł - to bardzo przepraszam. Ale chyba nikt nie wpadł, bo tłumaczenie tego jak działa Perkun czy Svarog idzie mi wyjątkowo ciężko.

0

Poczytałem sobie o infer.net i wyrobiłem sobie pogląd. Nie, to nie jest to samo. W infer.net mamy zmienne losowe i rozkłady (modele), i z jednych zmiennych możemy wnioskować o innych. Mój algorytm jest algorytmem planowania (tak jak minimax), który optymalizuje pewną funkcję (payoff) w grze z niepełną informacją i jednym agentem. Moje modele też są różne od tych z infer.net. Ja mam dowolny rozkład przy przejściu z INITIAL_STATE do TERMINAL_STATE, przy zadanej akcji agenta ACTION. Ponadto taka sieć infer.net nie jest w stanie planować. Nie ma w niej pojęcia drzewa gry, które dla mnie jest kluczowe.

Jeśli to co robię jest technologią niszową, to bardzo niedobrze. Cóż może być bardziej naturalnego niż taki problem - maksymalizuj zadaną funkcję na zmiennych wejściowych wyobrażając sobie dowolne zmienne ukryte i z zadanym stochastycznym modelem świata. Wszyscy gramy w tą grę, przynajmniej dopóki nie pojawią się w naszym otoczeniu inni gracze. Próbowałem zrobić odpowiednik mojego algorytmu dla n graczy (jest taki projekt - Perkun2), ale mam wrażenie, że mi się nie udało. Natomiast z Perkuna (i Svaroga) jestem zadowolony.

Jest taka gra, o której już pisałem - PerkunWars. To kiepska gra, ale dobry przykład jak używać Perkuna w swoich programach C++. Trzeba najpierw zrobić pipe'y, następnie fork (dla każdego NPC sterowanego interpreterem Perkuna) i w procesie potomnym trzeba stworzyć instancję klasy dziedziczonej z perkun::optimizer_with_all_data. W PerkunWars to jest klasa npc. Zredefiniować trzeba funkcje wirtualne get_input i execute żeby odpowiednio czytały/pisały przez utworzone pipe'y od i do procesu-rodzica. I odpalić metodę parse (parser od razu wykonuje wszystkie komendy).

NPC w samej grze dzielą się na dwie kategorie - to zwykli chłopi, którzy uciekają przed wampirem (zawsze gdy go zobaczą w następnym ruchu uciekają do innego miasta) - ich jest dwóch. Druga kategoria to Dorban, który jest wiedźminem i ściga wampira. Istnieją zasadniczo dwie strategie - albo polować na wampira z Dorbanem albo czekać z chłopami aż przyjdzie wampir i wtedy go zaatakować. Bo ci "chłopi" też pomagają w walce z wampirem, chociaż w następnym ruchu się wyniosą. I okazuje się, że ta druga strategia jest lepsza (bo ich jest dwóch). Ciekawy jest tylko "czat" w którym każdy NPC mówi co sądzi na temat jedynej w tej grze zmiennej ukrytej - gdzie jest wampir.

Jestem bardzo wdzięczny za wszystkie komentarze. Zajmuję się tym od wielu lat i kiedyś nawet pewien profesor mi powiedział, że nie mogę sobie czegoś ot tak po prostu napisać (to było w Niemczech). Wysyłałem maile do akademików w Helsinkach (ja mieszkam w Finlandii) żeby ich tym zainteresować. Bez rezultatu. Wysłałem kiedyś artykuł na jakąś konferencję - został odrzucony. Generalnie czuję się raczej "niszowo", chociaż mogę udowodnić, że mój program działa.

1

Nie rozumiem algorytmu, ale próbuję sobie jakoś to poukładać.

Z tego co napisałeś:

  1. Są stany świata (S1, ..., Sn)
  2. Są możliwe akcje do wykonania przez agenta (A1, ..., Am)
  3. Akcje skutkują przejściem z obecnego stanu do innego stanu.

W ogólności akcja A_k skutkuje przejściem ze stanu S_i do S_j z prawdopodobieństwem p_ijk:

  • 0<= p_ijk <=1
  • Dla ustalonego stanu S_i i akcji A_k , suma po j=1..n p_ijk = 1 (czyli zadajemy prawdopodobieństwa do przejścia z ustalonego stanu, do innego stanu, niektóre być może 0 <- przejście nie jest dozwolne)
  1. p_ijk zależą od stanu ukrytego, ale nie wiesz jaki jest "prawdziwy" rozkład

  2. Masz funkcję, którą optymalizujesz.

Czy działa to mniej więcej tak?
a) Ten rozkład p_ijk to Twój "belief" ?
b) Czy Twój algorytm aktualizuje prawdopodobieństwa p_ijk ? (odnoszę wrażenie, że tak <- na podstawie przykładu z wyborem bramki )
c) Czy Twój algorytm buduje drzewo: wykonam akcję A_1 i wyliczę wartość oczekiwaną, wykonam akcję A_2 i wyliczę wartość oczekiwaną, ..., wykonam akcję A_m i wyliczę wartość oczekiwaną -> wybieram maxa
d) Agent wykonuje akcję dającą maxa i obserwuje co się dzieje ? W przypadku gdy źle wybrał, to aktualizuję rozkład prawdopodobieństwa ? (W jaki sposób ta korekta jest liczona?)

To rzecz jasna moje wyobrażenie jak to działa (mój "belief" ;) ) i może działa zupełnie inaczej.

0
yarel napisał(a):

Nie rozumiem algorytmu, ale próbuję sobie jakoś to poukładać.

Z tego co napisałeś:

  1. Są stany świata (S1, ..., Sn)
  2. Są możliwe akcje do wykonania przez agenta (A1, ..., Am)
  3. Akcje skutkują przejściem z obecnego stanu do innego stanu.

W ogólności akcja A_k skutkuje przejściem ze stanu S_i do S_j z prawdopodobieństwem p_ijk:

  • 0<= p_ijk <=1
  • Dla ustalonego stanu S_i i akcji A_k , suma po j=1..n p_ijk = 1 (czyli zadajemy prawdopodobieństwa do przejścia z ustalonego stanu, do innego stanu, niektóre być może 0 <- przejście nie jest dozwolne)
  1. p_ijk zależą od stanu ukrytego, ale nie wiesz jaki jest "prawdziwy" rozkład

  2. Masz funkcję, którą optymalizujesz.

Czy działa to mniej więcej tak?
a) Ten rozkład p_ijk to Twój "belief" ?
b) Czy Twój algorytm aktualizuje prawdopodobieństwa p_ijk ? (odnoszę wrażenie, że tak <- na podstawie przykładu z wyborem bramki )
c) Czy Twój algorytm buduje drzewo: wykonam akcję A_1 i wyliczę wartość oczekiwaną, wykonam akcję A_2 i wyliczę wartość oczekiwaną, ..., wykonam akcję A_m i wyliczę wartość oczekiwaną -> wybieram maxa
d) Agent wykonuje akcję dającą maxa i obserwuje co się dzieje ? W przypadku gdy źle wybrał, to aktualizuję rozkład prawdopodobieństwa ? (W jaki sposób ta korekta jest liczona?)

To rzecz jasna moje wyobrażenie jak to działa (mój "belief" ;) ) i może działa zupełnie inaczej.

a). Nie, belief to coś innego. To o czym piszesz p_ijk to model. Belief to rozkład nad możliwymi stanami zawężonymi do stanu obserwowalnego. Weźmy taki przykład:

variables 
{
    input variable alpha:{false,true};
    hidden variable beta:{false,true};
    output variable gamma:{false,true};
}

Stanem obserwowalnym jest np. alpha=>false. Odpowiadają mu w naturalny sposób dwa stany: {alpha=>false,beta=>false} i {alpha=>false,beta=>true}. Belief jest rozkładem nad tymi dwoma stanami i nie ma nic wspólnego z modelem. Inny stan obserwowalny to alpha=>true. Odpowiadają mu dwa stany (chciałoby się rzec "ukryte") a mianowicie {alpha=>true,beta=>false} i {alpha=>true,beta=>true}. Znowuż belief dla tego stanu obserwowalnego byłby jakimś rozkładem nad tymi dwoma stanami.

Natomiast model wygląda być może tak (w Perkunie):

model
{
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>false ,beta=>false },0.10000);
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>false ,beta=>true },0.20000);
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>true ,beta=>false },0.30000);
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>true ,beta=>true },0.40000);
....
}

b). Nie, mój algorytm nie aktualizuje modelu. Model jest stały, z góry zadany. Aktualizuje belief.

c). Prawie tak. Funkcja get_optimal_action rozważa wszystkie akcje, dla każdej akcji A1 rozważane są wszystkie stany obserwowalne VS1, konstruowany jest nowy belief B2 i dla niego z kolei rozważamy wszystkie akcje, dla każdej akcji A2 rozważane są wszystkie stany obserwowalne VS2, konstruowany jest nowy belief B3 itd. Z funkcji get_optimal_action zwracany jest argmax funkcji get_payoff_expected_value_for_consequences.

d). Rozumiem, że odnosisz się do paradoksu MontyHall. Nie. Po prostu za każdym razem stara się zmaksymalizować wartość oczekiwaną funkcji payoff. Aktualizuje belief ZAWSZE, nie tylko gdy źle wybrał. Po prostu musi to zrobić, żeby dostosować belief do nowej sytuacji (nowych wartości na wejściu). Robi to uwzględniając poprzedni belief, wykonaną akcję, otrzymaną odpowiedź (stan widoczny) i model.

To jak belief jest aktualizowany to już moja słodka tajemnica. Żartuję, tak naprawdę to treść funkcji optimizer::populate_belief_for_consequence. To jest funkcja używana w dwóch miejscach - w normalnej pętli (loop), żeby zareagować na kolejny sygnał na wejściu (zinterpretować rezultat otrzymany z ostatnio wykonanej akcji) oraz w planowaniu, przy przejściu do kolejnego węzła drzewa gry. Możesz sprawdzić, że mówię prawdę odpalając w katalogu Svaroga polecenie:

find src -type f -exec grep -H "populate_belief_for_consequence" {} \;

Wyjdzie, że oprócz definicji mamy tylko jedno użycie w src/optimizer.cc (to jest to planowanie) i jedno w command_loop.cc (to jest zwykła interpretacja ostatniego rezultatu).

Żeby sobie wyobrazić czym jest mój model proponuję odpalić w Perkunie specyfikację:

values
{
	value false, true;
}

variables 
{
    input variable alpha:{false,true};
    hidden variable beta:{false,true};
    output variable gamma:{false,true};
}

payoff
{
}

model
{
}

cout << model << eol;

To wygeneruje ciąg zaczynający się od:

# model
# {gamma=>false}
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>false ,beta=>false },0.00000);
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>false ,beta=>true },0.00000);
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>true ,beta=>false },0.00000);
set({alpha=>false ,beta=>false },{gamma=>false },{alpha=>true ,beta=>true },0.00000);

# {gamma=>true}
set({alpha=>false ,beta=>false },{gamma=>true },{alpha=>false ,beta=>false },0.00000);
set({alpha=>false ,beta=>false },{gamma=>true },{alpha=>false ,beta=>true },0.00000);
set({alpha=>false ,beta=>false },{gamma=>true },{alpha=>true ,beta=>false },0.00000);
set({alpha=>false ,beta=>false },{gamma=>true },{alpha=>true ,beta=>true },0.00000);

# {gamma=>false}
set({alpha=>false ,beta=>true },{gamma=>false },{alpha=>false ,beta=>false },0.00000);
set({alpha=>false ,beta=>true },{gamma=>false },{alpha=>false ,beta=>true },0.00000);
set({alpha=>false ,beta=>true },{gamma=>false },{alpha=>true ,beta=>false },0.00000);
set({alpha=>false ,beta=>true },{gamma=>false },{alpha=>true ,beta=>true },0.00000);
...

Z prawdopodobieństwami ustawionymi na zero. To nie jest oczywiście poprawny model, w każdej takiej czwórce prawdopodobieństwa muszą się sumować do jedności, ale pomaga zrozumieć jak duże są modele.

2

Czy Twój język pozwala na zaimplementowanie reguł brydża?

0
haael napisał(a):

Czy Twój język pozwala na zaimplementowanie reguł brydża?

Nie znam brydża, raczej wątpię. Brydż jest grą dla czterech graczy. Mam taką nieśmiałą próbę rozwiązania problemu optymalizacji w stochastycznym środowisku dla n graczy, nazywa się Perkun2. Ale nie jestem do tego projektu przekonany. Wydaje mi się, że jest niepoprawny.

Jeżeli masz ochotę zerknij na examples/example5 w Perkunie2. Tam jest dwóch graczy, każdy ma własną funkcję odpłaty, jeden ma jedną kartę drugi ma drugą. Każda karta jest reprezentowana przez dwie zmienne. Jeśli wpiszesz w input np. "none jack hearts" wówczas gracz a1 się podda, bo z rachunku wyjdzie mu że ma większe szanse przegrać niż wygrać. Zmienne ukryte dla jednego gracza mogą być widoczne dla drugiego - ale może być i tak, że zmienna jest ukryta dla obu graczy.

W Perkunie2 zagnieździłem Perla. Można użyć Perla, żeby zmodyfikować zachowanie Perkuna2. Np. takie coś:

<<PERL
$$Perkun2::Optimizer::optimizer{print_prompt} = sub
{
        print "HELLO: ";
};
PERL

... zmodyfikuje prompt.

1

Troche to brzmi jak min-max bez części min gdzie starasz sie po prostu zawsze maksymalizować swój wynik ;)
Każdy node swojego przeszukania poddajesz ocenie, podobnie jak min-max
Dość niejasno tłumaczysz jak to działa wieć ciezko sie to analizuje.

0
xxx_xx_x napisał(a):

Troche to brzmi jak min-max bez części min gdzie starasz sie po prostu zawsze maksymalizować swój wynik ;)

To prawda, tak właśnie jest. Gdy model jest całkowicie deterministyczny (tylko prawdopodobieństwa 1.0 i 0.0) oraz gdy nie ma zmiennych ukrytych mój algorytm staje się jakby minimaksem bez części min. Stany się degenerują wyłącznie do stanów widocznych.

Każdy node swojego przeszukania poddajesz ocenie, podobnie jak min-max

Prawda. Moim wynalazkiem jest tylko funkcja jak aktualizować belief oraz użycie jej w planowaniu. Może jeszcze prawdopodobieństwo stanu widocznego zależnie od poprzedniego beliefu i wykonanej akcji.

Dość niejasno tłumaczysz jak to działa wieć ciezko sie to analizuje.

Przepraszam. Chciałem Wam przedstawić problem od strony gry, tej z wampirem - bez wnikania w szczegóły algorytmu.

1

Ok, wiec tutaj jest problem ;p
Na razie widzę tylko ograniczony min - max do wersji max. Implementując Min-Max zawsze trzeba samemu wymyślić funkcje oceny ponieważ jest zależna od problemu i jak rozumiem to jest twoja funkcja believe. Patrząc w ten sposób nie jest to nic odkrywczego.
Wskaż:

  1. Co tak bardzo wyróżnia twoje podejście
  2. W czym algorytm jest lepszy w porównaniu do innych już istniejących.
  3. Pokaż realne zastosowanie, gdzie widzisz potencjał użycia i dlaczego
0
xxx_xx_x napisał(a):

Ok, wiec tutaj jest problem ;p
Na razie widzę tylko ograniczony min - max do wersji max. Implementując Min-Max zawsze trzeba samemu wymyślić funkcje oceny ponieważ jest zależna od problemu i jak rozumiem to jest twoja funkcja believe. Patrząc w ten sposób nie jest to nic odkrywczego.

Nie. Belief i funkcja oceny to dwie różne rzeczy. Funkcja oceny to payoff. Belief się ciągle zmienia, z każdą nową obserwacją, jest, można powiedzieć, stanem optymalizatora. Natomiast funkcja payoff jest stała, z góry zadana. Mój język umożliwia użytkownikowi zdefiniowanie dowolnego payoffu, natomiast belief kontroluje sam i to bardzo restrykcyjnie.

Wskaż:

  1. Co tak bardzo wyróżnia twoje podejście

Najważniejsze dla mnie są zmienne ukryte. Nawet stochastyczne środowisko nie jest takie ważne. Moglibyśmy dyskutować o deterministycznym środowisku. One (zmienne ukryte) powodują, że stan optymalizatora musi zawierać belief, więc trzeba było wymyślić w jaki sposób belief się zmienia (zależnie od poprzedniego beliefu, wykonanej akcji, "odpowiedzi" środowiska czyli stanu widocznego i modelu). Gdy już miałem tą funkcję (jak modyfikować belief) było już z górki - moje podejście wyróżnia zastosowanie tej jednej jedynej funkcji (aktualizującej belief) w planowaniu. Dzięki temu mój algorytm jest w stanie przeprowadzać akcje, które dają mu informacje (różnicują w jakiś sposób belief). Bo planuje potem użycie tych informacji, to wynika z drzewa gry.

  1. W czym algorytm jest lepszy w porównaniu do innych już istniejących.

Z algorytmów planowania znam tylko minimax, dlatego ciągle go porównuję do mojego algorytmu. Mój algorytm jest lepszy w tym, że umożliwia rozwiązanie problemu optymalizacji w warunkach niepełnej informacji. To znaczy mam taką nadzieję, bo dowodu nie mam.

  1. Pokaż realne zastosowanie, gdzie widzisz potencjał użycia i dlaczego

Może pokażę coś, czego jeszcze nie pokazywałem. Ściągnij i rozpakuj plik programmer.zip. Zawiera specyfikację Perkuna, nie Svaroga, ale to nic nie szkodzi, one są logicznie równoważne. Problem jest taki - istnieje prymitywny język programowania, który składa się tylko z dwóch poleceń, instruction_if_then oraz instruction_print. Te instrukcje są tylko tak dla ustalenia uwagi. Rozważamy programy składające się dokładnie z dwóch poleceń. Dlatego istnieją dwie zmienne wejściowe - program_command_1 i program_command_2. Istnieje jeszcze trzecia zmienna wejściowa - it_works, która normalnie jest równa none, ale jeśli Perkun zażyczy sobie, żeby "wykonać program" wówczas powinna przyjąć wartość true lub false.

Są dwie zmienne wyjściowe - pierwsza to akcja, a druga (which_instruction_to_change) ma znaczenie tylko wtedy gdy pierwsza ma wartość "change_instruction_to_if_then" albo "change_instruction_to_print".

Uruchom:

perkun programmer_final.perkun

Po prompcie wpisz np. instruction_print instruction_print none. Odpowiedź będzie brzmiała:

action=execute_the_program which_instruction_to_change=one 

Czyli "wykonaj program". Mamy teraz dwie możliwości - powiedzieć mu, że program działa lub nie. Ciekawsza jest ta druga.
Wpisz: instruction_print instruction_print false.
Odpowiedź:

action=change_instruction_to_if_then which_instruction_to_change=one

Czyli zmień pierwszą instrukcję na if_then. Zróbmy to:
Wpisz: instruction_if_then instruction_print none
Odpowiedź będzie brzmiała:

action=execute_the_program which_instruction_to_change=one

Powiedzmy mu, że ten program też nie działa.
Wpisz: instruction_if_then instruction_print false
Teraz będzie ciekawie. Odpowiedź brzmi:

action=change_instruction_to_if_then which_instruction_to_change=two

Czyli zmień drugą instrukcję na if_then.

W skrócie mamy cztery zmienne ukryte, każda symbolizuje jeden z czterech możliwych programów. To my (środowisko) decydujemy czy program działa poprawnie. Perkun (programista) będzie wypróbowywał wszystkie możliwości i aktualizował swój belief po kolei eliminując szanse kolejnych programów. Zwracam uwagę, że jeśli powiemy mu, że program nie działa, to już więcej tego programu nie będzie próbował.

Może to nie jest wielkie osiągnięcie Sztucznej Inteligencji, ale wydaje mi się obiecujące. Dlaczego? Jak mówiłem, mój algorytm ma naturalną zdolność do "zadawania pytań", tj. przeprowadzania eksperymentów, takich, które mogą mu dać strategicznie cenną informację. Ocenia też, czy opłaca mu się zapłacić za taki eksperyment.

1

Witam wszystkich,

Mam ulepszenie Svaroga, to release 0.0.3, do ściągnięcia https://www.perkun.org/resources/targz/svarog-0.0.3.tar.gz.

Ulepszenie polega na tym, że jest dodatkowy program svarog-daemon, który można odpalić na swoich serwerach. W pliku konfiguracyjnym (~/.svarog/svarog.ini) można wypisać te serwery i wówczas svarog (interpreter) będzie korzystał z pomocy demonów.

Składnia uruchamiania demonów jest następująca:

svarog-daemon --port <port> --password <password>

Svarog jest o tyle lepszy od Perkuna, że nie alokuje pamięci na cały model. Jest przez to trochę wolniejszy, ale ze wsparciem demonów będzie bardzo silny. Zrobiłem też rozszerzenie składni języka, oprócz komendy loop można też za pomocą komend input, belief i action sformułować jednostkowy problem i rozwiązać go za pomocą komendy solve.

Po skonfigurowaniu demonów można je przetestować specjalnym programem o nazwie svarog-dummy-client, który ma następującą składnię:

svarog-dummy-client <file> <server> <port> <password>

Do demonów nie wolno wysyłać zapytań zawierających instrukcję loop, bo ona służy tylko do interakcji. Można do nich wysyłać tylko "problemy jednostkowe", takie jak w examples/example2_single_problem.svarog .

Svarog jest udostępniany z biblioteką (libsvarog), która umożliwia zagnieżdżenie mojego interpretera we własnych programach C++. Takie programy również mogą korzystać z demonów (nie próbowałem tego jeszcze).

2

Cześć,

Przepraszam, że sam sobie odpisuję.

Dorzuciłem do https://github.com/pawelbiernacki/svarog trochę bardziej ambitną specyfikację - są w niej trzy zmienne ukryte i pięć lokalizacji. To plik examples/example3_dorban.svarog . Ciekawy jest przykład odpalany komendą "make test5" w katalogu examples. W tym przykładzie Pregor może zginąć w walce z wampirem. Dorban widzi, czy Pregor żyje tylko wtedy, gdy znajduje się w tym samym mieście co Pregor. Ale wie, że jeśli nie żyje to już nie ożyje i wtedy Dorban zacznie atakować wampira sam. Odpalenie "make test5" uruchamia taką historię:

  1. Dorban jest w Krakowie i od razu spotyka Pregora (żywego), który wszakże mu nie towarzyszy, wampira nie widać
    decyzja - poproś Pregora, żeby ci towarzyszył
  2. Dorban nadal jest w Krakowie, jest też Pregor, żyje i zgadza się iść z Dorbanem, wampira nie widać
    decyzja - idź do Warszawy
  3. Dorban i Pregor są w Warszawie, wampira nie widać
    decyzja - idź do Poznania
  4. Dorban Pregor i wampir są w Poznaniu
    decyzja - zaatakuj wampira
  5. atak się udał, wszyscy nadal są w Poznaniu
    decyzja - zaatakuj wampira
  6. atak się nie udał, Pregor żyje ale odłącza się od Dorbana
    decyzja - poproś Pregora, żeby ci towarzyszył
  7. Pregor się zgadza, wszyscy są nadal w Poznaniu
    decyzja - zaatakuj wampira
  8. Pregor ginie (jest w Poznaniu, ale nie żyje)
    decyzja - zaatakuj wampira

Zwracam uwagę na to, że dopóki Pregor żyje Dorban stara się atakować wampira tylko razem z nim, ale gdy Dorban zobaczy, że Pregor zginął - wówczas zacznie atakować wampira sam. Ciekawe jest też to, że Dorban od razu prosi Pregora o pomoc, chociaż jeszcze nie wie gdzie jest wampir.

Graf miejsc wygląda jak w załączniku.graf miejsc

Obliczenia mogą trwać sporo, rzędu dwóch-trzech godzin. Ja korzystam z trzech maszyn z demonami (svarog-daemon), ale można i na jednej maszynie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1