日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Lesson 16.5 在Pytorch中实现卷积网络(上):卷积核、输入通道与特征图在PyTorch中实现卷积网络(中):步长与填充

發布時間:2025/4/5 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Lesson 16.5 在Pytorch中实现卷积网络(上):卷积核、输入通道与特征图在PyTorch中实现卷积网络(中):步长与填充 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

卷積神經網絡是使用卷積層的一組神經網絡。在一個成熟的CNN中,往往會涉及到卷積層、池化層、線性層(全連接層)以及各類激活函數。因此,在構筑卷積網絡時,需從整體全部層的需求來進行考慮。

1 二維卷積層nn.Conv2d

在PyTorch中,卷積層作為構成神經網絡的層,自然是nn.Module模塊下的類。

按照輸入數據的維度,卷積層可以分為三類:處理時序數據(samples,channels,length)的一維卷積(Conv1d),處理圖像數據的(samples,height,width,channels)的二維卷積(Conv2d),以及處理視頻數據(samples,frames,height,width,channels)的三維卷積(Conv3d)。時序數據是存在時間維度、受時間影響的三維數據,常被用于循環神經網絡中,但卷積也可以處理這種數據。視頻數據則是由多張圖像在時間軸上排列構成的,因此視頻數據可以被看做是圖像數據的序列。視頻數據中的frames是“幀數”,即一個視頻中圖像的總數量。在之后的課程中,我們會就視頻數據及其處理展開詳細說明。

按照卷積的操作和效果,又可分為普通卷積、轉置卷積、延遲初始化的lazyConv等等。最常用的是處理圖像的普通卷積nn.Conv2d。其類及其包含的超參數參數內容如下(注意Conv2d是大寫):

CLASS torch.nn.Conv2d (in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1,groups=1, bias=True, padding_mode=‘zeros’)

可以看到,除了之前的掃描操作之外,還有許多未知的參數。我們可以通過解析Conv2d的參數來說明卷積操作中的許多細節。需要說明的是,參數groups和dilation分別代表著分組卷積(Grouped Convolution)與膨脹卷積(Dilated Convolution),屬于卷積神經網絡的入門級操作,但卻比我們現在學習的內容更加復雜。之后,我們會詳細描述這兩種卷積網絡的原理與流程,現在,我們還是專注在普通卷積上。

1.1 卷積核尺寸kernel_size

kernel_size是我們第一個需要講解的參數,但同時也是最簡單的參數。

卷積核的高和寬一般用KHK_{H}KH?KWK_{W}KW?表示。在許多其他資料或教材中,如果把卷積核稱為filter過濾器,也可能使用字母FHF_{H}FH?FWF_{W}FW?來表示。卷積核的對整個卷積網絡的參數量有很大的影響。在之前的例子中,我們使用3X3結構的卷積核,每個卷積核就會攜帶9個參數(權重),如果我們使用3X2的結構,每個卷積核就會攜帶6個參數。

卷積核的尺寸應該如何選擇呢?如果你在使用經典架構,那經典架構的論文中所使用的尺寸就是最好的尺寸。如果你在寫自己的神經網絡,那3x3幾乎就是最好的選擇。對于這個幾乎完全基于經驗的問題,我可以提供以下幾點提示:

1、卷積核幾乎都是正方形。最初是因為在經典圖像分類任務中,許多圖像都被處理成正方形或者接近正方形的形狀(比如Fashion-MNIST,CIFAR,ImageNet)等等。如果你的原始圖像尺寸很奇妙(例如,非常長或非常寬),你可以使用與原圖尺寸比例一致的卷積核尺寸。

2、卷積核的尺寸最好是奇數,例如3x3,5x5,7x7等。這是一種行業慣例,傳統視覺中認為這是為了讓被掃描區域能夠“中心對稱”,無論經歷過多少次卷積變換,都能夠以正常比例還原圖像。

