黑猿大叔,自由职业(专注于深度学习在机器视觉领域内的应用),前台企威盛电子资深软件工程师。

当看到本篇时,根据TensorFlow官方标准《Deep MNIST for Experts》(https://tensorflow.google.cn/get_started/mnist/pros),你已经达到Expert Level,要恭喜了。

且不说是否夸大其词,换一种角度,假如能乘坐时光机仅往回飞5年,借此CNN实现,你也能在ImageNet上叱咤风云,战无不胜。就算飞不回去,它在今天依然是大杀伤力武器,大批大批老算法等着你去枪毙,大片大片垂直领域换代产品等着你去落地。这还不够么?

上一篇TensorFlow从1到2 | 第四章: 拆解CNN架构 准备好了CNN的理论基础,本篇从代码层面,来看看TensorFlow如何搞定CNN,使识别精度达到99%以上。

TensorFlow

分析代码的方式

再次说明下分析代码的方式。

与逐行分析代码不同,我偏好先清理代码涉及到的语言、工具的知识点,然后再去扫描逻辑。所以“Python必知必会”、“TensorFlow必知必会”将是首先出现的章节。

当然你也可以直接跳到代码部分:

tf_2-5_cnn.py:CNN识别MNIST数字,基于官方文档《Deep MNIST for Experts》(https://www.tensorflow.org/get_started/mnist/pros),略有微调;

tf_25_cnn_fashion_mnist.py(https://github.com/EthanYuan/TensorFlow/blob/master/TF1_3/tf_2-5_cnn_fashion_mnist.py):

CNN识别Fashion-MNIST(http://www.jianshu.com/p/2ed1707c610d);

代码运行环境:

1、Python 3.6.2;

2、TensorFlow 1.3.0 CPU version;

python必知必会
with

在本篇所分析的代码中,用到了大量的With,值得一说。

With要搭配上下文管理器(Context Manager)对象使用。

所谓的上下文管理器对象,就是实现了上下文管理器协议(Context Manager Protocol)的对象。协议要求对象定义中必须实现__enter__()和__exit__()方法。

当看到下面语句时:

With Context Manager Object [as target]:    Body

它有4个意思:

1、With块会在Body开始前自动调用Context Manager Object的__enter__()方法;

2、With块会在Body结束前自动调用Context Manager Object的__exit__()方法,即使Body还未执行完时发生了异常,__exit__()也总会被调用;

3、Body中出现异常时,Context Manager Object的__exit__()执行如果返回False,异常继续向上层抛出,如果返回True则该异常被忽略;

4、可选的as target并非是Context Manager Object本身,而是其调用__enter__()的返回值;

总的来说,With语句帮助上下文管理器对象实现了两个自动化的操作enter和exit,并充分考虑了异常情况。对于资源类对象(用完需要尽快释放)的使用,比如文件句柄、数据库连接等等,这无疑是一种简洁而完善的代码形式。

如果还想了解更多的细节,推荐阅读一篇老文章《浅谈Python的with语句》(https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/)。

TensorFlow必知必会

上面说的with,主要是为了配合TensorFlow的tf.name_scope。

tf.name_scope

先来体会下我设计的“玩具”(https://github.com/EthanYuan/TensorFlow/blob/master/TF1_3/tf_2-5_tf_name_scope.py)代码:

import tensorflow as tf with tf.name_scope('V1'):    a1 = tf.Variable([50])    a2 = tf.Variable([100], name='a1') assert a1.name == 'V1/Variable:0' assert a2.name == 'V1/a1:0' with tf.name_scope("V2"):    a1 = tf.add(a1, a2, name="Add_Variable_a1")    a2 = tf.multiply(a1, a2, name="Add_Variable_a1") with tf.Session() as sess:    sess.run(tf.global_variables_initializer())    assert a1.name == 'V2/Add_Variable_a1:0'    assert sess.run(a1) == 150    assert a2.name == 'V2/Add_Variable_a1_1:0'    assert sess.run(a2) == 15000 a2 = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='V1/a1:0')[0] assert a2.name == 'V1/a1:0'

可以看到,其中有两类与With的搭配。

一种是资源类的tf.Session,手工使用时总要记得在使用后调用tf.Session.close方法释放,而与With搭配使用,则会自动调用其__exit__()进行释放。

另一种是本节的重点,与With搭配的并不是“资源”,而是tf.name_scope()方法返回的对象,此时在With块中定义的节点,都会自动在属性name上添加name scope前缀:

  • 通过tf.Variable定义的变量节点,其属性name都添加了前缀;

  • 通过tf.add和tf.multiply定义的运算节点,其属性name也添加了前缀;

注意:通过tf.get_variable定义的节点,其属性name不受影响,tf.get_variable需要与tf.variable_scope搭配产生类似效果。

TensorFlow的name scope有什么作用呢?主要是两点:

1、起到名字空间的作用,name scope还可以嵌套,方便管理大规模计算图节点;

2、可视化优化控制,能够生成层次化的计算图,节点可以按照name scope进行折叠,见下图;

节点折叠

如果对上述介绍仍有疑问,请仔细读读下面我为此准备的:

  • tf.Variable()返回的a1、a2、a3等等Python变量,是对节点的引用,与节点的name属性没有半毛钱关系;

  • Node的name属性是计算图中节点的标识,Python层面的节点引用变量则不是,后者可以随时更改为对其他节点的引用;

  • 如果在Python层面失去了对某一节点的引用,节点并没有消失,也不会被自动回收,找回方法见玩具代码倒数第2行;

  • 有关TensorFlow计算图(Graph)基本构建单元Node的概念,请回顾《TensorFlow从0到1 - 2 - TensorFlow核心编程》。

CNN架构

扫清了障碍,终于可以开始构建CNN了。

TensorFlow官方《Deep MNIST for Experts》(https://tensorflow.google.cn/get_started/mnist/pros)中构建的CNN与LeNet-5的深度规模相当,具有5个隐藏层,但是卷积层滤波器的数量可多了不少:

  • 输入层placeholder;

  • reshape;

  • 隐藏层1:conv1卷积层,32个滤波器;

  • 隐藏层2:pool1池化层;

  • 隐藏层3:conv2卷积层,64个滤波器;

  • 隐藏层4:pool2池化层;

  • 隐藏层5:fc1全连接层;

  • dropout;

  • fc2输出层;

计算下网络中权重的数量:

5x5x1x32 + 5x5x32x64 + 7x7x64x1024 + 1024x10 = 800 + 51200 + 3211264 + 10240 = 3273504

这个并不算深的CNN有三百多万个参数,比之前识别MNIST所用的浅层神经网络,多了两个数量级。不过再仔细看下,两个卷积层包含的权重数量所占比例极小,导致参数量激增的是全连接网络层fc1。

下图是构建好的计算图(Computational Graph),得益于name scope的使用,它能够以“层”为粒度,清晰的显示出网络的骨架:

CNN

Tensors和Filters

示例代码中,有了更多工程化的考虑,对CNN的构建进行了封装,形成了函数deepnn,在函数内部,With代码块的使用,使得网络的前馈路径也相当清楚,这部分就不再赘述了。

本节的重点是我们构建的计算图节点上流动的Tensors,以及参与运算的Filters:

  • Tensor:4阶,shape形式为:[batch, width, height, channels];

  • Filter:4阶,shape形式为:[width, height, channels,F-amount];

deepnn函数定义如下(省略处用……代替):

def deepnn(x):    
with tf.name_scope('reshape'):        
x_image = tf.reshape(x, [-1, 28, 28, 1])    
with tf.name_scope('conv1'):        
W_conv1 = weight_variable([5, 5, 1, 32])      
 ……    
with tf.name_scope('pool1'):      
h_pool1 = max_pool_2x2(h_conv1)    
with tf.name_scope('conv2'):        
W_conv2 = weight_variable([5, 5, 32, 64])        
……    
with tf.name_scope('pool2'):        
h_pool2 = max_pool_2x2(h_conv2)    
with tf.name_scope('fc1'):        
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)    
with tf.name_scope('dropout'):      
 ……    
with tf.name_scope('fc2'):        
W_fc2 = weight_variable([1024, 10])      
 b_fc2 = bias_variable([10])        
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
 return y_conv, keep_prob

Tensors-[batch, width, height, channels]:

1、x_image = tf.reshape(x, [-1, 28, 28, 1]):要将数据输入进二维的卷积网络,首先要进行一次reshape,把[batch, 784]的数据变成[-1, 28, 28, 1],其中batch位填入“-1”可以自适应输入,width和height位为输入图像的原始宽高,最后一位是原始图像的通道数1(灰度图为单通道);

2、h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64]):在将卷积网络的输出数据,输入全连接层时,需要再把数据拉平回一个2阶Tensor;

Filters-[width, height, channels,F-amount]:

1、W_conv1 = weight_variable([5, 5, 1, 32]):第一卷积层滤波器,width和height位为卷积核的宽高,channels位代表滤波器通道数(匹配输入),最后一位F-amount位代表滤波器的数量为32个(官方文档从输出数据的角度定义其为output channels也颇为合理);

2、W_conv2 = weight_variable([5, 5, 32, 64]):第二卷积层滤波器,仍采用5x5卷积核,具有32个channels,滤波器数量64个;

跨距Strides

为防止代码重复,卷积和池化这两项操作也进行了封装,前面缺失的滤波器的跨距(strides)定义,包含在这里。

conv2d定义:

def conv2d(x, W):    
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

strides=[1, 1, 1, 1]:跨距(strides)默认情况下第一个参数batch与最后一个参数channels都是1,第二位width和第三位height这里也设为1;

max_pool_2x2定义:

def max_pool_2x2(x):    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],                          strides=[1, 2, 2, 1], padding='SAME')

ksize=[1, 2, 2, 1]:池化滤波器采用了固定尺寸,池化操作是逐channel进行的,所以默认情况下第一个参数batch与最后一个参数channels都是1,第二位width和第三位height这里设为2,视野范围如一个“田”字;

strides=[1, 2, 2, 1]:跨距(strides)默认情况下第一个参数batch与最后一个参数channels都是1,第二位width和第三位height这里设为2,表示从左到右、从上到下以“田”字进行搜索;

滤波器还有一个padding参数,官方文档给出的计算方法(https://tensorflow.google.cn/api_docs/python/tf/nn/convolution)见下:

padding == 'SAME':output_spatial_shape = ceil(input_spatial_shape / strides);

padding == 'VALID':output_spatial_shape = ceil((input_spatial_shape - (spatial_filter_shape-1)) / strides);

测试结果

运行代码进行实测,与TensorFlow官方基本一致:

  • MNIST识别达到99.3%,明显超越了浅层神经网络;

  • 60次迭代CPU运行时间:4 hours,接近无法忍受,更深的网络必须上GPU了;

相同架构下,基于Fashion MNIST(http://www.jianshu.com/p/2ed1707c610d)数据集对网络重新进行了训练,验证集识别精度达到了92.64%。CNN的全能型,由此可见一斑。

Fashion MNIST训练过程输出

附完整代码

import argparse
 import sys
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
FLAGS = None
def deepnn(x):    
"""deepnn builds the graph for a deep net for classifying digits.    
Args:      
x: an input tensor with the dimensions (N_examples, 784), where 784 is      
the number of pixels in a standard MNIST image.    
Returns:      
A tuple (y, keep_prob). y is a tensor of shape (N_examples, 10), with      
values equal to the logits of classifying the digit into one of 10      
classes (the digits 0-9). keep_prob is a scalar placeholder for the      
probability of dropout.  
 """    
# Reshape to use within a convolutional neural net.    
# Last dimension is for "features" - there is only one here, since images    
# are grayscale -- it would be 3 for an RGB image, 4 for RGBA, etc.  
 with tf.name_scope('reshape'):        
x_image = tf.reshape(x, [-1, 28, 28, 1])    
# First convolutional layer - maps one grayscale image to 32 feature maps.  
 with tf.name_scope('conv1'):        
W_conv1 = weight_variable([5, 5, 1, 32])        
b_conv1 = bias_variable([32])        
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)    
# Pooling layer - downsamples by 2X.    
with tf.name_scope('pool1'):      
 h_pool1 = max_pool_2x2(h_conv1)    
# Second convolutional layer -- maps 32 feature maps to 64.    
with tf.name_scope('conv2'):        
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)    
# Second pooling layer.    
with tf.name_scope('pool2'):        
h_pool2 = max_pool_2x2(h_conv2)    
# Fully connected layer 1 -- after 2 round of downsampling, our 28x28 image    
# is down to 7x7x64 feature maps -- maps this to 1024 features.    
with tf.name_scope('fc1'):        
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 - controls the complexity of the model, prevents co-adaptation of    
# features.    
with tf.name_scope('dropout'):        
keep_prob = tf.placeholder(tf.float32)        
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)    
# Map the 1024 features to 10 classes, one for each digit    
with tf.name_scope('fc2'):        
W_fc2 = weight_variable([1024, 10])        
b_fc2 = bias_variable([10])        
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2    
return y_conv, keep_prob def conv2d(x, W):    
"""conv2d returns a 2d convolution layer with full stride."""    
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') def max_pool_2x2(x):    """max_pool_2x2 downsamples a feature map by 2X."""    
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],                          
strides=[1, 2, 2, 1], padding='SAME') def weight_variable(shape):  
 """weight_variable generates a weight variable of a given shape."""    
