Zapewne większość korzystających z internetu osób ma jakieś doświadczenia dotyczące rozmów z botami (mówiąc dokładniej – chatbotami – tak bowiem określa się aplikacje prowadzące rozmowy z ludźmi). Swego czasu popularny w polskiej sieci był program Infobot – można było korzystać z niego m.in. za pośrednictwem Gadu-Gadu, a swój ponad 10-letni żywot zakończył niespełna 2 lata temu. Wśród jego funkcjonalności wymienić można wyświetlanie prognozy pogody, programu telewizyjnego, definicji z Wikipedii czy tłumaczonych słówek. Można też było robić notatki oraz – co dla wielu najważniejsze – po prostu rozmawiać. Zadawać pytania i śmiać się z mniej lub bardziej adekwatnych odpowiedzi.

Dekadę temu entuzjazm budził również inny władający polszczyzną chatbot: Snikers. Jego rola była czysto rozrywkowa i ograniczała się do możliwości prowadzenia z nim konwersacji. Na YouTube wciąż można znaleźć filmiki, które stanowią dziś jedną z ostatnich pozostałości po tym relikcie i pozwalają zobaczyć działanie programu (wystarczy wpisać zapytanie typu: „snikers bot„). Aplikację można było zakupić za niewielką kwotę (płatność była realizowana przez wysłanie SMS-a Premium), a następnie podłączyć do GG, osadzić na stronie internetowej, lub po prostu bawić się rozmową z nim na własnym komputerze.

Chociaż wspomniane boty dokonały już swego wirtualnego żywota, „era chatbotów” bynajmniej się nie skończyła. Obecnie spotykamy je np. w roli konsultantów/doradców, którzy niespodziewanie (a czasami też irytująco) wyskakują na niektórych stronach internetowych, możemy natrafić na nie na facebookowych fanpage’ach oraz dzwoniąc na infolinie, które odeszły od tonowego wybierania opcji na rzecz porozumiewania się głosowego. Nie wszystkie komunikujące się z człowiekiem boty oferują możliwość swobodnej pogawędki, ale wszystkie łączy jedno – przetwarzanie języka naturalnego (definiowane dość szeroko, czyli niekoniecznie związane z próbą zrozumienia znaczenia danych wejściowych).

Eliza, Julia, Alicja…

