CNN卷积神经网络

CNN卷积神经网络是DNN人工神经网络中最成功的一类。

CNN的基本架构

这个模型看起来有些庞大,也很复杂,但是我们可以一层一层地来拆解它。

卷积层

卷积层是对输入的数据进行一个卷积操作,我们可以通过一个动态图来理解:https://cs231n.github.io/assets/conv-demo/index.html
这个动态图也许有些难以理解,但是没关系,我们可以把它分成几帧来处理。而在具体讲解之前,我们先来解释几个专业术语。
1.Input Volume:指的是输入后经过填充大的三维矩阵。由于CNN主要应用于图像的识别和处理,所以我们可以象征性地把这个矩阵的长和宽理解为输入的图片的长和宽,把矩阵的深度理解为这个图片的特征。比如gdb图片,其实就是指这个图片上的每一个像素点都是由三个特征g,b,d组成的。
2.Filter:可 以近似的理解为人工神经网络中的权重参数,只不过人工神经网络中的参数往往是一个固定的数值,而CNN中的参数往往是一个我们自己定义的三维矩阵。
3.Bias:设置的偏置值。可以近似的理解为神经网络中的偏置值。
4.stride:步长。每计算出一个值,矩阵向右或者向下移动的距离。
5.padding:填充。在我们计算时,习惯在输入的矩阵的周围加上若干圈数值为零的行和列,方便后面的计算。填充的值就是这个加上的行或者列数。
6.Output Volume:经过一次卷积计算后输出的矩阵。
第一次计算:
图中我们不难看出,Filter的深度必须和Input Volume的深度相同。把图中这三对用黑色线相连的矩阵相乘,然后把得出的三个数值相加,在加上偏置值就是Output Volume第一个矩阵的第一个值。
如图,第一行黑线相连的两个矩阵,相乘是这样计算:
    0×1+0×1+0×1+0× -1+1× -1+1×0+0× -1+0×1+1×0= -2
每一对矩阵都是这样计算,计算完之后相加然后加上偏置值,就是最右边第四列Output Volume的第一个元素的值。
接下来,根据我们stride的长度,将每一层的矩阵向右平移两个单位,然后重复第一次计算的操作,将计算出来的值填入Output Volume,作为第二个元素的值。

不难看出,接下来的计算也是这样,将第一次平移后的矩阵再平移一次,重复第一次的操作,将计算出来的值填入Output Volume作为第三个元素的值

根据这三帧图像,我们可以大致总结卷积计算中矩阵移动的规律:
根据这样的移动规律,我们可以将整个矩阵针对第一个Filter进行计算。并以此类推,根据一样的方式对第二个FIlter进行计算,得出的结果填入Output Volume的第二个矩阵。

padding

在这个栗子中,padding = 1,那么在卷积计算中,为什么要用padding呢?从卷积神经网络的移动方式我们不难看出,依照卷积神经网络的计算方式,越靠近矩阵中间的数据,被计算的次数越多,也就意味着对于结果矩阵的影响越大,越靠近矩阵边缘的数据,被计算的次数就越少,比如四个角落的数据,被计算用到的次数只有一次。而这显然是不公平的,所以我们可以在Input Volume的周围加上一圈对计算结果不造成影响的0作为行和列,使矩阵中的每一个元素都能得到充分的计算,这就是padding的作用。

池化层

相较于卷积层,池化层则简单得多。池化层的操作主要是将输入矩阵的长和宽缩小一定的倍数,但是矩阵的深度不发生改变,也就是矩阵的特征数不发生改变。我们可以来看一个栗子
这个三维矩阵很大,我们通过缩减长和宽,比如将长和宽缩减到原来的1/2,这样矩阵的体积就缩减到了原来的1/4。
接下来我们来看池化的方式。既然是缩减到原来的1/4,方式就是以一个2×2的矩阵为一个单位,从每一个矩阵中选取一个数字来代表这个矩阵。我们可以形象地理解为,降低一张图片的像素。
接下来我们来看几个数据问题:
我们设输入的

