动手学深度学习(TF2 版)_第2章 预备知识

查看tf版本

import tensorflow as tf

print(tf.__version__)
# 2.4.0

2.2 数据操作

2.2.1 创建tensor

""" 先介绍tensor的最基本功能,我们用range函数创建一个行向量。"""
x = tf.constant(range(12))

print(x.shape)
# (12,)

# 一个tensor实例,其中包含了从0开始的12个连续整数。
print(x)
# tf.Tensor([ 0  1  2  3  4  5  6  7  8  9 10 11], shape=(12,), dtype=int32)

# 通过len得到tensor实例中元素(element)的总数
print(len(x))
# 12

"""下面使用reshape函数把行向量x的形状改为(3, 4),也就是一个3行4列的矩阵,并记作X。除了形状改变之外,X中的元素保持不变。"""
# 也可写成x.reshape((-1, 4))或x.reshape((3, -1))。由于x的元素个数是已知的,这里的-1是能够通过元素个数和其他维度的大小推断出来的。
X = tf.reshape(x, (3, 4))
print(X)
# tf.Tensor(
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]], shape=(3, 4), dtype=int32)

"""接下来,我们创建一个各元素为0,形状为(2, 3, 4)的张量。实际上,之前创建的向量和矩阵都是特殊的张量。"""
X0 = tf.zeros((2, 3, 4))
print(X0)
# tf.Tensor(
# [[[0. 0. 0. 0.]
#   [0. 0. 0. 0.]
#   [0. 0. 0. 0.]]
#
#  [[0. 0. 0. 0.]
#   [0. 0. 0. 0.]
#   [0. 0. 0. 0.]]], shape=(2, 3, 4), dtype=float32)

"""类似地,我们可以创建各元素为1的张量。"""
X1 = tf.ones((3, 4))
print(X1)
# tf.Tensor(
# [[1. 1. 1. 1.]
#  [1. 1. 1. 1.]
#  [1. 1. 1. 1.]], shape=(3, 4), dtype=float32)

"""也可以通过Python的列表(list)指定需要创建的tensor中每个元素的值。"""
Y = tf.constant([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(Y.shape)  # (3, 4)
print(Y)
# tf.Tensor(
# [[2 1 4 3]
#  [1 2 3 4]
#  [4 3 2 1]], shape=(3, 4), dtype=int32)

"""随机生成tensor中每个元素的值。下面我们创建一个形状为(3, 4)的tensor。它的每个元素都随机采样于均值为0、标准差为1的正态分布。"""
# X2 = tf.random.normal(shape=[3, 4], mean=0, stddev=1)
X2 = tf.random.normal(shape=(3, 4), mean=0, stddev=1)  # 这两种都可以·    1m
print(X2)
# tf.Tensor(
# [[-0.6286057  -1.5282005   2.149251   -0.5682216 ]
#  [ 0.03127453  0.5830824   0.6466156  -0.5903981 ]
#  [ 0.05115638 -1.6249142  -1.1675135  -0.78749204]], shape=(3, 4), dtype=float32)
print('###' * 30)

2.2.2 运算

print(X)
# tf.Tensor(
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]], shape=(3, 4), dtype=int32)
print(Y)
# tf.Tensor(
# [[2 1 4 3]
#  [1 2 3 4]
#  [4 3 2 1]], shape=(3, 4), dtype=int32)
print(X + Y)  # 按元素加法:对应位置相加
# tf.Tensor(
# [[ 2  2  6  6]
#  [ 5  7  9 11]
#  [12 12 12 12]], shape=(3, 4), dtype=int32)

print(X * Y)  # 按元素乘法:对应位置相乘
# tf.Tensor(
# [[ 0  1  8  9]
#  [ 4 10 18 28]
#  [32 27 20 11]], shape=(3, 4), dtype=int32)

print(X / Y)  # 按元素除法:对应位置相除
# tf.Tensor(
# [[ 0.    1.    0.5   1.  ]
#  [ 4.    2.5   2.    1.75]
#  [ 2.    3.    5.   11.  ]], shape=(3, 4), dtype=float64)

Y = tf.cast(Y, tf.float32)  # 类型转换:dtype=int32 --> dtype=float32
print(Y)
# tf.Tensor(
# [[2. 1. 4. 3.]
#  [1. 2. 3. 4.]
#  [4. 3. 2. 1.]], shape=(3, 4), dtype=float32)
print(tf.exp(Y))  # 按元素做指数运算:对应位置做指数运算
# tf.Tensor(
# [[ 7.389056   2.7182817 54.598152  20.085537 ]
#  [ 2.7182817  7.389056  20.085537  54.598152 ]
#  [54.59815   20.085537   7.389056   2.7182817]], shape=(3, 4), dtype=float32)

"""
除了按元素计算外,我们还可以使用matmul(matrix multiply)函数做矩阵乘法。下面将X与Y的转置做矩阵乘法。
由于X是3行4列的矩阵,Y转置为4行3列的矩阵,因此两个矩阵相乘得到3行3列的矩阵。
"""
Y = tf.cast(Y, tf.int32)
print(tf.matmul(X, tf.transpose(Y)))
# tf.Tensor(
# [[ 18  20  10]
#  [ 58  60  50]
#  [ 98 100  90]], shape=(3, 3), dtype=int32)

