NLP入門(十)使用LSTM進行文本情感分析

情感分析簡介

??文本情感分析(Sentiment Analysis)是自然語言處理(NLP)方法中常見的應用,也是一個有趣的基本任務劫窒,尤其是以提煉文本情緒內(nèi)容為目的的分類。它是對帶有情感色彩的主觀性文本進行分析已维、處理女气、歸納和推理的過程。
??本文將介紹情感分析中的情感極性(傾向)分析捣作。所謂情感極性分析,指的是對文本進行褒義鹅士、貶義券躁、中性的判斷。在大多應用場景下掉盅,只分為兩類也拜。例如對于“喜愛”和“厭惡”這兩個詞,就屬于不同的情感傾向趾痘。
??本文將詳細介紹如何使用深度學習模型中的LSTM模型來實現(xiàn)文本的情感分析慢哈。

文本介紹及語料分析

??我們以某電商網(wǎng)站中某個商品的評論作為語料(corpus.csv),該數(shù)據(jù)集的下載網(wǎng)址為:https://github.com/renjunxiang/Text-Classification/blob/master/TextClassification/data/data_single.csv 永票,該數(shù)據(jù)集一共有4310條評論數(shù)據(jù)卵贱,文本的情感分為兩類:“正面”和“反面”,該數(shù)據(jù)集的前幾行如下:

evaluation,label
用了一段時間侣集,感覺還不錯键俱,可以,正面
電視非常好,已經(jīng)是家里的第二臺了世分。第一天下單编振,第二天就到本地了,可是物流的人說車壞了臭埋,一直催踪央,客服也幫著催,到第三天下午5點才送過來瓢阴。父母年紀大了畅蹂,買個大電視畫面清晰,趁著耳朵還好使荣恐,享受幾年魁莉。,正面
電視比想象中的大好多,畫面也很清晰募胃,系統(tǒng)很智能旗唁,更多功能還在摸索中,正面
不錯,正面
用了這么多天了,感覺還不錯痹束。夏普的牌子還是比較可靠检疫。希望以后比較耐用,現(xiàn)在是考量質(zhì)量的時候祷嘶。,正面
物流速度很快屎媳,非常棒夺溢,今天就看了電視,非常清晰烛谊,非常流暢风响,一次非常完美的購物體驗,正面
非常好,客服還特意打電話做回訪,正面
物流小哥不錯丹禀,辛苦了状勤,東西還沒用,正面
送貨速度快,質(zhì)量有保障双泪,活動價格挺好的持搜。希望用的久,不出問題焙矛。,正面

??接著我們需要對語料做一個簡單的分析:

  • 數(shù)據(jù)集中的情感分布葫盼;
  • 數(shù)據(jù)集中的評論句子長度分布。

??使用以下Python腳本村斟,我們可以統(tǒng)計出數(shù)據(jù)集中的情感分布以及評論句子長度分布贫导。

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
from itertools import accumulate

# 設置matplotlib繪圖時的字體
my_font = font_manager.FontProperties(fname="/Library/Fonts/Songti.ttc")

# 統(tǒng)計句子長度及長度出現(xiàn)的頻數(shù)
df = pd.read_csv('./corpus.csv')
print(df.groupby('label')['label'].count())

df['length'] = df['evaluation'].apply(lambda x: len(x))
len_df = df.groupby('length').count()
sent_length = len_df.index.tolist()
sent_freq = len_df['evaluation'].tolist()

# 繪制句子長度及出現(xiàn)頻數(shù)統(tǒng)計圖
plt.bar(sent_length, sent_freq)
plt.title("句子長度及出現(xiàn)頻數(shù)統(tǒng)計圖", fontproperties=my_font)
plt.xlabel("句子長度", fontproperties=my_font)
plt.ylabel("句子長度出現(xiàn)的頻數(shù)", fontproperties=my_font)
plt.savefig("./句子長度及出現(xiàn)頻數(shù)統(tǒng)計圖.png")
plt.close()

# 繪制句子長度累積分布函數(shù)(CDF)
sent_pentage_list = [(count/sum(sent_freq)) for count in accumulate(sent_freq)]

# 繪制CDF
plt.plot(sent_length, sent_pentage_list)

# 尋找分位點為quantile的句子長度
quantile = 0.91
#print(list(sent_pentage_list))
for length, per in zip(sent_length, sent_pentage_list):
    if round(per, 2) == quantile:
        index = length
        break
print("\n分位點為%s的句子長度:%d." % (quantile, index))

