张量维度变换

在神经网络运算过程中,维度变换是最核心的张量操作,通过维度变换可以将数据任意地切换形式,满足不同场合的运算需求。
那么为什么需要维度变换?通过线性层的批量形式入手

Y = X@W + b

其中,假设X包含了2个样本,每个样本的特征长度为4,X的shape为[2,4]。
线性层的输出为3个节点,即W的 shape 定义为[4,3],偏置b的shape 定义为[3]。那么X@W 的运算结果张量 shape 为[2,3],需要叠加上 shape 为[3]的偏置b。不同shape的2个张量怎么直接相加?
因此,对于2个样本的输人X,需要将shape为[3]的偏置
b = [ b 1 b 2 b 3 ] b=\left[ \begin{matrix} {{b}_{1}} \\ {{b}_{2}} \\ {{b}_{3}} \\ \end{matrix} \right] b= b1b2b3
按按样本数量复制1份,变成如下矩阵形式B’:
B ′ = [ b 1 b 2 b 3 b 1 b 2 b 3 ] {{B}^{'}}=\left[ \begin{matrix} {{b}_{1}} & {{b}_{2}} & {{b}_{3}} \\ {{b}_{1}} & {{b}_{2}} & {{b}_{3}} \\ \end{matrix} \right] B=[b1b1b2b2b3b3]
即可满足矩阵相加的数学条件
通过这种方式,既满足了数学上矩阵相加需要shape 一致的条件,又达到了给每个输入样本的输出节点共享偏置向量的逻辑。为了实现这种运算方式,将偏置向量b插入一个新的维度,并把它定义为 Batch维度,然后在Batch维度将数据复制1份,得到变换后的 B’,新的shape为[2,3]。这一系列的操作就是维度变换操作。
维度变换的功能:算法的每个模块对于数据张量的格式有不同的逻辑要求,当现有的数据格式不满足算法要求时,需要通过维度变换将数据调整为正确的格式。
基本的维度变换操作函数包含改变视图reshape插入新维度expand_dims删除维度squeeze交换维度 transpose复制数据 tile 等函数。

1.1 改变视图

在理解改变视图reshape操作之前,先来认识张量的存储(Storage)视图(View)的概念。
张量的视图就是理解张量的方式,例如shape为[2,4,4,3]的张量A,从逻辑上可以理解为2张图片,每张图片4行4列,每个位置有RGB3个通道的数据。张量的存储体现在张量在内存上保存为一段连续的内存区域(平坦结构),对于同样的存储,可以有不同的理解方式,例如上述张量A,可以在不改变张量的存储下,将张量A理解为2个样本,每个样本的特征为长度48( 4 ∗ 4 ∗ 3 4*4*3 443)的向量。同一个存储,从不同的角度观察数据,可以产生不同的视图,这就是存储与视图的关系。
通过tf.range()模拟生成一个向量数据,并通过tf.reshape视图改变函数产生不同的视图,例如:

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

输出结果为:

tf.Tensor(
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95], shape=(96,), dtype=int32)
tf.Tensor(
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]
   [ 9 10 11]]

  [[12 13 14]
   [15 16 17]
   [18 19 20]
   [21 22 23]]

  [[24 25 26]
   [27 28 29]
   [30 31 32]
   [33 34 35]]

  [[36 37 38]
   [39 40 41]
   [42 43 44]
   [45 46 47]]]


 [[[48 49 50]
   [51 52 53]
   [54 55 56]
   [57 58 59]]

  [[60 61 62]
   [63 64 65]
   [66 67 68]
   [69 70 71]]

  [[72 73 74]
   [75 76 77]
   [78 79 80]
   [81 82 83]]

  [[84 85 86]
   [87 88 89]
   [90 91 92]
   [93 94 95]]]], shape=(2, 4, 4, 3), dtype=int32)