"""
也可以将多个tensor连结(concatenate)。
下面分别在行上(维度0,即形状中的最左边元素)和列上(维度1,即形状中左起第二个元素)连结两个矩阵。
可以看到,输出的第一个tensor在维度0的长度( 6 )为两个输入矩阵在维度0的长度之和( 3+3 ),
       而输出的第二个tensor在维度1的长度( 8 )为两个输入矩阵在维度1的长度之和( 4+4 )。
"""
print(tf.concat([X, Y], axis=0))
# tf.Tensor(
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]
#  [ 2  1  4  3]
#  [ 1  2  3  4]
#  [ 4  3  2  1]], shape=(6, 4), dtype=int32)

print(tf.concat([X, Y], axis=1))
# tf.Tensor(
# [[ 0  1  2  3  2  1  4  3]
#  [ 4  5  6  7  1  2  3  4]
#  [ 8  9 10 11  4  3  2  1]], shape=(3, 8), dtype=int32)

"""
使用条件判断式可以得到元素为0或1的新的tensor。
    以X == Y为例,如果X和Y在相同位置的条件判断为真(值相等),那么新的tensor在相同位置的值为1;反之为0。
"""
print(tf.equal(X, Y))
# tf.Tensor(
# [[False  True False  True]
#  [False False False False]
#  [False False False False]], shape=(3, 4), dtype=bool)

"""对tensor中的所有元素求和得到只有一个元素的tensor。"""
print(tf.reduce_sum(X))
# tf.Tensor(66, shape=(), dtype=int32)

X = tf.cast(X, tf.float32)
print(tf.norm(X))  # 各元素平方和的开方,即L2范数
# tf.Tensor(22.494444, shape=(), dtype=float32)

2.2.3 广播机制

# 前面我们看到如何对两个形状相同的tensor做按元素运算。
# 当对两个形状不同的tensor按元素运算时,可能会触发广播(broadcasting)机制。
# 广播(broadcasting)机制:先适当复制元素使这两个tensor形状相同后再按元素运算。

A = tf.reshape(tf.constant(range(3)), (3, 1))
B = tf.reshape(tf.constant(range(2)), (1, 2))
print(A)
# tf.Tensor(
# [[0]
#  [1]
#  [2]], shape=(3, 1), dtype=int32)
print(B)
# tf.Tensor([[0 1]], shape=(1, 2), dtype=int32)

"""
A和B分别是3行1列和1行2列的矩阵,
如果要计算A + B,那么A中第一列的3个元素被广播(复制)到了第二列,而B中第一行的2个元素被广播(复制)到了第二行和第三行。
如此,就可以对2个3行2列的矩阵按元素相加。
"""
print(A + B)
# tf.Tensor(
# [[0 1]
#  [1 2]
#  [2 3]], shape=(3, 2), dtype=int32)

2.2.4 索引

"""截取了矩阵X中行索引为1和2的两行"""
# 在tensor中,索引(index)代表了元素的位置。tensor的索引从0开始逐一递增。
# 例如,一个3行2列的矩阵的行索引分别为0、1和2,列索引分别为0和1。
# 在下面的例子中,我们指定了tensor的行索引截取范围[1:3]。依据左闭右开指定范围的惯例,它截取了矩阵X中行索引为1和2的两行。

print(X[1:3])
# [[ 4.  5.  6.  7.]
#  [ 8.  9. 10. 11.]], shape=(2, 4), dtype=float32)

"""我们可以指定tensor中需要访问的单个元素的位置,如矩阵中行和列的索引,并为该元素重新赋值。"""
X = tf.Variable(X)  # Variable是可更改的,而Tensor是不可更改的https://blog.csdn.net/u013841196/article/details/82960765
print(X[1, 2].assign(911))
# <tf.Variable 'UnreadVariable' shape=(3, 4) dtype=float32, numpy=
# array([[  0.,   1.,   2.,   3.],
#        [  4.,   5., 911.,   7.],
#        [  8.,   9.,  10.,  11.]], dtype=float32)>

"""当然,我们也可以截取一部分元素,并为它们重新赋值。在下面的例子中,我们为行索引为1的每一列元素重新赋值。"""
print(X[1:2, :].assign(tf.ones(X[1:2, :].shape, dtype=tf.float32) * 120))
# <tf.Variable 'UnreadVariable' shape=(3, 4) dtype=float32, numpy=
# array([[  0.,   1.,   2.,   3.],
#        [120., 120., 120., 120.],
#        [  8.,   9.,  10.,  11.]], dtype=float32)>

2.2.5 运算的内存开销


"""查看操作新开的内存"""
X = tf.Variable(X)
Y = tf.cast(Y, dtype=tf.float32)
before_id = id(Y)
Y = Y + X
now_id = id(Y)
print(before_id, now_id, before_id == now_id)
# 2145964333192 2145964333368 False

