开发者

Pytorch中的Broadcasting问题

目录
  • Numpy、Pytorch中的broadcasting
    • 写在前面
    • 问题描述
    • 尝试解决
    • BROADCASTING SEMANTICS
    • 解决问题
  • 总结

    Numpy、Pytorch中的broadcasting

    写在前面

    自己一直都不清楚numpy、pytorch里面不同维数的向量之间的element wise的计算究竟是按照什么规则来确认维数匹配和不匹配的情况的,比如

    >>> b = np.ones((4,5))
    >>> a = np.arange(5)
    >>> c = a + b
    >>> c.shape
    (4, 5)
    >>> c
    array([[1., 2., 3., 4., 5.],
       [1., 2., 3., 4., 5.],
       [1., 2., 3., 4., 5.],
       [1., 2., 3., 4., 5.]])

    上面这种情况就会自动让a和b的维数匹配,a加到了b的每一行上

    >>> b = np.ones((5,4))
    >>> a = np.arange(5)
    >>> c = a + b
    Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
    ValueError: operands could not be broadcast together with shapes (5,) (5,4)

    这种情况就无法匹配,此时我们希望的是a能自动加到b的每一android列上,但结果看来好像不行

    虽然一直存在这种疑惑,但因为平时遇到的各种运算都比较简单,遇到这种不是直接匹配的array的加法第一直觉就是去console里面试一试,报错就换编程客栈个姿势再试一试,总归问题可以快速地解决,但是最近在写模型的时候,遇到了绕不过去的问题,所以去查了文档,本文就以解决那个问题为目标,来解释清楚pytorch(numpy也是一样)中的broadcasting semantics的问题

    问题描述

    我有一个数据Tensor,维数是64 × 2048 64\times204864×2048,现在我想通过对这64 6464个2048 20482048维的向量做attention(也就是做一个加权和)来得到一个2048 20482048维的向量,因为模型的需要,我需要用五组不同的权值向量来计算出五个不同的加权结果,也就是我的计算结果应该是一个5 × 2048 5\times 20485×2048维的向量,因为在64 6464个向量上加权,所以一组权值向量是64 6464维,五组就是5 × 64 5\times 645×64维

    尝试解决

    现在我手头上有两个Tensor,一个是数据Tensor(64 × 2048 64\times 204864×2048)另一个是权值Tensor(5 × 64 5\times 645×64),我GAN!直到我写到了这里,我才发现这不是一个矩阵乘法就能解决的问题嘛+_+,当然,我想给自己正名,这里我简化了一下问题所以才发现原来这么容易就解决了,而原来我在写代码的时候因为还要考虑BATch_size等问题才云里雾里不知道咋办,还好当时没想出来,所以去查了文档发现了新的东西,然后写文章的时候想到也算是完满了(不然也不会发现自己好涝)

    以上都是题外话,现在,我们还是考虑用愚蠢的element wise的方法来解决,好在现在有两种方法可以解决问题,所以我们可以用来相互检验一下,element wise的解决方法就是,我希望这5个64维的权值向量分别和这64个2048维的向量进行element wise的乘法,也就是第一个64维权值向量先对64个2048维向量加权得到一个2048维的向量,然后第二个64维权值向量先编程对64个2048维向量加权得到一个2048维的向量…,以此类推总共五个,最终得到五个64 × 2048 64×204864×2048维的向量,然后求和得到最后的5 × 2048 5×20485×2048维的向量

    那么按照平常的javascript习惯,我就去先试试pytorch能不能直接地理解我的想法

    >>> import torch
    >>> bs = 10 # batch_size
    >>> x = torch.randn(bs,64,2048)
    >>> att = torch.randn(5,64)
    >>> out = att * x
    Traceback (most recent call last):
     File "<stdin>", line 1, in <module&g开发者_C学习t;
    RuntimeError: The size of tensor a (64) must match the size of tensor b (2048) at non-singleton dimension 2

    直接乘不行,因为维数是不匹配的,那怎样的维数才算匹配呢?

    BROADCASTING SEMANTICS

    以下内容主要来源于自官方文档

    很多pytorch的运算是支持broadcasting semantics的,而简单来说,如果运算支持broadcast,则参与运算的Tensor会自动进行扩展来使得运算符左右的Tensor维数匹配,而无需人手动地去拷贝其中的某个Tensor,这就类似于我们开头的那个例子

    >>> b = np.ones((4,5))
    >>> a = np.arange(5)
    >>> c = a + b
    >>> c.shape
    (4, 5)
    >>> c
    array([[1., 2., 3., 4., 5.],
       [1., 2., 3., 4., 5.],
       [1., 2., 3., 4., 5.],
       [1., 2., 3., 4., 5.]])

    我们无需让a的维数和b一样,因为numpy自动帮我们做了

    这里的另一个重要的概念是broadcastable,如果两个Tensor是broadcastable的,那么就可以对他俩使用支持broadcast的运算,比如直接加减乘除

    而两个向量要是broadcast的话,必须满足以下两个条件

    • 每个tensor至少是一维的
    • 两个tensor的维数从后往前,对应的位置要么是相等的,要么其中一个是1,或者不存在

    这是官方的例子解释

    >>> x=torch.empty(5,7,3)
    >>> y=torch.empty(5,7,3)
    # 相同维数的tensor一定是broadcastable的
    
    >>> x=torch.empty((0,))
    >>> y=torch.empty(2,2)
    # 不是broadcastable的,因为每个tensor维数至少要是1
    
    >>> x=torch.empty(5,3,4,1)
    >>> y=torch.empty( 3,1,1)
    # 是broadcastable的,因为从后往前看,一定要注意是从后往前看!
    # 第一个维度都是1,相等,满足第二个条件
    # 第二个维度其中有一个是1,满足第二个条件
    # 第三个维度都是3,相等,满足第二个条件
    # 第四个维度其中有一个不存在,满足第二个条件
    
    # 但是
    >>> x=torch.empty(5,2,4,1)
    >>> y=torch.empty( 3,1,1)
    # 不是broadcastable的,因为从后往前看第三个维度是不match的 2!=3,且都不是1

    如果x和y是broadcastable的,那么结果的tensor的size按照如下的规则计算

    • 如果两者的维度不一样,那么就自动增加1维(也就是unsqueeze)
    • 对于结果的每个维度,它取x和y在那一维上的最大值

    官方的例子

    >>> x=torch.empty(5,1,4,1)
    >>> y=torch.empty( 3,1,1)
    >>> (x+y).size()
    torch.Size([5, 3, 4, 1])
    
    >>> x=torch.empty(1)
    >>> y=torch.empty(3,1,7)
    >>> (x+y).size()
    torch.Size([3, 1, 7])
    
    >>> x=torch.empty(5,2,4,1)
    >>> y=torch.empty(3,1,1)
    >>> (x+y).size()
    RuntimeError: The size of tensor a (2) must match the size of tensor b (3) at non-singleton dimension 1

    此外,关于broadcast导致的就地(in-place)操作和梯度运算的兼容性等问题,可以自行参考官方文档

    解决问题

    上面我们看到,要想两个Tensor支持element wise的运算,需要它们是broadcastable的,而要想它们是broadcastable的,就需要它们的维度自后向前逐一匹配,回到我们原来的问题中,我们有两个Tensor x(64 × 2048) att(5 &thttp://www.devze.comimes; 64),为了让它们broadcastable,我们只需要

    >>> import torch
    >>> bs = 10 # batch_size
    >>> x = torch.randn(bs,64,2048)
    >>> att = torch.randn(5,64)
    >>> x = x.unsqueeze(1)
    >>> att = att.view(1,*att.shape,1)
    >>> x.shape
    torch.Size([10, 1, 64, 2048])
    >>> att.shape
    torch.Size([1, 5, 64, 1])
    >>> out = x * att
    >>> out.shape
    torch.Size([10, 5, 64, 2048])

    最后我们来验证两种方法是否结果相同

    >>> import torch
    >>> bs = 10
    >>> x = torch.randn(bs,64,2048)
    >>> att = torch.randn(5,64)
    >>> out1 = torch.matmul(att,x) # 直接矩阵相乘
    >>> out.shape
    torch.Size([10, 5, 2048])
    
    >>> x = x.unsqueeze(1)
    >>> att = att.view(1,*att.shape,1)
    >>> out2 = x * att # element wise的方法
    >>> out2 = out2.sum(dim=2)
    
    >>> test = torch.sum((out1-out2)<0.00001) # 浮点数有微小的误差
    >>> test
    tensor(102400)
    >>> out1.numel() # 最后表明两个out向量是相等的
    102400

    Reference

    [1] https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html#module-numpy.doc.broadcasting

    [2] https://pytorch.org/docs/stable/notes/broadcasting.html#broadcasting-semantics

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