《從鍋爐工到AI專家(6)》一文中,咱們把神經網絡模型降維,簡單的在二維空間中介紹了過擬合和欠擬合的現象和解決方法。可是由於條件所限,在該文中咱們只介紹了理論,並無實際觀察現象和應對。
如今有了TensorFLow 2.0 / Keras的支持,能夠很是容易的構建模型。咱們能夠方便的人工模擬過擬合的情形,實際來操做監控、調整模型,從而顯著改善模型指標。python
先借用上一篇的兩組圖:
先看上邊的一組圖,隨着訓練迭代次數的增長,預測的錯誤率迅速降低。
咱們上一篇中講,達到必定迭代次數以後,驗證的錯誤率就穩定不變了。實際上你仔細觀察,訓練集的錯誤率在穩定降低,但驗證集的錯誤率還會略有上升。二者之間的差別愈來愈大,圖中的兩條曲線,顯著分離了,而且分離的趨勢還在增長。這就是過擬合的典型特徵。
這表示,模型過度適應了當前的訓練集數據,對於訓練集數據有了較好表現。對於以外的數據,反而不適應,從而效果不好。
這一般都是因爲較小的數據樣本形成的。若是數據集足夠大,較多的訓練一般都能讓模型表現的更好。過擬合對於生產環境傷害是比較大的,由於生產中大多接收到的都是新數據,而過擬合沒法對這些新數據達成較好表現。
因此若是數據集不夠的狀況下,採用適當的迭代次數多是更好的選擇。這也是上一節咱們採用EarlyStopping機制的緣由之一。最終的表現是上邊下面一組圖的樣子。
欠擬合與此相反,表示模型還有較大改善空間。上面兩組圖中,左側降低沿的曲線均可以認爲是欠擬合。表現特徵是不管測試集仍是驗證集,都沒有足夠的正確率。固然也所以,測試集和驗證集表現相似,擬合很是緊密。
欠擬合的狀況,除了訓練不足以外,模型不夠強大或者或者模型不適合業務狀況都是可能的緣由。網絡
咱們使用IMDB影評樣本庫來作這個實驗。實驗程序主要部分來自於本系列第五篇中第二個例子,固然有較大的修改。
程序主要分爲幾個部分:函數
程序中,文本的編碼方式、模型都並非很合理,由於咱們不是想獲得一個最優的模型,而是想演示過擬合的場景。學習
#!/usr/bin/env python3 from __future__ import absolute_import, division, print_function, unicode_literals import tensorflow as tf from tensorflow import keras import numpy as np import matplotlib.pyplot as plt NUM_WORDS = 10000 # 載入IMDB樣本數據 (train_data, train_labels), (test_data, test_labels) = keras.datasets.imdb.load_data(num_words=NUM_WORDS) # 將單詞數字化,轉化爲multi-hot序列編碼方式 def multi_hot_sequences(sequences, dimension): # 創建一個空矩陣保存結果 results = np.zeros((len(sequences), dimension)) for i, word_indices in enumerate(sequences): results[i, word_indices] = 1.0 # 出現過的詞設置爲1.0 return results train_data = multi_hot_sequences(train_data, dimension=NUM_WORDS) test_data = multi_hot_sequences(test_data, dimension=NUM_WORDS) # 創建baseline模型,並編譯訓練 baseline_model = keras.Sequential([ # 指定`input_shape`以保證下面的.summary()能夠執行, # 不然在模型結構沒法肯定 keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(16, activation='relu'), keras.layers.Dense(1, activation='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) # 小模型定義、編譯、訓練 smaller_model = keras.Sequential([ keras.layers.Dense(4, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(4, activation='relu'), keras.layers.Dense(1, activation='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) # 大模型定義、編譯、訓練 bigger_model = keras.models.Sequential([ keras.layers.Dense(512, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(512, activation='relu'), keras.layers.Dense(1, activation='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) # 繪圖函數 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)])
程序在命令行的輸出就不貼出來了,除了輸出的訓練迭代過程,在以前還輸出了每一個模型的summary()。這裏主要看最後的binary_crossentropy曲線圖。
圖中的虛線都是驗證集數據的表現,實線是訓練集數據的表現。三個模型的訓練數據和測試數據交叉熵曲線都出現了較大的分離,表明出現了過擬合。尤爲是bigger模型的兩條綠線,幾乎是一開始就出現了較大的背離。測試
優化過擬合首先要知道過擬合產生的緣由,咱們借用一張前一系列講解過擬合時候用過的圖,是吳恩達老師課程的筆記:
若是一個模型產生過擬合,那這個模型的整體效果就多是一個很是複雜的非線性方程。方程很是努力的學習全部「可見」數據,致使了複雜的權重值,使得曲線彎來彎去,變得極爲複雜。多層網絡更加重了這種複雜度,最終的複雜曲線繞開了可行的區域,只對局部的可見數據有效,對於實際數據命中率低。因此從咱們程序跑的結果圖來看,也是越複雜的網絡模型,過擬合現象反而越嚴重。
這麼說簡單的模型就好嘍?並不是如此,太簡單的模型每每沒法表達複雜的邏輯,從而產生欠擬合。其實看當作熟的那些模型好比ResNet50,都是很是複雜的結構。
過擬合既然產生的主要緣由是在權重值上,咱們在這方面作工做便可。優化
一般有兩種方法,稱爲L1規範化和L2規範化。前者爲代價值增長必定比例的權重值的絕對值。後者增長必定比例權重值的平方值。具體的實現來源於公式,有興趣的能夠參考一下這篇文章《L1 and L2 Regularization》。
咱們刪除掉上面源碼中的bigger模型和small模型的部分,包括模型的構建、編譯和訓練,添加下面的代碼:編碼
# 構建一個L2規範化的模型 l2_model = keras.models.Sequential([ keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dense(16, kernel_regularizer=keras.regularizers.l2(0.001), activation='relu'), keras.layers.Dense(1, activation='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)
這個模型的邏輯結構同baseline的模型徹底一致,只是在前兩層中增長了L2規範化的設置參數。
先不着急運行,咱們繼續另一種方法。命令行
DropOut是咱們在上個系列中已經講過的方法,應用的很普遍也很是有效。
其機理很是簡單,就是在一層網絡中,「丟棄」必定比例的輸出(設置爲數值0)給下一層。丟棄的比例一般設置爲0.2至0.5。這個過程只在訓練過程當中有效,通常會在預測過程當中關閉這個機制。
咱們繼續在上面代碼中,添加一組採用DropOut機制的模型,模型的基本結構依然同baseline相同:code
dpt_model = keras.models.Sequential([ keras.layers.Dense(16, activation='relu', input_shape=(NUM_WORDS,)), keras.layers.Dropout(0.5), keras.layers.Dense(16, activation='relu'), keras.layers.Dropout(0.5), keras.layers.Dense(1, activation='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), ('l2', l2_model_history), ('dropout', dpt_model_history)])
如今能夠執行程序了。
程序得到的曲線圖以下,圖中可見,咱們在不下降模型的複雜度的狀況下,L2規範化(黃色曲線)和DropOut(綠色曲線)都有效的改善了模型的過擬合問題。
blog
(待續...)