"""
如果想指定结果到特定内存,我们可以使用前面介绍的索引来进行替换操作。
在下面的例子中,我们先通过zeros_like创建和Y形状相同且元素为0的tensor,记为Z。
接下来,我们把X + Y的结果通过[:]写进Z对应的内存中。
"""
Z = tf.Variable(tf.zeros_like(Y))
before_id = id(Z)
Z[:].assign(X + Y)
now_id = id(Z)
print(before_id, now_id, before_id == now_id)
# 2245196592520 2245196592520 True

"""
实际上,上例中我们还是为X + Y开了临时内存来存储计算结果,再复制到Z对应的内存。
如果想避免这个临时内存开销,我们可以使用assign_{运算符全名}函数。
"""
before_id = id(X)
X.assign_add(Y)
now_id = id(X)
print(before_id == now_id)  # True

"""
如果X的值在之后的程序中不会复用,我们也可以用 X[:] = X + Y 或者 X += Y 来减少运算的内存开销。
这个程序好像不太对!!!
"""
before_id = id(X)
X.assign_add(Y)
now_id = id(X)
print(before_id == now_id)  # True

2.2.6 tensor和numpy互换


"""
我们可以通过array函数和asnumpy函数令数据在NDArray和NumPy格式之间相互变换。
下面将NumPy实例变换成tensor实例。
"""
import numpy as np

P = np.ones((2, 3))
D = tf.constant(P)
print(D)
# tf.Tensor(
# [[1. 1. 1.]
#  [1. 1. 1.]], shape=(2, 3), dtype=float64)

"""再将ndarray实例变换成NumPy实例。"""
print(type(np.array(D)), '\n', np.array(D))
# <class 'numpy.ndarray'>
# [[1. 1. 1.]
#  [1. 1. 1.]]

2.3 自动求梯度

在深度学习中,我们经常需要对函数求梯度(gradient)。本节将介绍如何使用tf2提供的GradientTape来自动求梯度。
GradientTape 可以理解为‘梯度流 记录磁带’:
在记录阶段:记录被GradientTape包裹的运算过程中,依赖于 source node(被 watch ‘监视’的变量)的关系图。
在求导阶段:通过搜索 source node 到 target node 的路径,进而计算出偏微分。

source node 在记录运算过程之前进行指定:
自动“监控”所有可训练变量:
GradientTape 默认(watch_accessed_variables=True)将所有可训练变量(created by tf.Variable, where trainable=True)
视为需要“监控”的 source node 。

对于不可训练的变量(比如tf.constant)可以使用tape.watch()对其进行“监控”。
此外,还可以设定watch_accessed_variables=False,然后使用tf.watch()精确控制需要“监控”哪些变量。

2.3.1 简单示例

import tensorflow as tf

"""先看一个简单的示例"""
x = tf.reshape(tf.Variable(range(4), dtype=tf.float32), (4, -1))
print(x)
# tf.Tensor(
# [[0.]
#  [1.]
#  [2.]
#  [3.]], shape=(4, 1), dtype=float32)

print(tf.transpose(x))
# tf.Tensor([[0. 1. 2. 3.]], shape=(1, 4), dtype=float32)

print(tf.matmul(tf.transpose(x), x))
# tf.Tensor([[14.]], shape=(1, 1), dtype=float32)

with tf.GradientTape() as t:
    t.watch(x)  # 对于Variable类型的变量,一般不用加此监控
    y = 2 * tf.matmul(tf.transpose(x), x)

# y = [2x.T][x] 关于 x 的梯度应为 4x
dy_dx = t.gradient(y, x)
print(dy_dx)
# tf.Tensor(
# [[ 0.]
#  [ 4.]
#  [ 8.]
#  [12.]], shape=(4, 1), dtype=float32)

2.3.2 训练模式和预测模式

with tf.GradientTape(persistent=True) as g:
    g.watch(x)
    y = x * x
    z = y * y
    dz_dx = g.gradient(z, x)
    dy_dx = g.gradient(y, z)
print(dz_dx)
# tf.Tensor(
# [[ 0.]
#  [ 4.]
#  [ 8.]
#  [12.]], shape=(4, 1), dtype=float32)

print(dy_dx)
# tf.Tensor(
# [[  0.]
#  [  4.]
#  [ 32.]
#  [108.]], shape=(4, 1), dtype=float32)

2.3.3 对Python控制流求梯度

即使函数的计算图包含了Python的控制流(如条件控制和循环控制),我们也有可能对变量求梯度。
考虑下面的程序,其中包含了Python的条件控制和循环控制,
需要强调的是,这里循环(while循环)迭代的次数和条件判断(if语句)的执行都取决于输入的a值。

def f(a):
    b = a * 2
    while tf.norm(b) < 1000:
        b = b * 2
    if tf.reduce_sum(b) > 0:
        c = b
    else:
        c = 100 * b
    return c


# 我们来分析一下上面定义的f函数。事实上,给定任意输入a,其输出必然是 f(a) = x * a的形式,
# 其中标量系数x的值取决于输入a。由于c = f(a)有关a的梯度为x,且值为c / a,
# 我们可以像下面这样验证对本例中控制流求梯度的结果的正确性。
a = tf.random.normal((1, 1), dtype=tf.float32)
with tf.GradientTape() as t:
    t.watch(a)
    c = f(a)
