卷积神经网络(CNN)
关注公众号“轻松学编程”了解更多。
一、简介
卷积神经网络(Convolutional Neural Network,CNN)是一种前馈神经网络,它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色表现。 它包括卷积层(convolutional layer)和池化层(pooling layer)。
卷积神经网络包括一维卷积神经网络、二维卷积神经网络以及三维卷积神经网络。
一维卷积神经网络常应用于序列类的数据处理;
二维卷积神经网络常应用于图像类文本的识别;
三维卷积神经网络主要应用于医学图像以及视频类数据识别。
【参考:https://blog.csdn.net/ice_actor/article/details/78648780】
【参考:https://www.cnblogs.com/skyfsm/p/6790245.html】
二、几个概念
1、反向传播
几个人站成一排第一个人看一幅画(输入数据),描述给第二个人(隐层)……依此类推,到最后一个人(输出)的时候,画出来的画肯定不能看了(误差较大)。
反向传播就是,把画拿给最后一个人看(求取误差),然后最后一个人就会告诉前面的人下次描述时需要注意哪里(权值修正)。
卷积神经网络也是这样,哪个特征参数对结果影响较大就增加它的权重,影响较小就减少它的权重,通过不断的反馈,找到一系列合适的权重系数。
2、什么是卷积
卷积:convolution
已有运算:加减乘除,幂运算指数运算
卷积:卷积也是一种运算
【参考:https://www.zhihu.com/question/22298352?rf=21686447】
####2.1 卷积运算
卷积操作是使用一个二维的卷积核在一个批处理的图片上进行不断扫描。具体操作是将一个卷积核在每张图片上按照一个合适的尺寸在每个通道上面进行扫描。
卷积的过程:如下图所示,用一个3*3
的卷积核在5*5
的图像上做卷积的过程
在三通道图像上的卷积过程,如下:
计算步骤如下
计算步骤解释如下,原图大小为7*7
,通道数为3:,卷积核大小为3*3
;
Input Volume中的蓝色方框和Filter W0中红色方框的对应位置元素相乘再求和得到res(即,下图中的步骤1.res的计算),
再把res和Bias b0进行相加(即,下图中的步骤2),
得到最终的Output Volume
两个卷积核就有两个输出。
2.2 卷积函数
如下:卷积函数tf.nn.conv2d
- 第一个参数:input
- input就是需要做卷积的图像(这里要求用Tensor来表示输入图像,并且Tensor(一个4维的Tensor,要求类型为float32)的shape为**[batch, in_height, in_width, in_channels]**
- 具体含义[训练时一个batch图像的数量,图像高度,图像宽度, 图像通道数])
第二个参数:filter - filter就是卷积核(这里要求用Tensor来表示卷积核,并且Tensor(一个4维的Tensor,要求类型与input相同)的shape为**[filter_height, filter_width, in_channels, out_channels]**
- 具体含义[卷积核高度,卷积核宽度,数据(图像)通道数,卷积核个数],这里的图片通道数也就input中的图像通道数,二者相同。)
- 第三个参数:strides
-
strides就是卷积操作时在图像每一维的步长,strides是一个长度为4的一维向量
第四个参数:padding - padding是一个string类型的变量,只能是 “SAME” 或者 “VALID”,决定了两种不同的卷积方式。
下面我们来介绍 “SAME” 和 “VALID” 的卷积方式,如下图我们使用单通道的图像,图像大小为5*5
,卷积核用3*3
- 'VALID’表示有效的,输出的图像会变小(取中间的矩阵)。红色#表示卷积核中心点在图像上的滑动过程。最后得到3*3的图像大小
-
strides就是卷积操作时在图像每一维的步长,strides是一个长度为4的一维向量
- ‘SAME’ 表示相同的,输出的图像和原来一样。首先在原图外层补一圈0,将原图的第一点作为卷积核中心,若一圈0不够,继续补一圈0
- 第五个参数:use_cudnn_on_gpu
- 使用GPU来运行(效率更高)
- 第六个参数:data_format
- data_format就是input的Tensor格式,一般默认就可以了。都采用NHWC
- 第七个参数:name
- 就是用以指定该操作的name,仅此而已。
3、卷积构造方法
3.1 激活函数
在神经网络中,激活函数的作用是能够给神经网络加入一些非线性因素,使得神经网络可以更好地解决较为复杂的问题。
在神经网络中,有很多的非线性函数来作为激活函数,比如连续的平滑非线性函数(sigmoid,tanh和softplus),连续但不平滑的非线性函数(relu,relu6和relu_x)和随机正则化函数(dropout)
所有的激活函数都是单独应用在每个元素上面的,并且输出张量的维度和输入张量的维度一样(即不改变输入数组的形状)。
- tf.nn.relu(features, name = None)
- 这个函数的作用是计算激活函数relu,即max(features, 0)
当输入值小于或等于0时置为0,大于0时保持原状。
- tf.nn.relu6(features, name = None)
- 这个函数的作用是计算激活函数relu6,即min(max(features, 0), 6)
- tf.nn.softplus(features, name = None)
- 这个函数的作用是计算激活函数softplus,即log( exp( features ) + 1)
- tf.sigmoid(x, name = None)
- 这个函数的作用是计算 x 的 sigmoid 函数。具体计算公式为 y = 1 / (1 + exp(-x))
- tf.tanh(x, name = None)
- 这个函数的作用是计算 x 的 tanh 函数。具体计算公式为 ( exp(x) - exp(-x) ) / ( exp(x) + exp(-x) )
3.2 Dropout
当训练数据量比较小时,可能会出现因为追求最小差值导致训练出来的模型极度符合训练集,但是缺乏普适性(即泛化能力低),不能表达训练数据之外的数据。
本来有普适性的模型被训练成了具有训练集特殊性,即回归曲线应该是黑色的线,由于追求最小差值导致回归曲线是红色虚线。
以至于对于真实的数据产生了错误:
解决方案:
-
tf.nn.dropout(x, keep_prob, noise_shape = None, seed = None, name = None)
- 这个函数的作用是计算神经网络层的dropout。
- 一个神经元将以概率keep_prob决定是否放电,如果不放电,那么该神经元的输出将是0;
- 如果该神经元放电,那么该神经元的输出值将被放大到原来的1/keep_prob倍。
- 这里的放大操作是为了保持神经元输出总个数不变。比如,神经元的值为[1, 2],keep_prob的值是0.5,并且是第一个神经元是放电的,第二个神经元不放电,那么神经元输出的结果是[2, 0],也就是相当于,第一个神经元被当做了1/keep_prob个输出,即2个。这样保证了总和2个神经元保持不变
- tf.nn.dropout是TensorFlow里面为了防止或减轻过拟合而使用的函数,它一般用在全连接层。
- Dropout就是在不同的训练过程中随机扔掉一部分神经元。也就是让某个神经元的激活值以一定的概率p,让其停止工作,这次训练过程中不更新权值,也不参加神经网络的计算。但是它的权重得保留下来(只是暂时不更新而已),因为下次样本输入时它可能又得工作了。如下图所示:
3.3 卷积层
卷积操作是使用一个二维的卷积核在一个批处理的图片上进行不断扫描。具体操作是将一个卷积核在每张图片上按照一个合适的尺寸在每个通道上面进行扫描。
tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
这个函数的作用是对一个四维的输入数据 input 和四维的卷积核 filter 进行操作,然后对输入数据进行一个二维的卷积操作,最后得到卷积之后的结果
tf.nn.bias_add(value, bias, name = None):这个函数的作用是将偏差项 bias 加到 value 上面。
3.4 池化层
池化操作是利用一个矩阵窗口在输入张量上进行扫描,并且将每个矩阵窗口中的值通过取最大值,平均值或者XXXX来减少元素个数。
Maxpooling 就是在这个区域内选出最能代表边缘的值,然后丢掉那些没多大用的信息(保留最符合的特征)。
tf.nn.max_pool(value, ksize, strides, padding, name=None):
这个函数的作用是计算池化区域中元素的最大值。
参数value:需要池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map,依然是
[batch, height, width, channels]这样的shape
参数ksize:池化窗口的大小,取一个四维向量,一般是
[1, height, width, 1],因为我们不想在batch和channels上做池化,所以这两个维度设为了1
参数strides:和卷积类似,窗口在每一个维度上滑动的步长,一般也是
[1, stride,stride, 1]
参数padding:和卷积类似,可以取’VALID’ 或者’SAME’。
-
返回一个Tensor,类型不变,shape仍然是
[batch, height, width, channels]
这种形式。
3.5 图例
##三、卷积对图像去噪处理
1、平滑均值滤波
卷积核为:
导包
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import tensorflow as tf
获取图片
moon = plt.imread('./img/moonlanding.png')
plt.figure(figsize=(8,8))
plt.imshow(moon,cmap='gray')
moon.shape
图像转为4维Tensor
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [训练时一个batch图像的数量,图像高度,图像宽度, 图像通道数]
moon2 = moon.reshape(1,474,630,1)
moon2 = tf.constant(moon2,dtype=tf.float32)
设置卷积核
# 卷积核
# 使用平滑均值滤波
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [卷积核高度,卷积核宽度,图像通道数,卷积核个数]
filter_arg = np.array([1/9]*9).reshape(3,3,1,1)
filter_arg = tf.constant(filter_arg,dtype=tf.float32)
卷积运算
#卷积处理
# input,filter,strides,padding
conv_moon = tf.nn.conv2d(
moon2, #输入的图像数组
filter_arg, #卷积核
strides=[1,1,1,1], # 步长
padding='SAME' # 输出形状
)
显示滤波后的图片
with tf.Session() as sess:
conv_moon = sess.run(conv_moon)
print(conv_moon.shape)
plt.figure(figsize=(8,8))
plt.imshow(conv_moon.reshape(474,630),cmap='gray')
平滑均值滤波后,噪点与附近的像素点做了一个均值化处理,所以它们的像素值高的被拉低,低的被拉高,最后保持在同一水平线上,所以图像会变模糊,这个卷积核虽然可以对抗噪声,但是它牺牲了图像整体的边缘的清晰度。
###2、高斯平滑滤波
卷积核:
#使用高斯平滑
filter_arg_Gaus = np.array([1/16,2/16,1/16,
2/16,4/16,2/16,
1/16,2/16,1/16]).reshape(3,3,1,1)
filter_arg_Gaus = tf.constant(filter_arg_Gaus,dtype=tf.float32)
#卷积处理
# input,filter,strides,padding
conv_moon_Gaus = tf.nn.conv2d(
moon2, #输入的图像数组
filter_arg_Gaus, #卷积核
strides=[1,1,1,1], # 步长
padding='SAME' # 输出形状
)
with tf.Session() as sess:
conv_moon_Gaus = sess.run(conv_moon_Gaus)
print(conv_moon_Gaus.shape)
plt.figure(figsize=(8,8))
plt.imshow(conv_moon_Gaus.reshape(474,630),cmap='gray')
高斯平滑的水平和垂直方向呈现高斯分布,更突出了中心点在像素平滑后的权重,相比于均值滤波而言,有着更好的平滑效果。
四、卷积对图片风格化处理
1、浮雕
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
%matplotlib inline
flower = plt.imread('./img/flower.png')
plt.imshow(flower)
plt.axis('off')
flower.shape
图像的高是332,宽是444,图像通道数为3.
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [训练时一个batch图像的数量,图像高度,图像宽度, 图像通道数]
flower_input = flower.reshape(1,332,444,3)
flower_input = tf.constant(flower_input,dtype=tf.float32)
# 卷积核
# 使用不同的卷积核,图片处理效果会不一样
#浮雕
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [卷积核高度,卷积核宽度,图像通道数,卷积核个数]
filter_arg_relievo = tf.constant(np.array([
[-1,-1, 0],
[-1, 0, 1],
[ 0, 1, 1]
]*3).reshape(3,3,3,1),
dtype=tf.float32)
#卷积运算
cnn_flower = tf.nn.conv2d(flower_input,
filter_arg_relievo,
strides=[1,1,1,1],
padding='SAME'
)
#绘图
with tf.Session() as sess:
cnn_flower = sess.run(cnn_flower)
print(cnn_flower.shape)
plt.imshow(cnn_flower.reshape(332,444),cmap='gray')
plt.axis('off')
2、强调边缘
# 卷积核
# 使用不同的卷积核,图片处理效果会不一样
# 强调边缘
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [卷积核高度,卷积核宽度,图像通道数,卷积核个数]
filter_arg_strong_edge = tf.constant(np.array([
[1, 1, 1],
[1,-7, 1],
[1, 1, 1]
]*3).reshape(3,3,3,1),
dtype=tf.float32)
#卷积运算
cnn_flower = tf.nn.conv2d(flower_input,
filter_arg_strong_edge,
strides=[1,1,1,1],
padding='SAME'
)
with tf.Session() as sess:
cnn_flower = sess.run(cnn_flower)
print(cnn_flower.shape)
plt.imshow(cnn_flower.reshape(332,444),cmap='gray')
plt.axis('off')
3、保持原样
# 卷积核
# 使用不同的卷积核,图片处理效果会不一样
# 保持原样
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [卷积核高度,卷积核宽度,图像通道数,卷积核个数]
filter_arg_original = tf.constant(np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
]*3).reshape(3,3,3,1),
dtype=tf.float32)
#卷积运算
cnn_flower = tf.nn.conv2d(flower_input,
filter_arg_original,
strides=[1,1,1,1],
padding='SAME'
)
with tf.Session() as sess:
cnn_flower = sess.run(cnn_flower)
print(cnn_flower.shape)
plt.imshow(cnn_flower.reshape(332,444),cmap='gray')
4、边缘检测
# 卷积核
# 使用不同的卷积核,图片处理效果会不一样
# 边缘检测
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [卷积核高度,卷积核宽度,图像通道数,卷积核个数]
filter_arg_edge_check = tf.constant(np.array([
[-1,-1,-1],
[-1 ,9,-1],
[-1,-1,-1]
]*3).reshape(3,3,3,1),
dtype=tf.float32)
#卷积运算
cnn_flower = tf.nn.conv2d(flower_input,
filter_arg_edge_check,
strides=[1,1,1,1],
padding='SAME'
)
with tf.Session() as sess:
cnn_flower = sess.run(cnn_flower)
print(cnn_flower.shape)
plt.imshow(cnn_flower.reshape(332,444),cmap='gray')
plt.axis('off')
5、其它效果
# 卷积核
# 使用不同的卷积核,图片处理效果会不一样
# 其它效果
# 转成一个一个4维的Tensor,数据类型为tf.float32
# [卷积核高度,卷积核宽度,图像通道数,卷积核个数]
filter_arg_edge_check = tf.constant(np.array([[-1,-1,-1,-1,-1],
[-1,-1,-1,-1,-1],
[-1,-1,8,-1,-1],
[-1,-1,-1,-1,-1],
[-1,-1,-1,-1,-1]
]*3).reshape(5,5,3,1),
dtype=tf.float32)
#卷积运算
cnn_flower = tf.nn.conv2d(flower_input,
filter_arg_edge_check,
strides=[1,1,1,1],
padding='SAME'
)
with tf.Session() as sess:
cnn_flower = sess.run(cnn_flower)
print(cnn_flower.shape)
plt.imshow(cnn_flower.reshape(332,444),cmap='gray')
plt.axis('off')
五、CNN识别手写数字
导包
import numpy as np
import tensorflow as tf
#导入手写数字数据集
from tensorflow.examples.tutorials.mnist import input_data
获取数据
mnist = input_data.read_data_sets('./data/',one_hot=True)
声明权重、卷积、池化操作
#声明方法
#系数
def gen_w(shape):
# 正态分布随机数
v = tf.random_normal(shape,stddev=0.1)
return tf.Variable(v,dtype=tf.float32)
# 截距
def gen_b(shape):
v = tf.constant(0.1,shape=shape)
return tf.Variable(v,dtype=tf.float32)
# 卷积操作
def gen_conv(X,filter_arg):
# 返回数据的形状和X一样
return tf.nn.conv2d(X,filter_arg,
strides=[1,1,1,1],padding='SAME')
# 池化代码,进一步抽取特征
def gen_pool(conv):
#ksize = [1, height, width, 1]
return tf.nn.max_pool(value=conv,ksize=[1,2,2,1],
strides=[1,2,2,1],
padding='SAME')
声明X,y变量
x = tf.placeholder(dtype=tf.float32,shape = [None,784])
y = tf.placeholder(dtype=tf.float32,shape = [None,10])
第一层卷积
# 声明卷积核:[卷积核高度,卷积核宽度,图像通道数,卷积核个数]
#有32个卷积核就有32个输出
W_conv_1 = gen_w([5,5,1,32])
#偏置项(截距)
b_conv_1 = gen_b([32])
# 卷积的input形状[训练时一个batch图像的数量,图像高度,图像宽度, 图像通道数]
#最后一维代表图片的颜色通道数(因为是灰度图所以这里的通道数为1
#如果是rgb彩色图,则为3
# -1表示图像的数量待定
x_image = tf.reshape(X,[-1,28,28,1])
#把x_image和权值向量进行卷积,加上偏置项
#然后应用ReLU激活函数,最后进行max pooling。
conv_1 = tf.nn.relu(gen_conv(x_image,W_conv_1) + b_conv_1)
#池化
pool_1 = gen_pool(conv_1)
# pool_1 .shape = (-1,14,14,32)
第一层卷积后输出结果形状为(-1,14,14,32)。
第二层卷积
# 为了构建一个更深的网络,我们会把几个类似的层堆叠起来。
#第二层中,每个5x5的patch会得到64个特征。
W_conv_2 = gen_w([5,5,32,64])
b_conv_2 = gen_b([64])
# 激活函数 不改变数组形状
conv_2 = tf.nn.relu(gen_conv(pool_1,W_conv_2) + b_conv_2)
pool_2 = gen_pool(conv_2)
# pool_2 .shape = (-1,7,7,64)
第一层卷积后输出结果形状为(-1,7,7,64)。
全连接层
'''现在,图片尺寸减小到7x7,我们加入一个有1024个神经元的全连接层,
用于处理整个图片。
我们把池化层输出的张量reshape成一些向量,
乘上权重矩阵,加上偏置,
然后对其使用ReLU。'''
W_full = gen_w([7*7*64,1024])
b_full = gen_b([1024])
pool_full = tf.reshape(pool_2,[-1,7*7*64])
full_conn = tf.nn.relu(tf.matmul(pool_full,W_full) + b_full)
# full_conn.shape = (-1,1024)
第一层卷积后输出结果形状为(-1,1024)。
为了减少过拟合,在输出层之前加入dropout
'''我们用一个placeholder来代表一个神经元的输出在dropout中保持不变的概率
这样我们可以在训练过程中启用dropout,
在测试过程中关闭dropout。'''
keep_prob = tf.placeholder(tf.float32)
full_conn_drop = tf.nn.dropout(full_conn,keep_prob)
输出层
# 输出层[[10个概率],[10个概率],[概率]……].shape = (-1,10)
# (-1,1024)-------> (-1,10)
W_full_2 = gen_w([1024,10])
b_full_2 = gen_b([10])
#创建线性模型并计算概率分布
pred = tf.nn.softmax(tf.matmul(full_conn_drop,W_full_2) + b_full_2)
训练和评估模型
'''在feed_dict中加入额外的参数keep_prob来控制dropout比例。
然后每100次迭代输出一次日志。
'''
#损失函数 交叉熵
cost = -tf.reduce_sum(y*tf.log(pred))
#梯度下降优化器optimizer 获取最小交叉熵
optimizer = tf.train.AdamOptimizer(1e-4).minimize(cost)
#计算准确率
correct_prediction = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
# 训练20000次
for i in range(2000):
# 每次训练取100个样本
X_train,y_train = mnist.train.next_batch(50)
opt,cost_ = sess.run([optimizer,cost],
feed_dict={
X:X_train,
y:y_train,
keep_prob:0.5
}
)
# 每100次输出一次日志
if i%100 == 0:
accu = sess.run(accuracy,
feed_dict={X:mnist.test.images[:1000],
y:mnist.test.labels[:1000],
keep_prob:1
}
)
print('训练次数%d,损失cost:%0.4f,准确率:%0.4f'%(i,cost_,accu))
可以看到训练500次时,准确率就达到92%了,使用卷积神经网络随着训练次数的增加,模型准确率就会增高。但是缺点就是很耗时。
后记
【后记】为了让大家能够轻松学编程,我创建了一个公众号【轻松学编程】,里面有让你快速学会编程的文章,当然也有一些干货提高你的编程水平,也有一些编程项目适合做一些课程设计等课题。
也可加我微信【1257309054】,拉你进群,大家一起交流学习。
如果文章对您有帮助,请我喝杯咖啡吧!
公众号
关注我,我们一起成长~~