【深度學習系列】卷積神經網絡詳解(二)——自己手寫一個卷積神經網絡

2021-02-20 Python愛好者社區

點擊上圖,立即開啟AI急速修煉

作者:Charlotte    高級算法工程師 ,博客專家;

擅長用通俗易懂的方式講解深度學習和機器學習算法,熟悉Tensorflow,PaddlePaddle等深度學習框架,負責過多個機器學習落地項目,如垃圾評論自動過濾,用戶分級精準營銷,分布式深度學習平臺搭建等,都取了的不錯的效果。

博客專欄:https://www.cnblogs.com/charlotte77/

前文傳送門:

【好書推薦&學習階段】三個月教你從零入門深度學習

【深度學習系列】PaddlePaddle之手寫數字識別

【深度學習系列】卷積神經網絡CNN原理詳解(一)——基本原理

上篇文章中我們講解了卷積神經網絡的基本原理,包括幾個基本層的定義、運算規則等。本文主要寫卷積神經網絡如何進行一次完整的訓練,包括前向傳播和反向傳播,並自己手寫一個卷積神經網絡。如果不了解基本原理的,可以先看看上篇文章:【深度學習系列】卷積神經網絡CNN原理詳解(一)——基本原理

卷積神經網絡的前向傳播

  

首先我們來看一個最簡單的卷積神經網絡:


1.輸入層---->卷積層

  

以上一節的例子為例,輸入是一個4*4 的image,經過兩個2*2的卷積核進行卷積運算後,變成兩個3*3的feature_map


 

以卷積核filter1為例(stride = 1 ):


 

計算第一個卷積層神經元o11的輸入: 


 

神經元o11的輸出:(此處使用Relu激活函數)


 

其他神經元計算方式相同

2.卷積層---->池化層


計算池化層m11 的輸入(取窗口為 2 * 2),池化層沒有激活函數


 

3.池化層---->全連接層


池化層的輸出到flatten層把所有元素「拍平」,然後到全連接層。

  

4.全連接層---->輸出層

  

全連接層到輸出層就是正常的神經元與神經元之間的鄰接相連,通過softmax函數計算後輸出到output,得到不同類別的概率值,輸出概率值最大的即為該圖片的類別。

 

卷積神經網絡的反向傳播


傳統的神經網絡是全連接形式的,如果進行反向傳播,只需要由下一層對前一層不斷的求偏導,即求鏈式偏導就可以求出每一層的誤差敏感項,然後求出權重和偏置項的梯度,即可更新權重。而卷積神經網絡有兩個特殊的層:卷積層和池化層。池化層輸出時不需要經過激活函數,是一個滑動窗口的最大值,一個常數,那麼它的偏導是1。池化層相當於對上層圖片做了一個壓縮,這個反向求誤差敏感項時與傳統的反向傳播方式不同。從卷積後的feature_map反向傳播到前一層時,由於前向傳播時是通過卷積核做卷積運算得到的feature_map,所以反向傳播與傳統的也不一樣,需要更新卷積核的參數。下面我們介紹一下池化層和卷積層是如何做反向傳播的。

  

在介紹之前,首先回顧一下傳統的反向傳播方法:

1.通過前向傳播計算每一層的輸入值neti,j (如卷積後的feature_map的第一個神經元的輸入:neti11)

2.反向傳播計算每個神經元的誤差項,其中E為損失函數計算得到的總體誤差,可以用平方差,交叉熵等表示。

3.計算每個神經元權重wi,j的梯度,

4.更新權重 wi,j=wi,j−λ⋅ηi,j(其中λλ為學習率)

  

卷積層的反向傳播

  

由前向傳播可得:

neti11表示上一層的輸入,outo11表示上一層的輸出

首先計算卷積的上一層的第一個元素i11的誤差項δ11:


(注意這裡是neti11,因為i11=f(neti11),f表示激活函數,不是neto11)

註:原來這裡寫的是計算輸入層的誤差項是不準確的,這裡的i11

表示的是卷積層的上一層即可。

先計算

此處我們並不清楚 怎麼算,那可以先把input層通過卷積核做完卷積運算後的輸出feature_map寫出來:


 

然後依次對輸入元素ii,j求偏導

  

i11的偏導:

i12的偏導:


i13的偏導:


i21的偏導:


i22的偏導:


觀察一下上面幾個式子的規律,歸納一下,可以得到如下表達式:


 

圖中的卷積核進行了180°翻轉,與這一層的誤差敏感項矩陣deltai,j)周圍補零後的矩陣做卷積運算後,就可以得到,即

  

第一項求完後,我們來求第二項


此時我們的誤差敏感矩陣就求完了,得到誤差敏感矩陣後,即可求權重的梯度。

  

由於上面已經寫出了卷積層的輸入neto11與權重hi,j之間的表達式, 所以可以直接求出:


  

推論出權重的梯度


 

偏置項的梯度


 

