layout: post
title: 深度學(xué)習(xí)入門 基于Python的理論實(shí)現(xiàn)
subtitle: 第七章 卷積神經(jīng)網(wǎng)絡(luò)
tags: [Machine learning, Reading]
第七章 卷積神經(jīng)網(wǎng)絡(luò)
終于來(lái)到了激動(dòng)人心的卷積神經(jīng)網(wǎng)絡(luò)爬坑。卷積神經(jīng)網(wǎng)絡(luò)的重要性不用多說(shuō),所以這一章著重來(lái)介紹。
7.1 整體結(jié)構(gòu)
首先看一下CNN的網(wǎng)絡(luò)結(jié)構(gòu)锅知。神經(jīng)網(wǎng)絡(luò)和之前的神經(jīng)網(wǎng)絡(luò)一樣抠藕,可以像樂(lè)高積木一樣通過(guò)組裝層來(lái)構(gòu)建橡娄,不過(guò)出現(xiàn)了新的卷積層(Convolution層)和池化層(Pooling層)拔莱。
之前介紹的神經(jīng)網(wǎng)絡(luò)中立美,相鄰的所有神經(jīng)元只見(jiàn)痘有鏈接匿又,稱為全連接。結(jié)構(gòu)類似下圖所示建蹄。
對(duì)比一下碌更,CNN的結(jié)構(gòu)是什么樣的。下圖就是一個(gè)CNN的例子躲撰。
經(jīng)過(guò)對(duì)比可以看出,多了卷積層和池化層击费。其連接順序是“Convolution - ReLU - (Pooling)”(Pooling層有時(shí)也會(huì)被省略)拢蛋。
7.2 卷積層
CNN 中出現(xiàn)了一些特有的術(shù)語(yǔ),比如填充蔫巩、步幅等谆棱。此外,各層中傳 遞的數(shù)據(jù)是有形狀的數(shù)據(jù)(比如圆仔,3 維數(shù)據(jù))垃瞧,這與之前的全連接網(wǎng)絡(luò)不同, 因此剛開(kāi)始學(xué)習(xí) CNN 時(shí)可能會(huì)感到難以理解坪郭。本節(jié)我們將花點(diǎn)時(shí)間个从,認(rèn)真 學(xué)習(xí)一下 CNN 中使用的卷積層的結(jié)構(gòu)。
7.2.1 全連接層存在的問(wèn)題
之前介紹的全連接的神經(jīng)網(wǎng)絡(luò)中使用了全連接層(Affine 層)。在全連接 層中嗦锐,相鄰層的神經(jīng)元全部連接在一起嫌松,輸出的數(shù)量可以任意決定。
全連接層存在什么問(wèn)題呢?那就是數(shù)據(jù)的形狀被“忽視”了奕污。比如萎羔,輸入數(shù)據(jù)是圖像時(shí),圖像通常是高碳默、長(zhǎng)贾陷、通道方向上的3維形狀。但是嘱根,向全連接層輸入時(shí)髓废,需要將3維數(shù)據(jù)拉平為1維數(shù)據(jù)。實(shí)際上儿子,前面提到的使用了MNIST數(shù)據(jù)集的例子中瓦哎,輸入圖像就是1通道、高28像素柔逼、長(zhǎng)28像素的(1, 28, 28)形狀蒋譬,但卻被排成1列,以784個(gè)數(shù)據(jù)的形式輸入到最開(kāi)始的 Affine 層愉适。
其實(shí)就是說(shuō)犯助,作為圖像來(lái)說(shuō),三維圖像本身就蘊(yùn)含一些信息维咸,全連接層把圖像拉長(zhǎng)剂买,使得一些信息被去掉。
卷積層可以保持形狀不變癌蓖,輸入是圖像時(shí)瞬哼,卷積層會(huì)以三維數(shù)據(jù)的形式接收輸入數(shù)據(jù),并用三維形式輸出到下一層租副。因此坐慰,在 CNN 中,可以(有可能)正確理解圖像等具有形狀的數(shù)據(jù)用僧。
CNN 中结胀,有時(shí)將卷積層的輸入輸出數(shù)據(jù)稱為特征圖(feature map)。其中责循,卷積層的輸入數(shù)據(jù)稱為輸入特征圖(input feature map)糟港,輸出 數(shù)據(jù)稱為輸出特征圖(output feature map)。本書中將“輸入輸出數(shù)據(jù)”和“特
征圖”作為含義相同的詞使用院仿。
7.2.2 卷積運(yùn)算
卷積層進(jìn)行的處理就是卷積運(yùn)算秸抚。卷積運(yùn)算相當(dāng)于圖像處理中的“濾波 器運(yùn)算”速和。
上面吧濾波器在輸入數(shù)據(jù)上移動(dòng),對(duì)應(yīng)位置相乘最后整個(gè)相加耸别。
下面是加上某個(gè)固定值的圖健芭。
7.2.3 填充
在進(jìn)行卷積層的處理之前,有時(shí)要向輸入數(shù)據(jù)的周圍填入固定的數(shù)據(jù)(比 如 0 等)秀姐,這稱為填充(padding)慈迈,是卷積運(yùn)算中經(jīng)常會(huì)用到的處理。下面是填充0的情況省有。
使用填充主要是為了調(diào)整輸出的大小痒留。比如,對(duì)大小為 (4, 4) 的輸入 數(shù)據(jù)應(yīng)用 (3, 3) 的濾波器時(shí)蠢沿,輸出大小變?yōu)?(2, 2)伸头,相當(dāng)于輸出大小 比輸入大小縮小了 2 個(gè)元素。這在反復(fù)進(jìn)行多次卷積運(yùn)算的深度網(wǎng) 絡(luò)中會(huì)成為問(wèn)題舷蟀。為什么呢?因?yàn)槿绻看芜M(jìn)行卷積運(yùn)算都會(huì)縮小 空間恤磷,那么在某個(gè)時(shí)刻輸出大小就有可能變?yōu)?1,導(dǎo)致無(wú)法再應(yīng)用 卷積運(yùn)算野宜。為了避免出現(xiàn)這樣的情況扫步,就要使用填充。在剛才的例 子中匈子,將填充的幅度設(shè)為 1河胎,那么相對(duì)于輸入大小 (4, 4),輸出大小 也保持為原來(lái)的 (4, 4)虎敦。因此游岳,卷積運(yùn)算就可以在保持空間大小不變 的情況下將數(shù)據(jù)傳給下一層。
7.2.4 步幅
應(yīng)用濾波器的位置間隔稱為步幅(stride)其徙。之前的例子中步幅都是1胚迫,如
果將步幅設(shè)為2,則如圖所示唾那,應(yīng)用濾波器的窗口的間隔變?yōu)?個(gè)元素访锻。
直接總結(jié)式子。這里通贞,假設(shè)輸入大小為 朗若,濾波器大小為
恼五,輸出大小為
昌罩,填充為
,步幅為
灾馒。此時(shí)茎用,輸出大小可用如下式子進(jìn)行計(jì)算。
7.2.5 3維數(shù)據(jù)的卷積運(yùn)算
之前的卷積運(yùn)算的例子都是以有高、長(zhǎng)方向的2維形狀為對(duì)象的轨功。但是旭斥,圖像是3維數(shù)據(jù),除了高古涧、長(zhǎng)方向之外垂券,還需要處理通道方向。這里羡滑,我們按照與之前相同的順序菇爪,看一下對(duì)加上了通道方向的3維數(shù)據(jù)進(jìn)行卷積運(yùn)算的例子。
通道數(shù)只能設(shè)定為和輸入數(shù)據(jù)的通道數(shù)相同的值柒昏。
7.2.6 結(jié)合方塊思考
將數(shù)據(jù)和濾波器結(jié)合長(zhǎng)方體的方塊來(lái)考慮凳宙,3 維數(shù)據(jù)的卷積運(yùn)算會(huì)很 容易理解。
在這個(gè)例子中职祷,數(shù)據(jù)輸出是 1 張?zhí)卣鲌D氏涩。所謂 1 張?zhí)卣鲌D,換句話說(shuō)有梆, 就是通道數(shù)為 1 的特征圖是尖。那么,如果要在通道方向上也擁有多個(gè)卷積運(yùn)算的輸出淳梦,該怎么做呢?為此析砸,就需要用到多個(gè)濾波器(權(quán)重)。用圖表示的話爆袍,如圖所示首繁。
卷積運(yùn)算中(和全連接層一樣)存在偏置。在圖 7-11 的例子中陨囊,如果進(jìn)一步追加偏置的加法運(yùn)算處理弦疮,則結(jié)果如下面的圖所示。
7.2.7 批處理
圖示的批處理版的數(shù)據(jù)流中蜘醋,在各個(gè)數(shù)據(jù)的開(kāi)頭添加了批用的維度胁塞。 像這樣,數(shù)據(jù)作為 4 維的形狀在各層間傳遞压语。這里需要注意的是啸罢,網(wǎng)絡(luò)間傳 遞的是 4 維數(shù)據(jù),對(duì)這 N 個(gè)數(shù)據(jù)進(jìn)行了卷積運(yùn)算胎食。也就是說(shuō)扰才,批處理將 N 次 的處理匯總成了 1 次進(jìn)行。
7.3 池化層
池化是縮小高厕怜、長(zhǎng)方向上的空間的運(yùn)算衩匣。比如蕾总,如圖所示,進(jìn)行將
2 × 2 的區(qū)域集約成 1 個(gè)元素的處理琅捏,縮小空間大小生百。
上圖的例子是按步幅 2 進(jìn)行 2 × 2 的 Max 池化時(shí)的處理順序”樱“Max 池化”是獲取最大值的運(yùn)算蚀浆。
除了 Max 池化之外,還有 Average 池化等搜吧。相對(duì)于 Max 池化是從 目標(biāo)區(qū)域中取出最大值蜡坊,Average 池化則是計(jì)算目標(biāo)區(qū)域的平均值。 在圖像識(shí)別領(lǐng)域赎败,主要使用 Max 池化秕衙。
池化層的特征
沒(méi)有要學(xué)習(xí)的參數(shù)
池化層和卷積層不同,沒(méi)有要學(xué)習(xí)的參數(shù)僵刮。池化只是從目標(biāo)區(qū)域中取最 大值(或者平均值)据忘,所以不存在要學(xué)習(xí)的參數(shù)。
通道數(shù)不發(fā)生變化
經(jīng)過(guò)池化運(yùn)算搞糕,輸入數(shù)據(jù)和輸出數(shù)據(jù)的通道數(shù)不會(huì)發(fā)生變化勇吊。如圖 7-15 所示,計(jì)算是按通道獨(dú)立進(jìn)行的窍仰。
對(duì)微小的位置變化具有魯棒性(健壯)
輸入數(shù)據(jù)發(fā)生微小偏差時(shí)汉规,池化仍會(huì)返回相同的結(jié)果。因此驹吮,池化對(duì) 輸入數(shù)據(jù)的微小偏差具有魯棒性针史。
7.4 卷積層和池化層的實(shí)現(xiàn)
7.4.1 4維數(shù)組
import numpy as np
x = np.random.rand(10,1,28,28)
x.shape
如果要訪問(wèn)第1個(gè)數(shù)據(jù),只要寫x[0]就可以碟狞。
如果要訪問(wèn)第 1 個(gè)數(shù)據(jù)的第 1 個(gè)通道的空間數(shù)據(jù)啄枕,可以寫成下面這樣。
>>> x[0,0]
像這樣族沃,CNN 中處理的是 4 維數(shù)據(jù)频祝,因此卷積運(yùn)算的實(shí)現(xiàn)看上去會(huì)很復(fù) 雜,但是通過(guò)使用下面要介紹的 im2col 這個(gè)技巧脆淹,問(wèn)題就會(huì)變得很簡(jiǎn)單常空。
7.4.2 基于im2col的展開(kāi)
如果老老實(shí)實(shí)地實(shí)現(xiàn)卷積運(yùn)算,估計(jì)要重復(fù)好幾層的for語(yǔ)句盖溺。這樣的實(shí)現(xiàn)有點(diǎn)麻煩漓糙,而且,NumPy中存在使用for語(yǔ)句后處理變慢的缺點(diǎn)(NumPy中咐柜,訪問(wèn)元素時(shí)最好不要用for 語(yǔ)句)兼蜈。這里,我們不使用for語(yǔ)句拙友,而是使用im2col這個(gè)便利的函數(shù)進(jìn)行簡(jiǎn)單的實(shí)現(xiàn)为狸。
im2col 是一個(gè)函數(shù),將輸入數(shù)據(jù)展開(kāi)以適合濾波器(權(quán)重)遗契。如圖所示辐棒,對(duì) 3 維的輸入數(shù)據(jù)應(yīng)用 im2col 后,數(shù)據(jù)轉(zhuǎn)換為 2 維矩陣(正確地講牍蜂,是把包含 批數(shù)量的 4 維數(shù)據(jù)轉(zhuǎn)換成了 2 維數(shù)據(jù))漾根。
im2col會(huì)把輸入數(shù)據(jù)展開(kāi)以適合濾波器(權(quán)重)。具體地說(shuō)鲫竞,如圖所示辐怕,對(duì)于輸入數(shù)據(jù),將應(yīng)用濾波器的區(qū)域(3維方塊)橫向展開(kāi)為1列从绘。im2col會(huì)在所有應(yīng)用濾波器的地方進(jìn)行這個(gè)展開(kāi)處理寄疏。
使用 im2col 展開(kāi)輸入數(shù)據(jù)后,之后就只需將卷積層的濾波器(權(quán)重)縱 向展開(kāi)為1列僵井,并計(jì)算2個(gè)矩陣的乘積即可(參照?qǐng)D)陕截。這和全連接層的 Affine 層進(jìn)行的處理基本相同。
7.4.3 卷積層的實(shí)現(xiàn)
本書提供了 im2col 函數(shù)批什,并將這個(gè) im2col 函數(shù)作為黑盒(不關(guān)心內(nèi)部實(shí)現(xiàn)) 使用农曲。
im2col 這一便捷函數(shù)具有以下接口。
im2col (input_data, filter_h, filter_w, stride=1, pad=0)
- input_data― 由(數(shù)據(jù)量驻债,通道乳规,高,長(zhǎng))的4維數(shù)組構(gòu)成的輸入數(shù)據(jù)
- filter_h― 濾波器的高
- filter_w― 濾波器的長(zhǎng)
- stride― 步幅
- pad― 填充
import sys, os
import numpy as np
sys.path.append(os.pardir)
from common.util import im2col
x1 = np.random.rand(1, 3, 7, 7)
col1 = im2col(x1, 5, 5, stride=1, pad=0)
print(col1.shape) #(9, 75)
x2 = np.random.rand(10, 3, 7, 7)
col2 = im2col(x2, 5, 5, stride=1, pad=0)
print(col2.shape) #(90, 75)
第一個(gè)是批大小為 1合呐、通道為 3 的 7 × 7 的數(shù)據(jù)驯妄,第二個(gè)的批大小為 10,數(shù)據(jù)形狀和第一個(gè)相同合砂。分別對(duì)其應(yīng)用 im2col 函數(shù)青扔,在這兩種情形下,第 2 維的元素個(gè)數(shù)均為 75翩伪。這是濾波器(通道為 3微猖、大小為 5 × 5)的元素個(gè)數(shù)的總和。批大小為 1 時(shí)缘屹,im2col的結(jié)果是 (9, 75),9 是因?yàn)樵?7 × 7 的矩陣中移動(dòng) 5 × 5 的矩陣有9種不同的情況凛剥。而第 2 個(gè)例子中批大小為 10,所以保存了 10 倍的數(shù)據(jù)轻姿,即 (90, 75)犁珠。
接下來(lái)實(shí)現(xiàn)卷積層逻炊。
class Convolution:
def __init__(self, W, b, stride = 1, pad = 0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = int(1 + (H + 2*self.pad - FH) / self.stride)
out_w = int(1 + (W + 2*self.pad - FW) / self.stride)
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
return out
7.4.4 池化層的實(shí)現(xiàn)
池化層的實(shí)現(xiàn)和卷積層相同,都使用 im2col 展開(kāi)輸入數(shù)據(jù)犁享。不過(guò)余素,池化 的情況下,在通道方向上是獨(dú)立的炊昆,這一點(diǎn)和卷積層不同桨吊。
像這樣展開(kāi)之后,只需對(duì)展開(kāi)的矩陣求各行的最大值凤巨,并轉(zhuǎn)換為合適的 形狀即可视乐。
class Pooling:
def __init__(self, pool_h, pool_w, stride = 1, pad = 0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
out = np.max(col, axis=1)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
池化層的實(shí)現(xiàn)按下面 3 個(gè)階段進(jìn)行。
- 展開(kāi)輸入數(shù)據(jù)敢茁。
- 求各行的最大值佑淀。
- 轉(zhuǎn)換為合適的輸出大小。
7.5 CNN的實(shí)現(xiàn)
我們已經(jīng)實(shí)現(xiàn)了卷積層和池化層,現(xiàn)在來(lái)組合這些層,搭建進(jìn)行手寫數(shù)
字識(shí)別的 CNN骇扇。
上圖網(wǎng)絡(luò)的構(gòu)成是“Convolution - ReLU - Pooling -Affine - ReLU - Affine - Softmax”籽暇,我們將它實(shí)現(xiàn)為名為SimpleConvNet的類。
參數(shù)
- input_dim― 輸入數(shù)據(jù)的維度:(通道,高,長(zhǎng))
- conv_param― 卷積層的超參數(shù)(字典)。字典的關(guān)鍵字如下:
filter_num― 濾波器的數(shù)量 filter_size― 濾波器的大小 stride― 步幅
pad― 填充 - hidden_size― 隱藏層(全連接)的神經(jīng)元數(shù)量
- output_size― 輸出層(全連接)的神經(jīng)元數(shù)量
- weitght_int_std― 初始化時(shí)權(quán)重的標(biāo)準(zhǔn)差
卷積層的超參數(shù)通過(guò)名為 conv_param 的字典傳入隘道。我們?cè)O(shè)想它會(huì)像 {'filter_num':30,'filter_size':5, 'pad':0, 'stride':1} 這樣,保存必要 的超參數(shù)值郎笆。
SimpleConvNet 的初始化的實(shí)現(xiàn)稍長(zhǎng)谭梗,我們分成 3 部分來(lái)說(shuō)明,首先是初 始化的最開(kāi)始部分宛蚓。
class SimpleConvNet:
def __init__(self, input_dim=(1, 28, 28), conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1}, hidden_size = 100, output_size = 10, weight_init_std = 0.01):
filter_num = conv_param['filter_num']
filter_size = conv_param['filter_size']
filter_pad = conv_param['pad']
filter_stride = conv_param['stride']
input_size = input_dim[1]
conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
pool_output_size = int(filter_num * (conv_output_size/2)*(conv_output_size/2))
self.params = {}
self.params['W1'] = weight_init_std * \
np.random.randn(filter_num, input_dim[0],
filter_size, filter_size)
self.params['b1'] = np.zeros(filter_num)
self.params['W2'] = weight_init_std * np.random.randn(pool_output_size,hidden_size)
self.params['b2'] = np.zeros(hidden_size)
self.params['W3'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b3'] = np.zeros(output_size)
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'],
self.params['b1'],conv_param['stride'],conv_param['pad'])
self.layers['Relu1'] = Relu()
self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = Affine(self.params['W2'],
self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3'],
self.params['b3'])
self.last_layer = softmaxwithloss()
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
def gradient(self, x, t):
# forward
self.loss(x, t)
# backward
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 設(shè)定
grads = {}
grads['W1'] = self.layers['Conv1'].dW
grads['b1'] = self.layers['Conv1'].db
grads['W2'] = self.layers['Affine1'].dW
grads['b2'] = self.layers['Affine1'].db
grads['W3'] = self.layers['Affine2'].dW
grads['b3'] = self.layers['Affine2'].db
return grads
7.6 CNN的可視化
7.6.1 第一層權(quán)重的可視化
7.7 小結(jié)
本章介紹了 CNN激捏。構(gòu)成 CNN 的基本模塊的卷積層和池化層雖然有些復(fù) 雜,但是一旦理解了凄吏,之后就只是如何使用它們的問(wèn)題了远舅。本章為了使讀者 在實(shí)現(xiàn)層面上理解卷積層和池化層,花了不少時(shí)間進(jìn)行介紹痕钢。在圖像處理領(lǐng) 域图柏,幾乎毫無(wú)例外地都會(huì)使用 CNN。
- CNN在此前的全連接層的網(wǎng)絡(luò)中新增了卷積層和池化層
- 使用im2col函數(shù)可以簡(jiǎn)單高效的實(shí)現(xiàn)卷積層和池化層
- 通過(guò)CNN的可視化任连,克制隨著層次變深蚤吹,提取的信息愈加高級(jí)
- LeNet 和 AlexNet 是 CNN 的代表性網(wǎng)絡(luò)
- 在深度學(xué)習(xí)的發(fā)展中,大數(shù)據(jù)和GPU做出了很大的貢獻(xiàn)