TensorFlow 101(含全部代码)

代码下载链接:

https://github.com/PacktPublishing/Mastering-TensorFlow-1x/archive/master.zip

TensorFlow是解决机器学习和深度学习问题的流行库之一。TensorFlow在供Google内部使用一段时间后,作为开源发布供公众使用和开发。让我们了解TensorFlow的三个模型:数据模型编程模型执行模型

TensorFlow数据模型由张量组成,编程模型由数据流图或计算图组成。TensorFlow执行模型包括基于依赖条件从序列中触发节点,以及从依赖于输入的初始节点开始。

在本文中,我们将回顾构成这三个模型的TensorFlow元素,也称为核心TensorFlow。

我们将在本文中介绍以下主题:

  • TensorFlow 核心
    • 张量
    • 常量
    • 占位符
    • 运算
    • 根据 Python 对象创建张量
    • 变量
    • 使用库函数生成的张量
  • 数据流图或计算图
    • 执行顺序和延迟加载
    • 跨计算设备执行图——CPU和GPGPU
    • 多重图
  • TensorBoard 概览

什么是 TensorFlow ?

根据TensorFlow网站(www.tensorflow.orgtensorflow.google.cn):

TensorFlow是一个使用数据流图进行数值计算的开源库。

最初由谷歌开发用于其内部使用,TensorFlow 于2015年11月9日作为开源发布。从那时起,TensorFlow已被广泛用于开发各种领域的机器学习和深度神经网络模型,并继续在谷歌内部用于研究 和产品开发。TensorFlow 1.0于2017年2月15日发布。让人怀疑这是否是Google向机器学习工程师赠送的情人节礼物!

TensorFlow可以用数据模型,编程模型和执行模型来描述:

  • 数据模型:由张量组成,它们是在TensorFlow程序中创建,操作和保存的基本数据单元。
  • 编程模型:包括数据流图或计算图。在TensorFlow中创建程序意味着构建一个或多个TensorFlow计算图。
  • 执行:模型包括以依赖序列触发计算图的节点。从运行直接连接到输入的节点开始执行,且结果仅依赖于当前输入。

要在项目中使用TensorFlow,您需要学习如何使用TensorFlow API进行编程。TensorFlow有多个API,可用于与库交互。TF API或库分为两个级别:

  • 低级别库:较低级别的库(也称为TensorFlow核心)提供了非常精细的较低级别功能,从而可以完全控制如何在模型中使用和实现库。我们将在本文介绍TensorFlow核心。
  • 高级别库:这些库提供了高级功能,并且在模型中相对容易学习和实现。一些库包括TF Estimators、TFLearn、TFSlim,Sonnet和Keras。我们将在接下来的文章中介绍其中一些库。

TensorFlow 核心

TensorFlow核心是较低级别的库,其上构建了更高级别的TensorFlow模块。在深入学习高级TensorFlow之前,学习低级库的概念非常重要。在本节中,我们将快速回顾所有这些核心概念。

代码热身 —— Hello, TensorFlow

作为学习任何新编程语言,库或平台的习惯传统,让我们在深入探讨之前编写简单的Hello TensorFlow代码作为热身练习。

我们假设您已经安装了TensorFlow。 如果还没有,请参阅https://www.tensorflow.org/install/https://tensorflow.google.cn/install/ 上的TensorFlow安装指南,了解安装TensorFlow的详细说明。

现在 Google 已推出 TensorFlow 2.0 测试版。而本文及接下来的文章仍以 TensorFlow 1.x 为主。若 Google 为你呈现的是 TensorFlow 2.0 的有关内容,请自行切换到 TensorFlow 1.x 相关内容。

在Jupyter Notebook中打开文件ch-01_TensorFlow_101.ipynb,在学习文本时跟随并运行代码。

  1. 使用以下代码导入TensorFlow库:
import tensorflow as tf
  1. 获取TensorFlow会话。TensorFlow提供两种会话:Session()和InteractiveSession()。 我们将使用以下代码创建交互式会话:
tfs = tf.InteractiveSession()

Session() 和 InteractiveSession() 之间的唯一区别是使用InteractiveSession() 创建的会话成为默认会话。因此,我们不需要指定会话上下文以便稍后执行与会话相关的命令。例如,假设我们有一个会话对象,tfs和一个常量对象,hello。 如果tfs是一个InteractiveSession() 对象,那么我们可以使用代码hello.eval()来计算hello。如果tfs是Session()对象,那么我们必须使用tfs.hello.eval()或with块。最常见的做法是使用with块,这将在本文后面介绍。

  1. 定义一个 TensorFlow 常量 Hello:
hello = tf.constant("Hello TensorFlow !!")
  1. 在TensorFlow会话中执行常量并打印输出:
print(tfs.run(hello))
  1. 您将获得以下输出:
'Hello TensorFlow !!'

现在您已经使用TensorFlow编写并执行了前两行代码,让我们来看看TensorFlow的基本组成部分。

