Task03:過擬合、欠擬合及其解決方案;梯度消失、梯度爆炸;循環神經網絡進階

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

  1. 過擬合、欠擬合的概念
  2. 權重衰減
  3. 丟棄法

模型選擇、過擬合和欠擬合

訓練誤差和泛化誤差

在解釋上述現象之前,我們需要區分訓練誤差(training error)和泛化誤差(generalization error)。通俗來講,前者指模型在訓練數據集上表現出的誤差,後者指模型在任意一個測試數據樣本上表現出的誤差的期望,並常常通過測試數據集上的誤差來近似。計算訓練誤差和泛化誤差可以使用之前介紹過的損失函數,例如線性迴歸用到的平方損失函數和softmax迴歸用到的交叉熵損失函數。

機器學習模型應關注降低泛化誤差

模型選擇

驗證數據集

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

K折交叉驗證

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

過擬合和欠擬合

接下來,我們將探究模型訓練中經常出現的兩類典型問題:

  • 一類是模型無法得到較低的訓練誤差,我們將這一現象稱作欠擬合(underfitting);
  • 另一類是模型的訓練誤差遠小於它在測試數據集上的誤差,我們稱該現象爲過擬合(overfitting)。 在實踐中,我們要儘可能同時應對欠擬合和過擬合。雖然有很多因素可能導致這兩種擬合問題,在這裏我們重點討論兩個因素:模型複雜度和訓練數據集大小。

模型複雜度

爲了解釋模型複雜度,我們以多項式函數擬合爲例。給定一個由標量數據特徵x和對應的標量標籤y組成的訓練數據集,多項式函數擬合的目標是找一個K階多項式函數

來近似 y。在上式中,w_{k}是模型的權重參數,b是偏差參數。與線性迴歸相同,多項式函數擬合也使用平方損失函數。特別地,一階多項式函數擬合又叫線性函數擬合。

給定訓練數據集,模型複雜度和誤差之間的關係:

訓練數據集大小

影響欠擬合和過擬合的另一個重要因素是訓練數據集的大小。一般來說,如果訓練數據集中樣本數過少,特別是比模型參數數量(按元素計)更少時,過擬合更容易發生。此外,泛化誤差不會隨訓練數據集裏樣本數量增加而增大。因此,在計算資源允許的範圍之內,我們通常希望訓練數據集大一些,特別是在模型複雜度較高時,例如層數較多的深度學習模型。

多項式函數擬合實驗

%matplotlib inline
import torch
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
print(torch.__version__)

輸出:

1.3.0

初始化模型參數

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))#200*1
poly_features = torch.cat((features, torch.pow(features, 2), torch.pow(features, 3)), 1) #200*3
labels = (true_w[0] * poly_features[:, 0] + true_w[1] * poly_features[:, 1]
          + true_w[2] * poly_features[:, 2] + true_b)#200
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
features[:2], poly_features[:2], labels[:2]

輸出:

(tensor([[-0.8589],
         [-0.2534]]), tensor([[-0.8589,  0.7377, -0.6335],
         [-0.2534,  0.0642, -0.0163]]), tensor([-2.0794,  4.4039]))

定義、訓練和測試模型

def semilogy(x_vals, y_vals, x_label, y_label, x2_vals=None, y2_vals=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_vals, y_vals)
    if x2_vals and y2_vals:
        d2l.plt.semilogy(x2_vals, y2_vals, linestyle=':')
        d2l.plt.legend(legend)
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)
        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:])

線性函數擬合(欠擬合)

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

訓練樣本不足(過擬合)

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

權重衰減

方法

權重衰減等價於 L2L範數正則化(regularization)。正則化通過爲模型損失函數添加懲罰項使學出的模型參數值較小,是應對過擬合的常用手段。

L2 範數正則化(regularization)

高維線性迴歸實驗從零開始的實現

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

%matplotlib inline
import torch
import torch.nn as nn
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l

print(torch.__version__)

初始化模型參數

