3.11 模型選擇、欠拟合和過拟合
在前幾節基于Fashion-MNIST資料集的實驗中,我們評價了機器學習模型在訓練資料集和測試資料集上的表現。如果你改變過實驗中的模型結構或者超參數,你也許發現了:當模型在訓練資料集上更準确時,它在測試資料集上卻不一定更準确。這是為什麼呢?
3.11.1 訓練誤差和泛化誤差
在解釋上述現象之前,我們需要區分訓練誤差(training error)和泛化誤差(generalization error)。通俗來講,前者指模型在訓練資料集上表現出的誤差,後者指模型在任意一個測試資料樣本上表現出的誤差的期望,并常常通過測試資料集上的誤差來近似。計算訓練誤差和泛化誤差可以使用之前介紹過的損失函數,例如線性回歸用到的平方損失函數和softmax回歸用到的交叉熵損失函數。
讓我們以聯考為例來直覺地解釋訓練誤差和泛化誤差這兩個概念。訓練誤差可以認為是做往年聯考試題(訓練題)時的錯誤率,泛化誤差則可以通過真正參加聯考(測試題)時的答題錯誤率來近似。假設訓練題和測試題都随機采樣于一個未知的依照相同考綱的巨大試題庫。如果讓一名未學習中學知識的國小生去答題,那麼測試題和訓練題的答題錯誤率可能很相近。但如果換成一名反複練習訓練題的高三備考生答題,即使在訓練題上做到了錯誤率為0,也不代表真實的聯考成績會如此。
在機器學習裡,我們通常假設訓練資料集(訓練題)和測試資料集(測試題)裡的每一個樣本都是從同一個機率分布中互相獨立地生成的。基于該獨立同分布假設,給定任意一個機器學習模型(含參數),它的訓練誤差的期望和泛化誤差都是一樣的。例如,如果我們将模型參數設成随機值(國小生),那麼訓練誤差和泛化誤差會非常相近。但我們從前面幾節中已經了解到,模型的參數是通過在訓練資料集上訓練模型而學習出的,參數的選擇依據了最小化訓練誤差(高三備考生)。是以,訓練誤差的期望小于或等于泛化誤差。也就是說,一般情況下,由訓練資料集學到的模型參數會使模型在訓練資料集上的表現優于或等于在測試資料集上的表現。由于無法從訓練誤差估計泛化誤差,一味地降低訓練誤差并不意味着泛化誤差一定會降低。
機器學習模型應關注降低泛化誤差。
3.11.2 模型選擇
在機器學習中,通常需要評估若幹候選模型的表現并從中選擇模型。這一過程稱為模型選擇(model selection)。可供選擇的候選模型可以是有着不同超參數的同類模型。以多層感覺機為例,我們可以選擇隐藏層的個數,以及每個隐藏層中隐藏單元個數和激活函數。為了得到有效的模型,我們通常要在模型選擇上下一番功夫。下面,我們來描述模型選擇中經常使用的驗證資料集(validation data set)。
3.11.2.1 驗證資料集
從嚴格意義上講,測試集隻能在所有超參數和模型參數標明後使用一次。不可以使用測試資料選擇模型,如調參。由于無法從訓練誤差估計泛化誤差,是以也不應隻依賴訓練資料選擇模型。鑒于此,我們可以預留一部分在訓練資料集和測試資料集以外的資料來進行模型選擇。這部分資料被稱為驗證資料集,簡稱驗證集(validation set)。例如,我們可以從給定的訓練集中随機選取一小部分作為驗證集,而将剩餘部分作為真正的訓練集。
然而在實際應用中,由于資料不容易擷取,測試資料極少隻使用一次就丢棄。是以,實踐中驗證資料集和測試資料集的界限可能比較模糊。從嚴格意義上講,除非明确說明,否則本書中實驗所使用的測試集應為驗證集,實驗報告的測試結果(如測試準确率)應為驗證結果(如驗證準确率)。
3.11.2.3 K K K折交叉驗證
由于驗證資料集不參與模型訓練,當訓練資料不夠用時,預留大量的驗證資料顯得太奢侈。一種改善的方法是 K K K折交叉驗證( K K K-fold cross-validation)。在 K K K折交叉驗證中,我們把原始訓練資料集分割成 K K K個不重合的子資料集,然後我們做 K K K次模型訓練和驗證。每一次,我們使用一個子資料集驗證模型,并使用其他 K − 1 K-1 K−1個子資料集來訓練模型。在這 K K K次訓練和驗證中,每次用來驗證模型的子資料集都不同。最後,我們對這 K K K次訓練誤差和驗證誤差分别求平均。
3.11.3 欠拟合和過拟合
接下來,我們将探究模型訓練中經常出現的兩類典型問題:一類是模型無法得到較低的訓練誤差,我們将這一現象稱作欠拟合(underfitting);另一類是模型的訓練誤差遠小于它在測試資料集上的誤差,我們稱該現象為過拟合(overfitting)。在實踐中,我們要盡可能同時應對欠拟合和過拟合。雖然有很多因素可能導緻這兩種拟合問題,在這裡我們重點讨論兩個因素:模型複雜度和訓練資料集大小。
關于模型複雜度和訓練集大小對學習的影響的詳細理論分析可參見我寫的這篇部落格。
3.11.3.1 模型複雜度
為了解釋模型複雜度,我們以多項式函數拟合為例。給定一個由标量資料特征 x x x和對應的标量标簽 y y y組成的訓練資料集,多項式函數拟合的目标是找一個 K K K階多項式函數
y ^ = b + ∑ k = 1 K x k w k \hat{y} = b + \sum_{k=1}^K x^k w_k y^=b+k=1∑Kxkwk
來近似 y y y。在上式中, w k w_k wk是模型的權重參數, b b b是偏差參數。與線性回歸相同,多項式函數拟合也使用平方損失函數。特别地,一階多項式函數拟合又叫線性函數拟合。
因為高階多項式函數模型參數更多,模型函數的選擇空間更大,是以高階多項式函數比低階多項式函數的複雜度更高。是以,高階多項式函數比低階多項式函數更容易在相同的訓練資料集上得到更低的訓練誤差。給定訓練資料集,模型複雜度和誤差之間的關系通常如圖3.4所示。給定訓練資料集,如果模型的複雜度過低,很容易出現欠拟合;如果模型複雜度過高,很容易出現過拟合。應對欠拟合和過拟合的一個辦法是針對資料集選擇合适複雜度的模型。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL2UWN3UWYwIzM4UWN3AjZhZTOiRDO0ATZlZDZmFjZzM2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
圖3.4 模型複雜度對欠拟合和過拟合的影響
3.11.3.2 訓練資料集大小
影響欠拟合和過拟合的另一個重要因素是訓練資料集的大小。一般來說,如果訓練資料集中樣本數過少,特别是比模型參數數量(按元素計)更少時,過拟合更容易發生。此外,泛化誤差不會随訓練資料集裡樣本數量增加而增大。是以,在計算資源允許的範圍之内,我們通常希望訓練資料集大一些,特别是在模型複雜度較高時,例如層數較多的深度學習模型。
3.11.4 多項式函數拟合實驗
為了了解模型複雜度和訓練資料集大小對欠拟合和過拟合的影響,下面我們以多項式函數拟合為例來實驗。首先導入實驗需要的包或子產品。
%matplotlib inline
import torch
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
3.11.4.1 生成資料集
我們将生成一個人工資料集。在訓練資料集和測試資料集中,給定樣本特征 x x x,我們使用如下的三階多項式函數來生成該樣本的标簽:
y = 1.2 x − 3.4 x 2 + 5.6 x 3 + 5 + ϵ , y = 1.2x - 3.4x^2 + 5.6x^3 + 5 + \epsilon, y=1.2x−3.4x2+5.6x3+5+ϵ,
其中噪聲項 ϵ \epsilon ϵ服從均值為0、标準差為0.01的正态分布。訓練資料集和測試資料集的樣本數都設為100。
n_train, n_test, true_w, true_b = 100, 100, [1.2, -3.4, 5.6], 5
features = torch.randn((n_train + n_test, 1))
poly_features = torch.cat((features, torch.pow(features, 2), torch.pow(features, 3)), 1)
labels = (true_w[0] * poly_features[:, 0] + true_w[1] * poly_features[:, 1]
+ true_w[2] * poly_features[:, 2] + true_b)
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
看一看生成的資料集的前兩個樣本。
輸出:
(tensor([[-1.0613],
[-0.8386]]), tensor([[-1.0613, 1.1264, -1.1954],
[-0.8386, 0.7032, -0.5897]]), tensor([-6.8037, -1.7054]))
3.11.4.2 定義、訓練和測試模型
我們先定義作圖函數
semilogy
,其中 y y y 軸使用了對數尺度。
# 本函數已儲存在d2lzh_pytorch包中友善以後使用
def semilogy(x_vals, y_vals, x_label, y_label, x2_vals=None, y2_vals=None,
legend=None, figsize=(3.5, 2.5)):
d2l.set_figsize(figsize)
d2l.plt.xlabel(x_label)
d2l.plt.ylabel(y_label)
d2l.plt.semilogy(x_vals, y_vals)
if x2_vals and y2_vals:
d2l.plt.semilogy(x2_vals, y2_vals, linestyle=':')
d2l.plt.legend(legend)
和線性回歸一樣,多項式函數拟合也使用平方損失函數。因為我們将嘗試使用不同複雜度的模型來拟合生成的資料集,是以我們把模型定義部分放在
fit_and_plot
函數中。多項式函數拟合的訓練和測試步驟與3.6節(softmax回歸的從零開始實作)介紹的softmax回歸中的相關步驟類似。
num_epochs, loss = 100, torch.nn.MSELoss()
def fit_and_plot(train_features, test_features, train_labels, test_labels):
net = torch.nn.Linear(train_features.shape[-1], 1)
# 通過Linear文檔可知,pytorch已經将參數初始化了,是以我們這裡就不手動初始化了
batch_size = min(10, train_labels.shape[0])
dataset = torch.utils.data.TensorDataset(train_features, train_labels)
train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)
train_ls, test_ls = [], []
for _ in range(num_epochs):
for X, y in train_iter:
l = loss(net(X), y.view(-1, 1))
optimizer.zero_grad()
l.backward()
optimizer.step()
train_labels = train_labels.view(-1, 1)
test_labels = test_labels.view(-1, 1)
train_ls.append(loss(net(train_features), train_labels).item())
test_ls.append(loss(net(test_features), test_labels).item())
print('final epoch: train loss', train_ls[-1], 'test loss', test_ls[-1])
semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
print('weight:', net.weight.data,
'\nbias:', net.bias.data)
3.11.4.3 三階多項式函數拟合(正常)
我們先使用與資料生成函數同階的三階多項式函數拟合。實驗表明,這個模型的訓練誤差和在測試資料集的誤差都較低。訓練出的模型參數也接近真實值: w 1 = 1.2 , w 2 = − 3.4 , w 3 = 5.6 , b = 5 w_1 = 1.2, w_2=-3.4, w_3=5.6, b = 5 w1=1.2,w2=−3.4,w3=5.6,b=5。
fit_and_plot(poly_features[:n_train, :], poly_features[n_train:, :],
labels[:n_train], labels[n_train:])
輸出:
final epoch: train loss 0.00010175639908993617 test loss 9.790256444830447e-05
weight: tensor([[ 1.1982, -3.3992, 5.6002]])
bias: tensor([5.0014])
3.11.4.4 線性函數拟合(欠拟合)
我們再試試線性函數拟合。很明顯,該模型的訓練誤差在疊代早期下降後便很難繼續降低。在完成最後一次疊代周期後,訓練誤差依舊很高。線性模型在非線性模型(如三階多項式函數)生成的資料集上容易欠拟合。
fit_and_plot(features[:n_train, :], features[n_train:, :], labels[:n_train],
labels[n_train:])
輸出:
final epoch: train loss 249.35157775878906 test loss 168.37705993652344
weight: tensor([[19.4123]])
bias: tensor([0.5805])
3.11.4.5 訓練樣本不足(過拟合)
事實上,即便使用與資料生成模型同階的三階多項式函數模型,如果訓練樣本不足,該模型依然容易過拟合。讓我們隻使用兩個樣本來訓練模型。顯然,訓練樣本過少了,甚至少于模型參數的數量。這使模型顯得過于複雜,以至于容易被訓練資料中的噪聲影響。在疊代過程中,盡管訓練誤差較低,但是測試資料集上的誤差卻很高。這是典型的過拟合現象。
fit_and_plot(poly_features[0:2, :], poly_features[n_train:, :], labels[0:2],
labels[n_train:])
輸出:
final epoch: train loss 1.198514699935913 test loss 166.037109375
weight: tensor([[1.4741, 2.1198, 2.5674]])
bias: tensor([3.1207])
我們将在接下來的兩個小節繼續讨論過拟合問題以及應對過拟合的方法。
小結
- 由于無法從訓練誤差估計泛化誤差,一味地降低訓練誤差并不意味着泛化誤差一定會降低。機器學習模型應關注降低泛化誤差。
- 可以使用驗證資料集來進行模型選擇。
- 欠拟合指模型無法得到較低的訓練誤差,過拟合指模型的訓練誤差遠小于它在測試資料集上的誤差。
- 應選擇複雜度合适的模型并避免使用過少的訓練樣本。
注:本節除了代碼之外與原書基本相同,原書傳送門