本篇笔记包含张量的合并与分割,范数统计,张量填充,限幅等操作。
1.合并与分割
合并
张量的合并可以使用拼接(Concatenate)和堆叠(Stack)操作实现,拼接并不会产生新的维度,而堆叠会创建新维度。选择使用拼接还是堆叠操作来合并张量,取决于具体的场景是否需要创建新维度。
拼接 在 TensorFlow 中,可以通过 tf.concat(tensors, axis),其中 tensors 保存了所有需要合并的张量 List,axis 指定需要合并的维度。合并操作可以在任意的维度上进行,唯一的约束是非合并维度的长度必须一致。
a = tf.random.normal([4,35,8]) # 模拟成绩册 A
b = tf.random.normal([6,35,8]) # 模拟成绩册 B
tf.concat([a,b],axis=0) # 合并成绩册
Out[1]:
<tf.Tensor: id=13, shape=(10, 35, 8), dtype=float32, numpy=...>
堆叠 如果在合并数据时,希望创建一个新的维度,则需要使用 tf.stack 操作。使用 tf.stack(tensors, axis)可以合并多个张量 tensors,其中 axis 指定插入新维度的位置,axis 的用法与 tf.expand_dims 的一致,当axis ≥ 0时在 axis 之前插入;当axis < 0时,在 axis 之后插入新维度。axis 参数对应的插入位置设置如图:
a = tf.random.normal([35,8])
b = tf.random.normal([35,8])
tf.stack([a,b],axis=0)
Out[4]:
<tf.Tensor: id=55, shape=(2, 35, 8), dtype=float32, numpy=...>
tf.stack 也需要满足张量堆叠合并条件,它需要所有合并的张量 shape 完全一致才可合并。
分割
通过 tf.split(x, axis, num_or_size_splits)可以完成张量的分割操作,其中x代表待分割张量;axis代表分割的维度索引号;num_or_size_splits代表切割方案。当 num_or_size_splits 为单个数值时,如 10,表示切割为 10 份;当 num_or_size_splits 为 List 时,每个元素表示每份的长度,如[2,4,2,2]表示切割为 4 份,每份的长度分别为 2,4,2,2
x = tf.random.normal([10,35,8])
# 等长切割
result = tf.split(x,axis=0,num_or_size_splits=10)
result[0]
Out[9]: <tf.Tensor: id=136, shape=(1, 35, 8), dtype=float32, numpy=...>
切割后的shape 为[1,35,8],保留了维度,这一点需要注意。
特别地,如果希望在某个维度上全部按长度为 1 的方式分割,还可以直接使用 tf.unstack(x,axis)。这种方式是 tf.split 的一种特殊情况,切割长度固定为 1,只需要指定切割维度即可。
x = tf.random.normal([10,35,8])
result = tf.unstack(x,axis=0) # Unstack 为长度为 1
result[0]
Out[12]: <tf.Tensor: id=166, shape=(35, 8), dtype=float32, numpy=...>
可以看到,通过 tf.unstack 切割后,shape 变为[35,8],即班级维度消失了,这也是与 tf.split区别之处。
2.数据统计
在神经网络的计算过程中,经常需要统计数据的各种属性,如最大值,均值,范数等等。由于张量通常 shape 较大,直接观察数据很难获得有用信息,通过观察这些张量统计信息可以较轻松地推测张量数值的分布。
向量范数
向量范数(Vector norm)是表征向量“长度”的一种度量方法,对于矩阵、张量,同样可以利用向量范数的计算公式,等价于将矩阵、张量打平成向量后计算。在神经网络中,常用来表示张量的权值大小,梯度大小等。常用的向量范数有:
- L1 范数,定义为向量𝒙的所有元素绝对值之和
- L2 范数,定义为向量𝒙的所有元素的平方和,再开根号
- ∞ −范数,定义为向量𝒙的所有元素绝对值的最大值
在 TensorFlow 中,可以通过 tf.norm(x, ord)求解张量的 L1, L2, ∞等范数,其中参数 ord指定为 1,2 时计算 L1, L2 范数,指定为 np.inf 时计算∞ −范数
最大最小值、均值、和
通过 tf.reduce_max, tf.reduce_min, tf.reduce_mean, tf.reduce_sum 可以求解张量在某个维度上的最大、最小、均值、和,也可以求全局最大、最小、均值、和信息。
tf.reduce_max(x,axis=1) # 统计概率维度上的最大值
当不指定 axis 参数时,tf.reduce_*函数会求解出全局元素的最大、最小、均值、和
tf.reduce_max(x)
out = tf.random.normal([4,10]) # 网络预测输出
y = tf.constant([1,2,2,0]) # 真实标签
y = tf.one_hot(y,depth=10) # one-hot 编码
loss = keras.losses.mse(y,out) # 计算每个样本的误差
loss = tf.reduce_mean(loss) # 平均误差
loss
Out[20]:
<tf.Tensor: id=241, shape=(), dtype=float32, numpy=1.1921183>
与均值函数相似的是求和函数 tf.reduce_sum(x,axis),它可以求解张量在 axis 轴上所有特征的和
通过 tf.argmax(x, axis),tf.argmin(x, axis)可以求解在 axis 轴上,x 的最大值、最小值所在的索引号
3.张量比较
通过 tf.equal(a, b)(或 tf.math.equal(a, b))函数可以比较这 2个张量是否相等。tf.equal()函数返回布尔型的张量比较结果,只需要统计张量中 True 元素的个数,即可知道预测正确的个数。tf.cast(out, dtype=tf.float32) # 布尔型转 int 型
4.填充与复制
填充
对于图片数据的高和宽、序列信号的长度,维度长度可能各不相同。为了方便网络的并行计算,需要将不同长度的数据扩张为相同长度,通常的做法是,在需要补充长度的信号开始或结束处填充足够数量的特定数值,如 0,使得填充后的长度满足系统要求。那么这种
操作就叫做填充(Padding)。
填充操作可以通过 tf.pad(x, paddings)函数实现,paddings 是包含了多个[Left Padding ,Right Padding]的嵌套方案 List,如 [ [ 0 , 0 ] , [ 2 , 1 ] , [ 1 , 2 ] ]表示第一个维度不填充,第二个维度左边(起始处)填充两个单元,右边(结束处)填充一个单元,第三个维度左边填充
一个单元,右边填充两个单元。
复制
通过 tf.tile 函数可以在任意维度将数据重复复制多份,如 shape 为[4,32,32,3]的数据,复制方案 multiples=[2,3,3,1],即通道数据不复制,高宽方向分别复制 2 份,图片数再复1 份
5.数据限幅
在 TensorFlow 中,可以通过 tf.maximum(x, a)实现数据的下限幅:𝑦 ∈ [a,+∞);可以通过 tf.minimum(x, a)实现数据的上限幅:𝑦 ∈ (−∞,a]
ReLU 函数可以实现为:
def relu(x):
return tf.maximum(x,0.) # 下限幅为 0 即可(原书表述为return tf.minimum(x,0.)有误 )
通过组合 tf.maximum(x, a)和 tf.minimum(x, b)可以实现同时对数据的上下边界限幅:𝑦 ∈ [a,𝑏]:
tf.minimum(tf.maximum(x,a),b) # 限幅为 a~b
更方便地,我们可以使用 tf.clip_by_value 实现上下限幅,tf.clip_by_value(x,2,7) # 限幅为 2~7
6.高级操作
(1)tf.gather
tf.gather 可以实现根据索引号收集数据的目的。
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
tf.gather(x,[0,1],axis=0)
Out[38]:<tf.Tensor: id=83, shape=(2, 35, 8), dtype=int32, numpy=...>
tf.gather 非常适合索引没有规则的场合,其中索引号可以乱序排列,此时收集的数据也是对应顺序
(2)tf.gather_nd
通过 tf.gather_nd,可以通过指定每次采样的坐标来实现采样多个点的目的。一般地,在使用 tf.gather_nd 采样多个样本时,如果希望采样第 i 号班级,第 j 个学生,第 k 门科目的成绩,则可以表达为[...,[𝑗,𝑘,𝑙],...],外层的括号长度为采样样本的个数,内层列表包含了每个采样点的索引坐标
(3)tf.boolean_mask
通过 tf.boolean_mask(x, mask, axis)可以在 axis 轴上根据 mask 方案进行采样
tf.boolean_mask(x,mask=[True, False,False,True],axis=0)
tf.boolean_mask 既可以实现了 tf.gather 方式的一维掩码采样,又可以实现 tf.gather_nd 方式的多维掩码采样,与mask的维度有关
(4)tf.where
通过 tf.where(cond, a, b)操作可以根据 cond 条件的真假从 a 或 b 中读取数据,其中 i 为张量的索引,返回张量大小与 a,b 张量一致,当对应位置中cond 𝑖 为 True,o 𝑖 位置从a 𝑖 中复制数据;当对应位置中cond 𝑖 为False,o 𝑖 位置从b 𝑖 中复制数据。
In [53]: a = tf.ones([3,3]) # 构造 a 为全 1 b = tf.zeros([3,3]) # 构造 b 为全 0 # 构造采样条件 cond = tf.constant([[True,False,False],[False,True,False],[True,True,False]]) tf.where(cond,a,b) # 根据条件从 a,b 中采样 Out[53]:<tf.Tensor: id=384, shape=(3, 3), dtype=float32, numpy= array([[1., 0., 0.], [0., 1., 0.], [1., 1., 0.]], dtype=float32)>
当 a=b=None 即 a,b 参数不指定时,tf.where 会返回 cond 张量中所有 True 的元素的索引坐标
In [55]:tf.where(cond) # 获取 cond 中为 True 的元素索引 Out[55]:<tf.Tensor: id=387, shape=(4, 2), dtype=int64, numpy= array([[0, 0], [1, 1], [2, 0], [2, 1]], dtype=int64)>
(5)scatter_nd
通过 tf.scatter_nd(indices, updates, shape)可以高效地刷新张量的部分数据,但是只能在全 0 张量的白板上面刷新,因此可能需要结合其他操作来实现现有张量的数据刷新功能白板的形状表示为 shape 参数,需要刷新的数据索引为 indices,新数据为 updates,其中每个需要刷新的数据对应在白板中的位置,根据 indices 给出的索引位置将 updates 中新的数据依次写入白板中,并返回更新后的白板张量。
In [61]: # 构造需要刷新数据的位置 indices = tf.constant([[4], [3], [1], [7]]) # 构造需要写入的数据 updates = tf.constant([4.4, 3.3, 1.1, 7.7]) # 在长度为 8 的全 0 向量上根据 indices 写入 updates tf.scatter_nd(indices, updates, [8]) Out[61]:<tf.Tensor: id=467, shape=(8,), dtype=float32, numpy=array([0. , 1.1, 0. , 3.3, 4.4, 0. , 0. , 7.7], dtype=float32)>
(6)meshgrid
通过 tf.meshgrid 可以方便地生成二维网格采样点坐标,方便可视化等应用场合。tf.meshgrid 会返回在 axis=2 维度切割后的 2 个张量 a,b,其中张量 a 包含了所有点的 x坐标,b 包含了所有点的 y 坐标,shape 都为[100,100]
x = tf.linspace(-8.,8,100) # 设置 x 坐标的间隔 y = tf.linspace(-8.,8,100) # 设置 y 坐标的间隔 x,y = tf.meshgrid(x,y) # 生成网格点,并拆分后返回