Tensorflow的MNIST进阶教程CNN网络参数理解

  • 背景
  • 问题说明
  • 分析
    • LeNet5参数
    • MNIST程序参数
  • 遗留问题
  • 小结

背景

之前博文中关于CNN的模型训练功能上是能实现,但是研究CNN模型内部结构的时候,对各个权重系数w,偏差b的shape还是存在疑惑,为什么要取1024,为什么取7*7*64,最近找到了一些相关资料,对这个问题有了新的理解,下面和大家分享一下。


问题说明

# Input Layer
x = tf.placeholder(‘float‘,[None,784])
y_ = tf.placeholder(‘float‘,[None,10])
# First Layer
W_conv1 = weight_variable([5,5,1,32])
b_conv1 = bias_variable([32])

x_image = tf.reshape(x,[-1,28,28,1])
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
# Second Layer
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)
h_pool2 = max_pool_2x2(h_conv2)
# Connect Layer
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])

h_pool2_flat = tf.reshape(h_pool2,[-1,7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1) + b_fc1)
# Dropout Layer
keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1,keep_prob)
# Output Layer
W_fc2 = weight_variable([1024,10])
b_fc2 = bias_variable([10])

这段程序,是mnist_test2.py中关于Tensorflow Graph建立的代码,里面为所有使用到的Variables都分配了指定的大小。例如x通过placeholder预留了None*784大小的shape(None会随着batch获取的数据个数而改变),第一层的卷积系数W_conv1是5*5*1*32大小的shape,后面不再赘述。

有些shape比如输入的xNone*784的784是28*28个8位灰度值的shape,y_是None*10个数字分类的shape,输出的b_fc2是对应10个输出类别的偏差。但是对于其他参数的话,我就不禁要问:①这么做是为什么呢?②如果不这么设置参数行不行呢?③如果我换一套数据集,参数还是这么设置么?

先回答第二个问题吧,不行。如果随意更换参数,换的对可能能跑通,但是影响性能,但是更多的时候会是下面的结果:


分析

先看看LeNet5

在回答第一个问题之前,建议大家阅读一下2012年NIPS上做ImageNet的文献1和解释LaNet5的CNN模型的PPT,如果时间不够用的话,也可以直接看一下qiaofangjie的中文参数解析。里面可以先对LeNet5的参数有个大概了解。下面我对照着经典的图,大体归纳一下:

CNN里的层,除了输入/输出层之外,把隐含层又分成了卷积层、池化层、以及全连接层。三者的作用及介绍也可以参考小新的猫的理解。卷积层会对原始输入数据做加权求和的处理,学过通信的同学一定对一维线性卷积里面“错位相乘相加”很熟悉(原谅我强行奶一口原来的专业),这里就是线性卷积在二维里面的推广。池化层是为了减小模型复杂度,通过降采样(又是通信里有概念我会乱讲?)缩小特征图的尺寸。全连接层是将最终高度抽象化的特征图与输出直接进行的全连接,这与入门级MNIST库的模型训练做的事儿是一样的,一个简单的全映射。

具体的看,第一个卷积层C1对32*32的输入数据(假设成一个矩阵,其中每个元素代表当前位置像素点灰度值的强弱)进行二维加权相加

yl+1m,n=∑i,jwli,jxli,j+bl+1

其中上标l表示层数,下标i,j表示对应的像素点位置,w、x、b分别表示滤波器系数,输入值,偏差值。

(此处补零采用了VALID模式,这个问题后面会接着说)。我们假设卷积器的权重矩阵是一个5*5的,相当于对5*5的输入数据,与5*5的卷积器卷积后,得到了1*1的数值,如果是5*6的原始数据,卷积后会得到1*2的输出,所以推广到32*32的数据,输出会得到28*28的尺寸。再推广一下就会得到:

输出宽度=输入宽度-(卷积器宽度-1)
输出高度=输入高度-(卷积器高度-1)

至于为什么一幅输入会得到6组对应的卷积输出呢?这个问题对我来说还说不清楚,只能暂时理解为一幅输入的6个不同的特征(Hinton老师讲家谱树的时候就用了6个对应特征表示是英国人还是意大利,是爷爷辈儿还是孙子辈儿等),比如输入是否有圆圈形状,边缘是否明确,是否有纹理特征等。这个问题后面的话有可能会可视化验证一下,目前只能这么理解了。

接下来第二个池化层S2,对6*28*28的特征图进行下采样得到了6*14*14的池化层特征图,主要是因为其中采用每2*2的块儿进行一次容和统计,并且块与块儿直接不交叠,掐指一算还真是(28/2*28/2),池化操作并没有增加特征维度,只是单纯的下采样。

在后面的那层卷积层C3的shape也可以理解了,对14*14的特征输入,输出10*10的特征图,并且此时把6个特征抽象到了更多的16个特征。下面的S4就不说了。

