本文重點(diǎn)知識(shí)點(diǎn):
CNN的大致框架
卷積層
池化層
卷積層和池化層的實(shí)現(xiàn)
CNN的實(shí)現(xiàn)
CNN可視化介紹
如有細(xì)節(jié)處沒(méi)有寫(xiě)到的,請(qǐng)繼續(xù)精讀《深度學(xué)習(xí)入門(mén):基于Python的理論與實(shí)現(xiàn)》,對(duì)小白來(lái)說(shuō)真的是非常好的深度學(xué)習(xí)的入門(mén)書(shū)籍,通俗易懂。(書(shū)中的例子主要是基于CV的)
一、CNN大致框架
神經(jīng)網(wǎng)絡(luò):就是組裝層的過(guò)程。
CNN出現(xiàn)了新的層:卷積層、池化層。
Q:如何組裝構(gòu)成CNN?
全連接層:用Affine實(shí)現(xiàn)的:Affine-ReLU (Affine仿射變換 y = xw+b),如下為基于全連接層的5層神經(jīng)網(wǎng)絡(luò)。
ReLU也可替換成Sigmoid層,這里由4層Affine-ReLU組成,最后由Affine-Softmax輸出最終結(jié)果(概率) 常見(jiàn)的CNN:Affine-ReLU 變 Conv-ReLU-(Pooling),如下為基于CNN的五層神經(jīng)網(wǎng)絡(luò)。
Q:全連接層有什么問(wèn)題嘛?為什么要改進(jìn)為Conv層?
全連接層“忽視”了數(shù)據(jù)的形狀,3維數(shù)據(jù)被拉平為1維數(shù)據(jù);形狀因含有重要的空間信息:①空間臨近的像素為相似的值,相距較遠(yuǎn)的像素沒(méi)什么關(guān)系;②RBG的各個(gè)通道之間分別有密切的關(guān)聯(lián)性等;③3維形狀中可能隱藏有值得提取的本質(zhì)模式。
而卷積層可以保持形狀不變。可以正確理解圖像等具有形狀的數(shù)據(jù)。
特征圖:輸入、輸出數(shù)據(jù)
二、卷積層 2.1 卷積運(yùn)算
輸入特征圖與卷積核作乘積累加運(yùn)算,窗口以一定的步長(zhǎng)滑動(dòng),得到輸出特征圖,也可以加偏置(1*1)
卷積核(濾波器)相當(dāng)于全連接層中的權(quán)重。 卷積完后,偏置將應(yīng)用于所有數(shù)據(jù)
2.2 填充(padding)
向輸入數(shù)據(jù)的周?chē)钊牍潭ǖ臄?shù)據(jù)(比如0等) 填充的目的:調(diào)整輸出的大小。擴(kuò)大輸入特征圖,得到大一些的輸出。一般填充為0。 為什么要調(diào)整輸出的大???因?yàn)楸热巛斎耄?×4),卷積核為(3×3),得到輸出為(2×2),隨著層的加深,卷積完的輸出越來(lái)越小,直到輸出變1以后將無(wú)法再進(jìn)行卷積運(yùn)行。為了避免這樣的情況發(fā)生,需要用到填充,使輸出至少不會(huì)減小。
2.3 步幅(stride) 用途:指定濾波器的間隔。 步幅增大,輸出減小;填充增大,輸出增大。 計(jì)算輸出的大?。?/p>
其中:
(OH,OW) 輸出大小
(H,W) 輸入大小
(FH,FW) 濾波器大小
P 填充
S 步幅
注:式(7.1)中最好可以除盡。if無(wú)法除盡,需報(bào)錯(cuò)?;蛘呦蜃罱咏恼麛?shù)四舍五入,不進(jìn)行報(bào)錯(cuò)而繼續(xù)運(yùn)行。 2.4 三維數(shù)據(jù)的卷積運(yùn)算 (通道方向,高,長(zhǎng)):比二維數(shù)據(jù)多了一個(gè):通道方向,特征圖增加了 (Channel,height,width) (C,H,W)
方塊思維,上下兩張圖是一個(gè)意思。
輸入數(shù)據(jù)和濾波器的通道數(shù)是保持一致的。 每個(gè)通道分別卷積計(jì)算出值,再將各通道的值相加得到最終的一個(gè)輸出值。 這樣只能得到一個(gè)通道數(shù)的輸出,怎樣使得輸出也多通道訥?
應(yīng)用多個(gè)濾波器(權(quán)重),比如FN個(gè)。如下圖所示。
濾波器變四維,一個(gè)濾波器對(duì)應(yīng)一個(gè)輸出特征圖。還可追加偏置(FN,1,1) 作為4維數(shù)據(jù),濾波器按(output_channel, input_channel, height, width) 的順序書(shū)寫(xiě)。 比如,通道數(shù)為 3、大小為 5 × 5 的濾波器有20個(gè)時(shí),可以寫(xiě)成(20, 3, 5, 5)。 不同形狀的方塊相加時(shí),可以基于NumPy的廣播功能輕松實(shí)現(xiàn)(1.5.5節(jié))。 2.5 批處理
目的:實(shí)現(xiàn)數(shù)據(jù)的高效化,打包N個(gè)數(shù)據(jù)一起處理。即將N次處理匯總為一次
3維——> 4維,即(C,H,W) ——> (N,C,H,W)
三、池化層
目的:縮小H,W方向上的空間的運(yùn)算。比如將2 × 2區(qū)域集約成1個(gè)元素來(lái)處理,縮小空間大小。
Max池化:獲取最大值的運(yùn)算 一般設(shè)置:池化的窗口大小和步幅設(shè)定成相同的值。比如這里都是2。 在圖像識(shí)別中,主要用Max池化。 池化層的特征:
沒(méi)有要學(xué)習(xí)的參數(shù);
通道數(shù)不發(fā)生變化,輸入3張,輸出也是3張;
對(duì)微小的位置變化具有魯棒性(健壯),即輸入數(shù)據(jù)發(fā)生微小偏差時(shí),池化也可以返回相同的結(jié)果;即池化能吸收輸入數(shù)據(jù)的偏差。
四、Conv層和Pooling層的實(shí)現(xiàn) 4.1 4維數(shù)組
例如:隨機(jī)生成一個(gè)四維數(shù)據(jù)(10,1,28,28) 10個(gè)通道數(shù)為1,高長(zhǎng)為28的數(shù)據(jù)
>>> x = np.random.rand(10, 1, 28, 28) # 隨機(jī)生成10個(gè)通道數(shù)為1,高長(zhǎng)為28的輸入數(shù)據(jù)>>> x.shape(10,1,28,28)
>>> x[0].shape # (1, 28, 28) x[0]:第一個(gè)數(shù)據(jù)(第一個(gè)方塊)>>> x[1].shape # (1, 28, 28) x[1]:第二個(gè)數(shù)據(jù)>>> x[0, 0] # 或者x[0][0] 第一個(gè)數(shù)據(jù)的第一個(gè)通道>>>x[1,0]#或者x[1][0]第二個(gè)數(shù)據(jù)的第一個(gè)通道 4.2 基于im2col的展開(kāi)
問(wèn)題:實(shí)現(xiàn)卷積運(yùn)行,需要重復(fù)好幾層for,麻煩,而且numPy訪問(wèn)元素最好不要用 for(慢)
解決:用im2col函數(shù)(image to column 從圖像到矩陣):將輸入數(shù)據(jù)展開(kāi)以合適濾波器(權(quán)重)
將4維數(shù)據(jù) ——> 2維數(shù)據(jù)
(N,C,H,W),即(批處理器,通道數(shù),高,長(zhǎng))
使用im2col更消耗內(nèi)存,但轉(zhuǎn)換為矩陣運(yùn)算更高效,可有效利用線性代數(shù)庫(kù)。
4.3 卷積層的實(shí)現(xiàn)
im2col函數(shù):將4維數(shù)據(jù)輸入——> 2維數(shù)據(jù)輸入
x 輸入 4維——> 2維矩陣
W 濾波器 4維——> 2維矩陣
矩陣乘積 X*W + b ——> 輸出2維 —(reshape)—> 輸出4維
im2col(input_data,filter_h,filter_w,stride=1,pad=0)
卷積層forward的代碼實(shí)現(xiàn)
import sys, ossys.path.append(os.pardir)from common.util import im2col x1 = np.random.rand(1, 3, 7, 7) # 批大小為1、通道為3的7 × 7的數(shù)據(jù)col1 = im2col(x1, 5, 5, stride=1, pad=0)print(col1.shape) # (9, 75) x2 = np.random.rand(10, 3, 7, 7) # 批大小為10、通道為3的7 × 7的數(shù)據(jù)col2 = im2col(x2, 5, 5, stride=1, pad=0)print(col2.shape) # (90, 75) # 卷積層類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 # 濾波器的展開(kāi) out = np.dot(col, col_W) + self.b out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) return out #反向傳播在common/layer.py中,必須進(jìn)行im2col的逆處理——>col2im(矩陣轉(zhuǎn)圖像)
4.4 池化層的實(shí)現(xiàn)
使用im2col函數(shù)
在通道方向獨(dú)立,按通道單獨(dú)展開(kāi)(卷積層是最后各個(gè)通道相加)
池化層的forward實(shí)現(xià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 # 計(jì)算輸出大小 out_h = int(1 + (H - self.pool_h) / self.stride) out_w = int(1 + (W - self.pool_w) / self.stride) # 展開(kāi)(1) 1.展開(kāi)輸入數(shù)據(jù) col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad) col = col.reshape(-1, self.pool_h*self.pool_w) # 最大值(2) 2.求各行的最大值 out = np.max(col, axis=1) # 轉(zhuǎn)換(3) 3.轉(zhuǎn)換為合適的輸出大小 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)換為合適的輸出大小
四、CNN實(shí)現(xiàn)
class SimpleConvNet: """簡(jiǎn)單的ConvNet conv - relu - pool - affine - relu - affine - softmax Parameters ---------- input_size : 輸入大?。∕NIST的情況下為784,三維(1, 28, 28)) hidden_size_list : 隱藏層的神經(jīng)元數(shù)量的列表(e.g. [100, 100, 100]) output_size : 輸出大小(MNIST的情況下為10,十種輸出可能) activation : 'relu' or 'sigmoid' weight_init_std : 指定權(quán)重的標(biāo)準(zhǔn)差(e.g. 0.01) 指定'relu'或'he'的情況下設(shè)定“He的初始值” 指定'sigmoid'或'xavier'的情況下設(shè)定“Xavier的初始值” """ 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): # 30個(gè)5*5的濾波器 # 取濾波器參數(shù)到conv_param字典中,備用 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] # 28 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)) # 池化層輸出,H,W減半 # 初始化權(quán)重,三層 self.params = {} # 濾波器就是第一層的權(quán)重 W1 = (30,1,5,5)(四維數(shù)據(jù)),即30個(gè)高、長(zhǎng)為5,通道數(shù)為1的數(shù)據(jù) self.params['W1'] = weight_init_std * np.random.randn(filter_num, input_dim[0], filter_size, filter_size) # 每一個(gè)濾波器都加一個(gè)偏置b1,有30個(gè) 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) # 生成層,用以調(diào)用 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() # 前向傳播,從頭開(kāi)始依次調(diào)用層,并將結(jié)果傳給下一層 def predict(self, x): for layer in self.layers.values(): x = layer.forward(x) return x # 除了使用predict,還要進(jìn)行forward,直到到達(dá)最后的SoftmaxWithLoss層 def loss(self, x, t): """求損失函數(shù) 參數(shù)x是輸入數(shù)據(jù)、t是教師標(biāo)簽 """ y = self.predict(x) return self.last_layer.forward(y, t) def accuracy(self, x, t, batch_size=100): if t.ndim != 1 : t = np.argmax(t, axis=1) acc = 0.0 for i in range(int(x.shape[0] / batch_size)): tx = x[i*batch_size:(i+1)*batch_size] tt = t[i*batch_size:(i+1)*batch_size] y = self.predict(tx) y = np.argmax(y, axis=1) acc += np.sum(y == tt) return acc / x.shape[0] def numerical_gradient(self, x, t): """求梯度(數(shù)值微分) Parameters ---------- x : 輸入數(shù)據(jù) t : 教師標(biāo)簽 Returns ------- 具有各層的梯度的字典變量 grads['W1']、grads['W2']、...是各層的權(quán)重 grads['b1']、grads['b2']、...是各層的偏置 """ loss_w = lambda w: self.loss(x, t) grads = {} for idx in (1, 2, 3): grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)]) grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)]) return grads # 調(diào)用各層的backward,并把權(quán)重參數(shù)的梯度存到grads字典中 def gradient(self, x, t): """求梯度(誤差反向傳播法)(二選一) Parameters ---------- x : 輸入數(shù)據(jù) t : 教師標(biāo)簽 Returns ------- 具有各層的梯度的字典變量 grads['W1']、grads['W2']、...是各層的權(quán)重 grads['b1']、grads['b2']、...是各層的偏置 """ # forward self.loss(x, t) # backward dout = 1 dout = self.last_layer.backward(dout) layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) # 設(shè)定 grads = {} grads['W1'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
五、CNN可視化
濾波器會(huì)提取邊緣或斑塊等原始信息。隨著層的加深,提取的信息也愈加復(fù)雜。
提取信息愈加復(fù)雜:
邊緣——> 紋理——> 物體部件——> 分類
1998年,CNN元祖:LeNet——> 比如手寫(xiě)數(shù)字識(shí)別
2012年:深度學(xué)習(xí) AlexNet
他們都是疊加多個(gè)卷積層,池化層,最后經(jīng)由全連接層輸出
LeNet:
激活函數(shù)用sigmoid (現(xiàn)主要使用ReLU )
通過(guò)子采樣來(lái)縮小數(shù)據(jù)(現(xiàn)主要使用Max池化)
AlexNet:
激活函數(shù)用ReLU
使用進(jìn)行局部正規(guī)化的LRN層(local responce normalization)
使用dropout
關(guān)于網(wǎng)絡(luò)結(jié)構(gòu),LeNet和AlexNet沒(méi)太大差別,現(xiàn)如今大數(shù)據(jù)和GPU的快速發(fā)展推動(dòng)了深度學(xué)習(xí)的發(fā)展。 注:如有細(xì)節(jié)處沒(méi)有寫(xiě)到的,請(qǐng)繼續(xù)精讀《深度學(xué)習(xí)入門(mén)》,對(duì)小白來(lái)說(shuō)真的是非常通俗易懂的深度學(xué)習(xí)入門(mén)書(shū)籍。(書(shū)中的例子主要是基于CV的)
-
神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
42文章
4776瀏覽量
100948 -
深度學(xué)習(xí)
+關(guān)注
關(guān)注
73文章
5510瀏覽量
121337 -
cnn
+關(guān)注
關(guān)注
3文章
353瀏覽量
22265
原文標(biāo)題:【基礎(chǔ)詳解】手磕實(shí)現(xiàn) CNN卷積神經(jīng)網(wǎng)絡(luò)!
文章出處:【微信號(hào):zenRRan,微信公眾號(hào):深度學(xué)習(xí)自然語(yǔ)言處理】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論