與前面觀察過擬合和欠擬合現象的時候相似,在這裏不再解釋。

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:]
# 定義參數初始化函數,初始化模型參數並且附上梯度
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

定義訓練和測試

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

觀察過擬合

fit_and_plot(lambd=0)

使用權重衰減

fit_and_plot(lambd=3)

基於pytorch簡潔實現

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

fit_and_plot_pytorch(3)

丟棄法從零開始的實現

%matplotlib inline
import torch
import torch.nn as nn
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l

print(torch.__version__)
def dropout(X, drop_prob):
    X = X.float()
    assert 0 <= drop_prob <= 1
    keep_prob = 1 - drop_prob
    # 這種情況下把全部元素都丟棄
    if keep_prob == 0:
        return torch.zeros_like(X)#如果全部丟棄,就都爲0
    mask = (torch.rand(X.shape) < keep_prob).float()#製作mask,保留不丟棄的區域
    
    return mask * X / keep_prob#拉伸不丟棄的值

 

X = torch.arange(16).view(2, 8)
dropout(X, 0)

輸出:

tensor([[ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11., 12., 13., 14., 15.]])
dropout(X, 0.5)

輸出:

tensor([[ 0.,  0.,  0.,  6.,  8., 10.,  0., 14.],
        [ 0.,  0., 20.,  0.,  0.,  0., 28.,  0.]])
dropout(X, 1.0)

輸出:

tensor([[0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.]])

 

# 參數的初始化
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

W1 = torch.tensor(np.random.normal(0, 0.01, size=(num_inputs, num_hiddens1)), dtype=torch.float, requires_grad=True)
b1 = torch.zeros(num_hiddens1, requires_grad=True)
W2 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens1, num_hiddens2)), dtype=torch.float, requires_grad=True)
b2 = torch.zeros(num_hiddens2, requires_grad=True)
W3 = torch.tensor(np.random.normal(0, 0.01, size=(num_hiddens2, num_outputs)), dtype=torch.float, requires_grad=True)
b3 = torch.zeros(num_outputs, requires_grad=True)

params = [W1, b1, W2, b2, W3, b3]
drop_prob1, drop_prob2 = 0.2, 0.5

def net(X, is_training=True):
    X = X.view(-1, num_inputs)
    H1 = (torch.matmul(X, W1) + b1).relu()
    if is_training:  # 只在訓練模型時使用丟棄法
        H1 = dropout(H1, drop_prob1)  # 在第一層全連接後添加丟棄層
    H2 = (torch.matmul(H1, W2) + b2).relu()
    if is_training:
        H2 = dropout(H2, drop_prob2)  # 在第二層全連接後添加丟棄層
    return torch.matmul(H2, W3) + b3
def evaluate_accuracy(data_iter, net):
    acc_sum, n = 0.0, 0
    for X, y in data_iter:
        if isinstance(net, torch.nn.Module):
            net.eval() # 評估模式, 這會關閉dropout
            acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
            net.train() # 改回訓練模式
        else: # 自定義的模型
            if('is_training' in net.__code__.co_varnames): # 如果有is_training這個參數???
                # 將is_training設置成False
                acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item() 
            else:
                acc_sum += (net(X).argmax(dim=1) == y).float().sum().item() 
        n += y.shape[0]
    return acc_sum / n
num_epochs, lr, batch_size = 5, 100.0, 256  # 這裏的學習率設置的很大,原因與之前相同。
loss = torch.nn.CrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, root='/home/kesci/input/FashionMNIST2065')
d2l.train_ch3(
    net,
    train_iter,
    test_iter,
    loss,
    num_epochs,
    batch_size,
    params,
    lr)

結果:

epoch 1, loss 0.0046, train acc 0.549, test acc 0.704
epoch 2, loss 0.0023, train acc 0.785, test acc 0.737
epoch 3, loss 0.0019, train acc 0.825, test acc 0.834
epoch 4, loss 0.0017, train acc 0.842, test acc 0.763
epoch 5, loss 0.0016, train acc 0.848, test acc 0.813

