動(dòng)手學(xué)深度學(xué)習(xí)PyTorch版-Task06/07/08

深層次網(wǎng)絡(luò)訓(xùn)練瓶頸:梯度消失乾颁,網(wǎng)絡(luò)退化

深度卷積網(wǎng)絡(luò)自然的整合了低中高不同層次的特征棚品,特征的層次可以靠加深網(wǎng)絡(luò)的層次來豐富。從而杭隙,在構(gòu)建卷積網(wǎng)絡(luò)時(shí)哟绊,網(wǎng)絡(luò)的深度越高,可抽取的特征層次就越豐富痰憎。所以一般我們會(huì)傾向于使用更深層次的網(wǎng)絡(luò)結(jié)構(gòu)票髓,以便取得更高層次的特征。但是在使用深層次的網(wǎng)絡(luò)結(jié)構(gòu)時(shí)我們會(huì)遇到兩個(gè)問題铣耘,梯度消失洽沟,梯度爆炸問題和網(wǎng)絡(luò)退化的問題。
但是當(dāng)使用更深層的網(wǎng)絡(luò)時(shí)蜗细,會(huì)發(fā)生梯度消失裆操、爆炸問題怒详,這個(gè)問題很大程度通過標(biāo)準(zhǔn)的初始化和正則化層來基本解決,這樣可以確保幾十層的網(wǎng)絡(luò)能夠收斂踪区,但是隨著網(wǎng)絡(luò)層數(shù)的增加昆烁,梯度消失或者爆炸的問題仍然存在。同時(shí)還存在著網(wǎng)絡(luò)退化問題如(圖1)缎岗,在神經(jīng)網(wǎng)絡(luò)可以收斂的前提下静尼,隨著網(wǎng)絡(luò)深度增加,網(wǎng)絡(luò)的表現(xiàn)先是逐漸增加至飽和传泊,然后迅速下降鼠渺。網(wǎng)絡(luò)退化問題不是過擬合導(dǎo)致的,即便在模型訓(xùn)練過程中或渤,同樣的訓(xùn)練輪次下系冗,退化的網(wǎng)絡(luò)也比稍淺層的網(wǎng)絡(luò)的訓(xùn)練錯(cuò)誤更高。
這時(shí)候提出了ResNet薪鹦,很大程度上解決了當(dāng)今深度網(wǎng)絡(luò)頭疼的網(wǎng)絡(luò)退化問題和梯度消失問題掌敬。

圖1模型退化:深層模型反而取得更低的訓(xùn)練和測試誤差
參考1

批量歸一化(BatchNormalization)

對(duì)輸入的標(biāo)準(zhǔn)化(淺層模型)

處理后的任意一個(gè)特征在數(shù)據(jù)集中所有樣本上的均值為0、標(biāo)準(zhǔn)差為1池磁。
標(biāo)準(zhǔn)化處理輸入數(shù)據(jù)使各個(gè)特征的分布相近

批量歸一化(深度模型)

利用小批量上的均值和標(biāo)準(zhǔn)差奔害,不斷調(diào)整神經(jīng)網(wǎng)絡(luò)中間輸出,從而使整個(gè)神經(jīng)網(wǎng)絡(luò)在各層的中間輸出的數(shù)值更穩(wěn)定地熄。

1.對(duì)全連接層做批量歸一化

位置:全連接層中的仿射變換和激活函數(shù)之間华临。
全連接:
\boldsymbol{x} = \boldsymbol{W\boldsymbol{u} + \boldsymbol} \\ output =\phi(\boldsymbol{x})

批量歸一化:
output=\phi(\text{BN}(\boldsymbol{x}))

\boldsymbol{y}^{(i)} = \text{BN}(\boldsymbol{x}^{(i)})

\boldsymbol{\mu}_\mathcal{B} \leftarrow \frac{1}{m}\sum_{i = 1}^{m} \boldsymbol{x}^{(i)},
\boldsymbol{\sigma}_\mathcal{B}^2 \leftarrow \frac{1}{m} \sum_{i=1}^{m}(\boldsymbol{x}^{(i)} - \boldsymbol{\mu}_\mathcal{B})^2,

