Tensorflow快速入门
作者:叶 虎
编辑:李文臣
PART
01 Tensorflow简介
引言
实践深度学习肯定要至少学习并掌握一个深度学习框架。这里我们介绍一个最流行的深度学习框架:Tensorflow。Tensorflow是谷歌公司在2015年9月开源的一个深度学习框架。虽然我们称Tensorflow为一种深度学习框架,但是看看官网:
图1 Tensorflow官网界面
可以看到,从功能上看,Tensorflow定义为专为机器智能打造的开源软件库。而从内部机制上,Tensorflow定义为一个使用数据流图进行数值计算的开源软件库。这说明Tensorflow的功能远不是只应用在深度学习领域,但是通常我们还是用Tensorflow来搭建深度学习模型。其他流行的深度学习框架也有很多,如PyTorch, MXnet, Theano,Caffe等,还有根据这些框架衍生出来的高级深度学习框架,如Keras, TFLearn, TensorLayer等。其实各个深度学习框架都有自己独特的优势,网上也有针对各个框架的对比。但是,不管在学术界还是在工业界,Tensorflow绝对是学习深度学习的一个较好的选择,这里谈一下其优势:
平台支持性良好,无论是Windows, Linux, 和macOS等系统,还是IOS和Android;
提供简单且灵活的Python API接口,内部使用C++进行优化;
丰富的算子,可以很容易搭建各种深度学习模型,如CNN和RNN模型;
提供可视化工具TensorBoard,这个是TF独有的优势;
支持CPU和GPU,支持分布式多机多卡训练;
总之,TF的优势还是挺多的,基本上的需求都可以满足,毕竟背后有强大的谷歌大佬在维护。接下来,就从入门TF开始吧。
PART
02 Tensorflow 概念介绍
对于任何深度学习框架,你都要先了解张量(Tensor)的概念,张量可以看成是向量和矩阵的衍生。向量是一维的,而矩阵是二维的,对于张量其可以是任何维度的。一般情况下,你要懂得张量的两个属性:形状(shape)和秩(rank)。秩很好理解,就是有多少个维度;而形状是指的每个维度的大小。下面是常见的张量的形象图表示:
前面说过,从内部机制上来说,TF就是建立数据流图来进行数值计算。所以,当你使用TF来搭建模型时,其实主要涉及两个方面:根据模型建立计算图,然后送入数据运行计算图得到结果。计算图computational graph是TF中很重要的一个概念,其是由一系列节点(nodes)组成的图模型,每个节点对应的是TF的一个算子(operation)。每个算子会有输入与输出,并且输入和输出都是张量。所以我们使用TF的算子可以构建自己的深度学习模型,其背后就是一个计算图。还有一点这个计算图是静态的,意思是这个计算图每个节点接收什么样的张量和输出什么样的张量已经固定下来。要运行这个计算图,你需要开启一个会话(session),在session中这个计算图才可以真正运行。我们用一个简单的例子来说明图模型的机理,首先定义一个计算图:
import tensorflow as tf
a = tf.constant(5, name="input_a")
b = tf.constant(3, name="input_b")
c = tf.multiply(a, b, name="mul_c")
d = tf.add(a, b, name="add_d")
e = tf.add(c, d, name="add_e")
代码很简单,我们引入TF库,然后利用TF的基本算术算子(和Numpy接口一致)创建了一个简单的计算图如下图所示。其中每个圆圈代表的是每个算子定义的节点。节点之间的边就是张量,这里其实都是标量(scalers)。
图3 计算图实例
我们开启会话并运行这个计算图得到e:
sess = tf.Session() # 创建会话
print(sess.run(e)) # 运行计算图得到e
sess.close() # 关闭会话
当我们想得到e的值时,TF会根据创建好的计算图查找e节点所有的依赖节点,这可以看成一个抽取子计算图的过程。得到这个子计算图就可以从a和b节点开始计算最终得到e的值。还有一点TF会对这个计算过程做并行化优化,不过这都是底层的事了。TF更灵活的一点你可以指定各个节点在哪个具体的设备上运行,如CPU和GPU。理解好计算图,就掌握了TF的基本运作原理,下面说说TF中重要的几个概念。
对于TF的计算图,所有的数据都是张量。TF也提供了创建一些常用张量的函数,并且这些函数和Numpy的接口是一致的。如创建全0或者全1的张量:
# tf.zeros(shape, dtype=tf.float32, name=None)
a = tf.zeros([2, 3], tf.int32) # [[0, 0, 0], [0, 0, 0]]
# tf.ones(shape, dtype=tf.float32, name=None)
b = tf.ones([2, 3], tf.int32) # [[1, 1, 1], [1, 1, 1]]
创建张量时一般情况下,需要指定张量的shape,数据类型以及名字。其中数据类型是和Numpy是基本对应起来的。TF同时也提供了随机产生的张量,如常用的高斯分布:
# tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
a = tf.random_normal([5, 5], mean=0.0, stddev=1.0) # 均值为0,标准差为1的高斯分布
# tf.random_uniform(shape, minval=0, maxval=None, dtype=tf.float32, seed=None, name = None)
b = tf.random_uniform([5, 5], minval=0, maxval=1) # [0, 1]内的均匀分布
在TF中,上面这些张量的用处一般是用于初始化模型的参数,后面我们会谈到。但是有时候,你可以想创建一些自己定义的常量张量,这时可以使用tf.constant:
# tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)
a = tf.constant([[0, 1], [2, 3]]) # [[0, 1], [2, 3]]
对于cosntant张量来说,如果你不指定数据类型,那么会根据输入的数据自动推断。对于constant张量,一般是不会使用的,因为constant张量不够灵活,而且它如果太大,会导致你定义的计算图太庞大。那么当我们想训练模型时,怎么样可以送入自己的数据呢?TF提供了一个特殊的算子:tf.placeholder。翻译成中文就是占位符,其含义是你可以先定义张量的shape和数据类型,但是具体的数据可以等执行计算图时再送入,这是比较灵活的。还是看例子:
# 定义一个占位张量
a = tf.placeholder(tf.int32, [3,])
# 定义一个costant张量
b = tf.constant([1, 1, 1])
# 计算a+b
c = a + b
with tf.Session() as sess:
# 给占位张量送入数据,并执行计算图
print(sess.run(c, feed_dict={a: [1, 2, 3]})) # [2, 3, 4]
一般情况下可以使用placeholder封装训练数据,你只需要定义训练数据的占位张量,在真正训练时送入真实的训练样本就可以了。
对于模型来说,最重要的一部分是模型参数,我们说训练模型,就是找到最优的模型参数。TF中提供了一个变量类:tf.Variable,主要是用来定义模型参数。虽然这是一个类,你可以将其看成一个带有状态的张量,就是存储的实际值是可以被改变的。定义一个变量时,你需要提供初始值,有以下几种方式:
a = tf.Variable([[2, 3], [1, 2]]) # 初始值为[[2, 3], [1, 2]]
b = tf.Variable((tf.zeros([10, 10]))) # 初始值为全0,shape为[10,10]的张量
c = tf.Variable(tf.random_normal([5, 5], mean=0.0, stddev=1.0)) # 初始值为标准正态分布,shape为[5, 5]的张量
对于定义的变量,实际的静态计算图中并没有实际存储值,所以在使用前一定要进行初始化,这里有一个快捷方式,把定义的所有变量都初始化:
init = tf.global_variables_initializer() # 初始化所有变量的算子
with tf.Session() as sess:
sess.run(init) # 执行初始化,此时变量被填值
变量是有状态的,那么怎么是去改变其值呢?变量提供了一个方法:assign,举例说明:
a = tf.Variable([1, 1])
b = tf.Variable([2, 2])
assign_op = a.assign(b) # a的值用b替换
init = tf.global_variables_initializer() # 初始化所有变量的算子
with tf.Session() as sess:
sess.run(init) # 执行初始化,此时变量被填值
print(sess.run(a)) # [1, 1]
sess.run(assign_op) # 执行assign
print(sess.run(a)) # [2, 2]
TF实现了自动梯度的功能,前面的文章我们说过自动梯度有很多实现方式,TF是基于反向模式的自动梯度,或者说大家说的BP算法。简单地说一下,就是我们建立模型时一般是一个前向计算图,其实每个算子都实现了反向过程,那么是很容易自动建立反向计算图。这样,就可以实现自动梯度了。TF中计算梯度的函数是tf.gradient,还是例子说话:
x = tf.constant([2.0, 1.0])
y = tf.constant([1.0, 2.0])
z = x * y + x * x
dx, dy = tf.gradients(z, [x, y]) # 求z关于x,y的导数
with tf.Session() as sess:
dx_v, dy_v = sess.run([dx, dy])
print(dx_v) # [5.0, 4.0]
print(dy_v) # [2.0, 1.0]
但是其实我们一般用不到这个函数,这是因为TF提供了各种各样的优化器,如GradientDescentOptimizer, AdamOptimizer等,这些优化器内部封装了梯度的计算,并且实现了参数的更新。下面的例子会使用。
PART
03 Tensorflow实战
最后我们使用TF实现LR模型用于MNIST数据集上,代码如下:
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 加载mnist数据集
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
# 定义输入与输出
x = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, 10])
# 定义模型参数
W = tf.Variable(tf.random_normal([784, 10]))
b = tf.Variable(tf.zeros([10,]))
# 定义模型
output = tf.nn.xw_plus_b(x, W, b)
prob = tf.nn.softmax(output)
# 定义loss,采用交叉熵
cross_entropy = -tf.reduce_mean(tf.reduce_sum(y * tf.log(prob), axis=1))
# 定义优化器
learning_rate = 1e-04
train_op = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
# 定义精确度
correct_pred = tf.equal(tf.argmax(y, axis=1), tf.argmax(output, axis=1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
# 训练参数
steps = 10000
batch_size = 128
with tf.Session() as sess:
sess.run(tf.global_variables_initializer()) # 初始化参数
# 开始训练
for i in range(steps):
xs, ys = mnist.train.next_batch(batch_size)
# 执行训练
_, l = sess.run([train_op, cross_entropy], feed_dict={x:xs, y:ys})
if i % 1000 == 0:
print("Steps %d , loss: %f" % (i, l))
# 测试集上测试
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels}))
PART
04 结束
一篇短而精悍的小文肯定不能把TF所有的方面展示出来,但是这是一个起点,尽情地使用Tensorflow吧!
1. 官网: https://www.tensorflow.org
2. CS 20SI: Tensorflow for Deep Learning Research: http://web.stanford.edu/class/cs20si/.
往
期
推
荐
3.RNN入门与实践
扫描个人微信号,
拉你进机器学习大牛群。
福利满满,名额已不多…
80%的AI从业者已关注我们微信公众号