機器學習基礎(chǔ):過擬合奖亚、欠擬合淳梦、梯度消失與爆炸相關(guān)

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

  1. 過擬合昔字、欠擬合的概念
  2. 權(quán)重衰減
  3. 丟棄法

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

訓練誤差和泛化誤差

在解釋上述現(xiàn)象之前首繁,我們需要區(qū)分訓練誤差(training error)和泛化誤差(generalization error)。通俗來講陨囊,前者指模型在訓練數(shù)據(jù)集上表現(xiàn)出的誤差弦疮,后者指模型在任意一個測試數(shù)據(jù)樣本上表現(xiàn)出的誤差的期望,并常常通過測試數(shù)據(jù)集上的誤差來近似蜘醋。計算訓練誤差和泛化誤差可以使用之前介紹過的損失函數(shù)胁塞,例如線性回歸用到的平方損失函數(shù)和softmax回歸用到的交叉熵損失函數(shù)。

機器學習模型應關(guān)注降低泛化誤差压语。

模型選擇

驗證數(shù)據(jù)集

從嚴格意義上講啸罢,測試集只能在所有超參數(shù)和模型參數(shù)選定后使用一次。不可以使用測試數(shù)據(jù)選擇模型胎食,如調(diào)參扰才。由于無法從訓練誤差估計泛化誤差,因此也不應只依賴訓練數(shù)據(jù)選擇模型厕怜。鑒于此训桶,我們可以預留一部分在訓練數(shù)據(jù)集和測試數(shù)據(jù)集以外的數(shù)據(jù)來進行模型選擇。這部分數(shù)據(jù)被稱為驗證數(shù)據(jù)集酣倾,簡稱驗證集(validation set)。例如谤专,我們可以從給定的訓練集中隨機選取一小部分作為驗證集躁锡,而將剩余部分作為真正的訓練集。

K折交叉驗證

由于驗證數(shù)據(jù)集不參與模型訓練置侍,當訓練數(shù)據(jù)不夠用時映之,預留大量的驗證數(shù)據(jù)顯得太奢侈。一種改善的方法是K折交叉驗證(K-fold cross-validation)蜡坊。在K折交叉驗證中杠输,我們把原始訓練數(shù)據(jù)集分割成K個不重合的子數(shù)據(jù)集,然后我們做K次模型訓練和驗證秕衙。每一次蠢甲,我們使用一個子數(shù)據(jù)集驗證模型,并使用其他K-1個子數(shù)據(jù)集來訓練模型据忘。在這K次訓練和驗證中鹦牛,每次用來驗證模型的子數(shù)據(jù)集都不同。最后勇吊,我們對這K次訓練誤差和驗證誤差分別求平均曼追。

過擬合和欠擬合

接下來,我們將探究模型訓練中經(jīng)常出現(xiàn)的兩類典型問題:

  • 一類是模型無法得到較低的訓練誤差汉规,我們將這一現(xiàn)象稱作欠擬合(underfitting)礼殊;
  • 另一類是模型的訓練誤差遠小于它在測試數(shù)據(jù)集上的誤差,我們稱該現(xiàn)象為過擬合(overfitting)。
    在實踐中晶伦,我們要盡可能同時應對欠擬合和過擬合碟狞。雖然有很多因素可能導致這兩種擬合問題,在這里我們重點討論兩個因素:模型復雜度和訓練數(shù)據(jù)集大小坝辫。

模型復雜度

為了解釋模型復雜度篷就,我們以多項式函數(shù)擬合為例。給定一個由標量數(shù)據(jù)特征x和對應的標量標簽y組成的訓練數(shù)據(jù)集近忙,多項式函數(shù)擬合的目標是找一個K階多項式函數(shù)

\hat{y} = b + \sum_{k=1}^K x^k w_k

