Dacă ai trăi într-o lume unde opinia ta contează, ai încerca să schimbi ceva? Fără îndoială că opiniile sunt o prezență constantă în viața fiecăruia: recomandările, recenziile, sunt doar câteva din mijloacele folosite pentru a influența viitoarele decizii. În era vitezei, când multitudinea opțiunilor te inhibă, opiniile sunt cele ce te pot determina în a lua o decizie rapidă când ești sub lumina reflectorului. Dar să presupunem că am putea evalua totul în termeni de pro sau contra, ar fi deciziile noastre mai corecte? Sau nu e chiar atât de simplu?
Sentiment Analysis sau Opinion Mining este o ramură a domeniului de Natural Language Processing (NLP) ce se ocupă cu studiul opiniilor, sentimentelor, evaluărilor, atitudinilor, emoțiilor și caracteristicele acestora, direcționate spre anumite entități precum produse, organizații, indivizi, evenimente, etc. . Nu a existat un interes deosebit pentru această disciplină înainte de anul 2000, dar odată cu răspândirea aplicațiilor comerciale (online sau offline), perspectiva asupra analizei opiniilor s-a schimbat în mod radical. E și pentru prima dată în istorie când există o bază de date dogmatică consistentă, alcătuită cu ajutorul rețelelor de socializare. Nu e atât de surprinzătoare schimbarea de perspectivă ținând cont de aplicațiile sale în domenii politice, sociologice, economice ș.a. și de popularitatea de care se bucură socializarea online în zilele noastre.
Să ne întoarcem totuși la întrebarea inițială. Vom lua drept exemplu filmele și o serie de recenzii asupra acestora. Vom împărți astfel opiniile în două categorii, după cum am stabilit inițial: pozitiv (pro) și negativ (contra). Astfel putem enunța întrebarea: dacă ar exista o recenzie negativă despre actorul principal și una pozitivă despre regizor, ai mai viziona filmul? Ai mai avea nevoie de încă o părere despre film? Cum poți determina dacă următoarea opinie va fi una pozitivă sau una negativă? Și cum te-ar putea influența aceasta?
Există diferite metode de analiză depinzând de granularitatea cu care se abordează problema: analiza per document (Document Analysis) care determină dacă un întreg document exprimă o opinie pozitivă sau negativă; analiza per propoziție (Sentence Analysis) care stabilește dacă o propoziție este pozitivă, negativă sau neutră; analiza per entitate sau aspect (Entity and Aspect or Feature Analysis) care precizează asupra cărei entități se adresează opinia și polaritatea opiniei.
Există două metode de clasificare a opiniilor: aceea prin antrenarea unei rețele neuronale (Supervised Learning) și aceea ce nu implică o astfel de antrenare (Unsupervised Learning). Simplificând analiza la nivelul propoziției presupunând că avem la dispoziție doar documente cu tentă pozitivă sau negativă, vom aborda o soluție prin Supervised Learning cu ajutorul platformei NLTK (Natural Language Toolkit).
{python code}
from nltk.corpus import movie_reviews
positive_ids = movie_reviews.fileids('pos')
negative_ids = movie_reviews.fileids('neg')
{/python code}
După cum se poate observa corpusul de "movie_reviews" conține recenzii de filme deja segregate în cele două categorii stabilite inițial. Din acestea vom extrage cuvinte drept informațiile de care vom depinde pe viitor.
{python code}
positive_data = [movie_reviews.words(fileids=[f]) for f in positive_ids]
negative_data = [movie_reviews.words(fileids=[f]) for f in negative_ids]
{/python code}
Suntem în punctul unde trebuie să decidem cu ce vor fi reprezentate instanțele în momentul în care vom antrena rețeaua neuronală. Pentru simplitate vom alege drept caracteristică cuvintele cele mai frecvente din corpusul specific. Se implementează astfel o funcție care determină frecvența fiecărui cuvânt pentru cele două categorii, împreună și diferențial.
{python code}
import itertools
from nltk import FreqDist, ConditionalFreqDist
def buildFreqDistribution(positiveWords, negativeWords):
word_fd = FreqDist()
cond_word_fd = ConditionalFreqDist()
for word in list(itertools.chain(*positiveWords)):
word_fd[word.lower()] += 1
cond_word_fd['positive'][word.lower()] += 1
for word in list(itertools.chain(*negativeWords)):
word_fd[word.lower()] += 1
cond_word_fd['negative'][word.lower()] += 1
return (word_fd, cond_word_fd)
{/python code}
Bazându-ne pe frecvențele calculate anterior, vom construi un dicționar care va conține scorul fiecărui cuvânt. Funcția "BigramAssocMeasures" calculează relevanța cuvântului respectiv în contextul în care se află acesta, returnând o valoare reprezentativă.
{python code}
from nltk import BigramAssocMeasures
def buildWordsScores(word_fd, cond_word_fd, total_word_count):
word_scores = {}
for word, freq in word_fd.items():
positive_score = BigramAssocMeasures.chi_sq(cond_word_fd['positive'][word], (freq, cond_word_fd['positive'].N()), total_word_count)
negative_score = BigramAssocMeasures.chi_sq(cond_word_fd['negative'][word],
(freq, cond_word_fd['negative'].N()),total_word_count)
word_scores[word] = positive_score + negative_score
return word_scores
{/python code}
Se implementează și funcțiile care vor filtra informațiile relevante pentru antrenarea rețelei neuronale.
{python code}
def getFeatures(label, data, best_words):
features = []
for feat in data:
words = [selectBestWords(feat, best_words), label]
features.append(words)
return features
def selectBestWords(words, best_words):
return dict([(word, True) for word in words if word in best_words])
def findBestWords(scores, number):
best_vals = sorted(scores.items(), key=lambda w_s:
w_s[1], reverse=True)[:number]
best_words = set([w for w, s in best_vals])
return best_words
{/python code}
Tot ce mai rămâne este să le punem cap la cap. Cu informațiile extrase până acum vom antrena rețeaua neuronală reprezentată de NaiveBayesClassifier. Pentru simplitate vom reveni la platforma NTLK utilizând algoritmul său standard.
{python code}
(word_fd, cond_word_fd) = buildFreqDistribution(positive_data, negative_data)
total_word_count = cond_word_fd['positive'].N() + cond_word_fd['negative'].N()
word_scores = buildWordsScores(word_fd, cond_word_fd, total_word_count)
best_words = findBestWords(word_scores, 1000)
positive_features = getFeatures('positive', positive_data, best_words)
negative_features = getFeatures('negative', negative_data, best_words)
classifier = NaiveBayesClassifier.train(positive_features + negative_features)
classifier.show_most_informative_features(10)
{/python code}
Se pare că dispunem acum de un mecanism ce poate determina cu o oarecare precizie dacă o recenzie este pozitivă sau negativă. Să luam spre exemplu următoarea recenzie la un film cu Kevin Costner : "Once again Mr. Costner has dragged out a movie for far longer than necessary. Aside from the terrific sea rescue sequences, of which there are very few I just did not care about any of the characters [...]" O recenzie ce, la prima vedere, pare a fi una negativă.
{python code}
features = selectBestWords(words_in_review, best_words)
print(classifier.classify(features))
{/python code}
Se dovedește a fi pozitivă cu o diferență de scor de _1.4. Dacă adaugăm și restul recenziei : **"[...] Most of us have ghosts in the closet, and Costner's character are realized early on, and then forgotten until much later, by which time I did not care. The character we should really care about is a very cocky, overconfident Ashton Kutcher. The problem is he comes off as a kid who thinks he's better than anyone else around him and shows no signs of a cluttered closet."_, aceasta reiese a fi negativă cu o diferență de scor de 0.9**.
Utilizând algoritmul SVM (Support Vector Machines) în paralel cu algoritmul NaiveBayesClassifier și testând cei doi algoritmi pe o baza de date ajungând la peste 6 milioane de cuvinte, am obținut următoarele grafice. Odată cu creșterea numărului de entități extrase din text și folosit în antrenarea rețelelor neuronale, se pot observa diferențe de acuratețe generală și de clasificare.
Se poate observa cum precizia algoritmului rămâne constantă și în creștere până la un punct de plafonare în jurul valorii aproximative de : 0.81%. Chiar dacă numărul de features (entități) crește, acuratețea va oscila în jurul valorii de plafonare. Totuși, instabilitatea în cazul algoritmului NaiveBayesClassifier se poate observa mai bine în ultimul grafic. Algoritmul SVM se dovedește a fi mai fiabil în acest caz.
Se pare ca nu e atât de simplu. Am fost păcăliți chiar de propriul nostru algoritm. Totuși, nu există o metodă standardizată de determinare a "sentimentului" degajat de o opinie. Până și prin simplificarea radicală a problemei nu s-a putut ajunge la o acuratețe impresionantă, nici măcar apropiată de 95%. Există diferite metode și diferiți algoritmi ce se pot folosi în Supervised sau Unsupervised Learning, dar soluția cea mai bună este combinarea acestor două mari metode. Desigur o importanță deosebită o poartă bazele de date folosite, metodele implementate, corpusul de test, toate acestea având necesitatea de a corespunde intenției dezvoltatorului. Opiniile cumulate pot determina valoarea unui produs, a unui individ, a unei idei sau a unui eveniment. Cu opinii ești bombardat în fiecare zi pe orice device, pe orice aplicație, pe orice pagină Web, pe orice stradă. În final, contează sau nu părerea ta? Eu zic: Da!
de Peter Lawrey
de Răzvan Costa