可以看出,偏置項的偏導等於這一層所有誤差敏感項之和。得到了權重和偏置項的梯度後,就可以根據梯度下降法更新權重和梯度了。 

    

池化層的反向傳播

   

池化層的反向傳播就比較好求了,看著下面的圖,左邊是上一層的輸出,也就是卷積層的輸出feature_map,右邊是池化層的輸入,還是先根據前向傳播,把式子都寫出來,方便計算:


假設上一層這個滑動窗口的最大值是outo11


手寫一個卷積神經網絡

  

1.定義一個卷積層

   

首先我們通過ConvLayer來實現一個卷積層,定義卷積層的超參數

class ConvLayer(object):    '''    參數含義:    input_width:輸入圖片尺寸——寬度    input_height:輸入圖片尺寸——長度    channel_number:通道數,彩色為3,灰色為1    filter_width:卷積核的寬    filter_height:卷積核的長    filter_number:卷積核數量    zero_padding:補零長度    stride:步長    activator:激活函數    learning_rate:學習率    '''    def __init__(self, input_width, input_height,                 channel_number, filter_width,                 filter_height, filter_number,                 zero_padding, stride, activator,                 learning_rate):        self.input_width = input_width        self.input_height = input_height        self.channel_number = channel_number        self.filter_width = filter_width        self.filter_height = filter_height        self.filter_number = filter_number        self.zero_padding = zero_padding        self.stride = stride        self.output_width = \            ConvLayer.calculate_output_size(            self.input_width, filter_width, zero_padding,            stride)        self.output_height = \            ConvLayer.calculate_output_size(            self.input_height, filter_height, zero_padding,            stride)        self.output_array = np.zeros((self.filter_number,            self.output_height, self.output_width))        self.filters = []        for i in range(filter_number):            self.filters.append(Filter(filter_width,                filter_height, self.channel_number))        self.activator = activator        self.learning_rate = learning_rate

其中calculate_output_size用來計算通過卷積運算後輸出的feature_map大小

@staticmethod    def calculate_output_size(input_size,             filter_size, zero_padding, stride):         return (input_size - filter_size +             2 * zero_padding) / stride + 1

2.構造一個激活函數

  

此處用的是RELU激活函數,因此我們在activators.py裡定義,forward是前向計算,backforward是計算公式的導數:

class ReluActivator(object):    def forward(self, weighted_input):        #return weighted_input        return max(0, weighted_input)    def backward(self, output):        return 1 if output > 0 else 0

其他常見的激活函數我們也可以放到activators裡,如sigmoid函數,我們可以做如下定義:

class SigmoidActivator(object):    def forward(self, weighted_input):        return 1.0 / (1.0 + np.exp(-weighted_input))    #the partial of sigmoid    def backward(self, output):        return output * (1 - output)

如果我們需要自動以其他的激活函數,都可以在activator.py定義一個類即可。

  

3.定義一個類,保存卷積層的參數和梯度

class Filter(object):    def __init__(self, width, height, depth):        #初始權重        self.weights = np.random.uniform(-1e-4, 1e-4,            (depth, height, width))        #初始偏置        self.bias = 0        self.weights_grad = np.zeros(            self.weights.shape)        self.bias_grad = 0    def __repr__(self):        return 'filter weights:\n%s\nbias:\n%s' % (            repr(self.weights), repr(self.bias))    def get_weights(self):        return self.weights    def get_bias(self):        return self.bias    def update(self, learning_rate):        self.weights -= learning_rate * self.weights_grad        self.bias -= learning_rate * self.bias_grad

4.卷積層的前向傳播

  

1).獲取卷積區域

# 獲取卷積區域def get_patch(input_array, i, j, filter_width,              filter_height, stride):    '''    從輸入數組中獲取本次卷積的區域,    自動適配輸入為2D和3D的情況    '''    start_i = i * stride    start_j = j * stride    if input_array.ndim == 2:        input_array_conv = input_array[            start_i : start_i + filter_height,            start_j : start_j + filter_width]        print "input_array_conv:",input_array_conv        return input_array_conv    elif input_array.ndim == 3:        input_array_conv = input_array[:,            start_i : start_i + filter_height,            start_j : start_j + filter_width]        print "input_array_conv:",input_array_conv        return input_array_conv

2).進行卷積運算

def conv(input_array,         kernel_array,         output_array,         stride, bias):    '''    計算卷積,自動適配輸入為2D和3D的情況    '''    channel_number = input_array.ndim    output_width = output_array.shape[1]    output_height = output_array.shape[0]    kernel_width = kernel_array.shape[-1]    kernel_height = kernel_array.shape[-2]    for i in range(output_height):        for j in range(output_width):            output_array[i][j] = (                get_patch(input_array, i, j, kernel_width,                    kernel_height, stride) * kernel_array                ).sum() + bias

3).增加zero_padding