Jednak rozmawianie z komputerem w normalnym, ludzkim języku to pomysł praktycznie tak samo stary jak informatyka. Już w roku 1950 Alan Turing zaproponował The Imitation Game, dzisiaj znaną pod nazwą Testu Turinga – zdanie tego sprawdzianu przez program komputerowy polega na prowadzeniu przez niego konwersacji w sposób, który uniemożliwi sędziemu odróżnienie go na tej podstawie od człowieka. Test Turinga szybko stał się dla wielu programistów wyzwaniem, któremu próbowali podołać. Jak dotąd żadnemu programowi nie udało się jeszcze spełnić wszystkich wymagań testu, ale progres w tej dziedzinie niewątpliwie ma miejsce. Chatboty, które były w swoim czasie najlepsze, można znaleźć na liście zwycięzców Nagrody Loebnera, która co roku przyznawana jest programom najlepiej symulującym rozmowę z człowiekiem. Obecnie wyróżnia się kilka generacji chatbotów, choć kwestia przynależności do nich poszczególnych aplikacji jest nieco dyskusyjna. Pomińmy zatem problem kategoryzacji botów i po prostu zapoznajmy się z paroma wirtualnymi osobistościami (od razu widać, że wśród twórców takich aplikacji dominują mężczyźni):

  • Urodzona w 1966 roku  ELIZA, dzieło Josepha Weizenbauma, to jeden z pierwszych chatbotów. Jej działanie opierało się przede wszystkim na dopasowywaniu wzorców, używaniu synonimów i zadawaniu zawierających je pytań, dzięki czemu użytkownik zyskiwał iluzję rozmowy z kimś rozumiejącym sens zdań. Dokładna specyfikacja zachowania bota była dostarczana w postaci „skryptów” (początkowo pisanych w języku MAD-Slip). Najpopularniejszym ze stworzonych dla ELIZY skryptów okazał się DOCTOR, który przekształcał ją w wirtualnego psychoterapeutę (a więc kogoś, kogo charakteryzuje zadawanie wielu pytań).
  • Kilka lat później, w roku 1972, amerykański psychiatra Kenneth Colby zaimplementował bota o nazwie PARRY. Program ten miał imitować pacjenta chorego na schizofrenię paranoidalną. Z zadaniem tym poradził sobie chyba dość dobrze, bowiem w przeprowadzonych z udziałem kilkudziesięciu psychiatrów eksperymentach był odróżnialny przez nich od prawdziwych pacjentów na poziomie porównywalnym ze zgadywaniem. Co ciekawe, PARRY kilkukrotnie miał okazję rozmawiać z DOCTOR-em – transkrypty z tych spotkań przeczytać można w RFC 439.
  • JULIA SYLVIE to z kolei przykłady verbotow, czyli botów, które udzielają odpowiedzi werbalnie. Oprócz syntezowanego głosu w tego typu aplikacjach często występuje też graficzna wizualizacja ich „twarzy”, dzięki czemu wypowiedzi mogą być ubogacane mimiką. Autorem Julii jest Michael Mauldin, założyciel wyszukiwarki Lycos, który prace nad swoim verbotem rozpoczął w roku 1994. Sylvie to jego młodsze dzieło i zarazem pierwszy chatbot wyposażony zarówno w twarz, jak i głos.
  • Jednym z bardziej utytułowanych botów jest natomiast ALICE, która aż trzykrotnie (w roku 2000, 2001 i 2004) zdobyła Nagrodę Loebnera. Dzięki otwaremu kodowi źródłowego Alicji w Internecie do dziś znaleźć można wiele forków tego projektu. Ciekawostką jest, że nagrodzony Oscarem film „Ona” z 2013 roku powstał ponieważ jego reżyser, Spike Jonze, zainspirował się właśnie botem ALICE.
Fragment rozmowy botów ELIZA i PARRY

XML do sztucznej inteligencji

Jak przez mgłę pamiętam, jeszcze z czasów szkolnych, próby zaprogramowania bota Gadu-Gadu przy użyciu pythonowej biblioteki dołączonej do EKG, a także dopisywanie w PHP kolejnych ifów odpowiadających możliwym scenariuszom rozmowy, zdaje się że dla bota komunikującego się za pomocą protokołu XMPP. Niestety nie miałem wtedy pojęcia, że istnieją języki, które znacznie mogą ułatwić proces tworzenia chatbotów. Jednym z takich właśnie języków, na którym skupię się w tym wpisie, jest AIML.

AIML to skrót od Artificial Intelligence Markup Language (z ang. język znaczników sztucznej inteligencji), nietrudno jest się zatem domyślić, że język ten oparty jest na XML. Nie jest to więc język kompletny w sensie Turinga i służy przede wszystkim do reprezentacji wiedzy. W AIML definiuje się cały „mózg” bota, czyli to jak będzie reagował na poszczególne teksty pochodzące od użytkownika. Całość opiera się na dopasowywaniu wzorców, ale jest ono znacznie uproszczone względem wyrażeń regularnych. Dane mogą być przechowywane w zmiennych, a odpowiedzi programu można uzależnić od aktualnego kontekstu rozmowy i poprzednio udzielanych odpowiedzi oraz zdobytych od interlokutora informacji.

I… to właściwie tyle. AIML z założenia jest językiem prostym, niewymagającym właściwie żadnej programistycznej wiedzy, aby z powodzeniem rozwijać chatbota. Nauczenie się tej technologii to zatem kwestia paru godzin spędzonych przy czytaniu dokumentacji i zapoznawaniu się z tym, jak wyglądają trzewia botów stworzonych przez innych ludzi. Mnogość udostępnionych na licencjach open-source chatbotów to kolejna zaleta tego języka, choć oczywiście największe korzyści można z niej odnieść programując aplikację komunikującą się w języku angielskim. Bez problemu można wtedy po prostu rozbudowywać i modyfikować którąś z istniejących już baz wiedzy napisanych w AIML.

