9.1 卷積層的運算
傳送門: https://www.cnblogs.com/greentomlee/p/12314064.html
github: Leezhen2014: https://github.com/Leezhen2014/python_deep_learning
卷積的forward
卷積的計算程序網上的資料已經做夠好了,沒必要自己再寫一遍,只把資料搬運到這里:
http://deeplearning.net/software/theano_versions/dev/tutorial/conv_arithmetic.html#transposed-convolution-arithmetic
https://www.zhihu.com/question/43609045
https://blog.csdn.net/weixin_44106928/article/details/103079668
這里總結一下有padding\stride的卷積操作:

假設,輸入大小為(H,W,C),fileter大小為(FH,FW,C)*N ; padding=P, stride=S,卷積后的形狀為(OH,OW,OC)

1 def forward(self, x): 2 ''' 3 使用im2col 將輸入的x 轉換成2D矩陣 4 然后 y= w*x+b 以矩陣的形式完成 5 最后回傳y 6 :param x: x為4D tensor, 輸入資料 7 :return: out=w*x+b 8 ''' 9 FN, C, FH, FW = self.W.shape 10 N, C, H, W = x.shape 11 out_h = 1 + int((H + 2 * self.pad - FH) / self.stride) 12 out_w = 1 + int((W + 2 * self.pad - FW) / self.stride) 13 14 col = im2col(x, FH, FW, self.stride, self.pad) 15 col_W = self.W.reshape(FN, -1).T 16 print("col.shape=%s"%str(col.shape)) 17 print("col_W.shape=%s"%str(col_W.shape)) 18 19 out = np.dot(col, col_W) 20 print("out.shape=%s"%str(out.shape)) 21 out=out+ self.b 22 out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) 23 24 self.x = x 25 self.col = col 26 self.col_W = col_W 27 28 return out 29
卷積的backward
概念介紹: https://zhuanlan.zhihu.com/p/33802329
卷積的backward是對卷積的求導,
代碼實作如下:
1 def backward(self, dout): 2 ''' 3 反饋程序中也需要將2D 矩陣轉換為4D tensor 4 :param dout: 梯度差 5 :return: 6 ''' 7 FN, C, FH, FW = self.W.shape 8 dout = dout.transpose(0, 2, 3, 1).reshape(-1, FN) # NCHW 9 10 self.db = np.sum(dout, axis=0)# NHWC , 求和 11 self.dW = np.dot(self.col.T, dout) # 點乘w 12 self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW) 13 14 dcol = np.dot(dout, self.col_W.T) 15 dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad) 16 17 return dx
9.2 引入im2col 概念
再講卷積的實作之前,首先拋出一個問題:如果按照上述的卷積方式計算,是否會影響性能?
答案是肯定會受影響的,
因此,我們需要向優化一下conv的計算方式.
按照“以空間換時間”的思想,我們可以做一些優化,使得在conv和pool的時候運算速度加快,
首先,我們知道Numpy對大型矩陣的運算是有做優化的,這個特點我們應該好好利用;
其次,我們知道Numpy在做多個嵌套的for回圈的時候,O(n)會很大;應該避免做多個for回圈;
因此,要是將4D的卷積運算轉換成2D的矩陣乘法就會好很多;filter也可以變成2D的陣列;
Im2col便是將4D資料轉換成2D矩陣的函式,
該函式大致的思路是:filter按照行列展開成一個2D矩陣即可,input_data按照計算的單元重新組合,因此需要寫一個函式將影像轉換成2D矩陣,該函式可以將影像展開成適合與濾波去做乘法的矩陣,
展開和計算的流程如下:

9.3 單元測驗im2col
對filter計算有影響的因素有input_data,filter_h,filter_w,stride, padding;im2col會應該根據以上的因因素展開input_data,展開后的input_data一定是比之前要大的;
我們可以嘗試計算一下input_data展開后的資料形狀:
假設,輸入資料為4*4*3大小的tensor; filter有兩個為2*(2*2*3),filter_h=2,filter_w=2,stride=1, padding=0;這里可以計算出展開以后的大小:
Filter為有兩個,分別為f1和f2; shape=(2*2*3), 按照行展開成2D的矩陣以后如下圖所示:

Input_data為4*4*3的tensor,如下圖所示:

Input_data首先會找出filter對應的計算單元,這些還是需要padding\stride\filter_w\filter_h相關,找出計算的單元以后,按照行展開,最后得到的資料便是im2col的結果:

Input_data和filter這樣展開以后,卷積計算就可以按照矩陣乘法的方式計算,避免了重復的for回圈,如下圖所示,黑色和灰色區域是計算的結果,不必擔心矩陣過大是否會影響計算速度,Numpy對大規模矩陣乘法內部有優化加速,這樣展開以后恰恰也能充分的利用numpy的特性,

Im2col的實作:
1 def im2col(input_data, filter_h, filter_w, stride=1, pad=0): 2 ''' 3 4 :param input_data: 輸入資料由4維陣列組成(N,C,H,W) 5 :param filter_h: filer的高 6 :param filter_w: filter的寬 7 :param stride: stride 8 :param pad: padding 9 :return: 2D矩陣 10 ''' 11 # 計算輸出的大小 12 N, C, H, W = input_data.shape 13 out_h = (H + 2*pad - filter_h)//stride + 1 14 out_w = (W + 2*pad - filter_w)//stride + 1 15 # padding 16 img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant') 17 col = np.zeros((N, C, filter_h, filter_w, out_h, out_w)) 18 # 計算單元 19 for y in range(filter_h): 20 y_max = y + stride*out_h 21 for x in range(filter_w): 22 x_max = x + stride*out_w 23 col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride] 24 # 重新排列 25 col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1) 26 return col
測驗代碼:
1 # -*- coding: utf-8 -*- 2 # @File : test_im2col.py 3 # @Author: lizhen 4 # @Date : 2020/2/14 5 # @Desc : 測驗im2col 6 import numpy as np 7 8 from src.common.util import im2col,col2im 9 10 if __name__ == '__main__': 11 raw_data = https://www.cnblogs.com/greentomlee/p/[3, 0, 4, 2, 12 6, 5, 4, 3, 13 3, 0, 2, 3, 14 1, 0, 3, 1, 15 16 1, 2, 0, 1, 17 3, 0, 2, 4, 18 1, 0, 3, 2, 19 4, 3, 0, 1, 20 21 4, 2, 0, 1, 22 1, 2, 0, 4, 23 3, 0, 4, 2, 24 6, 2, 4, 5 25 ] 26 27 input_data = https://www.cnblogs.com/greentomlee/p/np.array(raw_data) 28 input_data = https://www.cnblogs.com/greentomlee/p/input_data.reshape(1,3,4,4) 29 print(input_data.shape) 30 col1 = im2col(input_data=https://www.cnblogs.com/greentomlee/p/input_data,filter_h=2,filter_w=2,stride=1,pad=0)#input_data, filter_h, filter_w, stride=1, pad=0 31 print(col1) 32
========輸出:可以發現和上面的繪圖的結果是一致的 =====
(1, 3, 4, 4)
[[3. 0. 6. 5. 1. 2. 3. 0. 4. 2. 1. 2.]
[0. 4. 5. 4. 2. 0. 0. 2. 2. 0. 2. 0.]
[4. 2. 4. 3. 0. 1. 2. 4. 0. 1. 0. 4.]
[6. 5. 3. 0. 3. 0. 1. 0. 1. 2. 3. 0.]
[5. 4. 0. 2. 0. 2. 0. 3. 2. 0. 0. 4.]
[4. 3. 2. 3. 2. 4. 3. 2. 0. 4. 4. 2.]
[3. 0. 1. 0. 1. 0. 4. 3. 3. 0. 6. 2.]
[0. 2. 0. 3. 0. 3. 3. 0. 0. 4. 2. 4.]
[2. 3. 3. 1. 3. 2. 0. 1. 4. 2. 4. 5.]]
9.3 卷積操作的實作
卷積操作也需要實作forward和backward函式,
Forward函式中用到了9.1\9.2的im2col
1 class Convolution: 2 def __init__(self, W, b, stride=1, pad=0): 3 ''' 4 conv的建構式 5 :param W: 2D矩陣 6 :param b: 7 :param stride: 8 :param pad: 9 ''' 10 self.W = W 11 self.b = b 12 self.stride = stride 13 self.pad = pad 14 15 # 中間結果(backward的時候使用) 16 self.x = None 17 self.col = None 18 self.col_W = None 19 20 # 權重的梯度/偏置的梯度 21 self.dW = None 22 self.db = None 23 24 def forward(self, x): 25 ''' 26 使用im2col 將輸入的x 轉換成2D矩陣 27 然后 y= w*x+b 以矩陣的形式完成 28 最后回傳y 29 :param x: x為4D tensor, 輸入資料 30 :return: out=w*x+b 31 ''' 32 FN, C, FH, FW = self.W.shape 33 N, C, H, W = x.shape 34 out_h = 1 + int((H + 2 * self.pad - FH) / self.stride) 35 out_w = 1 + int((W + 2 * self.pad - FW) / self.stride) 36 37 col = im2col(x, FH, FW, self.stride, self.pad) 38 col_W = self.W.reshape(FN, -1).T 39 40 out = np.dot(col, col_W) + self.b 41 out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) 42 43 self.x = x 44 self.col = col 45 self.col_W = col_W 46 47 return out 48 49 def backward(self, dout): 50 ''' 51 反饋程序中也需要將2D 矩陣轉換為4D tensor 52 :param dout: 梯度差 53 :return: 54 ''' 55 FN, C, FH, FW = self.W.shape 56 dout = dout.transpose(0, 2, 3, 1).reshape(-1, FN) 57 58 self.db = np.sum(dout, axis=0) 59 self.dW = np.dot(self.col.T, dout) 60 self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW) 61 62 dcol = np.dot(dout, self.col_W.T) 63 dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad) 64 65 return dx 66
9.4單元測驗卷積操作
輸入:input_data\filters
輸出:output