print(t.gradient(c, a))
# tf.Tensor([[204800.]], shape=(1, 1), dtype=float32)
print(c / a)
# tf.Tensor([[204800.]], shape=(1, 1), dtype=float32)

2.4 查阅文档

受篇幅所限,本书无法对所有用到的tensorflow2.0函数和类一一详细介绍。
读者可以查阅相关文档来做更深入的了解。

2.4.1 search for functions and classes

当我们想知道一个模块里面提供了哪些可以调用的函数和类的时候,可以使用dir函数。
下面我们打印dtypes和random模块中所有的成员或属性。

import tensorflow as tf
print(dir(tf.dtypes))
# ['DType',
# 'QUANTIZED_DTYPES',
# '__builtins__',
# '__cached__',
# '__doc__',
# '__file__',
# '__loader__',
# '__name__',
# '__package__',
# '__path__',
# '__spec__',
# '_sys',
# 'as_dtype',
# 'bfloat16',
# 'bool',
# 'cast',
# 'complex',
# 'complex128',
# 'complex64',
# 'double',
# 'float16',
# 'float32',
# 'float64',
# 'half',
# 'int16',
# 'int32',
# 'int64',
# 'int8',
# 'qint16',
# 'qint32',
# 'qint8',
# 'quint16',
# 'quint8',
# 'resource',
# 'saturate_cast',
# 'string',
# 'uint16',
# 'uint32',
# 'uint64',
# 'uint8',
# 'variant']

通常我们可以忽略掉由__开头和结尾的函数(Python的特别对象)或者由_开头的函数(一般为内部函数)。

其余成员通过名字我们大致猜测出这个模块提供了各种随机数的生成方法,包括从均匀分布采样(uniform)、从正态分布采样(normal)、从泊松分布采样(poisson)等。

print(dir(tf.random))
# ['Algorithm',
# 'Generator',
# '__builtins__',
# '__cached__',
# '__doc__',
# '__file__',
# '__loader__',
# '__name__',
# '__package__',
# '__path__',
# '__spec__',
# '_sys',
# 'all_candidate_sampler',
# 'categorical',
# 'create_rng_state',
# 'experimental',
# 'fixed_unigram_candidate_sampler',
# 'gamma',
# 'get_global_generator',
# 'learned_unigram_candidate_sampler',
# 'log_uniform_candidate_sampler',
# 'normal',
# 'poisson',
# 'set_global_generator',
# 'set_seed',
# 'shuffle',
# 'stateless_binomial',
# 'stateless_categorical',
# 'stateless_gamma',
# 'stateless_normal',
# 'stateless_parameterized_truncated_normal',
# 'stateless_poisson',
# 'stateless_truncated_normal',
# 'stateless_uniform',
# 'truncated_normal',
# 'uniform',
# 'uniform_candidate_sampler']

2.4.2.4.2 use of functions

想了解某个函数或者类的具体用法时,可以使用help函数。让我们以ones函数为例,查阅它的用法。
更详细的信息,可以通过Tensorflow的API文档版本选择页,选择与自己环境中的 tensorflow 版本一致的 API 版本进行查询。

print(help(tf.ones))
# Help on function ones in module tensorflow.python.ops.array_ops:
#
# ones(shape, dtype=tf.float32, name=None)
#     Creates a tensor with all elements set to one (1).
#
#     See also `tf.ones_like`, `tf.zeros`, `tf.fill`, `tf.eye`.
#
#     This operation returns a tensor of type `dtype` with shape `shape` and
#     all elements set to one.
#
#     >>> tf.ones([3, 4], tf.int32)
#     <tf.Tensor: shape=(3, 4), dtype=int32, numpy=
#     array([[1, 1, 1, 1],
#            [1, 1, 1, 1],
#            [1, 1, 1, 1]], dtype=int32)>
#
#     Args:
#       shape: A `list` of integers, a `tuple` of integers, or
#         a 1-D `Tensor` of type `int32`.
#       dtype: Optional DType of an element in the resulting `Tensor`. Default is
#         `tf.float32`.
#       name: Optional string. A name for the operation.
#
#     Returns:
#       A `Tensor` with all elements set to one (1).
#
# None

从文档信息我们了解到,ones函数会创建和输入tensor形状相同且元素为1的新tensor。我们可以验证一下:

print(tf.ones([2, 3], tf.int32))
# tf.Tensor(
# [[1 1 1]
#  [1 1 1]], shape=(2, 3), dtype=int32)

完整代码


import tensorflow as tf

print(tf.__version__)
# 2.4.0
"""
2.2 数据操作
在深度学习中,我们通常会频繁地对数据进行操作。作为动手学深度学习的基础,本节将介绍如何对内存中的数据进行操作。
在TensorFlow中,tensor是一个类,也是存储和变换数据的主要工具。如果你之前用过NumPy,你会发现tensor和NumPy的多维数组非常类似。然而,tensor提供GPU计算和自动求梯度等更多功能,这些使tensor更加适合深度学习。
"""

