简明机器学习教程(二)——实践:进入Tensorflow世界

经过了一年的休整,终于博客也要恢复原先坑着的系列了,《简明机器学习教程》也会恢复更新。说实在的,第二篇的原稿我其实在第一篇之后一星期就写出来了,但是后来因为原稿遗失与学业繁忙就一直拖了下来。历经一年,我对机器学习与这系列教程又有了些新的思考,所以我决定做出些许调整。首先,本系列不再单独分理论、实践篇,而是采用交织在一起的形式。其次,将matlab更换为tensorflow(python)。教程的定位依旧是面向初学者,所以会加入大篇幅的前置介绍。这篇就是为了之后内容而对tensorflow进行先行的介绍。

安装(Windows)

安装CUDA和cuDNN

如果有支持CUDA的显卡(仿佛听到农企在哭泣),那建议安装CUDA。这样tensorflow就可以调用GPU而不是CPU进行计算,这会大大提升计算效率。不过并不是所有显卡都可以适用tensorflow,一些算力过差的显卡依旧是不能使用的。这里牙膏厂采用了运算能力(capability)来衡量显卡的算力,在这里可以查看这个表格,tensorflow官方要求capability必须大于3.0。如果不满足这个条件,建议跳过安装CUDA,直接安装cpu版本的tensorflow。当然解决方案也是有的,参见StackOverflow,不过需要自己编译tensorflow。由于当前版本的tensorflow仅支持CUDA9.0,所以只能到官网下载9.0版本。选择版本之后可以下载network或local,这里建议选择local(network老是提示下载失败)。安装成功后,在命令行输入nvcc -V查看安装的版本,若有图示的信息则说明安装成功。

cuDNN可以在此处下载,不过需要注意的是,只有登录才能下载。选择适合CUDA9.0的版本即可。下载后解压至“安装路径\NVIDIA GPU Computing Toolkit\CUDA\v9.0”即可。

安装Anaconda

Anaconda集成了大量有关科学计算的包,而且自带了个非常棒的开发环境。当然,安装tensorflow时,Anaconda并不是必要的,但是还是很推荐安装。在官网就可以下载其安装包,如果无法下载或下载失败,也可以选择清华的镜像。安装完之后,打开Anaconda Navigator就可以看到jupyter notebook了。这里建议顺手装个jupyterlab,虽然用的人不是很多,但是jupyterlab极大的扩充了jupyter notebook的功能。

然后我们来做下准备工作,首先创建一个开发环境,并激活:

conda create -n tensorflow pip python=3.5
activate tensorflow

如果左侧出现“(tensorflow)”字样,则说明已经切换至此环境了。

安装Tensorflow

终于来到重头戏了。对于安装了Anaconda的情况,装了CUDA的话执行:

pip install --ignore-installed --upgrade tensorflow-gpu

注意要在刚刚创建的环境下执行。(左侧应该有”(tensorflow)”)没安装CUDA则需要执行:

pip install --ignore-installed --upgrade tensorflow

对于没安装Anaconda的情形,若安装了CUDA就执行:

pip3 install --upgrade tensorflow-gpu

若没有安装则执行:

pip3 install --upgrade tensorflow

至此为止,我们就完成了tensorflow的安装。

验证安装

打开jupyter notebook或者直接在shell键入python,然后依此输入以下命令:

>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print(sess.run(hello))

若能成功打印“Hello, TensorFlow!”则说明安装正常。如果报错,请看官网给出的常见问题

安装(Linux)

这部分先坑着,过段时间没那么懒的时候写。

简介

Tensorflow与其说是一个机器学习库,更应该看作其官网宣称的数值计算库。当然,机器学习才是其设计用途。Tensorflow最核心的原理与思想就是数据流图。数据流图是一张图(graph),这里的图指的是图论里的图。一张图通常包含了一些节点和连接这些节点的边,而数据流图就是这样一张图,进一步讲,是一张有向图(directed graph)。每个结点就代表了一个运算操作(operation,比如加减乘除),而数据就在途中定向的“走动”。例如,我们把(1+3)*2转换为数据流图,那么它会长这样:

