跳转至

12.3.张量进阶操作

Windows 10
Python 3.7.3 @ MSC v.1915 64 bit (AMD64)
Latest build date 2020.05.28
tensorflow version:  2.1.0

索引与切片

tensorflow 的索引操作比较简单,就是Python常用的整数索引和切片。

# 一共有4张图片
# 每张图片的大小为 32*32
# 每一个像素点由 3个 rgb值组成
x = tf.random.normal([4, 32, 32, 3])

# 第一张图片 第二行 第二列的像素
x[1][1][1]
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0.13605669,
0.43695715, 0.85433054], dtype=float32)>

维度操纵

x = tf.range(96)
x = tf.reshape(x, [2, 4, 4, 3])
x = tf.reshape(x, [-1, 2])

插入维度

在第x个轴之前插入一个轴

y = tf.expand_dims(x, axis=1)
y.shape
TensorShape([48, 1, 2])

删除长度为1的轴

不指定axis 则默认删除所有长度为1的轴

y = tf.squeeze(y, axis=1)
y.shape
TensorShape([48, 2])

交换维度

交换维度之后,内存中元素的储存顺序改变,因此交换维度的计算代价比改变视图形状要高。

z = tf.transpose(y, perm=[1,0])
z.shape
TensorShape([2, 48])

复制数据

b = tf.constant([1, 2])
P(b.shape)
b = tf.expand_dims(b, axis=0)
P(b.shape)
b = tf.tile(b, multiples=[2, 1])
P(b.shape)
TensorShape([2])
TensorShape([1, 2])
TensorShape([2, 2])

multiples 分别指定了每个维度上面的复制倍数,对应位置为1表明不复制,为2表明新长度为原来长度的2 倍,即数据复制一份,以此类推。

广播

broadcasting 的效果和 tf.tile 一样。

算术运算

a = tf.range(4)
b = tf.constant(2)

a/b
tf.divide(a,b)
<tf.Tensor: shape=(4,), dtype=float64, numpy=array([0. , 0.5, 1. ,
1.5])>

取余

a%b
<tf.Tensor: shape=(4,), dtype=int32, numpy=array([0, 1, 0, 1])>

平方运算

a**2
tf.square(a)
<tf.Tensor: shape=(4,), dtype=int32, numpy=array([0, 1, 4, 9])>

幂运算

a**b
tf.pow(a,b)
<tf.Tensor: shape=(4,), dtype=int32, numpy=array([0, 1, 4, 9])>

开根号 (必须是浮点数)

tf.sqrt(tf.constant(9.0))
<tf.Tensor: shape=(), dtype=float32, numpy=3.0>

指数

2**a
# 必须是浮点数
tf.exp(3.0)
np.e**3.0
20.085536923187664

对数

如果希望计算其它底数的对数,暂时只能通过换底公式实现

tf.math.log(tf.constant(9.0))
<tf.Tensor: shape=(), dtype=float32, numpy=2.1972246>

矩阵乘法

a = tf.constant([[2,2], [1,2]])
b = tf.constant([[2,3], [1,1]])
print(a)
print(b)

tf.matmul(a, b)
tf.Tensor(
[[2 2]
 [1 2]], shape=(2, 2), dtype=int32)
tf.Tensor(
[[2 3]
 [1 1]], shape=(2, 2), dtype=int32)
<tf.Tensor: shape=(2, 2), dtype=int32, numpy=
array([[6, 8],
       [4, 5]])>

合并

a = tf.random.normal([4,35,8])
b = tf.random.normal([6,35,8])

不需要合并的维度的大小必须完全相等

tf.concat([a,b], axis=0)
tf.concat([a,b], axis=0).shape
TensorShape([10, 35, 8])

堆叠

a = tf.random.normal([35,8])
b = tf.random.normal([35,8])
P(tf.stack([a,b], axis=0).shape)

a = tf.random.normal([35,8])
b = tf.random.normal([35,8])
P(tf.stack([a,b], axis=-1).shape)
TensorShape([2, 35, 8])
TensorShape([35, 8, 2])

拆分

x = tf.random.normal([10, 35, 8])
# 等长切割为 10 份
result = tf.split(x, num_or_size_splits=10, axis=0)
len(result) # 返回 10 个张量组成的列表
10

特别地,如果希望在某个维度上全部按长度为 1 的方式分割,还可以使用 tf.unstack(x, axis)函数。这种方式是 tf.split 的一种特殊情况,切割长度固定为 1,只需要指定切割维度的索引号即可。

x = tf.random.normal([10,35,8])
result = tf.unstack(x,axis=0) # Unstack 为长度为 1 的张量
len(result) # 返回 10 个张量的列表
10

统计计算

范数

x = tf.ones([2,2])
tf.norm(x,ord=1) # 计算 L1 范数
tf.norm(x,ord=2) # 计算 L2 范数
tf.norm(x,ord=np.inf) # 计算∞范数
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>