簡潔實現

net = nn.Sequential(
        d2l.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)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)

結果: 

epoch 1, loss 0.0046, train acc 0.553, test acc 0.736
epoch 2, loss 0.0023, train acc 0.785, test acc 0.803
epoch 3, loss 0.0019, train acc 0.818, test acc 0.756
epoch 4, loss 0.0018, train acc 0.835, test acc 0.829
epoch 5, loss 0.0016, train acc 0.848, test acc 0.851

總結

  • 欠擬合現象:模型無法達到一個較低的誤差

  • 過擬合現象:訓練誤差較低但是泛化誤差依然較高,二者相差較大

***********************************************************************************************************************************************

二、梯度消失、梯度爆炸以及Kaggle房價預測

  1. 梯度消失和梯度爆炸
  2. 考慮到環境因素的其他問題
  3. Kaggle房價預測

梯度消失和梯度爆炸

深度模型有關數值穩定性的典型問題是消失(vanishing)和爆炸(explosion)。

當神經網絡的層數較多時,模型的數值穩定性容易變差。

PyTorch的默認隨機初始化

隨機初始化模型參數的方法有很多。在線性迴歸的簡潔實現中,我們使用torch.nn.init.normal_()使模型net的權重參數採用正態分佈的隨機初始化方式。不過,PyTorch中nn.Module的模塊參數都採取了較爲合理的初始化策略(不同類型的layer具體採樣的哪一種初始化方法的可參考源代碼),因此一般不用我們考慮。

鏈接:https://github.com/pytorch/pytorch/tree/master/torch/nn/modules

Xavier隨機初始化

還有一種比較常用的隨機初始化方法叫作Xavier隨機初始化。 假設某全連接層的輸入個數爲aa,輸出個數爲bb,Xavier隨機初始化將使該層中權重參數的每個元素都隨機採樣於均勻分佈

它的設計主要考慮到,模型參數初始化後,每層輸出的方差不該受該層輸入個數影響,且每層梯度的方差也不該受該層輸出個數影響。

考慮環境因素

協變量偏移

這裏我們假設,雖然輸入的分佈可能隨時間而改變,但是標記函數,即條件分佈P(y∣x)不會改變。雖然這個問題容易理解,但在實踐中也容易忽視。

想想區分貓和狗的一個例子。我們的訓練數據使用的是貓和狗的真實的照片,但是在測試時,我們被要求對貓和狗的卡通圖片進行分類。

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

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

標籤偏移

當我們認爲導致偏移的是標籤P(y)上的邊緣分佈的變化,但類條件分佈是不變的P(x∣y)時,就會出現相反的問題。當我們認爲y導致x時,標籤偏移是一個合理的假設。例如,通常我們希望根據其表現來預測診斷結果。在這種情況下,我們認爲診斷引起的表現,即疾病引起的症狀。有時標籤偏移和協變量移位假設可以同時成立。例如,當真正的標籤函數是確定的和不變的,那麼協變量偏移將始終保持,包括如果標籤偏移也保持。有趣的是,當我們期望標籤偏移和協變量偏移保持時,使用來自標籤偏移假設的方法通常是有利的。這是因爲這些方法傾向於操作看起來像標籤的對象,這(在深度學習中)與處理看起來像輸入的對象(在深度學習中)相比相對容易一些。

病因(要預測的診斷結果)導致 症狀(觀察到的結果)。

訓練數據集,數據很少只包含流感p(y)的樣本。

而測試數據集有流感p(y)和流感q(y),其中不變的是流感症狀p(x|y)。

概念偏移

另一個相關的問題出現在概念轉換中,即標籤本身的定義發生變化的情況。這聽起來很奇怪,畢竟貓就是貓。的確,貓的定義可能不會改變,但我們能不能對軟飲料也這麼說呢?事實證明,如果我們周遊美國,按地理位置轉移數據來源,我們會發現,即使是如圖所示的這個簡單術語的定義也會發生相當大的概念轉變。

