CNN

一,卷积神经网络概述

卷积神经网络沿用了普通的神经元网络即多层感知器的结构,是一个前馈网络(网络拓扑结构上不存在环或者回路)。以应用于图像领域的CNN为例,大体结构如下图所示: 在这里插入图片描述
很明显,这个典型的结构分为四个大层次:

1.输入图像I。为了减小复杂度,一般使用灰度图像。当然,也可以使用RGB彩色图像,此时输入图像有三张,分别为RGB分量。输入图像一般需要归一化,如果使用sigmoid激活函数,则归一化到[0, 1],如果使用tanh激活函数,则归一化到[-1, 1]。

2.多个卷积(C)-下采样(S)层。将上一层的输出与本层权重W做卷积得到各个C层,然后下采样(池化)得到各个S层。这些层的输出称为Feature Map。

3.光栅化(X)。是为了与传统的多层感知器全连接。即将上一层的所有Feature Map的每个像素依次展开,排成一列。

4.传统的多层感知器(N&O)。最后的分类器一般使用Softmax Regression(针对多分类问题),如果是二分类,当然也可以使用Logistic Regression。
根据上面的基本结构,我们就逐层进行分析。

卷积层和下采样层

以上介绍的多层感知机存在一定的问题,它是一个全连接的网络,因此在输入比较大的时候,权值会特别多。比如一个有1000个节点的隐层,连接到一个1000×1000的图像上,那么就需要 10^9 个权值参数(外加1000个偏置参数)!这个问题,一方面限制了每层能够容纳的最大神经元数目,另一方面也限制了多层感知器的层数即深度。
多层感知器的另一个问题是梯度发散。一般情况下,我们需要把输入归一化,而每个神经元的输出在激活函数的作用下也是归一化的;另外,有效的参数其绝对值也一般是小于1的;这样,在BP过程中,多个小于1的数连乘,得到的会是更小的值。也就是说,在深度增加的情况下,从后传播到前边的残差会越来越小,甚至对更新权值起不到帮助,从而失去训练效果,使得前边层的参数趋于随机化(补充一下,其实随机参数也是能一定程度上捕捉到图像边缘的)。
既然多层感知器存在问题,那么卷积神经网络的出现,就是为了解决它的问题。卷积神经网络的核心出发点有三个。

1.局部感受野。形象地说,就是模仿你的眼睛,想想看,你在看东西的时候,目光是聚焦在一个相对很小的局部的吧?严格一些说,普通的多层感知器中,隐层节点会全连接到一个图像的每个像素点上,而在卷积神经网络中,每个隐层节点只连接到图像某个足够小局部的像素点上,从而大大减少需要训练的权值参数。举个栗子,依旧是1000×1000的图像,使用10×10的感受野,那么每个神经元只需要100个权值参数;不幸的是,由于需要将输入图像扫描一遍,共需要991×991个神经元!参数数目减少了一个数量级,不过还是太多。
2.权值共享。形象地说,就如同你的某个神经中枢中的神经细胞,它们的结构、功能是相同的,甚至是可以互相替代的。也就是,在卷积神经网中,同一个卷积核内,所有的神经元的权值是相同的,从而大大减少需要训练的参数。继续上一个栗子,虽然需要991×991个神经元,但是它们的权值是共享的呀,所以还是只需要100个权值参数,以及1个偏置参数。从MLP的 10^9 到这里的100,就是这么狠!作为补充,在CNN中的每个隐藏,一般会有多个卷积核。
3.池化。形象地说,你先随便看向远方,然后闭上眼睛,你仍然记得看到了些什么,但是你能完全回忆起你刚刚看到的每一个细节吗?同样,在卷积神经网络中,没有必要一定就要对原图像做处理,而是可以使用某种“压缩”方法,这就是池化,也就是每次将原图像卷积后,都通过一个下采样的过程,来减小图像的规模。以最大池化(Max Pooling)为例,1000×1000的图像经过10×10的卷积核卷积后,得到的是991×991的特征图,然后使用2×2的池化规模,即每4个点组成的小方块中,取最大的一个作为输出,最终得到的是496×496大小的特征图。

激活函数

新的激活函数-Relu (f(x) = max(0,u))
增加激活函数: 增加网络的非线性分割能力
为什么不使用sigmoid等其他的激活函数,而采用Relu
在反向传播求误差梯度时间,计算量相对大
对于深层网络,sigmoid函数反向传播时,很容易就会出现梯度爆炸的情况
把卷积后的值输入到激活函数里面。
在这里插入图片描述

二,卷积神经网络原理介绍

用CNN卷积神经网络识别图片,一般需要的步骤有:

  • 卷积层初步提取特征

  • 池化层提取主要特征

  • 全连接层将各部分特征汇总

  • 产生分类器,进行预测识别