#增加Zero paddingdef padding(input_array, zp):    '''    為數組增加Zero padding,自動適配輸入為2D和3D的情況    '''    if zp == 0:        return input_array    else:        if input_array.ndim == 3:            input_width = input_array.shape[2]            input_height = input_array.shape[1]            input_depth = input_array.shape[0]            padded_array = np.zeros((                input_depth,                input_height + 2 * zp,                input_width + 2 * zp))            padded_array[:,                zp : zp + input_height,                zp : zp + input_width] = input_array            return padded_array        elif input_array.ndim == 2:            input_width = input_array.shape[1]            input_height = input_array.shape[0]            padded_array = np.zeros((                input_height + 2 * zp,                input_width + 2 * zp))            padded_array[zp : zp + input_height,                zp : zp + input_width] = input_array            return padded_array

4).進行前向傳播

def forward(self, input_array):        '''        計算卷積層的輸出        輸出結果保存在self.output_array        '''        self.input_array = input_array        self.padded_input_array = padding(input_array,            self.zero_padding)        for f in range(self.filter_number):            filter = self.filters[f]            conv(self.padded_input_array,                filter.get_weights(), self.output_array[f],                self.stride, filter.get_bias())        element_wise_op(self.output_array,                        self.activator.forward)

其中element_wise_op函數是將每個組的元素對應相乘

# 對numpy數組進行element wise操作,將矩陣中的每個元素對應相乘def element_wise_op(array, op):    for i in np.nditer(array,                       op_flags=['readwrite']):        i[...] = op(i)

5.卷積層的反向傳播

  

1).將誤差傳遞到上一層

def bp_sensitivity_map(self, sensitivity_array,                           activator):        '''        計算傳遞到上一層的sensitivity map        sensitivity_array: 本層的sensitivity map        activator: 上一層的激活函數        '''        # 處理卷積步長,對原始sensitivity map進行擴展        expanded_array = self.expand_sensitivity_map(            sensitivity_array)        # full卷積,對sensitivitiy map進行zero padding        # 雖然原始輸入的zero padding單元也會獲得殘差        # 但這個殘差不需要繼續向上傳遞,因此就不計算了        expanded_width = expanded_array.shape[2]        zp = (self.input_width +              self.filter_width - 1 - expanded_width) / 2        padded_array = padding(expanded_array, zp)        # 初始化delta_array,用於保存傳遞到上一層的        # sensitivity map        self.delta_array = self.create_delta_array()        # 對於具有多個filter的卷積層來說,最終傳遞到上一層的        # sensitivity map相當於所有的filter的        # sensitivity map之和        for f in range(self.filter_number):            filter = self.filters[f]            # 將filter權重翻轉180度            flipped_weights = np.array(map(                lambda i: np.rot90(i, 2),                filter.get_weights()))            # 計算與一個filter對應的delta_array            delta_array = self.create_delta_array()            for d in range(delta_array.shape[0]):                conv(padded_array[f], flipped_weights[d],                    delta_array[d], 1, 0)            self.delta_array += delta_array        # 將計算結果與激活函數的偏導數做element-wise乘法操作        derivative_array = np.array(self.input_array)        element_wise_op(derivative_array,                        activator.backward)        self.delta_array *= derivative_array

2).保存傳遞到上一層的sensitivity map的數組

def create_delta_array(self):        return np.zeros((self.channel_number,            self.input_height, self.input_width))

3).計算代碼梯度

def bp_gradient(self, sensitivity_array):        # 處理卷積步長,對原始sensitivity map進行擴展        expanded_array = self.expand_sensitivity_map(            sensitivity_array)        for f in range(self.filter_number):            # 計算每個權重的梯度            filter = self.filters[f]            for d in range(filter.weights.shape[0]):                conv(self.padded_input_array[d],                     expanded_array[f],                     filter.weights_grad[d], 1, 0)            # 計算偏置項的梯度            filter.bias_grad = expanded_array[f].sum()

4).按照梯度下降法更新參數

def update(self):        '''        按照梯度下降,更新權重        '''        for filter in self.filters:            filter.update(self.learning_rate)

6.MaxPooling層的訓練

  

1).定義MaxPooling類

class MaxPoolingLayer(object):    def __init__(self, input_width, input_height,                 channel_number, filter_width,                 filter_height, stride):        self.input_width = input_width        self.input_height = input_height        self.channel_number = channel_number        self.filter_width = filter_width        self.filter_height = filter_height        self.stride = stride        self.output_width = (input_width -            filter_width) / self.stride + 1        self.output_height = (input_height -            filter_height) / self.stride + 1        self.output_array = np.zeros((self.channel_number,            self.output_height, self.output_width))

2).前向傳播計算

# 前向傳播    def forward(self, input_array):        for d in range(self.channel_number):            for i in range(self.output_height):                for j in range(self.output_width):                    self.output_array[d,i,j] = (                        get_patch(input_array[d], i, j,                            self.filter_width,                            self.filter_height,                            self.stride).max())