Kaggle 房價預測實戰

作爲深度學習基礎篇章的總結,我們將對本章內容學以致用。下面,讓我們動手實戰一個Kaggle比賽:房價預測。本節將提供未經調優的數據的預處理、模型的設計和超參數的選擇。我們希望讀者通過動手操作、仔細觀察實驗現象、認真分析實驗結果並不斷調整方法,得到令自己滿意的結果。

%matplotlib inline
import torch
import torch.nn as nn
import numpy as np
import pandas as pd
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
print(torch.__version__)
torch.set_default_tensor_type(torch.FloatTensor)

 

獲取和讀取數據集

比賽數據分爲訓練數據集和測試數據集。兩個數據集都包括每棟房子的特徵,如街道類型、建造年份、房頂類型、地下室狀況等特徵值。這些特徵值有連續的數字、離散的標籤甚至是缺失值“na”。只有訓練數據集包括了每棟房子的價格,也就是標籤。我們可以訪問比賽網頁,點擊“Data”標籤,並下載這些數據集。

我們將通過pandas庫讀入並處理數據。在導入本節需要的包前請確保已安裝pandas庫。 假設解壓後的數據位於/home/kesci/input/houseprices2807/目錄,它包括兩個csv文件。下面使用pandas讀取這兩個文件。

test_data = pd.read_csv("/home/kesci/input/houseprices2807/house-prices-advanced-regression-techniques/test.csv")
train_data = pd.read_csv("/home/kesci/input/houseprices2807/house-prices-advanced-regression-techniques/train.csv")

訓練數據集包括1460個樣本、80個特徵和1個標籤。

train_data.shape

結果:

(1460, 81)

讓我們來查看前4個樣本的前4個特徵、後2個特徵和標籤(SalePrice):

train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]]

可以看到第一個特徵是Id,它能幫助模型記住每個訓練樣本,但難以推廣到測試樣本,所以我們不使用它來訓練。我們將所有的訓練數據和測試數據的79個特徵按樣本連結。

all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))
#(2919, 79)

預處理數據

我們對連續數值的特徵做標準化(standardization):設該特徵在整個數據集上的均值爲μ,標準差爲σ。那麼,我們可以將該特徵的每個值先減去μ再除以σ得到標準化後的每個特徵值。對於缺失的特徵值,我們將其替換成該特徵的均值。

numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
#參考鏈接:https://pandas.pydata.org/pandas-#docs/stable/reference/api/pandas.DataFrame.dtypes.html?#highlight=dtypes#pandas.DataFrame.dtypes
all_features[numeric_features] = all_features[numeric_features].apply(
    lambda x: (x - x.mean()) / (x.std()))
# 標準化後,每個數值特徵的均值變爲0,所以可以直接用0來替換缺失值
all_features[numeric_features] = all_features[numeric_features].fillna(0)
#Fill NA/NaN values using the specified method.

接下來將離散數值轉成指示特徵。舉個例子,假設特徵MSZoning裏面有兩個不同的離散值RL和RM,那麼這一步轉換將去掉MSZoning特徵,並新加兩個特徵MSZoning_RL和MSZoning_RM,其值爲0或1。如果一個樣本原來在MSZoning裏的值爲RL,那麼有MSZoning_RL=1且MSZoning_RM=0。

# dummy_na=True將缺失值也當作合法的特徵值併爲其創建指示特徵
all_features = pd.get_dummies(all_features, dummy_na=True)
#Convert categorical variable into dummy/indicator variables.
all_features.shape

輸出: 

(2919, 331)

可以看到這一步轉換將特徵數從79增加到了331。

最後,通過values屬性得到NumPy格式的數據,並轉成Tensor方便後面的訓練。

n_train = train_data.shape[0]
train_features = torch.tensor(all_features[:n_train].values, dtype=torch.float)
test_features = torch.tensor(all_features[n_train:].values, dtype=torch.float)
train_labels = torch.tensor(train_data.SalePrice.values, dtype=torch.float).view(-1, 1)

