Co jest nie tak z libcorpus?

  1. Tagset KIPI zapisany na sztywno za pomocą #define. Definicje są do tego redundantne (kilkadziesiąt kopii prawie identycznego kodu), przez co jakakolwiek zmiana niesie ryzyko wprowadzenia wielu błędów. Te definicje są odzwierciedlone w podobnej strukturze całego zoo funkcji w klasie CTag
    http://nlp.pwr.wroc.pl/trac/takipi/browser/trunk/Corpus/Corpus/typedefs.h
    http://nlp.pwr.wroc.pl/trac/takipi/browser/trunk/Corpus/Corpus/Tag.cpp
    1. Utrudnia to przeprowadzenie eksperymentów z uczeniem i testowaniem tagera na tagsetach pośrednich (wymaga tworzenia wariantów tagera uzależnionych od tagsetu)
    2. Prawie wszystkie bity są już wykorzystane, więc w razie potrzeby dodania kilku nowych atrybutów zachodzi potrzeba zmiany całej reprezentacji danych.
    3. Jeśli ktoś będzie chciał kupić nasz tager do celów komercyjnych, będziemy musieli przeznaczyć spore środki na zmianę tagsetu. Istenieją tagery (np. TreeTagger, RFTagger, ten nowy warszawski -- Pantera), które tego nie wymagają.
  2. libcorpus nie wspiera unikodu, a struktury danych nie narzucają żadnego konkretnego kodowania znaków.
    1. W kodzie tagera obiekty CToken występują raz w kodowaniu CP1250, raz w UTF8. BB: to jest błąd, który powinien być już dawno naprawiony wewnątrz biblioteki
    2. Sentencer i tokeniser wymagają kodowania CP1250, przez co tager jako całość psuje znaki spoza CP1250. BB: to jest błąd, który powinien być już dawno naprawiony wewnątrz biblioteki
    3. Implementacje operatorów JOSKIPI związanych z wielkością liter zawierają trudne do wyeliminowania błędy. BB: Naprawdę? Przecież utf8 jest standardem, więc po poprawieniu dwóch powyższych błędów ten problem powinien sam zniknąć...
  3. Podstawowe struktury danych (Token, Lexem, Tag) opierają swoje działanie na egzotycznej implementacji licznika referencji.
    1. Korzystanie z tych struktur wymaga w niektórych sytuacjach ręcznego zmniejszanie/zwiększanie liczników. BB: poniżej opisałem jak to poprawić (w skrócie: metoda modyfikująca liczniki -> metoda pusta -> brak metody)

BB: tak naprawdę to jedynie 1 jest jedynym sensownym wytłumaczeniem (chociaż na upartego pliki z defines można generować dynamicznie a i liczba bitów nie jest ograniczona, bo można użyć większego typu, albo nawet typu nieograniczonego). Resztę można naprawić w ramach istniejącej biblioteki (np. wszędzie kodowanie utf8, zrobić porządne parsowanie gdzie indziej).
AR: masz rację co do parsera XML, te trefne konstruktory można wywalić (mała szansa, by ktoś korzystał z tej części API) i podpiąć wtedy parser. Wyrzuciłem ten punkt.
AR: wprowadzenie UTF-8 nie zmieniając typu danych (std::string na ICU::UnicodeString) wymaga karkołomnych zabiegów, by tokenizator i guesser mogły czytać znak po znaku. To też kwestia efektywności.
BB: nie wymaga karkołomnych zabiegów, kwestia tylko rozsądnego podejścia do kompatybilności wstecz. Wewnętrznie możesz trzymać to jak chcesz, na zewnątrz udostępniasz stare interface zgodne z tym co jest (+ nowe dla nowego kodu). To jest przecież podstawowa zasada programowania obiektowego!
BB: teraz dopiero mnie to uderzyło: a dlaczego supermatrix czy estratto miałoby korzystać z tokenizatora lub guessera (a nie tylko z wyników ich działania)? Teraz o ile mi wiadomo to żadne kody używające libcorpus nie dotykają ani tokenizatora ani guessera.
AR: liczniki referencji w CLexem definiują (dziwaczne) zachowanie obiektów. Reszta kodu jest teraz kompatybilna z tym zachowaniem. Np. usuwanie części leksemów w tokenie wymaga żognlowania liczbikami - jak w http://nlp.pwr.wroc.pl/trac/takipi/browser/trunk/JOSKIPI/JOSKIPI/Rules/Action.cpp
BB: a Action to jest sprawa wewnętrzna JOSKIPI i libcorpus (które są pod waszym nadzorem, więc możecie w środku wymusić to co chcecie). Liczniki referencji nie powinny być dotykane przez zewnętrzne rzeczy wprost (to trzeba i tak popawić - najpierw można wprowadzić metodę, która będzie modyfikować zawartość licznika referencji, później ta metoda nie będzie nic robić, a po dłuższym czasie się ją usunie).
AR: a co do dynamicznego generowania plików z #define, to poza karkołomnością tego zabiegu, możemy wtedy operować tylko na jednym tagsecie na raz.
BB: nie będę komentować "karkołomności", bo wiele rzeczy się dynamicznie generuje (nawet u nas, np. parser wyrażeń joskipi, wrappery do pythona). A operowanie na wielu tagsetach na raz: to jest dla mnie dopiero karkołomne z punktu widzenia np. supermatrix.
BB: Inna sprawa, że jak będzie tagset zewnętrzny i dynamiczny, to się pojawi mnóstwo miejsc w kodzie, które będą bardzo podatne na błędy. Np. zamiast porównania dwóch wartości binarnych o zdefiniowanych typach (kompilator to sprawdzi!), to będę musiał porównywać napisy, których mi już nic nie sprawdzi (dopiero np. po 5 godzinach przetwarzania się coś wywali). A jak ma już być maksymalnie dynamiczne, to lepiej to zrobić w pythonie a nie w c :P

Koncepcja zgodności SuperMatrix z libcorpus i macą

  1. Na obecnym etapie nie ma potrzeby przechodzenia z libcorpus na Macę,
  2. Gdy jasno ustalimy wymagania co do nowego JOSKIPI i zaimplementujemy to, sytuacja się zmieni. Część propozycji rozszerzenia i zmian dotyczyła SuperMatrix. W związku z tym i tak zajdzie potrzeba zmiany fragmentów kodu, które korzystają z JOSKIPI, by skorzystać z nowej funkcjonoalności. BB: w większości moje propozycje zmian wymagałyby zmian tylko w kodzie JOSKIPI; jedna z propozycji jedynie by wymagała zmian w kodzie - wywalenia części kodu
  3. W ramach planowanych rozszerzeń JOSKIPI planowaliśmy zmiany w strukturach danych, m.in. wprowadzenie anotacji zdań (pod kątem jednostek wielosłowowych, ekstrakcji informacji i chunkera). W związku z tym konieczne będą przynajmniej minimalne zmiany w kodzie SuperMatrix pod kątem kompatybilności z nowym JOSKIPI. BB: można zachować kompatybilność wstecz - i zmiany znowu ograniczą się do JOSKIPI
  4. SuperMatrix ma mnóstwo odwołań do struktur danych (Corpus::CToken i Corpus::CSentence), natomiast stosunkowo niewiele odwołań do ich czytników (CorpusSentenceReader itp.). Są tu dwie możliwości:
    1. Dodanie do Macy kodu udającego struktury CToken i CSentence -- pozbędziemy się dużej części "brudnego" kodu (np. licznik referencji w klasie CLexem, o który trzeba samemu dbać. Minus: dostosowanie może wymagać pewnych zmian w kodzie używającym tych struktur. Jeśli coś bezpośrednio pracuje bezpośrednio na dotychczasowej reprezentacji tagów, trzeba to będzie przepisać (coś poza JOSKIPI).
    2. Korzystanie z oryginalnych struktur CToken i CSentence -- dodajemy do Macy moduł kompatybilności. Problem: w kodzie będą musiały współistnieć stare struktury i nowe, bo nowe JOSKIPI nie będzie kompatybilne ze starym interfejsem. Stare struktury będą używane przez większość kodu, a nowe na wyjściu z JOSKIPI będą konwertowane na stare.
    3. BB: jest też trzecia opcja: przepisanie kodu tworzącego macierze, ale to zajmie sporo czasu, chociaż pozwoli rozwiązać parę problemów lepiej; i podejrzewam, że i tak odejście od libcorpus się do przepisania tego sprowadzi :(

BB: inna sprawa, że jeszcze inne rzeczy korzystają z libcorpus (estratto, moje kody związane z wydobywaniem cech na potrzeby WSD, pewnie kilka innych rzeczy), więc do powyższych uwag pewnie kilka rzeczy wyjdzie "w praniu".

BB: a dlaczego nie zrobić nowych struktur danych, które będą implementować stare interface?
AR: problemy opisane w poprzednim punkcie mają swoje źródło w interfejsie biblioteki Corpus. Tag to struktura z dwóch DLONG-ów. Orth i base to std::string zaindeksowane w jednej globalnej instancji słownika WordDict, który zawiera słowa w różnych kodowaniach (swoją drogą nie pozwala to na zrównoleglenie). Jeśli po prostu usuniemy liczniki referencji z CLexem, to dostaniemy albo błąd ochrony pamięci (podwójne delete), albo wycieki pamięci (a może jedno i drugie).
BB: Będąc użytkownikiem libcorpus (a w przyszłości libmacy) nie interesuje mnie co jest wewnątrz danej klasy. Z punktu widzenia OOP Tag to struktura dostarczająca dwa typy, które mają określony interface (np. można je porównać). Problem WordDict jest poważny i tak naprawdę nie wiem jak go rozwiązać. Indeksowanie jest konieczne, bo porównywanie stringów jest bardzo wolne, a trzymanie wielu kopii stringa jest kosztowne. Nikt nie mówi o tym, żeby usunąć liczniki referencji bezmyślnie. Jak się do tego sensownie podejdzie, to błędów ochrony pamięci i wycieków nie musi być - kwestia użycia wewnątrz biblioteki dynamicznych wskaźników. Pracy i tak będzie sporo (obojętnie czy to przy naprawianiu libcorpus czy robieniu nowego libcorpus), więc po co jeszcze dodawać dodatkowej pracy użytkownikom libcorpus?

BB: Nota bene, kody czytające korpus nie powinny być zależne od analizatorów morfologicznych (czyli libmaca o ile dobrze rozumiem jej funkcjonalność), bo kiedyś będziemy chcieli mieć nową bibliotekę i znowu wrócimy do problemu poruszanego w tym dokumencie. Libmaca i inne rzeczy powinny do czytania korpusu mieć wspólną bibliotekę (libcorpus.so.2)

BB: Inna sprawa, że ta dyskusja teraz jest bez sensu. Trzeba to było zrobić przed implementacją MACA. Teraz trzeba tylko zrobić tak, żeby zminimalizować szkody. Libmaca istnieje w takiej postaci jak jest i głupotą będzie cofanie się wstecz o dwa kroki i robienie tego od nowa. Wydaje mi się, że trzeba zrobić bibliotekę pośrednią, która będzie udawać libcorpus. Może i narzut czasu wykonania będzie duży, ale tak zazwyczaj jest jak się naprawia rzeczy post factum. W ostateczności jest też drugie wyjście: libcorpus zostaje taki jak jest; z libmacy wydzielacie libcorpus2 (usunięcie rzeczy specyficznych dla analizy morfologicznej) i jak powstanie nowe nowe joskipi niekompatybilne z libcorpus (1 i 2) to wtedy trzeba będzie napisać nowy kod do rozproszonego tworzenia macierzy. (A zakładając, że będą wrappery Pythonowe do tego, to można będzie to stosunkowo niedużym kosztem zrobić w Pythonie)