可以看到,1、3两个数字先“流”向了“Add(加)”这个结点,然后和2“流”向了“Multiply(乘)”这个结点。这样我们应该能更清楚的理解所谓的结点,每个结点都代表了处理若干数据的过程,它可以是函数或若干个步骤的计算。同时,这些结点也会给出一个“结果”。而这些“1”、“2”、“3”的标量,与矢量、矩阵之类的数据都统称为张量(Tensor)。因为这些张量在图中由一个结点“流(flow)”向另一个结点,所以才取名为TensorFlow。下面这张官方给出的动图就很能说明这个性质:

#使用数据流图的优点

张量

在数学中,有很多不同形式的量,比如标量(数量)、矢量(向量)、矩阵等。这些量都具有不同的维数,比如标量是0维的,矢量是1维的,矩阵是2维的。在tensorflow中,这些量都算张量,而维数就是它们的阶(rank,和矩阵的阶不同)。而如矢量、矩阵这类1阶以上的张量,它们还有不同的形状。比如: \begin{bmatrix}1 & 2\\ 3 & 4\\ 5 & 6\end{bmatrix}。这是一个3行2列的矩阵,而(3,2)就是它的形状。通过将所有数据都统一为具一定形状的张量,数据流图才得以个简单的结构。

开始

我们先引入tensorflow。之后的代码中,我们将使用别名tf来指代tensorflow。

import tensorflow as tf

从张量开始

之前已经介绍了张量,那我们就来看看张量在tensorflow中的具体实现。“在编写 TensorFlow 程序时,操控和传递的主要目标是 tf.Tensor。”而tf.Tensor具有数据类型和形状两个类型,我们先来看数据类型。到目前为止,tensorflow支持的数据类型如下表:

#tensorflow的数据类型

然后我们来看看张量的阶:

[table width=”95%” th=”1″]

数学实例

o
标量(只有大小)

1
矢量(大小和方向)

2
矩阵(数据表)

3
3 阶张量(数据立体)

n
n 阶张量(自行想象)
[/table]

由于之前的介绍已经简单的讲解了阶的定义,那我们就直接来看如何创建张量。以下是0-2阶张量的一些创建示例:

# 0阶张量,标量
t_r0 = tf.constant(1, tf.int16)
t_s_r0 = tf.constant("Elephant", tf.string)

# 1阶张量,矢量
t_r1 = tf.constant([1, 2, 3], tf.int16)

# 2阶张量,矩阵
t_r2 = tf.constant([ [1, 2], [3, 4] ], tf.int32)

同时,tensorflow还提供了一些自建函数以供张量创建:

# 2阶张量,2x3矩阵,元素皆为0
t_zero = tf.zeros((2, 3))

# 2阶张量,3x3矩阵,元素为[0,1)间均匀分布
t_rand = tf.random_uniform((3, 3), minval=0, maxval=1)

这些(2, 3)、(3, 3)就是这些张量的形状了。其实,创建张量的方法也远不止这些。事实上,Python原生类型、NumPy数组都可以直接传入tensorflow指令(tensorflow operation,下称“指令”)。并且在传入时也会被自动转化为对应的张量。

数据流图

还记得上面例子中的数据流图吗?本节我们就将学习如何创建这样一个数据流图。我们先来解析下这张数据流图的代码。

a = tf.constant(1, name="a")
b = tf.constant(3, name="b")
c = tf.constant(2, name="c")

op_add = tf.add(a, b, name="Add")
op_mul = tf.multiply(op_add, c, name="Multiply")

 

a、b、c是常量张量,op_add、op_mul是两个算符,对应于图中的Add、Multiply结点。而tf.add与tf.multiply就是构造这两个结点的函数。等等,op_add如果是指令,那为什么能直接传给tf.multiply呢?我们不妨试一试打印type(op_add),结果出人意料的是“<class ‘tensorflow.python.framework.ops.Tensor’>”。没错,op_add和op_mul都是张量!还记得那句话吗?“在编写 TensorFlow 程序时,操控和传递的主要目标是 tf.Tensor。”这些创建结点的函数并不直接返回结点本身,而是返回结点计算结果的张量,以便进行接下来的运算。得益于python支持算符重载的语言特性,我们可以把上面的代码改的更加简便。

op_add = a+b
op_mul = op_add*c