來近似 y竭业。在上式中,w_k是模型的權(quán)重參數(shù)及舍,b是偏差參數(shù)未辆。與線性回歸相同,多項式函數(shù)擬合也使用平方損失函數(shù)锯玛。特別地咐柜,一階多項式函數(shù)擬合又叫線性函數(shù)擬合。

給定訓練數(shù)據(jù)集攘残,模型復雜度和誤差之間的關(guān)系:

Image Name

訓練數(shù)據(jù)集大小

影響欠擬合和過擬合的另一個重要因素是訓練數(shù)據(jù)集的大小拙友。一般來說,如果訓練數(shù)據(jù)集中樣本數(shù)過少歼郭,特別是比模型參數(shù)數(shù)量(按元素計)更少時遗契,過擬合更容易發(fā)生。此外病曾,泛化誤差不會隨訓練數(shù)據(jù)集里樣本數(shù)量增加而增大牍蜂。因此,在計算資源允許的范圍之內(nèi)泰涂,我們通常希望訓練數(shù)據(jù)集大一些鲫竞,特別是在模型復雜度較高時,例如層數(shù)較多的深度學習模型逼蒙。

多項式擬合實驗

%matplotlib inline
import torch
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
print(torch.__version__)*/
## 初始化模型參數(shù)
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)

定義模型參數(shù)

使用線性模型擬合多項式

def semilogy(x_vals, y_vals, x_label, y_label, x2_vals=None, y2_vals=None,
             legend=None, figsize=(3.5, 2.5)):
    # 用來生成圖片直接觀測 
    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):
    # 初始化網(wǎng)絡模型
    net = torch.nn.Linear(train_features.shape[-1], 1)
    # 通過Linear文檔可知从绘,pytorch已經(jīng)將參數(shù)初始化了,所以我們這里就不手動初始化了
    
    # 設置批量大小
    batch_size = min(10, train_labels.shape[0])    
    dataset = torch.utils.data.TensorDataset(train_features, train_labels)      # 設置數(shù)據(jù)集
    train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True) # 設置獲取數(shù)據(jù)方式
    
    optimizer = torch.optim.SGD(net.parameters(), lr=0.01)                      # 設置優(yōu)化函數(shù)是牢,使用的是隨機梯度下降優(yōu)化
    # SGD函數(shù)優(yōu)化參數(shù)
    train_ls, test_ls = [], []
    for _ in range(num_epochs):
        for X, y in train_iter:                                                 # 取一個批量的數(shù)據(jù)
            l = loss(net(X), y.view(-1, 1))                                     # 輸入到網(wǎng)絡中計算輸出顶考,并和標簽比較求得損失函數(shù)
            optimizer.zero_grad()                                               # 梯度清零,防止梯度累加干擾優(yōu)化
            l.backward()                                                        # 求梯度
            optimizer.step()                                                    # 迭代優(yōu)化函數(shù)妖泄,進行參數(shù)優(yōu)化
        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)

三階函數(shù)的擬合結(jié)果(正常)

fit_and_plot(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:])
三階多項式擬合的損失圖像

線性函數(shù)的擬合結(jié)果(欠擬合)

fit_and_plot(features[:n_train, :], features[n_train:, :], labels[:n_train], labels[n_train:])
線性函數(shù)擬合的損失圖像

訓練樣本不足時的擬合結(jié)果(過擬合)

fit_and_plot(poly_features[0:2, :], poly_features[n_train:, :], labels[0:2], labels[n_train:])
訓練樣本不足時的損失圖像

權(quán)重衰減

方法

權(quán)重衰減等價于 L_2 范數(shù)正則化(regularization)驹沿。正則化通過為模型損失函數(shù)添加懲罰項使學出的模型參數(shù)值較小,是應對過擬合的常用手段蹈胡。

L2 范數(shù)正則化(regularization)

L_2范數(shù)正則化在模型原損失函數(shù)基礎(chǔ)上添加L_2范數(shù)懲罰項渊季,從而得到訓練所需要最小化的函數(shù)朋蔫。L_2范數(shù)懲罰項指的是模型權(quán)重參數(shù)每個元素的平方和與一個正的常數(shù)的乘積。以線性回歸中的線性回歸損失函數(shù)為例

