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()