(译文)开始学习tensorflow

貌似没看到网上又最新版的中文教程,反正自己是从零开始学tf,就译一下吧,也算加深理解。因为是自用,用语尽量简洁易懂,可能会有意译和原文没有的小改动、括号注释。我不会翻的英文学术名词不翻。联系:315067671@qq.com。

原文地址(2017年5月27日时):https://www.tensorflow.org/get_started/get_started

开始学习tensorflow

本教程将带你开始用tensorflow编程。读之前先安装tensorflow。要搞懂这个教程的大部分内容,你至少要:

  • 会写python
  • 至少懂一点数组
  • 最好是会点机器学习,不过不怎么懂或者完全不懂也没事,这仍然是你的第一篇教程。

Tensorflow提供了很多API,最底层的叫TensorFlow Core,它提供完整的编程控制(我理解就是说底层控制)。我们推荐搞机器学习的研究人员或者其他的想对自己的模型有一个很好的控制的人用TensorFlow Core。其他一些更上层的API可以使重复性的工作更简单,满足不同使用者的需求。像是tf.contrib.learn就可以帮你管理数据集、统计量,还有训练和统计推断。注意,少数的高级API——方法名里带contrib的那些——是还在开发中的。可能随着更新这些带contrib的方法是会变的。

本教程从TensorFlow Core开始讲。接下去我们会展示怎么用tf.contrib.learn来实现等价的模型。在你用其他的一些更高级紧凑的API的时候,知道TensorFlow Core的原则会使你大脑里面有关tf内部是怎么工作的图景更清楚一些。

Tensors

TensorFlow中核心的数据单位就是tensor。一个tensor就是一个装了原始数据任意维度的数组。维度叫做rank。举几个例子:

[] 空数组,rank为0的tensor

[1., 2., 3.] rank为1的tensor,也就是一个shape是[3]的向量

[[1., 2., 3.], [4., 5., 6.]] rank为2的tensor,也就是一个shape是[2, 3]的矩阵

[[[1., 2., 3.]], [[7., 8., 9.]]] rank为3的tensor,shape是[2, 1, 3]

TensorFlow Core教程

导入TensorFlow

权威的导入方法是这样的

import tensorflow as tf

这样就让Python可以访问所有的TensorFlow类、方法和符号。大部分的教程默认你已经做过这步了。

计算图 The Computational Graph

可以把TensorFlow Core编程想成两部分:

  1. 构造计算图
  2. 运行计算图

计算图 Computational Graph 这个概念就是把一系列的TensorFlow操作组织成一个由节点组成的图。下面来建个简单的计算图试试看。每个节点输入0或多个tensor,输出一个tensor。常量 constant就是一种节点,它不需要输入,输出的值是存储在自己内部的。我们可以来构造两个浮点数常量tensor node1和node2。

node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0) # 隐式的tf.float32
print(node1, node2)

输出:

Tensor("Const:0", shape=(), dtype=float32) Tensor("Const_1:0", shape=(), dtype=float32)

注意了,print出来的结果不是3.0和4.0,因为node1和node2是节点,会产生3.0和4.0这样的tensor的节点。要真正求节点的值就要用session。session封装了TensowFlow运行的控制和状态。

下面的代码建立一个Session对象然后用它的run方法来跑这计算图,然后求node1和node2的值。

sess = tf.Session()
print(sess.run([node1, node2]))

应该看到输出:

[3.0, 4.0]

用操作节点来把不同的Tensor结合到一起就可以构造更复杂一点的计算。比如说我们可以把两个常量加起来得到新的计算图:

node3 = tf.add(node1, node2)
print("node3: ", node3)
print("sess.run(node3): ",sess.run(node3))

这样就得到输出的结果是

node3:  Tensor("Add_2:0", shape=(), dtype=float32)
sess.run(node3):  7.0

TensorFlow提供了一个叫TensorBoard的功能来可视化计算图。截图如下:

这图什么太大意思,因为它就是输出个常量。不过一个计算图可以用参数来表示获得外部输入,称之为占位符 placeholder。占位符就是保证接下来这个位置会被提供一个值。

a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b  # +号提供了tf.add(a, b)的快捷方法

这三行代码有点像所谓的函数或者是lambda表达式,因为定义了两个输入参数a和b,然后执行了一个操作。我们可以传入feed_dict这个参数,明确指定提供具体值的tensor,来算这个图的值。

print(sess.run(adder_node, feed_dict = {a: 3, b:4.5}))
print(sess.run(adder_node, feed_dict = {a: [1,3], b: [2, 4]}))

输出

