與之前一樣,此示例中的代碼將使用tf.keras API川无,您可以在TensorFlow Keras指南中了解更多信息呛占。
在前面的兩個(gè)例子中 - 分類電影評(píng)論和預(yù)測(cè)住房?jī)r(jià)格 - 我們看到我們的模型對(duì)驗(yàn)證數(shù)據(jù)的準(zhǔn)確性在經(jīng)過(guò)多個(gè)時(shí)期的訓(xùn)練后會(huì)達(dá)到峰值,然后開始下降懦趋。
換句話說(shuō)晾虑,我們的模型會(huì)過(guò)度擬合訓(xùn)練數(shù)據(jù)。 學(xué)習(xí)如何處理過(guò)度擬合很重要。 盡管通持钠可以在訓(xùn)練集上實(shí)現(xiàn)高精度糙捺,但我們真正想要的是開發(fā)能夠很好地泛化測(cè)試數(shù)據(jù)(或之前未見(jiàn)過(guò)的數(shù)據(jù))的模型。
過(guò)度擬合的反面是欠擬合笙隙。 當(dāng)測(cè)試數(shù)據(jù)仍有改進(jìn)空間時(shí)洪灯,會(huì)發(fā)生欠擬合。 出現(xiàn)這種情況的原因有很多:如果模型不夠強(qiáng)大竟痰,過(guò)度正則化签钩,或者根本沒(méi)有經(jīng)過(guò)足夠長(zhǎng)時(shí)間的訓(xùn)練。 這意味著網(wǎng)絡(luò)尚未學(xué)習(xí)訓(xùn)練數(shù)據(jù)中的相關(guān)模式坏快。
如果訓(xùn)練時(shí)間過(guò)長(zhǎng),模型將開始過(guò)度擬合并從訓(xùn)練數(shù)據(jù)中學(xué)習(xí)模式莽鸿,而這些模式不會(huì)泛化到測(cè)試數(shù)據(jù)昧旨。 我們需要取得平衡。 如下所述祥得,了解如何訓(xùn)練適當(dāng)數(shù)量的時(shí)期是一項(xiàng)有用的技能兔沃。
為了防止過(guò)度擬合,最好的解決方案是使用更多的訓(xùn)練數(shù)據(jù)级及。 受過(guò)更多數(shù)據(jù)訓(xùn)練的模型自然會(huì)更好地泛化粘拾。 當(dāng)不再可能時(shí),下一個(gè)最佳解決方案是使用正規(guī)化等技術(shù)创千。 這些限制了模型可以存儲(chǔ)的信息的數(shù)量和類型缰雇。 如果一個(gè)網(wǎng)絡(luò)只能記住少量的模式,那么優(yōu)化過(guò)程將迫使它專注于最突出的模式追驴,這些模式有更好的泛化性械哟。
在本文中,我們將探索兩種常見(jiàn)的正則化技術(shù) - 權(quán)重正則化和丟失 - 并使用它們來(lái)改進(jìn)我們的IMDB電影評(píng)論分類殿雪。
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
print(tf.__version__)
1.11.0
下載IMDB數(shù)據(jù)集
我們不會(huì)像以前的本章一樣使用嵌入暇咆,而是對(duì)句子進(jìn)行多熱編碼。 該模型將很快適應(yīng)訓(xùn)練集丙曙。 它將用于證明何時(shí)發(fā)生過(guò)度擬合爸业,以及如何對(duì)抗它。
對(duì)我們的列表進(jìn)行多熱編碼意味著將它們轉(zhuǎn)換為0和1的向量亏镰。 具體地說(shuō)扯旷,這將意味著例如將序列[3,5]轉(zhuǎn)換為10,000維向量,除了索引3和5之外索抓,它將是全零钧忽,其將是1毯炮。
NUM_WORDS = 10000
(train_data, train_labels), (test_data, test_labels) = keras.datasets.imdb.load_data(num_words=NUM_WORDS)
def multi_hot_sequences(sequences, dimension):
# Create an all-zero matrix of shape (len(sequences), dimension)
results = np.zeros((len(sequences), dimension))
for i, word_indices in enumerate(sequences):
results[i, word_indices] = 1.0 # set specific indices of results[i] to 1s
return results
train_data = multi_hot_sequences(train_data, dimension=NUM_WORDS)
test_data = multi_hot_sequences(test_data, dimension=NUM_WORDS)
讓我們看一下生成的多熱矢量。 單詞索引按頻率排序耸黑,因此預(yù)計(jì)索引零附近有更多的1值桃煎,我們可以在此圖中看到:
plt.plot(train_data[0])
過(guò)擬合
防止過(guò)度擬合的最簡(jiǎn)單方法是減小模型的大小,即模型中可學(xué)習(xí)參數(shù)的數(shù)量(由層數(shù)和每層單元數(shù)決定)大刊。 在深度學(xué)習(xí)中为迈,模型中可學(xué)習(xí)參數(shù)的數(shù)量通常被稱為模型的“容量”。 直觀地缺菌,具有更多參數(shù)的模型將具有更多“記憶能力”葫辐,因此將能夠容易地學(xué)習(xí)訓(xùn)練樣本與其目標(biāo)之間的完美的字典式映射,沒(méi)有任何泛化能力的映射男翰,但是在做出預(yù)測(cè)時(shí)(新出現(xiàn)的數(shù)據(jù))這將是無(wú)用的另患。
始終牢記這一點(diǎn):深度學(xué)習(xí)模型往往善于擬合訓(xùn)練數(shù)據(jù)纽乱,但真正的挑戰(zhàn)是泛化蛾绎,而不是擬合。
另一方面鸦列,如果網(wǎng)絡(luò)具有有限的記憶資源租冠,則將不能容易地學(xué)習(xí)映射。 為了最大限度地減少損失薯嗤,它必須學(xué)習(xí)具有更強(qiáng)預(yù)測(cè)能力的壓縮表示顽爹。 同時(shí),如果您使模型太小骆姐,則難以擬合訓(xùn)練數(shù)據(jù)镜粤。 “太多容量”和“容量不足”之間存在平衡。
不幸的是玻褪,沒(méi)有神奇的公式來(lái)確定模型的正確尺寸或架構(gòu)(就層數(shù)而言肉渴,或每層的正確尺寸)。 您將不得不嘗試使用一系列不同的架構(gòu)带射。
要找到合適的模型大小同规,最好從相對(duì)較少的圖層和參數(shù)開始,然后開始增加圖層的大小或添加新圖層窟社,直到您看到驗(yàn)證損失的收益遞減為止券勺。 讓我們?cè)谖覀兊碾娪霸u(píng)論分類網(wǎng)絡(luò)上試試這個(gè)。
我們將僅使用Dense圖層作為基線創(chuàng)建一個(gè)簡(jiǎn)單模型灿里,然后創(chuàng)建更小和更大的版本关炼,并進(jìn)行比較。
創(chuàng)建基線模型
baseline_model = keras.Sequential([
# `input_shape` is only required here so that `.summary` works.
keras.layers.Dense(16, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
keras.layers.Dense(16, activation=tf.nn.relu),
keras.layers.Dense(1, activation=tf.nn.sigmoid)
])
baseline_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'binary_crossentropy'])
baseline_model.summary()
baseline_history = baseline_model.fit(train_data,
train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
創(chuàng)建較小的模型
讓我們創(chuàng)建一個(gè)隱藏單元較少的模型匣吊,與我們剛剛創(chuàng)建的基線模型進(jìn)行比較:
smaller_model = keras.Sequential([
keras.layers.Dense(4, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
keras.layers.Dense(4, activation=tf.nn.relu),
keras.layers.Dense(1, activation=tf.nn.sigmoid)
])
smaller_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'binary_crossentropy'])
smaller_model.summary()
并用相同的數(shù)據(jù)訓(xùn)練此模型:
smaller_history = smaller_model.fit(train_data,
train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
創(chuàng)建較大的模型
作為練習(xí)盗扒,您可以創(chuàng)建一個(gè)更大的模型跪楞,并查看它開始過(guò)度擬合的速度。 接下來(lái)侣灶,讓我們?cè)谶@個(gè)基準(zhǔn)測(cè)試中添加一個(gè)容量大得多的網(wǎng)絡(luò)甸祭,遠(yuǎn)遠(yuǎn)超出問(wèn)題的范圍:
bigger_model = keras.models.Sequential([
keras.layers.Dense(512, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
keras.layers.Dense(512, activation=tf.nn.relu),
keras.layers.Dense(1, activation=tf.nn.sigmoid)
])
bigger_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy'])
bigger_model.summary()
同樣,再次用相同的數(shù)據(jù)訓(xùn)練模型:
bigger_history = bigger_model.fit(train_data,train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
繪制訓(xùn)練和驗(yàn)證損失
實(shí)線表示訓(xùn)練損失褥影,虛線表示驗(yàn)證損失(記壮鼗А:較低的驗(yàn)證損失表示更好的模型)。 在這里凡怎,較小的網(wǎng)絡(luò)開始過(guò)度擬合晚于基線模型(在6個(gè)時(shí)期之后而不是4個(gè)時(shí)期)校焦,并且一旦開始過(guò)度擬合,其性能下降得慢得多统倒。
def plot_history(histories, key='binary_crossentropy'):
plt.figure(figsize=(16,10))
for name, history in histories:
val = plt.plot(history.epoch, history.history['val_'+key],'--', label=name.title()+' Val')
plt.plot(history.epoch, history.history[key], color=val[0].get_color(),label=name.title()+' Train')
plt.xlabel('Epochs')
plt.ylabel(key.replace('_',' ').title())
plt.legend()
plt.xlim([0,max(history.epoch)])
plot_history([('baseline', baseline_history),('smaller', smaller_history),('bigger', bigger_history)])
請(qǐng)注意寨典,較大的網(wǎng)絡(luò)在僅僅一個(gè)時(shí)期之后幾乎立即開始過(guò)度擬合,并且更加嚴(yán)重地過(guò)度配置房匆。 網(wǎng)絡(luò)容量越大耸成,能夠越快地對(duì)訓(xùn)練數(shù)據(jù)進(jìn)行建模(導(dǎo)致訓(xùn)練損失低),但過(guò)度擬合的可能性越大(導(dǎo)致訓(xùn)練和驗(yàn)證損失之間的差異很大)浴鸿。
策略
權(quán)重正則化
你可能熟悉奧卡姆的剃刀原則:給出兩個(gè)解釋的東西井氢,最可能是正確的解釋是“最簡(jiǎn)單”的解釋,即做出最少量假設(shè)的解釋岳链。 這也適用于神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)的模型:給定一些訓(xùn)練數(shù)據(jù)和網(wǎng)絡(luò)架構(gòu)花竞,有多組權(quán)重值(多個(gè)模型)可以解釋數(shù)據(jù),而簡(jiǎn)單模型比復(fù)雜模型更不容易過(guò)度擬合掸哑。
在這種情況下约急,“簡(jiǎn)單模型”是一個(gè)模型,其中參數(shù)值的分布具有較少的熵(或者具有較少參數(shù)的模型苗分,如我們?cè)谏厦娴牟糠种兴?jiàn))厌蔽。因此,減輕過(guò)度擬合的常見(jiàn)方法是通過(guò)強(qiáng)制其權(quán)重僅采用較小的值來(lái)對(duì)網(wǎng)絡(luò)的復(fù)雜性施加約束俭嘁,這使得權(quán)重值的分布更“規(guī)則”躺枕。這被稱為“權(quán)重正則化”,并且通過(guò)向網(wǎng)絡(luò)的損失函數(shù)添加與具有大權(quán)重相關(guān)聯(lián)的成本來(lái)完成供填。這個(gè)成本有兩種:
L1正則化拐云,其中所添加的成本與權(quán)重系數(shù)的絕對(duì)值成比例(即,稱為權(quán)重的“L1范數(shù)”)近她。
L2正則化叉瘩,其中所添加的成本與權(quán)重系數(shù)的值的平方成比例(即,與權(quán)重的所謂“L2范數(shù)”)成比例粘捎。 L2正則化在神經(jīng)網(wǎng)絡(luò)的背景下也稱為權(quán)重衰減薇缅。不要讓不同的名字讓你感到困惑:權(quán)重衰減在數(shù)學(xué)上與L2正則化完全相同危彩。
在tf.keras中,通過(guò)將權(quán)重正則化實(shí)例作為關(guān)鍵字參數(shù)傳遞給層來(lái)添加權(quán)重正則化泳桦。 現(xiàn)在讓我們添加L2權(quán)重正則化汤徽。
l2_model = keras.models.Sequential([
keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001),
activation=tf.nn.relu),
keras.layers.Dense(1, activation=tf.nn.sigmoid)
])
l2_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', 'binary_crossentropy'])
l2_model_history = l2_model.fit(train_data, train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
l2(0.001)表示該層的權(quán)重矩陣中的每個(gè)系數(shù)將為網(wǎng)絡(luò)的總損失增加0.001 * weight_coefficient_value ** 2。 請(qǐng)注意灸撰,由于此懲罰僅在訓(xùn)練時(shí)添加谒府,因此在訓(xùn)練時(shí)此網(wǎng)絡(luò)的損失將遠(yuǎn)高于在測(cè)試時(shí)間。
這是我們的L2正規(guī)化懲罰的影響:
plot_history([('baseline', baseline_history),
('l2', l2_model_history)])
正如您所看到的浮毯,L2正則化模型比基線模型更能抵抗過(guò)度擬合完疫,即使兩個(gè)模型具有相同數(shù)量的參數(shù)。
Add Dropout
Dropout是由Hinton和他在多倫多大學(xué)的學(xué)生開發(fā)的最有效和最常用的神經(jīng)網(wǎng)絡(luò)正則化技術(shù)之一债蓝。應(yīng)用于層的Dropout包括在訓(xùn)練期間隨機(jī)“丟棄”(即設(shè)置為零)該層的多個(gè)輸出特征壳鹤。假設(shè)一個(gè)給定的層通常會(huì)在訓(xùn)練期間為給定的輸入樣本返回一個(gè)向量[0.2,0.5,1.3,0.8,1.1];在應(yīng)用了丟失之后,該向量將具有隨機(jī)分布的幾個(gè)零條目饰迹,例如芳誓, [0,0.5,1.3,0,1.1]。 “dropout rate”是被淘汰的特征的一部分;它通常設(shè)置在0.2和0.5之間蹦锋。在測(cè)試時(shí)兆沙,沒(méi)有單位被剔除欧芽,而是將圖層的輸出值按比例縮小等于輟學(xué)率的因子莉掂,以便平衡更多單位活躍的事實(shí)而不是訓(xùn)練時(shí)間。
在tf.keras中千扔,您可以通過(guò)Dropout圖層在網(wǎng)絡(luò)中引入dropout憎妙,該圖層將在之前應(yīng)用于圖層的輸出。
讓我們?cè)贗MDB網(wǎng)絡(luò)中添加兩個(gè)Dropout圖層曲楚,看看它們?cè)跍p少過(guò)度擬合方面做得如何:
dpt_model = keras.models.Sequential([
keras.layers.Dense(16, activation=tf.nn.relu, input_shape=(NUM_WORDS,)),
keras.layers.Dropout(0.5),
keras.layers.Dense(16, activation=tf.nn.relu),
keras.layers.Dropout(0.5),
keras.layers.Dense(1, activation=tf.nn.sigmoid)
])
dpt_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy'])
dpt_model_history = dpt_model.fit(train_data, train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data, test_labels),
verbose=2)
plot_history([('baseline', baseline_history),
('dropout', dpt_model_history)])
添加dropout是對(duì)基線模型的明顯改進(jìn)厘唾。
回顧一下:這里是防止神經(jīng)網(wǎng)絡(luò)中過(guò)度擬合的最常見(jiàn)方法:
- 獲取更多培訓(xùn)數(shù)據(jù)。
- 減少網(wǎng)絡(luò)容量龙誊。
- 添加重量正規(guī)化抚垃。
- 添加dropout。
本文未涉及的兩個(gè)重要方法是數(shù)據(jù)增強(qiáng)和批量標(biāo)準(zhǔn)化趟大。
完整代碼:
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
print(tf.__version__)
# Download the IMDB dataset
NUM_WORDS = 10000
(train_data,train_labels),(test_data,test_labels) = keras.datasets.imdb.load_data(num_words = NUM_WORDS)
def multi_hot_sequences(sequences,dimension):
# Create an all-zero matrix of shape (len(sequences),dimension)
results = np.zeros((len(sequences),dimension))
for i,word_indices in enumerate(sequences):
results[i,word_indices] = 1.0 # set specific indices of results[i] to 1s
return results
train_data = multi_hot_sequences(train_data,dimension=NUM_WORDS)
test_data = multi_hot_sequences(test_data,dimension=NUM_WORDS)
plt.plot(train_data[0])
plt.show()
# Create a baseline model
baseline_model = keras.Sequential([
# 'input_shpae' is only required here so that '.summary' works.
keras.layers.Dense(16,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
keras.layers.Dense(16,activation=tf.nn.relu),
keras.layers.Dense(1,activation=tf.nn.sigmoid)
])
baseline_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy'])
baseline_model.summary()
baseline_history = baseline_model.fit(train_data,
train_labels,
epochs=20,
batch_size=512,
validation_data =(test_data,test_labels),
verbose=2)
# Create a smaller model
smaller_model = keras.Sequential([
keras.layers.Dense(4,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
keras.layers.Dense(4,activation=tf.nn.relu),
keras.layers.Dense(1,activation=tf.nn.sigmoid)
])
smaller_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy'])
smaller_model.summary()
smaller_history = smaller_model.fit(train_data,
train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data,test_labels),
verbose=2)
# Create a bigger model
bigger_model = keras.models.Sequential([
keras.layers.Dense(512,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
keras.layers.Dense(512,activation=tf.nn.relu),
keras.layers.Dense(1,activation=tf.nn.sigmoid)
])
bigger_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy'])
bigger_model.summary()
bigger_history = bigger_model.fit(train_data,
train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data,test_labels),
verbose=2)
# Plot the training and validation loss
def plot_history(histories,key='binary_crossentropy'):
plt.figure(figsize=(16,10))
for name,history in histories:
val = plt.plot(history.epoch,history.history['val_' + key],
'--',label = name.title() + ' Val')
plt.plot(history.epoch,history.history[key],color=val[0].get_color(),
label=name.title()+' Train')
plt.xlabel('Epochs')
plt.ylabel(key.replace('-',' ').title())
plt.legend()
plt.xlim([0,max(history.epoch)])
plt.show()
plot_history([('baseline',baseline_history),
('smaller',smaller_history),
('bigger',bigger_history)])
# Add weight regulatization
l2_model = keras.models.Sequential([
keras.layers.Dense(16,kernel_regularizer=keras.regularizers.l2(0.001),
activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
keras.layers.Dense(16,kernel_regularizer=keras.regularizers.l2(0.001),
activation=tf.nn.relu),
keras.layers.Dense(1,activation=tf.nn.sigmoid)])
l2_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy'])
l2_model_history = l2_model.fit(train_data,train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data,test_labels),
verbose=2)
plot_history([('baseline',baseline_history),
('l2',l2_model_history)])
# add dropout
dpt_model = keras.models.Sequential([
keras.layers.Dense(16,activation=tf.nn.relu,input_shape=(NUM_WORDS,)),
keras.layers.Dropout(0.5),
keras.layers.Dense(16,activation=tf.nn.relu),
keras.layers.Dropout(0.5),
keras.layers.Dense(1,activation=tf.nn.sigmoid)
])
dpt_model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy','binary_crossentropy'])
dpt_model_history = dpt_model.fit(train_data,train_labels,
epochs=20,
batch_size=512,
validation_data=(test_data,test_labels),
verbose=2)
plot_history([('baseline',baseline_history),
('dropout',dpt_model_history)])