Na laboratorium przeprowadzimy grupowanie dokumentów tekstowych z użyciem 3 metod:
Narzędzia:
jupyter labEtapy:
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
docker compose builddocker compose upnotebooks. 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
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.
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,)
Wyznaczanie wektorów osadzeń za pomocą spaCy jest czasochłonną operacją. SpaCy jest zainstalowane bez wsparcia dla GPU.
Zarejestrowane czasy to:
Użyj poniższego kodu:
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 })
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).")