5.6 深度卷積神經(jīng)網(wǎng)絡(luò)(AlexNet)
在LeNet提出后的將近20年里,神經(jīng)網(wǎng)絡(luò)一度被其他機(jī)器學(xué)習(xí)方法超越,如支持向量機(jī)。雖然LeNet可以在早期的小數(shù)據(jù)集上取得好的成績,但是在更大的真實(shí)數(shù)據(jù)集上的表現(xiàn)并不盡如人意。一方面,神經(jīng)網(wǎng)絡(luò)計(jì)算復(fù)雜。雖然20世紀(jì)90年代也有過一些針對神經(jīng)網(wǎng)絡(luò)的加速硬件,但并沒有像之后GPU那樣大量普及。因此,訓(xùn)練一個(gè)多通道、多層和有大量參數(shù)的卷積神經(jīng)網(wǎng)絡(luò)在當(dāng)年很難完成。另一方面,當(dāng)年研究者還沒有大量深入研究參數(shù)初始化和非凸優(yōu)化算法等諸多領(lǐng)域,導(dǎo)致復(fù)雜的神經(jīng)網(wǎng)絡(luò)的訓(xùn)練通常較困難。
我們在上一節(jié)看到,神經(jīng)網(wǎng)絡(luò)可以直接基于圖像的原始像素進(jìn)行分類。這種稱為端到端(end-to-end)的方法節(jié)省了很多中間步驟。然而,在很長一段時(shí)間里更流行的是研究者通過勤勞與智慧所設(shè)計(jì)并生成的手工特征。這類圖像分類研究的主要流程是:
- 獲取圖像數(shù)據(jù)集;
- 使用已有的特征提取函數(shù)生成圖像的特征;
- 使用機(jī)器學(xué)習(xí)模型對圖像的特征分類。
當(dāng)時(shí)認(rèn)為的機(jī)器學(xué)習(xí)部分僅限最后這一步。如果那時(shí)候跟機(jī)器學(xué)習(xí)研究者交談,他們會(huì)認(rèn)為機(jī)器學(xué)習(xí)既重要又優(yōu)美。優(yōu)雅的定理證明了許多分類器的性質(zhì)。機(jī)器學(xué)習(xí)領(lǐng)域生機(jī)勃勃、嚴(yán)謹(jǐn)而且極其有用。然而,如果跟計(jì)算機(jī)視覺研究者交談,則是另外一幅景象。他們會(huì)告訴你圖像識(shí)別里“不可告人”的現(xiàn)實(shí)是:計(jì)算機(jī)視覺流程中真正重要的是數(shù)據(jù)和特征。也就是說,使用較干凈的數(shù)據(jù)集和較有效的特征甚至比機(jī)器學(xué)習(xí)模型的選擇對圖像分類結(jié)果的影響更大。
5.6.1 學(xué)習(xí)特征表示
既然特征如此重要,它該如何表示呢?
我們已經(jīng)提到,在相當(dāng)長的時(shí)間里,特征都是基于各式各樣手工設(shè)計(jì)的函數(shù)從數(shù)據(jù)中提取的。事實(shí)上,不少研究者通過提出新的特征提取函數(shù)不斷改進(jìn)圖像分類結(jié)果。這一度為計(jì)算機(jī)視覺的發(fā)展做出了重要貢獻(xiàn)。
然而,另一些研究者則持異議。他們認(rèn)為特征本身也應(yīng)該由學(xué)習(xí)得來。他們還相信,為了表征足夠復(fù)雜的輸入,特征本身應(yīng)該分級表示。持這一想法的研究者相信,多層神經(jīng)網(wǎng)絡(luò)可能可以學(xué)得數(shù)據(jù)的多級表征,并逐級表示越來越抽象的概念或模式。以圖像分類為例,并回憶5.1節(jié)(二維卷積層)中物體邊緣檢測的例子。在多層神經(jīng)網(wǎng)絡(luò)中,圖像的第一級的表示可以是在特定的位置和?度是否出現(xiàn)邊緣;而第二級的表示說不定能夠?qū)⑦@些邊緣組合出有趣的模式,如花紋;在第三級的表示中,也許上一級的花紋能進(jìn)一步匯合成對應(yīng)物體特定部位的模式。這樣逐級表示下去,最終,模型能夠較容易根據(jù)最后一級的表示完成分類任務(wù)。需要強(qiáng)調(diào)的是,輸入的逐級表示由多層模型中的參數(shù)決定,而這些參數(shù)都是學(xué)出來的。
盡管一直有一群執(zhí)著的研究者不斷鉆研,試圖學(xué)習(xí)視覺數(shù)據(jù)的逐級表征,然而很長一段時(shí)間里這些野心都未能實(shí)現(xiàn)。這其中有諸多因素值得我們一一分析。
5.6.1.1 缺失要素一:數(shù)據(jù)
包含許多特征的深度模型需要大量的有標(biāo)簽的數(shù)據(jù)才能表現(xiàn)得比其他經(jīng)典方法更好。限于早期計(jì)算機(jī)有限的存儲(chǔ)和90年代有限的研究預(yù)算,大部分研究只基于小的公開數(shù)據(jù)集。例如,不少研究論文基于加州大學(xué)歐文分校(UCI)提供的若干個(gè)公開數(shù)據(jù)集,其中許多數(shù)據(jù)集只有幾百至幾千張圖像。這一狀況在2010年前后興起的大數(shù)據(jù)浪潮中得到改善。特別是,2009年誕生的ImageNet數(shù)據(jù)集包含了1,000大類物體,每類有多達(dá)數(shù)千張不同的圖像。這一規(guī)模是當(dāng)時(shí)其他公開數(shù)據(jù)集無法與之相提并論的。ImageNet數(shù)據(jù)集同時(shí)推動(dòng)計(jì)算機(jī)視覺和機(jī)器學(xué)習(xí)研究進(jìn)入新的階段,使此前的傳統(tǒng)方法不再有優(yōu)勢。
5.6.1.2 缺失要素二:硬件
深度學(xué)習(xí)對計(jì)算資源要求很高。早期的硬件計(jì)算能力有限,這使訓(xùn)練較復(fù)雜的神經(jīng)網(wǎng)絡(luò)變得很困難。然而,通用GPU的到來改變了這一格局。很久以來,GPU都是為圖像處理和計(jì)算機(jī)游戲設(shè)計(jì)的,尤其是針對大吞吐量的矩陣和向量乘法從而服務(wù)于基本的圖形變換。值得慶幸的是,這其中的數(shù)學(xué)表達(dá)與深度網(wǎng)絡(luò)中的卷積層的表達(dá)類似。通用GPU這個(gè)概念在2001年開始興起,涌現(xiàn)出諸如OpenCL和CUDA之類的編程框架。這使得GPU也在2010年前后開始被機(jī)器學(xué)習(xí)社區(qū)使用。
5.6.2 AlexNet
2012年,AlexNet橫空出世。這個(gè)模型的名字來源于論文第一作者的姓名Alex Krizhevsky [1]。AlexNet使用了8層卷積神經(jīng)網(wǎng)絡(luò),并以很大的優(yōu)勢贏得了ImageNet 2012圖像識(shí)別挑戰(zhàn)賽。它首次證明了學(xué)習(xí)到的特征可以超越手工設(shè)計(jì)的特征,從而一舉打破計(jì)算機(jī)視覺研究的前狀。

