Zamień ten tekst na URL Webhooka

Biznes i Automatyzacje

Fine-tuning GPT-3 bez kodu

Wydanie nr
8
opublikowane
2023-02-09
Adam Gospodarczyk
Adam Gospodarczyk
overment
No items found.

Od jakości zadanego pytania do GPT-3, w dużym stopniu zależy jakość otrzymanej odpowiedzi. Sprawa komplikuje się w przypadku złożonych zadań, wymagających precyzyjnych odpowiedzi. Bardzo szybko zapytanie (tzw. prompt) staje się rozbudowane. W połączeniu z kontekstem, dość łatwo dojść do limitu 4097 tokenów (łącznie dla pytania i odpowiedzi). Poza tym długość promptu, wpływa bezpośrednio na wysokie koszty. Wówczas do gry wchodzi dostosowanie modelu do swoich potrzeb — tzw. "Fine-tuning". Chociaż większość instrukcji w Internecie wymaga programowania w Pythonie lub Node.js, sam przeszedłem przez ten proces bez pisania kodu. I dziś pokażę go krok po kroku.


Na czym polegał mój problem?

Jak być może już wiesz, współpracuję z Avatarem AI o imieniu M.O.N.D.A.Y. Większość jej logiki opiera się aktualnie o rozwiązania no-code (Make, Airtable, Pinecone, Shortcuts). Całość zaprojektowana jest tak, że główna logika znajduje się w jednym scenariuszu. To wewnątrz niego dochodzi do określenia mojej intencji i podejmowania wszystkich pozostałych zadań.

No i właśnie określanie intencji jest zadaniem, którego realizacja szybko doprowadziła mnie do dość obszernego promptu. Mianowicie tylko na podstawie wprowadzonego tekstu (od jednego do kilku zdań), M.O.N.D.A.Y. musiała "zrozumieć" o co mi chodzi a następnie podjąć decyzję o kolejnych krokach.
Sama jest w stanie rozróżniać, kiedy pytam o moje notatki a kiedy o zgromadzone przez nią informacje. Co ciekawe, nie muszę wcale o tym wspominać. Po prostu bezpośrednie pytanie oznacza wiedzę ogólną a pytanie zawierające jakiekolwiek nawiązanie do mojej wiedzy, sugeruje moje notatki. Np. "Czym jest Meaningness?" to pytanie o zagadnienie z książki którą przeczytała. Natomiast pytania "Co wiem o Inwersji?" lub "Czy mówiłem Ci kiedyś o...?", sugerują odwołanie do moich notatek.
Jak się pewnie domyślasz, prompt z którego korzystałem, był dość złożony i bardzo precyzyjny. Na pewnym etapie dodałem do niego także kilka przykładów, aby jasno zasugerować o co mi chodzi. W efekcie jego długość wynosiła ~1000 tokenów (nie licząc generowanej odpowiedzi).
Pomimo tego, że jednorazowe wykonanie takiego zapytania nie kosztuje zbyt wiele, u mnie każda interakcja z M.O.N.D.A.Y. wymagała jego realizacji. W efekcie szybko zbliżałem się do kolejnych limitów API, które miałem ustawione na swoim koncie.

Poza kosztami, problemem był także odpowiednio długi czas odpowiedzi oraz zdarzające się problemy z precyzją klasyfikacji. Pomyślałem, że prompt można zoptymalizować.

Optymalizacja promptu z ChatGPT

Praktycznie wszystkie realizowane przeze mnie zadania, w jakiś sposób wspiera AI. Tym razem moim towarzyszem był ChatGPT. Opisałem mój problem, podałem prompt i poprosiłem o listę problemów. Otrzymałem taki wynik:

A potem poprosiłem o przejście przez kolejne punkty i wprowadzenie zmian. W efekcie długość promptu spadła z niemal 1000 tokenów do ~400 przy znaczącym wzroście skuteczności.
Niestety nadal było to niewystarczające, bo nie jest to jedyny prompt z którego korzysta M.O.N.D.A.Y. Potrzebowałem więc sposobu, który w 2 godziny doprowadził mnie do obniżenia liczby tokenów w prompcie do 30 (trzydziestu!) przy jednorazowym koszcie na poziomie $0.99.