\hat{\boldsymbol{x}}^{(i)} \leftarrow \frac{\boldsymbol{x}^{(i)} - \boldsymbol{\mu}_\mathcal{B}}{\sqrt{\boldsymbol{\sigma}_\mathcal{B}^2 + \epsilon}},

這?? > 0是個(gè)很小的常數(shù)端考,保證分母大于0

{\boldsymbol{y}}^{(i)} \leftarrow \boldsymbol{\gamma} \odot \hat{\boldsymbol{x}}^{(i)} + \boldsymbol{\beta}.

引入可學(xué)習(xí)參數(shù):拉伸參數(shù)γ和偏移參數(shù)β雅潭。若\boldsymbol{\gamma} = \sqrt{\boldsymbol{\sigma}_\mathcal{B}^2 + \epsilon}\boldsymbol{\beta} = \boldsymbol{\mu}_\mathcal{B},批量歸一化無效却特。

2.對(duì)卷積層做批量歸?化

位置:卷積計(jì)算之后扶供、應(yīng)?激活函數(shù)之前。
如果卷積計(jì)算輸出多個(gè)通道裂明,我們需要對(duì)這些通道的輸出分別做批量歸一化椿浓,且每個(gè)通道都擁有獨(dú)立的拉伸和偏移參數(shù)。
計(jì)算:對(duì)單通道闽晦,batchsize=m,卷積計(jì)算輸出=pxq
對(duì)該通道中m×p×q個(gè)元素同時(shí)做批量歸一化,使用相同的均值和方差扳碍。

3.預(yù)測時(shí)的批量歸?化

訓(xùn)練:以batch為單位,對(duì)每個(gè)batch計(jì)算均值和方差。
預(yù)測:用移動(dòng)平均估算整個(gè)訓(xùn)練數(shù)據(jù)集的樣本均值和方差仙蛉。

從零實(shí)現(xiàn)

import time
import torch
from torch import nn, optim
import torch.nn.functional as F
import torchvision
import sys
sys.path.append("/home/kesci/input/") 
import d2lzh1981 as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

def batch_norm(is_training, X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 判斷當(dāng)前模式是訓(xùn)練模式還是預(yù)測模式
    if not is_training:
        # 如果是在預(yù)測模式下笋敞,直接使用傳入的移動(dòng)平均所得的均值和方差
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4) #在表達(dá)式條件為 false 的時(shí)候觸發(fā)異常,X.shape為二維(全連接層)或者四維(卷積層)
        if len(X.shape) == 2:
            # 使用全連接層的情況,計(jì)算特征維上的均值和方差
            mean = X.mean(dim=0) 
            var = ((X - mean) ** 2).mean(dim=0)
        else:
            # 使用二維卷積層的情況荠瘪,計(jì)算通道維上(axis=1)的均值和方差液样。這里我們需要保持
            # X的形狀以便后面可以做廣播運(yùn)算
            #X是三維的:(channel, height, width)
            # 假設(shè)輸入的形狀是(m, n, k):
                #-如果指定axis(dim)=0, 輸出的size就是(1, n, k)或者(n, k)
                #-如果指定axis(dim)=1, 輸出的size就是(m, 1, k)或者(m, k)
               #-如果指定axis(dim)=2, 輸出的size就是(m, n, 1)或者(m, n).
            mean = X.mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True) 
            # dim=0,2,3振亮,則最終的mean是除了dim=1的通道維數(shù)不為1,其它都為1.
            #對(duì)這些通道的輸出分別做批量歸一化鞭莽,且每個(gè)通道都擁有獨(dú)立的拉伸和偏移參數(shù)坊秸,并均為標(biāo)量 
            var = ((X - mean) ** 2).mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
        # 訓(xùn)練模式下用當(dāng)前的均值和方差做標(biāo)準(zhǔn)化
        X_hat = (X - mean) / torch.sqrt(var + eps)
        # 更新移動(dòng)平均的均值和方差
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
    Y = gamma * X_hat + beta  # 拉伸和偏移
    return Y, moving_mean, moving_var
