使用tensorflow训练神经网络

tensorflow是一个采用数据流图(data flow graph)的机器学习平台,其特征在于用点和线来表示状态和计算过程。

tensorflow配置

tensorflow最方便的配置方式是在Ubuntu下直接使用pip安装wheel包。对于使用Windows的需求,可以选择Python3的版本,但是Windows下没有Python2的tensorflow版本,所以使用虚拟机或者Docker等容器或者Win10自带的Linux子系统是个可能的选择。

基本概念

  1. Tensor
    张量tf.python.framework.ops.Tensor是tensorflow的基础,表示一个多维向量,在Python中,Tensor就是numpy.ndarray类型。一个Tensor其接受若干个Tensor的输入,并产生若干个Tensor的输出。这称为一个操作op(operaton)。

    1. tf.Tensor.op
      产生这个TensorOperation
    2. tf.Tensor.consumers
      使用这个TensorOperation的列表
    3. tf.Tensor.graph
      这个Tensor所属的图
    4. tf.Tensor.name
      这个Tensor的名字
    5. tf.Tensor.get_shape()tf.Tensor.set_shape(shape)
      这个Tensor的形状,是一个TensorShape类型
  2. Operation
    操作tf.python.framework.ops.Operation是tensorflow计算流图中的一个节点。Operation可以通过调用op constructor(例如tf.matmul)或 tf.Graph.create_op()来产生,并通过tf.Session.run()op.run()tf.get_default_session().run(op))来执行。
    张量和操作的区别似乎是模糊的,根据Quora,可以把Operation对象当做一个void函数,例如a = tf.initialize_all_variables()返回的a是一个Operator。而Tensor对象是一个返回若干个Tensor的函数。

  3. Graph
    一张Graph由若干个Operation组成,用来描述数据流图的运算,还由若干个Tensor组成,表示在各个Operation之间传递的数据。一个op constructor 产生的Operation是属于默认Graph的,例如对于c = tf.constant(4.0),调用assert c.graph is tf.get_default_graph()可以确认。通过with g.as_default(),可以将with作用域的默认Graph设为g

  4. Variable
    神经网络是有多个感知机(perceptron)组成的,其中的例如W和b参数是训练的目标,随着迭代过程被优化,因此使用tf.Variable来表示它们。Variable是代表一个可修改的张量。

  5. Session
    通过定义op,定义的是计算流图的计算过程,但在未执行Session.run()前这操作并不会被执行,可以理解tensorflow中的op是“懒”的。
    下面的代码相加两个tensor:op1op2

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

    op1 = np.array([1, 2])
    op2 = np.array([3, 4])
    res1 = op1 + op2
    res2 = tf.add(op1, op2)
    print res1
    print res2
    with tf.Session() as sess:
    result = sess.run(res2)
    print result

    res1使用加法运算符,这等价于相加两个numpy.ndarray,因此立即输出[4 6]结果
    res2使用tf.add方法,此时得到了一个tensorflow.python.framework.ops.Tensor类型的Tensor("Add:0", shape=(2,), dtype=int64)
    之后使用Session.run()方法计算res2节点的值,得到了[4 6]的结果
    根据StackOverflow上的这个回答tf.add+的具体使用区别是,只要两个操作数中有一个是tf.Tensor,那么tf.add+是等价的,都是创建一个新的tf.Tensor。当需要给新创建的Tensor显式的名字的时候,一般会选择tf.add,否则重载了的+会更简便。

  6. Session.run
    Session.run()的第一个参数接受一个或一组(以list表示)需要被计算的op节点,并返回这些节点之后的计算值:
    对于下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    input1 = tf.constant(3.0)
    input2 = tf.constant(2.0)
    input3 = tf.constant(5.0)
    intermed = tf.add(input2, input3)
    # tensorflow 1.0.0 release notes:
    # tf.mul, tf.sub and tf.neg are deprecated in favor of tf.multiply, tf.subtract and tf.negative.
    mul = tf.mul(input1, intermed)

    with tf.Session() as sess:
    result = sess.run([mul, intermed])
    print result

    result返回list类型的[21, 7],分别是mul节点和intermed计算值
    由于Variable也是一个tensor,所以也需要通过Session.run()来获得它的值
    特别地,session.run(tensor)也可以写成with语句中的tensor.eval(),这两个写法是等价的

  7. placeholder和feed
    在使用op建立整个网络的数据流图之后,我们希望这个模型能够接受不同的输入进行训练,所以相对于上面直接相加两个tensor的方法,可以使用placeholder和feed在Session.run()时指定输入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import tensorflow as tf
    import numpy as np

    op1 = tf.placeholder(tf.int64, [2])
    op2 = tf.placeholder(tf.int64, [2])
    res2 = tf.add(op1, op2)
    with tf.Session() as sess:
    i1 = np.array([1, 2])
    i2 = np.array([3, 4])
    result = sess.run(res2, feed_dict = {op1:i1, op2:i2})
    print result

    在上面的代码中,首先并没有op1op2直接赋值为numpy.ndarray,而是指定了作为placeholder,在Session.run()使用feed_dict参数将op1op2传入。

  8. 数据类型
    在调用tf.matmul时常出现类型错误,需要注意tf.matmul等函数要求严格的类型,例如tf.float32并不能直接和tf.float64相乘,而应该在相乘前使用tf.cast函数进行转义。例如tf.cast(a, tf.int32)返回一个取整了的a的拷贝。

  9. 共享变量
    训练神经网络常常是分批的,因此需要将初始化各权值和使用给定训练集训练这两个操作分成两个函数,调用一次初始化各权值操作,然后将训练集分成若干批,对每批数据进行训练。显然这两个函数之间需要共享权值这个tf.Variable变量,相对于使用Python提供的global,tensorflow提供了tf.variable_scope()tf.get_variable()来实现这一点。
    get_variable用来引用一个带名字的变量(如果不存在,则创建该变量):

     tf.get_variable(<name>, <shape>, <initializer>):
    

    其中initializer指定初始化方式,可以选择:
    tf.constant_initializertf.random_uniform_initializertf.random_normal_initializer等,对应着random_uniform(a, b)Constant(value)truncated_normal(mean, stddev)
    variable_scope指定命名空间:

     tf.variable_scope(<scope_name>)
    

    这个语句常和with搭配使用,这样在该with作用域内的所有get_variable是针对这个variable_scope而言的了。这很类似于C++中的namespace的概念。

使用神经网络进行拟合

训练样本分批

tensorflow在优化目标函数的时候常使用SGD梯度下降的方法
SGD分为三种方法:
batch gradient descent方法一次更新使用全部样本,具有比较慢的收敛速度
stochastic gradient descent方法一次更新使用1个样本,梯度下降波动太过随机
综合考虑选择mini-batch gradient descent方法,一次更新比较小的batch

选择传输函数

  1. sigmoid、tanh
    sigmoidtanh更适合解决分类问题,且其值域是[0, 1],容易产生saturation的情况,当函数的输入绝对值比较大的时候函数输出无限接近于1。此外sigmoid恒为正,所以常使用sigmoid(x) - 0.5tanh(x) = 2*sigmoid(2x) - 1
    比较好的方法是根据具体数据规模在里面除个东西或开个根号来控制数据范围,或者可以选择归一化(如softmax)输入
  2. purelin
    purelin作为一个值域正负无穷的函数,也不适合作为激活函数,对于一般的数据,如果学习速率比较大很容易算到inf
  3. relu
    relu是没有负值的purelin,定义为max(0, x),同样不能使用过大的学习速率,否则容易让神经元die,也就是权值变成0
  4. softmax
    当问题是多个不相交的多类分类的问题时,使用一个softmax分类器比若干个logistic分类器要好

选择损失函数

常用的损失函数有均方误差、交叉熵和log-likelihood等

选择Optimizer

在之前一直使用的是SGD梯度下降(mini-batch gradient descent)的方法tf.train.GradientDescentOptimizer,这个方法是固定学习速率的,而且容易收敛到局部最优点或者鞍点
如果需要自适应的学习速率或者使用动量等方法可以使用其他的Optimizer。所有的Optimizer继承自tf.train.Optimizer
特别地,如果不要求在运行时可变学习速率,可以将learning rate作为一个placeholder保存,并feed给session.run()

可视化

可以将训练的过程写成summary到文件,并使用tensorboard --logdir=<path>来可视化,得到的结果在http://localhost:6006显示
主要步骤是先注册要记录的对象

1
2
3
4
5
6
7
with graph.as_default():
for value in [scalars]:
tf.summary.scalar(value.op.name, value)
for value in [tensors]:
tf.summary.tensor_summary(value.op.name, value)

summaries = tf.summary.merge_all()

注意到可能会发生叫”tags and values not the same shape”这个错误。这是因为试图summary一个张量,对于一个标量,例如loss函数的值,应当使用tf.summary.scalar(value.op.name, value),但是对于一个权值矩阵,应当使用tf.summary.tensor_summary(value.op.name, value)
tf.summary.scalar接受第一个参数表示在TensorBoard中显示的名字;第二个参数是一个仅有一个数字的Tensor
在训练时

1
2
3
4
5
with tf.Session(graph=graph) as session:
summary_writer = tf.summary.FileWriter('log_simple_stats', session.graph)
computed_summaries = session.run([summaries])
for step in xrange(num_steps):
summary_writer.add_summary(computed_summaries, step)

FileWriter

FileWriter可以创建一个event文件,并且把summary和event添加进去。
FileWriter具有下面的方法:

  1. add_summary(summary, global_step=None)
  2. add_session_log(session_log, global_step=None)
  3. add_event(event)
  4. add_graph(graph, global_step=None, graph_def=None)

模型保存

可以使用tf.train.Saver保存模型

1
2
3
4
5
6
with tf.Session(graph=graph) as session:
saver = tf.train.Saver()
# 从已保存的模型中恢复
saver.restore(session, "save/ada.ckpt")
# 保存到指定模型
saver.save(session, "save/ada.ckpt")

No variables to save错误

出现这个错误是因为saver = tf.train.Saver()出现在with块外部了