程序员人生 网站导航

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

栏目:框架设计时间:2016-06-15 15:03:31
  • 背景
  • 问题说明
  • 分析
    • LeNet5参数
    • MNIST程序参数
  • 遗留问题
  • 小结

背景

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


问题说明

# 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获得的数据个数而改变),第1层的卷积系数W_conv1是5*5*1*32大小的shape,后面不再赘述。
有些shape比如输入的xNone*784的784是28*28个8位灰度值的shape,y_是None*10个数字分类的shape,输出的b_fc2是对应10个输出种别的偏差。但是对其他参数的话,我就不由要问:①这么做是为何呢?②如果不这么设置参数行不行呢?③如果我换1套数据集,参数还是这么设置么?

先回答第2个问题吧,不行。如果随便更换参数,换的对可能能跑通,但是影响性能,但是更多的时候会是下面的结果:
这里写图片描述

这里写图片描述


分析

先看看LeNet5

在回答第1个问题之前,建议大家浏览1下2012年NIPS上做ImageNet的文献1和解释LaNet5的CNN模型的PPT,如果时间不够用的话,也能够直接看1下qiaofangjie的中文参数解析。里面可以先对LeNet5的参数有个大概了解。下面我对比着经典的图,大体归纳1下:
这里写图片描述
CNN里的层,除输入/输出层以外,把隐含层又分成了卷积层、池化层、和全连接层。3者的作用及介绍也能够参考小新的猫的理解。卷积层会对原始输入数据做加权求和的处理,学过通讯的同学1定对1维线性卷积里面“错位相乘相加”很熟习(谅解我强行奶1口原来的专业),这里就是线性卷积在2维里面的推行。池化层是为了减小模型复杂度,通过降采样(又是通讯里有概念我会乱讲?)缩小特点图的尺寸。全连接层是将终究高度抽象化的特点图与输出直接进行的全连接,这与入门级MNIST库的模型训练做的事儿是1样的,1个简单的全映照。
具体的看,第1个卷积层C1对32*32的输入数据(假定成1个矩阵,其中每一个元素代表当前位置像素点灰度值的强弱)进行2维加权相加
yl+1m,n=i,jwli,jxli,j+bl+1
其中上标l表示层数,下标i,j表示对应的像素点位置,wxb分别表示滤波器系数,输入值,偏差值。
(此处补零采取了VALID模式,这个问题后面会接着说)。我们假定卷积器的权重矩阵是1个5*5的,相当于对5*5的输入数据,与5*5的卷积器卷积后,得到了1*1的数值,如果是5*6的原始数据,卷积后会得到1*2的输出,所以推行到32*32的数据,输出会得到28*28的尺寸。再推行1下就会得到:

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

至于为何1幅输入会得到6组对应的卷积输出呢?这个问题对我来讲还说不清楚,只能暂时理解为1幅输入的6个不同的特点(Hinton老师讲家谱树的时候就用了6个对应特点表示是英国人还是意大利,是爷爷辈儿还是孙子辈儿等),比如输入是不是有圆圈形状,边沿是不是明确,是不是有纹理特点等。这个问题后面的话有可能会可视化验证1下,目前只能这么理解了。
接下来第2个池化层S2,对6*28*28的特点图进行下采样得到了6*14*14的池化层特点图,主要是由于其中采取每2*2的块儿进行1次容和统计,并且块与块儿直接不交叠,掐指1算还真是(28/2*28/2),池化操作并没有增加特点维度,只是单纯的下采样。
在后面的那层卷积层C3的shape也能够理解了,对14*14的特点输入,输出10*10的特点图,并且此时把6个特点抽象到了更多的16个特点。下面的S4就不说了。
看1下C5,这里又来了问题了,暂时还不能理解,为何会出现120这个数字,暂且搁置。1般CNN中的全连接层,我见到的都是1⑵层,并且两个层的shape是1致的。比如这里都是120,文献1中全是2048。

MNIST库程序

ok,以上就是对LeNet5的参数理解,下面我们来看1下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

这里写图片描述
像这样插在每次循环中,然后,我们可以试运行1下看看结果
这里写图片描述
这个小函数写的时候要注意,对Tensor类型的Variable,并没有shape这个属性,必须在每一个Tensor.eval()才可使用.shape属性,而Tensor.eval()的话,就需要feed_dict1个依赖值才可以取得tensor实例,否则Tensor对象只是1个代号而已。
在输入50个batch的时候可以看到输入,输出shape不说了,w_conv1触及到tf.conv2d的用法,具体可以参考官方文档,这里的前两个5对应的卷及系数shape,第3个参数1表示输入通道,对应输入数据只有灰度值1个特点,或说是对应LeNet分析中1幅图,最后1个32是输出通道数,表示输出32组特点。

稍等,对MNIST的28*28的输入数据,经过第1层卷积C1,为什么输出的h_conv1还是28*28的维度,而不是刚刚推论中的24*24呢?这我也研究了好久,缘由在于tf.conv2d函数的参数指定了padding类型为“SAME”,这个参数的作用在官方文档中直接决定了输出的shape,截选以下:
这里写图片描述
这只是数值上说明了,具体的缘由小新的猫的理解说的非常明白,他还对照了Matlab和Tensorflow对Padding参数的不同选项,可以视具体的利用环境在做测试。

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


遗留问题

现在看来,与卷积,池化相干的shape变化应当是可以理解了,不过还有两种参数不理解:
①是卷积层的特点数,为何LeNet里面两层卷积层的特点会是6和16,而MNIST是32和64,按道理MNIST是28*28的数据输入,还小于LeNet的32*32呢,却有着更高的特点维度。
②全连接层的参数,LeNet里面用的2个120参数的,而MNIST里面全连接却有1024个参数。
这些问题也问过Tensorflow群里群友,问到的都说是经验问题,需要自己掌控,真的是这样哇?这两个参数的选择难道只是个工程经验问题嘛?我目前的理解的话只能1边继续看资料,1边工程做实验测试1下模型收敛速度啦。结合TensorBoard可视化应当后者会给我个答案。


小结

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

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


------分隔线----------------------------
------分隔线----------------------------

最新技术推荐