改变x的视图,获得四维(4D)张量,存储并未改变。可以观察到数据仍然是0~95的顺序,可见数据并未改变,改变的是数据的结构。
存储数据时,内存并不支持这个维度层级概念,只能以平铺方式按序写人内存,为了方便表达,把张量shape 列表中相对靠左侧的维度称为大维度,shape列表中相对靠右侧的维度称为小维度,例如[2,4,4,3]的张量中,图片数量维度与通道数量相比,图片数量称为大维度,通道数称为小维度。
改变视图操作的默认前提是存储不需要改变,否则改变视图操作就是非法的
张量A按着初始视图[b,h,w,c]写人的内存布局,改变A的理解方式,它可以有如下多种合法的理解方式,如下:
(1)[b, h ∗ w h*w hw,c]张量理解为b张图片, h ∗ w h*w hw个像素点,c个通道;
(2)[b,h, w ∗ c w*c wc]张量理解为b张图片h行,每行的特征长度为 w ∗ c w*c wc
(3)[b, h ∗ w ∗ c h*w*c hwc]张量理解为b张图片,每张图片的特征长度为 h ∗ w ∗ c h*w*c hwc
不合法的情况举例如下:
[b,c, h ∗ w h*w hw]
一种正确使用视图变换操作的技巧就是跟踪存储的维度顺序。例如根据“图片数量-行-列-通道”初始视图保存的张量,存储也是按照“图片数量-行-列-通道”的顺序写人的。如果按着“图片数量-像素-通道”的方式恢复视图,并没有与“图片数量-行-列-通道”相悖,因此能得到合法的数据。但是如果按着“图片数量-通道-像素”的方式恢复数据,由于内存布局是按着“图片数量-行-列-通道”的顺序,视图维度顺序与存储维度顺序相悖,提取的数据将是错乱的。

1.2 增加、删除维度

1.2.1 增加维度

增加维度:增加一个长度为1的维度相当于给原有的数据添加一个新维度的概念,维度长度为1,故数据并不需要改变,仅仅是改变数据的理解方式,因此它其实可以理解为改变视图的一种特殊方式。
假如一张 28 ∗ 28 28*28 2828大小的灰度图片的数据保存为shape为[28,28]的张量,在末尾给张量增加一新维度,定义为通道数维度,此时张量的shape 变为[28,28,1],实现如下:

# 生成形状为 [3, 3] 的 tf.int32 类型随机数,值在 010 之间
x = tf.random.uniform([3, 3], minval=0, maxval=10, dtype=tf.int32)
print(x)

x = tf.expand_dims(x,axis=2)
print(x)

输出结果如下:

tf.Tensor(
[[0 4 2]
 [8 2 7]
 [0 9 7]], shape=(3, 3), dtype=int32)
tf.Tensor(
[[[0]
  [4]
  [2]]

 [[8]
  [2]
  [7]]

 [[0]
  [9]
  [7]]], shape=(3, 3, 1), dtype=int32)

可以看到,插入一个新维度后,数据的存储顺序并没有改变,依然按顺序保存,仅仅是在插人一个新的维度后,改变了数据的视图。
tf.expand_dimsaxis为正时,表示在当前维度之前插人一个新维度,为负时,表示当前维度之后插入一个新的维度。

1.2.1 删除维度

删除维度是增加维度的逆操作,与增加维度一样,删除维度只能删除长度为1的维度,也不会改变张量的存储。继续考虑增加维度后shape为[1,3,3,1]的例子,如果希望将图片数量维度删除,可以通过tf.squeeze(x,axis)函数实现,axis参数为待删除的维度的索引号,例如,图片数量的维度轴axis=0:

# 生成形状为 [3, 3] 的 tf.int32 类型随机数
x = tf.random.uniform([3, 3], minval=0, maxval=10, dtype=tf.int32)
print("x.shape:", x.shape)
print(x)

# 假设你有一个额外的维度(例如,通过 tf.expand_dims 添加的),然后你可以使用 tf.squeeze
x_expanded = tf.expand_dims(x, axis=0)  # 现在形状是 [1, 3, 3]
print("x_expanded.shape:", x_expanded.shape)

# 移除第一个维度(现在它是 1)
x_squeezed = tf.squeeze(x_expanded, axis=0)
print("x_squeezed.shape:", x_squeezed.shape)
print(x_squeezed)

输出结果如下:

x.shape: (3, 3)
tf.Tensor(
[[4 1 3]
 [4 4 2]
 [9 0 3]], shape=(3, 3), dtype=int32)
x_expanded.shape: (1, 3, 3)
x_squeezed.shape: (3, 3)
tf.Tensor(
[[4 1 3]
 [4 4 2]
 [9 0 3]], shape=(3, 3), dtype=int32)

