2017年9月4日 星期一

[Toolkit] Keras - IMDb 網路電影資料集

Source From Here (Ch13-14) 
前言 
情緒分析 (sentiment analysis) 又稱為意見探勘 (opinion mining). 是使用 "自然語言處理", 文字分析等方法, 找出作者某些話題上的態度, 情感, 評價或情緒. 情緒分析的商業價值, 可以提早得知顧客對公司或產品觀感, 以調整銷售策略方向. IMDb 網路資料庫 (Internet Movie Database), 是一個電影相關的線上資料庫. IMDb 開始於 1990 年, 自 1998 年起成為亞馬遜旗下的網站, 至今已經累積大量的電影資訊. IMDb 共收錄了四百多萬作品資料. 

IMDb 資料集共有 50,000 筆 "影評文字", 分為訓練資料與測試資料各 25,000 筆, 每一筆 "影評文字" 都被標記成 "正面評價" 或 "負面評價". 我們希望能建立一個模型, 經過大量 "影評文字" 訓練後, 此模型能用於預測 "影評文字" 是 "正面評價" 或 "負面評價". 如下面深度學習模型用於辨識 IMDb "影評文字", 可分為訓練與預測階段: 



Keras 自然語言處理介紹 
Keras 自然語言處理 IMDb 影評文字步驟如下: 