Czym jest Fine-tuning?

Wspomniałem, że fine-tuning to dostosowanie modelu do swoich potrzeb. Mówiąc bardziej precyzyjnie, chodzi o dostarczenie zestawu danych, w postaci par "promptu" (zapytania) oraz "completion" (odpowiedzi/dopełnienia). Na tej podstawie, możemy przygotować np. własną wersję modelu text-davinci-003, która będzie specjalizować się w realizowaniu konkretnego zadania. W moim przypadku, mowa o rozpoznaniu mojej intencji, poprzez określenie jeden z Grup Akcji oraz Typu Akcji.
Korzystając z przykładu bezpośrednio z dokumentacji OpenAI, potrzebny zestaw danych wygląda tak:

{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}

Większość poradników w Internecie wykorzystuje języki programowania, do stworzenia właśnie takiej struktury, na podstawie naszych informacji. Trzeba przyznać, że ma to ogromny sens, szczególnie gdy pracujemy na bardzo konkretnym zestawie informacji. Tym bardziej, że Fine-tuning wymaga od nas opracowania zestawu zawierającego od 120-150 do kilkuset tysięcy rekordów. Naturalnie im większy zestaw danych, tym większa skuteczność.

Pomyślałem, że na moje potrzeby, wystarczy ~200 rekordów. Problem jednak polegał na tym, że ich nie miałem. A napisanie z głowy 200 wiadomości oraz przyporządkowanie ich do kategorii a także napisanie tagów, zajęłoby mi cały dzień. Potrzebowałem sprytniejszego sposobu.

Generowanie zestawu danych

Na tym etapie, wystarczyło połączyć pewne kropki:\

  • moje zapytania, pasują do bardzo konkretnych kategorii i mają swoje cechy
  • są naturalne i nie są czymś zupełnie nietypowym (np. "W piątek widzę się z Martą")
  • ChatGPT może wygenerować mi przykłady wypowiedzi, pasujących do wspomnianych kategorii
  • Skuteczność nowej wersji promptu jest bardzo wysoka (~97%)
  • Mogę zmodyfikować prompt aby klasyfikował wiele wypowiedzi jednocześnie (to technika z artykułu, który linkowałem we wcześniejszym wydaniu newslettera)


Moim jedynym zadaniem, było tylko ogólne określenie tego, czy generowane odpowiedzi faktycznie pasują do mojego opisu. Następnie kopiowałem jej do mojego edytora (tutaj świetnie sprawdził się lekki SublimeText 4).
Dla każdej z kategorii wygenerowałem około 30-40 przykładowych odpowiedzi, w pojedynczych zapytaniach do ChatGPT. W ten sposób powstał długi plik, wyglądający tak:

Kolejnym krokiem była zamiana tych zdań na format JSONL, który pokazałem wyżej. Poza samym generowaniem odpowiedzi, potrzebowałem jeszcze kilku dodatkowych elementów, wymaganych do procesu fine-tuningu. Są to drobne aczkolwiek bardzo istotne detale dotyczące formatowania tekstu, które trzeba uwzględnić. Cały proces bardzo precyzyjnie opisany jest w dokumentacji.
W skrócie, chodzi o to aby:

  • prompt (czyli pierwsza część) zawierała jasny symbol oznaczający zakończenie promptu. Chodzi o to aby wyraźnie zaznaczyć, gdzie kończy się zapytanie. Rekomendowany separator to \n\n###\n\n. Chodzi jednak o zachowanie spójności i 100% pewności, że taki zestaw znaków nigdy nie wystąpi w treści prompta. Cokolwiek wybierzesz, musisz zapamiętać, ponieważ z tego separatora będziemy korzystać pracując z modelem
  • completion powinno zaczynać się od spacji. Jest to rekomendacja openAI
  • completion powinno kończyć się sekwencją znaków sygnalizującą zakończenie odpowiedzi, np. ###. Ponownie możesz wybrać cokolwiek, jednak ta wartość także będzie potrzebna później