3).反向傳播計算

#反向傳播    def backward(self, input_array, sensitivity_array):        self.delta_array = np.zeros(input_array.shape)        for d in range(self.channel_number):            for i in range(self.output_height):                for j in range(self.output_width):                    patch_array = get_patch(                        input_array[d], i, j,                        self.filter_width,                        self.filter_height,                        self.stride)                    k, l = get_max_index(patch_array)                    self.delta_array[d,                        i * self.stride + k,                        j * self.stride + l] = \                        sensitivity_array[d,i,j]

完整代碼請見:cnn.py (https://github.com/huxiaoman7/PaddlePaddle_code/blob/master/1.mnist/cnn.py)

#coding:utf-8'''Created by huxiaoman 2017.11.22'''import numpy as npfrom activators import ReluActivator,IdentityActivatorclass ConvLayer(object):    def __init__(self,input_width,input_weight,             channel_number,filter_width,             filter_height,filter_number,             zero_padding,stride,activator,             learning_rate):        self.input_width = input_width        self.input_height = input_height        self.channel_number = channel_number        self.filter_width = filter_width        self.filter_height = filter_height        self.filter_number = filter_number        self.zero_padding = zero_padding        self.stride = stride #此處可以加上stride_x, stride_y        self.output_width = ConvLayer.calculate_output_size(                self.input_width,filter_width,zero_padding,                stride)        self.output_height = ConvLayer.calculate_output_size(                self.input_height,filter_height,zero_padding,                stride)        self.output_array = np.zeros((self.filter_number,                self.output_height,self.output_width))        self.filters = []        for i in range(filter_number):                self.filters.append(Filter(filter_width,                filter_height,self.channel_number))        self.activator = activator        self.learning_rate = learning_rate    def forward(self,input_array):        '''        計算卷積層的輸出        輸出結果保存在self.output_array        '''        self.input_array = input_array        self.padded_input_array = padding(input_array,            self.zero_padding)        for i in range(self.filter_number):            filter = self.filters[f]            conv(self.padded_input_array,                 filter.get_weights(), self.output_array[f],                 self.stride, filter.get_bias())            element_wise_op(self.output_array,                    self.activator.forward)def get_batch(input_array, i, j, filter_width,filter_height,stride):    '''    從輸入數組中獲取本次卷積的區域,    自動適配輸入為2D和3D的情況    '''    start_i = i * stride    start_j = j * stride    if input_array.ndim == 2:        return input_array[            start_i : start_i + filter_height,            start_j : start_j + filter_width]    elif input_array.ndim == 3:        return input_array[            start_i : start_i + filter_height,                        start_j : start_j + filter_width]# 獲取一個2D區域的最大值所在的索引def get_max_index(array):    max_i = 0    max_j = 0    max_value = array[0,0]    for i in range(array.shape[0]):        for j in range(array.shape[1]):            if array[i,j] > max_value:                max_value = array[i,j]                max_i, max_j = i, j    return max_i, max_jdef conv(input_array,kernal_array,    output_array,stride,bias):    '''    計算卷積,自動適配輸入2D,3D的情況    '''    channel_number = input_array.ndim    output_width = output_array.shape[1]    output_height = output_array.shape[0]    kernel_width = kernel_array.shape[-1]    kernel_height = kernel_array.shape[-2]    for i in range(output_height):        for j in range(output_width):            output_array[i][j] = (                get_patch(input_array, i, j, kernel_width,                    kernel_height,stride) * kernel_array).sum() +biasdef element_wise_op(array, op):    for i in np.nditer(array,               op_flags = ['readwrite']):        i[...] = op(i)class ReluActivators(object):    def forward(self, weighted_input):        # Relu計算公式 = max(0,input)        return max(0, weighted_input)    def backward(self,output):        return 1 if output > 0 else 0class SigmoidActivator(object):    def forward(self,weighted_input):        return 1 / (1 + math.exp(- weighted_input))    def backward(self,output):        return output * (1 - output)

最後,我們用之前的4 * 4的image數據檢驗一下通過一次卷積神經網絡進行前向傳播和反向傳播後的輸出結果:

def init_test():    a = np.array(        [[[0,1,1,0,2],          [2,2,2,2,1],          [1,0,0,2,0],          [0,1,1,0,0],          [1,2,0,0,2]],         [[1,0,2,2,0],          [0,0,0,2,0],          [1,2,1,2,1],          [1,0,0,0,0],          [1,2,1,1,1]],         [[2,1,2,0,0],          [1,0,0,1,0],          [0,2,1,0,1],          [0,1,2,2,2],          [2,1,0,0,1]]])    b = np.array(        [[[0,1,1],          [2,2,2],          [1,0,0]],         [[1,0,2],          [0,0,0],          [1,2,1]]])    cl = ConvLayer(5,5,3,3,3,2,1,2,IdentityActivator(),0.001)    cl.filters[0].weights = np.array(        [[[-1,1,0],          [0,1,0],          [0,1,1]],         [[-1,-1,0],          [0,0,0],          [0,-1,0]],         [[0,0,-1],          [0,1,0],          [1,-1,-1]]], dtype=np.float64)    cl.filters[0].bias=1    cl.filters[1].weights = np.array(        [[[1,1,-1],          [-1,-1,1],          [0,-1,1]],         [[0,1,0],         [-1,0,-1],          [-1,1,0]],         [[-1,0,0],          [-1,0,1],          [-1,0,0]]], dtype=np.float64)    return a, b, cl

運行一下:

def test():    a, b, cl = init_test()    cl.forward(a)    print "前向傳播結果:", cl.output_array    cl.backward(a, b, IdentityActivator())    cl.update()    print "反向傳播後更新得到的filter1:",cl.filters[0]    print "反向傳播後更新得到的filter2:",cl.filters[1]if __name__ == "__main__":        test()

運行結果: 

前向傳播結果: [[[ 6.  7.  5.]  [ 3. -1. -1.]  [ 2. -1.  4.]] [[ 2. -5. -8.]  [ 1. -4. -4.]  [ 0. -5. -5.]]]反向傳播後更新得到的filter1: filter weights:array([[[-1.008,  0.99 , -0.009],        [-0.005,  0.994, -0.006],        [-0.006,  0.995,  0.996]],       [[-1.004, -1.001, -0.004],        [-0.01 , -0.009, -0.012],        [-0.002, -1.002, -0.002]],       [[-0.002, -0.002, -1.003],        [-0.005,  0.992, -0.005],        [ 0.993, -1.008, -1.007]]])bias:0.99099999999999999反向傳播後更新得到的filter2: filter weights:array([[[  9.98000000e-01,   9.98000000e-01,  -1.00100000e+00],        [ -1.00400000e+00,  -1.00700000e+00,   9.97000000e-01],        [ -4.00000000e-03,  -1.00400000e+00,   9.98000000e-01]],       [[  0.00000000e+00,   9.99000000e-01,   0.00000000e+00],        [ -1.00900000e+00,  -5.00000000e-03,  -1.00400000e+00],        [ -1.00400000e+00,   1.00000000e+00,   0.00000000e+00]],       [[ -1.00400000e+00,  -6.00000000e-03,  -5.00000000e-03],        [ -1.00200000e+00,  -5.00000000e-03,   9.98000000e-01],        [ -1.00200000e+00,  -1.00000000e-03,   0.00000000e+00]]])bias:-0.0070000000000000001

 PaddlePaddle卷積神經網絡源碼解析

  

卷積層

  

在上篇文章中,我們對paddlepaddle實現卷積神經網絡的的函數簡單介紹了一下。在手寫數字識別中,我們設計CNN的網絡結構時,調用了一個函數simple_img_conv_pool(上篇文章的連結已失效,因為已經把framework--->fluid,更新速度太快了 = =)使用方式如下:

conv_pool_1 = paddle.networks.simple_img_conv_pool(        input=img,        filter_size=5,        num_filters=20,        num_channel=1,        pool_size=2,        pool_stride=2,        act=paddle.activation.Relu())

這個函數把卷積層和池化層兩個部分封裝在一起,只用調用一個函數就可以搞定,非常方便。如果只需要單獨使用卷積層,可以調用這個函數img_conv_layer,使用方式如下:

conv = img_conv_layer(input=data, filter_size=1, filter_size_y=1,                              num_channels=8,                              num_filters=16, stride=1,                              bias_attr=False,                              act=ReluActivation())

我們來看一下這個函數具體有哪些參數(注釋寫明了參數的含義和怎麼使用)

def img_conv_layer(input,                   filter_size,                   num_filters,                   name=None,                   num_channels=None,                   act=None,                   groups=1,                   stride=1,                   padding=0,                   dilation=1,                   bias_attr=None,                   param_attr=None,                   shared_biases=True,                   layer_attr=None,                   filter_size_y=None,                   stride_y=None,                   padding_y=None,                   dilation_y=None,                   trans=False,                   layer_type=None):    """    適合圖像的卷積層。Paddle可以支持正方形和長方形兩種圖片尺寸的輸入    也可適用於圖像的反卷積(Convolutional Transpose,即deconv)。    同樣可支持正方形和長方形兩種尺寸輸入。    num_channel:輸入圖片的通道數。可以是1或者3,或者是上一層的通道數(卷積核數目 * 組的數量)    每一個組都會處理圖片的一些通道。舉個例子,如果一個輸入如偏的num_channel是256,設置4個group,    32個卷積核,那麼會創建32*4 = 128個卷積核來處理輸入圖片。通道會被分成四塊,32個卷積核會先    處理64(256/4=64)個通道。剩下的卷積核組會處理剩下的通道。    name:層的名字。可選,自定義。    type:basestring    input:這個層的輸入    type:LayerOutPut    filter_size:卷積核的x維,可以理解為width。                如果是正方形,可以直接輸入一個元祖組表示圖片的尺寸    type:int/ tuple/ list    filter_size_y:卷積核的y維,可以理解為height。                PaddlePaddle支持長方形的圖片尺寸,所以卷積核的尺寸為(filter_size,filter_size_y)    type:int/ None    act: 激活函數類型。默認選Relu    type:BaseActivation    groups:卷積核的組數量    type:int    stride: 水平方向的滑動步長。或者世界輸入一個元祖,代表水平數值滑動步長相同。    type:int/ tuple/ list    stride_y:垂直滑動步長。    type:int    padding: 補零的水平維度,也可以直接輸入一個元祖,水平和垂直方向上補零的維度相同。    type:int/ tuple/ list    padding_y:垂直方向補零的維度    type:int    dilation:水平方向的擴展維度。同樣可以輸入一個元祖表示水平和初值上擴展維度相同    :type:int/ tuple/ list    dilation_y:垂直方向的擴展維度    type:int    bias_attr:偏置屬性              False:不定義bias   True:bias初始化為0    type: ParameterAttribute/ None/ bool/ Any    num_channel:輸入圖片的通道channel。如果設置為None,自動生成為上層輸出的通道數    type: int    param_attr:卷積參數屬性。設置為None表示默認屬性    param_attr:ParameterAttribute    shared_bias:設置偏置項是否會在卷積核中共享    type:bool    layer_attr: Layer的 Extra Attribute    type:ExtraLayerAttribute    param trans:如果是convTransLayer,設置為True,如果是convlayer設置為conv    type:bool    layer_type:明確layer_type,默認為None。               如果trans= True,必須是exconvt或者cudnn_convt,否則的話要麼是exconv,要麼是cudnn_conv               ps:如果是默認的話,paddle會自動選擇適合cpu的ExpandConvLayer和適合GPU的CudnnConvLayer               當然,我們自己也可以明確選擇哪種類型    type:string    return:LayerOutput object    rtype:LayerOutput    """def img_conv_layer(input,                   filter_size,                   num_filters,                   name=None,                   num_channels=None,                   act=None,                   groups=1,                   stride=1,                   padding=0,                   dilation=1,                   bias_attr=None,                   param_attr=None,                   shared_biases=True,                   layer_attr=None,                   filter_size_y=None,                   stride_y=None,                   padding_y=None,                   dilation_y=None,                   trans=False,                   layer_type=None):    if num_channels is None:        assert input.num_filters is not None        num_channels = input.num_filters    if filter_size_y is None:        if isinstance(filter_size, collections.Sequence):            assert len(filter_size) == 2            filter_size, filter_size_y = filter_size        else:            filter_size_y = filter_size    if stride_y is None:        if isinstance(stride, collections.Sequence):            assert len(stride) == 2            stride, stride_y = stride        else:            stride_y = stride    if padding_y is None:        if isinstance(padding, collections.Sequence):            assert len(padding) == 2            padding, padding_y = padding        else:            padding_y = padding    if dilation_y is None:        if isinstance(dilation, collections.Sequence):            assert len(dilation) == 2            dilation, dilation_y = dilation        else:            dilation_y = dilation    if param_attr.attr.get('initial_smart'):        # special initial for conv layers.        init_w = (2.0 / (filter_size**2 * num_channels))**0.5        param_attr.attr["initial_mean"] = 0.0        param_attr.attr["initial_std"] = init_w        param_attr.attr["initial_strategy"] = 0        param_attr.attr["initial_smart"] = False    if layer_type:        if dilation > 1 or dilation_y > 1:            assert layer_type in [                "cudnn_conv", "cudnn_convt", "exconv", "exconvt"            ]        if trans:            assert layer_type in ["exconvt", "cudnn_convt"]        else:            assert layer_type in ["exconv", "cudnn_conv"]        lt = layer_type    else:        lt = LayerType.CONVTRANS_LAYER if trans else LayerType.CONV_LAYER    l = Layer(        name=name,        inputs=Input(            input.name,            conv=Conv(                filter_size=filter_size,                padding=padding,                dilation=dilation,                stride=stride,                channels=num_channels,                groups=groups,                filter_size_y=filter_size_y,                padding_y=padding_y,                dilation_y=dilation_y,                stride_y=stride_y),            **param_attr.attr),        active_type=act.name,        num_filters=num_filters,        bias=ParamAttr.to_bias(bias_attr),        shared_biases=shared_biases,        type=lt,        **ExtraLayerAttribute.to_kwargs(layer_attr))    return LayerOutput(        name,        lt,        parents=[input],        activation=act,        num_filters=num_filters,        size=l.config.size)

我們了解這些參數的含義後,對比我們之前自己手寫的CNN,可以看出paddlepaddle有幾個優點:

   

在我們自己寫的CNN中,只支持正方形的圖片長度,如果是長方形會報錯。滑動步長,補零的維度等也只支持水平和垂直方向上的維度相同。了解卷積層的參數含義後,我們來看一下底層的源碼是如何實現的:ConvBaseLayer.py 有興趣的同學可以在這個連結下看看底層是如何用C++寫的ConvLayer

  

池化層同理,可以按照之前的思路分析,有興趣的可以一直順延看到底層的實現,下次有機會再詳細分析。(佔坑明天補一下tensorflow的源碼實現)

 

總結  

  

本文主要講解了卷積神經網絡中反向傳播的一些技巧,包括卷積層和池化層的反向傳播與傳統的反向傳播的區別,並實現了一個完整的CNN,後續大家可以自己修改一些代碼,譬如當水平滑動長度與垂直滑動長度不同時需要怎麼調整等等,最後研究了一下paddlepaddle中CNN中的卷積層的實現過程,對比自己寫的CNN,總結了4個優點,底層是C++實現的,有興趣的可以自己再去深入研究。寫的比較粗糙,如果有問題歡迎留言:)

