5.1 Clasificarea textului
Spam sau nu? Recenzie pozitivă sau negativă? Știri sau sport? Clasificarea textului transformă cuvintele în numere, apoi aplică exact modelele din capitolul 2.
Teorie
Procesarea de text
Înainte de orice model, textul se aduce la o formă uniformă:
import re
def curata(text):
text = text.lower() # litere mici
text = re.sub(r"[^a-zăâîșț ]", " ", text) # doar litere și spații
text = re.sub(r"\s+", " ", text).strip() # spații multiple
return text
# opțional: eliminarea cuvintelor de umplutură (stop words)
stop = {"și", "de", "la", "cu", "un", "o", "în", "pe", "care", "este"}
tokens = [t for t in curata(s).split() if t not in stop]
Alte tehnici uzuale: tokenizare (spargerea în cuvinte), stemming/lematizare (reducerea la rădăcină: „alergam" → „alerg").
Bag-of-Words: textul devine vector
Construiește vocabularul tuturor cuvintelor din corpus; fiecare document devine un vector cu numărul de apariții ale fiecărui cuvânt. Ordinea cuvintelor se pierde („sacul cu cuvinte"), dar pentru clasificare tematică frecvențele sunt de obicei suficiente.
from sklearn.feature_extraction.text import CountVectorizer
texte = ["pisica bea lapte", "câinele bea apă", "pisica și câinele dorm"]
cv = CountVectorizer()
X = cv.fit_transform(texte) # matrice rară (3 documente × vocabular)
print(cv.get_feature_names_out()) # ['apă' 'bea' 'câinele' ...]
print(X.toarray())
TF-IDF: cuvintele rare cântăresc mai mult
Problema BoW: „este", „foarte" apar peste tot și domină. TF-IDF ponderează: TF (frecvența în document) × IDF (log al rarității în corpus). Cuvintele specifice unui document primesc greutate mare; cele omniprezente, aproape zero.
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(
max_features=20000, # limitează vocabularul
ngram_range=(1, 2), # cuvinte singure + perechi ("foarte bun")
min_df=2, # ignoră cuvintele care apar o singură dată
)
X_train = tfidf.fit_transform(texte_train) # fit doar pe train!
X_test = tfidf.transform(texte_test)
Antrenare și evaluare
Peste vectorii TF-IDF merg foarte bine modelele liniare — regresia logistică și
LinearSVC sunt standardul; Naive Bayes e o alternativă rapidă. Evaluarea: acuratețe /
F1 + matricea de confuzie ca să vezi ce clase se confundă (lecțiile 4.1 și 4.4).
Problemă rezolvată: clasificarea textelor în categorii tematice
Fiecare text trebuie etichetat cu tema lui (sport, politică, tehnologie...). Rețeta TF-IDF + model liniar duce la 100p.
Soluția pas cu pas
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
train = pd.read_csv("train.csv") # coloane: text, categorie
test = pd.read_csv("test.csv")
pipe = Pipeline([
("tfidf", TfidfVectorizer(max_features=30000, ngram_range=(1, 2),
sublinear_tf=True)),
("clf", LogisticRegression(max_iter=2000, C=5)),
])
print(cross_val_score(pipe, train["text"], train["categorie"],
cv=5, scoring="f1_macro").mean().round(3))
pipe.fit(train["text"], train["categorie"])
pd.DataFrame({
"SampleID": test["SampleID"],
"categorie": pipe.predict(test["text"])
}).to_csv("submission.csv", index=False)
Idei pentru puncte în plus: caracter n-grams
(analyzer="char_wb", ngram_range=(2,5)) — surprinzător de puternice pe română;
tuning pe C; combinarea predicțiilor mai multor vectorizatoare.
Probleme propuse
Corpus: d1 = „pisica bea lapte", d2 = „pisica doarme", d3 = „câinele bea apă". Calculează manual TF-IDF pentru „pisica" și „lapte" în d1. Care contează mai mult și de ce?
Aplică rețeta din problema rezolvată; compară BoW vs TF-IDF vs char n-grams.
Cine a scris strofa? Stilul se ascunde în frecvențele cuvintelor — teren perfect pentru TF-IDF.
După antrenarea regresiei logistice pe TF-IDF, afișează pentru fiecare clasă cele 10 cuvinte cu coeficienții cei mai mari. Au sens? (Aceasta e interpretarea modelului — subiect frecvent de discuție la prezentarea soluțiilor.)