相對的,如果掃描區域不是中心對稱的,在多次進行卷積操作之后,像素會“偏移”導致圖像失真(由最左側圖像變換為右側的狀況)。然而,這種說法缺乏有效的理論基礎,如果你發現你的神經網絡的確更適合偶數卷積核(并且你能夠證明、或說服你需要說服的人來接受你的決定),那你可以自由使用偶數卷積核。目前為止,還沒有卷積核的奇偶會對神經網絡的效果造成影響的明確理論。
3、在計算機視覺中,卷積核的尺寸往往都比較小(相對的,在NLP中,許多網絡的卷積核尺寸都可以很大)。這主要是因為較小的卷積核所需要的訓練參數會更少,之后我們會詳細地討論關于訓練參數量的問題。

1.2 卷積的輸入與輸出:in_channels,out_channels,bias

除了kernel_size之外,還有兩個必填參數:in_channels與out_channels。簡單來說:in_channels是輸入卷積層的圖像的通道數或上一層傳入特征圖的數量,out_channels是指這一層輸出的特征圖的數量。這兩個數量我們都可以自己來確定,但具體掃描流程中的細節還需要理清。

在之前的例子中,我們在一張圖像上使用卷積核進行掃描,得到一張特征圖。這里的“被掃描圖像”是一個通道,而非一張彩色圖片。如果卷積核每掃描一個通道,就會得到一張特征圖,那多通道的圖像應該被怎樣掃描呢?會有怎樣的輸出呢?
在一次掃描中,我們輸入了一張擁有三個通道的彩色圖像。對于這張圖,擁有同樣尺寸、但不同具體數值的三個卷積核會分別在三個通道上進行掃描,得出三個相應的“新通道”。由于同一張圖片中不同通道的結構一定是一致的,卷積核的尺寸也是一致的,因此卷積操作后得出的“新通道”的尺寸也是一致的。
得出三個“新通道”后,我們將對應位置的元素相加,形成一張新圖,這就是卷積層輸入的三彩色圖像的第一個特征圖。這個操作對于三通道的RGB圖像、四通道的RGBA或者CYMK圖像都是一致的。只不過,如果是四通道的圖像,則會存在4個同樣尺寸、但數值不同的卷積核分別掃描4個通道。
因此,在一次掃描中,無論圖像本身有幾個通道,卷積核會掃描全部通道之后,將掃描結果加和為一張feature map。所以,一次掃描對應一個feature map,無關原始圖像的通道數目是多少,所以out_channels就是掃描次數,這之中卷積核的數量就等于輸入的通道數in_channels x 掃描次數out_channels。那對于一個通道,我們還有可能多次掃描,得出多個feature map嗎?當然有可能!卷積核的作用是捕捉特征,同一個通道上很有可能存在多個需要不同的卷積核進行捕捉的特征,例如,能夠捕捉到孔雀脖子輪廓的卷積核,就不一定能夠捕捉到色彩絢麗的尾巴。因此,對同一個通道提供多個不同的卷積核來進行多次掃描是很普遍的操作。不過,我們并不能對不同的通道使用不同的卷積核數量。比如,若規定掃描三次,則每次掃描時通道都會分別獲得自己的卷積核,我們不能讓卷積網絡執行類似于“紅色通道掃描2次,藍色通道掃描3次”的操作。
需要注意的是,當feature maps被輸入到下一個卷積層時,它也是被當作“通道”來處理的。不太嚴謹地說,feature map其實也可以是一種“通道”,雖然沒有定義到具體的顏色,但它其實也是每個元素都在[0,255]之間的圖。這是說,當feature map進入到下一個卷積層時,新卷積層上對所有feature map完成之后,也會將它們的掃描結果加和成一個新feature map。所以,在新卷積層上,依然是一次掃描對應生成一個feature map,無關之前的層上傳入的feature map有多少。
這其實與DNN中的線性層很相似,在線性層中,下一個線性層輸入的數目就等于上一個線性層的輸出的數目。我們來看一下具體的卷積層的代碼:

import torch from torch import nn #假設一組數據 #還記得嗎?雖然默認圖像數據的結構是(samples, height, width, channels) #但PyTorch中的圖像結構為(samples, channels, height, width),channels排在高和寬前面 #PyTorch中的類(卷積)無法讀取channels所在位置不正確的圖像 data = torch.ones(size=(10,3,28,28)) #10張尺寸為28*28的、擁有3個通道的圖像 conv1 = nn.Conv2d(in_channels = 3,out_channels = 6 #全部通道的掃描值被合并,6個卷積核形成6個feature map,kernel_size = 3) #這里表示3x3的卷積核 conv2 = nn.Conv2d(in_channels = 6 #對下一層網絡來說,輸入的是上層生成的6個feature map,out_channels = 4 #全部特征圖的掃描值被合并,4個卷積核形成4個新的feature map,kernel_size = 3) #conv3 = nn.Conv2d(in? out?) #通常在網絡中,我們不會把參數都寫出來,只會寫成: #conv1 = nn.Conv2d(3,6,3) #查看一下通過卷積后的數據結構 conv1(data).shape #torch.Size([10, 6, 26, 26]) conv2(conv1(data)).shape #torch.Size([10, 4, 24, 24]) #嘗試修改一下conv2的in_channels,看會報什么錯? conv2 = nn.Conv2d(in_channels = 10,out_channels = 4,kernel_size = 3) conv2(conv1(data))

掌握卷積層輸入輸出的結構,對于構筑卷積網絡十分重要。

在DNN中,每一層權重www都帶有偏差 ,我們可以決定是否對神經網絡加入偏差,在卷積中也是一樣的。在這里我們的權重就是卷積核,因此每個卷積層中都可以加入偏差,偏差的數量與掃描的數量一致。當我們得到feature_map后,如果有偏差的存在,我們會將偏差值加到feature_map的每個元素中,與矩陣 + 常數的計算方法一致。

當參數bias=True時,最終的feature_map是包含常數項的,反之則不包含。

1.3 特征圖的尺寸:stride,padding,padding_mode

stride

不知你是否注意到,在沒有其他操作的前提下,經過卷積操作之后,新生成的特征圖的尺寸往往是小于上一層的特征圖的。在之前的例子中,我們使用3X3的卷積核在6X6大小的通道上進行卷積,得到的特征圖是4X4。如果在4X4的特征圖上繼續使用3X3的卷積核,我們得到的新特征圖將是2X2的尺寸。最極端的情況,我們使用1X1的卷積核,可以得到與原始通道相同的尺寸,但隨著卷積神經網絡的加深,特征圖的尺寸是會越來越小的。

對于一個卷積神經網絡而言,特征圖的尺寸非常重要,它既不能太小,也不能太大。如果特征圖太小,就可能缺乏可以提取的信息,進一步縮小的可能性就更低,網絡深度就會受限制。如果特征圖太大,每個卷積核需要掃描的次數就越多,所需要的卷積操作就會越多,影響整體計算量。同時,卷積神經網絡往往會在卷積層之后使用全連接層,而全連接層上的參數量和輸入神經網絡的圖像像素量有很大的關系(記得我們之前說的嗎?全連接層需要將像素拉平,每一個像素需要對應一個參數,對于尺寸600X400的圖片需要2.4* 個參數),因此,在全連接層登場之前,我們能夠從特征圖中提取出多少信息,并且將特征圖的尺寸、也就是整體像素量縮小到什么水平,將會嚴重影響卷積神經網絡整體的預測效果和計算性能。也因此,及時了解特征圖的大小,對于卷積神經網絡的架構來說很有必要。
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?那怎么找出卷積操作后的特征圖的尺寸呢?假設特征圖的高為 ,特征圖的寬為 ,則對于上圖所示的卷積操作,我們可以有如下式子:


其中,HinH_{i n}Hin?WinW_{i n}Win?是輸入數據的高和寬,對于第一個卷積層而言,也就是輸入圖像的高和寬,對于后續的卷積層而言,就是前面的層所輸出的特征圖的高和寬。KHK_{H}KH?KWK_{W}KW?如同之前提到的,則代表在這一層與輸入圖像進行卷積操作的卷積核的高和寬。在之前的例子中,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。但在實際情況中,圖像的寬高往往是不一致的。因行業的約定俗成,卷積核的形狀往往是正方形,但理論上來說KHK_{H}KH?KWK_{W}KW?也可以不一致。在PyTorch中,卷積核的大小由參數Kernel_size確定。設置kernel_size=(3,3),即表示卷積核的尺寸為(3,3)。

