刚刚做完一个项目,使用的Keras+TensorFlow框架,但是主题语言大多是用Keras写的,因为Keras封装的很好,总觉得写起来比较简单,想系统的学习一个主流框架的使用方式,目前主流框架有TensorFlow、Pytorch、Caffe等等,经过多方面考虑,打算系统学习TensorFlow,大打下较好的基础,从今天开始!
内容列表
TensorFlow环境搭建
提前说明:我使用的TensorFlow是搭建在Windows操作系统下的CPU版本,并且使用在Anaconda的环境下,我下载的是Anaconda3的版本,内置Python版本为3.7.1,所以酌情参考。
首先打开Anaconda Prompt:
进入到命令行控制之后使用conda list
查看conda的下载列表,如果没有TensorFlow那么使用如下命令安装TensorFlow:
pip install tensorflow
默认安装的是最新版本的,也可以手动选择版本,由于我下载的时候速度很快,就没有选择清华镜像源,读者可根据情况自行选择。
这样就显示tensorFlow安装完毕了,接下来查看是否可以使用TensorFlow,激活TensorFlow:
activate tensorflow
如果编译环境由base变换到TensorFlow那么说明你的TensorFlow激活成功了,如下图:
这时候先别急着启动python,先使用conda list
查看TensorFlow环境下的各种需求包是否齐全,如果没有类似于numpy这种TensorFlow的需求包,那么在之后会出现报错,输入以下代码,在TensorFlow环境下安装需求包:
conda install tensorflow
安装过后再查看conda list
可以看到需求包已经安装好了:
之后再启动python,然后输入import tensorflow as tf
,未出现报错,那么你的TensorFlow就安装好啦!下图演示:
TensorFlow入门介绍
TensorFlow计算模型——计算图
TensorFlow 从名字就可以看出来他最重要的两个概念——Tensor和Flow,Tensor是他的数据结构,也就是张量,可以被简单地理解为多维数组;Flow是流的意思,它直观地展现了张量之间通过计算相互转化的过程。
所有 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
在上面代码所示过程中,TensorFlow 会自动将定义的计算转化为计算图上的节点。在TensorFlow 程序中,系统会自动维护一个默认的计算图,通过 tf.get_default_graph 函数可以获取当前默认的计算图。以下代码示意了如何获取默认计算图以及如何查看一个运算所属的计算图 :
#通过 a.graph 可以查看张量所属的计算图。因为没有特意指定,所以这个计算图应该等于当前默认的计算图。所以下面这个操作输出值为 True 。
print(a.graph is tf.get_default_graph())
'''
输出为:
True
'''
除了使用默认的计算图, TensorFlow 支持通过 tf.Graph 函数来生成新的计算图。不同计算图上的张量和运算都不会共享。以下代码示意了如何在不同计算图上定义和使用变量:
import tensorflow as tf
g1 = tf.Graph()
with g1.as_default():
# 在计算阁 gl 中定义变量"v",并设置初始值为 0 。
v = tf.get_variable("v", initializer=tf.zeros_initializer(), shape=[1])
g2 = tf.Graph()
with g2.as_default():
# 在计算阁 g2 中定义变量"v",并设置初始值为 1 。
v = tf.get_variable("v", initializer=tf.ones_initializer(), shape=[1])
# 在计算图 g1 中读取变盘"v"的取值 。
with tf.Session(graph=g1) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope("", reuse=True):
# 在计算图 g1 中,变量"v"的取值应该为 0,所以下面这行会输出[0.]。
print(sess.run(tf.get_variable("v")))
# 在计算图 g2 中读取变量"v"的取值。
with tf.Session(graph=g2) as sess:
tf.global_variables_initializer().run()
with tf.variable_scope ("", reuse=True):
# 在计算图 g2 中,变量"v"的取值应该为 1,所以下面这行会输出[1.]。
print(sess.run(tf.get_variable("v")))
'''
输出为:
[0.]
[1.]
'''
可以看到当运行不同计算图时,变量 v 的值也是不一样的。TensorFlow 中的计算图不仅仅可以用来隔离张量和计算,它还提供了管理张量和计算的机制。下面是TensorFlow 中维护的几个自动维护的集合列表:
集合名称 | 集合内容 | 使用场景 |
---|---|---|
tf.GraphKeys.VARIABLES | 所有变量 | 持久化 TensorFlow 模型 |
tf.GraphKeys.TRAINABLE_VARIABLES | 可学习的变量(一般指神经网络中的参数) | 模型训练、生成模型可视化内容 |
tf.GraphKeys.SUMMARIS | 日志生成相关的张量 | TensorFlow 计算可视化 |
tf.GraphKeys.QUEUE_RUNNERS | 处理输入的 QueueRunner | 输入处理 |
tf.GraphKeys.MOVING_AVERAGE_VARIABLES | 所有计算了滑动平均值的变量 | 计算变量的滑动平均值 |
TensorFlow数据模型——张量
TensorFlow中的张量与Numpy中的数组不同,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)
'''
以上代码中第一个属性:“add:0”表示为“节点所进行运算的名字:来自节点的第几个输出”;第二个属性:“shape=(2,0)”表示为张量的维度,例子中为一维数组,长度为2;第三个属性:“dtype=float32”表示为张量的类型,需要注意不同类型在进行运算时会报错,例如int与float就不能匹配。
TensorFlow运行模型——会话
会话拥有并管理 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
# 创建一个会话。
sess = tf.Session()
# 使用这个创建好的会话来得到关心的运算的结果。比如可以调用 sess.run(result) ,
# 来得到 3.1 节样例中张量 result 的取值。
print(sess.run (result))
# 关闭会话使得本次运行巾使用到的资源可以被释放。
sess.close ()
'''
输出为:
[3. 5.]
'''
第二种是通过 Python 上下文管理器的机制,只要将所有的计算放在 “ with”的内部就可以 。:
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
# 创建一个会话,并通过 Python 中的上下文管理器来管理这个会话。
with tf.Session() as sess:
# 使用创建好的会话来计算关心的结果。
print(sess.run(result))
# 不需要再调用“Session.close()”函数来关闭会话,当上下文退出时会话关闭和资源释放也自动完成了。
'''
输出为:
[3. 5.]
'''
TensorFlow 会自动生成一个默认的计算图,如果没有特殊指定,运算会自动加入这个计算图中。 TensorFlow 中的会话也有类似的机制,但 TensorFlow 不会自动生成默认的会话,而是需要手动指定 。 当默认的会话被指定之后可以通过 tf.Tensor.eval 函数来计算一个张量的取值:
sess = tf.Session()
# 以下两个命令有相同的功能。
print(sess.run(result))
print(result.eval(session = sess))
'''
输出为:
[3. 5.]
[3. 5.]
'''
TensorFlow实现神经网络
具体的神经网络细节这里就不赘述了,可以看之前的深度学习笔记,这里有一个TensorFlow的神经网络训练演示图,很有帮助:TensorFlow playground
神经网络参数与TensorFlow变量
TensorFlow的随机生成函数如下表展示:
函数名称 | 随机数分布 | 主要参数 |
---|---|---|
tf.random_normal | 正态分布 | 平均值 、 标准差、取值类型 |
tf.truncated_normal | 正态分布,但如果随机出来的值偏离平均值超过2个标准差,那么这个数将会被重新随机 | 平均值、标准差、取值类型 |
tf.random_uniform | 均匀分布 | 最小 、最大取值 , 取值类型 |
tf.random_gamma | Gamma 分布 | 形状参数 alpha、尺度参数 beta 、 取值类型 |
实例展示:
weights= tf.Variable(tf.random_normal([2, 3], stddev=2))
上面代码会产生一个2x3的矩 阵,矩阵中的元素是均值为0,标准差为2的随机数。
TensorFlow 也支持通过常数来初始化一个变量。下表给出了 TensorFlow 中常用的常量声明方法:
函数名称 | 功能 | 样例 |
---|---|---|
tf.zeros | 产生全0的数组 | tf.zeros([2, 3], int32) -> [[0, 0, 0], [0, 0, 0]] |
tf.ones | 产生全1的数组 | tf.ones([2, 3], int32) -> [[1, 1, 1], [1, 1, 1]] |
tf.fill | 产生一个全部为给定数字的数组 | tf.fill([2, 3], 9) -> [[9, 9, 9], [9, 9, 9]] |
tf.constant | 产生一个给定值的常量 | tf.constant([1, 2, 3]) -> [1, 2, 3] |
除了使用随机数或者常数,TensorFlow 也支持通过其他变量的初始值来初始化新的变量。以下代码给出了具体的方法:
weights= tf.Variable(tf.random_normal([2, 3], stddev=2))
w2 = tf.Variable(weights.initialized_value())
w3 = tf.Variable(weights.initialized_value() * 2.0)
以上代码中,w2 的初始值被设置成了与 weights 变量相同。 w3 的初始值则是 weights
初始值的两倍。
前向传播的简化过程
知道了上述的常数与随机数生成方法,就可以大致写出前向传播的代码了:
import tensorflow as tf
# 声明 w1, w2 两个变盘。这里还通过 seed 参数设定了随机种子。这样可以保证每次运行得到的结果是一样的。
w1 = tf.Variable(tf.random_normal((2, 3), stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal((3, 1), stddev=1, seed=1))
# 暂时将输入的特征向量定义为一个常量。注意这里 x 是一个 1x2 的矩阵。
x = tf.constant([[0.7, 0.9]])
# 通过前向传播算法获得神经网络的输出。
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
sess = tf.Session()
# 这里不能直接通过 sess.run(y) 来获取 y 的取值,因为 w1 和 w2 都还没有运行初始化过程。以下两行分别初始化了 w1 和 w2 两个变量。
sess.run(w1.initializer) #初始化 w1.
sess.run(w2.initializer) #初始化 w2 。
print(sess.run(y))
sess.close ()
'''
输出为:
[[3.957578]]
'''
为了解决变量过多的问题, TensorFlow 提供了一种更加便捷的方式来完成变量初始化过程 。 以下程序展示了通过 tf.global_variables_initializer 函数实现初始化所有变量的过程:
init_op = tf.global_variables_initializer()
sess.run(init_op)
类似张量,维度(shape)和类型 (type) 也是变量最重要的两个属性。和大部分程序语言类似,变量的类型是不可改变的。一个变量在构建之后,它的类型就不能再改变了。比如在上面给出的前向传播样例中,w1 的类型为 random_normal 结果的默认类型 tf.float32,那么它将不能被赋予其他类型的值。以下代码将会报出类型不匹配的错误 :
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1) , name="w1")
w2 = tf.Variable(tf.random_normal([2, 3], dtype=tf.float64, stddev=1),
name="w2")
w1.assign(w2) #assign函数为将w1的值指定成w2
'''
程序将报错:
Input 'value' of 'Assign' Op has type float64 that does not match type float32 of argument 'ref'.
'''
#修改后:
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1), name='w1')
w2 = tf.Variable(tf.random_normal([2, 3], stddev=1), name='w2')
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(w1.assign(w2)))
'''
输出为:
[[-0.7800399 -0.24346626 2.6045554 ]
[-0.9888551 0.06832344 -1.5321901 ]]
'''
维度是变量另一个重要的属性。和类型不大一样的是,维度在程序运行中是有可能改变的,但是需要通过设置参数 validate_shape=False。下面给出了一段示范代码:
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1), name="w1")
w2 = tf.Variable(tf.random_normal([2, 2], stddev=1), name="w2")
tf.assign(w1, w2)
'''
程序将报错:
ValueError: Dimension 1 in both shapes must be equal, but are 3 and 2 for ’ Assign_1' (op:’Assign’) with input shapes : [2, 3], [2, 2].
'''
#修改后:
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(tf.assign(w1, w2, validate_shape=False)))
'''
输出为:
[[-1.2665275 -1.3673254 ]
[-0.28818193 -0.6087159 ]]
'''
不过虽然 TensorFlow 支持更改变量的维度,但是这种用法在实践中比较罕见 。
通过TensorFlow训练神经网络模型
上图就是神经网络优化流程图,每次迭代都会选取一个batch,通过前向传播算出loss,再通过loss反向更新权重,使得这个batch得到的预测结果与真实结果更接近。
之前使用过常量来表达一个batch的数据,但是一般来说,一个神经网络的训练过程会需要经过几百万轮甚至几亿轮的迭代,这样计算图就会非常大,而且利用率很低。为了避免这个问题,TensorFlow 提供了 placeholder 机制用于提供输入数据。placeholder 相当于定义了一个位置,这个位置中的数据在程序运行时再指定。这样在程序中就不需要生成大量常量来提供输入数据,而只需要将数据通过 placeholder 传入TensorFlow 计算图 。下面给出了通过 placeholder 实现前向传播算法的代码:
import tensorflow as tf
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1))
# 定义 placeholder 作为存放输入数据的地方。这里维度也不一定要定义。但如果维度是确定的,那么给出维度可以降低出错的概率。
x = tf.placeholder(tf.float32, shape = (1, 2), name = "input")
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
sess = tf.Session()
init_op = tf.global_variables_initializer()
sess.run(init_op)
print(sess.run(y))
'''
程序将报错:
InvalidArgumentError: You must feed a value for placeholder tensor 'input' with dtype float and shape [1,2]
'''
#修改后
print(sess.run(y, feed_dict={x:[[0.7, 0.9]]}))
'''
输出为:
[[0.9562113]]
'''
在这段程序中替换了原来通过常量定义的输入 x。在新的程序中计算前向传播结果时,需要提供一个 feed_dict 来指定 x 的取值 。 feed_dict 是一个字典(dict),在字典中需要给出每个用到的 placeholder 的取值。如果某个需要的 placeholder 没有被指定取值,那么程序在运行时将会报错,可以在字典中给入多个值,来模拟一个batch:
# 因为修改了给入的大小,所以需要修改shape()
x = tf.placeholder(tf.float32, shape = (3, 2), name = "input")
...
print(sess.run(y, feed_dict={x:[[0.7, 0.9],[0.1, 0.4], [0.5, 0.8]]}))
'''
输出为:
[[-1.285603 ]
[-0.08650386]
[-0.862041 ]]
'''
当前向传播做完之后,接下来就是使用loss函数进行后向传播,下面是一个简单的损失函数与反向传播算法:
# 使用 sigmoid 函数将 y 转换为 0~ 1 之间的数值。转换后 y 代表预测是正样本的概率, 1-y 代表预测是负样本的概率 。
y=tf.sigmoid(y)
# 定义损失函数来刻画预测值与真实值得差距。
cross_entropy = -tf.reduce_mean(
y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0)) #y_表示真实概率,clip_by_value函数是将y限制在1e-10与1.0之间
+(1-y_) * tf.log(tf.clip_by_value(1-y, 1e-10, 1.0)))
# 定义学习率
learning_rate = 0.001
# 定义反向传播算法来优化神经网络中的参数。
train_step =\ #\表示换行符
tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
完整神经网络样例
使用完整程序来解决一个二分类问题:
import tensorflow as tf
# NumPy 是一个科学计算的工具包,这里通过 NumPy 工具包生成模拟数据集。
from numpy.random import RandomState
# 定义训练数据 batch 的大小 。
batch_size = 8
# 定义神经网络的参数,这里还是沿用之前给出的神经网络结构。
w1 = tf.Variable(tf.random_normal([2, 3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3, 1], stddev=1, seed=1))
# 在 shape 的一个维度上使用 None 可以方便使用不同的 batch 大小。在训练时需要把数据分成比较小的 batch , 但是在测试时,可以一次性使用全部的数据。当数据集比较小时这样比较方便测试,但数据集比较大时,将大量数据放入一个 batch 吁能会导致内存溢出。
x = tf.placeholder(tf.float32, shape=(None, 2), name= 'x-input')
y_ = tf.placeholder(tf.float32, shape=(None, 1), name= 'y-input')
# 定义神经网络前向传播的过程。
a = tf.matmul(x, w1)
y = tf.matmul(a, w2)
# 定义损失函数和反向传播的算法。
y=tf.sigmoid(y)
cross_entropy = -tf.reduce_mean(
y_ * tf.log(tf.clip_by_value(y, 1e-10, 1.0))
+ (1 - y_) * tf.log(tf.clip_by_value(1 - y, 1e-10, 1.0)))
train_step = tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
# 通过随机数生成一个模拟数据集。
rdm = RandomState(1)
dataset_size = 128
X = rdm.rand(dataset_size, 2)
# 定义规则来给出样本的标签。在这里所有 x1+x2<1 的样例都被认为是正样本(比如零件合格),而其他为负样本(比如零件不合格)。和 TensorFlow playground中的表示法不大一样的地方是,在这里使用 0 来表示负样本,1 来表示正样本。大部分解决分类问题的神经网络都会采用 0 和 1 的表示方法。
Y = [[int(x1 + x2) < 1] for x1, x2 in X]
# 创建一个会话来运行 TensorFlow 程序。
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
# 初始化变量
sess.run(init_op)
print(sess.run(w1))
print(sess.run(w2))
'''
在训练之前神经网络参数的值:
[[-0.8113182 1.4845988 0.06532937]
[-2.4427042 0.0992484 0.5912243 ]]
[[-0.8113182 ]
[ 1.4845988 ]
[ 0.06532937]]
'''
# 设定训练的轮数。
STEPS = 5000
for i in range(STEPS):
# 每次选取 batch_size 个样本进行训练。
start = (i * batch_size) % dataset_size
end = min(start + batch_size, dataset_size)
# 通过选取的样本训练神经网络并更新参数。
sess.run(train_step,
feed_dict = {x:X[start:end], y_:Y[start:end]})
if i % 1000 == 0:
# 每隔一段时间计算在所有数据上的交叉煽并输出。
total_cross_entropy = sess.run(
cross_entropy, feed_dict = {x:X, y_:Y})
print("After %d training step(s), cross entropy on all data is %g" % (i, total_cross_entropy))
'''
输出结果:
After 0 training step(s), cross entropy on all data is 1.89805
After 1000 training step(s), cross entropy on all data is 0.655075
After 2000 training step(s), cross entropy on all data is 0.626172
After 3000 training step(s), cross entropy on all data is 0.615096
After 4000 training step(s), cross entropy on all data is 0.610309
'''
print(sess.run(w1))
print(sess.run(w2))
'''
在训练之后神经网络参数的值:
[[ 0.02476983 0.56948674 1.6921941 ]
[-2.1977348 -0.23668918 1.1143897 ]]
[[-0.45544705]
[ 0.49110925]
[-0.98110336]]
'''
以上程序实现了训练神经网络的全部过程。从这段程序可以总结出训练神经网络的过程可以分为以下三个步骤 :
- 定义神经网络的结构和前向传播的输出结果。
- 定义损失函数以及选择反向传播优化的算法。
- 生成会话(tf.Session)并且在训练数据上反复运行反向传播优化算法。
无论神经网络的结构如何变化,这三个步骤是不变的。
小结
在TensorFlow安装之后,了解TensorFlow的三个重要的模型,分别是计算模型,数据模型与运行模型,之后讲解神经网络在TensorFlow的具体实现过程,最后实现了一个简单的神经网络算法,以下是总结的几个小要点或者说是比较容易忽略的点:
- TensorFlow会自动将每一个运算转化为计算图上的节点,可以通过定义计算图来产生多个计算图。
- TensorFlow中运算得到的内容直接打印出来是张量的结构,而不是运算的结果。
- 张量之间的运算需要严格匹配数值的类型,不同类型之间不可以运算。
- 要想得到张量得到的具体数值需要运用会话(tf.Session),在每次sess.run()之后需要sess.close()释放资源,或者通过python中的上下文管理器with来管理会话,就不需要sess.close()了。
- 需要记住TensorFlow内的几个随机数生成函数与常数生成函数。
- 在定义随机数矩阵时,需要对大小(shape)进行把控,以得到自己想要的结果。
- 每次定义随机树矩阵之后都需要初始化全局变量(tf.global_variables_initializer())。
- 每次加入输入数据时,需要placeholder函数来定义数据,否则数据量太大计算图的节点会过多。
- 记住神经网络实现的三个步骤,无论神经网络结构如何变化,这三个步骤是不变的。