Natomiast polecam i tak zajrzeć do dokumentacji aby przeczytać o dobrych praktykach, których nie będę tutaj komentował i tym bardziej duplikował. Wszystko znajdziesz w tutaj.

Zestaw danych (JSONL)

No i teraz przyszedł czas na wykorzystanie wygenerowanych zdań, do zbudowania zestawu danych treningowych. Zamiast pisać skrypty, skorzystałem z Playground.
Struktura mojego promptu wyglądała tak:
[oryginalny prompt klasyfikujący zapytanie]
[przykład formatu JSONL]

Messages:
[lista wiadomości]
JSONL:(od tego miejsca GPT-3 uzupełniało dane)
Przykładowa linia wyglądała tak:
{"prompt": "Wiadomość ###", "completion": " [kategorie]END"}
I dokładnie w ten sposób, wygenerowałem ~200 rekordów. Samo generowanie (zarówno przykładowych zdań jak i docelowego zestawu treningowego) zajęło mi około 40 minut. Większość czasu potrzebowałem na samo dojście do tego rozwiązania, więc w ten sposób bez problemu mógłbym w sensownym czasie wygenerować zestaw ~1500 rekordów.

Warto podkreślić, że rozwiązanie o którym piszę raczej nie sprawdziłoby się dla setek tysięcy rekordów. Dlatego pisanie kodu w Pythonie czy Node.js, jest w pewnym momencie uzasadnione. Warto o tym pamiętać. Tym bardziej, że w niektórych sytuacjach konieczne jest także "odszumienie" informacji w celu uzyskania możliwie jak najlepszych wyników.

Fine-tuning

Tutaj zaczyna się najbardziej techniczna część, ponieważ musimy skorzystać z terminala. W pierwszej kolejności, musimy zainstalować Brew (o ile już nie masz), Python3.9 oraz openAI:

  • /bin/bash -c "$(curl -fsSL <https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh>)"
  • brew install python@3.9
  • pip install --upgrade openai

Po instalacji zależności, musimy ustawić nasz klucz API. Pamiętaj, że to na nim pojawi się dostosowany model.

Następnie w katalogu w którym znajduje się plik z danymi treningowymi, musimy uruchomić polecenie przygotowujące je do fine-tuningu:
openai tools fine_tunes.prepare_data -f nazwa_pliku.jsonl

Jak wyraźnie widać, zawartość pliku została przeanalizowana. W sytuacji gdyby w pliku pojawiły się duplikaty lub jakiekolwiek błędy czy złe praktyki, zostalibyśmy o tym poinformowani. W niektórych sytuacjach problemy mogą zostać rozwiązane automatycznie, po wyrażeniu naszej zgody.
Kolejne polecenie uruchamia proces fine-tuningu. Musimy przekazać do niego zestaw danych treningowych, model bazowy z jakim chcemy pracować (u mnie to davinci) oraz suffix, który zostanie uwzględniony w nazwie modelu.
UWAGA: Po uruchomieniu tego polecenia rozpocznie się płatny proces fine-tuningu.
openai api fine_tunes.create -t "categorize_prepared.jsonl" -m "davinci" --suffix "monday-classify"

W moim przypadku szybko wystąpił problem, który można było naprawić poprzez wznowienie strumienia:

Po uruchomieniu polecenia wznawiającego, wszystko poszło w porządku. Tym razem dopisało mi szczęście (prawdopodobnie ze względu na bardzo późną porę) i byłem pierwszy w kolejce. Koszt fine-tuningu wyniósł $0.99 a całość trwała mniej niż 15 minut.

Praca z modelem

Po zakończeniu fine-tuningu, na naszym koncie pojawi się nowy model, który możemy przetestować bezpośrednio w Playground.
Konieczne jest jednak zadbanie o kilka detali:

  • model musi być ustawiony na ten, który został dla nas utworzony
  • na końcu promptu musi pojawić się sekwencja sygnalizująca jego zakończenie
  • Jako Stop sequence musimy podać tekst, który ustaliliśmy podczas trenowania


