使用Tensorflow实现线性支持向量机的形式来作为 Tensorflow 的“应用式入门教程

本文拟通过使用 tensorflow 实现线性支持向量机(linearsvm)的形式来作为 tensorflow 的“应用式入门教程”。虽说用 mnist 做入门教程项目几乎是约定俗成的事了,但总感觉照搬这么个东西过来当专栏有些水……所以还是自己亲手写了个 linearsvm ( σ'ω')σ
在实现之前,先简要介绍一下 linearsvm 算法(详细介绍可以参见这里):
以及介绍一下 tensorflow 的若干思想:
tensorflow 的核心在于它能构建出一张“运算图(graph)”,我们需要做的是往这张 graph 里加入元素
基本的元素有如下三种:常量(constant)、可训练的变量(variable)和不可训练的变量(variable(trainable=false))
由于机器学习算法常常可以转化为最小化损失函数,tensorflow 利用这一点、将“最小化损失”这一步进行了很好的封装。具体而言,你只需要在 graph 里面将损失表达出来后再调用相应的函数、即可完成所有可训练的变量的更新
其中第三点我们会在实现 linearsvm 时进行相应说明,这里则会把重点放在第二点上。首先来看一下应该如何定义三种基本元素以及相应的加、减、乘、除(值得一提的是,在 tensorflow 里面、我们常常称处于 graph 之中的 tensorflow 变量为“tensor”,于是 tensorflow 就可以理解为“tensor 的流动”)(注:tensor 这玩意儿叫张量,数学上是挺有来头的东西;然而个人认为如果不是做研究的话就完全可以不管它数学内涵是啥、把它当成高维数组就好 ( σ'ω')σ):
import tensorflow as tf
# 定义常量、同时把数据类型定义为能够进行 gpu 计算的 tf.float32 类型
x = tf.constant(1, dtype=tf.float32)
# 定义可训练的变量
y = tf.variable(2, dtype=tf.float32)
# 定义不可训练的变量
z = tf.variable(3, dtype=tf.float32, trainable=false)
x_add_y = x + y
y_sub_z = y – z
x_times_z = x * z
z_div_x = z / x
此外,tensorflow 基本支持所有 numpy 中的方法、不过它留给我们的接口可能会稍微有些不一样。以“求和”操作为例:
# 用 numpy 数组进行 tensor 的初始化
x = tf.constant(np.array([[1, 2], [3, 4]]))
# tensorflow 中对应于 np.sum 的方法
axis0 = tf.reduce_sum(x, axis=0) # 将会得到值为 [ 4 6 ] 的 tensor
axis1 = tf.reduce_sum(x, axis=1) # 将会得到值为 [ 3 7 ] 的 tensor
更多的操作方法可以参见这里(https://zhuanlan.zhihu.com/p/26657869)
最后要特别指出的是,为了将 graph 中的 tensor 的值“提取”出来、我们需要定义一个 session 来做相应的工作。可以这样理解 graph 和 session 的关系(注:该理解可能有误!如果我确实在瞎扯的话,欢迎观众老爷们指出 ( σ'ω')σ):
graph 中定义的是一套“运算规则”
session 则会“启动”这一套由 graph 定义的运算规则,而在启动的过程中、session 可能会额外做三件事:
从运算规则中提取出想要的中间结果
更新所有可训练的变量(如果启动的运算规则包括“更新参数”这一步的话)
赋予“运算规则”中一些“占位符”以具体的值
其中“更新参数”和“占位符”的相关说明会放在后文进行,这里我们只说明“提取中间结果”是什么意思。比如现在 graph 中有这么一套运算规则:,而我只想要运算规则被启动之后、y 的运算结果。该需求的代码实现如下:
x = tf.constant(1)
y = x + 1
z = y + 1
print(tf.session().run(y)) # 将会输出 2
如果我想同时获得 y 和 z 的运算结果的话,只需将第 4 行改为如下代码即可:
print(tf.session().run([y, z])) # 将会输出 [2, 3]
最后想要特别指出一个非常容易犯错的地方:当我们使用了 variable 时,必须要先调用初始化的方法之后、才能利用 session 将相应的值从 graph 里面提取出来。比如说,下面这段代码是会报错的:
x = tf.variable(1)
print(tf.session().run(x)) # 报错!
应该改为:
x = tf.variable(1)
with tf.session().as_default() as sess:
sess.run(tf.global_variables_initializer())
print(sess.run(x))
其中 tf.global_variables_initializer() 的作用可由其名字直接得知:初始化所有 variable
接下来就是 linearsvm 的实现了,由前文的讨论可知,关键只在于把损失函数的形式表达出来(利用到了 classifierbase(https://link.zhihu.com/?target=https%3a//github.com/carefree0910/machinelearning/blob/master/util/bases.py%23l196);同时为了简洁,我们设置c=1):
import tensorflow as tf
from util.bases import classifierbase
class tflinearsvm(classifierbase):
def __init__(self):
super(tflinearsvm, self).__init__()
self._w = self._b = none
# 使用 self._sess 属性来存储一个 session 以方便调用
self._sess = tf.session()
def fit(self, x, y, sample_weight=none, lr=0.001, epoch=10 ** 4, tol=1e-3):
# 将 sample_weight(样本权重)转换为 constant tensor
if sample_weight is none:
sample_weight = tf.constant(
np.ones(len(y)), dtype=tf.float32, name=sample_weight)
else:
sample_weight = tf.constant(
np.array(sample_weight) * len(y), dtype=tf.float32, name=sample_weight)
# 将输入数据转换为 constant tensor
x, y = tf.constant(x, dtype=tf.float32), tf.constant(y, dtype=tf.float32)
# 将需要训练的 w、b 定义为可训练 variable
self._w = tf.variable(np.zeros(x.shape[1]), dtype=tf.float32, name=w)
self._b = tf.variable(0., dtype=tf.float32, name=b)
# ========== 接下来的步骤很重要!!! ==========
# 调用相应方法获得当前模型预测值
y_pred = self.predict(x, true, false)
# 利用相应函数计算出总损失:
# cost = ∑_(i=1)^n max⁡(1-y_i⋅(w⋅x_i+b),0)+1/2 + 0.5 * ‖w‖^2
cost = tf.reduce_sum(tf.maximum(
1 - y * y_pred, 0) * sample_weight) + tf.nn.l2_loss(self._w)
# 利用 tensorflow 封装好的优化器定义“更新参数”步骤
# 该步骤会调用相应算法、以减少上述总损失为目的来进行参数的更新
train_step = tf.train.adamoptimizer(learning_rate=lr).minimize(cost)
# 初始化所有 variable
self._sess.run(tf.global_variables_initializer())
# 不断调用“更新参数”步骤;如果期间发现误差小于阈值的话就提前终止迭代
for _ in range(epoch):
# 这种写法是比较偷懒的写法,得到的 cost 将不太精确
if self._sess.run([cost, train_step])[0] < tol:
break
然后就要定义获取模型预测值的方法——self.predict 了:
def predict(self, x, get_raw_results=false, out_of_sess=true):
# 利用 reduce_sum 方法算出预测向量
rs = tf.reduce_sum(self._w * x, axis=1) + self._b
if not get_raw_results:
rs = tf.sign(rs)
# 如果 out_of_sess 参数为 true、就要利用 session 把具体数值算出来
if out_of_sess:
rs = self._sess.run(rs)
# 否则、直接把 tensor 返回即可
return rs
之所以要额外用一个 out_of_sess 参数控制输出的原因如下:
tensorflow 在内部进行 graph 运算时是无需把具体数值算出来的、不如说使用原生态的 tensor 进行运算反而会快很多
当模型训练完毕后,在测试阶段我们希望得到的当然是具体数值而非 tensor、此时就需要 session 帮我们把中间结果提取出来了
以上就是 linearsvm 的完整实现,可以看到还是相当简洁的
这里特别指出这么一点:利用 session 来提取中间结果这个过程并非是没有损耗的;事实上,当 graph 运算本身的计算量不大时,开启、关闭 session 所造成的开销反而会占整体开销中的绝大部分。因此在我们编写 tensorflow 程序时、要注意避免由于贪图方便而随意开启 session
在本文的最后,我们来看一下 tensorflow 里面 placeholder 这个东西的应用。目前实现的 linearsvm 虽说能用,但其实存在着内存方面的隐患。为了解决这个隐患,一个常见的做法是分 batch 训练,这将会导致“更新参数”步骤每次接受的数据都是“不固定”的数据——原数据的一个小 batch。为了描述这个“不固定”的数据、我们就需要利用到 tensorflow 中的“占位符(placeholder)”,其用法非常直观:
# 定义一个数据类型为 tf.float32、“长”未知、“宽”为 2 的矩阵
placeholder x = tf.placeholder(tf.float32, [none, 2])
# 定义一个 numpy 数组:[ [ 1 2 ], [ 3 4 ], [ 5 6 ] ]
y = np.array([[1, 2], [3, 4], [5, 6]])
# 定义 x + 1 对应的 tensor
z = x + 1
# 利用 session 及其 feed_dict 参数、将 y 的值赋予给 x、同时输出 z 的值 print(tf.session().run(z, feed_dict={x: y}))
# 将会输出 [ [ 2 3 ], [ 4 5 ], [ 6 7 ] ]
于是分 batch 运算的实现步骤就很清晰了:
把计算损失所涉及的所有 x、y 定义为占位符
每次训练时,通过 feed_dict 参数、将原数据的一个小 batch 赋予给 x、y
占位符还有许多其它有趣的应用手段,它们的思想都是相通的:将未能确定的信息以 placeholder 的形式进行定义、在确实调用到的时候再赋予具体的数值
事实上,基本所有 tensorflow 模型都要用到 placeholder。虽然我们上面实现的 tflinearsvm 没有用到,但正因如此、它是存在巨大缺陷的(比如说,如果在同一段代码中不断地调用参数 out_of_sess 为 true 的 predict 方法的话,会发现它的速度越来越慢。观众老爷们可以思考一下这是为什么 ( σ'ω')σ)
以上就是 tensorflow 的一个简要教程,虽然我是抱着“即使从来没用过 tensorflow 也能看懂”的心去写的,但可能还是会有地方说得不够详细;若果真如此,还愿不吝指出 ( σ'ω')σ

8路键盘D触发锁存器的制作
AMD24核心48线程撕裂者3960X性能实测 TR3系列进入了无敌的寂寞境界
采用IBM-PC工业控制计算机和PLC实现两辊式轧机控制系统的设计
智能镜面显示屏让健身房变得更智能和便捷
利用DSP处理器实现步进电机高速细分模块的设计方案研究
使用Tensorflow实现线性支持向量机的形式来作为 Tensorflow 的“应用式入门教程
是德科技与高通实现突破性的10Gbps数据连接
TCL大屏电视使用,轻松打消你的顾虑
跨界巨头纷纷涌入,智能锁行业喜忧参半
贸泽备货Qorvo QPQ1298高性能BAW滤波器 为n41子频段 5G网络基础设施提供支持
航天通信子公司智慧海派荣登2019年电子信息百强企业榜单!
百度Apollo伴你“童”行,打造极致体验的新出行生活方式
人工合成并彻底改变了首个全基因组生物
Intel的7nm基于EUV光刻技术有望2020年底推出 10nm或将成为Intel最短命的一代制程
国星光电正式推出一系列第三代半导体新产品
首曝英特尔八代,频率提高
骁龙820和天玑810哪个好
Vishay新款红外传感器可在阳光直射下稳定测距
利用废弃的3D打印粉末和零件来制造注塑汽车零件
小米MAX2什么时候上市?小米MAX2最新消息:小米MAX2值得期待的3大理由,小米MIUI9不发布小米Note3来弥补