当运算不是很复杂时,数据流图的结构在代码中体现的还是很清楚的。但是一旦运算复杂,数据流图的结构就不是很清楚了。复杂的代码结构,重复的结点命名无疑都会给代码维护增加难度。为了让代码结构更加清楚,tensorflow加入了tf.name_scope的设计。name_scope可以“向特定上下文中创建的所有指令添加名称范围前缀。”看官方文档里的示例。

c_0 = tf.constant(0, name="c")  # => 指令名为 "c"

# 已被使用过的名字将会被"唯一化".
c_1 = tf.constant(2, name="c")  # => 指令名为 "c_1"

# name_scope给在相同上下文里的所有指令添加了前缀
with tf.name_scope("outer"):
  c_2 = tf.constant(2, name="c")  # => 指令名为 "outer/c"

  # name_scope的嵌套就像分层文件系统的路径
  with tf.name_scope("inner"):
    c_3 = tf.constant(3, name="c")  # => 指令名为 "outer/inner/c"

  # 离开一个name_scope上下文时,指令名将会返回使用上一个前缀
  c_4 = tf.constant(4, name="c")  # => 指令名为 "outer/c_1"

  # 已被使用过的name_scope名将会被"唯一化".
  with tf.name_scope("inner"):
    c_5 = tf.constant(5, name="c")  # => 指令名为 "outer/inner_1/c"

结合python的缩进,这样代码的结构就会清楚不少。而且在数据流图的可视化时,使用name_scope也可以适当的隐藏细节,以使整个计算结构更加清楚(如上文中的GIF)。

不知各位有没有发现,讲了那么久的数据流图,然而我们的代码中似乎都没有出现一个明确的数据流图声明。我们的代码之所以能正确运作,其实是因为tensorflow会自行创建一张默认数据流图,而我们创建的指令都会被自动加入其中。我们可以通过tf.get_default_graph来获取默认图的引用。如果我们需要自己创建数据流图,可以调用其构造函数tf.Graph。以下是新建一个图并在其中加入一个指令的代码。

g = tf.Graph()

with g.as_default():
    op_g_mul = tf.multiply(3,4)

会话

如果你尝试过打印之前的Tensor,你会发现你并不能获得你想要的运算结果。事实上,tensorflow的底层都是由C++进行编写的,而Python部分只是承担“指示操作”的工作。而会话(Session)就相当于Python(当然也有其他语言)和C++运行时(C++ Runtime)之间的桥梁,所以数据图必须传入会话执行(有时,在编写基于其他API的代码时,例如使用tf.estimator.Estimator时我们并不需要显式的创建会话,事实上这些API本身已经实现了会话的创建和管理)。调用tf.Session我们便可以创建一个会话。

sess = tf.Session()

而调用sess.run函数,我们就可以计算一个Tensor对象的结果了。例如,我们希望计算前文图中的结果,可以如是编写。

result = sess.run(op_mul)
print(result)

输出为8。不过当我们运行sess.run(op_g_mul)时却遇到了问题,程序抛出了“ValueError: Tensor Tensor(“Mul:0”, shape=(), dtype=int32) is not an element of this graph”。其实,虽然我们没有直接传入,但是在创建会话的过程中,默认的数据流图已经被隐式传入了。而op_g_mul并不在默认图中,所以就抛出了错误。我们可以在创建会话时显式的指定图来解决这个问题。

sess = tf.Session(graph=g)
result = sess.run(op_g_mul)
print(result)

输出为12。事实上,会话的构造函数还接受许多其它的参数。不过这些参数大多与我们当前的情形无关,所以暂且不进行介绍,有兴趣的朋友可以查阅官方文档[2]。tf.Session.run方法接受一个参数fetches,此外还有三个可选参数:feed_dict、options、run_metadata。后面两个参数我们目前用不到,所以暂且不提。而前两个参数正好可以与两种特殊的张量结合,那我们就在下一节讲解。

fetches

fetches接受一个或多个tf.Tensor或tf.Operation,并会执行之。之所以能接受多个,是指这些对象可以以列表或是字典值的形式传入,而返回时也会保持这种形式。传入tf.Tensor的例子之前已经有了,不过tf.Operation是什么?之前不是说创建结点的函数会返回张量么,那tf.Operation从何而来?这里我们讲一个重要的函数tf.global_variables_initializer,因为这个tf.Variable有关所以我们来讲讲变量。

