pytorch_task3過擬合欠擬合;梯度消失爆炸;循環神經網絡

過擬合、欠擬合及其解決方案

訓練誤差、泛化誤差

前者指模型在訓練數據集上表現出的誤差。
後者指模型在任意一個測試數據樣本上表現出的誤差的期望,並常常通過測試數據集上的誤差來近似。

模型選擇

驗證數據集

從嚴格意義上講,測試集只能在所有超參數和模型參數選定後使用一次。不可以使用測試數據選擇模型,如調參。
由於無法從訓練誤差估計泛化誤差,因此也不應只依賴訓練數據選擇模型。鑑於此,我們可以預留一部分在訓練數據集和測試數據集以外的數據來進行模型選擇。
這部分數據被稱爲驗證數據集,簡稱驗證集(validation set)。例如,我們可以從給定的訓練集中隨機選取一小部分作爲驗證集,而將剩餘部分作爲真正的訓練集。

K折交叉驗證

由於驗證數據集不參與模型訓練,當訓練數據不夠用時,預留大量的驗證數據顯得太奢侈。一種改善的方法是K折交叉驗證(K-fold cross-validation)。在K折交叉驗證中,我們把原始訓練數據集分割成K個不重合的子數據集,然後我們做K次模型訓練和驗證。每一次,我們使用一個子數據集驗證模型,並使用其他K-1個子數據集來訓練模型。在這K次訓練和驗證中,每次用來驗證模型的子數據集都不同。最後,我們對這K次訓練誤差和驗證誤差分別求平均。

過擬合欠擬合概念

一類是模型無法得到較低的訓練誤差,我們將這一現象稱作欠擬合(underfitting);
另一類是模型的訓練誤差遠小於它在測試數據集上的誤差,我們稱該現象爲過擬合(overfitting)。
在實踐中,我們要儘可能同時應對欠擬合和過擬合。雖然有很多因素可能導致這兩種擬合問題,在這裏我們重點討論兩個因素:模型複雜度和訓練數據集大小。
影響欠擬合和過擬合的另一個重要因素是訓練數據集的大小。一般來說,如果訓練數據集中樣本數過少,特別是比模型參數數量(按元素計)更少時,過擬合更容易發生。此外,泛化誤差不會隨訓練數據集裏樣本數量增加而增大。因此,在計算資源允許的範圍之內,我們通常希望訓練數據集大一些,特別是在模型複雜度較高時,例如層數較多的深度學習模型。

模型複雜度

在這裏插入圖片描述

import matplotlib.pyplot as plt
import torch
import torchvision
import torchvision.transforms as transforms
import time
import numpy as np
from d2lzh_pytorch.utils import *
import torch.nn as nn
from torch.nn import init

#生成人工數據集
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)

#訓練
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)#轉換shape,用於求loss
        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, 'epochs', 'loss',
             range(1, num_epochs + 1), test_ls, ['train', 'test'])
    print('weight:', net.weight.data,
          '\nbias:', net.bias.data)

fit_and_plot(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:])

在這裏插入圖片描述

解決過擬合

權重衰減(加上L2範數懲罰項)

權重衰減等價於 𝐿2 範數正則化(regularization)。正則化通過爲模型損失函數添加懲罰項使學出的模型參數值較小,是應對過擬合的常用手段。
在這裏插入圖片描述

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())

丟棄法

在這裏插入圖片描述

# 參數的初始化
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256
num_epochs, lr, batch_size = 5, 100.0, 256  # 這裏的學習率設置的很大,原因與之前相同。
drop_prob1, drop_prob2 = 0.2, 0.5
loss = torch.nn.CrossEntropyLoss()
train_iter, test_iter = load_data_fashion_mnist(batch_size, root='../data')
net = nn.Sequential(
        FlattenLayer(),
        nn.Linear(num_inputs, num_hiddens1),
        nn.ReLU(),
        nn.Dropout(drop_prob1),
        nn.Linear(num_hiddens1, num_hiddens2),
        nn.ReLU(),
        nn.Dropout(drop_prob2),
        nn.Linear(num_hiddens2, 10)
        )

for param in net.parameters():
    nn.init.normal_(param, mean=0, std=0.01)

optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)

梯度消失、梯度爆炸

深度模型有關數值穩定性的典型問題是消失(vanishing)和爆炸(explosion)。
在這裏插入圖片描述

初始化模型參數

在神經網絡中,通常需要隨機初始化模型參數。下面我們來解釋這樣做的原因。

回顧多層感知機一節描述的多層感知機。爲了方便解釋,假設輸出層只保留一個輸出單元01(刪去02和03以及指向它們的箭頭),且隱藏層使用相同的激活函數。如果將每個隱藏單元的參數都初始化爲相等的值,那麼在正向傳播時每個隱藏單元將根據相同的輸入計算出相同的值,並傳遞至輸出層。在反向傳播中,每個隱藏單元的參數梯度值相等。因此,這些參數在使用基於梯度的優化算法迭代後值依然相等。之後的迭代也是如此。在這種情況下,無論隱藏單元有多少,隱藏層本質上只有1個隱藏單元在發揮作用。因此,正如在前面的實驗中所做的那樣,我們通常將神經網絡的模型參數,特別是權重參數,進行隨機初始化。

在這裏插入圖片描述

Xavier隨機初始化

在這裏插入圖片描述

協變量偏移

這裏我們假設,雖然輸入的分佈p(x)可能隨時間而改變,但是標記函數,即條件分佈P(y∣x)不會改變。雖然這個問題容易理解,但在實踐中也容易忽視。
訓練集由照片組成,而測試集只包含卡通。在一個看起來與測試集有着本質不同的數據集上進行訓練,而不考慮如何適應新的情況,這是不是一個好主意。不幸的是,這是一個非常常見的陷阱。

統計學家稱這種協變量變化是因爲問題的根源在於特徵分佈的變化(即協變量的變化)。數學上,我們可以說P(x)改變了,但P(y∣x)保持不變。儘管它的有用性並不侷限於此,當我們認爲x導致y時,協變量移位通常是正確的假設。

標籤偏移

訓練集由照片組成,而測試集只包含卡通。在一個看起來與測試集有着本質不同的數據集上進行訓練,而不考慮如何適應新的情況,這是不是一個好主意。不幸的是,這是一個非常常見的陷阱。

統計學家稱這種協變量變化是因爲問題的根源在於特徵分佈的變化(即協變量的變化)。數學上,我們可以說P(x)改變了,但P(y∣x)保持不變。儘管它的有用性並不侷限於此,當我們認爲x導致y時,協變量移位通常是正確的假設。

概念偏移

如果我們要建立一個機器翻譯系統,分佈P(y∣x)可能因我們的位置而異。這個問題很難發現。另一個可取之處是P(y∣x)通常只是逐漸變化。

循環神經網絡

在這裏插入圖片描述

循環神經網絡構造

在這裏插入圖片描述

RNN簡潔實現

我們使用Pytorch中的nn.RNN來構造循環神經網絡。在本節中,我們主要關注nn.RNN的以下幾個構造函數參數:

input_size - The number of expected features in the input x
hidden_size – The number of features in the hidden state h
nonlinearity – The non-linearity to use. Can be either 'tanh' or 'relu'. Default: 	'tanh'
batch_first – If True, then the input and output tensors are provided as (batch_size, num_steps, input_size). Default: False

這裏的batch_first決定了輸入的形狀,我們使用默認的參數False,對應的輸入形狀是 (num_steps, batch_size, input_size)。

forward函數的參數爲:

	input of shape (num_steps, batch_size, input_size): tensor containing the features of the input sequence.
	h_0 of shape (num_layers * num_directions, batch_size, hidden_size): tensor containing the initial hidden state for each element in the batch. Defaults to zero if not provided. If the RNN is bidirectional, num_directions should be 2, else it should be 1.

forward函數的返回值是:

	output of shape (num_steps, batch_size, num_directions * hidden_size): tensor containing the output features (h_t) from the last layer of the RNN, for each t.
	h_n of shape (num_layers * num_directions, batch_size, hidden_size): tensor containing the hidden state for t = num_steps.

實踐

one-hot向量

我們需要將字符表示成向量,這裏採用one-hot向量。假設詞典大小是N,每次字符對應一個從0到N-1的唯一的索引,則該字符的向量是一個長度爲的向量,若字符的索引是i,則該向量的第i個位置爲1,其他位置爲0。下面分別展示了索引爲0和2的one-hot向量,向量長度等於詞典大小。

def one_hot(x, n_class, dtype=torch.float32):
    result = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device)  # shape: (n, n_class)
    result.scatter_(1, x.long().view(-1, 1), 1)  # result[i, x[i, 0]] = 1
    return result

我們每次採樣的小批量的形狀是(批量大小, 時間步數)。下面的函數將這樣的小批量變換成數個形狀爲(批量大小, 詞典大小)的矩陣,矩陣個數等於時間步數。也就是說,時間步t的輸入爲Xt,其中n爲批量大小,d爲詞向量大小,即one-hot向量長度(詞典大小)。

def to_onehot(X, n_class):
    '''
    :param X:輸入的採樣批量
    :param n_class:詞典大小
    :return: 返回(批量大小,詞典大小)的矩陣,矩陣個數爲步數
    '''
    return [one_hot(X[:, i], n_class) for i in range(X.shape[1])]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章