Warto jeszcze wspomnieć o korzeniach historycznych AIML-a. Sięgają one roku 1995, kiedy to dr Richard S. Wallace, amerykański informatyk, rozpoczął pracę nad tym językiem. Celem było stworzenie chatbota, który byłby realnym konkurentem dla innych istniejących wtedy botów. W prace zaangażowało się kilkaset osób, a owocem tych działań był wydany w 2001 roku program A.L.I.C.E (opisany wyżej). Alice i inne oparte na niej programy to dobre przykłady realnego zastosowania języka AIML. Niestety mogą one ostudzić zapał tych, którzy chcieliby małym kosztem stworzyć inteligentnego chatbota. Napisanie sensownej bazy wiedzy, dzięki której bot będzie sprawiał wrażenie przynajmniej półinteligentnego, to naprawdę żmudne zadanie, wymagające mnóstwa zasobów czasowych i ludzkich. „AI” w nazwie AIML to bowiem przykład bardzo prostej sztucznej inteligencji. Nie uczy się ona, nie korzysta z żadnych zaawansowanych algorytmów, a jedynie szuka dopasowań do wprowadzonego tekstu. Niemniej, sądzę że zabawa z tym językiem może stanowić dobry wstęp dla osób zainteresowanych tematem NLP oraz AI.

Środowisko programistyczne

Do pisania plików AIML w zasadzie nie potrzebujemy żadnego wyspecjalizowanego środowiska. Składania języka jest na tyle prosta, że spokojnie można obejść się bez typowych dla programowania w normalnych językach udogodnień. Wystarczy więc jakikolwiek edytor, choć oczywiście najwygodniej będzie skorzystać z takiego, który oferuje wsparcie dla XML-a. Ostrzeże nas on o ewentualnych błędach syntaktycznych, takich jak np. brak tagów zamykających. Listę popularnych edytorów XML można znaleźć chociażby w Wikpedii.

Używając Linuksa możemy na przykład doinstalować odpowiednie rozszerzenie do Vima (np. xmledit) lub wykorzystać emacsowy nXML Mode. Z kolei dla Windowsa powstały dedykowane IDE, ułatwiające edytowanie plików AIML: GaitoBot oraz Simple AIML Editor.

Oprócz wybrania edytora przed przystąpieniem do tworzenia AIML-owego bota musimy jeszcze dokonać wyboru interpretera. Tu również mamy sporą dowolność, ponieważ dostępne są implementacje AIML napisane w takich językach jak C++, Java, Ruby, Perl, PHP, C# i Pascal. Można też skorzystać ze strony Pandorabots, która za opłatą (najtańszy plan to 9$/miesiąc, dostępny jest 10-dniowy trial) udostępnia swoje serwery do tworzenia i odpalania chatbotów. Serwis funkcjonuje więc na zasadzie AIaaS, czyli Artificial Intelligence as a Service (z ang. sztuczna inteligencja jako usługa).

W celu szybkiego startu z testowaniem możliwości AIML-a polecam wyposażyć się w pythonową implementację tego języka, noszącą nazwę pyAIML. Pakiet ten jest nawet w repozytoriach systemowych niektórych dystrybucji, np. Ubuntu, w którym zainstalujemy go wpisując:

sudo apt-get install python-aiml

Następnie tworzymy dwa pliki (oba w tym samym folderze): script.py oraz data.aiml. Pierwszy z nich będzie na początek wyglądał tak:

import aiml

k = aiml.Kernel()
k.learn("data.aiml")
while True:
    print(k.respond(raw_input("> ")))

Z kolei drugi uzupełnimy za chwilę, definiując w nim już tylko bazę wiedzy dla naszego bota. Program będziemy uruchamiać za pomocą polecenia python script.py.

Warto jeszcze zaznaczyć, że wersja języka, którą będziemy się tu posługiwać to 1.0.1. Lista interpreterów wspierających standard AIML 2.0 jest niestety nieco krótsza i tą wersją języka zajmiemy się kiedy indziej, w osobnym wpisie (a z pewnością warto się nią zająć, gdyż możliwości, które oferuje są znaczące).

Definiowanie bazy wiedzy

Skoro rozmowy kulturalnych ludzi zazwyczaj rozpoczynają się od przywitań, to zaprogramujmy tę odrobinę kultury naszemu botowi i nauczmy go odpowiadać na „Hej”, edytując plik data.aiml:

<?xml version = "1.0" encoding = "UTF-8"?>
<aiml version = "1.0.1" encoding = "UTF-8">
<category>
    <pattern>HEJ</pattern>
    <template>Witaj, Programisto!</template>
</category>
</aiml>

Po uruchomieniu programu i wpisaniu „HEJ” bot zareaguje w spodziewany sposób. W pliku AIML zdefiniowaliśmy bowiem kategorię, reprezentującą pojedynczą jednostkę wiedzy. Każda taka kategoria musi zawierać dwa tagi: <pattern>, wewnątrz którego określamy wzorzec, mający pojawić się w tekście od użytkownika, oraz <template>, czyli odpowiedź bota. Przyjęło się, że tekst między znacznikami <pattern>powinien być zapisany wielkimi literami, ale nie oznacza to, że rozmawiając z chatbotem musimy krzyczeć do niego z włączonym Caps Lockiem. Reakcja aplikacji będzie taka sama niezależnie czy napiszemy „HEJ’, HEJ!”, Hej”, hEJ…” itd. – znaki interpunkcyjne w wypowiedzi użytkownika są po prostu pomijane, a wielkość liter nie ma w niej znaczenia. Sprawa ma się już jednak inaczej jeśli spróbujemy zagadać do bota w sposób bardziej osobisty, np.:

> Hej, Bocie
WARNING: No match found for input: Hej, Bocie

Nie powinno być żadnym zaskoczeniem, że w takiej sytuacji swoje zastosowanie znajduje wieloznacznik w postaci gwiazdki:

<category>
    <pattern>HEJ</pattern>
    <template>Witaj, Programisto!</template>
</category>

<category>
    <pattern>HEJ *</pattern>
    <template>
        <srai>HEJ</srai>
    </template>
</category>

Ponieważ w AIML gwiazdka nie oznacza „zero lub więcej”, ale symbolizuje jeden lub więcej znaków (innych niż znaki białe i interpunkcyjne), musimy stworzyć kolejną kategorię. Nie powtarzamy w niej już jednak definicji naszej odpowiedzi, ale, używając znacznika <srai>, dodajemy przekierowanie do istniejącej już kategorii. Funkcjonalność ta określana jest mianem redukcji symbolicznej (ang. Symbolic Reduction).

Bot, który zawsze odpowiada to samo z pewnością nie stwarza pozorów zbyt naturalnego. Aby wymusić na nim losowy wybór spośród kilku zdefiniowanych odpowiedzi użyjmy więc tagu <random>:

<category>
    <pattern>HEJ</pattern>
    <template>
        <random>
            <li>Witaj, Programisto!</li>
            <li>Hello, world!</li>
            <li>Dzień dobry</li>
            <li>Cześć</li>
        </random>
    </template>
</category>

Hm, użytkownik już się przywitał, ale co dalej? Nie wiadomo o czym będzie chciał rozmawiać, warto zatem dodać kategorię ze wzorcem „*”, przechwytującą wszystko, czego bot nie jest w stanie dopasować w inny sposób. Warto pamiętać, że dobrą techniką jest naprowadzanie użytkownika na takie tematy, o których to my chcemy rozmawiać. W tym celu musimy po prostu zadawać odpowiednią liczbę pytań, a później reagować na padające odpowiedzi. Obrazuje to poniższy przykład:

<category>
    <pattern>HEJ</pattern>
    <template>
        <random>
            <li>Witaj, Programisto!</li>
            <li>Cześć!</li>
        </random>
        Jaki jest Twój ulubiony język programowania? 
    </template>
</category>

