TensorFlow导读翻译

Hello TensorFlow翻译


By Aaron Schumacher June 20, 2016
在文中我会保留关于TensorFlow的一些专有名词为英文以便在实践中更好地理解    

TensorFlow项目可能比之前我们意识到的更为强大,它丰富的Deep Learning库以及它和Google的关联使得它获得了很大的关注。但是在大肆宣传的表面下,这个项目实际上有这不少值得我们关注的元素:

  • 它核心的类库适用于大多数机器学习方法,而不仅仅局限于深度学习
  • 线性代数以及其他的内部组件我们可以直观地看到
  • 除了核心的机器学习功能,TensorFlow同时有着自己的日志系统,有着自己的交互式日志查看器和自己强大的工程服务架构
  • TensorFlow的实现模型和Python的类库scikit-learn以及大多数的R语言工具都有所不同。

对于一个第一次接触想探索机器学习的人来说,Tensorflow有很多东西等着你去探索。
TensorFlow是如何工作的呢?我们在此停住,然后抽丝剥茧地一步步去了解。我们将探索数据流图(graph)以及构成图的各种计算过程(operations),TensorFlow是如何在训练模型中完成梯度下降的以及TensorFlow是如何将你的工程可视化的。以下的例子不能解决生产上的机器学习问题,但是会帮助你理解TensorFlow的组成,以及你之后会用到的模块。

Python和TensorFlow中变量名和程序执行的差异

TensorFlow管理变量和计算执行方式和传统的Python执行方式有所不同,Python中,一个变量名指向着一个对象(A name has an object),但是一个对象是没有名称的(An object has no name)。当我们定义foo = [] 然后定义bar = foo 这表示 foobar 相等,或者说foo 就是 bar,他们指向了同一个列表对象。

>>>foo = []
>>>bar = []
>>>foo == bar
## True
>>> foo is bar
## True

同时你也会发现id(foo)id(bar) 也是相同的。如果这个概念被混淆了,那会在程序中带来意想不到的BUG,特别实在列表这样的不确定数据结构中。在内部,Python通过持续追踪记录程序中的变量名和它们指向的对象来管理所有的对象。TensorFlow中的图(Graph)则展现了这种管理方式的另外一个层面,我们将会看到,Python中的变量名往往和TensorFlow的各种计算过程(operations)在一个粒度层次。

当我们在Python的即时交互终端中输入一个变量,不管你输入什么,它都会马上给你一个计算的结果,例如,如果我输入foo.append(bar) Python会立即对列表进行扩展,即使我之后再也不会使用到foo这个列表。
而一种更‘懒惰’的计算方式将会是这样,当我输入foo.append(bar),Python仅仅记住我这个操作,当我在之后再次用到foo 这个参数的时候再真正地执行扩展操作。这样的计算执行方式就和TensorFlow比较接近了,TensorFlow定义一个操作和真正地执行出结果是分开的:一个graph定义了所有的variables和operations,但是这些操作仅仅会在一个session中被操作,Graph和session是单独被定义和创建的。一个Graph好比一张蓝图,而一个session就好比一个施工工地了。

回到我们刚才的例子,foobar,他们指向了同一个列表对象,并且我们将bar插入foo中,就是我们将一个列表插入了它自己中,我们可以将这个结构想象成TensorFlow中一个包含一个节点的Graph,嵌套列表是表达TensorFlow计算图的很好的一种方式。

>>>foo.append(bar)
>>>foo
##[[...]]

当然,真正地TensorFlow计算图会更为复杂和有趣。

最简单的TensorFlow计算图

在我们正式上手之前,我们来从头开始创建我们可以创建的最简单TensorFlow计算图。TensorFlow相对于其他机器学习框架而言,安装十分简单,大家可以参考 https://www.tensorflow.org/install/ Windows版本需要Python 3.3的环境(译者注)。本文中的例子是运行环境是Python 2.7,TensorFlow的版本是0.8。

