tensorflow 之 张量与变量

2020-10-09  本文已影响0人  水之心

TensorFlow 与 MXNet、PyTorch 很相似,上手速度很快(相比于 TensorFlow1.x)。

TensorFlow 拆开来看,就是 TensorFlow,即数据流程图,也是有向无环图(DAG)。在 TensorFlow 中以 Tensor(张量)表示数据,即 DAG 的(实线)边,而虚线的边用来表示流程的控制关系。DAG 的节点表示数学操作符(即数学运算)。

1 张量 tf.Tensor

张量是由 tf.Tensor 对象表示的具有统一类型(称为 dtype)的多维数组。您可以在 tf.dtypes.DType 中查看所有支持的 dtypes。就像 Python 数值和字符串一样,所有张量都是不可变的:永远无法更新张量的内容,只能创建新的张量。

编写 TensorFlow 程序时,主要操纵和传递的对象是 tf.Tensortf.Tensor 具有以下属性:

TensorFlow 支持 eager 执行和 graph 执行。eager 执行时,将立即执行运算。在 graph 中执行时,构造一个计算图为以后运算。TensorFlow 默认 eager 执行。在以下示例中,立即计算矩阵乘法结果。其中 tf.constant 是张量的一种。

# 使用 Tensor 计算一些值
c = tf.constant([[1.0, 2.0], [3.0, 4.0]]) # 常量
d = tf.constant([[1.0, 1.0], [0.0, 1.0]])
e = tf.matmul(c, d) # 矩阵乘法
print(e)

输出:

tf.Tensor(
[[1. 3.]
 [3. 7.]], shape=(2, 2), dtype=float32)

请注意,在 eager 执行过程中,您可能会发现您的 Tensors 实际上是类型为“EagerTensor”。这是内部细节,但确实可以给您访问有用的函数 numpy()

在 TensorFlow 中,tf.function 是定义 graph 执行的常用方法。张量的 shape(即张量的 rank 和每个维度的大小)可能并不总是完全已知的。在 tf.function 定义中,shape 可能仅是部分已知的。

如果也可以完全知道其输入的形状,则大多数操作都会生成形状已知的张量,但是在某些情况下,只有在执行时才能找到张量的形状。

有许多专用的张量:参见 tf.constant, tf.sparse.SparseTensor, 和 tf.RaggedTensor

1.1 常量 tf.constant

def constant(value, dtype=None, shape=None, name="Const"):

从 tensor-like 的对象创建恒定张量,即常量。

注意:所有 eager 的 tf.Tensor 值都是不可变的(与 tf.Variable 相反)。从 tf.constant 返回的值没有特别常数。此功能与 tf.convert_to_tensor 基本上没有区别。名称 tf.constant 来自符号API(例如 tf.datatf.keras functional models),其中 value 嵌入在 tf.GraphConst 节点中。tf.constant 可用于断言该值可以通过这种方式嵌入。

我们来创建一些基本张量。

下面是一个“标量”(或称“0 秩”张量)。标量包含单个值,但没有“轴”。

rank_0_tensor = tf.constant(4)
print(rank_0_tensor)

输出:

tf.Tensor(4, shape=(), dtype=int32)

“向量”(或称“1 秩”张量)就像一个值的列表。向量有 1 个轴:

rank_1_tensor = tf.constant([2.0, 3.0, 4.0])
print(rank_1_tensor)

输出:

tf.Tensor([2. 3. 4.], shape=(3,), dtype=float32)

“矩阵”(或称“2 秩”张量)有 2 个轴:

rank_2_tensor = tf.constant([[1, 2],
                             [3, 4],
                             [5, 6]], dtype=tf.float16)
print(rank_2_tensor)

输出:

tf.Tensor(
[[1. 2.]
 [3. 4.]
 [5. 6.]], shape=(3, 2), dtype=float16)

张量的轴可能更多,下面是一个包含 3 个轴的张量:

rank_3_tensor = tf.constant([
  [[0, 1, 2, 3, 4],
   [5, 6, 7, 8, 9]],
  [[10, 11, 12, 13, 14],
   [15, 16, 17, 18, 19]],
  [[20, 21, 22, 23, 24],
   [25, 26, 27, 28, 29]],])
                    
print(rank_3_tensor)

输出:

tf.Tensor(
[[[ 0  1  2  3  4]
  [ 5  6  7  8  9]]

 [[10 11 12 13 14]
  [15 16 17 18 19]]

 [[20 21 22 23 24]
  [25 26 27 28 29]]], shape=(3, 2, 5), dtype=int32)

