MaDi's Blog

一個紀錄自己在轉職軟體工程師路上的學習小空間

0%

用LSTM模型分類IMDB電影資料集評論

IMDB 網路資料庫 (Internet Movie Database),是一個電影相關的線上資料庫,內部資料集共有50000筆影評,訓練資料與測試資料各25000筆,每一筆影評都被分為正評負評

本篇文章利用LSTM長短期記憶體模型去分類IMDB中的影評

LSTM

載入套件/讀取資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Basic
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# Sklearn
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle

# DL
import keras
from keras.layers import Embedding, LSTM, Dense, Dropout
from keras.models import Sequential
from keras.utils import plot_model

# IMDB
from keras.datasets import imdb

把IMDB的資料集讀取進來

1
2
# 僅保留訓練資料集前10000個最常出現的單詞,捨棄低頻的單詞
(train_data,train_labels),(test_data,test_labels) = imdb.load_data(num_words=10000)

看一下訓練資料與測試資料的維度

1
2
3
4
print(train_data.shape) 
print(train_labels.shape)
print(test_data.shape)
print(test_labels.shape)

(25000,)
(25000,)
(25000,)
(25000,)

看一下訓練資料的第一筆資料的長相

1
2
# 看第一筆load進來的train_data的前十個字,已經做好text_to_sequence
display(train_data[0][0:10])

[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65]

1
2
# 看第一筆train_labels的資料,0:負評,1:正評
display(train_labels[0])

1

把訓練資料與測試資料轉成DataFrame觀察

1
2
3
4
df = pd.DataFrame()
df['TRAIN_text_to_sequence'] = train_data
df['TEST_text_to_sequence'] = test_data
df

將數字序列轉為評論

因為原始資料都已經是把文字轉成序列,看不到原始的評論內容,所以我們讀取IMDB的詞彙字典,試著把數字序列轉回評論的文字

1
2
# 下載IMDB的字典 word_index -> word:index
word_index = imdb.get_word_index()

{ ‘fawn’: 34701,
‘tsukino’: 52006,
‘nunnery’: 52007,
… }

1
2
# 鍵值對調 reverse_word_index -> index:word
reverse_word_index = {value:key for key,value in word_index.items()}

{ 34701: ‘fawn’,
52006: ‘tsukino’,
52007: ‘nunnery’,
… }

查看每一筆評論內容,這裡index要扣3,因為index=0,1和2分別是“填充”,“序列開始”,“未知”的保留索引,查不到的文字以?表示

1
2
3
def read_IMDB_text(train_data):
text = ' '.join([reverse_word_index.get(i-3,'?') for i in train_data])
return text

把DataFrame的數字序列轉成評論文字來看看原文長怎樣

1
2
3
df['TRAIN_text'] = df['TRAIN_text_to_sequence'].apply(read_IMDB_text)
df['TEST_text'] = df['TEST_text_to_sequence'].apply(read_IMDB_text)
df.head()

zero-padding

可以看到每一筆評論的數字序列長度其實都不一樣

1
2
for seq in df['TRAIN_text_to_sequence'][:10]:
print(len(seq), seq[:5], ' ...')

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
2
max_seq_len = max([len(seq) for seq in df['TRAIN_text_to_sequence']])
max_seq_len

2494

此處用平均值作為MAX_SEQUENCE_LENGTH,有時候會用第三分位數

1
2
mean_seq_len = np.mean([len(seq) for seq in df['TRAIN_text_to_sequence']])
mean_seq_len

238.71364

為了讓所有序列的長度一致,我們採用zero-padding的方式,設定MAX_SEQUENCE_LENGTH作為容許的最大評論長度,若長度超過此數字序列的長度,爾後的數字會被刪掉;若長度不足數字序列的長度,則在詞彙前面補零。

Keras內建有sequence.pad_sequences函式協助我們做到zero-padding的功能

1
2
3
4
5
6
7
MAX_SEQUENCE_LENGTH = 240  #設定最大容許的序列長度(以長度平均值)

