過擬合、欠擬合及其解決方案
- 過擬合、欠擬合的概念
- 權重衰減
- 丟棄法
模型選擇、過擬合和欠擬合
訓練誤差和泛化誤差
在解釋上述現象之前,我們需要區分訓練誤差(training error)和泛化誤差(generalization error)。通俗來講,前者指模型在訓練數據集上表現出的誤差,后者指模型在任意一個測試數據樣本上表現出的誤差的期望,並常常通過測試數據集上的誤差來近似。計算訓練誤差和泛化誤差可以使用之前介紹過的損失函數,例如線性回歸用到的平方損失函數和softmax回歸用到的交叉熵損失函數。
機器學習模型應關注降低泛化誤差。
模型選擇
驗證數據集
從嚴格意義上講,測試集只能在所有超參數和模型參數選定后使用一次。不可以使用測試數據選擇模型,如調參。由於無法從訓練誤差估計泛化誤差,因此也不應只依賴訓練數據選擇模型。鑒於此,我們可以預留一部分在訓練數據集和測試數據集以外的數據來進行模型選擇。這部分數據被稱為驗證數據集,簡稱驗證集(validation set)。例如,我們可以從給定的訓練集中隨機選取一小部分作為驗證集,而將剩余部分作為真正的訓練集。
K折交叉驗證
由於驗證數據集不參與模型訓練,當訓練數據不夠用時,預留大量的驗證數據顯得太奢侈。一種改善的方法是K折交叉驗證(K-fold cross-validation)。在K折交叉驗證中,我們把原始訓練數據集分割成K個不重合的子數據集,然后我們做K次模型訓練和驗證。每一次,我們使用一個子數據集驗證模型,並使用其他K-1個子數據集來訓練模型。在這K次訓練和驗證中,每次用來驗證模型的子數據集都不同。最后,我們對這K次訓練誤差和驗證誤差分別求平均。
過擬合和欠擬合
接下來,我們將探究模型訓練中經常出現的兩類典型問題:
- 一類是模型無法得到較低的訓練誤差,我們將這一現象稱作欠擬合(underfitting);
- 另一類是模型的訓練誤差遠小於它在測試數據集上的誤差,我們稱該現象為過擬合(overfitting)。 在實踐中,我們要盡可能同時應對欠擬合和過擬合。雖然有很多因素可能導致這兩種擬合問題,在這里我們重點討論兩個因素:模型復雜度和訓練數據集大小。
模型復雜度
為了解釋模型復雜度,我們以多項式函數擬合為例。給定一個由標量數據特征
和對應的標量標簽組成的訓練數據集,多項式函數擬合的目標是找一個
階多項式函數
來近似
。在上式中,是模型的權重參數,
是偏差參數。與線性回歸相同,多項式函數擬合也使用平方損失函數。特別地,一階多項式函數擬合又叫線性函數擬合。
給定訓練數據集,模型復雜度和誤差之間的關系:

訓練數據集大小
影響欠擬合和過擬合的另一個重要因素是訓練數據集的大小。一般來說,如果訓練數據集中樣本數過少,特別是比模型參數數量(按元素計)更少時,過擬合更容易發生。此外,泛化誤差不會隨訓練數據集里樣本數量增加而增大。因此,在計算資源允許的范圍之內,我們通常希望訓練數據集大一些,特別是在模型復雜度較高時,例如層數較多的深度學習模型。
多項式函數擬合實驗
%matplotlib inline import torch import numpy as np import sys sys.path.append("/home/kesci/input") import d2lzh1981 as d2l print(torch.__version__)
初始化模型參數
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)
features[:2], poly_features[:2], labels[:2]
定義、訓練和測試模型
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)
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()) # 將訓練損失保存到train_ls中 test_ls.append(loss(net(test_features), test_labels).item()) # 將測試損失保存到test_ls中 print('final epoch: train loss', train_ls[-1], 'test loss', test_ls[-1]) semilogy(range(1, num_epochs + 1), train_ls, 