\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是權(quán)重參數(shù)却汉,b是偏差參數(shù)驯妄,樣本i的輸入為x_1^{(i)}, x_2^{(i)},標簽為y^{(i)}合砂,樣本數(shù)為n青扔。將權(quán)重參數(shù)用向量\boldsymbol{w} = [w_1, w_2]表示,帶有L_2范數(shù)懲罰項的新?lián)p失函數(shù)為

\ell(w_1, w_2, b) + \frac{\lambda}{2n} |\boldsymbol{w}|^2,

其中超參數(shù)\lambda > 0翩伪。當權(quán)重參數(shù)均為0時微猖,懲罰項最小。當\lambda較大時缘屹,懲罰項在損失函數(shù)中的比重較大凛剥,這通常會使學到的權(quán)重參數(shù)的元素較接近0。當\lambda設為0時轻姿,懲罰項完全不起作用犁珠。上式中L_2范數(shù)平方|\boldsymbol{w}|^2展開后得到w_1^2 + w_2^2
有了L_2范數(shù)懲罰項后互亮,在小批量隨機梯度下降中犁享,我們將線性回歸一節(jié)中權(quán)重w_1w_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范數(shù)正則化令權(quán)重w_1w_2先自乘小于1的數(shù)豹休,再減去不含懲罰項的梯度炊昆。因此,L_2范數(shù)正則化又叫權(quán)重衰減慕爬。權(quán)重衰減通過懲罰絕對值較大的模型參數(shù)為需要學習的模型增加了限制,這可能對過擬合有效屏积。

高維線性回歸實驗從零開始的實現(xiàn)

下面医窿,我們以高維線性回歸為例來引入一個過擬合問題,并使用權(quán)重衰減來應對過擬合炊林。設數(shù)據(jù)樣本特征的維度為p姥卢。對于訓練數(shù)據(jù)集和測試數(shù)據(jù)集中特征為x_1, x_2, \ldots, x_p的任一樣本,我們使用如下的線性函數(shù)來生成該樣本的標簽:

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

其中噪聲項\epsilon服從均值為0渣聚、標準差為0.01的正態(tài)分布独榴。為了較容易地觀察過擬合,我們考慮高維線性回歸問題奕枝,如設維度p=200棺榔;同時,我們特意把訓練數(shù)據(jù)集的樣本數(shù)設低隘道,如20症歇。

解決過擬合使用增加二范數(shù)實現(xiàn)

%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__)
## 初始化模型參數(shù)
# 與前面觀察過擬合和欠擬合現(xiàn)象的時候相似郎笆,在這里不再解釋。
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:]
# 定義參數(shù)初始化函數(shù)忘晤,初始化模型參數(shù)并且附上梯度
def init_params():
    w = torch.randn((num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]
## 定義L2范數(shù)懲罰項
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范數(shù)懲罰項
            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)
## 使用權(quán)重衰減
fit_and_plot(lambd=3)
過擬合圖像

注:上面的紅線是未加懲罰項的結(jié)果宛蚓,下面的紅線是添加懲罰項后的結(jié)果,很清楚看出增加懲罰項后驗證機損失顯著下降

pytorh簡潔代碼實現(xiàn)

def fit_and_plot_pytorch(wd):
    # 對權(quán)重參數(shù)衰減设塔。權(quán)重名稱一般是以weight結(jié)尾
    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) # 對權(quán)重參數(shù)衰減
    optimizer_b = torch.optim.SGD(params=[net.bias], lr=lr)  # 不對偏差參數(shù)衰減
    
    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實例分別調(diào)用step函數(shù)凄吏,從而分別更新權(quán)重和偏差
            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)

丟棄法

多層感知機中神經(jīng)網(wǎng)絡圖描述了一個單隱藏層的多層感知機。其中輸入個數(shù)為4闰蛔,隱藏單元個數(shù)為5痕钢,且隱藏單元h_ii=1, \ldots, 5)的計算表達式為

