Pytorch——张量 Tensors

Posted 专注人工智能

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Pytorch——张量 Tensors相关的知识,希望对你有一定的参考价值。

张量 Tensors

1、torch.is_tensor

torch.is_tensor(obj)

用法:判断是否为张量,如果是 pytorch 张量,则返回 True。

参数:obj (Object) – 判断对象

例子:

torch.is_tensor(torch.rand(2,3))

True

 

2、 torch.is_storage

torch.is_storage(obj)

用法:判断是否为pytorch Storage,如何是,则返回True

参数:input (Object) – 判断对象

例子:

torch.is_storage(torch.rand(2,3))
False

 

3、torch.numel

torch.numel(input)->int

用法:返回input 张量中的元素个数

参数:input (Tensor) – 输入张量

例子:

torch.numel(torch.rand(2,3))
6

 

4、torch.eye

torch.eye(n, m=None, out=None)

用法:返回一个2维张量,对角线数字为1,其它位置为0

参数:

  • n (int) – 行数
  • m (int, 可选) – 列数.如果为None,则默认为n
  • out (Tensor,可选) - 输出张量

例子:

torch.eye(3, m=2)
tensor([[1., 0.],
        [0., 1.],
        [0., 0.]])
torch.eye(3)
tensor([[1., 0., 0.],
        [0., 1., 0.],
        [0., 0., 1.]])

 

5、torch.from_numpy

torch.from_numpy(ndarray) → Tensor

用法:将 numpy.ndarray 转换为 Tensor。 返回的张量 tensor 和 numpy 的 ndarray 共享同一内存空间。修改一个会导致另外一个也被修改。返回的张量不能调整大小。

参数:ndarray

例子:

x = np.random.rand(2,3)
x
array([[0.84130586, 0.64710973, 0.82838384],
       [0.50825928, 0.3054745 , 0.22876226]])
y = torch.from_numpy(x)
y
tensor([[0.8413, 0.6471, 0.8284],
        [0.5083, 0.3055, 0.2288]], dtype=torch.float64)

 

6、torch.linspace

torch.linspace(start, end, steps=100, out=None) → Tensor

用法:返回start和end之间长度为steps的一维张量 参数:

参数:

  • start (float) – 点集的起始值
  • end (float) – 点集的最终值
  • steps (int) – 在start 和 end间的采样数,即返回多少个数
  • out (Tensor, 可选的) – 结果张量

例子:

x = torch.linspace(1,10,steps=5)
x
tensor([ 1.0000,  3.2500,  5.5000,  7.7500, 10.0000])

 

7、torch.logspace

torch.logspace(start, end, steps=100, out=None) → Tensor

用法:返回一个 1 维张量,包含在区间10^start和10^end上以对数刻度均匀间隔的steps个点。 输出1维张量的长度为steps

参数:

  • start (float) – 该点集的起始点
  • end (float) – 该点集的最终值
  • steps (int) – 在start 和 end间生成的样本数
  • out (Tensor, 可选) – 结果张量

例子:

x = torch.logspace(1,10,steps=5)
x
tensor([1.0000e+01, 1.7783e+03, 3.1623e+05, 5.6234e+07, 1.0000e+10])

 

8、torch.ones

torch.ones(*sizes, out=None) → Tensor

用法:返回一个全为1的张量,形状由可变参数sizes定义。

参数:

  • sizes (int...) – 整数序列,定义了输出形状,如:(5,5),(2)
  • out (Tensor, 可选) – 结果张量

例子:

x = torch.ones(5,5)
x
tensor([[1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1.]])
x = torch.ones(5)
x
tensor([1., 1., 1., 1., 1.])

 

9、torch.randn

torch.randn(*sizes, out=None) → Tensor

用法:返回一个张量,包含了从正态分布(均值为0,方差为 1,即高斯白噪声)中抽取一组随机数。 Tensor的形状由变量sizes定义。

参数:

  • sizes (int...) – 整数序列,定义了输出形状

  • `out (Tensor, 可选) - 结果张量

例子:

x = torch.randn(2)
x
tensor([ 0.1526, -0.0788])
x = torch.randn(2,2)
x
tensor([[-0.4000,  0.6303],
        [ 0.5029,  0.3646]])

 

10、torch.randperm

torch.randperm(n, out=None) → LongTensor

用法:输入参数n,返回一个从0 到n -1的随机整数排列。

参数:

  • n(int) – 上限(独占),即最大值

例子:

x = torch.randperm(10)
x
tensor([9, 2, 5, 3, 1, 0, 8, 4, 7, 6])

 

11、torch.arange

torch.arange(start, end, step=1, out=None) → Tensor

 用法:返回一个1维张量,长度为floor((end−start)/step),floor代表向下取整。包含从startend,以step为步长的一组序列值(默认步长为1)。

参数:

  • start (float) – 该点集的起始点
  • end (float) – 该点集的终止点
  • step (float) – 相邻点的间隔大小
  • out (Tensor, 可选的) – 结果张量

 

例子:

x = torch.arange(1,10,step=2)
x
tensor([1, 3, 5, 7, 9])
x = torch.arange(1, 2.5, 0.5)
x
tensor([1.0000, 1.5000, 2.0000])

 

12、torch.range

torch.range(start, end, step=1, out=None) → Tensor

用法:返回一个1维张量,长度为floor((end−start)/step)+1,其中floor代表向下取整数。从start开始,end为结尾,以step为步长的一组值。 step 是两个值之间的间隔,即 $X_i+1=X_i+step$

参数:

  • start (float) – 该点集的起始点
  • end (float) – 该点集的最终值
  • step (int) – 相邻点之间的间隔大小
  • out (Tensor, 可选的) – 结果张量

例子:

x = torch.range(1,10,step=2)
x
tensor([1., 3., 5., 7., 9.])
x = torch.range(1, 2.5, 0.5)
x
tensor([1.0000, 1.5000, 2.0000, 2.5000])

 

13、torch.zeros

torch.zeros(*sizes, out=None) → Tensor

用法:返回一个全0的张量,形状由可变参数sizes定义。

参数:

  • sizes (int...) – 整数序列,定义了输出形状
  • out (Tensor, 可选) – 结果张量

例子:

x = torch.zeros(3)
x
tensor([0., 0., 0.])
x = torch.zeros(3,2)
x
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])

索引,切片,连接,变异操作

1、torch.cat

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

用法:在给定维度上对输入的张量序列seq 进行连接操作。

参数:

  • seq(Tensors的序列) - 可以是相同类型的Tensor的任何python序列。
  • dim(int,可选) - 张量连接的尺寸
  • out(Tensor,可选) - 输出参数

例子:

x = torch.arange(0,4).view(-1,2)
print(x[0])
print(x[1])
tensor([0, 1])
tensor([2, 3])
torch.cat((x,x,x),dim=0)
tensor([[0, 1],
        [2, 3],
        [0, 1],
        [2, 3],
        [0, 1],
        [2, 3]])
torch.cat((x,x,x),dim=1)
tensor([[0, 1, 0, 1, 0, 1],
        [2, 3, 2, 3, 2, 3]])

C=torch.cat((A,B),0)就表示按维数0(行)拼接A和B,也就是竖着拼接,A上B下。

C=torch.cat((A,B),1)就表示按维数1(列)拼接A和B,也就是横着拼接,A左B右。

 

2、torch.nonzero

torch.nonzero(input, out=None) → LongTensor

用法:返回一个包含输入input中非零元素索引的张量。输出张量中的每行包含输入中非零元素的索引。

   如果输入input有n维,则输出的索引张量output的形状为 z x n, 这里 z 是输入张量input中所有非零元素的个数。

参数:

  • input (Tensor) – 源张量
  • out (LongTensor, 可选的) – 包含索引值的结果张量

例子:

torch.nonzero(torch.Tensor([1, 1, 1, 0, 1]))
tensor([[0],
        [1],
        [2],
        [4]])
torch.nonzero(torch.Tensor([[0.6, 0.0, 1.0, 0.0],
                             [0.0, 0.4, 0.0, 0.0],
                             [0.0, 0.0, 1.2, 0.0],
                             [0.0, 0.0, 0.0,-0.4]]))
tensor([[0, 0],
        [0, 2],
        [1, 1],
        [2, 2],
        [3, 3]])

3、torch.split

torch.split(tensor, split_size, dim=0)

用法:将输入张量分割成相等形状的chunks(如果可分)。 如果沿指定维的张量形状大小不能被split_size 整分, 则最后一个分块会小于其它分块。

参数:

  • tensor (Tensor) – 待分割张量
  • split_size (int) – 单个分块的形状大小
  • dim (int) – 沿着此维进行分割

例子:

x = torch.Tensor([[0.6, 0.0, 1.0, 0.0],
                 [0.0, 0.4, 0.0, 0.0],
                 [0.0, 0.0, 1.2, 0.0],
                 [0.0, 0.0, 0.0,-0.4]])
torch.split(x, 2, dim=0)
(tensor([[0.6000, 0.0000, 1.0000, 0.0000],
         [0.0000, 0.4000, 0.0000, 0.0000]]),
 tensor([[ 0.0000,  0.0000,  1.2000,  0.0000],
         [ 0.0000,  0.0000,  0.0000, -0.4000]]))
torch.split(x, 2, dim=1)
(tensor([[0.6000, 0.0000],
         [0.0000, 0.4000],
         [0.0000, 0.0000],
         [0.0000, 0.0000]]),
 tensor([[ 1.0000,  0.0000],
         [ 0.0000,  0.0000],
         [ 1.2000,  0.0000],
         [ 0.0000, -0.4000]]))

 

4、torch.squeeze

torch.squeeze(input, dim=None, out=None)

用法:将输入张量形状中的1 去除并返回。 如果输入是形如($A \\times 1\\times B \\times 1 \\times C \\times 1 \\times D$) ,那么输出形状就为:($A \\times B \\times C \\times D$) 

     当给定dim时,那么挤压操作只在给定维度上。例如,输入形状为: $(A \\times 1 \\times B) $, squeeze(input, 0) 将会保持张量不变,只有用 squeeze(input, 1),形状会变成 $(A \\times B )$。

参数:

  • input (Tensor) – 输入张量
  • dim (int, 可选的) – 如果给定,则input只会在给定维度挤压
  • out (Tensor, 可选的) – 输出张量

例子:

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])
torch.Size([2, 1, 2, 1, 2])
torch.Size([2, 1, 2, 1, 2])
y = torch.squeeze(x, 1)
y.size()
torch.Size([2, 2, 1, 2])

 

5、torch.stack

torch.stack(sequence, dim=0)

用法:沿着一个新维度对输入张量序列进行连接。 序列中所有的张量都应该为相同形状。

参数:

  • sqequence (Sequence) – 待连接的张量序列
  • dim (int) – 插入的维度。必须介于 0 与 待连接的张量序列数之间。

 

6、torch.t

torch.t(input, out=None) → Tensor

用法:输入一个矩阵(2维张量),并转置0, 1维。 可以被视为函数transpose(input, 0, 1)的简写函数。

参数:

  • input (Tensor) – 输入张量
  • out (Tensor, 可选的) – 结果张量

例子:

x = torch.randn(2, 3)
torch.t(x)
tensor([[ 0.2929,  0.1270],
        [-0.0673, -0.3026],
        [-0.4359,  0.4589]])

 

7、torch.transpose

torch.transpose(input, dim0, dim1, out=None) → Tensor

用法:返回输入矩阵input的转置。交换维度dim0dim1。 输出张量与输入张量共享内存,所以改变其中一个会导致另外一个也被修改。

参数:

  • input (Tensor) – 输入张量
  • dim0 (int) – 转置的第一维
  • dim1 (int) – 转置的第二维

例子:

x = torch.randn(2, 3)
x
tensor([[-0.0635, -0.4873,  0.1029],
        [ 0.3269,  1.8284,  0.1268]])
torch.transpose(x, 0, 1)
tensor([[-0.0635,  0.3269],
        [-0.4873,  1.8284],
        [ 0.1029,  0.1268]])

 

8、torch.unbind

torch.unbind(tensor, dim=0)[source]

用法:移除指定维后,返回一个元组,包含了沿着指定维切片后的各个切片

参数:

  • tensor (Tensor) – 输入张量
  • dim (int) – 删除的维度

 

9、torch.unsqueeze

torch.unsqueeze(input, dim, out=None)

用法:返回一个新的张量,对输入的制定位置插入维度 1

参数:

  • tensor (Tensor) – 输入张量
  • dim (int) – 插入维度的索引
  • out (Tensor, 可选的) – 结果张量

例子:

x = torch.Tensor([1, 2, 3, 4])
torch.unsqueeze(x, 0)
tensor([[1., 2., 3., 4.]])
torch.unsqueeze(x, 1)
tensor([[1.],
        [2.],
        [3.],
        [4.]])

 

随机抽样 Random sampling

1、torch.manual_seed

torch.manual_seed(seed)

用法:设定生成随机数的种子,并返回一个 _torch.C.Generator 对象.

参数:seed (int or long) – 种子.

例子:

torch.manual_seed(1)
<torch._C.Generator at 0x19749eb5890>

 

2、torch.initial_seed

torch.initial_seed()

用法:返回生成随机数的原始种子值(python long)。

例子:

torch.manual_seed(12)
torch.initial_seed()
12

 

3、torch.bernoulli

torch.bernoulli(input, out=None) → Tensor

用法:

 

  从伯努利分布中抽取二元随机数(0 或者 1)。

  输入张量须包含用于抽取上述二元随机值的概率。 因此,输入中的所有值都必须在[0,1]区间,即 $( 0<=input_i<=1 )$

  输出张量的第 $i$ 个元素值, 将会以输入张量的第 $i$ 个概率值等于1

  返回值将会是与输入相同大小的张量,每个值为 0 或者 1 参数:

参数:

  • input (Tensor) – 输入为伯努利分布的概率值
  • out (Tensor, 可选的) – 输出张量(可选)

例子:

a = torch.Tensor(3, 3).uniform_(0, 1) # generate a uniform random matrix with range [0, 1]
a
tensor([[0.4657, 0.2328, 0.4527],
        [0.5871, 0.4086, 0.1272],
        [0.6373, 0.2421, 0.7312]])
torch.bernoulli(a)
tensor([[0., 1., 0.],
        [1., 0., 0.],
        [1., 0., 0.]])
a = torch.ones(3, 3) # probability of drawing "1" is 1
torch.bernoulli(a)
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
a = torch.zeros(3, 3) # probability of drawing "1" is 0
torch.bernoulli(a)
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

 

4、torch.multinomial

torch.multinomial(input, num_samples,replacement=False, out=None) → LongTensor

用法:

 

  返回一个张量,每行包含从input相应行中定义的多项分布中抽取的num_samples个样本。

 

  当抽取样本时,依次从左到右排列(第一个样本对应第一列)。

 

  如果输入input是一个向量,输出out也是一个相同长度 num_samples 的向量。如果输入 input 是有 m 行的矩阵,输出 out 是形如 $m \\times n$ 的矩阵。

 

  如果参数 replacement 为 True, 则样本抽取可以重复。否则,一个样本在每行不能被重复抽取。

 

  参数 num_samples 必须小于 input 长度(即,input 的列数,如果是 input 是一个矩阵)。

 

参数:

 

  • input (Tensor) – 包含概率值的张量
  • num_samples (int) – 抽取的样本数
  • replacement (bool, 可选的) – 布尔值,决定是否能重复抽取
  • out (Tensor, 可选的) – 结果张量

 

例子:

weights = torch.Tensor([0, 10, 3, 0]) # create a Tensor of weights
torch.multinomial(weights, 4)
tensor([1, 2, 0, 3])
torch.multinomial(weights, 4, replacement=True)
tensor([2, 1, 1, 1])

 

5、torch.normal()

torch.normal(means, std, out=None)

用法:返回一个张量,包含从给定参数means,std的离散正态分布中抽取随机数。 均值means是一个张量,包含每个输出元素相关的正态分布的均值。 std是一个张量,包含每个输出元素相关的正态分布的标准差。 均值和标准差的形状不须匹配,但每个张量的元素个数须相同。

参数:

  • means (Tensor) – 均值
  • std (Tensor) – 标准差
  • out (Tensor) – 可选的输出张量

例子:

torch.normal(means=torch.arange(1, 11), std=torch.arange(1, 0, -0.1))

 1.5104

 1.6955

 2.4895

 4.9185

 4.9895

 6.9155

 7.3683

 8.1836

 8.7164

 9.8916

 

因上求缘,果上努力~~~~ 作者:希望每天涨粉,转载请注明原文链接:https://www.cnblogs.com/BlairGrowing/p/15430754.html

PyTorch 命名张量(Named Tensors)的介绍

请到这里去查看图文教程:http://studyai.com/pytorch-1.4/intermediate/named_tensor_tutorial.html

命名张量旨在通过允许用户将显式名称与张量维度关联,使张量更易于使用。在大多数情况下, 采用维度参数的操作将接受维度名称,从而避免了按位置跟踪维度的需要。此外,命名张量使用 名称自动检查API在运行时是否正确使用,从而提供额外的安全性。名称还可用于重新排列维度,例如, 支持“按名称广播”,而不是“按位置广播”。

本教程旨在作为1.3发布中包含的功能的指南。最后,您将能够:

创建具有命名维度的张量,并移除或重命名这些维度。
了解操作/算子(operations)如何传播维度名称的基础知识

请参见命名维度如何在两个关键领域实现更清晰的代码:
        广播操作(Broadcasting operations)
        展平和收缩维度(Flattening and unflattening dimensions)

最后,我们将通过使用named-tensors构建多头注意模块(multi-head attention module)来学习实践命名张量的这些知识点。

PyTorch中的命名张量是由Sasha Rush启发并与 Sasha Rush 合作完成的。 Sasha在他的博客 January 2019 blog post 中提出了最初的想法和概念证明。 基础: 命名维度(named dimensions)

PyTorch现在允许张量具有命名维度;工厂函数(factory functions)采用一个新的“names”参数,该参数将把每个维度与一个名称相关联。 这一方法在很多工厂函数中都可以使用:

tensor
empty
ones
zeros
randn
rand

现在我们构造一个伴有名称的张量:

import torch imgs = torch.randn(1, 2, 2, 3, names=('N', 'C', 'H', 'W')) print(imgs.names)

命名维度是这样排序的: tensor.names[i] is the name of the i th dimension of tensor.

有两种方法去重新命名 Tensor 的维度(dimensions):

方法 #1: 设置 .names 属性(attribute) (这种方法可以原位修改指定维度的名称)

imgs.names = ['batch', 'channel', 'width', 'height'] print(imgs.names)

方法 #2: 指定新的names (this changes names out-of-place)

imgs = imgs.rename(channel='C', width='W', height='H') print(imgs.names)

删除名称的首选方法是调用 tensor.rename(None) :

imgs = imgs.rename(None) print(imgs.names)

未命名张量 (tensors with no named dimensions) 仍然可以正常工作, 并且在他们的 “repr” 中也没有名称

unnamed = torch.randn(2, 1, 3) print(unnamed) print(unnamed.names)

命名张量不要求所有维度都命名。

imgs = torch.randn(3, 1, 1, 2, names=('N', None, None, None)) print(imgs.names)

因为命名张量可以与未命名张量共存,所以我们需要一种很好的方法来编写命名的张量感知代码(named tensor-aware code), 它可以同时感知命名的和未命名的张量。 使用 tensor.refine_names(*names) 来改善维度命名情况,把未命名的维度都变成命名维度(lift unnamed dims to named dims)。 改善一个维度(Refining a dimension)是指带有下列约束条件的一个重命名(“rename”)操作:

A None dim can be refined to have any name.(没有名称的维度 的 名称 可以有 任意的名称)
A named dim can only be refined to have the same name.(已经有名称的维度 的 名称 保持不变)

imgs = torch.randn(3, 1, 1, 2) named_imgs = imgs.refine_names('N', 'C', 'H', 'W') print(named_imgs.names)

Refine the last two dims to 'H' and 'W'. In Python 2, use the string '...'

instead of ...

named_imgs = imgs.refine_names(..., 'H', 'W') print(named_imgs.names)

def catch_error(fn): try: fn() assert False except RuntimeError as err: err = str(err) if len(err) > 180: err = err[:180] + "..." print(err)

named_imgs = imgs.refine_names('N', 'C', 'H', 'W')

尝试将一个 已经存在名称的维度 的 名称 修改成 别的名称

catch_error(lambda: named_imgs.refine_names('N', 'C', 'H', 'width'))

大多数简单的操作可以传播名称. 命名张量的终极目标是所有操作都可以以合理的、直观的方式传播名称(propagate names). 在1.3版本中增加了对许多常见操作的支持, 比如: .abs() :

print(named_imgs.abs().names)

访问与压缩(Accessors 和 Reduction)

你可以使用维度名称而不是位置来引用维度。 这些操作也支持名称传播. 索引当前还未实现但已经在规划实现了。 使用 named_imgs 张量, 我们可以做下列操作:

output = named_imgs.sum('C') # 沿着channel维度执行求和操作 print(output.names)

img0 = named_imgs.select('N', 0) # 获取一张图像 print(img0.names)

名称推理(name inference)

名称在两步操作之间传播的过程称之为: name inference:

检查名称: 操作算子可以在运行时执行自动检查,以检查某些维度名称是否必须匹配。
传播名称: 名称推理传播输出名称到输出张量。

我们先来体验两个很小的例子:adding 2 one-dim tensors with no broadcasting.

x = torch.randn(3, names=('X',)) y = torch.randn(3) z = torch.randn(3, names=('Z',))

检查名称: 首先, 我们将检查这两个张量的名称是否匹配,两个名称要匹配只要名称对应的字符串 相等即可,或者至少有一个是“None”(这里的 “None”可以理解为通配符式的名称)。 按照这一规则,上面的三个量的相互加法中, 只有一个会失败,即 x + z:

catch_error(lambda: x + z)

传播名称: 通过返回两个名称中最精炼的那个名称来 统一(unify) 两个名称。 在 x + y 中, X 比 None 更精炼(refine).

print((x + y).names)

大多数名称推断规则都很简单,但其中一些规则可能具有意外的语义(unexpected semantics)。 让我们看看你可能会遇到的这些场景: 广播和矩阵乘法 . 广播(Broadcasting)

命名张量不会改变广播行为本身:任然按照位置进行广播. 然而, 当检查两个维度是否可以被广播的时候, PyTorch也会同时检查这些维度的名称是否匹配。

这将导致命名张量在广播操作期间防止意外对齐(preventing unintended alignment)。 在下面的例子中,我们将 per_batch_scale 应用到 imgs.

imgs = torch.randn(2, 2, 2, 2, names=('N', 'C', 'H', 'W')) per_batch_scale = torch.rand(2, names=('N',)) catch_error(lambda: imgs * per_batch_scale)

如果没有名称(names), 张量 per_batch_scale 会被对齐到 imgs 的最后一维,这不是我们希望的。 我们实际上想要执行的操作是把 per_batch_scale 和 imgs 的 batch 维对齐。 请查看 “通过名称显式广播” 功能 来实现通过名称对齐张量操作维度。 矩阵相乘

torch.mm(A, B) 在 A 的第二维和B的第一维执行点积(product)操作, 返回张量的第一维和A的第一维相同,而其第二维和B的第二维相同。 (其他一些矩阵乘法操作, 比如torch.matmul, torch.mv, 和 torch.dot, 运算行为是类似的).

markov_states = torch.randn(128, 5, names=('batch', 'D')) transition_matrix = torch.randn(5, 5, names=('in', 'out'))

实行一次状态转移过程

new_state = markov_states @ transition_matrix print(new_state.names)

如您所见,矩阵乘法不检查缩减维度(contracted dimensions)是否具有相同的名称。

接下来,我们将介绍两个由命名张量赋予的新行为:通过名称进行显式广播 和 通过名称展平/收缩维度 新行为一: 通过名称进行显式广播(Explicit broadcasting by names)

使用多维度的一个主要抱怨是需要对 “伪(dummy)” 维度进行 “unsqueze” ,以便某些操作可以成功执行。 比如, 在我们上面的 per-batch-scale 案例中, 在张量不命名的情况下,我们需要这样做:

imgs = torch.randn(2, 2, 2, 2) # N, C, H, W per_batch_scale = torch.rand(2) # N

correct_result = imgs * per_batch_scale.view(2, 1, 1, 1) # N, C, H, W incorrect_result = imgs * per_batch_scale.expand_as(imgs) assert not torch.allclose(correct_result, incorrect_result)

通过使用命名张量,我们可以使这些操作更安全(而且在不确定维度的数量时也很容易执行操作)。 我们提供一个新的 tensor.align_as(other) 操作,

该操作可以改变张量的顺序来匹配在 other.names 中的特定顺序, adding one-sized dimensions where appropriate (tensor.align_to(names) works as well):

imgs = imgs.refine_names('N', 'C', 'H', 'W') per_batch_scale = per_batch_scale.refine_names('N')

named_result = imgs * per_batch_scale.align_as(imgs)

注意: named tensors do not yet work with allclose

assert torch.allclose(named_result.rename(None), correct_result)

新行为二: 通过名称展平/收缩维度

一个常见操作是展平/收缩维度: flattening and unflattening dimensions. 目前,用户执行这一过程使用的是 view, reshape , 或 flatten ; 常见用法包括:将批处理维度展平以将张量发送到必须接受具有特定维度数的输入的运算符中 (i.e., conv2d 接受 4D 输入).

为了使这些操作比 view, reshape更有语义意义,我们 介绍一个新的方法tensor.unflatten(dim, namedshape) 方法 并更新 flatten 使其可以在命名张量中工作: tensor.flatten(dims, new_dim).

flatten can only flatten adjacent dimensions but also works on non-contiguous dims. One must pass into unflatten a named shape, which is a list of (dim, size) tuples, to specify how to unflatten the dim. It is possible to save the sizes during a flatten for unflatten but we do not yet do that.

imgs = imgs.flatten(['C', 'H', 'W'], 'features') print(imgs.names)

imgs = imgs.unflatten('features', (('C', 2), ('H', 2), ('W', 2))) print(imgs.names)

自动微分的支持

自动微分(Autograd) 目前会忽略所有张量上的名称并将其视为常规张量进行计算。 虽然梯度的计算仍然是正确的但是却损失了张量命名带来的安全性。 对自动微分的支持也在开发路线图中。

x = torch.randn(3, names=('D',)) weight = torch.randn(3, names=('D',), requires_grad=True) loss = (x - weight).abs() grad_loss = torch.randn(3) loss.backward(grad_loss)

correct_grad = weight.grad.clone() print(correct_grad) # 现在还是未命名的. 未来的版本会实现这一点

weight.grad.zero_() grad_loss = grad_loss.refine_names('C') loss = (x - weight).abs()

理想情况下,我们会检查loss和grad_loss的名称是否匹配,但我们还没有实现这一点

loss.backward(grad_loss)

print(weight.grad) # 仍然是未命名的 assert torch.allclose(weight.grad, correct_grad)

其他一些已支持的和未支持的特色

有关1.3版本支持的内容的详细分解, 请看这儿: 。

特别是,我们要指出目前不支持的三个重要功能:

通过 torch.save 或 torch.load 保存和加载张量
通过``torch.multiprocessing`` 进行多线程处理
JIT 支持; 比如, 以下代码会出错

imgs_named = torch.randn(1, 2, 2, 3, names=('N', 'C', 'H', 'W'))

@torch.jit.script def fn(x): return x

catch_error(lambda: fn(imgs_named))

作为权宜之计, 在使用任何尚不支持命名张量的名称之前,请通过tensor=tensor.rename(None)删除名称。 一个比较长的案例: Multi-head attention

现在,我们将通过一个完整的示例来实现一个 PyTorch 的 “nn.Module”: multi-head attention. 我们假设读者已经熟悉: multi-head attention; 如果你是新手, 请看 这个解释 或 这个解释.

我们采用了来自 ParlAI 的实现:multi-head attention; 尤其是,这里的代码. 阅读该示例中的代码;然后,与下面的代码进行比较, 请注意,代码中有四个地方加了注释 (I), (II), (III), 和 (IV), 其中 使用命名张量使得代码的可读性更好; 我们将在代码块之后深入研究每一个。

import torch.nn as nn import torch.nn.functional as F import math

class MultiHeadAttention(nn.Module): def init(self, n_heads, dim, dropout=0): super(MultiHeadAttention, self).init() self.n_heads = n_heads self.dim = dim

    self.attn_dropout = nn.Dropout(p=dropout)
    self.q_lin = nn.Linear(dim, dim)
    self.k_lin = nn.Linear(dim, dim)
    self.v_lin = nn.Linear(dim, dim)
    nn.init.xavier_normal_(self.q_lin.weight)
    nn.init.xavier_normal_(self.k_lin.weight)
    nn.init.xavier_normal_(self.v_lin.weight)
    self.out_lin = nn.Linear(dim, dim)
    nn.init.xavier_normal_(self.out_lin.weight)

def forward(self, query, key=None, value=None, mask=None):
    # (I)
    query = query.refine_names(..., 'T', 'D')
    self_attn = key is None and value is None
    if self_attn:
        mask = mask.refine_names(..., 'T')
    else:
        mask = mask.refine_names(..., 'T', 'T_key')  # enc attn

    dim = query.size('D')
    assert dim == self.dim, \\
        f'Dimensions do not match: dim query vs self.dim configured'
    assert mask is not None, 'Mask is None, please specify a mask'
    n_heads = self.n_heads
    dim_per_head = dim // n_heads
    scale = math.sqrt(dim_per_head)

    # (II)
    def prepare_head(tensor):
        tensor = tensor.refine_names(..., 'T', 'D')
        return (tensor.unflatten('D', [('H', n_heads), ('D_head', dim_per_head)])
                      .align_to(..., 'H', 'T', 'D_head'))

    assert value is None
    if self_attn:
        key = value = query
    elif value is None:
        # key and value are the same, but query differs
        key = key.refine_names(..., 'T', 'D')
        value = key
    dim = key.size('D')

    # Distinguish between query_len (T) and key_len (T_key) dims.
    k = prepare_head(self.k_lin(key)).rename(T='T_key')
    v = prepare_head(self.v_lin(value)).rename(T='T_key')
    q = prepare_head(self.q_lin(query))

    dot_prod = q.div_(scale).matmul(k.align_to(..., 'D_head', 'T_key'))
    dot_prod.refine_names(..., 'H', 'T', 'T_key')  # just a check

    # (III)
    attn_mask = (mask == 0).align_as(dot_prod)
    dot_prod.masked_fill_(attn_mask, -float(1e20))

    attn_weights = self.attn_dropout(F.softmax(dot_prod / scale,
                                               dim='T_key'))

    # (IV)
    attentioned = (
        attn_weights.matmul(v).refine_names(..., 'H', 'T', 'D_head')
        .align_to(..., 'T', 'H', 'D_head')
        .flatten(['H', 'D_head'], 'D')
    )

    return self.out_lin(attentioned).refine_names(..., 'T', 'D')

(I) 改善细化(refine)输入张量的维度

def forward(self, query, key=None, value=None, mask=None): # (I) query = query.refine_names(..., 'T', 'D')

query=query.refine_names(…,'T','D') 用作可强制执行的文档[serves as enforcable documentation], 并将输入的未命名维度提升为命名维度。它检查最后两个维度是否可以细化[refine]为[‘T’,’D’], 以防止以后可能出现的无提示或混淆大小不匹配错误。

*(II) 操控 prepare_head 函数中张量的维度 *

(II)

def prepare_head(tensor): tensor = tensor.refine_names(..., 'T', 'D') return (tensor.unflatten('D', [('H', n_heads), ('D_head', dim_per_head)]) .align_to(..., 'H', 'T', 'D_head'))

首先要注意的是代码如何清楚地说明输入和输出维度:输入张量必须以 T 维度和 D 维度结束, 输出张量以 H 维度、T 维度和 D_head 维度结束。

第二件要注意的事情是代码如何清楚地描述了正在发生的事情。 prepare_head 获取key、query和value, 并将嵌入的dim拆分为多个head,最后将dim的顺序重新排列为 […,'H','T','D_head'] 。 ParlAI 实现的 prepare_head 如下所示, 使用了 view 和 transpose 操作:

def prepare_head(tensor): # input is [batch_size, seq_len, n_heads * dim_per_head] # output is [batch_size * n_heads, seq_len, dim_per_head] batch_size, seq_len, _ = tensor.size() tensor = tensor.view(batch_size, tensor.size(1), n_heads, dim_per_head) tensor = ( tensor.transpose(1, 2) .contiguous() .view(batch_size * n_heads, seq_len, dim_per_head) ) return tensor

我们的命名张量所实现的 prepare_head 函数变体使用的操作虽然更详细,但比 view 和 transpose 实现的 prepare_head 版本具有更多的语义意义,并且包含以名称形式存在的可执行文档[enforcable documentation]。

(III) 通过名称显式广播

def ignore(): # (III) attn_mask = (mask == 0).align_as(dot_prod) dot_prod.masked_fill_(attn_mask, -float(1e20))

mask 通常具有维度 [N, T] (在self attention中) 或者 [N, T, T_key] (在encoder attention中) 而 dot_prod 具有维度 [N, H, T, T_key]. To make mask broadcast correctly with dot_prod, we would usually unsqueeze dims 1 and -1 in the case of self attention or unsqueeze dim 1 in the case of encoder attention. Using named tensors, we simply align attn_mask to dot_prod using align_as and stop worrying about where to unsqueeze dims.

*(IV) 更多维度操控使用 align_to 和 flatten *

def ignore(): # (IV) attentioned = ( attn_weights.matmul(v).refine_names(..., 'H', 'T', 'D_head') .align_to(..., 'T', 'H', 'D_head') .flatten(['H', 'D_head'], 'D') )

这里, 就像在(II)中一样, align_to 和 flatten 相比 view 和 transpose 有更强的语义意义 (尽管更加冗长)。 运行该案例

n, t, d, h = 7, 5, 2 * 3, 3 query = torch.randn(n, t, d, names=('N', 'T', 'D')) mask = torch.ones(n, t, names=('N', 'T')) attn = MultiHeadAttention(h, d) output = attn(query, mask=mask)

works as expected!

print(output.names)

以上工作如期望地那样进行。此外,请注意,在代码中我们根本没有提到批处理维度(batch dimension)的名称。 事实上,我们的MultiHeadAttention 模块是不知道 批处理维度(batch dimension)的 存在的。

query = torch.randn(t, d, names=('T', 'D')) mask = torch.ones(t, names=('T',)) output = attn(query, mask=mask) print(output.names)

以上是关于Pytorch——张量 Tensors的主要内容,如果未能解决你的问题,请参考以下文章

PyTorch-20 命名张量(Named Tensors)的介绍

PyTorch-20 命名张量(Named Tensors)的介绍

PyTorch: 张量的拼接切分索引

深度之眼PyTorch训练营第二期 ---2张量操作与线性回归

Pytorch入门演练

pytorch-torch2:张量计算和连接