Your Techmate

Tworzenie bloków Gutenberg z użyciem ChatGPT.

Zobacz kurs

Podstawy AWS.

Automatyzacja procesów z Make.com

  • Jak stworzyć jedną wtyczkę dla wielu bloków Gutenberga?

    Jak stworzyć jedną wtyczkę dla wielu bloków Gutenberga?

  • Nowości w WordPress 6.6.2: Bug Fixes i zmiany w edytorze Gutenberga.
  • Cwicly: Krótka historia innowacji i przemian
  • Konfiguracja środowiska do tworzenia bloków Gutenberga: Przewodnik krok po kroku
  • Gutenberg vs. Site Editor vs. FSE – co jest czym w WordPressie?
  • Jak bez kodowania dodać Google Font do Gutenberga?

Budujemy aplikację do zadawania pytań do PDFów w LangChain i ChatGPT.

Awatar Mike Tomala

Narzędzia służące do zadawania pytań do treści zawartych w PDF’ach (chatboty PDF), zyskują sporą popularność. Zasada działania takich narzędzi jest prosta dla użytkownika. Wgrywamy wielostronicowy PDF, którego nie mamy czasu przeczytać, a po chwili możemy poprosić model o wygenerowanie podsumowania dokumentu lub zadać pytania do treści pliku. W tym wpisie sami stworzymy chatbota PDF z wykorzystaniem LangChain oraz ChatGPT jako modelu NLP.

Jeśli nie wiesz, czym jest LangChain, przeczytaj koniecznie nasz wpis Wprowadzenie do frameworka LangChain.

Wybieramy testowy plik PDF

Zanim przystąpimy do budowania aplikacji, wybierzmy plik PDF na którym będziemy pracować. Ja wybrałem plik umieszczony na arXiv, dotyczący bardzo szeroko komentowanego systemu DragGAN, a którego nie miałem jeszcze czasu przeczytać wcześniej. Mam nadzieję, że dzięki własnemu chatbotowi PDF dowiem się więcej o DragGAN.

PDF można pobrać tutaj: https://arxiv.org/pdf/2305.10973.pdf

Przetwarzamy PDF na tekst

W pierwszej kolejności musimy wyektraktować teksty, które znajdują się w PDFie. Wykorzystamy do tego bibliotekę PyPDF2.

import PyPDF2

def extract_text_from_pdf(file_path):
    pdf_file_obj = open(file_path, 'rb')
    pdf_reader = PyPDF2.PdfFileReader(pdf_file_obj)
    text = ""
    for page_num in range(pdf_reader.numPages):
        page_obj = pdf_reader.getPage(page_num)
        text += page_obj.extractText()
    pdf_file_obj.close()
    return text

file_path = "2305.10973.pdf"  # Podmień na ścieżkę do swojego pliku PDF
text = extract_text_from_pdf(file_path)
print(text)

Kod powyżej definiuje funkcję extract_text_from_pdf, która z pliku PDF w podanej ścieżce wyciągnie tekst i przypisze go do zmiennej.

Trenujemy GPT-4, aby poznał zawartość pliku PDF

Zanim przejdziemy do kodu, poznajmy odrobinę teorii, która ułatwi nam zrozumienie działania aplikacji.

Osadzenia (Embeddings)

Embeddings, czyli osadzenia, to wektory (listy) liczb zmiennoprzecinkowych, które służą do mierzenia związku między ciągami tekstowymi. Odległość między dwoma wektorami mierzy ich związek. Małe odległości sugerują wysoki związek, a duże odległości sugerują niski związek.

Można to sobie wyobrazić jako przypisanie każdemu słowu, frazie lub zdaniu unikalnej pozycji na mapie, gdzie podobne słowa, frazy lub zdania są umieszczone blisko siebie. Na przykład, słowa „pies” i „kot” będą miały swoje osadzenia blisko siebie, ponieważ oba odnoszą się do zwierząt domowych.

Osadzenia są bardzo użyteczne, ponieważ pozwalają maszynom lepiej zrozumieć kontekst i znaczenie słów. Na przykład, bez osadzeń, maszyna mogłaby myśleć, że słowa „król” i „królowa” są całkowicie różne, ponieważ mają różne litery. Ale dzięki osadzeniom, maszyna może zrozumieć, że oba słowa są powiązane, ponieważ oba odnoszą się do monarchów.

Więcej o osadzeniach możesz poczytać na blogu OpenAI: https://openai.com/blog/introducing-text-and-code-embeddings

Wektorowe bazy danych – Vectorstores

Bazy danych wektorowe, znane również jako bazy danych wyszukiwania wektorowego, są specjalnym rodzajem baz danych, które są zoptymalizowane do przechowywania i wyszukiwania wektorów, zamiast tradycyjnych typów danych, takich jak ciągi, liczby całkowite czy daty.

Bazy danych wektorowe są szczególnie przydatne w przypadkach, gdy potrzebujemy szybko znaleźć wektory, które są najbardziej podobne do danego wektora zapytania. Na przykład, mogą być używane do budowy systemów rekomendacyjnych, które sugerują produkty podobne do tego, który użytkownik właśnie ogląda lub do budowy systemów wyszukiwania obrazów, które znajdują obrazy podobne do obrazu zapytania.

LangChain oferuje możliwość integracji z wieloma vectorstores, tj. Chroma, Deep Lake, ElasticSearch, Redis, Pinecone etc.

Praca z długim tekstem