对于包含 2 个以上的轴的张量,您可以通过多种方式将其可视化。

通过使用 np.arraytensor.numpy 方法,您可以将张量转换为 NumPy 数组:

张量通常包含浮点型和整型数据,但是还有许多其他数据类型,包括:

tf.Tensor 基类要求张量是“矩形”——也就是说,每个轴上的每一个元素大小相同。但是,张量有可以处理不同形状的特殊类型。

上面并没有指定参数 dtype,而从 value 的类型中自动推断出类型。

>>> # Constant 1-D Tensor from a python list.
>>> tf.constant([1, 2, 3, 4, 5, 6])
  <tf.Tensor: shape=(6,), dtype=int32,
      numpy=array([1, 2, 3, 4, 5, 6], dtype=int32)>
>>> # Or a numpy array
>>> a = np.array([[1, 2, 3], [4, 5, 6]])
>>> tf.constant(a)
  <tf.Tensor: shape=(2, 3), dtype=int64, numpy=
    array([[1, 2, 3],
           [4, 5, 6]])>

如果指定了 dtype,则将结果张量值强制转换为请求的 dtype

>>> tf.constant([1, 2, 3, 4, 5, 6], dtype=tf.float64)
  <tf.Tensor: shape=(6,), dtype=float64,
      numpy=array([1., 2., 3., 4., 5., 6.])>

如果设置了 shape,则将 value 调整为匹配的形状。标量被扩展以填充 shape

>>> tf.constant(0, shape=(2, 3))
    <tf.Tensor: shape=(2, 3), dtype=int32, numpy=
    array([[0, 0, 0],
           [0, 0, 0]], dtype=int32)>
>>> tf.constant([1, 2, 3, 4, 5, 6], shape=[2, 3])
  <tf.Tensor: shape=(2, 3), dtype=int32, numpy=
    array([[1, 2, 3],
           [4, 5, 6]], dtype=int32)>

如果将 eager 张量作为 value 传递,tf.constant 无效,它甚至会传输梯度:

v = tf.Variable([0.0])
with tf.GradientTape() as g:
    loss = tf.constant(v + v)
g.gradient(loss, v).numpy()

输出:

array([2.], dtype=float32)

但是,由于 tf.constant 将值嵌入到 tf.Graph 中,因此对于符号张量(symbolic tensors)而言失败:

Related Ops:

>>> i = tf.keras.layers.Input(shape=[None, None])
>>> t = tf.convert_to_tensor(i)

1.2 张量的简单运算

我们可以对张量执行基本数学运算,包括加法、逐元素乘法和矩阵乘法运算。

a = tf.constant([[1, 2],
                 [3, 4]])
b = tf.constant([[1, 1],
                 [1, 1]]) # Could have also said `tf.ones([2,2])`

tf.print(tf.add(a, b), "\n") # 矩阵加法
tf.print(tf.multiply(a, b), "\n") # 逐元素乘法
tf.print(tf.matmul(a, b), "\n") # 矩阵乘法

输出:

[[2 3]
 [4 5]] 

[[1 2]
 [3 4]] 

[[3 3]
 [7 7]] 

其中,tf.print 用于打印信息。

TensorFlow 也重载了运算符:

tf.print(a + b, "\n") # element-wise addition
tf.print(a * b, "\n") # element-wise multiplication
tf.print(a @ b, "\n") # matrix multiplication```

输出:

[[2 3]
 [4 5]] 

[[1 2]
 [3 4]] 

[[3 3]
 [7 7]] 

张量支持其他各种运算 (op) 。

c = tf.constant([[4.0, 5.0], [10.0, 1.0]])

# Find the largest value
tf.print(tf.reduce_max(c), '\n')
# Find the index of the largest value
tf.print(tf.argmax(c), '\n')
# Compute the softmax
tf.print(tf.nn.softmax(c), '\n')

输出:

10 

[1 0] 

[[0.268941432 0.731058598]
 [0.999876618 0.00012339458]] 

1.3 形状简介

张量有形状。下面是几个相关术语:

注:虽然您可能会看到“二维张量”之类的表述,但 2 秩张量通常并不是用来描述二维空间。

张量和 tf.TensorShape 对象提供了方便的属性来访问:

虽然通常用索引来指代轴,但是您始终要记住每个轴的含义。轴一般按照从全局到局部的顺序进行排序:首先是批次轴,随后是空间维度,最后是每个位置的特征。这样,在内存中,特征向量就会位于连续的区域。

tf.size(x).numpy()len(x) 等效。

