Instrukcje posługiwania się oprogramowaniem


Środowisko języka Lisp

Krótki przewodnik po języku Common Lisp stanowi oddzielny tekst, który zawiera informacje nie tylko ułatwiające posługiwanie się dostarczonym oprogramowaniem, lecz także umożliwiające dokonywanie w nim prostych modyfikacji, nawet bez uprzedniej znajomości tego języka. Jednak używanie programów w podstawowym zakresie, jaki jest omawiany w niniejszej dyskusji, nie wymaga nawet powierzchownej wiedzy o języku Lisp. Niezbędna jest natomiast podstawowa orientacja w specyfice środowiska tego języka pozwalająca na ładowanie kodu programu i jego uruchamianie oraz śledzenie jego działania.

Interakcja z interpreterem

Język Lisp jest językiem interpretowanym. Dowolny program -- w postaci źródłowej lub przetłumaczonej do binarnej reprezentacji wewnętrznej -- jest interpretowany przez specjalny program, który zapewnia to, co nazywamy środowiskiem języka Lisp. Jedną z jego podstawowych cech jest interaktywność: użytkownik wydaje mu polecenia ładowania plików zawierających kod programu i uruchamiania funkcji, lecz może także sprawdzać i zmieniać wartości zmiennych, modyfikować i definiować nowe funkcje oraz obliczać dowolne wyrażenia języka. Ma to duży walor zwłaszcza w przypadku oprogramowania o charakterze raczej badawczym lub dydaktycznym niż użytkowym, kiedy łatwość wprowadzania niewielkich zmian jest cenna.

Środowisko zgłasza gotowość przyjęcia poleceń użytkownika przez wypisanie tekstu zachęty, który często ma postać znaku > -- tak będziemy przyjmować w poniższych przykładach. Wówczas użytkownik może wpisać dowolne poprawne wyrażenie w języku Lisp, czy też -- jak przyjmuje się je nazywać -- formę. Najbardziej podstawowe rodzaje form, którymi można się łatwo posługiwać bez znajomości języka, to zmienne oraz wywołania funkcji. Wartością formy będącej zmienną jest aktualna wartość tej zmiennej. Wartością formy będącej wywołaniem funkcji jest wartośc zwrócona przez tę funkcję wywołaną dla podanych argumentów. W każdym przypadku wpisanie formy po znaku zachęty i wciśnięcie klawisza Enter powoduje wyznaczanie jej wartości (czyli wartościowanie), która następnie jest wypisywana, po czym środowisko oczekuje na podanie kolejnej formy.

Forma wyznaczająca wartość zmiennej zapisywana jest po prostu jako nazwa tej zmiennej. Forma wywołania funkcji zapisywana jest w postaci (fun arg1 arg2 ... argn), gdzie fun oznacza nazwę funkcji, a arg1, arg2,..., argn są jej argumentami. Poniższy przykład demonstruje sprawdzanie wartości zmiennych pogoda-atrybuty i pogoda-kategorie, przy założeniu, że zostały one wcześniej zdefiniowane:

> pogoda-atrybuty
((AURA (SLONECZNA POCHMURNA DESZCZOWA))
  (TEMPERATURA (ZIMNA UMIARKOWANA CIEPLA)) (WILGOTNOSC (NORMALNA WYSOKA))
  (WIATR (SLABY SILNY))
)
> pogoda-kategorie
(1 0)
Z kolei następujący przykład pokazuje, jak wywołać funkcję o nazwie learn z jednym argumentem pogoda-przyklady, przy założeniu, że funkcja learn i zmienna pogoda-przyklady zostały wcześniej zdefiniowane:
> (learn pogoda-przyklady)
 < AURA >
  =SLONECZNA
      < WILGOTNOSC >
       =NORMALNA
            [TAK]
       =WYSOKA
            [NIE]
  =POCHMURNA
      [TAK]
  =DESZCZOWA
      < WIATR >
       =SLABY
            [TAK]
       =SILNY
            [NIE]

Ładowanie plików

Do ładowania plików do środowiska służy funkcja load, której argumentem jest ujęta w cudzysłowy nazwa pliku. W niektórych środowiskach można podając nazwę pominąć domyślny przyrostek .lsp lub .lisp. Ładowany plik musi zawierać poprawne formy języka Lisp. W trakcie ładowania są one interpretowane i wartościowane. W przypadku, gdy plik zawiera kod programu, są to przede wszystkim definicje funkcji i definicje zmiennych. W trakcie ładowania pliku mogą pojawiac się komunikaty towarzyszące wartościowaniu znajdującym się w nim form. Może też nastąpić ładowanie innych plików, jeśli ładowany plik tego wymaga. Poniższy przykład demonstruje ładowanie jednego z plików źródłowych dostarczonego oprogramowania:

> (load "tdidt")
;; Loading file /home/pawel/src/lisp/tdidt.lsp ...
;; Loading file /home/pawel/src/lisp/concept.lsp ...
;; Loading file /home/pawel/src/lisp/util.lsp ...
;; Loading of file /home/pawel/src/lisp/util.lsp is finished.
;; Loading of file /home/pawel/src/lisp/concept.lsp is finished.
;; Loading file /home/pawel/src/lisp/info.lsp ...
;; Loading of file /home/pawel/src/lisp/info.lsp is finished.
;; Loading of file /home/pawel/src/lisp/tdidt.lsp is finished.
T

W przypadku przeprowadzania czasochłonnych obliczeń warto ładować pliki źródłowe w postaci skompilowanej do specjalnej wewnętrznej postaci binarnej (specyficznej dla konkretnych środowisk), co może przyspieszyć wykonanie o kilka lub nawet kilkanaście razy. Do przeprowadzania kompilacji służy funkcja compile-file, której argumentem jest nazwa pliku w cudzysłowach. Następnie wywołując funkcję load należy jej podać nazwę pliku, który powstanie w wyniku kompilacji. Jest ona tworzona przez dodanie do nazwy oryginalnej przyrostka zależnego od konkretnego środowiska.

Można również spowodować kompilowanie plików źródłowych na bieżąco w trakcie ładowania dostarczając funkcji load dodatkowy specjalny argument kluczowy :compiling z wartością t (logiczna prawda), tak jak w poniższym przykładzie:

> (load "cover" :compiling t)
;; Loading file /home/pawel/src/lisp/cover.lsp ...
;; Loading of file /home/pawel/src/lisp/cover.lsp is finished.
T

Jeśli nazwa pliku nie zawiera ścieżki dostępu, jest on poszukiwany w katalogu bieżącym lub zależnym od parametrów danego środowiska katalogu domyślnym. Zazwyczaj środowiska pozwalają na zmianę katalogu bieżącego, lecz służąca do tego funkcja nie jest objęta standardem języka. W przypadku środowiska CLISP jest to funkcja cd, która jako argument przyjmuje nazwę katalogu ujętą w cudzysłowy.

Kod źródłowy dostarczonego oprogramowania znajduje się w plikach odpowiadających pakietom języka Lisp. Każdy pakiet stanowi pewien zbiór zdefiniowanych symboli, przede wszystkim funkcji i zmiennych, które dzielą się na publiczne (dostępne dla innych pakietów) i prywatne. Podział na logicznie odrębne pakiety służy bardziej przejrzystej organizacji kodu źródłowego i czytelnego wyrażenia występujących w nim zależności. Jest to możliwe dzięki jawnemu deklarowaniu, które funkcje każdego pakietu są dostępne na zewnątrz oraz jakie inne pakiety są w nim wykorzystywane.

Pliki źródłowe składające się na udostępnione oprogramowanie są szczegółowo omówione dalej. Obecnie musimy wiedzieć, że aby było możliwe korzystanie z funkcji zdefiniowanych w którymkolwiek z tych plików, musi on zostać najpierw załadowany. W przypadku, gdy w ładowanym pliku korzysta się z funkcji zdefiniowanych w innych plikach, zostaną one także załadowane automatycznie. Również dane dla algorytmów indukcyjnego uczenia się umieszczono w plikach mających postać plików źródłowych języka Lisp. Pliki te zawierają definicje zmiennych przechowujących opisy atrybutów, kategorii oraz przykładów. Postać tych opisów będzie przedstawiona dalej. Obecnie wystyarczy zaznaczyć, że korzystanie z któregokolwiek z dostarczoncyh zbiorów danych możliwe jest po uprzednim załadowaniu zawierającego go pliku źródłowego.

Uruchamianie funkcji

Używanie programów w języku Lisp sprowadza się zazwyczaj do uruchamiania wybranych funkcji, które składają się na te programy. W dalszym opisie znajdują się szczegółowe informacje na temat tego, które funkcje i w jaki sposób należy wywołać w celu eksperymentowania z zaimplementowanymi algorytmami uczenia się. W każdym przypadku wywołanie ma podaną wyżej postać ujętej w nawiasy nazwy funkcji i jej argumentów oddzielonych dowolnymi białymi znakami (odstępu, tabulacji lub nowegi wiersza). Należy przy tym zapewnić, że definicja wywoływanej funkcji jest dostępna, a więc odpowiedni plik źródłowy został załadowany, a związany z nim pakiet jest aktualnie używany lub jego nazwa występuje w przedrostku kwalifikującym nazwę funkcji. Przedrostek kwalifikacyjny składa się z nazwy pakietu i znaku :, np. kwalifikowaną nazwą funkcji learn z pakietu "TDIDT" jest tdidt:learn. Aby jego podawanie nie było konieczne, należy zadeklarować używanie pakietu korzystając z funkcji use-package, której argumentem jest nazwa pakietu zapisana w cudzysłowiu wielkimi literami, tak jak w poniższym przykładzie:

> (use-package "TDIDT")
T

