iT邦幫忙

2022 iThome 鐵人賽

DAY 21
0
AI & Data

文理組人都能上手的入門 NLP(自然語言處理)系列 第 22

[Day 21] 監督式機器學習模型:掌握賺大錢的關鍵-實作SVM & 文本情感分析(Sentiment Analysis)

  • 分享至 

  • xImage
  •  

  午安各位~昨天講解完支持向量機(Support Vector Machine)的原理之後,今天要用推特上面對不同產品的評價來進行文本情感分析。會需要做這件事情是因為東西賣出去之後,企業需要使用者的回饋才能知道商品有什麼需要改進的地方,也可以透過正面評價制定新的行銷策略。但是評論那麼多,光是把他們從眾多貼文裡面找出來就已經很花時間了,如果還要一個一個慢慢讀也太可怕了。所以讓機器先幫我們把資料分類過就可以節省很多時間。
  上次在示範羅吉斯迴歸實作的時候,我們用TF-IDF做了簡單的情單分析去判斷貼為是否具有憂鬱傾向,但嚴格說起來那其實不太算是情感分析,可能還是比較偏向應用詞頻做出的分類任務。會這樣說的原因是,那時候我們沒有特別去看到底哪些特徵的重要性比較高,對模型分類有比較大的影響力。今天我們要做的事情是在詞頻的基礎之上去找出文本當中包含的情緒詞,再透過這些詞彙計算每一篇文本的情感分數,看看能不能透過這個分數幫助機器判斷評價正面與否。

進階一點的文本情感分析

  雖然傳統的文本情感分析利用建立情緒詞字典的方式去把文本裡面的情緒詞抓出來之後計算它的正面情緒分數跟負面情緒分數聽起來是很理想的做法,但是當我們把"I am not happy with this."裡面的happy抓出來之後,模型會覺得這句話是一個正面評論。也就是說當文本裡面出現否定詞彙或轉折語(but之類的詞)的時候,我們單純抓情緒詞出來的方式就會失準,所以我們應該把上下文也考慮進去才能得到更全面的結果。放心!我們今天不用做得這麼複雜,因為「前人種樹,後人乘涼」,nltk裡面的sentiment.vader module已經把這些東西都考量進去,寫了一個工具幫我們計算情感分數。馬上就讓我們一起來實作看看。

特徵萃取

  首先我們要把資料讀進來然後稍微做一點處理。因為SVM比較適合做兩種類別的分類任務,所以我們要手動把裡面標示為中性情緒跟無關評論的資料先拿掉。要特別注意的是,用完drop()之後一定要reset_index()才不會造成後續資料處理遇到問題,因為他不會自動更新資料的index。

drop_list = []
for rows in range(len(comments_train["label"])):
  if comments_train.at[rows, "label"] == "Neutral" or comments_train.at[rows, "label"] == "Irrelevant":
    drop_list.append(rows)
clean_train = comments_train.drop(drop_list, axis = 0).reset_index()

drop_list = []
for rows in range(len(comments_test["label"])):
  if comments_test.at[rows, "label"] == "Neutral" or comments_test.at[rows, "label"] == "Irrelevant":
    drop_list.append(rows)
clean_test = comments_test.drop(drop_list, axis = 0).reset_index()

  這邊真的要表白一下寫出這個套件的人,因為他甚至不用斷詞,只需要把字串送進去就可以得到一個紀錄正向分數、負向分數、中性分數、跟綜合情感分數的字典(dict)。這完全就是為了我們這種示範教學設計的好東西。我們先把套件引進來。

import nltk
nltk.download('vader_lexicon')
from nltk.sentiment.vader import SentimentIntensityAnalyzer

  接著用一個簡單的句子嘗試看他會回傳什麼樣的東西,再思考要怎麼變成dataframe。

experiment_sentence = "I am not happy wiht this."
sid = SentimentIntensityAnalyzer() # 初始化工具/幫他取短一點的名字
score = sid.polarity_scores(sid)
score
# 輸出
{'neg': 0.428, 'neu': 0.572, 'pos': 0.0, 'compound': -0.4585}

  這邊可以看到他真的能辨識這句話其實是帶有負面情緒,然後就跟剛剛講的一樣回傳給我們字典的型態,基本上應該用迭代的方法把各個分數分別寫到不同的串列裡面之後變成字典轉換成dataframe就可以了。看起來可能有點複雜,但其實也就幾行程式碼而已啦。