X_train = keras.preprocessing.sequence.pad_sequences(train_data,maxlen=MAX_SEQUENCE_LENGTH)
Y_train = keras.utils.to_categorical(train_labels)

display(X_train)
display(Y_train)

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
2
display(X_train.shape)
display(Y_train.shape)

(25000, 240)
(25000, 2)

切割train/valid資料

設定train跟valid的切割比例,此處以4:1做為切割的比例。

1
2
3
4
5
6
7
8
9
VALIDATION_RATIO = 0.2
RANDOM_STATE = 914

x_train, x_val, y_train, y_val = train_test_split(X_train, Y_train, test_size=VALIDATION_RATIO, random_state=RANDOM_STATE)

print(x_train.shape)
print(x_val.shape)
print(y_train.shape)
print(y_val.shape)

(20000, 240)
(5000, 240)
(20000, 2)
(5000, 2)

架構LSTM模型

參數設定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 有幾個分類
NUM_CLASSES = 2

# 在語料庫裡有多少詞彙
MAX_NUM_WORDS = 40000

# 一個詞向量的維度
NUM_EMBEDDING_DIM = 256

# Dense層輸出的向量維度
NUM_DENSE_UNITS = 256

# LSTM 輸出的向量維度
NUM_LSTM_UNITS = 128

# 決定一次要放多少評論給模型訓練
BATCH_SIZE = 512

# 決定模型要看整個訓練資料集幾遍
NUM_EPOCHS = 20

建立 LSTM 架構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 線性model
model = Sequential()

# 詞嵌入,把最大長度為240的數字序列轉成一個詞向量的序列,而每個詞向量的維度為256
model.add(Embedding(MAX_NUM_WORDS, NUM_EMBEDDING_DIM, input_length=MAX_SEQUENCE_LENGTH))

# 訓練期間drop掉20%神經元
model.add(Dropout(0.2))

# LSTM層輸出128個神經元
model.add(LSTM(NUM_LSTM_UNITS))

# 隱藏層有256個神經元
model.add(Dense(units=NUM_DENSE_UNITS, activation='relu'))

# 輸出層的活化函式為sigmoid(適合二分類)
model.add(Dense(units=NUM_CLASSES, activation='sigmoid'))

輸出模型的架構圖

1
2
3
4
5
6
plot_model(
model,
to_file='model.png', # 輸出成png檔
show_shapes=True,
show_layer_names=False,
rankdir='LR')

模型詳細的各層參數

1
model.summary()

模型編譯: 以compile函數定義優化函數(optimizer)、損失函數(loss)及成效衡量指標(mertrics)

1
2
3
4
model.compile(
optimizer='adam', # 自己經驗:adam>rmsprop
loss='binary_crossentropy', # 二分法
metrics=['accuracy'])

實際訓練模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
history = model.fit(

# 輸入是兩個長度為 MAX_SEQUENCE_LENGTH 的數字序列
x = x_train,
y = y_train,
batch_size = BATCH_SIZE,
epochs = NUM_EPOCHS,

# 每個epoch完後計算驗證資料集上的loss以及acc
validation_data=(x_val, y_val),

# 每個epoch隨機洗牌訓練資料集
shuffle=True
)

history找出acc/valid的歷程資料來繪圖

1
2
3
4
5
6
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

繪圖函式

1
2
3
4
5
6
7
def plot_fig(epochs,train,val,train_label,val_label,type):
plt.plot(epochs, train, 'bo', label=train_label)
plt.plot(epochs, val, 'b', label=val_label)
plt.title('Training and validation '+type)
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

畫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
2
3
df['IMDB_label_ans'] = test_labels
df['test_predict'] = np.argmax(predictions, axis=1)
df

計算預測的結果

1
2
pred_acc = df[df['IMDB_label_ans']==df['test_predict']].count()[0]/df.shape[0]
pred_acc

0.84208

實測幾次後發現,單純用LSTM模型去做IMDB資料集的分類,平均準確率約0.86左右

完整程式碼

IMDB-LSTM

參考