变量

根据上一篇的教程我们知道,在学习时有一些量是会随着迭代而被更新的。而这些特殊的,会改变的张量在tensorflow中以tf.Variable的形式存在。创建变量的方法很简单,我们直接调用tf.Variable即可。

v_int = tf.Variable(1, name="var_int")
v_mat = tf.Variable(tf.zeros((2,3)), name="var_mat")

可以看到,张量对象也是可以被传入的。但是和之前相同,这些张量对象不会立刻被计算,所以我们就需要一些方法来初始化这些变量。tf.global_variables_initializer函数就是初始化当前图中所有变量的函数,不过它也不会立刻被执行。事实上,tf.global_variables_initializer会返回一个tf.Operation对象,换句话说,初始化变量也是一个指令。所以我们可以通过sess.run(tf.global_variables_initializer())来初始化所有变量。tensorflow还提供了tf.initialize_variables来初始化指定变量,它接受一个变量列表。tf.Variable还接受一个可选参数trainable,如果他为否,那tensorflow自带的训练方法就不能改变它的值,只能由下文提到的方法改变。

有创建自然有修改。tf.Variable.assign就是用于赋值的方法,它接受一个新值。它接受的基本和变量声明时接受的相同,不过值得注意的是,输入张量的形状要和声明时相同。这个方法最重要的还是其返回值,和其他指令一样tf.Variable.assign返回的是一个值为变量修改后值的张量。tf.Variable还提供了assign_add和assign_sub方法以供自增自减,用法类似于tf.Variable.assign。这里给出一段代码,大家可以通过猜测输出来测试下自己对变量的理解是否正确。

v_int = tf.Variable(1, name="var_int")

v_int2 = v_int.assign(2)
v_int3 = v_int.assign_add(1)

sess.run(tf.global_variables_initializer())
print(sess.run(v_int))
print(sess.run(v_int2))
print(sess.run(v_int3))
print(sess.run(v_int3))
print(sess.run(v_int))

#答案

feed_dict与占位符

在训练过程中,我们需要将一些训练样本传入以进行计算。而当模型训练完毕之后,我们也需要传入一些数据以供模型进行预测。很明显,我们需要一个传入数据的方法,而占位符(placeholder)就是为此设计的。我们可以通过tf.placeholder来创建一个占位符。

p_int = tf.placeholder(tf.int32, name="input_int")

tf.placeholder接受一个参数dtype和两个可选参数shape、name。dtype即数据类型,shape指定了占位符的形状,它默认为None,即可接受任意形状的张量。name指定了占位符在图中的名称。

可以看出,占位符的创建中并没有给占位符赋值。而给占位符以数据的方式,是在tf.Session.run的方法调用时传入feed_dict。feed_dict的键是一个张量对象,即创建占位符返回的张量对象,而值就是需要传入的张量。我们可以将上面的图进行一些修改。

a = tf.placeholder(tf.int32, name="a")
b = tf.placeholder(tf.int32, name="b")
c = tf.placeholder(tf.int32, name="c")

op_add = tf.add(a, b, name="Add")
op_mul = tf.multiply(op_add, c, name="Multiply")

sess = tf.Session()
inputs = { a: 1, b: 3, c: 2 }
result = sess.run(op_mul, feed_dict=inputs)
print(result)

输出为8。当然,我们也可以通过tf.placeholder_with_default函数创造一个带默认值的占位符,它接受两个参数input、shape,一个可选参数name。input即默认值,其他与tf.placeholder相仿。比如,我们可以给上述图中的占位符c以默认值2。

c = tf.placeholder_with_default(2, shape=None, name="c")

那么,当我们的feed_dict中没有给c指定值时,它的值就会是默认值2了。

实践

经过上面的介绍,相信你对tensorflow已经有了一个基本的了解,那我们就以上篇教程中的感知机为例,简单介绍下在tensorflow中如何进行机器学习。

以感知机为例