################################
#
# 2.2.1 创建tensor
#
################################

""" 先介绍tensor的最基本功能,我们用range函数创建一个行向量。"""
x = tf.constant(range(12))

print(x.shape)
# (12,)

# 一个tensor实例,其中包含了从0开始的12个连续整数。
print(x)
# tf.Tensor([ 0  1  2  3  4  5  6  7  8  9 10 11], shape=(12,), dtype=int32)

# 通过len得到tensor实例中元素(element)的总数
print(len(x))
# 12

"""下面使用reshape函数把行向量x的形状改为(3, 4),也就是一个3行4列的矩阵,并记作X。除了形状改变之外,X中的元素保持不变。"""
# 也可写成x.reshape((-1, 4))或x.reshape((3, -1))。由于x的元素个数是已知的,这里的-1是能够通过元素个数和其他维度的大小推断出来的。
X = tf.reshape(x, (3, 4))
print(X)
# tf.Tensor(
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]], shape=(3, 4), dtype=int32)

"""接下来,我们创建一个各元素为0,形状为(2, 3, 4)的张量。实际上,之前创建的向量和矩阵都是特殊的张量。"""
X0 = tf.zeros((2, 3, 4))
print(X0)
# tf.Tensor(
# [[[0. 0. 0. 0.]
#   [0. 0. 0. 0.]
#   [0. 0. 0. 0.]]
#
#  [[0. 0. 0. 0.]
#   [0. 0. 0. 0.]
#   [0. 0. 0. 0.]]], shape=(2, 3, 4), dtype=float32)

"""类似地,我们可以创建各元素为1的张量。"""
X1 = tf.ones((3, 4))
print(X1)
# tf.Tensor(
# [[1. 1. 1. 1.]
#  [1. 1. 1. 1.]
#  [1. 1. 1. 1.]], shape=(3, 4), dtype=float32)

"""也可以通过Python的列表(list)指定需要创建的tensor中每个元素的值。"""
Y = tf.constant([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
print(Y.shape)  # (3, 4)
print(Y)
# tf.Tensor(
# [[2 1 4 3]
#  [1 2 3 4]
#  [4 3 2 1]], shape=(3, 4), dtype=int32)

"""随机生成tensor中每个元素的值。下面我们创建一个形状为(3, 4)的tensor。它的每个元素都随机采样于均值为0、标准差为1的正态分布。"""
# X2 = tf.random.normal(shape=[3, 4], mean=0, stddev=1)
X2 = tf.random.normal(shape=(3, 4), mean=0, stddev=1)  # 这两种都可以·    1m
print(X2)
# tf.Tensor(
# [[-0.6286057  -1.5282005   2.149251   -0.5682216 ]
#  [ 0.03127453  0.5830824   0.6466156  -0.5903981 ]
#  [ 0.05115638 -1.6249142  -1.1675135  -0.78749204]], shape=(3, 4), dtype=float32)
print('###' * 30)

################################
#
# 2.2.2 运算
#
################################

print(X)
# tf.Tensor(
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]], shape=(3, 4), dtype=int32)
print(Y)
# tf.Tensor(
# [[2 1 4 3]
#  [1 2 3 4]
#  [4 3 2 1]], shape=(3, 4), dtype=int32)
print(X + Y)  # 按元素加法:对应位置相加
# tf.Tensor(
# [[ 2  2  6  6]
#  [ 5  7  9 11]
#  [12 12 12 12]], shape=(3, 4), dtype=int32)

print(X * Y)  # 按元素乘法:对应位置相乘
# tf.Tensor(
# [[ 0  1  8  9]
#  [ 4 10 18 28]
#  [32 27 20 11]], shape=(3, 4), dtype=int32)

print(X / Y)  # 按元素除法:对应位置相除
# tf.Tensor(
# [[ 0.    1.    0.5   1.  ]
#  [ 4.    2.5   2.    1.75]
#  [ 2.    3.    5.   11.  ]], shape=(3, 4), dtype=float64)

Y = tf.cast(Y, tf.float32)  # 类型转换:dtype=int32 --> dtype=float32
print(Y)
# tf.Tensor(
# [[2. 1. 4. 3.]
#  [1. 2. 3. 4.]
#  [4. 3. 2. 1.]], shape=(3, 4), dtype=float32)
print(tf.exp(Y))  # 按元素做指数运算:对应位置做指数运算
# tf.Tensor(
# [[ 7.389056   2.7182817 54.598152  20.085537 ]
#  [ 2.7182817  7.389056  20.085537  54.598152 ]
#  [54.59815   20.085537   7.389056   2.7182817]], shape=(3, 4), dtype=float32)

"""
除了按元素计算外,我们还可以使用matmul(matrix multiply)函数做矩阵乘法。下面将X与Y的转置做矩阵乘法。
由于X是3行4列的矩阵,Y转置为4行3列的矩阵,因此两个矩阵相乘得到3行3列的矩阵。
"""
Y = tf.cast(Y, tf.int32)
print(tf.matmul(X, tf.transpose(Y)))
# tf.Tensor(
# [[ 18  20  10]
#  [ 58  60  50]
#  [ 98 100  90]], shape=(3, 3), dtype=int32)