1.4 操作形状

通过重构可以改变张量的形状。重构的速度很快,资源消耗很低,因为不需要复制底层数据。

x = tf.constant([[1], [2], [3]])
reshaped = tf.reshape(x, [1, 3])

print(x.shape)
print(reshaped.shape)

输出:

(3, 1)
(1, 3)

数据在内存中的布局保持不变,同时使用请求的形状创建一个指向同一数据的新张量。TensorFlow 采用 C 样式的“行优先”内存访问顺序,即最右侧的索引值递增对应于内存中的单步位移。

如果您展平张量,则可以看到它在内存中的排列顺序。

一般来说,tf.reshape 唯一合理的用途是用于合并或拆分相邻轴(或添加/移除 1)。

对于 3x2x5 张量,重构为 (3x2)x5 或 3x(2x5) 都合理,因为切片不会混淆:

重构可以处理总元素个数相同的任何新形状,但是如果不遵从轴的顺序,则不会发挥任何作用。

利用 tf.reshape 无法实现轴的交换,要交换轴,您需要使用 tf.transpose

您可能会遇到非完全指定的形状。要么是形状包含 None 维度(维度的长度未知),要么是形状为 None(张量的秩未知)。

除了 tf.RaggedTensor 外,这种情况只会在 TensorFlow 的符号化计算图构建 API 环境中出现:

1.5 DTypes 详解

使用 Tensor.dtype 属性可以检查 tf.Tensor 的数据类型。

从 Python 对象创建 tf.Tensor 时,您可以选择指定数据类型。

如果不指定,TensorFlow 会选择一个可以表示您的数据的数据类型。TensorFlow 将 Python 整数转换为 tf.int32,将 Python 浮点数转换为 tf.float32。另外,当转换为数组时,TensorFlow 会采用与 NumPy 相同的规则。

数据类型可以相互转换。

1.6 广播

广播是从 NumPy 中的等效功能借用的一个概念。简而言之,在一定条件下,对一组张量执行组合运算时,为了适应大张量,会对小张量进行“扩展”。

最简单和最常见的例子是尝试将张量与标量相乘或相加。在这种情况下会对标量进行广播,使其变成与其他参数相同的形状。

同样,可以扩展大小为 1 的维度,使其符合其他参数。在同一个计算中可以同时扩展两个参数。

在本例中,一个 3x1 的矩阵与一个 1x4 进行元素级乘法运算,从而产生一个 3x4 的矩阵。注意前导 1 是可选的:y 的形状是 [4]。

下面是不使用广播的同一运算:

在大多数情况下,广播的时间和空间效率更高,因为广播运算不会在内存中具体化扩展的张量。

使用 tf.broadcast_to 可以了解广播的运算方式。

与数学运算不同,比方说,broadcast_to 并不会节省内存。在这个例子中,张量已经具体化。

这可能会变得更复杂。Jake VanderPlas 的《Python 数据科学手册》一书中的这一节介绍了更多广播技巧(同样使用 NumPy)。

1.7 tf.convert_to_tensor

大部分运算(如 tf.matmultf.reshape)会使用 tf.Tensor 类的参数。不过,在上面的示例中,您会发现我们经常传递形状类似于张量的 Python 对象。

大部分(但并非全部)运算会在非张量参数上调用 convert_to_tensor。我们提供了一个转换注册表,大多数对象类(如 NumPy 的 ndarrayTensorShape、Python 列表和 tf.Variable)都可以自动转换。

有关更多详细信息,请参阅 tf.register_tensor_conversion_function。如果您有自己的类型,则可能希望自动转换为张量。

1.8 不规则张量

如果张量的某个轴上的元素个数可变,则称为“不规则”张量。对于不规则数据,请使用 tf.ragged.RaggedTensor

例如,下面的例子无法用规则张量表示:

应使用 tf.ragged.constant 来创建 tf.RaggedTensor

tf.RaggedTensor 的形状包含未知维度:

1.9 字符串张量

tf.string 是一种 dtype,也就是说,在张量中,我们可以用字符串(可变长度字节数组)来表示数据。

字符串是原子类型,无法像 Python 字符串一样编制索引。字符串的长度并不是张量的一个维度。有关操作字符串的函数,请参阅 tf.strings

下面是一个标量字符串张量:

下面是一个字符串向量:

在上面的打印输出中,b 前缀表示 tf.string dtype 不是 Unicode 字符串,而是字节字符串。有关在 TensorFlow 如何使用 Unicode 文本的详细信息,请参阅 Unicode 教程

