Podstawy
2 godz. 43 min · OpenAI · Full-stack i Programowanie
Adam GospodarczykChociaż dokładne działanie dużych modeli językowych nie jest zrozumiałe nawet dla twórców tych narzędzi, to i tak istnieje szereg dobrze znanych faktów. Zapoznanie się z nimi pozwoli Ci lepiej wyobrazić sobie sposób działania LLM bez konieczności wchodzenia w głębokie detale techniczne. To z kolei w sposób bezpośredni przełoży się na skuteczność projektowanych promptów. W pierwszej części kursu skupiamy na wysokopoziomowym spojrzeniu na modele firmy OpenAI, uwzględniając ich aktualne możliwości oraz aktualne ograniczenia.
Instrukcje kierowane do modeli takich jak GPT-3.5-Turbo czy GPT-4, mogą realizować najróżniejsze zadania. Pomimo tego ich ogólna struktura uwzględnia pewne schematy z których możemy korzystać. W zależności od celu jaki chcemy osiągnąć, możemy uwzględniać lub pomijać poszczególne elementy promptu. I chociaż wspomniane modele są w stanie świetnie radzić sobie z nieustrukturyzowanymi danymi, to w przypadku projektowania instrukcji, bardzo istotne jest zachowanie precyzji oraz wyraźne oddzielenie jej fragmentów. Na praktycznych przykładach zobaczysz, jak łatwo niewłaściwe formatowanie może negatywnie wpływać na generowaną odpowiedź. Poznasz także ogólne zasady, które pomogą Ci kształtować instrukcje w sposób, który zwiększy prawdopodobieństwo wygenerowania odpowiedzi, której oczekujesz.
Zero-shot, Few-shot, Chain of Thought czy Tree of Thoughts — to techniki projektowania promptów, dzięki którym znacząco zwiększysz ich skuteczność. I choć każda z nich zwykle opiera się o bardzo proste koncepcje, tak ich praktyczne wykorzystanie nie zawsze jest oczywiste. W tym kursie nie tylko poznasz przykłady ich zastosowania, ale także będziesz w stanie połączyć je z ogólnymi zasadami tworzenia promptów, co jeszcze bardziej ulepszy tworzone przez Ciebie instrukcje. Ciekawostka: Technika Tree of Thoughts w niektórych przypadkach zwiększa skuteczność GPT-4 z 4% do nawet 74%.
Ostatnia część kursu uwzględnia kilka przykładów, których celem jest pokazanie Ci szerokiego kontekstu w którym umiejętność projektowania promptów, umożliwia wyjście daleko poza zwykłe odpowiadanie na pytania i generowanie tekstu. Zobaczysz, jak tworzone przez Ciebie instrukcje mogą zostać wykorzystane w aplikacjach webowych oraz systemach uwzględniających realizujących złożone zadania z pomocą dużych modeli językowych. Mowa tutaj o pamięci długoterminowej, embeddingu danych oraz nawet autonomicznych agentach, zdolnych do posługiwania się narzędziami. Pamiętaj jednak, że wszystkie z wymienionych tematów zostały omówione tylko z szerokiej perspektywy, ponieważ ich faktyczna implementacja, wykracza poza zakres tego kursu.
Ten kurs został zaprojektowany z myślą o osobach chcących rozpocząć naukę projektowania promptów dla dużych modeli językowych, takich jak GPT-3.5-Turbo czy GPT-4. Wcześniejsze doświadczenie oraz zaawansowane umiejętności techniczne czy programistyczne nie są wymagane. Wiedza z tego kursu da Ci podstawy do dalszej nauki projektowania promptów oraz pozwoli Ci uzyskać szerszą perspektywę na temat roli promptów przy codziennej pracy z narzędziami takimi jak ChatGPT czy integracjami dużych modeli językowych z aplikacjami.
W poprzednich lekcjach wspominałem,
że ogromne znaczenie.
Odgrywa to, w jaki sposób formatuje nasz prompt.
Pomyślałem, że nie do końca może być jasne,
dlaczego jest to aż tak istotne.
Dlatego przygotowałem kilka przykładów, które pomogą Ci to
lepiej zrozumieć oraz dodatkowo
pokażę Ci kilka
dodatkowych technik, które pomogą
w kształtowaniu zachowania modelu,
tak aby możliwie najlepiej
realizował mu zlecone zadania oraz skutecznie odpowiadał na pytania.
Przykład, który mamy tutaj, ponownie
dotyczy asystenta, który odpowiada na pytania na podstawie kontekstu
ale dodatkowo mamy także drugą
sekcję zawierającą podsumowanie bieżącej rozmowy.
Odróżnienie tych poszczególnych sekcji jest o tyle istotne, że
asystent w trakcie konwersacji musi być w stanie wyraźnie wiedzieć
który z tych zestawów informacji
dotyczy jego pamięci długoterminowej, a który
aktualnej dyskusji. No i teraz, w
momencie, gdy wszystko
mamy wyraźnie tutaj oddzielone, to po zadaniu pytania, które
w sposób niebezpośredni nawiązuje do aktualnej rozmowy
asystent jest w stanie połączyć zarówno moje zainteresowania, które wynikają z
kontekstu oraz informacje wynikające z podsumowania bieżącej rozmowy.
Dzięki temu jego odpowiedź jest tutaj
niesamowicie precyzyjna.
Zanim jednak pokażę Ci przykłady,
które mogą zaburzyć takie zachowanie.
To chciałbym także podkreślić fakt,
że wprowadzenie nawet najmniejszych zmian,
będzie miało bezpośredni wpływ na
zachowanie naszego asystenta.
Co więcej, w niektórych sytuacjach może się nawet.
Okazać, że wprowadzając jedną zmianę. Sprawiamy, że pozostała część instrukcji zostaje
zagubiona i tym samym zachowanie asystenta również zostaje zmienione.
Najprostszym przykładem tego, o czym mówię w tej chwili, może być dodanie praktycznie dwóch.
Słów takich jak te. Jak widać, jeżeli porównamy sobie wypowiedzi asystenta na to samo pytanie, to.
W obu przypadkach mamy podobne, ale zasadniczo różniące się od siebie wypowiedzi.
Ta druga jest zdecydowanie bardziej konkretna.
Podczas gdy ta pierwsza jest nieco bardziej opisowa.
No i teraz problem polega na tym, że
modyfikując w następujący sposób nie możemy mieć stuprocentowej pewności.
Czy pozostała część instrukcji została nienaruszona i nadal będzie brana pod uwagę.
Coś takiego można oczywiście.
Testować na różnego rodzaju sposoby,
najlepiej wykorzystując zestawy danych testowych, a następnie prompty, które będą weryfikować poprawność.
Działania naszego oryginalnego promptu.
Poza tym chciałbym także podkreślić, że nawet jeżeli
teraz usuniemy tą odpowiedź, a następnie
wygenerujemy ją ponownie, to tym razem otrzymaliśmy nieco inną wypowiedź.
Pomimo tego, że prompt w zupełności się nie zmienił. Dla porównania. Wklejam tutaj tą wcześniejszą.
Wypowiedź, która jak widać, znacząco się różni.
Takie zachowanie wynika ze wspominanej już przeze mnie niedeterministycznej.
I natury modeli takich jak GPT-4. Oznacza to, że dokładnie dla takiego samego zestawu
danych będziemy otrzymywać wyniki, które będą się od siebie różnić.
Jest to zachowanie, które dość trudno zaakceptować w szczególności, gdy posiadamy.
Programistyczne doświadczenie, ponieważ tam przykładowo
czyste funkcje, zachowywały się dokładnie.
Tak samo dla tego samego zestawu danych.
Inaczej mówiąc, były deterministyczne.
W tej chwili możemy przejść do jeszcze jednego
przykładu, który wygląda podobnie jak ten pierwszy.
Pokazywałem, aczkolwiek nie ma tutaj wyraźnego podziału.
Na podsumowanie oraz kontekst. Coś takiego.
Sprawia, że gdy zadamy dość ogólne pytanie, asystent tak jak w tym
przypadku, pomylił się, mieszając
fakty dotyczące samego kontekstu, czy też jego pamięci długoterminowej
oraz naszej bieżącej dyskusji.
Pomimo tego, że w samym podsumowaniu znalazła się tutaj
informacja dotycząca innej osoby o imieniu Alice, to asystent nie był w stanie
odróżnić tej informacji od tego, co potencjalnie zostało zawarte w kontekście.
Jest to bezpośredni dowód na to i
też doskonały przykład mówiący o tym, że wyraźne
oddzielanie poszczególnych fragmentów naszego promptu,
jest istotne do zachowania spójności oraz dokładności.
Dodatkowo poza samym oddzieleniem istotne jest także etykietowanie,
czyli nadawanie nazw dla poszczególnych sekcji, tak, abyśmy mogli z poziomu instrukcji się
do nich odwoływać. I teraz ponownie jeżeli
delikatnie zmodyfikujemy ten prompt, a następnie uwzględnimy odpowiednie
formatowanie oraz etykiety, to prowadzona rozmowa wygląda teraz zupełnie inaczej.
Jak widać, na tak samo zadane pytanie uzyskaliśmy już
poprawną odpowiedź opartą o informacje zawarte
w kontekście, a dodatkowo
nawet jeżeli w tym przypadku do uzyskania pewności zadałem kolejne pytanie dotyczące specjalizacji, to asystent
poprawnie odpowiedział, że nie posiada żadnych informacji na ten temat.
Pomimo tego, że wewnątrz podsumowania naszej bieżącej rozmowy,
wyraźnie jest zaznaczone, że Alice jest programistką.
W tym przypadku model był w stanie odróżnić, że chodzi o inną osobę.
Zaznaczę jednak, że takie zachowanie jest
widoczne tylko w przypadku modelu GPT-4, ponieważ gdy zmienimy na GPT-3 to z dużym
prawdopodobieństwem uzyskamy tutaj nieprawidłową odpowiedź, ale akurat w tym przypadku wygląda na to, że wszystko się zgadza.
Muszę jednak tutaj powiedzieć, że na etapie projektowania tego promptu,
zdarzyło mi się tutaj w przypadku GPT-3.5-Turbo
otrzymywać informacje mówiące nawet o tym, że
asystent jako AI nie posiada żadnej specjalizacji
i w związku z tym nie jest w stanie odpowiedzieć nam
na takie pytanie.
Zatem wniosek jest tutaj następujący:
wyraźne oddzielenie poszczególnych fragmentów
sekcji jest bardzo istotne oraz także
nadawanie im etykiet, do których bezpośrednio
i w sposób jednoznaczny jesteśmy w stanie odwoływać się z poziomu naszej instrukcji.
Ważne jest tutaj także to
aby odpowiednio wybrać nazwy dla tych poszczególnych
sekcji i konkretnie chodzi tutaj o ich unikatowość, aby przypadkowo
nie występowały w pozostałej części naszej instrukcji.
Dzięki temu ponownie unikniemy tutaj
dwuznaczności oraz potencjalnego błędnego zachowania.
Poza tym wszystkim chciałbym raz jeszcze zaznaczyć, że model GPT-3.5-Turbo
nie do końca podąża za instrukcją systemową, a jego
zachowanie może się znacząco różnić, pomimo tego. Że nasz prompt będzie poprawnie zapisany.
Myślę, że najprostszym możliwym rozwiązaniem w takiej sytuacji jest zmiana modelu na GPT-4.
Oczywiście możemy dalej pracować nad tym,
aby usprawniać nasz prompt i sprawić
aby odpowiednio działał również w przypadku GPT-3.5 Turbo
natomiast będzie to zdecydowanie trudniejsze.
Poza tym istnieje jeszcze jedna technika, polegająca na tym, aby prompt systemowy.
Umieścić jako wiadomość przesłaną przez użytkownika na początku konwersacji.
W związku z tym, że cały czas mówimy tutaj tak naprawdę o
jednym prompcie, prawie nic nie stoi na
przeszkodzie, aby zrobić dokładnie w ten sposób.
Wówczas nawet w przypadku modelu GPT-3.5 Turbo będziemy
otrzymywać tutaj znacznie lepsze wypowiedzi.
Trzeba jednak pamiętać, że wiadomość systemowa została tutaj dodana nie bez przyczyny ze względu na to, że
przykładowo w momencie, gdy będziemy mieć
aplikację wyświetlającą czat pomiędzy użytkownikiem a asystentem.
To programistycznie będziemy musieli pamiętać o
tym, że pierwsza wiadomość użytkownika stanowi
prompt i nie może być wyświetlona w interfejsie.
Teraz mam nadzieję, że na pewnym etapie ta informacja może
okazać się dla Ciebie przydatna i jednocześnie mam taką samą
nadzieję, że niebawem nie będzie to stanowiło dla nas żadnego
problemu ze względu na ewentualne możliwe aktualizacje tych modeli.
Kontynuując temat samego formatowania
promptu, być może zastanawiasz się w jaki sposób dobieram tutaj te separatory?
Otóż tak jak już powiedziałem w jednej z poprzednich lekcji, chodzi tutaj wyłącznie o to, aby upewnić się
że jest to symbol bądź ciąg znaków, które
nie wystąpi przypadkowo w zestawie danych, na którym pracujemy.
Poza tym istnieje jeszcze jeden powód, dla którego decyduję się np. na takie separatory.
Mianowicie jak widać mamy tutaj dwa znaki, ale jeden token.
To samo obowiązuje dla takiego symbolu oraz następującego.
Dzięki temu jesteśmy w stanie zamiennie bądź naprzemiennie stosować te separatory.
Po to, aby oddzielić poszczególne fragmenty tekstu.
Dodatkowo warto też brać pod uwagę, że również znaki nowej
linii, dokładnie te, które mamy tutaj, stanowią również pojedynczy token i w
wielu przypadkach mogą się sprawdzić jako znak separatora.
Tutaj muszę jednak przyznać, że
osobiście wykorzystuję je najrzadziej,
ze względu na to, że w moich notatkach i zestawach informacji na których pracuję
takie podwójne znaki nowej linii występują szczególnie często.
Jeżeli zbierzemy teraz to wszystko w
całość, to możemy dojść nawet do następującego promptu.
Mamy tutaj dość rozbudowaną wersję promptu, który pokazywałem wcześniej.
I teraz taka wiadomość systemowa wydaje się dość długa.
Jeżeli jednak skopiujemy ją do Tokenizera, to zobaczymy, że pomimo tego, że znajduje się
tutaj prawie 1200 znaków, mamy jedynie wykorzystanych 296 tokenów.
Oznacza to, że mój prompt jest całkiem dobrze napisany. Ze względu na to, że doskonale realizuje swoje zadanie.
A jednocześnie pozostawia sporo miejsca na dyskusje.
Coś takiego wynika. m.in. z faktu, że do napisania tego. promptu, wykorzystałem tutaj, jak widać język angielski.
Dzięki temu, jak widać, niektóre nawet długie słowa traktowane są jako pojedynczy token.
Dzięki temu byłem w stanie znacząco zoptymalizować ten prompt.
I szczerze mówiąc, wykorzystując tokenizer,
mógłbym zoptymalizować ten prompt jeszcze bardziej.
Oczywiście takie podejście można traktować także w przypadku innych języków, ponieważ nic nie stoi na przeszkodzie
aby samą instrukcja napisać w języku angielskim, natomiast dodatkowo
uwzględnić w niej to aby nasz asystent.
Rozmawiał w innym języku bądź też jeszcze lepiej,
dopasowywał język swojej aktualnej wypowiedzi do pytania użytkownika.
Poza tym, że ten prompt wydaje się dość dobrze zoptymalizowany, to mamy tutaj
przede wszystkim bardzo dobry rezultat.
Mianowicie, jak widać, konwersacja którą tutaj prowadzę,
jest bardzo naturalna i szczerze mówiąc, momentami nawet trudno odróżnić, że rozmawiamy
tutaj z asystentem AI, a nie z drugą osobą.
I teraz chciałbym tutaj podkreślić to, że sam prompt nie tylko zawiera
poszczególne słowa kluczowe oraz informacje czy wyrażenia, które definiują zachowanie naszego asystenta
ale także wykorzystuje słowa kluczowe, które wprost nadają w tym przypadku luźny ton całej dyskusji.
Dzięki temu asystent w pewnym sensie rozumie, że może wykorzystywać bardzo luźne określenia, podczas swoich wypowiedzi.
W ten sposób eliminujemy, bądź bardziej redukujemy dość sztywny i oficjalny ton, w
jakim domyślnie działają modele takie jak GPT-4.
W tym wszystkim, poza samym tonem wypowiedzi, istnieją
także charakterystyczne np. dla modelu GPT-4 różnego rodzaju wyrażenia, które bardzo ciężko nadpisać.
Doskonałym przykładem takiego wyrażenia, jest odpowiedź na pytanie: "jak się masz?"
Niemal zawsze otrzymamy wariant odpowiedzi mówiący o tym,
że jako model AI nasz asystent nie posiada ani żadnych uczuć ani emocji.
W związku z tym nie jest w stanie odpowiedzieć na to pytanie.
Co więcej, nawet jeżeli zapiszemy tutaj prompt systemowy, który jasno sugeruje, że mamy do czynienia
z człowiekiem, który posiada emocje oraz uczucia,
to nadal otrzymujemy to ogólne wyrażenie podkreślające fakt, że rozmawiamy z AI.
Oczywiście w większości przypadków jest to jak najbardziej pożądane i wręcz wskazane, aby użytkownik wiedział
z kim ma do czynienia.
Z drugiej jednak strony, potraktuj ten przykład wyłącznie jako pokaz tego, że czasem
trudno jest nadpisać domyślne zachowanie modelu,
nawet jeżeli jasno zdefiniujemy nasz prompt.
Co ciekawe, akurat w tym konkretnym przypadku stosunkowo
proste jest napisanie instrukcji, która nadpisze to zachowanie
ale tylko na początku naszej dyskusji.
Później wraz z rozwojem rozmowy bardzo często model, nawet GPT-4,
gubi podkreślone przez nas instrukcje, wracając do swojego domyślnego zachowania.
Jednak w przypadku mojego promptu, pomimo tego, że dyskusja
była dość długa, byłem w stanie zachować to zdefiniowane w instrukcji zachowanie.
Coś takiego wynika przede wszystkim z faktu
bardzo wyraźnego podkreślenia tego, że asystent nie może zdradzać, że jest AI oraz także podania
pewnego rodzaju przykładu, który pokazuje, jak asystent ma się zachowywać a także ustawiłem tutaj
temperaturę na 0, aby skupić się tylko na tych najbardziej prawdopodobnych tokenach.
Oczywiście nie mam tutaj stuprocentowej pewności, czy np. po wymianie kolejnych dziesięciu wiadomości to zachowanie nadal będzie tutaj obecne,
natomiast według moich dotychczasowych testów. Wszystko przebiegało tutaj pomyślnie.
Zatem wniosek z tej drugiej części naszej lekcji jest taki, że jesteśmy w stanie
tak projektować prompty, aby w miarę skutecznie sterować tym, jak zachowuje się model i tym samym realizować. Nasze założenia.
I teraz jeżeli chodzi o tę lekcję, to byłoby już na tyle.
Dziękuję więc za uwagę i słyszymy się za moment.