Faza 05 · lecția 05
Analiza sentimentului
Scopul lecției: Sarcina NLP canonică. Aici apare cea mai mare parte din ceea ce trebuie să știți despre clasificarea clasică a textului.
Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.
Cuprinsul lecției
- Problema
- Conceptul
- Construiți
- Pasul 1: un mini-set de date real
- Pasul 2: Naive Bayes multinomial de la zero
- Pasul 3: regresie logistică de la zero
- Pasul 4: tratarea negației (modul de eșec)
- Pasul 5: metrici de evaluare care contează
- Folosiți
- Când să apelați la un Transformer
- Capcana reproductibilității (din nou)
- Livrați
- Exerciții
- Termeni-cheie
- Lecturi suplimentare
Sarcina NLP canonică. Aici apare cea mai mare parte din ceea ce trebuie să știți despre clasificarea clasică a textului.
Tip: Construire Limbaje: Python Cerințe prealabile: Faza 5 · 02 (BoW + TF-IDF), Faza 2 · 14 (Naive Bayes) Timp: ~75 de minute
Problema
„The food was not great.” Pozitiv sau negativ?
Sentimentul pare simplu. Un recenzent a spus că i-a plăcut sau nu i-a plăcut ceva. Etichetați propoziția. A devenit sarcina NLP canonică fiindcă fiecare caz aparent ușor ascunde unul dificil. Negația inversează sensul. Sarcasmul îl răstoarnă. „Not bad at all” este pozitiv, deși conține două cuvinte asociate cu negativul. Emojiurile poartă mai mult semnal decât textul din jur. Vocabularul domeniului contează (tight într-o recenzie muzicală față de tight într-o recenzie de modă).
Analiza sentimentului este un laborator practic pentru NLP clasic. Dacă înțelegeți de ce fiecare bază naivă are un mod de eșec specific, înțelegeți de ce a fost inventat fiecare model mai bogat. Această lecție construiește de la zero o bază Naive Bayes, adaugă regresia logistică și numește capcanele care fac sentimentul din producție o problemă de nivel conformitate.
Conceptul
Sentimentul clasic este o rețetă în doi pași.
- Reprezentați. Transformați textul într-un vector de caracteristici: BoW, TF-IDF sau n-grame.
- Clasificați. Potriviți un model liniar (Naive Bayes, regresie logistică, SVM) pe exemple etichetate.
Naive Bayes este cel mai simplist model care funcționează. Presupuneți că fiecare caracteristică este independentă condiționat de etichetă. Estimați P(word | positive) și P(word | negative) din numărări. La inferență, înmulțiți probabilitățile. Ipoteza „naivă” de independență este evident greșită, dar rezultatele sunt surprinzător de puternice. Motivul: pentru caracteristici text rare și date moderate, clasificatorului îi pasă mai mult de direcția în care se înclină fiecare cuvânt decât de intensitate.
Regresia logistică corectează ipoteza independenței. Învață o pondere pentru fiecare caracteristică, inclusiv ponderi negative. not good, ca n-gram, primește o pondere negativă. Naive Bayes nu poate face aceasta pentru n-grame pe care nu le-a etichetat niciodată.
sentiment-logits
Ilustrație originală din assets/sentiment.svg al lecției-sursă, mapată explicit la cheia de figură sentiment-logits. SVG-ul este păstrat fără modificări; etichetele sale în limba engleză sunt explicate în textul românesc.
Notă tehnică a traducerii: Nici Naive Bayes, nici regresia logistică nu pot învăța direct o pondere pentru un n-gram care nu apare în antrenare; ambelor le lipsește acea caracteristică. Cu n-grame observate, Naive Bayes poate totuși folosi probabilități diferite pe clase. Diferențele de performanță depind de reprezentare, netezire, regularizare și date, nu numai de ipoteza de independență.
Construiți
Pasul 1: un mini-set de date real
POSITIVE = [
"absolutely loved this movie",
"beautiful cinematography and a great story",
"one of the best films of the year",
"brilliant acting from the lead",
"heartwarming and funny",
]
NEGATIVE = [
"boring and far too long",
"not worth your time",
"the plot made no sense",
"terrible acting, awful script",
"i want my two hours back",
]
Mic intenționat. Munca reală folosește zeci de mii de exemple (IMDb, SST-2, Yelp polarity). Matematica este identică.
Pasul 2: Naive Bayes multinomial de la zero
import math
from collections import Counter
def train_nb(docs_by_class, vocab, alpha=1.0):
class_priors = {}
class_word_probs = {}
total_docs = sum(len(d) for d in docs_by_class.values())
for cls, docs in docs_by_class.items():
class_priors[cls] = len(docs) / total_docs
counts = Counter()
for doc in docs:
for token in doc:
counts[token] += 1
total = sum(counts.values()) + alpha * len(vocab)
class_word_probs[cls] = {
w: (counts[w] + alpha) / total for w in vocab
}
return class_priors, class_word_probs
def predict_nb(doc, class_priors, class_word_probs):
scores = {}
for cls in class_priors:
s = math.log(class_priors[cls])
for token in doc:
if token in class_word_probs[cls]:
s += math.log(class_word_probs[cls][token])
scores[cls] = s
return max(scores, key=scores.get)
Netezirea aditivă (alpha=1.0) este netezirea Laplace. Fără ea, un cuvânt nevăzut într-o clasă are probabilitatea zero, iar logaritmul explodează. alpha=0.01 este frecvent în practică. alpha=1.0 este valoarea didactică implicită.
Notă tehnică a traducerii: Alegerea lui
alphatrebuie validată pe datele sarcinii; nu există o valoare practică universală. De asemenea, codul ignoră tokenurile care nu apar în vocabularul transmis, astfel încât un cuvânt complet necunoscut nu produce automat probabilitate zero sau o contribuție de netezire la predicție.
Pasul 3: regresie logistică de la zero
import numpy as np
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-np.clip(x, -20, 20)))
def train_lr(X, y, epochs=500, lr=0.05, l2=0.01):
n_features = X.shape[1]
w = np.zeros(n_features)
b = 0.0
for _ in range(epochs):
logits = X @ w + b
preds = sigmoid(logits)
err = preds - y
grad_w = X.T @ err / len(y) + l2 * w
grad_b = err.mean()
w -= lr * grad_w
b -= lr * grad_b
return w, b
def predict_lr(X, w, b):
return (sigmoid(X @ w + b) >= 0.5).astype(int)
Regularizarea L2 contează aici. Caracteristicile text sunt rare; fără L2, modelul memorează exemplele de antrenare. Începeți de la 0.01 și reglați.
Notă tehnică a traducerii: L2 reduce de regulă supraînvățarea, dar nu împiedică automat memorarea și nu are o valoare inițială universală. Efectul depinde de scalarea caracteristicilor, dimensiunea setului de date, reprezentare și criteriul de evaluare; alegeți
l2prin validare, nu ca regulă fixă.
Pasul 4: tratarea negației (modul de eșec)
Luați în considerare „not good” și „not bad”. Un clasificator BoW vede {not, good} și {not, bad} și învață din ceea ce apare mai des la antrenare. Un clasificator cu bigrame vede not_good și not_bad și le învață ca caracteristici distincte. De obicei este suficient.
O corecție mai grosieră care funcționează când nu aveți bigrame: domeniul negației. Prefixați tokenurile de după un cuvânt de negație cu NOT_ până la următoarea punctuație.
NEGATION_WORDS = {"not", "no", "never", "nor", "none", "nothing", "neither"}
NEGATION_TERMINATORS = {".", "!", "?", ",", ";"}
def apply_negation(tokens):
out = []
negate = False
for token in tokens:
if token in NEGATION_TERMINATORS:
negate = False
out.append(token)
continue
if token in NEGATION_WORDS:
negate = True
out.append(token)
continue
out.append(f"NOT_{token}" if negate else token)
return out
>>> apply_negation(["not", "good", "at", "all", ".", "but", "funny"])
['not', 'NOT_good', 'NOT_at', 'NOT_all', '.', 'but', 'funny']
Acum good și NOT_good sunt caracteristici diferite. Clasificatorul le poate pondera în direcții opuse. Trei linii de preprocesare, un salt măsurabil al acurateții pe benchmarkuri de sentiment.
Notă tehnică a traducerii: Această euristică nu descoperă domeniul sintactic al negației și poate greși la conjuncții, cuvinte de intensitate, propoziții subordonate sau ironie. Bigramele și prefixarea pot ajuta pe anumite corpusuri, dar saltul de acuratețe trebuie măsurat cu același split și aceeași metrică; nu este garantat.
Pasul 5: metrici de evaluare care contează
Numai acuratețea induce în eroare dacă clasele sunt dezechilibrate. Corpusurile reale de sentiment sunt de obicei 70–80% pozitive sau 70–80% negative; un clasificator care prezice mereu clasa majoritară obține 80% acuratețe și este inutil. Raportați fiecare dintre următoarele:
- Precizie și reamintire per clasă. O pereche pentru fiecare clasă. Calculați media macro pentru a obține un singur număr care respectă echilibrul claselor.
- F1 macro (metrică principală pentru date dezechilibrate). Media scorurilor F1 per clasă, ponderate egal. Folosiți-o în loc de acuratețe când clasele sunt dezechilibrate.
- F1 ponderat (alternativă). La fel ca macro, dar ponderat cu frecvența claselor. Raportați-l alături de F1 macro când dezechilibrul însuși are semnificație de afaceri.
- Matrice de confuzie. Numărări brute. Inspectați-o întotdeauna înainte să aveți încredere într-o metrică scalară; arată ce pereche de clase confundă modelul.
- Eșantioane de erori per clasă. Extrageți 5 predicții greșite per clasă. Citiți-le. Nimic nu înlocuiește citirea erorilor reale.
Pentru date puternic dezechilibrate (raport > 95–5), raportați AUROC și AUPRC în locul acurateții. AUPRC este mai sensibilă la clasa minoritară, care este de obicei cea importantă (spam, fraudă, sentiment rar).
Bug comun de evitat. Raportarea F1 micro în loc de F1 macro pe date dezechilibrate dă un număr care pare mare deoarece este dominat de clasa majoritară. F1 macro vă obligă să vedeți performanța clasei minoritare.
Notă tehnică a traducerii: Proporția claselor și alegerea metricii sunt specifice corpusului și costului erorilor; nu toate seturile de sentiment sunt dezechilibrate. AUROC și AUPRC cer scoruri continue sau probabilități, nu numai etichete dure, iar AUPRC depinde de clasa pozitivă și de prevalență. Pentru probleme multiclasă sunt necesare definiții one-vs-rest și raportare per clasă. Consultați documentația scikit-learn pentru evaluare.
def evaluate(y_true, y_pred):
tp = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 1)
fp = sum(1 for t, p in zip(y_true, y_pred) if t == 0 and p == 1)
fn = sum(1 for t, p in zip(y_true, y_pred) if t == 1 and p == 0)
tn = sum(1 for t, p in zip(y_true, y_pred) if t == 0 and p == 0)
precision = tp / (tp + fp) if tp + fp else 0
recall = tp / (tp + fn) if tp + fn else 0
f1 = 2 * precision * recall / (precision + recall) if precision + recall else 0
return {"tp": tp, "fp": fp, "tn": tn, "fn": fn, "precision": precision, "recall": recall, "f1": f1}
Notă tehnică a traducerii:
evaluateeste un evaluator binar pentru clasa codificată1;f1este F1-ul acelei clase, nu F1 macro. Pentru afirmațiile din această secțiune trebuie calculate metrici per clasă și agregările macro/ponderate, iar pentru AUROC/AUPRC trebuie păstrate scorurile înainte de prag.
Folosiți
scikit-learn face corect acest lucru în șase linii.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
pipe = Pipeline([
("tfidf", TfidfVectorizer(ngram_range=(1, 2), min_df=2, sublinear_tf=True, stop_words=None)),
("clf", LogisticRegression(C=1.0, max_iter=1000)),
])
pipe.fit(X_train, y_train)
print(pipe.score(X_test, y_test))
Trei lucruri de observat. stop_words=None păstrează negațiile. ngram_range=(1, 2) adaugă bigrame, astfel încât not_good devine o caracteristică. sublinear_tf=True atenuează cuvintele repetate. Aceste trei opțiuni fac diferența dintre o bază cu 75% acuratețe și una cu 85% acuratețe pe SST-2.
Notă tehnică a traducerii: În
TfidfVectorizer, bigrama este în mod normal o caracteristică precumnot good, cu separator spațiu, nu șirulnot_good; forma cu prefix este produsă deapply_negation, nu de vectorizatorul standard.apply_negationprimește o listă de tokenuri și nu este conectată la acestPipeline, care primește șiruri și tokenizează intern; este necesar un wrapper sau un tokenizer/analyzer configurat, cu o politică explicită pentru punctuație.pipe.scoreraportează acuratețea, nu F1. Pentru clase dezechilibrate calculați macro-F1 și, când clasa pozitivă este relevantă, AP dinpredict_probasaudecision_function. Efectul setărilor și diferența 75%/85% depind de split, preprocesare, versiuni, hiperparametri și benchmark; validați-le pe datele proprii. Consultați documentația TfidfVectorizer și documentația despre evaluare.
Când să apelați la un Transformer
- Detectarea sarcasmului. Modelele clasice eșuează aici. Punct.
- Recenzii lungi, în care sentimentul se schimbă la mijlocul documentului.
- Sentiment bazat pe aspecte. „Camera was great but battery was terrible.” Trebuie să atribuiți sentimentul aspectelor. Numai Transformerele sau modelele cu ieșire structurată.
- Limbi non-engleze, cu puține resurse. BERT multilingv oferă gratuit o bază zero-shot.
Dacă aveți nevoie de oricare dintre cele de mai sus, săriți la faza 7 (aprofundare despre Transformere). Altfel, Naive Bayes sau regresia logistică pe TF-IDF, plus bigrame și tratarea negației, reprezintă baza dumneavoastră de producție pentru 2026.
Notă tehnică a traducerii: Acestea sunt indicii de selecție, nu condiții necesare. Modelele clasice pot fi utile pentru anumite sarcini pe aspecte sau limbi cu puține resurse, iar Transformerele pot eșua la sarcasm, la schimbarea domeniului și la limbile ori varietățile slab acoperite. Un checkpoint multilingv zero-shot necesită evaluare pentru limbă și domeniu; nu este o bază „gratuită” garantată. Alegerea din 2026 nu este permanentă și trebuie validată pentru latență, confidențialitate, cost și metrică.
Capcana reproductibilității (din nou)
Reantrenarea modelelor de sentiment este obișnuită. Reevaluarea lor nu este. Numerele de acuratețe raportate în lucrări folosesc splituri specifice, preprocesare specifică, tokenizatoare specifice. Dacă comparați noul model cu o bază fără a folosi fluxul identic, veți obține diferențe înșelătoare. Regenerați întotdeauna baza pe fluxul propriu, nu pe numărul din lucrare.
Notă tehnică a traducerii: Reproducerea trebuie să fixeze și versiunea datelor, regulile de curățare, spliturile, semințele, hiperparametrii, pragurile și metricile. O comparație corectă poate necesita atât rularea aceleiași conducte, cât și documentarea diferențelor justificate față de protocolul lucrării.
Livrați
Salvați ca outputs/prompt-sentiment-baseline.md:
---
name: sentiment-baseline
description: Proiectează o bază de analiză a sentimentului pentru un set de date nou.
phase: 5
lesson: 05
---
Pentru o descriere a setului de date (domeniu, limbă, dimensiune, granularitatea etichetelor, buget de latență), produceți:
1. Rețeta de extragere a caracteristicilor. Specificați tokenizerul, intervalul de n-grame, politica pentru cuvinte de oprire (de obicei le păstrați), tratarea negației (prefix cu domeniu de aplicare sau bigrame).
2. Clasificatorul. Naive Bayes pentru bază, regresie logistică pentru producție, Transformer numai dacă domeniul are nevoie de sarcasm / aspecte / transversal între limbi.
3. Planul de evaluare. Raportați precizie, reamintire, F1, matricea de confuzie și eșantioane de erori per clasă (nu numai scalari).
4. Un mod de eșec de monitorizat după lansare. Deriva domeniului și sarcasmul sunt primele două.
Refuzați să recomandați eliminarea cuvintelor de oprire pentru sarcini de sentiment. Refuzați să raportați acuratețea drept singura metrică atunci când clasele sunt dezechilibrate (de exemplu, 90% pozitive). Semnalați limbile bogate în subcuvinte ca necesitând reprezentări FastText sau Transformer în locul TF-IDF la nivel de cuvânt.
Notă tehnică a traducerii: Aceste refuzuri sunt reguli prudente, nu legi. Eliminarea selectivă a cuvintelor de oprire poate fi evaluată dacă negațiile și alte semnale sunt protejate; un model liniar poate fi o alegere bună și pentru limbi bogate morfologic, cu segmentare și date potrivite. „Primele două” moduri de eșec depind de produs, iar producția are nevoie și de monitorizarea distribuțiilor, calității etichetelor, pragurilor și erorilor cu cost ridicat.
Exerciții
- Ușor. Adăugați
apply_negationca pas de preprocesare în fluxul scikit-learn și măsurați diferența F1 pe un set de date mic pentru sentiment. - Mediu. Implementați regresia logistică ponderată pe clase (transmiteți
class_weight="balanced"către scikit-learn sau derivați singuri gradientul). Măsurați efectul asupra unui dezechilibru sintetic al claselor de 90–10. - Dificil. Construiți un detector de sarcasm antrenând un al doilea clasificator pe reziduurile modelului de sentiment. Documentați configurația experimentală. Avertizați cititorul când acuratețea este sub nivelul aleatoriu (nivelul aleatoriu pentru sarcasm cu 2 clase este ~50%, iar majoritatea primelor încercări ajung acolo).
Notă tehnică a traducerii: Acuratețea de șansă este 50% numai pentru clase echilibrate și predicții uniforme; pentru distribuții dezechilibrate, o bază majoritară poate depăși 50%. Reziduurile unui clasificator de sentiment nu constituie automat date etichetate pentru sarcasm. Definiți etichetele, spliturile fără scurgere, referința relevantă și metricile înainte de a interpreta rezultatul.
Termeni-cheie
| Termen | Ce spun oamenii | Ce înseamnă de fapt |
|---|---|---|
| Polaritate | Pozitiv sau negativ | Etichetă binară; uneori extinsă la neutru sau granulară fin (5 stele). |
| Sentiment bazat pe aspecte | Polaritate per aspect | Atribuie sentimentul unor entități sau atribute specifice menționate în text. |
| Domeniul negației | Inversarea tokenurilor apropiate | Prefixează tokenurile după „not” cu NOT_ până la punctuație. |
| Netezire Laplace | Adăugarea lui 1 la numărări | Previne caracteristicile cu probabilitate zero în Naive Bayes. |
| Regularizare L2 | Micșorarea ponderilor | Adaugă lambda * sum(w^2) la pierdere. Esențială pentru caracteristici text rare. |
Notă tehnică a traducerii: Pentru funcția
train_lrdin lecție, termenull2 * wdin gradient corespunde unei penalizări de forma(l2 / 2) * sum(w^2). Dacă pierderea ar conține literallambda * sum(w^2), gradientul regulării ar fi2 * lambda * w. Convenția constantei nu schimbă ideea regularizării, dar contează când comparați hiperparametri.
Lecturi suplimentare
- Pang and Lee (2008). Opinion Mining and Sentiment Analysis — sondajul fundamental. Lung, dar primele patru secțiuni acoperă tot ce este clasic.
- Wang and Manning (2012). Baselines and Bigrams: Simple, Good Sentiment and Topic Classification — lucrarea care a arătat că bigramele + Naive Bayes sunt greu de depășit pe text scurt.
- Documentația scikit-learn despre extragerea caracteristicilor de text — referință pentru
CountVectorizer,TfidfVectorizerși fiecare opțiune pe care o veți regla.
Sursă: Originalul în limba engleză
Navigare: ← Lecția 05.04 — GloVe, FastText și reprezentări vectoriale de subcuvinte · Faza 5 — NLP: de la fundamente la subiecte avansate · Lecția 05.06 — Recunoașterea entităților denumite (NER) → · Catalog complet