Skip to content

Latest commit

 

History

46 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chatbot Asystent Mykologiczny - Ekspert ds. Grzybów

Projekt w ramach przedmiotu Algorytmy Tekstowe

Projekt realizuje zaawansowany system wyszukiwania i konwersacji oparty na architekturze RAG (Retrieval-Augmented Generation), dedykowany identyfikacji, klasyfikacji oraz dostarczaniu wiedzy na temat mykologii. System łączy klasyczne algorytmy wyszukiwania tekstowego (rzadkie indeksy) z nowoczesnymi metodami semantycznymi (gęste wektory embeddingowe), wykorzystując zaawansowane mechanizmy przetwarzania języka naturalnego (NLP) dla języka polskiego.


1. Architektura Systemu

System zbudowany jest wokół potoku wyszukiwania hybrydowego, mającego na celu zminimalizowanie ograniczeń pojedynczych metod wyszukiwania i dostarczenie jak najdokładniejszego kontekstu do modelu generatywnego.

Kluczowe komponenty algorytmiczne:

  1. HyDE (Hypothetical Document Embeddings): Zapytania użytkowników bywają krótkie i potoczne (np. "znalazłem grzyba z czerwonym kapeluszem i rurkami"). HyDE wykorzystuje LLM do przekształcenia zapytania w hipotetyczny, encyklopedyczny opis morfologiczny przy użyciu profesjonalnej terminologii. Wyszukiwanie odbywa się na bazie tego wygenerowanego dokumentu, co eliminuje asymetrię między zapytaniem a dokumentami źródłowymi.
  2. Przetwarzanie tekstu i Lemmatyzacja: Tekst poddawany jest usunięciu polskich znaków (w celu uodpornienia na błędy literowe), usuwane są znaki interpunkcyjne oraz tzw. stop-words. Następnie, za pomocą modelu językowego pl_core_news_lg, przeprowadzana jest lematyzacja (sprowadzenie wyrazów do formy bazowej), co maznaczenie dla skuteczności wyszukiwania leksykalnego.
  3. BM25Okapi: Klasyczny algorytm probabilistyczny oceniający relewancję dokumentów na podstawie częstości występowania rzadkich termów (TF-IDF z uwzględnieniem normalizacji długości dokumentu).
  4. Wyszukiwanie Wektorowe: Teksty reprezentowane są jako 1024-wymiarowe wektory gęste za pomocą modelu bge-m3. Odległość semantyczna w bazie danych obliczana jest przy użyciu metryki odległości cosinusowej obsługiwanej przez rozszerzenie pgvector w bazie PostgreSQL.
  5. RRF (Reciprocal Rank Fusion): Łączy wyniki z BM25 oraz wyszukiwania wektorowego. Zamiast bezwzględnych wartości ocen (scores), które mają różne skale, RRF operuje na pozycjach (rangach) dokumentów na listach wynikowych, wyliczając wspólną notę według wzoru:

$$RRF(d) = \sum_{m \in M} \frac{1}{\beta + r_m(d)}$$

gdzie $r_m(d)$ to ranga dokumentu $d$ w metodzie $m$, a $\beta$ to stała wygładzająca (w projekcie $\beta = 60$). 6. Cross-Encoder Reranking: Końcowy etap uściślania wyników. Podczas gdy klasyczne embeddingi (Bi-Encodery) kodują zapytanie i dokument niezależnie, Cross-Encoder analizuje zapytanie i dokument jednocześnie za pomocą mechanizmu uwagi (Attention), co drastycznie podnosi jakość selekcji 6 najlepszych fragmentów tekstu, które trafią do promptu systemowego.


2. Szczegółowy Opis Plików Projektowych

config.py

Plik centralnej konfiguracji projektu. Definiuje stałe środowiskowe, parametry połączenia z bazą danych Supabase (PostgreSQL), ścieżki do katalogów, a także ustawienia modeli językowych:

  • Wyszukiwanie i Embeddings: Definiuje model bge-m3 dla wektorów oraz plik indeksu bm25.joblib.
  • Konfiguracja HyDE: Przechowuje dedykowany prompt systemowy instruujący LLM (gemma3:4b), jak tłumaczyć potoczne opisy na profesjonalny żargon mykologiczny bez zgadywania nazwy gatunkowej.
  • Strukturyzacja Danych: Zawiera prompt METADATA_PROMPT odpowiedzialny za wymuszenie formatu JSON przy ekstrakcji cech z surowego tekstu oraz listę modeli LLM wykorzystywanych jako fallback (gemini-3.1-flash-lite, gemini-2.5-flash itd.).

