在线试读

get_product_contenthtml

9.2  TensorFlow核心应用

谷歌于2015年开源了其诸多智能产品使用的机器学习软件库TensorFlow。相对于Python的其他机器学习库,它的主要优势在于优秀的架构设计和活跃的开发社区,因此本书选择TensorFlow作为深度学习实践工具,该框架可以用pip3工具进行管理。

# pip3 install tensorflow                             # 安装

# pip3 install --upgrade tensorflow==1.9.0            # 如已安装,尝试升级

 

条命令用于安装,第二条命令用于升级到本书使用的2018年7月发布的1.9.0版本。

9.2.1  张量

张量(Tensor)是在人工智能领域常被提到的一个概念,非理科出身的读者可能对这个概念比较陌生。一个常见的问题就是:什么是张量?

1.意图

张量的概念来源于物理学中需要用与坐标无关的语言描述物理现象的需要。为什么普通的数字量(标量)不能满足这个需求呢?想象有两个观众在不同位置观看跑步比赛,观察者A在跑道的侧方,而观察者B在跑道的正前方,如图9-10所示。

 

图9-10  位置对物理现象解读的影响

设图中跑者的速度为s,那么观察者A应该能更好地体会到该速度值,而在观察者B看来跑者的速度可能远小于s,甚至为零。这就是为什么体育场中主席台的位置是在椭圆跑道的侧方而不是正前方的原因。

显然跑者的速度与观察者的位置无关,导致观察者B产生“错觉”的原因是,他与观察者A有不同的观察坐标系。那么应该如何描述才能使AB都能体会到该速度值呢?物理学家想到的方式就是用更多的语言描述该速度,比如“该跑者向北的速度为s1,向东的速度为s2,向上的速度为s3”,这样的语言不仅能适应不同位置的观察者,还能描述一个正在爬坡的跑者的移动方式,这样一组用于描述同一个物理现象的数值组合就被称为“张量”。

2.阶

在上面的例子中,用三维空间中不同方向的值描述了速度张量的概念,其实对其更确切的说法应该是“一阶张量”。现在假设图9-10中的跑者在跑步过程中有一股旋风吹过,如何描述它对跑者的影响呢?如果仅用风力值描述就会犯忽略坐标系的错误,由于此时有两股力驱动着跑者(自身的跑动、旋风),好的描述该现象的语言应该如下。

◎   在跑者向北的运动中,旋风对其产生了推力:向北p11,向东p12,向上p13。

◎   在跑者向南的运动中,旋风对其产生了推力:向北p21,向东p22,向上p23。

◎   在跑者向上的运动中,旋风对其产生了推力:向北p31,向东p32,向上p33。

这样用三维空间中的3×3个数值才完整描述了该事件。这也是一种张量,被称为“二阶张量”。这样的想法被不断扩展,张量被数学家定义成了一种多重线性映射,可以产生“零阶张量”“三阶张量”“四阶张量”……这其中零阶张量就是平时所说的标量,一阶张量就是向量,二阶张量就是矩阵。

注意:需要区分张量中“维度”与“阶数”的概念。具体地说,一个张量总由个数值分量构成,其中m是维度数,n是阶数。

3.TensorFlow中的张量

张量是TensorFlow中基本的数据单元,而神经网络的运算可以看成张量在不同计算结点之间的流动,这也就不难理解TensorFlow名称的由来了。

虽然张量的物理与数学背景高深,但在TensorFlow这样的工程应用中却可以通过简单的多维数组进行表达。在TensorFlow中每个张量除了其包含的数值外,还有两个重要属性。

◎   dtype:数值分量的类型,比如float32、int32、string等。

◎   shape:张量的形状。比如[]表示零阶张量,[d]表示d维空间的一阶张量,[d1, d2]表示二阶张量。

TensorFlow中允许张量各阶的维度数不同,比如在二阶张量shape=[d1, d2]中,d1和d2可以取不同的整数。

开发者主要通过四种方式定义TensorFlow中的张量:Variable、constant、placeholder、SparseTensor,代码如下。

>>> import tensorflow as tf               # 引入TensorFlow开发包

 

# 零阶浮点数张量

>>> circle_rate = tf.Variable(3.14159265359, tf.float64)     

# 一阶字符串张量

>>> hellos = tf.Variable(["Hello", “Hi”, “How are you”], tf.string) 

# 二阶整数张量

>>> squares = tf.Variable([ [4, 9], [16, 25] ], tf.int32)

 

>>> print(hellos)                         # 查看张量属性

<tf.Variable 'Variable:0' shape=(3,) dtype=string_ref>

 

像Numpy中的ndarray、Python中的list等类似多维数组对象一样,TensorFlow中的tensor也支持slicing、reshape等常用操作。

>>> squares[1, 0]                         # 读取[1, 0]索引处的分量

>>> squares[:, 0]                         # 读取第二阶索引为0处的分量

 

>>> tf.reshape(hellos, [-1, 1])           # 转换hellos为新形状[-1, 1]

 

>>> rank_three = tf.ones([3, 4, 3])        # 初始化一个全1的三阶张量

>>> zeros = tf.zeros(rank_three.shape)     # 初始化一个全零张量

 

>>> tf.enable_eager_execution()

>>> tf.executing_eagerly()                # 开启eagerly模式

>>> tf.rank(rank_three)                   # 查询张量阶数

<tf.Tensor: id=11, shape=(), dtype=int32, numpy=3> # numpy=3 表示阶数为3

 

注意:在初学或调试时开启eagerly模式可以随时查看TensorFlow表达式的执行结果,在生产环境中开发者应该编码管理Session的执行以提高执行效率,后续会逐步介绍。

在上述代码中,-1、冒号“:”逗号“,”“shape”等符号的意义与Numpy的ndarray相同,不再赘述。

由于list、ndarray等数组形式也可以用来表达张量,在TensorFlow中它们被称为“Tensor-like objects”,在很多接口中与tf.Tensor类型的参数通用。

9.2.2  开发架构

如果仅仅用“张量流”描述TensorFlow架构对于程序员来说太过简单,官网上如图9-11所示的TensorFlow开发栈给出了一个非常好的鸟瞰视角。

 

图9-11  TensorFlow开发栈

对于TensorFlow使用者来说,以图9-11从上到下的顺序学习是比较好的选择。Estimators中提供了若干封装模型,在使用方式上类似于scikit-learn;中层的Layers、Datasets、Metrics接口给予开发者更多、更细致的模型控制能力;底层的其他接口包括各种数学封装、硬件利用、分布式执行等接口。除了原生的Python接口,TensorFlow在低层接口还支撑C 、Java、Go等其他语言。由于本书主题是模型与算法的应用,主要讲述的是图中左上五部分的开发应用。