前言
卷积神经网络是第一个我系统学习的神经网络,在学习的过程中,通过不断地查阅资料也好,看书也好,对它的理解逐渐的深入了,下面我就来介绍一下卷积神经网络。
神经网络
说到卷积神经网络,就不得不先说一下神经网络,不论卷积神经网络也好,还是其他的一些深度神经网络也好,都是在最原始的(人工)神经网络的基础之上发展出来的。
我们所说的神经网络其实是人工神经网络的简称,它可以模拟人的大脑对一些信号进行处理。而人的神经网络的基本单位是神经元,所以专家就提出了一种人造神经元,就是我们的所说的感知器,下图是一个最基本的感知器
可以看到里面有一些参数,a1-an表示的是输入,w1-wn表示的是权重,b表示的是偏置,就相当于是一个函数y=ax+b,这只是一个形象的函数,并不确切表示一个函数。一个神经网络最简单的结构包括输入层、隐含层和输出层,每一层网络有多个神经元,上一层的神经元通过激活函数映射到下一层神经元,每个神经元之间有相对应的权值,输出即为我们的分类类别。
上图是一个多层感知器的结构,意思是我们通过动态地改变权重和偏置来让我们的模型达到最优效果。举个例子,我们要做汽车车牌号的识别,首先我们需要给我们的模型输入大量的已经标注好的数据,如下图那样的,我们对各个层的权重进行矫正而创建模型的过程,称为自动学习过程,具体的学习方法因网络结构和模型不同,还通过反向传播来验证修改。
卷积神经网络
上面已经介绍过神经网络了,它能够完成一些并不复杂的分类任务,但是对于图像识别领域来说,传统的神经网络并不适用,比方说一个(28,28,1)的图片,表示这是一个长宽均为28的一个灰度图,如果使用全连接(网络中的神经元与相邻的每一个神经元都链接)的传统神经网络,那我们就需要有28*28=784个神经元,那样的网络结构就会像下图那样十分复杂,可以想象参数的多和计算量的大
鉴于传统神经网络的复杂,所以就提出了不用全连接的卷积神经网络,下面来详细介绍一下卷积神经网络。卷积神经网络最主要的过程包括卷积,池化,全连接
卷积
卷积层主要进行的操作是对图片进行特征提取,随着卷积层的深入它提取到的特征就越高级。对于一张输入图片,将其转化为矩阵,矩阵的元素为对应的像素值,对于一张大小为5×5的图像,使用3×3的感受野进行移动长度为1的非零填充卷积,可以得到大小为3×3的特征平面,如下图所示
具体操作是image中蓝色的部分和卷积和对应位置相乘后累加:10+03+…+1*2=4
进行完这一次运算之后,卷积核会先向左移动,在向下移动,知道最后和每一个值进行了运算。
池化
池化层的作用是把卷积层得到的特征平面的数据量降低。池化之后的图像大小虽然发生了变化,但是图像的大致轮廓并没有丢失,而且生成的平面作为下次卷积的输入在卷积核不变的情况下相当于增加了卷积核对于图片信息的提取能力。
池化有平均值池化和最大值池化,上图是最大值池化操作
全连接
全连接层是为了将卷积层中提取的特征进行整和,一般全连接层的输出会交给softmax函数处理分类。
其他
上面介绍过了卷积神经网络的一些基本操作,但是在实际运行过程中还是会出现一些问题,比如过拟合(模型在训练数据上损失函数较小,预测准确率较高;但是在测试数据上损失函数比较大,预测准确率较低)。出现这样的问题可以采用drop out技术,Dropout说的简单一点就是:我们在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征,从而减小模型的过拟合。
代码分析
初始化权值和偏置
#初始化权值
def weight_variable(shape):
initial = tf.truncated_normal(shape,stddev=0.1)#生成一个截断的正太分布
return tf.Variable(initial)
#初始化偏置
def bias_variable(shape):
initial = tf.constant(0.1,shape=shape)#创建常量
return tf.Variable(initial)
truncated_normal()函数的作用是让权值W呈正态分布,标准差为0.1;
constant()函数是创建一个结构为shape矩阵也可以说是数组shape,初始化所有值为0.1
初始化卷积层和池化层
#卷积层
def conv2d_same(x,W):
#x input tensor of shape '[batch, in_height. in_width. in_channels]' 输入的图片张量
#W filter / kernel tensor of shape [filter_heiht. filter_width, in_channels, out_channels] 卷积核
#'strides[0] = strides[3] = 1' strides[1]代表x方向的步长,strides[2]代表y方向的步长
#padding:A 'string' from:'"SAME", "VALID"' SAME表示考虑边界
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME')
def conv2d_valid(x,W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding="VALID")
#池化层
def max_pool_2x2(x):
#ksize[1,x,y,1] 参数依次是:输入(feature map),池化窗口大小,步长,边界
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
tf.nn:提供神经网络相关操作的支持,包括卷积操作(conv)、池化操作(pooling)、归一化、loss、分类操作、embedding、RNN、Evaluation。
padding:padding参数的作用是决定在进行卷积或池化操作时,是否对输入的图像矩阵边缘补0,‘SAME’ 为补零,‘VALID’ 则不补,其原因是因为在这些操作过程中过滤器可能不能将某个方向上的数据刚好处理完。
定义输入输出结构
主要是使用占位符先声明一下结构
with tf.name_scope('inputs'):
# 声明一个占位符,None表示输入图片的数量不定,28*28图片分辨率
xs = tf.placeholder(tf.float32, [None, 28*28])
# 类别是0-9总共10个类别,对应输出分类结果
ys = tf.placeholder(tf.float32, [None, 10])
keep_prob = tf.placeholder(tf.float32)
# x_image又把xs reshape成了28*28*1的形状,因为是灰色图片,所以通道是1.作为训练时的input,-1代表图片数量不定
x_image = tf.reshape(xs, [-1, 28, 28, 1])
reshape()函数用来重新定义数组的形状,最后的1表示灰度图,-1表示图片数量不定。
tf.name_scope()函数的作用是指定区域中定义的所有的对象及各种操作,他们的name属性上会增加该命名区域的区域名,用于区别对象属于那个区域。
搭建网络
第一次卷积
with tf.name_scope('Conv_layer1'):
# 初始化第一个卷积层的权值和偏置
W_conv1 = weight_variable([3,3,1,4])#3*3的采样窗口,4个卷积核从1个平面抽取特征
b_conv1 = bias_variable([4])#每一个卷积核一个偏置值
#把x_image和权值进行卷积,在加上偏置值,然后应用relu激活函数
with tf.name_scope('w_plus_b1'):
res_conv1 = conv2d_valid(x_image,W_conv1)+b_conv1
tf.summary.histogram('res_conv1',res_conv1)
h_conv1 = tf.nn.relu(res_conv1,name='conv1_relu')
h_pool1 = max_pool_2x2(h_conv1)#运行max_pooling
tf.summary.histogram(): 在训练神经网络时,当需要查看一个张量在训练过程中值的分布情况时,可通过tf.summary.histogram()将其分布情况以直方图的形式在TensorBoard直方图仪表板上显示.
tf.nn.relu(): 函数是将大于0的数保持不变,小于0的数置为0,作用是激活函数.
第二次卷积
with tf.name_scope('Conv_layer2'):
#初始化第二个卷积层的权值和偏置
W_conv2 = weight_variable([3,3,4,8])#3*3的采样窗口,8个卷积核从4个平面抽取特
b_conv2 = bias_variable([8])
#把和权值进行卷积,在加上偏置值,然后应用relu激函数h_pool1
with tf.name_scope('w_plus_b2'):
res_conv2 = conv2d_same(h_pool1,W_conv2)+b_conv2
tf.summary.histogram("res_conv2",res_conv2)
h_conv2 = tf.nn.relu(res_conv2)
h_pool2 = max_pool_2x2(h_conv2)#运行max_pooling
和第一次卷积类似,改变的是卷积核的个数和特征平面的个数
第一个全连接层
#初始化第一个全连接层的权值
with tf.name_scope('FC1'):
W_fc1 = weight_variable([8*8*8,2048])#上一层有8*8*8个神经院,全连接层有2048个神经元
b_fc1 = bias_variable([2048])#2048个偏置值
#把池化层2的输出扁平化为1维
h_pool2_flat = tf.reshape(h_pool2,[-1,8*8*8])
#求第一个全连接层的输出
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1) + b_fc1)
# keep_prob 用来表示神经元的输出概率
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
tf.matmul()将矩阵a乘以矩阵b,生成a * b
tf.nn.dropout()是tensorflow里面为了防止或减轻过拟合而使用的函数,它一般用在全连接层
第二个全连接层
with tf.name_scope('FC2'):
#初始化第二个全连接层
W_fc2 = weight_variable([2048,10])
b_fc2 = bias_variable([10])
with tf.name_scope('Pr'):
#计算输出
preduction = tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
交叉熵代价函数
with tf.name_scope('loss'):
cross_entorpy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=preduction))
tf.summary.scalar("loss",cross_entorpy)
tf.reduce_mean()函数用于计算张量tensor沿着指定的数轴(tensor的某一维度)上的的平均值,主要用作降维或者计算tensor(图像)的平均值。
tf.summary.scalar()函数用来显示标量信息,一般在画loss,accuary时会用到。
AdamOptinizer优化
with tf.name_scope('train'):
train_step = tf.train.AdamOptimizer(LR).minimize(cross_entorpy)
tf.train.AdamOptimizer()函数是Adam优化算法:是一个寻找全局最优点的优化算法,引入了二次方梯度校正。
存放结果并求准确率
#存放结果在一个bool列表中
with tf.name_scope('accuracy'):
with tf.name_scope("correct_prediction"):
correct_prediction = tf.equal(tf.argmax(preduction,1),tf.argmax(y,1))
#求准确率
with tf.name_scope('accuracy'):
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
tf.summary.scalar('accuracy',accuracy)
tf.equal()的作用是判断里面的两个参数的值是否相等,相等则返回True,否则为False,在这里是比较预测值和计算值的大小,返回的是大数所在数组的索引值。
tf.cast()函数的作用是执行 tensorflow 中张量数据类型转换,比如读入的图片如果是int8类型的,一般在要在训练前把图像的数据格式转换为float32类型。
总结
卷积的大概步骤都很相似,主要是使用的激活函数和设置的参数以及卷积的层数不同而有所区别,希望这篇文章能够帮助到一些新手小白,我也是刚入门没多久,一起学习进步吧!