>>> import tensorflow as tf

当我们引用了之后,就会存在一个隐藏的默认图,它被存放在_default_graph_stack 中,但是我们不能直接访问它,需要使用tf.get_default_graph()

>>> graph1 = tf.get_default_graph()

在TensorFlow计算图中的节点被称为operations或者ops。我可以通过graph.get_operations()来查看计算图中的ops

>>>graph1.get_operations()
## []

目前,在当前的graph1这个计算图中没有任何操作,我们会将任何我们需要TensorFlow帮我们计算的资源放入去其中,让我们从一个简单的常量开始。

>>> input_value = tf.constant(1.0)

这个常量现在就会以一个节点或者说一个ops存在于计算图中。Python变量名input_value直接指向到这个ops,当然,在默认的计算图中我们会找到这个操作存在。

```python
>>> operations = graph1.get_operations() #这是一个list
>>> print type(operations)
## <type 'list'>
>>> operations
## [<tensorflow.python.framework.ops.Operation object at 0x4db7910>]
>>> operations[0].node_def
## name: "Const"
## op: "Const"
## attr {
##   key: "dtype"
##   value {
##     type: DT_FLOAT
##   }
## }
## attr {
##   key: "value"
##   value {
##     tensor {
##       dtype: DT_FLOAT
##       tensor_shape {
##       }
##       float_val: 1.0
##     }
##   }
## }
```

TensorFlow内部采用了协议缓冲区(和JSON类似)。用于打印操作的具体信息的方法node_def可以让我们看到TensorFlow在内部是如何存放operations列表中第一个操作的。对于一个刚接触TensorFlow的人可能会疑惑为什么我们不能直接使用Python的变量而是去定义一大堆这样的”TensorFlow版本”的对象?在官方文档中有这么一段解释:

To do efficient numerical computing in Python, we typically use libraries like NumPy that do expensive operations such as matrix multiplication outside Python, using highly efficient code implemented in another language. Unfortunately, there can still be a lot of overhead from switching back to Python every operation. This overhead is especially bad if you want to run computations on GPUs or in a distributed manner, where there can be a high cost to transferring data.

TensorFlow also does its heavy lifting outside Python, but it takes things a step further to avoid this overhead. Instead of running a single expensive operation independently from Python, TensorFlow lets us describe a graph of interacting operations that run entirely outside Python. This approach is similar to that used in Theano or Torch.

TensorFlow可以完成很多的任务,但它仅仅和明确给予它的对象工作,这对于一个简单的常量也是适用的,如果我们查看刚才的input_value,我们可以看到它是一个常量,一个没有维度的32-bit浮点型张量(Tensor):其实就是个数字。

>>> input_value
## <tf.Tensor 'Const:0' shape=() dtype=float32>

这里需要注意,我们并没有看到这个数字到底是什么。如果我们想通过计算inpur_value得到一个数字输出,我们必须创建一个session,在这个session中,一个计算图中的操作才能被真正地运行run(如果没有指定计算图,那session就会选取默认计算图)。

>>> sess = tf.Session()
>>> sess.run(input_value)
## 1.0

这里我们可能会感觉有点奇怪去运行一个常量,但是这里想说明的是TensorFlow运行一个表达式和Python常规的方式是很不一样的。TensorFlow有自己的管理方式和计算方法。

一个最简单的TensorFlow神经元

到现在为止我们定义了一个运行一个简单计算图的session,接下来让我们去构建一个包含一个参数(权重)的神经元。即使是一个最简单的神经元,也包含了一个贝叶斯偏置项和一个激活函数,但是我们在这里把这些先忽略。

神经元的权重不会是一个不变的常量,我们希望它在我们使用已知的正确的输入输出训练之后通过学习而变化,这样的一个参数会被定义成TensorFlow变量。我们给这个变量赋予0.8的初始值。

>>> weight = tf.Variable(0.8)