Zadeklarowanie używania pakietu lub poprzedzenie nazwy przedrostkiem kwalifiuacyjnym o podanej postaci pozwala na dostęp tylko do publicznych funkcji pakietu (i w ogólnym przypadku dowolnych symboli), czyli tych, które pakiet eksportuje. Używanie innych, niepublicznych funkcji i symboli pakietów, nie powinno być na ogół konieczne w celach innych niż diagnostyczne. W każdym jednak razie jest to możliwe przez użycie przedrostka kwalifikującego z dwoma znakami : zamiast jednego.

Zagadnienia dostępności poszczególnych symboli pakietów będą wyjaśnione na konkretnych przykładach w trakcie omawiania sposobu używania najważniejszych funkcji.

Śledzenie funkcji

Dzięki interaktywności środowiska języka Lisp możliwe jest łatwe śledzenie procesu wykonywania programów. Dzięki temu dostarczone implementacje algorytmów uczenia się pozwalają nie tylko na przeprowadzanie z nimi eksperymentów, lecz również na obserwowanie "od środka" ich działania i przez to ich lepsze zrozumienie. Śledzenie polega na drukowaniu przez środowisko komunikatów diagnostycznych podających wszystkie wywołania wybranych do śledzenia funkcji wraz z ich argumentami oraz wartościami zwracanymi. Żądanie śledzenia funkcji zgłaszane jest za pomocą funkcji trace, której jako argumenty przekazuje się nazwę jednej lub nazwy większej liczby funkcji do śledzenia, tak jak w poniższym przykładzie:

(trace cover::complex-generate-aq cover::star-generate-aq)
;; Tracing function COVER::COMPLEX-GENERATE-AQ.
;; Tracing function COVER::STAR-GENERATE-AQ.
(COVER::COMPLEX-GENERATE-AQ COVER::STAR-GENERATE-AQ)
Śledzone funkcje są prywatnymi funkcjami pakietu "COVER", stąd przy ich nazwach występuje odpowiedni przedrostek kwalifikujący.

Każde wywołanie funkcji, które nastapi po zgłoszeniu żądania jej śledzenia, spowoduje wypisanie odpowiedniego komunikatu diagnostycznego, o postaci zależnej od konkretnego środowiska. W celu zaprzestania śledzenia funkcji należy wywołać funkcję untrace, przekazując jej jako argumenty nazwy funkcji, których śledzenie ma zostać zakończone. Wywołanie tej funkcji bez argumentów powoduje zaprzestanie śledzenia wszystkich dotąd śledzonych funkcji.

Zachowywanie przebiegu sesji

Przebieg sesji ze środowiskiem języka Lisp, a więc wszystkie wpisywane przez użytkownika formy i drukowane przez środowisko wyniki, można zachować w pliku w celu późniejszej analizy. Służy do tego funkcja dribble, której jako argument dostarcza się nazwę pliku (jak zwykle w cudzysłowach), w którym ma być zapisany przebnieg sesji. Zapisywanmie kontynuowane jest do zakończenia działania środowiska lub następnego wywołania tej funkcji bez argumentu.

Pliki źródłówe

W katalogu /lisp znajdują się następujące pliki źródłowe w języku Common Lisp:
  1. util.lsp -- zawierający definicje pomocniczych funkcji wykorzystywanych przez funkcje z innych plików,
  2. info.lsp -- zawierający definicje prostych funkcji teorioinformacyjnych używanych głównie przy indukcji drzew decyzyjnych,
  3. concept.lsp -- zawierający definicje funkcji pomocniczych używanych przy uczeniu się pojęć zapewniających wygodny dostęp do informacji o atrybutach i kategoriach przykładów,
  4. tdidt.lsp -- zawierający implementację zstępującej indukcji drzew decyzyjnych (algorytm ID3),
  5. cover.lsp -- zawierający implementację indukcji reguł metodą sekwencyjnego pokrywania (algorytmy AQ i CN2),
  6. bayes.lsp -- zawierający implementację naiwnego klasyfikatora bayesowskiego,
  7. cluster.lsp -- zawierający implementację probabilistycznego grupowania pojęciowego (algorytm COBWEB),
  8. missing.lsp -- zawierający funkcje do obsługi brakujących wartości atrybutów przy uczeniu się pojęć,
  9. discretize.lsp -- zawierający funkcje do dyskretyzacji atrybutów ciągłych przy uczeniu się pojęć,
  10. tester.lsp -- zawierający definicje funkcji wspomagających testowanie algorytmów uczenia się pojęć,
  11. loader.lsp -- powodujący załadowanie do środowiska wszystkich wymienionych wyżej plików związanych z uczeniem się pojęć,
  12. automata.lsp -- zawierający implementację indukcji automatów skończonych (algorytm L*).

Jak widać, zdecydowana większość dostarczanego kodu implementuje podstawowe algorytmy uczenia się pojęć wraz z różnymi operacjami pomocniczymi. Nie dotyczy to tylko pliku automata.lsp.

Dla wygody i przejrzystości każdy z wymienionych plików źródłowych jednoznacznie związany jest z pakietem języka Lisp o takiej samej nazwie (bez przyrostka .lsp). Pakiety mają określony zestaw publicznych (dostępnych w innych pakietach) definicji funkcji i w niektórych przypadkach zmiennych. Te publiczne interfejsy pakietów zostaną omówione w końcowej części tego opisu. Po informacje o niepublicznych definicjach znajdujących się w poszczególnych pakietach można sięgnąć bezpośrednio do odpowiednich plików źródłowych.

Realizacja uczenia się i tworzenia pojęć

Zdecydowana większość udostępnionego kodu poświęcona jest implementacji podstawowych algorytmów uczenia się i tworzenia pojęć, więc sposobowi wykorzystania oprogramowania do tych celów poświęcimy najwięcej uwagi. Poniższa dyskusja dotyczyć będzie pakietów "TDIDT", "COVER", "BAYES" i "CLUSTER" oraz wykorzystywanego przez wszystkie z nich pakietu "CONCEPT" i ułatwiającego korzystanie z nich pakietu "TESTER". Omawiając kolejno reprezentację informacji o atrybutach, kategoriach i przykładach, wywoływanie funkcji przeprowadzających proces uczenia się, używanie uzyskiwanych hipotez oraz mechanizmy testowania jakości hipotez oraz różnych algorytmów uczenia się, jako przykładem posługiwać się będziemy wykorzystywanym w książce zbiorem trenującym dla dziedziny stanów pogody.

Reprezentacja atrybutów i kategorii

Używanie funkcji któregokolwiek z omawianych pakietów możliwe jest tylko pod warunkiem ustalenia informacji o atrybutach, za pomocą których opisywane są przykłady, i kategoriach pojęcia docelowego. Opis pojedynczego atrybutu ma postać listy, którego pierwszym elementem jest nazwa tego atrybutu, a drugim lista złożona z jego wartości. W przypadku atrybutu aura dla dziedziny stanów pogody opis ma postać listy:

(aura (sloneczna pochmurna deszczowa))

Wszystkie określona na dziedzinie atrybuty opisuje sekwencja złożona z opisów każdego z tych atrybutów. W implementacji żadnego pakietu nie zakładano, czy jest to lista, czy wektor, aby umożliwić wybór reprezentacji bardziej efektywnej dla konkretnego interpretera. Poniższa forma języka Lisp definiuje zmienną pogoda-atrybuty jako listę opisów wszystkich atrybutów dla tej dziedziny:

(defparameter pogoda-atrybuty
  '((aura (sloneczna pochmurna deszczowa))
    (temperatura (zimna umiarkowana ciepla))
    (wilgotnosc (normalna wysoka))
    (wiatr (slaby silny))))
Analogiczna konstrukcja wykorzystująca wektor ma postać:
(defparameter pogoda-atrybuty
  '#((aura (sloneczna pochmurna deszczowa))
     (temperatura (zimna umiarkowana ciepla))
     (wilgotnosc (normalna wysoka))
     (wiatr (slaby silny))))

W dostarczonych wraz z oprogramowaniem plikach z danymi oraz dalszych przykładach będziemy konskwentnie używać sekwencji atrybutów będących listami. To samo dotyczy innych przypadków, w których może być wykorzystana sekwencja dowolnego typu: będziemy wówczas wyraźnie stwierdzać, że chodzi o sekwencję, lecz w przykładach wykorzystywać listę.

Użycie konstrukcji defparameter powoduje, że przy każdorazowym jej wartościowaniu (np. podczas wielokrotnego wczytywania pliku z danymi) wartość definiowanmej zmiennej wyznaczana jest na nowo. Dzięki temu, jeśli opisy atrybutów umieszczone są w pliku, wprowadzenie do tego pliku zmian i ponowne wczytanie go osiąga oczekiwany efekt. Alternatywna konstrukcja defvar powoduje, że podana wartość nadawana jest zmiennej tylko wtedy, gdy dotychczas jej wartość nie była określona.

Nazwy atrybutów mogą być dowolnymi poprawnymi symbolami języka Lisp. Wartości atrybutów mogą być dowolnymi poprawnymi symbolami lub liczbami. Niezależnie od ich postaci wymienione w opisie atrybutu wartości są zawsze traktowane jako wartości atrybutu nominalnego. Możliwe jest także wykorzystywanie atrybutów ciągłych. Aby atrybut był traktowany jako ciągły, w jego opisie należy pominąć listę wartości. Wówczas opis atrybutu jest jednoelementową listą zawierającą tylko jego nazwę. Aby atrybut temperatura dla dziedziny stanów pogody był traktowany jako ciągły, powyższą definicję atrybutów należałoby zmodyfikować w następujący sposób:

(defparameter pogoda-atrybuty
  '((aura (sloneczna pochmurna deszczowa))
    (temperatura)
    (wilgotnosc (normalna wysoka))
    (wiatr (slaby silny))))

Opis kategorii pojęcia docelowego ma postać sekwencji (listy lub wektora) etykiet tych kategorii, które mogą być dowolnymi symbolami lub liczbami języka Lisp. Poniższa konstrukcja definiuje zmienną pogoda-kategorie określającą, że dla dziedziny stanów pogody pojęcie docelowe ma dwie kategorie 1 i 0:

(defparameter pogoda-kategorie
  '(1 0))

Aby wprowadzić opisy atrybutów i kategorii, co jest niezbędne do korzystania z większości funkcji rozważanych pakietów, można wykorzystać funkcję concept-setyp z pakietu "CONCEPT", której argumentami są sekwencja opisów atrybutów i sekwencja opisów kategorii. Zakładając dostępność zmiennych pogoda-atrybuty i pogoda-kategorie zdefiniowanych w podany wyżej sposób, można przykładowe wywołanie tej funkcji zapisać następująco:

(concept:concept-setup pogoda-atrybuty pogoda-kategorie)

Później zobaczymy, że używając funkcji pakietu "TESTER" można informacje o atrybutach i kategoriach przekazać jako ichumenty.

Poprzedzenie nazwy funkcji przedrostkiem kwalifikującym złożonym z nazwy pakietu i znaku : jest niezbędne, jeśli wywołanie następuje z innego pakietu (domyślnym pakietem jest w większości środowisk języka Lisp pakiet "USER"). Można tego uniknąć zapewniając przed wywołaniem, że publiczne funkcje pakietu "CONCEPT" są dostępne za pomocą formy:

(use-package "CONCEPT")

Ta sama uwaga dotyczy używania innych publicznych funkcji dowolnego pakietu innym pakiecie. W dalszych przykładach zakładać będziemy na ogół, że zostały wykonane odpowiednie formy use-package i wykorzystywane funkcje są dostępne bez potrzeby poprzedzania ich nazw przedrostkiem kwalifikującym.

Reprezentacja przykładów

Pojedynczy przykład reprezentowany jest za pomocą listy, której pierwszy element jest sekwencją wartości atrybutów dla tego przykładu, a drugim elementem jest jego etykieta kategorii. Wartości atrybutów muszą występować w kolejności zgodnej z kolejnością atrybutów, jaka została określona. Pierwszy przykład trenujący dla dziedziny stanów pogody ma więc następujący opis:

((sloneczna ciepla wysoka slaby) 0)

Każda wartość atrybutu musi pochodzić listy wartości podanej przy określaniu atrybutów. W przypadku atrybutów ciągłych jako wartość może wystąpić dowolna liczba. Gdyby atrybut temperatura był ciągły, powyższy opis przykładu mógłby mieć postać:

((sloneczna 27 wysoka slaby) 0)

Nieznane (brakujące) wartości atrybutów dla przykładów reprezentowane są przez nil. Dla przykładu o następującym opisie wartość atrybutu aura jest nieznana:

((nil ciepla normalna silny) 1)

W opisie przykładu można pominąć etykietę kategorii, jeśli nie jest znana. Wówczas mamy do czynienia z przykładem nieetykietowanym. Pomijając kategorię pierwszego przykładu trenującego dla dziedziny stanów pogody dostajemy:

((sloneczna ciepla wysoka slaby))

Jako przykłady trenujące dla uczenia się pojęć mogą być oczywiście używane wyłącznie przykłady etykietowane. Przykłady nieetykietowane mogą być klasyfikowane za pomocą uzyskanych hipotez i używane jako dane trenujące dla grupowania.

Zbiór przykładów jest reprezentowany przez listę przykładów, opisanych w określony wyżej sposób. Wprawdzie niektóre z funkcji operujących na zbiorach przykładów może posługiwać się dowolną sekwencją (czyli także wektorem), lecz w implementacji części funkcji założenie o reprezentacji listowej ma istotne znaczenie. Zbiór trenujący dla dziedziny stanów pogody można przedstawić następująco:

(defparameter pogoda-przyklady
  '(((sloneczna ciepla wysoka slaby) 0)
    ((sloneczna ciepla wysoka silny) 0)
    ((pochmurna ciepla wysoka slaby) 1)
    ((deszczowa umiarkowana wysoka slaby) 1)
    ((deszczowa zimna normalna slaby) 1)
    ((deszczowa zimna normalna silny) 0)
    ((pochmurna zimna normalna silny) 1)
    ((sloneczna umiarkowana wysoka slaby) 0)
    ((sloneczna zimna normalna slaby) 1)
    ((deszczowa umiarkowana normalna slaby) 1)
    ((sloneczna umiarkowana normalna silny) 1)
    ((pochmurna umiarkowana wysoka silny) 1)
    ((pochmurna ciepla normalna slaby) 1)
    ((deszczowa umiarkowana wysoka silny) 0)))

Powyższa konstrukcja definiuje zmienną pogoda-przyklady przechowującą listę przykładów trenujących.

Generowanie i używanie hipotez

Każdy z pakietów "TDIDT", "COVER", "BAYES" i "CLUSTER" udostępnia publiczną funkcję learn, która zwraca hipotezę wygenerowaną za pomocą odpowiedniego algorytmu uczenia się na podstawie dostarczonego zbioru przykładów. W każdym przypadku lista przykładów jest pierwszym, a w większości przypadków jedynym argumentem funkcji. Dla zbioru trenującego dotyczącego dziedziny stanów pogody wywołanie ma więc postać:
(learn pogoda-przyklady)

Oczywiście aby używać funkcji learn z wybranego pakietu należy albo porzedzić ją przedrostkiem kwalifikującym, albo wcześniej zadeklarować używanie tego pakietu za pomocą konstrukcji use-package. W tym drugim przypadku należy pamiętać, że próba używania więcej niż jednego pakietu zawierającego definicję tego samego symbolu (w tym przypadku funkcji learn) powoduje konflikt, który powoduje błąd lub wymaga rozstrzygnięcia przez użytkownika. Można tego uniknąć rezygnując z używania poprzedniego pakietu przed użyciem następnego za pomocą konstrukcji unuse-package.

Aby wygenerować hipotezę na podstawie zbioru trenującego dla dziedziny stanów pogody najpierw korzystając z pakietu "TDIDT", a następnie korzystając z pakietu "COVER", można wykonać następujący ciąg form:

(concept:concept-setup pogoda-atrybuty pogoda-kategorie)

(tdidt:learn pogoda-przyklady)

(cover:learn pogoda-przyklady)
albo równoważnie:
(use-package "CONCEPT")
(concept-setup pogoda-atrybuty pogoda-kategorie)

(use-package "TDIDT")
(learn pogoda-przyklady)

(unuse-package "TDIDT")
(use-package "COVER")
(learn pogoda-przyklady)

Oczywiście powyższe wywołania funkcji są poprawne pod warunkiem, że zmienne pogoda-atrybuty, pogoda-kategorie i pogoda-przyklady zostały odpowiednio zdefiniowane, na przykład tak jak było to podane wyżej.

W przypadku pakietu "COVER" funkcja learn może poza listą przykładów otrzymywać dodatkowe argumenty kluczowe:

Poniższe formy powodują wygenerowanie zbiorów reguł na podstawie przykładów trenujących dla dziedziny stanów pogody za pomocą algorytmu AQ i algorytmu CN2:

(cover:learn pogoda-przyklady :target 1 :algorithm :aq)

(cover:learn pogoda-przyklady :algorithm :cn2)

W pierwszym przypadku została dodatkowo przekazana kategoria docelowa 1, co spowoduje wygenerowanie reguł tylko dla tej kategorii.

Pewne parametry wpływające na działanie algorytmów AQ i CN2 implementowanych przez pakiet "COVER" są zdefiniowane jako publiczne zmienne tego pakietu. Ich domyślne wartości mogą być zmieniane przez użytkownika. Są to następujące zmienne:

Poniższy przykład demonstruje zmianę tych parametrów przez użytkownika:

(setq *maxstar* 3)
(setq *cn2-theta* 1.0)
(setq *cn2-disjuncts* 2)

Funkcja learn każdego z omawianych pakietów jako swój wynik zwraca uzyskaną hipotezę. Jej reprezentacja jest oczywiście inna dla każdego pakietu i można ją poznać przeglądając kod źródłowy, nie ma ona jednak dużego znaczenia dla użytkownika, który hipotezy obserwuje zapisywane w bardziej czytelnej postaci. Poniżej przedstawione jest w takim zapisie drzewo decyzyjne oraz zbiór reguł wygenerowany przez algorytm AQ dla dziedziny stanów pogody:

< AURA >
  =SLONECZNA
      < WILGOTNOSC >
       =NORMALNA
            [1]
       =WYSOKA
            [0]
  =POCHMURNA
      [1]
  =DESZCZOWA
      < WIATR >
       =SLABY
            [1]
       =SILNY
            [0]
Rules:
1. IF < AURA=( SLONECZNA DESZCZOWA ) TEMPERATURA=( ZIMNA CIEPLA ) WILGOTNOSC=( WYSOKA ) >
   THEN 0
2. IF < AURA=( POCHMURNA DESZCZOWA ) WIATR=( SLABY ) >
   THEN 1
3. IF < AURA=( DESZCZOWA ) TEMPERATURA=( ZIMNA UMIARKOWANA ) WIATR=( SILNY ) >
   THEN 0
4. IF < AURA=( SLONECZNA POCHMURNA ) TEMPERATURA=( ZIMNA UMIARKOWANA ) WIATR=( SILNY ) >
   THEN 1