Każdy duży model językowy posiada limity tokenów (fraz), które mogą być do niego przesyłane za jednym razem. Pracując z długim dokumentem PDF, całość tekstu musi zostać podzielona na mniejsze części. Choć może się to wydawać proste, temat ten skrywa wiele potencjalnych złożoności. Idealnie chcielibyśmy utrzymać semantycznie powiązane fragmenty tekstu razem. To, co oznacza „semantycznie powiązane”, może zależeć od rodzaju tekstu.

Pierwsze pytanie do własnego chatbota PDF…

Kod, który podsumuje nam wskazany dokument PDF:

from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.text_splitter import CharacterTextSplitter, RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma

file_path = "2305.10973.pdf"  # Podmień na ścieżkę do swojego pliku PDF
text = extract_text_from_pdf(file_path)

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
pages = text_splitter.split_text(text)

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
texts = text_splitter.create_documents(pages)

embeddings = OpenAIEmbeddings()
db = Chroma.from_documents(texts, embeddings)

retriever = db.as_retriever()
qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=retriever)

query = "Podsumuj ten dokument"
print('result:', qa.run(query))

W powyższym przykładzie wykorzystaliśmy 2 klasy do przetworzenia tekstu, który wyciągnęliśmy z pliku PDF: CharacterTextSplitter oraz RecursiveCharacterTextSplitter.

Do zbudowania indeksu wykorzystaliśmy vectorstore Chroma. Chroma jest domyślną wektorową bazą danych dla LangChain. Chroma ułatwia budowanie aplikacji opartych na modelach języka (LLM) poprzez umożliwienie łatwego dodawania wiedzy, faktów i umiejętności do modeli języka.

Po uruchomieniu kodu otrzymamy rezultat:

result: Ten dokument opisuje system DragGAN, który składa się z dwóch głównych składników: 1) nadzoru ruchu opartego na cechach, który napędza punkt uchwytu do przesuwania się w kierunku pożądanej pozycji i 2) nowego podejścia śledzenia punktu, które wykorzystuje dyskryminacyjne cechy generatora, aby utrzymać lokalizację punktów uchwytu. System DragGAN pozwala dowolnie deformować obraz z precyzyjną kontrolą, dokąd idą piksele, co pozwala manipulować pozą, kształtem, wyrazem twarzy, układem i innymi kategorią.

Voila! Otrzymaliśmy podsumowanie naszego dokumentu PDF!

Wykorzystanie Document Loader z LangChain

W przykładzie powyżej stworzyliśmy własną funkcję, która wyciągała tekst z pliku PDF do jednej zmiennej. Dzięki LangChain i Document Loader’om możemy to zrobić jeszcze prościej. Document Loader to interfejs, który pozwala na załadowanie różnego rodzaju plików do modelu NLP. Możemy załadować plik Word (.doc), Excel (.xls), wiadomość email (.eml), plik XML, plik HTML, PDF i wiele innych rodzajów plików.

W przykładzie poniżej zmodyfikowaliśmy kod, aby uprościć ładowanie pliku PDF z wykorzystaniem DocumentLoadera.

from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter

from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader("2305.10973.pdf") # Podmień na ścieżkę do swojego pliku PDF

text_splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100) # Aby nie przekroczyć liczby tokenów ChatGPT zmniajszamy domyślny chunk_size
pages = loader.load_and_split(text_splitter=text_splitter)

embeddings = OpenAIEmbeddings()
db = Chroma.from_documents(pages, embeddings)

retriever = db.as_retriever()
qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=retriever)

query = "Jak przebiega nadzór ruchu opartego na cechach?"
print('result:', qa.run(query))

W odpowiedzi dostajemy:

result: Nadzór ruchu oparty na cechach polega na wykorzystaniu algorytmu iteracyjnego połączonego z konwencjonalnym algorytmem wykrywania cech, aby ustalić ruch punktów w obrazie. Algorytm generuje bardziej naturalnie wyglądający ruch punktów, w którym punkty sterujące (czerwone kropki) są przesuwane w kierunku punktów docelowych (niebieskie kropki).

Dlaczego sam ChatGPT nam nie odpowie, a nasza aplikacja tak?

Koncepcja zadawania pytań do plików PDF polega na tym, że przekazujemy do modelu językowego treść pliku, do którego następnie zadajemy pytania. W konsekwencji model poznaje nasz plik. Proces przekazywania treści pliku lub wielu plików do modelu językowego, aby poznał kontekst pytań można określić z angielskiego „combining documents with large language models” – czyli łączenia dokumentów z modelem językowym. Ktoś mógłby zapytać: „Jeśli będę miał 200 stronicowy plik, a każde przesłanie informacji do ChatGPT kosztuje, to czy nie zbankrutuję na tym?” 🙂

Do budowy naszego chatbot’a PDF wykorzystaliśmy klasę RetrievalQA. Retriver’y to koncepcja w LangChain udostępniająca interfejs, aby ułatwić łączenie dokumentów czyli tekstów z modelami językowymi. Dzięki temu, zamiast wysyłać cały dokument do ChatGPT możemy najpierw otrzymać fragment w którym potencjalnie znajduje się odpowiedź na nasze pytanie i przesłać do LLM tylko ten fragment jako kontekst do wygenerowania odpowiedzi.

W kolejnych wpisach na pewno pokażemy, jak można dalej rozwinąć naszego chatbot’a PDF. Stay tuned!