卷积神经网络
学习目标
- 了解卷积神经网络的构成
- 记忆卷积的原理以及计算过程
- 了解池化的作用以及计算过程
1.卷积神经网络的组成
卷积神经网络由一个或多个卷积层、池化层以及全连接层等组成。与其他深度学习结构相比,卷积神经网络在图像等方面能够给出更好的结果。这一模型也可以使用反向传播算法进行训练。相比较其他浅层或深度神经网络,卷积神经网络需要考量的参数更少,使之成为一种颇具吸引力的深度学习结构。
我们来看一下卷积网络的整体结构什么样子。
其中包含了几个主要结构:
- 卷积层(Convolutions)
- 池化层(Subsampling)
- 全连接层(Full connection)
- 激活函数
1.1 卷积层
- 目的:
- 卷积运算的目的是提取输入的不同特征,某些卷积层可能只能提取一些低级的特征如边缘、线条和角等层级,更多层的网路能从低级特征中迭代提取更复杂的特征。
- 参数:
- size:卷积核/过滤器大小,选择有1*1, 3 *3, 5 * 5等
- padding:零填充,Valid 与Same
- stride:步长,通常默认为1
- 计算公式:
1.2 卷积运算过程
- 一个步长,3 X 3 卷积核运算
假设是一张5 X 5 的单通道图片,通过使用3 X 3 大小的卷积核运算得到一个 3 X 3大小的运算结果(图片像素数值仅供参考)
- 缺点
- 图像变小
- 边缘信息丢失
1.2 padding-零填充
零填充:在图片像素的最外层加上若干层0值,若一层,记做p =1。
- 为什么增加的是0?
因为0在权重乘积和运算中对最终结果不造成影响,也就避免了图片增加了额外的干扰信息。
1.3 Valid and Same卷积
有两种两种形式,所以为了避免上述情况,大家选择都是Same这种填充卷积计算方式
所以当知道了卷积核的大小之后,就可以得出要填充多少层像素。
1.4 奇数维度的过滤器
通过上面的式子,如果F不是奇数而是偶数个,那么最终计算结果不是一个整数,造成0.5,1.5…这种情况,这样填充不均匀,所以也就是为什么卷积核默认都去使用奇数维度大小.
当然这个都是一些假设的原因,最终原因还是在F对于计算结果的影响。所以通常选择奇数维度的过滤器,是大家约定成俗的结果,可能也是基于大量实验奇数能得出更好的结果。
1.5 stride-步长
以上例子中我们看到的都是每次移动一个像素步长的结果,如果将这个步长修改为2,3,那结果如何?
1.6 多通道卷积
当输入有多个通道(channel)时(例如图片可以有 RGB 三个通道),卷积核需要拥有相同的channel数,每个卷积核 channel 与输入层的对应 channel 进行卷积,将每个 channel 的卷积结果按位相加得到最终的 Feature Map。
1.6.1 多卷积核
当有多个卷积核时,可以学习到多种不同的特征,对应产生包含多个 channel 的 Feature Map, 例如上图有两个 filter,所以 output 有两个 channel。这里的多少个卷积核也可理解为多少个神经元。
相当于我们把多个功能的卷积核的计算结果放在一起,比如水平边缘检测和垂直边缘检测器。
1.6.2 卷积总结
我们来通过一个例子看一下结算结果,以及参数的计算:
-
假设我们有10 个Filter,每个Filter3 X 3 X 3(计算RGB图片),并且只有一层卷积,那么参数有多少?
计算:每个Filter参数个数为:3 3 3 + 1 bias = 28个权重参数,总共28 * 10 = 280个参数,即使图片任意大小,我们这层的参数也就这么多。 -
假设一张200 200 3的图片,进行刚才的FIlter,步长为1,最终为了保证最后输出的大小为200 * 200,需要设置多大的零填充
1.7 池化层
池化层主要对卷积层学习到的特征图进行亚采样(subsampling)处理,主要由两种
- 最大池化:Max Pooling,取窗口内的最大值作为输出
- 平均池化:Avg Pooling,取窗口内的所有值的均值作为输出
意义在于:
- 降低了后续网络层的输入维度,缩减模型大小,提高计算速度
- 提高了Feature Map 的鲁棒性,防止过拟合
对于一个输入的图片,我们使用一个区域大小为2 *2,步长为2的参数进行求最大值操作。同样池化也有一组参数f,s,得到2 *2的大小。当然如果我们调整这个超参数,比如说3 * 3,那么结果就不一样了,通常选择默认都是f = 2 * 2, s = 2f=2∗2,s=2
池化超参数特点:不需要进行学习,不像卷积通过梯度下降进行更新。
如果是平均池化则:
1.8 全连接层
卷积层+激活层+池化层可以看成是CNN的特征学习/特征提取层,而学习到的特征(Feature Map)最终应用于模型任务(分类、回归):
- 先对所有 Feature Map 进行扁平化(flatten, 即 reshape 成 1 x N 向量)
- 再接一个或多个全连接层,进行模型学习
2.经典分类网络解构
学习目标:
- 知道LeNet-5网络结构
- 了解经典的分类网络结构
- 知道一些常见的卷机网络结构的优化
- 知道NIN中1x1卷积原理以及作用
- 知道Inception的作用
- 了解卷积神经网络学习过程内容
面我们主要以一些常见的网络结构去解析,并介绍大部分的网络的特点。这里看一下卷积的发展历史图。
2.1 LeNet-5解析
首先我们从一个稍微早一些的卷积网络结构LeNet-5(这里稍微改了下名字),开始的目的是用来识别数字的。从前往后介绍完整的结构组成,并计算相关输入和输出。
此图中,f=5,s=1,n=6代表 6个5*5的卷积核.
事实上,在过去很多年,许多机构或者学者都发布了各种各样的网络,其实去了解设计网络最好的办法就是去研究现有的网络结构或者论文。大多数网络设计出来是为了Image Net的比赛(解决ImageNet中的1000类图像分类或定位问题),后来大家在各个业务上进行使用。
2.2 AlexNet
2012年,Alex Krizhevsky、Ilya Sutskever在多伦多大学Geoff Hinton的实验室设计出了一个深层的卷积神经网络AlexNet,夺得了2012年ImageNet LSVRC的冠军,且准确率远超第二名(top5错误率为15.3%,第二名为26.2%),引起了很大的轰动。AlexNet可以说是具有历史意义的一个网络结构。
- 总参数量:60M=6000万,5层卷积+3层全连接
- 使用了非线性激活函数:ReLU
- 防止过拟合的方法:Dropout,数据扩充(Data augmentation)
- 批标准化层的使用
2.3 GoogleNet
3.案例-CNN手写数字识别
- API:
- 网络结构分析:
代码如下,同样的数据集,训练20000步,学习率为0.01.
比较CNN和NN的效果:NN博客
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
#填写"1"进行训练,"0"进行预测.
tf.app.flags.DEFINE_integer("is_train", 0, "训练or预测")
FLAGS = tf.app.flags.FLAGS
#定义两个专门初始化权重和偏置的函数,其中主意stddv要设置小一点,否则容易梯度爆炸
def weight_variables(shape):
w =tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=0.1))
return w
def bias_variables(shape):
b =tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=0.1))
return b
def cnn_model():
'''
自定义CNN模型
第一层:
卷积:32个filter,大小5*5 ,strides = 1, padding = "SAME"
激活:Relu
池化:大小2 * 2, stride =2
第二层:
卷积:32个filter,大小5*5 ,strides = 1, padding = "SAME"
激活:Relu
池化:大小2 * 2, stride =2
全连接层:[7*7*64,10] [10]
:return:
'''
#1.准备数据占位符,便与后面卷积计算
#x[None, 784] ,y_true = [None, 10]
with tf.variable_scope("x_data"):
x = tf.placeholder(tf.float32, [None, 784], name="x")
y_true = tf.placeholder(tf.float32, [None, 10], "y_true")
#2.第一层
#卷积: 32个filter, 大小5 * 5, strides = 1, padding = "SAME"
# 激活: Relu
# 池化: 大小2 * 2, stride = 2
with tf.variable_scope("conv_1"):
#准备权重和偏置参数
#权重数量:[5, 5, 1, 32]
#偏置数量:[32]
w_conv1 = weight_variables([5, 5, 1, 32])
b_conv1 = bias_variables([32])
#特征形状由2维,变为4维,用于卷积运算
x_reshape = tf.reshape(x, [-1, 28, 28, 1])
#进行卷积,激活函数运算
#[None, 28, 28, 1]------>[None, 28, 28, 32]
x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape,
w_conv1,
strides=[1, 1, 1, 1],
padding="SAME", name="conv1") + b_conv1,
name="relu1")
#进行池化层
#[None 28, 28, 32]-------->[None, 14, 14, 32]
x_pool1 = tf.nn.max_pool(x_relu1,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding="SAME",
name="pool1")
#3.第二层
# 卷积: 32个filter, 大小5 * 5, strides = 1, padding = "SAME"
# 激活: Relu
# 池化: 大小2 * 2, stride = 2
with tf.variable_scope("conv2"):
w_conv2 = weight_variables([5, 5, 32, 64])
b_conv2 = bias_variables(([64]))
#进行卷积,激活运算
#卷积:[None, 14, 14, 32]-------->[None, 14, 14, 64]
#激活:[None, 14, 14, 64]-------->[None, 14, 14, 64]
x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1,
w_conv2,
strides=[1, 1, 1, 1],
padding="SAME",
name="conv2") + b_conv2,
name="relu2")
#进行池化运算
#池化:[None, 14, 14, 64]--------->[None, 7, 7, 64]
x_pool2 = tf.nn.max_pool(x_relu2,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding="SAME",
name="pol2")
#4.全连接层: 全连接层:[7*7*64,10] [10]
with tf.variable_scope("fc"):
#初始化权重,偏置
w_fc = weight_variables([7*7*64, 10])
b_fc = bias_variables([10])
#矩阵运算转换二维
x_fc_reshape = tf.reshape(x_pool2, [-1, 7*7*64])
#全连接层矩阵运算
y_predict = tf.matmul(x_fc_reshape, w_fc) + b_fc
return x, y_true, y_predict
def cnn_train():
'''
卷积网咯识别训练
:return:
'''
#1.准备数据输入
mnist = input_data.read_data_sets("./data/mnist/", one_hot=True)
#2.建立卷积网咯模型
#y_true:[None, 10]
#y_predict:{None, 10}
x, y_true, y_predict = cnn_model()
#3.计算softmax值,计算交叉熵损失
with tf.variable_scope("compute_loss"):
# 目标值的softmax结果与真实值的交叉熵损失计算,labels是真实值,logits是网络输出值
# 计算出了所有样本的损失列表
all_loss = tf.nn.softmax_cross_entropy_with_logits(labels=y_true,
logits=y_predict,
name="compute_loss")
# 计算平均损失
loss = tf.reduce_mean(all_loss)
#4.梯度下降优化
with tf.variable_scope("optimizer"):
#梯度下降优化
#学习率
train_op = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
#5.准确率计算
with tf.variable_scope("accuracy"):
#求出每个样本是否相等的一个列表,就是每列预测结果的最大值和hot-hot标签的最大值比较
equal_list = tf.equal(tf.argmax(y_predict, 1), tf.argmax(y_true, 1))
accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))
# 创建一个saver
saver = tf.train.Saver()
#开启会话进行训练
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
# 加载模型
ckpt = tf.train.latest_checkpoint("./temp/model/")
if ckpt:
saver.restore(sess, ckpt)
if FLAGS.is_train == 1:
# 建立保存观察的文件
file_writer = tf.summary.FileWriter("./temp/summary/", graph=sess.graph)
#循环每一步去训练
for i in range(2000):
# 每一步训练指定的50个样本
# 或者每一步把所有样本都训练一遍
mnist_x, mnist_y = mnist.train.next_batch(50)
# 会话进行训练,向占位符喂数据
loss_run, acc, _ = sess.run([loss, accuracy, train_op],
feed_dict={x: mnist_x, y_true: mnist_y})
print("迭代第%d步,损失为:%f,准确率为:%f" % (i, loss_run, acc))
# 每100次保存一下参数
# 需要一个模型名字
if i % 100 == 0:
saver.save(sess, "./temp/model/fc_nn_model")
else:
#拿100个测试集样本去预测
for i in range(100):
#label[1, 10]
image, label =mnist.test.next_batch(1)
#网络输出结果,对比计算结果
print("第%d个样本的真实值为:%d, 模型预测结果为:%d" % (
i + 1,
tf.argmax(sess.run(y_true, feed_dict={x: image, y_true:label}), 1).eval(),
tf.argmax(sess.run(y_predict, feed_dict={x: image, y_true: label}), 1).eval()
)
)
return None
if __name__ =="__main__":
cnn_train()
输出结果:
迭代第19987步,损失为:0.010499,准确率为:1.000000
迭代第19988步,损失为:0.002744,准确率为:1.000000
迭代第19989步,损失为:0.013770,准确率为:1.000000
迭代第19990步,损失为:0.011562,准确率为:1.000000
迭代第19991步,损失为:0.002075,准确率为:1.000000
迭代第19992步,损失为:0.010849,准确率为:1.000000
迭代第19993步,损失为:0.080974,准确率为:0.960000
迭代第19994步,损失为:0.034719,准确率为:0.980000
迭代第19995步,损失为:0.071193,准确率为:0.960000
迭代第19996步,损失为:0.000917,准确率为:1.000000
迭代第19997步,损失为:0.010071,准确率为:1.000000
迭代第19998步,损失为:0.007908,准确率为:1.000000
迭代第19999步,损失为:0.108528,准确率为:0.980000
Process finished with exit code 0
当tf.app.flags.DEFINE_integer("is_train", 0, "训练or预测"),当参数为0时,
从模型中加载参数,进行预测.
第90个样本的真实值为:5, 模型预测结果为:5
第91个样本的真实值为:6, 模型预测结果为:6
第92个样本的真实值为:5, 模型预测结果为:5
第93个样本的真实值为:2, 模型预测结果为:2
第94个样本的真实值为:4, 模型预测结果为:4
第95个样本的真实值为:0, 模型预测结果为:0
第96个样本的真实值为:4, 模型预测结果为:4
第97个样本的真实值为:7, 模型预测结果为:7
第98个样本的真实值为:6, 模型预测结果为:6
第99个样本的真实值为:5, 模型预测结果为:5
第100个样本的真实值为:6, 模型预测结果为:6
Process finished with exit code 0