# 3.12 權重衰減
上一節中我們觀察了過擬合現象,即模型的訓練誤差遠小于它在測試集上的誤差。雖然增大訓練數據集可能會減輕過擬合,但是獲取額外的訓練數據往往代價高昂。本節介紹應對過擬合問題的常用方法:權重衰減(weight decay)。
## 3.12.1 方法
權重衰減等價于 `$ L_2 $` 范數正則化(regularization)。正則化通過為模型損失函數添加懲罰項使學出的模型參數值較小,是應對過擬合的常用手段。我們先描述`$ L_2 $`范數正則化,再解釋它為何又稱權重衰減。
`$ L_2 $`范數正則化在模型原損失函數基礎上添加`$ L_2 $`范數懲罰項,從而得到訓練所需要最小化的函數。`$ L_2 $`范數懲罰項指的是模型權重參數每個元素的平方和與一個正的常數的乘積。以3.1節(線性回歸)中的線性回歸損失函數
```[tex]
\ell(w_1, w_2, b) = \frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right)^2
```
為例,其中`$ w_1, w_2 $`是權重參數,`$ b $`是偏差參數,樣本`$ i $`的輸入為`$ x_1^{(i)}, x_2^{(i)} $`,標簽為`$ y^{(i)} $`,樣本數為`$ n $`。將權重參數用向量`$ \boldsymbol{w} = [w_1, w_2] $`表示,帶有`$ L_2 $`范數懲罰項的新損失函數為
```[tex]
\ell(w_1, w_2, b) + \frac{\lambda}{2n} \|\boldsymbol{w}\|^2,
```
其中超參數`$ \lambda > 0 $`。當權重參數均為0時,懲罰項最小。當`$ \lambda $`較大時,懲罰項在損失函數中的比重較大,這通常會使學到的權重參數的元素較接近0。當`$ \lambda $`設為0時,懲罰項完全不起作用。上式中`$ L_2 $`范數平方`$ \|\boldsymbol{w}\|^2 $`展開后得到`$ w_1^2 + w_2^2 $`。有了`$ L_2 $`范數懲罰項后,在小批量隨機梯度下降中,我們將線性回歸一節中權重`$ w_1 $`和`$ w_2 $`的迭代方式更改為
```[tex]
\begin{aligned}
w_1 &\leftarrow \left(1- \frac{\eta\lambda}{|\mathcal{B}|} \right)w_1 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_1^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right),\\
w_2 &\leftarrow \left(1- \frac{\eta\lambda}{|\mathcal{B}|} \right)w_2 - \frac{\eta}{|\mathcal{B}|} \sum_{i \in \mathcal{B}}x_2^{(i)} \left(x_1^{(i)} w_1 + x_2^{(i)} w_2 + b - y^{(i)}\right).
\end{aligned}
```
可見,`$ L_2 $`范數正則化令權重`$ w_1 $`和`$ w_2 $`先自乘小于1的數,再減去不含懲罰項的梯度。因此,`$ L_2 $`范數正則化又叫權重衰減。權重衰減通過懲罰絕對值較大的模型參數為需要學習的模型增加了限制,這可能對過擬合有效。實際場景中,我們有時也在懲罰項中添加偏差元素的平方和。
## 3.12.2 高維線性回歸實驗
下面,我們以高維線性回歸為例來引入一個過擬合問題,并使用權重衰減來應對過擬合。設數據樣本特征的維度為`$ p $`。對于訓練數據集和測試數據集中特征為`$ x_1, x_2, \ldots, x_p $`的任一樣本,我們使用如下的線性函數來生成該樣本的標簽:
```[tex]
y = 0.05 + \sum_{i = 1}^p 0.01x_i + \epsilon
```
其中噪聲項`$ \epsilon $`服從均值為0、標準差為0.01的正態分布。為了較容易地觀察過擬合,我們考慮高維線性回歸問題,如設維度`$ p=200 $` ;同時,我們特意把訓練數據集的樣本數設低,如20。
``` python
%matplotlib inline
import torch
import torch.nn as nn
import numpy as np
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
n_train, n_test, num_inputs = 20, 100, 200
true_w, true_b = torch.ones(num_inputs, 1) * 0.01, 0.05
features = torch.randn((n_train + n_test, num_inputs))
labels = torch.matmul(features, true_w) + true_b
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
train_features, test_features = features[:n_train, :], features[n_train:, :]
train_labels, test_labels = labels[:n_train], labels[n_train:]
```
## 3.12.3 從零開始實現
下面先介紹從零開始實現權重衰減的方法。我們通過在目標函數后添加`$ L_2 $`范數懲罰項來實現權重衰減。
### 3.12.3.1 初始化模型參數
首先,定義隨機初始化模型參數的函數。該函數為每個參數都附上梯度。
``` python
def init_params():
w = torch.randn((num_inputs, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
return [w, b]
```
### 3.12.3.2 定義`$ L_2 $`范數懲罰項
下面定義`$ L_2 $`范數懲罰項。這里只懲罰模型的權重參數。
``` python
def l2_penalty(w):
return (w**2).sum() / 2
```
### 3.12.3.3 定義訓練和測試
下面定義如何在訓練數據集和測試數據集上分別訓練和測試模型。與前面幾節中不同的是,這里在計算最終的損失函數時添加了`$ L_2 $`范數懲罰項。
``` python
batch_size, num_epochs, lr = 1, 100, 0.003
net, loss = d2l.linreg, d2l.squared_loss
dataset = torch.utils.data.TensorDataset(train_features, train_labels)
train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)
def fit_and_plot(lambd):
w, b = init_params()
train_ls, test_ls = [], []
for _ in range(num_epochs):
for X, y in train_iter:
# 添加了L2范數懲罰項
l = loss(net(X, w, b), y) + lambd * l2_penalty(w)
l = l.sum()
if w.grad is not None:
w.grad.data.zero_()
b.grad.data.zero_()
l.backward()
d2l.sgd([w, b], lr, batch_size)
train_ls.append(loss(net(train_features, w, b), train_labels).mean().item())
test_ls.append(loss(net(test_features, w, b), test_labels).mean().item())
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
print('L2 norm of w:', w.norm().item())
```
### 3.12.3.4 觀察過擬合
接下來,讓我們訓練并測試高維線性回歸模型。當`lambd`設為0時,我們沒有使用權重衰減。結果訓練誤差遠小于測試集上的誤差。這是典型的過擬合現象。
``` python
fit_and_plot(lambd=0)
```
輸出:
```
L2 norm of w: 15.114808082580566
```
:-: 
### 3.12.3.5 使用權重衰減
下面我們使用權重衰減。可以看出,訓練誤差雖然有所提高,但測試集上的誤差有所下降。過擬合現象得到一定程度的緩解。另外,權重參數的`$ L_2 $`范數比不使用權重衰減時的更小,此時的權重參數更接近0。
``` python
fit_and_plot(lambd=3)
```
輸出:
```
L2 norm of w: 0.035220853984355927
```
:-: 
## 3.12.4 簡潔實現
這里我們直接在構造優化器實例時通過`weight_decay`參數來指定權重衰減超參數。默認下,PyTorch會對權重和偏差同時衰減。我們可以分別對權重和偏差構造優化器實例,從而只對權重衰減。
``` python
def fit_and_plot_pytorch(wd):
# 對權重參數衰減。權重名稱一般是以weight結尾
net = nn.Linear(num_inputs, 1)
nn.init.normal_(net.weight, mean=0, std=1)
nn.init.normal_(net.bias, mean=0, std=1)
optimizer_w = torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd) # 對權重參數衰減
optimizer_b = torch.optim.SGD(params=[net.bias], lr=lr) # 不對偏差參數衰減
train_ls, test_ls = [], []
for _ in range(num_epochs):
for X, y in train_iter:
l = loss(net(X), y).mean()
optimizer_w.zero_grad()
optimizer_b.zero_grad()
l.backward()
# 對兩個optimizer實例分別調用step函數,從而分別更新權重和偏差
optimizer_w.step()
optimizer_b.step()
train_ls.append(loss(net(train_features), train_labels).mean().item())
test_ls.append(loss(net(test_features), test_labels).mean().item())
d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
range(1, num_epochs + 1), test_ls, ['train', 'test'])
print('L2 norm of w:', net.weight.data.norm().item())
```
與從零開始實現權重衰減的實驗現象類似,使用權重衰減可以在一定程度上緩解過擬合問題。
``` python
fit_and_plot_pytorch(0)
```
輸出:
```
L2 norm of w: 12.86785888671875
```
:-: 
``` python
fit_and_plot_pytorch(3)
```
輸出:
```
L2 norm of w: 0.09631537646055222
```
:-: 
## 小結
* 正則化通過為模型損失函數添加懲罰項使學出的模型參數值較小,是應對過擬合的常用手段。
* 權重衰減等價于`$ L_2 $`范數正則化,通常會使學到的權重參數的元素較接近0。
* 權重衰減可以通過優化器中的`weight_decay`超參數來指定。
* 可以定義多個優化器實例對不同的模型參數使用不同的迭代方法。
------------
> 注:本節除了代碼之外與原書基本相同,[原書傳送門](https://zh.d2l.ai/chapter_deep-learning-basics/weight-decay.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