class BatchNorm(nn.Module):
    def __init__(self, num_features, num_dims):
        super(BatchNorm, self).__init__()
        if num_dims == 2:
            shape = (1, num_features) #全連接層輸出神經(jīng)元
        else:
            shape = (1, num_features, 1, 1)  #通道數(shù)
        # 參與求梯度和迭代的拉伸和偏移參數(shù),分別初始化成0和1
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 不參與求梯度和迭代的變量澎怒,全在內(nèi)存上初始化成0
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.zeros(shape)

    def forward(self, X):
        # 如果X不在內(nèi)存上褒搔,將moving_mean和moving_var復(fù)制到X所在顯存上
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
        # 保存更新過的moving_mean和moving_var, Module實(shí)例的traning屬性默認(rèn)為true, 調(diào)用.eval()后設(shè)成false
        Y, self.moving_mean, self.moving_var = batch_norm(self.training, 
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y

基于LeNet的應(yīng)用

net = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            BatchNorm(6, num_dims=4),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            BatchNorm(16, num_dims=4),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            d2l.FlattenLayer(),
            nn.Linear(16*4*4, 120),
            BatchNorm(120, num_dims=2),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            BatchNorm(84, num_dims=2),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )
print(net)
#batch_size = 256  
##cpu要調(diào)小batchsize
batch_size=16

def load_data_fashion_mnist(batch_size, resize=None, root='/home/kesci/input/FashionMNIST2065'):
    """Download the fashion mnist dataset and then load into memory."""
    trans = []
    if resize:
        trans.append(torchvision.transforms.Resize(size=resize))
    trans.append(torchvision.transforms.ToTensor())
    
    transform = torchvision.transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
    mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)

    train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=2)
    test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=2)

    return train_iter, test_iter
train_iter, test_iter = load_data_fashion_mnist(batch_size)
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

簡潔實(shí)現(xiàn)

net = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.BatchNorm2d(6),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.BatchNorm2d(16),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            d2l.FlattenLayer(),
            nn.Linear(16*4*4, 120),
            nn.BatchNorm1d(120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.BatchNorm1d(84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

殘差網(wǎng)絡(luò)(ResNet)

深度學(xué)習(xí)的問題:深度CNN網(wǎng)絡(luò)達(dá)到一定深度后再一味地增加層數(shù)并不能帶來進(jìn)一步地分類性能提高,反而會(huì)招致網(wǎng)絡(luò)收斂變得更慢喷面,準(zhǔn)確率也變得更差星瘾。而按照常理,假設(shè)存在當(dāng)前的某個(gè)s層的網(wǎng)絡(luò) f 是當(dāng)前最優(yōu)的網(wǎng)絡(luò)惧辈,那么可以構(gòu)造一個(gè)更深的網(wǎng)絡(luò)琳状,其最后幾層僅是該網(wǎng)絡(luò)f第s層輸出的恒等映射(Identity Mapping),就可以取得與 f 一致的結(jié)果盒齿;假設(shè)s層還不是當(dāng)前的“最佳層數(shù)”念逞,那么就存在比s層更深的網(wǎng)絡(luò)可以取得更好的結(jié)果”呶蹋可以推出翎承,與淺層網(wǎng)絡(luò)相比,更深的網(wǎng)絡(luò)的表現(xiàn)不應(yīng)該更差符匾。由此得出的合理猜測應(yīng)該是多層非線性網(wǎng)絡(luò)無法逼近恒等映射網(wǎng)絡(luò)叨咖。[2]
Resnet通過在一個(gè)淺層網(wǎng)絡(luò)基礎(chǔ)上疊加y=x的層(稱identity mappings,恒等映射)啊胶,可以讓增加網(wǎng)絡(luò)的深度而不產(chǎn)生網(wǎng)路退化甸各。

殘差塊(Residual Block)

恒等映射:
左邊:f(x)=x
右邊:f(x)-x=0 (易于捕捉恒等映射的細(xì)微波動(dòng))

在殘差塊中,輸?可通過跨層的數(shù)據(jù)線路更快地向前傳播焰坪。
1x1的卷積層用來修改通道數(shù)痴晦,即進(jìn)行升維或者降維。[3]
(In Convolutional Nets, there is no such thing as “fully-connected layers”. There are only convolution layers with 1x1 convolution kernels and a full connection table)

class Residual(nn.Module):  # 本類已保存在d2lzh_pytorch包中方便以后使用
    #可以設(shè)定輸出通道數(shù)琳彩、是否使用額外的1x1卷積層來修改通道數(shù)以及卷積層的步幅。
    def __init__(self, in_channels, out_channels, use_1x1conv=False, stride=1):
        super(Residual, self).__init__()
        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, stride=stride)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)

    def forward(self, X):
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        return F.relu(Y + X)
blk = Residual(3, 3)
X = torch.rand((4, 3, 6, 6))
blk(X).shape # torch.Size([4, 3, 6, 6])
blk = Residual(3, 6, use_1x1conv=True, stride=2)
blk(X).shape # torch.Size([4, 6, 3, 3])

