tensorflow relu中relu函数能找到在哪么

966,690 八月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
深入浅出TensorFlow(二):TensorFlow解决MNIST问题入门
深入浅出TensorFlow(二):TensorFlow解决MNIST问题入门
12&他的粉丝
日. 估计阅读时间:
智能化运维、Serverless、DevOps......2017年有哪些最新运维技术趋势?!
相关厂商内容
cargo.caicloud.io/tensorflow/tensorflow:0.12.0 cargo.caicloud.io/tensorflow/tensorflow:0.12.0-gpu cargo.caicloud.io/tensorflow/tensorflow:0.12.1 cargo.caicloud.io/tensorflow/tensorflow:0.12.1-gpu cargo.caicloud.io/tensorflow/tensorflow:1.0.0
cargo.caicloud.io/tensorflow/tensorflow:1.0.0-gpu
当Docker安装完成之后(Docker安装可以参考 /engine/installation/),可以通过以下命令来启动一个TensorFlow容器。在第一次运行的时候,Docker会自动下载镜像。
$ docker run -p
cargo.caicloud.io/tensorflow/tensorflow:1.0.0
在这个命令中,-p
将容器内运行的Jupyter服务映射到本地机器,这样在浏览器中打开localhost:8888就能看到Jupyter界面。在此镜像中运行的Jupyter是一个网页版的代码编辑器,它支持创建、上传、修改和运行Python程序。
-p 将容器内运行的TensorFlow可视化工具TensorBoard映射到本地机器,通过在浏览器中打开localhost:6006就可以将TensorFlow在训练时的状态、图片数据以及神经网络结构等信息全部展示出来。此镜像会将所有输出到/log目录底下的日志全部可视化。
-it将提供一个Ubuntu 14.04的bash环境,在此环境中已经将TensorFlow和一些常用的机器学习相关的工具包(比如Scikit)安装完毕。注意这里无论本地机器操作系统是什么,这个bash环境都是基于Ubuntu 14.04的。这是由编译Docker镜像的方式决定的,和本地的操作系统没有关系。
虽然有支持GPU的Docker镜像,但是要运行这些镜像需要安装最新的NVidia驱动以及nvidia-docker。在安装完成nvidia-docker之后,可以通过以下的命令运行支持GPU的TensorFlow镜像。在镜像启动之后可以通过和上面类似的方式使用TensorFlow。
$ nvidia-docker run -it -p
cargo.caicloud.io/tensorflow/tensorflow:1.0.0-gpu
除了Docker安装,在本地使用最方便的TensorFlow安装方式是pip。通过以下命令可以在Linux环境下使用pip安装TensorFlow 1.0.0。
$ sudo apt-get install python-pip python-dev
# 安装pip和Python 2.7
$ sudo pip install tensorflow
# 安装只支持CPU的TensorFlow
$ sudo pip install tensorflow-gpu
# 安装支持GPU的TensorFlow
目前只有在安装了CUDA toolkit 8.0和CuDNN v5.1的64位Ubuntu下可以通过pip安装支持GPU的TensorFlow,对于其他系统或者其他CUDA/CuDNN版本的用户则需要从源码进行安装来支持GPU使用。从源码安装TensorFlow可以参考https://www.tensorflow.org/install/。
TensorFlow样例
TensorFlow对Python语言的支持是最全面的,所以本文中将使用Python来编写TensorFlow程序。下面的程序给出一个简单的TensorFlow样例程序来实现两个向量求和。
import tensorflow as tf
a = tf.constant([1.0, 2.0], name=&a&)
b = tf.constant([2.0, 3.0], name=&b&)
result = a + b
print result
# 输出&Tensor(&add:0&, shape=(2,), dtype=float32) &
sess = tf.Session()
print sess.run(result)
# 输出&[ 3.
sess.close()
TensorFlow基本概念
TensorFlow的名字中已经说明了它最重要的两个概念&&Tensor和Flow。Tensor就是张量。在TensorFlow中,所有的数据都通过张量的形式来表示。从功能的角度上看,张量可以被简单理解为多维数组。但张量在TensorFlow中的实现并不是直接采用数组的形式,它只是对TensorFlow中运算结果的引用。在张量中并没有真正保存数字,它保存的是如何得到这些数字的计算过程。在上面给出的测试样例程序中,第一个print输出的只是一个引用而不是计算结果。
一个张量中主要保存了三个属性:名字(name)、维度(shape)和类型(type)。张量的第一个属性名字不仅是一个张量的唯一标识符,它同样也给出了这个张量是如何计算出来的。张量的命名是通过&node:src_output&的形式来给出。其中node为计算节点的名称,src_output表示当前张量来自节点的第几个输出。
比如张量&add:0&就说明了result这个张量是计算节点&add&输出的第一个结果(编号从0开始)。张量的第二个属性是张量的维度(shape)。这个属性描述了一个张量的维度信息。比如&shape=(2,) &说明了张量result是一个一维数组,这个数组的长度为2。张量的第三个属性是类型(type),每一个张量会有一个唯一的类型。TensorFlow会对参与运算的所有张量进行类型的检查,当发现类型不匹配时会报错。
如果说TensorFlow的第一个词Tensor表明了它的数据结构,那么Flow则体现了它的计算模型。Flow翻译成中文就是&流&,它直观地表达了张量之间通过计算相互转化的过程。
TensorFlow是一个通过计算图的形式来表述计算的编程系统。TensorFlow中的每一个计算都是计算图上的一个节点,而节点之间的边描述了计算之间的依赖关系。图1展示了通过TensorBoard画出来的测试样例的计算图。
图1 通过TensorBoard可视化测试样例的计算图
图1中的每一个节点都是一个运算,而每一条边代表了计算之间的依赖关系。如果一个运算的输入依赖于另一个运算的输出,那么这两个运算有依赖关系。在图1中,a和b这两个常量不依赖任何其他计算。而add计算则依赖读取两个常量的取值。于是在图1中可以看到有一条从a到add的边和一条从b到add的边。在图1中,没有任何计算依赖add的结果,于是代表加法的节点add没有任何指向其他节点的边。所有TensorFlow的程序都可以通过类似图1所示的计算图的形式来表示,这就是TensorFlow的基本计算模型。
TensorFlow计算图定义完成后,我们需要通过会话(Session)来执行定义好的运算。会话拥有并管理TensorFlow程序运行时的所有资源。当所有计算完成之后需要关闭会话来帮助系统回收资源,否则就可能出现资源泄漏的问题。TensorFlow可以通过Python的上下文管理器来使用会话。以下代码展示了如何使用这种模式。
# 创建一个会话,并通过Python中的上下文管理器来管理这个会话。
with tf.Session() as sess
# 使用这创建好的会话来计算关心的结果。
sess.run(...)
# 不需要再调用&Session.close()&函数来关闭会话,
# 当上下文退出时会话关闭和资源释放也自动完成了。
通过Python上下文管理器的机制,只要将所有的计算放在&with&的内部就可以。当上下文管理器退出时候会自动释放所有资源。这样既解决了因为异常退出时资源释放的问题,同时也解决了忘记调用Session.close函数而产生的资源泄。
TensorFlow实现前向传播
为了介绍神经网络的前向传播算法,需要先了解神经元的结构。神经元是构成一个神经网络的最小单元,图2显示了一个神经元的结构。
图2& 神经元结构示意图
从图2可以看出,一个神经元有多个输入和一个输出。每个神经元的输入既可以是其他神经元的输出,也可以是整个神经网络的输入。所谓神经网络的结构就是指的不同神经元之间的连接结构。如图2所示,神经元结构的输出是所有输入的加权和加上偏置项再经过一个激活函数。图3给出了一个简单的三层全连接神经网络。之所以称之为全连接神经网络是因为相邻两层之间任意两个节点之间都有连接。这也是为了将这样的网络结构和后面文章中将要介绍的卷积层、LSTM结构区分。图3中除了输入层之外的所有节点都代表了一个神经元的结构。本小节将通过这个样例来解释前向传播的整个过程。
图3& 三层全连接神经网络结构图
计算神经网络的前向传播结果需要三部分信息。第一个部分是神经网络的输入,这个输入就是从实体中提取的特征向量。第二个部分为神经网络的连接结构。神经网络是由神经元构成的,神经网络的结构给出不同神经元之间输入输出的连接关系。神经网络中的神经元也可以称之为节点。在图3中,a11节点有两个输入,他们分别是x1和x2的输出。而a11的输出则是节点Y的输入。最后一个部分是每个神经元中的参数。图3用w来表示神经元中的权重,b表示偏置项。W的上标表明了神经网络的层数,比如W(1)表示第一层节点的参数,而W(2)表示第二层节点的参数。W的下标表明了连接节点编号,比如W1,2(1)表示连接x1和a12节点的边上的权重。给定神经网络的输入、神经网络的结构以及边上权重,就可以通过前向传播算法来计算出神经网络的输出。下面公式给出了在ReLU激活函数下图3神经网络前向传播的过程。
a11=f(W1,1(1)x1+W2,1(1)x2+b1(1))=f(0.7&0.2+0.9&0.3+(-0.5))=f(-0.09)=0
a12=f(W1,2(1)x1+W2,2(1)x2+b2(1))=f(0.7&0.1+0.9&(-0.5)+0.1)=f(-0.28)=0
a13=f(W1,3(1)x1+W2,3(1)x2+b3(1))=f(0.7&0.4+0.9&0.2+(-0.1))=f(0.36)=0.36
Y=f(W1,1(2)a11+W1,2(2)a12+W1,3(2)a13+b1(2))=f(0.054+0.028+(-0.072)+0.1)=f(0.11)=0.11
在TensorFlow中可以通过矩阵乘法的方法实现神经网络的前向传播过程。
a = tf.nn.relu(tf.matmul(x, w1)+b1)
y = tf.nn.relu(tf.matmul(a, w2)+b2)
在上面的代码中并没有定义w1、w2、b1、b2,TensorFlow可以通过变量(tf.Variable)来保存和更新神经网络中的参数。比如通过下面语句可以定义w1:
weights = tf.Variable(tf.random_normal([2, 3], stddev=2))
这段代码调用了TensorFlow变量的声明函数tf.Variable。在变量声明函数中给出了初始化这个变量的方法。TensorFlow中变量的初始值可以设置成随机数、常数或者是通过其他变量的初始值计算得到。在上面的样例中,tf.random_normal([2, 3], stddev=2)会产生一个2&3的矩阵,矩阵中的元素是均值为0,标准差为2的随机数。tf.random_normal函数可以通过参数mean来指定平均值,在没有指定时默认为0。通过满足正太分布的随机数来初始化神经网络中的参数是一个非常常用的方法。下面的样例介绍了如何通过变量实现神经网络的参数并实现前向传播的过程。
import tensorflow as tf
# 声明变量。
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
b1 = tf.Variable(tf.constant(0.0, shape=[3]))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))
b2 = tf.Variable(tf.constant(0.0, shape=[1]))
# 暂时将输入的特征向量定义为一个常量。注意这里x是一个1*2的矩阵。
x = tf.constant([[0.7, 0.9]])
# 实现神经网络的前向传播过程,并计算神经网络的输出。
a = tf.nn.relu(tf.matmul(x, w1)+b1)
y = tf.nn.relu(tf.matmul(a, w2)+b2)
sess = tf.Session()
# 运行变量初始化过程。
init_op = tf.global_variables_initializer()
sess.run(init_op)
# 输出[[3.]]
print(sess.run(y))
sess.close()
TensorFlow实现反向传播
在前向传播的样例程序中,所有变量的取值都是随机的。在使用神经网络解决实际的分类或者回归问题时需要更好地设置参数取值。使用监督学习的方式设置神经网络参数需要有一个标注好的训练数据集。以判断零件是否合格为例,这个标注好的训练数据集就是收集的一批合格零件和一批不合格零件。监督学习最重要的思想就是,在已知答案的标注数据集上,模型给出的预测结果要尽量接近真实的答案。通过调整神经网络中的参数对训练数据进行拟合,可以使得模型对未知的样本提供预测的能力。
在神经网络优化算法中,最常用的方法是反向传播算法(backpropagation)。图4展示了使用反向传播算法训练神经网络的流程图。本文将不过多讲解反向传播的数学公式,而是重点介绍如何通过TensorFlow实现反向传播的过程。
图4& 使用反向传播优化神经网络的流程图
从图4中可以看出,通过反向传播算法优化神经网络是一个迭代的过程。在每次迭代的开始,首先需要选取一小部分训练数据,这一小部分数据叫做一个batch。然后,这个batch的样例会通过前向传播算法得到神经网络模型的预测结果。因为训练数据都是有正确答案标注的,所以可以计算出当前神经网络模型的预测答案与正确答案之间的差距。最后,基于这预测值和真实值之间的差距,反向传播算法会相应更新神经网络参数的取值,使得在这个batch上神经网络模型的预测结果和真实答案更加接近。通过TensorFlow实现反向传播算法的第一步是使用TensorFlow表达一个batch的数据。在上面的样例中使用了常量来表达过一个样例:
x = tf.constant([[0.7, 0.9]])&&
但如果每轮迭代中选取的数据都要通过常量来表示,那么TensorFlow的计算图将会太大。因为每生成一个常量,TensorFlow都会在计算图中增加一个节点。一般来说,一个神经网络的训练过程会需要经过几百万轮甚至几亿轮的迭代,这样计算图就会非常大,而且利用率很低。为了避免这个问题,TensorFlow提供了placeholder机制用于提供输入数据。placeholder相当于定义了一个位置,这个位置中的数据在程序运行时再指定。这样在程序中就不需要生成大量常量来提供输入数据,而只需要将数据通过placeholder传入TensorFlow计算图。在placeholder定义时,这个位置上的数据类型是需要指定的。和其他张量一样,placeholder的类型也是不可以改变的。placeholder中数据的维度信息可以根据提供的数据推导得出,所以不一定要给出。下面给出了通过placeholder实现前向传播算法的代码。
x = tf.placeholder(tf.float32, shape=(1, 2), name=&input&)
# 其他部分定义和上面的样例一样。
print(sess.run(y, feed_dict={x: [[0.7,0.9]]}))
在调用sess.run时,我们需要使用feed_dict来设定x的取值。在得到一个batch的前向传播结果之后,需要定义一个损失函数来刻画当前的预测值和真实答案之间的差距。然后通过反向传播算法来调整神经网络参数的取值使得差距可以被缩小。损失函数将在后面的文章中更加详细地介绍。以下代码定义了一个简单的损失函数,并通过TensorFlow定义了反向传播的算法。
# 定义损失函数来刻画预测值与真实值得差距。
cross_entropy = -tf.reduce_mean(
y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)))
# 定义学习率。
learning_rate = 0.001
# 定义反向传播算法来优化神经网络中的参数。
train_step =
tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
在上面的代码中,cross_entropy定义了真实值和预测值之间的交叉熵(cross entropy),这是分类问题中一个常用的损失函数。第二行train_step定义了反向传播的优化方法。目前TensorFlow支持10种不同的优化器,读者可以根据具体的应用选择不同的优化算法。比较常用的优化方法有三种:tf.train.GradientDescentOptimizer、class tf.train.AdamOptimizer和tf.train.MomentumOptimizer。
TensorFlow解决MNIST问题
MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例。MNIST数据集是NIST数据集的一个子集,它包含了60000张图片作为训练数据,10000张图片作为测试数据。在MNIST数据集中的每一张图片都代表了0-9中的一个数字。图片的大小都为28&28,且数字都会出现在图片的正中间。图5展示了一张数字图片及和它对应的像素矩阵:
图5. MNIST数字图片及其像素矩阵。
在图5的左侧显示了一张数字1的图片,而右侧显示了这个图片所对应的像素矩阵。MNIST数据集中图片的像素矩阵大小为28&28,但为了更清楚的展示,图5右侧显示的为14&14的矩阵。在Yann LeCun教授的网站中(/exdb/mnist)对MNIST数据集做出了详细的介绍。TensorFlow对MNIST数据集做了更高层的封装,使得使用起来更加方便。下面给出了样例TensorFlow代码来解决MNIST数字手写体分类问题。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# MNIST数据集相关的常数。
INPUT_NODE = 784
# 输入层的节点数。对于MNIST数据集,这个就等于图片的像素。
OUTPUT_NODE = 10
# 输出层的节点数。这个等于类别的数目。因为在MNIST数据集中
# 需要区分的是0~9这10个数字,所以这里输出层的节点数为10。
# 配置神经网络的参数。
LAYER1_NODE = 500
# 隐藏层节点数。这里使用只有一个隐藏层的网络结构作为样例。
# 这个隐藏层有500个节点。
BATCH_SIZE = 100
# 一个训练batch中的训练数据个数。数字越小时,训练过程越接近
# 随机梯度下降;数字越大时,训练越接近梯度下降。
LEARNING_RATE = 0.01
# 学习率。
TRAINING_STEPS = 10000
# 训练轮数。
# 训练模型的过程。
def train(mnist):
x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')
# 定义神经网络参数。
weights1 = tf.Variable(
tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))
bias1 = tf.Variable(tf.constant(0.0, shape=[LAYER1_NODE]))
weights2 = tf.Variable(
tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
bias2 = tf.Variable(tf.constant(0.0, shape=[OUTPUT_NODE]))
# 计算在当前参数下神经网络前向传播的结果。
layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + bias1)
y = tf.matmul(layer1, weights2) + bias2
# 定义存储训练轮数的变量。
global_step = tf.Variable(0, trainable=False)
# 计算交叉熵作为刻画预测值和真实值之间差距的损失函数。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(
labels=y_, logits=y)
loss = tf.reduce_mean(cross_entropy)
# 使用tf.train.GradientDescentOptimizer优化算法来优化损失函数。注意这里损失
# 函数包含了交叉熵损失和L2正则化损失。
train_op=tf.train.GradientDescentOptimizer(LEARNING_RATE)\
.minimize(loss, global_step=global_step)
# 检验神经网络的正确率。
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 初始化会话并开始训练过程。
with tf.Session() as sess:
tf.initialize_all_variables().run()
# 准备验证数据。一般在神经网络的训练过程中会通过验证数据来大致判断停止的
# 条件和评判训练的效果。
validate_feed = {x: mnist.validation.images,
y_: mnist.validation.labels}
# 准备测试数据。在真实的应用中,这部分数据在训练时是不可见的,这个数据只是作为
# 模型优劣的最后评价标准。
test_feed = {x: mnist.test.images, y_: mnist.test.labels}
# 迭代地训练神经网络。
for i in range(TRAINING_STEPS):
# 每1000轮输出一次在验证数据集上的测试结果。
if i % 1000 == 0:
validate_acc = sess.run(accuracy, feed_dict=validate_feed)
print(&After %d training step(s), validation accuracy &
&using average model is %g & % (i, validate_acc))
# 产生这一轮使用的一个batch的训练数据,并运行训练过程。
xs, ys = mnist.train.next_batch(BATCH_SIZE)
sess.run(train_op, feed_dict={x: xs, y_: ys})
# 在训练结束之后,在测试数据上检测神经网络模型的最终正确率。
test_acc = sess.run(accuracy, feed_dict=test_feed)
print(&After %d training step(s), test accuracy using average &
&model is %g& % (TRAINING_STEPS, test_acc))
# 主程序入口
def main(argv=None):
# 声明处理MNIST数据集的类,这个类在初始化时会自动下载数据。
mnist = input_data.read_data_sets(&/tmp/data&, one_hot=True)
train(mnist)
# TensorFlow提供的一个主程序入口,tf.app.run会调用上面定义的main函数。
if __name__ == '__main__':
tf.app.run()
运行上面代码可以得到结果:
After 0 training step(s), validation accuracy using average model is 0.103
After 1000 training step(s), validation accuracy using average model is 0.9044
After 2000 training step(s), validation accuracy using average model is 0.9174
After 3000 training step(s), validation accuracy using average model is 0.9258
After 4000 training step(s), validation accuracy using average model is 0.93
After 5000 training step(s), validation accuracy using average model is 0.9346
After 6000 training step(s), validation accuracy using average model is 0.94
After 7000 training step(s), validation accuracy using average model is 0.9422
After 8000 training step(s), validation accuracy using average model is 0.9472
After 9000 training step(s), validation accuracy using average model is 0.9498
After 10000 training step(s), test accuracy using average model is 0.9475
通过该程序可以将MNIST数据集的准确率达到~95%。
Author Contacted
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
找回密码....
InfoQ账号使用的E-mail
关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。
内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。
设置通知机制以获取内容更新对您而言是否重要
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。7214人阅读
tensorflow(66)
tesorflow中的激活函数
所有激活函数 输入 和 输出 的维度是一样的
tf.nn.relu()
tf.nn.sigmoid()
tf.nn.tanh()
tf.nn.elu()
tf.nn.bias_add()
tf.nn.crelu()
tf.nn.relu6()
tf.nn.softplus()
tf.nn.softsign()
tf.nn.dropout()
tf.nn.relu_layer(x, weights, biases,name=None)
def relu_layer(x, weights, biases, name=None):
"""Computes Relu(x * weight + biases).
x: a 2D tensor.
Dimensions typically: batch, in_units
weights: a 2D tensor.
Dimensions typically: in_units, out_units
biases: a 1D tensor.
Dimensions: out_units
name: A name for the operation (optional).
If not specified
"nn_relu_layer" is used.
A 2-D Tensor computing relu(matmul(x, weights) + biases).
Dimensions typically: batch, out_units.
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:365704次
积分:4959
积分:4959
排名:第5836名
原创:128篇
转载:23篇
评论:107条
文章:13篇
阅读:42803
文章:49篇
阅读:239992
(6)(6)(9)(10)(15)(16)(5)(8)(18)(17)(19)(2)(2)(1)(1)(1)(10)(10)如何使用TensorFlow实现卷积神经网络
我的图书馆
如何使用TensorFlow实现卷积神经网络
编者按:本文节选自图书《TensorFlow实战》第五章,本书将重点从实用的层面,为读者讲解如何使用TensorFlow实现全连接神经网络、卷积神经网络、循环神经网络,乃至Deep Q-Network。同时结合TensorFlow原理,以及深度学习的部分知识,尽可能让读者通过学习本书做出实际项目和成果。卷积神经网络简介卷积神经网络(Convolutional?Neural?Network,CNN)最初是为解决图像识别等问题设计的,当然其现在的应用不仅限于图像和视频,也可用于时间序列信号,比如音频信号、文本数据等。在早期的图像识别研究中,最大的挑战是如何组织特征,因为图像数据不像其他类型的数据那样可以通过人工理解来提取特征。在股票预测等模型中,我们可以从原始数据中提取过往的交易价格波动、市盈率、市净率、盈利增长等金融因子,这即是特征工程。但是在图像中,我们很难根据人为理解提取出有效而丰富的特征。在深度学习出现之前,我们必须借助SIFT、HoG等算法提取具有良好区分性的特征,再集合SVM等机器学习算法进行图像识别。如图5-1所示,SIFT对一定程度内的缩放、平移、旋转、视角改变、亮度调整等畸变,都具有不变性,是当时最重要的图像特征提取方法之一。可以说,在之前只能依靠SIFT等特征提取算法才能勉强进行可靠的图像识别。图5-1??SIFT、HoG等图像特征提取方法然而SIFT这类算法提取的特征还是有局限性的,在ImageNet?ILSVRC比赛的最好结果的错误率也有26%以上,而且常年难以产生突破。卷积神经网络提取的特征则可以达到更好的效果,同时它不需要将特征提取和分类训练两个过程分开,它在训练时就自动提取了最有效的特征。CNN作为一个深度学习架构被提出的最初诉求,是降低对图像数据预处理的要求,以及避免复杂的特征工程。CNN可以直接使用图像的原始像素作为输入,而不必先使用SIFT等算法提取特征,减轻了使用传统算法如SVM时必需要做的大量重复、烦琐的数据预处理工作。和SIFT等算法类似,CNN训练的模型同样对缩放、平移、旋转等畸变具有不变性,有着很强的泛化性。CNN的最大特点在于卷积的权值共享结构,可以大幅减少神经网络的参数量,防止过拟合的同时又降低了神经网络模型的复杂度。CNN的权值共享其实也很像早期的延时神经网络(TDNN),只不过后者是在时间这一个维度上进行权值共享,降低了学习时间序列信号的复杂度。卷积神经网络的概念最早出自19世纪60年代科学家提出的感受野(Receptive?Field)。当时科学家通过对猫的视觉皮层细胞研究发现,每一个视觉神经元只会处理一小块区域的视觉图像,即感受野。到了20世纪80年代,日本科学家提出神经认知机(Neocognitron)的概念,可以算作是卷积网络最初的实现原型。神经认知机中包含两类神经元,用来抽取特征的S-cells,还有用来抗形变的C-cells,其中S-cells对应我们现在主流卷积神经网络中的卷积核滤波操作,而C-cells则对应激活函数、最大池化(Max-Pooling)等操作。同时,CNN也是首个成功地进行多层训练的网络结构,即前面章节提到的LeCun的LeNet5,而全连接的网络因为参数过多及梯度弥散等问题,在早期很难顺利地进行多层的训练。卷积神经网络可以利用空间结构关系减少需要学习的参数量,从而提高反向传播算法的训练效率。在卷积神经网络中,第一个卷积层会直接接受图像像素级的输入,每一个卷积操作只处理一小块图像,进行卷积变化后再传到后面的网络,每一层卷积(也可以说是滤波器)都会提取数据中最有效的特征。这种方法可以提取到图像中最基础的特征,比如不同方向的边或者拐角,而后再进行组合和抽象形成更高阶的特征,因此CNN可以应对各种情况,理论上具有对图像缩放、平移和旋转的不变性。一般的卷积神经网络由多个卷积层构成,每个卷积层中通常会进行如下几个操作。图像通过多个不同的卷积核的滤波,并加偏置(bias),提取出局部特征,每一个卷积核会映射出一个新的2D图像。将前面卷积核的滤波输出结果,进行非线性的激活函数处理。目前最常见的是使用ReLU函数,而以前Sigmoid函数用得比较多。对激活函数的结果再进行池化操作(即降采样,比如将2×2的图片降为1×1的图片),目前一般是使用最大池化,保留最显著的特征,并提升模型的畸变容忍能力。这几个步骤就构成了最常见的卷积层,当然也可以再加上一个LRN(Local?Response?Normalization,局部响应归一化层)层,目前非常流行的Trick还有Batch?Normalization等。一个卷积层中可以有多个不同的卷积核,而每一个卷积核都对应一个滤波后映射出的新图像,同一个新图像中每一个像素都来自完全相同的卷积核,这就是卷积核的权值共享。那我们为什么要共享卷积核的权值参数呢?答案很简单,降低模型复杂度,减轻过拟合并降低计算量。举个例子,如图5-2所示,如果我们的图像尺寸是1000像素×1000像素,并且假定是黑白图像,即只有一个颜色通道,那么一张图片就有100万个像素点,输入数据的维度也是100万。接下来,如果连接一个相同大小的隐含层(100万个隐含节点),那么将产生100万×100万=一万亿个连接。仅仅一个全连接层(Fully?Connected?Layer),就有一万亿连接的权重要去训练,这已经超出了普通硬件的计算能力。我们必须减少需要训练的权重数量,一是降低计算的复杂度,二是过多的连接会导致严重的过拟合,减少连接数可以提升模型的泛化性。图像在空间上是有组织结构的,每一个像素点在空间上和周围的像素点实际上是有紧密联系的,但是和太遥远的像素点就不一定有什么关联了。这就是前面提到的人的视觉感受野的概念,每一个感受野只接受一小块区域的信号。这一小块区域内的像素是互相关联的,每一个神经元不需要接收全部像素点的信息,只需要接收局部的像素点作为输入,而后将所有这些神经元收到的局部信息综合起来就可以得到全局的信息。这样就可以将之前的全连接的模式修改为局部连接,之前隐含层的每一个隐含节点都和全部像素相连,现在我们只需要将每一个隐含节点连接到局部的像素节点。假设局部感受野大小是10×10,即每个隐含节点只与10×10个像素点相连,那么现在就只需要10×10×100万=1亿个连接,相比之前的1万亿缩小了10000倍。图5-2??全连接(左)和局部连接(右)上面我们通过局部连接(Locally?Connect)的方法,将连接数从1万亿降低到1亿,但仍然偏多,需要继续降低参数量。现在隐含层每一个节点都与10×10的像素相连,也就是每一个隐含节点都拥有100个参数。假设我们的局部连接方式是卷积操作,即默认每一个隐含节点的参数都完全一样,那我们的参数不再是1亿,而是100。不论图像有多大,都是这10×10=100个参数,即卷积核的尺寸,这就是卷积对缩小参数量的贡献。我们不需要再担心有多少隐含节点或者图片有多大,参数量只跟卷积核的大小有关,这也就是所谓的权值共享。但是如果我们只有一个卷积核,我们就只能提取一种卷积核滤波的结果,即只能提取一种图片特征,这不是我们期望的结果。好在图像中最基本的特征很少,我们可以增加卷积核的数量来多提取一些特征。图像中的基本特征无非就是点和边,无论多么复杂的图像都是点和边组合而成的。人眼识别物体的方式也是从点和边开始的,视觉神经元接受光信号后,每一个神经元只接受一个区域的信号,并提取出点和边的特征,然后将点和边的信号传递给后面一层的神经元,再接着组合成高阶特征,比如三角形、正方形、直线、拐角等,再继续抽象组合,得到眼睛、鼻子和嘴等五官,最后再将五官组合成一张脸,完成匹配识别。因此我们的问题就很好解决了,只要我们提供的卷积核数量足够多,能提取出各种方向的边或各种形态的点,就可以让卷积层抽象出有效而丰富的高阶特征。每一个卷积核滤波得到的图像就是一类特征的映射,即一个Feature?Map。一般来说,我们使用100个卷积核放在第一个卷积层就已经很充足了。那这样的话,如图5-3所示,我们的参数量就是100×100=1万个,相比之前的1亿又缩小了10000倍。因此,依靠卷积,我们就可以高效地训练局部连接的神经网络了。卷积的好处是,不管图片尺寸如何,我们需要训练的权值数量只跟卷积核大小、卷积核数量有关,我们可以使用非常少的参数量处理任意大小的图片。每一个卷积层提取的特征,在后面的层中都会抽象组合成更高阶的特征。而且多层抽象的卷积网络表达能力更强,效率更高,相比只使用一个隐含层提取全部高阶特征,反而可以节省大量的参数。当然,我们需要注意的是,虽然需要训练的参数量下降了,但是隐含节点的数量并没有下降,隐含节点的数量只跟卷积的步长有关。如果步长为1,那么隐含节点的数量和输入的图像像素数量一致;如果步长为5,那么每5×5的像素才需要一个隐含节点,我们隐含节点的数量就是输入像素数量的1/25。图5-3??局部连接(左)和卷积操作(右)我们再总结一下,卷积神经网络的要点就是局部连接(Local?Connection)、权值共享(Weight?Sharing)和池化层(Pooling)中的降采样(Down-Sampling)。其中,局部连接和权值共享降低了参数量,使训练复杂度大大下降,并减轻了过拟合。同时权值共享还赋予了卷积网络对平移的容忍性,而池化层降采样则进一步降低了输出参数量,并赋予模型对轻度形变的容忍性,提高了模型的泛化能力。卷积神经网络相比传统的机器学习算法,无须手工提取特征,也不需要使用诸如SIFT之类的特征提取算法,可以在训练中自动完成特征的提取和抽象,并同时进行模式分类,大大降低了应用图像识别的难度;相比一般的神经网络,CNN在结构上和图片的空间结构更为贴近,都是2D的有联系的结构,并且CNN的卷积连接方式和人的视觉神经处理光信号的方式类似。大名鼎鼎的LeNet5?诞生于1994年,是最早的深层卷积神经网络之一,并且推动了深度学习的发展。从1988年开始,在多次成功的迭代后,这项由Yann?LeCun完成的开拓性成果被命名为LeNet5。LeCun认为,可训练参数的卷积层是一种用少量参数在图像的多个位置上提取相似特征的有效方式,这和直接把每个像素作为多层神经网络的输入不同。像素不应该被使用在输入层,因为图像具有很强的空间相关性,而使用图像中独立的像素直接作为输入则利用不到这些相关性。LeNet5当时的特性有如下几点。每个卷积层包含三个部分:卷积、池化和非线性激活函数使用卷积提取空间特征降采样(Subsample)的平均池化层(Average?Pooling)双曲正切(Tanh)或S型(Sigmoid)的激活函数MLP作为最后的分类器层与层之间的稀疏连接减少计算复杂度LeNet5中的诸多特性现在依然在state-of-the-art卷积神经网络中使用,可以说LeNet5是奠定了现代卷积神经网络的基石之作。Lenet-5的结构如图5-4所示。它的输入图像为32×32的灰度值图像,后面有三个卷积层,一个全连接层和一个高斯连接层。它的第一个卷积层C1包含6个卷积核,卷积核尺寸为5×5,即总共(5×5+1)×6=156个参数,括号中的1代表1个bias,后面是一个2×2的平均池化层S2用来进行降采样,再之后是一个Sigmoid激活函数用来进行非线性处理。而后是第二个卷积层C3,同样卷积核尺寸是5×5,这里使用了16个卷积核,对应16个Feature?Map。需要注意的是,这里的16个Feature?Map不是全部连接到前面的6个Feature?Map的输出的,有些只连接了其中的几个Feature?Map,这样增加了模型的多样性。下面的第二个池化层S4和第一个池化层S2一致,都是2×2的降采样。接下来的第三个卷积层C5有120个卷积核,卷积大小同样为5×5,因为输入图像的大小刚好也是5×5,因此构成了全连接,也可以算作全连接层。F6层是一个全连接层,拥有84个隐含节点,激活函数为Sigmoid。LeNet-5最后一层由欧式径向基函数(Euclidean?Radial?Basis?Function)单元组成,它输出最后的分类结果。图5-4??LeNet-5结构示意图TensorFlow实现简单的卷积网络本节将讲解如何使用TensorFlow实现一个简单的卷积神经网络,使用的数据集依然是MNIST,预期可以达到99.2%左右的准确率。本节将使用两个卷积层加一个全连接层构建一个简单但是非常有代表性的卷积神经网络,读者应该能通过这个例子掌握设计卷积神经网络的要点。首先载入MNIST数据集,并创建默认的Interactive?Session。本节代码主要来自TensorFlow的开源实现。接下来要实现的这个卷积神经网络会有很多的权重和偏置需要创建,因此我们先定义好初始化函数以便重复使用。我们需要给权重制造一些随机的噪声来打破完全对称,比如截断的正态分布噪声,标准差设为0.1。同时因为我们使用ReLU,也给偏置增加一些小的正值(0.1)用来避免死亡节点(dead?neurons)。卷积层、池化层也是接下来要重复使用的,因此也为他们分别定义创建函数。这里的tf.nn.conv2d是TensorFlow中的2维卷积函数,参数中x是输入,W是卷积的参数,比如[5,5,1,32]:前面两个数字代表卷积核的尺寸;第三个数字代表有多少个channel。因为我们只有灰度单色,所以是1,如果是彩色的RGB图片,这里应该是3。最后一个数字代表卷积核的数量,也就是这个卷积层会提取多少类的特征。Strides代表卷积模板移动的步长,都是1代表会不遗漏地划过图片的每一个点。Padding代表边界的处理方式,这里的SAME代表给边界加上Padding让卷积的输出和输入保持同样(SAME)的尺寸。tf.nn.max_pool是TensorFlow中的最大池化函数,我们这里使用2×2的最大池化,即将一个2×2的像素块降为1×1的像素。最大池化会保留原始像素块中灰度值最高的那一个像素,即保留最显著的特征。因为希望整体上缩小图片尺寸,因此池化层的strides也设为横竖两个方向以2为步长。如果步长还是1,那么我们会得到一个尺寸不变的图片。在正式设计卷积神经网络的结构之前,先定义输入的placeholder,x是特征,y_是真实的label。因为卷积神经网络会利用到空间结构信息,因此需要将1D的输入向量转为2D的图片结构,即从1×784的形式转为原始的28×28的结构。同时因为只有一个颜色通道,故最终尺寸为[-1,28,28,1],前面的-1代表样本数量不固定,最后的1代表颜色通道数量。这里我们使用的tensor变形函数是tf.reshape。接下来定义我们的第一个卷积层。我们先使用前面写好的函数进行参数初始化,包括weights和bias,这里的[5,5,1,32]代表卷积核尺寸为5×5,1个颜色通道,32个不同的卷积核。然后使用conv2d函数进行卷积操作,并加上偏置,接着再使用ReLU激活函数进行非线性处理。最后,使用最大池化函数max_pool_2x2对卷积的输出结果进行池化操作。现在定义第二个卷积层,这个卷积层基本和第一个卷积层一样,唯一的不同是,卷积核的数量变成了64,也就是说这一层的卷积会提取64种特征。因为前面经历了两次步长为2×2的最大池化,所以边长已经只有1/4了,图片尺寸由28×28变成了7×7。而第二个卷积层的卷积核数量为64,其输出的tensor尺寸即为7×7×64。我们使用tf.reshape函数对第二个卷积层的输出tensor进行变形,将其转成1D的向量,然后连接一个全连接层,隐含节点为1024,并使用ReLU激活函数。为了减轻过拟合,下面使用一个Dropout层,Dropout的用法第4章已经讲过,是通过一个placeholder传入keep_prob比率来控制的。在训练时,我们随机丢弃一部分节点的数据来减轻过拟合,预测时则保留全部数据来追求最好的预测性能。最后我们将Dropout层的输出连接一个Softmax层,得到最后的概率输出。我们定义损失函数为cross?entropy,和之前一样,但是优化器使用Adam,并给予一个比较小的学习速率1e-4。再继续定义评测准确率的操作。下面开始训练过程。首先依然是初始化所有参数,设置训练时Dropout的keep_prob比率为0.5。然后使用大小为50的mini-batch,共进行20000次训练迭代,参与训练的样本数量总共为100万。其中每100次训练,我们会对准确率进行一次评测(评测时keep_prob设为1),用以实时监测模型的性能。全部训练完成后,我们在最终的测试集上进行全面的测试,得到整体的分类准确率。最后,这个CNN模型可以得到的准确率约为99.2%,基本可以满足对手写数字识别准确率的要求。相比之前MLP的2%错误率,CNN的错误率下降了大约60%。这其中主要的性能提升都来自于更优秀的网络设计,即卷积网络对图像特征的提取和抽象能力。依靠卷积核的权值共享,CNN的参数量并没有爆炸,降低计算量的同时也减轻了过拟合,因此整个模型的性能有较大的提升。本节我们只实现了一个简单的卷积神经网络,没有复杂的Trick。接下来,我们将实现一个稍微复杂一些的卷积网络,而简单的MNIST数据集已经不适合用来评测其性能,我们将使用CIFAR-10数据集进行训练,这也是深度学习可以大幅领先其他模型的一个数据集。TensorFlow实现进阶的卷积网络本节使用的数据集是CIFAR-10,这是一个经典的数据集,包含6的彩色图像,其中训练集50000张,测试集10000张。CIFAR-10如同其名字,一共标注为10类,每一类图片6000张。这10类分别是airplane、automobile、bird、cat、deer、dog、frog、horse、ship和truck,其中没有任何重叠的情况,比如automobile只包括小型汽车,truck只包括卡车,也不会在一张图片中同时出现两类物体。它还有一个兄弟版本CIFAR-100,其中标注了100类。这两个数据集是前面章节提到的深度学习之父Geoffrey?Hinton和他的两名学生Alex?Krizhevsky和Vinod?Nair收集的,图片来源于80?million?tiny?images这个数据集,Hinton等人对其进行了筛选和标注。CIFAR-10数据集非常通用,经常出现在各大会议的论文中用来进行性能对比,也曾出现在Kaggle竞赛而为大家所知。图5-5所示为这个数据集的一些示例。图5-5??CIFAR-10数据集示例许多论文中都在这个数据集上进行了测试,目前state-of-the-art的工作已经可以达到3.5%的错误率了,但是需要训练很久,即使在GPU上也需要十几个小时。CIFAR-10数据集上详细的Benchmark和排名在classification?datasets?results上(http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html)。据深度学习三巨头之一LeCun说,现有的卷积神经网络已经可以对CIFAR-10进行很好的学习,这个数据集的问题已经解决了。本节中实现的卷积神经网络没有那么复杂(根据Alex描述的cuda-convnet模型做了些许修改得到),在只使用3000个batch(每个batch包含128个样本)时,可以达到73%左右的正确率。模型在GTX?1080单显卡上大概只需要几十秒的训练时间,如果在CPU上训练则会慢很多。如果使用100k个batch,并结合学习速度的decay(即每隔一段时间将学习速率下降一个比率),正确率最高可以到86%左右。模型中需要训练的参数约为100万个,而预测时需要进行的四则运算总量在2000万次左右。在这个卷积神经网络模型中,我们使用了一些新的技巧。对weights进行了L2的正则化。如图5-6所示,我们对图片进行了翻转、随机剪切等数据增强,制造了更多样本。在每个卷积-最大池化层后面使用了LRN层,增强了模型的泛化能力。图5-6??数据增强示例(水平翻转,随机裁切)我们首先下载TensorFlow?Models库,以便使用其中提供CIFAR-10数据的类。然后我们载入一些常用库,比如NumPy和time,并载入TensorFlow?Models中自动下载、读取CIFAR-10数据的类。本节代码主要来自TensorFlow的开源实现。接着定义batch_size、训练轮数max_steps,以及下载CIFAR-10数据的默认路径。这里定义初始化weight的函数,和之前一样依然使用tf.truncated_normal截断的正态分布来初始化权重。但是这里会给weight加一个L2的loss,相当于做了一个L2的正则化处理。在机器学习中,不管是分类还是回归任务,都可能因特征过多而导致过拟合,一般可以通过减少特征或者惩罚不重要特征的权重来缓解这个问题。但是通常我们并不知道该惩罚哪些特征的权重,而正则化就是帮助我们惩罚特征权重的,即特征的权重也会成为模型的损失函数的一部分。可以理解为,为了使用某个特征,我们需要付出loss的代价,除非这个特征非常有效,否则就会被loss上的增加覆盖效果。这样我们就可以筛选出最有效的特征,减少特征权重防止过拟合。这也即是奥卡姆剃刀法则,越简单的东西越有效。一般来说,L1正则会制造稀疏的特征,大部分无用特征的权重会被置为0,而L2正则会让特征的权重不过大,使得特征的权重比较平均。我们使用wl控制L2?loss的大小,使用tf.nn.l2_loss函数计算weight的L2?loss,再使用tf.multiply让L2?loss乘以wl,得到最后的weight?loss。接着,我们使用tf.add_to_collection把weight?loss统一存到一个collection,这个collection名为“losses”,它会在后面计算神经网络的总体loss时被用上。下面使用cifar10类下载数据集,并解压、展开到其默认位置。再使用cifar10_input类中的distorted_inputs函数产生训练需要使用的数据,包括特征及其对应的label,这里返回的是已经封装好的tensor,每次执行都会生成一个batch_size的数量的样本。需要注意的是我们对数据进行了Data?Augmentation(数据增强)。具体的实现细节,读者可以查看cifar10_input.distorted_inputs函数,其中的数据增强操作包括随机的水平翻转(tf.image.random_flip_left_right)、随机剪切一块24×24大小的图片(tf.random_crop)、设置随机的亮度和对比度(tf.image.random_brightness、tf.image.random_contrast),以及对数据进行标准化tf.image.per_image_whitening(对数据减去均值,除以方差,保证数据零均值,方差为1)。通过这些操作,我们可以获得更多的样本(带噪声的),原来的一张图片样本可以变为多张图片,相当于扩大样本量,对提高准确率非常有帮助。需要注意的是,我们对图像进行数据增强的操作需要耗费大量CPU时间,因此distorted_inputs使用了16个独立的线程来加速任务,函数内部会产生线程池,在需要使用时会通过TensorFlow?queue进行调度。我们再使用cifar10_input.inputs函数生成测试数据,这里不需要进行太多处理,不需要对图片进行翻转或修改亮度、对比度,不过需要裁剪图片正中间的24×24大小的区块,并进行数据标准化操作。这里创建输入数据的placeholder,包括特征和label。在设定placeholder的数据尺寸时需要注意,因为batch_size在之后定义网络结构时被用到了,所以数据尺寸中的第一个值即样本条数需要被预先设定,而不能像以前一样可以设为None。而数据尺寸中的图片尺寸为24×24,即是裁剪后的大小,而颜色通道数则设为3,代表图片是彩色有RGB三条通道。做好了准备工作,接下来开始创建第一个卷积层。先使用之前写好的variable_with_weight_loss函数创建卷积核的参数并进行初始化。第一个卷积层使用5×5的卷积核大小,3个颜色通道,64个卷积核,同时设置weight初始化函数的标准差为0.05。我们不对第一个卷积层的weight进行L2的正则,因此wl(weight?loss)这一项设为0。下面使用tf.nn.conv2d函数对输入数据image_holder进行卷积操作,这里的步长stride均设为1,padding模式为SAME。把这层的bias全部初始化为0,再将卷积的结果加上bias,最后使用一个ReLU激活函数进行非线性化。在ReLU激活函数之后,我们使用一个尺寸为3×3且步长为2×2的最大池化层处理数据,注意这里最大池化的尺寸和步长不一致,这样可以增加数据的丰富性。再之后,我们使用tf.nn.lrn函数,即LRN对结果进行处理。LRN最早见于Alex那篇用CNN参加ImageNet比赛的论文,Alex在论文中解释LRN层模仿了生物神经系统的“侧抑制”机制,对局部神经元的活动创建竞争环境,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。Alex在ImageNet数据集上的实验表明,使用LRN后CNN在Top1的错误率可以降低1.4%,因此在其经典的AlexNet中使用了LRN层。LRN对ReLU这种没有上限边界的激活函数会比较有用,因为它会从附近的多个卷积核的响应(Response)中挑选比较大的反馈,但不适合Sigmoid这种有固定边界并且能抑制过大值的激活函数。现在来创建第二个卷积层,这里的步骤和第一步很像,区别如下。上一层的卷积核数量为64(即输出64个通道),所以本层卷积核尺寸的第三个维度即输入的通道数也需要调整为64;还有一个需要注意的地方是这里的bias值全部初始化为0.1,而不是0。最后,我们调换了最大池化层和LRN层的顺序,先进行LRN层处理,再使用最大池化层。在两个卷积层之后,将使用一个全连接层,这里需要先把前面两个卷积层的输出结果全部flatten,使用tf.reshape函数将每个样本都变成一维向量。我们使用get_shape函数,获取数据扁平化之后的长度。接着使用variable_with_weight_loss函数对全连接层的weight进行初始化,这里隐含节点数为384,正态分布的标准差设为0.04,bias的值也初始化为0.1。需要注意的是我们希望这个全连接层不要过拟合,因此设了一个非零的weight?loss值0.04,让这一层的所有参数都被L2正则所约束。最后我们依然使用ReLU激活函数进行非线性化。接下来的这个全连接层和前一层很像,只不过其隐含节点数下降了一半,只有192个,其他的超参数保持不变。下面是最后一层,依然先创建这一层的weight,其正态分布标准差设为上一个隐含层的节点数的倒数,并且不计入L2的正则。需要注意的是,这里不像之前那样使用softmax输出最后结果,这是因为我们把softmax的操作放在了计算loss的部分。我们不需要对inference的输出进行softmax处理就可以获得最终分类结果(直接比较inference输出的各类的数值大小即可),计算softmax主要是为了计算loss,因此softmax操作整合到后面是比较合适的。到这里就完成了整个网络inference的部分。梳理整个网络结构可以得到表5-1。从上到下,依次是整个卷积神经网络从输入到输出的流程。可以观察到,其实设计CNN主要就是安排卷积层、池化层、全连接层的分布和顺序,以及其中超参数的设置、Trick的使用等。设计性能良好的CNN是有一定规律可循的,但是想要针对某个问题设计最合适的网络结构,是需要大量实践摸索的。表5-1??卷积神经网络结构表完成了模型inference部分的构建,接下来计算CNN的loss。这里依然使用cross?entropy,需要注意的是我们把softmax的计算和cross?entropy?loss的计算合在了一起,即tf.nn.sparse_softmax_cross_entropy_with_logits。这里使用tf.reduce_mean对cross?entropy计算均值,再使用tf.add_to_collection把cross?entropy的loss添加到整体losses的collection中。最后,使用tf.add_n将整体losses的collection中的全部loss求和,得到最终的loss,其中包括cross?entropy?loss,还有后两个全连接层中weight的L2?loss。接着将logits节点和label_placeholder传入loss函数获得最终的loss。优化器依然选择Adam?Optimizer,学习速率设为1e-3。使用tf.nn.in_top_k函数求输出结果中top?k的准确率,默认使用top?1,也就是输出分数最高的那一类的准确率。使用tf.InteractiveSession创建默认的session,接着初始化全部模型参数。这一步是启动前面提到的图片数据增强的线程队列,这里一共使用了16个线程来进行加速。注意,如果这里不启动线程,那么后续的inference及训练的操作都是无法开始的。现在正式开始训练。在每一个step的训练过程中,我们需要先使用session的run方法执行images_train、labels_train的计算,获得一个batch的训练数据,再将这个batch的数据传入train_op和loss的计算。我们记录每一个step花费的时间,每隔10个step会计算并展示当前的loss、每秒钟能训练的样本数量,以及训练一个batch数据所花费的时间,这样就可以比较方便地监控整个训练过程。在GTX?1080上,每秒钟可以训练大约1800个样本,如果batch_size为128,则每个batch大约需要0.066s。损失loss在一开始大约为4.6,在经过了3000步训练后会下降到1.0附近。接下来评测模型在测试集上的准确率。测试集一共有10000个样本,但是需要注意的是,我们依然要像训练时那样使用固定的batch_size,然后一个batch一个batch地输入测试数据。我们先计算一共要多少个batch才能将全部样本评测完。同时,在每一个step中使用session的run方法获取images_test、labels_test的batch,再执行top_k_op计算模型在这个batch的top?1上预测正确的样本数。最后汇总所有预测正确的结果,求得全部测试样本中预测正确的数量。最后将准确率的评测结果计算并打印出来。最终,在CIFAR-10数据集上,通过一个短时间小迭代次数的训练,可以达到大致73%的准确率。持续增加max_steps,可以期望准确率逐渐增加。如果max_steps比较大,则推荐使用学习速率衰减(decay)的SGD进行训练,这样训练过程中能达到的准确率峰值会比较高,大致接近86%。而其中L2正则及LRN层的使用都对模型准确率有提升作用,他们都可以从某些方面提升模型的泛化性。数据增强(Data?Augmentation)在我们的训练中作用很大,它可以给单幅图增加多个副本,提高图片的利用率,防止对某一张图片结构的学习过拟合。这刚好是利用了图片数据本身的性质,图片的冗余信息量比较大,因此可以制造不同的噪声并让图片依然可以被识别出来。如果神经网络可以克服这些噪声并准确识别,那么它的泛化性必然会很好。数据增强大大增加了样本量,而数据量的大小恰恰是深度学习最看重的,深度学习可以在图像识别上领先其他算法的一大因素就是它对海量数据的利用效率非常高。用其他算法,可能在数据量大到一定程度时,准确率就不再上升了,而深度学习只要提供足够多的样本,准确率基本可以持续提升,所以说它是最适合大数据的算法。如图5-6所示,传统的机器学习算法在获取了一定量的数据后,准确率上升曲线就接近瓶颈,而神经网络则可以持续上升到更高的准确率才接近瓶颈。规模越大越复杂的神经网络模型,可以达到的准确率水平越高,但是也相应地需要更多的数据才能训练好,在数据量小时反而容易过拟合。我们可以看到Large?NN在数据量小的时候,并不比常规算法好,直到数据量持续扩大才慢慢超越了常规算法、Small?NN和Medium?NN,并在最后达到了一个非常高的准确率。根据Alex在cuda-convnet上的测试结果,如果不对CIFAR-10数据使用数据增强,那么错误率最低可以下降到17%;使用数据增强后,错误率可以下降到11%左右,模型性能的提升非常显著。图5-6??传统机器学习算法和深度学习在不同数据量下的表现从本章的例子中可以发现,卷积层一般需要和一个池化层连接,卷积加池化的组合目前已经是做图像识别时的一个标准组件了。卷积网络最后的几个全连接层的作用是输出分类结果,前面的卷积层主要做特征提取的工作,直到最后的全连接层才开始对特征进行组合匹配,并进行分类。卷积层的训练相对于全连接层更复杂,训练全连接层基本是进行一些矩阵乘法运算,而目前卷积层的训练基本依赖于cuDNN的实现(另有nervana公司的neon也占有一席之地)。其中的算法相对复杂,有些方法(比如Facebook开源的算法)还会涉及傅里叶变换。同时,卷积层的使用有很多Trick,除了本章提到的方法,实际上有很多方法可以防止CNN过拟合,加快收敛速度或者提高泛化性,这些会在后续章节中讲解。
TA的最新馆藏
喜欢该文的人也喜欢}

我要回帖

更多关于 tensorflow relu 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信