Tensorflow的基本开发步骤

本节通过一个简单的逻辑回归实例来熟悉Tensorflow的开发流程,整个流程可以总结为五个步骤:准备数据搭建模型迭代训练评价分析使用模型

准备数据

一般地,根据具体的任务,我们会得到一定的标注数据,我们通常会把数据拆分为训练集验证集测试集,训练集用来训练模型,验证集用来调参,测试集用来评估性能。这里,我们为了操作的简单性,自行生成一组数据集,这是关于$x$和$y$的简单线性关系,其中$x$和$y$的对应关系为 $y \approx 2x$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

train_X = np.linspace(-1, 1, 80)
#在y=2x的基础上加入噪声
train_Y = 2 * train_X + np.random.randn(*train_X.shape) * 0.3
plt.plot(train_X, train_Y, 'ro', label='Original data')
plt.legend()
plt.show()

eval_X = np.linspace(-1, 1, 10)
eval_Y = 2 * eval_X + np.random.randn(*eval_X.shape) * 0.3
test_X = np.linspace(-1, 1, 10)
test_Y = 2 * test_X + np.random.randn(*test_X.shape) * 0.3

png

从训练集的可视化图像中可以看出,我们刻意加入的噪声使得部分数据点随机地分布在了$y=2x$的直线两侧,而我们的目标正是通过数据这种近似的分布拟合出$y=2x$的线性关系。

搭建模型

搭建Tensorflow的模型分为两步:正向搭建反向搭建。正向搭建是自底向上逐层架起神经网络的过程,反向搭建是模型的优化过程。由于我们的任务本身很简单,在这里只需要搭建一个包含单一神经元的最简单神经网络就可以实现对$y \approx 2x$的线性关系的拟合。

1
2
3
4
5
6
7
8
9
10
11
12
13
import tensorflow as tf

#创建模型
X = tf.placeholder("float")
Y = tf.placeholder("float")
W = tf.Variable(tf.random_normal([1]), name="weight")
b = tf.Variable(tf.zeros([1]), name="bias")
z = tf.multiply(X, W) + b

#反向优化
cost = tf.reduce_mean(tf.square(Y - z))
learning_rate = 0.01
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

尽管上面的代码很简单,但足以说明一个完整的Tensorflow建模过程的主要步骤:

  1. 为输入数据和目标值(输出)定义占位符
  2. 将模型中的参数定义为变量(在训练过程中会不断地变更)
  3. 定义数据与参数的运算关系(包括神经网络的非线性计算)
  4. 定义损失函数,它是真实输出值与模型输出值的评价函数
  5. 选择优化器,用来最小化损失函数

其实,我们可以把占位符、变量、算子都当做是节点,通过运算关系将这些节点串联起来,这种串联具有一定的流动方向,仿佛是一张有向图,所谓的搭建模型其实就是画出数据的运算流程图。

迭代训练

前面构建好的模型如同搭建起了一套自来水的管道(静态的),训练的时候我们需要启动一个会话(动态的),在会话中分批次地把数据注入管道,并从管道网络中指定的节点处取得流出的数据。这个对于训练过程流出的数据就是损失loss,通常我们会把每次取到的这个值输出,以便观察训练过程是否朝着正确的方向(不断减小损失)不断迭代。

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
#初始化所有变量
init = tf.global_variables_initializer()
#定义训练相关的参数
training_epochs = 20
display_step = 2

predict_Y = None
plotdata = {"batchsize":[], "loss":[]}