initial = tf.truncated_normal(shape, stddev=0.1)    
return tf.Variable(initial)
def bias_variable(shape):    
"""bias_variable generates a bias variable of a given shape."""    
initial = tf.constant(0.1, shape=shape)    
return tf.Variable(initial) def main(_):    
# Import data    
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True,                                    validation_size=10000)    
# Create the model    
x = tf.placeholder(tf.float32, [None, 784])  
 # Define loss and optimizer    
y_ = tf.placeholder(tf.float32, [None, 10])    
# Build the graph for the deep net    
y_conv, keep_prob = deepnn(x)    
with tf.name_scope('loss'):      
 cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_,                                                                logits=y_conv)        
cross_entropy = tf.reduce_mean(cross_entropy)    
with tf.name_scope('adam_optimizer'):      
 train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)    
with tf.name_scope('accuracy'):        
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))        
correct_prediction = tf.cast(correct_prediction, tf.float32)        
accuracy = tf.reduce_mean(correct_prediction)    
graph_location = 'MNIST/logs/tf2-4/train'    
print('Saving graph to: %s' % graph_location)    
train_writer = tf.summary.FileWriter(graph_location)    train_writer.add_graph(tf.get_default_graph())    
best = 0    
with tf.Session() as sess:        
sess.run(tf.global_variables_initializer())        
for epoch in range(60):            
for _ in range(1000):              
 batch = mnist.train.next_batch(50)                