5. IF < AURA=( SLONECZNA POCHMURNA ) TEMPERATURA=( ZIMNA UMIARKOWANA ) WILGOTNOSC=( WYSOKA ) WIATR=( SLABY ) >
   THEN 0
6. IF < WILGOTNOSC=( NORMALNA ) WIATR=( SLABY ) >
   THEN 1
Default category: 1

Hipoteza generowana przez funkcję learn z pakietu "TDIDT" jest drzewem decyzyjnym, którego struktura w zapisie tekstowym jest ukazywana za pomocą odpowiednich wcięć. W nawiasach trójkątnych znajdują się nazwy atrybutów odpowiadające węzłom, w nawiasach kwadratowych nazwy kategorii odpowiadające liściom, a wartości atrybutów odpowiadające gałęziom poprzedzone są znakiem równości. Zbiór reguł generowany w przypadku pakietu "COVER" zapisywany jest w postaci ponumerowanej listy (choć zbiór ten traktować należy jako uporządkowany tylko wtedy, jeśli do jego tworzenia użyto algorytmu CN2), po której następuje dodatkowo wskazanie kategorii domyślnej, do której klasyfikowane będą przykłady niepokryte przez żadną z tych reguł. Zapisując reguły uwzględnia się tylko selektory nieuniwersalne ich kompleksów, poprzedzając listę wartości dozwolonych każdego takiego selektora nazwą odpowiedniego atrybutu i znakiem równości. Dla naiwnego klasyfikatora bayesowskiego, który implementuje pakiet "BAYES" hipoteza ma postać zbioru oszacowań prawdopodobieństw kategorii oraz warunkowych prawdopodobieństw wartości atrybutów. Przy ich zapisie jawnie podaje się, których kategorii, atrybutów i ich wartości dotyczą. Najtrudniejsza do interpretacji jest postać hipotez tworzonych za pomocą algorytmu COBWEB w pakiecie "CLUSTER". W jego węzłach nie są fizycznie przechowywane przykłady, a tylko liczniki wystąpień wartości atrybutów, jednak podawanie ich wszystkich dla każdego węzła prowadziłoby do zapisu bardzo nieczytelnego. W obecnej implementacji przyjęto, że przechowuje się także liczniki wystąpienia poszczególnych etykiet kategorii, o ile przykłady trenujące są etykietowane, co umożliwia użycie drzewa grupowania jako hipotezy dla uczenia się pojęć. Właśnie te liczniki są wypisywane dla każdego węzła (w kolejności zgodnej z kolejnością w sekwencji kategorii), przy czym do tekstowej reprezentacji struktury drzewiastej wykorzystane są wcięcia.

Zwróconą przez funkcję learn hipotezę można oczywiście nie tylko obejrzeć, ale również zachować w pewnej zmiennej i następnie używać do klasyfikowania przykładów. Służy do tego również udostępniana przez każdy z pakietów "TDIDT", "COVER", "BAYES" i "CLUSTER" funkcja classify, której pierwszym argumentem jest hipoteza, a drugim przykład do klasyfikacji. Wartością zwracaną jest wyznaczona za pomocą hipotezy kategoria przykładu. Ilustrację dla dziedziny stanów pogody stanowią dwie poniższe formy, przy założeniu, że wcześniej została wykonana forma use-package dla wybranego pakietu:

(setq h (learn pogoda-przyklady))
(classify h '((sloneczna zimna wysoka silny)))

Oczywiście funkcji classify można przekazać tylko hipotezę wygenerowaną przez funkcję learn z tego samego pakietu.

Przetwarzanie zbiorów trenujących

Funkcje zajmujące się wstępnym przetwarzaniem zbiorów przykładów używanych do uczenia się udostępniają pakiety "MISSING" i "DISCRETIZE", poświęcone odpowiednio eliminacji lub wypełnianiu brakujących wartości atrybutów oraz dyskretyzacji atrybutów ciągłych. Zgodnie z tym, co napisano wcześniej, brakujące wartości atrybutów są reprezentowane przez nil w opisie przykładu, a atrybuty ciągłe określa się przez pominięcie w ich opisie listy wartości. Pakiet "CONCEPT" udostępnia funkcję value-unknown-p, która jest predykatem sprawdzającym, czy wartość atrybutu przekazana jako jej argument jest wartością nieznaną (brakującą), oraz funkcję attribute-continuous-p, która jest predykatem sprawdzającym, czy przekazany jako jej argument atrybut (dokładniej, opis atrybutu w podanym wyżej sensie) jest atrybutem ciągłym.

Funkcja missing-eliminate z pakietu "MISSING" zwraca kopię przekazanej jako argument listy przykładów z pominięciem tych z nich, dla których występują brakujące wartości atrybutów. Lista wynikowa zawiera więc tylko te przykłady z listy oryginalnej, dla których wartości wszystkich atrybutów są znane. Gdyby zbiór trenujący dla dziedziny stanów pogody został zdefiniowany w sposób następujący:

(defparameter pogoda-nieznane-instances
  '(((sloneczna ciepla wysoka slaby) 0)
    ((sloneczna ciepla wysoka silny) 0)
    ((pochmurna ciepla wysoka slaby) 1)
    ((deszczowa umiarkowana wysoka slaby) 1)
    ((deszczowa zimna normalna slaby) 1)
    ((deszczowa zimna normalna silny) 0)
    ((pochmurna zimna normalna silny) 1)
    ((sloneczna umiarkowana wysoka slaby) 0)
    ((sloneczna zimna normalna slaby) 1)
    ((deszczowa umiarkowana normalna slaby) 1)
    ((sloneczna umiarkowana normalna silny) 1)
    ((pochmurna umiarkowana wysoka silny) 1)
    ((pochmurna ciepla normalna slaby) 1)
    ((deszczowa umiarkowana wysoka silny) 0)
    ((nil ciepla normalna silny) 1)))
to wykonanie formy:
(setq pogoda-znane-instances (missing-eliminate pogoda-nieznane-instances))
powoduje umieszczenie w zmiennej pogoda-znane-instances kopii listy pogoda-nieznane-instances bez ostatniego przykładu. Lista pogoda-nieznane-instances nie jest przy tym w żaden sposób modyfikowana.

Zamiast pomijania przykładów z brakującymi wartościami atrybutów można te wartości wypełnić za pomocą funkcji missing-fill z pakietu "MISSING". Wypełnianie może być przeprowadzone zgodnie z dwoma prostymi strategiami: albo najczęściej występującą wartością w całym zbiorze przykładów, albo wartością najczęściej występującą w ramach kategorii, którego dotyczy wypełnianie (jeśli więcej niż jedna wartość ma taką samą maksymalną liczbę wystąpień, może zostać użyta dowolna z nich, zależnie od implementacji). Do wyboru jednej z dwóch możliwości służy argument kluczowy :algorithm tej funkcji, który jako wartości może przyjmować symbole odpowiednio :most-common i :category-most-common. Pierwszym argumentem jest lista przykładów. W wyniku działania funkcji jest ona modyfikowana i następnie zwracana. Do podanego wyżej zbioru przykładów dla dziedziny stanów pogody można ją zastosować następująco:

(missing-eliminate pogoda-nieznane-instances :algorithm :category-most-common)
Forma ta spowoduje wypełnienie brakującej wartości atrybutu aura dla ostatniego przykładu wartością najczęściej występującą dla przykładów kategorii 1.

Po wyeliminowaniu lub wypełnieniu brakujących wartości atrybutów w zbiorze przykładów można wykorzystywać go jako dane trenujące lub testujące dla dowolnego algorytmu uczenia się zaimplementowanego w pakietach "TDIDT", "COVER", "BAYES" i "CLUSTER".

Pakiet "DISCRETIZE" udostępnia dwie funkcje służące do dwukrokowego przeprowadzania dyskretyzacji atrybutów ciągłych. Pierwsza z nich, attributes-discretize na podstawie dostarczonego zbioru przykładów wyznacza przedziały dyskretyzacji (zgodnie z wybranym algorytmem) i odpowiednio modyfikuje opisy atrybutów ciągłych, wprowadzając do nich listy odpowiadających tym przedziałom wartości dyskretnych. Każdemu wyznaczonemu przedziałowi o końcach v1 i v2 przyporządkowywana jest jednoznacznie dyskretna wartość atrybutu mająca postać nowo tworzonego symbolu, któreho nazwa w zapisie tekstowym ma postać "v1:v2". Zmieniona sekwnencja opisów atrybutów jest zwracana, a lista przykładów nie ulega zmianie. Lista ta jest pierwszym argumentem funkcji, której mogą być także przekazane następujące argumenty kluczowe:

Jeśli dla dziedziny stanów pogody atrybuty temperatura i wilgotjność zostały określone jako ciągłe:

(defparameter pogoda-ciagle-atrybuty
  '((aura (sloneczna pochmurna deszczowa))
    (temperatura)
    (wilgotnosc)
    (wiatr (slaby silny))))

(defparameter pogoda-kategorie
  '(1 0))

(concept-setup pogoda-ciagle-atrybuty pogoda-kategorie)

(defparameter pogoda-ciagle-przyklady
  '(((sloneczna 37.0 80.0 slaby) 0)
    ((sloneczna 35.0 85.0 silny) 0)
    ((pochmurna 32.0 90.0 slaby) 1)
    ((deszczowa 25.0 83.0 slaby) 1)
    ((deszczowa 18.0 70.0 slaby) 1)
    ((deszczowa 17.0 65.0 silny) 1)
    ((pochmurna 19.0 72.0 silny) 1)
    ((sloneczna 24.0 95.0 slaby) 0)
    ((sloneczna 20.0 55.0 slaby) 1)
    ((deszczowa 26.0 60.0 slaby) 1)
    ((sloneczna 23.0 67.0 slaby) 1)
    ((pochmurna 22.0 87.0 silny) 1)
    ((pochmurna 30.0 59.0 slaby) 1)
    ((deszczeowa 27.0 93.0 silny) 0)))
Przykładowe wywołanie funkcji attributes-discretize może mieć postać:
(discretize:attributes-discretize pogoda-ciagle-przyklady
                                  :intervals 3 :extend t
                                  :algorithm :chi-merge)

Druga funkcja, instances-discretize, dokonuje faktyczne rzutowania zbioru przykładów do przestrzeni atrybutów otrzymanej po dyskretyzacji. Przekształca ona dostarczony jej zbiór przykładów zastępując występujące w nim wartości atrybutów poprzednio ciągłych odpowiednimi wartościami dyskretnymi, pobranymi z (uprzednio zmodyfikowanych przez funkcję attributes-discretize) opisów tych atrybutów. Lista przykładów jest jedynym argumentem tej funkcji, który po dokonaniu modyfikacji jest zwracany. Kontynuując powyższy przykład możemy wywołanie tej funkcji zapisać następująco:

(discretize:instances-discretize pogoda-ciagle-przyklady)

Po przeprowadzeniu dyskretyzacji atrybutów i następnie odpowiedniego rzutowania zbioru przykładów można go używać jako danych trenujących lub testujących dla wszystkich algorytmów uczenia się zaimplementowanych w pakietach "TDIDT", "COVER", "BAYES" i "CLUSTER".

Testowanie hipotez i algorytmów

Omówione dotychczas funkcje pakietów "TDIDT", "COVER", "BAYES" i "CLUSTER" oraz "MISSING" i "DISCRETIZE" pozwalają używanie ich do generowania hipotez i klasyfikowania przykładów, a także wstępne przetwarzanie -- w razie potrzeby -- zbiorów trenujących i testujących. Wykorzystując te funkcje można w łatwy sposób przeprowadzać eksperymenty mające na celu określenie jakości hipotez uzyskiwanych przez różne algorytmy uczenia się dla różnych zbiorów danych. Usprawnieniu tego typu procedur eksperymentalnych służy pakiet "TESTER". Oferowane przez niego usprawnienia mają dwojaki charakter:

Dostęp do funkcji różnych pakietów

Każdy z czterech pakietów implementujących algorytmy uczenia się i tworzenia pojęć udostępnia funkcję learn, służącą do wygenerowania hipotezy na podstawie dostarczonego jako argument zbioru trenującego. Jednolity dostęp do wersji tej funkcji pochodzących z różnych pakietów zapewnia funkcja train z pakietu "TESTER", która również otrzymuje zbiór przykładów jako argument i zwraca uzyskaną na jego podstawie hipotezę. Funkcja ta otrzymuje dodatkowo następujące argumenty kluczowe:

Pierwsze dwa argumenty pozwalają na podanie opisów atrybutów i kategorii w takiej postaci, w jakiej podaje się je dla funkcji concept-setup z pakietu "CONCEPT" i która była wyjaśniona wyżej. W przypadku ich pominięcia wykorzystywane będą wcześniej podane informacje o atrybutach i kategoriach (np. przez wcześniejsze wywołanie tej funkcji). Ostatni argument pozwala na podanie nazwy pakietu, którego funkcja learn będzie wywołana dla podanego zbioru przykładów. Aby więc korzystając z funkcji train pakietu "TESTER" wygenerować drzewo decyzyjne na podstawie zbioru trenującego dla dziedziny stanów pogody, zapiszemy formę:
(train pogoda-przyklady
       :attributes pogoda-atrybuty
       :categories pogoda-kategorie
       :package "TDIDT")
Jej skutki są równoważne skutkom wykonania poniższych form, dla których stanowi ona w istocie alternatywny zapis:
(concept:concept-setup pogoda-atrybuty pogoda-kategorie)
(tdidt:learn pogoda-przyklady)

Wprawdzie zapis używający funkcji train nie jest krótszy niż bezpośrednio odwołujący się do funkcji concept-setup i learn, ale jego istotną przewagą jesto, że nazwa pakietu występuje tam jako argument wywołania, przez co może być zmienną. W ten sposób można wygenerować hipotezę za pomocą funkcji learn z pakietu, którego nazwa jest znana dopiero w czasie wykonywania formy (np. odczytana z pliku, podana przez użytkownika) lub w zautomatyzowany sposób wykorzystać różne pakiety. Następująca forma daje w wyniku listę hipotez dla dziedziny stanów pogody uzyskanych za pomocą wszystkich czterech pakietów udostępniających funkcję learn:

(mapcar #'(lambda (p)
            (train pogoda-przyklady
                   :attributes pogoda-atrybuty
                   :categories pogoda-kategorie
                   :package p))
        '("TDIDT" "COVER" "BAYES" "CLUSTER"))
Ten prosty przykład ilustruje, jak za pomocą pakietu "TESTER" można automatyzować przeprowadzanie eksperymentów. Dalej zobaczymy kolejne przykłady tego typu.

Funkcja train z pakietu "COVER" oprócz zbioru przykładów może przyjmować argumenty kluczowe określające docelową i domyślną kategorię oraz wykorzystywany algorytm uczenia się. W związku z tym funkcja train z pakietu "TESTER" umożliwia przekazanie do wywoływanej funkcji learn dowolnych dodatkowych argumentów kluczowych różnych od tych, które one sama wykorzystuje. Jest to mechanizm uniwersalny, który działa nie tylko dla pakietu "COVER", ale może być też użyty dla ewentualnych nowych pakietów lub w przypadku wprowadzenia do funkcji learn z pozostałych obecnie istniejących pakietów dodatkowych argumentów. Dla ilustracji, aby do generowania hipotezy dla dziedziny stanów pogody użyć algorytmu CN@, napiszemy:

(train pogoda-przyklady
       :attributes pogoda-atrybuty
       :categories pogoda-kategorie
       :package "COVER"
       :algorithm :aq)

Dostęp do funkcji classify z różnych pakietów umożliwia funkcja o tej samej nazwie z pakietu "TESTER" na takich samych zasadach, jak omówione wyżej dla funkcji train. Tym razem pierwsze dwa argumenty to hipoteza i przykład do klasyfikacji, kluczowe argumenty :attributes i :categories służą do podania informacji o atrybutach i kategoriach, a kluczowy argument :package określa pakiet, z którego ma pochodzić funkcja classify użyta do klasyfikacji tego przykładu za pomocą tej hipotezy. Oczywiście musi być to ten sam pakiet, który był użyty do generowania hipotezy. Tak jak poprzednio, ewentualne dodatkowe argumenty kluczowe są przekazywane do wywoływanej funkcji, lecz obecnie żadna z dostępnych wersji funkcji classify takich argumentów nie używa.

Poniższe dwie formy powodują zapamiętanie hipotezy wygenerowanej dla dziedziny stanów pogody za pomocą pakietu "TDIDT", a następnie wykorzystanie jej do klasyfikacji nowego przykładu:

(setq h (train pogoda-przyklady
               :attributes pogoda-atrybuty
               :categories pogoda-kategorie
               :package "TDIDT"))
(classify h '((sloneczna zimna wysoka silny))
          :package "TDIDT")
W wywołaniu funkcji classify pominięto argumenty :attributes i categories, aby użyć informacji o atryhutach i kategoriach ustalonych przez wcześniejsze wywołanie funkcji train.

Wynikiem wykonania poniższej formy jest lista kategorii przypisanych temu samemu nowemu przykładowi przez każdą z hipotez uzyskanych dla dziedziny stanów pogody za pomocą wszystkich czterech pakietów udostępniających funkcje learn i classify:

(mapcar #'(lambda (p)
            (let ((h (train pogoda-przyklady
                            :attributes pogoda-atrybuty
                            :categories pogoda-kategorie
                            :package p)))
              (classify h '((sloneczna zimna wysoka silny))
                        :package p)))
        '("TDIDT" "COVER" "BAYES" "CLUSTER"))

Testowanie hipotez

Często jakość uzyskanej hipotezy oceniamy obliczając jej błąd próbki na pewnym zbiorze przykładów używanych do testowania. Aby uniknąć konieczności jawnego wielokrotnego wywoływania funkcji classify i obliczania liczby pomyłek, w pakiecie "TESTER" zaimplementowano funkcję test, która dla hipotezy i zbioru przykładów podanych jako odpowiednio pierwszy i drugi argument wyznacza błąd próbki. Działanie tej funkcji polega oczywiście na wielokrotnym wywoływaniu funkcji classify, więc należy jej przekazać argumenty kluczowe niezbędne do poprawnego wykonania takiego wywołanie -- w razie potrzeby argumenty :attributes i :categories (jeśli informacje o atrybutach i kategoriach nie zostały wcześniej właściwie określone) oraz w każdym przypadku argument :package w celu podania, z którego pakietu funkcja classify ma być użyta. Poniżej pokazano sposób testowania za pomocą funkcji test drzewa decyzyjnego dla dziedziny stanów pogody, zakładając, że wartością zmiennej pogoda-test-instances jest pewna lista przykładów testujących:

(setq h (train pogoda-przyklady
               :attributes pogoda-atrybuty
               :categories pogoda-kategorie
               :package "TDIDT"))
(test h pogoda-test-instances
      :package "TDIDT")

Testowanie algorytmów

Dwie podstawowe metody eksperymentalnej oceny skuteczności algorytmu uczenia się dla ustalonego zbioru danych to walidacja krzyżowa i k-krotna walidacja krzyżowa. Ze względu na ich częste stosowanie pakiet "TESTER" pozwala przeprowadzić je w sposób w pełni zautomatyzowany, bez potrzeby jawnego manipulowania na zbiorze przykładów oraz odpowiedniego wywoływania funkcji train i test. Pierwszym argumentem funkcji cross-validate i k-fold-cross-validate jest zbiór przykładów etykietowanych, który ma być we właściwy sposób dzielony na zbioru trenujący i testujący. Dla pierwszej z tych funkcji zbiór trenujący jest losowym podzbiorem całego zbioru przykładów o rozmiarze równym iloczynowi jego rozmiaru i ułamkowego współczynnika przekazanego jako jej drugi argument. Pozostałe przykłady wchodzą w skład zbioru testującego. Błąd próbki uzyskanej hipotezy na tym zbiorze stanowi pierwszą wartość zwracaną przez funkcję. Zwraca ona także drugą wartość, którą jest błąd tej hipotezy na zbiorze trenującym, oraz trzecią, będąca rozmiarem hipotezy (ściślej, listą wartości zwróconych dla niej przez funkcję hypothesis-size, której tu nie omawiamy, lecz której opis znajduje się w ramach prezentacji publicznych interfejsów wszystkich pakietów). Z kolei dla funkcji k-fold-cross-validate drugim argumentem jest liczba w przybliżeniu równolicznych losowych podzbiorów, na które dzielony jest zbiór przykładów. Każdy z tych podzbiorów pełni rolę zbioru testującego dla hipotezy wygenerowanej na podstawie zbioru trenującego powstałego przez połączenie pozostałych podzbiorów. Jako pierwszą wartość funkcja zwraca średni uzyskany błąd próbki na zbiorze testującym. Drugą wartość zwracaną stanowi lista złożona z list zawierających wszystkie uzyskane błędy testowania i odpowiadające im rozmiary hipotez (a ściślej, listy wartości zwróconych przez funkcję hypothesis-size). Ponieważ obie funkcje realizują swoje zadanie przez odpowiednie wywoływanie funkcji train i test, należy im przekazać niezbędne dla takich wywołań argumenty kluczowe.

Poniższa forma wykonuje walidację krzyżową dla algorytmu ID3 na podstawie zbioru przykładów dla dziedziny stanów pogody, używając jako zbioru trenującego zbioru przykładów złożonego z połowy dostępnych dla tej dziedziny danych:

(cross-validate pogoda-przyklady 0.5
                :attributes pogoda-atrybuty
                :categories pogoda-kategorie
                :package "TDIDT")
Z kolei następna forma demonstruje 7-krotną walidację krzyżową dla algorytmu CN2 na podstawie tych samych danych:
(k-fold-cross-validate pogoda-przyklady 7
                       :attributes pogoda-atrybuty
                       :categories pogoda-kategorie
                       :package "COVER"
                       :algorithm :cn2)

Automatyzacja eksperymentów

Ostatnią ważną cechę funkcjonalną pakietu "TESTER" stanowi możliwość zautomatyzowanego wykonywania tych samych eksperymentów dla różnych algorytmów uczenia się. Dzięki niej można powtórzyć uczenie się, walidację krzyżową lub k-krotną walidację krzyżową na podstawie tych samych danych dla dowolnego dostępnego algorytmu. Operacją wykonywaną w ramach takiego eksperymentu może być dowolna funkcja, która niezależnie od używanego algorytmu uczenia się przyjmuje takie same argumenty. Spośród omawianych wyżej funkcji pakietu "TESTER" wyklucza to funkcje classify i test, które jako argument otrzymują hipotezę o postaci specyficznej dla każdego algorytmu, ale pozwala na używanie funkcji train, cross-validate i k-fold-cross-validate. Jedna z tych funkcji może być pierwszym argumentem służącej do automatyzacji eksperymentów funkcji experiment (nazwę funkcji poprzedza się wówczas przedrostkiem '#). Oczywiście można użyć także pewnej funkcji zdefiniowanej uprzednio przez użytkownika, o ile spełnia podany warunek. Drugi argument to z kolei lista stałych, wspólnych dla wszystkich algorytmów uczenia się argumentów (niekluczowych), jakie mają być do tej wybranej funkcji przekazywane przy jej wywoływaniu. Trzeci argument jest listą określającą, dla jakich algorytmów eksperyment ma być przeprowadzony. Każdy element tej listy może być albo nazwą pakietu, albo listą złożoną z nazwy pakietu i dodatkowych, specyficznych dla tego pakietu argumentów. Jak wiemy, obecnie takie argumenty mają znaczenie tylko dla pakietu "COVER". Ewentualne dodatkowe argumenty kluczowe funkcji experiment przekazywane są do wywoływanej przez nią dla każdego algorytmu funkcji, co w szczególności pozwala podać informacje o atrybutach i kategoriach. Wynikiem funkcji jest lista asocjacyjna, w której każdy używany algorytm związany jest z uzyskanym dla niego wynikiem eksperymentu.

Przedstawiona niżej forma powoduje przeprowadzenie 7-krotnej walidacji krzyżowej na podstawie dostępnych danych dla dziedziny stanów pogody dla każdego dostępnego algorytmu uczenia się:

(experiment #'k-fold-cross-validate
            `(,pogoda-przyklady 7)
            '("TDIDT"
              ("COVER" :algorithm :aq)
              ("COVER" :algorithm :cn2)
              "BAYES"
              "CLUSTER")
            :attributes pogoda-atrybuty
            :categories pogoda-kategorie)
Warto zauważyć, że drugim argumentem powyższego wywołania musi być lista argumentów przekazywanych przy każdym wywołaniu funkcji k-fold-cross-validate. Jej pierwszym argumentem ma być zbiór przykładów, a drugim liczba 7, aby przeprowadzona została 7-krotna walidacja krzyżowa. Listę taką uzyskuje się za pomocą konstrukcji:
`(,pogoda-przyklady 7)
Poprzedzenie listy wstecznym apostrofem ` zamiast zwykłego ' powoduje, że można jako jej element umieścić wartość zmiennej (w tym przypadku pogoda-przyklady), jeśli poprzedzi się nazwę tej zmiennej przecinkiem. "Naiwny" zapis:
'(pogoda-przyklady 7)
tworzy tymczasem listę, której pierwszym elementem jest symbol pogoda-przyklady, a nie wartość zmiennej o takiej nazwie.

Zamiast nazw pakietów wywołując funkcję experiment można też z identycznym skutkiem użyć odpwiednich symboli, czyli powyższy przykład może być równie dobrze zapisany następująco:

(experiment #'k-fold-cross-validate
            `(,pogoda-przyklady 7)
            '(tdidt
              (cover :algorithm :aq)
              (cover :algorithm :cn2)
              bates
              cluster)
            :attributes pogoda-atrybuty
            :categories pogoda-kategorie)

Aby przy korzystaniu z algorytmów indukcyjnego uczenia się i tworzenia pojęć całkowicie wyeliminować potrzebę bezpośredniego odwoływania się do funkcji pakietów innych niż "TESTER", zawarto w nim także funkcje missing-eliminate i missing-fill oraz discretize-attributes, discretize-instances i discretize-all. Ich działanie sprowadza się do odpowiedniego wywoływania funkcji z pakietów "MISSING" i "DISCRETIZE". Nie będziemy ich w tym miejscu dokładniej omawiać, uznając za wystarczający ich opis przedstawiony niżej przy prezentacji publicznych interfejsów pakietów.

Realizacja uczenia się automatów

Pakiet "AUTOMATA" jako jedyny z dostarczonych pakietów nie dotyczy uczenia się lub tworzenia pojęć, lecz uczenia się automatów skończonych. Zawiera on implementację podstawowego algorytmu L* uczącego się na podstawie zapytań o przynależność i równoważność. Zdefiniowane w nim struktury danych do reprezentacji automatów i obsługujące je funkcje moga być oczywiście wykorzystane do implementacji innych algorytmów. Dla większej ogólności przyjęto, że poszukiwana hipoteza ma postać automatu z wyjściami typu Moore'a -- automat skończony bez wyjśc (ze stanami końcowymi) może być traktowany jako jego szczególny przypadek.

Reprezentacja symboli i napisów

Symbolami wejściowymi i wyjściowymi automatu mogą być dowolne symbole języka Lisp. Alfabet wejściowy i wyjściowy przechowują odpowiednio zmienne *inalpha* i *outalpha zdefiniowane w pakiecie "AUTOMATA", których wartościami mogą być dowolne listy symboli. Domyślnie obie zmienne mają wartość (0 1), czyli alfabet wejściowy i wyjściowy składa się z symboli 0 i 1.

Napis, czyli ciąg symboli wejściowych, reprezentowany jest przez listę złożoną z symboli alfabetu wejściowego. Zatem napis 0010 ma reprezentację (0 0 1 0).

Zadawanie zapytań

Informacja trenująca dla algorytmu L* ma postać odpowiedzi na zapytania o przynależność i równoważność. Aby umożliwić wykorzystywanie implementacji tego algorytmu, jaką dostarcza pakiet "AUTOMATA", w maksymalnie elastyczny sposób, przyjęto, że zadanie zapytania każdego z tych rodzajów i pozyskanie na nie odpowiedzi realizują specjalne funkcje, które mogą być zdefiniowane przez użytkownika. Pozwala to w szczególności generować odpowiedzi na te zapytania przez wykorzystanie zaimplementowanego odrębnie symulatora automatu docelowego, co ułatwia automatyzację testowania algorytmu L*. Jednocześnie udostępnione zostały domyślne definicje tych funkcji, które zadają zapytania interaktywnie użytkownikowi.

Funkcja odpowiedzialna za zadawanie zapytań o przynależność otrzymuje jako argument pewien napis (czyli listę złożoną z symboli wejściowych). Odpowiedzią na to zapytanie powinien być symbol wyjściowy, jaki automat docelowy wygeneruje po przetworzeniu tego napisu (zaczynając w stanie początkowym). Domyślna wersja tej funkcji, zdefiniowana w pakiecie "AUTOMATA" pod nazwą default-mq, przedstawia zapytanie użytkownikowi i czyta jego odpowiedź z wejścia. Użytkownik powinien podać wówczas pewien symbol wyjściowy automatu (bez znaku cytowania ').

Argumentem funkcji zadającej zapytanie o równoważność jest automat, reprezentowany przez zdefiniowaną w pakiecie "AUTOMATA" strukturę służącą do tego celu. Szczegóły tej reprezentacji można poznać zaglądając do kodu źródłowego. Oczekiwany wynik tej funkcji jest listą, której pierwszy element stanowi pozytywna (czyli t) lub negatywna (czyli nil) odpowiedź na zapytanie, a drugi element -- który jest konieczny tylko w przypadku odpowiedzi negatywnej -- jest kontrprzykładem, mającym tak jak każdy napis postać listy symboli wejściowych. Domyślna wersja funkcji zadającej zapytania o równoważność, zdefiniowana w pakiecie "AUTOMATA" pod nazwą default-eq, kieruje je do użytkownika i czyta z wejścia jego odpowiedź. Użytkownik powinien wprowadzić listę podanej postaci, z pominięciem znaku cytowania '.

Generowanie hipotez

Do inicjalizacji procesu uczenia się automatu za pomocą algorytmu L* służy funkcja learn. Można jej przekazać następujące argumenty kluczowe:

W wyniku wywołania funkcji learn wykonywane są kolejne iteracje algorytmu L*, przy czym zadawane są niezbędne do tego zapytania. Po uzyskaniu pozytywnej odpowiedzi na zapytanie o równoważność funkcja kończy działanie i zwraca uzyskany automat.

Przykład użycia

Jako ilustrację sposobu korzystania z pakietu L* przedstawimy przebieg jego działania podczas uczenia się języka docelowego, którego napisy są złożone z parzystej liczby symboli 0 i 1. Dla większej czytelności tekst wprowadzany przez użytkownika w odpowiedzi na zapytania podany jest z wcięciem.


> (learn)
Membership query for NIL
    1
Membership query for (0)
    0
Membership query for (1)
    0
Membership query for (0 0)
    1
Membership query for (0 1)
    0

Equivalence query for 
A=(0 1)
Y=(0 1)
0: #(1 1) | 1
1: #(0 1) | 0

    (nil (1 1))

Membership query for (1 1)
    1
Membership query for (1 1 0)
    0
Membership query for (1 1 1)
    0
Membership query for (1 0)
    0
Membership query for (1 0 0)
    0
Membership query for (1 1 1 0)
    0
Membership query for (1 1 0 0)
    1
Membership query for (0 1 0)
    0
Membership query for (0 0 0)
    0

Equivalence query for 
A=(0 1)
Y=(0 1)
0: #(2 1) | 1
1: #(1 0) | 0
2: #(0 1) | 0

    (nil (0 1 1))

Membership query for (0 1 1 0)
    1
Membership query for (0 1 1)
    0
Membership query for (0 1 1 0 0)
    0
Membership query for (0 1 1 1 0)
    0
Membership query for (0 1 1 1)
    0
Membership query for (0 1 0 0)
    0
Membership query for (0 1 0 1)
    1
Membership query for (0 1 1 1 1)
    0
Membership query for (0 1 1 0 1)
    0
Membership query for (1 0 1)
    0
Membership query for (1 1 1 1)
    1
Membership query for (1 1 0 1)
    0
Membership query for (0 0 1)
    0

Equivalence query for 
A=(0 1)
Y=(0 1)
0: #(2 3) | 1
1: #(3 2) | 0
2: #(0 1) | 0
3: #(1 0) | 0

    (t)

A=(0 1)
Y=(0 1)
0: #(2 3) | 1
1: #(3 2) | 0
2: #(0 1) | 0
3: #(1 0) | 0
 

Publiczne interfejsy pakietów

Zgodnie z przyjętą w języku Lisp konwencją nazwy pakietów zapisywane są wielkimi literami i ujmowane w cydzysłowy. Dla każdego pakietu podamy listę zdefiniowanych w nim publicznych zmiennych (jeśli występują) i funkcji, wraz z krótką charakterystyką. Sposób posługiwania się najważniejszymi z nich będzie dokładniej omówiony oddzielnie.

Pakiet "UTIL"

Funkcje udostępniane przez ten pakiet wykorzystywane są w większości pozostałych pakietów. Nie wiążą się one w ścisły sposób z algorytmami uczenia się, lecz mają charakter pewnych pomocniczych operacji ogólnego przeznaczenia.

Pakiet "INFO"

W tym pakiecie zdefiniowane są funkcje obliczające wartości teorioinformacyjnych heurystyk używanych przy indukcji drzew decyzyjnych oraz indukcji reguł za pomocą algorytmu CN2. Obliczane wartości informacji i entropii odnoszą się do rozkładu kategorii przykładów.

Pakiet "CONCEPT"

Funkcje udostępniane przez ten pakiet wykorzystywane są przez wszystkie pakiety implementujące algorytmy uczenia się pojęć, a także pakiet "INFO", poświęcony implementacji grupowania pojęciowego pakiet "CLUSTER", pakiety "MISSING" i "DISCRETIZE" zajmujące się przetwarzaniem zbiorów trenujących i pakiet "TESTER", który ułatwia testowanie algorytmów indukcji.

Pakiet "TDIDT"

Pakiet ten zawiera implementację indukcji drzew decyzyjnych za pomocą algorytmu ID3. Pozwala on na generowanie drzewa na podstawie zbioru przykładów oraz używanie go do klasyfikacji nowych przykładów.

Pakiet "COVER"

Pakiet ten zawiera implementację indukcji reguł za pomocą algorytmów AQ i CN2. Pozwala on na generowanie zbioru reguł (nieuporządkowanego dla algorytmu AQ oraz uporządkowanego dla algorytmu CN2) na podstawie zbioru przykładów oraz używanie go do klasyfikacji nowych przykładów. Oba algorytmy są zaimplementowane zgodnie z ich opisem w książce. Szczegóły techniczne można poznać przeglądając kod źródłowy pakietu.

Pakiet "BAYES"

Funkcje tego pakietu zajmują się szacowaniem na podstawie dostarczonych przykładów prawdopodobieństw używanych przez naiwny klasyfikator bayesowski oraz wykorzystywaniem ich do klasyfikowania nowych przykładów.

Pakiet "CLUSTER"

Pakiet ten implementuje grupowanie pojęciowe za pomocą algorytmu COBWEB. Pozwala na zbudowanie drzewa grupowania na podstawie zbioru przykładów, a także na traktowanie go (jeśli było generowane na podstawie przykładów etykietowanych) jako hipotezy dla uczenia się pojęć, pozwalającej na klasyfikowanie nowych przykładów.

  • learn (instances) -- funkcja; zwraca drzewo grupowania zbudowane na podstawie sekwencji przykładów instances za pomocą algorytmu COBWEB.
  • classify (ctree i) -- funkcja; zwraca kategorię wyznaczoną dla przykładu i za pomocą drzewa grupowania ctree; użycie tej funkcji ma sens tylko, jeśli drzewo było budowane na podstawie zbioru przykładów etykietowanych, kiedy przykłady klasyfikuje się do większościowych kategorii dla węzłów będących ich najlepszymi pojemnikami na poziomie 1 drzewa grupowania.
  • hypothesis-size (tree) -- funkcja; zwraca rozmiar drzewa grupowania ctree: liczbę wszystkich węzłów jako pierwszą wartość i liczbę węzłów poziomu 1 jako drugą wartość.

    Pakiet "MISSING"

    Jest to pakiet udostępniający funkcje do eliminowania przykładów z brakującymi wartościami atrybutów oraz do wypełniania tych brakujących wartości zgodnie z prostymi algorytmami.

    • missing-eliminate (instances) -- funkcja; zwraca kopię sekwencji przykładów instances z pominięciem tych, dla których występuje co najmniej jedna brakująca wartość atrybutu.
    • missing-fill (instances &key algorithm) -- funkcja; zwraca sekwencję przykładów instances zmodyfikowaną przez wypełnienie wszystkich brakujących wartości atrybutów zgodnie z algorytmem określonym przez parametr algorithm: most-common oznacza wypełnianie najczęściej występującymi wartościami, category-most-common oznacza wypełnianie najczęściej występującymi wartościami w ramach kategorii poszczególnych przykładów.

    Pakiet "DISCRETIZE"

    W pakiecie tym zdefiniowano funkcje realizujące dyskretyzację atrybutów ciągłych i odpowiednie przekształcanie zbioru przykładów. Zaimplementowane są prymitywne metody dyskretyzacji według równej szerokości i równej częstości oraz algorytm ChiMerge.

    • *chi-theta* -- zmienna (parametr); określa próg statystycznej istotności dla łączenia przedziałów przez algorytm ChiMerge.
    • attributes-discretize (instances &key intervals extend algorithm) -- funkcja; zwraca sekwencję opisów atrybutów zmodyfikowanych przez dyskretyzację wszystkich atrybutów ciągłych na podstawie sekwencji przykładów instances za pomocą algorytmu określonego przez parametr algorithm: discretize:equal-width (według równej szerokości), discretize:equal-frequency (według równej częstości), discretize:chi-merge (algorytm ChiMerge); parametr intervals określa minimalną liczbę tworzonych przedziałów; wartość t parametru extend oznacza, że tworzony zbiór przedziałów dyskretyzacji powinien być rozszerzony o dwa specjalne przedziały dla wszystkich wartości mniejszych i większych niż odpowiednio minimalna i maksymalna wartość dyskretyzowanego atrybutu; powstające wartości dyskretne mają postać symboli tworzonych z napisów "v1:v2", gdzie v1 i v2 są końcami przedziału; sekwencja przykładów instances nie jest w żaden sposób modyfikowana.
    • instances-discretize (instances) -- funkcja; zwraca zmodyfikowaną sekwencję przykładów instances, w której wszystkie ciągłe wartości atrybutów są zastąpione przez odpowiednie wartości dyskretne; wymagane jest wcześniejsze wywołanie funkcji attributes-discretize.

    Pakiet "TESTER"

    Głównym celem tego pakietu jest udostępnienie wspólnego interfejsu dla pakietów "TDIDT", "COVER", "BAYES", "CLUSTER". Eliminuje on potrzebę bezpośredniego odwoływania się do ich funkcji, gdyż są one wszystkie dostępne za pośrednictwem funkcji pakietu "TESTER", którym wystarczy wskazać, którego z tych pakietów należy użyć do generowania hipotezy lub klasyfikowania przykładów. Oprócz tych dwóch operacji, dostępnych w każdym z tych pakietów, pakiet "TESTER" dodatkowo umożliwia testowanie hipotez (wyznaczanie błędu próbki) oraz testowanie algorytmów metodą walidacji krzyżowej i k-krotnej walidacji krzyżowej. Dzięki funkcjom tego pakietu przeprowadzanie takich testów może zostać w znacznym stopniu zautomatyzowane.

    • train (instances &rest options &key attributes categories package) -- funkcja; zwraca hipotezę otrzymaną przez wybrany algorytm uczenia się na podstawie dostarczonego zbioru przykładów instances; powoduje wywołanie funkcji concept-setup z pakietu "CONCEPT" z argumentami attributes i categories, a następnie funkcji learn z pakietu określonego przez parametr package z argumentami instances (zbiór trenujący) i options (ewentualne dodatkowe argumenty zależne od wybranego pakietu); zwracany jest wynik otrzymany z wywołania funkcji learn.
    • classify (hypothesis i &rest options &key attributes categories package) -- funkcja; zwraca kategorię przykładu i wyznaczoną za pomocą hipotezy hypothesis wybranego algorytmu uczenia się; pozostałe parametry mają interpretację taką jak dla funkcji train; zwracany wynik uzyskiwany jest przez wywołanie funkcji classify z pakietu określonego przez parametr package z argumentami hypothesis, i oraz options.
    • hypothesis-size (hypothesis &rest options &key attributes categories package) -- funkcja; zwraca rozmiar hipotezy hypothesis dla wybranego algorytmu uczenia się; pozostałe parametry mają interpretację taką jak dla funkcji train; zwracany wynik uzyskiwany jest przez wywołanie funkcji hypothesis-size z pakietu określonego przez parametr package z argumentami hypothesis i options.
    • test (hypothesis instances &rest options) -- funkcja; zwraca błąd próbki hipotezy hypothesis na zbiorze przykładów instances; do wyznaczania etykiet przykładów używana jest funkcja classify i jako parametr options muszą być przekazane odpowiednie dla niej argumenty.
    • cross-validate (instances r &rest options) -- funkcja; przeprowadza walidację krzyżową na podstawie zbioru przykładów instances; losowo wybrany podzbiór zbioru przykładów instances o rozmiarze stanowiącym określony przez parametr r ułamek jego całkowitego rozmiaru jest wybierany jako zbiór trenujący, na podstawie którego generowana jest hipoteza przez wywołanie funkcji train, następnie testowana dla pozostałych przykładów przez wywołanie funkcji test; jako parametr options muszą być przekazane odpowiednie argumenty dla funkcji train i test; zwracany jest błąd próbki uzyskanej hipotezy na zbiorze testującym jako pierwsza wartość, jej błąd próbki na zbiorze trenującym jako druga wartość i jej rozmiar wyznaczony za pomocą funkcji hypothesis-size jako trzecia wartość (jest ona listą wartości zwracanych przez hypothesis-size).
    • k-fold-cross-validate (instances k &rest options) -- funkcja; przeprowadza k-krotną walidację krzyżową na podstawie zbioru przykładów instances; zbiór przykładów dzielony jest losowo na k podzbiorów, a następnie każdy z nich jest kolejno używany do testowania za pomocą funkcji test hipotezy uzyskanej za pomocą funkcji train na podstawie zbioru trenującego otrzymanego przez połączenie pozostałych podzbiorów; jako parametr options muszą być przekazane odpowiednie argumenty dla funkcji train i test; zwracany jest średni błąd próbki na zbiorze testującym jako pierwsza wartość oraz lista złożona z list zawierających błędy próbki uzyskane dla wszystkich zbiorów testujących i odpowiednie rozmiary hipotez (będące listami wartości zwracanych przez hypothesis-size).
    • discretize-attributes (instances &rest options &key attributes categories) -- funkcja; powoduje wywołanie funkcji concept-setup z pakietu "CONCEPT" z argumentami attributes i categories, a następnie funkcji attributes-discretize z pakietu "DISCRETIZE" z argumentami instances (zbiór trenujący) i options (dodatkowe argumenty tej funkcji); zwracany jest wynik otrzymany z z wywołania funkcji attributes-discretize, czyli zmodyfikowane przez dyskretyzację opisy atrybutów.
    • discretize-instances (instances &rest options &key attributes categories) -- funkcja; powoduje wywołanie funkcji concept-setup z pakietu "CONCEPT" z argumentami attributes i categories, a następnie funkcji instances-discretize z pakietu "DISCRETIZE" z argumentami instances (zbiór trenujący) i options (dodatkowe argumenty tej funkcji); zwracany jest wynik otrzymany z z wywołania funkcji instances-discretize, czyli zmodyfikowany przez dyskretyzację zbiór przykładów instances.
    • discretize-all (instances &rest options &key attributes categories) -- funkcja; powoduje wywołanie funkcji concept-setup z pakietu "CONCEPT" z argumentami attributes i categories, a następnie kolejno funkcji attributes-discretize i instances-discretize z pakietu "DISCRETIZE" z argumentami instances (zbiór trenujący) i options (dodatkowe argumenty tych funkcji); zwracany jest wynik otrzymany z z wywołania funkcji instances-discretize, czyli zmodyfikowany przez dyskretyzację zbiór przykładów instances.
    • missing-eliminate (instances &rest options &key attributes categories) -- funkcja; powoduje wywołanie funkcji concept-setup z pakietu "CONCEPT" z argumentami attributes i categories, a następnie funkcji missing-eliminate z pakietu "MISSING" z argumentami instances i options (dodatkowe argumenty tej funkcji); zwracany jest wynik tego wywołania, czyli zbiór przykładów stanowiący kopię instances z pominięciem przykładów z brakującymi wartościami atrybutów.
    • missing-fill (instances &rest options &key attributes categories) -- funkcja; powoduje wywołanie funkcji concept-setup z pakietu "CONCEPT" z argumentami attributes i categories, a następnie funkcji missing-fill z pakietu "MISSING" z argumentami instances i options (dodatkowe argumenty tej funkcji); zwracany jest wynik tego wywołania, czyli zbiór przykładów instances zmodyfikowany przez wypełnienie brakujących wartości atrybutów.
    • experiment (testfn testargs algorithms &rest options) -- funkcja; wykonuje eksperyment polegający na wywołaniu funkcji określonej przez parametr testfn (jedna z funkcji train, cross-validate, k-fold-cross-validate) z argumentami z listy testargs dla każdego z algorytmów z listy algorithms, której każdy element może być albo nazwą pakietu (ewentualnie symbolem traktowanym jako nazwa pakietu), albo listą, której pierwszy element jest traktowany jako nazwa pakietu, a pozostałe stanowią dodatkowe argumenty przekazywane do wywoływanej dla niego funkcji; zwracana jest lista asocjacyjna, w której każdy algorytm związany jest z uzyskanym dla niego wynikiem wywołania funkcji testfn.

    Pakiet "AUTOMATA"

    W tym pakiecie zaimplementowane zostało uczenie się automatów skończonych za pomocą algorytmu L*. Domyślne wersje funkcji zadających zapytania o przynależność i równoważność, które kierują je do użytkownika i czytają jego odpowiedź z wejścia, mogą zostać zastąpione innymi wersjami tych funkcji.

    • learn (&key mq eq inalpha outalpha) -- funkcja; zwraca automat skończony uzyskany przez algorytm L* na podstawie zapytań o przynależność zadawanych przez wywołanie funkcji mq i zapytań o równoważnośc zadawanych przez wywołanie funkcji eq; argumentem obu tych funkcji jest pewien napis s reprezentowany przez listę złożoną z symboli alfabetu wejściowego; funkcja mq musi zwracać pewien symbol wyjściowy (wyjście automatu docelowego po przetworzeniu napisu przekazanego jako argument); funkcja eq musi zwracać listę, której pierwszym argumentem jest wartość logiczna określająca równoważność (prawda: t albo fałsz: nil), a drugim w przypadku braku równoważności jest kontrprzykład, czyli lista złożona z symboli alfabetu wejściowego; obie funkcje realizujące zapytania mają wersje domyślne, które kierują te zapytania do użytkownika i wczytują z wejścia jego odpowiedzi; parametry inalpha i outalpha są listami symboli wejściowych i wyjściowych, w obu przypadkach domyślnie '(0 1).