過擬合、欠擬合及其解決方案
- 過擬合昔字、欠擬合的概念
- 權(quán)重衰減
- 丟棄法
模型選擇爆袍、過擬合和欠擬合
訓練誤差和泛化誤差
在解釋上述現(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ù)特征和對應的標量標簽組成的訓練數(shù)據(jù)集近忙,多項式函數(shù)擬合的目標是找一個階多項式函數(shù)
來近似 竭业。在上式中,是模型的權(quán)重參數(shù)及舍,是偏差參數(shù)未辆。與線性回歸相同,多項式函數(shù)擬合也使用平方損失函數(shù)锯玛。特別地咐柜,一階多項式函數(shù)擬合又叫線性函數(shù)擬合。
給定訓練數(shù)據(jù)集攘残,模型復雜度和誤差之間的關(guān)系:
訓練數(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:])
訓練樣本不足時的擬合結(jié)果(過擬合)
fit_and_plot(poly_features[0:2, :], poly_features[n_train:, :], labels[0:2], labels[n_train:])
權(quán)重衰減
方法
權(quán)重衰減等價于 范數(shù)正則化(regularization)驹沿。正則化通過為模型損失函數(shù)添加懲罰項使學出的模型參數(shù)值較小,是應對過擬合的常用手段蹈胡。
L2 范數(shù)正則化(regularization)
范數(shù)正則化在模型原損失函數(shù)基礎(chǔ)上添加范數(shù)懲罰項渊季,從而得到訓練所需要最小化的函數(shù)朋蔫。范數(shù)懲罰項指的是模型權(quán)重參數(shù)每個元素的平方和與一個正的常數(shù)的乘積。以線性回歸中的線性回歸損失函數(shù)為例
其中是權(quán)重參數(shù)却汉,是偏差參數(shù)驯妄,樣本的輸入為,標簽為合砂,樣本數(shù)為青扔。將權(quán)重參數(shù)用向量表示,帶有范數(shù)懲罰項的新?lián)p失函數(shù)為
其中超參數(shù)翩伪。當權(quán)重參數(shù)均為0時微猖,懲罰項最小。當較大時缘屹,懲罰項在損失函數(shù)中的比重較大凛剥,這通常會使學到的權(quán)重參數(shù)的元素較接近0。當設為0時轻姿,懲罰項完全不起作用犁珠。上式中范數(shù)平方展開后得到。
有了范數(shù)懲罰項后互亮,在小批量隨機梯度下降中犁享,我們將線性回歸一節(jié)中權(quán)重和的迭代方式更改為
可見,范數(shù)正則化令權(quán)重和先自乘小于1的數(shù)豹休,再減去不含懲罰項的梯度炊昆。因此,范數(shù)正則化又叫權(quán)重衰減慕爬。權(quán)重衰減通過懲罰絕對值較大的模型參數(shù)為需要學習的模型增加了限制,這可能對過擬合有效屏积。
高維線性回歸實驗從零開始的實現(xiàn)
下面医窿,我們以高維線性回歸為例來引入一個過擬合問題,并使用權(quán)重衰減來應對過擬合炊林。設數(shù)據(jù)樣本特征的維度為姥卢。對于訓練數(shù)據(jù)集和測試數(shù)據(jù)集中特征為的任一樣本,我們使用如下的線性函數(shù)來生成該樣本的標簽:
其中噪聲項服從均值為0渣聚、標準差為0.01的正態(tài)分布独榴。為了較容易地觀察過擬合,我們考慮高維線性回歸問題奕枝,如設維度棺榔;同時,我們特意把訓練數(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痕钢,且隱藏單元()的計算表達式為
這里是激活函數(shù),是輸入钞护,隱藏單元的權(quán)重參數(shù)為盖喷,偏差參數(shù)為。當對該隱藏層使用丟棄法時难咕,該層的隱藏單元將有一定概率被丟棄掉课梳。設丟棄概率為,那么有的概率會被清零余佃,有的概率會除以做拉伸暮刃。丟棄概率是丟棄法的超參數(shù)。具體來說爆土,設隨機變量為0和1的概率分別為和椭懊。使用丟棄法時我們計算新的隱藏單元
由于,因此
即丟棄法不改變其輸入的期望值步势。讓我們對之前多層感知機的神經(jīng)網(wǎng)絡中的隱藏層使用丟棄法氧猬,一種可能的結(jié)果如圖所示,其中和被清零坏瘩。這時輸出值的計算不再依賴和盅抚,在反向傳播時,與這兩個隱藏單元相關(guān)的權(quán)重的梯度均為0倔矾。由于在訓練中隱藏層神經(jīng)元的丟棄是隨機的妄均,即都有可能被清零,輸出層的計算無法過度依賴中的任一個哪自,從而在訓練模型時起到正則化的作用丰包,并可以用來應對過擬合。在測試模型時壤巷,我們?yōu)榱四玫礁哟_定性的結(jié)果邑彪,一般不使用丟棄法
丟棄法實現(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)化步長過大,難以收斂
當神經(jīng)網(wǎng)絡的層數(shù)較多時,模型的數(shù)值穩(wěn)定性容易變差柑潦。
假設一個層數(shù)為的多層感知機的第層的權(quán)重參數(shù)為享言,輸出層的權(quán)重參數(shù)為。為了便于討論渗鬼,不考慮偏差參數(shù)览露,且設所有隱藏層的激活函數(shù)為恒等映射(identity mapping)。給定輸入譬胎,多層感知機的第層的輸出差牛。此時,如果層數(shù)較大堰乔,的計算可能會出現(xiàn)衰減或爆炸偏化。舉個例子,假設輸入和所有層的權(quán)重參數(shù)都是標量镐侯,如權(quán)重參數(shù)為0.2和5侦讨,多層感知機的第30層輸出為輸入分別與(消失)和(爆炸)的乘積。當層數(shù)較多時苟翻,梯度的計算也容易出現(xiàn)消失或爆炸韵卤。
隨機初始化模型參數(shù)
在神經(jīng)網(wǎng)絡中,通常需要隨機初始化模型參數(shù)崇猫。下面我們來解釋這樣做的原因沈条。
回顧多層感知機一節(jié)描述的多層感知機。為了方便解釋邓尤,假設輸出層只保留一個輸出單元(刪去和以及指向它們的箭頭)拍鲤,且隱藏層使用相同的激活函數(shù)贴谎。如果將每個隱藏單元的參數(shù)都初始化為相等的值汞扎,那么在正向傳播時每個隱藏單元將根據(jù)相同的輸入計算出相同的值,并傳遞至輸出層擅这。在反向傳播中澈魄,每個隱藏單元的參數(shù)梯度值相等。因此仲翎,這些參數(shù)在使用基于梯度的優(yōu)化算法迭代后值依然相等痹扇。之后的迭代也是如此铛漓。在這種情況下,無論隱藏單元有多少鲫构,隱藏層本質(zhì)上只有1個隱藏單元在發(fā)揮作用浓恶。因此,正如在前面的實驗中所做的那樣结笨,我們通常將神經(jīng)網(wǎng)絡的模型參數(shù)包晰,特別是權(quán)重參數(shù),進行隨機初始化炕吸。
PyTorch的默認隨機初始化
隨機初始化模型參數(shù)的方法有很多伐憾。在線性回歸的簡潔實現(xiàn)中,我們使用torch.nn.init.normal_()
使模型net
的權(quán)重參數(shù)采用正態(tài)分布的隨機初始化方式赫模。不過树肃,PyTorch中nn.Module
的模塊參數(shù)都采取了較為合理的初始化策略(不同類型的layer具體采樣的哪一種初始化方法的可參考源代碼),因此一般不用我們考慮瀑罗。
Xavier隨機初始化
還有一種比較常用的隨機初始化方法叫作Xavier隨機初始化胸嘴。
假設某全連接層的輸入個數(shù)為,輸出個數(shù)為廓脆,Xavier隨機初始化將使該層中權(quán)重參數(shù)的每個元素都隨機采樣于均勻分布
它的設計主要考慮到筛谚,模型參數(shù)初始化后,每層輸出的方差不該受該層輸入個數(shù)影響停忿,且每層梯度的方差也不該受該層輸出個數(shù)影響驾讲。
考慮環(huán)境因素
協(xié)變量偏移
這里我們假設,雖然輸入的分布可能隨時間而改變席赂,但是標記函數(shù)吮铭,即條件分布P(y∣x)不會改變。雖然這個問題容易理解颅停,但在實踐中也容易忽視谓晌。
想想?yún)^(qū)分貓和狗的一個例子。我們的訓練數(shù)據(jù)使用的是貓和狗的真實的照片癞揉,但是在測試時纸肉,我們被要求對貓和狗的卡通圖片進行分類。
cat | cat | dog | dog |
---|---|---|---|
測試數(shù)據(jù):
cat | cat | dog | dog |
---|---|---|---|
顯然喊熟,這不太可能奏效柏肪。訓練集由照片組成,而測試集只包含卡通芥牌。在一個看起來與測試集有著本質(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)變秧骑。
如果我們要建立一個機器翻譯系統(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:
應用實例:泰坦尼克號數(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ù)集上的均值為屑那,標準差為拱镐。那么,我們可以將該特征的每個值先減去再除以得到標準化后的每個特征值持际。對于缺失的特征值沃琅,我們將其替換成該特征的均值。
## 預處理數(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)