訓練模型

loss = torch.nn.MSELoss()

def get_net(feature_num):
    net = nn.Linear(feature_num, 1)
    for param in net.parameters():
        nn.init.normal_(param, mean=0, std=0.01)
    return net

def log_rmse(net, features, labels):
    with torch.no_grad():
        # 將小於1的值設成1,使得取對數時數值更穩定
        clipped_preds = torch.max(net(features), torch.tensor(1.0))
        rmse = torch.sqrt(2 * loss(clipped_preds.log(), labels.log()).mean())#感覺寫的有點問題
    return rmse.item()

下面的訓練函數跟本章中前幾節的不同在於使用了Adam優化算法。相對之前使用的小批量隨機梯度下降,它對學習率相對不那麼敏感。我們將在之後的“優化算法”一章裏詳細介紹它。

def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, learning_rate, weight_decay, batch_size):
    train_ls, test_ls = [], []#定義訓練和測試的loss值
    dataset = torch.utils.data.TensorDataset(train_features, train_labels)
    train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)
    # 這裏使用了Adam優化算法
    optimizer = torch.optim.Adam(params=net.parameters(), lr=learning_rate, weight_decay=weight_decay) 
    net = net.float()
    for epoch in range(num_epochs):
        for X, y in train_iter:
            l = loss(net(X.float()), y.float())
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
        train_ls.append(log_rmse(net, train_features, train_labels))
        if test_labels is not None:
            test_ls.append(log_rmse(net, test_features, test_labels))
    return train_ls, test_ls

K折交叉驗證

我們在模型選擇、欠擬合和過擬閤中介紹了K折交叉驗證。它將被用來選擇模型設計並調節超參數。下面實現了一個函數,它返回第i折交叉驗證時所需要的訓練和驗證數據。

def get_k_fold_data(k, i, X, y):
    # 返回第i折交叉驗證時所需要的訓練和驗證數據
    assert k > 1
    fold_size = X.shape[0] // k
    X_train, y_train = None, None
    for j in range(k):#這裏面還定義了一個循環,用來取數據
        idx = slice(j * fold_size, (j + 1) * fold_size)
        X_part, y_part = X[idx, :], y[idx]
        if j == i:#當相等的時候就是驗證集
            X_valid, y_valid = X_part, y_part
        elif X_train is None:#第一次不等的時候就是訓練
            X_train, y_train = X_part, y_part
        else:#第2次以及之後不等的時候,就累加測試集
            X_train = torch.cat((X_train, X_part), dim=0)
            y_train = torch.cat((y_train, y_part), dim=0)
    return X_train, y_train, X_valid, y_valid

 在K折交叉驗證中我們訓練K次並返回訓練和驗證的平均誤差

def k_fold(k, X_train, y_train, num_epochs,
           learning_rate, weight_decay, batch_size):
    train_l_sum, valid_l_sum = 0, 0
    for i in range(k):
        data = get_k_fold_data(k, i, X_train, y_train)
        net = get_net(X_train.shape[1])
        train_ls, valid_ls = train(net, *data, num_epochs, learning_rate,
                                   weight_decay, batch_size)
        train_l_sum += train_ls[-1]
        valid_l_sum += valid_ls[-1]
        if i == 0:
            d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse',
                         range(1, num_epochs + 1), valid_ls,
                         ['train', 'valid'])
        print('fold %d, train rmse %f, valid rmse %f' % (i, train_ls[-1], valid_ls[-1]))
    return train_l_sum / k, valid_l_sum / k#輸出train 和   test的loss 

模型選擇

我們使用一組未經調優的超參數並計算交叉驗證誤差。可以改動這些超參數來儘可能減小平均測試誤差。 有時候你會發現一組參數的訓練誤差可以達到很低,但是在KK折交叉驗證上的誤差可能反而較高。這種現象很可能是由過擬合造成的。因此,當訓練誤差降低時,我們要觀察KK折交叉驗證上的誤差是否也相應降低。