這是特征圖尺寸計算的“最簡單”的情況。在實際進行卷積操作時,還有很多問題。比如說,現在每執行一次卷積,我們就將感受野向右移動一個像素,每掃描完一行,我們就向下移動一個像素,直到整張圖片都被掃描完為止。在尺寸較小的圖片上(比如,28X28像素),這樣做并沒有什么問題,但對于很大的圖片來說(例如600X800),執行一次卷積計算就需要掃描很久,并且其中有許多像素都是被掃描了很多次的,既浪費時間又浪費資源。于是,我們定義一個新的超參數:卷積操作中的“步長”,參數名稱stride(也譯作步幅)。

步長是每執行完一次卷積、或掃描完一整行后,向右、向下移動的像素數。水平方向的步長管理橫向移動,豎直方向的步長管理縱向移動。在pytorch中,當我們對參數stride輸入整數時,則默認調整水平方向掃描的步長。當輸入數組時,則同時調整水平和豎直方向上的步長。默認狀況下,水平和豎直方向的步長都是1,當我們把步長調整為(2,2),則每次橫向和縱向移動時,都會移動2個像素。
步長可以根據自己的需求進行調整,通常都設置為1-3之間的數字,也可以根據kernel_size來進行設置。在DNN中,我們把形如(sampels, features)結構的表數據中的列,也就是特征也叫做“維度”。對于表數據來說,要輸入DNN,則需要讓DNN的輸入層上擁有和特征數一樣數量的神經元,因此“高維”就意味著神經元更多。之前我們提到過,任何神經網絡中一個神經元上都只能有一個數字,對圖像來說一個像素格子就是一個神經元,因此卷積網絡中的“像素”就是最小特征單位,我們在計算機視覺中說“降維”,往往是減少一張圖上的像素量。參數步長可以被用于“降維”,也就是可以讓輸入下一層的特征圖像素量降低,特征圖的尺寸變得更小。

以上圖中的特征圖為例,通道尺寸為7X7,卷積核尺寸為3X3,若沒有步長,則會生成5X5的特征圖(7-3+1)。但在(2,2)的步長加持下,只會生成3X3的特征圖。帶步長的特征圖尺寸計算公式為:
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]代表橫向的步長,S[1]代表縱向的步長。步長可以加速對特征圖的掃描,并加速縮小特征圖,令計算更快。

Padding,Padding_mode

除了步長之外,還有一個常常在神經網絡中出現的問題:掃描不完全或掃描不均衡。

先來看掃描不完全,同樣還是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?當步長為3時,feature map的尺寸出現了小數,無法再包含完整的像素了。在圖像上來看也非常明顯,當步長為3的時候,向右移動一次后,就沒有足夠的圖像來進行掃描了。此時,我們不得不舍棄掉沒有掃描的最后一列像素。同時,在我們進行掃描的時候,如果我們的步長小于卷積核的寬度和長度,那部分像素就會在掃描的過程中被掃描多次,而邊緣的像素則只會在每次感受野來到邊緣時被掃描到,這就會導致“中間重邊緣輕”,掃描不均衡。為了解決這個問題,我們要采用“填充法”對圖像進行處理。
所謂填充法,就是在圖像的兩側使用0或其他數字填上一些像素,擴大圖像的面積,使得卷積核能將整個圖像盡量掃描完整。
在PyTorch中,填充與否由參數padding控制和padding_mode控制。padding接受大于0的正整數或數組作為參數,但通常我們只使用整數。padding=1則時在原通道的上下左右方向各添上1個像素,所以通道的尺寸實際上會增加2*padding。padding_mode則可以控制填充什么內容。在圖上展示的是zero_padding,也就是零填充,但我們也可以使用其他的填充方式。pytorch提供了兩種填充方式,0填充與環型填充。在padding_mode中輸入“zero”則使用0填充,輸入“circular”則使用環型填充(原始通道的數據復制出去用)。

