Podstawy NLP: tokenizacja, osadzanie i nowoczesny potok
Za każdym razem, gdy poprosisz o coś asystenta głosowego, przetłumacz tekst za pomocą Tłumacza Google, filtruj spam w swojej skrzynce odbiorczej lub czytaj automatyczne napisy do filmu, czy używasz Przetwarzanie języka naturalnego (NLP). Ta dyscyplina, zajmuje się na styku językoznawstwa, informatyki i sztucznej inteligencji uczenie maszyn rozumienia, interpretowania i generowania ludzkiego języka.
W ostatnich latach dziedzina NLP przeszła radykalną transformację. Minęliśmy odręczne reguły i słowniki statyczne do modeli neuronowych zdolnych zrozumieć niuanse, kontekst, a nawet ironia. BERT, GPT, Lama a modele nowej generacji nie byłyby możliwe bez podstaw, które będziemy badać w tym artykule: tokenizacja, gli osadzania e la Nowoczesny rurociąg NLP.
To pierwszy artykuł z tej serii Nowoczesne NLP: od BERT do LLM. Zaczniemy od absolutnych podstaw, krok po kroku budując niezbędne intuicje aby zrozumieć bardziej zaawansowane modele językowe. Zwrócimy szczególną uwagę do specyfiki języka włoskiego, często pomijanej w zasobach anglojęzycznych.
Czego się nauczysz
- Czym jest NLP i dlaczego jest podstawą niemal każdej współczesnej aplikacji AI
- Jak tekst jest wstępnie przetwarzany: małe litery, stopwordy, stemming i lematyzacja
- Różne podejścia do tokenizacji: na poziomie słowa, na poziomie znaku i podsłowie (BPE, WordPiece, SentencePiece)
- Klasyczne reprezentacje tekstowe: Bag of Words i TF-IDF
- Osadzanie słów: Word2Vec, GloVe i geometryczna intuicja znaczenia
- Osadzanie kontekstowe: od reprezentacji statycznych do BERT
- Osadzanie zdań i ich praktyczne zastosowania
- Nowoczesny potok NLP: od surowego tekstu do przewidywania
- Specyfika wstępnego przetwarzania dla języka włoskiego
- Kompletny, kompleksowy przykład z kodem Python
Przegląd serii
| # | Przedmiot | Centrum |
|---|---|---|
| 1 | Jesteś tutaj - Podstawy NLP | Tokenizacja, osadzanie, potoki |
| 2 | BERT i Transformers | Uwaga na architekturę, szkolenie wstępne |
| 3 | Analiza sentymentów | Klasyfikacja tekstu za pomocą BERT |
| 4 | Rozpoznawanie nazwanych podmiotów | Ekstrakcja bytów z tekstu |
| 5 | Transformatory HuggingFace | Wstępnie przeszkolona biblioteka i modele |
| 6 | Dostrajanie modeli | Dostosuj BERT do swojej domeny |
| 7 | NLP dla języka włoskiego | Szablony i zasoby dla języka włoskiego |
| 8 | Od BERT do LLM | GPT, LLaMA i generowanie tekstu |
1. Wstępne przetwarzanie tekstu: Przygotuj dane
Zanim jakikolwiek model NLP będzie mógł pracować z tekstem, musi to być model NLP czyste i znormalizowane. Szorstki i hałaśliwy tekst: interpunkcja, wielkie litery, skróty, emoji, HTML, URL. Przetwarzanie wstępne zamienia ten chaos w ustrukturyzowanym i spójnym formacie.
1.1 Małe litery i normalizacja
Pierwszym krokiem jest konwersja całego tekstu na małe litery. W przypadku komputera „Dom”, „dom” i „HOME” to trzy zupełnie różne ciągi znaków. Małe litery ujednolicają je.
import re
import unicodedata
def normalize_text(text: str) -> str:
"""Normalizzazione base del testo."""
# Lowercasing
text = text.lower()
# Rimozione accenti (opzionale, NON consigliato per italiano)
# text = unicodedata.normalize('NFKD', text)
# Rimozione punteggiatura
text = re.sub(r'[^\w\s]', '', text)
# Rimozione spazi multipli
text = re.sub(r'\s+', ' ', text).strip()
return text
testo = "L'NLP e FANTASTICO! Analizza testi in 50+ lingue."
print(normalize_text(testo))
# Output: "lnlp e fantastico analizza testi in 50 lingue"
Zwróć uwagę na akcenty w języku włoskim
W wielu anglojęzycznych procesach NLP usuwanie akcentu jest standardowym krokiem. W języku włoskim jednak akcenty zmieniają znaczenie słów: "Ale" (spójnik) vs "Ale" (drzewo), "I" (spójnik) vs "I" (czasownik być). Nigdy nie usuwaj akcentów podczas pracy z tekstem w języku włoskim.
1.2 Stopwords: słowa bez treści informacyjnej
Le słowa stopu Są to bardzo częste słowa, które nie mają większego znaczenia semantycznego: rodzajniki, przyimki, spójniki. Usunięcie ich zmniejsza wymiarowość danych, np pomaga modelom skupić się na znaczących słowach.
Stopwordy włoskie i angielskie
| Język | Przykłady słów stop | Typowy licznik |
|---|---|---|
| angielski | the, jest, w, na, a, an i lub | ~180 słów |
| włoski | the, the, the, of, from, in, with, on, for, that i, not, a | ~300 słów |
W języku włoskim występuje więcej stopwordów niż w języku angielskim ze względu na większe bogactwo artykułów (il, lo, la, i, gli, le), przyimki artykułowane (del, dello, della, nei, nei, nelle) i formy czasowników pomocniczych.
# Approccio 1: NLTK
from nltk.corpus import stopwords
import nltk
nltk.download('stopwords')
stop_it = set(stopwords.words('italian'))
print(f"Stopwords italiane NLTK: {len(stop_it)}")
# Output: Stopwords italiane NLTK: 279
testo = "il gatto mangia il pesce sul tavolo della cucina"
tokens = testo.split()
filtrato = [t for t in tokens if t not in stop_it]
print(filtrato)
# Output: ['gatto', 'mangia', 'pesce', 'tavolo', 'cucina']
# Approccio 2: spaCy (più completo)
import spacy
nlp = spacy.load("it_core_news_lg")
doc = nlp("il gatto mangia il pesce sul tavolo della cucina")
filtrato_spacy = [token.text for token in doc if not token.is_stop]
print(filtrato_spacy)
# Output: ['gatto', 'mangia', 'pesce', 'tavolo', 'cucina']
1.3 Stemming a lematyzacja
Obie techniki redukują słowa do ich podstawowej formy, ale robią to na bardzo różne sposoby.
Stemming a lematyzacja – porównanie
| Czekam | Przybitka | Lematyzacja |
|---|---|---|
| Metoda | Wytnij przyrostki za pomocą reguł heurystycznych | Skorzystaj ze słownika i analizy morfologicznej |
| Wynik | Rdzeń (nie zawsze prawdziwe słowo) | Lemat (prawdziwe słowo ze słownika) |
| Przykład IT | „jesz” -> „jesz” | „jeść” -> „jeść” |
| Prędkość | Bardzo szybko | Wolniejsze (wymaga słownika) |
| Precyzja | Niski (często przesadzający) | Wysoki (poprawne formy) |
| Dla Włocha | Łodyga Śnieżki (Porter Włoski) | spaCy it_core_news_lg |
from nltk.stem.snowball import SnowballStemmer
import spacy
# Stemming con Snowball
stemmer = SnowballStemmer("italian")
parole = ["mangiando", "mangiare", "mangiato", "correre", "correndo", "bellissimo"]
stems = [stemmer.stem(p) for p in parole]
print(dict(zip(parole, stems)))
# {'mangiando': 'mang', 'mangiare': 'mang', 'mangiato': 'mang',
# 'correre': 'corr', 'correndo': 'corr', 'bellissimo': 'bellissim'}
# Lemmatization con spaCy
nlp = spacy.load("it_core_news_lg")
doc = nlp("Le ragazze stavano mangiando le mele più belle")
lemmi = [(token.text, token.lemma_, token.pos_) for token in doc]
for testo, lemma, pos in lemmi:
print(f" {testo:15s} -> {lemma:15s} ({pos})")
# le -> il (DET)
# ragazze -> ragazza (NOUN)
# stavano -> stare (AUX)
# mangiando -> mangiare (VERB)
# le -> il (DET)
# mele -> mela (NOUN)
# più -> più (ADV)
# belle -> bello (ADJ)
Dla Włochów, lematyzacja za pomocą spaCy i prawie zawsze preferowane
do stemplowania. Model it_core_news_lg zawiera 500 000 wektorów słów
i obsługuje tokenizację, tagowanie POS, analizowanie zależności, NER i lematyzację.
2. Tokenizacja: jak maszyny czytają tekst
La tokenizacja oraz proces dzielenia tekstu na odrębne jednostki dzwoni znak. Oraz pierwszy i najbardziej krytyczny krok w każdym procesie NLP: jakość tokenizacji bezpośrednio wpływa na wydajność każdego kolejnego modelu.
Istnieją trzy podstawowe podejścia, każde z różnymi korzyściami i kompromisami.
2.1 Tokenizacja na poziomie słowa
Najbardziej intuicyjne podejście: każde słowo staje się symbolem.
# Approccio naive: split per spazio
testo = "L'intelligenza artificiale cambia il mondo"
tokens_naive = testo.split()
print(tokens_naive)
# ["L'intelligenza", 'artificiale', 'cambia', 'il', 'mondo']
# Problema: "L'intelligenza" e un singolo token!
# Approccio migliore: spaCy
import spacy
nlp = spacy.load("it_core_news_lg")
doc = nlp(testo)
tokens_spacy = [token.text for token in doc]
print(tokens_spacy)
# ["L'", 'intelligenza', 'artificiale', 'cambia', 'il', 'mondo']
# spaCy gestisce correttamente le elisioni italiane
Ograniczenia tokenizacji na poziomie słowa
- Ogromne słownictwo: Każde unikalne słowo wymaga wpisu słownictwa. Włoski ma setki tysięcy form fleksyjnych
- Słowa spoza słownika (OOV): Słowa nigdy nie widziane podczas szkolenia stają się <UNK> (nieznane)
- Brak podziału morfologicznego: „jeść”, „jeść”, „zjadać” to trzy zupełnie odrębne, niepowiązane ze sobą tokeny
2.2 Tokenizacja na poziomie znaku
Z drugiej strony każda postać staje się symbolem. Słownictwo jest malutkie (26 liter + cyfry + znaki interpunkcyjne), ale sekwencje stają się bardzo długie.
Testo: "ciao mondo"
Word-level: ["ciao", "mondo"] -> 2 token
Char-level: ["c","i","a","o"," ","m","o","n","d","o"] -> 10 token
Testo di 1000 parole:
Word-level: ~1.000 token
Char-level: ~5.000 token (5x più lungo!)
Tokenizacja znaków rozwiązuje problem nieznanych słów (dowolne słowo można przedstawić), ale bardzo długie sekwencje utrudniają tworzenie modeli uchwyć w tekście dalekosiężne relacje.
2.3 Tokenizacja podsłów: optymalny kompromis
La tokenizacja podsłów i metoda stosowana we wszystkich nowoczesnych modelach (BERT, GPT, LLaMA, T5). Pomysł jest genialny: pospolite słowa pozostają w całości, natomiast słowa rzadkie są podzielone na podjednostki (słowa podrzędne), które model już widział.
Algorytmy tokenizacji podsłów
| Algorytm | Używany przez | Strategia | Kierunek |
|---|---|---|---|
| BPE (Kodowanie par bajtów) | GPT-2, GPT-3, GPT-4, LLaMA, RoBERTa | Iteracyjne łączenie najczęstszych par | Od dołu do góry |
| Kawałek słowa | BERT, DistilBERT, ELEKTRA | Scal, który maksymalizuje prawdopodobieństwo | Od dołu do góry |
| Fragment zdania | T5, ALBERT, XLNet, mBART | Traktuj tekst jako surowy strumień znaków | Niezależny od języka |
| Unigram | Fragment zdania (opcjonalnie), ALBERT | Zaczyna od dużego słownictwa, usuwa mniej przydatne tokeny | Z góry na dół |
Jak działa BPE (kodowanie par bajtów).
BPE zaczyna się od pojedynczych znaków i iteracyjnie łączy najczęstsze pary aż osiągniesz żądany rozmiar słownictwa.
Corpus: "basso basso bassa basso"
Passo 0 - Vocabolario iniziale (caratteri):
b, a, s, o
Passo 1 - Coppia più frequente: (s, s) -> "ss"
b a ss o b a ss o b a ss a b a ss o
Passo 2 - Coppia più frequente: (a, ss) -> "ass"
b ass o b ass o b ass a b ass o
Passo 3 - Coppia più frequente: (b, ass) -> "bass"
bass o bass o bass a bass o
Passo 4 - Coppia più frequente: (bass, o) -> "basso"
basso basso bass a basso
Vocabolario finale: [b, a, s, o, ss, ass, bass, basso]
WordPiece kontra BPE
WordPiece stosuje podobne podejście do BPE, ale zamiast wybierać parę plus
częsty, wybierz ten, który maksymalizuje prawdopodobieństwo korpusu
szkolenia. W praktyce WordPiece preferuje połączenia, które dają bardziej przydatne tokeny
dla modelu języka, a nie tylko tych najpowszechniejszych. Tokeny, które się nie uruchamiają
słowo jest poprzedzone prefiksem ##.
Fragment zdania: Niezależność językowa
Kluczowa różnica w SentencePiece polega na tym nie wymaga wstępnej tokenizacji. BPE i WordPiece zakładają, że tekst jest już podzielony na słowa (zazwyczaj spacją), co działa dobrze w przypadku angielskiego i włoskiego, ale zawodzi w przypadku języków takich jak chiński, japoński czy tajski nie używają spacji między słowami. SentencePiece traktuje tekst jako surowy strumień bajtów, czyniąc go naprawdę niezależnym językowo.
3. Praktyczny przykład: Tokenizacja za pomocą HuggingFace
Przyjrzyjmy się konkretnie, jak BERT i GPT-2 tokenizują ten sam włoski tekst.
Będziemy korzystać z biblioteki transformers przez HuggingFace.
from transformers import AutoTokenizer
# Testo di esempio in italiano
testo = "L'intelligenza artificiale sta rivoluzionando il mondo"
# --- BERT (WordPiece) ---
bert_tok = AutoTokenizer.from_pretrained("dbmdz/bert-base-italian-cased")
bert_tokens = bert_tok.tokenize(testo)
bert_ids = bert_tok.encode(testo)
print("BERT tokens:", bert_tokens)
print("BERT IDs: ", bert_ids)
# BERT tokens: ['L', "'", 'intelligenza', 'artificiale', 'sta',
# 'rivoluzionando', 'il', 'mondo']
# BERT IDs: [102, 55, 7, 5765, 6892, 379, 28648, 42, 1601, 103]
# --- GPT-2 (BPE) ---
gpt2_tok = AutoTokenizer.from_pretrained("gpt2")
gpt2_tokens = gpt2_tok.tokenize(testo)
gpt2_ids = gpt2_tok.encode(testo)
print("\nGPT-2 tokens:", gpt2_tokens)
print("GPT-2 IDs: ", gpt2_ids)
# GPT-2 tokens: ['L', "'", 'int', 'ell', 'ig', 'enza', ' art',
# 'ific', 'iale', ' sta', ' riv', 'oluz', 'ion',
# 'ando', ' il', ' mondo']
# --- Confronto ---
print(f"\nBERT: {len(bert_tokens)} token")
print(f"GPT-2: {len(gpt2_tokens)} token")
Kluczowe obserwacje
- BERT włoski (
dbmdz/bert-base-italian-cased) rozpoznaje „inteligencję” i „rivoluzionando” jako tokeny całkowite, ponieważ jego słownictwo zostało przeszkolone na tekście włoskim - GPT-2 dzieli włoskie słowa na znacznie więcej podsłów, ponieważ jego słownictwo zostało przeszkolone głównie na tekście angielskim
- Tokenizer przeszkolony w zakresie języka docelowego generuje mniej tokenów, co oznacza więcej kontekstu w oknie uwagi e niższe koszty na token
- BERT dodaje specjalne tokeny:
[CLS]na początku i[SEP]w końcu. GPT-2 nie
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("dbmdz/bert-base-italian-cased")
# Dimensione vocabolario
print(f"Vocabolario BERT italiano: {tok.vocab_size} token")
# Output: Vocabolario BERT italiano: 31102 token
# Token speciali
print(f"[CLS] = {tok.cls_token} (ID: {tok.cls_token_id})")
print(f"[SEP] = {tok.sep_token} (ID: {tok.sep_token_id})")
print(f"[PAD] = {tok.pad_token} (ID: {tok.pad_token_id})")
print(f"[UNK] = {tok.unk_token} (ID: {tok.unk_token_id})")
print(f"[MASK] = {tok.mask_token} (ID: {tok.mask_token_id})")
# Decodifica: da token IDs -> testo
ids = tok.encode("NLP e fantastico")
print(f"\nEncode: {ids}")
print(f"Decode: {tok.decode(ids)}")
# Decode: [CLS] NLP e fantastico [SEP]
4. Zbiór słów i TF-IDF: reprezentacje klasyczne
Przed osadzeniem słów tekst był reprezentowany jako rzadkie wektory na podstawie częstotliwości słów. Metody te są nadal używane w wielu kontekstach a zrozumienie ich ograniczeń pomaga zrozumieć, dlaczego osadzanie było rewolucją.
4.1 Worek słów (BoW)
Model Torba słów reprezentuje dokument jako wektor gdzie każda pozycja odpowiada słowu słownikowemu oraz wartości i liczbie wystąpienia tego słowa w dokumencie.
from sklearn.feature_extraction.text import CountVectorizer
documenti = [
"il gatto mangia il pesce",
"il cane mangia la carne",
"il gatto insegue il cane"
]
vectorizer = CountVectorizer()
bow_matrix = vectorizer.fit_transform(documenti)
print("Vocabolario:", vectorizer.get_feature_names_out())
# ['cane', 'carne', 'gatto', 'il', 'insegue', 'la', 'mangia', 'pesce']
print("\nMatrice BoW:")
print(bow_matrix.toarray())
# [[0, 0, 1, 2, 0, 0, 1, 1], # doc 1
# [1, 1, 0, 1, 0, 1, 1, 0], # doc 2
# [1, 0, 1, 2, 1, 0, 0, 0]] # doc 3
4.2 TF-IDF (częstotliwość terminów – odwrotna częstotliwość dokumentów)
TF-IDF ulepsza BoW, dobierając słowa do własnych potrzeb względne znaczenie. Słowa, które występują często w dokumencie, ale są rzadkie w całym korpusie, zyskują większą wagę. Powszechnie używane słowa (takie jak „the”, „the”) mają niewielką wagę.
TF-IDF(t, d) = TF(t, d) x IDF(t)
dove:
TF(t, d) = frequenza del termine t nel documento d
IDF(t) = log(N / df(t))
N = numero totale di documenti
df(t) = numero di documenti che contengono il termine t
Esempio:
Parola "gatto" in documento 1:
TF = 1/5 = 0.2 (1 occorrenza su 5 parole)
IDF = log(3/2) = 0.405 (appare in 2 documenti su 3)
TF-IDF = 0.2 x 0.405 = 0.081
from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np
documenti = [
"il gatto mangia il pesce",
"il cane mangia la carne",
"il gatto insegue il cane"
]
tfidf = TfidfVectorizer()
tfidf_matrix = tfidf.fit_transform(documenti)
print("Feature:", tfidf.get_feature_names_out())
print("\nMatrice TF-IDF (arrotondata):")
print(np.round(tfidf_matrix.toarray(), 3))
# "pesce" e "carne" hanno pesi più alti perchè appaiono
# in un solo documento (più discriminanti)
Granice BoW i TF-IDF
- Brak semantyki: „pies” i „ps” są zupełnie inne; „bank” (rzeka) i „bank” (instytucja) są identyczne
- Brak zamówienia: „Kot zjada mysz” i „Mysz zjada kota” mają tę samą reprezentację
- Wysoka wymiarowość: Słownictwo składające się ze 100 000 słów tworzy 100 000 wektorów wymiarowych, prawie same zera (wektory rzadkie)
- Bez uogólnień: Nie oddają relacji między słowami („król” i „królowa” nie mają bliskości)
5. Osadzanie słów: znaczenie jako geometria
I osadzanie słów zrewolucjonizowali NLP, przekształcając słowa w gęstych, niskowymiarowych wektorach (zwykle 100–300 wymiarów), które przechwytują relacje semantyczne między słowami. Będą miały dwa słowa o podobnym znaczeniu wektory sąsiedzi w przestrzeni wektorowej.
5.1 Word2Vec: Wynalazek, który wszystko zmienił
Wprowadzony przez Tomasa Mikolova i współpracowników (Google, 2013), Word2Stary uczy się wektorów słów zaczynając od kontekstu, w jakim się pojawiają. Intuicja hipoteza podstawowa i dystrybucyjna: „słowo charakteryzuje się towarzystwie, w którym przebywa” (J.R. Firth, 1957).
Dwie architektury Word2Vec
| Architektura | Wejście | Wyjścia | Intuicja |
|---|---|---|---|
| CBOW (Ciągły worek słów) | Kontekst (słowa otaczające) | Docelowe słowo | Biorąc pod uwagę kontekst „___ zjada”, przewiduj „kot” |
| Pomiń gram | Docelowe słowo | Kontekst (słowa otaczające) | Biorąc pod uwagę słowo „kot”, przewiduj „the”, „jeść” itp. |
W rzeczywistości, Pomiń gram działa najlepiej w przypadku małych zestawów danych i przechwytywania rzadkie słowa są lepsze. CBOW i szybciej oraz działa dobrze z częstymi słowami.
Frase: "il gatto nero mangia il pesce fresco"
^
parola target
Con window_size = 2, Skip-gram impara:
gatto -> il (contesto a sinistra, distanza 1)
gatto -> nero (contesto a destra, distanza 1)
gatto -> mangia (contesto a destra, distanza 2)
Dopo milioni di frasi, parole che appaiono in contesti
simili avranno vettori simili:
gatto ~ felino ~ micio (contesti simili: "il ___ mangia")
cane ~ canino ~ cucciolo (contesti simili: "il ___ corre")
5.2 Arytmetyka słów
Najbardziej zaskakującą właściwością osadzania słów jest to, że relacje semantyczne stają się operacjami algebraicznymi na przewoźnikach. Słynna analogia:
król - mężczyzna + kobieta = królowa
To nie przypadek: wektor prowadzący od „mężczyzny” do „kobiety” jest tym samym, który prowadzi od „króla” do „królowej”. Działa to w przypadku wielu relacji: kraj-stolica, czasownik-czas przeszły, najwyższy przymiotnik.
import gensim.downloader as api
# Carica word embeddings pre-addestrati
model = api.load("word2vec-google-news-300")
# Analogia: re - uomo + donna = ?
result = model.most_similar(
positive=["king", "woman"],
negative=["man"],
topn=3
)
print("king - man + woman =")
for word, score in result:
print(f" {word}: {score:.4f}")
# king - man + woman =
# queen: 0.7118
# monarch: 0.6189
# princess: 0.5902
# Similarità tra parole
print(f"\ncat ~ dog: {model.similarity('cat', 'dog'):.4f}")
print(f"cat ~ car: {model.similarity('cat', 'car'):.4f}")
# cat ~ dog: 0.7609
# cat ~ car: 0.2004
5.3 GloVe: Globalne wektory do reprezentacji słów
Rękawica (Stanford, 2014) przyjmuje inne podejście: zamiast przewidywać kontekst słowo po słowie, taki jak Word2Vec, GloVe tworzy pierwszy globalna macierz współwystępowań całego korpusu, a następnie rozłóż na czynniki tę macierz, aby otrzymać wektory. Łączy zalety metod opartych na globalne statystyki z wynikami lokalnego uczenia się Word2Vec.
Word2Vec kontra GloVe
| Czekam | Word2Stary | Rękawica |
|---|---|---|
| Metoda | Predykcyjne (sieć neuronowa) | Oparta na liczbie (faktoryzacja macierzy) |
| Kontekst | Okno lokalne | Globalne statystyki korpusowe |
| Szkolenie | Online (przewija tekst) | Partia (pełna tablica) |
| Typowe rozmiary | 100, 200, 300 | 50, 100, 200, 300 |
| Wydajność | Doskonały do analogii | Świetne ze względu na podobieństwo |
6. Osadzenie kontekstowe: to samo słowo, różne znaczenia
Word2Vec i GloVe mają podstawowe ograniczenie: przypisują pojedynczy wektor z każdym słowem, niezależnie od kontekstu. Ale języka jest pełno dwuznaczność: słowo „bank” ma zupełnie inne znaczenie w „biurko szkolne” i „biurko w Neapolu”.
The osadzania kontekstowe rozwiązać ten problem: wektor wyrazu zależy od całego zdania, w którym ono występuje. Jest to podejście stosowane przez BERT, GPT i wszystkie modele oparte na transformatorach.
Osadzanie statyczne a kontekstowe
| Czekam | Statyczne (Word2Vec, GloVe) | Kontekstowe (BERT, GPT) |
|---|---|---|
| Wektor dla słowa | Stały, zawsze taki sam | Różne w zależności od kontekstu |
| Polisemia | Niezarządzane („bank” ma tylko jednego przewoźnika) | Zarządzane („transakcja” ma różne wektory dla każdego znaczenia) |
| Model | Tabela przeglądowa | Głęboka sieć neuronowa (transformator) |
| Rozmiar modelu | Kilka MB | Setki MB lub GB |
| Prędkość | Natychmiastowy | Wymaga podania w przód |
from transformers import AutoTokenizer, AutoModel
import torch
import torch.nn.functional as F
# Carica BERT italiano
tokenizer = AutoTokenizer.from_pretrained("dbmdz/bert-base-italian-cased")
model = AutoModel.from_pretrained("dbmdz/bert-base-italian-cased")
def get_word_embedding(sentence: str, word: str):
"""Ottieni l'embedding contestuale di una parola nella frase."""
inputs = tokenizer(sentence, return_tensors="pt")
tokens = tokenizer.tokenize(sentence)
with torch.no_grad():
outputs = model(**inputs)
# outputs.last_hidden_state: [batch, seq_len, hidden_dim]
embeddings = outputs.last_hidden_state[0] # [seq_len, 768]
# Trova l'indice del token target
word_idx = tokens.index(word) + 1 # +1 per [CLS]
return embeddings[word_idx]
# "banco" in contesti diversi
emb_scuola = get_word_embedding("Il banco di scuola e rotto", "banco")
emb_banca = get_word_embedding("Il banco di Napoli e storico", "banco")
emb_pesce = get_word_embedding("Il banco del pesce e fresco", "banco")
# Calcola similarità coseno
sim_12 = F.cosine_similarity(emb_scuola.unsqueeze(0), emb_banca.unsqueeze(0))
sim_13 = F.cosine_similarity(emb_scuola.unsqueeze(0), emb_pesce.unsqueeze(0))
sim_23 = F.cosine_similarity(emb_banca.unsqueeze(0), emb_pesce.unsqueeze(0))
print(f"banco(scuola) ~ banco(banca): {sim_12.item():.4f}")
print(f"banco(scuola) ~ banco(pesce): {sim_13.item():.4f}")
print(f"banco(banca) ~ banco(pesce): {sim_23.item():.4f}")
# I vettori saranno DIVERSI perchè BERT capisce il contesto!
Jest to zasadniczy skok koncepcyjny: w przypadku BERT słowo „bank” już nie istnieje stałe znaczenie. Jego wektor zmienia się w zależności od otoczenia, podobnie jak dzieje się w ludzkim rozumieniu języka.
7. Osadzanie zdań: jeden wektor na całe zdanie
Często nie potrzebujemy osadzania pojedynczego słowa, ale npcałe zdanie lub akapit. TO osadzanie zdań kompresują znaczenie tekstu o dowolnej długości w jednym wektorze o stałym rozmiarze.
Model referencyjny tj Zdanie-BERT (SBERT), co się zmienia
architekturę BERT do tworzenia osadzania zdań zoptymalizowanych pod kątem porównań
podobieństwa. Dla Włocha model paraphrase-multilingual-MiniLM-L12-v2
obsługuje ponad 50 języków z 384 wektorami wymiarowymi.
Zastosowania osadzania zdań
| Aplikacja | Opis | Jak to działa |
|---|---|---|
| Wyszukiwanie semantyczne | Szukaj według znaczenia, a nie słowa kluczowego | Osadź zapytanie, wyszukaj najbliższe dokumenty |
| Klastrowanie | Grupuj podobne teksty automatycznie | K-średnie lub HDBSCAN na osadzaniu |
| Wykrywanie duplikatów | Znajdź duplikaty lub prawie duplikaty | Próg podobieństwa cosinusa > 0,9 |
| Klasyfikacja Zero-Shot | Ranking bez danych treningowych | Porównaj osadzanie tekstu z osadzaniem etykiet |
from sentence_transformers import SentenceTransformer, util
# Modello multilingue (supporta italiano)
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
# Frasi in italiano
frasi = [
"Il gatto dorme sul divano",
"Il felino riposa sul sofa",
"La borsa e salita oggi",
"I mercati finanziari sono in crescita",
"Ho comprato un nuovo computer portatile"
]
# Genera embeddings (1 vettore per frase, 384 dimensioni)
embeddings = model.encode(frasi, convert_to_tensor=True)
print(f"Shape: {embeddings.shape}") # [5, 384]
# Calcola matrice di similarità
cosine_scores = util.cos_sim(embeddings, embeddings)
print("\nMatrice di Similarità:")
for i in range(len(frasi)):
for j in range(i + 1, len(frasi)):
print(f" {frasi[i][:40]:40s} <-> {frasi[j][:40]:40s}")
print(f" Similarità: {cosine_scores[i][j]:.4f}")
# Risultati attesi:
# "gatto dorme" <-> "felino riposa" : ~0.85 (molto simili)
# "borsa salita" <-> "mercati crescita": ~0.70 (correlati)
# "gatto dorme" <-> "borsa salita" : ~0.10 (non correlati)
8. Nowoczesny rurociąg NLP
Widzieliśmy poszczególne elementy. Teraz zestawmy je razem, aby zrozumieć, jak to działa jeden Nowoczesny, kompleksowy potok NLP, ten używany przez BERT, GPT i wszystkie modele oparte na Transformerze.
Testo Grezzo
|
v
[1. TOKENIZZAZIONE]
Input: "L'intelligenza artificiale e fantastica"
Output: ["L'", "intelligenza", "artificiale", "e", "fantastica"]
|
v
[2. ENCODING (Token -> ID)]
Input: ["L'", "intelligenza", "artificiale", "e", "fantastica"]
Output: [102, 55, 5765, 6892, 15, 23456, 103]
^[CLS] ^[SEP]
|
v
[3. EMBEDDING LAYER]
Input: [102, 55, 5765, 6892, 15, 23456, 103]
Output: Matrice [7 x 768] - un vettore 768-dim per ogni token
|
v
[4. TRANSFORMER ENCODER/DECODER]
Self-Attention: ogni token "guarda" tutti gli altri
Input: Matrice [7 x 768]
Output: Matrice [7 x 768] (vettori contestualizzati)
|
v
[5. TASK HEAD]
Classificazione: [CLS] embedding -> softmax -> classe
NER: ogni token -> etichetta entità
Generazione: ultimo token -> prossimo token
QA: posizione inizio/fine risposta
Rola tokena [CLS]
W BERT specjalny token [CLS] jest wstawiany na początku każdego wejścia.
Po przejściu przez wszystkie warstwy Transformatora następuje jego osadzenie
całą sekwencję. I używane jako dane wejściowe do zadań klasyfikacyjnych
(analiza nastrojów, wykrywanie spamu itp.).
9. NLP dla języka włoskiego: specyfika i narzędzia
Język włoski stwarza dla NLP wyjątkowe wyzwania, które odróżniają go od języka angielskiego i z innych języków. Znajomość tych cech jest niezbędna do budowania systemów Skuteczne NLP dla języka włoskiego.
9.1 Wyzwania językowe języka włoskiego
Specjalne cechy języka włoskiego w NLP
| Wyzwanie | Opis | Przykład |
|---|---|---|
| Bogata morfologia | Każdy czasownik ma dziesiątki form sprzężonych | „jeść” ma ponad 50 form (jem, jem, jem, jemy...) |
| Elizje i apostrofy | Artykuły i przyimki łączą się | „człowiek”, „sztuka”, „przyjaciel”, „w tym roku” |
| Artykułowane przyimki | Przyimek + rodzajnik jednym słowem | „del” (di+il), „nello” (in+lo), „sulla” (su+la) |
| Znaczące akcenty | Zmieniają znaczenie | „e” (i) vs „e” (jest), „da” (od) vs „da” (daje) |
| Zaimki klityczne | Trzymają się czasownika | „daj mi to” (daj+mi+to), „przynieś mu to” |
| Bezpłatne zamówienie | SVO nie jest obowiązkowe | „Marco zjada ciasto” = „Marco zjada ciasto” |
9.2 Wstępnie wytrenowane modele dla języka włoskiego
Główne włoskie modele
| Model | Podstawowy | Zadania | Repozytoria |
|---|---|---|---|
| dbmdz/bert-base-włoski-cased | BERT | Włoski NLP ogólnego przeznaczenia | Przytulana twarz |
| Alberto | BERT | Włoskie media społecznościowe (Twitter) | Przytulana twarz |
| poczuj to-włoskie-nastroje | UmBERTo | Analiza nastrojów Włochów | MilaNLProc |
| poczuj-włoskie-emocje | UmBERTo | Wykrywanie emocji (radość, złość, strach, smutek) | MilaNLProc |
| Włosko-prawny-BERT | BERT | Włoskie teksty prawne | dlicari |
| DeepMount00/włoski_NER_XXL | BERT | Rozpoznawanie podmiotu nazwanego w języku włoskim | Przytulana twarz |
| it_core_news_lg | spaCy CNN | Tokenizacja, POS, NER, lemat, parsowanie | spaCy |
9.3 Przetwarzanie wstępne specyficzne dla języka włoskiego
import spacy
import re
class ItalianPreprocessor:
"""Pipeline di preprocessing specifica per l'italiano."""
def __init__(self):
self.nlp = spacy.load("it_core_news_lg")
# Stopwords aggiuntive regionali/informali
self.custom_stops = {
"cioe", "quindi", "comunque", "praticamente",
"allora", "insomma", "magari", "ecco", "tipo",
"boh", "mah", "vabbe", "ok", "okay"
}
def preprocess(self, text: str, remove_stops: bool = True,
lemmatize: bool = True) -> list[str]:
"""Preprocessing completo per testo italiano."""
# 1. Normalizzazione base
text = text.lower()
text = re.sub(r'http\S+|www\.\S+', '', text) # rimuovi URL
text = re.sub(r'[^\w\s\']', '', text) # mantieni apostrofi
text = re.sub(r'\d+', '', text) # rimuovi numeri
text = re.sub(r'\s+', ' ', text).strip()
# 2. Analisi con spaCy
doc = self.nlp(text)
# 3. Filtraggio e lemmatizzazione
tokens = []
for token in doc:
# Salta punteggiatura e spazi
if token.is_punct or token.is_space:
continue
# Salta stopwords se richiesto
if remove_stops and (token.is_stop or
token.text in self.custom_stops):
continue
# Lemmatizza o usa la forma originale
word = token.lemma_ if lemmatize else token.text
if len(word) > 1: # salta caratteri singoli
tokens.append(word)
return tokens
# Esempio d'uso
prep = ItalianPreprocessor()
testo = """L'intelligenza artificiale sta rivoluzionando
il modo in cui le aziende italiane gestiscono i loro
processi, cioe praticamente tutto sta cambiando."""
risultato = prep.preprocess(testo)
print("Token processati:", risultato)
# ['intelligenza', 'artificiale', 'rivoluzionare', 'modo',
# 'azienda', 'italiano', 'gestire', 'processo', 'cambiare']
10. Kompleksowy przykład: wyszukiwanie semantyczne w języku włoskim
Połączmy wszystko, czego się nauczyliśmy, w jeden kompletny przykład: a wyszukiwanie semantyczne na zbiorze tekstów włoskich. Biorąc pod uwagę zestaw dokumentów i na podstawie zapytania użytkownika, znajdziemy najodpowiedniejsze dokumenty osadzanie zdań.
from sentence_transformers import SentenceTransformer, util
import torch
class SemanticSearchIT:
"""Motore di ricerca semantica per testi italiani."""
def __init__(self, model_name: str =
"paraphrase-multilingual-MiniLM-L12-v2"):
self.model = SentenceTransformer(model_name)
self.documents: list[str] = []
self.embeddings = None
def index_documents(self, documents: list[str]) -> None:
"""Indicizza i documenti calcolando gli embeddings."""
self.documents = documents
self.embeddings = self.model.encode(
documents,
convert_to_tensor=True,
show_progress_bar=True
)
print(f"Indicizzati {len(documents)} documenti")
print(f"Shape embeddings: {self.embeddings.shape}")
def search(self, query: str, top_k: int = 3) -> list[dict]:
"""Cerca i documenti più rilevanti per la query."""
query_embedding = self.model.encode(
query, convert_to_tensor=True
)
scores = util.cos_sim(query_embedding, self.embeddings)[0]
top_results = torch.topk(scores, k=min(top_k, len(self.documents)))
results = []
for score, idx in zip(top_results.values, top_results.indices):
results.append({
"documento": self.documents[idx],
"score": round(score.item(), 4),
"indice": idx.item()
})
return results
# --- Esempio d'uso ---
corpus = [
"Python e un linguaggio di programmazione versatile e facile da imparare",
"Il machine learning permette ai computer di imparare dai dati",
"Angular e un framework per costruire applicazioni web moderne",
"La pasta alla carbonara e un piatto tipico della cucina romana",
"I database relazionali usano SQL per interrogare i dati",
"Il deep learning utilizza reti neurali con molti strati nascosti",
"Roma e la capitale d'Italia e ha una storia millenaria",
"Le API REST permettono la comunicazione tra servizi web",
"Il Natural Language Processing analizza e comprende il testo",
"La pizza napoletana e patrimonio UNESCO dal 2017"
]
# Crea il motore di ricerca e indicizza
search_engine = SemanticSearchIT()
search_engine.index_documents(corpus)
# Esegui alcune ricerche
queries = [
"come analizzare il linguaggio naturale",
"framework per sviluppo frontend",
"cucina tradizionale italiana"
]
for query in queries:
print(f"\nQuery: '{query}'")
print("-" * 60)
results = search_engine.search(query, top_k=3)
for i, r in enumerate(results, 1):
print(f" {i}. [{r['score']:.4f}] {r['documento']}")
Oczekiwane wyniki
Wyszukiwanie semantyczne obejmuje oznaczający, nie tylko słowa. Na przykład:
- „jak analizować język naturalny” znajdzie dokument NLP, nawet jeśli nie zawiera dokładnie tych słów
- "framework dla rozwoju frontendu" znajdzie Angulara, nawet jeśli w dokumencie nie pojawia się „frontend” (ale „nowoczesne aplikacje internetowe” są semantycznie powiązane)
- "tradycyjna kuchnia włoska" znajdzie zarówno carbonarę, jak i pizzę, ponieważ model rozumie zależność semantyczną
Mapa drogowa: Stąd do LLM
W tym artykule zbudowaliśmy podstawy współczesnego NLP, zaczynając od wstępne przetwarzanie tekstu aż do osadzania kontekstowego i całego potoku. Podsumujmy przebytą drogę:
Podsumowanie koncepcji
| Pojęcie | Co On robi | Ewolucja |
|---|---|---|
| Przetwarzanie wstępne | Czyści i normalizuje nieprzetworzony tekst | Reguły ręczne -> potok spaCy |
| Tokenizacja | Dzieli tekst na oddzielne jednostki | Słowo -> Znak -> Podsłowo (BPE/WordPiece) |
| BoW/TF-IDF | Reprezentuje tekst jako rzadkie wektory | Proste, ale bez semantyki |
| Osadzanie słów | Gęste wektory oddające znaczenie | Word2Vec -> GloVe -> FastText |
| Osadzanie kontekstowe | Wektory zależne od kontekstu | ELMo -> BERT -> GPT |
| Osadzanie zdań | Wektor całego zdania | Łączenie mediów -> Zdanie-BERT |
Nel następny artykuł dokonamy skoku do architektury, która ma zrewolucjonizował wszystko: Transformatory. Zobaczymy szczegółowo mechanizm z Samouważnośćzrozumiemy, dlaczego BERT zmienił zasady gry i dowiemy się tego używać go do rzeczywistych zadań, takich jak klasyfikacja tekstu i odpowiadanie na pytania.
Zasoby, aby dowiedzieć się więcej
- spaCy włoskie modele: Oficjalna dokumentacja włoskich modeli spaCy (spacy.io/models/it)
- Modele z przytulną twarzą: Repozytorium wstępnie wytrenowanych włoskich modeli (huggingface.co/models?language=it)
- Zdanie-BERT: dokumentacja transformatorów zdań (sbert.net)
- POCZUJ TO: Analiza sentymentów i klasyfikacja emocji dla języka włoskiego (MilaNLProc)
- Papierowe Word2Vec: „Efektywne szacowanie reprezentacji słów w przestrzeni wektorowej” (Mikolov i in., 2013)
- Rękawiczki papierowe: „Globalne wektory reprezentacji słów” (Pennington i in., 2014)
- Papier BERT: „BERT: Wstępne szkolenie głębokich transformatorów dwukierunkowych” (Devlin i in., 2019)







