Fundamentele NLP: tokenizare, încorporare și conducta modernă
De fiecare dată când cereți ceva unui asistent vocal, traduceți un text cu Google Translate, filtrează spam-ul în căsuța de e-mail sau citește subtitrările automate ale unui videoclip, folosești Procesarea limbajului natural (NLP). Această disciplină, la intersecția dintre lingvistică, informatică și inteligență artificială, se ocupă de mașini de predare pentru a înțelege, interpreta și genera limbajul uman.
În ultimii ani, domeniul NLP a suferit o transformare radicală. Am trecut pe acolo reguli scrise de mână și dicționare statice la modele neuronale capabile să înțeleagă nuanțele, context și chiar ironie. BERT, GPT, Lamă iar modelele de generație următoare nu ar fi posibile fără fundamentele pe care le vom explora în acest articol: the tokenizare, The înglobări iar cel Conducta NLP modernă.
Acesta este primul articol din serie NLP modern: de la BERT la LLM. Vom pleca de la bazele absolute, construind pas cu pas intuițiile necesare pentru a înțelege modele de limbaj mai avansate. Vom acorda o atenție deosebită la specificul limbii italiene, adesea trecute cu vederea în resursele anglofone.
Ce vei învăța
- Ce este NLP și de ce stă la baza aproape tuturor aplicațiilor moderne de inteligență artificială
- Cum este preprocesat textul: minuscule, cuvinte oprite, stemming și lematizare
- Diferitele abordări ale tokenizării: nivel de cuvânt, nivel de caracter și subcuvânt (BPE, WordPiece, SentencePiece)
- Reprezentări clasice de text: Bag of Words și TF-IDF
- Încorporarea cuvintelor: Word2Vec, GloVe și intuiția geometrică a sensului
- Înglobări contextuale: de la reprezentări statice la BERT
- Încorporarea de propoziții și aplicațiile lor practice
- Conducta modernă NLP: de la text brut la predicție
- Specificitatea preprocesării pentru limba italiană
- Un exemplu complet de la capăt la capăt cu cod Python
Prezentare generală a seriei
| # | Articol | Concentrează-te |
|---|---|---|
| 1 | Sunteți aici - Fundamentele NLP | Tokenizare, încorporare, conducte |
| 2 | BERT și Transformers | Atentie Arhitectura, Pre-training |
| 3 | Analiza sentimentelor | Clasificarea textului cu BERT |
| 4 | Recunoașterea entității numite | Extragerea de entități din text |
| 5 | HuggingFace Transformers | Bibliotecă și modele pre-instruite |
| 6 | Reglajul fin al modelelor | Adaptați BERT la domeniul dvs |
| 7 | NLP pentru italiană | Șabloane și resurse pentru limba italiană |
| 8 | De la BERT la LLM | GPT, LLaMA și generare de text |
1. Preprocesarea textului: Pregătiți datele
Înainte ca orice model NLP să poată funcționa cu text, acest lucru trebuie să fie curat si normalizat. Textul dur și zgomotos: punctuația, scriere cu majuscule, abrevieri, emoji, HTML, URL. Preprocesarea transformă acest haos într-un format structurat și coerent.
1.1 Minuscule și normalizare
Primul pas este să convertiți tot textul în litere mici. Pentru un computer, „Acasă”, „acasă” și „HOME” sunt trei șiruri complet diferite. Minusculele le unifică.
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"
Acordați atenție accentelor în italiană
În multe conducte NLP vorbitoare de engleză, eliminarea accentului este un pas standard. În italiană, totuși, accentele schimbă sensul cuvintelor: "dar" (conjuncție) vs "dar" (copac), "Şi" (conjuncție) vs "Şi" (verbul a fi). Nu eliminați niciodată accentele când lucrezi cu text în italiană.
1.2 Cuvinte oprite: Cuvinte fără conținut informativ
Le cuvinte oprite Sunt cuvinte foarte frecvente care au puțină semnificație semantică: articole, prepoziții, conjuncții. Eliminarea acestora reduce dimensionalitatea datelor e ajută modelele să se concentreze asupra cuvintelor semnificative.
Cuvinte oprite italiană vs engleză
| Limba | Exemple de cuvinte oprite | Numărul tipic |
|---|---|---|
| engleză | the, is, at, which, on, a, an, and, or | ~180 de cuvinte |
| italian | the, the, the, of, to, from, in, with, on, for, that, and, not, a | ~300 de cuvinte |
Italiana are mai multe cuvinte oprite decât engleza din cauza bogăției mai mari de articole (il, lo, la, i, gli, le), prepoziții articulate (del, dello, della, nei, nei, nelle) și formele verbului auxiliar.
# 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 vs Lematizare
Ambele tehnici reduc cuvintele la forma lor de bază, dar o fac în moduri foarte diferite.
Stemming vs Lematization - Comparație
| astept | Stringing | Lematizare |
|---|---|---|
| Metodă | Tăiați sufixele cu reguli euristice | Utilizați un dicționar și o analiză morfologică |
| Rezultat | Stem (nu întotdeauna un cuvânt real) | Lema (cuvânt real din dicționar) |
| Exemplu IT | „mânânc” -> „mănânci” | „a mânca” -> „a mânca” |
| Viteză | Foarte rapid | Mai lent (necesită dicționar) |
| Precizie | Scăzut (supra-tulpină frecventă) | Ridicat (forme corecte) |
| Pentru italiană | Snowball Stemmer (Porter italian) | 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)
Pentru italiană, lematizare cu spaCy și aproape întotdeauna de preferat
la tulpina. Modelul it_core_news_lg conține 500.000 de vectori de cuvinte
și acceptă tokenizarea, etichetarea POS, analizarea dependențelor, NER și lematizarea.
2. Tokenizare: cum citesc mașinile textul
La tokenizare și procesul de împărțire a textului în unități discrete apeluri jeton. Și primul și cel mai critic pas al oricărei conducte NLP: calitatea tokenizării afectează direct performanța fiecărui model ulterior.
Există trei abordări de bază, fiecare cu beneficii și compromisuri diferite.
2.1 Tokenizare la nivel de cuvânt
Cea mai intuitivă abordare: fiecare cuvânt devine un simbol.
# 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
Limitele tokenizării la nivel de cuvânt
- Vocabular uriaș: Fiecare cuvânt unic necesită o intrare de vocabular. Italiana are sute de mii de forme flexate
- Cuvinte din vocabular (OOV): Cuvintele nevăzute niciodată în timpul antrenamentului devin <UNK> (necunoscute)
- Fără împărtășire morfologică: „mănâncă”, „mâncă”, „mâncă” sunt trei jetoane complet separate, fără legătură
2.2 Tokenizare la nivel de caractere
La cealaltă extremă, fiecare personaj devine un simbol. Vocabularul este mic (26 de litere + cifre + punctuație), dar secvențele devin foarte lungi.
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!)
Tokenizarea caracterelor rezolvă problema cuvintelor necunoscute (orice cuvânt pot fi reprezentate), dar secvențele foarte lungi îngreunează modelele surprinde relații pe termen lung în text.
2.3 Tokenizarea subcuvintelor: compromisul optim
La tokenizare subcuvânt și metoda folosită de toate modelele moderne (BERT, GPT, LLaMA, T5). Ideea este genială: cuvintele comune rămân întregi, în timp ce cuvintele rare sunt împărțite în subunități (subcuvinte) pe care modelul le-a văzut deja.
Algoritmi de tokenizare subcuvinte
| Algoritm | Folosit de | Strategie | Direcţie |
|---|---|---|---|
| BPE (Codare pereche de octeți) | GPT-2, GPT-3, GPT-4, LLaMA, Roberta | Fuziune iterativă a celor mai frecvente perechi | De jos în sus |
| WordPiece | BERT, DistilBERT, ELECTRA | Fuzionare care maximizează probabilitatea | De jos în sus |
| SentencePiece | T5, ALBERT, XLNet, mBART | Tratați textul ca pe un flux brut de caractere | Independent de limbaj |
| Unigramă | SentencePiece (opțional), ALBERT | Pornește de la un vocabular mare, elimină jetoanele mai puțin utile | De sus în jos |
Cum funcționează BPE (Byte Pair Encoding).
BPE începe de la un singur caracter și îmbină iterativ cele mai frecvente perechi până când ajungeți la dimensiunea dorită a vocabularului.
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 vs BPE
WordPiece folosește o abordare similară cu BPE, dar în loc să aleagă perechea plus
frecvent, alege-l pe cel care maximizează probabilitate a corpusului
de antrenament. În practică, WordPiece preferă îmbinări care produc jetoane mai utile
pentru modelul lingvistic, nu doar pentru cele mai comune. Jetoane care nu pornesc
un cuvânt este prefixat cu ##.
SentencePiece: Independenta Limbii
Diferența cheie a SentencePiece este că nu necesită pre-tokenizare. BPE și WordPiece presupun că textul este deja împărțit în cuvinte (de obicei prin spațiu), care funcționează bine pentru engleză și italiană, dar eșuează pentru limbi precum chineză, japoneză sau thailandeză nu folosesc spații între cuvinte. SentencePiece tratează textul ca pe un flux brut de octeți, făcându-l cu adevărat independent de limbă.
3. Exemplu practic: Tokenizare cu HuggingFace
Să vedem concret cum BERT și GPT-2 tokenizează același text italian.
Vom folosi biblioteca transformers de 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")
Observații cheie
- BERT italian (
dbmdz/bert-base-italian-cased) recunoaște „inteligență” și „rivoluzionando” ca simboluri întregi, deoarece vocabularul său a fost instruit pe textul italian - GPT-2 descompune cuvintele italiene în mai multe subcuvinte, deoarece vocabularul său a fost instruit în principal pe text în limba engleză
- Un tokenizer antrenat pe limba țintă produce mai puține jetoane, ceea ce înseamnă mai mult context în fereastra de atenție e costuri mai mici pe token
- BERT adaugă jetoane speciale:
[CLS]la început şi[SEP]în cele din urmă. GPT-2 nu
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. Bag of Words și TF-IDF: Reprezentări clasice
Înainte de încorporarea cuvintelor, textul era reprezentat ca vectori rari pe baza frecvenței cuvintelor. Aceste metode sunt încă folosite în multe contexte iar înțelegerea limitărilor lor ajută la înțelegerea de ce înglobările au fost o revoluție.
4.1 Pungă de cuvinte (BoW)
Modelul Pungă de cuvinte reprezintă un document ca vector unde fiecărei poziții îi corespunde un cuvânt de vocabular și valoarea și numărul de aparițiile acelui cuvânt în document.
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 (frecvența termenului - frecvența inversă a documentului)
TF-IDF îmbunătățește BoW prin ponderarea cuvintelor proprii importanță relativă. Cuvintele care sunt frecvente într-un document, dar rare în corpus general primesc o pondere mai mare. Cuvintele uzuale peste tot (cum ar fi „the”, „the”) primesc greutate redusă.
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)
Limitele BoW și TF-IDF
- Fără semantică: „câine” și „canin” sunt complet diferite; „bank” (râu) și „bank” (instituție) sunt identice
- Fără comandă: „pisica mănâncă șoarecele” și „șoarecele mănâncă pisica” au aceeași reprezentare
- Dimensiuni ridicate: Un vocabular de 100.000 de cuvinte produce vectori de 100.000 de dimensiuni, aproape toți zerouri (vectori rari)
- Fara generalizare: Nu surprind relațiile dintre cuvinte („regele” și „regina” nu au nicio apropiere)
5. Înglobare de cuvinte: înțeles ca geometrie
I înglobări de cuvinte au revoluționat NLP prin transformarea cuvintelor în vectori densi de dimensiuni joase (de obicei 100-300 de dimensiuni) care captează relații semantice dintre cuvinte. Două cuvinte cu sens similar vor avea vectori vecinii în spațiul vectorial.
5.1 Word2Vec: Invenția care a schimbat totul
Introdus de Tomas Mikolov și colegii (Google, 2013), Word2Old învaţă vectorii cuvintelor plecând de la contextul în care acestea apar. Intuiția fundamentală și ipoteza distribuțională: „un cuvânt se caracterizează prin companie pe care o ține” (J.R. Firth, 1957).
Două arhitecturi Word2Vec
| Arhitectură | Intrare | Ieșiri | Intuiţie |
|---|---|---|---|
| CBOW (Sacul continuu de cuvinte) | Context (cuvinte înconjurătoare) | Cuvânt țintă | Având în vedere contextul „___ mănâncă”, prezice „pisica” |
| Skip-gram | Cuvânt țintă | Context (cuvinte înconjurătoare) | Având în vedere cuvântul „pisica”, preziceți „cel”, „mâncăm”, etc. |
În practică, Skip-gram works best with small datasets and captures cuvintele rare sunt mai bune. CBOW și mai rapid și funcționează bine cu cuvinte frecvente.
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 Aritmetica cuvintelor
Cea mai surprinzătoare proprietate a înglobărilor de cuvinte este că acestea relații semantice devin operaţii algebrice pe transportatori. Celebra analogie:
rege - bărbat + femeie = regină
Nu este o coincidență: vectorul care duce de la „bărbat” la „femeie” este același care duce de la „rege” la „regina”. Acest lucru funcționează pentru multe relații: țară-capitala, verb-trecut, adjectiv la superlativ.
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: Vectori globali pentru reprezentarea cuvintelor
mănușă (Stanford, 2014) adoptă o abordare diferită: în loc să prezică contextul cuvânt cu cuvânt precum Word2Vec, GloVe construiește o premieră matricea de co-ocurență globală a întregului corpus, apoi factorizați această matrice pentru a obține vectorii. Combină avantajele metodelor bazate pe statisticile globale cu cele ale învățării locale Word2Vec.
Word2Vec vs GloVe
| astept | Word2Old | mănușă |
|---|---|---|
| Metodă | Predictiv (rețea neuronală) | Pe baza numărului (factorizare matrice) |
| Context | Fereastra locală | Statistica globală a corpusului |
| Antrenamentul | Online (defilări de text) | Lot (matrice completă) |
| Dimensiuni comune | 100, 200, 300 | 50, 100, 200, 300 |
| Performanţă | Excelent pentru analogii | Excelent pentru asemănare |
6. Înglobări contextuale: același cuvânt, semnificații diferite
Word2Vec și GloVe au o limitare fundamentală: atribuie un singur vector cu fiecare cuvânt, indiferent de context. Dar limbajul este plin de ambiguitate: cuvântul „bancă” are un cu totul alt sens în „Biroul școlii” și „Biroul Napoli”.
The înglobări contextuale rezolvă această problemă: vectorul a unui cuvânt depinde de întreaga propoziție în care apare. Aceasta este abordarea folosită de BERT, GPT și toate modelele bazate pe Transformer.
Înglobări statice vs contextuale
| astept | Static (Word2Vec, GloVe) | Contextual (BERT, GPT) |
|---|---|---|
| Vector cu cuvânt | Una fixă, mereu la fel | Diferite în funcție de context |
| Polisemie | Negestionat („banca” are un singur operator) | Gestionat („deal” are vectori diferiți pentru fiecare semnificație) |
| Model | Tabel de căutare | Rețea neuronală profundă (transformator) |
| Dimensiunea modelului | Puțini MB | Sute de MB sau GB |
| Viteză | instant | Necesită trecere înainte |
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!
Acesta este saltul conceptual fundamental: cu BERT, cuvântul „bancă” nu mai există un sens fix. Vectorul său se schimbă în funcție de mediul înconjurător, la fel ca se întâmplă în înțelegerea umană a limbajului.
7. Înglobare de propoziții: un vector pentru o propoziție întreagă
Adesea nu avem nevoie de încorporarea unui singur cuvânt, ci a unuipropoziție întreagă sau paragraful. THE înglobări de propoziții they compress the meaning de text de orice lungime într-un singur vector de dimensiune fixă.
Modelul de referință e Sentence-BERT (SBERT), care se schimbă
arhitectura BERT pentru a produce încorporari de propoziții optimizate pentru comparație
de asemănare. Pentru italian, modelul paraphrase-multilingual-MiniLM-L12-v2
acceptă peste 50 de limbi cu 384 de vectori dimensionali.
Aplicații ale înglobărilor de propoziții
| Aplicație | Descriere | Cum funcționează |
|---|---|---|
| Căutare semantică | Căutați după semnificație, nu după cuvânt cheie | Încorporați interogarea, căutați cele mai apropiate documente |
| Clustering | Grupați automat texte similare | K-means sau HDBSCAN pe înglobări |
| Detectare duplicat | Găsiți duplicate sau aproape duplicate | Pragul de asemănare cosinus > 0,9 |
| Clasificare Zero-Shot | Clasament fără date de antrenament | Comparați încorporarea textului cu încorporarea etichetelor |
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. Conducta NLP modernă
Am văzut componentele individuale. Acum să le punem împreună pentru a înțelege cum funcționează unul Conductă NLP modernă de la capăt la capăt, cel folosit de BERT, GPT și toate modelele bazate pe Transformer.
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
Rolul jetonului [CLS]
În BERT, simbolul special [CLS] este introdus la începutul fiecărei intrări.
După ce a trecut prin toate straturile Transformatorului, încorporarea acestuia reprezintă
întreaga secvență. Și folosit ca intrare pentru sarcinile de clasificare
(analiza sentimentelor, detectarea spamului etc.).
9. NLP pentru italiană: specificități și instrumente
Limba italiană prezintă provocări unice pentru NLP, care o deosebesc de engleză și din alte limbi. Cunoașterea acestor specificități este esențială pentru sistemele de construcție PNL eficient pentru italiană.
9.1 Provocări lingvistice ale limbii italiene
Caracteristici speciale ale italianului pentru NLP
| Provocare | Descriere | Exemplu |
|---|---|---|
| Morfologie bogată | Fiecare verb are zeci de forme conjugate | „eat” are peste 50 de forme (mănânc, mănânc, mănânc, mâncăm...) |
| Eliziuni și apostrofe | Articolele și prepozițiile se îmbină | „omul”, „al artei”, „un prieten”, „anul acesta” |
| Prepoziții articulate | Prepoziție + articol într-un cuvânt | "del" (di+il), "nello" (in+lo), "sulla" (su+la) |
| Accente semnificative | Ele schimbă sensul | "e" (și) vs "e" (este), "da" (din) vs "da" (dă) |
| Pronume clitic | Se lipesc de verb | „da-mi-o” (da+mi+o), „adu-i-o” |
| Comanda gratuită | SVO nu este obligatoriu | „Marco eats the cake” = „Marco eats the cake” |
9.2 Modele pre-antrenate pentru italiană
Principalele modele italiene
| Model | De bază | Sarcini | Depozitele |
|---|---|---|---|
| dbmdz/bert-base-italian-cased | BERT | NLP italian de uz general | HuggingFace |
| Alberto | BERT | Rețelele sociale italiene (Twitter) | HuggingFace |
| simt-o-sentiment-italian | UmBERTo | Analiza sentimentului italian | MilaNLProc |
| simți-o-emoție-italiană | UmBERTo | Detectarea emoțiilor (bucurie, furie, frică, tristețe) | MilaNLProc |
| Italian-Legal-BERT | BERT | texte legale italiene | dlicari |
| DeepMount00/Italian_NER_XXL | BERT | Recunoaștere italiană a entității denumite | HuggingFace |
| it_core_news_lg | spaCy CNN | Tokenizare, POS, NER, lemă, parsare | spațios |
9.3 Preprocesare specifică pentru limba italiană
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. Exemplu de la capăt la capăt: Căutare semantică în italiană
Să punem tot ce am învățat împreună într-un singur exemplu complet: a căutare semantică pe un corpus de texte italiene. Dat un set of documents and a user query, we will find the most relevant documents using înglobări de propoziții.
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']}")
Rezultate așteptate
Căutarea semantică include sens, nu doar cuvintele. De exemplu:
- „cum să analizăm limbajul natural” va găsi documentul NLP chiar dacă nu conține exact acele cuvinte
- „cadru pentru dezvoltarea frontend” va găsi Angular, chiar dacă „frontend” nu apare în document (dar „aplicațiile web moderne” sunt legate semantic)
- „bucătărie tradițională italiană” va găsi atât carbonara cât și pizza, deoarece modelul înțelege relația semantică
Foaia de parcurs: de aici la LLM
În acest articol am construit bazele NLP-ului modern, pornind de la preprocesarea textului până la încorporarea contextuală și pipeline complet. Să rezumam drumul pe care l-am parcurs:
Rezumatul conceptelor
| Concept | Ce face El | Evoluţie |
|---|---|---|
| Preprocesare | Curăță și normalizează textul brut | Reguli manuale -> conductă spaCy |
| Tokenizare | Împarte textul în unități discrete | Cuvânt -> Char -> Subcuvânt (BPE/WordPiece) |
| BoW/TF-IDF | Reprezintă textul ca vectori rare | Simplu dar fără semantică |
| Înglobare de cuvinte | Vectori densi care capteaza sensul | Word2Vec -> GloVe -> FastText |
| Înglobări contextuale | Vectori dependenți de context | ELMo -> BERT -> GPT |
| Încorporarea de propoziții | Un vector pentru o propoziție întreagă | Media pooling -> Sentence-BERT |
În articolul urmator vom face saltul spre arhitectura care are a revoluționat totul: cel Transformatoare. Vom vedea mecanismul în detaliu de Auto-atenție, vom înțelege de ce BERT a schimbat jocul și vom învăța pentru a-l folosi pentru sarcini reale, cum ar fi clasificarea textului și răspunsul la întrebări.
Resurse pentru a afla mai multe
- modele spaCy italiene: Documentație oficială pentru modelele spaCy italiene (spacy.io/models/it)
- Modele HuggingFace: Depozitul de modele italiene pre-instruite (huggingface.co/models?language=it)
- Propoziție-BERT: documentația transformatorilor de propoziții (sbert.net)
- FEL-IT: Analiza sentimentelor și clasificarea emoțiilor pentru italiană (MilaNLProc)
- Hârtie Word2Vec: „Estimarea eficientă a reprezentărilor cuvintelor în spațiul vectorial” (Mikolov et al., 2013)
- Mănușă de hârtie: „Vectorii globali pentru reprezentarea cuvintelor” (Pennington et al., 2014)
- Hârtia BERT: „BERT: Pre-antrenamentul transformatoarelor bidirecționale profunde” (Devlin și colab., 2019)