"""
也可以将多个tensor连结(concatenate)。
下面分别在行上(维度0,即形状中的最左边元素)和列上(维度1,即形状中左起第二个元素)连结两个矩阵。
可以看到,输出的第一个tensor在维度0的长度( 6 )为两个输入矩阵在维度0的长度之和( 3+3 ),
       而输出的第二个tensor在维度1的长度( 8 )为两个输入矩阵在维度1的长度之和( 4+4 )。
"""
print(tf.concat([X, Y], axis=0))
# tf.Tensor(
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]
#  [ 2  1  4  3]
#  [ 1  2  3  4]
#  [ 4  3  2  1]], shape=(6, 4), dtype=int32)

print(tf.concat([X, Y], axis=1))
# tf.Tensor(
# [[ 0  1  2  3  2  1  4  3]
#  [ 4  5  6  7  1  2  3  4]
#  [ 8  9 10 11  4  3  2  1]], shape=(3, 8), dtype=int32)

"""
使用条件判断式可以得到元素为0或1的新的tensor。
    以X == Y为例,如果X和Y在相同位置的条件判断为真(值相等),那么新的tensor在相同位置的值为1;反之为0。
"""
print(tf.equal(X, Y))
# tf.Tensor(
# [[False  True False  True]
#  [False False False False]
#  [False False False False]], shape=(3, 4), dtype=bool)

"""对tensor中的所有元素求和得到只有一个元素的tensor。"""
print(tf.reduce_sum(X))
# tf.Tensor(66, shape=(), dtype=int32)

X = tf.cast(X, tf.float32)
print(tf.norm(X))  # 各元素平方和的开方,即L2范数
# tf.Tensor(22.494444, shape=(), dtype=float32)


################################
#
# 2.2.3 广播机制
#
################################

# 前面我们看到如何对两个形状相同的tensor做按元素运算。
# 当对两个形状不同的tensor按元素运算时,可能会触发广播(broadcasting)机制。
# 广播(broadcasting)机制:先适当复制元素使这两个tensor形状相同后再按元素运算。

A = tf.reshape(tf.constant(range(3)), (3, 1))
B = tf.reshape(tf.constant(range(2)), (1, 2))
print(A)
# tf.Tensor(
# [[0]
#  [1]
#  [2]], shape=(3, 1), dtype=int32)
print(B)
# tf.Tensor([[0 1]], shape=(1, 2), dtype=int32)

"""
A和B分别是3行1列和1行2列的矩阵,
如果要计算A + B,那么A中第一列的3个元素被广播(复制)到了第二列,而B中第一行的2个元素被广播(复制)到了第二行和第三行。
如此,就可以对2个3行2列的矩阵按元素相加。
"""
print(A + B)
# tf.Tensor(
# [[0 1]
#  [1 2]
#  [2 3]], shape=(3, 2), dtype=int32)

################################
#
# 2.2.4 索引
#
################################
"""截取了矩阵X中行索引为1和2的两行"""
# 在tensor中,索引(index)代表了元素的位置。tensor的索引从0开始逐一递增。
# 例如,一个3行2列的矩阵的行索引分别为0、1和2,列索引分别为0和1。
# 在下面的例子中,我们指定了tensor的行索引截取范围[1:3]。依据左闭右开指定范围的惯例,它截取了矩阵X中行索引为1和2的两行。

print(X[1:3])
# [[ 4.  5.  6.  7.]
#  [ 8.  9. 10. 11.]], shape=(2, 4), dtype=float32)

"""我们可以指定tensor中需要访问的单个元素的位置,如矩阵中行和列的索引,并为该元素重新赋值。"""
X = tf.Variable(X)  # Variable是可更改的,而Tensor是不可更改的https://blog.csdn.net/u013841196/article/details/82960765
print(X[1, 2].assign(911))
# <tf.Variable 'UnreadVariable' shape=(3, 4) dtype=float32, numpy=
# array([[  0.,   1.,   2.,   3.],
#        [  4.,   5., 911.,   7.],
#        [  8.,   9.,  10.,  11.]], dtype=float32)>

"""当然,我们也可以截取一部分元素,并为它们重新赋值。在下面的例子中,我们为行索引为1的每一列元素重新赋值。"""
print(X[1:2, :].assign(tf.ones(X[1:2, :].shape, dtype=tf.float32) * 120))
# <tf.Variable 'UnreadVariable' shape=(3, 4) dtype=float32, numpy=
# array([[  0.,   1.,   2.,   3.],
#        [120., 120., 120., 120.],
#        [  8.,   9.,  10.,  11.]], dtype=float32)>


################################
#
# 2.2.5 运算的内存开销
#
################################
"""查看操作新开的内存"""
X = tf.Variable(X)
Y = tf.cast(Y, dtype=tf.float32)
before_id = id(Y)
Y = Y + X
now_id = id(Y)
print(before_id, now_id, before_id == now_id)
# 2145964333192 2145964333368 False

