经过了一年的休整,终于博客也要恢复原先坑着的系列了,《简明机器学习教程》也会恢复更新。说实在的,第二篇的原稿我其实在第一篇之后一星期就写出来了,但是后来因为原稿遗失与学业繁忙就一直拖了下来。历经一年,我对机器学习与这系列教程又有了些新的思考,所以我决定做出些许调整。首先,本系列不再单独分理论、实践篇,而是采用交织在一起的形式。其次,将 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
conda create -n tensorflow pip python=3.5
activate tensorflow
conda create -n tensorflow pip python=3.5
activate tensorflow
如果左侧出现 “(tensorflow)” 字样,则说明已经切换至此环境了。
安装 Tensorflow
终于来到重头戏了。对于安装了 Anaconda 的情况,装了 CUDA 的话执行:
pip install --ignore-installed --upgrade tensorflow-gpu
pip install --ignore-installed --upgrade tensorflow-gpu
pip install --ignore-installed --upgrade tensorflow-gpu
注意要在刚刚创建的环境下执行。(左侧应该有”(tensorflow)”)没安装 CUDA 则需要执行:
pip install --ignore-installed --upgrade tensorflow
pip install --ignore-installed --upgrade tensorflow
pip install --ignore-installed --upgrade tensorflow
对于没安装 Anaconda 的情形,若安装了 CUDA 就执行:
pip3 install --upgrade tensorflow-gpu
pip3 install --upgrade tensorflow-gpu
pip3 install --upgrade tensorflow-gpu
若没有安装则执行:
pip3 install --upgrade tensorflow
pip3 install --upgrade tensorflow
pip3 install --upgrade tensorflow
至此为止,我们就完成了 tensorflow 的安装。
验证安装
打开 jupyter notebook 或者直接在 shell 键入 python,然后依此输入以下命令:
>>> import tensorflow as tf
>>> hello = tf. constant ( 'Hello, TensorFlow!' )
>>> print ( sess. run ( hello ))
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print(sess.run(hello))
>>> 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 阶以上的张量,它们还有不同的形状。比如:[ 1 2 3 4 5 6 ] \begin{bmatrix}1 & 2\\ 3 & 4\\ 5 & 6\end{bmatrix} ⎣ ⎡ 1 3 5 2 4 6 ⎦ ⎤ 。这是一个 3 行 2 列的矩阵,而 (3,2) 就是它的形状。通过将所有数据都统一为具一定形状的张量,数据流图才得以个简单的结构。
开始
我们先引入 tensorflow。之后的代码中,我们将使用别名 tf 来指代 tensorflow。
import tensorflow as tf
从张量开始
之前已经介绍了张量,那我们就来看看张量在 tensorflow 中的具体实现。“在编写 TensorFlow 程序时,操控和传递的主要目标是 tf.Tensor。” 而 tf.Tensor 具有数据类型和形状两个类型,我们先来看数据类型。到目前为止,tensorflow 支持的数据类型如下表:
[table width=”95%” th=”1″]
数据类型
Python 类型
描述
DT_FLOAT
tf.float32
32 位浮点数.
DT_DOUBLE
tf.float64
64 位浮点数.
DT_INT8
tf.int8
8 位有符号整型.
DT_INT16
tf.int16
16 位有符号整型.
DT_INT32
tf.int32
32 位有符号整型.
DT_INT64
tf.int64
64 位有符号整型.
DT_UINT8
tf.uint8
8 位无符号整型.
DT_UINT16
tf.uint16
16 位无符号整型.
DT_STRING
tf.string
可变长度的字节数组。每一个张量元素都是一个字节数组.
DT_BOOL
tf.bool
布尔型.
DT_COMPLEX64
tf.complex64
由两个 32 位浮点数组成的复数:实数和虚数.
DT_COMPLEX128
tf.complex128
由两个 64 位浮点数组成的复数:实数和虚数.
DT_QINT8
tf.qint8
用于量化 Ops 的 8 位有符号整型.
DT_QINT32
tf.qint32
用于量化 Ops 的 32 位有符号整型.
DT_QUINT8
tf.quint8
用于量化 Ops 的 8 位无符号整型.
[/table]
然后我们来看看张量的阶:
[table width=”95%” th=”1″]
阶
数学实例
o
标量(只有大小)
1
矢量(大小和方向)
2
矩阵(数据表)
3
3 阶张量(数据立体)
n
n 阶张量(自行想象)
[/table]
由于之前的介绍已经简单的讲解了阶的定义,那我们就直接来看如何创建张量。以下是 0-2 阶张量的一些创建示例:
t_r0 = tf. constant ( 1 , tf.int16 )
t_s_r0 = tf. constant ( "Elephant" , tf.string )
t_r1 = tf. constant ([ 1 , 2 , 3 ] , tf.int16 )
t_r2 = tf. constant ([ [ 1 , 2 ] , [ 3 , 4 ] ] , tf.int32 )
# 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)
# 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 还提供了一些自建函数以供张量创建:
t_zero = tf. zeros (( 2 , 3 ))
# 2 阶张量,3x3 矩阵,元素为 [0,1) 间均匀分布
t_rand = tf. random_uniform (( 3 , 3 ) , minval= 0 , maxval= 1 )
# 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阶张量,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 = 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 = 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
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"
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"
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。以下是新建一个图并在其中加入一个指令的代码。
op_g_mul = tf. multiply ( 3 , 4 )
g = tf.Graph()
with g.as_default():
op_g_mul = tf.multiply(3,4)
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 )
result = sess.run(op_mul)
print(result)
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 )
sess = tf.Session(graph=g)
result = sess.run(op_g_mul)
print(result)
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" )
v_int = tf.Variable(1, name="var_int")
v_mat = tf.Variable(tf.zeros((2,3)), name="var_mat")
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_int3 = v_int. assign_add ( 1 )
sess. run ( tf. global_variables_initializer ())
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))
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))
print ( sess. run ( v_int )) # 1
print ( sess. run ( v_int2 )) # 2
print ( sess. run ( v_int3 )) # 3
print ( sess. run ( v_int3 )) # 4
print ( sess. run ( v_int )) # 4
print(sess.run(v_int)) # 1
print(sess.run(v_int2)) # 2
print(sess.run(v_int3)) # 3
print(sess.run(v_int3)) # 4
print(sess.run(v_int)) # 4
print(sess.run(v_int)) # 1
print(sess.run(v_int2)) # 2
print(sess.run(v_int3)) # 3
print(sess.run(v_int3)) # 4
print(sess.run(v_int)) # 4
可以看到,变量 v_int 的值确实变化了。但是如果不执行之后的更改,v_int 依旧是 1。虽然 v_int2 等等是 Tensor,但是它们都与生成它们的指令息息相关,它们实际上就 “代表”(更准确的说,是关联)了这些指令。
feed_dict 与占位符
在训练过程中,我们需要将一些训练样本传入以进行计算。而当模型训练完毕之后,我们也需要传入一些数据以供模型进行预测。很明显,我们需要一个传入数据的方法,而占位符(placeholder)就是为此设计的。我们可以通过 tf.placeholder 来创建一个占位符。
p_int = tf. placeholder ( tf.int32, name= "input_int" )
p_int = tf.placeholder(tf.int32, name="input_int")
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" )
inputs = { a: 1 , b: 3 , c: 2 }
result = sess. run ( op_mul, feed_dict=inputs )
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)
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" )
c = tf.placeholder_with_default(2, shape=None, name="c")
c = tf.placeholder_with_default(2, shape=None, name="c")
那么,当我们的 feed_dict 中没有给 c 指定值时,它的值就会是默认值 2 了。
实践
经过上面的介绍,相信你对 tensorflow 已经有了一个基本的了解,那我们就以上篇教程中的感知机为例,简单介绍下在 tensorflow 中如何进行机器学习。
以感知机为例
还记得感知机吗?如果不记得,赶紧去看上一篇 。我们先声明预测函数和代价函数。线性函数和判断函数是为了后续编码方便而声明的。
return tf. matmul ( x, tf. transpose ( w )) + b
def judge_func ( x, w, b, y ) :
return linear ( x, w, b ) * y
return tf. sign ( linear ( x, w, b ))
def cost_func ( x, w, b, y ) :
return -tf. reduce_sum ( judge_func ( x, w, b, y ))
# 线性函数
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))
# 线性函数
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" )
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")
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 ()
init = tf.global_variables_initializer()
sess.run(init)
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 )
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]
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 ]
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]
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 ))
inputs = { p_X: X, p_y: y }
w, b = sess. run ( train_step, feed_dict=inputs )
# 随机取一个误分类点
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)
# 随机取一个误分类点
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 ))
# 计算损失
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))
# 计算损失
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 .
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.
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 )
cost = cost_func(p_X, var_w, var_b, p_y)
train_step = tf.train.GradientDescentOptimizer(alpha).minimize(cost)
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 ])
# 更新参数
inputs = {p_X: wrong_X, p_y: wrong_y}
sess.run (train_step, feed_dict=inputs)
w, b = sess.run ([var_w, var_b])
# 更新参数
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 .
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.
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 )
writer = tf.summary.FileWriter('./perceptron_logs', sess.graph)
writer = tf.summary.FileWriter('./perceptron_logs', sess.graph)
能在 Tensorboard 中显示的数据的组织形式是 summary,它可以记录各种类型的数据。暂且只讲解标量(scalar)和图像(image)的记录。可以通过调用 tf.summary.scalar 来记录一个标量,它接受标量的名称和一个张量。
tf.summary. scalar ( 'loss' , cost )
tf.summary.scalar('loss', cost)
tf.summary.scalar('loss', cost)
这个函数会返回一个 tf.Operation,而只有我们手动在会话中执行这个指令,标量才会被记录。随着需要记录的数据增多,手动逐个调用是非常繁琐的,所以 tensorflow 就提供了一个方法 tf.summary.merge_all 来把所有 summry 指令合并为一个指令,所以我们暂且不需要记录它的返回值。
记录图的操作会稍微复杂一点。因为记录图像的初衷是为了调试能生成图像的一些模型,所以记录的图像是以张量的形式存储的。而要记录 Matplot 库绘制的图像,我们首先要将其转换为张量形式。这个函数可以将当前绘制的图像转为张量并返回。
plt. savefig ( buf, format= 'png' )
image = tf. image . decode_png ( buf. getvalue () , channels= 4 )
image = tf. expand_dims ( image, 0 )
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 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 ()
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 )
op_summary = tf.summary.image("Train result", draw_image())
summary = sess.run(op_summary)
writer.add_summary(summary)
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" )
var_img = tf.Variable(tf.ones((1,480,640,4), dtype=tf.uint8), dtype=tf.uint8, name="Plot")
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 )
tf.summary.image("Train result", var_img)
tf.summary.image("Train result", var_img)
每次迭代我们都需要生成并更新变量以记录新图片。
sess. run ( var_img. assign ( draw_image ()))
sess.run(var_img.assign(draw_image()))
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. save ( sess, './percetron' )
saver = tf.train.Saver()
saver.save(sess, './percetron')
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 点击此处 。
import matplotlib.pyplot as plt
return tf. matmul ( x, tf. transpose ( w )) + b
return tf. sign ( linear ( x, w, b ))
def judge_func ( x, w, b, y ) :
return linear ( x, w, b ) * y
def cost_func ( x, w, b, y ) :
return -tf. reduce_sum ( judge_func ( x, w, b, y ))
var_w = tf. Variable ( tf. ones (( 1 , n )) , dtype=tf.float32, name= "Weight" )
var_b = tf. Variable ( 1 , dtype=tf.float32, name= "Bias" )
var_img = tf. Variable ( tf. ones (( 1 , 480 , 640 , 4 ) , dtype=tf.uint8 ) , dtype=tf.uint8, name= "Plot" )
p_X = tf. placeholder ( tf.float32, shape= ( None , n ) , name= "input_X" )
p_y = tf. placeholder ( tf.float32, shape= ( None , 1 ) , name= "input_y" )
writer = tf.summary. FileWriter ( './perceptron_logs' , sess.graph )
init = tf. global_variables_initializer ()
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 )
tf.summary. scalar ( 'loss' , cost )
tf.summary. image ( "Train result" , var_img )
merged = tf.summary. merge_all ()
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 ]
idx = np.random. choice ( len ( wrong_idx ))
inputs = { p_X: X, p_y: y }
w, b = sess. run ( train_step, feed_dict=inputs )
sess. run ( var_img. assign ( draw_image ()))
inputs = { p_X: wrong_X, p_y: wrong_y }
summary, loss = sess. run ([ merged, cost ] , feed_dict=inputs )
writer. add_summary ( summary, i )
print ( "Step %d/%d, w = %s, b = %f, loss = %f." %
( i, steps, str ( w ) , b, loss ))
def train_with_optimizer ( d_X, d_y ) :
"""使用 tensorflow 的 Optimizer 进行训练"""
init = tf. global_variables_initializer ()
cost = cost_func ( p_X, var_w, var_b, p_y )
train_step = tf.train. GradientDescentOptimizer ( alpha ) . minimize ( cost )
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 ]
inputs = { p_X: wrong_X, p_y: wrong_y }
sess. run ( train_step, feed_dict=inputs )
w, b = sess. run ([ var_w, var_b ])
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 ))
plt. savefig ( buf, format= 'png' )
image = tf.image. decode_png ( buf. getvalue () , channels= 4 )
image = tf. expand_dims ( image, 0 )
l_k = - w [ 0 ][ 0 ] / w [ 0 ][ 1 ]
l_x = x = np. arange ( -5 , 10 )
plt. scatter ( p_x, p_y, color= [ colors [ int (( i [ 0 ] + 1 ) / 2 )] for i in d_y ])
if __name__ == '__main__' :
print ( sess. run ( predict ([[ 1. , 3. ]] , var_w, var_b )))
saver. save ( sess, './percetron' )
import io
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
alpha = 0.05 # 学习率
steps = 50 # 最大迭代步数
m = 4 # 样本个数
n = 2 # 样本维数
def read_input ():
"""读入数据"""
x = [[1., 1.],
[1., 2.],
[4., 1.],
[5., 2.]]
y = [[1.],
[1.],
[-1.],
[-1.]]
return x, y
def linear (x, w, b):
"""线性函数"""
return tf.matmul (x, tf.transpose (w)) + b
def predict (x, w, b):
"""预测函数"""
return tf.sign (linear (x, w, b))
def judge_func (x, w, b, y):
"""判断函数"""
return linear (x, w, b) * y
def cost_func (x, w, b, y):
"""代价函数"""
return -tf.reduce_sum (judge_func (x, w, b, y))
# 声明变量
var_w = tf.Variable (tf.ones ((1, n)), dtype=tf.float32, name="Weight")
var_b = tf.Variable (1, dtype=tf.float32, name="Bias")
var_img = tf.Variable (tf.ones ((1,480,640,4), dtype=tf.uint8), dtype=tf.uint8, name="Plot")
# 声明占位符
p_X = tf.placeholder (tf.float32, shape=(None, n), name="input_X")
p_y = tf.placeholder (tf.float32, shape=(None, 1), name="input_y")
# 初始化
sess = tf.Session ()
writer = tf.summary.FileWriter ('./perceptron_logs', sess.graph)
def train (d_X, d_y):
"""训练"""
# 变量初始化
init = tf.global_variables_initializer ()
sess.run (init)
# 构建数据流图
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]
# 记录 summary
tf.summary.scalar ('loss', cost)
tf.summary.image ("Train result", var_img)
merged = tf.summary.merge_all ()
# 开始迭代
for i in range (steps):
# 计算误分类点
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]
# 判断退出
if (len (wrong_idx) < 1):
break
# 随机取一个误分类点
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)
# 绘图
sess.run (var_img.assign (draw_image ()))
# 计算损失并记录 summary
inputs = {p_X: wrong_X, p_y: wrong_y}
summary, loss = sess.run ([merged, cost], feed_dict=inputs)
writer.add_summary (summary, i)
# 打印
print ("Step % d/% d, w = % s, b = % f, loss = % f." %
(i, steps, str (w), b, loss))
def train_with_optimizer (d_X, d_y):
"""使用 tensorflow 的 Optimizer 进行训练"""
# 计算误分类点
init = tf.global_variables_initializer ()
sess.run (init)
# 构建数据流图
cost = cost_func (p_X, var_w, var_b, p_y)
train_step = tf.train.GradientDescentOptimizer (alpha).minimize (cost)
# 开始迭代
for i in range (steps):
# 计算误分类点
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]
# 判断退出
if (len (wrong_idx) < 1):
break
# 更新参数
inputs = {p_X: wrong_X, p_y: wrong_y}
sess.run (train_step, feed_dict=inputs)
w, b = sess.run ([var_w, var_b])
# 计算损失
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))
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')
w = sess.run (var_w)
b = sess.run (var_b)
l_k = - w [0][0] /w [0][1]
l_b = - b /w [0][1]
l_x = x = np.arange (-5, 10)
l_y = l_k * x + l_b
plt.plot (l_x, l_y)
colors = ['y', 'b']
points = np.array (d_X)
p_x = points [:,0]
p_y = points [:,1]
plt.scatter (p_x, p_y, color=[colors [int ((i [0]+1)/2)] for i in d_y])
plt.xlim ((0, 6))
plt.ylim ((0, 4))
return gen_plot ()
if __name__ == '__main__':
d_X, d_y = read_input ()
train (d_X, d_y)
# 测试预测
print (sess.run (predict ([[1., 3.]], var_w, var_b)))
# 保存检查点
saver = tf.train.Saver ()
saver.save (sess, './percetron')
# 显示图
plt.show ()
writer.close ()
import io
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
alpha = 0.05 # 学习率
steps = 50 # 最大迭代步数
m = 4 # 样本个数
n = 2 # 样本维数
def read_input():
"""读入数据"""
x = [[1., 1.],
[1., 2.],
[4., 1.],
[5., 2.]]
y = [[1.],
[1.],
[-1.],
[-1.]]
return x, y
def linear(x, w, b):
"""线性函数"""
return tf.matmul(x, tf.transpose(w)) + b
def predict(x, w, b):
"""预测函数"""
return tf.sign(linear(x, w, b))
def judge_func(x, w, b, y):
"""判断函数"""
return linear(x, w, b) * y
def cost_func(x, w, b, y):
"""代价函数"""
return -tf.reduce_sum(judge_func(x, w, b, y))
# 声明变量
var_w = tf.Variable(tf.ones((1, n)), dtype=tf.float32, name="Weight")
var_b = tf.Variable(1, dtype=tf.float32, name="Bias")
var_img = tf.Variable(tf.ones((1,480,640,4), dtype=tf.uint8), dtype=tf.uint8, name="Plot")
# 声明占位符
p_X = tf.placeholder(tf.float32, shape=(None, n), name="input_X")
p_y = tf.placeholder(tf.float32, shape=(None, 1), name="input_y")
# 初始化
sess = tf.Session()
writer = tf.summary.FileWriter('./perceptron_logs', sess.graph)
def train(d_X, d_y):
"""训练"""
# 变量初始化
init = tf.global_variables_initializer()
sess.run(init)
# 构建数据流图
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]
# 记录summary
tf.summary.scalar('loss', cost)
tf.summary.image("Train result", var_img)
merged = tf.summary.merge_all()
# 开始迭代
for i in range(steps):
# 计算误分类点
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]
# 判断退出
if (len(wrong_idx) < 1):
break
# 随机取一个误分类点
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)
# 绘图
sess.run(var_img.assign(draw_image()))
# 计算损失并记录summary
inputs = {p_X: wrong_X, p_y: wrong_y}
summary, loss = sess.run([merged, cost], feed_dict=inputs)
writer.add_summary(summary, i)
# 打印
print("Step %d/%d, w = %s, b = %f, loss = %f." %
(i, steps, str(w), b, loss))
def train_with_optimizer(d_X, d_y):
"""使用tensorflow的Optimizer进行训练"""
# 计算误分类点
init = tf.global_variables_initializer()
sess.run(init)
# 构建数据流图
cost = cost_func(p_X, var_w, var_b, p_y)
train_step = tf.train.GradientDescentOptimizer(alpha).minimize(cost)
# 开始迭代
for i in range(steps):
# 计算误分类点
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]
# 判断退出
if (len(wrong_idx) < 1):
break
# 更新参数
inputs = {p_X: wrong_X, p_y: wrong_y}
sess.run(train_step, feed_dict=inputs)
w, b = sess.run([var_w, var_b])
# 计算损失
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))
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')
w = sess.run(var_w)
b = sess.run(var_b)
l_k = - w[0][0] / w[0][1]
l_b = - b / w[0][1]
l_x = x = np.arange(-5, 10)
l_y = l_k * x + l_b
plt.plot(l_x, l_y)
colors = ['y', 'b']
points = np.array(d_X)
p_x = points[:,0]
p_y = points[:,1]
plt.scatter(p_x, p_y, color=[colors[int((i[0]+1)/2)] for i in d_y])
plt.xlim((0, 6))
plt.ylim((0, 4))
return gen_plot()
if __name__ == '__main__':
d_X, d_y = read_input()
train(d_X, d_y)
# 测试预测
print(sess.run(predict([[1., 3.]], var_w, var_b)))
# 保存检查点
saver = tf.train.Saver()
saver.save(sess, './percetron')
# 显示图
plt.show()
writer.close()
后记
感谢你阅读这篇博客。我写这篇文章的初衷是想以一篇简明扼要的文章快速介绍 Tensorflow 这个机器学习框架的,但是没想到,随着各种知识的扩充,这篇文章最后都快接近两万字了。其实现在流行的机器学习框架很多,光是支持 Python 的同类框架就还有 MXNet、Theano、PyTorch 等等。其中尤其是 PyTorch,编写出来的代码短小精悍,而且各方面完全没有比 tesorflow 差。但是我最后还是选择了 tensorflow,原因就是 tensorflow 更广。诚然,可视化 PyTorch 有 Visdom,各类缺失的函数也有第三方库予以补足,但是 tensorflow 基本上都以一套很完整的体系囊括进来了。甚至把训练好的模型迁移到手机端 APP 也非常容易,这样的广度,目前所有能做到的且支持 Python 的框架,我想很难找到第二个了。当然,调试时 tensorflow 可能会显得速度较慢,这里推荐一个库 miniflow 。它在基本兼容 tensorflow API 的基础上大大提升了运行速度,可以说很适合调试。
本文在匆忙中写就,而且由于作者水平问题,文章难免有很多疏漏。希望各位大神能及时指出,直接评论即可。如有疑问,可以发邮件至 admin@kaaass.net,能力范围内的问题我会择日回答。再次感谢你能耐心的读完这么长的博文。
Reference
在 Windows 上安装 TensorFlow (https://www.tensorflow.org/install/install_windows )
图和会话 (https://www.tensorflow.org/programmers_guide/graphs )
张量的阶、形状、数据类型 (http://wiki.jikexueyuan.com/project/tensorflow-zh/resources/dims_types.html )
Tensor Ranks, Shapes, and Types (https://www.tensorflow.org/versions/r1.1/programmers_guide/dims_types )
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 )
tf.train.Saver (https://www.tensorflow.org/api_docs/python/tf/train/Saver )
喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)
感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/abli33 欢迎点赞支持!使用开发者头条 App 搜索 69380 即可订阅《KAAAsS Blog》