iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 16
2
AI & Machine Learning

以100張圖理解 Neural Network -- 觀念與實踐系列 第 16

Day 16:『長短期記憶網路』(LSTM) 應用 -- 情緒分析(Sentiment Analysis)

前言

現在網友都勇於發聲,網路聲量高漲,往往會引領群眾的意向,引發巨大能量,影響國家命運,例如太陽花運動、埃及茉莉花革命,因此,輿情分析已經變成顯學,如何收集網路正反意見,進行大數據分析,已是各個先進國家政府必修的課程,所以,這次我們就以『情緒分析』(Sentiment Analysis) 為主題,說明 LSTM 如何進行情緒分析。

程式

以下範例檔案名稱為 Sentiment1.py,程式來自利用 Keras 下的 LSTM 进行情感分析,同樣的,我加了一些註解,也可從這裡下載。

# scikit-learn 須升級至 0.19
# pip install -U scikit-learn
# 在 python 執行 nltk.download(), 下載 data
from keras.layers.core import Activation, Dense
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.models import Sequential
from keras.preprocessing import sequence
from sklearn.model_selection import train_test_split
import collections
import nltk
import numpy as np
from keras.models import load_model

## 探索數據分析(EDA)
# 計算訓練資料的字句最大字數
maxlen = 0
word_freqs = collections.Counter()
num_recs = 0
with open('./Sentiment1_training.txt','r+', encoding='UTF-8') as f:
    for line in f:
        label, sentence = line.strip().split("\t")
        words = nltk.word_tokenize(sentence.lower())
        if len(words) > maxlen:
            maxlen = len(words)
        for word in words:
            word_freqs[word] += 1
        num_recs += 1
print('max_len ',maxlen)
print('nb_words ', len(word_freqs))

## 準備數據
MAX_FEATURES = 2000
MAX_SENTENCE_LENGTH = 40
vocab_size = min(MAX_FEATURES, len(word_freqs)) + 2
word_index = {x[0]: i+2 for i, x in enumerate(word_freqs.most_common(MAX_FEATURES))}
word_index["PAD"] = 0
word_index["UNK"] = 1
index2word = {v:k for k, v in word_index.items()}
X = np.empty(num_recs,dtype=list)
y = np.zeros(num_recs)
i=0
# 讀取訓練資料,將每一單字以 dictionary 儲存
with open('./Sentiment1_training.txt','r+', encoding='UTF-8') as f:
    for line in f:
        label, sentence = line.strip().split("\t")
        words = nltk.word_tokenize(sentence.lower())
        seqs = []
        for word in words:
            if word in word_index:
                seqs.append(word_index[word])
            else:
                seqs.append(word_index["UNK"])
        X[i] = seqs
        y[i] = int(label)
        i += 1