comments_train = pd.read_csv("/content/twitter_training.csv", names=['index', 'product', 'label', 'text'])
comments_test = pd.read_csv("/content/twitter_validation.csv", names=['index', 'product', 'label', 'text'])

neu = []
pos = []
neg = []
total = []

  先把資料讀進來再寫分別幫不同分數寫空的串列,讓等一下算出來的分數有地方去。接著寫一個loop讓sid一行一行計算分數,再把出來的結果寫成字典之後轉換成dataframe。

for rows in range(len(comments_train["text"])):
    score = sid.polarity_scores(str(comments_train.at[rows, "text"]))
    neu.append(score["neu"])
    pos.append(score["pos"])
    neg.append(score["neg"])
    total.append(score["compound"])

train_dict = {
    "neu":neu,
    "pos":pos,
    "neg":neg,
    "total":total
}
train_feature = pd.DataFrame(train_dict)

train_feature.head()

  然後我們就可以看到下面的結果:
https://ithelp.ithome.com.tw/upload/images/20221006/20151687KUc9I5bvpC.png

  接著把一樣的事情對測試集資料再做一次:

neu = []
pos = []
neg = []
total = []
for rows in range(len(comments_test["text"])):
    score = sid.polarity_scores(str(comments_test.at[rows, "text"]))
    neu.append(score["neu"])
    pos.append(score["pos"])
    neg.append(score["neg"])
    total.append(score["compound"])
test_dict = {
    "neu":neu,
    "pos":pos,
    "neg":neg,
    "total":total
}
test_feature = pd.DataFrame(test_dict)

模型訓練

  有了特徵之後我們就可以開始訓練模型了。因為這次的資料事先就分好訓練及跟測試集了,我們直接從scikit learn裡面呼喚Support Vector Machine出來就行。

from sklearn.svm import LinearSVC

model = LinearSVC(random_state=0)
model.fit(train_feature, clean_train["label"])
prediction = model.predict(test_feature)

模型評估

  有了模型預測的結果之後我們要接著做模型表現的評估:

from sklearn.metrics import confusion_matrix, precision_recall_fscore_support, accuracy_score
 
print(confusion_matrix(clean_test["label"], prediction))
evaluation = precision_recall_fscore_support(clean_test["label"], prediction, average='macro')
accuracy = accuracy_score(clean_test["label"], prediction)
print("accuracy: " + str(round(accuracy, 2)) + "\nprecision: " + str(round(evaluation[0], 2)) + "\nrecall: " + str(round(evaluation[1], 2)) + "\nfscore: " + str(round(evaluation[2],2)))
# 輸出
[[199  67]
 [ 88 189]]
accuracy: 0.71
precision: 0.72
recall: 0.72
fscore: 0.71

  做到這邊你就會發現,誒?怎麼結果跟前兩個模型比起來差這麼多?(其實這才是機器學習的現實)首先做的事情不一樣當然不能一起比啦,但百分之70這個數字確實差強人意。這種時候我們可以做的事情就是思考看看是不是有哪幾個特徵並不重要,結果影響到模型的表現。以今天的特徵來說,因為我們的任務是區分正面跟反面評論,中立分數或許根本就不需要拿來看,所以應該要把它拿掉之後再訓練一次模型看看。但是這樣猜來猜去未免太沒有效率,就沒有比較聰明一點的方法嗎?當然有!但是我今天累了,所以明天再來介紹聰明一點的方法XDD(同場加映:機器學習必學概念-資訊熵(entropy))那就明天見囉~


上一篇
[Day 20] 監督式機器學習模型:找那個逃跑空間最大的地方龜就對了!-支持向量機(Support Vector Machine)
下一篇
[Day 22] 機器學習好朋友:到底誰是模型的真朋友?- 特徵重要性(Feature Importance)
系列文
文理組人都能上手的入門 NLP(自然語言處理)31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言