张量

张量是TensorFlow中计算的基本元素和基本数据结构,也可能是您需要学习使用TensorFlow的唯一数据结构。张量是由维度,形状和类型标识的n维数据集合。

是张量的维数,形状是表示每个维的大小的列表。张量可以具有任意数量的维度。 您可能已经熟悉零维集合(标量),一维集合(矢量),二维集合(矩阵)和多维集合的维度数量了。

标量值是秩为0的张量,因此具有[1]的形状。 向量或一维数组是秩为1的张量,其形状为[columns]或[rows]。矩阵或二维阵列是秩为2的张量,并且具有[行,列]的形状。三维阵列将是秩为3的张量,并且以此类推,n维阵列将是秩为n的张量。

请参阅以下资源以了解有关张量及其数学基础的更多信息:

Wikipedia 的“张量”:https://en.wikipedia.org/wiki/Tensor

来自 NASA 的有关“张量”的介绍: https://www.grc.nasa.gov/www/k-12/Numbers/Math/documents/Tensors_TM2002211716.pdf

张量可以在其所有维度中存储一种类型的数据,并且其元素的数据类型被称为张量的数据类型。

您还可以在以下位置检查最新版本的TensorFlow库中定义的数据类型: https://www.tensorflow.org/api_docs/python/tf/dtypes/DType

https://tensorflow.google.cn/api_docs/python/tf/dtypes/DType

在撰写本文时,TensorFlow定义了以下数据类型:

TensorFlow Python API 数据类型描述
tf.float1616位半精度浮点型
tf.float3232位单精度浮点型
tf.float6464位双精度浮点型
tf.bfloat1616位截断浮点型
tf.complex6464位单精度复数型
tf.complex128128位双精度复数型
tf.int88位有符号整型
tf.uint88位无符号整型
tf.int1616位有符号整型
tf.uint1616位无符号整型
tf.int3232位有符号整型
tf.int6464位有符号整型
tf.bool布尔型
tf.string字符串型
tf.qint88位量化有符号整型
tf.quint88位量化无符号整型
tf.qint1616位量化有符号整型
tf.quint1616位量化无符号整型
tf.qint3232位量化有符号整型
tf.resource处理可变资源

我们建议您避免使用Python本机数据类型。使用TensorFlow数据类型来定义张量,而不是Python本机数据类型。

可以通过以下方式创建张量:

  • 通过定义常量、运算和变量,并将值传递给它们的构造函数。
  • 通过定义占位符并将值传递给session.run()。
  • 通过使用tf.convert_to_tensor()函数转换标量值,列表和NumPy数组等Python对象。

让我们来看看创建张量的不同方法。

常量

常量值张量是使用具有以下签名的tf.constant()函数创建的:

tf.constant(
 value,
 dtype=None,
 shape=None,
 name='Const',
 verify_shape=False
)

让我们看看本文中Jupyter笔记本中提供的示例代码:

c1=tf.constant(5,name='x')
c2=tf.constant(6.0,name='y')
c3=tf.constant(7.0,tf.float32,name='z')

让我们详细研究一下代码:

  • 第一行定义了一个张量常量c1,赋值为5,并将其命名为x。
  • 第二行定义了一个张量常量c2,赋值为6.0,并将其命名为y。
  • 当我们打印这些张量时,我们看到c1和c2的数据类型是由TensorFlow自动推导出来的。
  • 要专门定义数据类型,我们可以使用dtype参数或将数据类型作为第二个参数。 在前面的代码示例中,我们将c3的数据类型定义的tf.float32。

让我们打印常量c1,c2和c3:

print('c1 (x): ',c1)
print('c2 (y): ',c2)
print('c3 (z): ',c3)

当我们打印这些常量时,我们得到以下输出:

c1 (x): Tensor("x:0", shape=(), dtype=int32)
c2 (y): Tensor("y:0", shape=(), dtype=float32)
c3 (z): Tensor("z:0", shape=(), dtype=float32)

为了打印这些常量的值,我们必须使用tfs.run()命令在TensorFlow会话中执行它们:

print('run([c1,c2,c3]) : ',tfs.run([c1,c2,c3]))

我们看到以下输出:

run([c1,c2,c3]) : [5, 6.0, 7.0]

运算

TensorFlow为我们提供了许多可以应用于张量的运算。一个运算定义为,通过传递值并将输出分配给另一个张量。例如,在提供的Jupyter Notebook文件中,我们定义了两个操作,op1和op2。

op1 = tf.add(c2,c3)
op2 = tf.multiply(c2,c3)

当我们打印op1和op2时,我们发现它们被定义为张量:

print('op1 : ', op1)
print('op2 : ', op2)

输出如下:

op1 : Tensor("Add:0", shape=(), dtype=float32)
op2 : Tensor("Mul:0", shape=(), dtype=float32)

要打印这些运算的值,我们必须在TensorFlow会话中运行它们:

