PyTorch框架学习十六——正则化与Dropout
PyTorch框架學(xué)習(xí)十六——正則化與Dropout
- 一、泛化誤差
- 二、L2正則化與權(quán)值衰減
- 三、正則化之Dropout
- 補充:
這次筆記主要關(guān)注防止模型過擬合的兩種方法:正則化與Dropout。
一、泛化誤差
一般模型的泛化誤差可以被分解為三部分:偏差、方差與噪聲。按照周志華老師西瓜書中的定義,這三者分別如下所示:
這樣的表達(dá)可能不太好理解,下面給出了一張圖,幫助一下理解:
二、L2正則化與權(quán)值衰減
正則化是一種減小方差的策略,具體可以學(xué)習(xí)吳恩達(dá)老師的機器學(xué)習(xí)的視頻。
損失函數(shù)衡量模型的輸出與真實標(biāo)簽的差異,正則化使用的地方在目標(biāo)函數(shù),即在原來的代價函數(shù)的基礎(chǔ)上再加上正則化項。
因為在實際使用中L2正則化使用的較多,所以這里重點介紹L2正則化。
L2正則化的目標(biāo)函數(shù)如下所示:
其中正則化項前的系數(shù)lambda/2,lambda調(diào)節(jié)正則化的程度,/2是為了反向傳播求導(dǎo)的時候和平方的2可以約掉。
在反向傳播進(jìn)行權(quán)值更新時,無正則化項和有正則化項的更新公式如下所示:
一般lambda是在0到1之間的數(shù),所以從上圖得知,有正則化時更新后的權(quán)值會比無正則化時更小一點,所以一般也將L2正則化稱為權(quán)值衰減(weight decay)。
下面構(gòu)建了兩個相同的網(wǎng)絡(luò),一個沒有用正則化項,一個使用了正則化項,可以觀察它們各自的擬合效果:
import torch import torch.nn as nn import matplotlib.pyplot as plt import sys, os from tools.common_tools import set_seed from torch.utils.tensorboard import SummaryWriterset_seed(1) # 設(shè)置隨機種子 n_hidden = 200 # 自定義一個全連接網(wǎng)絡(luò),每層200個神經(jīng)元 max_iter = 2000 # 最大迭代次數(shù)2000次 disp_interval = 200 # 繪圖的epoch間隔 lr_init = 0.01 # 初始化學(xué)習(xí)率# ============================ step 1/5 數(shù)據(jù) ============================ # 構(gòu)造一批虛擬的數(shù)據(jù) def gen_data(num_data=10, x_range=(-1, 1)):w = 1.5train_x = torch.linspace(*x_range, num_data).unsqueeze_(1)train_y = w*train_x + torch.normal(0, 0.5, size=train_x.size())test_x = torch.linspace(*x_range, num_data).unsqueeze_(1)test_y = w*test_x + torch.normal(0, 0.3, size=test_x.size())return train_x, train_y, test_x, test_ytrain_x, train_y, test_x, test_y = gen_data(x_range=(-1, 1))# ============================ step 2/5 模型 ============================ class MLP(nn.Module):def __init__(self, neural_num):super(MLP, self).__init__()self.linears = nn.Sequential(nn.Linear(1, neural_num),nn.ReLU(inplace=True),nn.Linear(neural_num, neural_num),nn.ReLU(inplace=True),nn.Linear(neural_num, neural_num),nn.ReLU(inplace=True),nn.Linear(neural_num, 1),)def forward(self, x):return self.linears(x)# 實例化兩個上面構(gòu)建的全連接網(wǎng)絡(luò),用于比較 net_normal = MLP(neural_num=n_hidden) net_weight_decay = MLP(neural_num=n_hidden)# ============================ step 3/5 優(yōu)化器 ============================ # net_normal 無正則化,net_weight_decay 有正則化,系數(shù)為1e-2 optim_normal = torch.optim.SGD(net_normal.parameters(), lr=lr_init, momentum=0.9) optim_wdecay = torch.optim.SGD(net_weight_decay.parameters(), lr=lr_init, momentum=0.9, weight_decay=1e-2)# ============================ step 4/5 損失函數(shù) ============================ loss_func = torch.nn.MSELoss()# ============================ step 5/5 迭代訓(xùn)練 ============================ writer = SummaryWriter(comment='_test_tensorboard', filename_suffix="12345678")for epoch in range(max_iter):# forwardpred_normal, pred_wdecay = net_normal(train_x), net_weight_decay(train_x)loss_normal, loss_wdecay = loss_func(pred_normal, train_y), loss_func(pred_wdecay, train_y)optim_normal.zero_grad()optim_wdecay.zero_grad()loss_normal.backward()loss_wdecay.backward()optim_normal.step()optim_wdecay.step()if (epoch+1) % disp_interval == 0:# 可視化for name, layer in net_normal.named_parameters():writer.add_histogram(name + '_grad_normal', layer.grad, epoch)writer.add_histogram(name + '_data_normal', layer, epoch)for name, layer in net_weight_decay.named_parameters():writer.add_histogram(name + '_grad_weight_decay', layer.grad, epoch)writer.add_histogram(name + '_data_weight_decay', layer, epoch)test_pred_normal, test_pred_wdecay = net_normal(test_x), net_weight_decay(test_x)# 繪圖plt.scatter(train_x.data.numpy(), train_y.data.numpy(), c='blue', s=50, alpha=0.3, label='train')plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='red', s=50, alpha=0.3, label='test')plt.plot(test_x.data.numpy(), test_pred_normal.data.numpy(), 'r-', lw=3, label='no weight decay')plt.plot(test_x.data.numpy(), test_pred_wdecay.data.numpy(), 'b--', lw=3, label='weight decay')plt.text(-0.25, -1.5, 'no weight decay loss={:.6f}'.format(loss_normal.item()), fontdict={'size': 15, 'color': 'red'})plt.text(-0.25, -2, 'weight decay loss={:.6f}'.format(loss_wdecay.item()), fontdict={'size': 15, 'color': 'red'})plt.ylim((-2.5, 2.5))plt.legend(loc='upper left')plt.title("Epoch: {}".format(epoch+1))plt.show()plt.close()最終的擬合效果:
紅色的線顯然已經(jīng)過擬合了,而藍(lán)色的線加了正則化項,比紅色的線的效果好一點,這就是L2正則化項緩解過擬合的一個舉例。
因為在代碼中使用了TensorBoard可視化每一網(wǎng)絡(luò)層的權(quán)重,可以看一下有無正則化兩種情況下權(quán)值分布的差異:
可以看出不加正則化時,權(quán)值是比較分散的,而且存在較大的值,加入正則化之后,權(quán)值的分布都集中在較小值的范圍,不存在較大的權(quán)值,這也是減輕過擬合的體現(xiàn)。
三、正則化之Dropout
Dropout的概念發(fā)揚光大于AlexNet之中,簡單來說就是對網(wǎng)絡(luò)中的每個神經(jīng)元進(jìn)行隨機失活。
- 隨機:有一個失活概率Dropout probability。
- 失活:該神經(jīng)元對應(yīng)的權(quán)值為0,即該神經(jīng)元不與其他神經(jīng)元連接。
如下圖所示就是一次隨機失活的情況:
訓(xùn)練過程的每個epoch得到的失活后的網(wǎng)絡(luò)都不一樣,這樣使得模型具有多樣性,不會特別依賴某些固定的神經(jīng)元,不會使得某些神經(jīng)元的權(quán)重過大,緩解了過擬合的問題。
注意:Dropout是在訓(xùn)練過程進(jìn)行隨機失活,在測試的時候是恢復(fù)為原來的網(wǎng)絡(luò)結(jié)構(gòu)的,所以測試的時候,要將所有的權(quán)重乘以1-Dropout probability,以保持輸入與權(quán)值相乘的結(jié)果與訓(xùn)練時一樣大。
下面我們看一下在PyTorch中Dropout的實現(xiàn):
torch.nn.Dropout(p: float = 0.5, inplace: bool = False)參數(shù)如下:
主要就是一個失活概率p,默認(rèn)為0.5。
然后需要注意的是,PyTorch在實現(xiàn)Dropout的時候,訓(xùn)練時權(quán)重均乘以了1/(1-p),即除以1-p,這樣的話測試的時候就不用手動將所有的權(quán)重乘以1-Dropout probability了,方便了測試的過程。
用上面L2正則化的代碼示例稍加修改作為Dropout的舉例:
import torch import torch.nn as nn import matplotlib.pyplot as plt import sys, os from tools.common_tools import set_seed from torch.utils.tensorboard import SummaryWriterset_seed(1) # 設(shè)置隨機種子 n_hidden = 200 max_iter = 2000 disp_interval = 400 lr_init = 0.01# ============================ step 1/5 數(shù)據(jù) ============================ def gen_data(num_data=10, x_range=(-1, 1)):w = 1.5train_x = torch.linspace(*x_range, num_data).unsqueeze_(1)train_y = w*train_x + torch.normal(0, 0.5, size=train_x.size())test_x = torch.linspace(*x_range, num_data).unsqueeze_(1)test_y = w*test_x + torch.normal(0, 0.3, size=test_x.size())return train_x, train_y, test_x, test_ytrain_x, train_y, test_x, test_y = gen_data(x_range=(-1, 1))# ============================ step 2/5 模型 ============================ class MLP(nn.Module):def __init__(self, neural_num, d_prob=0.5):super(MLP, self).__init__()self.linears = nn.Sequential(nn.Linear(1, neural_num),nn.ReLU(inplace=True),nn.Dropout(d_prob),nn.Linear(neural_num, neural_num),nn.ReLU(inplace=True),nn.Dropout(d_prob),nn.Linear(neural_num, neural_num),nn.ReLU(inplace=True),nn.Dropout(d_prob),nn.Linear(neural_num, 1),)def forward(self, x):return self.linears(x)net_prob_0 = MLP(neural_num=n_hidden, d_prob=0.) net_prob_05 = MLP(neural_num=n_hidden, d_prob=0.5)# ============================ step 3/5 優(yōu)化器 ============================ optim_normal = torch.optim.SGD(net_prob_0.parameters(), lr=lr_init, momentum=0.9) optim_reglar = torch.optim.SGD(net_prob_05.parameters(), lr=lr_init, momentum=0.9)# ============================ step 4/5 損失函數(shù) ============================ loss_func = torch.nn.MSELoss()# ============================ step 5/5 迭代訓(xùn)練 ============================writer = SummaryWriter(comment='_test_tensorboard', filename_suffix="12345678") for epoch in range(max_iter):pred_normal, pred_wdecay = net_prob_0(train_x), net_prob_05(train_x)loss_normal, loss_wdecay = loss_func(pred_normal, train_y), loss_func(pred_wdecay, train_y)optim_normal.zero_grad()optim_reglar.zero_grad()loss_normal.backward()loss_wdecay.backward()optim_normal.step()optim_reglar.step()if (epoch+1) % disp_interval == 0:net_prob_0.eval()net_prob_05.eval()# 可視化for name, layer in net_prob_0.named_parameters():writer.add_histogram(name + '_grad_normal', layer.grad, epoch)writer.add_histogram(name + '_data_normal', layer, epoch)for name, layer in net_prob_05.named_parameters():writer.add_histogram(name + '_grad_regularization', layer.grad, epoch)writer.add_histogram(name + '_data_regularization', layer, epoch)test_pred_prob_0, test_pred_prob_05 = net_prob_0(test_x), net_prob_05(test_x)# 繪圖plt.clf()plt.scatter(train_x.data.numpy(), train_y.data.numpy(), c='blue', s=50, alpha=0.3, label='train')plt.scatter(test_x.data.numpy(), test_y.data.numpy(), c='red', s=50, alpha=0.3, label='test')plt.plot(test_x.data.numpy(), test_pred_prob_0.data.numpy(), 'r-', lw=3, label='d_prob_0')plt.plot(test_x.data.numpy(), test_pred_prob_05.data.numpy(), 'b--', lw=3, label='d_prob_05')plt.text(-0.25, -1.5, 'd_prob_0 loss={:.8f}'.format(loss_normal.item()), fontdict={'size': 15, 'color': 'red'})plt.text(-0.25, -2, 'd_prob_05 loss={:.6f}'.format(loss_wdecay.item()), fontdict={'size': 15, 'color': 'red'})plt.ylim((-2.5, 2.5))plt.legend(loc='upper left')plt.title("Epoch: {}".format(epoch+1))plt.show()plt.close()net_prob_0.train()net_prob_05.train()做的修改就是網(wǎng)絡(luò)模型里加入了Dropout層,刪去了L2正則化項,然后實例化了兩個模型,一個失活概率為0,即等效為不加Dropout層,一個失活概率為0.5,即加入Dropout層,將這兩個模型進(jìn)行數(shù)據(jù)擬合,觀察結(jié)果如下:
也可以看出,Dropout層的加入也一定程度上緩解了過擬合。
同樣,我們也來看一下這時的權(quán)值分布,因為第一層是輸入層,沒有加Dropout層,所以我們看第三層的權(quán)值分布(第二層為ReLU層):
也有類似的作用——收縮權(quán)重。
補充:
在使用Dropout層,以及后面要講的BN層這些東西時,還有一個需要注意的小細(xì)節(jié),因為這些層在訓(xùn)練模式和測試模式是有差別的,所以在不同時刻需要切換模式,用到的就是上面代碼中的
net_prob_0.eval() net_prob_05.eval()以及:
net_prob_0.train() net_prob_05.train()總結(jié)
以上是生活随笔為你收集整理的PyTorch框架学习十六——正则化与Dropout的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实数序列频谱的共轭对称性(DFT与IDF
- 下一篇: JavaSE——数组基础(创建格式、下标