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

来源:互联网 发布:数控编程 过程 编辑:程序博客网 时间:2024/06/08 16:03
  • 背景
  • 问题说明
  • 分析
    • LeNet5参数
    • MNIST程序参数
  • 遗留问题
  • 小结

背景

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


问题说明

# Input Layerx = tf.placeholder('float',[None,784])y_ = tf.placeholder('float',[None,10])# First LayerW_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 LayerW_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 LayerW_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 Layerkeep_prob = tf.placeholder("float")h_fc1_drop = tf.nn.dropout(h_fc1,keep_prob)# Output LayerW_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表示对应的像素点位置,wxb分别表示滤波器系数,输入值,偏差值。
(此处补零采用了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


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 word页眉和正文有段距离怎么办 酷派大神x7卡在开机界面怎么办 妹妹结婚跟领导请假不批怎么办 网页分享后不想显示连接网址怎么办 魔兽世界肉弹随从的任务断了怎么办 在美发店上班被小混混打了怎么办 孩子上四年级了不知道写作业怎么办 运动完第二天恶心想吐怎么办 吃减肥药皮肤变差了怎么办 生了孩子子宫没恢复好怎么办 cf我是新手不知道怎么报点怎么办 挑战人物已经有了又刷到怎么办 小米手机账户验证码隐藏了怎么办? 苹果手机绑定的邮箱密码忘了怎么办 魔域手机版不要的装备怎么办 魔域永恒多了的幻兽装备怎么办 魔域怀旧版70星以后宝宝怎么办 天猫确认收货后不寄发票怎么办 足球运动员踢了一半想撒尿了怎么办 17个月宝宝0型腿怎么办 走的路多了腿疼怎么办 如果新兵老被老兵打怎么办求求个位 做古董拍卖诈骗被警察抓了怎么办 武汉个人社保怎么办停转到海南来 狗狗车祸前脚软组织受伤了怎么办 6岁儿童夜里盗汗惊战发抖怎么办 一个月大的小狗尾巴掉毛怎么办 口袋妖怪用修改器被ban了怎么办 联币金融提现不到账怎么办 秘密花园的手抄报怎么办?五年级 雷蛇鼠标不亮了怎么办呢 汽车后备箱的垫子好臭怎么办 摩托尾箱内衬味道太重怎么办 买房子把办贷款的单据丢了怎么办 鞋盒破损卖家拒绝退货怎么办 顺丰快递退回发件人签收失败怎么办 客户收到衣服后说衣服破损怎么办 纱窗被老鼠咬了个洞怎么办 双色印刷机开槽刀调不动怎么办 水管软管两端的螺帽下包生锈怎么办 棉花被子盖时间长了被芯变小怎么办