print('run(op1) : ', tfs.run(op1))
print('run(op2) : ', tfs.run(op2))

输出如下:

run(op1) : 13.0
run(op2) : 42.0

下表列出了一些内置操作:

运算类型运算
算术运算tf.add, tf.subtract, tf.multiply, tf.scalar_mul, tf.div,
tf.divide, tf.truediv, tf.floordiv, tf.realdiv, tf.truncatediv,
tf.floor_div, tf.truncatemod, tf.floormod, tf.mod, tf.cross
基本数学运算tf.add_n, tf.abs, tf.negative, tf.sign, tf.reciprocal,
tf.square, tf.round, tf.sqrt, tf.rsqrt, tf.pow, tf.exp, tf.expm1,
tf.log, tf.log1p, tf.ceil, tf.floor, tf.maximum, tf.minimum,
tf.cos, tf.sin, tf.lbeta, tf.tan, tf.acos, tf.asin, tf.atan,
tf.lgamma, tf.digamma, tf.erf,
tf.erfc, tf.igamma, tf.squared_difference, tf.igammac, tf.zeta,
tf.polygamma, tf.betainc, tf.rint
矩阵数学运算tf.diag, tf.diag_part, tf.trace,
tf.transpose, tf.eye, tf.matrix_diag, tf.matrix_diag_part,
tf.matrix_band_part, tf.matrix_set_diag, tf.matrix_transpose,
tf.matmul, tf.norm, tf.matrix_determinant, tf.matrix_inverse,
tf.cholesky, tf.cholesky_solve, tf.matrix_solve,
tf.matrix_triangular_solve, tf.matrix_solve_ls, tf.qr,
tf.self_adjoint_eig, tf.self_adjoint_eigvals, tf.svd
张量数学运算tf.tensordot
复数运算tf.complex, tf.conj, tf.imag, tf.real
字符串运算tf.string_to_hash_bucket_fast, tf.string_to_hash_bucket_strong,
tf.as_string, tf.encode_base64, tf.decode_base64,
tf.reduce_join, tf.string_join, tf.string_split, tf.substr,
tf.string_to_hash_bucket

占位符

虽然常量允许我们在定义张量时提供值,但占位符允许我们创建可在运行时提供其值的张量。TensorFlow为tf.placeholder()函数提供以下签名以创建占位符:

tf.placeholder(
 dtype,
 shape=None,
 name=None
)

例如,让我们创建两个占位符并打印它们:

p1 = tf.placeholder(tf.float32)
p2 = tf.placeholder(tf.float32)
print('p1 : ', p1)
print('p2 : ', p2)

我们看到以下输出:

p1 : Tensor("Placeholder:0", dtype=float32)
p2 : Tensor("Placeholder_1:0", dtype=float32)

现在让我们使用这些占位符定义一个运算:

op4 = p1 * p2

TensorFlow允许使用速记符号进行各种运算。在前面的例子中,p1 * p2是tf.multiply(p1,p2)的简写:

print('run(op4,{p1:2.0, p2:3.0}) : ',tfs.run(op4,{p1:2.0, p2:3.0}))

上面的命令在TensorFlow会话中运行op4,使用p1和p2送给Python字典(传递给run()运算的第二个参数)。

输出如下:

run(op4,{p1:2.0, p2:3.0}) : 6.0

我们还可以在run()运算中使用feed_dict参数指定字典:

print('run(op4,feed_dict = {p1:3.0, p2:4.0}) : ', tfs.run(op4, feed_dict={p1: 3.0, p2: 4.0}))

输出如下:

run(op4,feed_dict = {p1:3.0, p2:4.0}) : 12.0

让我们看一下最后一个例子,向量被送到同一个运算:

print('run(op4,feed_dict = {p1:[2.0,3.0,4.0], p2:[3.0,4.0,5.0]}) : ', tfs.run(op4,feed_dict = {p1:[2.0,3.0,4.0], p2:[3.0,4.0,5.0]}))

输出如下:

run(op4,feed_dict={p1:[2.0,3.0,4.0],p2:[3.0,4.0,5.0]}):[ 6. 12. 20.]

两个输入向量的逐个元素方式相乘。

根据 Python 对象创建张量

我们可以使用带有以下签名的tf.convert_to_tensor()运算根据Python对象(如list和NumPy数组)创建张量:

tf.convert_to_tensor(
 value,
 dtype=None,
 name=None,
 preferred_dtype=None
)

让我们创建一些张量并打印出来进行练习:

  1. 创建并打印一个零维张量:
tf_t=tf.convert_to_tensor(5.0,dtype=tf.float64)

print('tf_t : ',tf_t)
print('run(tf_t) : ',tfs.run(tf_t))

输出如下:

tf_t : Tensor("Const_1:0", shape=(), dtype=float64)
run(tf_t) : 5.0
  1. 创建并打印一个一维张量:
a1dim = np.array([1,2,3,4,5.99])
print("a1dim Shape : ",a1dim.shape)

tf_t=tf.convert_to_tensor(a1dim,dtype=tf.float64)

print('tf_t : ',tf_t)
print('tf_t[0] : ',tf_t[0])
print('tf_t[0] : ',tf_t[2])
print('run(tf_t) : \n',tfs.run(tf_t))

输出如下:

a1dim Shape : (5,)
tf_t : Tensor("Const_2:0", shape=(5,), dtype=float64)
tf_t[0] : Tensor("strided_slice:0", shape=(), dtype=float64)
tf_t[0] : Tensor("strided_slice_1:0", shape=(), dtype=float64)
run(tf_t) : [ 1. 2. 3. 4. 5.99]
  1. 创建并打印一个二维张量:
a2dim = np.array([(1,2,3,4,5.99),
    (2,3,4,5,6.99),
    (3,4,5,6,7.99)
])

print("a2dim Shape : ",a2dim.shape)

tf_t=tf.convert_to_tensor(a2dim,dtype=tf.float64)

print('tf_t : ',tf_t)
print('tf_t[0][0] : ',tf_t[0][0])
print('tf_t[1][2] : ',tf_t[1][2])
print('run(tf_t) : \n',tfs.run(tf_t))

输出如下:

a2dim Shape : (3, 5)
tf_t : Tensor("Const_3:0", shape=(3, 5), dtype=float64)
tf_t[0][0] : Tensor("strided_slice_3:0", shape=(), type=float64)
tf_t[1][2] : Tensor("strided_slice_5:0", shape=(), type=float64)
run(tf_t) :
 [[ 1. 2. 3. 4. 5.99]
  [ 2. 3. 4. 5. 6.99]
  [ 3. 4. 5. 6. 7.99]]
  1. 创建并打印一个三维张量:
a3dim = np.array([[[1,2],[3,4]],
                 [[5,6],[7,8]]
])

print("a3dim Shape : ",a3dim.shape)

tf_t=tf.convert_to_tensor(a3dim,dtype=tf.float64)

print('tf_t : ',tf_t)
print('tf_t[0][0][0] : ',tf_t[0][0][0])
print('tf_t[1][1][1] : ',tf_t[1][1][1])
print('run(tf_t) : \n',tfs.run(tf_t))

输出如下:

a3dim Shape : (2, 2, 2)

tf_t : Tensor("Const_4:0", shape=(2, 2, 2), dtype=float64)

tf_t[0][0][0] : Tensor("strided_slice_8:0", shape=(), dtype=float64)

tf_t[1][1][1] : Tensor("strided_slice_11:0", shape=(), dtype=float64)
run(tf_t) :
 [[[ 1. 2.][ 3. 4.]]
  [[ 5. 6.][ 7. 8.]]]

TensorFlow可以将NumPy ndarray无缝转换为TensorFlow张量,反之亦然。

变量

到目前为止,我们已经看到了如何创建各种张量对象:常量,运算和占位符。在使用TensorFlow构建和训练模型时,通常需要将参数值保存在可在运行时更新的内存位置。该内存位置由TensorFlow中的变量标识。

在TensorFlow中,变量是张量对象,它们包含可在程序执行期间修改的值。

虽然tf.Variable看起来与tf.placeholder类似,但两者之间存在细微差别:

tf.placeholdertf.Variable
tf.placeholder定义不随时间变化的输入数据tf.Variable定义随时间修改的变量值
tf.placeholder在定义时不需要初始值tf.Variable在定义时需要初始值

在TensorFlow中,可以使用 tf.Variable() 创建变量。让我们看一个带有线性模型的占位符和变量的示例:

y = W \times x + b

  1. 我们将模型参数w和b分别定义为初始值为[.3]和[-0.3]的变量:
w = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
  1. 输入x定义为占位符,输出y定义为运算:
x = tf.placeholder(tf.float32)
y = w * x + b
  1. 让我们打印w,v,x和y,看看我们得到了什么:
print("w:",w)
print("x:",x)
print("b:",b)
print("y:",y)

我们得到以下输出:

w: <tf.Variable 'Variable:0' shape=(1,) dtype=float32_ref>
x: Tensor("Placeholder_2:0", dtype=float32)
b: <tf.Variable 'Variable_1:0' shape=(1,) dtype=float32_ref>
y: Tensor("add:0", dtype=float32)

输出显示x是占位符张量,y是运算张量,而w和b是形状(1,)和数据类型float32的变量。

在TensorFlow会话中使用变量之前,必须先初始化它们。您可以通过运行其初始化程序操作来初始化单个变量。

例如,让我们初始化变量w:

tfs.run(w.initializer)

但是,在实践中,我们使用TensorFlow提供的便利函数来初始化所有变量:

tfs.run(tf.global_variables_initializer())

您还可以使用tf.variables_initializer()函数仅初始化一组变量。

