【深度学习系列】卷积神经网络详解(二)——自己手写一个卷积神经网络

浏览: 953

作者:Charlotte77  数学系的数据挖掘民工  

博客专栏:http://www.cnblogs.com/charlotte77/ 

个人公众号:Charlotte数据挖掘(ID:CharlotteDataMining)  

精彩回顾:

【深度学习系列】卷积神经网络CNN原理详解(一)——基本原理

【深度学习系列】PaddlePaddle之数据预处理

 上篇文章中我们讲解了卷积神经网络的基本原理,包括几个基本层的定义、运算规则等。本文主要写卷积神经网络如何进行一次完整的训练,包括前向传播和反向传播,并自己手写一个卷积神经网络。如果不了解基本原理的,可以先看看上篇文章:‍‍‍‍【深度学习系列】卷积神经网络CNN原理详解(一)——基本原理


 卷积神经网络的前向传播

  首先我们来看一个最简单的卷积神经网络:

image.png

1.输入层---->卷积层

  以上一节的例子为例,输入是一个4*4 的image,经过两个2*2的卷积核进行卷积运算后,变成两个3*3的feature_map

image.png

以卷积核filter1为例(stride = 1 ):

image.png

计算第一个卷积层神经元o11的输入: 

image.png

 神经元o11的输出:(此处使用Relu激活函数)

image.png

 其他神经元计算方式相同

2.卷积层---->池化层

 image.png

计算池化层m11 的输入(取窗口为 2 * 2),池化层没有激活函数 

image.png

3.池化层---->全连接层

  池化层的输出到flatten层把所有元素“拍平”,然后到全连接层。

4.全连接层---->输出层

  全连接层到输出层就是正常的神经元与神经元之间的邻接相连,通过softmax函数计算后输出到output,得到不同类别的概率值,输出概率值最大的即为该图片的类别。

 


 卷积神经网络的反向传播

  传统的神经网络是全连接形式的,如果进行反向传播,只需要由下一层对前一层不断的求偏导,即求链式偏导就可以求出每一层的误差敏感项,然后求出权重和偏置项的梯度,即可更新权重。而卷积神经网络有两个特殊的层:卷积层和池化层。池化层输出时不需要经过激活函数,是一个滑动窗口的最大值,一个常数,那么它的偏导是1。池化层相当于对上层图片做了一个压缩,这个反向求误差敏感项时与传统的反向传播方式不同。从卷积后的feature_map反向传播到前一层时,由于前向传播时是通过卷积核做卷积运算得到的feature_map,所以反向传播与传统的也不一样,需要更新卷积核的参数。下面我们介绍一下池化层和卷积层是如何做反向传播的。

  在介绍之前,首先回顾一下传统的反向传播方法:

  1.通过前向传播计算每一层的输入值neti,jneti,j  (如卷积后的feature_map的第一个神经元的输入:neti11neti11)

  2.反向传播计算每个神经元的误差项δi,jδi,j ,δi,j=∂E∂neti,jδi,j=∂E∂neti,j,其中E为损失函数计算得到的总体误差,可以用平方差,交叉熵等表示。

  3.计算每个神经元权重wi,jwi,j 的梯度,ηi,j=∂E∂neti,j⋅∂neti,j∂wi,j=δi,j⋅outi,jηi,j=∂E∂neti,j⋅∂neti,j∂wi,j=δi,j⋅outi,j

  4.更新权重 wi,j=wi,j−λ⋅ηi,jwi,j=wi,j−λ⋅ηi,j(其中λλ为学习率)

  卷积层的反向传播

  由前向传播可得:

image.png

 所以上一层的输出也就是这一层的输入,即:outi11=activators(neti11)=i11outi11=activators(neti11)=i11

  首先计算输入层的误差项δ11δ11:

image.png

(注意这里是neti11neti11,代表的是上一层的输入,不是neto11neto11)

  先计算∂E∂i11∂E∂i11

  此处我们并不清楚∂E∂i11∂E∂i11怎么算,那可以先把input层通过卷积核做完卷积运算后的输出feature_map写出来:

image.png

 然后依次对输入元素ii,jii,j求偏导

  i11i11的偏导: 

image.png

image.png

 观察一下上面几个式子的规律,归纳一下,可以得到如下表达式:

image.png

 图中的卷积核进行了180°翻转,与这一层的误差敏感项矩阵deltai,j)deltai,j)周围补零后的矩阵做卷积运算后,就可以得到∂E∂i11∂E∂i11,即

∂E∂ii,j=∑m⋅∑nhm,nδi+m,j+n∂E∂ii,j=∑m⋅∑nhm,nδi+m,j+n

  第一项求完后,我们来求第二项∂i11∂neti11

image.png

 此时我们的误差敏感矩阵就求完了,得到误差敏感矩阵后,即可求权重的梯度。

  由于上面已经写出了卷积层的输入neto11neto11与权重hi,jhi,j之间的表达式,所以可以直接求出:

 image.png

推论出权重的梯度:

image.png

偏置项的梯度:

image.png

  可以看出,偏置项的偏导等于这一层所有误差敏感项之和。得到了权重和偏置项的梯度后,就可以根据梯度下降法更新权重和梯度了。 

    

    池化层的反向传播

    池化层的反向传播就比较好求了,看着下面的图,左边是上一层的输出,也就是卷积层的输出feature_map,右边是池化层的输入,还是先根据前向传播,把式子都写出来,方便计算:

image.png

 假设上一层这个滑动窗口的最大值是outo11

image.png

 这样就求出了池化层的误差敏感项矩阵。同理可以求出每个神经元的梯度并更新权重。


手写一个卷积神经网络

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 +5
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 padding

def 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 np

from activators import ReluActivator,IdentityActivator


class 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_j


def 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() +bias



def 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 0


class 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有几个优点:

  • 支持长方形和正方形的图片尺寸

  • 支持滑动步长stride、补零zero_padding、扩展dilation在水平和垂直方向上设置不同的值

  • 支持偏置项卷积核中能够共享

  • 自动适配cpu和gpu的卷积网络

   在我们自己写的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  



公众号后台回复关键词学习

回复 人工智能          揭开人工智能的神秘面纱

回复 贝叶斯算法      贝叶斯算法与新闻分类

回复 机器学习          R&Python机器学习

回复 阿里数据          阿里数据系列课程

回复 Python             Python机器学习案例实战

回复 Spark               征服Spark第一季

回复 kaggle             机器学习kaggle案例

回复 大数据             大数据系列视频

回复 数据分析          数据分析人员的转型

回复 数据挖掘          数据挖掘与人工智能

回复 机器学习          R&Python机器学习

回复 阿里数据          阿里数据系列课程

回复 R                      R&Python机器学习入门

推荐 0
本文由 人工智能爱好者社区 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

0 个评论

要回复文章请先登录注册