h_i = \phi\left(x_1 w_{1i} + x_2 w_{2i} + x_3 w_{3i} + x_4 w_{4i} + b_i\right)

這里\phi是激活函數(shù),x_1, \ldots, x_4是輸入钞护,隱藏單元i的權(quán)重參數(shù)為w_{1i}, \ldots, w_{4i}盖喷,偏差參數(shù)為b_i。當對該隱藏層使用丟棄法時难咕,該層的隱藏單元將有一定概率被丟棄掉课梳。設丟棄概率為p,那么有p的概率h_i會被清零余佃,有1-p的概率h_i會除以1-p做拉伸暮刃。丟棄概率是丟棄法的超參數(shù)。具體來說爆土,設隨機變量\xi_i為0和1的概率分別為p1-p椭懊。使用丟棄法時我們計算新的隱藏單元h_i'

h_i' = \frac{\xi_i}{1-p} h_i

由于E(\xi_i) = 1-p,因此

E(h_i') = \frac{E(\xi_i)}{1-p}h_i = h_i

即丟棄法不改變其輸入的期望值步势。讓我們對之前多層感知機的神經(jīng)網(wǎng)絡中的隱藏層使用丟棄法氧猬,一種可能的結(jié)果如圖所示,其中h_2h_5被清零坏瘩。這時輸出值的計算不再依賴h_2h_5盅抚,在反向傳播時,與這兩個隱藏單元相關(guān)的權(quán)重的梯度均為0倔矾。由于在訓練中隱藏層神經(jīng)元的丟棄是隨機的妄均,即h_1, \ldots, h_5都有可能被清零,輸出層的計算無法過度依賴h_1, \ldots, h_5中的任一個哪自,從而在訓練模型時起到正則化的作用丰包,并可以用來應對過擬合。在測試模型時壤巷,我們?yōu)榱四玫礁哟_定性的結(jié)果邑彪,一般不使用丟棄法

隱藏層丟棄h2和h5的神經(jīng)網(wǎng)絡

丟棄法實現(xiàn)

%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)
    mask = (torch.rand(X.shape) < keep_prob).float()
    
    return mask * X / keep_prob
X = torch.arange(16).view(2, 8)
dropout(X, 0)
dropout(X, 0.5)
dropout(X, 1.0)
# 參數(shù)的初始化
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() # 評估模式, 這會關(guān)閉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這個參數(shù)
                # 將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)

簡潔代碼實現(xiàn)

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)

梯度消失和梯度爆炸

深度模型有關(guān)數(shù)值穩(wěn)定性的典型問題是消失(vanishing)和爆炸(explosion)锌蓄。
梯度消失會導致模型訓練困難升筏,對參數(shù)的優(yōu)化步長過小,收效甚微瘸爽,模型收斂十分緩慢
梯度爆炸會導致模型訓練困難您访,對參數(shù)的優(yōu)化步長過大,難以收斂

\color{red}{在深層網(wǎng)絡中盡量避免選擇sigmoid和tanh激活函數(shù)剪决,原因是這兩個激活函數(shù)會把元素轉(zhuǎn)換到[0, 1]和[-1, 1]之間灵汪,會加劇梯度消失的現(xiàn)象}

當神經(jīng)網(wǎng)絡的層數(shù)較多時,模型的數(shù)值穩(wěn)定性容易變差柑潦。