用numpy来实现离散卷积

import numpy as np
import h5py
#import matplotlib.pyplot as plt

#%load_ext autoreload
#%autoreload 2

np.random.seed(1) # 指定随机数种子
def zero_pad(X, pad):#把输入的矩阵进行填充
    """
    把数据集X的图像边界用0值填充。填充情况发生在每张图像的宽度和高度上。
    
    参数:
    X -- 图像数据集 (m, n_H, n_W, n_C),分别表示样本数、图像高度、图像宽度、通道数 
    pad -- 整数,每个图像在垂直和水平方向上的填充量
    
    返回:
    X_pad -- 填充后的图像数据集 (m, n_H + 2*pad, n_W + 2*pad, n_C)
    """
    # X数据集有4个维度,填充发生在第2个维度和第三个维度上;填充方式为0值填充
    X_pad = np.pad(X, (
        (0, 0),# 样本数维度,不填充
        (pad, pad), #n_H维度,上下各填充pad个像素
        (pad, pad)), #n_C维度,不填充
                 mode='constant', constant_values = (0, 0))
    
    return X_pad

def numpy_conv(inputs,filter,_result,padding="VALID"):#计算每一个filter对应的结果矩阵的值
    H, W = inputs.shape
    filter_size = filter.shape[0]
    # default np.floor
    filter_center = int(filter_size / 2.0)
    filter_center_ceil = int(np.ceil(filter_size / 2.0))

    #这里先定义一个和输入一样的大空间,但是周围一圈后面会截掉
    result = np.zeros((_result.shape))
    #更新下新输入,SAME模式下,会改变HW
    H, W = inputs.shape
    #print("new size",H,W)
    #卷积核通过输入的每块区域,stride=1,注意输出坐标起始位置
    for r in range(0, 3):
        for c in range(0, 3):
            cur_input = inputs[2*r:2*r + filter_size,
                        2*c:2*c + filter_size]# 池化大小的输入区域
            cur_output = cur_input * filter#和核进行乘法计算
            conv_sum = np.sum(cur_output) #再把所有值求和
            result[r, c] = conv_sum #当前点输出值
    return result

def _conv(inputs, filter,strides=[1,1], bias = [1,0], padding="VALTD"):
    C_in, H, W = inputs.shape
    filter_size = filter.shape[2]
    # C_out指核对个数,也是最后结果对通道个数
    C_out = filter.shape[0]
    # 同样我们任务核对宽高相等
    result = np.zeros([C_out, int(np.ceil(H - filter_size + 2) /2), int(np.ceil(W - filter_size + 2) /2)],np.float32) 
    # 核个数对循环
    for channel_out in range(C_out):#遍历每一个filter矩阵
        for channel_in in range(C_in):#遍历输入矩阵的每一层(也可以说是遍历filter的每一层) # 当前通道对数据
            channel_data = inputs[channel_in]
            if channel_out == 0:
                result[channel_out, :, :] += numpy_conv(channel_data, filter[channel_out][channel_in], result[0],padding)+1
            else:
                result[channel_out, :, :] += numpy_conv(channel_data, filter[channel_out][channel_in], result[0],padding)

    # print(result)
    return result

if __name__ == '__main__':
    #输入[C_in,H,W]
    X = 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]]])
    inputs = zero_pad(X,1)

    print("input:\n",inputs,"\n")

    #卷积核[C_out,C_in,K,K]
    filter = np.zeros([2, 3, 3, 3])
    filter[0] = 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]]])
    filter[1] = 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]]])

    print("filter\n",filter,"\n")
    
    final_result = _conv(inputs, filter, strides=[1,1],bias = [1,0], padding = "VALID")

    print("result\n",final_result,"\n")

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=15i4llkq69fhl