tensorflow是一个采用数据流图(data flow graph)的机器学习平台,其特征在于用点和线来表示状态和计算过程。
tensorflow配置
tensorflow最方便的配置方式是在Ubuntu下直接使用pip安装wheel包。对于使用Windows的需求,可以选择Python3的版本,但是Windows下没有Python2的tensorflow版本,所以使用虚拟机或者Docker等容器或者Win10自带的Linux子系统是个可能的选择。
基本概念
Tensor
张量tf.python.framework.ops.Tensor
是tensorflow的基础,表示一个多维向量,在Python中,Tensor
就是numpy.ndarray
类型。一个Tensor
其接受若干个Tensor
的输入,并产生若干个Tensor
的输出。这称为一个操作op(operaton)。tf.Tensor.op
产生这个Tensor
的Operation
tf.Tensor.consumers
使用这个Tensor
的Operation
的列表tf.Tensor.graph
这个Tensor
所属的图tf.Tensor.name
这个Tensor
的名字tf.Tensor.get_shape()
和tf.Tensor.set_shape(shape)
这个Tensor
的形状,是一个TensorShape
类型
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
的函数。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
。Variable
神经网络是有多个感知机(perceptron)组成的,其中的例如W和b参数是训练的目标,随着迭代过程被优化,因此使用tf.Variable
来表示它们。Variable是代表一个可修改的张量。Session
通过定义op,定义的是计算流图的计算过程,但在未执行Session.run()
前这操作并不会被执行,可以理解tensorflow中的op是“懒”的。
下面的代码相加两个tensor:op1
和op2
1
2
3
4
5
6
7
8
9
10
11
12import 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 resultres1
使用加法运算符,这等价于相加两个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
,否则重载了的+
会更简便。Session.run
Session.run()
的第一个参数接受一个或一组(以list表示)需要被计算的op节点,并返回这些节点之后的计算值:
对于下面的代码:1
2
3
4
5
6
7
8
9
10
11input1 = 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 resultresult
返回list类型的[21, 7]
,分别是mul
节点和intermed
计算值
由于Variable也是一个tensor,所以也需要通过Session.run()
来获得它的值
特别地,session.run(tensor)
也可以写成with语句中的tensor.eval()
,这两个写法是等价的placeholder和feed
在使用op建立整个网络的数据流图之后,我们希望这个模型能够接受不同的输入进行训练,所以相对于上面直接相加两个tensor的方法,可以使用placeholder和feed在Session.run()
时指定输入1
2
3
4
5
6
7
8
9
10
11import 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在上面的代码中,首先并没有
op1
和op2
直接赋值为numpy.ndarray
,而是指定了作为placeholder
,在Session.run()
使用feed_dict
参数将op1
和op2
传入。数据类型
在调用tf.matmul
时常出现类型错误,需要注意tf.matmul
等函数要求严格的类型,例如tf.float32
并不能直接和tf.float64
相乘,而应该在相乘前使用tf.cast
函数进行转义。例如tf.cast(a, tf.int32)
返回一个取整了的a
的拷贝。共享变量
训练神经网络常常是分批的,因此需要将初始化各权值和使用给定训练集训练这两个操作分成两个函数,调用一次初始化各权值操作,然后将训练集分成若干批,对每批数据进行训练。显然这两个函数之间需要共享权值这个tf.Variable
变量,相对于使用Python提供的global
,tensorflow提供了tf.variable_scope()
和tf.get_variable()
来实现这一点。
get_variable
用来引用一个带名字的变量(如果不存在,则创建该变量):tf.get_variable(<name>, <shape>, <initializer>):
其中
initializer
指定初始化方式,可以选择:
tf.constant_initializer
、tf.random_uniform_initializer
、tf.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
选择传输函数
- sigmoid、tanh
sigmoid
和tanh
更适合解决分类问题,且其值域是[0, 1]
,容易产生saturation的情况,当函数的输入绝对值比较大的时候函数输出无限接近于1。此外sigmoid
恒为正,所以常使用sigmoid(x) - 0.5
或tanh(x) = 2*sigmoid(2x) - 1
比较好的方法是根据具体数据规模在里面除个东西或开个根号来控制数据范围,或者可以选择归一化(如softmax
)输入 - purelin
purelin
作为一个值域正负无穷的函数,也不适合作为激活函数,对于一般的数据,如果学习速率比较大很容易算到inf
- relu
relu
是没有负值的purelin
,定义为max(0, x)
,同样不能使用过大的学习速率,否则容易让神经元die,也就是权值变成0 - 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 | with graph.as_default(): |
注意到可能会发生叫”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 | with tf.Session(graph=graph) as session: |
FileWriter
FileWriter
可以创建一个event文件,并且把summary和event添加进去。FileWriter
具有下面的方法:
add_summary(summary, global_step=None)
add_session_log(session_log, global_step=None)
add_event(event)
add_graph(graph, global_step=None, graph_def=None)
模型保存
可以使用tf.train.Saver
保存模型
1 | with tf.Session(graph=graph) as session: |
No variables to save错误
出现这个错误是因为saver = tf.train.Saver()
出现在with
块外部了