最值、均值、和

x = tf.random.normal([2, 4])
tf.reduce_max(x, axis=1) # 最大值
tf.reduce_min(x, axis=1) # 最小值
tf.reduce_mean(x, axis=1) # 均值
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-0.16865723,
0.29473132], dtype=float32)>

统计全局的最大、最小、均值、和,返回的张量均为标量

tf.reduce_max(x), tf.reduce_min(x), tf.reduce_mean(x)

tf.reduce_sum(x,axis=-1) # 求最后一个维度的和
<tf.Tensor: shape=(2,), dtype=float32, numpy=array([-0.6746289,
1.1789253], dtype=float32)>

通过 tf.argmax(x, axis)tf.argmin(x, axis) 可以求解在 axis 轴上,x 的最大值、最小值所在的索引

tf.argmax(x)
tf.argmin(x)
<tf.Tensor: shape=(4,), dtype=int64, numpy=array([0, 0, 1, 0],
dtype=int64)>

张量比较

#  tf.equal(a, b)
# tf.math.greater 𝑏 > 𝑐
# tf.math.less 𝑏 < 𝑐
# tf.math.greater_equal 𝑏 ≥ 𝑐
# tf.math.less_equal 𝑏 ≤ 𝑐
# tf.math.not_equal 𝑏 ≠ 𝑐
# tf.math.is_nan 𝑏 = nan

填充与复制

这里所说的填充实际上是插入

a = tf.constant([1,2,3,4,5,6]) # 第一个句子
b = tf.constant([7,8,1,6])     # 第二个句子
b = tf.pad(b, [[0,2]])         # 句子末尾填充 2 个 0
b
<tf.Tensor: shape=(6,), dtype=int32, numpy=array([7, 8, 1, 6, 0, 0])>

数据限幅运算

x = tf.range(9)
P(tf.maximum(x, 2)) # 下限幅到 2
P(tf.minimum(x, 7)) # 上限幅到 7
P(tf.minimum(x, 11))
<tf.Tensor: shape=(9,), dtype=int32, numpy=array([2, 2, 2, 3, 4, 5, 6,
7, 8])>
<tf.Tensor: shape=(9,), dtype=int32, numpy=array([0, 1, 2, 3, 4, 5, 6,
7, 7])>
<tf.Tensor: shape=(9,), dtype=int32, numpy=array([0, 1, 2, 3, 4, 5, 6,
7, 8])>

高级操作

x = tf.random.uniform([4, 5, 6], maxval=100, dtype=tf.int32)
tf.gather(x, [0,1], axis=0)
<tf.Tensor: shape=(2, 5, 6), dtype=int32, numpy=
array([[[20, 91, 41, 61, 83,  7],
        [28, 74,  8, 56, 23, 28],
        [21, 71, 19, 35, 85, 24],
        [59, 96, 14, 87, 17, 22],
        [65, 27, 42, 94, 52, 94]],

       [[35, 66, 70,  6, 18, 99],
        [90, 15, 85, 12, 74, 20],
        [ 4, 97, 55, 91, 90,  1],
        [79, 74, 81, 83, 78,  9],
        [85, 32, 71,  1,  9, 28]]])>

实际上,对于上述需求,通过切片y[:2]可以更加方便地实现。但是对于不规则的索引方式,比如,需要抽取第二轴索引为 0、2、3 的数据,则切片方式实现起来非常麻烦,而 tf.gather 则是针对于此需求设计的,使用起来更加方便。

tf.gather(x,[0, 2, 3], axis=1)
<tf.Tensor: shape=(4, 3, 6), dtype=int32, numpy=
array([[[20, 91, 41, 61, 83,  7],
        [21, 71, 19, 35, 85, 24],
        [59, 96, 14, 87, 17, 22]],

       [[35, 66, 70,  6, 18, 99],
        [ 4, 97, 55, 91, 90,  1],
        [79, 74, 81, 83, 78,  9]],

       [[90, 46,  9, 12, 51, 19],
        [99, 16, 88, 28, 24, 73],
        [79, 28, 45, 55, 66, 84]],

       [[73, 33,  4, 57, 75, 86],
        [95, 46, 59, 67, 48, 67],
        [20, 89, 20, 94, 87, 38]]])>

通过 tf.gather_nd 函数,可以通过指定每次采样点的多维坐标来实现采样多个点的目的:

tf.gather_nd(x, [[1,1], [2,2], [3,3]])
<tf.Tensor: shape=(3, 6), dtype=int32, numpy=
array([[90, 15, 85, 12, 74, 20],
       [99, 16, 88, 28, 24, 73],
       [20, 89, 20, 94, 87, 38]])>

通过 tf.where(cond, a, b) 操作可以根据 cond 条件的真假从参数b或c中读取数据。

# tf.where()
# tf.scatter_nd()