在日常工作、學習中,數(shù)據(jù)科學家最常遇到的問題之一就是過擬合。你是否曾有過這樣一個模型,它在訓練集上表現(xiàn)優(yōu)秀,在測試集上卻一塌糊涂。你是否也曾有過這樣的經(jīng)歷,當你參加建模競賽時,從跑分上看你的模型明明應(yīng)該高居榜首,但在賽方公布的成績榜上,它卻名落孫山,遠在幾百名之后。如果你有過類似經(jīng)歷,那本文就是專為寫的——它會告訴你如何避免過擬合來提高模型性能。
在這篇文章中,我們將詳細講述過擬合的概念和用幾種用于解決過擬合問題的正則化方法,并輔以Python案例講解,以進一步鞏固這些知識。注意,本文假設(shè)讀者具備一定的神經(jīng)網(wǎng)絡(luò)、Keras實現(xiàn)的經(jīng)驗。
什么是正則化
在深入探討這個話題之前,請看一下這張圖片:
每次談及過擬合,這張圖片就會時不時地被拉出來“鞭尸”。如上圖所示,剛開始的時候,模型還不能很好地擬合所有數(shù)據(jù)點,即無法反映數(shù)據(jù)分布,這時它是欠擬合的。而隨著訓練次數(shù)增多,它慢慢找出了數(shù)據(jù)的模式,能在盡可能多地擬合數(shù)據(jù)點的同時反映數(shù)據(jù)趨勢,這時它是一個性能較好的模型。在這基礎(chǔ)上,如果我們繼續(xù)訓練,那模型就會進一步挖掘訓練數(shù)據(jù)中的細節(jié)和噪聲,為了擬合所有數(shù)據(jù)點“不擇手段”,這時它就過擬合了。
換句話說,從左往右看,模型的復雜度逐漸提高,在訓練集上的預測錯誤逐漸減少,但它在測試集上的錯誤率卻呈現(xiàn)一條下凸曲線。
如果你之前構(gòu)建過神經(jīng)網(wǎng)絡(luò),想必你已經(jīng)得到了這個教訓:網(wǎng)絡(luò)有多復雜,過擬合就有多容易。為了使模型在擬合數(shù)據(jù)的同時更具推廣性,我們可以用正則化對學習算法做一些細微修改,從而提高模型的整體性能。
正則化和過擬合
過擬合和神經(jīng)網(wǎng)絡(luò)的設(shè)計密切相關(guān),因此我們先來看一個過擬合的神經(jīng)網(wǎng)絡(luò):
如果你之前閱讀過我們的從零學習:從Python和R理解和編碼神經(jīng)網(wǎng)絡(luò)(完整版),或?qū)ι窠?jīng)網(wǎng)絡(luò)正則化概念有初步了解,你應(yīng)該知道上圖中帶箭頭的線實際上都帶有權(quán)重,而神經(jīng)元是儲存輸入輸出的地方。為了公平起見,也就是為了防止網(wǎng)絡(luò)在優(yōu)化方向上過于放飛自我,這里我們還需要加入一個先驗——正則化懲罰項,用來懲罰神經(jīng)元的加權(quán)矩陣。
如果我們設(shè)的正則化系數(shù)很大,導致一些加權(quán)矩陣的值幾乎為零——那最后我們得到的是一個更簡單的線性網(wǎng)絡(luò),它很可能是欠擬合的。
因此這個系數(shù)并不是越大越好。我們需要優(yōu)化這個正則化系數(shù)的值,以便獲得一個良好擬合的模型,如下圖所示。
深度學習中的正則化
L2和L1正則化
L1和L2是最常見的正則化方法,它們的做法是在代價函數(shù)后面再加上一個正則化項。
代價函數(shù) = 損失(如二元交叉熵) + 正則化項
由于添加了這個正則化項,各權(quán)值被減小了,換句話說,就是神經(jīng)網(wǎng)絡(luò)的復雜度降低了,結(jié)合“網(wǎng)絡(luò)有多復雜,過擬合就有多容易”的思想,從理論上來說,這樣做等于直接防止過擬合(奧卡姆剃刀法則)。
當然,這個正則化項在L1和L2里是不一樣的。
對于L2,它的代價函數(shù)可表示為:
這里λ就是正則化系數(shù),它是一個超參數(shù),可以被優(yōu)化以獲得更好的結(jié)果。對上式求導后,權(quán)重w前的系數(shù)為1?ηλ/m,因為η、λ、m都是正數(shù),1?ηλ/m小于1,w的趨勢是減小,所以L2正則化也被稱為權(quán)重衰減。
而對于L1,它的代價函數(shù)可表示為:
和L2不同,這里我們懲罰的是權(quán)重w的絕對值。對上式求導后,我們得到的等式里包含一項-sgn(w),這意味著當w是正數(shù)時,w減小趨向于0;當w是負數(shù)時,w增大趨向于0。所以L1的思路就是把權(quán)重往0靠,從而降低網(wǎng)絡(luò)復雜度。
因此當我們想要壓縮模型時,L1的效果會很好,但如果只是簡單防止過擬合,一般情況下還是會用L2。在Keras中,我們可以直接調(diào)用regularizers在任意層做正則化。
例:在全連接層使用L2正則化的代碼:
from keras import regularizers
model.add(Dense(64, input_dim=64,
kernel_regularizer=regularizers.l2(0.01)
注:這里的0.01是正則化系數(shù)λ的值,我們可以通過網(wǎng)格搜索對它做進一步優(yōu)化。
Dropout
Dropout稱得上是正則化方法中最有趣的一種,它的效果也很好,所以是深度學習領(lǐng)域常用的方法之一。為了更好地解釋它,我們先假設(shè)我們的神經(jīng)網(wǎng)絡(luò)長這樣:
那么Dropout到底drop了什么?我們來看下面這幅圖:在每次迭代中,它會隨機選擇一些神經(jīng)元,并把它們“滿門抄斬”——把神經(jīng)元連同相應(yīng)的輸入輸出一并“刪除”。
比起L1和L2對代價函數(shù)的修改,Dropout更像是訓練網(wǎng)絡(luò)的一種技巧。隨著訓練進行,神經(jīng)網(wǎng)絡(luò)在每一次迭代中都會忽視一些(超參數(shù),常規(guī)是一半)隱藏層/輸入層的神經(jīng)元,這就導致不同的輸出,其中有的是正確的,有的是錯誤的。
這個做法有點類似集成學習,它能更多地捕獲更多的隨機性。集成學習分類器通常比單一分類器效果更好,同樣的,因為網(wǎng)絡(luò)要擬合數(shù)據(jù)分布,所以Dropout后模型大部分的輸出肯定是正確的,而噪聲數(shù)據(jù)影響只占一小部分,不會對最終結(jié)果造成太大影響。
由于這些因素,當我們的神經(jīng)網(wǎng)絡(luò)較大且隨機性更多時,我們一般用Dropout。
在Keras中,我們可以使用keras core layer實現(xiàn)dropout。下面是它的Python代碼:
from keras.layers.core importDropout
model = Sequential([
Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
])
注:這里我們把0.25設(shè)為Dropout的超參數(shù)(每次“刪”1/4),我們可以通過網(wǎng)格搜索對它做進一步優(yōu)化。
數(shù)據(jù)增強
既然過擬合是模型對數(shù)據(jù)集中噪聲和細節(jié)的過度捕捉,那么防止過擬合最簡單的方法就是增加訓練數(shù)據(jù)量。但是在機器學習任務(wù)中,增加數(shù)據(jù)量并不是那么容易實現(xiàn)的,因為搜集、標記數(shù)據(jù)的成本太高了。
假設(shè)我們正在處理的一些手寫數(shù)字圖像,為了擴大訓練集,我們能采取的方法有——旋轉(zhuǎn)、翻轉(zhuǎn)、縮小/放大、位移、截取、添加隨機噪聲、添加畸變等。下面是一些處理過的圖:
這些方式就是數(shù)據(jù)增強。從某種意義上來說,機器學習模型的性能是靠數(shù)據(jù)量堆出來的,因此數(shù)據(jù)增強可以為模型預測的準確率提供巨大提升。有時為了改進模型,這也是一種必用的技巧。
在Keras中,我們可以使用ImageDataGenerator執(zhí)行所有這些轉(zhuǎn)換,它提供了一大堆可以用來預處理訓練數(shù)據(jù)的參數(shù)列表。以下是實現(xiàn)它的示例代碼:
from keras.preprocessing.image importImageDataGenerator
datagen = ImageDataGenerator(horizontal flip=True)
datagen.fit(train)
早停法
這是一種交叉驗證策略。訓練前,我們從訓練集中抽出一部分作為驗證集,隨著訓練的進行,當模型在驗證集上的性能越來越差時,我們立即手動停止訓練,這種提前停止的方法就是早停法。
在上圖中,我們應(yīng)該在虛線位置就停止訓練,因為在那之后,模型就開始過擬合了。
在Keras中,我們可以調(diào)用callbacks函數(shù)提前停止訓練,以下是它的示例代碼:
from keras.callbacks importEarlyStopping
EarlyStopping(monitor='val_err', patience=5)
在這里,monitor指的是需要監(jiān)控的epoch數(shù)量;val_err表示驗證錯誤(validation error)。
patience表示經(jīng)過5個連續(xù)epoch后模型預測結(jié)果沒有進一步改善。結(jié)合上圖進行理解,就是在虛線后,模型每訓練一個epoch就會有更高的驗證錯誤(更低的驗證準確率),因此連續(xù)訓練5個epoch后,它會提前停止訓練。
注:有一種情況是當模型訓練5個epoch后,它的驗證準確率可能會提高,因此選取超參數(shù)時我們要小心。
用keras實例研究MNIST數(shù)據(jù)
數(shù)據(jù)集:datahack.analyticsvidhya.com/contest/practice-problem-identify-the-digits/
學了這么多正則化方法,現(xiàn)在我們就要開始動手實踐了。在這個案例中,我們用的是Analytics Vidhya的數(shù)字識別數(shù)據(jù)集。
我們先導幾個基本庫:
%pylab inline
import numpy as np
import pandas as pd
from scipy.misc import imread
from sklearn.metrics import accuracy_score
from matplotlib import pyplot
import tensorflow as tf
import keras
# 阻止?jié)撛诘碾S機性
seed = 128
rng = np.random.RandomState(seed)
然后加載數(shù)據(jù)集:
root_dir = os.path.abspath('/Users/shubhamjain/Downloads/AV/identify the digits/')
data_dir = os.path.join(root_dir, 'data')
sub_dir = os.path.join(root_dir, 'sub')
## 只讀取訓練文件
train = pd.read_csv(os.path.join(data_dir, 'Train', 'train.csv'))
train.head()
檢查一下圖像:
img_name = rng.choice(train.filename)
filepath = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
img = imread(filepath, flatten=True)
pylab.imshow(img, cmap='gray')
pylab.axis('off')
pylab.show()
# 在numpy數(shù)組中存儲圖像
temp = []
for img_name in train.filename:
image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
img = imread(image_path, flatten=True)
img = img.astype('float32')
temp.append(img)
x_train = np.stack(temp)
x_train /= 255.0
x_train = x_train.reshape(-1, 784).astype('float32')
y_train = keras.utils.np_utils.to_categorical(train.label.values)
創(chuàng)建驗證數(shù)據(jù)集(7:3):
split_size = int(x_train.shape[0]*0.7)
x_train, x_test = x_train[:split_size], x_train[split_size:]
y_train, y_test = y_train[:split_size], y_train[split_size:]
構(gòu)建一個帶有5個隱藏層的簡單神經(jīng)網(wǎng)絡(luò),每層包含500個神經(jīng)元:
# 導入keras模塊
from keras.models importSequential
from keras.layers importDense
# define vars
input_num_units = 784
hidden1_num_units = 500
hidden2_num_units = 500
hidden3_num_units = 500
hidden4_num_units = 500
hidden5_num_units = 500
output_num_units = 10
epochs = 10
batch_size = 128
model = Sequential([
Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu'),
Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu'),
Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu'),
Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu'),
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
])
先跑10個epoch,快速檢查一下模型性能:
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))
L2正則化
from keras import regularizers
model = Sequential([
Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu',
kernel_regularizer=regularizers.l2(0.0001)),
Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu',
kernel_regularizer=regularizers.l2(0.0001)),
Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu',
kernel_regularizer=regularizers.l2(0.0001)),
Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu',
kernel_regularizer=regularizers.l2(0.0001)),
Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu',
kernel_regularizer=regularizers.l2(0.0001)),
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))
λ等于0.0001,模型預測準確率更高了!
L1正則化
## l1
model = Sequential([
Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu',
kernel_regularizer=regularizers.l1(0.0001)),
Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu',
kernel_regularizer=regularizers.l1(0.0001)),
Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu',
kernel_regularizer=regularizers.l1(0.0001)),
Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu',
kernel_regularizer=regularizers.l1(0.0001)),
Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu',
kernel_regularizer=regularizers.l1(0.0001)),
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))
模型準確率沒有任何提高,PASS!
Dropout
## dropout
from keras.layers.core importDropout
model = Sequential([
Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))
還可以,準確率比一開始提高了一些。
數(shù)據(jù)增強
from keras.preprocessing.image importImageDataGenerator
datagen = ImageDataGenerator(zca_whitening=True)
# 加載數(shù)據(jù)
train = pd.read_csv(os.path.join(data_dir, 'Train', 'train.csv'))
temp = []
for img_name in train.filename:
image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
img = imread(image_path, flatten=True)
img = img.astype('float32')
temp.append(img)
x_train = np.stack(temp)
X_train = x_train.reshape(x_train.shape[0], 1, 28, 28)
X_train = X_train.astype('float32')
# 從數(shù)據(jù)中擬合參數(shù)——增加訓練數(shù)據(jù)
datagen.fit(X_train)
在這里,我們用了zca_whitening,它突出了每個數(shù)字的輪廓,如下圖所示:
## splitting
y_train = keras.utils.np_utils.to_categorical(train.label.values)
split_size = int(x_train.shape[0]*0.7)
x_train, x_test = X_train[:split_size], X_train[split_size:]
y_train, y_test = y_train[:split_size], y_train[split_size:]
## reshaping
x_train=np.reshape(x_train,(x_train.shape[0],-1))/255
x_test=np.reshape(x_test,(x_test.shape[0],-1))/255
## structure using dropout
from keras.layers.core importDropout
model = Sequential([
Dense(output_dim=hidden1_num_units, input_dim=input_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden2_num_units, input_dim=hidden1_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden3_num_units, input_dim=hidden2_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden4_num_units, input_dim=hidden3_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=hidden5_num_units, input_dim=hidden4_num_units, activation='relu'),
Dropout(0.25),
Dense(output_dim=output_num_units, input_dim=hidden5_num_units, activation='softmax'),
])
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test))
提升非常明顯!
早停法
from keras.callbacks importEarlyStopping
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
trained_model_5d = model.fit(x_train, y_train, nb_epoch=epochs, batch_size=batch_size, validation_data=(x_test, y_test)
, callbacks = [EarlyStopping(monitor='val_acc', patience=2)])
和上面那些方法相比,早停法只跑了5個epoch就停止了,因為預測準確率沒有提高。但是如果我們增加迭代的次數(shù),它應(yīng)該能給出更好的結(jié)果。
-
神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
42文章
4781瀏覽量
101198 -
正則化
+關(guān)注
關(guān)注
0文章
17瀏覽量
8154 -
python
+關(guān)注
關(guān)注
56文章
4809瀏覽量
85053
原文標題:一文概述深度學習中的正則化(含Python代碼)
文章出處:【微信號:jqr_AI,微信公眾號:論智】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論