深度學(xué)習(xí)基礎(chǔ)入門篇[8]::計(jì)算機(jī)視覺與卷積神經(jīng)網(wǎng)絡(luò)、卷積模型CNN綜述、池化講解、CNN參數(shù)計(jì)算
1.計(jì)算機(jī)視覺與卷積神經(jīng)網(wǎng)絡(luò)
1.1計(jì)算機(jī)視覺綜述
計(jì)算機(jī)視覺作為一門讓機(jī)器學(xué)會(huì)如何去“看”的學(xué)科,具體的說,就是讓機(jī)器去識(shí)別攝像機(jī)拍攝的圖片或視頻中的物體,檢測出物體所在的位置,并對目標(biāo)物體進(jìn)行跟蹤,從而理解并描述出圖片或視頻里的場景和故事,以此來模擬人腦視覺系統(tǒng)。因此,計(jì)算機(jī)視覺也通常被叫做機(jī)器視覺,其目的是建立能夠從圖像或者視頻中“感知”信息的人工系統(tǒng)。
計(jì)算機(jī)視覺技術(shù)經(jīng)過幾十年的發(fā)展,已經(jīng)在交通(車牌識(shí)別、道路違章抓拍)、安防(人臉閘機(jī)、小區(qū)監(jiān)控)、金融(刷臉支付、柜臺(tái)的自動(dòng)票據(jù)識(shí)別)、醫(yī)療(醫(yī)療影像診斷)、工業(yè)生產(chǎn)(產(chǎn)品缺陷自動(dòng)檢測)等多個(gè)領(lǐng)域應(yīng)用,影響或正在改變?nèi)藗兊娜粘I詈凸I(yè)生產(chǎn)方式。未來,隨著技術(shù)的不斷演進(jìn),必將涌現(xiàn)出更多的產(chǎn)品和應(yīng)用,為我們的生活創(chuàng)造更大的便利和更廣闊的機(jī)會(huì)。
圖1 計(jì)算機(jī)視覺技術(shù)在各領(lǐng)域的應(yīng)用
1.2 計(jì)算機(jī)視覺的發(fā)展歷程
計(jì)算機(jī)視覺的發(fā)展歷程要從生物視覺講起。對于生物視覺的起源,目前學(xué)術(shù)界尚沒有形成定論。有研究者認(rèn)為最早的生物視覺形成于距今約7億年前的水母之中,也有研究者認(rèn)為生物視覺產(chǎn)生于距今約5億年前寒武紀(jì)。寒武紀(jì)生物大爆發(fā)的原因一直是個(gè)未解之謎,不過可以肯定的是在寒武紀(jì)動(dòng)物具有了視覺能力,捕食者可以更容易地發(fā)現(xiàn)獵物,被捕食者也可以更早的發(fā)現(xiàn)天敵的位置。視覺系統(tǒng)的形成有力地推動(dòng)了食物鏈的演化,加速了生物進(jìn)化過程,是生物發(fā)展史上重要的里程碑。經(jīng)過幾億年的演化,目前人類的視覺系統(tǒng)已經(jīng)具備非常高的復(fù)雜度和強(qiáng)大的功能,人腦中神經(jīng)元數(shù)目達(dá)到了1000億個(gè),這些神經(jīng)元通過網(wǎng)絡(luò)互相連接,這樣龐大的視覺神經(jīng)網(wǎng)絡(luò)使得我們可以很輕松的觀察周圍的世界,如 圖2 所示。
圖2 人類視覺感知
對人類來說,識(shí)別貓和狗是件非常容易的事。但對計(jì)算機(jī)來說,即使是一個(gè)精通編程的高手,也很難輕松寫出具有通用性的程序(比如:假設(shè)程序認(rèn)為體型大的是狗,體型小的是貓,但由于拍攝角度不同,可能一張圖片上貓占據(jù)的像素比狗還多)。那么,如何讓計(jì)算機(jī)也能像人一樣看懂周圍的世界呢?研究者嘗試著從不同的角度去解決這個(gè)問題,由此也發(fā)展出一系列的子任務(wù),如 圖3 所示。
圖3 計(jì)算機(jī)視覺子任務(wù)示意圖
-
(a) Image Classification: 圖像分類,用于識(shí)別圖像中物體的類別(如:bottle、cup、cube)。
-
(b) Object Localization: 目標(biāo)檢測,用于檢測圖像中每個(gè)物體的類別,并準(zhǔn)確標(biāo)出它們的位置。
-
(c) Semantic Segmentation: 語義分割,用于標(biāo)出圖像中每個(gè)像素點(diǎn)所屬的類別,屬于同一類別的像素點(diǎn)用一個(gè)顏色標(biāo)識(shí)。
-
(d) Instance Segmentation: 實(shí)例分割,值得注意的是,目標(biāo)檢測任務(wù)只需要標(biāo)注出物體位置,而實(shí)例分割任務(wù)不僅要標(biāo)注出物體位置,還需要標(biāo)注出物體的外形輪廓。
這里以圖像分類任務(wù)為例,為大家介紹計(jì)算機(jī)視覺技術(shù)的發(fā)展歷程。在早期的圖像分類任務(wù)中,通常是先人工提取圖像特征,再用機(jī)器學(xué)習(xí)算法對這些特征進(jìn)行分類,分類的結(jié)果強(qiáng)依賴于特征提取方法,往往只有經(jīng)驗(yàn)豐富的研究者才能完成,如 圖4 所示。
圖4 早期的圖像分類任務(wù)
在這種背景下,基于神經(jīng)網(wǎng)絡(luò)的特征提取方法應(yīng)運(yùn)而生。Yann LeCun是最早將卷積神經(jīng)網(wǎng)絡(luò)應(yīng)用到圖像識(shí)別領(lǐng)域的,其主要邏輯是使用卷積神經(jīng)網(wǎng)絡(luò)提取圖像特征,并對圖像所屬類別進(jìn)行預(yù)測,通過訓(xùn)練數(shù)據(jù)不斷調(diào)整網(wǎng)絡(luò)參數(shù),最終形成一套能自動(dòng)提取圖像特征并對這些特征進(jìn)行分類的網(wǎng)絡(luò),如 圖5 所示。
圖5 早期的卷積神經(jīng)網(wǎng)絡(luò)處理圖像任務(wù)示意
這一方法在手寫數(shù)字識(shí)別任務(wù)上取得了極大的成功,但在接下來的時(shí)間里,卻沒有得到很好的發(fā)展。其主要原因一方面是數(shù)據(jù)集不完善,只能處理簡單任務(wù),在大尺寸的數(shù)據(jù)上容易發(fā)生過擬合;另一方面是硬件瓶頸,網(wǎng)絡(luò)模型復(fù)雜時(shí),計(jì)算速度會(huì)特別慢。
目前,隨著互聯(lián)網(wǎng)技術(shù)的不斷進(jìn)步,數(shù)據(jù)量呈現(xiàn)大規(guī)模的增長,越來越豐富的數(shù)據(jù)集不斷涌現(xiàn)。另外,得益于硬件能力的提升,計(jì)算機(jī)的算力也越來越強(qiáng)大。不斷有研究者將新的模型和算法應(yīng)用到計(jì)算機(jī)視覺領(lǐng)域。由此催生了越來越豐富的模型結(jié)構(gòu)和更加準(zhǔn)確的精度,同時(shí)計(jì)算機(jī)視覺所處理的問題也越來越豐富,包括分類、檢測、分割、場景描述、圖像生成和風(fēng)格變換等,甚至還不僅僅局限于2維圖片,包括視頻處理技術(shù)和3D視覺等。
1.3 卷積神經(jīng)網(wǎng)絡(luò)
卷積神經(jīng)網(wǎng)絡(luò)是目前計(jì)算機(jī)視覺中使用最普遍的模型結(jié)構(gòu)。圖6 是一個(gè)典型的卷積神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu),多層卷積和池化層組合作用在輸入圖片上,在網(wǎng)絡(luò)的最后通常會(huì)加入一系列全連接層,ReLU激活函數(shù)一般加在卷積或者全連接層的輸出上,網(wǎng)絡(luò)中通常還會(huì)加入Dropout來防止過擬合。
圖6 卷積神經(jīng)網(wǎng)絡(luò)經(jīng)典結(jié)構(gòu)
-
卷積層:卷積層用于對輸入的圖像進(jìn)行特征提取。卷積的計(jì)算范圍是在像素點(diǎn)的空間鄰域內(nèi)進(jìn)行的,因此可以利用輸入圖像的空間信息。卷積核本身與輸入圖片大小無關(guān),它代表了對空間鄰域內(nèi)某種特征模式的提取。比如,有些卷積核提取物體邊緣特征,有些卷積核提取物體拐角處的特征,圖像上不同區(qū)域共享同一個(gè)卷積核。當(dāng)輸入圖片大小不一樣時(shí),仍然可以使用同一個(gè)卷積核進(jìn)行操作。
-
池化層:池化層通過對卷積層輸出的特征圖進(jìn)行約減,實(shí)現(xiàn)了下采樣。同時(shí)對感受域內(nèi)的特征進(jìn)行篩選,提取區(qū)域內(nèi)最具代表性的特征,保留特征圖中最主要的信息。
-
激活函數(shù):激活函數(shù)給神經(jīng)元引入了非線性因素,對輸入信息進(jìn)行非線性變換,從而使得神經(jīng)網(wǎng)絡(luò)可以任意逼近任何非線性函數(shù),然后將變換后的輸出信息作為輸入信息傳給下一層神經(jīng)元。
-
全連接層:全連接層用于對卷積神經(jīng)網(wǎng)絡(luò)提取到的特征進(jìn)行匯總,將多維的特征映射為二維的輸出。其中,高維代表樣本批次大小,低維代表分類或回歸結(jié)果。
2. 池化
2.1 基礎(chǔ)概念(平均池化和最大池化)
在圖像處理中,由于圖像中存在較多冗余信息,可用某一區(qū)域子塊的統(tǒng)計(jì)信息(如最大值或均值等)來刻畫該區(qū)域中所有像素點(diǎn)呈現(xiàn)的空間分布模式,以替代區(qū)域子塊中所有像素點(diǎn)取值,這就是卷積神經(jīng)網(wǎng)絡(luò)中池化(pooling)操作。
池化操作對卷積結(jié)果特征圖進(jìn)行約減,實(shí)現(xiàn)了下采樣,同時(shí)保留了特征圖中主要信息。比如:當(dāng)識(shí)別一張圖像是否是人臉時(shí),我們需要知道人臉左邊有一只眼睛,右邊也有一只眼睛,而不需要知道眼睛的精確位置,這時(shí)候通過池化某一片區(qū)域的像素點(diǎn)來得到總體統(tǒng)計(jì)特征會(huì)顯得很有用。
池化的幾種常見方法包括:平均池化、最大池化、K-max池化。其中平均池化和最大池化如 圖1 所示,K-max池化如 圖2 所示。
圖1 平均池化和最大池化
-
平均池化: 計(jì)算區(qū)域子塊所包含所有像素點(diǎn)的均值,將均值作為平均池化結(jié)果。如 圖1(a),這里使用大小為$2\times2$的池化窗口,每次移動(dòng)的步幅為2,對池化窗口覆蓋區(qū)域內(nèi)的像素取平均值,得到相應(yīng)的輸出特征圖的像素值。池化窗口的大小也稱為池化大小,用$k_h \times k_w$表示。在卷積神經(jīng)網(wǎng)絡(luò)中用的比較多的是窗口大小為$2 \times 2$,步幅為2的池化。
-
最大池化: 從輸入特征圖的某個(gè)區(qū)域子塊中選擇值最大的像素點(diǎn)作為最大池化結(jié)果。如 圖1(b),對池化窗口覆蓋區(qū)域內(nèi)的像素取最大值,得到輸出特征圖的像素值。當(dāng)池化窗口在圖片上滑動(dòng)時(shí),會(huì)得到整張輸出特征圖。
圖2 K-max池化
- K-max池化: 對輸入特征圖的區(qū)域子塊中像素點(diǎn)取前K個(gè)最大值,常用于自然語言處理中的文本特征提取。如圖2,從包含了4個(gè)取值的每一列中選取前2個(gè)最大值就得到了K最大池化結(jié)果。
2.2池化特點(diǎn)
- 當(dāng)輸入數(shù)據(jù)做出少量平移時(shí),經(jīng)過池化后的大多數(shù)輸出還能保持不變,因此,池化對微小的位置變化具有魯棒性。例如 圖3 中,輸入矩陣向右平移一個(gè)像素值,使用最大池化后,結(jié)果與平移前依舊能保持不變。
圖3 微小位置變化時(shí)的最大池化結(jié)果
- 由于池化之后特征圖會(huì)變小,如果后面連接的是全連接層,能有效的減小神經(jīng)元的個(gè)數(shù),節(jié)省存儲(chǔ)空間并提高計(jì)算效率。
2.3 池化中填充的方式
在飛槳中,各種Pooling API中的Padding參數(shù), 接收值類型都包括int、list、tuple和string。下面用代碼和公式來介紹一下這些方式吧。
2.3.1 int輸入
int輸入即接收一個(gè)int類型的數(shù)字n,對圖片的四周包裹n行n列的0來填充圖片。如果要保持圖片尺寸不變,n的值和池化窗口的大小是有關(guān)的。假如 $H_{in}, W_{in}$ 為圖片輸入的大小,$k_h, k_w$ 為池化窗口的大小,$H_{out}, H_{out}$ 為結(jié)果圖的大小的話,他們之間有著這樣的關(guān)系。 $$H_{out} = \frac{H_{in} + 2p_h - k_h}{s_h} + 1 \ W_{out} = \frac{W_{out} + 2p_w -k_w}{s_w} + 1$ $在使用3×3的池化窗口且步長為1的情況下,還要保持圖片大小不變,則需要使用padding=1的填充。 那么,公式就變?yōu)榱? $H_{out} = \frac{6 - 3 + 21}{1} + 1 \ W_{out} = \frac{6 - 3 + 21}{1} + 1$ $另外,在stride不為1且不能被整除的情況下,整體結(jié)果向下取整。 關(guān)于Padding和K的公式如下$ $Padding = \frac{(k-1)}{2} \quad \quad (k % 2 != 0)$ $
關(guān)于上面的講解,下面用飛槳的API來看看吧。
import paddle # No padding
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=3, stride=1, padding=0)
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 4, 4]
這是池化中不padding,stride為1的結(jié)果,可以根據(jù)公式填入,$H_{out} = W_{out} = (6 + 0 - 3) + 1 = 4$,因此池化后的結(jié)果為4。如果填充呢?
import paddle # Padding 1
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=3, stride=1, padding=1)
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 6, 6]
正如我們上面的公式,$H_{out} = W_{out} = (6 + 2 - 3) + 1 = 6$, 填充為1的時(shí)候圖像保持為原大小。
2.3.2 list和tuple輸入
因?yàn)閳D像有寬和高,所以list和tuple的長度應(yīng)該為2,里面的兩個(gè)值分別對應(yīng)了高和寬,計(jì)算方式和上面int輸入的一樣,單獨(dú)計(jì)算。一般用作輸入圖片寬高不一致,或者池化窗口大小不一的情況。我們直接用飛槳的API來看看吧。
import paddle # No padding and different kernel size
x = paddle.rand((1, 1, 12, 6)) # 12為高H, 6為寬W
avgpool = paddle.nn.AvgPool2D(kernel_size=(3, 5), stride=1, padding=0)
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 12, 6] shape of result: [1, 1, 10, 2]
這里我們帶入公式推理一下,$H_{out} = 12 - 3 + 1 = 10, W_{out} = 6 - 5 + 1 = 2$.與結(jié)果相符。下面是有填充的情況,且3的滑動(dòng)窗口大小我們需要填充1,5的話則需要填充2了。下面來看看吧。
import paddle # No padding and different kernel size
x = paddle.rand((1, 1, 12, 6)) # 12為高H, 6為寬W
avgpool = paddle.nn.AvgPool2D(kernel_size=(3, 5), stride=1, padding=(1, 2))
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 12, 6] shape of result: [1, 1, 12, 6]
這里的結(jié)果與我們的期望一致,大家試著帶入公式算一下吧。
2.3.3 string輸入
string輸入有兩個(gè)值,一個(gè)是SAME,一個(gè)是VALID。這兩個(gè)的計(jì)算公式如下:
SAME:$H_{out} = \lceil \frac{H_{in}}{s_h} \rceil$, $W_{out} = \lceil\frac{W_{in}}{s_w}\rceil$
VALID:$H_{out} = \frac{H_{in} - k_h}{s_h} + 1$, $W_{out} = \frac{W_{in} - k_w}{s_w} + 1$
可以看到,VALID方式就是默認(rèn)采用的不填充的方式,與上面不Padding的公式一樣。而SAME則與池化窗口的大小無關(guān),若$s_h$和$s_w$為1,無論池化窗口的大小,輸出的特征圖的大小都與原圖保持一致。當(dāng)任意一個(gè)大于1時(shí),如果能整除,輸出的尺寸就是整除的結(jié)果,如果不能整除,則通過padding的方式繼續(xù)向上取整。理論過于難懂,我們直接用飛槳的API來看看吧。
import paddle # Padding SAME kernel_size 2
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=2, padding='SAME')
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 3, 3]
代碼的結(jié)果出來了,我們來直接帶入公式來計(jì)算吧,$H_{out} = 6/2 = 3, W_{out} = 6/2 = 3$,結(jié)果一致。
import paddle # Padding SAME kernel_size 1
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=1, padding='SAME')
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 6, 6]
這個(gè)呢,就和我們上面說的一致。下面來看看VALID填充方式吧。
import paddle # Padding VALID
x = paddle.rand((1, 1, 6, 6))
avgpool = paddle.nn.AvgPool2D(kernel_size=2, padding='VALID')
y = avgpool(x)
print('result:', 'shape of x:', x.shape, 'shape of result:', y.shape)
result: shape of x: [1, 1, 6, 6] shape of result: [1, 1, 3, 3]
這就是VALID的填充方式的結(jié)果啦。大家自己按照公式算算,看看你的答案和程序輸出的對不對哦。
2.4 應(yīng)用示例
與卷積核類似,池化窗口在圖片上滑動(dòng)時(shí),每次移動(dòng)的步長稱為步幅,當(dāng)寬和高方向的移動(dòng)大小不一樣時(shí),分別用$s_w$和$s_h$表示。也可以對需要進(jìn)行池化的圖片進(jìn)行填充,填充方式與卷積類似,假設(shè)在第一行之前填充$p_{h1}$行,在最后一行后面填充$p_{h2}$行。在第一列之前填充$p_{w1}$列,在最后一列之后填充$p_{w2}$列,則池化層的輸出特征圖大小為:
$$H_{out} = \frac{H + p_{h1} + p_{h2} - k_h}{s_h} + 1$$
$$W_{out} = \frac{W + p_{w1} + p_{w2} - k_w}{s_w} + 1$$
在卷積神經(jīng)網(wǎng)絡(luò)中,通常使用$2\times2$大小的池化窗口,步幅也使用2,填充為0,則輸出特征圖的尺寸為:
$$H_{out} = \frac{H}{2}$$
$$W_{out} = \frac{W}{2}$$
通過這種方式的池化,輸出特征圖的高和寬都減半,但通道數(shù)不會(huì)改變。
這里以 圖1 中的2個(gè)池化運(yùn)算為例,此時(shí),輸入大小是$4 \times 4$ ,使用大小為$2 \times 2$ 的池化窗口進(jìn)行運(yùn)算,步幅為2。此時(shí),輸出尺寸的計(jì)算方式為:
$$H_{out} = \frac{H + p_{h1} + p_{h2} - k_h}{s_h} + 1=\frac{4 + 0 + 0 - 2}{2} + 1=\frac{4}{2}=2$$
$$W_{out} = \frac{W + p_{w1} + p_{w2} - k_w}{s_w} + 1=\frac{4 + 0 + 0 - 2}{2} + 1=\frac{4}{2}=2$$
圖1(a) 中,使用平均池化進(jìn)行運(yùn)算,則輸出中的每一個(gè)像素均為池化窗口對應(yīng)的 $2 \times 2$ 區(qū)域求均值得到。計(jì)算步驟如下:
-
池化窗口的初始位置為左上角,對應(yīng)粉色區(qū)域,此時(shí)輸出為 $3.5 = \frac{1 + 2 + 5 + 6}{4}$ ;
-
由于步幅為2,所以池化窗口向右移動(dòng)兩個(gè)像素,對應(yīng)藍(lán)色區(qū)域,此時(shí)輸出為 $5.5 = \frac{3 + 4 + 7 + 8}{4}$ ;
-
遍歷完第一行后,再從第三行開始遍歷,對應(yīng)綠色區(qū)域,此時(shí)輸出為 $11.5 = \frac{9 + 10 + 13 + 14}{4}$ ;
-
池化窗口向右移動(dòng)兩個(gè)像素,對應(yīng)黃色區(qū)域,此時(shí)輸出為 $13.5 = \frac{11 + 12 + 15 + 16}{4}$ 。
圖1(b) 中,使用最大池化進(jìn)行運(yùn)算,將上述過程的求均值改為求最大值即為最終結(jié)果。
3.CNN中模型的參數(shù)量與FLOPs計(jì)算
一個(gè)卷積神經(jīng)網(wǎng)絡(luò)的基本構(gòu)成一般有卷積層、歸一化層、激活層和線性層。這里我們就通過逐步計(jì)算這些層來計(jì)算一個(gè)CNN模型所需要的參數(shù)量和FLOPs吧. 另外,F(xiàn)LOPs的全程為floating point operations的縮寫(小寫s表復(fù)數(shù)),意指浮點(diǎn)運(yùn)算數(shù),理解為計(jì)算量??梢杂脕砗饬克惴?模型的復(fù)雜度。
3.1 卷積層
卷積層,最常用的是2D卷積,因此我們以飛槳中的Conv2D來表示。
3.1.1卷積層參數(shù)量計(jì)算
Conv2D的參數(shù)量計(jì)算較為簡單,先看下列的代碼,如果定義一個(gè)Conv2D,卷積層中的參數(shù)會(huì)隨機(jī)初始化,如果打印其shape,就可以知道一個(gè)Conv2D里大致包含的參數(shù)量了,Conv2D的參數(shù)包括兩部分,一個(gè)是用于卷積的weight,一個(gè)是用于調(diào)節(jié)網(wǎng)絡(luò)擬合輸入特征的bias。如下
import paddle
import numpy as np
cv2d = paddle.nn.Conv2D(in_channels=2, out_channels=4, kernel_size=(3, 3), stride=1, padding=(1, 1))
params = cv2d.parameters()
print("shape of weight: ", np.array(params[0]).shape)
print("shape of bias: ", np.array(params[1]).shape)
shape of weight: (4, 2, 3, 3)
shape of bias: (4,)
這里解釋一下上面的代碼,我們先定義了一個(gè)卷積層cv2d,然后輸出了這個(gè)卷積層的參數(shù)的形狀,參數(shù)包含兩部分,分別是weight和bias,這兩部分相加才是整個(gè)卷積的參數(shù)量。因此,可以看到,我們定義的cv2d的參數(shù)量為:$423*3+4 = 76$, 4對應(yīng)的是輸出的通道數(shù),2對應(yīng)的是輸入的通道數(shù),兩個(gè)3是卷積核的尺寸,最后的4就是bias的數(shù)量了, 值得注意的是, bias是數(shù)量與輸出的通道數(shù)保持一致。因此,我們可以得出,一個(gè)卷積層的參數(shù)量的公式,如下: $ $Param_{conv2d} = C_{in} * C_{out} * K_h * K_w + C_{out}$ $其中,$C_{in} $表示輸入的通道數(shù),$C_{out} $表示輸出的通道數(shù),$ K_h$,$ K_w $表示卷積核的大小。當(dāng)然了,有些卷積會(huì)將bias設(shè)置為False,那么我們不加最后的$C_{out}$即可。
3.1.2 卷積層FLOPs計(jì)算
參數(shù)量會(huì)計(jì)算了,那么FLOPs其實(shí)也是很簡單的,就一個(gè)公式: $$FLOP_{conv2d} = Param_{conv2d} * M_{outh} * M_{outw}$ $這里,$M_{outh}$,$M_{outw}$ 為輸出的特征圖的高和寬,而不是輸入的,這里需要注意一下。
3.1.3 卷積層參數(shù)計(jì)算示例
Paddle有提供計(jì)算FLOPs和參數(shù)量的API,paddle.flops, 這里我們用我們的方法和這個(gè)API的方法來測一下,看看一不一致吧。代碼如下:
import paddle
from paddle import nn
from paddle.nn import functional as F
class TestNet(nn.Layer):
def __init__(self):
super(TestNet, self).__init__()
self.conv2d = nn.Conv2D(in_channels=2, out_channels=4, kernel_size=(3, 3), stride=1, padding=1)
def forward(self, x):
x = self.conv2d(x)
return x
if __name__ == "__main__":
net = TestNet()
paddle.flops(net, input_size=[1, 2, 320, 320])
Total GFlops: 0.00778 Total Params: 76.00
API得出的參數(shù)量為76,GFLOPs為0.00778,這里的GFLOPs就是FLOPs的10$^9$倍,我們的參數(shù)量求得的也是76,那么FLOPs呢?我們來算一下,輸入的尺寸為320 * 320, 卷積核為3 * 3, 且padding為1,那么圖片輸入的大小和輸出的大小一致,即輸出也是320 * 320, 那么根據(jù)我們的公式可得: $76 * 320 * 320 = 7782400$, 與API的一致!因此大家計(jì)算卷積層的參數(shù)和FLOPs的時(shí)候就可以用上面的公式。
3.2 歸一化層
最常用的歸一化層為BatchNorm2D啦,我們這里就用BatchNorm2D來做例子介紹。在算參數(shù)量和FLOPs,先看看BatchNorm的算法流程吧!
輸入為:Values of $x$ over a mini-batch:$B={x_1,...,m}$,
$\quad\quad\quad$Params to be learned: $\beta$, $\gamma$
輸出為:{$y_i$=BN$_{\gamma}$,$\beta(x_i)$}
流程如下:
$\quad\quad\quad$$\mu_B\gets$ $\frac{1}{m}\sum_{1}^mx_i$
$\quad\quad\quad\sigma_{B}2\gets\frac{1}{m}\sum_{1}m(x_i-\mu_B)^2$
$\quad\quad\quad\hat{x}i\gets\frac{x_i-\mu_B}{\sqrt{\sigma^2+\epsilon}}$
$\quad\quad\quad y_i\gets\gamma\hat{x}i+\beta\equiv BN$,$\beta(x_i)$
在這個(gè)公式中,$B$ 為一個(gè)Batch的數(shù)據(jù),$\beta$ 和 $\gamma$ 為可學(xué)習(xí)的參數(shù),$\mu$ 和 $\sigma^2$ 為均值和方差,由輸入的數(shù)據(jù)的值求得。該算法先求出整體數(shù)據(jù)集的均值和方差,然后根據(jù)第三行的更新公式求出新的x,最后根據(jù)可學(xué)習(xí)的$\beta$ 和 $\gamma$調(diào)整數(shù)據(jù)。第三行中的 $\epsilon$ 在飛槳中默認(rèn)為 1e-5, 用于處理除法中的極端情況。
3.2.1 歸一化層參數(shù)量計(jì)算
由于歸一化層較為簡單,這里直接寫出公式: $$Param_{bn2d} = 4 * C_{out}$ $ 其中4表示四個(gè)參數(shù)值,每個(gè)特征圖對應(yīng)一組四個(gè)元素的參數(shù)組合;
beta_initializer $\beta$ 權(quán)重的初始值設(shè)定項(xiàng)。
gamma_initializer $\gamma$ 伽馬權(quán)重的初始值設(shè)定項(xiàng)。
moving_mean_initializer $\mu$ 移動(dòng)均值的初始值設(shè)定項(xiàng)。
moving_variance_initializer $\sigma^2$ 移動(dòng)方差的初始值設(shè)定項(xiàng)。
3.2.2 歸一化層FLOPs計(jì)算
因?yàn)橹挥袃蓚€(gè)可以學(xué)習(xí)的權(quán)重,$\beta$ 和 $\gamma$,所以FLOPs只需要2乘以輸出通道數(shù)和輸入的尺寸即可。 歸一化的FLOPs計(jì)算公式則為: $ $FLOP_{bn2d} = 2 * C_{out} * M_{outh} * M_{outw}$ $ 與1.3相似,歡迎大家使用上面的代碼進(jìn)行驗(yàn)證。
3.3 線性層
線性層也是常用的分類層了,我們以飛槳的Linear為例來介紹。
3.3.1線性層參數(shù)量計(jì)算
其實(shí)線性層是比較簡單的,它就是相當(dāng)于卷積核為1的卷積層,線性層的每一個(gè)參數(shù)與對應(yīng)的數(shù)據(jù)進(jìn)行矩陣相乘,再加上偏置項(xiàng)bias,線性層沒有類似于卷積層的“卷”的操作的,所以計(jì)算公式如下: $$Param_{linear} = C_{in} * C_{out} + C_{out}$ $。我們這里打印一下線性層參數(shù)的形狀看看。
import paddle
import numpy as np
linear = paddle.nn.Linear(in_features=2, out_features=4)
params = linear.parameters()
print("shape of weight: ", np.array(params[0]).shape)
print("shape of bias: ", np.array(params[1]).shape)
shape of weight: (2, 4)
shape of bias: (4,)
可以看到,線性層相較于卷積層還是簡單的,這里我們直接計(jì)算這個(gè)定義的線性層的參數(shù)量為 $2 * 4 + 4 = 12$。具體對不對,我們在下面的實(shí)例演示中檢查。
3.3.2 線性層FLOPs計(jì)算
與卷積層不同的是,線性層沒有”卷“的過程,所以線性層的FLOPs計(jì)算公式為: $ $FLOP_{linear} = C_{in} * C_{out}$$
3.4 實(shí)例演示
這里我們就以LeNet為例子,計(jì)算出LeNet的所有參數(shù)量和計(jì)算量。LeNet的結(jié)構(gòu)如下。輸入的圖片大小為28 * 28
LeNet(
(features): Sequential(
(0): Conv2D(1, 6, kernel_size=[3, 3], padding=1, data_format=NCHW)
(1): ReLU()
(2): MaxPool2D(kernel_size=2, stride=2, padding=0)
(3): Conv2D(6, 16, kernel_size=[5, 5], data_format=NCHW)
(4): ReLU()
(5): MaxPool2D(kernel_size=2, stride=2, padding=0)
)
(fc): Sequential(
(0): Linear(in_features=400, out_features=120, dtype=float32)
(1): Linear(in_features=120, out_features=84, dtype=float32)
(2): Linear(in_features=84, out_features=10, dtype=float32)
)
)
我們先來手動(dòng)算一下參數(shù)量和FLOPs。
features[0] 參數(shù)量: $6 * 1 * 3 * 3 + 6 = 60$, FLOPs : $60 * 28 * 28 = 47040$
features[1] 參數(shù)量和FLOPs均為0
features[2] 參數(shù)量和FLOPs均為0, 輸出尺寸變?yōu)?4 * 14
features[3] 參數(shù)量: $16 * 6 * 5 * 5 + 16 = 2416$, FLOPs : $2416 * 10 * 10 = 241600$, 需要注意的是,這個(gè)卷積沒有padding,所以輸出特征圖大小變?yōu)?10 * 10
features[4] 參數(shù)量和FLOPs均為0
features[5] 參數(shù)量和FLOPs均為0,輸出尺寸變?yōu)? * 5, 然后整個(gè)被拉伸為[1, 400]的尺寸,其中400為5 * 5 * 16。
fc[0] 參數(shù)量: $400 * 120 + 120 = 48120$, FLOPs : $400 * 120 = 48000$ (輸出尺寸變?yōu)閇1, 120])
fc[1] 參數(shù)量: $120 * 84 + 84 = 10164$, FLOPs : $120 * 84 = 10080$ (輸出尺寸變?yōu)閇1, 84])
fc[2] 參數(shù)量: $84 * 10 + 10 = 850$, FLOPs : $84 * 10 = 840$ (輸出尺寸變?yōu)閇1, 10])。
總參數(shù)量為: $60 + 2416 + 48120 + 10164 + 850 = 61610$
總FLOPs為:$47040 + 241600 + 48000 + 10080 + 840 = 347560$
下面我們用代碼驗(yàn)證以下:文章來源:http://www.zghlxwxcb.cn/news/detail-453687.html
from paddle.vision.models import LeNet
net = LeNet()
print(net)
paddle.flops(net, input_size=[1, 1, 28, 28], print_detail=True)
+--------------+-----------------+-----------------+--------+--------+
| Layer Name | Input Shape | Output Shape | Params | Flops |
+--------------+-----------------+-----------------+--------+--------+
| conv2d_0 | [1, 1, 28, 28] | [1, 6, 28, 28] | 60 | 47040 |
| re_lu_0 | [1, 6, 28, 28] | [1, 6, 28, 28] | 0 | 0 |
| max_pool2d_0 | [1, 6, 28, 28] | [1, 6, 14, 14] | 0 | 0 |
| conv2d_1 | [1, 6, 14, 14] | [1, 16, 10, 10] | 2416 | 241600 |
| re_lu_1 | [1, 16, 10, 10] | [1, 16, 10, 10] | 0 | 0 |
| max_pool2d_1 | [1, 16, 10, 10] | [1, 16, 5, 5] | 0 | 0 |
| linear_0 | [1, 400] | [1, 120] | 48120 | 48000 |
| linear_1 | [1, 120] | [1, 84] | 10164 | 10080 |
| linear_2 | [1, 84] | [1, 10] | 850 | 840 |
+--------------+-----------------+-----------------+--------+--------+
Total GFlops: 0.00034756 Total Params: 61610.00
可以看到,與我們的計(jì)算是一致的,大家可以自己把VGG-16的模型算一下參數(shù)量FLOPs,相較于LeNet, VGG-16只是模型深了點(diǎn),并沒有其余額外的結(jié)構(gòu)。文章來源地址http://www.zghlxwxcb.cn/news/detail-453687.html
到了這里,關(guān)于深度學(xué)習(xí)基礎(chǔ)入門篇[8]::計(jì)算機(jī)視覺與卷積神經(jīng)網(wǎng)絡(luò)、卷積模型CNN綜述、池化講解、CNN參數(shù)計(jì)算的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!