saver = tf.train.Saver()
#启动session
with tf.Session() as sess:
sess.run(init)
#向模型注入数据
for epoch in range(training_epochs):
for (x, y) in zip(train_X, train_Y):
sess.run(optimizer, feed_dict={X:x, Y:y})
#显示训练中的详细信息
if epoch % display_step == 0:
loss = sess.run(cost, feed_dict={X:train_X, Y:train_Y})
print("Epoch:", epoch+1, "cost=", loss, "W=", sess.run(W), "b=", sess.run(b))
if not (loss == "NA"):
plotdata["batchsize"].append(epoch)
plotdata["loss"].append(loss)
print("Finished!")
print("cost=", sess.run(cost, feed_dict={X:train_X, Y:train_Y}), "W=", sess.run(W), "b=", sess.run(b))
predict_Y = sess.run(W)*train_X+sess.run(b)
#保存模型
saver.save(sess, "./model.ckpt")
Epoch: 1 cost= 0.9330804 W= [0.5178577] b= [0.46305528]
Epoch: 3 cost= 0.16383864 W= [1.5301253] b= [0.19926287]
Epoch: 5 cost= 0.095674135 W= [1.8054278] b= [0.09571465]
Epoch: 7 cost= 0.08990188 W= [1.8768404] b= [0.06834576]
Epoch: 9 cost= 0.08921726 W= [1.8953096] b= [0.06125886]
Epoch: 11 cost= 0.08909434 W= [1.900085] b= [0.05942642]
Epoch: 13 cost= 0.08906618 W= [1.9013196] b= [0.05895258]
Epoch: 15 cost= 0.08905912 W= [1.9016391] b= [0.05883]
Epoch: 17 cost= 0.08905732 W= [1.901722] b= [0.05879813]
Epoch: 19 cost= 0.08905686 W= [1.901743] b= [0.0587901]
Finished!
cost= 0.0890568 W= [1.9017464] b= [0.05878884]

通过上面的代码可以看到,模型的迭代训练可以总结为如下几个步骤:

  1. 初始化所有的变量( init = tf.global_variables_initializer() )
  2. 启动一个会话( with tf.Session() as sess )
  3. 按轮次迭代( for epoch in training_epochs )
  4. 按数据批量更新训练,训练就是执行optimizer( sess.run(optimizer, feed_dict={X:x_batch, Y:y_batch}) )
  5. 定时输出训练参数或保存之以便绘图分析( 训练参数主要关注loss的变化、模型参数的变化、评测指标的变化等 )
  6. 训练完成后保存模型( saver.save(sess, “./model.ckpt”) )

为了能更形象地去理解Tensorflow的运行机制,我们可以把构建的网络当做一个水管管道布局,输入的数据就是水,而会话就是一台抽水机,刚刚构建好的管道是空的,需要我们把水在源头通过feed_dict注入到管道中,而无论我们在管道的哪一个节点获取水(包括在源头注入),都需要运行这台抽水机sess.run(node)

评价分析

可视化地展示训练过程中训练、评测参数或模型参数的变化趋势,有利于我们分析训练过程中何时模型趋于稳定,以及是否出现了过拟合问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#图形显示
def moving_average(a, w=10):
if len(a) < w:
return a[:]
return [val if idx < w else sum(a[(idx-w):idx])/w for idx, val in enumerate(a)]

plt.plot(train_X, train_Y, 'ro', label='Original data')
plt.plot(train_X, predict_Y, label='Fittedline')
plt.legend()
plt.show()

plotdata["avgloss"] = moving_average(plotdata["loss"])
plt.figure(1)
plt.subplot(211)
plt.plot(plotdata["batchsize"], plotdata["avgloss"], 'b--')
plt.xlabel('Minibatch number')
plt.ylabel('Loss')
plt.title("Minibatch run vs. Trainig loss")
plt.show()

png

png

复用模型

复用模型的前提是我们在训练过程结束时已经使用saver对模型进行了保存,复用时只需要restore模型就可以了。restore的过程相当于重新把那套自来水管道复原,同时把管道上的参数(相当于水流控制开关)也加载恢复了,使用模型时再次注入水流(数据)就可以了,不同于训练过程,此时应该获取水流的节点是最终输出的那个节点(不是loss所在的节点)。

1
2
3
4
with tf.Session() as new_sess:
new_sess.run(tf.global_variables_initializer())
saver.restore(new_sess, "./model.ckpt")
print("x=0.2, z=", new_sess.run(z, feed_dict={X: 0.2}))
INFO:tensorflow:Restoring parameters from ./model.ckpt
x=0.2, z= [0.4391381]