前言

  从老师那里得知,未来我们使用的比较多的都是深度学习,因此想要系统地学习一下TensorFlow的使用,之前都是半半拉拉的初学而已。

TensorFlow入门

张量

  在TensorFlow中,张量可以被简单地理解为多维数组。其中零阶张量可以理解为标量,也就是一个数;第一阶张量为向量,也就是一个一维数组;第n阶张量可以理解为一个n维数组。TensorFlow中张量中没有真正的保存数字,它保存的是如何得到这些数字的计算过程,获得是对结果的一个引用

会话

  在TensorFlow中,是使用会话(session)来执行定义好的运算。会话拥有并管理TensorFlow程序运行时的所有资源。所有计算完成之后需要关闭会话来帮助系统回收资源,否则可能出现资源泄露
  可以使用python的上下文管理器来使用会话。

1
2
3
4
with tf.Session() as sess:
sess.run()
# 不需要调用"session.close()"函数关闭会话
# 当上下文退出时会话关闭和资源释放叶自动完成了

  TensorFlow中会有着这样的机制,它不会自动生成默认的会话,而是手动指定。当默认的会话被指定之后可以通过tf.Tensor.eval函数来计算一个张量的取值。以下代码展示了通过设定默认会话计算张量的取值。

1
2
3
4
5
sess=tf.Session()

# 以下两个命令有着相同的功能
print(sess.run(result))
print(result.eval(session=sess))

  TensorFlow提供了一种在交互式环境下直接构建默认会话的函数。这个函数就是tf.InteractiveSession。使用这个函数会自动将生成的会话注册为默认会话。

1
2
3
sess=tf.InteractiveSession()
print(result.eval())
sess.close()

  并且也可以通过ConfigProto Protocol Buffer来配置需要生成的会话,下面给出了通过ConfigProto配置会话的方法;

1
2
3
4
config = tf.ConfigProto(allow_soft_placement=True,log_device_placement=True)

sess1=tf.InteractiveSession(config=config)
sess2=tf.Session(config=config)

  通过ConfigProto可以配置类似并行的线程数、GPU分配策略、运算超时时间等参数,在这些参数中,最常用的有两个。第一个是allow_soft_placement,这是一个布尔型的参数,当它为True时,在以下任意一个条件成立时,GPU上的运算可以放到CPU上进行:

  1. 运算无法在GPU上执行。
  2. 没有GPU资源(比如运算被放在第二个GPU上运行,但是机器上只有一个GPU)
  3. 运算输入包含对CPU计算结果的引用

  这个参数一般默认为False,但是为了使得代码的可移植性更强,在有GPU的环境下,这个参数一般会被设置为True。
  第二个使用的比较多的配置参数是log_device_placement。这也是一个布尔型的参数,当它为True是日志中会详细记录以方便调试。而在生产环境中将该参数设置为Flase可以减少日志量。

前向传播算法介绍

神经网络前向传播算法示意图

  前向传播算法通过矩阵乘法的方式表达。在TensorFlow中矩阵乘法是非常容易实现的。以下TensorFlow程序实现了如图所示的前向传播算法过程。

1
2
a = tf.matmul(x,w1)
y = tf.matmul(a,w2)

TensorFlow变量

  在TensorFlow中变量(tf.Variable)的作用就是保存和更新神经网络中的参数。变量一定是要初始化的,一般使用随机数给变量进行初始化,下面一段代码就是声明一个$2\times3$的矩阵变量的方法:

1
2
# 产生2*3的矩阵,矩阵中的元素是均值为0,标准差为2的随机数。
weights = tf.Variable(tf.random_normal([2,3],stddev=2))

  这段代码调用了TensorFlow变量的声明函数tf.Variable。在变量声明函数中给出了初始化这个变量的方法。tf.random_normal函数是通过参数mean来指定平均值,当没有指定的时候默认为0。下表为Tensorflow支持的部分随机数生成函数(随机数生成函数)

函数名称 随机数分布 主要参数
tf.random_normal 正态分布 平均值、标准差、取值类型
tf.truncated_normal 正态分布,但如果随机出来的值偏离平均值超过2个标准差,那么这个数将会被重新随机 平均值、标准差、取值类型
tf.random_uniform 均匀分布 最小、最大取值、取值类型
tf.random_gamma Gamma分布 形状参数alpha、尺度参数beta、取值类型

  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提供了一种更加便捷的方式来完成变量初始化过程。通过tf.global_variables_initalizer函数实现初始化所有变量的过程:

1
2
init_op = tf.global_variables_initializer()
sess.run(init_op)

反向传播算法

  反向传播算法实现了一个迭代的过程。在每次迭代的开始首先需要选取一小部分训练数据,这一小部分数据叫做一个batch。然后,这个batch的样例会通过前向传播算法得到神经网络模型的预测结果。因为训练数据都有数据标注,所以可以计算出当前神经网络模型的预测答案和标准答案之间的差距(LOSS),最后,基于loss反向传播算法会相应更新神经网络参数的取值,使得在这个batch上神经网络模型的预测结果和真实答案更加接近。

  为了避免数据过大而导致的计算图过大,TensorFlow提供了placeholder机制用于提供输入数据。placeholder相当于定义了一个位置,这个位置中的数据在程序运行时再指定。在placeholder定义时,这个位置上的数据类型是需要指定的。和其他张量一样,placeholder的类型是不可改变的。

1
2
3
4
5
x = tf.placeholder(tf.float32,shape=(None, 2),name="input")

......

print(sess.run(y,feed_dict={x:[ [] , [] , [] ]}))

  在得到一个batch的前向传播结果之后,需要定义一个损失函数来刻画当前的预测值和真实答案之间的差距。然后通过反向传播算法来调整神经网络参数的取值使得差距可以被缩小。以下代码定义了一个简单的损失函数,并通过TensorFlow定义了反向传播的算法:

1
2
3
4
5
6
7
8
9
# 使用sigmoid函数使y转化为0~1之间的概率,y为预测是正样本的概率
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)))
# 定义学习率
learning_rate = 0.001
# 定义反向传播算法来优化神经网络中的参数
train_step=tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

  在以上代码中,cross_entropy定义了真实值和预测值之间的交叉熵,这是分类问题中的一个损失函数。第二行train_step定义了反向传播的优化方法。常用的优化方法有三种:

  1. tf.train.GradientDescentOptimizer
  2. tf.train.AdamOptimizer
  3. tf.train.MomentumOptimizer

  在定义了反向传播算法之后,通过运行sess.run(train_step)就可以对所有在GraphKeys.TRAINABLE_VARIABLES集合中的变量进行优化,使得在当前batch下损失函数更小。

神经网络样例程序

  最后,叙述一下训练神经网络的三个步骤:

  1. 定义神经网络的结构和前向传输的输出结果
  2. 定义损失函数以及选择反向传播优化的算法
  3. 声称会话并且在训练数据上反复运行反向传播优化算法

  这里列出我们的样例程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import tensorflow as tf

# Numpy是一个科学计算的工具包,可以通过NumPy工具包生成模拟数据集
from numpy.random import RandomState

# 定义训练数据batch的大小
batch_size = 8

# 定义神经网络的参数,这里还是沿用3.4.2小节中给出的神经网络结构
w1 = tf.Variable(tf.random_normal([2,3],stddev=1,seed=1),name="w1")
w2 = tf.Variable(tf.random_normal([3,1],stddev=1,seed=1),name="w2")

'''
#在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(1-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 = 6400
X = rdm.rand(dataset_size,2)
# 定义规则来给出样本的标签,在这里所有x1+x2<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("After w1:\n",sess.run(w1))
print("After w2:\n",sess.run(w2))
print("="*80)

#设定训练的轮数
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))

print("="*80)
print("final w1: \n",sess.run(w1))
print("final w2: \n",sess.run(w2))

运行输出