7.5
[ 3.  7.]

TensorBoard中的结构是这样的:

也可以再加一些操作,让计算图变得更加复杂,比如:

add_and_triple = adder_node * 3.
print(sess.run(add_and_triple, {a: 3, b:4.5}))

得到输出

22.5

现在的计算图是这个样子的:

最典型的就是在机器学习里面我们就想要一个模型可以接受这样的任意输入,这一点已经解决了。接下来要让这个模型变得可训练,就要有变量这个概念,让模型(在训练前后)对于相同的输入能给出不同的输出。变量 Variables允许我们向图里加可以训练的参量。变量由类型和初始值来构造:

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
linear_model = W * x + b

用tf.constant建立一个常量的话,它马上就被初始化然后值就永远不变了。与之不同的是,变量在执行tf.Variable的时候是不被初始化的。要初始化TensorFlow项目中的所有变量,还要显式地执行以下操作:

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

理解这点很重要。init是一个要求TensorFlow子图初始化所有变量的操作,在做sess.run(init)之前,所有的变量都是未初始化的。

我们可以一齐输入好几个x来计算linear_model的值:

print(sess.run(linear_model, {x:[1,2,3,4]}))

产生输出:

[ 0.          0.30000001  0.60000002  0.90000004]

现在我们建立了一个模型,但是还不知道这个模型好不好。为了在一些训练数据上面评价这个模型,我们需要一个y占位符来提供目标输出,还要写一个损失函数。

损失函数是用来丈量现在这个模型和给定的真实数据之间的差距的。对于线性模型来说,一个标准的损失函数可以是模型给出的值和真正目标输出之间差的平方和。

linear_model - y就可以得到一系列模型输出值和目标输出值的差,这个结果是一个向量,每一个元素就是一个差。我们用tf.square平方一下这个差。然后用tf.reduce_sum方法把他们都加起来就得到了总的平方差的量了。

y = tf.placeholder(tf.float32)
squared_deltas = tf.square(linear_model - y)
loss = tf.reduce_sum(squared_deltas)
print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))

结果这个损失量是:

23.66

我们可以手工来改变W和b的值来减小这个差距。tf.assign可以改变初始化过的变量的值,很明显这里让W=b=-1就行了。

fixW = tf.assign(W, [-1.])
fixb = tf.assign(b, [1.])
sess.run([fixW, fixb])
print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))

这个正好是我们数据的分布,所以损失就是0:

0.0

这里我们猜出了W和b的完美值,不过机器学习的目的就是自动去找到正确的模型参数。下一节我们展示如何来做到这一点。

tf.train API

对于机器学习比较的完备讨论已经超出了这个教程的探讨内容。不过可以简单地说,TensorFlow提供了优化器 optimizer,这些优化器可以缓慢地改变各个变量,使得损失函数最小化。最简单的优化器是梯度下降(gradient descent)优化器。GD会对loss函数里面的某一变量求导数,然后根据这个导数的值去改变对应的变量。总的来说用参量符号去手动求微分是一个又无聊又容易出错的过程。因而,给定一个模型的描述,用tf.gradients方法,TensorFlow可以自动生成对应导数。简单起见,优化器可以帮你做这件事。举例来说:

optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)
sess.run(init) # 把变量的值重置为不正确的随机值
for i in range(1000):
  sess.run(train, {x:[1,2,3,4], y:[0,-1,-2,-3]})

print(sess.run([W, b]))

结果:

[array([-0.9999969], dtype=float32), array([ 0.99999082],
 dtype=float32)]

到这里我们真正意义上实现了机器学习!达成这个简单的线性回归不需要很多TensorFlow code的代码,不过如果要构建更加复杂的模型和方法就使得写更多的代码了。因此TensorFlow为公共的一些模式、结构和功能提供了更加高层次的抽象。下一节,我们将会学习如何使用这些抽象。

完整代码

完整的可训练的线性回归模型代码如下:

import numpy as np
import tensorflow as tf

# 模型参数
W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
# 模型的输入和输出
x = tf.placeholder(tf.float32)
linear_model = W * x + b
y = tf.placeholder(tf.float32)
# 损失函数
loss = tf.reduce_sum(tf.square(linear_model - y)) # 平方差和
# 优化器
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)
# 训练数据
x_train = [1,2,3,4]
y_train = [0,-1,-2,-3]
# 训练循环
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init) # 初始化变量
for i in range(1000):
  sess.run(train, {x:x_train, y:y_train})

# 验证训练出来的准确率
curr_W, curr_b, curr_loss  = sess.run([W, b, loss], {x:x_train, y:y_train})
print("W: %s b: %s loss: %s"%(curr_W, curr_b, curr_loss))

运行的话会得到结果:

W: [-0.9999969] b: [ 0.99999082] loss: 5.69997e-11

这个更加复杂一点的函数仍旧可以再TensorBoard里被可视化。

tf.contrib.learn

tf.contrib.learn是一个抽象层次很高的TensorFlow库,它主要是用来简化机器学习的方法,包括:

  • 运行训练循环
  • 运行验证循环
  • 管理数据集
  • 管理数据的Feeding(一种数据提供机制)

基本用法

看看线性回归程序用tf.contrib.learn变得多简单:

import tensorflow as tf
# 通常numpy需要引入,用来导入、操作和处理数据
import numpy as np

# 声明特征的列表。我们要的就是一个单值特征。还有一些其他有用和复杂的类型。
features = [tf.contrib.layers.real_valued_column("x", dimension=1)]

# 一个评估器 estimator是使用数据训练和验证的端点。像是线性回归、logistic回归,线性分类,logistic分类以及其他很多神经网络分类器和回归器都是预定义好了的。下面的代码提供了一个线性回归评估器。
estimator = tf.contrib.learn.LinearRegressor(feature_columns=features)

# TensorFlow提供了很多帮助函数来读取和建立数据集,这里我们用numpy_input_fn。另外还必须声明我们想要多大批量的数据(num_epochs)和每个batch应该有多大。
x = np.array([1., 2., 3., 4.])
y = np.array([0., -1., -2., -3.])
input_fn = tf.contrib.learn.io.numpy_input_fn({"x":x}, y, batch_size=4,
                                              num_epochs=1000)

# 传给fit方法训练数据和steps以训练一千次。
estimator.fit(input_fn=input_fn, steps=1000)

# 这里测试我们的模型的效果。在真实的例子里面应该用一个独立的测试集来做验证,避免过拟合。
print(estimator.evaluate(input_fn=input_fn))

运行,将会得到结果:

    {'global_step': 1000, 'loss': 1.9650059e-11}

定制模型

tf.contrib.learn并不会把你限死在预定义的模型里。假如说我们要建立一个定制化的模型,在TensorFlow里面又没有预制,我们依旧可以使用tf.contrib.learn的一些高级的抽象,包括数据集、feeding、训练,等等。为了说明这个问题,我们将会展示如何利用我们的知识和更底层的TensorFlow API来实现我们自己的等价于LinearRegressor的模型。

来利用tf.contrib.learn定义一个定制的模型,我们需要使用tf.contrib.learn.Estimator。实际上tf.contrib.learn.LinearRegressor就是tf.contrib.learn.Estimator的一个子类。这里我们直接传给Estimator一个model_fn函数,告诉tf.contrib.learn如何验证预测结果、训练程度和损失。代码如下:

import numpy as np
import tensorflow as tf
# 声明一系列的特征,我们只需要一个单值特征。
def model(features, labels, mode):
  # 建立一个线性模型来预测值
  W = tf.get_variable("W", [1], dtype=tf.float64)
  b = tf.get_variable("b", [1], dtype=tf.float64)
  y = W*features['x'] + b
  # 损失函数子图
  loss = tf.reduce_sum(tf.square(y - labels))
  # 训练子图  
  global_step = tf.train.get_global_step()
  optimizer = tf.train.GradientDescentOptimizer(0.01)
  train = tf.group(optimizer.minimize(loss),
                   tf.assign_add(global_step, 1))
  # ModelFnOps 方法把我们建立的子图和适当的方法结合起来。
  return tf.contrib.learn.ModelFnOps(
      mode=mode, predictions=y,
      loss=loss,
      train_op=train)

estimator = tf.contrib.learn.Estimator(model_fn=model)
# 定义数据集
x = np.array([1., 2., 3., 4.])
y = np.array([0., -1., -2., -3.])
input_fn = tf.contrib.learn.io.numpy_input_fn({"x": x}, y, 4, num_epochs=1000)

# 训练
estimator.fit(input_fn=input_fn, steps=1000)
# 验证模型
print(estimator.evaluate(input_fn=input_fn, steps=10))

运行后输出结果

{'loss': 5.9819476e-11, 'global_step': 1000}

注意到定制的model()函数的内容和我们手动创建的模型里面训练循环是很相似的。

下一步

现在你已经有了TensorFlow的基本知识。可以看看其他的教程来学习更多的东西。如果你是机器学习的初学者,可以看MNIST for beginners,否则的话看看Deep MNIST for experts。

2017-05-27 11:3945