看一下C5,这里又来了问题了,暂时还不能理解,为什么会出现120这个数字,暂且搁置。一般CNN中的全连接层,我见到的都是1-2层,并且两个层的shape是一致的。比如这里都是120,文献1中全是2048。

MNIST库程序

ok,以上就是对LeNet5的参数理解,下面我们来看一下MNIST程序中的各个参数。首先,建议使用如下代码,在跑通的程序中,方便查看每个变量的shape,具体代码如下:

def showSize():
  print "x size:",batch[0].shape
  #print "x_value",batch[0]
  print "y_ size:",batch[1].shape
  #print "y_example",batch[1][0]
  #print "y_example",batch[1][9]
  #print "y_example",batch[1][10]
  print "w_conv1 size:",W_conv1.eval().shape
  print "b_conv1 size:",b_conv1.eval().shape
  print "x_image size:",x_image.eval(feed_dict={x:batch[0]}).shape
  print "h_conv1 size:",h_conv1.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape
  print "h_pool1 size:",h_pool1.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape
  print "w_conv2 size:",W_conv2.eval().shape
  print "b_conv2 size:",b_conv2.eval().shape
  print "h_conv2 size:",h_conv2.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape
  print "h_pool2 size:",h_pool2.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape
  print "w_fc1 size:",W_fc1.eval().shape
  print "b_fc1 size:",b_fc1.eval().shape
  print "h_pool2_flat size:",h_pool2_flat.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape
  print "h_fc1 size:",h_fc1.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape
  print "h_fc1_drop size:",h_fc1_drop.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape
  print "w_fc2 size:",W_fc2.eval().shape
  print "b_fc2 size:",b_fc2.eval().shape
  print "y_conv size:",y_conv.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}).shape

像这样插在每次循环中,然后,我们可以试运行一下看看结果

这个小函数写的时候要注意,对于Tensor类型的Variable,并没有shape这个属性,必须在每个Tensor.eval()才可以使用.shape属性,而Tensor.eval()的话,就需要feed_dict一个依赖值才可以获得tensor实例,否则Tensor对象只是一个代号而已。

在输入50个batch的时候可以看到输入,输出shape不说了,w_conv1涉及到tf.conv2d的用法,具体可以参考官方文档,这里的前两个5对应的卷及系数shape,第三个参数1表示输入通道,对应输入数据只有灰度值一个特征,或者说是对应LeNet分析中一幅图,最后一个32是输出通道数,表示输出32组特征。

稍等,对于MNIST的28*28的输入数据,经过第一层卷积C1,为何输出的h_conv1还是28*28的维度,而不是刚刚推论中的24*24呢?这我也研究了好久,原因在于tf.conv2d函数的参数指定了padding类型为“SAME”,这个参数的作用在官方文档中直接决定了输出的shape,截选如下:

这只是数值上说明了,具体的原因小新的猫的理解说的非常明白,他还对比了Matlab和Tensorflow对于Padding参数的不同选项,可以视具体的应用环境在做测试。

最后看一下全连接层的参数w_fc1,对接上面的卷基层输出,第一个参数是池化后7*7的尺寸*64组特征,第二个参数与LeNet的120一致,是输出给全连接的参数。


遗留问题

现在看来,与卷积,池化相关的shape变化应该是可以理解了,不过还有两种参数不理解:

①是卷积层的特征数,为什么LeNet里面两层卷积层的特征会是6和16,而MNIST是32和64,按道理MNIST是28*28的数据输入,还小于LeNet的32*32呢,却有着更高的特征维度。

②全连接层的参数,LeNet里面用的2个120参数的,而MNIST里面全连接却有1024个参数。

这些问题也问过Tensorflow群里群友,问到的都说是经验问题,需要自己把握,真的是这样哇?这两个参数的选择难道只是个工程经验问题嘛?我目前的理解的话只能一边继续看资料,一边工程做实验测试一下模型收敛速度啦。结合TensorBoard可视化应该后者会给我个答案。


小结

这篇文章,对CNN的网络中参数的shape做了分析,希望能对大家理解起来有所帮助,至于文中说到的两个参数遗留问题,也希望有大神能不吝指教呀。

PS:我必须要吐槽!CSDN为什么只能保存一个草稿,我写第一篇博文的时候突然觉得应该把这个问题剥离出来先讲一讲,结果之前那篇的草稿就没有了!那也是1个半小时的思路整理啊!不过也怪自己没有备份。TAT


时间: 2024-10-11 15:27:22

Tensorflow的MNIST进阶教程CNN网络参数理解的相关文章

ABP进阶教程5 - 布局配置