<category>
    <pattern>*</pattern>
    <that>* JAKI JEST TWÓJ ULUBIONY JĘZYK PROGRAMOWANIA</that>
    <template>
        Świetnie, ja też bardzo lubię język <set name="favouriteLang"><star /></set>
        <think><set name="favouriteLangSet">true</set></think>
    </template>
</category>

<category>
    <pattern>*</pattern>
    <template>
        Nie wiem czy dobrze Cię zrozumiałem. 
        <condition name="favouriteLangSet" value="true">
            Ale może opowiesz mi coś więcej o języku <get name="favouriteLang"/>?
        </condition>
    </template>
</category>

Tag <that>, który zawiera dopasowanie poprzedniej wypowiedzi bota, musi być umiejscowiony pomiędzy wzorcem a odpowiedzią. Jak widać, również w nim można używać wieloznaczników (może być to kilka gwiazdek, a może być to też znak „_”, który w AIML ma to samo znaczenie, ale wyższy priorytet).

W powyższym kodzie użyliśmy także tagów <set>oraz <get>aby zapisywać i odczytywać wartości zmiennych oraz znacznika <condition>pełniącego rolę instrukcji warunkowej. Niestety specyfikacja AIML 1.0 nie zawiera atrybutu „exists”, którym można by sprawdzić czy dana zmienna jest już ustawiona – niektóre interpretery implementują go, inne (jak np. pyAIML) nie, a wtedy trzeba sobie radzić wprowadzając pomocniczą zmienną, wskazującą czy użytkownik podał już interesującą nas wartość. Wartością tą jest w naszym przypadku <star />, czyli po prostu te znaki, które są zastępowane we wzorcu przez symbol asterysku.

Warto zauważyć, że jedno z wywołań <set>zostało umieszczone pomiędzy tagami <think>– rezultatem takiego zabiegu jest nie wyświetlanie użytkownikowi tekstu, który znajduje się w myślach bota. Gdyby pominąć ten znacznik, to na końcu odpowiedzi otrzymalibyśmy niechciany dopisek „true”.

Innym sposobem na dopasowywanie wzorców w kontekście prowadzonej rozmowy jest użycie funkcjonalności tematów:

<category>
    <pattern>HEJ</pattern>
    <template>
     Cześć! Lubisz programować?
        <think><set name="topic">PROGRAMOWANIE</set></think>
    </template>
</category>

<topic name="PROGRAMOWANIE">
    <category>
        <pattern>TAK</pattern>
        <template>Świetnie! A jaki język programowania lubisz najbardziej?</template>
    </category>

    <category>
        <pattern>NIE</pattern>
        <template>Szkoda... O czym więc chcesz porozmawiać?</template>
    </category>
</topic>

Jak widać, pomiędzy tagami <topic>możemy umieścić kilka kategorii. Posługując się tematami należy pamiętać, że w którymś momencie warto byłoby „wyskoczyć” z obecnego tematu i przejść do kolejnego, inaczej możemy zablokować się w obrębie tylko kilku kategorii, a nasz bot nie zareaguje nawet na próby pożegnania się z nim. Rozwiązaniem tego problemu może też być stworzenie ogólnego tematu <topic name="*">, do którego w ostateczności będzie pasować każdy wzorzec.

W procesie rozwijania umiejętności naszego bota jego baza wiedzy szybko rozrasta się o kolejne kategorie. Aby zachować względny porządek i nie pogubić się w tym bałaganie dobrym pomysłem jest podzielenie bazy na kilka plików (czyli coś, co jest standardem w każdym normalnym projekcie programistycznym). Kilkukrotnie wywołanie metody learn()sprawi, że bot nauczy się kolejnych rzeczy, wcale nie zapominając o starych, np.:

k = aiml.Kernel()
k.learn("powitania.aiml")
k.learn("pozegnania.aiml")
k.learn("zagadki.aiml")

Gdy z kolei rozmiar naszej bazy będzie już tak duży, że jej uczenie zajmować będzie botowi zbyt dużo czasu jak na naszą cierpliwość, można zastosować usprawnienie w postaci zapisu całego nauczonego już mózgu do jednego pliku. Tutorial jak to zrobić można znaleźć pod tym linkiem.