如果传递 Unicode 字符,则会使用 utf-8 编码。

tf.strings 中可以找到用于操作字符串的一些基本函数,包括 tf.strings.split

以及 tf.string.to_number

虽然不能使用 tf.cast 将字符串张量转换为数值,但是可以先将其转换为字节,然后转换为数值。

tf.string dtype 可用于 TensorFlow 中的所有原始字节数据。tf.io 模块包含在数据与字节类型之间进行相互转换的函数,包括解码图像和解析 csv 的函数。

1.10 稀疏张量

在某些情况下,数据很稀疏,比如说在一个非常宽的嵌入空间中。为了高效存储稀疏数据,TensorFlow 支持 tf.sparse.SparseTensor 和相关运算。

2 变量

TensorFlow 变量是用于表示程序处理的共享持久状态的推荐方法。本指南介绍在 TensorFlow 中如何创建、更新和管理 tf.Variable 的实例。

变量通过 tf.Variable 类进行创建和跟踪。tf.Variable 表示张量,对它执行运算可以改变其值。利用特定运算可以读取和修改此张量的值。更高级的库(如 tf.keras)使用 tf.Variable 来存储模型参数。

2.1 设置

本笔记本讨论变量布局。如果要查看变量位于哪一个设备上,请取消注释这一行代码。

import tensorflow as tf

# Uncomment to see where your variables get placed (see below)
# tf.debugging.set_log_device_placement(True)

2.2 创建变量

要创建变量,请提供一个初始值。tf.Variable 与初始值的 dtype 相同。

my_tensor = tf.constant([[1.0, 2.0], [3.0, 4.0]])
my_variable = tf.Variable(my_tensor)

# Variables can be all kinds of types, just like tensors
bool_variable = tf.Variable([False, False, False, True])
complex_variable = tf.Variable([5 + 4j, 6 + 1j])

变量与张量的定义方式和操作行为都十分相似,实际上,它们都是 tf.Tensor 支持的一种数据结构。与张量类似,变量也有 dtype 和形状,并且可以导出至 NumPy。

大部分张量运算在变量上也可以按预期运行,不过变量无法重构形状。

如上所述,变量由张量提供支持。您可以使用 tf.Variable.assign 重新分配张量。调用 assign(通常)不会分配新张量,而会重用现有张量的内存。

如果在运算中像使用张量一样使用变量,那么通常会对支持张量执行运算。

从现有变量创建新变量会复制支持张量。两个变量不能共享同一内存空间。

2.3 生命周期、命名和监视

在基于 Python 的 TensorFlow 中,tf.Variable 实例与其他 Python 对象的生命周期相同。如果没有对变量的引用,则会自动将其解除分配。

为了便于跟踪和调试,您还可以为变量命名。两个变量可以使用相同的名称。

保存和加载模型时会保留变量名。默认情况下,模型中的变量会自动获得唯一变量名,所以除非您希望自行命名,否则不必多此一举。

虽然变量对微分很重要,但某些变量不需要进行微分。在创建时,通过将 trainable 设置为 False 可以关闭梯度。例如,训练计步器就是一个不需要梯度的变量。

step_counter = tf.Variable(1, trainable=False)

2.4 放置变量和张量

为了提高性能,TensorFlow 会尝试将张量和变量放在与其 dtype 兼容的最快设备上。这意味着如果有 GPU,那么大部分变量都会放置在 GPU 上。

不过,我们可以重写变量的位置。在以下代码段中,即使存在可用的 GPU,我们也可以将一个浮点张量和一个变量放置在 CPU 上。通过打开设备分配日志记录(参阅设置),可以查看变量的所在位置。

注:虽然可以手动放置变量,但使用分布策略是一种可优化计算的更便捷且可扩展的方式。

如果在有 GPU 和没有 GPU 的不同后端上运行此笔记本,则会看到不同的记录。请注意,必须在会话开始时打开设备布局记录。

您可以将变量或张量的位置设置在一个设备上,然后在另一个设备上执行计算。但这样会产生延迟,因为需要在两个设备之间复制数据。

不过,如果您有多个 GPU 工作进程,但希望变量只有一个副本,则可以这样做。

注:由于 tf.config.set_soft_device_placement 默认处于打开状态,所以,即使在没有 GPU 的设备上运行此代码,它也会运行,只不过乘法步骤在 CPU 上执行。

有关分布式训练的详细信息,请参阅指南。要了解变量的一般使用方法,请参阅关于自动微分的指南。

上一篇下一篇

猜你喜欢

热点阅读