还记得感知机吗?如果不记得,赶紧去看上一篇。我们先声明预测函数和代价函数。线性函数和判断函数是为了后续编码方便而声明的。

# 线性函数
def linear(x, w, b):
    return tf.matmul(x, tf.transpose(w)) + b

# 判断函数
def judge_func(x, w, b, y):
    return linear(x, w, b) * y

# 预测函数
def predict(x, w, b):
    return tf.sign(linear(x, w, b))

# 代价函数
def cost_func(x, w, b, y):
    return -tf.reduce_sum(judge_func(x, w, b, y))

然后我们创建变量w和b,并创建用于传入数据的占位符。

var_w = tf.Variable(tf.ones((1, n)), dtype=tf.float32, name="Weight")
var_b = tf.Variable(1, dtype=tf.float32, name="Bias")
p_X = tf.placeholder(tf.float32, shape=(None, n), name="input_X")
p_y = tf.placeholder(tf.float32, shape=(None, 1), name="input_y")

然后就是训练了。首先声明Session,初始化变量。

init = tf.global_variables_initializer()
sess.run(init)

下面来计算代价函数,并使用tf.gradients函数给代价求导(偏导),然后根据求得的梯度(grad_w和grad_b)更新两个参数。

cost = cost_func(p_X, var_w, var_b, p_y)
grad_w, grad_b = tf.gradients(cost, [var_w, var_b])

t_w = var_w.assign_sub(alpha * grad_w)
t_b = var_b.assign_sub(alpha * grad_b)
train_step = [t_w, t_b]

到此为止,我们已经完成了计算图的搭建,那么接下来我们就来编写迭代更新的代码。首先我们需要获得当前所有的误分类点,这里运用了之前的判断函数并结合python的列表解析进行筛选。d_X和d_y代表数据集。

inputs = {p_X: d_X, p_y: d_y}
judge = sess.run(judge_func(p_X, var_w, var_b, p_y), feed_dict=inputs)
wrong_idx = [i for i in range(m) if judge[i][0]<0]
wrong_X = [d_X[i] for i in wrong_idx]
wrong_y = [d_y[i] for i in wrong_idx]

如果有误分类点,那我们就随机选择一个误分类点并更新参数。np是Numpy库的别名,当然也可以使用python的random库,即将其改为random.randint(0, len(wrong_idx) – 1)。

# 随机取一个误分类点
idx = np.random.choice(len(wrong_idx))
X = [wrong_X[idx]]
y = [wrong_y[idx]]
# 更新参数
inputs = {p_X: X, p_y: y}
w, b = sess.run(train_step, feed_dict=inputs)

这里需要注意,如果直接传入wrong_X[idx]的话是不行的。因为wrong_X[idx]是列表,被视为一阶的张量,但是占位符p_X却是二阶的。

然后计算当前的损失并打印。

# 计算损失
inputs = {p_X: wrong_X, p_y: wrong_y}
loss = sess.run(cost, feed_dict=inputs)
# 打印
print("Step %d/%d, w = %s, b = %f, loss = %f." %
      (i, steps, str(w), b, loss))

这样我们便完成了一次迭代,之后重复进行筛选-更新的操作即可。运行之后打印如下。

Step 0/100, w = [[0.75 0.9 ]], b = 0.950000, loss = 11.350000.
Step 1/100, w = [[0.55       0.84999996]], b = 0.900000, loss = 9.299999.
Step 2/100, w = [[0.3        0.74999994]], b = 0.850000, loss = 6.650000.
Step 3/100, w = [[0.05000001 0.6499999 ]], b = 0.800000, loss = 4.000000.
Step 4/100, w = [[-0.19999999  0.5499999 ]], b = 0.750000, loss = 1.350000.
Step 5/100, w = [[-0.39999998  0.49999988]], b = 0.700000, loss = -0.700000.

从图中我们也可以看到,这个超平面(此处是直线)确实将数据集分为了两块。

训练结果

使用Optimizer训练模型