db/fetchdata.py

Moduł odpowiedzialny za asynchroniczne pobieranie danych z bazy danych za pomocą biblioteki asyncpg.

  • Mechanizm działania: Aby uniknąć przeciążenia pamięci RAM przy przetwarzaniu dziesiątek tysięcy rekordów, skrypt implementuje kursor wewnątrz transakcji (conn.cursor()).
  • Pobiera dane paczkami (batchami) o rozmiarze 1000 rekordów. Wyciąga pola id, name, embedding, description oraz metadata z tabeli mushrooms, mierzy łączny czas operacji i zwraca kompletną listę przetworzonych struktur.

db/insert_data.py

Asynchroniczny moduł zapisu danych do bazy PostgreSQL.

  • Mechanizm działania: Wykorzystuje metodę conn.executemany(), która pozwala na masowe i zoptymalizowane wykonywanie zapytań INSERT w ramach jednej transakcji bazodanowej. Redukuje to narzut sieciowy i czas komunikacji z chmurą Supabase.

llms/embedding.py

Skrypt pośredniczący w komunikacji z lokalnym serwerem Ollama w celu generowania wektorów gęstych.

  • Mechanizm działania: Wywołuje funkcję ollama.embeddings z modelem bge-m3.

llms/hyde.py

Implementacja koncepcji Hypothetical Document Embeddings.

  • Mechanizm działania: Zawiera funkcję generate_hyde_document(query), która wysyła zapytanie użytkownika do modelu gemma3:4b. Zgodnie z instrukcją z konfiguracji, generuje tekst będący bogatym opisem morfologicznym (trzon, kapelusz, zarodniki, zapach), imitującym artykuł encyklopedyczny, stanowiący idealną podstawę do dalszego wyszukiwania hybrydowego.

bm25preprocessor.py

Kluczowy komponent lingwistyczny przygotowujący tekst pod algorytmy wyszukiwania pełnotekstowego.

  • Mechanizm działania:
    1. Przekształca znaki narodowe na ich odpowiedniki ASCII przy użyciu tablicy mapowania PL_TO_ASCII.
    2. Za pomocą wyrażeń regularnych oczyszcza tekst ze znaków specjalnych i interpunkcji, zastępując je spacjami.
    3. Analizuje potok tekstu za pomocą modelu spaCy. Iteruje po tokenach, filtrując słowa stopu (token.is_stop), interpunkcję i białe znaki.
    4. Pobiera lemat wyrazu (token.lemma_), sprowadza go do małych liter i dodatkowo odrzuca znaki niealfanumeryczne, budując czystą tablicę tokenów reprezentującą dokument.

bm_25.py

Skrypt budujący statyczny indeks wyszukiwania leksykalnego.

  • Mechanizm działania: Pobiera dane z bazy (za pomocą fetch_data_in_batches), a następnie uruchamia wieloprocesowe przetwarzanie tekstu za pomocą ProcessPoolExecutor. Podział na paczki (BATCH_SIZE) dystrybuowany jest pomiędzy niezależne procesy systemowe (workery CPU), z których każdy tokenizuje złączone pola nazwy i opisu grzyba za pomocą klasy BM25Preprocessor.
  • Po zebraniu wyników tworzona jestinstancja klasy BM25Okapi z biblioteki rank_bm25, która wraz z mapowaniami identyfikatorów i metadanych zostaje zrzutowana do pliku binarnego za pomocą joblib.dump.

generate_json_api.py

Potok ETL (Extract, Transform, Load) odpowiedzialny za ustrukturyzowanie zgromadzonych surowych plików tekstowych w formacie Markdown (.md) do ujednoliconych struktur JSON.

  • Mechanizm działania: Skrypt asynchronicznie odczytuje pliki tekstowe z katalogu danych. Wykorzystuje bibliotekę Google GenAI (client.aio.models.generate_content) w celu wyciągnięcia metadanych.

