Lesson 16.5 在Pytorch中实现卷积网络(上):卷积核、输入通道与特征图在PyTorch中实现卷积网络(中):步长与填充
卷積神經(jīng)網(wǎng)絡(luò)是使用卷積層的一組神經(jīng)網(wǎng)絡(luò)。在一個(gè)成熟的CNN中,往往會(huì)涉及到卷積層、池化層、線性層(全連接層)以及各類激活函數(shù)。因此,在構(gòu)筑卷積網(wǎng)絡(luò)時(shí),需從整體全部層的需求來(lái)進(jìn)行考慮。
1 二維卷積層nn.Conv2d
在PyTorch中,卷積層作為構(gòu)成神經(jīng)網(wǎng)絡(luò)的層,自然是nn.Module模塊下的類。
按照輸入數(shù)據(jù)的維度,卷積層可以分為三類:處理時(shí)序數(shù)據(jù)(samples,channels,length)的一維卷積(Conv1d),處理圖像數(shù)據(jù)的(samples,height,width,channels)的二維卷積(Conv2d),以及處理視頻數(shù)據(jù)(samples,frames,height,width,channels)的三維卷積(Conv3d)。時(shí)序數(shù)據(jù)是存在時(shí)間維度、受時(shí)間影響的三維數(shù)據(jù),常被用于循環(huán)神經(jīng)網(wǎng)絡(luò)中,但卷積也可以處理這種數(shù)據(jù)。視頻數(shù)據(jù)則是由多張圖像在時(shí)間軸上排列構(gòu)成的,因此視頻數(shù)據(jù)可以被看做是圖像數(shù)據(jù)的序列。視頻數(shù)據(jù)中的frames是“幀數(shù)”,即一個(gè)視頻中圖像的總數(shù)量。在之后的課程中,我們會(huì)就視頻數(shù)據(jù)及其處理展開詳細(xì)說(shuō)明。
按照卷積的操作和效果,又可分為普通卷積、轉(zhuǎn)置卷積、延遲初始化的lazyConv等等。最常用的是處理圖像的普通卷積nn.Conv2d。其類及其包含的超參數(shù)參數(shù)內(nèi)容如下(注意Conv2d是大寫):
CLASS torch.nn.Conv2d (in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1,groups=1, bias=True, padding_mode=‘zeros’)
可以看到,除了之前的掃描操作之外,還有許多未知的參數(shù)。我們可以通過(guò)解析Conv2d的參數(shù)來(lái)說(shuō)明卷積操作中的許多細(xì)節(jié)。需要說(shuō)明的是,參數(shù)groups和dilation分別代表著分組卷積(Grouped Convolution)與膨脹卷積(Dilated Convolution),屬于卷積神經(jīng)網(wǎng)絡(luò)的入門級(jí)操作,但卻比我們現(xiàn)在學(xué)習(xí)的內(nèi)容更加復(fù)雜。之后,我們會(huì)詳細(xì)描述這兩種卷積網(wǎng)絡(luò)的原理與流程,現(xiàn)在,我們還是專注在普通卷積上。
1.1 卷積核尺寸kernel_size
kernel_size是我們第一個(gè)需要講解的參數(shù),但同時(shí)也是最簡(jiǎn)單的參數(shù)。
卷積核的高和寬一般用KHK_{H}KH?和KWK_{W}KW?表示。在許多其他資料或教材中,如果把卷積核稱為filter過(guò)濾器,也可能使用字母FHF_{H}FH?和FWF_{W}FW?來(lái)表示。卷積核的對(duì)整個(gè)卷積網(wǎng)絡(luò)的參數(shù)量有很大的影響。在之前的例子中,我們使用3X3結(jié)構(gòu)的卷積核,每個(gè)卷積核就會(huì)攜帶9個(gè)參數(shù)(權(quán)重),如果我們使用3X2的結(jié)構(gòu),每個(gè)卷積核就會(huì)攜帶6個(gè)參數(shù)。
卷積核的尺寸應(yīng)該如何選擇呢?如果你在使用經(jīng)典架構(gòu),那經(jīng)典架構(gòu)的論文中所使用的尺寸就是最好的尺寸。如果你在寫自己的神經(jīng)網(wǎng)絡(luò),那3x3幾乎就是最好的選擇。對(duì)于這個(gè)幾乎完全基于經(jīng)驗(yàn)的問(wèn)題,我可以提供以下幾點(diǎn)提示:
1、卷積核幾乎都是正方形。最初是因?yàn)樵诮?jīng)典圖像分類任務(wù)中,許多圖像都被處理成正方形或者接近正方形的形狀(比如Fashion-MNIST,CIFAR,ImageNet)等等。如果你的原始圖像尺寸很奇妙(例如,非常長(zhǎng)或非常寬),你可以使用與原圖尺寸比例一致的卷積核尺寸。
2、卷積核的尺寸最好是奇數(shù),例如3x3,5x5,7x7等。這是一種行業(yè)慣例,傳統(tǒng)視覺(jué)中認(rèn)為這是為了讓被掃描區(qū)域能夠“中心對(duì)稱”,無(wú)論經(jīng)歷過(guò)多少次卷積變換,都能夠以正常比例還原圖像。
相對(duì)的,如果掃描區(qū)域不是中心對(duì)稱的,在多次進(jìn)行卷積操作之后,像素會(huì)“偏移”導(dǎo)致圖像失真(由最左側(cè)圖像變換為右側(cè)的狀況)。然而,這種說(shuō)法缺乏有效的理論基礎(chǔ),如果你發(fā)現(xiàn)你的神經(jīng)網(wǎng)絡(luò)的確更適合偶數(shù)卷積核(并且你能夠證明、或說(shuō)服你需要說(shuō)服的人來(lái)接受你的決定),那你可以自由使用偶數(shù)卷積核。目前為止,還沒(méi)有卷積核的奇偶會(huì)對(duì)神經(jīng)網(wǎng)絡(luò)的效果造成影響的明確理論。
3、在計(jì)算機(jī)視覺(jué)中,卷積核的尺寸往往都比較小(相對(duì)的,在NLP中,許多網(wǎng)絡(luò)的卷積核尺寸都可以很大)。這主要是因?yàn)檩^小的卷積核所需要的訓(xùn)練參數(shù)會(huì)更少,之后我們會(huì)詳細(xì)地討論關(guān)于訓(xùn)練參數(shù)量的問(wèn)題。
1.2 卷積的輸入與輸出:in_channels,out_channels,bias
除了kernel_size之外,還有兩個(gè)必填參數(shù):in_channels與out_channels。簡(jiǎn)單來(lái)說(shuō):in_channels是輸入卷積層的圖像的通道數(shù)或上一層傳入特征圖的數(shù)量,out_channels是指這一層輸出的特征圖的數(shù)量。這兩個(gè)數(shù)量我們都可以自己來(lái)確定,但具體掃描流程中的細(xì)節(jié)還需要理清。
在之前的例子中,我們?cè)谝粡垐D像上使用卷積核進(jìn)行掃描,得到一張?zhí)卣鲌D。這里的“被掃描圖像”是一個(gè)通道,而非一張彩色圖片。如果卷積核每掃描一個(gè)通道,就會(huì)得到一張?zhí)卣鲌D,那多通道的圖像應(yīng)該被怎樣掃描呢?會(huì)有怎樣的輸出呢?
在一次掃描中,我們輸入了一張擁有三個(gè)通道的彩色圖像。對(duì)于這張圖,擁有同樣尺寸、但不同具體數(shù)值的三個(gè)卷積核會(huì)分別在三個(gè)通道上進(jìn)行掃描,得出三個(gè)相應(yīng)的“新通道”。由于同一張圖片中不同通道的結(jié)構(gòu)一定是一致的,卷積核的尺寸也是一致的,因此卷積操作后得出的“新通道”的尺寸也是一致的。
得出三個(gè)“新通道”后,我們將對(duì)應(yīng)位置的元素相加,形成一張新圖,這就是卷積層輸入的三彩色圖像的第一個(gè)特征圖。這個(gè)操作對(duì)于三通道的RGB圖像、四通道的RGBA或者CYMK圖像都是一致的。只不過(guò),如果是四通道的圖像,則會(huì)存在4個(gè)同樣尺寸、但數(shù)值不同的卷積核分別掃描4個(gè)通道。
因此,在一次掃描中,無(wú)論圖像本身有幾個(gè)通道,卷積核會(huì)掃描全部通道之后,將掃描結(jié)果加和為一張feature map。所以,一次掃描對(duì)應(yīng)一個(gè)feature map,無(wú)關(guān)原始圖像的通道數(shù)目是多少,所以out_channels就是掃描次數(shù),這之中卷積核的數(shù)量就等于輸入的通道數(shù)in_channels x 掃描次數(shù)out_channels。那對(duì)于一個(gè)通道,我們還有可能多次掃描,得出多個(gè)feature map嗎?當(dāng)然有可能!卷積核的作用是捕捉特征,同一個(gè)通道上很有可能存在多個(gè)需要不同的卷積核進(jìn)行捕捉的特征,例如,能夠捕捉到孔雀脖子輪廓的卷積核,就不一定能夠捕捉到色彩絢麗的尾巴。因此,對(duì)同一個(gè)通道提供多個(gè)不同的卷積核來(lái)進(jìn)行多次掃描是很普遍的操作。不過(guò),我們并不能對(duì)不同的通道使用不同的卷積核數(shù)量。比如,若規(guī)定掃描三次,則每次掃描時(shí)通道都會(huì)分別獲得自己的卷積核,我們不能讓卷積網(wǎng)絡(luò)執(zhí)行類似于“紅色通道掃描2次,藍(lán)色通道掃描3次”的操作。
需要注意的是,當(dāng)feature maps被輸入到下一個(gè)卷積層時(shí),它也是被當(dāng)作“通道”來(lái)處理的。不太嚴(yán)謹(jǐn)?shù)卣f(shuō),feature map其實(shí)也可以是一種“通道”,雖然沒(méi)有定義到具體的顏色,但它其實(shí)也是每個(gè)元素都在[0,255]之間的圖。這是說(shuō),當(dāng)feature map進(jìn)入到下一個(gè)卷積層時(shí),新卷積層上對(duì)所有feature map完成之后,也會(huì)將它們的掃描結(jié)果加和成一個(gè)新feature map。所以,在新卷積層上,依然是一次掃描對(duì)應(yīng)生成一個(gè)feature map,無(wú)關(guān)之前的層上傳入的feature map有多少。
這其實(shí)與DNN中的線性層很相似,在線性層中,下一個(gè)線性層輸入的數(shù)目就等于上一個(gè)線性層的輸出的數(shù)目。我們來(lái)看一下具體的卷積層的代碼:
掌握卷積層輸入輸出的結(jié)構(gòu),對(duì)于構(gòu)筑卷積網(wǎng)絡(luò)十分重要。
在DNN中,每一層權(quán)重www都帶有偏差 ,我們可以決定是否對(duì)神經(jīng)網(wǎng)絡(luò)加入偏差,在卷積中也是一樣的。在這里我們的權(quán)重就是卷積核,因此每個(gè)卷積層中都可以加入偏差,偏差的數(shù)量與掃描的數(shù)量一致。當(dāng)我們得到feature_map后,如果有偏差的存在,我們會(huì)將偏差值加到feature_map的每個(gè)元素中,與矩陣 + 常數(shù)的計(jì)算方法一致。
當(dāng)參數(shù)bias=True時(shí),最終的feature_map是包含常數(shù)項(xiàng)的,反之則不包含。
1.3 特征圖的尺寸:stride,padding,padding_mode
stride
不知你是否注意到,在沒(méi)有其他操作的前提下,經(jīng)過(guò)卷積操作之后,新生成的特征圖的尺寸往往是小于上一層的特征圖的。在之前的例子中,我們使用3X3的卷積核在6X6大小的通道上進(jìn)行卷積,得到的特征圖是4X4。如果在4X4的特征圖上繼續(xù)使用3X3的卷積核,我們得到的新特征圖將是2X2的尺寸。最極端的情況,我們使用1X1的卷積核,可以得到與原始通道相同的尺寸,但隨著卷積神經(jīng)網(wǎng)絡(luò)的加深,特征圖的尺寸是會(huì)越來(lái)越小的。
對(duì)于一個(gè)卷積神經(jīng)網(wǎng)絡(luò)而言,特征圖的尺寸非常重要,它既不能太小,也不能太大。如果特征圖太小,就可能缺乏可以提取的信息,進(jìn)一步縮小的可能性就更低,網(wǎng)絡(luò)深度就會(huì)受限制。如果特征圖太大,每個(gè)卷積核需要掃描的次數(shù)就越多,所需要的卷積操作就會(huì)越多,影響整體計(jì)算量。同時(shí),卷積神經(jīng)網(wǎng)絡(luò)往往會(huì)在卷積層之后使用全連接層,而全連接層上的參數(shù)量和輸入神經(jīng)網(wǎng)絡(luò)的圖像像素量有很大的關(guān)系(記得我們之前說(shuō)的嗎?全連接層需要將像素拉平,每一個(gè)像素需要對(duì)應(yīng)一個(gè)參數(shù),對(duì)于尺寸600X400的圖片需要2.4* 個(gè)參數(shù)),因此,在全連接層登場(chǎng)之前,我們能夠從特征圖中提取出多少信息,并且將特征圖的尺寸、也就是整體像素量縮小到什么水平,將會(huì)嚴(yán)重影響卷積神經(jīng)網(wǎng)絡(luò)整體的預(yù)測(cè)效果和計(jì)算性能。也因此,及時(shí)了解特征圖的大小,對(duì)于卷積神經(jīng)網(wǎng)絡(luò)的架構(gòu)來(lái)說(shuō)很有必要。
Hout?=Hin??KH+1Wout?=Win??KW+1\begin{array}{l} H_{\text {out }}=H_{\text {in }}-K_{H}+1 \\ W_{\text {out }}=W_{\text {in }}-K_{W}+1 \end{array}Hout??=Hin???KH?+1Wout??=Win???KW?+1?那怎么找出卷積操作后的特征圖的尺寸呢?假設(shè)特征圖的高為 ,特征圖的寬為 ,則對(duì)于上圖所示的卷積操作,我們可以有如下式子:
其中,HinH_{i n}Hin?與WinW_{i n}Win?是輸入數(shù)據(jù)的高和寬,對(duì)于第一個(gè)卷積層而言,也就是輸入圖像的高和寬,對(duì)于后續(xù)的卷積層而言,就是前面的層所輸出的特征圖的高和寬。KHK_{H}KH?和KWK_{W}KW?如同之前提到的,則代表在這一層與輸入圖像進(jìn)行卷積操作的卷積核的高和寬。在之前的例子中,HinH_{i n}Hin?和WinW_{i n}Win?都等于6,KHK_{H}KH?和KWK_{W}KW?都等于3,因此HoutH_{out}Hout?=WoutW_{out}Wout?= 6-3+1 = 4。但在實(shí)際情況中,圖像的寬高往往是不一致的。因行業(yè)的約定俗成,卷積核的形狀往往是正方形,但理論上來(lái)說(shuō)KHK_{H}KH?和KWK_{W}KW?也可以不一致。在PyTorch中,卷積核的大小由參數(shù)Kernel_size確定。設(shè)置kernel_size=(3,3),即表示卷積核的尺寸為(3,3)。
這是特征圖尺寸計(jì)算的“最簡(jiǎn)單”的情況。在實(shí)際進(jìn)行卷積操作時(shí),還有很多問(wèn)題。比如說(shuō),現(xiàn)在每執(zhí)行一次卷積,我們就將感受野向右移動(dòng)一個(gè)像素,每掃描完一行,我們就向下移動(dòng)一個(gè)像素,直到整張圖片都被掃描完為止。在尺寸較小的圖片上(比如,28X28像素),這樣做并沒(méi)有什么問(wèn)題,但對(duì)于很大的圖片來(lái)說(shuō)(例如600X800),執(zhí)行一次卷積計(jì)算就需要掃描很久,并且其中有許多像素都是被掃描了很多次的,既浪費(fèi)時(shí)間又浪費(fèi)資源。于是,我們定義一個(gè)新的超參數(shù):卷積操作中的“步長(zhǎng)”,參數(shù)名稱stride(也譯作步幅)。
步長(zhǎng)是每執(zhí)行完一次卷積、或掃描完一整行后,向右、向下移動(dòng)的像素?cái)?shù)。水平方向的步長(zhǎng)管理橫向移動(dòng),豎直方向的步長(zhǎng)管理縱向移動(dòng)。在pytorch中,當(dāng)我們對(duì)參數(shù)stride輸入整數(shù)時(shí),則默認(rèn)調(diào)整水平方向掃描的步長(zhǎng)。當(dāng)輸入數(shù)組時(shí),則同時(shí)調(diào)整水平和豎直方向上的步長(zhǎng)。默認(rèn)狀況下,水平和豎直方向的步長(zhǎng)都是1,當(dāng)我們把步長(zhǎng)調(diào)整為(2,2),則每次橫向和縱向移動(dòng)時(shí),都會(huì)移動(dòng)2個(gè)像素。
步長(zhǎng)可以根據(jù)自己的需求進(jìn)行調(diào)整,通常都設(shè)置為1-3之間的數(shù)字,也可以根據(jù)kernel_size來(lái)進(jìn)行設(shè)置。在DNN中,我們把形如(sampels, features)結(jié)構(gòu)的表數(shù)據(jù)中的列,也就是特征也叫做“維度”。對(duì)于表數(shù)據(jù)來(lái)說(shuō),要輸入DNN,則需要讓DNN的輸入層上擁有和特征數(shù)一樣數(shù)量的神經(jīng)元,因此“高維”就意味著神經(jīng)元更多。之前我們提到過(guò),任何神經(jīng)網(wǎng)絡(luò)中一個(gè)神經(jīng)元上都只能有一個(gè)數(shù)字,對(duì)圖像來(lái)說(shuō)一個(gè)像素格子就是一個(gè)神經(jīng)元,因此卷積網(wǎng)絡(luò)中的“像素”就是最小特征單位,我們?cè)谟?jì)算機(jī)視覺(jué)中說(shuō)“降維”,往往是減少一張圖上的像素量。參數(shù)步長(zhǎng)可以被用于“降維”,也就是可以讓輸入下一層的特征圖像素量降低,特征圖的尺寸變得更小。
以上圖中的特征圖為例,通道尺寸為7X7,卷積核尺寸為3X3,若沒(méi)有步長(zhǎng),則會(huì)生成5X5的特征圖(7-3+1)。但在(2,2)的步長(zhǎng)加持下,只會(huì)生成3X3的特征圖。帶步長(zhǎng)的特征圖尺寸計(jì)算公式為:
Hout=Hin?KHS[0]+1Wout=Win?KWS[1]+1\begin{aligned} H_{o u t} &=\frac{H_{i n}-K_{H}}{S[0]}+1 \\ W_{o u t} &=\frac{W_{i n}-K_{W}}{S[1]}+1 \end{aligned} Hout?Wout??=S[0]Hin??KH??+1=S[1]Win??KW??+1?其中S[0]代表橫向的步長(zhǎng),S[1]代表縱向的步長(zhǎng)。步長(zhǎng)可以加速對(duì)特征圖的掃描,并加速縮小特征圖,令計(jì)算更快。
Padding,Padding_mode
除了步長(zhǎng)之外,還有一個(gè)常常在神經(jīng)網(wǎng)絡(luò)中出現(xiàn)的問(wèn)題:掃描不完全或掃描不均衡。
先來(lái)看掃描不完全,同樣還是7X7的特征圖和3X3的卷積核:
S[0]=1,Hout?=(7?3)/1+1=5S[0]=2,Hout?=(7?3)/2+1=3S[0]=3,Hout?=(7?3)/3+1=2.33\begin{array}{l} S[0]=1, H_{\text {out }}=(7-3) / 1+1=5 \\ S[0]=2, H_{\text {out }}=(7-3) / 2+1=3 \\ S[0]=3, H_{\text {out }}=(7-3) / 3+1=2.33 \end{array} S[0]=1,Hout??=(7?3)/1+1=5S[0]=2,Hout??=(7?3)/2+1=3S[0]=3,Hout??=(7?3)/3+1=2.33?當(dāng)步長(zhǎng)為3時(shí),feature map的尺寸出現(xiàn)了小數(shù),無(wú)法再包含完整的像素了。在圖像上來(lái)看也非常明顯,當(dāng)步長(zhǎng)為3的時(shí)候,向右移動(dòng)一次后,就沒(méi)有足夠的圖像來(lái)進(jìn)行掃描了。此時(shí),我們不得不舍棄掉沒(méi)有掃描的最后一列像素。同時(shí),在我們進(jìn)行掃描的時(shí)候,如果我們的步長(zhǎng)小于卷積核的寬度和長(zhǎng)度,那部分像素就會(huì)在掃描的過(guò)程中被掃描多次,而邊緣的像素則只會(huì)在每次感受野來(lái)到邊緣時(shí)被掃描到,這就會(huì)導(dǎo)致“中間重邊緣輕”,掃描不均衡。為了解決這個(gè)問(wèn)題,我們要采用“填充法”對(duì)圖像進(jìn)行處理。
所謂填充法,就是在圖像的兩側(cè)使用0或其他數(shù)字填上一些像素,擴(kuò)大圖像的面積,使得卷積核能將整個(gè)圖像盡量掃描完整。
在PyTorch中,填充與否由參數(shù)padding控制和padding_mode控制。padding接受大于0的正整數(shù)或數(shù)組作為參數(shù),但通常我們只使用整數(shù)。padding=1則時(shí)在原通道的上下左右方向各添上1個(gè)像素,所以通道的尺寸實(shí)際上會(huì)增加2*padding。padding_mode則可以控制填充什么內(nèi)容。在圖上展示的是zero_padding,也就是零填充,但我們也可以使用其他的填充方式。pytorch提供了兩種填充方式,0填充與環(huán)型填充。在padding_mode中輸入“zero”則使用0填充,輸入“circular”則使用環(huán)型填充(原始通道的數(shù)據(jù)復(fù)制出去用)。
需要注意的是,雖然pytorch官方文檔上說(shuō)padding_mode可以接受四種填充模式,但實(shí)際上截至版本1.7.1,仍然只有"zeros"和"circular"兩種模式有效,其他輸入都會(huì)被當(dāng)成零填充。如果想要使用填充鏡面翻轉(zhuǎn)值的reflection padding,則必須使用單獨(dú)定義的層nn.ReflectionPad2d,同樣的,"replicate"模式所指代的填充邊緣重復(fù)值需要使用單獨(dú)的類nn.ReplicationPad2d。
不難發(fā)現(xiàn),如果輸入通道的尺寸較小,padding數(shù)目又很大,padding就可能極大地?cái)U(kuò)充通道的尺寸,并讓feature map在同樣的卷積核下變得更大。我們之前說(shuō),在沒(méi)有其他操作時(shí)feature map往往是小于輸入通道的尺寸的,而加入padding之后feature map就有可能大于輸入通道了,這在經(jīng)典卷積網(wǎng)絡(luò)的架構(gòu)中也曾出現(xiàn)過(guò)。通常來(lái)說(shuō),我們還是會(huì)讓feature map隨著卷積層的深入逐漸變小,這樣模型計(jì)算才會(huì)更快,因此,padding的值也不會(huì)很大,基本只在1~3之間。
實(shí)際上,Padding并不能夠保證圖像一定被掃描完全或一定均衡。看下面的例子:
不難發(fā)現(xiàn),即便已經(jīng)填充了一個(gè)像素,在現(xiàn)在的步長(zhǎng)與卷積核大小下,依然無(wú)法將整張圖掃描完全。此時(shí),有兩種解決方案,一種叫做"valid",一種叫做"same"。
valid模式就是放棄治療,對(duì)于掃描不到的部分,直接丟棄。“same”模式是指,在當(dāng)前卷積核與步長(zhǎng)設(shè)置下無(wú)法對(duì)全圖進(jìn)行掃描時(shí),對(duì)圖像右側(cè)和下邊進(jìn)行“再次填充”,直到掃描被允許進(jìn)行為止。從上圖看,same模式下的padding設(shè)置本來(lái)是1(即左右兩側(cè)都填上0),但在右側(cè)出現(xiàn)11、12、13和填充的0列無(wú)法被掃描的情況,則神經(jīng)網(wǎng)絡(luò)自動(dòng)按照kernel_size的需要,在右側(cè)再次填充一個(gè)0列,實(shí)現(xiàn)再一次掃描,讓全部像素都被掃描到。
這個(gè)操作看上去很智能,但遺憾的是只能夠在tensorflow中實(shí)現(xiàn)。對(duì)于PyTorch而言,沒(méi)有“same”的選項(xiàng),只要無(wú)法掃描完全,一律拋棄。為什么這樣做呢?主要還是因?yàn)閗ernel_size的值一般比較小,所以被漏掉的像素點(diǎn)不會(huì)很多,而且基本集中在邊緣。隨著計(jì)算機(jī)視覺(jué)中所使用的圖片分辨率越來(lái)越高,圖像尺寸越來(lái)越大,邊緣像素包含關(guān)鍵信息的可能性會(huì)越來(lái)越小,丟棄邊緣就變得越來(lái)越經(jīng)濟(jì)。對(duì)于一張28x28的圖像而言,丟棄2、3列或許會(huì)有不少信息損失,但對(duì)于720x1080的圖片而言,究竟是720x1078還是720x1080,其實(shí)并無(wú)太大區(qū)別。
那如果,你的圖像尺寸確實(shí)較小,你希望盡量避免未掃描的像素被丟棄,那你可以如下設(shè)置:
- 1、卷積核尺寸控制在5x5以下,并且kernel_size > stride
- 2、令2*padding > stride
這樣做不能100%避免風(fēng)險(xiǎn),但可以大規(guī)模降低像素被丟棄的風(fēng)險(xiǎn)(個(gè)人經(jīng)驗(yàn),無(wú)理論基礎(chǔ))。
padding操作會(huì)影響通道的大小,因此padding也會(huì)改變feature map的尺寸,當(dāng)padding中輸入的值為P時(shí),特征圖的大小具體如下:Hout=Hin+2P?KHS[0]+1Wout=Win?+2P?KWS[1]+1\begin{aligned} H_{o u t} &=\frac{H_{i n}+2 P-K_{H}}{S[0]}+1 \\ W_{o u t} &=\frac{W_{\text {in }}+2 P-K_{W}}{S[1]}+1 \end{aligned} Hout?Wout??=S[0]Hin?+2P?KH??+1=S[1]Win??+2P?KW??+1?我們?cè)诖a中來(lái)感受一下特征圖尺寸的變化:
#記住我們的計(jì)算公式 #(H + 2p - K)/S + 1 #(W + 2p - K)/S + 1 #并且卷積網(wǎng)絡(luò)中,默認(rèn)S=1,p=0 data = torch.ones(size=(10,3,28,28)) conv1=nn.Conv2d(3,6,3) conv2=nn.Conv2d(6,10,3) conv3=nn.Conv2d(10,16,5,stride=2,padding=1) conv4=nn.Conv2d(16,3,5,stride=(2,3),padding=2) #(28 + 0 - 3)/1 + 1 = 26 #驗(yàn)證一下 conv1(data).shape #torch.Size([10, 6, 26, 26]) #conv2,輸入結(jié)構(gòu)26*26 #(26 + 0 - 3)/1 + 1 = 24 #驗(yàn)證 conv2(conv1(data)).shape #torch.Size([10, 10, 24, 24]) #conv3,輸入結(jié)構(gòu)24*24 #(24 + 2 - 5)/2 + 1 = 11,掃描不完全的部分會(huì)被舍棄 conv3(conv2(conv1(data))).shape #torch.Size([10, 16, 11, 11]) #conv4,輸入結(jié)構(gòu)11*11 #(11 + 4 - 5)/3 + 1 = 4.33,掃描不完全的部分會(huì)被舍棄 conv4(conv3(conv2(conv1(data)))).shape #torch.Size([10, 3, 6, 4])padding和stride是卷積層最基礎(chǔ)的操作,之后的課程中我們還會(huì)有各種各樣的操作,他們都有可能會(huì)改變卷積層的輸出結(jié)構(gòu)或參數(shù)量。對(duì)于入門而言,學(xué)會(huì)padding和stride也就足夠了。
2 池化層nn.MaxPool & nn.AvgPool
無(wú)論是調(diào)整步長(zhǎng)還是加入填充,我們都希望能夠自由控制特征圖的尺寸。除了卷積層之外,另一種可以高效減小特征圖尺寸的操作是“池化”Pooling。池化是一種非常簡(jiǎn)單(甚至有些粗暴的)的降維方式,經(jīng)常跟在卷積層之后,用以處理特征圖。最常見(jiàn)的是最大池化(Max Pooling)和平均池化(Average Pooling)兩種操作,他們都很容易理解:
池化層也有核,但它的核沒(méi)有值,只有尺寸。在上圖之中,池化核的尺寸就是(2,2)。池化核的移動(dòng)也有步長(zhǎng)stride,但默認(rèn)步長(zhǎng)就等于它的核尺寸,這樣可以保證它在掃描特征圖時(shí)不出現(xiàn)重疊。當(dāng)然,如果我們需要,我們也可以設(shè)置參數(shù)令池化核的移動(dòng)步長(zhǎng)不等于核尺寸,在行業(yè)中這個(gè)叫“Overlapping Pooling”,即重疊池化,但它不是非常常見(jiàn)。通常來(lái)說(shuō),對(duì)于特征圖中每一個(gè)不重疊的、大小固定的矩陣,池化核都按照池化的標(biāo)準(zhǔn)對(duì)數(shù)字進(jìn)行計(jì)算或篩選。在最大池化中,它選出掃描區(qū)域中最大的數(shù)字。在平均池化中,它對(duì)掃描區(qū)域中所有的數(shù)字求平均。在加和池化中,它對(duì)掃描區(qū)域中所有的數(shù)字進(jìn)行加和。
在這幾種簡(jiǎn)單的方法中,最大池化是應(yīng)用最廣泛的,也是比較有效的。考慮看看feature map中的信息是怎么得來(lái)的?feature map中每個(gè)像素上的信息都是之前的卷積層中,圖像與卷積核進(jìn)行互相關(guān)操作的結(jié)果。對(duì)之前的卷積層而言,卷積核都是一致的,唯一不同的就是每次被掃描的區(qū)域中的像素值。像素值越大,說(shuō)明顏色信息越多,像素值越小,說(shuō)明圖像顯示約接近黑色,因此經(jīng)過(guò)卷積層之后,像素值更高的點(diǎn)在原始圖像上更有可能帶有大量信息。MaxPooling通過(guò)摘取這些帶有更多信息的像素點(diǎn),有效地將冗余信息消除,實(shí)現(xiàn)了特征的“提煉”。相對(duì)的,平均和加和的“提煉”效應(yīng)就弱一些。
在PyTorch中,池化層也有多種選項(xiàng),但這些多屬于“普通池化”的范圍。在傳統(tǒng)計(jì)算機(jī)視覺(jué)中,我們還有空間金字塔池化(Spatial Pyramid Pooling)等操作。
以MaxPool2d為例,其類和參數(shù)的詳情如下:
CLASS torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False,ceil_mode=False)
其中kernel_size就是池化核的尺寸,一般都設(shè)置為2X2或3X3。Padding參數(shù)與Stride參數(shù)一般都不填寫。需要提醒的是,池化層的步長(zhǎng)一般與核尺寸保持一致,因此stride參數(shù)的默認(rèn)值就是kernel_size。池化層對(duì)特征圖尺寸的影響,也符合我們之前所寫的這個(gè)公式:Hout=Hin+2P?KHS[0]+1Wout=Win?+2P?KWS[1]+1\begin{aligned} H_{o u t} &=\frac{H_{i n}+2 P-K_{H}}{S[0]}+1 \\ W_{o u t} &=\frac{W_{\text {in }}+2 P-K_{W}}{S[1]}+1 \end{aligned} Hout?Wout??=S[0]Hin?+2P?KH??+1=S[1]Win??+2P?KW??+1?只不過(guò)此時(shí)的padding、kernel_size以及stride都是池化層的參數(shù)。我們?cè)诖a中來(lái)看看:
data = torch.ones(size=(10,3,28,28)) conv1 = nn.Conv2d(3,6,3) #(28 + 0 - 3)/1 + 1 = 26 conv3 = nn.Conv2d(6,16,5,stride=2,padding=1) # (26 + 2 - 5)/2 +1 = 12 pool1 = nn.MaxPool2d(2) #唯一需要輸入的參數(shù),kernel_size=2,則默認(rèn)使用(2,2)結(jié)構(gòu)的核,默認(rèn)步長(zhǎng)stride=(2,2) # (12 + 0 - 2)/2 + 1 =6 #驗(yàn)證一下 conv1(data).shape #torch.Size([10, 6, 26, 26]) conv3(conv1(data)).shape #torch.Size([10, 16, 12, 12]) pool1(conv3(conv1(data))).shape #torch.Size([10, 16, 6, 6])事實(shí)上,使用(2,2)結(jié)構(gòu)的池化層總是會(huì)將featrue map的行列都減半,將整個(gè)feature map的像素?cái)?shù)減少3/4,因此它是非常有效的降維方法。
與卷積不同,池化層的操作簡(jiǎn)單,沒(méi)有任何復(fù)雜的數(shù)學(xué)原理和參數(shù),這為我們提供了精簡(jiǎn)池化層代碼的可能性。通常來(lái)說(shuō),當(dāng)我們使用池化層的時(shí)候,我們需要像如上所示的方法一樣來(lái)計(jì)算輸出特征圖的尺寸,但PyTorch提供的“Adaptive”相關(guān)類,允許我們輸入我們希望得到的輸出尺寸來(lái)執(zhí)行池化:
CLASS torch.nn.AdaptiveMaxPool2d (output_size, return_indices=False)
CLASS torch.nn.AdaptiveAvgPool2d (output_size)
可以看到,在這兩個(gè)類中,我們可以輸入output_size,而池化層可以自動(dòng)幫我們將特征圖進(jìn)行裁剪。我們來(lái)試試看:
data = torch.ones(size=(10,3,28,28)) conv1 = nn.Conv2d(3,6,3) #(28 + 0 - 3)/1 + 1 = 26 conv3 = nn.Conv2d(6,16,5,stride=2,padding=1) # (26 + 2 - 5)/2 +1 = 12 pool1 = nn.AdaptiveMaxPool2d(7) #輸入單一數(shù)字表示輸出結(jié)構(gòu)為7x7,也可輸入數(shù)組 pool1(conv3(conv1(data))).shape #torch.Size([10, 16, 7, 7])可惜的是,PyTorch官方?jīng)]有給出這兩個(gè)類具體是怎樣根據(jù)給出的輸出特征圖的尺寸數(shù)來(lái)倒推步幅和核尺寸的,在PyTorch官方論壇也有一些關(guān)于這個(gè)問(wèn)題的討論,但都沒(méi)有給出非常令人滿意的結(jié)果。大家達(dá)成一致的是:如果輸入一些奇怪的數(shù)字,例如(3,7),那在池化的過(guò)程中是會(huì)出現(xiàn)比較多的數(shù)據(jù)損失的。
除了能夠有效降低模型所需的計(jì)算量、去除冗余信息之外,池化層還有特點(diǎn)和作用呢?
1、提供非線性變化。卷積層的操作雖然復(fù)雜,但本質(zhì)還是線性變化,所以我們通常會(huì)在卷積層的后面增加激活層,提供ReLU等非線性激活函數(shù)的位置。但池化層自身就是一種非線性變化,可以為模型帶來(lái)一些活力。然而,學(xué)術(shù)界一直就池化層存在的必要性爭(zhēng)論不休,因?yàn)橛斜姸嘌芯勘砻鞒鼗瘜硬⒉荒芴嵘P托Ч?#xff08;有爭(zhēng)議)。
2、有一定的平移不變性(有爭(zhēng)議)。
3、池化層所有的參數(shù)都是超參數(shù),不涉及到任何可以學(xué)習(xí)的參數(shù),這既是優(yōu)點(diǎn)(增加池化層不會(huì)增加參數(shù)量),也是致命的問(wèn)題(池化層不會(huì)隨著算法一起進(jìn)步)。
4、按照所有的規(guī)律對(duì)所有的feature map一次性進(jìn)行降維,feature map不同其本質(zhì)規(guī)律不然不同,使用同樣的規(guī)則進(jìn)行降維,必然引起大估摸信息損失。
不過(guò),在經(jīng)典神經(jīng)網(wǎng)絡(luò)架構(gòu)中,池化層依然是非常關(guān)鍵的存在。如果感興趣的話,可以就池化與卷積的交互相應(yīng)深入研究下去,繼續(xù)探索提升神經(jīng)網(wǎng)絡(luò)效果的可能性。
3 Dropout2d與BatchNorm2d
Dropout與BN是神經(jīng)網(wǎng)絡(luò)中非常經(jīng)典的,用于控制過(guò)擬合、提升模型泛化能力的技巧,在卷積神經(jīng)網(wǎng)絡(luò)中我們需要應(yīng)用的是二維Dropout與二維BN。對(duì)于BN我們?cè)谇懊娴恼n程中有深入的研究,它是對(duì)數(shù)據(jù)進(jìn)行歸一化處理的經(jīng)典方法,對(duì)于圖像數(shù)據(jù),我們所需要的類如下:
CLASS torch.nn.BatchNorm2d (num_features, eps=1e-05, momentum=0.1, affine=True,track_running_stats=True)
BN2d所需要的輸入數(shù)據(jù)是四維數(shù)據(jù)(第一個(gè)維度是samples),我們需要填寫的參數(shù)幾乎只有num_features一個(gè)。在處理表數(shù)據(jù)的BatchNorm1d里,num_features代表了輸入bn層的神經(jīng)元個(gè)數(shù),然而對(duì)于卷積網(wǎng)絡(luò)來(lái)說(shuō),由于存在參數(shù)共享機(jī)制,則必須以卷積核/特征圖為單位來(lái)進(jìn)行歸一化,因此當(dāng)出現(xiàn)在卷積網(wǎng)絡(luò)前后時(shí),BatchNorm2d所需要輸入的是上一層輸出的特征圖的數(shù)量。例如:
data = torch.ones(size=(10,3,28,28)) conv1 = nn.Conv2d(3,32,5,padding=2) bn1 = nn.BatchNorm2d(32) bn1(conv1(data)).shape #不會(huì)改變feature map的形狀 #torch.Size([10, 32, 28, 28]) #輸入其他數(shù)字則報(bào)錯(cuò) #bn1 = nn.BatchNorm2d(10)同時(shí),BN層帶有β\betaβ和γ\gammaγ參數(shù),這兩個(gè)參數(shù)的數(shù)量也由特征圖的數(shù)量決定。例如,對(duì)有32張?zhí)卣鲌D的數(shù)據(jù)進(jìn)行歸一化時(shí),就需要使用32組不同的β\betaβ和γ\gammaγ參數(shù),總參數(shù)量為特征圖數(shù) * 2 = 64。
理論上BN能完全替代Dropout的功能。Dropout是課程中首次提到的概念,它是指在神經(jīng)網(wǎng)絡(luò)訓(xùn)練過(guò)程中,以概率p隨機(jī)地“沉默”一部分神經(jīng)元的技術(shù)。具體來(lái)說(shuō),當(dāng)整體神經(jīng)元數(shù)量為N時(shí),Dropout層會(huì)隨機(jī)選擇p * N個(gè)神經(jīng)元,讓這些神經(jīng)元在這一次訓(xùn)練中不再有效,當(dāng)相遇使選出的神經(jīng)元的權(quán)重變?yōu)?,使神經(jīng)元失活。在每次訓(xùn)練中,都有一組隨機(jī)挑選的神經(jīng)元被沉默,這樣會(huì)減弱全體神經(jīng)元之間的聯(lián)合適應(yīng)性,減少過(guò)擬合的可能性。在進(jìn)行測(cè)試時(shí),dropout會(huì)對(duì)所有神經(jīng)元上的系數(shù)都乘以概率ppp,用以模擬在訓(xùn)練中這些神經(jīng)元只有ppp的概率被用于向前傳播的狀況。
對(duì)于卷積神經(jīng)網(wǎng)絡(luò)來(lái)說(shuō),我們需要使用的類是Dropout2d,唯一需要輸出的參數(shù)是p,其輸入數(shù)據(jù)同樣是帶有samples維度的四維數(shù)據(jù)。不過(guò)在卷積中,Dropout不會(huì)以神經(jīng)元為單位執(zhí)行“沉默”,而是一次性斃掉一個(gè)通道。因此,當(dāng)通道總數(shù)不多時(shí),使用Dropout或Dropout中的p值太大都會(huì)讓CNN喪失學(xué)習(xí)能力,造成欠擬合。通常來(lái)說(shuō),使用Dropout之后模型需要更多的迭代才能夠收斂,所以我們總是從p=0.1,0.25開始嘗試,最多使用p=0.5,否則模型的學(xué)習(xí)能力會(huì)出現(xiàn)明顯下降。
CLASS torch.nn.Dropout2d (p=0.5, inplace=False)
data = torch.ones(size=(10,1,28,28)) conv1 = nn.Conv2d(1,32,5,padding=2) dp1 = nn.Dropout2d(0.5) dp1(conv1(data)).shape #不會(huì)改變feature map的形狀Dropout層本身不帶有任何需要學(xué)習(xí)的參數(shù),因此不會(huì)影響參數(shù)量。
接下來(lái),我們來(lái)實(shí)現(xiàn)一些由卷積層和池化層組成的神經(jīng)網(wǎng)絡(luò)架構(gòu),幫助大家回顧一下神經(jīng)網(wǎng)絡(luò)的定義過(guò)程,同時(shí)也加深對(duì)卷積、池化等概念的印象。
總結(jié)
以上是生活随笔為你收集整理的Lesson 16.5 在Pytorch中实现卷积网络(上):卷积核、输入通道与特征图在PyTorch中实现卷积网络(中):步长与填充的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: CatBoost讲解
- 下一篇: Lesson 16.6Lesson 16