需要注意的是,雖然pytorch官方文檔上說padding_mode可以接受四種填充模式,但實際上截至版本1.7.1,仍然只有"zeros"和"circular"兩種模式有效,其他輸入都會被當成零填充。如果想要使用填充鏡面翻轉值的reflection padding,則必須使用單獨定義的層nn.ReflectionPad2d,同樣的,"replicate"模式所指代的填充邊緣重復值需要使用單獨的類nn.ReplicationPad2d。
不難發現,如果輸入通道的尺寸較小,padding數目又很大,padding就可能極大地擴充通道的尺寸,并讓feature map在同樣的卷積核下變得更大。我們之前說,在沒有其他操作時feature map往往是小于輸入通道的尺寸的,而加入padding之后feature map就有可能大于輸入通道了,這在經典卷積網絡的架構中也曾出現過。通常來說,我們還是會讓feature map隨著卷積層的深入逐漸變小,這樣模型計算才會更快,因此,padding的值也不會很大,基本只在1~3之間。

實際上,Padding并不能夠保證圖像一定被掃描完全或一定均衡。看下面的例子:
不難發現,即便已經填充了一個像素,在現在的步長與卷積核大小下,依然無法將整張圖掃描完全。此時,有兩種解決方案,一種叫做"valid",一種叫做"same"。
valid模式就是放棄治療,對于掃描不到的部分,直接丟棄。“same”模式是指,在當前卷積核與步長設置下無法對全圖進行掃描時,對圖像右側和下邊進行“再次填充”,直到掃描被允許進行為止。從上圖看,same模式下的padding設置本來是1(即左右兩側都填上0),但在右側出現11、12、13和填充的0列無法被掃描的情況,則神經網絡自動按照kernel_size的需要,在右側再次填充一個0列,實現再一次掃描,讓全部像素都被掃描到。

這個操作看上去很智能,但遺憾的是只能夠在tensorflow中實現。對于PyTorch而言,沒有“same”的選項,只要無法掃描完全,一律拋棄。為什么這樣做呢?主要還是因為kernel_size的值一般比較小,所以被漏掉的像素點不會很多,而且基本集中在邊緣。隨著計算機視覺中所使用的圖片分辨率越來越高,圖像尺寸越來越大,邊緣像素包含關鍵信息的可能性會越來越小,丟棄邊緣就變得越來越經濟。對于一張28x28的圖像而言,丟棄2、3列或許會有不少信息損失,但對于720x1080的圖片而言,究竟是720x1078還是720x1080,其實并無太大區別。

那如果,你的圖像尺寸確實較小,你希望盡量避免未掃描的像素被丟棄,那你可以如下設置:

  • 1、卷積核尺寸控制在5x5以下,并且kernel_size > stride
  • 2、令2*padding > stride

這樣做不能100%避免風險,但可以大規模降低像素被丟棄的風險(個人經驗,無理論基礎)。

padding操作會影響通道的大小,因此padding也會改變feature map的尺寸,當padding中輸入的值為P時,特征圖的大小具體如下: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?我們在代碼中來感受一下特征圖尺寸的變化:

#記住我們的計算公式 #(H + 2p - K)/S + 1 #(W + 2p - K)/S + 1 #并且卷積網絡中,默認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 #驗證一下 conv1(data).shape #torch.Size([10, 6, 26, 26]) #conv2,輸入結構26*26 #(26 + 0 - 3)/1 + 1 = 24 #驗證 conv2(conv1(data)).shape #torch.Size([10, 10, 24, 24]) #conv3,輸入結構24*24 #(24 + 2 - 5)/2 + 1 = 11,掃描不完全的部分會被舍棄 conv3(conv2(conv1(data))).shape #torch.Size([10, 16, 11, 11]) #conv4,輸入結構11*11 #(11 + 4 - 5)/3 + 1 = 4.33,掃描不完全的部分會被舍棄 conv4(conv3(conv2(conv1(data)))).shape #torch.Size([10, 3, 6, 4])

padding和stride是卷積層最基礎的操作,之后的課程中我們還會有各種各樣的操作,他們都有可能會改變卷積層的輸出結構或參數量。對于入門而言,學會padding和stride也就足夠了。

2 池化層nn.MaxPool & nn.AvgPool

無論是調整步長還是加入填充,我們都希望能夠自由控制特征圖的尺寸。除了卷積層之外,另一種可以高效減小特征圖尺寸的操作是“池化”Pooling。池化是一種非常簡單(甚至有些粗暴的)的降維方式,經常跟在卷積層之后,用以處理特征圖。最常見的是最大池化(Max Pooling)和平均池化(Average Pooling)兩種操作,他們都很容易理解:
池化層也有核,但它的核沒有值,只有尺寸。在上圖之中,池化核的尺寸就是(2,2)。池化核的移動也有步長stride,但默認步長就等于它的核尺寸,這樣可以保證它在掃描特征圖時不出現重疊。當然,如果我們需要,我們也可以設置參數令池化核的移動步長不等于核尺寸,在行業中這個叫“Overlapping Pooling”,即重疊池化,但它不是非常常見。通常來說,對于特征圖中每一個不重疊的、大小固定的矩陣,池化核都按照池化的標準對數字進行計算或篩選。在最大池化中,它選出掃描區域中最大的數字。在平均池化中,它對掃描區域中所有的數字求平均。在加和池化中,它對掃描區域中所有的數字進行加和。

在這幾種簡單的方法中,最大池化是應用最廣泛的,也是比較有效的。考慮看看feature map中的信息是怎么得來的?feature map中每個像素上的信息都是之前的卷積層中,圖像與卷積核進行互相關操作的結果。對之前的卷積層而言,卷積核都是一致的,唯一不同的就是每次被掃描的區域中的像素值。像素值越大,說明顏色信息越多,像素值越小,說明圖像顯示約接近黑色,因此經過卷積層之后,像素值更高的點在原始圖像上更有可能帶有大量信息。MaxPooling通過摘取這些帶有更多信息的像素點,有效地將冗余信息消除,實現了特征的“提煉”。相對的,平均和加和的“提煉”效應就弱一些。
在PyTorch中,池化層也有多種選項,但這些多屬于“普通池化”的范圍。在傳統計算機視覺中,我們還有空間金字塔池化(Spatial Pyramid Pooling)等操作。
以MaxPool2d為例,其類和參數的詳情如下:

CLASS torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False,ceil_mode=False)

其中kernel_size就是池化核的尺寸,一般都設置為2X2或3X3。Padding參數與Stride參數一般都不填寫。需要提醒的是,池化層的步長一般與核尺寸保持一致,因此stride參數的默認值就是kernel_size。池化層對特征圖尺寸的影響,也符合我們之前所寫的這個公式: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?只不過此時的padding、kernel_size以及stride都是池化層的參數。我們在代碼中來看看:

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) #唯一需要輸入的參數,kernel_size=2,則默認使用(2,2)結構的核,默認步長stride=(2,2) # (12 + 0 - 2)/2 + 1 =6 #驗證一下 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])

事實上,使用(2,2)結構的池化層總是會將featrue map的行列都減半,將整個feature map的像素數減少3/4,因此它是非常有效的降維方法。

與卷積不同,池化層的操作簡單,沒有任何復雜的數學原理和參數,這為我們提供了精簡池化層代碼的可能性。通常來說,當我們使用池化層的時候,我們需要像如上所示的方法一樣來計算輸出特征圖的尺寸,但PyTorch提供的“Adaptive”相關類,允許我們輸入我們希望得到的輸出尺寸來執行池化:

CLASS torch.nn.AdaptiveMaxPool2d (output_size, return_indices=False)
CLASS torch.nn.AdaptiveAvgPool2d (output_size)

可以看到,在這兩個類中,我們可以輸入output_size,而池化層可以自動幫我們將特征圖進行裁剪。我們來試試看:

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) #輸入單一數字表示輸出結構為7x7,也可輸入數組 pool1(conv3(conv1(data))).shape #torch.Size([10, 16, 7, 7])

可惜的是,PyTorch官方沒有給出這兩個類具體是怎樣根據給出的輸出特征圖的尺寸數來倒推步幅和核尺寸的,在PyTorch官方論壇也有一些關于這個問題的討論,但都沒有給出非常令人滿意的結果。大家達成一致的是:如果輸入一些奇怪的數字,例如(3,7),那在池化的過程中是會出現比較多的數據損失的。

除了能夠有效降低模型所需的計算量、去除冗余信息之外,池化層還有特點和作用呢?