train_step.run(                    
feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})            
accuracy_validation = accuracy.eval(                
feed_dict={                    
x: mnist.validation.images,                    
y_: mnist.validation.labels,                    
keep_prob: 1.0})            
print('epoch %d, validation accuracy %s' % (                
epoch, accuracy_validation))            
best = (best, accuracy_validation)[                
best <= accuracy_validation]  
 # Test trained model

print("best: %s" % best) if __name__ == '__main__':    parser = argparse.ArgumentParser()    parser.add_argument('--data_dir', type=str, default='../MNIST/',                      
 help='Directory for storing input data')    
FLAGS, unparsed = parser.parse_known_args()    
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

下载tf_25_cnn.py(https://github.com/EthanYuan/TensorFlow/blob/master/TF1_3/tf_2-5_cnn.py)

原文链接:http://www.jianshu.com/p/79269521dc78

   BY  简书

往期精彩回顾

深度学习视频(一) | 免费放送—深度学习的应用场景和数学基础

深度学习视频(二) | 免费放送—卷积神经网络(一)

深度学习视频(三) | 免费放送—卷积神经网络(二)

深度学习视频(四) | 免费放送—深度学习的具体模型和方法

深度学习视频(五) | 免费放送—上机实操(一)

点击“阅读原文”直接打开【北京站 | GPU CUDA 进阶课程】报名链接

TensorFlow从1到2 | 第五章 非专家莫入!TensorFlow实现CNN相关推荐

  1. 鸟哥Linux私房菜(基础篇)——第五章:首次登入与在线求助 man page笔记

    1.X Winsows与文本模式的切换 ●[Ctrl] + [Alt] + [F1] ~ [F6] :文字接口登入 tty1 ~ tty6 终端机.        ●[Ctrl] + [Alt] + ...

  2. 第五章:Tensorflow 2.0 利用十三层卷积神经网络实现cifar 100训练(理论+实战)

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/LQ_qing/article/deta ...

  3. Tensorflow 2.x(keras)源码详解之第十五章:迁移学习与微调

      大家好,我是爱编程的喵喵.双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中.从事机器学习以及相关的前后端开发工作.曾在阿里云.科大讯飞.CCF等比赛获得多次Top名次.现 ...

  4. 译文 | 与TensorFlow的第一次接触 第五章:多层神经网络

    北京 深度学习与人工智能研修12月23-24日 再设经典课程 重温深度学习阅读全文> 正文共5270个字,15张图,预计阅读时间14分钟. 本章中,我们继续使用之前章节中的MNIST数字识别问题 ...

  5. 第五章 TensorFlow工具库(下)

    01.TensorFlow与自定义预估器 1.1 预估器 预估器也是一种高级API,其优点为: 不必编写大量样板文件代码 灵活,模型允许替换默认行为 可以通过两种可能的方式构建模型: 预制预估器:预先 ...

  6. 机器学习原来这么有趣!第五章:Google 翻译背后的黑科技:神经网络和序列到序列学习

    第一章:全世界最简单的机器学习入门指南 https://blog.csdn.net/wskzgz/article/details/89917343 第二章:用机器学习制作超级马里奥的关卡 https: ...

  7. 王道考研 计算机网络笔记 第五章:传输层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

  8. 数字图像处理——第五章 图像复原与重建

    数字图像处理--第五章 图像复原与重建 文章目录 数字图像处理--第五章 图像复原与重建 写在前面 1 图像退化/复原过程的模型 2 噪声模型 2.1 高斯噪声 2.2 椒盐噪声 3 仅有噪声的复原- ...

  9. c语言逐步搜索法求有根区间,[C语言第五章.ppt

    [C语言第五章 算法举例 基本思想是,根据提出的问题,列举所有可能的情况,并 用问题中给定的条件检验哪些是需要的,哪些是不需要的. 教学进程 列举与试探 列举法常用于解决"是否存在" ...

最新文章

  1. 从传感器到算法原理,机器人、视觉避障尽在此文
  2. 异地备份同步校验脚本
  3. sizzle分析记录:getAttribute和getAttributeNode
  4. Python入门教程以及资料免费下载
  5. python不能创建字典的是_用Python创建带有重复键的字典
  6. 安装加密中间件 crypto - 命令篇
  7. django-模型类管理器-create方法-models属性
  8. 为什么不能把CSS放到html中,为什么我的CSS代码不能在我的HTML文件中工作?
  9. ora-28500 ora-02063 mysql_oracle dblink mysql 报错ORA-28500
  10. c语言程序计算4阶行列式的值,如何用降阶法求解四阶行列式的计算,请帮我编一个C语言程序。...
  11. sudo echo x **.** 时 base: : Permission denied
  12. flutter报错[!] Android toolchain - develop for Android devices (Android SDK version 29.0.3) X Andr
  13. 导致Android手机崩溃的壁纸,一张壁纸导致安卓手机崩溃作者首发声:绝非故意...
  14. C语言程序设计学习笔记:P3-判断
  15. 解决使用vscode写typescript变量报错无法重新声明块范围变量
  16. 盘古开源:技术为基创新驱动数据存储体系完善
  17. 快要2022年了,拼多多还在做这件事
  18. 计算机总是提示网络电缆没有插,网络电缆没有插好原因与解决方法【图文教程】...
  19. ssis-状态为在执行中,组件的颜色一直为黄色
  20. java微信小程序、APP 西门子PLC通信,java S7-S200、java S7-300、java S7-400,java与PROFINET协议通信 Java与西门子PLC通信

热门文章

  1. mysql 指定tcpip连接数_tcp ip连接数据库
  2. mysql 时间段在不在另外的时间段中_你知道自来水一天中哪个时间段最脏、最具毒性吗?记住这几点避开致命自来水...
  3. 数据库编程连接mysql_使用JDBC编程-连接MySQL数据库
  4. Jupyter Notebook命令行启动报错: DLL load failed
  5. GNU 关闭 MMU 和 Icache 和 Dcache
  6. iis如何处理并发请求
  7. 【转】 C++中的new VS C语言中的malloc
  8. 基于Redis实现简单的分布式锁
  9. 有限状态机与应用一例
  10. 【经典回顾】静态结构不能满足模型部署性能需求?微软提出动态卷积结构,Top-1准确率提高2.9%!(附复现代码)...