假設一個層數(shù)為L的多層感知機的第l\boldsymbol{H}^{(l)}的權(quán)重參數(shù)為\boldsymbol{W}^{(l)}享言,輸出層\boldsymbol{H}^{(L)}的權(quán)重參數(shù)為\boldsymbol{W}^{(L)}。為了便于討論渗鬼,不考慮偏差參數(shù)览露,且設所有隱藏層的激活函數(shù)為恒等映射(identity mapping)\phi(x) = x。給定輸入\boldsymbol{X}譬胎,多層感知機的第l層的輸出\boldsymbol{H}^{(l)} = \boldsymbol{X} \boldsymbol{W}^{(1)} \boldsymbol{W}^{(2)} \ldots \boldsymbol{W}^{(l)}差牛。此時,如果層數(shù)l較大堰乔,\boldsymbol{H}^{(l)}的計算可能會出現(xiàn)衰減或爆炸偏化。舉個例子,假設輸入和所有層的權(quán)重參數(shù)都是標量镐侯,如權(quán)重參數(shù)為0.2和5侦讨,多層感知機的第30層輸出為輸入\boldsymbol{X}分別與0.2^{30} \approx 1 \times 10^{-21}(消失)和5^{30} \approx 9 \times 10^{20}(爆炸)的乘積。當層數(shù)較多時苟翻,梯度的計算也容易出現(xiàn)消失或爆炸韵卤。

隨機初始化模型參數(shù)

在神經(jīng)網(wǎng)絡中,通常需要隨機初始化模型參數(shù)崇猫。下面我們來解釋這樣做的原因沈条。

回顧多層感知機一節(jié)描述的多層感知機。為了方便解釋邓尤,假設輸出層只保留一個輸出單元o_1(刪去o_2o_3以及指向它們的箭頭)拍鲤,且隱藏層使用相同的激活函數(shù)贴谎。如果將每個隱藏單元的參數(shù)都初始化為相等的值汞扎,那么在正向傳播時每個隱藏單元將根據(jù)相同的輸入計算出相同的值,并傳遞至輸出層擅这。在反向傳播中澈魄,每個隱藏單元的參數(shù)梯度值相等。因此仲翎,這些參數(shù)在使用基于梯度的優(yōu)化算法迭代后值依然相等痹扇。之后的迭代也是如此铛漓。在這種情況下,無論隱藏單元有多少鲫构,隱藏層本質(zhì)上只有1個隱藏單元在發(fā)揮作用浓恶。因此,正如在前面的實驗中所做的那樣结笨,我們通常將神經(jīng)網(wǎng)絡的模型參數(shù)包晰,特別是權(quán)重參數(shù),進行隨機初始化炕吸。

全聯(lián)接神經(jīng)網(wǎng)絡

PyTorch的默認隨機初始化

隨機初始化模型參數(shù)的方法有很多伐憾。在線性回歸的簡潔實現(xiàn)中,我們使用torch.nn.init.normal_()使模型net的權(quán)重參數(shù)采用正態(tài)分布的隨機初始化方式赫模。不過树肃,PyTorch中nn.Module的模塊參數(shù)都采取了較為合理的初始化策略(不同類型的layer具體采樣的哪一種初始化方法的可參考源代碼),因此一般不用我們考慮瀑罗。

Xavier隨機初始化

還有一種比較常用的隨機初始化方法叫作Xavier隨機初始化胸嘴。
假設某全連接層的輸入個數(shù)為a,輸出個數(shù)為b廓脆,Xavier隨機初始化將使該層中權(quán)重參數(shù)的每個元素都隨機采樣于均勻分布

U\left(-\sqrt{\frac{6}{a+b}}, \sqrt{\frac{6}{a+b}}\right).

它的設計主要考慮到筛谚,模型參數(shù)初始化后,每層輸出的方差不該受該層輸入個數(shù)影響停忿,且每層梯度的方差也不該受該層輸出個數(shù)影響驾讲。

考慮環(huán)境因素

協(xié)變量偏移

這里我們假設,雖然輸入的分布可能隨時間而改變席赂,但是標記函數(shù)吮铭,即條件分布P(y∣x)不會改變。雖然這個問題容易理解颅停,但在實踐中也容易忽視谓晌。

想想?yún)^(qū)分貓和狗的一個例子。我們的訓練數(shù)據(jù)使用的是貓和狗的真實的照片癞揉,但是在測試時纸肉,我們被要求對貓和狗的卡通圖片進行分類。