根据之前的观察,我们可能会认为我们添加了一个变量会给计算图中添加了一个操作,但是实际上这一行代码为计算图添加了四个操作,我们来看下:

>>> operations = graph.get_operations()
>>> for op in operations : print(op.name)
## Const
## Variable/initial_value
## Variable
## Variable/Assign
## Variable/read

我们不用一个个单独去follow每一个操作,但是至少跟踪一个感觉上像是真正的计算操作会比较好。

>>> output_value = weight * input_value

现在我们的计算图中已经有了6个操作了,最后一个就是上面定义的乘法。

>>> op = graph.get_operations()[-1]
>>> op.name
## u'mul'
>>> for op_input in op.inputs: print(op_input)
## Tensor("Variable/read:0", shape=(), dtype=float32)
## Tensor("Const:0", shape=(), dtype=float32)

这里展示了我们的乘法操作从计算图中的其他操作中获得它的输入,我们可以使用TensorFlow的可视化Web工具来帮助我们更好地理解一个计算图。

为了得到它的输出,我们必须run output_value这个操作。但是这个操作是依赖于一个变量weight的。我们已经在定义时告诉TensorFlow这个变量的初始值是0.8,但是在当前的这个session中,它还没有被定义。tf.initialize_all_variables()函数可以用来生成一个操作来初始化所有变量,在变量初始化之后我们就可以运行操作了。

>>> init = tf.initialize_all_variables()
>>> sess.run(init)
>>> see.run(output_value)
## 0.80000001

tf.initialize_all_variables()函数的结果会包含当前计算图中的所有变量的初始值,如果我们变量的数量发生变化的话,我们需要重新使用这个函数。

在TensorBoard中查看计算图

到现在为止,我们已经经历创建一个简单图的所有过程。现在我们来用TensorBoard来生成一个可视化图表,TensorBoard读取的是存放于每个操作中的命名空间。我们可以用这些TensorFlow命名来了解每个操作的用途,而在Python中就可以使用一个简单的变量名。

>>> x = tf.constant(1.0, name='input')
>>> w = tf.Variable(0.8, name='weight')
>>> y = tf.mul(w,x, name='output')

这里的tf.mul操作和之前的乘法相同,但是这样我们可以给予这个操作一个名字。
TensorBoard工作基于一个TensorFlowsession生成的一个输出目录。这个目录我们可以通过SummaryWriter来产生,它的第一个参数就是输出路径。(它会在你当前的Python运行目录产生一个文件夹,译者注)

>>> s_writer = tf.train.SummaryWriter('log_simple_graph',sess.graph)

之后我们就可以在终端中开启TensorBoard。

[root@testtensor Desktop]# tensorboard --logdir=log_simple_graph

TensorBoard以一个本地Web应用开启,端口是6006(“6006”是“goog”倒过来写),我们可以在TensorBoard中看到我们刚才的计算图 Figure 1。
Figure 1. TensorBoard中的可视化计算图

开启神经元的学习功能

当前我们构建了一个最简单的神经元,那如何才能让神经元开始学习呢?我们设置了一个input初始值为1.0,我们暂时简单地假设正确的输出应该是0,那么我们有了一个非常简单的训练数据集,训练数据集中仅有一个包含一个特征的训练样本,输入值为1.0,标签值为0。我们希望神经元通过学习能使得函数在输入1.0时输出值为0。

当前的系统,获取输入1.0之后输出0.8,这个在我们简单地假设中并不正确,正确的值应该是0。我们需要定义一个方法来衡量系统的错误大小,(一般在机器学习中这个方法称为损失函数或者成本函数,译者注)。在这里我们设定一个操作loss定义系统的错误大小,并设定系统的目标为尽可能地减小这个错误大小,当然如果这个值变成了负数,那就使得系统减小它的目标和我们预期地背道而驰,所以我们一般会用上偶次方来使得这个值非负。

>>> y_ = tf.constant(0.0, name='lable')
>>> loss = (y - y_)**2

