3.4 電影影評分類
二元分類霹琼,或者稱為二值分類,可能是應用最廣泛的機器學習問題。通過學習本例茄靠,你將掌握如何基于文本內(nèi)容將影評分為正茂契、負二類。
3.4.1 IMDB數(shù)據(jù)集
本文將從互聯(lián)網(wǎng)電影數(shù)據(jù)庫(IMDB)獲取50,000個流行電影影評作為數(shù)據(jù)集慨绳。這里將其分割為25,000個影評的訓練集和25,000個影評的測試集掉冶。其中每個數(shù)據(jù)集都包含50%的好評和50%的差評真竖。
為什么要將數(shù)據(jù)集分割成訓練集和測試集呢?因為測試機器學習模型所使用的數(shù)據(jù)集不能和訓練該模型的數(shù)據(jù)集是同一個厌小。在訓練集上表現(xiàn)良好的模型恢共,并不意味著一定會在“未曾見過”的測試集上也有相同的表現(xiàn)。也就是說璧亚,你更關注的是訓練的模型在新數(shù)據(jù)集上的性能(因為訓練集數(shù)據(jù)的標簽是已知的讨韭,很顯然這些是不需要去預測的)。例如癣蟋,可能你的模型可以將訓練樣本和對應的目標在內(nèi)存中進行一一映射透硝,但是這個模型對從“未見過的”數(shù)據(jù)無法進行預測。下一章會更詳細的討論該觀點疯搅。
Keras已經(jīng)包括IMDB數(shù)據(jù)集濒生,并進行了數(shù)據(jù)預處理:影評(單詞序列)轉換成整數(shù)序列,這里每個整數(shù)代表對應單詞在字典的索引值幔欧。
下面的代碼將加載IMDB數(shù)據(jù)集罪治,當你首次運行該代碼,將會在服務器上下載大約80M的數(shù)據(jù)礁蔗。
#Listing 3.1 Loading the IMDB dataset
from keras.datasets import imdv
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
num_words=10000)
設置參數(shù)num_words=10000觉义,保留訓練集中詞頻為top 10000的單詞,低頻單詞丟棄瘦麸。變量train_data和test_data是影評列表(list)谁撼,每條影評看成是單詞序列,用單詞索引進行編碼滋饲。train_labels和test_labels是0和1的列表厉碟,其中0代表差評(negative),1代表好評(positive)屠缭。
>>> train_data[0]
[1, 14, 22, 16, ... 178, 32]
>>> train_labels[0]
1
前面限制影評中的單詞詞頻為top 10000箍鼓,所以單詞的索引不會超過10000:
>>> max([max(sequence) for sequence in train_data])
9999
下面來個好玩的,如何將編碼后的影評進行解碼得到單詞呢呵曹?
'''
word_index is a dictionary mapping
words to an integer index.
'''
word_index = imdb.get_word_index()
reverse_word_index = dict(
'''
Reverses it, mapping integer indices to words
'''
[(value, key) for (key, value) in word_index.items()])
decoded_review = ' '.join(
'''
Decodes the review. Note that the indices
are offset by 3 because 0, 1, and 2 are
reserved indices for “padding,” “start of
sequence,” and “unknown.”
'''
[reverse_word_index.get(i - 3, '?') for i in train_data[0]])
3.4.2 準備數(shù)據(jù)
神經(jīng)網(wǎng)絡不能輸入整數(shù)列表款咖,所以需要將整數(shù)列表轉換成張量。有兩種方式可以實現(xiàn):
- 填充列表:先將列表填充成相同長度的奄喂,再轉成形狀為(樣本數(shù)铐殃,單詞索引長度)的整數(shù)張量。接著用神經(jīng)網(wǎng)絡的第一層layer(Embedding layer)處理整數(shù)張量跨新。
- one-hot編碼:one-hot編碼是將單詞索引轉成0富腊、1的向量。比如域帐,將序列[3, 5]轉成10,000維向量赘被,其中索引3和5的值為1是整,其它索引對應的值為0。然后使用神經(jīng)網(wǎng)絡的Dense layer作為第一層layer處理浮點型向量數(shù)據(jù)民假。
下面采用后一種方法向量化數(shù)據(jù):
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
#Creates an all-zero matrix of shape (len(sequences), dimension)
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
'''
Sets specific indices of results[i] to 1s
'''
results[i, sequence] = 1.
return results
'''
Vectorized training data and test data
'''
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
下面看下向量化后的結果:
>>> x_train[0]
array([ 0., 1., 1., ..., 0., 0., 0.]
同理浮入,向量化對應的label:
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
數(shù)據(jù)準備好了,就等著傳入神經(jīng)網(wǎng)絡模型羊异。
3.4.3 構建神經(jīng)網(wǎng)絡模型
輸入數(shù)據(jù)為向量事秀,label為標量(1和0),相當簡單球化。一系列帶有relu激活函數(shù)的全聯(lián)接層(Dense layer)的神經(jīng)網(wǎng)絡就可以很好的解決影評分類:Dense(16, activation='relu')秽晚。
每個Dense layer設置隱藏單元(hidden unit)數(shù)為16。hidden unit是layer的一維表征空間筒愚。由第二章得知赴蝇,每個帶有relu激勵函數(shù)的Dense layer可以實現(xiàn)下面鏈式的張量操作:
output = relu(dot(W, input) + b)
16個hidden unit意味著權重矩陣的形狀為(輸入維度,16):輸入數(shù)據(jù)與權重矩陣W點積的結果是投影到16維表征空間(巢掺,接著加上偏置向量b句伶,然后應用relu激活操作)。給人的直覺是表征空間的維數(shù)即是中間學習表示的自由度陆淀。hidden unit越多(高維表征空間)允許神經(jīng)網(wǎng)絡學習更復雜的表示考余,但同時也讓神經(jīng)網(wǎng)絡計算成本增加,可能導致不可預期的模式(模式會提高訓練集上的性能轧苫,降低測試集上的表現(xiàn)楚堤,也就是常說的“過擬合”現(xiàn)象)。
逐層排列的Dense layer架構有兩個關鍵點:
- 選擇多少層Dense layer
- 每個Dense layer選擇多少個hidden unit
在第四章中的常規(guī)性原則將會指導你對上述問題做出選擇含懊。此時身冬,你就暫時相信下面的架構選擇哦:
- 兩個具有16個hidden unit的中間層
- 第三層layer將輸出當前影評的情感預測值(標量)
中間層使用relu作為激活函數(shù),最后一層layer使用sigmoid激活函數(shù)岔乔,輸出0到1之間的概率值酥筝。激活函數(shù)relu(rectified linear unit(修正線性單元),ReLU)雏门,對于所有負值都置為0嘿歌,而正值不變,見圖3.4茁影;而激活函數(shù)sigmoid將變量值映射為[0, 1]區(qū)間宙帝,可以看作是概率值,見圖3.5募闲。
圖3.4 Relu激活函數(shù)
圖3.5 Sigmoid激活函數(shù)
圖3.6 三層layer神經(jīng)網(wǎng)絡
圖3.6顯示了神經(jīng)網(wǎng)絡的大體架構步脓。下面是Keras的實現(xiàn),和前面MNIST數(shù)字識別的例子類似:
#Listing 3.3 The model definition
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu')) model.add(layers.Dense(1, activation='sigmoid'))
什么是激活函數(shù)呢?為什么要使用激活函數(shù)沪编?
沒有像relu這樣的激活函數(shù)(俗稱非線性單元)的話,那Dense layer只剩下兩個線性操作:點積和加法年扩。
output = dot(W, input) + b
這樣layer只能學習到輸入數(shù)據(jù)的線性變換(仿射變換):layer的假設空間就成了輸入數(shù)據(jù)的所有可能的線性變換到16維表征空間的集合蚁廓。這樣的假設空間并不能學習到多層layer的表征,因為一系列的線性layer等效于一個線性操作:layer數(shù)的增加并不會擴展假設空間厨幻。
為了從深度學習中得到更豐富的假設空間相嵌,你需要加入非線性部分,或者激活函數(shù)况脆。relu是深度學習中最常用的激活函數(shù)之一饭宾,但是也有其它可選:prelu激活函數(shù)、elu激活函數(shù)等等格了。
接著看铆,選擇損失函數(shù)和優(yōu)化器。因為本例是二值分類問題盛末,神經(jīng)網(wǎng)絡模型輸出是概率值(網(wǎng)絡的最后一層layer帶有sigmoid激活函數(shù)弹惦,輸出一維數(shù)據(jù)),所以最好的損失函數(shù)是binary_crossentropy損失函數(shù)悄但。但這不是唯一的選擇棠隐,你也可以使用mean_squared_error損失函數(shù)。一般輸出為概率值的模型優(yōu)先選擇交叉熵損失函數(shù)(crossentropy)檐嚣。交叉熵是信息論中的指標助泽,用來度量概率分布之間的距離。本例是用來度量實際分布與預測值的差距嚎京。
這里為模型選擇binary_crossentropy損失函數(shù)和rmsprop優(yōu)化器嗡贺。注意監(jiān)控模型訓練過程中的準確度。
#Listing 3.4 Compiling the model
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
上面?zhèn)魅氲膬?yōu)化器optimizer挖藏、損失函數(shù)loss和指標metrics三個參數(shù)都是字符串型暑刃,這是因為'rmsprop'、'binary_crossentropy'和'accuracy'都是Keras內(nèi)置實現(xiàn)的膜眠。如果想配置自定義的優(yōu)化器或者損失函數(shù)或者指標函數(shù)岩臣,你可以用參數(shù)optimizer傳入優(yōu)化器類,見代碼3.5宵膨;用參數(shù)loss和metrics傳入函數(shù)對象架谎,見代碼3.6:
#Listing 3.5 Configuring the optimiser
from keras import optimisers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss='binary_crossentropy',
metrics=['accuracy'])
#Listing 3.6 Using custom losses and metrics
from keras import losses
from keras import metrics
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
loss=losses.binary_crossentropy,
metrics=[metrics.binary_accuracy])
3.4.4 驗證模型
為了監(jiān)控模型訓練過程中模型在新數(shù)據(jù)上的準確度,需要從原始的訓練數(shù)據(jù)集中分出10,000個樣本作為驗證集辟躏。
#Listing 3.7 Setting aside a validation set
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
現(xiàn)在開始模型訓練谷扣,迭代訓練的epoch(在所有訓練集數(shù)據(jù)上跑完一次稱為一個epoch)為20個,mini-batch大小為512。訓練過程中監(jiān)控驗證集數(shù)據(jù)上的損失函數(shù)和準確度会涎,設置參數(shù)validation_data裹匙。
#Listing 3.8 Training your model
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))
在CPU上訓練模型時,每個epoch耗時不到2秒末秃,整個訓練過程大概持續(xù)20秒概页。每個epoch結束時,會有個短暫的停頓练慕,這時模型會計算驗證集數(shù)據(jù)上的損失值和準確度惰匙。
注意,調(diào)用model.fit()會返回一個History對象铃将,該對象有個history成員项鬼,它是一個包含訓練過程的每個數(shù)據(jù)的字典。下面來看下:
>>> history_dict = history.history
>>> history_dict.keys()
[u'acc', u'loss', u'val_acc', u'val_loss']
history字典有四項:模型訓練和驗證中每個指標一項劲阎。接下來的兩段代碼绘盟,使用Matplotlib在同一幅圖中繪制訓練集損失和驗證集損失,見圖3.7悯仙;同時將訓練集準確度和驗證集準確度繪制在同一幅圖中奥此,見圖3.8。注意雁比,因為神經(jīng)網(wǎng)絡的初始化是隨機的稚虎,可能會導致你的結果與本例稍有差別。
#Listing 3.9 Plotting the training and validation loss
import matplotlib.pyplot as pet
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
epochs = range(1, len(acc) + 1)
'''
“bo” is for “blue dot.”
“b” is for “solid blue line.”
'''
plt.plot(epochs, loss_values, 'bo', label='Training loss') plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
圖3.7 迭代訓練中訓練集和驗證集的損失趨勢
#Listing 3.10 Plotting the training and validation accuracy
#Clears the figure
plt.clf()
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
圖3.8 迭代訓練中的訓練集和驗證集的準確度趨勢
正如你所見偎捎,隨著迭代訓練的epoch蠢终,訓練損失值不斷減小,而準確度不斷提升茴她。這也是執(zhí)行梯度下降優(yōu)化器期待的結果:不斷迭代訓練減小損失寻拂。但是驗證集數(shù)據(jù)上的損失值和準確度表現(xiàn)的并不是如此:驗證集在第四個epoch后效果達到最好。這也是前面提醒過的:模型在訓練集上表現(xiàn)良好并不代表在新數(shù)據(jù)集上也有同樣的表現(xiàn)丈牢。準確地來講祭钉,這是過擬合(overfiting):在迭代訓練第2個epoch后,模型在訓練集上出現(xiàn)了過度優(yōu)化己沛,最終的學習表征像是為訓練集特制的慌核,對新數(shù)據(jù)喪失了泛化能力。
本例中申尼,為了防止過擬合出現(xiàn)垮卓,需要在迭代訓練3個epoch后停止訓練。一般來講师幕,我們可以使用多種技術解決過擬合粟按,這些會在第四章中詳細介紹。
下面從頭迭代訓練4個epoch生成新的神經(jīng)網(wǎng)絡,并在測試集上評估效果:
#Listing 3.11 Retraining a model from scratch
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,))) model.add(layers.Dense(16, activation='relu')) model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['accuracy'])
model.fit(x_train, y_train, epochs=4, batch_size=512)
results = model.evaluate(x_test, y_test)
最好的評估結果如下:
>>> results
[0.2929924130630493, 0.88327999999999995]
這個相當直白的方法獲得了88%的準確度灭将。使用最新的方法疼鸟,你將會得到接近95%的準確度。
3.4.5 模型預測
訓練完神經(jīng)網(wǎng)絡模型庙曙,使用predict方法進行影評情感預測:
>>> model.predict(x_test)
array([[ 0.98006207]
[ 0.99758697]
[ 0.99975556]
...,
[ 0.82167041]
[ 0.02885115]
[ 0.65371346]], dtype=float32)
正如你所看到的結果愚臀,神經(jīng)網(wǎng)絡模型對一些樣本數(shù)據(jù)的預測結果自信(概率為0.99或者更高,或者0.01或者更蟹),但是對另外一些的預測結果不是太自信(概率為0.6馋袜,0.4的情況)男旗。
3.4.6 延伸實驗
下面的一些實驗使得神經(jīng)網(wǎng)絡的架構選擇更合理些(雖然還是有待提升的空間):
- 本例使用的兩個隱藏層⌒辣睿可以嘗試選擇一個或者三個隱藏層察皇,看下會怎樣影響驗證集和測試集的準確度;
- 選擇更多的hidden unit或者更少的hidden unit:32個unit泽台,64個unit等等什荣;
- 使用mse損失函數(shù)代替binary_crossentropy損失函數(shù);
- 使用tanh激活函數(shù)(在神經(jīng)網(wǎng)絡早期常用的激活函數(shù))代替relu激活函數(shù)怀酷。
3.4.7 總結
從本實例學到的知識點:
- 原始數(shù)據(jù)集預處理為張量傳入神經(jīng)網(wǎng)絡稻爬。單詞序列編碼為二值向量或者其它形式;
- 一系列帶有relu激活函數(shù)的Dense layer能解決廣泛的問題蜕依,包括情感分類桅锄,后續(xù)會常用到的;
- 二值分類問題(輸出兩個類別)中样眠,最后的一個Dense layer帶有一個sigmoid激活函數(shù)和一個單元:網(wǎng)絡輸出是0到1之間的標量友瘤,代表概率值;
- 二分類問題中有sigmoid標量輸出的檐束,損失函數(shù)選擇binary_crossentropy損失函數(shù)辫秧;
- rmsprop優(yōu)化器對于大部分深度學習模型來說是足夠好的選擇;
- 隨著在訓練集上表現(xiàn)越來越好被丧,神經(jīng)網(wǎng)絡模型開始過擬合盟戏,在新數(shù)據(jù)上表現(xiàn)越來越差。關注驗證集上的監(jiān)控指標
未完待續(xù)甥桂。抓半。。
Enjoy!
翻譯本書系列的初衷是格嘁,覺得其中把深度學習講解的通俗易懂笛求。不光有實例,也包含作者多年實踐對深度學習概念、原理的深度理解探入。最后說不重要的一點狡孔,F(xiàn)ran?ois Chollet是Keras作者。
聲明本資料僅供個人學習交流蜂嗽、研究苗膝,禁止用于其他目的。如果喜歡植旧,請購買英文原版辱揭。
俠天,專注于大數(shù)據(jù)病附、機器學習和數(shù)學相關的內(nèi)容问窃,并有個人公眾號分享相關技術文章。
若發(fā)現(xiàn)以上文章有任何不妥完沪,請聯(lián)系我域庇。