1、卷积层工作原理

卷积层的作用:就是提取图片每个小部分里具有的特征

假定我们有一个尺寸为6*6 的图像,每一个像素点里都存储着图像的信息。我们再定义一个卷积核(相当于权重),用来从图像中提取一定的特征。卷积核与数字矩阵对应位相乘再相加,得到卷积层输出结果。
在这里插入图片描述

(429=181+540+511+550+1211+750+351+240+204*1)
卷积核的取值在没有以往学习的经验下,可由函数随机生成,再逐步训练调整

当所有的像素点都至少被覆盖一次后,就可以产生一个卷积层的输出(下图的步长为1)
在这里插入图片描述
机器一开始并不知道要识别的部分具有哪些特征,是通过与不同的卷积核相作用得到的输出值,相互比较来判断哪一个卷积核最能表现该图片的特征——比如我们要识别图像中的某种特征(比如曲线),也就是说,这个卷积核要对这种曲线有很高的输出值,对其他形状(比如三角形)则输出较低。卷积层输出值越高,就说明匹配程度越高,越能表现该图片的特征。

卷积层具体工作过程:

比如我们设计的一个卷积核如下左,想要识别出来的曲线如下右:在这里插入图片描述
现在我们用上面的卷积核,来识别这个简化版的图片——一只漫画老鼠在这里插入图片描述
当机器识别到老鼠的屁股的时候,卷积核与真实区域数字矩阵作用后,输出较大:6600 在这里插入图片描述
而用同一个卷积核,来识别老鼠的耳朵的时候,输出则很小:0
在这里插入图片描述
我们就可以认为:现有的这个卷积核保存着曲线的特征,匹配识别出来了老鼠的屁股是曲线的。我们则还需要其他特征的卷积核,来匹配识别出来老鼠的其他部分。卷积层的作用其实就是通过不断的改变卷积核,来确定能初步表征图片特征的有用的卷积核是哪些,再得到与相应的卷积核相乘后的输出矩阵

补零层(padding)

之前在讨论卷积神经网络的时候,我们是使用filter来做元素乘法运算来完成卷积运算的。目的是为了完成探测垂直边缘这种特征。但这样做会带来两个问题。

  • 卷积运算后,输出图片尺寸缩小
  • 越是边缘的像素点,对于输出的影响越小,因为卷积运算在移动的时候到边缘就结束了。中间的像素点有可能会参与多次计算,但是边缘像素点可能只参与一次。所以我们的结果可能会丢失边缘信息。

那么为了解决这个问题,我们引入padding, 什么是padding呢,就是我们认为的扩充图片, 在图片外围补充一些像素点,把这些像素点初始化为0. 如下图:
在这里插入图片描述
经过padding之后,原始图片尺寸为(n+2p) x (n+2p),filter尺寸为f x f,则卷积后的图片尺寸为(n+2p-f+1) x (n+2p-f+1)。若要保证卷积前后图片尺寸不变,则p应满足:
在这里插入图片描述

2、池化层工作原理

池化层的输入就是卷积层输出的原数据与相应的卷积核相乘后的输出矩阵
池化层的目的:

为了减少训练参数的数量,降低卷积层输出的特征向量的维度

减小过拟合现象,只保留最有用的图片信息,减少噪声的传递

最常见的两种池化层的形式:

最大池化:max-pooling——选取指定区域内最大的一个数来代表整片区域

均值池化:mean-pooling——选取指定区域内数值的平均值来代表整片区域

举例说明两种池化方式:(池化步长为2,选取过的区域,下一次就不再选取)在这里插入图片描述
在44的数字矩阵里,以步长22选取区域,比如上左将区域[1,2,3,4]中最大的值4池化输出;上右将区域[1,2,3,4]中平均值5/2池化输出

3、全连接层工作原理

卷积层和池化层的工作就是提取特征,并减少原始图像带来的参数。然而,为了生成最终的输出,我们需要应用全连接层来生成一个等于我们需要的类的数量的分类器。

全连接层的工作原理和之前的神经网络学习很类似,我们需要把池化层输出的张量重新切割成一些向量,乘上权重矩阵,加上偏置值,然后对其使用ReLU激活函数,用梯度下降法优化参数既可。

卷积层的返向传播

假如有特征图与卷积核如下:

在这里插入图片描述
且输出与这两个矩阵的关系如下:
在这里插入图片描述
那么,关于卷积核F的每一项F_ij的梯度计算公式如下:
在这里插入图片描述
也就等于:
在这里插入图片描述
当我们仔细观察上图这几个式子的规律,可以发现,卷积核的梯度可以这样得来:
在这里插入图片描述
然后卷积核各项都可以根据此梯度进行调整。但是,我们还要把梯度传递给上一层,就需要计算关于输入的梯度。通过与计算卷积核的梯度同样的方法,我们可以得到关于各个X_ij的梯度:
在这里插入图片描述
仔细观察上图这几个式子的规律,可以发现,输入的梯度可以化为全卷积操作:
在这里插入图片描述
全卷积的具体操作如下:
在这里插入图片描述