ResNet模型

卷積(64,7x7,3)
批量一體化
最大池化(3x3,2)

殘差塊x4 (通過步幅為2的殘差塊在每個(gè)模塊之間減小高和寬)

全局平均池化

全連接

net = nn.Sequential(
        nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
        nn.BatchNorm2d(64), 
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
def resnet_block(in_channels, out_channels, num_residuals, first_block=False):
    if first_block:
        assert in_channels == out_channels # 第一個(gè)模塊的通道數(shù)同輸入通道數(shù)一致
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(in_channels, out_channels, use_1x1conv=True, stride=2))
        else:
            blk.append(Residual(out_channels, out_channels))
    return nn.Sequential(*blk)

net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))
net.add_module("resnet_block2", resnet_block(64, 128, 2))
net.add_module("resnet_block3", resnet_block(128, 256, 2))
net.add_module("resnet_block4", resnet_block(256, 512, 2))
net.add_module("global_avg_pool", d2l.GlobalAvgPool2d()) # GlobalAvgPool2d的輸出: (Batch, 512, 1, 1)
net.add_module("fc", nn.Sequential(d2l.FlattenLayer(), nn.Linear(512, 10))) #全連接層輸出
X = torch.rand((1, 1, 224, 224))
for name, layer in net.named_children():
    X = layer(X)
    print(name, ' output shape:\t', X.shape)
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
參考

【1】. Deep Residual Learning for Image Recognition
【2】. https://zhuanlan.zhihu.com/p/80226180
【3】. https://iamaaditya.github.io/2016/03/one-by-one-convolution

梯度下降各算法比較

梯度下降優(yōu)化算法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末部凑,一起剝皮案震驚了整個(gè)濱河市露乏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涂邀,老刑警劉巖瘟仿,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異比勉,居然都是意外死亡劳较,警方通過查閱死者的電腦和手機(jī)驹止,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來观蜗,“玉大人臊恋,你說我怎么就攤上這事∧鼓恚” “怎么了抖仅?”我有些...
    開封第一講書人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長砖第。 經(jīng)常有香客問我撤卢,道長,這世上最難降的妖魔是什么梧兼? 我笑而不...
    開封第一講書人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任放吩,我火速辦了婚禮,結(jié)果婚禮上羽杰,老公的妹妹穿的比我還像新娘渡紫。我一直安慰自己,他們只是感情好忽洛,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開白布腻惠。 她就那樣靜靜地躺著,像睡著了一般欲虚。 火紅的嫁衣襯著肌膚如雪集灌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,166評(píng)論 1 308
  • 那天复哆,我揣著相機(jī)與錄音欣喧,去河邊找鬼。 笑死梯找,一個(gè)胖子當(dāng)著我的面吹牛唆阿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锈锤,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼驯鳖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了久免?” 一聲冷哼從身側(cè)響起浅辙,我...
    開封第一講書人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阎姥,沒想到半個(gè)月后记舆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呼巴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年泽腮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了御蒲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诊赊,死狀恐怖厚满,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情豪筝,我是刑警寧澤痰滋,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站续崖,受9級(jí)特大地震影響敲街,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜严望,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一多艇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧像吻,春花似錦峻黍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惭每,卻和暖如春骨饿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背台腥。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國打工宏赘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人黎侈。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓察署,卻偏偏與公主長得像,于是被迫代替她去往敵國和親峻汉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贴汪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容