NLP w pigułce

namiekko.pl 7 lat temu

Mam koleżankę, która jest doświadczoną programistką Javy oraz Scali. Kilkanaście razy w roku występuje na międzynarodowych konferencjach. Z opisu łatwo wydedukować, ze na co dzień nie potrzebuje mojej ekspertyzy technicznej ? Jednak kilka dni temu poprosiła mnie o pilne wsparcie. Napisała, iż do tej pory traktowała NLP (przetwarzanie języka naturalnego, ang. natural language processing) jako „coś, z czego ludzie robią doktoraty”, a teraz musi sama poprawić kod z tej dziedziny.

Kod okazał się pluginem do Slacka, który pozwala programistom wydawać polecenia w języku naturalnym i w ten sposób zarządzać procesem budowania, testowania i wdrażania aplikacji. Chciała dopisać kilka poleceń, ale wystraszyły ją sformułowania takie jak „tokenizacja” czy „VP”, i inne, bezpośrednio związane z wykorzystywaną tam biblioteką OpenNLP. Poprosiła mnie o praktyczny kurs NLP w pigułce, opisujący kroki niezbędne do zrozumienia, o co użytkownikowi adekwatnie chodzi.

Oto on.

Pomijam kwestię rozpoznawania mowy, ponieważ niespecjalnie się na niej znam i nie ma ona znaczenia w przypadku pisania pluginów do slacka. Rozpoznawanie mowy to zagadnienie z dziedziny przetwarzania sygnałów, w tej chwili realizowane niemalże wyłącznie przy użyciu uczenia maszynowego.

Na początku jest zdanie.

Tak przynajmniej załóżmy. W rzeczywistości zdań może być kilka – wtedy trzeba ja jakoś rozdzielić, co w zależności od języka może sprowadzić się do cięcia po kropkach, albo być super skomplikowanym zadaniem wymagającym wnikania w składnię.

Nietrywialny przykład z języka polskiego:

Obiecał pomóc dr. Rogasiowi.

Poza tym nie wszystko jest zdaniem. Bezpieczniejszy jest termin wypowiedzenie (ang. utterance), który obejmuje również wszelkiego typu równoważniki i pojedyncze słowa.

Załóżmy, iż nasze zdanie to „wrzuć wersję 3.1 config na produkcję”.

Zdanie trzeba podzielić na tokeny.

Token to, w przybliżeniu, słowo. W niektórych językach tokenizacja jest prosta. W polskim możemy chcieć rozdzielić np. „jasnozielony” na dwa tokeny: „jasno” i „zielony”. W angielskim zamienić „wanna” na „want to”. Po hiszpańsku „pokaż mi to” pisze się łącznie „muéstramelo”, co, żeby móc przeprowadzić jakąkolwiek analizę, musimy rozbić na trzy tokeny: „muestra” „me” „lo” (i po drodze zgubić akcent).

Normalizacja.

Czasami razem z tokenizacją przeprowadza się normalizację, czyli np. zamienia słowa „jeden”, „jedna” itp. na „1”.

Wszystko to po to, żeby uprościć sobie życie i zmniejszyć rozmiary modelu, w oparciu o który analizujemy tekst.

Dalej musimy rozpoznać byty nazwane (ang. named entities).

Byty nazwane to, w uproszczeniu, byty reprezentowane przez nazwy własne – w odróżnieniu od rzeczowników pospolitych. Ich rozpoznanie nie zawsze jest łatwe. Często zależy od kontekstu. Nierzadko w wyniku takiej analizy dostajemy kilka hipotez. Przykładowo, „house” to po angielsku „dom”, ale może to być również nazwa serialu lub nazwisko jego głównego bohatera.

Po tym kroku analizy powinniśmy wiedzieć, która część naszego wypowiedzenia to byt nazwany, i jakiego typu jest to byt (miejsce, osoba, tytuł, …).

W naszym przykładzie bytem nazwanym może być „config” (jako nazwa aplikacji) oraz „produkcja” (jako nazwa środowiska).

Ujednolicenie form wyrazów: lematyzacja lub stemming.

Etap ten ma różne znaczenie w zależności od języka. Chodzi tu o sprowadzenie różnych form tego samego słowa do wersji uznawanej za podstawową.

  • Stemming to obcięcie wszelkiego rodzaju przedrostków i przyrostków, mające na celu dotarcie do nieodmiennego „rdzenia” reprezentującego wyraz. Sam rdzeń niekoniecznie musi być poprawnym słowem. Algorytm stemmera nie musi być zależny od języka.
  • Lematyzacja to sprowadzenie słowa do jego podstawowej postaci. W przypadku czasownika będzie do bezokolicznik, w przypadku rzeczownika – mianownik liczby pojedynczej. Do wykonania tego zadania potrzebny jest słownik lub rozbudowany zestaw reguł fleksyjnych dla danego języka.

Oczywiście zadanie to jest trudniejsze dla silnie fleksyjnych języków, jak polski.

Rozpoznawanie części mowy.

W końcu powinniśmy się dowiedzieć, z jakimi częściami mowy mamy do czynienia, najlepiej również w jakich odmianach. Z reguły informację te zwraca lematyzator. Tegu typu analiza może być oparta o słownik, ale może również wykorzystywać końcówki fleksyjne (np. jeżeli nieznane nam polskie słowo kończy się “zować”, można przyjąć hipotezę, iż to czasownik).

Wreszcie dochodzimy do parsowania.

Parsowanie to analiza składniowa, jednak w praktyce już na tym etapie często wydobywa się także informacje semantyczną, czyli znaczenie wypowiedzenia.

Zasadniczo, do znaczenia można dobrać się na jeden z trzech sposobów:

Wyrażenia regularne.

To najprostsza, najbardziej naiwna metoda, jednak w przypadku pluginów do slacka, które mają pozwalać na wykonanie kilku prostych operacji, nierzadko okazuje się wystarczająca.

Możemy napisać:

wdr(\S+) wersj. (\d\S*) (?:aplikacji )?(\S+) na (produkcję|prod|staging)

lub coś odrobinę bardziej skomplikowanego i już dostaniemy wszystkie potrzebne nam informacje.

Gramatyki formalne

Oparte na teorii Chomskiego, gramatyki (tworzone manualnie lub automatycznie) to zestawy reguł, które definiują, jak może wyglądać poprawne zdanie w danym języku (naturalnym, jak polski, albo sztucznym, jak C++).

Gramatyka może składać się np. z takich reguł:

NP -> ART N ART -> a ART -> the N -> cat N -> dog

Wówczas nasza grupa rzeczownikowa (NP) może mieć postać “a cat”, “the cat”, “a dog” i “the dog”.

Gramatykę można zapisać w postaci automatu skończonego.

Uczenie maszynowe

Efekt jest ostatecznie taki sam jak w przypadku gramatyk, tylko zamiast żmudnie definiowanych reguł wykorzystywane są wnioski wyciągnięte podczas trenowania systemu przy użyciu wybranej metody (np. CRF) na dużej ilości odpowiednio opisanego tekstu (na korpusie)

Reprezentacja danych semantycznych.

Ostatecznie powinniśmy otrzymać coś w rodzaju „ramy”, która często przypomina strukturę zdania. W naszym wypadku będzie to mniej więcej coś takiego:

akcja: wdrożyć aplikacja: config wersja: 3.1 środowisko: produkcja termin: null

Parsowanie płytkie i głębokie.

Głębokie parsowanie to pełne odtworzenie drzewa składniowego zdania. Parsowanie płytkie (czasami określane terminem chunking) ma na celu wydobycie istotnej informacji, bez potrzeby wyciągania wszystkich zależności składniowych pomiędzy słowami.

Analiza koreferencji.

Na początku napisałam, iż punktem wyjścia jest dla nas zdanie (bądź wypowiedzenie), ale w języku naturalnym zdania mogą być ze sobą powiązane w taki sposób, iż w celu zrozumienia zdania musimy cofnąć się o jedno lub więcej zdań wstecz.

W analizie koreferencji chodzi przede wszystkim o anaforę. jeżeli użytkownik wyda polecenie „wdróż to”, musimy zrozumieć, do jakiego wcześniej wspomnianego bytu się odnosi.

Przykład: OpenNLP

Istnieje trochę gotowych narzędzi i zasobów do uprawiania NLP: parsery, korpusy tekstów, treebanks (zdania rozbite na drzewa składniowe), biblioteki implementujące algorytmy uczenia maszynowego. Kod, przy którym pracowała Jessica, korzystał z javowej biblioteki OpenNLP, która oferuje trochę gotowych modeli językowych, a także umożliwia trenowanie własnych.

Co możemy od niej dostać?

Np. dla zdania “namiekko.pl is a the coolest site” otrzymamy:

(TOP (S (NP (NN namiekko.pl)) (VP (VBZ is) (NP (DT the) (JJS coolest)(NN site.)))))

Jak przełożyć to na nasze potrzeby? Musimy stworzyć jakieś mapowanie pomiędzy odnalezionymi grupami a naszą reprezentacją danych. Możemy napisać reguły takiego typu: jeżeli czasownik w VP (verb phrase) to „deploy” lub „push”, to chcemy wykonać naszą akcję deploy. Argumentów dla tej akcji należy szukać w następujących elementach drzewa składniowego (…)

Zasoby

Idź do oryginalnego materiału