search_engine.py

Serce całego systemu informacyjnego – klasa SearchEngine implementująca hybrydowy system wyszukiwania wieloetapowego.

  • Mechanizm działania:
    1. Przyjmuje zapytanie użytkownika i przekazuje je do modułu HyDE, zyskując rozbudowany pseudo-dokument.
    2. Równolegle odpala dwa wątki wyszukiwania: bm25_ranking (wyszukiwanie na zapisanym indeksie leksykalnym) oraz rag_ranking (generowanie embeddingu przez Ollamę i wykonanie zapytania SQL z operatorem <=> cosinusowej odległości wektorowej w PostgreSQL).
    3. Łączy listy wynikowe za pomocą algorytmu Reciprocal Rank Fusion (RRF), selekcjonując zestaw unikalnych kandydatów (domyślnie 100).
    4. Przygotowuje pary [zapytanie_pierwotne, opis_kandydata] i przesyła je do głębokiego modelu rerankera BAAI/bge-reranker-v2-m3 ładowanego w pamięci GPU/CPU przy użyciu PyTorcha i precyzji float16.
    5. Sortuje kandydatów według wynikowej oceny podobieństwa Cross-Encodera i zwraca zestaw top_k najlepiej dopasowanych rekordów (nazwa + opis).

chat.py

Interfejs użytkownika w postaci pętli konwersacyjnej CLI.

  • Mechanizm działania: Inicjalizuje asynchronicznie SearchEngine. W nieskończonej pętli pobiera zapytanie tekstowe użytkownika, wywołuje potok wyszukiwania hybrydowego dla top_k=10 dokumentów i agreguje je w jeden spójny blok tekstu (Context).
  • Tworzy prompt systemowy definiujący rolę bota jako eksperta mykologa, po czym wysyła do modelu gemma3:4b pełny kontekst oraz kompletną, dotychczasową historię konwersacji (chat_history), zapewniając asystentowi pamięć kontekstową. Odpowiedź asystenta jest dopisywana do pamięci i wyświetlana na ekranie.

semantic_map_umap.py

Zaawansowany skrypt do analizy matematycznej i wizualizacji rozkładu semantycznego zgromadzonych danych o grzybach.

  • Mechanizm działania:
    1. Pobiera rekordy z bazy i parsuje tekstowe reprezentacje embeddingów za pomocą wyrażeń regularnych do macierzy NumPy typu float32.
    2. Tworzy zbinaryzowaną wersję macierzy (X > 0) na potrzeby metryk binarnych.
    3. Przeprowadza grupowanie nienadzorowane przy użyciu algorytmu KMeans dla 100 klastrów w celu automatycznego wyznaczenia "rodzin semantycznych" grzybów.
    4. W celu oszczędzania pamięci RAM, skrypt sekwencyjnie dla 4 różnych metryk odległości (L2 (Euklidesowa), L1 (Manhattan), Cosine, Jaccard) wylicza pełną macierz odległości parzystych za pomocą scipy.spatial.distance.cdist.
    5. Przekazuje macierze odległości do algorytmu UMAP (Uniform Manifold Approximation and Projection) z parametrem metric="precomputed", redukując przestrzeń 1024-wymiarową do przestrzeni trójwymiarowej (3D).
    6. Generuje interaktywne, trójwymiarowe wykresy punktowe za pomocą biblioteki Plotly Express, mapując wyznaczone klastry KMeans na kolory, a metadane (nazwa polska, łacińska, źródło, nagłówek) na etykiety hover. Wykresy są automatycznie eksportowane do samowystarczalnych plików .html.

3. Sekcja Scraperów Danych

W celu zasilenia bazy wiedzy bota mykologicznego, dane zostały pozyskane z czterech różnych stron internetowych. Poniższe sekcje stanowią miejsce na szczegółowy opis implementacji, architekturę kodu oraz zastosowane metody ekstrakcji danych.

Scraper 1: ekologia.pl