cat cat dog dog
image
image
image
image

測試數(shù)據(jù):

cat cat dog dog
image
image
image
image

顯然喊熟,這不太可能奏效柏肪。訓練集由照片組成,而測試集只包含卡通芥牌。在一個看起來與測試集有著本質(zhì)不同的數(shù)據(jù)集上進行訓練烦味,而不考慮如何適應新的情況,這是不是一個好主意壁拉。不幸的是谬俄,這是一個非常常見的陷阱柏靶。
統(tǒng)計學家稱這種協(xié)變量變化是因為問題的根源在于特征分布的變化(即協(xié)變量的變化)。數(shù)學上溃论,我們可以說P(x)改變了屎蜓,但P(y∣x)保持不變。盡管它的有用性并不局限于此钥勋,當我們認為x導致y時梆靖,協(xié)變量移位通常是正確的假設。

標簽偏移

當我們認為導致偏移的是標簽P(y)上的邊緣分布的變化笔诵,但類條件分布是不變的P(x∣y)時返吻,就會出現(xiàn)相反的問題。當我們認為y導致x時乎婿,標簽偏移是一個合理的假設测僵。例如,通常我們希望根據(jù)其表現(xiàn)來預測診斷結(jié)果谢翎。在這種情況下捍靠,我們認為診斷引起的表現(xiàn),即疾病引起的癥狀森逮。有時標簽偏移和協(xié)變量移位假設可以同時成立榨婆。例如,當真正的標簽函數(shù)是確定的和不變的褒侧,那么協(xié)變量偏移將始終保持良风,包括如果標簽偏移也保持。有趣的是闷供,當我們期望標簽偏移和協(xié)變量偏移保持時烟央,使用來自標簽偏移假設的方法通常是有利的。這是因為這些方法傾向于操作看起來像標簽的對象歪脏,這(在深度學習中)與處理看起來像輸入的對象(在深度學習中)相比相對容易一些疑俭。

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

訓練數(shù)據(jù)集婿失,數(shù)據(jù)很少只包含流感p(y)的樣本钞艇。

而測試數(shù)據(jù)集有流感p(y)和流感q(y),其中不變的是流感癥狀p(x|y)豪硅。

概念偏移

另一個相關(guān)的問題出現(xiàn)在概念轉(zhuǎn)換中哩照,即標簽本身的定義發(fā)生變化的情況。這聽起來很奇怪,畢竟貓就是貓。的確肌索,貓的定義可能不會改變,但我們能不能對軟飲料也這么說呢眯牧?事實證明,如果我們周游美國赖草,按地理位置轉(zhuǎn)移數(shù)據(jù)來源学少,我們會發(fā)現(xiàn),即使是如圖所示的這個簡單術(shù)語的定義也會發(fā)生相當大的概念轉(zhuǎn)變秧骑。

image

美國軟飲料名稱的概念轉(zhuǎn)變
如果我們要建立一個機器翻譯系統(tǒng)版确,分布P(y∣x)可能因我們的位置而異。這個問題很難發(fā)現(xiàn)乎折。另一個可取之處是P(y∣x)通常只是逐漸變化绒疗。

eg: 一個在冬季部署的物品推薦系統(tǒng)在夏季的物品推薦列表中出現(xiàn)了圣誕禮物
可以理解為在夏季的物品推薦系統(tǒng)與冬季相比,時間或者說季節(jié)發(fā)生了變化骂澄,導致了夏季推薦圣誕禮物的不合理的現(xiàn)象吓蘑,這個現(xiàn)象是由于協(xié)變量時間發(fā)生了變化造成的。


PS:\color{red}{如果數(shù)據(jù)量足夠的情況下磨镶,確保訓練數(shù)據(jù)集和測試集中的數(shù)據(jù)取自同一個數(shù)據(jù)集私痹,可以防止協(xié)變量偏移和標簽偏移是正確的雹锣。如果數(shù)據(jù)量很少蕊爵,少到測試集中存在訓練集中未包含的標簽攒射,就會發(fā)生標簽偏移}