# 字句長度不足補空白        
X = sequence.pad_sequences(X, maxlen=MAX_SENTENCE_LENGTH)
# 資料劃分訓練組及測試組
Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, test_size=0.2, random_state=42)
# 模型構建
EMBEDDING_SIZE = 128
HIDDEN_LAYER_SIZE = 64
BATCH_SIZE = 32
NUM_EPOCHS = 10
model = Sequential()
# 加『嵌入』層
model.add(Embedding(vocab_size, EMBEDDING_SIZE,input_length=MAX_SENTENCE_LENGTH))
# 加『LSTM』層
model.add(LSTM(HIDDEN_LAYER_SIZE, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(1))
model.add(Activation("sigmoid"))
# binary_crossentropy:二分法
model.compile(loss="binary_crossentropy", optimizer="adam",metrics=["accuracy"])

# 模型訓練
model.fit(Xtrain, ytrain, batch_size=BATCH_SIZE, epochs=NUM_EPOCHS,validation_data=(Xtest, ytest))

# 預測
score, acc = model.evaluate(Xtest, ytest, batch_size=BATCH_SIZE)
print("\nTest score: %.3f, accuracy: %.3f" % (score, acc))
print('{}   {}      {}'.format('預測','真實','句子'))
for i in range(5):
    idx = np.random.randint(len(Xtest))
    xtest = Xtest[idx].reshape(1,MAX_SENTENCE_LENGTH)
    ylabel = ytest[idx]
    ypred = model.predict(xtest)[0][0]
    sent = " ".join([index2word[x] for x in xtest[0] if x != 0])
    print(' {}      {}     {}'.format(int(round(ypred)), int(ylabel), sent))
    
# 模型存檔
model.save('Sentiment1.h5')  # creates a HDF5 file 'model.h5'
    
##### 自己輸入測試
INPUT_SENTENCES = ['I love it.','It is so boring.', 'I love it althougn it is so boring.']
XX = np.empty(len(INPUT_SENTENCES),dtype=list)
# 轉換文字為數值
i=0
for sentence in  INPUT_SENTENCES:
    words = nltk.word_tokenize(sentence.lower())
    seq = []
    for word in words:
        if word in word_index:
            seq.append(word_index[word])
        else:
            seq.append(word_index['UNK'])
    XX[i] = seq
    i+=1

XX = sequence.pad_sequences(XX, maxlen=MAX_SENTENCE_LENGTH)
# 預測,並將結果四捨五入,轉換為 0 或 1
labels = [int(round(x[0])) for x in model.predict(XX) ]
label2word = {1:'正面', 0:'負面'}
# 顯示結果
for i in range(len(INPUT_SENTENCES)):
    print('{}   {}'.format(label2word[labels[i]], INPUT_SENTENCES[i]))

程式執行

  1. Sentiment1_training.txt 與 Sentiment1.py 必須在同資料夾,程式執行方式如下:
    python Sentiment1.py
  2. 執行時若發生 scikit-learn error,請執行下列指令,升級 scikit-learn 至 0.19 以上:
    pip install -U scikit-learn

程式說明

  1. 程式以 Sentiment1_training.txt 檔案內容當作訓練內容,我們也可以載入『影評資料』(IMDB),不過,Keras已幫我們轉成整數代碼,並不能看到完整處理流程,故採用自行提供的訓練資料。
  2. 前置處理:先找出所有的單字,給予序號,再將每一句訓練資料的單字轉為序號。
  3. 『嵌入』層是幫我們輸入的正整數轉為實數域上的向量,這是『詞嵌入』(Word Embedding)的技術,把一個維數為所有詞的數量的高維空間嵌入到一個維數低得多的連續向量空間中,每個單詞或詞組被映射為實數域上的向量。Google Tomas Mikolov 領導的團隊發明了一套工具『Word2Vec』來進行詞嵌入,訓練向量空間模型的速度比以往的方法都快,『詞嵌入』(Word Embedding)及『Word2Vec』後續再找時間探討。
  4. 因為 Neural Network 是以矩陣運算為主,必須固定長度,所以,訓練前先調整輸入的句子,過長就截掉,過短就補空白。
  5. nltk是一個自然語言處理函數庫(Natural Language Toolkit),同時,它提供許多訓練資料,在 DOS 下執行 python,再輸入 nltk.download(),可下載 相關資料。
  6. LSTM 模型常會造成過度擬合(Overfit),因此使用LSTM函數時,不要忘了加上參數 dropout 及 recurrent_dropout,前者是針對 input 抑制過度擬合,後者是針對上一個 output state 抑制過度擬合。

結語

我們再次見識到 Neural Network 的強大威力,要作到輿情分析,只要先撰寫爬蟲程式,抓取網路評論,再將資料餵入LSTM 模型,統計正負評或作更細的分類,就可以自己開民調公司了(開開玩笑!!),下次見了。


上一篇
Day 15:『長短期記憶網路』(Long Short Term Memory Network, LSTM)
下一篇
Day 17:GRU (Gated Recurrent Unit) 概念介紹與實作
系列文
以100張圖理解 Neural Network -- 觀念與實踐31

2 則留言

0

在這個資料集中的X是句子用onehot轉成向量,Y是情緒(我看了一下原文,只有1與0),不知道我的理解有沒有錯。
如果沒有錯的話,想請教一個問題,你文章都說,不管是RNN或是LSTM都是透過把前面字詞的權重,用來預測中間或是下一個要出現的字,這樣的問題中,X是前面出現過的字,Y是即將要出現的字,LSTM跟RNN的差別只是在,權重的安排RNN會遞減,LSTM會選擇性紀錄。但是怎麼樣把這樣的問題mapping成情緒分析問題?
具體一點來問,我不太清楚,他recurrent的東西如果是裡面的前後字詞,那是不是會比較像在input layer中每個node之間做forward prop,而不是在layer間做forward prop,那怎麼把w傳遞到下一個layer,並且最後預測出最後的結果?

看更多先前的回應...收起先前的回應...
  1. Y是情緒只有1與0 ==> 是的,最頂層使用 sigmoid,output 會介於[0,1]之間,loss="binary_crossentropy" 計算損失函數時,每個觀察值與預測值之差,也是介於[0,1]之間,所以,輸出經過int(),只會得到 0 or 1。
  2. Simple NN 只會考慮前一個Layer的傳來的input,而RNN/LSTM會額外考慮『同一個Layer』前面的output,請參考 https://ithelp.ithome.com.tw/articles/10193469 的公式。
  3. 以本例而言,在訓練時,模型會額外考慮input的前文,不會單純只考慮特定的單字決定output(正面/負面),因此,RNN/LSTM 適用於NLP,包括情緒分析。

以上個人淺見,如有疏漏或錯誤,請不令指正。

另外,為了讓了RNN/LSTM模型能更準確判斷前文,可以試試看加入 Stop words,把不重要的單字(I, you, it, ...)移除掉,讓前文的影響力更明確,記憶(Ct)判斷更精準,建議讀者可以異想天開的實驗,也許您可能是下一位AI大師喔。

抱歉一直打擾你,因為我最近卡在這裡,無法理解QQ
你這幾篇文章我都有認真看,不過還是不是很清楚。我想確定一下你的意思,「RNN/LSTM會額外考慮『同一個Layer』前面的output」的意思,是不是指,假設onehot的情況下,第1個epoch的Input layer中某個字(node)的output,會被傳遞到第2個epoch的Input layer同個字的Input?
但我真的好難想像,但這樣真的好難跟「例如,聽到『我看見...』,下面應該就是一個名詞,通常是人或物,又例如『它很...』,下面應該就是一個形容詞,例如『辣』、『鹹』。」這種東西連結起來。
它可以讓預測變得更準確的原因感覺又更神秘了。

感謝你認真地回應TAT

不要客氣,相互切磋,才會進步。
simple NN 不考慮 activation function 時,y=Wx+b。
RNN 不考慮 activation function 時,公式如下,會多考慮 h(t-1), 這裡的h可簡單視為y。
https://ithelp.ithome.com.tw/upload/images/20171210/20001976JvMnANowAr.jpg

0
chouyuting
iT邦新手 5 級 ‧ 2018-10-01 10:49:27

您好,我在spyder上執行Sentiment1.py時出現了問題FileNotFoundError: [Errno 2] No such file or directory: './Sentiment1_training.txt'

我有先執行過nltk.download(),並下載了他所有的package,也試過將Sentiment1.py放在資料下載的地方,請問我該如何解決?
https://ithelp.ithome.com.tw/upload/images/20181001/20112112TzIiQ5KLDC.png

看更多先前的回應...收起先前的回應...

Sentiment1_training.txt 未放入程式所在的目錄。

那麼請問該如何取得Sentiment1_training.txt檔案呢?不是用nltk.download()嗎?

這裡下載

找到了,萬分感謝

我要留言

立即登入留言