也可以通过以下方式调用全局初始化程序便捷函数,而不是在会话对象的run()函数内调用:

tf.global_variables_initializer().run()

在初始化变量之后,让我们运行我们的模型来给出x = [1,2,3,4]的值的输出:

print('run(y,{x:[1,2,3,4]}) : ',tfs.run(y,{x:[1,2,3,4]}))

我们得到以下输出:

run(y,{x:[1,2,3,4]}) : [ 0. 0.30000001 0.60000002 0.90000004]

从库函数生成张量

张量也可以从各种TensorFlow函数生成。这些生成的张量可以分配给常量或变量,也可以在初始化时提供给它们的构造函数。

例如,以下代码生成100个零的向量并将其打印出来:

a=tf.zeros((100,))
print(tfs.run(a))

TensorFlow提供了不同类型的函数来在定义时填充张量:

  • 使用相同的值填充所有元素
  • 使用序列填充元素
  • 使用随机概率分布填充元素,例如正态分布或均匀分布

使用相同的值填充张量元素

下表列出了一些张量生成库函数,用于使用相同的值填充张量的所有元素:

张量生成函数描述
zeros(
shape,
dtype=tf.float32,
name=None
)
创建所提供形状的张量,所有元素都设置为零。
zeros_like(
tensor,
dtype=None,
name=None,
optimize=True
)
创建与参数形状相同的张量,所有元素都设置为零。
ones(
shape,
dtype=tf.float32,
name=None
)
创建所提供形状的张量,所有元素都设置为1。
ones_like(
tensor,
dtype=None,
name=None,
optimize=True
)
创建与参数形状相同的张量,所有元素都设置为1。
fill(
dims,
value,
name=None
)
创建一个 dims 参数指定形状的张量,且所有元素都设置为 value;例如,a = tf.fill([100], 0)

使用序列填充元素

下表列出了一些张量生成函数,用于使用序列填充张量元素:

张量生成函数描述
lin_space(
start,
stop,
num,
name=None
)
从[start,stop]范围内的num数字序列生成一维张量。张量与start参数具有相同的数据类型。例如,a = tf.lin_space(1,100,10)生成一个值为[1,12,23,34,45,56,67,78,89,100]的张量。
range(
limit,
delta=1,
dtype=None,
name=’range’
)

range(
start,
limit,
delta=1,
dtype=None,
name=’range’
)
从[start,limit]范围内的数字序列生成一维张量,增量为delta。如果未指定dtype参数,则张量具有与start参数相同的数据类型。此功能有两个版本。 在第二个版本中,如果省略start参数,则start变为数字0。例如,a = tf.range(1,91,10)生成一个值为[1,11,21,31,41,51,61,71,81]的张量。请注意,limit参数的值(即91)不包含在最终生成的序列中。

使用随机概率分布填充元素,例如正态分布或均匀分布

TensorFlow为我们提供了生成填充随机值分布的张量的函数。

生成的分布受图表级别或操作级别种子的影响。使用tf.set_random_seed设置图级别种子,而在所有随机分布函数中将操作级种子作为参数种子给出。如果未指定种子,则使用随机种子。

有关TensorFlow中随机种子的更多详细信息,请访问以下链接:

https://www.tensorflow.org/api_docs/python/tf/set_random_seed

https://tensorflow.google.cn/api_docs/python/tf/random/set_random_seed

下表列出了一些张量生成函数,用于使用随机值分布填充张量元素:

张量生成函数描述
random_normal(
shape,
mean=0.0,
stddev=1.0,
dtype=tf.float32,
seed=None,
name=None
)
生成指定形状的张量,填充正态分布的值:normal(mean,stddev)。
truncated_normal(
shape,
mean=0.0,
stddev=1.0,
dtype=tf.float32,
seed=None,
name=None
)
生成指定形状的张量,填充来自截断的正态分布的值:normal(mean,stddev)。截断意味着返回的值始终与平均值的距离小于两个标准偏差。
random_uniform(
shape,
minval=0,
maxval=None,
dtype=tf.float32,
seed=None,
name=None
)
生成指定形状的张量,填充均匀分布的值:uniform([minval,maxval])。
random_gamma(
shape,
alpha,
beta=None,
dtype=tf.float32,
seed=None,
name=None
)
生成指定形状的张量,填充伽马分布的值:gamma(alpha,beta)。
有关random_gamma函数的更多详细信息,请访问以下链接:
https://www.tensorflow.org/api_docs/python/tf/random/gamma

https://tensorflow.google.cn/api_docs/python/tf/random/gamma

使用 tf.get_variable() 获取变量

如果使用之前定义的名称定义变量,则TensorFlow会抛出异常。因此,使用tf.get_variable()函数而不是tf.Variable()是很方便的。函数tf.get_variable()返回具有相同名称的现有变量(如果存在),并创建具有指定形状的变量和初始化器(如果它不存在)。例如:

w = tf.get_variable(name='w',shape=1],dtype=tf.float32,initializer=[.3])
b = tf.get_variable(name='b',shape=1],dtype=tf.float32,initializer=[-.3])

初始化程序可以是上面示例中显示的张量或值列表,也可以是内置初始化程序之一:

  • tf.constant_initializer
  • tf.random_normal_initializer
  • tf.truncated_normal_initializer
  • tf.random_uniform_initializer
  • tf.uniform_unit_scaling_initializer
  • tf.zeros_initializer
  • tf.ones_initializer
  • tf.orthogonal_initializer

在分布式TensorFlow中,我们可以跨机器运行代码,tf.get_variable()为我们提供了全局变量。要获取局部变量,TensorFlow具有一个具有类似签名的函数:tf.get_local_variable()。

共享或重用变量:获取已定义的变量可促进重用。但是,如果未使用tf.variable_scope.reuse_variable()或tf.variable.scope(reuse = True)设置重用标志,则会引发异常。

现在您已经学习了如何定义张量,常量,运算,占位符和变量,让我们了解TensorFlow中的下一个抽象级别,它将这些基本元素组合在一起形成一个基本的计算单元,数据流图或计算图形。

数据流图或计算图

数据流图计算图是TensorFlow中的基本计算单位。我们将从现在开始将它们称为计算图。计算图由节点和边组成。每个节点表示一个运算(tf.Operation),每个边表示在节点之间传递的张量(tf.Tensor)。

TensorFlow中的程序基本上是计算图。您可以使用表示变量、常量、占位符和运算的节点创建图形,并将其提供给TensorFlow。TensorFlow找到它可以触发或执行的第一个节点。触发这些节点会导致其他节点触发,依此类推。

因此,TensorFlow程序由计算图上的两种运算组成:

  • 构建计算图
  • 运行计算图

TensorFlow附带一个默认图表。除非明确指定了另一个图形,否则会将新节点隐式添加到默认图形中。我们可以使用以下命令显式访问默认图:

graph = tf.get_default_graph()

例如,如果我们想要定义三个输入并添加它们以产生输出 y = x_1 + x_2 + x_3 ,我们可以使用以下计算图表示:

计算图

在 TensorFlow 中,前述图中的加法运算对应代码为:

y = tf.add( x1 + x2 + x3 )

在我们创建变量、常量和占位符时,它们会添加到图形中。然后我们创建一个会话对象来执行运算对象并评估张量对象。

让我们构建并执行一个计算图来计算,正如我们在前面的例子中已经看到的那样:

# Assume Linear Model y = w * x + b
# Define model parameters
w = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
# Define model input and output
x = tf.placeholder(tf.float32)
y = w * x + b
output = 0
with tf.Session() as tfs:
    # initialize and print the variable y
    tf.global_variables_initializer().run()
    output = tfs.run(y,{x:[1,2,3,4]})
print('output : ',output)

在with块中创建和使用会话可确保在块完成时会话自动关闭。否则,必须使用tfs.close()命令显式关闭会话,其中tfs是会话名称。

执行顺序和延迟加载

节点按依赖顺序执行。如果节点a依赖于节点b,则在请求执行b时a将在b之前执行。除非未请求执行节点本身或取决于它的其他节点,否则不执行节点。这也称为延迟加载;即,在需要之前不创建和初始化节点对象。

有时,您可能希望控制在图形中执行节点的顺序。这可以通过tf.Graph.control_dependencies()函数来实现。例如,如果图形具有节点a,b,c和d,并且您希望在a和b之前执行c和d,则使用以下语句:

with graph_variable.control_dependencies([c,d]):
    # other statements here

这确保了只有在执行了节点c和d之后才执行上述with块的任何节点。

跨计算设备执行计算图 – CPU和GPGPU

计算图可以分为多个部分,每个部分可以放置在不同的设备上执行,例如CPU或GPU。您可以使用以下命令列出可用于执行计算图的所有设备:

from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())

我们得到以下输出(您的输出会有所不同,具体取决于系统中的计算设备):

[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 12900903776306102093, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 611319808
locality {
  bus_id: 1
}
incarnation: 2202031001192109390
physical_device_desc: "device: 0, name: Quadro P5000, pci bus id:
0000:01:00.0, compute capability: 6.1"
]

TensorFlow中的设备使用字符串/device:<device_type>:<device_id>标识。在上面的输出中,CPU和GPU表示设备类型,0表示设备索引。

关于上述输出需要注意的一点是它只显示一个CPU,而我们的计算机有8个CPU。原因是TensorFlow隐式地在CPU单元中分配代码,因此默认情况下CPU:0表示TensorFlow可用的所有CPU。当TensorFlow开始执行计算图时,它在一个单独的线程中运行每个计算图中的独立路径,每个线程在一个单独的CPU上运行。我们可以通过更改inter_op_parallelism_threads的数量来限制用于此目的的线程数。类似地,如果在独立路径中,运算能够在多个线程上运行,TensorFlow将在多个线程上启动该特定运算。 可以通过设置intra_op_parallelism_threads的数量来更改此池中的线程数。

将图形节点放置在特定的计算设备上

让我们通过定义配置对象来启用变量放置的记录,将log_device_placement属性设置为true,然后将此配置对象传递给会话,如下所示:

tf.reset_default_graph()

# Define model parameters
w = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
# Define model input and output
x = tf.placeholder(tf.float32)
y = w * x + b

config = tf.ConfigProto()
config.log_device_placement=True

with tf.Session(config=config) as tfs:
    # initialize and print the variable y
    tfs.run(global_variables_initializer())
    print('output',tfs.run(y,{x:[1,2,3,4]}))

在Jupyter控制台中,我们看到现在变量已经放在CPU上,并且执行也发生在CPU上:

b: (VariableV2): /job:localhost/replica:0/task:0/device:CPU:0
b/read: (Identity): /job:localhost/replica:0/task:0/device:CPU:0
b/Assign: (Assign): /job:localhost/replica:0/task:0/device:CPU:0
w: (VariableV2): /job:localhost/replica:0/task:0/device:CPU:0
w/read: (Identity): /job:localhost/replica:0/task:0/device:CPU:0
mul: (Mul): /job:localhost/replica:0/task:0/device:CPU:0
add: (Add): /job:localhost/replica:0/task:0/device:CPU:0
w/Assign: (Assign): /job:localhost/replica:0/task:0/device:CPU:0
init: (NoOp): /job:localhost/replica:0/task:0/device:CPU:0
x: (Placeholder): /job:localhost/replica:0/task:0/device:CPU:0
b/initial_value: (Const): /job:localhost/replica:0/task:0/device:CPU:0
Const_1: (Const): /job:localhost/replica:0/task:0/device:CPU:0
w/initial_value: (Const): /job:localhost/replica:0/task:0/device:CPU:0
Const: (Const): /job:localhost/replica:0/task:0/device:CPU:0

简单放置

TensorFlow遵循这些简单的规则,也称为简单放置,用于将变量放在设备上:

If the graph was previously run,
    then the node is left on the device where it was placed earlier
Else If the tf.device() block is used,
    then the node is placed on the specified device
Else If the GPU is present
    then the node is placed on the first available GPU
Else If the GPU is not present
    then the node is placed on the CPU

动态放置

tf.device()也可以传递函数名而不是设备字符串。在这种情况下,该函数必须返回设备字符串。此功能允许使用复杂的算法将变量放在不同的设备上。例如,TensorFlow在tf.train.replica_device_setter()中提供循环设备设置函数,我们将在下一节中讨论。

软放置

当您在GPU上放置TensorFlow运算时,TF必须具有该运算的GPU实现,也就是内核。 如果内核不存在,则放置会导致运行时错误。此外,如果您请求的GPU设备不存在,您将收到运行时错误。处理此类错误的最佳方法是,如果请求GPU设备产生了错误,则允许将操作置于CPU上。 这可以通过设置以下配置值来实现:

config.allow_soft_placement = True

显存处理

当您开始运行TensorFlow会话时,默认情况下它会抓取所有GPU内存,即使您将操作和变量仅放置在多GPU系统中的一个GPU上也是如此。如果您尝试同时运行另一个会话,则会出现内存不足错误。这可以通过多种方式解决:

  • 对于多GPU系统,请设置环境变量
CUDA_VISIBLE_DEVICES=<list of device idx>
os.environ['CUDA_VISIBLE_DEVICES']='0'

在此设置之后执行的代码将能够获取仅可见GPU的所有内存。

  • 当您不希望会话占用GPU的所有内存时,您可以使用配置选项per_process_gpu_memory_fraction来分配一定百分比的内存:
config.gpu_options.per_process_gpu_memory_fraction = 0.5

这将分配所有GPU设备的50%的内存。

  • 您还可以结合上述两种策略,即只使用一个百分比,同时只让部分GPU对当前进程可见。
  • 您还可以将TensorFlow进程限制为仅在进程开始时获取所需的最小内存。随着进程的进一步执行,您可以设置配置选项以允许此内存的增长。
config.gpu_options.allow_growth = True

此选项仅允许分配的内存逐渐增长,但已分配内存永远不会被释放。

您将后面的文章中学习跨多个计算设备和多个节点分配计算的技术。

多重计算图

您可以创建与默认计算图相区别的计算图,并在会话中执行它们。但是,不建议创建和执行多个计算图,因为它具有以下缺点:

  • 在同一程序中创建和使用多个计算图将需要多个TensorFlow会话,并且每个会话将消耗大量资源;
  • 您无法直接在计算图之间传递数据。

因此,推荐的方法是在单个图中包含多个子图。如果您希望使用自己的计算图而不是默认图形,可以使用tf.graph()命令。下面是我们创建自己的计算图g的示例,并将其作为默认计算图执行:

g = tf.Graph()
output = 0

# Assume Linear Model y = w * x + b

with g.as_default():
    # Define model parameters
    w = tf.Variable([.3], tf.float32)
    b = tf.Variable([-.3], tf.float32)
    # Define model input and output
    x = tf.placeholder(tf.float32)
    y = w * x + b

with tf.Session(graph=g) as tfs:
    # initialize and print the variable y
    tf.global_variables_initializer().run()
    output = tfs.run(y,{x:[1,2,3,4]})

print('output : ',output)

TensorBorad

即使对于中等大小的问题,计算图的复杂性也很高。代表复杂机器学习模型的大型计算图形可能变得非常混乱且难以理解。可视化有助于轻松理解和解释计算图,从而加速TensorFlow程序的调试和优化。TensorFlow附带了一个内置工具,可以让我们可视化计算图形,即TensorBoard。

TensorBoard可视化计算图形结构,提供统计分析并绘制在计算图形执行期间作为摘要捕获的值。 让我们看看它在实践中是如何运作的。

TensorBoard最小的例子

  1. 首先为我们的线性模型定义变量和占位符:
# Assume Linear Model y = w * x + b
# Define model parameters
w = tf.Variable([.3], name='w',dtype=tf.float32)
b = tf.Variable([-.3], name='b', dtype=tf.float32)
# Define model input and output
x = tf.placeholder(name='x',dtype=tf.float32)
y = w * x + b
  1. 初始化会话,并在此会话的上下文中,执行以下步骤:
    • 初始化全局变量
    • 创建tf.summary.FileWriter,它将使用默认计算图中的事件在tflogs文件夹中产生输出日志
    • 获取节点y的值,高效地执行我们的线性模型
with tf.Session() as tfs:
    tfs.run(tf.global_variables_initializer())
    writer=tf.summary.FileWriter('tflogs',tfs.graph)
    print('run(y,{x:3}) : ', tfs.run(y,feed_dict={x:3}))
  1. 我们看到以下输出:
run(y,{x:3}) : [ 0.60000002]

在程序执行时,日志将收集在tenslogBoard用于可视化的tflogs文件夹中。打开命令行界面,导航到运行ch-01_TensorFlow_101笔记本的文件夹,然后执行以下命令:

tensorboard --logdir='tflogs'

您会看到类似于此的输出:

Starting TensorBoard b'47' at http://0.0.0.0:6006

打开浏览器并导航到http://0.0.0.0:6006。看到TensorBoard仪表板后,不必担心显示任何错误或警告,只需单击顶部的GRAPHS选项卡即可。您将看到以下屏幕:

TensorBoard 控制台

您可以看到TensorBoard将我们的第一个简单模型可视化为计算图:

TensorBoard 中的计算图

现在让我们试着了解TensorBoard的详细工作原理。

TensorBoard 细节

TensorBoard通过读取TensorFlow生成的日志文件来工作。因此,我们需要修改此处定义的编程模型,以包含其他操作节点,这些操作节点将在我们想要使用TensorBoard可视化的日志中生成信息。编程模型或使用TensorBoard的程序流程通常可以按如下描述:

  1. 像往常一样创建计算图。
  2. 创建摘要节点。将tf.summary包中的摘要操作附加到输出要收集和分析的值的节点。
  3. 运行摘要节点以及运行模型节点。通常,您将使用便捷函数tf.summary.merge_all()将所有汇总节点合并到一个汇总节点中。然后执行此合并节点将基本上执行所有摘要节点。合并的摘要节点生成一个序列化的Summary ProtocolBuffers对象,其中包含所有摘要的并集。
  4. 通过将Summary ProtocolBuffers对象传递给tf.summary.FileWriter对象,将事件日志写入磁盘。
  5. 启动TensorBoard并分析可视化数据。

在本节中,我们没有创建汇总节点,而是以非常简单的方式使用TensorBoard。我们将在后续文章中介绍TensorBoard的高级用法。

总结

在本文中,我们快速回顾了TensorFlow库。我们了解了可用于构建TensorFlow计算图的TensorFlow数据模型元素,例如常量、变量和占位符。我们学习了如何从Python对象创建张亮。张量对象也可以作为特定值,序列或来自TensorFlow中可用的各种库函数的随机值分布生成。

TensorFlow编程模型包括构建和执行计算图。计算图具有节点和边。节点表示操作,边表示将数据从一个节点传输到另一个节点的张量。我们介绍了如何创建和执行计算图,执行顺序以及如何在不同的计算设备(如GPU和CPU)上执行计算图。我们还学习了可视化TensorFlow计算图TensorBoard的工具。

在接下来的文章中,我们将探索构建在TensorFlow之上的一些高级库,并允许我们快速构建模型。

关于 “TensorFlow 101(含全部代码)” 的 1 个意见

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据