Pomimo tego, że tym razem nasz prompt zawiera wyłącznie zdanie, które należy sklasyfikować, odpowiedź będzie nawet bardziej precyzyjna niż gdybyśmy stosowali oryginalny prompt. W moim przypadku pojawi się bezpośrednio odpowiedź, klasyfikująca przekazane zdanie i sugerująca M.O.N.D.A.Y., że ma się wypowiedzieć, korzystając z naszych prywatnych notatek.
Pozostałe ustawienia (np. max length i temperature) możesz ustawiać dowolnie, tak jak przy pracy z podstawowym modelem.

Integracja z make.com

Make.com posiada natywny moduł GPT-3. Niestety nie wystarczy nam w przypadku naszego modelu (pomimo tego, że wyświetli się na liście). Powodem jest fakt, że musimy ustawić parametr "Stop sequence", który nie jest tam dostępny.
Z tego powodu, konieczne jest nawiązanie połączenia bezpośrednio z API GPT-3, poprzez moduł HTTP. W tym celu musimy utworzyć obiekt JSON, zawierający wszystkie niezbędne parametry.
Korzystając z modułu JSON i akcji Create JSON, możemy wygenerować strukturę danych na podstawie takiego obiektu:

{
    "prompt": "How are you?### ",
    "max_tokens": 256,
    "temperature": 0.1,
    "top_p": 1,
    "frequency_penalty": 0,
    "presence_penalty": 0,
    "best_of": 1,
    "echo": true,
    "logprobs": 0,
    "stop": [
        "END"
    ],
    "model": "NAZWA",
    "stream": true
}


W ten sposób powstanie formularz, który będziemy mogli uzupełnić. Zwróć uwagę na fakt, że w prompcie umieściłem sekwencję "###" oraz Stop ustawiony na (w moim przypadku "END").

Po utworzeniu takiego obiektu, możemy wysłać zapytanie HTTP. Zwróć uwagę na zaznaczone fragmenty. W celu autoryzacji połączenia musisz wykorzystać klucz API, powiązany z kontem na którym znajduje się nowy model. Warto też zaznaczyć opcję "parse response" no i oczywiście, przekazać obiekt JSON.

W ten sposób otrzymamy odpowiedź, wygenerowaną przez nasz dostosowany model.

Podsumowanie

Jak widać, fine-tuning, w niektórych przypadkach można skutecznie przeprowadzić bez pisania kodu. Poza tym do wygenerowania danych możemy wykorzystać ChatGPT jak i GPT-3.
Bez wątpienia warto zachować wysoką precyzję i ostrożność, przygotowując dane treningowe. Szczególnie, że w przypadku większych zestawów, czas potrzebny na fine-tuning oraz powiązane z tym koszty (obejmujące także przygotowanie danych), są odpowiednio wyższe. Dobrym pomysłem jest przećwiczenie procesu na małych zestawach danych (~100 rekordów) a potem dążenie do optymalizacji oraz stosowania bardziej zaawansowanych technik.
Bez wątpienia fine-tuning na omówionym przykładzie, przełożył się u mnie na znaczące obniżenie kosztów oraz czasu generowanej odpowiedzi. Zatem zalety wymienione przez openAI są prawdziwe. Mam tu na myśli m.in.:

  • wyższa jakość zwracanej odpowiedzi, niż w przypadku promptów
  • możliwość podania większej liczby przykładów, niż w samym prompcie
  • oszczędność tokenów
  • szybkość działania

Aczkolwiek naturalnie wymaga to większej inwestycji czasu, niż napisanie samego promptu. Dlatego w niektórych przypadkach sprawdzi się rozbudowany prompt a innym fine-tuning. Nic też nie stoi na przeszkodzie, aby połączyć oba podejścia.
Mam nadzieję, że odnajdziesz wartość dla siebie w powyższym wpisie. Powodzenia!