以全連接層為基礎(chǔ)的深度神經(jīng)網(wǎng)絡(luò)DNN是整個(gè)深度學(xué)習(xí)的基石。要說(shuō)應(yīng)用最廣、影響最大的深度神經(jīng)網(wǎng)絡(luò),那就是卷積神經(jīng)網(wǎng)絡(luò)(Convolutional Neural Network,CNN)震庭。卷積神經(jīng)網(wǎng)絡(luò)雖然發(fā)布的時(shí)間較早,但直到2006年Hilton解決深度神經(jīng)網(wǎng)絡(luò)的訓(xùn)練問(wèn)題后才煥發(fā)生機(jī)你雌。卷積神經(jīng)網(wǎng)絡(luò)現(xiàn)在幾乎是圖像識(shí)別研究的標(biāo)配器联。
簡(jiǎn)單回顧卷積神經(jīng)網(wǎng)絡(luò)的發(fā)展歷程。日本科學(xué)家福島邦彥在1986年提出了Neocognitron(神經(jīng)認(rèn)知機(jī))婿崭,直接啟發(fā)了后來(lái)的卷積神經(jīng)網(wǎng)絡(luò)拨拓。Yann LeCun于1998年提出的卷積神經(jīng)網(wǎng)絡(luò)LeNet,首次使用了多層級(jí)聯(lián)的卷積結(jié)構(gòu)氓栈,可對(duì)手寫(xiě)數(shù)字進(jìn)行有效識(shí)別渣磷。2012年,Alex依靠卷積神經(jīng)網(wǎng)絡(luò)AlexNet奪得ILSVRC 2012比賽的冠軍授瘦,吹響了卷積神經(jīng)網(wǎng)絡(luò)研究的號(hào)角醋界。AlexNet成功應(yīng)用了ReLU、Dropout提完、最大池化形纺、LRN(Local Response Normalization,局部響應(yīng)歸一化)徒欣、GPU加速等新技術(shù)逐样,啟發(fā)了后續(xù)更多的技術(shù)創(chuàng)新,加速了卷積神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí)的研究打肝。從此脂新,深度學(xué)習(xí)研究進(jìn)入了蓬勃發(fā)展的新階段。2014年Google提出的GoogleNet粗梭,運(yùn)用Inception Module這個(gè)可以反復(fù)堆疊争便、高效的卷積神經(jīng)結(jié)構(gòu),獲得了當(dāng)年ImageNet ILSVRC比賽的冠軍楼吃,同年的亞軍VGGNet全程使用3x3的卷積始花,成功訓(xùn)練了深度達(dá)19層的網(wǎng)絡(luò)。2015年孩锡,微軟提出了ResNet酷宵,包含殘差學(xué)習(xí)模塊,成功訓(xùn)練了152層的網(wǎng)絡(luò)躬窜,一舉拿下當(dāng)年的ILSVRC比賽的冠軍浇垦。
卷積神經(jīng)網(wǎng)絡(luò)技術(shù)的發(fā)展風(fēng)起云涌,盡管卷積神經(jīng)網(wǎng)絡(luò)最初是為了解決計(jì)算機(jī)視覺(jué)等問(wèn)題設(shè)計(jì)的荣挨,現(xiàn)在其應(yīng)用范圍不僅僅局限于圖像和視頻領(lǐng)域男韧,也可用于時(shí)間序列信號(hào),比如音頻信號(hào)等默垄。本章主要通過(guò)卷積神經(jīng)網(wǎng)絡(luò)在計(jì)算機(jī)視覺(jué)上的應(yīng)用來(lái)介紹卷積神經(jīng)網(wǎng)絡(luò)的原理以及如何使用PyTorch實(shí)現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)此虑。
本章首先介紹人類(lèi)視覺(jué)和計(jì)算機(jī)視覺(jué)的基本原理,以及計(jì)算機(jī)視覺(jué)中特征的提取和選擇口锭;然后介紹卷積神經(jīng)網(wǎng)絡(luò)的主體思想和整體結(jié)構(gòu)朦前,并將詳細(xì)講解卷積層和池化層的網(wǎng)絡(luò)結(jié)構(gòu)、PyTorch對(duì)這些網(wǎng)絡(luò)的支持鹃操、如何設(shè)置每一層神經(jīng)網(wǎng)絡(luò)的配置韭寸,以及更加復(fù)雜的卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),如AlexNet荆隘、VGGNet恩伺、ResNet等;最后在MNIST數(shù)據(jù)集上通過(guò)PyTorch使用卷積神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)圖片分類(lèi)椰拒。
1.計(jì)算機(jī)視覺(jué)
1.人類(lèi)視覺(jué)和計(jì)算機(jī)視覺(jué)
視覺(jué)是人類(lèi)觀察和認(rèn)識(shí)世界非常重要的手段晶渠。據(jù)統(tǒng)計(jì),人類(lèi)從外部世界獲取的信息約80%是來(lái)自視覺(jué)耸三,既說(shuō)明視覺(jué)信息量巨大乱陡,同時(shí)又體現(xiàn)了視覺(jué)功能的重要性。同時(shí)仪壮,人類(lèi)視覺(jué)是如此的功能強(qiáng)大憨颠,在很短的時(shí)間里,迅速地辨識(shí)視線中的物體积锅,在人的視覺(jué)系統(tǒng)中爽彤,人的眼睛捕捉物體得到光信息。這些光信息經(jīng)過(guò)處理缚陷,運(yùn)送到大腦的視覺(jué)皮層适篙,分析得到以下信息:有關(guān)物體的空間、色彩箫爷、形狀和紋理等嚷节。有了這些信息聂儒,大腦做出對(duì)該物體的辨識(shí)。對(duì)于人類(lèi)而言硫痰,通過(guò)視覺(jué)來(lái)辨識(shí)數(shù)字衩婚、識(shí)別圖片中的物體或者找出圖片中人臉的輪廓是非常簡(jiǎn)單的事情。然而對(duì)于計(jì)算機(jī)而言效斑,讓計(jì)算機(jī)識(shí)別圖片中的內(nèi)容就不是一件容易的事情非春。計(jì)算機(jī)視覺(jué)希望借助計(jì)算機(jī)程序來(lái)處理、分析和理解圖片中的內(nèi)容缓屠,使得計(jì)算機(jī)可以從圖片中自動(dòng)識(shí)別各種不同模式的目標(biāo)和對(duì)象奇昙。
在深度學(xué)習(xí)出現(xiàn)之前,圖像識(shí)別的一般過(guò)程是敌完,前端為特征提取储耐,后端為模式識(shí)別算法。后端的模式識(shí)別算法包括K近鄰算法滨溉、支持向量機(jī)衡瓶、神經(jīng)網(wǎng)絡(luò)等晓避。對(duì)于不同的識(shí)別場(chǎng)景和越來(lái)越復(fù)雜的識(shí)別目標(biāo)宫补,尋找合適的前端特征顯得尤為重要植阴。
2.特征提取
對(duì)于特征提取,抽象于人的視覺(jué)原理勤家,提取有關(guān)輪廓腹尖、色彩、紋理伐脖、空間等相關(guān)的特征热幔。以色彩為例,它是一種現(xiàn)在仍然在廣泛使用的特征讼庇,稱為顏色直方圖特征绎巨,這是一種簡(jiǎn)單、直觀蠕啄、對(duì)實(shí)際圖片顏色進(jìn)行數(shù)字化表達(dá)的方式场勤。顏色的值用RGB三原色進(jìn)行表示,顏色直方圖的橫軸表示顏色的RGB值歼跟,表示該物品所有顏色的集合和媳,縱軸表示整個(gè)圖像具有每個(gè)顏色值像素的數(shù)量,這樣計(jì)算機(jī)可以對(duì)圖像進(jìn)行顏色表征哈街。
以紋理特征為例留瞳,橘子會(huì)有凸凹不平的紋理,而蘋(píng)果的紋理則非常光滑骚秦。這種局部的紋理刻畫(huà)她倘,如何通過(guò)特征抽象表示出來(lái)璧微?Gabor特征可以用來(lái)描述圖像紋理信息的特征,Gabor濾波器的頻率和方向與人類(lèi)的視覺(jué)系統(tǒng)類(lèi)似硬梁,特別適合于紋理表示與判別往毡。SIFT(Scale Invariant Feature Transform,尺寸不變特征變換)特征是一種檢測(cè)局部特征的算法靶溜,該算法通過(guò)把圖片中特征點(diǎn)用特征向量進(jìn)行描述,該特征向量具有對(duì)象縮放懒震、平移罩息、旋轉(zhuǎn)不變的特性,對(duì)于光照个扰、仿射和投影變換也有一定的不變性瓷炮。
形狀特征也是圖像特征的重要一類(lèi),HOG(Histogram of Oriented Gridients)特征就是其中的一種递宅。HOG是一種描述圖像局部梯度方向和梯度強(qiáng)度分布的特征娘香。其核心內(nèi)容是:在邊緣具體位置未知的情況下,邊緣方向的分布也可以很好地表示目標(biāo)的外形輪廓办龄。
上述特征提取算法提取的特征還是有局限的烘绽,盡管在顏色為黑白的數(shù)據(jù)集MNIST上的最好結(jié)果的錯(cuò)誤率為0.54%,但是在大型和復(fù)雜的數(shù)據(jù)ImageNet ILSVRC比賽的最好結(jié)果的錯(cuò)誤率也在26%以上俐填,而且難以突破安接。同時(shí),提取的特征只在特定的場(chǎng)合有效英融,場(chǎng)景變化后盏檐,需要重新提取特征和調(diào)整模型參數(shù)。卷積神經(jīng)網(wǎng)絡(luò)能夠自動(dòng)提取特征驶悟,不必人為地提取特征胡野,這樣提取的特征能夠達(dá)到更好的效果。同時(shí)痕鳍,它不需要將特征提取和分類(lèi)訓(xùn)練兩個(gè)過(guò)程分開(kāi)硫豆,在訓(xùn)練的過(guò)程中自動(dòng)提取特征、循環(huán)迭代笼呆、自動(dòng)選取最優(yōu)的特征够庙。
3.數(shù)據(jù)集
對(duì)于卷積神經(jīng)網(wǎng)絡(luò)的成功,計(jì)算機(jī)視覺(jué)領(lǐng)域的幾大數(shù)據(jù)集可謂功不可沒(méi)抄邀。在計(jì)算機(jī)視覺(jué)中有以下幾大基礎(chǔ)數(shù)據(jù)集耘眨。
(1)MNIST
MNIST數(shù)據(jù)集是用作手寫(xiě)體識(shí)別的數(shù)據(jù)集。MNIST數(shù)據(jù)集包括60000張訓(xùn)練圖片和10000張測(cè)試圖片境肾。如圖所示剔难。其中胆屿,每一張圖片都是0~9中的一個(gè)數(shù)字。圖片尺寸為28x28偶宫。由于數(shù)據(jù)集中數(shù)據(jù)相對(duì)比較簡(jiǎn)單非迹,人工標(biāo)注錯(cuò)誤率僅為0.2%。
(2)CIFAR
CIFAR數(shù)據(jù)集是一個(gè)圖像分類(lèi)數(shù)據(jù)集纯趋。CIFAR數(shù)據(jù)集分為Cifar-10和Cifar-100兩個(gè)數(shù)據(jù)集憎兽。CIFAR數(shù)據(jù)集中的圖片為32x32的彩色圖片,這些圖片是由Alex Krizhenevsky教授吵冒、Vinod Nair博士和Geoffrey Hilton教授整理的纯命。Cifar-10數(shù)據(jù)集收集了來(lái)自10個(gè)不同種類(lèi)的60000張圖片,這些種類(lèi)有飛機(jī)痹栖、汽車(chē)亿汞、鳥(niǎo)、貓揪阿、鹿疗我、狗、青蛙南捂、馬吴裤、船和卡車(chē)。在Cifar-10數(shù)據(jù)集上溺健,人工標(biāo)注的正確率是94%嚼摩。
(3)ImageNet
ImageNet數(shù)據(jù)集是一個(gè)大型圖片數(shù)據(jù)集,由斯坦福大學(xué)的李飛飛教授帶頭整理而成矿瘦。在ImageNet中枕面,近1500萬(wàn)張圖片關(guān)聯(lián)到WordNet中20000個(gè)名詞同義詞集上。ImageNet每年舉行計(jì)算機(jī)視覺(jué)相關(guān)的競(jìng)賽——Image Large Scala Visual Recognition Challenge缚去,即ILSVRC潮秘。ImageNet數(shù)據(jù)集涵蓋了計(jì)算機(jī)視覺(jué)的各個(gè)研究方向,其用作圖像分類(lèi)的數(shù)據(jù)集是ILSVRC2012圖像分類(lèi)數(shù)據(jù)集易结。ILSVRC2012數(shù)據(jù)集和Cifar-10數(shù)據(jù)集一樣枕荞,識(shí)別圖像中主要物體,其中包含了來(lái)自1000個(gè)種類(lèi)的120萬(wàn)張圖片搞动,每張圖片只屬于一個(gè)種類(lèi)躏精,大小從幾K到幾M不等。卷積神經(jīng)網(wǎng)絡(luò)在此數(shù)據(jù)集上一戰(zhàn)成名鹦肿。
2.卷積神經(jīng)網(wǎng)絡(luò)
計(jì)算機(jī)視覺(jué)作為人工智能的重要領(lǐng)域矗烛,在2006年后取得了很多突破性的進(jìn)展。本章介紹的卷積神經(jīng)網(wǎng)絡(luò)就是這些突破性進(jìn)展背后的技術(shù)基礎(chǔ)箩溃。在前面章節(jié)中介紹的神經(jīng)網(wǎng)絡(luò)每?jī)蓪拥乃泄?jié)點(diǎn)都是兩兩相連的瞭吃,所以稱這種網(wǎng)絡(luò)結(jié)構(gòu)為全連接層網(wǎng)絡(luò)結(jié)構(gòu)碌嘀。可將只包含全連接層的神經(jīng)網(wǎng)絡(luò)稱為全連接神經(jīng)網(wǎng)絡(luò)歪架。
卷積神經(jīng)網(wǎng)絡(luò)利用卷積結(jié)構(gòu)減少需要學(xué)習(xí)的參數(shù)量股冗,從而提高反向傳播算法的訓(xùn)練效率。如下圖所示和蚪,在卷積神經(jīng)網(wǎng)絡(luò)中止状,第一個(gè)卷積層會(huì)直接接受圖像像素級(jí)的輸入,每一個(gè)卷積操作只處理一小塊圖像攒霹,進(jìn)行卷積操作后傳遞給后面的網(wǎng)絡(luò)怯疤,每一層卷積都會(huì)提取數(shù)據(jù)中最有效的特征。這種方法可以提取到圖像中最基礎(chǔ)的特征剔蹋,比如不同方向的拐角或者邊,而后進(jìn)行組合和抽象成更高階的特征辅髓,因此卷積神經(jīng)網(wǎng)絡(luò)對(duì)圖像縮放泣崩、平移和旋轉(zhuǎn)具有不變性。
在圖像處理中洛口,圖像是一個(gè)或者多個(gè)二維矩陣矫付,如前面提到的MNIST手寫(xiě)體圖片是一個(gè)28x28的二維矩陣。傳統(tǒng)的神經(jīng)網(wǎng)絡(luò)都是采用全連接的方式第焰,即輸入層到隱含層的神經(jīng)元都是全連接的买优,這樣導(dǎo)致參數(shù)量巨大,使得網(wǎng)絡(luò)訓(xùn)練耗時(shí)甚至難以訓(xùn)練挺举,并且容易過(guò)擬合杀赢,而卷積神經(jīng)網(wǎng)絡(luò)則通過(guò)局部連接、權(quán)值共享等方法避免這一困難湘纵。如下圖所示脂崔。
對(duì)于一個(gè)200x200的輸入圖像而言,如果下一個(gè)隱含層的神經(jīng)元數(shù)目為10000個(gè)梧喷,采用全連接則有200x200
x10000=400000000個(gè)權(quán)值參數(shù)砌左,如此巨大的參數(shù)幾乎難以訓(xùn)練;而采用局部連接铺敌,隱含層的每個(gè)神經(jīng)元僅與圖像中4x4的局部圖像相連接汇歹,那么此時(shí)的權(quán)值參數(shù)個(gè)數(shù)為4x4x10000=160000,大大減少了參數(shù)的個(gè)數(shù)偿凭。
盡管大大減少了參數(shù)個(gè)數(shù)产弹,但是參數(shù)數(shù)量依然較多。能否再進(jìn)一步減少參數(shù)呢弯囊?方法就是權(quán)值共享取视。一個(gè)卷積層可以有多個(gè)不同的卷積核硝皂,而每一個(gè)卷積核都對(duì)應(yīng)一個(gè)濾波后映射出的新圖像,同一個(gè)新圖像中每一個(gè)像素都來(lái)自完全相同的卷積核作谭,就是卷積核的權(quán)值共享稽物。具體的做法是,在局部連接中隱含層的每一個(gè)神經(jīng)元連接的是一個(gè)4x4的局部圖像折欠,因此有4x4個(gè)權(quán)值參數(shù)贝或,將這4x4個(gè)權(quán)值參數(shù)共享給剩下的神經(jīng)元,也就是說(shuō)隱含層中4x10000個(gè)神經(jīng)元的權(quán)值參數(shù)相同锐秦,那么此時(shí)不管隱含層神經(jīng)元的數(shù)目是多少咪奖,需要訓(xùn)練的參數(shù)就是這4x4個(gè)權(quán)值參數(shù)(也就是卷積核的大小)酱床,如下圖所示羊赵。
這大概就是卷積神經(jīng)網(wǎng)絡(luò)的神奇之處,盡管只有這么少的參數(shù)扇谣,依舊有出色的性能昧捷。但是,這樣僅提取了圖像的一種特征罐寨,如果要多提取一些特征靡挥,可以增加多個(gè)卷積核,不同的卷積核能夠得到圖像的不同映射下的特征鸯绿,稱為特征映射跋破。如果有100個(gè)卷積核,最終的權(quán)值參數(shù)也僅為100x100=10000個(gè)而已瓶蝴。另外毒返,偏置參數(shù)也是共享的,同一種濾波器共享一個(gè)偏置參數(shù)舷手。
總結(jié)一下饿悬,卷積神經(jīng)網(wǎng)絡(luò)的要點(diǎn)就是卷積層中的局部連接、權(quán)值共享和池化層中下采樣聚霜。局部連接狡恬、權(quán)值共享和下采樣降低了參數(shù)量,使得訓(xùn)練復(fù)雜度大大降低蝎宇,并降低了過(guò)擬合的風(fēng)險(xiǎn)弟劲。同時(shí)還賦予了卷積神經(jīng)網(wǎng)絡(luò)對(duì)于平移、形變姥芥、縮放的某種程度的不變性兔乞,提高了模型的泛化能力。
一般的卷積神經(jīng)網(wǎng)絡(luò)由卷積層、池化層庸追、全連接層霍骄、Softmax層組成,這四者構(gòu)成了常見(jiàn)的卷積神經(jīng)網(wǎng)絡(luò)淡溯。
(1)卷積層读整。卷積層是卷積神經(jīng)網(wǎng)絡(luò)最重要的部分,也是卷積神經(jīng)網(wǎng)絡(luò)得名的緣由咱娶。卷積層中每一個(gè)節(jié)點(diǎn)的輸入是上一層神經(jīng)網(wǎng)絡(luò)中的一小塊米间,卷積層試圖將神經(jīng)網(wǎng)絡(luò)中的每一小塊進(jìn)行更加深入的分析,從而得到抽象程度更高的特征膘侮。
(2)池化層屈糊。池化層的神經(jīng)網(wǎng)絡(luò)不會(huì)改變?nèi)S矩陣的深度,但是它將縮小矩陣的大小琼了。池化層將分辨率較高的圖片轉(zhuǎn)換為分辨率較低的圖片逻锐。
(3)全連接層。經(jīng)過(guò)多輪的卷積層和池化層處理后雕薪,卷積神經(jīng)網(wǎng)絡(luò)一般會(huì)接1到2層全連接層來(lái)給出最后的分類(lèi)結(jié)果昧诱。
(4)Softmax層。Softmax層主要用于分類(lèi)問(wèn)題蹦哼。
1.卷積層
下圖顯示了卷積神經(jīng)網(wǎng)絡(luò)中最重要的部分鳄哭,可稱之為卷積核(kernel)或?yàn)V波器(filter)要糊。在PyTorch文檔中將這個(gè)結(jié)構(gòu)稱為卷積核纲熏,因此這里我們也統(tǒng)稱為卷積核。如圖所示锄俄,卷積核將當(dāng)前層神經(jīng)網(wǎng)絡(luò)上的一個(gè)子節(jié)點(diǎn)矩陣轉(zhuǎn)換為下一層神經(jīng)網(wǎng)絡(luò)上的一個(gè)節(jié)點(diǎn)矩陣局劲。
在卷積層中,卷積核所處理的節(jié)點(diǎn)矩陣的長(zhǎng)奶赠、寬都是人工指定的鱼填,這個(gè)節(jié)點(diǎn)矩陣的尺寸稱為卷積核的尺寸。卷積核處理的深度和當(dāng)前層神經(jīng)網(wǎng)絡(luò)節(jié)點(diǎn)矩陣的深度是一致的毅戈,即便節(jié)點(diǎn)矩陣是三維的苹丸,卷積核的尺寸只需要指定兩個(gè)維度。一般而言苇经,卷積核的尺寸是3x3和5x5赘理。如上圖中,左邊表示輸入的數(shù)據(jù)扇单,輸入的數(shù)據(jù)尺寸為3x32x32商模,中間表示卷積核,右邊每一個(gè)小圓點(diǎn)表示一個(gè)神經(jīng)元,圖中有5個(gè)神經(jīng)元施流。假設(shè)卷積核尺寸為5x5响疚,卷積層中每個(gè)神經(jīng)元會(huì)有輸入數(shù)據(jù)中3x5x5區(qū)域的權(quán)重,一共75個(gè)權(quán)重瞪醋。這里再次強(qiáng)調(diào)下卷積核的深度必須為3忿晕,和輸入數(shù)據(jù)保持一致。
在卷積層中趟章,還需要說(shuō)明神經(jīng)元的數(shù)量杏糙,以及它們的排列方式、滑動(dòng)步長(zhǎng)和邊界填充蚓土。
(1)卷積核的數(shù)量就是卷積層的輸出深度宏侍,如上圖所示的5個(gè)神經(jīng)元,該參數(shù)是用戶指定的蜀漆,和使用的卷積核數(shù)量一致谅河。
(2)卷積核計(jì)算運(yùn)算時(shí)必須指定滑動(dòng)步長(zhǎng)。比如步長(zhǎng)為1确丢,說(shuō)明卷積核每次移動(dòng)一個(gè)像素點(diǎn)绷耍;步長(zhǎng)為2,卷積核會(huì)滑動(dòng)2個(gè)像素點(diǎn)鲜侥」邮迹滑動(dòng)的操作使得輸出的數(shù)據(jù)變得更少。
(3)邊界填充如果為0描函,可以保證輸入和輸出在空間上尺寸一致崎苗;如果邊界填充大于0,可以確保在進(jìn)行卷積操作時(shí)不損失邊界信息舀寓。
那么胆数,輸出的尺寸最終如何計(jì)算呢?在PyTorch中互墓,可以用一個(gè)公式來(lái)計(jì)算必尼,就是floor((W-F+2P)/S+1)。其中floor表示向下取整操作篡撵,W表示輸入數(shù)據(jù)的大小判莉,F(xiàn)表示卷積層中卷積核的尺寸,S表示步長(zhǎng)育谬,P表示邊界填充0的數(shù)量券盅。比如輸入是5x5,卷積核是3x3斑司,步長(zhǎng)是1渗饮,填充的數(shù)量是0但汞,那么根據(jù)公式就能得到(5 - 3 + 2 x 0)/ 1 + 1 = 3,輸出的空間大小為3 x 3互站;如果步長(zhǎng)為2私蕾,那么(5 - 3 + 2 x 0)/ 2 + 1 = 2,輸出空間的大小為2 x 2胡桃。
以一維空間來(lái)說(shuō)明卷積操作踩叭,如下圖所示。其中翠胰,輸入數(shù)據(jù)大小為5容贝,卷積核的大小為3,填充為1之景,滑動(dòng)步長(zhǎng)分別為1和2的卷積操作結(jié)果如下:
在PyTorch中斤富,類(lèi)nn.Conv2d()是卷積核模塊。卷積核及其調(diào)用的例子如下:
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0,dilation=1,groups=1,bias=True)
# 方形卷積核和等長(zhǎng)的步長(zhǎng)
m = nn.Conv2d(16,33,3,stride=2)
# 非方形卷積核锻狗,非等長(zhǎng)的步長(zhǎng)和邊界填充
m = nn.Conv2d(16,33,(3,5),stride=(2,1),padding=(4,2))
# 非方形卷積核满力,非等長(zhǎng)的步長(zhǎng),邊界填充和空間間隔
m = nn.Conv2d(16,33,(3,5),stride=(2,1),padding=(4,2),dilation=(3,1))
input = autograd.Variable(torch.randn(20,16,50,100))
output = m(input)
在nn.Conv2d()中轻纪,in_channels表示輸入數(shù)據(jù)體的深度油额,out_channels表示輸出數(shù)據(jù)體的深度,kernel_size表示卷積核的大小刻帚,stride表示滑動(dòng)步長(zhǎng)潦嘶,padding表示0邊界填充個(gè)數(shù),dilation表示數(shù)據(jù)體的空間間隔崇众,groups表示輸入數(shù)據(jù)體和輸出數(shù)據(jù)體在深度上的關(guān)聯(lián)掂僵,bias表示偏置。
2.池化層
通常會(huì)在卷積層后面插入池化層校摩,其作用是逐漸減低網(wǎng)絡(luò)的空間尺寸看峻,達(dá)到減少網(wǎng)絡(luò)中參數(shù)的數(shù)量阶淘,減少計(jì)算資源使用的目的衙吩,同時(shí)也能有效控制過(guò)擬合。
池化層一般有兩種形式:Max Pooling和Mean Pooling溪窒。下面以Max Pooling來(lái)說(shuō)明池化層的具體內(nèi)容。池化層操作不改變模型的深度澈蚌,對(duì)輸入數(shù)據(jù)在深度上切片作為輸入摹芙,不斷地滑動(dòng)窗口,取這些窗口的最大值作為輸出結(jié)果宛瞄,減少它的空間尺寸浮禾。池化層的效果如圖所示。
下圖說(shuō)明了池化層的具體計(jì)算過(guò)程。以窗口大小為2盈电,滑動(dòng)步長(zhǎng)為2舉例:每次都是從2x2的窗口中選擇最大的數(shù)值蝴簇,同時(shí)每次滑動(dòng)2個(gè)步長(zhǎng)進(jìn)入新的窗口。
池化層為什么有效匆帚?圖片特征具有局部不變性熬词,也就是說(shuō),即便通過(guò)下采樣也不會(huì)丟失圖片擁有的特征吸重。由于這種特性互拾,可以將圖片縮小再進(jìn)行卷積處理,大大降低卷積計(jì)算的時(shí)間嚎幸。最常用的池化層尺寸是2x2颜矿,滑動(dòng)步長(zhǎng)是2,對(duì)圖像進(jìn)行下采樣嫉晶,將其中75%的信息丟棄或衡,選擇其中最大的保留下來(lái),這樣也能達(dá)到去除一些噪聲信息的目的车遂。
在PyTorch中封断,池化層包括nn.MaxPool2d和nn.AvgPool2d等。下面以nn.MaxPool2d為例進(jìn)行說(shuō)明:
nn.MaxPool2d(kernel_size,stride=None,padding=0,dilation=1,return_indices=False,ceil_mode=False)
# 方形窗口尺寸為3舶担,等長(zhǎng)滑動(dòng)步長(zhǎng)為2
m = nn.MaxPool2d(3,stride=2)
# 非方形窗口坡疼,非等長(zhǎng)滑動(dòng)步長(zhǎng)
m = nn.MaxPool2d((3,2),stride=(2,1))
input = autograd.Variable(torch.randn(20,16,50,32))
output = m(input)
在nn.MaxPool2d中,kernel_size衣陶,stride柄瑰,padding,dilation參數(shù)在nn.Conv2d中已經(jīng)解釋過(guò)剪况,return_indices表示是否返回最大值所處的下標(biāo)教沾,ceil_mode表示使用方格代替層結(jié)構(gòu)。
3.經(jīng)典卷積神經(jīng)網(wǎng)絡(luò)
這里介紹三種經(jīng)典的卷積神經(jīng)網(wǎng)絡(luò):LeNet译断,AlexNet授翻,VGGNet。這三種卷積神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)不算特別復(fù)雜孙咪,有興趣的也可以了解GoogleNet和ResNet堪唐。
(1)LeNet
LeNet具體指的是LeNet-5。LeNet-5模型是Yann LeCun教授于1998年提出的翎蹈,它是第一個(gè)成功應(yīng)用于數(shù)字識(shí)別的卷積神經(jīng)網(wǎng)絡(luò)淮菠。在MNIST數(shù)據(jù)集上,LeNet-5模型可以達(dá)到約99.2%的正確率荤堪。LeNet-5模型總共有7層合陵,包括2個(gè)卷積層枢赔,2個(gè)池化層,2個(gè)全連接層和1個(gè)輸出層拥知。下圖是LeNet-5的模型架構(gòu)糠爬。
LeNet-5在PyTorch中的實(shí)現(xiàn)如下:
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
out = F.relu(self.conv1(x))
out = F.max_pool2d(out, 2)
out = F.relu(self.conv2d(out))
out = F.max_pool2d(out, 2)
out = out.view(out.size(0), -1)
out = F.relu(self.fc1(out))
out = F.relu(self.fc2(out))
out = self.fc3(out)
return out
(2)AlexNet
2012年,Hilton的學(xué)生Alex Krizhevsky提出了卷積神經(jīng)網(wǎng)絡(luò)模型AlexNet举庶。AlexNet在卷積神經(jīng)網(wǎng)絡(luò)上成功運(yùn)用了ReLU执隧,Dropout和LRN等技巧。在ImageNet的競(jìng)賽上户侥,AlexNet以領(lǐng)先第二名10%的準(zhǔn)確率而奪得冠軍镀琉,成功地展示了深度學(xué)習(xí)的威力。AlexNet的網(wǎng)絡(luò)結(jié)構(gòu)如下圖所示蕊唐。
上圖看起來(lái)有點(diǎn)復(fù)雜屋摔,這是由于當(dāng)時(shí)GPU計(jì)算能力不強(qiáng),AlexNet使用了兩個(gè)GPU并行計(jì)算替梨,現(xiàn)在可以用一個(gè)GPU替換钓试。以單個(gè)GPU的AlexNet模型為例,包括5個(gè)卷積層副瀑,3個(gè)池化層弓熏,3個(gè)全連接層。其中卷積層和全連接層包含有ReLU層糠睡,在全連接層中還有Dropout層挽鞠。具體參數(shù)配置如下圖所示:
具體的參數(shù)配置可以查看PyTorch源代碼。下面給出PyTorch實(shí)現(xiàn)AlexNet模型的卷積神經(jīng)網(wǎng)絡(luò)程序狈孔。
class AlexNet(nn.module):
def __init__(self, num_classes):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(64, 256, 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
(3)VGGNet
VGGNet是牛津大學(xué)計(jì)算機(jī)視覺(jué)組和Google DeepMind公司的研究人員一起研發(fā)的卷積神經(jīng)網(wǎng)絡(luò)信认。通過(guò)堆疊3x3的小型卷積核和2x2的最大池化層,VGGNet成功構(gòu)筑了深達(dá)19層的卷積神經(jīng)網(wǎng)絡(luò)均抽。VGGNet取得了2014年ImageNet比賽的第二名嫁赏,由于拓展性強(qiáng),遷移到其他圖片數(shù)據(jù)上的泛化性比較好油挥,因此可用作遷移學(xué)習(xí)潦蝇。下圖顯示的是VGGNet各個(gè)級(jí)別的的網(wǎng)絡(luò)結(jié)構(gòu)圖。雖然從A到E每一級(jí)網(wǎng)絡(luò)逐層變深喘漏,但是網(wǎng)絡(luò)的參數(shù)量并沒(méi)有增長(zhǎng)很多护蝶,因?yàn)閰?shù)量主要都消耗在最后3個(gè)全連接層华烟。前面的卷積層參數(shù)很深翩迈,參數(shù)量并不是很多,但是在訓(xùn)練時(shí)計(jì)算量大盔夜,比較耗時(shí)负饲。D和E模型就是VGGNet-16和VGGNet-19堤魁。
下面給出PyTorch實(shí)現(xiàn)VGGNet模型的卷積神經(jīng)網(wǎng)絡(luò)程序。
cfg = {
'VGG11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}
class VGG(nn.Module):
def __init__(self, vgg_name):
super(VGG, self).__init__()
self.features = self._make_layers(sfg[vgg_name])
self.classifier = nn.Linear(512, 10)
def forward(self, x):
out = self.features(x)
out = out.view(out.size(0), -1)
out = self.classifier(out)
return out
def _make_layers(self, cfg):
layers = []
ln_channels = 3
for x in cfg:
if x == 'M':
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
else:
layers += [nn.Conv2d(in_channels, x, kernel_size=3, padding=1),
nn.BatchNorm2d(x),
nn.ReLU(inplace=True)]
in_channels = x
layers += [nn.AvgPool2d(kernel_size=1, stride=1)]
return nn.Sequential(*layers)
3.MNIST數(shù)據(jù)集上的卷積神經(jīng)網(wǎng)絡(luò)的實(shí)現(xiàn)
本節(jié)介紹如何使用PyTorch實(shí)現(xiàn)一個(gè)簡(jiǎn)單的卷積神經(jīng)網(wǎng)絡(luò)返十,使用的數(shù)據(jù)集是MNIST妥泉,預(yù)期可以達(dá)到97%的準(zhǔn)確率。該神經(jīng)網(wǎng)絡(luò)由2個(gè)卷積層和3個(gè)全連接層構(gòu)成洞坑。通過(guò)這個(gè)例子我們可以掌握設(shè)計(jì)卷積神經(jīng)網(wǎng)絡(luò)的特征以及參數(shù)的配置盲链。
1.配置庫(kù)和配置參數(shù)
import torch
from torch import nn,optim
import torch.nn.functional as F
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms,datasets
# 配置參數(shù)
torch.manual_seed(1) # 設(shè)置隨機(jī)種子,確保結(jié)果可重復(fù)
batch_size = 128 # 批處理大小
learning_rate = 1e-2 # 學(xué)習(xí)率
num_epoches = 10 # 訓(xùn)練次數(shù)
2.加載MNIST數(shù)據(jù)集
# 下載訓(xùn)練集MNIST(手寫(xiě)數(shù)字?jǐn)?shù)據(jù))
train_dataset = datasets.MNIST(root='./data', # 數(shù)據(jù)集保存路徑
train=True, # 訓(xùn)練數(shù)據(jù)集
transform=transforms.ToTensor(), # 轉(zhuǎn)為T(mén)ensor
download=True) # 下載數(shù)據(jù)
test_dataset = datasets.MNIST(root='./data',
train=False, # 測(cè)試數(shù)據(jù)集
transform=transforms.ToTensor())
# 訓(xùn)練數(shù)據(jù)的加載方式:每次從train_dataset中隨機(jī)(shuffle=True)選擇batch_size個(gè)樣本作為一個(gè)批次返回迟杂,因此所選擇的數(shù)據(jù)可能會(huì)重復(fù)
train_loader = DataLoader(train_dataset,batch_size=batch_size,shuffle=True)
# 測(cè)試數(shù)據(jù)的加載方式:每次從test_dataset中選擇batch_size個(gè)不同的樣本作為一個(gè)批次返回刽沾,要覆蓋到所有測(cè)試樣本
test_loader = DataLoader(test_dataset,batch_size=batch_size,shuffle=False)
3.創(chuàng)建CNN模型
class Cnn(nn.Module):
def __init__(self,in_dim,n_class): # 28*28*1
super(Cnn,self).__init__()
# 定義卷積層
self.conv = nn.Sequential(
nn.Conv2d(in_dim,6,3,stride=1,padding=1), # 28*28
nn.ReLU(True),
nn.MaxPool2d(2,2), # 14*14
nn.Conv2d(6,16,5,stride=1,padding=0), # 10*10*16
nn.ReLU(True),
nn.MaxPool2d(2,2)) # 5*5*16
# 定義全連接層
self.fc = nn.Sequential(
nn.Linear(400,120), # 400 = 5*5*16
nn.Linear(120,84),
nn.Linear(84,n_class))
# 前向傳播
def forward(self,x):
out = self.conv(x)
out = out.view(out.size(0),400) # 400 = 5*5*16
out = self.fc(out)
return out
model = Cnn(1,10) # 圖片大小是28*28,10是數(shù)據(jù)的種類(lèi)
print(model)
輸出如下:
Cnn(
(conv): Sequential(
(0): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace)
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(4): ReLU(inplace)
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(fc): Sequential(
(0): Linear(in_features=400, out_features=120, bias=True)
(1): Linear(in_features=120, out_features=84, bias=True)
(2): Linear(in_features=84, out_features=10, bias=True)
)
)
4.訓(xùn)練模型
# 定義損失:交叉熵?fù)p失
criterion = nn.CrossEntropyLoss()
# 定義優(yōu)化器:隨機(jī)梯度下降SGD
optimizer = optim.SGD(model.parameters(),lr=learning_rate)
# 共訓(xùn)練num_epoches輪
for epoch in range(num_epoches):
running_loss = 0.0 # 當(dāng)前損失
running_acc = 0.0 # 當(dāng)前準(zhǔn)確度
# 訓(xùn)練集:60000,批處理:128
for i,data in enumerate(train_loader,1):
img,label = data
img = Variable(img)
label = Variable(label)
out = model(img) # 前向傳播
loss = criterion(out,label) # 這個(gè)損失是當(dāng)前批次的平均損失
running_loss += loss.item() * label.size(0) # 累計(jì)損失大小排拷,乘積表示當(dāng)前批次的總損失
_,pred=torch.max(out,1) # 多分類(lèi)問(wèn)題的類(lèi)別取概率最大的類(lèi)別
num_correct = (pred == label).sum() # 當(dāng)前批次預(yù)測(cè)正確的個(gè)數(shù)
running_acc += num_correct.item() # 累計(jì)預(yù)測(cè)正確的個(gè)數(shù)
optimizer.zero_grad() # 梯度清零
loss.backward() # 誤差反向傳播
optimizer.step() # 梯度更新
# 每訓(xùn)練一輪侧漓,打印一次信息
print('Train {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(epoch+1,running_loss/(len(train_dataset)),running_acc/(len(train_dataset))))
輸出如下:
Train 1 epoch, Loss: 1.366457, Acc: 0.626950
Train 2 epoch, Loss: 0.411538, Acc: 0.877867
Train 3 epoch, Loss: 0.294756, Acc: 0.911117
Train 4 epoch, Loss: 0.232250, Acc: 0.930517
Train 5 epoch, Loss: 0.188249, Acc: 0.943533
Train 6 epoch, Loss: 0.159317, Acc: 0.952500
Train 7 epoch, Loss: 0.139562, Acc: 0.957750
Train 8 epoch, Loss: 0.125770, Acc: 0.962000
Train 9 epoch, Loss: 0.115610, Acc: 0.964783
Train 10 epoch, Loss: 0.108309, Acc: 0.966983
5.評(píng)估模型
# 由于訓(xùn)練和測(cè)試的BatchNorm,Drop等配置不同监氢,所以需要說(shuō)明是模型評(píng)估
model.eval()
eval_loss = 0
eval_acc = 0
for data in test_loader:
img,label = data
img = Variable(img)
# 測(cè)試不需要 label = Variable(label)
out = model(img) # 前向傳播
loss = criterion(out,label) # 當(dāng)前批次的平均損失
eval_loss += loss.item() * label.size(0) # 累計(jì)損失
_,pred = torch.max(out,1) # 多分類(lèi)問(wèn)題的類(lèi)別取概率最大的類(lèi)別
num_correct = (pred==label).sum() # 當(dāng)前批次預(yù)測(cè)正確的個(gè)數(shù)
eval_acc += num_correct.item() # 累計(jì)預(yù)測(cè)正確的個(gè)數(shù)
# 打印測(cè)試集上的評(píng)估結(jié)果
print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss/(len(test_dataset)),eval_acc * 1.0 / (len(test_dataset))))
輸出如下:
Test Loss: 0.091677, Acc: 0.972800