AI - rekurencyjne typy wyliczeniowe - nowy pomysł na reprezentację wiedzy

3

Od czasu do czasu zawracam Wam głowę moimi projektami ze Sztucznej Inteligencji. Ostatnim projektem był Svarog i jego demko – Dorban, dostępne z www.perkun.org . Ostatnio napisałem po polsku książkę popularno naukową pt. Algorytm Perkuna, bez wzorów i bez kodu – jest dostępna tu. Chciałem luźno pogawędzić (jeżeli ktoś zechce) o moim kolejnym projekcie, który z założenia będzie wspierał dużą liczbę zmiennych ukrytych. Będę musiał zmodyfikować nieco algorytm. Ale nie o tym chciałem. Wymyśliłem ostatnio nieco lepszy niż predykaty (znane z Prologu) sposób reprezentacji wiedzy, przynajmniej tak mi się wydaje. Można oczywiście zapisać te same dane i przez predykaty, ale Prolog nie wspiera pewnej szczególnej koncepcji, którą najlepiej wyjaśnić na przykładzie.

Założmy, że deklarujemy typy wyliczeniowe w następującej składni:

type osoba={Gotrek, Gerrudir, Gwaigilion};
type miasto={Krakow,Warszawa,Poznan,Wroclaw,Gdansk};
type klucz={czerwony_klucz,zielony_klucz,niebieski_klucz};
type bron={brak,miecz,topor};
type budynek={zamek,dom,chatka};
type boolean={falsz,prawda};

Składnię tą rozszerzamy w ten sposób, że wartości wyliczone w poszczególnych typach mogą mieć postać: <item1><item2>...<item_n>, przy czym każdy kolejny item to literał alfanumeryczny albo wyrażenie postaci

(X:<nazwa typu>) 

oznaczające jakby placeholder pewnego typu.

type
    akcja={nic_nie_rob, 
            idz_do_(X:miasto),
            powiedz_(Y:osoba)_(X:informacja),
            popros_(X:osoba)_zeby_(Y:akcja),
            spytaj_(X:osoba)_czy_(Y:informacja),
            zaatakuj_(X:osoba),
            ukradnij_(X:bron)_(Y:osoba),
            ukradnij_(K:klucz)_(Y:osoba),
            daj_(X:osoba)_(Y:bron),
            daj_(X:osoba)_(K:klucz),
            sprobuj_otworzyc_(X:budynek)_(K:klucz)},
    
    informacja={(X:osoba)_jest_w_(Y:miasto),
                (X:osoba)_wykonuje_akcje_(Y:akcja),
                (X:osoba)_mysli_ze_(Y:informacja),
                (X:osoba)_powiedzial_(Y:osoba)_(Z:informacja),
                (X:osoba)_zaatakowal_(Y:osoba),
                (X:osoba)_ma_bron_(Y:bron),
                (X:osoba)_ma_klucz_(K:klucz),
                (X:osoba)_ukradl_(Y:osoba)_(K:klucz),
                (X:osoba)_ukradl_(Y:osoba)_(Z:bron),
                (X:osoba)_dal_(Y:osoba)_(K:klucz),
                (X:osoba)_dal_(Y:osoba)_(Z:bron),                
                (X:osoba)_sprobowal_otworzyc_(Y:budynek)_kluczem_(K:klucz),
                (X:klucz)_moze_otworzyc_(Y:budynek),
                (X:budynek)_jest_w_(Y:miasto)};

Ciekawe jest to, że w niektórych wartościach typu “informacja” używamy typu “informacja”, więc jest to niejako typ rekurencyjny. Więcej, są nawet informacje zależne od typu akcja i są akcje zależne z kolei od typu informacja. Poza tym wolno używać niezdefiniowanych jeszcze typów, pod warunkiem, że gdzieś jednak będą zdefiniowane. Możliwe są “cykle”, nawet bardzo skomplikowane.

Istnieje specjalne polecenie – expand(<integer>), które powoduje rozwinięcie tej rekurencji do zadanej głębokości. Jeżeli damy expand(1), to jedyną akcją jest “nic_nie_rób”, a typ informacja jest pusty. Ale już expand(2) daje mnóstwo wartości.

Jak się domyślacie mam już interpreter i nie liczyłem tego na piechotę.
W oparciu o identyczną składnię definiuję zmienne, np. zmienna wyjściowa to:

output variable wykonaj:akcja;

