環(huán)境搭建
案例為Google TensorFlow Demo
影評(píng)分為“正面”或“負(fù)面”影評(píng)。這是一個(gè)二元分類(又稱為兩類分類)的示例,也是一種重要且廣泛適用的機(jī)器學(xué)習(xí)問(wèn)題
案例使用的是 tf.keras,它是一種用于在 TensorFlow 中構(gòu)建和訓(xùn)練模型的高階 API
下載 IMDB 數(shù)據(jù)集
TensorFlow 中包含 IMDB 數(shù)據(jù)集。google已對(duì)該數(shù)據(jù)集進(jìn)行了預(yù)處理,將影評(píng)(字詞序列)轉(zhuǎn)換為整數(shù)序列,其中每個(gè)整數(shù)表示字典中的一個(gè)特定字詞。
以下代碼會(huì)將 IMDB 數(shù)據(jù)集下載到你的計(jì)算機(jī)上(如果您已下載該數(shù)據(jù)集,則會(huì)使用緩存副本)
準(zhǔn)備數(shù)據(jù)
影評(píng)(整數(shù)數(shù)組)必須轉(zhuǎn)換為張量,然后才能饋送到神經(jīng)網(wǎng)絡(luò)中。可以通過(guò)以下兩種方法實(shí)現(xiàn)這種轉(zhuǎn)換:
對(duì)數(shù)組進(jìn)行獨(dú)熱編碼,將它們轉(zhuǎn)換為由 0 和 1 構(gòu)成的向量。例如,序列 [3, 5] 將變成一個(gè) 10000 維的向量,除索引 3 和 5 轉(zhuǎn)換為 1 之外,其余全轉(zhuǎn)換為 0。然后,將它作為網(wǎng)絡(luò)的第一層,一個(gè)可以處理浮點(diǎn)向量數(shù)據(jù)的密集層。這種方法會(huì)占用大量?jī)?nèi)存,需要一個(gè)大小為
num_words * num_reviews
的矩陣。或者,我們可以填充數(shù)組,使它們都具有相同的長(zhǎng)度,然后創(chuàng)建一個(gè)形狀為
max_length * num_reviews
的整數(shù)張量。我們可以使用一個(gè)能夠處理這種形狀的嵌入層作為網(wǎng)絡(luò)中的第一層。
在本教程中,用第二種方法。
由于影評(píng)的長(zhǎng)度必須相同,我們將使用 pad_sequences 函數(shù)將長(zhǎng)度標(biāo)準(zhǔn)化:
構(gòu)建模型
神經(jīng)網(wǎng)絡(luò)通過(guò)堆疊層創(chuàng)建而成,這需要做出兩個(gè)架構(gòu)方面的主要決策:
要在模型中使用多少個(gè)層?
要針對(duì)每個(gè)層使用多少個(gè)隱藏單元?
在本示例中,輸入數(shù)據(jù)由字詞-索引數(shù)組構(gòu)成。要預(yù)測(cè)的標(biāo)簽是 0 或 1。
接下來(lái),為此問(wèn)題構(gòu)建一個(gè)模型
按順序堆疊各個(gè)層以構(gòu)建分類器:
第一層是 Embedding 層。該層會(huì)在整數(shù)編碼的詞匯表中查找每個(gè)字詞-索引的嵌入向量。模型在接受訓(xùn)練時(shí)會(huì)學(xué)習(xí)這些向量。這些向量會(huì)向輸出數(shù)組添加一個(gè)維度。生成的維度為:(batch, sequence, embedding)。
接下來(lái),一個(gè) GlobalAveragePooling1D 層通過(guò)對(duì)序列維度求平均值,針對(duì)每個(gè)樣本返回一個(gè)長(zhǎng)度固定的輸出向量。這樣,模型便能夠以盡可能簡(jiǎn)單的方式處理各種長(zhǎng)度的輸入。
該長(zhǎng)度固定的輸出向量會(huì)傳入一個(gè)全連接 (Dense) 層(包含 16 個(gè)隱藏單元)。
最后一層與單個(gè)輸出節(jié)點(diǎn)密集連接。應(yīng)用 sigmoid 激活函數(shù)后,結(jié)果是介于 0 到 1 之間的浮點(diǎn)值,表示概率或置信水平。
隱藏單元
上述模型在輸入和輸出之間有兩個(gè)中間層(也稱為“隱藏”層)。輸出(單元、節(jié)點(diǎn)或神經(jīng)元)的數(shù)量是相應(yīng)層的表示法空間的維度。換句話說(shuō),該數(shù)值表示學(xué)習(xí)內(nèi)部表示法時(shí)網(wǎng)絡(luò)所允許的自由度。
如果模型具有更多隱藏單元(更高維度的表示空間)和/或更多層,則說(shuō)明網(wǎng)絡(luò)可以學(xué)習(xí)更復(fù)雜的表示法。不過(guò),這會(huì)使網(wǎng)絡(luò)耗費(fèi)更多計(jì)算資源,并且可能導(dǎo)致學(xué)習(xí)不必要的模式(可以優(yōu)化在訓(xùn)練數(shù)據(jù)上的表現(xiàn),但不會(huì)優(yōu)化在測(cè)試數(shù)據(jù)上的表現(xiàn))。這稱為過(guò)擬合,稍后會(huì)加以探討。
損失函數(shù)和優(yōu)化器
模型在訓(xùn)練時(shí)需要一個(gè)損失函數(shù)和一個(gè)優(yōu)化器。由于這是一個(gè)二元分類問(wèn)題且模型會(huì)輸出一個(gè)概率(應(yīng)用 S 型激活函數(shù)的單個(gè)單元層),因此將使用 binary_crossentropy 損失函數(shù)。
該函數(shù)并不是唯一的損失函數(shù),例如,可以選擇 mean_squared_error。但一般來(lái)說(shuō),binary_crossentropy 更適合處理概率問(wèn)題,它可測(cè)量概率分布之間的“差距”,在本例中則為實(shí)際分布和預(yù)測(cè)之間的“差距”。
稍后,在探索回歸問(wèn)題(比如預(yù)測(cè)房?jī)r(jià))時(shí),將了解如何使用另一個(gè)稱為均方誤差的損失函數(shù)。
現(xiàn)在,配置模型以使用優(yōu)化器和損失函數(shù):
驗(yàn)證集
在訓(xùn)練時(shí)需要檢查模型處理從未見(jiàn)過(guò)的數(shù)據(jù)的準(zhǔn)確率。我們從原始訓(xùn)練數(shù)據(jù)中分離出 10000 個(gè)樣本,創(chuàng)建一個(gè)驗(yàn)證集。(為什么現(xiàn)在不使用測(cè)試集?我們的目標(biāo)是僅使用訓(xùn)練數(shù)據(jù)開(kāi)發(fā)和調(diào)整模型,然后僅使用一次測(cè)試數(shù)據(jù)評(píng)估準(zhǔn)確率)
訓(xùn)練模型
用有 512 個(gè)樣本的小批次訓(xùn)練模型 40 個(gè)周期。這將對(duì) x_train 和 y_train 張量中的所有樣本進(jìn)行 40 次迭代。在訓(xùn)練期間,監(jiān)控模型在驗(yàn)證集的 10000 個(gè)樣本上的損失和準(zhǔn)確率:
評(píng)估模型
看看模型的表現(xiàn)如何。模型會(huì)返回兩個(gè)值:損失(表示誤差的數(shù)字,越低越好)和準(zhǔn)確率
code
import tensorflow as tf
from tensorflow import keras
import numpy as np
print(tf.__version__)
#TensorFlow 中包含 IMDB 數(shù)據(jù)集。
#google已對(duì)該數(shù)據(jù)集進(jìn)行了預(yù)處理,將影評(píng)(字詞序列)轉(zhuǎn)換為整數(shù)序列,其中每個(gè)整數(shù)表示字典中的一個(gè)特定字詞。
#以下代碼會(huì)將 IMDB 數(shù)據(jù)集下載到你的計(jì)算機(jī)上(如果您已下載該數(shù)據(jù)集,則會(huì)使用緩存副本)
imdb = keras.datasets.imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
#了解下數(shù)據(jù)
print("Training entries: {}, labels: {}".format(len(train_data), len(train_labels)))
print(train_data[0])
len(train_data[0]), len(train_data[1])
#創(chuàng)建一個(gè)輔助函數(shù)來(lái)查詢包含整數(shù)到字符串映射的字典對(duì)象
word_index = imdb.get_word_index()
word_index = {k:(v+3) for k,v in word_index.items()}
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2 # unknown
word_index["<UNUSED>"] = 3
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
def decode_review(text):
return ' '.join([reverse_word_index.get(i, '?') for i in text])
#整數(shù)數(shù)組)必須轉(zhuǎn)換為張量,然后才能饋送到神經(jīng)網(wǎng)絡(luò)中
train_data = keras.preprocessing.sequence.pad_sequences(train_data,
value=word_index["<PAD>"],
padding='post',
maxlen=256)
test_data = keras.preprocessing.sequence.pad_sequences(test_data,
value=word_index["<PAD>"],
padding='post',
maxlen=256)
#樣本長(zhǎng)度
len(train_data[0]), len(train_data[1])
#打印第一條
print(train_data[0])
#構(gòu)建模型
vocab_size = 10000
model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))
model.summary()
#配置模型以使用優(yōu)化器和損失函數(shù)
model.compile(optimizer=tf.train.AdamOptimizer(),
loss='binary_crossentropy',
metrics=['accuracy'])
#從原始訓(xùn)練數(shù)據(jù)中分離出 10000 個(gè)樣本,創(chuàng)建一個(gè)驗(yàn)證集
x_val = train_data[:10000]
partial_x_train = train_data[10000:]
y_val = train_labels[:10000]
partial_y_train = train_labels[10000:]
#訓(xùn)練:用有 512 個(gè)樣本的小批次訓(xùn)練模型 40 個(gè)周期。這將對(duì) x_train 和 y_train 張量中的所有樣本進(jìn)行 40 次迭代。在訓(xùn)練期間,監(jiān)控模型在驗(yàn)證集的 10000 個(gè)樣本上的損失和準(zhǔn)確率
history = model.fit(partial_x_train,
partial_y_train,
epochs=40,
batch_size=512,
validation_data=(x_val, y_val),
verbose=1)
#評(píng)估:模型會(huì)返回兩個(gè)值:損失(表示誤差的數(shù)字,越低越好)和準(zhǔn)確率
results = model.evaluate(test_data, test_labels)
print(results)
#創(chuàng)建準(zhǔn)確率和損失隨時(shí)間變化的圖
#model.fit() 返回一個(gè) History 對(duì)象,該對(duì)象包含一個(gè)字典,其中包括訓(xùn)練期間發(fā)生的所有情況:
history_dict = history.history
history_dict.keys()
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
#
plt.clf() # clear figure
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('Accuracy')
plt.legend()
plt.show()
訓(xùn)練結(jié)果
- 訓(xùn)練損失隨著周期數(shù)的增加而降低,訓(xùn)練準(zhǔn)確率隨著周期數(shù)的增加而提高
- 大約 20 個(gè)周期后準(zhǔn)確率達(dá)到峰值
這是一種過(guò)擬合現(xiàn)象,過(guò)擬合說(shuō)白了就是:只認(rèn)識(shí)訓(xùn)練過(guò)的,沒(méi)訓(xùn)練過(guò)的不認(rèn)識(shí)
為了放置過(guò)擬合,一般準(zhǔn)確率達(dá)到峰值后應(yīng)該停止訓(xùn)練