1、提供非線性變化。卷積層的操作雖然復雜,但本質還是線性變化,所以我們通常會在卷積層的后面增加激活層,提供ReLU等非線性激活函數的位置。但池化層自身就是一種非線性變化,可以為模型帶來一些活力。然而,學術界一直就池化層存在的必要性爭論不休,因為有眾多研究表明池化層并不能提升模型效果(有爭議)。
2、有一定的平移不變性(有爭議)。
3、池化層所有的參數都是超參數,不涉及到任何可以學習的參數,這既是優點(增加池化層不會增加參數量),也是致命的問題(池化層不會隨著算法一起進步)。
4、按照所有的規律對所有的feature map一次性進行降維,feature map不同其本質規律不然不同,使用同樣的規則進行降維,必然引起大估摸信息損失。

不過,在經典神經網絡架構中,池化層依然是非常關鍵的存在。如果感興趣的話,可以就池化與卷積的交互相應深入研究下去,繼續探索提升神經網絡效果的可能性。

3 Dropout2d與BatchNorm2d

Dropout與BN是神經網絡中非常經典的,用于控制過擬合、提升模型泛化能力的技巧,在卷積神經網絡中我們需要應用的是二維Dropout與二維BN。對于BN我們在前面的課程中有深入的研究,它是對數據進行歸一化處理的經典方法,對于圖像數據,我們所需要的類如下:

CLASS torch.nn.BatchNorm2d (num_features, eps=1e-05, momentum=0.1, affine=True,track_running_stats=True)

BN2d所需要的輸入數據是四維數據(第一個維度是samples),我們需要填寫的參數幾乎只有num_features一個。在處理表數據的BatchNorm1d里,num_features代表了輸入bn層的神經元個數,然而對于卷積網絡來說,由于存在參數共享機制,則必須以卷積核/特征圖為單位來進行歸一化,因此當出現在卷積網絡前后時,BatchNorm2d所需要輸入的是上一層輸出的特征圖的數量。例如:

data = torch.ones(size=(10,3,28,28)) conv1 = nn.Conv2d(3,32,5,padding=2) bn1 = nn.BatchNorm2d(32) bn1(conv1(data)).shape #不會改變feature map的形狀 #torch.Size([10, 32, 28, 28]) #輸入其他數字則報錯 #bn1 = nn.BatchNorm2d(10)

同時,BN層帶有β\betaβγ\gammaγ參數,這兩個參數的數量也由特征圖的數量決定。例如,對有32張特征圖的數據進行歸一化時,就需要使用32組不同的β\betaβγ\gammaγ參數,總參數量為特征圖數 * 2 = 64。

理論上BN能完全替代Dropout的功能。Dropout是課程中首次提到的概念,它是指在神經網絡訓練過程中,以概率p隨機地“沉默”一部分神經元的技術。具體來說,當整體神經元數量為N時,Dropout層會隨機選擇p * N個神經元,讓這些神經元在這一次訓練中不再有效,當相遇使選出的神經元的權重變為0,使神經元失活。在每次訓練中,都有一組隨機挑選的神經元被沉默,這樣會減弱全體神經元之間的聯合適應性,減少過擬合的可能性。在進行測試時,dropout會對所有神經元上的系數都乘以概率ppp,用以模擬在訓練中這些神經元只有ppp的概率被用于向前傳播的狀況。

對于卷積神經網絡來說,我們需要使用的類是Dropout2d,唯一需要輸出的參數是p,其輸入數據同樣是帶有samples維度的四維數據。不過在卷積中,Dropout不會以神經元為單位執行“沉默”,而是一次性斃掉一個通道。因此,當通道總數不多時,使用Dropout或Dropout中的p值太大都會讓CNN喪失學習能力,造成欠擬合。通常來說,使用Dropout之后模型需要更多的迭代才能夠收斂,所以我們總是從p=0.1,0.25開始嘗試,最多使用p=0.5,否則模型的學習能力會出現明顯下降。

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 #不會改變feature map的形狀

Dropout層本身不帶有任何需要學習的參數,因此不會影響參數量。

接下來,我們來實現一些由卷積層和池化層組成的神經網絡架構,幫助大家回顧一下神經網絡的定義過程,同時也加深對卷積、池化等概念的印象。

總結

以上是生活随笔為你收集整理的Lesson 16.5 在Pytorch中实现卷积网络(上):卷积核、输入通道与特征图在PyTorch中实现卷积网络(中):步长与填充的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。