# 繪制句子長度累積分布函數(shù)圖
plt.plot(sent_length, sent_pentage_list)
plt.hlines(quantile, 0, index, colors="c", linestyles="dashed")
plt.vlines(index, 0, quantile, colors="c", linestyles="dashed")
plt.text(0, quantile, str(quantile))
plt.text(index, 0, str(index))
plt.title("句子長度累積分布函數(shù)圖", fontproperties=my_font)
plt.xlabel("句子長度", fontproperties=my_font)
plt.ylabel("句子長度累積頻率", fontproperties=my_font)
plt.savefig("./句子長度累積分布函數(shù)圖.png")
plt.close()

輸出的結果如下:

label
正面    1908
負面    2375
Name: label, dtype: int64

分位點為0.91的句子長度:183.

可以看到,正反面兩類情感的比例差不多蟆盹。句子長度及出現(xiàn)頻數(shù)統(tǒng)計圖如下:

句子長度及出現(xiàn)頻數(shù)統(tǒng)計圖

句子長度累積分布函數(shù)圖如下:

句子長度累積分布函數(shù)圖

可以看到脱盲,大多數(shù)樣本的句子長度集中在1-200之間,句子長度累計頻率取0.91分位點日缨,則長度為183左右钱反。

使用LSTM模型

??接著我們使用深度學習中的LSTM模型來對上述數(shù)據(jù)集做情感分析,筆者實現(xiàn)的模型框架如下:

LSTM模型

完整的Python代碼如下:

# -*- coding: utf-8 -*-

import pickle
import numpy as np
import pandas as pd
from keras.utils import np_utils, plot_model
from keras.models import Sequential
from keras.preprocessing.sequence import pad_sequences
from keras.layers import LSTM, Dense, Embedding, Dropout
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 導入數(shù)據(jù)
# 文件的數(shù)據(jù)中匣距,特征為evaluation, 類別為label.
def load_data(filepath, input_shape=20):
    df = pd.read_csv(filepath)

    # 標簽及詞匯表
    labels, vocabulary = list(df['label'].unique()), list(df['evaluation'].unique())

    # 構造字符級別的特征
    string = ''
    for word in vocabulary:
        string += word

    vocabulary = set(string)

    # 字典列表
    word_dictionary = {word: i+1 for i, word in enumerate(vocabulary)}
    with open('word_dict.pk', 'wb') as f:
        pickle.dump(word_dictionary, f)
    inverse_word_dictionary = {i+1: word for i, word in enumerate(vocabulary)}
    label_dictionary = {label: i for i, label in enumerate(labels)}
    with open('label_dict.pk', 'wb') as f:
        pickle.dump(label_dictionary, f)
    output_dictionary = {i: labels for i, labels in enumerate(labels)}

    vocab_size = len(word_dictionary.keys()) # 詞匯表大小
    label_size = len(label_dictionary.keys()) # 標簽類別數(shù)量

    # 序列填充面哥,按input_shape填充,長度不足的按0補充
    x = [[word_dictionary[word] for word in sent] for sent in df['evaluation']]
    x = pad_sequences(maxlen=input_shape, sequences=x, padding='post', value=0)
    y = [[label_dictionary[sent]] for sent in df['label']]
    y = [np_utils.to_categorical(label, num_classes=label_size) for label in y]
    y = np.array([list(_[0]) for _ in y])

    return x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary

# 創(chuàng)建深度學習模型毅待, Embedding + LSTM + Softmax.
def create_LSTM(n_units, input_shape, output_dim, filepath):
    x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary = load_data(filepath)
    model = Sequential()
    model.add(Embedding(input_dim=vocab_size + 1, output_dim=output_dim,
                        input_length=input_shape, mask_zero=True))
    model.add(LSTM(n_units, input_shape=(x.shape[0], x.shape[1])))
    model.add(Dropout(0.2))
    model.add(Dense(label_size, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

    plot_model(model, to_file='./model_lstm.png', show_shapes=True)
    model.summary()

    return model

# 模型訓練
def model_train(input_shape, filepath, model_save_path):

    # 將數(shù)據(jù)集分為訓練集和測試集尚卫,占比為9:1
    # input_shape = 100
    x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary = load_data(filepath, input_shape)
    train_x, test_x, train_y, test_y = train_test_split(x, y, test_size = 0.1, random_state = 42)

    # 模型輸入?yún)?shù),需要自己根據(jù)需要調(diào)整
    n_units = 100
    batch_size = 32
    epochs = 5
    output_dim = 20

    # 模型訓練
    lstm_model = create_LSTM(n_units, input_shape, output_dim, filepath)
    lstm_model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=1)

    # 模型保存
    lstm_model.save(model_save_path)

    N = test_x.shape[0]  # 測試的條數(shù)
    predict = []
    label = []
    for start, end in zip(range(0, N, 1), range(1, N+1, 1)):
        sentence = [inverse_word_dictionary[i] for i in test_x[start] if i != 0]
        y_predict = lstm_model.predict(test_x[start:end])
        label_predict = output_dictionary[np.argmax(y_predict[0])]
        label_true = output_dictionary[np.argmax(test_y[start:end])]
        print(''.join(sentence), label_true, label_predict) # 輸出預測結果
        predict.append(label_predict)
        label.append(label_true)

    acc = accuracy_score(predict, label) # 預測準確率
    print('模型在測試集上的準確率為: %s.' % acc)

if __name__ == '__main__':
    filepath = './corpus.csv'
    input_shape = 180
    model_save_path = './corpus_model.h5'
    model_train(input_shape, filepath, model_save_path)

對上述模型尸红,共訓練5次吱涉,訓練集和測試集比例為9:1,輸出的結果為:

......
Epoch 5/5
......
3424/3854 [=========================>....] - ETA: 2s - loss: 0.1280 - acc: 0.9565
3456/3854 [=========================>....] - ETA: 1s - loss: 0.1274 - acc: 0.9569
3488/3854 [==========================>...] - ETA: 1s - loss: 0.1274 - acc: 0.9570
3520/3854 [==========================>...] - ETA: 1s - loss: 0.1287 - acc: 0.9568
3552/3854 [==========================>...] - ETA: 1s - loss: 0.1290 - acc: 0.9564
3584/3854 [==========================>...] - ETA: 1s - loss: 0.1284 - acc: 0.9568
3616/3854 [===========================>..] - ETA: 1s - loss: 0.1284 - acc: 0.9569
3648/3854 [===========================>..] - ETA: 0s - loss: 0.1278 - acc: 0.9572
3680/3854 [===========================>..] - ETA: 0s - loss: 0.1271 - acc: 0.9576
3712/3854 [===========================>..] - ETA: 0s - loss: 0.1268 - acc: 0.9580
3744/3854 [============================>.] - ETA: 0s - loss: 0.1279 - acc: 0.9575
3776/3854 [============================>.] - ETA: 0s - loss: 0.1272 - acc: 0.9579
3808/3854 [============================>.] - ETA: 0s - loss: 0.1279 - acc: 0.9580
3840/3854 [============================>.] - ETA: 0s - loss: 0.1281 - acc: 0.9581
3854/3854 [==============================] - 18s 5ms/step - loss: 0.1298 - acc: 0.9577
......
給父母買的外里,特意用了一段時間再來評價怎爵,電視非常好,沒有壞點和損壞盅蝗,界面也很簡潔鳖链,便于操作,稍微不足就是開機會比普通電視慢一些墩莫,這應該是智能電視的通病吧芙委,如果可以希望微鯨大大可以更新系統(tǒng)優(yōu)化下開機時間~電視真的很棒逞敷,性價比爆棚,值得大家考慮購買灌侣。 客服很細心推捐,快遞小哥很耐心的等我通電驗貨,態(tài)度非常好侧啼。 負面 正面
長須鯨和海獅回答都很及時牛柒,雖然物流不夠快但是服務不錯電視不錯,對比了樂視小米和微鯨論性價比還是微鯨好點 負面 負面
所以看不到4k效果慨菱,但是應該可以焰络。 自帶音響戴甩,中規(guī)中矩吧符喝,好像沒有別人說的好。而且甜孤,到現(xiàn)在沒連接上我的漫步者协饲,這個非常不滿意,因為看到網(wǎng)上說好像普通3.5mm的連不上或者連上了聲音小缴川。希望廠家接下來開發(fā)的電視有改進茉稠。不知道我要不要換個音響。其他的用用再說把夸。 放在地上的是跟我混了兩年的tcl而线,天氣受潮,修了一次恋日,下崗了膀篮。 最后,我也覺得底座不算太穩(wěn)岂膳,湊合著用誓竿。 負面 負面
電視機一般,低端機不要求那么高咯谈截。 負面 負面
很好筷屡,兩點下單上午就到了,服務很好簸喂。 正面 正面
幫朋友買的毙死,好好好好好好好好 正面 正面
......
模型在測試集上的準確率為: 0.9020979020979021.

可以看到,該模型在訓練集上的準確率為95%以上喻鳄,在測試集上的準確率為90%以上规哲,效果還是相當不錯的。

模型預測

??接著诽表,我們利用剛剛訓練好的模型唉锌,對新的數(shù)據(jù)進行測試隅肥。筆者隨機改造上述樣本的評論,然后預測其情感傾向袄简。情感預測的Python代碼如下:

# -*- coding: utf-8 -*-

# Import the necessary modules
import pickle
import numpy as np
from keras.models import load_model
from keras.preprocessing.sequence import pad_sequences


# 導入字典
with open('word_dict.pk', 'rb') as f:
    word_dictionary = pickle.load(f)
with open('label_dict.pk', 'rb') as f:
    output_dictionary = pickle.load(f)

try:
    # 數(shù)據(jù)預處理
    input_shape = 180
    sent = "電視剛安裝好腥放,說實話,畫質(zhì)不怎么樣绿语,很差秃症!"
    x = [[word_dictionary[word] for word in sent]]
    x = pad_sequences(maxlen=input_shape, sequences=x, padding='post', value=0)

    # 載入模型
    model_save_path = './sentiment_analysis.h5'
    lstm_model = load_model(model_save_path)

    # 模型預測
    y_predict = lstm_model.predict(x)
    label_dict = {v:k for k,v in output_dictionary.items()}
    print('輸入語句: %s' % sent)
    print('情感預測結果: %s' % label_dict[np.argmax(y_predict)])

except KeyError as err:
    print("您輸入的句子有漢字不在詞匯表中,請重新輸入吕粹!")
    print("不在詞匯表中的單詞為:%s." % err)

輸出結果如下:

輸入語句: 電視剛安裝好种柑,說實話,畫質(zhì)不怎么樣匹耕,很差聚请!
情感預測結果: 負面

??讓我們再嘗試著測試一些其他的評論:

輸入語句: 物超所值,真心不錯
情感預測結果: 正面
輸入語句: 很大很好稳其,方便安裝驶赏!
情感預測結果: 正面
輸入語句: 卡,慢既鞠,死機煤傍,閃退。
情感預測結果: 負面
輸入語句: 這種貨色就這樣吧嘱蛋,別期待怎樣蚯姆。
情感預測結果: 負面
輸入語句: 啥服務態(tài)度碼,出了事情一個推一個洒敏,送貨安裝還收我50
情感預測結果: 負面
輸入語句: 京東服務很好龄恋!但我買的這款電視兩天后就出現(xiàn)這樣的問題,很后悔買了這樣的電視
情感預測結果: 負面
輸入語句: 產(chǎn)品質(zhì)量不錯桐玻,就是這位客服的態(tài)度十分惡劣篙挽,對相關服務不予解釋說明,缺乏耐心镊靴,
情感預測結果: 負面
輸入語句: 很滿意铣卡,電視非常好。護眼模式偏竟,很好煮落,也很清晰。
情感預測結果: 負面

總結

??當然踊谋,該模型并不是對一切該商品的評論都會有好的效果蝉仇,還是應該針對特定的語料去訓練,去預測。
??本文主要介紹了LSTM模型在文本情感分析方面的應用轿衔,該項目已上傳Github沉迹,地址為: https://github.com/percent4/Sentiment_Analysis

注意:不妨了解下筆者的微信公眾號: Python爬蟲與算法(微信號為:easy_web_scrape)害驹, 歡迎大家關注~

參考文獻

  1. Python機器學習 -- NLP情感分析:https://blog.csdn.net/qq_38328378/article/details/81198322
  2. 數(shù)據(jù)集來源:https://github.com/renjunxiang/Text-Classification/blob/master/TextClassification/data/data_single.csv
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鞭呕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子宛官,更是在濱河造成了極大的恐慌葫松,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件底洗,死亡現(xiàn)場離奇詭異腋么,居然都是意外死亡,警方通過查閱死者的電腦和手機亥揖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門珊擂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人徐块,你說我怎么就攤上這事未玻≡侄” “怎么了胡控?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旁趟。 經(jīng)常有香客問我昼激,道長,這世上最難降的妖魔是什么锡搜? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任橙困,我火速辦了婚禮,結果婚禮上耕餐,老公的妹妹穿的比我還像新娘凡傅。我一直安慰自己,他們只是感情好肠缔,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布夏跷。 她就那樣靜靜地躺著,像睡著了一般明未。 火紅的嫁衣襯著肌膚如雪槽华。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天趟妥,我揣著相機與錄音猫态,去河邊找鬼。 笑死,一個胖子當著我的面吹牛亲雪,可吹牛的內(nèi)容都是我干的勇凭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼义辕,長吁一口氣:“原來是場噩夢啊……” “哼套像!你這毒婦竟也來了?” 一聲冷哼從身側響起终息,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤夺巩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后周崭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柳譬,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年续镇,在試婚紗的時候發(fā)現(xiàn)自己被綠了美澳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡摸航,死狀恐怖制跟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酱虎,我是刑警寧澤雨膨,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站读串,受9級特大地震影響聊记,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恢暖,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一排监、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸认轨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脱拼,卻和暖如春瞒瘸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熄浓。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工情臭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留省撑,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓俯在,卻偏偏與公主長得像竟秫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子跷乐,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容