IMDB 網路資料庫 (Internet Movie Database),是一個電影相關的線上資料庫,內部資料集共有50000筆影評,訓練資料與測試資料各25000筆,每一筆影評都被分為
正評
或負評
。本篇文章利用LSTM長短期記憶體模型去分類IMDB中的影評
LSTM
載入套件/讀取資料
1 | # Basic |
把IMDB的資料集讀取進來
1 | # 僅保留訓練資料集前10000個最常出現的單詞,捨棄低頻的單詞 |
看一下訓練資料與測試資料的維度
1 | print(train_data.shape) |
(25000,)
(25000,)
(25000,)
(25000,)
看一下訓練資料的第一筆資料的長相
1 | # 看第一筆load進來的train_data的前十個字,已經做好text_to_sequence |
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65]
1 | # 看第一筆train_labels的資料,0:負評,1:正評 |
1
把訓練資料與測試資料轉成DataFrame觀察
1 | df = pd.DataFrame() |
將數字序列轉為評論
因為原始資料都已經是把文字轉成序列,看不到原始的評論內容,所以我們讀取IMDB的詞彙字典,試著把數字序列轉回評論的文字
1 | # 下載IMDB的字典 word_index -> word:index |
{ ‘fawn’: 34701,
‘tsukino’: 52006,
‘nunnery’: 52007,
… }
1 | # 鍵值對調 reverse_word_index -> index:word |
{ 34701: ‘fawn’,
52006: ‘tsukino’,
52007: ‘nunnery’,
… }
查看每一筆評論內容,這裡index要扣3,因為index=0,1和2分別是“填充”,“序列開始”,“未知”的保留索引,查不到的文字以?表示
1 | def read_IMDB_text(train_data): |
把DataFrame的數字序列轉成評論文字來看看原文長怎樣
1 | df['TRAIN_text'] = df['TRAIN_text_to_sequence'].apply(read_IMDB_text) |
zero-padding
可以看到每一筆評論的數字序列長度其實都不一樣
1 | for seq in df['TRAIN_text_to_sequence'][:10]: |
218 [1, 14, 22, 16, 43] …
189 [1, 194, 1153, 194, 8255] …
141 [1, 14, 47, 8, 30] …
550 [1, 4, 2, 2, 33] …
147 [1, 249, 1323, 7, 61] …
…
最長的序列甚至有快達到2500個詞彙
1 | max_seq_len = max([len(seq) for seq in df['TRAIN_text_to_sequence']]) |
2494
此處用平均值作為MAX_SEQUENCE_LENGTH
,有時候會用第三分位數
1 | mean_seq_len = np.mean([len(seq) for seq in df['TRAIN_text_to_sequence']]) |
238.71364
為了讓所有序列的長度一致,我們採用zero-padding
的方式,設定MAX_SEQUENCE_LENGTH
作為容許的最大評論長度,若長度超過此數字序列的長度,爾後的數字會被刪掉;若長度不足數字序列的長度,則在詞彙前面補零。
Keras內建有sequence.pad_sequences
函式協助我們做到zero-padding的功能
1 | MAX_SEQUENCE_LENGTH = 240 #設定最大容許的序列長度(以長度平均值) |
array([[ 0, 0, 0, …, 19, 178, 32],
[ 0, 0, 0, …, 16, 145, 95],
[ 0, 0, 0, …, 7, 129, 113],
…,
[ 0, 0, 0, …, 4, 3586, 2],
[ 0, 0, 0, …, 12, 9, 23],
[ 0, 0, 0, …, 204, 131, 9]], dtype=int32)
array([[0., 1.],
[1., 0.],
[1., 0.],
…,
[1., 0.],
[0., 1.],
[1., 0.]], dtype=float32)
做完zero-padding之後的長度,可以看到數字序列的長度都被統一成240個
1 | display(X_train.shape) |
(25000, 240)
(25000, 2)
切割train/valid資料
設定train跟valid的切割比例,此處以4:1做為切割的比例。
1 | VALIDATION_RATIO = 0.2 |
(20000, 240)
(5000, 240)
(20000, 2)
(5000, 2)
架構LSTM模型
參數設定
1 | # 有幾個分類 |
建立 LSTM 架構
1 | # 線性model |
輸出模型的架構圖
1 | plot_model( |
模型詳細的各層參數
1 | model.summary() |
模型編譯: 以compile函數定義優化函數(optimizer)、損失函數(loss)及成效衡量指標(mertrics)
1 | model.compile( |
實際訓練模型
1 | history = model.fit( |
從history
找出acc
/valid
的歷程資料來繪圖
1 | acc = history.history['accuracy'] |
繪圖函式
1 | def plot_fig(epochs,train,val,train_label,val_label,type): |
畫loss圖
1 | plot_fig(epochs,loss,val_loss,'train loss','validation loss','loss') |
畫acc圖
1 | plot_fig(epochs,acc,val_acc,'train acc','validation acc','acc') |
預測測試的資料
把測試資料做zero-padding
1 | x_test = keras.preprocessing.sequence.pad_sequences(test_data, maxlen=MAX_SEQUENCE_LENGTH) |
用剛剛訓練好的模型去預測測試資料
1 | predictions = model.predict([x_test]) |
把LSTM模型預測的資料跟正解放在一起做比較
1 | df['IMDB_label_ans'] = test_labels |
計算預測的結果
1 | pred_acc = df[df['IMDB_label_ans']==df['test_predict']].count()[0]/df.shape[0] |
0.84208
實測幾次後發現,單純用LSTM模型去做IMDB資料集的分類,平均準確率約0.86左右