參考文章:

1.https://www.cnblogs.com/pinard/p/6494810.html

2.https://www.zybuluo.com/hanbingtao/note/476663 

從零開始學人工智慧

下圖掃碼了解本文作者胡老師的系列課程吧!

限時優惠價299!

相關焦點

  • 深度學習入門:淺析卷積神經網絡
    至今已有數種深度學習方法,如卷積神經網絡(CNN)、自編碼神經網絡(包括Auto encoder和Sparse Coding)和深度置信網絡(DBN),並在各個領域中取得了極好的效果。針對這些問題,人們提出了卷積神經網絡,從而很好地解決了上面的三個問題。與常規神經網絡不同,卷積神經網絡的各層中的神經元是3維排列的:寬度、高度和深度(這個深度指的是某層數據的第三個維度,而不是整個網絡的深度,整個網絡的深度指的是網絡的層數)。下圖是一個卷積神經網絡,它的神經元被排列在3個維度(寬、高和深度)。
  • 在深度學習的革命中,談談卷積神經網絡(CNN)
    #掃描上方二維碼進入報名#文章連結:https://my.oschina.net/u/876354/blog/1620906
  • 深度學習入門筆記系列 ( 六 ) ——卷積神經網絡(CNN)學習筆記
    卷積神經網絡(CNN)學習筆記本系列將分為 8 篇 。
  • CNN卷積神經網絡— LeNet(二)
    卷積神經網絡( Convolutional Neural Network, CNN):是一種常見的深度學習架構,受生物自然視覺認知機制(
  • 卷積神經網絡(CNN)綜述
    1989年,LeCun[4]結合反向傳播算法與權值共享的卷積神經層發明了卷積神經網絡,並首次將卷積神經網絡成功應用到美國郵局的手寫字符識別系統中。1998年,LeCun[5]提出了卷積神經網絡的經典網絡模型LeNet-5,並再次提高手寫字符識別的正確率。
  • 第六講 走進卷積神經網絡
    從本講開始,我們正式進入卷積神經網絡(Conventional Neural Networks, CNN)的學習了
  • 【深度學習系列】卷積神經網絡CNN原理詳解(一)——基本原理
    ,熟悉Tensorflow,PaddlePaddle等深度學習框架,負責過多個機器學習落地項目,如垃圾評論自動過濾,用戶分級精準營銷,分布式深度學習平臺搭建等,都取了的不錯的效果。博客專欄:https://www.cnblogs.com/charlotte77/前文傳送門:【好書推薦&學習階段】三個月教你從零入門深度學習【深度學習系列】PaddlePaddle之手寫數字識別上篇文章我們給出了用paddlepaddle來做手寫數字識別的示例,並對網絡結構進行到了調整,提高了識別的精度。
  • 卷積神經網絡學習路線(五)| 卷積神經網絡參數設置,提高泛化能力?
    type:優化算法的選擇,一共有六種可選:SGD、AdaDelta、AdaGrad、Adam、Nesterov和RMSProp。默認為SGD。原理建議看看史丹福大學的CS231N視頻課程。數據增強是指在數據有限的情況通過一些幾何操作對圖像進行變換,使得同類數據的表現形式更加豐富,以此提高模型的泛化能力。數據增強是一門比較大的學問,在分類,檢測,分割中數據增強的方式都有區別,我們可以通過研究優秀的開原始碼實現的數據增強策略來應用到我們自己的任務中。修改損失函數。這方面有大量的工作,如目標檢測中的Focal Loss, GHM Loss,IOU Loss等都是為了提升模型的泛化能力。
  • 卷積神經網絡(CNN)新手指南
    從那時起許多公司開始將深度學習應用在他們的核心服務上,如Facebook將神經網絡應用到他們的自動標註算法中,Google(谷歌)將其應用到圖片搜索裡,Amazon(亞馬遜)將其應用到產品推薦服務,Pinterest將其應用到主頁個性化信息流中,Instagram也將深度學習應用到它們的圖像搜索架構中。
  • 卷積神經網絡概念與原理
    最早將CNN用於手寫數字識別並一直保持了其在該問題的霸主地位。近年來卷積神經網絡在多個方向持續發力,在語音識別、人臉識別、通用物體識別、運動分析、自然語言處理甚至腦電波分析方面均有突破。       卷積神經網絡與普通神經網絡的區別在於,卷積神經網絡包含了一個由卷積層和子採樣層構成的特徵抽取器。在卷積神經網絡的卷積層中,一個神經元只與部分鄰層神經元連接。
  • 大話卷積神經網絡CNN,小白也能看懂的深度學習算法教程,全程乾貨...
    更簡單來說,多層神經網絡做的步驟是:特徵映射到值。特徵是人工挑選。深度學習做的步驟是 信號->特徵->值。特徵是由網絡自己選擇。深度學習是一個框架,包含多個重要算法:Convolutional Neural Networks(CNN)卷積神經網絡AutoEncoder自動編碼器Sparse Coding
  • 神奇GIF動畫讓你秒懂各種深度學習卷積神經網絡操作原理
    打開APP 神奇GIF動畫讓你秒懂各種深度學習卷積神經網絡操作原理 深度學習思考者 發表於 2017-11-15 18:58:34
  • 卷積神經網絡(CNN)介紹與實踐
    - 來源:http://cs231n.github.io/classification/為了「教會」一種算法如何識別圖像中的對象,我們使用特定類型的人工神經網絡:卷積神經網絡(CNN)。他們的名字源於網絡中最重要的一個操作:卷積。卷積神經網絡受到大腦的啟發。
  • 深度學習、圖像分類入門,從VGG16卷積神經網絡開始
    向AI轉型的程式設計師都關注了這個號👇👇👇大數據挖掘DT機器學習  公眾號: datayx剛開始接觸深度學習、卷積神經網絡的時候非常懵逼,不知道從何入手,我覺得應該有一個進階的過程,也就是說,理應有一些基本概念作為奠基石,讓你有底氣去完全理解一個龐大的卷積神經網絡:本文思路:
  • 機器學習|卷積神經網絡(CNN) 手寫體識別 (MNIST)入門
    強化學習 (Reinforcement Learnong): 強化學習應當是機器學習當中最吸引人的一個部分了,例如 Gym 上就有很多訓練電腦自己玩遊戲最後拿高分的例子。強化學習主要就是通過試錯 (Action),找到能讓自己收益最大的方法,這也是為什麼很多都例子都是電腦玩遊戲。
  • 卷積神經網絡模型發展及應用(中文版),20頁pdf
    卷積神經網絡(CNN)模型是深度學習模型中最重要的一種經典結構,其性能在近年來深度學習任務上逐步提高。由於可以自動學習樣本數據的特徵表示,卷積神經網絡已經廣泛應用於圖像分類、目標檢測、語義分割以及自然語言處理等領域。
  • 【卷積神經網絡】04-1VGG網絡結構詳解與原理分析
    我們看最初的兩個卷積神經網絡LeNet和AlexNet,他們都是將一個n×n×c的圖像最終變成了1×1×k的矩陣。其中n表示的是輸入圖像的尺寸(行與列),c表示圖像的通道數(深度),取值為1或3,k表示的是最終分類的個數。
  • 卷積神經網絡(CNN)系列介紹之一
    深度學習領域內有眾多的任務和問題有待解決和改進,比如圖像分類,目標檢測,目標分割,實例分割,圖像超分,圖像去噪,圖像生成,增強學習,系統風險預測等等,但是這些任務雖然多種多樣,都離不開一個關鍵的問題,那就是這些任務基本上都是圍繞基礎網絡進行改進和創新的。深度學習基礎網絡的更迭和變遷是推動深度學習其他領域任務得以進步和提升的原動力。
  • 卷積神經網絡CNN與深度學習常用框架的介紹與使用
    2.那神經網絡是怎麼做到的呢?神經網絡其實就是使用AND和OR操作把樣本點中得那一塊摳出來。正如下圖,最上面的綠色區域的每一個變都可以看成一個線性分類器,把樣本分成正例和負例,那這些分類器做AND操作,得出的結果就是一個綠色的區域,然後把多個綠色的區域再用OR操作。而一個神經元就可以實現AND操作或者OR操作,我們只需要提供樣本,神經網絡就可以自己學到。
  • 卷積神經網絡小白入門手冊
    學習深度學習,最常接觸到的就是各式各樣的神經網絡了,其中卷積神經網絡是諸多神經網絡中最典型最常用的神經網絡了。本文原始素材來源於freecodecamp博客,經本人翻譯首發於此。希望能幫助到大家!覺得不錯就點個讚,或者關注下我吧,後續我還會分享更多相關的精彩內容。