阅读本文大概需要 16 分钟。
在本文中将介绍与我的毕设论文演示案例相关的TensorFlow的一些基础知识,包括张量、计算图、操作、数据类型和维度以及模型的保存,接着在第二部分,本文将介绍演示案例代码中用到的一些TensorFlow 2.0中的高阶API,代码中不会涉及像TensorFlow 1.x版本中的Session等一些较为复杂的东西,所有的代码都是基于高阶API中的tf.keras.models来构建的(具体模型构建使用Sequential按层顺序构建),可以大大的方便读者更好的理解代码。
需要注意的一点,本论文中所实现的两个案例均在本机CPU上进行运算,对于更大数量级的数据训练建议采用添加GPU的方法或者托管在Google cloud、AWS云平台上进行数据的处理。
1.1 基础知识概述
第一次看到TensorFlow这个名词,第一反应是去翻译一下这代表什么意思,通过查阅相关字典可以知道,Tensor被翻译为张量,Flow被翻译为流或者流动,组合起来TensorFlow可以被翻译为张量流。那什么是张量,什么又是流呢?
一般来将,把任意维度的数据称为张量,比如说一维数组(任意一门编程语言里都会学到一维数组的概念)、二维矩阵(我们在线性代数中学过关于矩阵的概念,这里不做赘述)以及N维数据。而流是指让数据在不同的计算设备上进行传输并计算(因为只有Tensor形式的数据可以实现在不同的设备之间进行传递)。
总结起来,我们可以认为TensorFlow的意思就是:让Tensor类型的数据在各个计算设备之间进行流动并完成计算。那为什么要让数据流动起来呢?Tensor类型又具体包括什么呢?接下来先来看一段演示代码:
# 将通过清华镜像下载的tensorflow包导入
import tensorflow as tf
a = tf.constant([[1.0,-2],[-3,4]])
print(a)
控制台输出结果如下:
tf.Tensor(
[[ 1. -2.]
[-3. 4.]], shape=(2, 2), dtype=float32)```
在上述代码中规定了一个2*2的矩阵,并将其打印在控制台。通过结果可以发现控制台输出的Tensor里面有三个参数:
【注】:在上述对于代码部分的解释中提到一个名词二阶张量,接下来将通过表格的形式来区分一下标量、向量、矩阵的阶数的细微差异:
表1-1 标量向量和矩阵的阶数
rank(阶) | 实例 | 例子 |
---|---|---|
0 | 标量(只有大小) | a=1 |
1 | 向量(有大小和方向) | b=[1,1,1,1] |
2 | 矩阵(数据表) | c=[[1,1],[1,1]] |
3 | 3阶张量(数据立体) | d=[[[1],[1]],[[1],[1]]] |
n | n阶 | n层括号 |
简单解释一下,阶指的就是维度,它与矩阵的阶不同。
举个例子,对于a=[[1,1,1],[2,2,2],[3,3,3]]从矩阵的角度看,这是一个3*3的方阵,也就是说它的阶数为3,而从张量的角度看,它的阶数为2,即维度为2,因为它只有两层中括号。
首先来看看TensorFlow官网中的这幅图,一方面是帮助我们理解流的概念,另一方面是为我们理解图的概念做下铺垫。
【注】TensorFlow官网中的动图演示请参考如下网址:
http://www.shipudong.com/2020/03/24/bi-ye-she-ji-nei-rong-bu-chong/
图1.1 TensorFlow官网流图演示
将图一般分为两种,包括动态计算图和静态计算图。我以中铁某局修建地铁为例来讲解这两种图的区别:
修建一条地铁需要设计图纸和施工队:
第一种情况,当设计师在设计图纸的时候(包括隧道走向、站点设置等,具体细节不予赘述)施工队什么也不干,必须等到设计工作完成之后,施工队才开始工作,(我们可以把这种情况理解为计算机中的同步方式,把设计工作和施工操作看作两个任务,当前任务未完成之前,不能进行其他操作)也就是说设计工作和具体施工完全分开,这就是所谓的静态计算图,我们称能够支持静态计算图的为静态框架,主要包括TensorFlow、Theano等;
第二种情况,设计工作和施工操作一起进行,设计方要求开凿隧道,施工队立即完成任务,如此下去,一经设计方下达任务,施工队必须立即完成操作,如此良性循环直到项目完成(我们把这种情况理解为计算机中的异步方式),这就是所谓的动态计算图,我们称能够支持动态计算图的为动态框架,主要包括Torch等。
在了解了动态计算图和静态计算图的例子之后,我们很明显的可以看出两种图的差异:静态计算图在未执行之前就必须定义好执行顺序和内存分配,简单来说,在程序未执行之前就知道了所有操作,有助于较快地执行计算操作;相比动态计算图,每次的执行顺序规划和内存分配都是局部的,并非全局最优,虽然灵活性较静态计算图有很大提升,但是代价太高,所以在现在流行的框架中,还是以静态框架为主,比如本论文中的由谷歌公司开源的TensorFlow。
从图1.1可以观察到,数据一经输入(Input),会被进行不同的操作,首先会将数据进行预处理(比如图中的reshape操作),接着给处理好的数据中加入非线性操作(ReLU操作)等,使数据更符合自然界中的普遍关系,然后我们根据输入数据的类型进而采取比较合适的交叉熵函数(Crossentropy),用来衡量真实值与预测值的偏差,最后我们我们将根据项目真实情况选取合适的优化器(图中选用的为sgd,即随机梯度下降法)。图中的一个节点就代表一个操作,我们从计算图中了解到,TensorFlow属于静态计算图,也就是说在未执行前就已经定义好了执行的顺序,简单来讲,图中的各个操作之间是存在执行顺序的,而这些操作之间的依赖就是图中的边。我们以一个非常简单的图示来讲解这个关系,首先来看一段代码:
# 定义变量a
a = tf.Variable(1.0,name="a")
# 定义操作b为a+1
b = tf.add(a,1,name="b")
# 定义操作c为b+1
c = tf.add(b,1,name="c")
# 定义操作d为b+10
d = tf.add(b,10,name="d")
上述代码认为a、b、c和d均为需要进行的操作,下图中的x表示一个常数,值为1。
图1.2 操作之间的依赖关系
首先定义a=1.0,b=a+1,即b=2.0,以此类推,c=3.0,d=11.0,可以这样理解,操作b的进行需要依赖操作a,操作c的进行需要依赖操作b的完成,操作d的进行需要依赖操作b,且操作c和d之间没有依赖关系。
对于任意一门编程语言都会有数据类型,区别就在于每一门编程语言定义不同数据类型的方式不一样,在本章开始的时候了解过,在TensorFlow中,用张量(Tensor)来表示数据结构,接下来我们就将TensorFlow中的的数据类型与Python中的数据类型作以简单的对比,并通过表格的形式清晰的展现出来:
表1-2 TensorFlow和Python中数据类型的对应关系
TensorFlow数据类型 | Python中的表示 | 说明 |
---|---|---|
DT_FLOAT | tf.float32 | 32位浮点数 |
DT_DOUBLE | tf.float64 | 64位浮点数 |
DT_INT8 | tf.int8 | 8位有符号整数 |
DT_INT16 | tf.int16 | 16位有符号整数 |
DT_INT32 | tf.int32 | 32位有符号整数 |
DT_INT64 | tf.int64 | 64位有符号整数 |
DT_UINT8 | tf.uint8 | 8位无符号整数 |
DT_UINT16 | tf.uint16 | 16位无符号整数 |
DT_STRING | tf.string | byte类型数组 |
DT_BOOL | tf.bool | 布尔型 |
DT_COMPLEX64 | tf.complex64 | 复数类型,由32位浮点数的实部和虚部组成 |
DT_COMPLEX128 | tf.complex128 | 复数类型,由64位浮点数的实部和虚部组成 |
DT_QINT8 | tf.qint8 | 量化操作的8位有符号整数 |
DT_QINT32 | tf.quint32 | 量化操作的32位有符号整数 |
DT_QUINT8 | tf.quint8 | 量化操作的8位无符号整数 |
维度的相关概念,在上述文章中的张量部分已经详细讲过,此处不再赘述。一般来说张量的阶数(维度)就是看有几层中括号,接下来看一段代码:
import tensorflow as tf
value_shape_0 = tf.Variable(1002)
value_shape_1 = tf.Variable([1,2,3])
value_shape_2 = tf.Variable([[1,2,3],[3,4,5],[5,6,7]])
print(value_shape_0.get_shape())
print(value_shape_1.get_shape())
print(value_shape_2.get_shape())
控制台输出结果如下:
()
(3,)
(3, 3)
(2, 3, 2)
代码解释:
当我们完成一个案例之后,我们想要把当前训练好的模型保存下来(保存模型是指把训练的参数保存下来),方便我们之后重新使用。当我们重新使用的时候,我们只需要重新载入模型即可。
首先我们来看一下保存模型的代码:
# 保存模型
model.save("my_model.h5")
在关于MNIST手写字的例子中将我们训练好的模型保存下来,并命名为my_model.h5,接下来我们看一段载入模型的代码:
# 加载模型文件
model = tf.keras.models.load_model("my_model.h5")
同样是在MNIST手写字的例子中,我们将保存好的模型导入,并通过matplotlib函数画出模型图,具体模型图我会在本毕设系列推文的案例讲解部分中进行展示。
2. 相关API介绍
一般来讲,TensorFlow共有5个不同的层次结构,从低到高分别是硬件层、内核层、低阶API、中阶API、高阶API,我们对每一层作以简单的介绍:
图2.1 API详解
上述内容是我们对TensorFlow中的API做了宏观的描述,接下来我将着重介绍5个代码案例中较为重要的API:
model = tf.keras.models.Sequential([
# 里面是添加的模型层,比如说卷积层、池化层等
])
图2.2 TensorFlow官网tf.keras.layers部分API
以下是代码演示:
tf.keras.layers.Conv2D(input_shape = (28,28,1),filters = 32,kernel_size = 5,strides = 1,padding = "same",activation = "relu"), # 28*28
mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()
图2.3 compile函数官网介绍
具体代码如下:
tf.keras.layers.Conv2D(input_shape = (28,28,1),filters = 32,kernel_size = 5,strides = 1,padding = "same",activation = "relu"), # 28*28# 编译模型 优化器--adam (sgd--随机梯度下降法)损失函数---均方误差 metrics---训练过程中计算准确率accuracy
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',metrics=['accuracy'])
图2.4 fit函数官网介绍
具体代码如下:
# 训练模型 epochs --- 迭代周期 batch_size默认为32
model.fit(x_train, y_train, batch_size=32,epochs=5)