pytorch中tensor维度操作

在pytorch中,需要对tensor进行各种型式的维度、复制等操作,比如squeeze, view, gather,expand,contiguous,copy等,下面对这些操作进行介绍。

1) squeeze & unsqueeze

torch.squeeze(input, dim=None, out=None)是将输入张量形状中的1 去除并返回。

  • 输入是形如(A×1×B×1×C×1×D),那么输出形状就为: (A×B×C×D)。
  • 当给定dim时,那么挤压操作只在给定维度上。例如,输入形状为: (A×1×B), squeeze(input, 0) 将会保持张量不变,只有用 squeeze(input, 1),形状会变成 (A×B)。
  • 注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。

torch.unsqueeze(input, dim,out=None)返回一个张量,对输入的制定位置插入维度 1。

  • 注意: 返回张量与输入张量共享内存,所以改变其中一个的内容会改变另一个。如果dim为负,则将会被转化dim+input.dim()+1

2) contiguous & view & resize

contiguous()→ Tensor返回一个内存连续的有相同数据的tensor,如果原tensor内存连续则返回原tensor。

view(*args) → Tensor, 返回一个有相同数据但大小不同的tensor。 返回的tensor必须有与原tensor相同的数据和相同数目的元素,但可以有不同的形状。一个tensor必须是连续的contiguous()才能使用view()函数。

resize_(*size)将tensor的大小调整为指定的大小。如果元素个数比当前的内存大小大,就将底层存储大小调整为与新元素数目一致的大小。如果元素个数比当前内存小,则底层存储不会被改变。原来tensor中被保存下来的元素将保持不变,但新内存将不会被初始化。

3) gather

torch.gather(input, dim, index, out=None)是沿给定轴dim,将输入索引张量index指定位置的值进行聚合。其中,在dim方向上,input和index必须有相同的维数;非dim方向上,input和index必须有相同的形状,即非dim方向的size相同。

假设 \[src \in \mathbb{R}^{m \times n}, idx \in \mathbb{R}^{m \times n}\],

trg = torch.gather(src,dim=0,idx),则

1
2
tmp = idx[i][j]
trg[i][j] = src[tmp][j]

trg = torch.gather(src,dim=1,idx),则

1
2
tmp = idx[i][j]
trg[i][j] = src[i][tmp]

4) expand vs. repeat

expand(*sizes),返回tensor的一个新视图,单个维度扩大为更大的尺寸。比如[12,15]的矩阵想expand成[12,15,7]的张量,需要先unsqueeze成[12,15,1],然后再expand成[12,15,7],也就是说expand()只能从单个维度扩展到多个维度(1->n),而不能新增维数。

expand_as(a)这是tensor变量的一个内置方法,如果使用b.expand_as(a)就是将b进行扩充,扩充到a的维度,需要说明的是a的低维度需要比b大,例如b的shape是31,如果a的shape是32不会出错,但是是2*2就会报错了。

repeat(sizes)沿着指定的维度重复tensor。不同与expand(),本函数复制的是tensor中的数据。参数:size(torch.size ot int…)-沿着每一维重复的次数

5) cat, stack vs. split, chunk

拼接

torch.cat((in1,in2,in3), dimension=dim)实现张量拼接。in1, in2,in3在非dim的维度上应size完全相同。

torch.stack(sequence, dim=0, out=None),做tensor的拼接。sequence表示Tensor列表,dim表示拼接的维度。注意这个函数和cat是不同的,cat是在已有的维度上拼接,而stack是建立一个新的维度,然后再在该维度上进行拼接。

分隔

torch.split(tensor, split_size, dim=0),将输入张量分割成相等形状的chunks。 如果沿指定维的张量形状大小不能被split_size 整分, 则最后一个分块会小于其它分块。

torch.chunk(tensor, chunks, dim=0),在给定维度(轴)上将输入张量进行分块儿。其中chunks (int) – 分块的个数,dim (int) – 沿着此维度进行分块。

6) clone, copy & ‘=’

a. 共享内存

tesB = tesA, 使用这种复制方式是共享内存的,即tesB, tesA的内存地址相同,改变任何一个tensor,另一个随之改变。

b. 浅拷贝

tesC=copy.copy(tesA),使用浅拷贝复制。此时只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。例如:

1
2
3
4
5
6
7
8
9
>>> a=[1,2,[3,4]]  #第三个值为列表[3,4],即内部元素
>>> d=copy.copy(a) #浅拷贝a中的[3,4]内部元素的引用,非内部元素对象的本身
>>> id(a)==id(d)
False # a和d的外围对象不共享内存,内存地址不同
>>> id(a[2])==id(d[2])
True # 但是浅拷贝中,a和d的内部元素仍然共享内存
>>> a[2][0]=3333 #改变a中内部原属列表中的第一个值
>>> d #这时d中的列表元素也会被改变
[1, 2, [3333, 4]]

c. 深拷贝

tesC=copy.deepcopy(tesA),使用深拷贝复制。内部元素的内存也独立。

d. clone()

clone() → Tensor,返回与原tensor有相同大小和数据类型的tensor,且与原tensor内存地址不同,若未指定cuda_device(),默认与原tensor的GPU相同。

1
2
var_x.data = y.clone() # y is a tensor, var_x is variable
# y, var_x 内存地址不同,

7) other func

  • 注意: 会改变tensor的函数操作会用一个下划线后缀来标示。比如,torch.FloatTensor.abs_()会在原地计算绝对值,并返回改变后的tensor,而tensor.FloatTensor.abs()将会在一个新的tensor中计算结果。
  • 使用Tensor型数据进行比较的时候需要注意,如果比较的是其中的值,那么必须将其化为普通值再进行比较,即使是一维的单个数据,也要用[0]操作符来进行读取。
  • torch.transpose(input, dim0, dim1, out=None)返回输入矩阵input的转置。交换维度dim0和dim1。 输出张量与输入张量共享内存,所以改变其中一个会导致另外一个也被修改。