下載 IMDB 資料集 
下載讀取 IMDB 資料集 (http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz), 分為訓練與測試資料: 
- ch13_1.py (link
  1. #!/usr/bin/env python3  
  2. import os, urllib, logging  
  3. import tarfile  
  4. from urllib.request import urlretrieve  
  5.   
  6. ###################  
  7. # Step0: Global setting  
  8. ####################  
  9. LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'  
  10. logging.basicConfig(format=LOG_FORMAT)  
  11. logger = logging.getLogger('IMDBb')  
  12. logger.setLevel(logging.DEBUG)  
  13.   
  14. ###################  
  15. # Step1: Download IMDB  
  16. ####################  
  17. url = "http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz"  
  18. filepath = 'datas/aclImdb_v1.tar.gz'  
  19. dataPath = 'datas/aclImdb'  
  20. if not os.path.isfile(filepath):  
  21.     print('Downloading from {}...'.format(url))  
  22.     result = urlretrieve(url, filepath)  
  23.     print('download: {}'.format(result))  
  24.   
  25. if not os.path.isdir(dataPath):  
  26.     print('Extracting {} to datas...'.format(filepath))  
  27.     tfile = tarfile.open(filepath, 'r:gz')  
  28.     result = tfile.extractall('datas/')  
下載並解壓縮後的檔案結構: 



讀取 IMDB 資料 
這邊將建立函數來讀入影評文件提供後續訓練與測試使用: 
  1. ###################  
  2. # Step2: Download IMDB  
  3. ####################  
  4. from keras.preprocessing import sequence  
  5. from keras.preprocessing.text import Tokenizer  
  6. import re  
  7.   
  8. def rm_tags(text):  
  9.     r'''  
  10.     Remove HTML markers  
  11.     '''  
  12.     re_tag = re.compile(r'<[^>]+>')  
  13.     return re_tag.sub('', text)  
  14.   
  15. def read_files(filetype):  
  16.     r'''  
  17.     Read data from IMDb folders  
  18.   
  19.     @param filetype(str):  
  20.         "train" or "test"  
  21.   
  22.     @return:  
  23.         Tuple(List of labels, List of articles)  
  24.     '''  
  25.     file_list = []  
  26.     positive_path = os.path.join(os.path.join(dataPath, filetype), 'pos')  
  27.     for f in os.listdir(positive_path):  
  28.         file_list.append(os.path.join(positive_path, f))  
  29.   
  30.     negative_path = os.path.join(os.path.join(dataPath, filetype), 'neg')  
  31.     for f in os.listdir(negative_path):  
  32.         file_list.append(os.path.join(negative_path, f))  
  33.   
  34.     logger.debug('Read {} with {} files...'.format(filetype, len(file_list)))  
  35.     all_labels = ([1] * 12500 + [0] * 12500)  
  36.     all_texts = []  
  37.     for fi in file_list:  
  38.         logger.debug('Read {}...'.format(fi))  
  39.         with open(fi, encoding='utf8') as fh:  
  40.             all_texts += [rm_tags(" ".join(fh.readlines()))]  
  41.   
  42.     return all_labels, all_texts  
  43.   
  44. train_labels, train_text = read_files('train')  
  45. test_labels, test_text = read_files('test')  
查看 IMDB 資料 
讀進 IMDb 資料集進入記憶體後, 接著我們便可以如下查看影評文字: 
>>> from ch13_1 import *
>>> train_labels, train_texts = read_files('train')
>>> train_texts[0] // 查看第 0 筆 "影評文字"
'Zentropa has much in common with The Third Man, another noir-like film set among the rubble of postwar Europe. Like TTM, .... Grim, but intriguing, and frightening.'
>>> train_labels[0] // 第 0 筆的評價 label 為 1, 也就是 正面評價
1
>>> train_texts[12501]
'"I Am Curious: Yellow" is a risible and pretentious steaming pile. It doesn\'t matter what one\'s political views...'
>>> train_labels[12501] // 第 12,501 筆的評價 label 為 0, 也就是 負面評價
0

建立 Token 
接下來將介紹如何建立 Token 以及 Token 的特性. 
  1. ###################  
  2. # Step3: Tokenize  
  3. ####################  
  4. MAX_LEN_OF_TOKEN = 100  
  5.   
  6. logger.info('Tokenizing document...')  
  7. token = Tokenizer(num_words = 2000)  
  8. ''' Create a dictionary of 2,000 words '''  
  9. token.fit_on_texts(train_text)  
  10. ''' Read in all training text and select top 2,000 words according to frequency sorting descendingly '''  
  11.   
  12. logger.info('Total {} document being handled...'.format(token.document_count))  
  13. logger.info('Top 10 word index:')  
  14. c = 0  
  15. for t,i in token.word_index.items():  
  16.     print("\t'{}'\t{}".format(t, i))  
  17.     c += 1  
  18.     if c == 10:  
  19.         break  
  20. print("")  
  21. logger.info('Translating raw text into token number list...')  
  22. x_train_seq = token.texts_to_sequences(train_text)  
  23. x_test_seq = token.texts_to_sequences(test_text)  
  24.   
  25. logger.info('Padding/Trimming the token number list to same length={}...'.format(MAX_LEN_OF_TOKEN))  
  26. x_train = sequence.pad_sequences(x_train_seq, maxlen=MAX_LEN_OF_TOKEN)  
  27. x_test = sequence.pad_sequences(x_test_seq, maxlen=MAX_LEN_OF_TOKEN)  
Keras 建立 MLP, RNN, LSTM 模型進行情緒分析 
前面已經完成 IMDb 資料的前處理, 接著我們便可以使用 Keras 來建立多層感知器 (MLP), 遞歸神經網路 RNN (Recurrent Neural Network), 短期記憶 LSTM (Long-Short Term Memory) 的模型, 進行 IMDb 情緒分析並且訓練模型已進行預測. 

建立 MLP 模型 
底下代碼依序進行: 
1. 建立 Embedding 層: 將 "數字 list" 轉為 "向量 list". (這邊使用 32 維度來表式數字 1-2000
2. 建立多層感知器 (MLP): 
* 平坦層: 共有 3,200 = 32 * 100 個神經元, 共有 100 個 Token 數字, 每一個 Token 使用 32 維度來表示數字 1-2000.
* 隱藏層: 共有 256 個神經元
* 輸出層: 只有一個神經元. 1 表示正面評價; 0 表示負面評價.

  1. ###################  
  2. # Step4: Building MODEL  
  3. ####################  
  4. from keras.models import Sequential  
  5. from keras.layers.core import Dense, Dropout, Activation, Flatten  
  6. from keras.layers.embeddings import Embedding  
  7.   
  8. MODEL_TYPE = 'mlp'  
  9.   
  10. if MODEL_TYPE == 'mlp':  
  11.     model = Sequential()  
  12.     model.add(Embedding(output_dim=32,  
  13.                         input_dim=2000,  
  14.                         input_length=100))  
  15.     model.add(Dropout(0.2))  
  16.     '''Drop 20% neuron during training '''  
  17.     model.add(Flatten())  
  18.     model.add(Dense(units=256, activation='relu'))  
  19.     ''' Total 256 neuron in hidden layers'''  
  20.     model.add(Dropout(0.35))  
  21.     model.add(Dense(units=1, activation='sigmoid'))  
  22.     ''' Define output layer with 'sigmoid activation' '''  
  23.   
  24. logger.info('Model summary:\n{}\n'.format(model.summary()))  
訓練模型 
當我們建立深度學習模型後, 就可以使用 Back propagation 進行訓練: 
  1. ###################  
  2. # Step5: Training  
  3. ###################  
  4. logger.info('Start training process...')  
  5. model.compile(loss='binary_crossentropy',  
  6.               optimizer='adam',  
  7.               metrics=['accuracy'])  
  8. train_history = model.fit(x_train, train_labels, batch_size=100, epochs=10, verbose=2, validation_split=0.2)  
執行訓練過程: 
...
Epoch 2/10
3s - loss: 0.2710 - acc: 0.8900 - val_loss: 0.4341 - val_acc: 0.8122
Epoch 3/10
3s - loss: 0.1654 - acc: 0.9395 - val_loss: 0.6880 - val_acc: 0.7552
Epoch 4/10
3s - loss: 0.0857 - acc: 0.9698 - val_loss: 0.9303 - val_acc: 0.7354
Epoch 5/10
3s - loss: 0.0520 - acc: 0.9816 - val_loss: 1.0649 - val_acc: 0.7426
Epoch 6/10
3s - loss: 0.0347 - acc: 0.9886 - val_loss: 1.1937 - val_acc: 0.7402
Epoch 7/10
3s - loss: 0.0275 - acc: 0.9902 - val_loss: 1.3158 - val_acc: 0.7432
Epoch 8/10
3s - loss: 0.0257 - acc: 0.9918 - val_loss: 1.3365 - val_acc: 0.7506
Epoch 9/10
3s - loss: 0.0236 - acc: 0.9915 - val_loss: 1.3566 - val_acc: 0.7610
Epoch 10/10
3s - loss: 0.0282 - acc: 0.9894 - val_loss: 1.1596 - val_acc: 0.7816

評估模型準確率 
接著我們可以使用測試資料集來評估訓練出來模型的準確率: 
  1. ###################  
  2. # Step6: Evaluation  
  3. ###################  
  4. logger.info('Start evaluation...')  
  5. scores = model.evaluate(x_test, test_labels, verbose=1)  
  6. print("")  
  7. logger.info('Score={}'.format(scores[1]))  
某次的執行結果如下: 
2017-08-26 15:26:35,660 - IMDBb - INFO - Start evaluation...
23840/25000 [===========================>..] - ETA: 0s
2017-08-26 15:26:36,564 - IMDBb - INFO - Score=0.79736

進行預測 
目前模型的準確率接近 80%, 接下來我們要使用此模型進行預測: 
>>> from ch13_1 import *
>>> predict = model.predict_classes(x_test) // 進行預測
>>> predict[:10] // 顯示前 10 筆預測結果
array([[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1],
[1]], dtype=int32)


>>> predict_classes = predict.reshape(-1) // 將預測結果轉為一維陣列
>>> predict_classes[:10]
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1], dtype=int32)