"""
如果想指定结果到特定内存,我们可以使用前面介绍的索引来进行替换操作。
在下面的例子中,我们先通过zeros_like创建和Y形状相同且元素为0的tensor,记为Z。
接下来,我们把X + Y的结果通过[:]写进Z对应的内存中。
"""
Z = tf.Variable(tf.zeros_like(Y))
before_id = id(Z)
Z[:].assign(X + Y)
now_id = id(Z)
print(before_id, now_id, before_id == now_id)
# 2245196592520 2245196592520 True

"""
实际上,上例中我们还是为X + Y开了临时内存来存储计算结果,再复制到Z对应的内存。
如果想避免这个临时内存开销,我们可以使用assign_{运算符全名}函数。
"""
before_id = id(X)
X.assign_add(Y)
now_id = id(X)
print(before_id == now_id)  # True

"""
如果X的值在之后的程序中不会复用,我们也可以用 X[:] = X + Y 或者 X += Y 来减少运算的内存开销。
这个程序好像不太对!!!
"""
before_id = id(X)
X.assign_add(Y)
now_id = id(X)
print(before_id == now_id)  # True

################################
#
# 2.2.6 tensor和numpy互换
#
################################
"""
我们可以通过array函数和asnumpy函数令数据在NDArray和NumPy格式之间相互变换。
下面将NumPy实例变换成tensor实例。
"""
import numpy as np

P = np.ones((2, 3))
D = tf.constant(P)
print(D)
# tf.Tensor(
# [[1. 1. 1.]
#  [1. 1. 1.]], shape=(2, 3), dtype=float64)

"""再将ndarray实例变换成NumPy实例。"""
print(type(np.array(D)), '\n', np.array(D))
# <class 'numpy.ndarray'>
# [[1. 1. 1.]
#  [1. 1. 1.]]



"""
2.3 自动求梯度

在深度学习中,我们经常需要对函数求梯度(gradient)。本节将介绍如何使用tf2提供的GradientTape来自动求梯度。
GradientTape 可以理解为‘梯度流 记录磁带’:
    在记录阶段:记录被GradientTape包裹的运算过程中,依赖于 source node(被 watch ‘监视’的变量)的关系图。
    在求导阶段:通过搜索 source node 到 target node 的路径,进而计算出偏微分。

source node 在记录运算过程之前进行指定:
自动“监控”所有可训练变量:
    GradientTape 默认(watch_accessed_variables=True)将所有可训练变量(created by tf.Variable, where trainable=True)
    视为需要“监控”的 source node 。
    
    对于不可训练的变量(比如tf.constant)可以使用tape.watch()对其进行“监控”。
    此外,还可以设定watch_accessed_variables=False,然后使用tf.watch()精确控制需要“监控”哪些变量。
"""


import tensorflow as tf
###########################################
#
# 2.3.1 简单示例
#
###########################################
"""先看一个简单的示例"""
x = tf.reshape(tf.Variable(range(4), dtype=tf.float32), (4, -1))
print(x)
# tf.Tensor(
# [[0.]
#  [1.]
#  [2.]
#  [3.]], shape=(4, 1), dtype=float32)

print(tf.transpose(x))
# tf.Tensor([[0. 1. 2. 3.]], shape=(1, 4), dtype=float32)

print(tf.matmul(tf.transpose(x), x))
# tf.Tensor([[14.]], shape=(1, 1), dtype=float32)

with tf.GradientTape() as t:
    t.watch(x)  # 对于Variable类型的变量,一般不用加此监控
    y = 2 * tf.matmul(tf.transpose(x), x)

# y = [2x.T][x] 关于 x 的梯度应为 4x
dy_dx = t.gradient(y, x)
print(dy_dx)
# tf.Tensor(
# [[ 0.]
#  [ 4.]
#  [ 8.]
#  [12.]], shape=(4, 1), dtype=float32)



###########################################
#
# 2.3.2 训练模式和预测模式
#
###########################################
with tf.GradientTape(persistent=True) as g:
    g.watch(x)
    y = x * x
    z = y * y
    dz_dx = g.gradient(z, x)
    dy_dx = g.gradient(y, z)
print(dz_dx)
# tf.Tensor(
# [[ 0.]
#  [ 4.]
#  [ 8.]
#  [12.]], shape=(4, 1), dtype=float32)

print(dy_dx)
# tf.Tensor(
# [[  0.]
#  [  4.]
#  [ 32.]
#  [108.]], shape=(4, 1), dtype=float32)



###########################################
#
# 2.3.3 对Python控制流求梯度
#
###########################################
"""
即使函数的计算图包含了Python的控制流(如条件控制和循环控制),我们也有可能对变量求梯度。
考虑下面的程序,其中包含了Python的条件控制和循环控制,
需要强调的是,这里循环(while循环)迭代的次数和条件判断(if语句)的执行都取决于输入的a值。
"""
def f(a):
    b = a * 2
    while tf.norm(b) < 1000:
        b = b * 2
    if tf.reduce_sum(b) > 0:
        c = b
    else:
        c = 100 * b
    return c