点这里进入ABP进阶教程目录 解读参数 l - length changing input control (左上,每页显示记录数) f - filtering input (右上,过滤条件) t - The table (中央,数据表格) i - Table information summary (左下,表格信息) p - pagination control (右下,分页) r - processing display element (中央,过场动画) 布局配置 打开展示层(即JD.CR

【深度学习系列】用PaddlePaddle和Tensorflow实现经典CNN网络Vgg

上周我们讲了经典CNN网络AlexNet对图像分类的效果,2014年,在AlexNet出来的两年后,牛津大学提出了Vgg网络,并在ILSVRC 2014中的classification项目的比赛中取得了第2名的成绩(第一名是GoogLeNet,也是同年提出的).在论文<Very Deep Convolutional Networks for Large-Scale Image Recognition>中,作者提出通过缩小卷积核大小来构建更深的网络. Vgg网络结构 VGGnet是Oxford的

使用tensorflow操作MNIST数据

本节开始学习使用tensorflow教程,当然从最简单的MNIST开始.这怎么说呢,就好比编程入门有Hello World,机器学习入门有MNIST.在此节,我将训练一个机器学习模型用于预测图片里面的数字. MNIST 是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用做深度学习的入门样例.而Tensorflow的封装让MNIST数据集变得更加方便.MNIST是NIST数据集的一个子集,它包含了60000张图片作为训练数据,10000张图片作为测试数据.在MNIST数据集中的

TensorFlow深入MNIST笔记[三]

TensorFlow深入MNIST笔记[三] TensorFlow是进行大规模数值计算的强大库.其优点之一是实施和训练深层神经网络. 加载MNIST数据 from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets('MNIST_data', one_hot=True) 这mnist是一个轻量级的类,它将训练,验证和测试集存储为NumPy数组.它还提供了一个迭代数据服务的功

Nodejs爬虫进阶教程之异步并发控制

Nodejs爬虫进阶教程之异步并发控制 之前写了个现在看来很不完美的小爬虫,很多地方没有处理好,比如说在知乎点开一个问题的时候,它的所有回答并不是全部加载好了的,当你拉到回答的尾部时,点击加载更多,回答才会再加载一部分,所以说如果直接发送一个问题的请求链接,取得的页面是不完整的.还有就是我们通过发送链接下载图片的时候,是一张一张来下的,如果图片数量太多的话,真的是下到你睡完觉它还在下,而且我们用nodejs写的爬虫,却竟然没有用到nodejs最牛逼的异步并发的特性,太浪费了啊. 思路 这次的的爬

Android高手进阶教程(二十八)之---Android ViewPager控件的使用(基于ViewPager的横向相册)!!!

分类: Android高手进阶 Android基础教程 2012-09-14 18:10 29759人阅读 评论(35) 收藏 举报 android相册layoutobjectclassloaderencoding 大家好,相信大家用的ListView控件一定很多的,是竖向滑动的,复用convertView,我们可以加载成千上万的数据,但有时候我们会有 这个需求比如相册,我们想横向滑动,并且数据有好多,这时候ViewPager控件就派上用场了,ViewPager使用时候我们需要导入第三方包 an

shell进阶教程

背景:就自己常用的shell脚本写作风格,总结了一些知识点.也是作为交接工作的一部分文档.部分内容单独写 #!/bin/sh # shell脚本进阶教程 # 1.常用知识点:变量设置/日期设置/格式化输出/定义函数/函数传参/脚步传参/变量的嵌套和迭代 # 2.常用环境:/数据库监控/本地日志监控/批量处理/定期获取表数据/备份 # 3.常用循环:for/while # 4.常用命令:sed/cut/awk/ # 5.crontab 计划任务 # 第一部分:常用知识点 # 1.[变量设置及变量替

Nmap扫描教程之网络基础服务DHCP服务类

Nmap扫描教程之网络基础服务DHCP服务类 Nmap网络基础服务 网络基础服务是网络正常工作的基石,常见的网络基础服务包括DHCP服务和DNS服务.其中,DHCP服务用来为计算机动态分配IP地址:DNS服务用来对主机名进行解析.本章将介绍网络基础服务的扫描方法. NmapDHCP服务类 DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是一种局域网的网络协议.它的主要作用就是,给内部网络或网络服务供应商自动分配IP地址.当一台客户机需要一个IP

Kail Linux渗透测试教程之网络扫描和嗅探工具Nmap

Kail Linux渗透测试教程之网络扫描和嗅探工具Nmap 网络扫描和嗅探工具--Nmap Nmap也就网络映射器(Network Mapper),是一个免费开放的网络扫描和嗅探工具.该工具可以扫描主机是否在线.所开放的端口号.提供的网络服务及操作系统类型等.本节将介绍Nmap工具的使用.在使用Nmap工具之前,首先需要了解它的几种扫描类型.Nmap主要的扫描类型如表4-1所示. 表4-1  Nmap扫描类型 [实例4-1]使用nmap工具扫描目标主机192.168.6.105的端口号.执行命