動手學深度學習-05 梯度消失和梯度爆炸

梯度消失、梯度爆炸以及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)

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章