接著底下函數方便我們檢視測試資料某筆的預測結果: 
  1. predict_classes = model.predict_classes(x_test).reshape(-1)  
  2. print("")  
  3. sentiDict = {1:'Pos'0:'Neg'}  
  4. def display_test_Sentiment(i):  
  5.     r'''  
  6.     Show prediction on i'th test data  
  7.     '''  
  8.     logger.debug('{}\'th test data:\n{}\n'.format(i, test_text[i]))  
  9.     logger.info('Ground truth: {}; prediction result: {}'.format(sentiDict[test_labels[i]], sentiDict[predict_classes[i]]))  
  10.   
  11. logger.info('Show prediction on 2\'th test data:')  
  12. display_test_Sentiment(2)  
文字處理使用較大的字典, 擷取更多的文字 
上面的模型準確率為 0.8, 我們希望能夠再提升預測的準確率, 方法如下: 
* 建立字典的字數: 原本是 2,000 個字的字典, 這邊會增加到 3,800 個字的字典.
* 數字 list 截長補短的長度: 原本 "數字 list" 長度都是 100 個數字, 這邊改為 380 個數字.

- ch13_2.py (link
  1. ...  
  2. ###################  
  3. # Step3: Tokenize  
  4. ####################  
  5. MAX_LEN_OF_TOKEN = 380  
  6. DICT_NUM_WORDS = 3800  
  7.   
  8. logger.info('Tokenizing document...')  
  9. token = Tokenizer(num_words = DICT_NUM_WORDS)  
  10. ''' Create a dictionary of 2,000 words '''  
  11. token.fit_on_texts(train_text)  
  12. ''' Read in all training text and select top 2,000 words according to frequency sorting descendingly '''  
  13.   
  14. logger.info('Total {} document being handled...'.format(token.document_count))  
  15. logger.info('Top 10 word index:')  
  16. c = 0  
  17. for t,i in token.word_index.items():  
  18.     print("\t'{}'\t{}".format(t, i))  
  19.     c += 1  
  20.     if c == 10:  
  21.         break  
  22. print("")  
  23. logger.info('Translating raw text into token number list...')  
  24. x_train_seq = token.texts_to_sequences(train_text)  
  25. x_test_seq = token.texts_to_sequences(test_text)  
  26.   
  27. logger.info('Padding/Trimming the token number list to length={}...'.format(MAX_LEN_OF_TOKEN))  
  28. x_train = sequence.pad_sequences(x_train_seq, maxlen=MAX_LEN_OF_TOKEN)  
  29. x_test = sequence.pad_sequences(x_test_seq, maxlen=MAX_LEN_OF_TOKEN)  
  30. ...  
執行結果: 
# ./ch13_2.py
...
2017-08-28 21:35:27,261 - IMDBb - INFO - Score=0.85144
 // Accuracy up from 0.8 to 0.85

遞歸神經網路 RNN 模型介紹 
接下來我們將使用遞迴神經網路 RNN (Recurrent Neural Network) 進行 IMDB 情緒分析, 並且訓練模型進行預測, 最後產生預測結果 (正面評價 或 負面評價). 

為何要使用 RNN 模型 
之前我們介紹的 Mnist 資料集 (辨識數字影像), Cifar 資料集 (辨識照片) 影像並不會隨著時間而改變, 所以使用多層感知器 (MLP), 或 卷積神經網路 (CNN), 都能達到不錯的效果. 然而在人工智慧所要解決的問題中, 很多是順序的, 例如自然語言處理 (同一時間只能聽到一個字, 之前的語言會影響之後語言的意義), 視訊影片處理 (影片是一張張的照片, 依照時間順序所組成), 氣象觀測資料 (氣象資訊隨著時間不斷改變), 股市交易資料 (股市開盤後, 股價隨著時間不斷變動). 

以自然語言處理為例, 當我們在聽人說話時, 因為同一時間只能聽一個字, 所以我們會根據之前時間點所聽到的話語, 來理解目前時間點這句話的意義. 例如 "我家住在台北市", "我在市政府上班". 因為前一句話說已經住在台北市, 所以當我們理解後面那一句話會認為市政府是 "台北市" 的市政府, 不會是其他縣市的市政府. 因為多層感知器 (MLP) 或卷積神經網路 (CNN), 都只能依照當下的狀態進行辨識, 如果要處理時間序列的問題, 就必須使用 RNN 與 LSTM 模型. 

遞歸神經網路 RNN 模型原理 
遞歸神經網路 RNN (Recurrent Neural Network) 其原理是將神經元的輸出, 再接回神經元的輸入. 這樣的設計使神經網路具有 "記憶" 的功能, 如下圖: 
* X 是神經元的輸入
* O 是神經元的輸出
* (U,V,W) 都是神經網路的參數
* S 是隱藏狀態, 代表著神經元網路的 "記憶"



如上圖共有三個時間點依序是 "t-1", "t" "t+1". 在 "t" 的時間點: 
1. Xt 是 t 時間點神經網路的輸入
2. Ot 是 t 時間點神經網路的輸出
3. (U,V,W) 都是神經網路的參數, W 參數是 t-1 時間點的輸出, 但是作為 t 時間點的輸入.
4. St 是隱藏狀態, 代表神經網路上的記憶, 是經過目前時間點的輸入 Xt, 再加上上個時間點的狀態 St-1, 再加上 U 與 W 的參數, 共同評估的結果:
St = f(U*Xt + W*St-1)

上面的 f 函數是非線性函數, 例如 Relu. 

使用 Keras RNN 模型進行 IMDb 情緒分析 

STEP1. 建立模型 > STEP2. 查看模型摘要 > STEP3. 評估模型準確率 
使用 SimpleRNN 建立 16 個神經元的 RNN, 完整代碼請參考 "ch13_3.py", 準確率約 84%. 

長短期記憶 LSTM 模型介紹 
長短期記憶 LSTM (Long-Short Term Memory) 也是一種時間遞歸神經網路 (RNN). 專門設計來解決 RNN 的 Long-term dependencies 問題. 

RNN 的 long-term dependencies 問題 
前一小節介紹的 RNN 再訓練時會有 Long-term dependencies 的問題, 這是由於 RNN 模型在訓練時會遇到 Gradient vanishing 或 Gradient exploding 的問題. 訓練時在計算 Back Propagation 時, 梯度頃向於每一時刻遞增或遞減, 經過段時間後會發散到無窮大或收斂到零. long-term dependencies 的問題簡單說就是在每一個時間間隔不斷增大時, RNN 會喪失學習到連接遠的訊息能力

長短期記憶 LSTM 介紹 
簡單說 RNN 只能記得短期記憶, 不能記得長期記憶. 所以深度學習專家 Schmidhuber 提出了長短期記憶 LSTM (Long-Short Term Memory) 模型, 專門設計來解決 RNN 的 long-term dependencies 問題. 考慮下圖: 



在 LSTM 神經網路中, 每一個神經元相當於一個記憶細胞 (Cell) 說明如下: 
* Xt: 輸入向量
* ht: 輸出向量
* Ct: "Cell" 是 LSTM 的記憶細胞的狀態 (cell state). LSTM 透過一種名為 "閘門" (gate) 的機制控制 cell 記憶細胞的狀態, 刪減或增加其中的訊息.
* It: "輸入閘門" (Input Gate) 用於決定哪些訊息要被增加到 cell
* Ft: "忘記閘門" (Forget Gate) 用於決定哪些訊息要自 cell 刪除.
* Ot: "輸出閘門" (Output Gate) 用於決定哪些訊息要自 cell 輸出.

使用 Keras LSTM 模型進行 IMDb 情緒分析 

STEP1. 建立模型 > STEP2. 查看模型摘要 > STEP3. 評估模型準確率 
使用 LSTM 建立 32 個神經元的 LSTM 層, 完整代碼請參考 "ch13_3.py". 使用 LSTM 模型準確率提升至 86%. 

Supplement 
循環神經網絡 (RNN, Recurrent Neural Networks) 介紹 
A Beginner’s Guide to Recurrent Networks and LSTMs 
Recurrent Neural Networks, LSTM and GRU

沒有留言:

張貼留言

[ Python 文章收集 ] SQLAlchemy quick start with PostgreSQL

Source From   Here   Preface   This is a quick tutorial for getting started with   SQLAlchemy Core API .   Prerequisites   In this quick st...