動手學深度學習13-權重衰減


上一節中提提到的過擬合現象,在模型的訓練誤差遠小於測試集上的誤差。雖然增大訓練接數據集可以減輕過擬合,但是獲得額外的訓練數據往往代價過大,本節介紹過擬合常用的方式:權重衰減(weight decay)。

權重衰減

權重衰減等價於L2范數正則化(regularzation)。正則化通過模型損失函數添加懲罰項使學到的模型參數值較小,是應對過擬合的常用方法,我們先描述L2范數正則化,再解釋它為何稱為權重衰減。
L2范數正則化在模型原損失函數的基礎上添加L2范數懲罰項,從而得到訓練得到所需的最小化函數。L2范數懲罰項是模型權重參數每個元素的平凡和與一個正的常數的乘積。以線性回歸中線性回歸損失函數
\(l(w_{1},w_{2},b )= \frac{1}{n}\sum_{i=1}^{n}\frac{1}{2}(x_{1}^{(i)}w_{1}+x_{2}^{(i)}w_{2} +b - y^{(i)})^{2}\) ,其中w1,w2是權重參數,b為偏置,樣本i的輸入為\(x_{1}^{(i)},x_{2}^{(i)},標簽是y^{(i)},樣本數為n,將權重參數向量\)w=[w_{1},w_{2}]表示,$帶有L2范數懲罰項的新損失函數為

\[l(w_{1},w_{2},b )+\frac{\lambda}{2n}\|\boldsymbol{w}\|^{2} \]

其中超參數為λ>0。當權重參數均為0時,懲罰項最小。當λ較大時,懲罰項在損失函數中的權重較大,通常會使學習到的權重參數的元素較接近0;當λ設為0時,懲罰項完全不起作用。上式中L2范數平方\(\|w\|^{2}展開后得到w_{1}^{2}+w_{2}^{2}\)
有了L2范數懲罰項后,在小批量隨機梯度下降匯總,我們將線性回歸一節中權重w1和w2的迭代方式更改為

\[\begin{array} { l } { 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{array} \]

可見,L2范數正則化令權重w1和w2先自乘一個小於1的數,再減去不含懲罰項的梯度。因此L2范數正則化又叫做權重衰減。有時候在懲罰項中也添加偏置元素的平方和。

高維線性回歸實驗

高緯線性過擬合數據,並使用權重衰減來應對過擬合。設定特征的維度為p,對於訓練和測試數據中特征為\(x_{1},x_{2},x_{3},,,,,x_{p}\)的任一樣本,我們使用如下的形象函數來生成該樣本的標簽:
\(y = 0.05+\sum_{i=1}^{p}0.01x_{i}+\epsilon, 其中噪音項\epsilon\)服從均值為0,標准差為0.01的正態分布。為了較容易的觀察過擬合,我們考慮高緯線性回歸問題,設定維度200,同時故意把訓練數據集的樣本降低為20。

%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:]
從零開始實現

從零開始實現權重衰減的方法,在目標函數后添加L2范數懲罰項來實現權重衰減。

初始化模型參數
def init_params():
    w = torch.randn((num_inputs,1),requires_grad=True)
    b = torch.zeros(1,requires_grad=True)
    return [w,b]
定義L2范數懲罰項
def l2_penalty(w):
    return (w**2).sum()/2
def linreg(X,w,b):
    return torch.mm(X,w)+b


def squared_loss(y_hat,y):
    return (y_hat-y.view(y_hat.size()))**2/2

def sgd(params,lr,batch_size):
    for param in params:
        param.data -=lr* param.grad/batch_size  

        
        #定義作圖函數 semiplgy 其中y軸使用了對數尺度
def semilogy(x,y,x_label,y_label,x2=None,y2=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,y)
    if x2 and y2:
        d2l.plt.semilogy(x2,y2,linestyle=':')
        d2l.plt.legend(legend)

定義訓練和測試
batch_size,num_epochs ,lr =1,100,0.003
net,loss = linreg,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:

            #l = loss(net(X, w, b), y) + lambd * l2_penalty(w)
            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()
            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())
        
    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())
fit_and_plot(lambd=0)
L2 norm of w 13.360041618347168

使用權重衰減
fit_and_plot(lambd=3)
# 訓練集的誤差有所提高,但是測試集上的誤差同樣下降,過擬合的現象在一定程度上緩解,
#另外權重參數L2范數比不適用權重衰減更小,此時的權重參數更加接近0
L2 norm of w 0.0406213141977787

pytorch簡潔實現

這里我們直接在構造優化器實例時通過weight_decay參數來指定權重衰減超參數,默認是pytorch會對權重和偏置同事衰減。我們可以分別對權重和偏置購置優化器實例,從而只對權重衰減。

def fit_and_plot_pytorch(wd):
    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_b.step()
            optimizer_w.step()
            
        train_ls.append(loss(net(train_features),train_labels).mean().item())
        test_ls.append(loss(net(test_features),test_labels).mean().item())
    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())
fit_and_plot_pytorch(0)
L2 norm of w 13.489049911499023

fit_and_plot_pytorch(3)
L2 norm of w 0.13610510528087616

小結
  • 正則化通過為模型損失函數中添加懲罰項使學習到的模型參數值較小,是對應過擬合的常用方法
  • 權重衰減等於L2范數正則化,通常使學習到的權重參數接近0
  • 權重衰減可以通過優化器的weight_decay超參數來指定
  • 可以定義多個優化器實例對不同的模型參數使用不同的迭代方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM