张量维度操作(拼接、维度扩展、压缩、转置、重复……)

note: torch.fun(tensor1)和tensor1.fun()都只会返回改变后的tensor,但是tensor本身的维度和数据都不会变。包括unsqueeze、expand等等。


张量切片选择TORCH.INDEX_SELECT

torch.index_select(input, dim, index, *, out=None)

示例

>>> x = torch.randn(3, 4)
 >>> x
 tensor([[ 0.1427,  0.0231, -0.5414, -1.0009],
         [-0.4664,  0.2647, -0.1228, -1.1068],
         [-1.1734, -0.6571,  0.7230, -0.6004]])
 >>> indices = torch.tensor([0, 2])
 >>> torch.index_select(x, 0, indices)
 tensor([[ 0.1427,  0.0231, -0.5414, -1.0009],
         [-1.1734, -0.6571,  0.7230, -0.6004]])
 >>> torch.index_select(x, 1, indices)
 tensor([[ 0.1427, -0.5414],
         [-0.4664, -0.1228],
         [-1.1734,  0.7230]])

 

torch.cat多个tensor在已有维度上拼接

torch.cat(seq, dim=0, out=None) 

在指定的维度dim上对序列seq进行连接操作。

参数:

seq (sequence of Tensors) - Python序列或相同类型的张量序列
 dim (int, optional) - 沿着此维度连接张量
 out (Tensor, optional) - 输出参数
 例子:x = torch.randn(2, 3)
 x
 -0.5866 -0.3784 -0.1705
 -1.0125 0.7406 -1.2073
 [torch.FloatTensor of size 2x3]
 torch.cat((x, x), 0)
 -0.5866 -0.3784 -0.1705
 -1.0125 0.7406 -1.2073
 -0.5866 -0.3784 -0.1705
 -1.0125 0.7406 -1.2073
 [torch.FloatTensor of size 4x3]
 torch.cat((x, x), 1)
 -0.5866 -0.3784 -0.1705 -0.5866 -0.3784 -0.1705
 -1.0125 0.7406 -1.2073 -1.0125 0.7406 -1.2073
 [torch.FloatTensor of size 2x6]

模拟np.concatenate功能

torch.cat只能对多个tensor操作,不能对单个操作,只能先split再cat。
x = [[[0], [1], [2]], [[3], [1], [2]]]
 print(torch.tensor(np.concatenate(x, axis=-1)))
 print(torch.cat(torch.split(torch.tensor(x), 1, dim=0), dim=-1).squeeze(0))
 # print(torch.cat(tuple(i.squeeze(0) for i in torch.split(torch.tensor(x), 1, dim=0)), dim=-1))
 # tensor([[0, 3],
 #         [1, 1],
 #         [2, 2]])torch.stack拼接后维度会扩张
torch.stack(tensors, dim=0, *, out=None)
示例1:
x = torch.randn(4)
 print(x.size())  # torch.Size([4])
 print(torch.stack((x, x), 0).size())  # torch.Size([2, 4])
 print(torch.stack((x, x), 1).size())  # torch.Size([4, 2])示例2:
x = torch.randn(3, 4)
 print(torch.stack((x, x), 0).size())  # torch.Size([2, 3, 4])
 print(torch.stack((x, x), 1).size())  # torch.Size([3, 2, 4])torch.split分割tensor
torch.split(tensor, split_size_or_sections, dim=0)
不过split后,还是会保留还有的维度,如果不想要可以squeeze(dim)去除。
示例
将一个n*m tensor分解成n个1*m tensor
 result = torch.split(tensor, 1, dim=0)x = np.asarray([[[0], [1], [2]], [[3], [1], [2]]])
 print(tuple(i.squeeze(0) for i in torch.split(torch.tensor(x), 1, dim=0)))
 # (tensor([[0],
 #         [1],
 #         [2]], dtype=torch.int32), tensor([[3],
 #         [1],
 #         [2]], dtype=torch.int32))

torch.squeeze压缩张量

torch.squeeze(input, dim=None)

参数:

input (Tensor) – 输入张量
dim (int, optional) – 如果给定,则只会在给定维度压缩。Changed in version 2.0: dim now accepts tuples of dimensions而不需要多次squeeze.

除去输入张量input中数值为1的维度,并返回新的张量。如果输入张量的形状为(A*1*B*C*1*D),那么输出张量的形状为( A*B*C*D)。

当通过dim参数指定维度时,维度压缩操作只会在指定的维度上进行。如果输入向量的形状为(A*1*B),squeeze(input, 0)会保持张量的维度不变,只有在执行squeeze(input, 1)时,输入张量的形状会被压缩至( A*B )。

如果一个张量只有1个维度,那么它不会受到上述方法的影响。

squeeze有个缺陷,即size所有维度都是1时,squeeze后维度全没了,所以当知道需要对某个维度进行squeeze时,尽量指定dim。
 a = torch.tensor([[0.4965]])
 a.size()  #torch.Size([1, 1])
 a.squeeze().size()   #torch.Size([])

输出的张量与原张量共享内存,如果改变其中的一个,另一个也会改变。

为何只去掉 1 呢?多维张量本质上就是一个变换,如果维度是 1 ,那么,1 仅仅起到扩充维度的作用,而没有其他用途,因而,在进行降维操作时,为了加快计算,是可以去掉这些 1 的维度。
例子:

x = torch.zeros(2, 1, 2, 1, 2)
 x.size()
 torch.Size([2, 1, 2, 1, 2])
 y = torch.squeeze(x)
 y.size()
 torch.Size([2, 2, 2])
 y = torch.squeeze(x, 0)
 y.size()
 torch.Size([2, 1, 2, 1, 2])
 y = torch.squeeze(x, 1)
 y.size()
 torch.Size([2, 2, 1, 2])

torch.unsqueeze/tensor.unsqueeze扩展张量维度

torch.unsqueeze(input, dim)

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

参数
tensor (Tensor) – 输入张量
dim (int) – 插入维度的索引
out (Tensor, optional) – 结果张量

Note: tensor1.unsqueeze(1)可以使用tensor1[:, None, :, :, :]来等价代替,这时tensor1的维度从[a,b,c,d]变成[a,1,b,c,d]。同理,tensor1.unsqueeze(0).unsqueeze(3)通过x[None, :, :, None, :]等价代替,维度从[a,b,c]变成[1,a,b,1,c]。

tensor.unsqueeze_和tensor.unsqueeze/torch.unsqueeze 的区别

torch.unsqueeze_(tensor1,1)相当于tensor1.unsqueeze(1)。

区别在于 tensor1.unsqueeze_ 是 in_place 操作,即unsqueeze_ 则会对自己改变。torch.unsqueeze 不会对使用 unsqueeze 的 tensor 进行改变,想要获取 unsqueeze 后的值必须赋予个新值。
示例:

x = torch.Tensor([1, 2, 3, 4])  # torch.Tensor是默认的tensor类型(torch.FlaotTensor)的简称。
 print(x.size())  # torch.Size([4]) print(torch.unsqueeze(x, 0))  # tensor([[1., 2., 3., 4.]])
 print(torch.unsqueeze(x, 0).size())  # torch.Size([1, 4]) print(torch.unsqueeze(x, 1))
 # tensor([[1.],
 #         [2.],
 #         [3.],
 #         [4.]])
 print(torch.unsqueeze(x, 1).size())  # torch.Size([4, 1])

 print(torch.unsqueeze(x, -1))
 # tensor([[1.],
 #         [2.],
 #         [3.],
 #         [4.]])
 print(torch.unsqueeze(x, -1).size())  # torch.Size([4, 1]) print(torch.unsqueeze(x, -2))  # tensor([[1., 2., 3., 4.]])
 print(torch.unsqueeze(x, -2).size())  # torch.Size([1, 4])[torch.unsqueeze() 和 torch.squeeze()]

torch.Tensor.expand(*sizes)扩大张量

返回张量的一个新视图,可以将张量的单个维度扩大为更大的尺寸。

张量也可以扩大为更高维,新增加的维度将附在前面。 扩大张量不需要分配新内存,仅仅是新建一个张量的视图。任意一个一维张量在不分配新内存情况下都可以扩展为任意的维度。

传入-1则意味着维度扩大不涉及这个维度。从一维扩张时,只能用.expand(4, -1)来变成二维=4*原来1维维度。所有expand都不能用.expand(-1, 4),除非二维刚好=1。

参数:sizes (torch.Size or int...) – 想要扩展的目标维度

示例1:

a = torch.tensor([[1, 2], [3, 4], [5, 6]])
a1 = (a ** 2).sum(1).expand(3, 4)

(a**2).sum(dim=1, keepdim=True).expand(-1, 4)

或者a1 = torch.sum((a ** 2), 1, keepdim=True).expand(3, 4)  # size=(3,1)可以expand为(3,4)
示例2:

x = torch.Tensor([[1], [2], [3]])
x.size()
torch.Size([3, 1])
x.expand(3, 4)
1 1 1 1
2 2 2 2
3 3 3 3
[torch.FloatTensor of size 3x4]

Flatten张量

>>> t = torch.tensor([[[1, 2],
                       [3, 4]],
                      [[5, 6],
                       [7, 8]]])
>>> torch.flatten(t)
tensor([1, 2, 3, 4, 5, 6, 7, 8])
>>> torch.flatten(t, start_dim=1)
tensor([[1, 2, 3, 4],
        [5, 6, 7, 8]])

[torch.flatten(input, start_dim=0, end_dim=-1) ]

-柚子皮-

torch.Tensor.repeat(*sizes)重复张量

沿着指定的维度重复张量。不同于expand()方法,本函数复制的是张量中的数据。

参数:size (torch.size ot int...) - 沿着每一维重复的次数
例子:

x = torch.Tensor([1, 2, 3])
x.repeat(3, 2)
1 2 3 1 2 3
1 2 3 1 2 3
1 2 3 1 2 3
[torch.FloatTensor of size 3x6]

torch.Tensor.unfold(dim, size, step) 

返回一个新的张量,其中元素复制于有原张量在dim维度上的数据,复制重复size次,复制时的步进值为step。

参数:

dim (int) - 目标维度
 size (int) - 复制重复的次数(展开维度)
 step (int) - 步长
 例子:x = torch.arange(1, 8)
 x
 1
 2
 3
 4
 5
 6
 7
 [torch.FloatTensor of size 7]
 x.unfold(0, 2, 1)
 1 2
 2 3
 3 4
 4 5
 5 6
 6 7
 [torch.FloatTensor of size 6x2]
 x.unfold(0, 2, 2)
 1 2
 3 4
 5 6
 [torch.FloatTensor of size 3x2]

torch.Tensor.narrow(dimension, start, length)缩小张量

返回一个经过缩小后的张量。操作的维度由dimension指定。缩小范围是从start开始到start+length。执行本方法的张量与返回的张量共享相同的底层内存。

参数:
dimension (int) – 要进行缩小的维度
 start (int) – 开始维度索引
 length (int) – 缩小持续的长度
 例子:x = torch.Tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
 x.narrow(0, 0, 2)
 1 2 3
 4 5 6
 [torch.FloatTensor of size 2x3]
 x.narrow(1, 1, 2)
 2 3
 5 6
 8 9
 [torch.FloatTensor of size 3x2]

torch.gather(input, dim, index, out=None)

沿给定轴dim,将输入索引张量index指定位置的值进行聚合。index的类型必须是LongTensor类型的。index的大小就是输出的大小。

对一个3维张量,输出可以定义为:

           

out[i][j][k] = tensor[index[i][j][k]][j][k] # dim=0
                out[i][j][k] = tensor[i][index[i][j][k]][k] # dim=1
                out[i][j][k] = tensor[i][j][index[i][j][k]] # dim=3

gather中index实际上是索引,具体是行还是列的索引要看dim。

