一、张量的存储和视图(View)概念
张量的视图就是我们理解张量的方式,例如shape 为[2,4,4,3]的张量A,我们从逻辑上可以理解为2 张图片,每张图片4 行4 列,每个位置有RGB 3 个通道的数据;
张量的存储体现在张量在内存上保存为一段连续的内存区域,对于同样的存储,我们可以有不同的理解方式,比如上述A,我们可以在不改变张量的存储下,将张量A 理解为2 个样本,每个样本的特征为长度48 的向量。
在存储数据时,内存并不支持这个维度层级概念,只能以平铺方式按序写入内存,因此这种层级关系需要人为管理,也就是说,每个张量的存储顺序需要人为跟踪。
为了方便表达,我们把张量shape 中相对靠左侧的维度叫做大维度,shape 中相对靠右侧的维度叫做小维度,比如[2,4,4,3]的张量中,图片数量维度与通道数量相比,图片数量叫做大维度,通道数叫做小维度。
例如:在优先写入小维度的设定下,张量 [2,4,4,3] 的内存布局为
为了能够正确恢复出数据,必须保证张量的存储顺序与新视图的维度顺序一致,
例如根据图片数量-行-列-通道
初始视图保存的张量,按照图片数量-行-列-通道(𝑏 − ℎ −w − 𝑐)
的顺序可以获得合法数据。
二、Reshape 操作
改变视图是神经网络中非常常见的操作,可以通过串联多个Reshape 操作来实现复杂逻辑,
但是在通过Reshape 改变视图时,必须始终记住张量的存储顺序,新视图的维度顺序不能与存储顺序相悖,否则需要通过交换维度操作将存储顺序同步过来。
例如,对于shape 为[4,32,32,3]的图片数据,通过Reshape 操作将shape 调整为[4,1024,3],此时视图的维度顺序为 𝑏 − 𝑝𝑖𝑥𝑒𝑙 − 𝑐,张量的存储顺序为 [𝑏, ℎ, w, 𝑐]。
在 TensorFlow 中,可以通过张量的ndim 和shape 成员属性获得张量的维度数和形状
#matrix为数据,其shape为[5, 28, 28, 3]
matrix = tf.random.normal([5, 28, 28, 3])
matrix_ndim = matrix.ndim #计算数据的维度
matrix_shape = matrix.shape #数据的形状
通过 tf.reshape(x,new_shape)将张量的视图任意的合法改变
#例如将shape为[5, 28, 28, 3]的数据转为shape为[5, 28*28, 3]的数据
matrix1 = tf.reshape(matrix, [5, -1, 3])
print("matrix1_shape:", matrix1.shape)
#例如将shape为[5, 28, 28, 3]的数据转为shape为[5, 28*28*3]的数据
matrix2 = tf.reshape(matrix, [5, -1])
print("matrix2_shape:", matrix2.shape)
当不知道填入什么数字合适时,可以选用 -1
来替代,由python通过其他值进行推算得知具体值
数据在创建时按着初始的维度顺序写入,改变张量的视图仅仅是改变了张量的理解方式,并不需要改变张量的存储顺序,张量只需要满足新视图的元素总量与存储区域大小相等即可
像上面:5 ∗ 28 ∗ 28 * 3 = 5 ∗ 784 ∗ 3 = 5 * 2352 总量没变;且张量的存储顺序始终没有改变
三、交换维度
以[𝑏, ℎ, w, 𝑐]转换到[𝑏, 𝑐, ℎ, w]为例,即[5, 28, 28, 3] -> [5, 3, 28, 28]
通过 tf.transpose(data, perm)函数完成维度交换操作
"""
变换维度
tf.transpose(data, perm)
data:数据
perm:新维度的顺序 List
"""
#[𝑏, ℎ, w, 𝑐]转换到[𝑏, 𝑐, ℎ, w]为例,即[5, 28, 28, 3] -> [5, 3, 28, 28]
matrix3 = tf.transpose(matrix, perm=[0, 3, 1, 2])
matrix3_shape = matrix3.shape
print("matrix3_shape:", matrix3_shape)
通过tf.transpose
完成维度交换后,张量的存储顺序已经改变,视图也随之改变,后续的所有操作必须基于新的存续顺序进行
四、增删维度
1. 增加维度
增加一个长度为1 的维度即为给原有的数据增加一个新维度,维度长度为1,故数据并不需要改变,仅仅是改变数据的理解方式
,因此它其实可以理解为改变视图的一种特殊方式
通过 tf.expand_dims(data, axis)可在指定的axis 轴前可以插入一个新的维度
"""
增加维度
tf.expand_dims(data, axis)
data:数据
axis:axis为正数时,表示在axis维度之前插入一个新维度;为负时,表示在axis前维度之后插入一个新的维度
"""
matrix4 = tf.expand_dims(matrix, axis= 1)
matrix4_shape = matrix4.shape#[5, 1, 28, 28, 3]
matrix5 = tf.expand_dims(matrix, axis= -1)
matrix5_shape = matrix5.shape#[5, 28, 28, 3, 1]
需要注意的是,tf.expand_dims 的axis 为正时,表示在当前维度之前插入一个新维度;为负时,表示当前维度之后插入一个新的维度。以[𝑏, ℎ, w, 𝑐]张量为例,不同axis 参数的实际插入位置如图所示:
增加维度的逆操作,与增加维度一样,删除维度只能删除长度为1 的维度,也不会改变张量的存储。
可以通过 tf.squeeze(data, axis)函数,axis 参数为待删除的维度的索引号
matrix6 = tf.squeeze(matrix5)#matrix5.shpae=[5, 28, 28, 3, 1]
matrix6_shape = matrix6.shape#matrix6.shpae=[5, 28, 28, 3]
matrix7 = tf.squeeze(matrix4, axis=1)#matrix4_shape: (5, 1, 28, 28, 3)
matrix7_shape = matrix7.shape#matrix7_shape: (5, 28, 28, 3)
如果不指定维度参数 axis,即 tf.squeeze(data),那么会默认删除所有长度为1的维度
五、数据复制
tf.tile(x, multiples)函数完成数据在指定维度上的复制操作,multiples 分别指定了每个维度上面的复制倍数
例如数据的shape为(5, 28, 28, 3),其维度为4, 则multiples的维度也应该为4
若multiples=[2, 1, 1, 1],即表示为在第0维度上复制一遍数据
matrix8 = tf.tile(matrix, multiples=[2, 2, 1, 1])
matrix8_shape = matrix8.shape#matrix8_shape: (10, 56, 28, 3)
六、Broadcasting(自动扩展)
Broadcasting 也叫广播机制(自动扩展也许更合适),它是一种轻量级张量复制的手段,在逻辑上扩展张量数据的形状,但是只要在需要时才会执行实际存储复制操作。对于大部分场景,Broadcasting 机制都能通过优化手段避免实际复制数据而完成逻辑运算,相对于tf.tile 函数,减少了大量计算代价
Broadcasting 机制的核心思想是普适性,即同一份数据能普遍适合于其他位置。在验证普适性之前, 需要将张量shape 靠右对齐,然后进行普适性判断:对于长度为1 的维度,默认这个数据普遍适合于当前维度的其他位置;对于不存在的维度,则在增加新维度后默认当前数据也是普适性于新维度的,从而可以扩展为更多维度数、其他长度的张量形状。
在 c 维度上,张量已经有2 个特征数据,新shape 对应维度长度为c(𝑐 ≠ 2,比如c=3),那么当前维度上的这2 个特征无法普适到其他长度,故不满足普适性原则,无法应用 Broadcasting 机制,将会触发错误
在进行张量运算时,有些运算可以在处理不同shape 的张量时,会隐式自动调用 Broadcasting 机制,如+,-,*,/等运算等,将参与运算的张量Broadcasting 成一个公共shape,再进行