Scraper 2: grzyby.pl

  • Opis działania: Strona grzyby.pl była stosunkowo prosta do scrapowania, ponieważ jej zawartość była statyczna i nie wymagała renderowania JavaScript. Skrypt oparty na bibliotece requests pobierał surowy kod HTML, który następnie był parsowany za pomocą BeautifulSoup4. W celu poprawienia wydajności użyto ThreadPoolExecutor do równoległego przetwarzania wielu URL-i, co znacznie skróciło czas pozyskiwania danych. Z perspektywy czasu wystarczyło użyć asyncio do asynchornicznego pobierania.

  • Wykonanie zadania:

    • Zaprogramowanie crawlera, który startował od stronu główenj i podstrony atlasu grzybów, iterując po podstronach zaczynających sie od /gatunki/ i zbierając linki do poszczególnych gatunków.
    • Aby pamiętać które strony zostały już odwiedzone użyto struktury danych set do przechowywania unikalnych URL-i, co zapobiegało duplikacji i nieskończonym pętlom.

Scraper 3: nagrzyby.pl

  • Opis działania: Głównym wyzwaniem w przypadku tej strony był fakt, że strona jest renderowana dynamicznie, przez co klasyczne statyczne żądania HTTP (np. przez bibliotekę requests) nie pobierały właściwej treści. Aby to rozwiązać, scraper został oparty na bibliotece Playwright (działającej na silniku Chromium). Skrypt uruchamia przeglądarkę w trybie widocznym (headless=False), co pozwala na ręczne zaakceptowanie ciasteczek w trakcie zaprogramowanego odliczania, a następnie czeka na pełne załadowanie drzewa DOM (wait_until="domcontentloaded"). Dopiero wyrenderowany kod HTML trafia do parsowania za pomocą BeautifulSoup4.
  • Wykonane zadania:
    • Zautomatyzowana iteracja po 6000 podstronach atlasu na podstawie identyfikatorów URL.
    • Oczyszczanie drzewa DOM ze zbędnych elementów UI strony (usuwanie tagów button, nav, svg), aby uniknąć zanieczyszczenia tekstu.
    • Ekstrakcja polskich i łacińskich nazw grzybów (rozpoznawanie tagów h1, h2 oraz znaczników kursywy i).
    • Parser formatujący surowy tekst do ustrukturyzowanego formatu Markdown - automatyczne dodawanie nagłówków drugiego poziomu (np. ## Kapelusz, ## Trzon) oraz odrzucanie kategorii, w których widnieje wartość "brak" lub "brak opisu".
    • Zapis danych do czystych plików .md z poprawnie zdezynfekowanymi nazwami plików (usunięcie polskich znaków i znaków specjalnych) oraz nagłówkami typu Frontmatter zawierającymi metadane o źródle.

Scraper 4: wikipedia.org

WIKIPEDIA 1

WIKIPEDIA 2

  • Opis działania: na podstawie już pobranych plików wyekstrahowano listę nazw gatunków grzybów. Następnie każdą nazwę wyszukiwano w polskiej Wikipedii za pomocą API MediaWiki.

  • Wykonane zadania:

    • Pobranie listy gatunków z wcześniej zebranych danych.
    • Wysłanie zapytań do API Wikipedii dla każdej nazwy gatunku, pobierając zawartość artykułu w formacie JSON.
    • Ekstrakcja kluczowych informacji (opis, występowanie, cechy charakterystyczne) i zapis do plików .md z odpowiednimi nagłówkami.

4. Wykorzystane technologie

  • Język programowania: Python 3.10+
  • Przetwarzanie Języka Naturalnego (NLP): spaCy (model pl_core_news_lg), rank_bm25
  • Infrastruktura LLM & Wektory: ollama (lokalne modele gemma3:4b oraz bge-m3), google-genai (modele chmurowe Gemini do zaawansowanego ETL)
  • Baza Danych: PostgreSQL z rozszerzeniem pgvector hostowany na platformie Supabase, klient asyncpg
  • Sztuczna Inteligencja i Uczenie Maszynowe: torch (PyTorch do uruchomienia Rerankera), sentence-transformers (CrossEncoder), scikit-learn (KMeans)
  • Wizualizacja i Matematyka: umap-learn, plotly, numpy, pandas, scipy
  • Inne: joblib (serializacja indeksu), python-dotenv

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages