1. 程式人生 > >從頭學pytorch(六):權重衰減

從頭學pytorch(六):權重衰減

深度學習中常常會存在過擬合現象,比如當訓練資料過少時,訓練得到的模型很可能在訓練集上表現非常好,但是在測試集上表現不好.
應對過擬合,可以通過資料增強,增大訓練集數量.我們這裡先不介紹資料增強,先從模型訓練的角度介紹常用的應對過擬合的方法.

權重衰減

權重衰減等價於 \(L_2\) 範數正則化(regularization)。正則化通過為模型損失函式新增懲罰項使學出的模型引數值較小,是應對過擬合的常用手段。我們先描述\(L_2\)範數正則化,再解釋它為何又稱權重衰減。

\(L_2\)範數正則化在模型原損失函式基礎上新增\(L_2\)範數懲罰項,從而得到訓練所需要最小化的函式。\(L_2\)範數懲罰項指的是模型權重引數每個元素的平方和與一個正的常數的乘積。線性迴歸一文中的線性迴歸損失函式

\[ \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\)範數懲罰項的新損失函式為

\[\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\)。
顯然,相比沒有正則化項的loss,有了\(L_2\)範數懲罰項後求導後將多出來一項\({\lambda}w_i\),所以,在小批量隨機梯度下降中,權重\(w_1\)和\(w_2\)的迭代方式將變為

\[ \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\)範數正則化又叫權重衰減.
權重衰減通過懲罰絕對值較大的模型引數為需要學習的模型增加了限制,這可能對過擬合有效。實際場景中,我們有時也在懲罰項中新增偏差元素的平方和。

高維線性迴歸實驗

我們建立一個數據集,來模擬過擬合,以及權重衰減針對過擬合的效果.
設資料樣本特徵的維度為\(p\)。對於訓練資料集和測試資料集中特徵為\(x_1, x_2, \ldots, x_p\)的任一樣本,我們使用如下的線性函式來生成該樣本的標籤:

\[ y = 0.05 + \sum_{i = 1}^p 0.01x_i + \epsilon \]

其中噪聲項\(\epsilon\)服從均值為0、標準差為0.01的正態分佈。為了較容易地觀察過擬合,我們考慮高維線性迴歸問題,如設維度\(p=200\);同時,我們特意把訓練資料集的樣本數設低,如20。

匯入必要的包

import torch
import torch.nn as nn
import numpy as np

資料集建立

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:]

dataset = torch.utils.data.TensorDataset(train_features, train_labels)

引數初始化

def init_params():
    w = torch.rand((num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]

模型定義

def linreg(X, w, b):
    # print(X.dtype,b.dtype)
    return torch.mm(X, w) + b

損失函式定義

由於我們想驗證L2正則項的作用,所以需要定義l2_penalty(w),loss由2部分構成,一部分就是正常的均方誤差,一部分是
L2正則項,用以控制w的大小. \(\lambda\)則表示這兩部分誤差的比例.

(y_hat - y.view(y_hat.size())) ** 2 / 2是一個shape為[batch,1]的Tensor,(w**2).sum()/2是一個標量,
他們二者相加時,後者會自動擴充套件成與前者相同shape的張量.

def squared_loss(y_hat, y):
    # 注意這裡返回的是向量, 另外, pytorch裡的MSELoss並沒有除以 2
    return (y_hat - y.view(y_hat.size())) ** 2 / 2

def l2_penalty(w):
    return (w**2).sum()/2

def total_loss(y_hat, y,w,lambd):
    return (y_hat - y.view(y_hat.size())) ** 2 / 2 + lambd * (w**2).sum()/2 #這裡用了廣播機制

定義優化器

def sgd(params, lr, batch_size):
    for param in params:
        param.data -= lr * param.grad / batch_size # 注意這裡更改param時用的param.data

訓練

注意,在訓練階段,在反向傳播時,我們計算loss時用的是total_loss,即加入了L2正則項的.在推導階段,計算在訓練集/測試集上的loss
用的是squared_loss.

batch_size, num_epochs, lr = 2, 100, 0.003
train_iter = torch.utils.data.DataLoader(dataset,batch_size=batch_size,shuffle=True)
net = linreg

def train(lamda):
    w,b = init_params()
    train_ls, test_ls = [], []
    for epoch in range(num_epochs):
        for X,y in train_iter:
            y_hat = net(X,w,b)
            l = total_loss(y_hat,y,w,lamda).sum()
            #print(w.grad.data)
            if w.grad is not None:
                #print(w.grad.data)
                w.grad.data.zero_()
                b.grad.data.zero_()
            else:
                print("grad 0 epoch %d" % (epoch))

            l.backward()

            sgd([w,b], lr, batch_size)

            #print(l.item())
        train_l = squared_loss(net(train_features,w,b),train_labels)
        print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
        train_ls.append(train_l.mean().item())

        test_l = squared_loss(net(test_features,w,b),test_labels)
        print('epoch %d, loss %f' % (epoch + 1, test_l.mean().item()))
        test_ls.append(test_l.mean().item())

    d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
                 range(1, num_epochs + 1), test_ls, ['train', 'test'])

當train(0)時,即相當於不帶正則項的loss.繪製出的曲線如下:

當train(1)時,即相當於squared_loss和L2正則項為1:1,繪製出的曲線如下:


以上是我們手動實現了損失函式,優化器等.用torch裡封裝好的MSELoss,optim等實現如下:

def train_use_torch(wd):
    net = torch.nn.Linear(num_inputs,1)
    loss = nn.MSELoss()

    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 epoch in range(num_epochs):
        for X,y in train_iter:
            y_hat = net(X)
            l = loss(y_hat,y).sum()
            optimizer_w.zero_grad()
            optimizer_b.zero_grad()

            l.backward()

            optimizer_w.step()
            optimizer_b.step()

        train_l = squared_loss(net(train_features),train_labels)
        print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
        train_ls.append(train_l.mean().item())

        test_l = squared_loss(net(test_features),test_labels)
        print('epoch %d, loss %f' % (epoch + 1, test_l.mean().item()))
        test_ls.append(test_l.mean().item())

    d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'loss',
                 range(1, num_epochs + 1), test_ls, ['train', 'test'])

對不同的引數,我們用不同的optimizer例項,w需要衰減,b不需要.
optimizer_w =torch.optim.SGD(params=[net.weight],lr=lr,weight_decay=wd) #權重衰減 optimizer_b =torch.optim.SGD(params=[net.bias],lr=lr) #偏差引數衰減 
同樣的,在更新引數時,需要對兩個optimizer例項都呼叫

    optimizer_w.step()
    optimizer_b.step()

最終繪製效果如下:

相關推薦

從頭pytorch():權重衰減

深度學習中常常會存在過擬合現象,比如當訓練資料過少時,訓練得到的模型很可能在訓練集上表現非常好,但是在測試集上表現不好. 應對過擬合,可以通過資料增強,增大訓練集數量.我們這裡先不介紹資料增強,先從模型訓練的角度介紹常用的應對過擬合的方法. 權重衰減 權重衰減等價於 \(L_2\) 範數正則化(regular

從頭pytorch(十):VGG NET

VGG AlexNet在Lenet的基礎上增加了幾個卷積層,改變了卷積核大小,每一層輸出通道數目等,並且取得了很好的效果.但是並沒有提出一個簡單有效的思路. VGG做到了這一點,提出了可以通過重複使⽤簡單的基礎塊來構建深度學習模型的思路. 論文地址:https://arxiv.org/abs/1409.155

從頭pytorch(三) 線性迴歸

關於什麼是線性迴歸,不多做介紹了.可以參考我以前的部落格https://www.cnblogs.com/sdu20112013/p/10186516.html 實現線性迴歸 分為以下幾個部分: 生成資料集 讀取資料 初始化模型引數 定義模型 定義損失函式 定義優化演算法 訓練模型 生成資料集 我們構造一個

從頭pytorch(四) softmax迴歸實現

FashionMNIST資料集共70000個樣本,60000個train,10000個test.共計10種類別. 通過如下方式下載. mnist_train = torchvision.datasets.FashionMNIST(root='/home/sc/disk/keepgoing/lear

從頭pytorch(五) 多層感知機及其實現

多層感知機 上圖所示的多層感知機中,輸入和輸出個數分別為4和3,中間的隱藏層中包含了5個隱藏單元(hidden unit)。由於輸入層不涉及計算,圖3.3中的多層感知機的層數為2。由圖3.3可見,隱藏層中的神經元和輸入層中各個輸入完全連線,輸出層中的神經元和隱藏層中的各個神經元也完全連線。因此,多層感知機

從頭pytorch(七):dropout防止過擬合

上一篇講了防止過擬合的一種方式,權重衰減,也即在loss上加上一部分\(\frac{\lambda}{2n} \|\boldsymbol{w}\|^2\),從而使得w不至於過大,即不過分偏向某個特徵. 這一篇介紹另一種防止過擬合的方法,dropout,即丟棄某些神經元的輸出.由於每次訓練的過程裡,丟棄掉哪些神

從頭pytorch(九):模型構造

模型構造 nn.Module nn.Module是pytorch中提供的一個類,是所有神經網路模組的基類.我們自定義的模組要繼承這個基類. import torch from torch import nn class MLP(nn.Module): # 宣告帶有模型引數的層,這裡聲明瞭兩個全連線層

從頭pytorch(十二):模型儲存和載入

模型讀取和儲存 總結下來,就是幾個函式 torch.load()/torch.save() 通過python的pickle完成序列化與反序列化.完成記憶體<-->磁碟轉換. Module.state_dict()/Module.load_state_dict() state_dict()獲

從頭pytorch(十四):lenet

卷積神經網路 在之前的文章裡,對28 X 28的影象,我們是通過把它展開為長度為784的一維向量,然後送進全連線層,訓練出一個分類模型.這樣做主要有兩個問題 影象在同一列鄰近的畫素在這個向量中可能相距較遠。它們構成的模式可能難以被模型識別。 對於大尺寸的輸入影象,使用全連線層容易造成模型過大。假設輸入是高和

從頭pytorch(十五):AlexNet

AlexNet AlexNet是2012年提出的一個模型,並且贏得了ImageNet影象識別挑戰賽的冠軍.首次證明了由計算機自動學習到的特徵可以超越手工設計的特徵,對計算機視覺的研究有著極其重要的意義. AlexNet的設計思路和LeNet是非常類似的.不同點主要有以下幾點: 啟用函式由sigmoid改為R

從頭pytorch(十七):網路中的網路NIN

網路中的網路NIN 之前介紹的LeNet,AlexNet,VGG設計思路上的共同之處,是加寬(增加捲積層的輸出的channel數量)和加深(增加捲積層的數量),再接全連線層做分類.   NIN提出了一個不同的思路,串聯多個由卷積層和'全連線層'(1x1卷積)構成的小網路來構建一個深層網路. 論文地址:http

從頭pytorch(十八):GoogLeNet

GoogLeNet GoogLeNet和vgg分別是2014的ImageNet挑戰賽的冠亞軍.GoogLeNet則做了更加大膽的網路結構嘗試,雖然深度只有22層,但大小卻比AlexNet和VGG小很多,GoogleNet引數為500萬個,AlexNet引數個數是GoogleNet的12倍,VGGNet引數又是

從頭pytorch(十九):批量歸一化batch normalization

批量歸一化 論文地址:https://arxiv.org/abs/1502.03167 批量歸一化基本上是現在模型的標配了. 說實在的,到今天我也沒搞明白batch normalize能夠使得模型訓練更穩定的底層原因,要徹底搞清楚,涉及到很多凸優化的理論,需要非常紮實的數學基礎才行. 目前為止,我理解的批量歸

從頭pytorch(二十):殘差網路resnet

殘差網路ResNet resnet是何凱明大神在2015年提出的.並且獲得了當年的ImageNet比賽的冠軍. 殘差網路具有里程碑的意義,為以後的網路設計提出了一個新的思路. googlenet的思路是加寬每一個layer,resnet的思路是加深layer. 論文地址:https://arxiv.org/a

從頭pytorch(二十一):全連線網路dense net

DenseNet 論文傳送門,這篇論文是CVPR 2017的最佳論文. resnet一文裡說了,resnet是具有里程碑意義的.densenet就是受resnet的啟發提出的模型. resnet中是把不同層的feature map相應元素的值直接相加.而densenet是將channel維上的feature

【小白PyTorch】4 構建模型三要素與權重初始化

文章目錄: [TOC] ## 1 模型三要素 三要素其實很簡單 1. 必須要繼承nn.Module這個類,要讓PyTorch知道這個類是一個Module 2. 在\_\_init\_\_(self)中設定好需要的元件,比如conv,pooling,Linear,BatchNorm等等 3. 最後在forwa

[從頭數學] 第156節 概率初步

銀河 sha snippet 分享 i+1 否則 script edr img 劇情提要: [機器小偉]在[project師阿偉]的陪同下進入了築基後期的修煉。 這次要修煉的目標是[概率初步]。 正劇開始: 星歷2016年04月02日 08:44:0

[從頭數學] 第176節 概率

分享 pan 說明 offset project name pre sha 次數 劇情提要: [機器小偉]在[project師阿偉]的陪同下進入了結丹中期的修煉, 這次要修煉的目標是[概率]。 正劇開始: 星歷2016年04月14日 09:46:28

從零開始Python3()--叠代

pri 循環 true python 元素 常見 nbsp rabl rom   只要是可叠代對象,無論有無下標,都可以叠代, 默認情況下,dict叠代的是key。如果要叠代value,可以用for value in d.values(),如果要同時叠代key和value,

跟我一起docker()--數據管理

修改 ubun 數據 image db2 ria 本地 afa 解壓 1.數據卷數據卷是一個可供容器使用的特殊目錄,有如下特性:數據卷可以在容器之間共享和重用數據卷修改會立即生效數據卷的更新不會影響鏡像如果有容器使用數據卷,該卷會一直存在準備工作: 創建一個目錄,並在