測驗代碼:
1 2 # -*- coding: utf-8 -*- 3 # @File : test_im2col.py 4 # @Author: lizhen 5 # @Date : 2020/2/14 6 # @Desc : 測驗im2col 7 import numpy as np 8 9 from src.common.util import im2col,col2im 10 from src.common.layers import Convolution 11 12 13 if __name__ == '__main__': 14 raw_data = https://www.cnblogs.com/greentomlee/p/[3, 0, 4, 2, 15 6, 5, 4, 3, 16 3, 0, 2, 3, 17 1, 0, 3, 1, 18 19 1, 2, 0, 1, 20 3, 0, 2, 4, 21 1, 0, 3, 2, 22 4, 3, 0, 1, 23 24 4, 2, 0, 1, 25 1, 2, 0, 4, 26 3, 0, 4, 2, 27 6, 2, 4, 5 28 ] 29 30 raw_filter=[ 31 1, 1, 1, 1, 1, 1, 32 1, 1, 1, 1, 1, 1, 33 2, 2, 2, 2, 2, 2, 34 2, 2, 2, 2, 2, 2, 35 36 ] 37 38 39 40 input_data = https://www.cnblogs.com/greentomlee/p/np.array(raw_data) 41 filter_data = https://www.cnblogs.com/greentomlee/p/np.array(raw_filter) 42 43 x = input_data.reshape(1,3,4,4)# NCHW 44 W = filter_data.reshape(2,3,2,2) # NHWC 45 b = np.zeros(2) 46 # b = b.reshape((2,1)) 47 # col1 = im2col(input_data=https://www.cnblogs.com/greentomlee/p/x,filter_h=2,filter_w=2,stride=1,pad=0)#input_data, filter_h, filter_w, stride=1, pad=0 48 # print(col1) 49 50 print("input_data.shape=%s"%str(input_data.shape)) 51 print("W.shape=%s"%str(W.shape)) 52 print("b.shape=%s"%str(b.shape)) 53 conv = Convolution(W,b) # def __init__(self, W, b, stride=1, pad=0) 54 out = conv.forward(x) 55 print("bout.shape=%s"%str(out.shape)) 56 print(out)
Conv的輸出結果,與上圖的結果一致,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/506552.html
標籤:其他