AlexNet網(wǎng)絡(luò)結(jié)構(gòu)
AlexNet與LeNet的設(shè)計(jì)理念非常相似,但也有顯著的區(qū)別。
第一,與相對較小的LeNet相比,AlexNet包含8層變換,其中有5層卷積和2層全連接隱藏層,以及1個(gè)全連接輸出層。下面我們來詳細(xì)描述這些層的設(shè)計(jì)。
AlexNet第一層中的卷積窗口形狀是11×11。因?yàn)镮mageNet中絕大多數(shù)圖像的高和寬均比MNIST圖像的高和寬大10倍以上,ImageNet圖像的物體占用更多的像素,所以需要更大的卷積窗口來捕獲物體。第二層中的卷積窗口形狀減小到5×5,之后全采用3×3。此外,第一、第二和第五個(gè)卷積層之后都使用了窗口形狀為3×33×3、步幅為2的最大池化層。而且,AlexNet使用的卷積通道數(shù)也大于LeNet中的卷積通道數(shù)數(shù)十倍。
緊接著最后一個(gè)卷積層的是兩個(gè)輸出個(gè)數(shù)為4096的全連接層。這兩個(gè)巨大的全連接層帶來將近1 GB的模型參數(shù)。由于早期顯存的限制,最早的AlexNet使用雙數(shù)據(jù)流的設(shè)計(jì)使一個(gè)GPU只需要處理一半模型。幸運(yùn)的是,顯存在過去幾年得到了長足的發(fā)展,因此通常我們不再需要這樣的特別設(shè)計(jì)了。
第二,AlexNet將sigmoid激活函數(shù)改成了更加簡單的ReLU激活函數(shù)。一方面,ReLU激活函數(shù)的計(jì)算更簡單,例如它并沒有sigmoid激活函數(shù)中的求冪運(yùn)算。另一方面,ReLU激活函數(shù)在不同的參數(shù)初始化方法下使模型更容易訓(xùn)練。這是由于當(dāng)sigmoid激活函數(shù)輸出極接近0或1時(shí),這些區(qū)域的梯度幾乎為0,從而造成反向傳播無法繼續(xù)更新部分模型參數(shù);而ReLU激活函數(shù)在正區(qū)間的梯度恒為1。因此,若模型參數(shù)初始化不當(dāng),sigmoid函數(shù)可能在正區(qū)間得到幾乎為0的梯度,從而令模型無法得到有效訓(xùn)練。
第三,AlexNet通過丟棄法(參見3.13節(jié))來控制全連接層的模型復(fù)雜度。而LeNet并沒有使用丟棄法。
第四,AlexNet引入了大量的圖像增廣,如翻轉(zhuǎn)、裁剪和顏色變化,從而進(jìn)一步擴(kuò)大數(shù)據(jù)集來緩解過擬合。我們將在后面的9.1節(jié)(圖像增廣)詳細(xì)介紹這種方法。
下面我們實(shí)現(xiàn)稍微簡化過的AlexNet。
import time
import torch
from torch import nn, optim
import torchvision
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 減小卷積窗口,使用填充為2來使得輸入與輸出的高和寬一致,且增大輸出通道數(shù)
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 連續(xù)3個(gè)卷積層,且使用更小的卷積窗口。除了最后的卷積層外,進(jìn)一步增大了輸出通道數(shù)。
# 前兩個(gè)卷積層后不使用池化層來減小輸入的高和寬
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 這里全連接層的輸出個(gè)數(shù)比LeNet中的大數(shù)倍。使用丟棄層來緩解過擬合
self.fc = nn.Sequential(
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 輸出層。由于這里使用Fashion-MNIST,所以用類別數(shù)為10,而非論文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
打印看看網(wǎng)絡(luò)結(jié)構(gòu)。
net = AlexNet()
print(net)
輸出:
AlexNet(
(conv): Sequential(
(0): Conv2d(1, 96, kernel_size=(11, 11), stride=(4, 4))
(1): ReLU()
(2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(96, 256, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(4): ReLU()
(5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(7): ReLU()
(8): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(9): ReLU()
(10): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU()
(12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(fc): Sequential(
(0): Linear(in_features=6400, out_features=4096, bias=True)
(1): ReLU()
(2): Dropout(p=0.5)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU()
(5): Dropout(p=0.5)
(6): Linear(in_features=4096, out_features=10, bias=True)
)
)
5.6.3 讀取數(shù)據(jù)
雖然論文中AlexNet使用ImageNet數(shù)據(jù)集,但因?yàn)镮mageNet數(shù)據(jù)集訓(xùn)練時(shí)間較長,我們?nèi)杂们懊娴腇ashion-MNIST數(shù)據(jù)集來演示AlexNet。讀取數(shù)據(jù)的時(shí)候我們額外做了一步將圖像高和寬擴(kuò)大到AlexNet使用的圖像高和寬224。這個(gè)可以通過torchvision.transforms.Resize實(shí)例來實(shí)現(xiàn)。也就是說,我們在ToTensor實(shí)例前使用Resize實(shí)例,然后使用Compose實(shí)例來將這兩個(gè)變換串聯(lián)以方便調(diào)用。
# 本函數(shù)已保存在d2lzh_pytorch包中方便以后使用
def load_data_fashion_mnist(batch_size, resize=None, root='~/Datasets/FashionMNIST'):
"""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=4)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)
return train_iter, test_iter
batch_size = 128
# 如出現(xiàn)“out of memory”的報(bào)錯(cuò)信息,可減小batch_size或resize
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
5.6.4 訓(xùn)練
這時(shí)候我們可以開始訓(xùn)練AlexNet了。相對于LeNet,由于圖片尺寸變大了而且模型變大了,所以需要更大的顯存,也需要更長的訓(xùn)練時(shí)間了。
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)
輸出:
training on cuda
epoch 1, loss 0.0047, train acc 0.770, test acc 0.865, time 128.3 sec
epoch 2, loss 0.0025, train acc 0.879, test acc 0.889, time 128.8 sec
epoch 3, loss 0.0022, train acc 0.898, test acc 0.901, time 130.4 sec
epoch 4, loss 0.0019, train acc 0.908, test acc 0.900, time 131.4 sec
epoch 5, loss 0.0018, train acc 0.913, test acc 0.902, time 129.9 sec
小結(jié)
- AlexNet跟LeNet結(jié)構(gòu)類似,但使用了更多的卷積層和更大的參數(shù)空間來擬合大規(guī)模數(shù)據(jù)集ImageNet。它是淺層神經(jīng)網(wǎng)絡(luò)和深度神經(jīng)網(wǎng)絡(luò)的分界線。
- 雖然看上去AlexNet的實(shí)現(xiàn)比LeNet的實(shí)現(xiàn)也就多了幾行代碼而已,但這個(gè)觀念上的轉(zhuǎn)變和真正優(yōu)秀實(shí)驗(yàn)結(jié)果的產(chǎn)生令學(xué)術(shù)界付出了很多年。
參考文獻(xiàn)
[1] Krizhevsky, A., Sutskever, I., & Hinton, G. E. (2012). Imagenet classification with deep convolutional neural networks. In Advances in neural information processing systems (pp. 1097-1105).
注:除代碼外本節(jié)與原書此節(jié)基本相同,原書傳送門