如果不指定维度参数 axis,即tf.squeeze(x),那么它会默认删除所有长度为1的维度

1.3 交换维度

改变视图、增删维度都不会影响张量的存储。在实现算法逻辑时,保持维度顺序不变的条件下,仅仅改变张量的理解方式是不够的,有时需要直接调整存储顺序,即交换维度(Transpose)。
通过交换维度操作,改变了张量的存储顺序,同时也改变了张量的视图。交换维度操作是非常常见的,例如在TensorFlow中,图片张量的默认存储格式是通道后行格式为[b,h,w,c],但是部分库的图片格式是通道先行格式为[b,c,h,w],因此需要完成[b,h,w,c]到[b,c,h,w]维度交换运算,此时若简单地使用改变视图函数reshape,则新视图的存储方式需要改变,因此使用改变视图函数是不合法的。以[b,h,w,c]转换到[b,c,h,w]为例,介绍如何使用tf.transpose(x,perm)函数完成维度交换操作,其中参数perm表示新维度的顺序List。考虑图片张量shape为[2,2,2,3],“图片数量、行、列、通道数”的维度索引分别为0、1、2、3,如果需要交换为[b,c,h,w]格式,则新维度的排序为“图片数量、通道数、行、列”,对应的索引号为[0,3,1,2],因此参数perm需设置为[0,3,1,2]。

x = tf.random.uniform([2,2,2,3], minval=0, maxval=10, dtype=tf.int32)
print(x)


x = tf.transpose(x, perm=[0,2,1,3])
print(x)

输出结果如下:

tf.Tensor(
[[[[8 2 4]
   [6 0 1]]

  [[6 6 7]
   [4 3 9]]]


 [[[2 3 7]
   [7 8 4]]

  [[9 8 0]
   [1 4 0]]]], shape=(2, 2, 2, 3), dtype=int32)
tf.Tensor(
[[[[8 2 4]
   [6 6 7]]

  [[6 0 1]
   [4 3 9]]]


 [[[2 3 7]
   [9 8 0]]

  [[7 8 4]
   [1 4 0]]]], shape=(2, 2, 2, 3), dtype=int32)

由输出结果可知成功将[b,h,w,c]交换为[b,w,h,c],即将高、宽维度互换,新维度索引为[0,2,1,3]。
通过tf.transpose完成维度交换后,张量的存储顺序已经改变,视图也随之改变,后续的所有操作必须基于新的存续顺序和视图进行。相对于改变视图操作,维度交换操作的计算代价更高。

1.4 复制数据

当通过增加维度操作插入新维度后,可能希望在新的维度上面复制若干份数据,满足后续算法的格式要求。考虑Y=X@W+b的例子,偏置b插入样本数的新维度后,需要在新维度上复制 Batch Size份数据,将shape变为与X@W 一致后,才能完成张量相加运算。
可以通过 tf.tile(x,multiples)函数完成数据在指定维度上的复制操作,multiples分别指定了每个维度上面的复制倍数,对应位置为1表明不复制,为2表明新长度为原来长度的2倍,即数据复制一份,以此类推。
以输人为[2,4],输出为3个节点线性变换层为例,偏置b定义为:
b = [ b 1 b 2 b 3 ] b=\left[ \begin{matrix} {{b}_{1}} \\ {{b}_{2}} \\ {{b}_{3}} \\ \end{matrix} \right] b= b1b2b3
通过 tf.expand_dims(b,axis=0)插人新维度,变成矩阵:
B = [   b 1 b 2 b 3 ] B=\left[ \begin{matrix} \ {{b}_{1}} & {{b}_{2}} & {{b}_{3}} \\ \end{matrix} \right] B=[ b1b2b3]
此时B的shape 变为[1,3],需要在axis=0图片数量维度上根据输入样本的数量复制若干次,这里的BatchSize为2,即复制一份,变成:
B ′ = [ b 1 b 2 b 3 b 1 b 2 b 3 ] {{B}^{'}}=\left[ \begin{matrix} {{b}_{1}} & {{b}_{2}} & {{b}_{3}} \\ {{b}_{1}} & {{b}_{2}} & {{b}_{3}} \\ \end{matrix} \right] B=[b1b1b2b2b3b3]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值