比如对于[[1,2,3],[4,5,6]],指定dim=1,也就是横向,索引就是列号。index是[[0,1],[2,0]],那么看index第一行,列0指的是1, 列1指的是2,同理,第二行为6,4,这样就输出为[[1,2], [6,4]。

示例1:

b = torch.Tensor([[1,2,3],[4,5,6]])
 print b
 index_1 = torch.LongTensor([[0,1],[2,0]])
 index_2 = torch.LongTensor([[0,1,1],[0,0,0]])
 print torch.gather(b, dim=1, index=index_1)
 print torch.gather(b, dim=0, index=index_2)1  2  3
  4  5  6
 [torch.FloatTensor of size 2x3]
  1  2
  6  4
 [torch.FloatTensor of size 2x2]
  1  5  6
  1  2  3
 [torch.FloatTensor of size 2x3]


示例2:
torch.gather(final_dist, dim=-1, index=target)
其中final_dist是预测概率输出tensor=(batch_size, vocab_size),target是目标输出tensor=(batch_size, seq_len),每个值表示在vocab中的id。整个语句就是通过target取final_dist中对应的概率。

示例3:和2一个意思。gather在one-hot为输出的多分类问题中,可以把最大值坐标作为index传进去,然后提取到每一行的正确预测结果,这也是gather的一个应用。

def cross_entropy(logits, target):
     batch_size = logits.size(0)
     log_probs_flat = functional.log_softmax(logits)
     losses_flat = -torch.gather(log_probs_flat, dim=1, index=target)
     loss = losses_flat.sum() / batch_size
     return loss[Pytorch中的torch.gather函数的含义][pytorch之torch.gather方法]

torch.scatter(input, dim, index, src)

Out-of-place version of torch.Tensor.scatter_()

scatter() 和 scatter_() 的作用是一样的,但是 scatter() 不会直接修改原来的 Tensor,而 scatter_() 会修改原先的 Tensor。

Tensor.scatter_(dim, index, src, reduce=None)

Tensor.scatter_(dim, index, src, reduce=None)
This is the reverse operation of the manner described in gather().
Reducing with the addition operation is the same as using scatter_add_().[tensor.scatter_add]

示例:pytorch multihot实现

import torch.nn.functional as F
 import torchtensor = torch.tensor([[0, 2], [2, 1]])
multi_hot = torch.zeros(2, 4).scatter_(1, tensor, 1)
 print(multi_hot)
 # tensor([[1., 0., 1., 0.],
 #         [0., 1., 1., 0.]])

torch.Tensor.view(*args) 张量变形

返回一个有相同数据但是不同形状的新的向量。

返回的装两必须与原张量有相同的数据和相同的元素个数,但是可以有不同的尺寸。

参数:

args (torch.Size or int....) - 理想的指定尺寸
示例1:

x = torch.randn(4, 4)
 x.size()
 torch.Size([4, 4])
 y = x.view(16)
 y.size()
 torch.Size([16])示例2:
embs = torch.Tensor([[[1, 2], [1, 2]], [[3, 4], [3, 4]], [[5, 6], [5, 6]]])
 print(embs.size())
 embs = embs.view(embs.size(0), embs.size(1) * embs.size(2))
 print(embs.size())
 torch.Size([3, 2, 2])
 torch.Size([3, 4])
 view和下面的resize最大的区别是,resize的张量不能是grad的:RuntimeError: cannot resize variables that require grad

torch.Tensor.resize_(*sizes)重设张量尺寸

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

参数:

sizes (torch.Size or int....) - 需要调整的大小
 例子:x = torch.Tensor([[1, 2], [3, 4], [5, 6]])
 x.resize_(2, 2)
 x
 1 2
 3 4
 [torch.FloatTensor of size 2x2]

交换张量的维度

二维张量交换torch.t(x)/tensor.t()/tensor.T

Expects input to be <= 2-D tensor and transposes dimensions 0 and 1.
[torch.t(input) → Tensor]
torch.Tensor.t (Python method, in torch.Tensor)
 torch.Tensor.T (Python attribute, in torch.Tensor)

交换多维张量的两个维度torch.transpose()/tensor.transpose(dim0, dim1)

torch.transpose(input, dim0, dim1) → Tensor
输出和输入是共享内存的。
transpose中的两个维度参数的顺序是可以交换位置的,即transpose(x, 0, 1,) 和transpose(x, 1, 0)效果是相同的。
>>> x = torch.randn(2, 3)
 >>> x
 tensor([[ 1.0028, -0.9893,  0.5809],
         [-0.1669,  0.7299,  0.4942]])
 >>> torch.transpose(x, 0, 1)
 tensor([[ 1.0028, -0.1669],
         [-0.9893,  0.7299],
         [ 0.5809,  0.4942]])

torch.Tensor.permute(*dims)置换张量维度

将执行本方法的张量的维度换位。

参数:dim (int) - 指定换位顺序
例子:

x = torch.randn(2, 3, 5)
 x.size()
 torch.Size([2, 3, 5])
 x.permute(2, 0, 1).size()
 torch.Size([5, 2, 3])from: -柚子皮-
ref: [张量维度操作(拼接、维度扩展、压缩、转置、重复……)]