1. 程式人生 > >深度學習經典網路回顧:AlexNet+VGG

深度學習經典網路回顧:AlexNet+VGG

一.AlexNet:

雖然深度學習元年是2006年,但是真正使深度學習火起來的是2012年的ImageNet上的AlexNet,由於本人本科畢設有用到該網路模型,雖然後面更優秀的網路模型層出不窮本人再沒有使用過該網路,但是該網路的設計對後序網路的設計還是又不可磨滅的影響。首先該網路出自論文:《ImageNet Classification with Deep ConvolutionalNeural Networks》。其中主要貢獻有如下幾點:

1.從實踐中證明了深度神經網路的可行性,確立了多GPU+深度神經網路解決大規模非結構化資料(CV、NLP、語音)問題的工作正規化,它得益於現代硬體尤其是GPU的成熟發展,卻也反過來激勵與AI相關硬體的更高速的發展。

2.啟用函式單元創新:引入了ReLU非線性啟用函式,對tanh和sigmoid會引起的梯度消失問題有一個很好的解決。

3.引入了Dropout,對過擬合問題有一個很好的解決。

4.引入了正則化層:區域性響應正則化(LRN),不過後來被證明並不很work,被各大網路捨棄。btw,後面BN層橫空出世。

下面從上述幾點分別闡述:

1.網路結構如圖所示:

更直觀的觀察:

為什麼第一層有96個卷積核,論文原圖上畫了兩個48,後面幾層也是這樣??因為當時是在兩個gpu上跑的。

2.ReLU啟用函式:

長這樣:簡單點就是max(0,x)

優點如下:

(1).不會像sigmoid一樣梯度消失。

(2).計算很快。

(3).收斂迅速,比sigmoid/tanh快6倍。

(4).更合理的生物性解釋。

簡單點為什麼sigmoid會梯度消失它不會?ReLU不會梯度消失直觀看來梯度為常數,所以當然不會啦。那sigmoid為什麼會:因為sigmoid的導數f'(x)=f(x)(1-f(x))值域為(0,1/4),在網路層數過深時經反向穿鼻即(1/4)^n約等於0,因此肯定會梯度消失啦。

3.Dropout:

原理為:Dropout以p的概率將隱層神經元的輸出置為0,這些神經元不參與前向傳播與反向傳播。這樣可以減少神經元之間的依賴性,因為從生物的角度上來說,一個神經元不會依賴於其他神經元而存在。藉助Dropout,網路可以學到更具有泛化能力的特徵。測試時,將所有神經元的輸出乘0.5來產生對Dropout的近似,該模型將Dropout機制用於全連線層中。

個人理解:該機制多用於全連線網路,可以將Dropout看作是整合學習的一種,本質解決的是過擬合問題。

溫馨提示:實踐中多采用“Inverted dropout”,即訓練時輸出處以概率值p,測試時不變。

4.LRN區域性相應歸一化,作為被歷史捨棄的產物不想再贅述,後來被batch-normal、instance-norm、group-nrom拍死在沙灘上。

最後貼一下pytorch官方實現的alexNet程式碼(看起來很簡單,捨棄掉了LRN層):

class AlexNet(nn.Module):

    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), 256 * 6 * 6)
        x = self.classifier(x)
        return x

二:VGG:

VGG的思想可以精簡為:Small filter,Deeper networks。個人覺得它主要貢獻為確立了卷積塊單元(block)設計的正規化,即網路設計結構的規範化。因為VGG網路看起來就是一個非常結構規範的網路:同一個block內特徵圖不變,相鄰的block之間通過stride為2的max-pooling操作使特徵圖尺寸減半,其最大的亮點為全網路的卷積核都是3*3的卷積,stride為1,padding為1。網路結構如下圖所示:conv3-64表示3*3的卷積核,channel數為64,圖中省去了ReLU單元,btw當時batch-norm還沒有問世,在batch-norm出現後又出現了好多vgg加bn的版本,其效果要好過原vgg結構。。

為什麼全是3*3的卷積核可以work?

從感受野的角度上說,理論上兩個3*3的卷積核堆疊在一起的感受野等同於一個5*5的卷積核,且使用更少的引數量(3*3*2<5*5),同時兩層比一層非線性抽象能力更強,擬合能力更強。

最後上程式碼:

class VGG(nn.Module):

    def __init__(self, features, num_classes=1000, init_weights=True):
        super(VGG, self).__init__()
        self.features = features
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, num_classes),
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
                if m.bias is not None:
                    m.bias.data.zero_()
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                m.weight.data.normal_(0, 0.01)
                m.bias.data.zero_()


def make_layers(cfg, batch_norm=False):
    layers = []
    in_channels = 3
    for v in cfg:
        if v == 'M':
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            if batch_norm:
                layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
            else:
                layers += [conv2d, nn.ReLU(inplace=True)]
            in_channels = v
    return nn.Sequential(*layers)


cfg = {
    'A': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'B': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'D': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'E': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}

以上即兩個網路的基本結構。

下面來說下對網路的思考和麵試中可能會遇到的與本文相關問題:

首先幾個面試中的問題:

1.ReLU相比sigmoid的優點,sigmoid為什麼會梯度消失?答案如上文所說。

2.深度學習中常用的防止過擬合的手段?

加正則、dropout、資料增強、batchnorm、early stopping、模型整合、加噪聲。。

當然以上屬於常規操作。面試中也碰到一些讓我印象深刻的深層次問題:

春招騰訊實習面試二面:

問:深度學習網路演化規律?答:越來越深,卷積核越來越小。

問:還記得AlexNet的網路結構嗎?第一層卷積核多大?

答:9*9。

問:為什麼後面卷積核越來越小?

答:按上述感受野和引數量的角度說了下。

問:那以前人們不知道這個道理嗎?為什麼以前的卷積核設計的那麼大?

我:喵喵喵?內心OS:可能以前人們真不知道。憋了半天不會。

面試官說了答案:之前的影象資料清晰度不高,資訊量不豐富,比較稀疏。適用於大卷積核,小卷積核反而效果不阿紅,現在影象都是高清,資訊量大。

我:好吧,這也可以,你贏了。

當時二面被懟的懷疑人生回去心情沮喪,不過最後竟然給過了也是amazing,後來順利拿到了實習的offer,還是很開心。

秋招面陌陌時碰到的問題:

1.pooling的反向傳播時候是怎麼實現的?

2.caffe和tensorflow中的dropout的實現方法不一樣,分別是怎麼實現的?

喵喵喵?不會不好意思。。後來知道caffe產生一個貝努力分佈的vector,用輸出乘以這個vector。

還有面試中我記得好幾次被問到對batchnorm的理解,可見batchnorm很重要。

對網路結構的思考:之前的網路最後都會連幾層很寬的全連線層eg:4096。這樣做的不好的地方?(全連線層佔了大多數的引數量,造成引數冗餘)。ResNet和googleNet捨棄了全連線層,這樣做的好處?(輸入影象任意尺寸)。

加BN和不加BN對網路輸入的要求有何不同?

不加BN層輸入影象必須進行歸一化,加BN之後可以不進行歸一化。

最後說一句:實踐出真知,注意細節。Devils in the details。