除了自行使用梯度进行训练,tensorflow其实本身就提供了很多算法以供模型训练。在tensorflow里这些算法都以Optimizer的形式存在,而这里我们使用tf.train.GradientDescentOptimizer来进行代价函数的最小化。tf.train.GradientDescentOptimizer接受一个参数learning_rate即学习率,也就是alpha。除此之外,它还接受两个可选参数use_locking和name,后者和其他name一样,前者暂时不进行介绍。那么创建了一个Optimizer之后,我们只要调用其minimize方法(返回一个tf.Operation对象)并传入代价函数的张量就可以顺利的进行训练了。对上面的代码进行微小的调整即可,构建图可以简化为。

cost = cost_func(p_X, var_w, var_b, p_y)
train_step = tf.train.GradientDescentOptimizer(alpha).minimize(cost)

同时,参数更新需要更改为。

# 更新参数
inputs = {p_X: wrong_X, p_y: wrong_y}
sess.run(train_step, feed_dict=inputs)
w, b = sess.run([var_w, var_b])

运行之后打印如下。

Step 0/50, w = [[0.55 0.85]], b = 0.900000, loss = 9.300000.
Step 1/50, w = [[0.10000001 0.70000005]], b = 0.800000, loss = 4.600000.
Step 2/50, w = [[-0.35       0.5500001]], b = 0.700000, loss = -0.100000.
Step 3/50, w = [[-0.6         0.45000008]], b = 0.650000, loss = -1.450000.

从训练结果图中我们可以看出,经过训练的模型可以正确分类。

训练结果(使用Optimizer)

可视化:Tensorboard

Tensorflow还自带了一个很棒的可视化工具——Tensorboard,那么我们就来看看如何使用这个工具。首先我们要修改我们的代码,以记录一些能供Tensorboard显示的数据。这里我们需要使用tf.summary.FileWriter,我们来创建一个FileWriter。我们需要传入一个目录以存储相应数据,如果需要将图可视化也可以传入相应的图。

writer = tf.summary.FileWriter('./perceptron_logs', sess.graph)

能在Tensorboard中显示的数据的组织形式是summary,它可以记录各种类型的数据。暂且只讲解标量(scalar)和图像(image)的记录。可以通过调用tf.summary.scalar来记录一个标量,它接受标量的名称和一个张量。

tf.summary.scalar('loss', cost)

这个函数会返回一个tf.Operation,而只有我们手动在会话中执行这个指令,标量才会被记录。随着需要记录的数据增多,手动逐个调用是非常繁琐的,所以tensorflow就提供了一个方法tf.summary.merge_all来把所有summry指令合并为一个指令,所以我们暂且不需要记录它的返回值。

记录图的操作会稍微复杂一点。因为记录图像的初衷是为了调试能生成图像的一些模型,所以记录的图像是以张量的形式存储的。而要记录Matplot库绘制的图像,我们首先要将其转换为张量形式。这个函数可以将当前绘制的图像转为张量并返回。

def gen_plot():
    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    buf.seek(0)
    image = tf.image.decode_png(buf.getvalue(), channels=4)
    image = tf.expand_dims(image, 0)
    return image

我们声明一个新函数以绘制图像,最后调用上述函数返回一个张量。

def draw_image():
    """绘制图像"""
    plt.close('all')

    # 绘制过程略

    return gen_plot()

这样,我们就可以通过调用draw_image函数来生成一张图片,下面我们来记录它。如果要直接记录这张图片,可以直接在最后执行如下代码。

op_summary = tf.summary.image("Train result", draw_image())
summary = sess.run(op_summary)
writer.add_summary(summary)

不过如果我们希望记录训练过程中的图像,就不能这么写了。因为这样记录的图像并不会依照迭代次数归类,而是会依单张的形式存在,一旦图像较多,那么Tensorboard内就会非常混乱。所以我们需要用一个变量来存储这张图。

var_img = tf.Variable(tf.ones((1,480,640,4), dtype=tf.uint8), dtype=tf.uint8, name="Plot")

这里的张量形状和图片大小有关,暂不深入讲解。下面,我们需要记录这张图。

tf.summary.image("Train result", var_img)

每次迭代我们都需要生成并更新变量以记录新图片。

sess.run(var_img.assign(draw_image()))

这样我们就能记录一系列图片了。

记录完数据之后,我们在对应文件夹下打开控制台,并键入tensorboard –logdir=perceptron_logs。等待片刻后复制地址就可以在浏览器中打开了。