# 我们来分析一下上面定义的f函数。事实上,给定任意输入a,其输出必然是 f(a) = x * a的形式,
# 其中标量系数x的值取决于输入a。由于c = f(a)有关a的梯度为x,且值为c / a,
# 我们可以像下面这样验证对本例中控制流求梯度的结果的正确性。
a = tf.random.normal((1,1), dtype=tf.float32)
with tf.GradientTape() as t:
    t.watch(a)
    c = f(a)
print(t.gradient(c, a))
# tf.Tensor([[204800.]], shape=(1, 1), dtype=float32)
print(c / a)
# tf.Tensor([[204800.]], shape=(1, 1), dtype=float32)



import tensorflow as tf

"""
2.4 查阅文档
受篇幅所限,本书无法对所有用到的tensorflow2.0函数和类一一详细介绍。
读者可以查阅相关文档来做更深入的了解。
"""



#############################################
#
# 2.4.1 search for functions and classes
#
#############################################
"""
当我们想知道一个模块里面提供了哪些可以调用的函数和类的时候,可以使用dir函数。
下面我们打印dtypes和random模块中所有的成员或属性。
"""
print(dir(tf.dtypes))
# ['DType',
# 'QUANTIZED_DTYPES',
# '__builtins__',
# '__cached__',
# '__doc__',
# '__file__',
# '__loader__',
# '__name__',
# '__package__',
# '__path__',
# '__spec__',
# '_sys',
# 'as_dtype',
# 'bfloat16',
# 'bool',
# 'cast',
# 'complex',
# 'complex128',
# 'complex64',
# 'double',
# 'float16',
# 'float32',
# 'float64',
# 'half',
# 'int16',
# 'int32',
# 'int64',
# 'int8',
# 'qint16',
# 'qint32',
# 'qint8',
# 'quint16',
# 'quint8',
# 'resource',
# 'saturate_cast',
# 'string',
# 'uint16',
# 'uint32',
# 'uint64',
# 'uint8',
# 'variant']
"""
通常我们可以忽略掉由__开头和结尾的函数(Python的特别对象)或者由_开头的函数(一般为内部函数)。
其余成员通过名字我们大致猜测出这个模块提供了各种随机数的生成方法,
包括从均匀分布采样(uniform)、从正态分布采样(normal)、从泊松分布采样(poisson)等。
"""
print(dir(tf.random))
# ['Algorithm',
# 'Generator',
# '__builtins__',
# '__cached__',
# '__doc__',
# '__file__',
# '__loader__',
# '__name__',
# '__package__',
# '__path__',
# '__spec__',
# '_sys',
# 'all_candidate_sampler',
# 'categorical',
# 'create_rng_state',
# 'experimental',
# 'fixed_unigram_candidate_sampler',
# 'gamma',
# 'get_global_generator',
# 'learned_unigram_candidate_sampler',
# 'log_uniform_candidate_sampler',
# 'normal',
# 'poisson',
# 'set_global_generator',
# 'set_seed',
# 'shuffle',
# 'stateless_binomial',
# 'stateless_categorical',
# 'stateless_gamma',
# 'stateless_normal',
# 'stateless_parameterized_truncated_normal',
# 'stateless_poisson',
# 'stateless_truncated_normal',
# 'stateless_uniform',
# 'truncated_normal',
# 'uniform',
# 'uniform_candidate_sampler']




#############################################
#
# 2.4.2.4.2 use of functions
#
#############################################
"""
想了解某个函数或者类的具体用法时,可以使用help函数。让我们以ones函数为例,查阅它的用法。
更详细的信息,可以通过Tensorflow的API文档版本选择页,选择与自己环境中的 tensorflow 版本一致的 API 版本进行查询。
"""
print(help(tf.ones))
# Help on function ones in module tensorflow.python.ops.array_ops:
#
# ones(shape, dtype=tf.float32, name=None)
#     Creates a tensor with all elements set to one (1).
#
#     See also `tf.ones_like`, `tf.zeros`, `tf.fill`, `tf.eye`.
#
#     This operation returns a tensor of type `dtype` with shape `shape` and
#     all elements set to one.
#
#     >>> tf.ones([3, 4], tf.int32)
#     <tf.Tensor: shape=(3, 4), dtype=int32, numpy=
#     array([[1, 1, 1, 1],
#            [1, 1, 1, 1],
#            [1, 1, 1, 1]], dtype=int32)>
#
#     Args:
#       shape: A `list` of integers, a `tuple` of integers, or
#         a 1-D `Tensor` of type `int32`.
#       dtype: Optional DType of an element in the resulting `Tensor`. Default is
#         `tf.float32`.
#       name: Optional string. A name for the operation.
#
#     Returns:
#       A `Tensor` with all elements set to one (1).
#
# None

"""从文档信息我们了解到,ones函数会创建和输入tensor形状相同且元素为1的新tensor。我们可以验证一下:"""
print(tf.ones([2, 3], tf.int32))
# tf.Tensor(
# [[1 1 1]
#  [1 1 1]], shape=(2, 3), dtype=int32)






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>