Lesson 16.1016.1116.1216.13 卷积层的参数量计算,1x1卷积核分组卷积与深度可分离卷积全连接层 nn.Sequential全局平均池化,NiN网络复现
二 架構對參數量/計算量的影響
在自建架構的時候,除了模型效果之外,我們還需要關注模型整體的計算效率。深度學習模型天生就需要大量數據進行訓練,因此每次訓練中的參數量和計算量就格外關鍵,因此在設計卷積網絡時,我們希望相似預測效果下,參數量越少越好。為此我們必須理解卷積中的每層會如何影響模型整體的參數量和計算量。
模型參數是需要學習的參數,例如權重 和常數項 ,任何不需要學習、人為輸入的超參數都不在“參數量”的計算范圍內。對于卷積神經網絡中的任意元素(層或函數),有兩種方式影響模型的參數量:
1、這個層自帶參數,其參數量與該層的超參數的輸入有關
2、這個層會影響feature map的尺寸,影響整體像素量和計算量,從而影響全連接層的輸入
全連接層、bn通過第一種方式影響參數,而池化、padding、stride等操作則通過第二種方法影響參數,卷積層通過兩種方式影響參數,dropout、激活函數等操作不影響參數量。接下來,我們仔細說明一下卷積的參數量問題。
1 卷積層
1.1 參數量計算
一個卷積網絡的卷積層究竟包含多少參數量,就是由卷積核的尺寸kernel_size、輸入的通道數
in_channels,輸出的通道數out_channels(卷積核的數量)共同決定的。其參數量如下:Nparameters?=(KH?KW?Cin?)?Cout?+Cout?N_{\text {parameters }}=\left(K_{H} * K_{W} * C_{\text {in }}\right) * C_{\text {out }}+C_{\text {out }} Nparameters??=(KH??KW??Cin??)?Cout??+Cout??其中,加號前面的部分是權重222的數量,加號之后的是偏置bbb的數量。這個公式是怎么來的呢?
對任意卷積核而言,參數量由卷積核的寬和高決定。在一次掃描中,一個通道上的所有像素是共享權重的(共享卷積核),但不同的通道卻使用不同的權重(使用不同的卷積核),因此一次掃描中的權重=(1個卷積核上的參數量 * 需要掃描的通道數),其中需要掃描的通道數也就是被輸入卷積層的通道/特征圖數量 CinC_{i n}Cin?。同時,若存在偏置項,則每次掃描完畢之后,都要在新生成的特征圖上加上1個偏置,因此一次掃描中的全部參數 = (卷積核參數量 * 需要掃描的通道數) +1。在卷積層中,一次掃描會輸出一張特征圖,需要輸出CoutC_{out}Cout?張特征圖,就需要進行CoutC_{out}Cout?次掃描,因此整個卷積層上的參數量 = 掃描次數CoutC_{out}Cout? *(掃描一次的參數量 + 1),將CoutC_{out}Cout?乘到括號中去,就得到了上面的式子。你可能在其他教材或其他地方看見不同的寫法或計算邏輯,但是最終計算出的參數量一定是一致的。
來看兩個簡單的例子:
conv1 = nn.Conv2d(3,6,3) #(3 * 3 * 3)*6 + 6 conv2 = nn.Conv2d(6,4,3) #(3 * 3 * 6)*4 + 4 #檢查一下結果 conv1.weight.numel() #162 conv1.bias.numel() #6 conv2.weight.numel() #216 conv2.bias.numel() #4相對的,padding以及stride這些參數,不影響卷積層的所需要的參數量:
conv3 = nn.Conv2d(4,16,5,stride=2,padding=1) # (5*5*4)*16 + 16 conv4 = nn.Conv2d(16,3,5,stride=3,padding=2) # (5*5*16)*3 + 3 conv3.weight.numel() #1600 conv3.bias.numel() #16 conv4.weight.numel() #1200 conv4.bias.numel() #3從卷積層的參數計算公式來看,較大的卷積核、較多的輸入和輸出都會對參數量影響較大,由于實際中使用的卷積核都很小,所以真正對卷積核參數有影響力的是輸出和輸入的特征圖的數量。在較為復雜的架構中,卷積層的輸出數量可能達到256、512、甚至更大的數字,巨大的數字足以讓一個卷積層包含的參數達到百萬級別。例如VGG16中,比較深的幾個卷積層,他們的參數都在百萬以上。
通常來說,如果我們希望減小卷積神經網絡的參數量,那我們優先會考慮減少的就是輸出的特征圖數量。但隨著網絡加深,特征圖是越來越小的,學習到更多深入的信息,特征圖數量必然會增加(依照慣例,每經過一個池化層,就將特征圖數量翻倍)。因此,如果希望消減卷積層的參數量,可以考慮不使用那么多卷積+池化的組合(不要那么深的深度),如果一定要保持深度,則在第一層時就使用較小的特征圖數量,例如32。
1.2 大尺寸卷積核vs小尺寸卷積核
在深度卷積網絡使用的眾多場景中,我們都默認使用小卷積核,雖然我們列舉了各種各樣的理由,但在卷積網絡的發展歷史上,真正讓大家都放棄大卷積核、轉而使用小卷積核的是VGG的論文中提出的一個事實:大尺寸卷積核的效果可由多個小尺寸卷積核累積得到。具體如下:
在講解感受野時我們曾經使用過卷積層的俯視圖,假設我們有兩層核尺寸為3x3的卷積層,對于第二個卷積層輸出的特征圖而言,一個神經元映射到原始圖像上的感受野尺寸為5x5。同樣的圖像,假設我們使用一層5x5的卷積層,也可以得到5x5的感受野。同樣的,2個3x3卷積層將10x10的特征圖縮小為了6x6,一個5x5卷積層也將特征圖縮小到了6x6。可以說,在“捕獲的信息量”、“壓縮尺寸”這兩個層次上,兩個3x3的卷積層和一個5x5的卷積層獲得了一樣的結果。同理,我們也可以用三層3x3卷積核的卷積層替代一層7x7的卷積核,更大的卷積核亦然。
對比一下,一個5x5卷積層在一次掃描中需要的參數是25個,2個3x3卷積層卻只需要9 + 9 = 18個,因此兩個3x3卷積層所需要的參數更少。當特征圖數量巨大時,這一點點參數量的差異會被放大:假設輸入的特征圖數量為64,conv1和conv2輸出的特征圖也是64個,則有如下參數量:
兩個3x3的卷積層總共需要7萬+參數,而一個5x5的卷積層卻需要10萬+參數。對于VGG16這種重復架構的網絡而言,如果將所有的3x3卷積核都替換成5x5卷積核,那整體參數量將增加3個億。可見,3x3的兩個卷積層不僅加深了深度,一定程度上讓提取出的特征信息更“抽象”、更“復雜”,同時也讓參數量大幅減少。這又給了我們一個堅定使用小卷積核的理由。
1.3 1x1卷積核
在眾多的小卷積核中,小到極致的就是1x1尺寸的卷積核。
1x1的卷積核上只有一個權重,每次進行卷積操作時,該權重會與原始圖像中每個像素相乘,并得到特征圖上的新像素,因此1x1卷積也被叫做“逐點卷積”(Pointwise Convolution)。這種計算方式和矩陣 * 常數一致,同時,其本質也非常像我們在CV第一堂課時直接給像素直接乘上一個值來改變圖像的某些屬性的做法:
當卷積核尺寸設置為1x1之后,實際的卷積操作流程并沒有發生改變。在一次掃描中,一個通道上的所有像素是共享1x1卷積核中這唯一一個權重,不同的通道使用不同的權重,因此一次掃描中的權重 =(1 * 需要掃描的通道數),若存在偏置項,則每次掃描完畢之后,都要在新生成的特征圖上加上1個偏置,因此一次掃描中的全部參數 = (1 * 需要掃描的通道數) +1。在卷積層中,一次掃描會輸出一張特征圖,需要輸出Cout?C_{\text {out }}Cout??張特征圖,就需要進行Cout?C_{\text {out }}Cout??次掃描,因此整個卷積層上的參數量 = 掃描次數Cout?C_{\text {out }}Cout??*(掃描一次的參數量 + 1),因此1x1卷積核下的參數量為:Nparameters?=Cin??Cout?+Cout?N_{\text {parameters }}=C_{\text {in }} * C_{\text {out }}+C_{\text {out }}Nparameters??=Cin???Cout??+Cout??比起普通卷積核,1x1卷積核的參數量是原來的1k2\frac{1}{k^{2}}k21?,其中kkk是卷積核尺寸。但由于只有一個像素大小,所以1x1的卷積核不像普通卷積核一樣可以捕捉到特征圖/原始圖像上“一小塊的信息”,即它無法識別高和寬維度上相鄰元素之間構成的模式。然而,由于1x1卷積核可以在不增加也不減少信息的情況下維持特征圖的尺寸(相對的,普通的卷積層必須在增加padding的情況下才能夠維持特征圖的尺寸),因此1x1卷積核可以完整地將縮小特征圖時會損失掉的“位置信息”傳輸到下一層網絡中。這是它在提取特征這個過程中的小優勢。
在實際中,1x1卷積的重要作用之一就是加深CNN的深度。1x1卷積不會改變特征圖的尺寸,因此可以被用于加深CNN的深度,讓卷積網絡獲得更好的特征表達。這個性質被論文《Network in Network》所使用,并在架構NiN中發揮了重要的作用。NiN是AlexNet誕生不久之后被提出的架構,雖然也是2014年的論文,但早于VGG之前誕生,其架構如下:
在NiN的架構中,存在著一種特殊的層:MLP layer。雖然在NiN的論文中,MLP layer是被看成是一個獨立的單元來說明,但從其結構、操作和輸出的特征圖來看,MLP layer毫無疑問就是1x1的卷積層。NiN是以每個3x3卷積層后緊跟2個1x1卷積層組成一個block,并重復3個block達成9層卷積層架構的網絡。之后我們會簡單復現NiN的架構。
1x1卷積層不會改變特征圖的尺寸,這個性質雖然有用,但和使用padding的卷積層差異不是特別大。從今天的眼光來看,1x1卷積核在加深深度方面最關鍵的作用還是用在卷積層之間,用于調整輸出的通道數,協助大幅度降低計算量和參數量,從而協助加深網絡深度,這一作用又被稱為“跨通道信息交互”。
在VGG架構中,我們串聯不縮小特征圖大小的數個卷積層,每層輸出128、256或512個特征圖。當輸出256個特征圖的卷積層串聯時,輸入和輸出的特征圖數目很多,會使得整個卷積層的參數量變得很巨大。為此,我們可以如下圖右側所示的架構,在兩個含有256個特征圖的輸出之間使用(1x1, 3x3,1x1)的三個卷積層來代替原始的3x3卷積層。在右側架構中,雖然兩個含有256特征圖并沒有直接交互,但他們之間的信息通過1x1卷積層進行了交換,這也是這個架構的作用被稱為“跨通道信息交互”的原因。
這種在核尺寸為1x1的2個卷積層之間包裝其他卷積層的架構被稱為瓶頸設計(bottleneck design),也可簡稱叫做瓶頸或bottleneck,它被廣泛使用在各種深層網絡當中,代表了CNN目前為止最高水平架構之一的殘差網絡ResNet的論文中也使用了瓶頸架構。從直覺上來說,通道數目縮小意味著提取的信息量會變少,但瓶頸設計基本只會出現在超過100層的深度網絡中,實踐經驗證明這樣的架構在深度網絡中幾乎不會造成信息損失,但帶來的參數量的驟減卻是肯定的,因此瓶頸設計在現實中應用非常廣泛。以上圖的3x3卷積層的瓶頸設計為例,具體參數量如下所示:
可以看到,雖然最后都輸出了256個相同尺寸的特征圖,并且所有信息都經過了3x3的卷積核的掃描,但瓶頸架構所需要的參數量只有2.6萬個,一個3x3卷積層所需要的參數卻有59萬個。對于百層以上的深層神經網絡來說,這個參數差異足以讓人放棄一些性能,也要堅持使用瓶頸設計。
1.4 減少參數量:分組卷積與深度分離卷積
除了1x1卷積之外,分組卷積(Grouped Convolution)也是一種高效的減少參數量的形式。要理解分組卷積,我們最好先理解下面這張圖像(注意,在此圖中,卷積圖中的“格子”不代表具體像素數):
如圖所示,圖像左側是全連接層,右側是卷積層。對于普通的全連接層,連接是存在于神經元之間,上一層所有的神經元都與下一層所有的神經元相連接。在普通全連接層上,每個神經元都攜帶一個特征,一個連接上存在一個權重w,每個特征值都與權重值相乘并進入下一層,而下一層的神經元會將上層收到的全部信息進行加和,所以下一層神經元上的值是z=w1x1+w2x2……wnxnz=w_{1} x_{1}+w_{2} x_{2} \ldots \ldots w_{n} x_{n}z=w1?x1?+w2?x2?……wn?xn?。
帶著這個流程,我們來回顧一下在卷積層中,輸入的數個特征圖是怎樣被處理的:
我們使用XXX表示輸入的每張特征圖,對每張特征圖,尺寸為k*k的卷積核wkw_{k}wk?會分別進行掃描,生成與原通道數量相同的單獨的圖像(使用數學符號fff表示)。之后,全部的特征圖fff會被加和并加上偏置BBB,生成輸出卷積層的一張特征圖(用數學符號FFF表示)。如果使用數學符號,就可以表示為:F=wk,r?Xr+wk,g?Xg+wk,b?Xb+B=fr+fg+fb+B\begin{aligned} &F=w_{k, r} * X_{r}+w_{k, g} * X_{g}+w_{k, b} * X_{b}+B\\ &=f_{r}+f_{g}+f_{b}+B \end{aligned} ?F=wk,r??Xr?+wk,g??Xg?+wk,b??Xb?+B=fr?+fg?+fb?+B?這個公式看著是不是和z=wX+bz=\boldsymbol{w} \boldsymbol{X}+bz=wX+b很像呢?只不過這里www和XXX之間的星號不是相乘,而是卷積操作。實際上卷積層在每張特征圖上進行掃描,生成單獨的特征圖之后,也需要一個加和過程才能夠生成下一層的輸入,這一點和全連接層的“加和”過程是一模一樣的,因此,我們可以將卷積層也表達為“特征圖與特征圖相連”的形式。并且在這張圖像上,每個“連接”上都是一個k * k尺寸的卷積核。依據圖像再回顧卷積層的參數計算公式Nparameters?=(KH?Kw?Cin?)?Cout?+Cout?N_{\text {parameters }}=\left(K_{H} * K_{w} * C_{\text {in }}\right) * C_{\text {out }}+C_{\text {out }}Nparameters??=(KH??Kw??Cin??)?Cout??+Cout??,想必非常容易了(實際上,1x1卷積核就是所有連接上都只有一個w的卷積方式)。
結合這張圖與參數計算公式,很容易想到三種消減參數量的辦法:消減輸入特征圖數量,消減輸出特征圖數量,消減每個連接上的核的尺寸,或者消減輸入特征圖與輸出特征圖之間的連接數量。分組卷積就是通過給輸入特征圖及輸出特征圖分組來消減連接數量的卷積方式。
我們來看分組卷積的具體操作:首先,分組卷積需要一個超參數groups,當groups=g時,則表示“將特征圖分成g組”。我們可以讓groups為任何正整數,但慣例來說,特征圖數目一般都是偶數,因此分組數一般也是偶數。當確定g的數量之后,我們將輸入的特征圖和輸出的特征圖都分成g組。如上圖,輸入特征圖數量為4,輸出特征圖數量為8,g=2,于是我們便將輸入特征圖和輸出特征圖分別分為2組。分組之后,一組輸入特征圖負責生成一組輸出特征圖。如圖所示,上方兩個輸入特征圖只負責上方4個輸出特征圖,在不考慮偏置的情況下,這個操作需要的參數為 3 * 3 * 2 * 4 = 72。同理,下方兩個輸入特征圖只負責下方的4個輸出特征圖,因此需要的參數也為72個。分貝生成4個特征圖后,再堆疊在一起,形成總共8個輸出特征圖。
對于分組卷積而言,若用數學公式來表示,則有:group?1=(KH?Kw?Cin?g)?Cout?ggroup?2=(KH?Kw?Cin?g)?Cout?gtotal?=group?1+group2=(KH?Kw?Cin?g)?Cout?g?g=1g(KH?Kw?Cin??Cout?)\begin{aligned} \text { group } 1 &=\left(K_{H} * K_{w} * \frac{C_{\text {in }}}{g}\right) * \frac{C_{\text {out }}}{g} \\ \text { group } 2 &=\left(K_{H} * K_{w} * \frac{C_{\text {in }}}{g}\right) * \frac{C_{\text {out }}}{g} \\ \text { total } &=\text { group } 1+g r o u p 2 \\ &=\left(K_{H} * K_{w} * \frac{C_{\text {in }}}{g}\right) * \frac{C_{\text {out }}}{g} * g \\ &=\frac{1}{g}\left(K_{H} * K_{w} * C_{\text {in }} * C_{\text {out }}\right) \end{aligned} ?group?1?group?2?total??=(KH??Kw??gCin???)?gCout???=(KH??Kw??gCin???)?gCout???=?group?1+group2=(KH??Kw??gCin???)?gCout????g=g1?(KH??Kw??Cin???Cout??)?若考慮偏置,則有:group?1=(KH?Kw?Cing)?Cout?g+Cout?ggroup?2=(KH?Kw?Cing)?Cout?g+Coutgtotal?=group?1+group2=((KH?Kw?Cing)?Cout?g+Coutg)?g=1g(KH?Kw?Cin?Cout)+Cout\begin{aligned} \text { group } 1 &=\left(K_{H} * K_{w} * \frac{C_{i n}}{g}\right) * \frac{C_{\text {out }}}{g}+\frac{C_{\text {out }}}{g} \\ \text { group } 2 &=\left(K_{H} * K_{w} * \frac{C_{i n}}{g}\right) * \frac{C_{\text {out }}}{g}+\frac{C_{o u t}}{g} \\ \text { total } &=\text { group } 1+g r o u p 2 \\ &=\left(\left(K_{H} * K_{w} * \frac{C_{i n}}{g}\right) * \frac{C_{\text {out }}}{g}+\frac{C_{o u t}}{g}\right) * g \\ &=\frac{1}{g}\left(K_{H} * K_{w} * C_{i n} * C_{o u t}\right)+C_{o u t} \end{aligned} ?group?1?group?2?total??=(KH??Kw??gCin??)?gCout???+gCout???=(KH??Kw??gCin??)?gCout???+gCout??=?group?1+group2=((KH??Kw??gCin??)?gCout???+gCout??)?g=g1?(KH??Kw??Cin??Cout?)+Cout??不難發現,分組的存在不影響偏置,偏置只與輸出的特征圖數量有關。這個公式可以在pytorch中被輕松驗證。
直接報錯,無法運行。
可以看到,分組卷積可以有效減少參數量。雖然在之前的課程中沒有提到過,但實際上AlexNet所使用的架構中包含groups=2的分組卷積,因此在AlexNet的論文中架構看起來是這樣的:
groups參數最大可以取到和CinC_{in}Cin?或CoutC_{out}Cout?中較小的那個值一樣大。通常來說在應用分組卷積時,應當是輸入的特征圖尺寸更小,我們稱groups =CinC_{in}Cin?的分組卷積叫做“深度卷積(Depthwise Convolution)。深度卷積的參數量為:parameters?=1g(KH?Kw?Cin??Cout?)+Cout?\text { parameters }=\frac{1}{g}\left(K_{H} * K_{w} * C_{\text {in }} * C_{\text {out }}\right)+C_{\text {out }} ?parameters?=g1?(KH??Kw??Cin???Cout??)+Cout??其中 g=cing=c_{i n}g=cin?, 則有:
parameters?=KH?Kw?Cout?+Cout?\text { parameters }=K_{H} * K_{w} * C_{\text {out }}+C_{\text {out }} ?parameters?=KH??Kw??Cout??+Cout??比起普通卷積,參數量是原來的1Cin\frac{1}{C_{i n}}Cin?1?倍,當特征圖數量巨大時,分組卷積可以節省非常多的參數。當表示在圖像上,深度卷積所展示的鏈接方式為:
我們還可以將深度卷積與1x1卷積核結合使用。對輸入特征圖,我們首先進行深度卷積,產出一組特征圖,然后再這組特征圖的基礎上執行1x1卷積,對特征圖進行線性變換。兩種卷積打包在一起成為一個block,這個block就叫做“深度可分離卷積”(Depthwise separable convolution),也被稱為“分離卷積”(separable convolution)。對于深度可分離卷積的一個block,若不考慮偏置,則整個block的參數量為:
parameters?=KH?Kw?Cout?depth?+Cin?pair??Cout?pair?\text { parameters }=K_{H} * K_{w} * C_{\text {out }}^{\text {depth }}+C_{\text {in }}^{\text {pair }} * C_{\text {out }}^{\text {pair }} ?parameters?=KH??Kw??Cout?depth??+Cin?pair???Cout?pair??若原始卷積也不考慮偏置,則深度可分離卷積的參數比上原始卷積的參數的比例為:ratio?=KH?Kw?Cout?depth?+Cin?pair??Cout?pair?(KH?Kw?Cin?depth?)?Cout?pair?=Cout?depth?Cindepth??Cout?pair?+Cin?pair?KH?Kw?Cin?depth?\begin{aligned} &\text { ratio }=\frac{K_{H} * K_{w} * C_{\text {out }}^{\text {depth }}+C_{\text {in }}^{\text {pair }} * C_{\text {out }}^{\text {pair }}}{\left(K_{H} * K_{w} * C_{\text {in }}^{\text {depth }}\right) * C_{\text {out }}^{\text {pair }}}\\ &=\frac{C_{\text {out }}^{\text {depth }}}{C_{i n}^{\text {depth }} * C_{\text {out }}^{\text {pair }}}+\frac{C_{\text {in }}^{\text {pair }}}{K_{H} * K_{w} * C_{\text {in }}^{\text {depth }}} \end{aligned} ??ratio?=(KH??Kw??Cin?depth??)?Cout?pair??KH??Kw??Cout?depth??+Cin?pair???Cout?pair???=Cindepth???Cout?pair??Cout?depth???+KH??Kw??Cin?depth??Cin?pair????假設 1?11 * 11?1 卷積層不改變特征圖數量, 則有 Cinpair?=Coutpair?=Cout?depth?C_{i n}^{\text {pair }}=C_{o u t}^{\text {pair }}=C_{\text {out }}^{\text {depth }}Cinpair??=Coutpair??=Cout?depth??, 則有 :
=1Cindepth?+Coutpair?KH?Kw?Cindepth?=\frac{1}{C_{i n}^{\text {depth }}}+\frac{C_{o u t}^{\text {pair }}}{K_{H} * K_{w} * C_{i n}^{\text {depth }}} =Cindepth??1?+KH??Kw??Cindepth??Coutpair???當輸入特征圖與輸出特征圖數目相等的時候,分子與分母上的CinC_{in}Cin?就可以約掉了。注意,此處的KKK是深度卷積中的核尺寸,與1x1卷積無關。我們也可以在代碼中來驗證這個式子:
深度可分離卷積在2017年的論文《Xception: Deep Learning with Depthwise Separable
Convolutions》中被提出,現在是谷歌的深度學習模型GoogLeNet進化版中非常關鍵的block。論文中提出,分組卷積核深度可分離卷積不僅可以幫助卷積層減少參數量,更可以削弱特征圖與特征圖之間的聯系來控制過擬合。更多詳細內容可以參考課程附件中的論文。之后我們還會用到深度可分離卷積的相關內容。
在卷積層上,我們還可以進行更多更豐富的操作來減少參數量并提升模型的效果,之后若有機會我們會就其他卷積相關操作繼續展開來討論。
2 全連接層
卷積層上減少參數的操作非常豐富,但從經典架構來看,真正對CNN參數量“貢獻”巨大的是全連接層。數據在進入全連接層時,需要將所有像素拉平,而全連接層中的一個像素點就對應著一個參數,因此全連接層所攜帶大量參數。為什么卷積網絡里需要有全連接層呢?全連接層的作用主要有以下兩個:
1、作為分類器,實現對數據的分類。在卷積網絡中,卷積層和池化層的作用是提取特征,但提取出來的一張張特征圖與我們希望要的對應類別的輸出還相差很遠,為了將信息轉化為輸出,我們需要在卷積和池化層的后面加上能夠實現分類的結構,而全連接層是一切能夠實現分類的結構中,較為簡單、較為熟悉、同時成本也相對低的存在。本質上來說,卷積層提供了一系列有意義且穩定的特征值,構成了一個與輸入圖像相比維數更少的特征空間,而全連接層負責學習這個空間上的(可能是非線性的)函數關系,并輸出預測結果。(其他可能的選擇是,在卷積層后面放置一個SVM,或者放置其他機器學習算法作為分類器。)
2、作為整合信息的工具,將特征圖中的信息進行整合。基于卷積層的輸出來進行分類是一件困難的事情。由于卷積層輸出的結果是特征圖,因此我們有以下兩種方式來進行分類:
由于我們在計算設置上的各種努力,卷積網絡生成的特征圖每張都由不同的卷積核掃描生成,因此每張都攜帶不同的信息,讓不同特征圖對應不同類別,很可能會損失掉一些本能夠將樣本判斷正確的信息。而同時,又由于我們在參數設置上的各種努力(例如,使用奇數卷積核來保證圖像不會失真太多),卷積層生成的特征圖是自帶位置信息的:任意像素映射到自己的特征圖上的位置,與該像素的感受野映射到原圖上的位置幾乎是一致的,因此,若使用“特征圖的不同區域”的信息來進行類別劃分,可能會造成“只有某個區域的數據參與了某個標簽的預測”的情況。在進行預測之前,將所有可能的信息充分混合、進行學習,對預測效果有重大的意義。全連接層能夠確保所有信息得到恰當的“混合”,以保證預測的效果。
基于上面的兩個理由,再加上約定俗成,我們一般都會在形似AlexNet或VGG的網絡中包含全連接層。但通常來說,一旦有可以替換全連接層、并不影響模型效果的手段,大家就會嘗試將全連接層替代掉,因為全連接層所帶來的參數量對算力有很高的要求,并且,全連接層的存在讓CNN整體變得更容易過擬合。2012年時,Hinton團隊提出了dropout來控制全連接層的過擬合,后來又有了batch normalization方法,現在的CNN架構已經不太容易過擬合了。但是全連接層令人頭疼的參數量問題依然沒有解決。
在討論各種替換全連接層的可能性之前,我們先來看使用全連接層時性價比較高的參數組合:
更多層,還是更多神經元?
對于CNN中的全連接層來說,在一個層上增加足夠多的神經元,會比增加層效果更好。一般來說,CNN中的全連接層最多只有3-4層(包括輸出層),過于多的層會增加計算的負擔,還會將模型帶入過擬合的深淵。對于小型網絡,3層全連接層已是極限了。需要注意的是,在卷積層和全連接層的連接中,通常全連接的輸出神經元個數不會少于輸入的通道數。對于全連接層之間的連接,只要不是輸出層,也很少出現輸出神經元少于輸入神經元的情況。對全連接層而言,更大的參數代表了更高的復雜度、更強的學習能力、更大的過擬合可能,因此對于小型網絡來說,除非你的數據量龐大或數據異常復雜,盡量不使用1024以上的參數。
2.1 從卷積到全連接層
決定全連接層參數數量的有兩個因素:最后一個卷積層上的特征圖所含的像素量,以及我們在全連接層之間設定的輸出神經元個數。在自建網絡時,全連接層的參數輸入一直是無數卷積網絡新手的盲點。在互聯網資料齊全、代碼基本靠復制粘貼、架構基本照著架構圖寫的今天,許多深度學習的學習者甚至意識不到這個問題的存在(也有一部分理由是,tensorflow不需要輸入in_channels和in_features,因此許多深度學習學者可能沒有考慮過這個問題)。以下圖為例,假設輸入數據的結構為(10,3,229,229),沒有架構圖,請問兩個箭頭處應該分別填寫什么數字呢?
代碼留給大家,大家可以自己先試試看,如果你能夠順利讓數據通過Model、不報錯的話,則說明你的參數設置正確了。
首先你應當認識到,兩個箭頭處需要填寫的數字是一樣的,而這個數字就是最后一個卷積/池化層輸出的特征圖上所有的像素。通常在我們的架構中,這個輸入會被寫作如(64 * 7* 7)這樣的形式,其中64就是最后一個卷積/池化層上輸出的特征圖的數量,而7*7就是我們的特征圖尺寸。在一個架構中,輸出的特征圖數量是我們自己規定的,用上面的架構來看,就是128,因此唯一的問題就是特征圖尺寸是多少。
在之前的課程中,我們使用torchinfo包中的summary來自動計算特征圖尺寸,不難發現,要使用summary函數,前提是已經建好了能夠順利運行的model,但尷尬的是,當我們不知道架構中紅色箭頭處應該填寫什么數字時,model是不可能跑通的。那怎么在模型架構不完整的情況下,找出最后一個池化層/卷積層上輸出的特征圖的尺寸呢?一種簡單的方法是,將Model中所有的線性層都注釋掉,只留下卷積層,然后將model輸入summary進行計算,但有更簡單的方法,使用另一種構筑神經網路架構的方式:nn.Sequential。
nn.Sequential是一種非常簡單的構筑神經網絡的方式,它可以將“以序列方式從前往后運行的層”打包起來,組合成類似于機器學習中的管道(Pipeline)的結構,以此避開建立類、繼承類等稍微有些抽象的python代碼。大多數深度學習課程和教材在最開始的時候就會介紹它,并且一直以它作為例子運行各類神經網絡,我們來看具體的例子:
data = torch.ones(size=(10,3,229,229)) #不使用類,直接將需要串聯的網絡、函數等信息寫在一個“序列”里 #重現上面的4個卷積層、2個池化層的架構 net = nn.Sequential(nn.Conv2d(3,6,3),nn.ReLU(inplace=True),nn.Conv2d(6,4,3),nn.ReLU(inplace=True),nn.MaxPool2d(2),nn.Conv2d(4,16,5,stride=2,padding=1),nn.ReLU(inplace=True),nn.Conv2d(16,3,5,stride=3,padding=2),nn.ReLU(inplace=True),nn.MaxPool2d(2)) #nn.Sequential組成的序列不是類,因此不需要實例化,可以直接輸入數據 net(data).shape #torch.Size([10, 3, 9, 9])看見最終的特征圖的結構了嗎?3?9?93 * 9 * 93?9?9就是我們需要輸入到紅色箭頭處的數字。同樣的,我們也可以將nn.Sequential放入torch_receptive_field查看感受野的尺寸:
from torch_receptive_field import receptive_field #net不是類所以不需要實例化 rfdict = receptive_field(net,(3,229,229)) #------------------------------------------------------------------------------ # Layer (type) map size start jump receptive_field #============================================================================== # 0 [229, 229] 0.5 1.0 1.0 # 1 [227, 227] 1.5 1.0 3.0 # 2 [227, 227] 1.5 1.0 3.0 # 3 [225, 225] 2.5 1.0 5.0 # 4 [225, 225] 2.5 1.0 5.0 # 5 [112, 112] 3.0 2.0 6.0 # 6 [55, 55] 5.0 4.0 14.0 # 7 [55, 55] 5.0 4.0 14.0 # 8 [19, 19] 5.0 12.0 30.0 # 9 [19, 19] 5.0 12.0 30.0 # 10 [9, 9] 11.0 24.0 42.0 #==============================================================================使用nn.Sequential結構的好處多多,最明顯的就是代碼量的減少,至少在nn.Sequential中,我們可以不用再寫一堆self.,而只需要按順序列舉數據會通過的類就可以了。由于nn.Sequential表示的是各個元素之間的串聯計算過程,因此我們需要將架構寫成輸入數據的“運行管道”,讓數據能夠從上向下進行計算,因此我們實際上是將網絡架構(各類層)和計算過程(激活函數、數據處理方式BN等)混寫,這樣做代碼量會減少很多。當然,混寫既是優點,也是缺點——混寫之后,卷積層架構將不再像左側一樣清晰明顯,比較不容易看出分割的blocks,因此在深度學習的入門階段,需要熟悉網絡架構的時候,我們并沒有讓大家采用nn.Sequential的形式來構建網絡。
在較為復雜的網絡架構中,我們通常利用nn.Sequential來區分網絡的不同部分:例如,在普通CNN中,卷積層、池化層負責的是特征提取,全連接層負責的是整合信息、進行預測,因此我們可以使用nn.Sequential來區別這兩部分架構。
以VGG16為例,使用nn.Sequential的架構如下:
可以看到,forward函數變得異常簡單,整體代碼量也縮小了。在構筑架構時,我們可以將代碼稍作整理,讓其更接近我們看到的網絡架構,但是summary函數并不會對架構和層做這么多的區分,因此summary中的架構看上去就更長更深了。在實際構筑自己的神經網絡時,我們常常會使用nn.Sequential來調試卷積層架構,并不斷查看感受野的變化。
2.2 代替全連接層:1x1卷積核與全局平均池化(GAP)
雖然全連接層很有用,但它的參數量帶來的計算成本的確是一個很大的問題。因此,研究者們曾經嘗試找出各種方法,用來替代全連接層。其中流傳比較廣泛的方法之一,就是使用1x1卷積核來進行替代全連接層。雖然大部分持有此觀點的材料的描述都模糊不清、甚至有胡言亂語之嫌,但是人們還是對1x1卷積核替代全連接層的效果深信不疑。那究竟可不可以呢?可以,但這么做的價值其實微乎其微,除了特殊的應用場景之外,既沒有經典架構、也沒有實際應用會這么做。我們來看看是怎么回事。
為什么人們說1x1卷積核可以替代全連接層呢?還記得之前繪制過的普通全連接層與卷積層在“鏈接”數量上的對比圖嗎?
對于普通卷積層而言,每個連接上的w就是完整的卷積核,一般至少帶有9個參數。當使用1x1卷積核時,每個w中就只有一個參數,這就讓1x1卷積層和普通全連接層更加相似,只不過普通全連接層的連接是在神經元與神經元之間,而1x1卷積層的連接是在特征圖與特征圖之間。因此從數學公式來看,全連接層和1x1的卷積層之間是可以互相轉換的。對于卷積層來說,只要讓特征圖的尺寸為1x1,再讓卷積核的尺寸也為1x1,就可以實現和普通全連接層一模一樣的計算了。
在計算機視覺中,不包含全連接層,只有卷積層和池化層的卷積網絡被叫做全卷積網絡(fullyconvolutional network,FCN)。在無數減少全連接層的努力中,1x1卷積核可以在架構上完全替代掉全連接層,來看下面的例子:
這是一個4分類的例子。在卷積和池化層之后,我們得到的特征圖是(5x5)共16張,通過三個線性層(包括輸出層)、或1個5x5卷積層+3個1x1卷積層,都可以將最終輸出結果轉化為我們需要的4個類別。
在1x1卷積層替代全連接層的例子中,輸出的特征圖的個數必須和全連接層上的神經元個數一致,這樣才能使用輸出的特征圖“替代”掉全連接層,但在這樣的要求下,不難發現,卷積層所需要的參數量是更大的。因此,使用1x1卷積層代替全連接層不能減少參數量。同時,沒有證據能表明將全連接層更換成1x1卷積層之后能夠提升模型的擬合效果,所謂“跨通道信息交互”等等的效果,在之前的課程中已經說明,和參數量以及是否替換全連接層都無關。因此,雖然1x1卷積核可以替換全連接層,但這么做的價值其實非常小。
如果要說1x1卷積核替換全連接層之后帶來的最大的好處,那就是解放了輸入層對圖像尺寸的限制。在之前的學習中,我們已經知道卷積層和全連接層的連接處需要進行數據的“拉平”處理,并且需要人為手動輸入全連接層的神經元數量,一旦無法正確計算卷積層輸出的特征圖尺寸,網絡架構就會報錯,無法運行。因為這個特點,輸入卷積網絡的圖片的尺寸總是被嚴格規定的,一旦改變輸入尺寸,網絡架構就不能再使用了。而當整個架構中都只有卷積層的時候,無論如何調整輸入圖像的尺寸,網絡都可以運行。比如上面的架構,輸入尺寸是(3, 14, 14),最后輸出的結果是4和(4, 1, 1)。現在我們將輸入尺寸修改為(3,16,16),在普通CNN的架構中,數據就會因為無法通過全連接層而報錯,但在FCN里就可以順暢運行下去,最終輸出(4, 2, 2)的結果。
無論多大的圖像都能輸出結果,這個性質在物體檢測的實例中有一個有趣的應用。在物體檢測中,我們需要判斷一個物體位于圖像的什么位置,因此需要使用小于圖像尺寸的正方形區域對圖像進行“滑窗”識別。在每一個窗口里,我們都需要執行一個單獨的卷積網絡,用以判斷“物體是否在這個范圍內”。
假設現在我們建立的網絡是帶有線性層的CNN網絡,輸入尺寸為14x14。那對于16x16尺寸的圖像,就需要將下面四個14x14的區域分別輸入CNN來進行判斷,對每個區域輸出“是”或“否”的結果。
但對于FCN而言,我們可以直接將這一張16x16的輸入整個網絡,最終會輸出(2,2)大小的特征圖。由于卷積網絡層可以保留位置信息,所以這(2,2)的特征圖中,每個像素的感受野都可以對應到原始圖像中的相應區域,相當于使用一個網絡一次性完成了對整個圖像的四次掃描,并得到了四個相應的結果(2x2)。這種掃描方式比將圖像切分成14x14的四塊再運行4個CNN要高效得多,不過該應用僅限于物體檢測中需要“滑窗”的場景。
總之,1x1卷積核的確可以替代全連接層,但效益不高。有些資料或文獻會主張NiN網絡使用1x1卷積替代了全連接層,但這一點不是非常嚴謹。在NiN的架構中,最后一個普通核尺寸的卷積核之后跟著的是MLP layers,并且這些MLP Layers最終將特征圖數目縮小到了softmax公式要求的10個,因此說編號13、14的MLP Layers替代了普通CNN中全連接層的位置,也不是沒有道理。不過在論文中,實現了全連接層的兩個目標“整合信息”、“輸出結果”的實際上是跟在MLP layers后的全局平均池化層(Global Average Pooling)。在NiN論文中,作者也明確表示,用來替代全連接層的是GAP層,我們來看看GAP層是如何運作的。
從之前1x1卷積核的例子來看,不難發現,只要在網絡架構的最后能夠將輸出結果變成softmax函數可接受的格式,比如(n_class,1),并且確定用于生成這些輸入值的信息是從之前的特征圖中整合出來的,那任意架構在理論上來說都足以替代全連接層。GAP層就是這樣的一個例子。GAP層的本質是池化層,它使用池化方式是平均池化,它的職責就是將上一層傳入的無論多少特征圖都轉化成(n_class,1, 1)結構。為了能夠將無論什么尺寸的特征圖化為1x1的尺寸,GAP層所使用的核尺寸就等于輸入的特征圖尺寸。在NiN網絡中,最后一個卷積層的輸出是(10, 7, 7),因此全局平均池化層的核尺寸也是7x7,由于只能掃描一次,因此全局平均池化層不設置參數步長,一般也不會設置padding。
在PyTorch中,沒有專門的GAP類,但我們可以使用普通的平均池化層,并令這個池化層的核尺寸為上層輸入的特征圖尺寸,以此來模擬全局平均池化。
使用1x1卷積核連接GAP的方式,NiN網絡中完全沒有使用全連接層,這讓NiN網絡整體的參數量減少不少,同時,GAP作為池化層,沒有任何需要學習的參數,這讓GAP的抗過擬合能力更強。在論文中,作者們還做了對比實驗,證明GAP方法抗過擬合的能力更強:
下一節,我們來復現一下NiN網絡的架構。
3 NiN網絡的復現
在之前的課程中,我們已經見過很多次NiN網絡的架構了,經過這一節關于減少參數量和提升模型性能的討論,我們已經對NiN網絡中的每個細節都比較熟悉。現在,我們就使用nn.Sequential來打包實現一下NiN網絡。
代碼如下:
作為9層卷積層、最大特征圖數目達到192的網絡,NiN的參數量在百萬之下,可以說都是歸功于沒有使用全連接層。不過,1x1卷積層所帶來的參數量也不少,因此NiN可以說是在各方面都中規中矩的網絡。從今天的眼光來看,NiN網絡最大的貢獻就是在于讓人們意識到了1x1卷積層可能的用途,并且將“舍棄線性層”的議題擺在了研究者面前。受到NiN網絡啟發而誕生GoogLeNet以及ResNet都使用了1x1卷積層,并且在各種消減參數的操作下使網絡變得更加深。
到這里,我們已經講述了好幾種降低參數量的操作,你還知道其他常用的降低參數量或計算量的手段嗎?歡迎隨時在群內與小伙伴們分享最新的論文和研究成果。從下一節開始,我們將開始了解視覺領域最前沿的數個模型。
總結
以上是生活随笔為你收集整理的Lesson 16.1016.1116.1216.13 卷积层的参数量计算,1x1卷积核分组卷积与深度可分离卷积全连接层 nn.Sequential全局平均池化,NiN网络复现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Lesson 16.6Lesson 16
- 下一篇: 金融风控实战—模型可解释之shap