應用實例:泰坦尼克號數(shù)據(jù)集訓練

%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)
## 獲取和讀取數(shù)據(jù)集

# 比賽數(shù)據(jù)分為訓練數(shù)據(jù)集和測試數(shù)據(jù)集钉凌。兩個數(shù)據(jù)集都包括每棟房子的特征滥搭,如街道類型、建造年份栽惶、房頂類型、地下室狀況等特征值席爽。這些特征值有連續(xù)的數(shù)字齐饮、離散的標簽甚至是缺失值“na”。只有訓練數(shù)據(jù)集包括了每棟房子的價格崇裁,也就是標簽。我們可以訪問比賽網(wǎng)頁,點擊“Data”標簽挺庞,并下載這些數(shù)據(jù)集。

# 我們將通過`pandas`庫讀入并處理數(shù)據(jù)稼病。在導入本節(jié)需要的包前請確保已安裝`pandas`庫选侨。
# 假設解壓后的數(shù)據(jù)位于`/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")
# 訓練數(shù)據(jù)集包括1460個樣本、80個特征和1個標簽芍瑞。
#train_data.shape
# 測試數(shù)據(jù)集包括1459個樣本和80個特征晨仑。我們需要將測試數(shù)據(jù)集中每個樣本的標簽預測出來。
#test_data.shape
# 讓我們來查看前4個樣本的前4個特征拆檬、后2個特征和標簽(SalePrice):
#train_data.iloc[0:4, [0, 1, 2, 3, -3, -2, -1]]
# 可以看到第一個特征是Id洪己,它能幫助模型記住每個訓練樣本,但難以推廣到測試樣本竟贯,所以我們不使用它來訓練答捕。我們將所有的訓練數(shù)據(jù)和測試數(shù)據(jù)的79個特征按樣本連結(jié)。
all_features = pd.concat((train_data.iloc[:, 1:-1], test_data.iloc[:, 1:]))

我們對連續(xù)數(shù)值的特征做標準化(standardization):設該特征在整個數(shù)據(jù)集上的均值為\mu屑那,標準差為\sigma拱镐。那么,我們可以將該特征的每個值先減去\mu再除以\sigma得到標準化后的每個特征值持际。對于缺失的特征值沃琅,我們將其替換成該特征的均值。

## 預處理數(shù)據(jù)
numeric_features = all_features.dtypes[all_features.dtypes != 'object'].index
all_features[numeric_features] = all_features[numeric_features].apply(
    lambda x: (x - x.mean()) / (x.std()))
# 標準化后蜘欲,每個數(shù)值特征的均值變?yōu)?益眉,所以可以直接用0來替換缺失值
all_features[numeric_features] = all_features[numeric_features].fillna(0)
# 接下來將離散數(shù)值轉(zhuǎn)成指示特征。舉個例子姥份,假設特征MSZoning里面有兩個不同的離散值RL和RM呜叫,那么這一步轉(zhuǎn)換將去掉MSZoning特征,并新加兩個特征MSZoning\_RL和MSZoning\_RM殿衰,其值為0或1朱庆。如果一個樣本原來在MSZoning里的值為RL,那么有MSZoning\_RL=1且MSZoning\_RM=0闷祥。
# dummy_na=True將缺失值也當作合法的特征值并為其創(chuàng)建指示特征
all_features = pd.get_dummies(all_features, dummy_na=True)
all_features.shape
# 可以看到這一步轉(zhuǎn)換將特征數(shù)從79增加到了331娱颊。