Chociaż język AIML jest prosty, to oczywiście w tym krótkim artykule przedstawiłem tylko pewien wycinek z jego specyfikacji. Jeśli pragniesz poszerzyć swoją wiedzę z zakresu AIML, polecam zaglądnąć pod podane na końcu wpisu odnośniki.

Nie tylko wiedza

Chociaż pisanie chatbota z wykorzystaniem AIML to w dużej mierze praca nad zdefiniowaną w XML-owych plikach bazą danych, to czasami pojawia się potrzeba pogrzebania w kodzie ją otaczającym. Jeśli bot ma być programem użytecznym, to w wielu przypadkach trudno będzie to zrealizować jeśli nie zapewnimy mu dostępu do internetu, aby mógł się komunikować z innymi serwisami, wysyłać i odbierać zapytania itd. Jak do tego podejść? Po pierwsze, możemy skorzystać ze znacznika <system>, w którym umieszczamy polecenie powłoki systemowej. Przekazujemy mu odpowiednie parametry, a bot wyświetla użytkownikowi output wywołanej komendy:

<category>
    <pattern>ANG PL *</pattern>
    <template>
       <system>translate <star /></system>
    </template>
</category>

W powyższym przykładzie program translate może być chociażby skryptem bashowym, który odpytuje API jakiegoś translatora internetowego i wyświetla otrzymaną odpowiedź. Skrypt musi zostać umieszczony w katalogu, który znajduje się w zmiennej PATH, czyli np. w /usr/local/bin.

Innym sposobem jest „postprocessing” odpowiedzi bota. W pliku AIML dodajemy do odpowiedzi jakiś charakterystyczny ciąg znaków, a w kodzie Pythona (albo innego języka, z którego korzystamy) robimy dokonujemy jego podmiany:

<category>
    <pattern>PODAJ OPIS PRODUKTU *</pattern>
    <template>
        Oto opis produktu: [OPIS_PRODUKTU <star />]
    </template>
</category>
import aiml

PRODUCT_DESC = "[OPIS_PRODUKTU "

def get_description(product):
    # magiczne tricki, w ktorych zdobywamy opis produktu
    return description

def process_response(text):
    if PRODUCT_DESC in text:
        start_index = text.index(PRODUCT_DESC)
        end_index = start_index + len(PRODUCT_DESC)
        product_name = text[end_index:-1]
        product_description = get_description(product_name)
        text = text[:start_index] + product_description

    return text

k = aiml.Kernel()
k.learn("data.aiml")
while True:
    resp = k.respond(raw_input("> "))
    resp = process_response(resp)
    print(resp)

Poza AIML-owym kodem warto też zrobić jeszcze jedną rzecz, której sam AIML nie potrafi – pozbyć się polskich znaków. Nie chcemy przecież pisać masy przekierowań (<srai>) takich jak z „CZEŚĆ” do „CZESC”. Polecam do tego znakomitą bibliotekę o nazwie unidecode (można zainstalować ją poprzez pip install unidecode). Poniżej zaś program, który jej używa:

import aiml
import unidecode

def remove_diacritic(text):
    text = text.decode("utf-8")
    return unidecode.unidecode(text)

k = aiml.Kernel()
k.learn("data.aiml")
while True:
    input_bytes = raw_input("> ")
    input_text = remove_diacritic(input_bytes)
    print(k.respond(input_text))

Podsumowanie

Pomimo faktu, że AIML jest w gruncie rzeczy technologią o bardzo niskim stopniu skomplikowania, i tak można wykorzystać ją do stworzenia całkiem satysfakcjonujących programów oraz botów wystarczających do niektórych prostych zastosowań. Pamiętajmy tylko, że wbrew swojej nazwie język ten nie jest wcale narzędziem jakoś wybitnie ukierunkowanym na to, co w dzisiejszych czasach nazywamy sztuczną inteligencją. Miłej zabawy! 😉

Linki

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *