Solvro Talk - Jak działają współczesne modele językowe
Wprowadzenie
W ostatnim czasie duże modele językowe (ang. Large Language Models, LLMs) zyskują coraz większą popularność. Przyczyną tego zjawiska niewątpliwie jest sukces narzędzi takich jak ChatGPT. Pomimo niewątpliwego sukcesu takich rozwiązań nadal są to tylko modele bardzo duże modele matematyczne, za których działaniem kryje się przede umiejętne wykorzystanie statystyki. W tym wpisie postaram się przybliżyć ich działanie i pomóc w ich zrozumieniu. Zapraszam.
Sztuczna inteligencja, uczenie maszynowe i uczenie głębokie
Wpis ten zacznę od samych podstaw czyli od określenia czym w ogóle jest sztuczna inteligencja. Jest to dziedzina informatyki obejmująca algorytmy, których działanie niejako imituje ludzką inteligencję. Zaliczamy do nich algorytmy genetyczne, systemy ekspertowe, metaheurystyki, uczenie maszynowe itp. To właśnie uczeniu maszynowemu zawdzięczamy innowacje, które mają miejsce w ostatnich latach. Działanie algorytmów zaliczanych do tej kategorii bazuje przede wszystkim na szukaniu i wykorzystywaniu schematów występujących w zbiorach danych, na których uczony jest nasz model. Sieci neuronowe, na których bazują współczesne modele językowe, mają swoją oddzielną kategorię zwaną uczeniem głębokim.
Czym są sieci neuronowe
Najprostsze sieci neuronowe zwane jednokierunkowymi to nic innego jak transformacje liniowe wektorów. Przedstawienie graficzne pojedynczej warstwy wygląda następująco
Matematycznie możemy to zapisać jako
Gdzie W i b to odpowiednio macierz oraz wektor parametrów. () to tzw. funkcja aktywacji, ale o niej później, na ten moment załóżmy, że (x)=x. Jak widać warstwa sieci neuronowej to najzwyklejsze pomnożenie wektora wejściowego przez jakąś macierz, a następnie dodanie do uzyskanego wyniku kolejnego wektora. Działanie pojedynczej warstwy nie jest zbyt imponujące, za to złożenie ze sobą kilku z nich ma pewną bardzo ciekawą właściwość co zostanie pokazane później.
Przykład: regresja liniowa
Załóżmy, że mamy jakiś zbiór punktów X, rozkładają się one w następujący sposób
Naszym celem jest znalezienie parametrów W oraz b, które najlepiej dopasują się do danych, wizualnie wygląda to w ten sposób
W naszym dwuwymiarowym przykładzie parametry W i b to zwyczajne skalary, dopiero w modelach o większej liczbie wymiarów potrzebujemy macierzy i wektorów parametrów. Poszukiwanie najlepszych parametrów modelu zaczynamy od losowego zainicjowania wag W i b. Są one następnie dopasowywane do naszych, co nazywamy uczeniem modelu. Jak w takim razie znaleźć odpowiednie parametry czyli wytrenować ową sieć neuronową? Dawniej służył do tego algorytm zwany Stochastycznym spadkiem wzdłuż gradientu. Współcześnie wykorzystuje się jego modyfikacje takie jak Adam oraz AdamW. Dokładny opis ich działania wykracza poza zakres tego wpisu.
Powyższy przykład jest oczywiście skrajnie uproszczony. Dodatkowo istnieje rozwiązanie tego problemu w postaci analitycznej, więc stosowanie algorytmów gradientowych dających tylko przybliżone rozwiązanie nie jest konieczne.
Przykład 2: regresja wielomianowa
Teraz weźmy pod uwagę nieco bardziej skomplikowany przykład, taki w którym mamy do czynienia z zależnościami nieliniowymi:
Mamy tutaj wielomian trzeciego stopnia. Spróbujmy znaleźć jego aproksymację wykorzystując sieć neuronową. W poprzednim przykładzie korzystaliśmy z pojedynczej warstwy liniowej, a funkcja aktywacji była funkcją identycznościową, czyli szukaliśmy po prostu równania prostej. W tym przypadku nasz model wymaga większej złożoności. Potrzebujemy dodatkowej warstwy sieci neuronowej zwanej warstwą ukrytą, matematycznie wygląda to następująco
W momencie, w którym dokładamy kolejne warstwy sieci neuronowych widać dlaczego funkcje aktywacji są niezbędne to ich poprawnego działania. Już tłumaczę: pojedyncza warstwa sieci jest transformacją liniową. Złożenie takich transformacji liniowych można zastąpić pojedynczą, równoważną transformacją, co sprawia, że te dodatkowe warstwy nie mają sensu. Weźmy na przykład funkcje f(x)=x+5 oraz g(x)=x+3. Ich złożenie f(g(x))=x+3+5 możemy zwyczajnie zastąpić funkcją h(x)=x+8. Tak samo dzieje się w sieciach neuronowych gdy nie stosujemy funkcji aktywacji. Współcześnie najpopularniejszą z nich aktywacji jest ReLU (ang. rectified linear unit)
Lub matematycznie
Zadaniem funkcji aktywacji jest wprowadzenie nieliniowości między poszczególnymi warstwami sieci. Dzięki temu uzyskujemy model zdolny do aproksymowania dowolnej zależności funkcyjnej. Wystarczy, że użyta sieć neuronowa będzie dostatecznie głęboka. Jak widać na poniższym obrazku uzyskana aproksymacja nie różni się znacząco od prawdziwej funkcji
Warto tutaj dodać, że sieć o identycznej architekturze, ale o innych wartościach parametrów jest w stanie przyjąć zupełnie inny kształt
Jak widać sieć neuronowa to po prostu model zdolny do znajdowania schematów w zbiorach danych. Zastosowanie odpowiednio dużej warstwy ukrytej sprawia, że jest ona w stanie dostosować się do dowolnej zależności funkcyjnej, a więc dopasować się do dowolnego zbioru danych. Współczesne architektury składają się często z wielu warstw ukrytych.
Rzeczywiste zastosowanie: klasyfikacja obrazu
Teraz przejdziemy do przykładowego praktycznego przykładu zastosowania takiej sieci. Załóżmy, że mamy do czynienia z dwoma klasami obiektów
Klasa 0
Klasa 1
Naszym zadaniem jest zbudowanie modelu, który przypisze podany obrazek do jednej z tych dwóch klas. W jaki sposób sieć neuronowa ma nam pomóc w tym zadaniu? Przypomnijmy sobie poprzednie podrozdziały. W każdej z prezentowanej sytuacji istniała pewna zależność między zbiorami punktów. Tak się składa, że powyższe obrazki również można przedstawić jako punkty występujące w przestrzeni wielowymiarowej. Taka reprezentacja ma pewną skuteczność, ale powoduje zgubienie zależności między poszczególnymi pikselami na obrazie. Z tego powodu powszechną procedurą jest ekstrakcja cech z obrazu. Rolę ekstraktora cech może przykładowo pełnić konwolucyjna sieć neuronowa. Przetworzony przez nią obraz może zostać sprowadzony do postaci wektora, na podstawie którego jednokierunkowa sieć neuronowa przypisuje obraz do danej kategorii. Stąd cała procedura wygląda w ten sposób:
Przepuszczenie obrazu przez ekstraktor cech pozwala wyodrębnić z niego informacje kluczowe dla rozwiązywanego zadania, w tym wypadku klasyfikacji obrazu. W idealnej sytuacji przykłady należące do poszczególnych klas są odseparowane jak w poniższym przykładzie:
W poprzednich przykładach sieć neuronowa została wykorzystana do oszacowania pewnej zależności funkcyjnej y=f(x). Tutaj aby rozwiązać problem klasyfikacji sieć neuronowa będzie nam zwracała na wyjściu 2 liczby. Często wyjście z takiej sieci jest przepuszczane na końcu przez funkcję softmax. Dzięki temu liczby zwrócone przez model możemy traktować jako prawdopodobieństwo tego, że dana obserwacja należy do danej kategorii. Obszar, w którym prawdopodobieństwo tego, że punkt należy do klasy 0 jest wyższa niż do klasy 1 jest na rysunku oznaczony na czerwono, natomiast tam gdzie wiarygodność, że przykład należy do klasy 0 jest mniejsza na niebiesko. Stąd bierze się widoczna na rysunku granica decyzyjna.
Sieci neuronowe w przetwarzaniu języka
Jak wcześniej wspomniano uniwersalna architektura sieci neuronowych sprawia, mogą również zostać wykorzystane do zadań związanych z językiem. Dla uproszczenia pomijamy tu proces zamiany słowa na wektor. Potraktujmy to jako czarną skrzynkę. Najprostszym zastosowaniem sieci neuronowej w przetwarzaniu języka jest przewidywanie kolejnego słowa w zdaniu. Na wejściu podajemy wektor reprezentujący jedno słowo np. dzień, a na wyjściu otrzymujemy wektor reprezentujący inne słowo np. dobry.
W ten sposób jesteśmy w stanie wygenerować sekwencję dowolnej długości:
Wygenerowana w ten sposób zapewne nie będzie przypominała prawdziwej, ludzkiej wypowiedzi. Każde kolejne słowo jest przewidywane jedynie na podstawie poprzedniego. Uniemożliwia to tak naprawdę generowanie sensownych oraz realistycznych tekstów. Do tego przydałaby się wiedza o wszystkich słowach które, do tej pory zostały wygenerowane. Na szczęście istnieje modyfikacja tradycyjnej sieci liniowej zwana rekurencyjną siecią neuronową.
Rekurencyjne sieci neuronowe
Rekurencyjna sieć neuronowa to modyfikacja tradycyjnej sieci jednokierunkowej. Rzeczą, która ją wyróżnia, jest występowanie tzw. stanu ukrytego, który można traktować jako pamięć sieci. Matematycznie warstwę rekurencyjną można zapisać jako:
Stan ukryty h sprawia, że kolejne wygenerowane słowo będzie zależało od wszystkich poprzednich słów w sekwencji, a nie tylko od ostatniego, co pozwala na generowanie bardziej realistycznych wypowiedzi. Wyjście z warstwy rekurencyjnej jest nowym stanem ukrytym.
Dzięki stanowi ukrytemu sieć rekurencyjna nie musi otrzymywać w każdej iteracji nowego wektora wejściowego, ponieważ może generować nowe elementy sekwencji na podstawie stanu ukrytego.
Rekurencyjne sieci neuronowe mają wiele zastosowań, my się skupimy na architekturach typu koder-dekoder. Rolą takiej architektury jest przekształcanie sekwencji wejściowej na jakąś inną sekwencję. Najprostszym przykładem jest zadanie tłumaczenia maszynowego, w którym na wejście wprowadzamy sekwencję w języku oryginalnym, a na wyjściu otrzymujemy jej tłumaczenie w języku docelowym.
Inne zastosowanie to odpowiadanie na pytania zadawane modelowi. Zgadza się, modele GPT bazują na architekturach typu koder-dekoder.
Problemy rekurencyjnych sieci neuronowych
Rekurencyjne sieci neuronowe mają szczególne trudności w modelowaniu długich sekwencji. Zawarte w stanie ukrytym informacje z początku sekwencji mają tendencję do jej stopniowego zanikania. Na przestrzeni lat powstawały różne modyfikacje mające zniwelować ten problem takie jak sieci LSTM oraz GRU. My jednak skupimy się na innym podejściu. Mianowicie na mechanizmach uwagi.
Mechanizmy uwagi
Mechanizmy uwagi są innowacją, która sprawiła, że modele językowe są dzisiaj tak efektywne. Stanowią one w pewnym stopniu odpowiedź na problem zanikającej pamięci. W modelu koder-dekoder tradycyjny RNN generuje sekwencję wyjściową jedynie na podstawie stanu ukrytego. Jak zostało wspomniane w poprzednim podpunkcie, wykorzystanie samego wektora stanu ukrytego nie jest najlepszą metodą zapamiętywania sekwencji, zwłaszcza podczas dekodowania, gdy model musi być świadomy tego jak wyglądała oryginalna sekwencja oraz tego co zostało do tej pory przetłumaczone. Tutaj z pomocą przychodzi mechanizm uwagi, który podaje do modelu ważoną sumę elementów oryginalnej sekwencji. Fragmenty oryginalnej sekwencji, które w danym momencie są dekodowane mają większa wagę. Stąd wzięła się nazwa mechanizm uwagi. Poza przypomnieniem oryginalnej sekwencji do modelu trafia również informacja o tym, na którym fragmencie powinien się teraz skupić.
Bahdanau, Dzmitry, Kyunghyun Cho, and Yoshua Bengio. "Neural machine translation by jointly learning to align and translate." arXiv preprint arXiv:1409.0473 (2014).
Mechanizmy uwagi okazały się na tyle skuteczne, że w 2017 opublikowana została praca pt. “Attention Is All You Need”. Jej autorzy zaproponowali nową architekturę zwaną Transformerem, która zamiast wykorzystania rekurencji opiera się jedynie na mechanizmach uwagi.
Transformer, czyli podstawa współczesnych modeli językowych
Transformer to architektura koder-dekoder, jednak w przeciwieństwie do rekurencyjnych sieci neuronowych nie przetwarza on poszczególnych elementów sekwencji po kolei. Zamiast tego cały ciąg wejściowy przetwarzany jest jednocześnie. Dzięki temu mamy pewność, że początkowe elementy sekwencji nie zostaną zapomniane przez model. Wadą tego rozwiązania jest natomiast ustalona z góry maksymalna długość sekwencji.
Wykorzystywany w Transformerach mechanizm samouwagi modeluje zależności między poszczególnymi słowami w sekwencji. Tak uzyskana reprezentacja pozwala na generowanie lepszych sekwencji docelowych.
Tutaj trzeba powiedzieć o jeszcze jednym problemie Transformerów. Wykorzystanie mechanizmu samoouwagi sprawia, że model nie ma pojęcia o tym w jakiej kolejności elementy w sekwencji faktycznie występowały. Z tego powodu wprowadzono tzw. kodowanie pozycyjne, które dostarcza do modelu informacje, o tym który w kolejności jest dany element sekwencji.
Warto też rozróżnić mechanizmy uwagi stosowane w obu opisywanych architekturach. RNN wykorzystuje powiązania stanu ukrytego z poszczególnymi elementami sekwencji wejściowej. W Transformerze mamy powiązania między poszczególnymi elementami sekwencji wejściowej, a nie stanu ukrytego. Z tego powodu nazywany jest on mechanizmem samouwagi. Schemat transformera jest przedstawiony na poniższym rysunku:
Vaswani, Ashish. "Attention is all you need." arXiv preprint arXiv:1706.03762 (2017)
Współczesne modele językowe są bardzo mocno inspirowane opisaną w tym podrozdziale architekturą. Rozwinięcie skrótu GPT oznacza Generative Pre-trained Transformer, czyli Generatywnie Pretrenowany Transformer. Ze względu na ich powszechne zastosowania warto znać podstawowy zamysł ich działania.