关于输入的梯度的用途

本来我感觉奇怪,如果关于卷积核的梯度是用于调整卷积核各项的值的话,那关于输入的梯度是用来做什么的呢?

原来,它是用于计算上一层的梯度用的。其实,这一层对输入的梯度
在这里插入图片描述
就等于上一层对输出的梯度
在这里插入图片描述
也就是说,卷积操作主要是求出两个:关于卷积核的梯度以及关于输入的梯度。其中。关于卷积核的梯度是用于调整卷积核各项的值的,关于输入的梯度则是用于给更上一层作为输出梯度的。

池化层反向传播

池化层一般没有参数,所以反向传播的时候,只需对输入参数求导,不需要进行权值更新。但是具体在计算的时候是要根据Max还是Average来进行区分,进行参数更新的。

Max-Pooling

如果只看输出矩阵中的一个点y,则有 y = max( x1 , x2, x3, … )。所以对x求导后有(可以理解成分段函数的求导),则有
在这里插入图片描述
也就是寻找顶层与下层之间的映射关系,之后将上层的结果添加到下层中去。
这个xn如果影响多个y,则会叠加起来。具体来说可以使用下面这幅图片进行说明
在这里插入图片描述
在这里插入图片描述

Average-Pooling

如果只看输出矩阵中的一个点y,则有 y = ( x1 , x2, x3, … ,xn )/n。所以对x求导后有
在这里插入图片描述
也就是映射过去之后去均值
具体来说可以使用下面这幅图片进行说明
在这里插入图片描述
在这里插入图片描述

代码

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
​
#读取MNIST数据集
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
 
sess = tf.InteractiveSession()
#预定义输入值X、输出真实值Y    placeholder为占位符
​
x = tf.placeholder(tf.float32, shape=[None, 784])#每张图片像素的一位数组内存
​
y_ = tf.placeholder(tf.float32, shape=[None, 10])
keep_prob = tf.placeholder(tf.float32)#是改变参与计算的神经元个数
x_image = tf.reshape(x, [-1,28,28,1])#把x化为4维数组其中类型为[1,28,28,1]
#权重,偏置值函数
def weight_variable(shape):
    # 产生随机变量
    # truncated_normal:选取位于正态分布均值=0.1附近的随机值
    initial = tf.truncated_normal(shape, stddev=0.1)
    #with tf.Session() as sess:
      #print(sess.run(initial))
  
    return tf.Variable(initial)#将常量变为变量
​
def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape) 
    #with tf.Session() as sess:
      #print(sess.run(initial))
    return tf.Variable(initial)#将常量变为变量
#卷积函数,池化函数的定义
def conv2d(x, W):
    # stride = [1,水平移动步长,竖直移动步长,1],x为输入数据W为卷积核
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
  
​
def max_pool_2x2(x):
    # stride = [1,水平移动步长,竖直移动步长,1]
    #ksize为池化窗口
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
#卷积层1网络结构定义
#卷积核1:patch=5×5;输入通路1;输出通路32;激活函数reLU非线性处理
W_conv1 = weight_variable([5, 5, 1, 32])#权重
b_conv1 = bias_variable([32])#偏置
#激活函数
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)         #输出28*28*32
h_pool1 = max_pool_2x2(h_conv1)               
#卷积层2网络结构定义
#卷积核2:patch=5×5;输入通路 32;输出通路64;激活函数reLU非线性处理
W_conv2 = weight_variable([5, 5, 32, 64])#
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)         #output size 14*14*64
h_pool2 = max_pool_2x2(h_conv2)
# 全连接层1
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64])   #[n_samples,7,7,64]->>[n_samples,7*7*64]
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)#激活函数;tf.matmul(矩阵乘法)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 防止过拟合,减少计算量dropout
# 全连接层2
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
#二次代价函数:预测值与真实值的误差
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction))
#print(loss)
#梯度下降法:数据太庞大,选用AdamOptimizer优化器
train_step = tf.train.AdamOptimizer(1e-4).minimize(loss)
 
#结果存放在一个布尔型列表中
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1))
#求准确率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
saver = tf.train.Saver()  # defaults to saving all variables
sess.run(tf.global_variables_initializer())
 
for i in range(500):
   
    
    batch = mnist.train.next_batch(50)
    if i % 100 == 0:
        train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})
        print("step", i, "training accuracy", train_accuracy)
 
    train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
#保存模型参数
saver.save(sess,'./el.ckpt')
​
​
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值