#最后傲诵,通過`values`屬性得到NumPy格式的數(shù)據(jù),并轉(zhuǎn)成`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
#下面定義比賽用來評價模型的對數(shù)均方根誤差拴竹。對數(shù)均方根誤差的實現(xiàn)如下。
def log_rmse(net, features, labels):
    with torch.no_grad():
        # 將小于1的值設成1剧罩,使得取對數(shù)時數(shù)值更穩(wěn)定
        clipped_preds = torch.max(net(features), torch.tensor(1.0))
        rmse = torch.sqrt(2 * loss(clipped_preds.log(), labels.log()).mean())
    return rmse.item()
# 下面的訓練函數(shù)跟本章中前幾節(jié)的不同在于使用了Adam優(yōu)化算法栓拜。相對之前使用的小批量隨機梯度下降,它對學習率相對不那么敏感惠昔。我們將在之后的“優(yōu)化算法”一章里詳細介紹它幕与。
def train(net, train_features, train_labels, test_features, test_labels,
          num_epochs, learning_rate, weight_decay, batch_size):
    train_ls, test_ls = [], []
    dataset = torch.utils.data.TensorDataset(train_features, train_labels)
    train_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True)
    # 這里使用了Adam優(yōu)化算法
    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$折交叉驗證镇防。它將被用來選擇模型設計并調(diào)節(jié)超參數(shù)啦鸣。下面實現(xiàn)了一個函數(shù),它返回第`i`折交叉驗證時所需要的訓練和驗證數(shù)據(jù)来氧。
def get_k_fold_data(k, i, X, y):
    # 返回第i折交叉驗證時所需要的訓練和驗證數(shù)據(jù)
    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:
            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
## 模型選擇
# 我們使用一組未經(jīng)調(diào)優(yōu)的超參數(shù)并計算交叉驗證誤差诫给。可以改動這些超參數(shù)來盡可能減小平均測試誤差啦扬。
# 有時候你會發(fā)現(xiàn)一組參數(shù)的訓練誤差可以達到很低中狂,但是在K折交叉驗證上的誤差可能反而較高。這種現(xiàn)象很可能是由過擬合造成的扑毡。因此吃型,當訓練誤差降低時,我們要觀察K折交叉驗證上的誤差是否也相應降低僚楞。
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中提交結(jié)果
# 下面定義預測函數(shù)勤晚。在預測之前,我們會使用完整的訓練數(shù)據(jù)集來重新訓練模型泉褐,并將預測結(jié)果存成提交所需要的格式赐写。
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")
train_and_pred(train_features, test_features, train_labels, test_data, num_epochs, lr, weight_decay, batch_size)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市膜赃,隨后出現(xiàn)的幾起案子挺邀,更是在濱河造成了極大的恐慌,老刑警劉巖跳座,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件端铛,死亡現(xiàn)場離奇詭異,居然都是意外死亡疲眷,警方通過查閱死者的電腦和手機禾蚕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狂丝,“玉大人换淆,你說我怎么就攤上這事哗总。” “怎么了倍试?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵讯屈,是天一觀的道長。 經(jīng)常有香客問我县习,道長涮母,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任躁愿,我火速辦了婚禮叛本,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘攘已。我一直安慰自己炮赦,他們只是感情好怜跑,可當我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布样勃。 她就那樣靜靜地躺著,像睡著了一般性芬。 火紅的嫁衣襯著肌膚如雪峡眶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天植锉,我揣著相機與錄音辫樱,去河邊找鬼。 笑死俊庇,一個胖子當著我的面吹牛狮暑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辉饱,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼搬男,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了彭沼?” 一聲冷哼從身側(cè)響起缔逛,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姓惑,沒想到半個月后褐奴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡于毙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年敦冬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唯沮。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡匪补,死狀恐怖伞辛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情夯缺,我是刑警寧澤蚤氏,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站踊兜,受9級特大地震影響竿滨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捏境,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一于游、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垫言,春花似錦贰剥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凛捏,卻和暖如春担忧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坯癣。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工瓶盛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人示罗。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓惩猫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚜点。 傳聞我的和親對象是個殘疾皇子轧房,可洞房花燭夜當晚...
    茶點故事閱讀 43,494評論 2 348