到目前为止,系统并没有做什么学习,所以我们需要添加一个优化器,我们将使用常用的梯度下降优化器来更新权重。优化器需要设定一个更新速率(一般我们成为学习速率,译者注)来控制更新的速度。

>>> optim = tf.train.GradientDescentOptimizer(learning_rate=0.025)

优化器十分智能,它能自动地在整个网络中采用合适地梯度进行计算,完成误差向后传播修正权重的过程。让我们来在我们简单的计算图中使用梯度下降优化器。

>>> grad_and_vars = optim.compute_gradients(loss)
>>> sess.run(tf.initialize_all_variables())
>>> sess.run(grad_and_vars[1][0])
## 1.6

为什么梯度是1.6呢?我们设置的损失函数为 loss=(yy_)2 所以它的导数是 2x ,所以结果显然是正确的。在复杂的系统中,TensorFlow会很好地帮我们计算和应用梯度。让我们来完成反向传播的过程。

>>> sess.run(optim.apply_gradients(grads_and_vars))
>>> sess.run(w)
## 0.75999999

weight 变量减小了大约0.04,因为优化器把权重减小了计算出的梯度*学习速率 1.60.025=0.04 ,使得权重向正确的方向变化,(可想而知,如果 y<y_ ,那梯度就是负的)。
这样手动网络调优效率不高,我可以定义一个操作,然后循环运行,以达到最终的调优结果。

>>> train_step = tf.train.GradientDescentOptimizer(0.025).minimize(loss)
>>> for i in range(100):
...     sess.run(train_step)
>>>
>>> sess.run(y)

经过100次学习后,网络权重和输出已经十分接近0了,网络学习成功。

用TensorBoard进行训练诊断

我们可能会对训练过程中变量的变化感兴趣,所以我们可以在训练过程中将误差或者y值打印出来。

>>> sess.run(tf.initialize_all_variables())
>>> for i in range(100):
...     print('before step {0},loss is {1}'.format(i,sess.run(loss)))
...     sess.run(train_step)

这样我们就能很好地参与到学习的过程中去,但是一连串的数字毕竟难以理解,所以,如果能以图表的形式展现出来那就更好了。并且我们可能对很多东西都很关心,所以有组织地记录所有信息显得很重要。
幸运的是,之前我们用来可视化计算图的TensorBoard也同样有这样的功能,我们之前通过总结为计算图添加的操作的状态来描绘计算图,在这里,我们建立一个操作来报告当前的y值,也就是当前的神经元的输出。

>>> summary_y = tf.scalar_summary('output', y)

当我们运行这个操作的时候,就会返回一串protocol buffer文本,然后我们将这串文本同样用SummaryWriter写入日志目录。

>>> summary_writer = tf.train.SummaryWriter('log_simple_stats')
>>> sess.run(tf.initialize_all_variables())
>>> for i in range(100):
...     summary_str = sess.run(summary_y)
...     SummaryWriter.add_summary(summary_str, i)
...     sess.run(train_step)

现在当我们运行tensorboard --logdir=log_simple_stats就可以在localhost:6006/#events看到图表。

我们最终版的代码如下:

import tensorflow as tf

x = tf.constant(1.0, name='input')
w = tf.Variable(0.8, name='weight')
y = tf.mul(w, x, name='output')
y_ = tf.constant(0.0, name='correct_value')
loss = tf.pow(y - y_, 2, name='loss')
train_step = tf.train.GradientDescentOptimizer(0.025).minimize(loss)

for value in [x, w, y, y_, loss]:
    tf.scalar_summary(value.op.name, value)

summaries = tf.merge_all_summaries()

sess = tf.Session()
summary_writer = tf.train.SummaryWriter('log_simple_stats', sess.graph)

sess.run(tf.initialize_all_variables())
for i in range(100):
    summary_writer.add_summary(sess.run(summaries), i)
    sess.run(train_step)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值