Oczywiście nie poprzestaję na rozwinięciu tych typów do głębokości 2, np. już dla 4 są takie akcje:

spytaj_Gotrek_czy_Gwaigilion_powiedzial_Gerrudir_chatka_jest_w_Wroclaw

To znaczy po polsku “spytaj Gotreka czy Gwaigilion powiedział Gerrudirowi, że chatka jest we Wrocławiu”.

Oczywiście w Prologu dałoby się napisać odpowiedni ekwiwalent, ale mój język wspiera tę rekurencję typów i wydaje mi się przez to bardzo intrygujący. Jak powiedziałem dzięki tym placeholderom będzie można prosto wygenerować całe “rodziny” zmiennych, np:

input variable gdzie_jestem:miasto, 
	(X:osoba)_mowi_mi_ze_(Y:informacja):boolean;

Ta pierwsza zmienna to po prostu:

gdzie_jestem:{Krakow,Warszawa,Poznan,Wroclaw,Gdansk};

Ale ta druga to cała rodzina zmiennych wejściowych, do których należy na przykład zmienna:

Gerrudir_mowi_mi_ze_Gotrek_powiedzial_Gwaigilion_Gerrudir_powiedzial_Gwaigilion_chatka_jest_w_Gdansk:{falsz,prawda};

Reprezentuję wiedzę o moich zmiennych w postaci reguł, ale na razie projekt nie jest skończony, więc więcej Wam nie powiem. Mam nadzieję uzyskać jakąś formę mojego algorytmu planowania, który będzie zakładał tym razem, że zmienne są niezależne, więc to, w co agent wierzy będzie musiało być nieco inaczej reprezentowane. I będzie mogło być bardzo dużo zmiennych ukrytych. Zależy mi zwłaszcza na zmiennych ukrytych. Wyobraźcie sobie na przykład:

hidden variable (X:osoba)_mysli_ze_(Y:informacja):boolean;

Dla głębokości rekurencji 3 daje to 8460 zmiennych ukrytych (tylko jedna rodzina). To jest liczba niemożliwa do udźwignięcia dla Svaroga, bo miałby 2 ^ 8460 stanów. A w moim nowym projekcie będzie 2*8460 takich jakby “projekcji”, czyli rozsądna ilość. Mam nadzieję zrobić coś podobnego jak prekalkulacja wiedzy w Svarogu, czyli zadajemy model świata (za pomocą bardzo silnych reguł opartych o te placeholdery) i dajemy komputerowi czas. Po kilku, kilkunastu dniach oblicza sobie reguły dla każdej ewentualności (a właściwie tylko dla tych “projekcji”). Potem będzie działał szybko, tak samo jak Svarog. Postaram się zrównoleglić te obliczenia (tak jak w Svarogu).

Robocza nazwa projektu to “Borsuk”. Napiszę od razu demko, będzie w tym samym projekcie.

0

spytaj_Gotrek_czy_Gwaigilion_powiedzial_Gerrudir_chatka_jest_w_Wroclaw

Brzmi to intrygująco, może dlatego że nie używałem Prologa

Ale jak tego używać? masz jakiś przykład w zwykłym programowaniu dla noobów?

0

Dzięki. Ta nowa składnia nie jest niestety obsługiwana w Perkunie ani w Svarogu. Będzie dopiero w trzecim projekcie z tej rodziny, który nazwałem Triglav. Trochę dokumentacji jest na www.perkun.org, m.in. svarog_0_0_6.pdf. Po polsku mam taką wersję "popularnonaukową" - algorytm_perkuna.pdf. Triglav nie jest jeszcze opublikowany. Co do Svaroga - jest trochę lepszy niż Perkun, ale reprezentuje nadal "naiwne" podejście - stąd przestrzeń możliwych stanów bardzo szybko rośnie. Triglav jest dużo lepszy, pozwala na setki zmiennych wygenerowanych za pomocą "wyrażeń kartezjańskich", np.

hidden variable (X:person)_thinks_that_(Y:person)_has_done_(A:action);

Te wyrażenia kartezjańskie nazywam tak dlatego, że po rozwinięciu (expand) generują instancje zmiennych ukrytych (na przykład takich jak ta powyżej) dla iloczynu kartezjańskiego zbiorów możliwych wartości wszystkich placeholderów (w przykładzie powyżej dla wszystkich trójek (X,Y,A)). To już działa w Triglavie i możesz się tym pobawić.

