Laboratorium 12 + 13: grupowanie dokumentów tekstowych

Na laboratorium przeprowadzimy grupowanie dokumentów tekstowych z użyciem 3 metod:

  • k-means
  • EM
  • DBSCAN (i jego wariantu HDBSCAN)

Narzędzia:

  • Docker i docker-compose - do budowy środowiska z zainstalowanymi pakietami i udostępniania jupyter lab
  • duckdb - do zapisu danych i wykonania kwerend SQL
  • https://spacy.io/|spaCy - do wektoryzacji i tokenizacji tekstów

Etapy:

  1. Pobrane zostaną teksty 10000 artykułów z Wikipedii
  2. Zbudowane zostaną ich reprezentacji wektorowe (wektory osadzeń i wektory TF-IDF)
  3. Zastosujemy UMAP do redukcji wymiarów wektorów na potrzeby klasteryzacji i wizualizacji
  4. Do dokumentów zostaną dodane etykiety klasteryzacji dla różnych algorytmów
  5. Porównamy ich wyniki

Budowa obrazu dockera

Pobierz i rozpakuj archiwum clustering-jupyter.zip

Znajduje się tam plik pyproject.toml ze specyfikacją pakietów do zainstalowania

[project]
name = "clustering-nlp"
version = "0.1.0"
description = "Projekt na zajęcia: Eksploracja danych, 2026"
readme = "README.md"
requires-python = ">=3.12"

dependencies = [
    "pandas",
    "tabulate",
    "datasets",            # Pobieranie Wikipedii z Hugging Face
    "spacy",               # NLP i embeddingi statyczne
    "scikit-learn",        # K-means, GMM, DBSCAN
    "hdbscan",             # Warian DBSCAN
    "umap-learn",          # Redukcja wymiarów
    "plotly",              # Interaktywne wykresy
    "nbformat",            
    "tqdm",                # Paski postępu
    "matplotlib",          
    "seaborn",
    "wordcloud",             
    "jupyterlab",          
    "ipywidgets", 
    "jupysql",             # SQL w notatniku
    "duckdb",              # Baza OLAP 
    "duckdb-engine",
    "ollama",              # Opcjonalnie: biblioteka do komunikacji z lokalnym LLM
    "pyarrow",             
    "fastparquet",         
]

Plik Dockerfile

FROM python:3.12-slim

RUN apt-get update && apt-get install -y \
    curl \
    build-essential \
#    pandoc \
    && rm -rf /var/lib/apt/lists/*

# Instalacja uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app

COPY pyproject.toml .

# Instalacja zależności przez uv 
RUN uv sync

# Pobranie modelu spaCy
RUN uv run python -m spacy download pl_core_news_lg


WORKDIR /app/notebooks

oraz plik docker-compose.yaml

version: '3.8'

services:
  nlp-jupyter:
    build: .
    image: nlp-jupyter
    container_name: eksploracja_danych_nlp
    ports:
      - "9888:8888"
    volumes:
      - ./notebooks:/app/notebooks
      - ./hf_cache:/root/.cache/huggingface
    shm_size: '4gb'
    deploy:
      resources:
        limits:
          memory: 12gb
    command: >
      uv run jupyter lab 
      --ip=0.0.0.0 
      --allow-root 
      --no-browser 
      --IdentityProvider.token='nlp' 
      --notebook-dir=/app/notebooks

Uruchomienie

  • aby zbudować obraz wydaj komendę docker compose build
  • aby uruchomić kontener wydaj komendę docker compose up
  • Po uruchomieniu otwórz link http://localhost:9888/lab?token=nlp
  • Defaultowym katalogiem dla jupyetera lab jest notebooks. Znajduje się tam notatnik wikipedia.ipynb, w którym można kontynuować implementację kodu

Uwagi: Jako menadżer pakietów stosowany jest uv. Aby dodać pakiet należy w notatniku uruchomić komendę !uv add package

1. Ładowanie danych tekstowych

Kod, który pobiera 10 000 artykułów z Hugging Face znajduje się w notatniku wikipedia.ipynb. Kluczowym elementem jest losowanie wartości OFFSET - numeru dokumentu od którego zostanie ropoczęte pobieranie. Celem jest zróżnicowanie wyników i wizualizacji.

Zapisz korpus po załadowaniu dokumentów.

2. SpaCy i wektory osadzeń (embeddingi)

Spacy oferuje ciągi przetwarzania dostosowane do konkretnego języka. Użyjemy modelu o dużej dokładności pl_core_news_lg

import spacy
 
nlp = spacy.load("pl_core_news_lg")
 
doc = nlp('Ala ma kota')
print(doc.vector.shape)
for t in doc:
    print(t,t.vector.shape)

W wyniku przetwarzania dokumentu dzielony jets on na tokeny, każdy token ma przypisane rózne atrybuty, w tym wektor osadzeń (o długości 300 dla wybranego wcześniej modelu). Wektory osadzeń tokenów po zsumowaniu dają wektor dokumentu. Wynik:

(300,)
Ala (300,)
ma (300,)
kota (300,)

Dodawanie wektorów osadzeń

Wyznaczanie wektorów osadzeń za pomocą spaCy jest czasochłonną operacją. SpaCy jest zainstalowane bez wsparcia dla GPU.

Zarejestrowane czasy to:

  • 8 min z wydajnością 20 dokumentów/sekundę [wariant optymistyczny]
  • 23 minuty wydajnością 7.3 dokumentów/sekundę [wariant średni na laptopie z procesorem i7]
  • 47 minut z wydajnością 3.5 dokumentów/sekundę [wariant pesymistyczny]

Użyj poniższego kodu:

spacy_pipeline.py
import numpy as np
from tqdm import tqdm
 
processed_data = []
 
texts=df.text.to_list()
titles=df.title.to_list()
 
 
with nlp.select_pipes(enable=["tok2vec", "lemmatizer", "attribute_ruler"]):
    # pipe() jest wydajniejszy niż nlp() w pętli
    for doc in tqdm(nlp.pipe(texts, batch_size=20), total=len(texts)):
 
        valid_vectors = [t.vector for t in doc if not t.is_stop and t.is_alpha]
 
        if valid_vectors:
            # NumPy jest zoptymalizowany pod C, to będzie szybkie
            doc_vector = np.mean(valid_vectors, axis=0)
        else:
            doc_vector = np.zeros(nlp.vocab.vectors_length)
 
        processed_data.append({
            "title": titles[len(processed_data)],
            "text": texts[len(processed_data)],
            "tokens": [t.lemma_.lower() for t in doc if not t.is_stop and t.is_alpha],
            "vector": doc_vector
        })
  • W celu przyspieszenia wykonywane są tylko części pipeline, np. nie jest budowany gram syntaktyczny zdania
  • Obliczane są embeddingi dla dokumentów z pominięciem stop words i tokenów zawierających liczby

Zapisz wyniki Po zakończeniu przetwarzania koniecznie zapisz wyniki. SpaCy zwraca wektory numpy. Należy je przekonwertować do postaci list, ponieważ ułatwi to późniejsze przetwarzanie.

df_vec = pd.DataFrame(processed_data)
df_vec['vector'] = df_vec['vector'].apply(lambda x: x.tolist() if isinstance(x, np.ndarray) else x)
 
# Na przykład zapis do Parquet
df_vec.to_parquet('wiki_vect.parquet', index=False)
print("Zapisano do Parquet (wektory jako listy).")
ed/lab_clustering.txt · Last modified: 2026/05/18 05:08 by pszwed
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0