打开网页之后,就可以点击上方的标签页来查看相关数据了。

左侧的工具栏也可以对页面内容进行调整。可以看到,使用Tensorboard可以大大降低数据可视化的难度。

SL大法:保存检查点

当我们成功训练了一个模型之后,我们可能会希望保存下这个模型中的变量,以供之后预测。除此之外,在训练一个复杂的模型的过程中,定时保存当前的训练结果也是很重要的,这样一旦发生意外,也可以从就近的检查点(checkpoint)进行恢复。在tensorflow中,我们需要通过tf.train.Saver来进行储存。tf.train.Saver的构造方法并不要求传入任何参数,但是接受许多可选参数,感兴趣的朋友可以查看相关文档[6]。创建saver对象之后,我们可以调用tf.train.Saver.save方法来储存一个检查点。它接受sess和save_path两个参数,前者接受一个tf.Session对象,后者接受一个路径以存储文件。它同样具有很多可选参数,不过其中global_step比较实用,如果传入,他就会在文件名后加上当前的步数。比如“filepath”=>“filepath-1”。

saver = tf.train.Saver()
saver.save(sess, './percetron')

复原时,只需要调用tf.train.Saver.restore。他只接受sess和save_path两个参数,并且没有可选参数,用法与tf.train.Saver.save相同。不过需要注意的是,tf.train.Saver.restore并不能指定global_step,所以要恢复相应的检查点,只能通过手动的加上“-步数”。

代码汇总

这里是上述代码的汇总。通过更改train为train_with_optimizer,你可以使用tensorflow的Optimizer进行训练。train_with_optimizer没有加入summary的存储,你可以自行加入。Gist点击此处

#代码汇总

后记

感谢你阅读这篇博客。我写这篇文章的初衷是想以一篇简明扼要的文章快速介绍Tensorflow这个机器学习框架的,但是没想到,随着各种知识的扩充,这篇文章最后都快接近两万字了。其实现在流行的机器学习框架很多,光是支持Python的同类框架就还有MXNet、Theano、PyTorch等等。其中尤其是PyTorch,编写出来的代码短小精悍,而且各方面完全没有比tesorflow差。但是我最后还是选择了tensorflow,原因就是tensorflow更广。诚然,可视化PyTorch有Visdom,各类缺失的函数也有第三方库予以补足,但是tensorflow基本上都以一套很完整的体系囊括进来了。甚至把训练好的模型迁移到手机端APP也非常容易,这样的广度,目前所有能做到的且支持Python的框架,我想很难找到第二个了。当然,调试时tensorflow可能会显得速度较慢,这里推荐一个库miniflow。它在基本兼容tensorflow API的基础上大大提升了运行速度,可以说很适合调试。

本文在匆忙中写就,而且由于作者水平问题,文章难免有很多疏漏。希望各位大神能及时指出,直接评论即可。如有疑问,可以发邮件至admin@kaaass.net,能力范围内的问题我会择日回答。再次感谢你能耐心的读完这么长的博文。

Reference

  1. 在 Windows 上安装 TensorFlow (https://www.tensorflow.org/install/install_windows)
  2. 图和会话 (https://www.tensorflow.org/programmers_guide/graphs)
  3. 张量的阶、形状、数据类型 (http://wiki.jikexueyuan.com/project/tensorflow-zh/resources/dims_types.html)
  4. Tensor Ranks, Shapes, and Types (https://www.tensorflow.org/versions/r1.1/programmers_guide/dims_types)
  5. Tensorflow: How to Display Custom Images in Tensorboard (e.g. Matplotlib Plots) (https://stackoverflow.com/questions/38543850/tensorflow-how-to-display-custom-images-in-tensorboard-e-g-matplotlib-plots)
  6. tf.train.Saver (https://www.tensorflow.org/api_docs/python/tf/train/Saver)
分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

  1. 没有图片
  2. 没有图片
  3. 没有图片

    2016.04.24

    Java小测试1
  4. 没有图片

评论

  1. 开发者头条 2018.07.13 9:57上午

    感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/abli33 欢迎点赞支持!使用开发者头条 App 搜索 69380 即可订阅《KAAAsS Blog》

在此评论中不能使用 HTML 标签。