Jak bardzo chcesz mogę Ci przesłać mailem dokumentację Triglava i sam kod, ale jedno i drugie nie jest dokończone. (Musiałbyś mi przysłać adres email na priva, albo zaczekać do jutra, to bym to wrzucił na serwer). Wymaga kompilatora C++ wspierającego C++17 z konceptami, oraz bisona i flexa.

Dla Svaroga jest przykładowy program o nazwie Dorban. On najpierw tworzy pipe'y a potem robi fork i w procesie-dziecku uruchamia interpreter Svaroga. Możesz to prześledzić rozpakowując kod źródłowy i analizując plik src/dorban.cc.

Tam jest taki kod:

if (my_npc.parse(s.str().c_str()))
	{
		std::cerr << "failed to parse the specification " << s.str() << "\n";		
	}
	else 
	{
		my_npc.run();
	}

To jest moment, w którym parsujemy a potem uruchamiamy specyfikację Svaroga. Ta specyfikacja jest w pliku svarog/dorban.svarog. Dziecko komunikuje się przez te pipe'y z rodzicem, bo klasa npc_dorban dziedziczy z svarog::optimizer. W tych integerach poniżej są uchwyty potoków (pipes).

class npc_dorban: public svarog::optimizer
{
	private:
	int parent_to_child_fd, child_to_parent_fd;
	const std::string name;
	const svarog::action * last_action;

	protected:		
	virtual void on_surprise(
		const svarog::belief & b1, const svarog::action & a, 
		const svarog::visible_state & vs, svarog::belief & target);
	
	virtual void print_current_belief() const;
	
	virtual void get_input(std::map<svarog::variable*, svarog::value*> & m, bool & flag_eof);
	
	virtual void execute(const svarog::action * a);

	public:
	npc_dorban(int pc, int cp): 
		name("Dorban"),
		parent_to_child_fd(pc), child_to_parent_fd(cp), last_action(NULL) {}
};

Wielkie dzięki za zainteresowanie, zajmuję się tym od lat, algorytm wymyśliłem jeszcze na studiach (z dwadzieścia kilka lat temu) i strasznie ciężko mi idzie popularyzacja mojego pomysłu. Nikt mi nie wierzy. Muszę dodać, że nie jestem informatykiem w sensie naukowcem, nie mam nawet doktoratu, te programy to moje hobby. Próbowałem zainteresować tym akademików, ale nikt nie chce ze mną gadać.

0

To jeszcze raz ja. Wrzuciłem na mój serwer dokumentację triglav_0_0_0.pdf oraz samego Triglava: triglav-0.0.0.tar.gz.

Proponuję po skompilowaniu i zainstalowaniu odpalić następujący kod w Triglavie:

type boolean={false,true},
    place={Krakow,Warszawa,Wroclaw,Poznan,Gdansk},
    person={Conan, Gotrek, Gwaigilion},
    
    action={doing_nothing, 
            going_to_(X:place),
            telling_(Y:person)_(X:information),
            asking_(X:person)_to_do_(Y:action),
            asking_(X:person)_whether_(Y:information),
            attacking_(X:person)},
    
    information={(X:person)_is_in_(Y:place),
                (X:person)_is_(Y:action),
                (X:person)_thinks_(Y:information),
                (X:person)_has_told_(Y:person)_(Z:information),
                (X:person)_has_attacked_(Y:person)};
    
input variable where_am_I:place,
    I_have_won_a_fight:boolean usually {false},
    I_can_see_(X:person):boolean,
    (X:person)_is_telling_me_(Y:information):boolean usually {false},
    (X:person)_is_attacking_me:boolean usually {false};
    
output variable I_do:action;
hidden variable where_is_(X:person):place,
                (X:person)_can_see_(Y:person):boolean,
                (X:person)_has_told_(Y:person)_(Z:information):boolean usually {false},
                (X:person)_has_attacked_(Y:person):boolean usually {false},
                (X:person)_thinks_(Y:information):boolean usually {false};
                
expand(2);
report();

To znaczy trzeba powyższy tekst umieścić w pliku np. x.triglav i odpalić w terminalu:

triglav x.triglav

Ten kod "rozwija" wszystkie typy i zmienne, tj. tworzy instancje zmiennych. Proponuję poeksperymentować z różnymi wartościami argumentu "expand", np. 3, 4 itd.

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