L2正則化和權重衰退(Weight Decay)
一、權重衰退介紹
1.什麼是權重衰減/權重衰退——weight_decay
-
L2正則化
-
主要作用是:解決過擬合,在損失函式中加入L2正則化項
2. L2範數
L2範數,也被稱作歐幾里得範數或者Frobenius範數(當應用於矩陣時),是最常用的向量範數之一,用於衡量向量元素的大小。在數學上,L2範數定義為向量元素的平方和的平方根。對於向量\(x = [x_1,x_2,...,x_n]\) ,其L2範數可以表示為:
weight _decay
本質上是一個 L2正則化係數
3 .L2正則化
在數學表示式中,L2正則化通常被表達為損失函式的一個額外組成部分,如下所示:
其中:
- \(Loss_{total}\) 是模型在資料上的原始損失。
- \(\lambda\)是L2正則化係數,用於控制正則項對總損失的貢獻程度。
- \(||w||^2\) 是權重向量\(w\)的L2範數的平方。
weight _decay
本質上是一個 L2正則化係數
可以理解為:
- 加上這個 L2正則化,會限制模型的權重都會趨近於0
- 理解就是當
w
趨近 0 時,w
平方和 會小, 模型損失也會變小 - 而
weight_decay
的大小就是公式中的λ
,可以理解為λ
越大,最佳化器就越限制權重變得趨近 0
4 範數的限制
範數的限制有兩種,一種是硬性限制,第二種是柔性限制。
硬性限制:
我們如何控制一個模型的容量呢?
- 選擇的引數比較少
- 使得每個引數選擇的值的範圍比較小
而我們的權重衰退就是透過限制引數值的選擇範圍來控制模型的容量的。
我們增加限制,限制w:如下圖所示,w向量中每一個元素的值都小於θ的根號。
柔性限制(常用):
λ是一個平滑的,不像硬性限制,這種方法不會強制要求模型引數在每一步更新時都嚴格滿足約束條件,而是透過在損失函式中加入一個與範數成比例的正則化項來進行折衷。
拉格朗日乘子法原本是用於解決約束條件下的多元函式極值問題。舉例,求f(x,y)的最小值,但是有約束C(x,y) = 0。乘子法給的一般思路是,構造一個新的函式g(x,y,λ) = f(x,y) +λC(x,y),當同時滿足g'x = g'y = 0時,函式取到最小值。這件結論的幾何含義是,當f(x,y)與C(x,y)的等高線相切時,取到最小值。
具體到機器學習這裡,C(x,y) = w^2 -θ。所以圖中的黃色圓圈,代表不同θ下的約束條件。θ越小,則最終的parameter離原點越近。
5. 演示柔性限制對最優解的影響
① 綠色的線就是原始損失函式l的等高線,最佳化原始損失l的最優解(波浪號即最優解)在中心位置。
② 黃色圓圈,代表不同θ下的約束條件。θ越小,則最終的parameter離原點越近。
③ 當原始損失加入二分之λ的項後,這個項是一個二次項,假如w就兩個值,x1(橫軸)、x2(縱軸),那麼在圖上這個二次項的損失以原點為中心的等高線為橙色的圖所示。所以合併後的損失為綠色的和黃色的線加一起的損失。
④ 當加上損失項後,可以知道原來最優解對應的二次項的損失特別大,因此原來的最優解不是加上二次項後的公式的最優解了(因為原始最優解對於我們的損失項(黃線)來說就會特別大)。若沿著橙色的方向走,原有l損失值會大一些,但是二次項罰的損失會變小,當拉到平衡點以內時,懲罰項減少的值不足以原有l損失增大的值,這樣w * 就是加懲罰項後的最優解。
⑤ 損失函式加上正則項成為目標函式,目標函式最優解不是損失函式最優解。正則項就是防止達到損失函式最優導致過擬合,把損失函式最優點往外拉一拉。鼓勵權重分散,將所有額特徵運用起來,而不是依賴其中的少數特徵,並且權重分散的話它的內積就小一些。
⑥ l2正則項會對大數值的權值進行懲罰。
6.引數更新法則
推導過程:
帶有正則化的損失函式的一般形式是:\(L = l(w,b) + \dfrac{\lambda}{2}||w||^2\)
其中:
- \(l(w,b)\)是原始損失函式
- \(\dfrac{\lambda}{2}||w||^2\)是L2正則項,也被稱為權重衰減項,用來對大的權重值進行懲罰,避免過擬合。
- \(\lambda\)是正則化係數,控制著正則化項的強度。
進行梯度下降時,目標是找到使損失函式\(L\)最小的\(w\)。求\(L\)對\(w\)的偏導數,得到:
更新權重\(w\)的規則是從當前權重減去學習率乘以這個梯度,因此得到未加括號的具體更新步驟:
注意,這裡\(1-\eta\lambda\)部分,因為 \(\eta\lambda<1\),起到了縮減當前權重值的作用,這個操作也就是所謂的權重衰減。
總結
- 權重衰退透過L2正則項使得模型引數不會過大,從而控制模型複雜度
- 正則項權重是控制模型複雜度的超引數
二、程式碼部分
1. 權重衰退(使用自定義)
① 權重衰退是最廣泛使用的正則化的技術之一。
② 像以前一樣生成一些資料(人工資料集):\(y = 0.05+\sum_{i = 1}^d0.01x_i+\epsilon where\epsilon \sim N(0,0.01^2)\)
%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l
n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5 # 資料越簡單,模型越複雜,越容易過擬合。num_inputs為特徵維度
true_w, true_b = torch.ones((num_inputs,1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train) # 生成人工資料集
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)
# 初始化模型引數
def init_params():
w = torch.normal(0,1,size=(num_inputs,1),requires_grad=True)
b = torch.zeros(1,requires_grad=True)
return [w,b]
# 定義L2範數懲罰
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2 # 這裡我們沒有把lambda寫進了,我們會寫在外面
# 定義訓練函式
def train(lambd):
w, b = init_params()
net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
num_epochs, lr = 100, 0.003
animator = d2l.Animator(xlabel='epoch',ylabel='loss',yscale='log',xlim=[5,num_epochs],legend=['train','test'])
for epoch in range(num_epochs):
for X, y in train_iter:
#with torch.enable_grad():
l = loss(net(X),y) + lambd * l2_penalty(w) # lambda * l2_penalty
l.sum().backward()
d2l.sgd([w,b],lr,batch_size)
if(epoch+1) % 5 == 0:
if(epoch+1) % 5 ==0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net,test_iter,loss)))
print('w的L2範數是',torch.norm(w).item())
help(d2l.synthetic_data) # 檢視函式用法
Help on function synthetic_data in module d2l.torch:
synthetic_data(w, b, num_examples)
Generate y = Xw + b + noise.
忽略正則化直接訓練:
# 忽略正則化直接訓練
train(lambd=0) # 訓練集小,過擬合,測試集損失不下降
使用權重衰退:
# 使用權重衰退
train(lambd=3)
2.權重衰退(使用框架)
# 簡潔實現
def train_concise(wd):
net = nn.Sequential(nn.Linear(num_inputs,1))
for param in net.parameters():
param.data.normal_()
loss = nn.MSELoss()
num_epochs, lr = 100, 0.003
trainer = torch.optim.SGD([{"params":net[0].weight,"weight_decay":wd},{"params":net[0].bias}],lr=lr) # 懲罰項既可以寫在目標函式里面,也可以寫在訓練演算法裡面,每一次在更新之前把當前的w乘以衰退因子weight_decay
animator = d2l.Animator(xlabel='epoch',ylabel='loss',yscale='log',xlim=[5,num_epochs],legend=['train','test'])
for epoch in range(num_epochs):
for X, y in train_iter:
with torch.enable_grad():
trainer.zero_grad()
l = loss(net(X),y)
l.backward()
trainer.step()
if(epoch + 1) % 5 == 0:
animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net,test_iter,loss)))
print('w的L2範數是',net[0].weight.norm().item())
忽略正則化直接訓練:
# 這些圖看起來和我們從零開始實現權重衰減時的圖相同
train_concise(0)
使用權重衰退::
train_concise(3)