k, num_epochs, lr, weight_decay, batch_size = 5, 100, 5, 0, 64
train_l, valid_l = k_fold(k, train_features, train_labels, num_epochs, lr, weight_decay, batch_size)
print('%d-fold validation: avg train rmse %f, avg valid rmse %f' % (k, train_l, valid_l))

預測並在Kaggle中提交結果

下面定義預測函數。在預測之前,我們會使用完整的訓練數據集來重新訓練模型,並將預測結果存成提交所需要的格式。

def train_and_pred(train_features, test_features, train_labels, test_data,
                   num_epochs, lr, weight_decay, batch_size):
    net = get_net(train_features.shape[1])
    train_ls, _ = train(net, train_features, train_labels, None, None,
                        num_epochs, lr, weight_decay, batch_size)
    d2l.semilogy(range(1, num_epochs + 1), train_ls, 'epochs', 'rmse')
    print('train rmse %f' % train_ls[-1])
    preds = net(test_features).detach().numpy()
    test_data['SalePrice'] = pd.Series(preds.reshape(1, -1)[0])
    submission = pd.concat([test_data['Id'], test_data['SalePrice']], axis=1)
    submission.to_csv('./submission.csv', index=False)
    # sample_submission_data = pd.read_csv("../input/house-prices-advanced-regression-techniques/sample_submission.csv")

 設計好模型並調好超參數之後,下一步就是對測試數據集上的房屋樣本做價格預測。如果我們得到與交叉驗證時差不多的訓練誤差,那麼這個結果很可能是理想的,可以在Kaggle上提交結果。

train_and_pred(train_features, test_features, train_labels, test_data, num_epochs, lr, weight_decay, batch_size)

希望大家自己動手完成房價預測的實現,多參與討論。

三、循環神經網絡進階

GRU

RNN存在的問題:梯度較容易出現衰減或爆炸(BPTT)
⻔控循環神經⽹絡:捕捉時間序列中時間步距離較⼤的依賴關係

 

• 重置⻔有助於捕捉時間序列⾥短期的依賴關係;
• 更新⻔有助於捕捉時間序列⾥⻓期的依賴關係。

載入數據集

import os
os.listdir('/home/kesci/input')

結果: 

['d2lzh1981', 'houseprices2807', 'jaychou_lyrics4703', 'd2l_jay9460']

 

import numpy as np
import torch
from torch import nn, optim
import torch.nn.functional as F

import sys
sys.path.append("../input/")
import d2l_jay9460 as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

(corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics()

初始化參數

num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size
print('will use', device)

def get_params():  
    def _one(shape):
        ts = torch.tensor(np.random.normal(0, 0.01, size=shape), device=device, dtype=torch.float32) #正態分佈
        return torch.nn.Parameter(ts, requires_grad=True)
    def _three():
        return (_one((num_inputs, num_hiddens)),
                _one((num_hiddens, num_hiddens)),
                torch.nn.Parameter(torch.zeros(num_hiddens, device=device, dtype=torch.float32), requires_grad=True))
     
    W_xz, W_hz, b_z = _three()  # 更新門參數
    W_xr, W_hr, b_r = _three()  # 重置門參數
    W_xh, W_hh, b_h = _three()  # 候選隱藏狀態參數
    
    # 輸出層參數
    W_hq = _one((num_hiddens, num_outputs))
    b_q = torch.nn.Parameter(torch.zeros(num_outputs, device=device, dtype=torch.float32), requires_grad=True)
    return nn.ParameterList([W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q])

def init_gru_state(batch_size, num_hiddens, device):   #隱藏狀態初始化
    return (torch.zeros((batch_size, num_hiddens), device=device), )

 

 

 

 

 

 

 

 

 

 

 

 

 

 

源代碼:https://github.com/pytorch/pytorch/tree/master/torch/nn/modules

 

 

 

 

 

發佈了116 篇原創文章 · 獲贊 96 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章