首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在PyTorch中加快一维卷积

在PyTorch中加快一维卷积
EN

Stack Overflow用户
提问于 2022-03-31 16:10:08
回答 2查看 400关注 0票数 0

在我的项目中,我使用pytorch作为线性代数后端。对于我的代码的性能部分,我需要做2个小的一维卷积(长度在2到9之间)向量(一维张量)很多次。我的代码允许批量处理输入,因此我可以叠加几个输入向量来创建矩阵,然后这些矩阵可以同时进行转换。由于torch.conv1d不允许二维输入沿单一维进行卷积,所以我不得不编写自己的卷积函数,称为convolve。然而,这个新函数由一个双循环组成,因此非常慢。

问题:如何通过更好的代码设计使convolve 函数执行得更快,并让它能够处理批处理输入(=2D张量)?

部分回答:在某种程度上避免了双循环

下面是三个jupyter笔记本单元,它们重新创建了一个最小的示例。请注意,您需要line_profiler%%writefile魔术命令来完成这个工作!

代码语言:javascript
复制
%%writefile SO_CONVOLVE_QUESTION.py
import torch

def conv1d(a, v):
    padding = v.shape[-1] - 1
    return torch.conv1d(
        input=a.view(1, 1, -1), weight=v.flip(0).view(1, 1, -1), padding=padding, stride=1
    ).squeeze()

def convolve(a, v):
    if a.ndim == 1:
        a = a.view(1, -1)
        v = v.view(1, -1) 

    nrows, vcols = v.shape
    acols = a.shape[1]

    expanded = a.view((nrows, acols, 1)) * v.view((nrows, 1, vcols))
    noutdim = vcols + acols - 1
    out = torch.zeros((nrows, noutdim))
    for i in range(acols):  
        for j in range(vcols):
            out[:, i+j] += expanded[:, i, j]  
    return out.squeeze()
    
x = torch.randn(5)
y = torch.randn(7)

我将代码编写到SO_CONVOLVE_QUESTION.py,因为这对于line_profiler和用作timeit.timeit的设置是必要的。

现在,我们可以对非批处理输入(x, y)和批处理输入(x_batch, y_batch)评估上述代码的输出和性能:

代码语言:javascript
复制
from SO_CONVOLVE_QUESTION import *
# Without batch processing
res1 = conv1d(x, y)
res = convolve(x, y)
print(torch.allclose(res1, res)) # True

# With batch processing, NB first dimension!
x_batch = torch.randn(5, 5)
y_batch = torch.randn(5, 7)

results = []
for i in range(5):
    results.append(conv1d(x_batch[i, :], y_batch[i, :]))
res1 = torch.stack(results)
res = convolve(x_batch, y_batch)
print(torch.allclose(res1, res))  # True

print(timeit.timeit('convolve(x, y)', setup=setup, number=10000)) # 4.83391789999996
print(timeit.timeit('conv1d(x, y)', setup=setup, number=10000))   # 0.2799923000000035

在上面的块中,您可以看到,使用conv1d函数执行5次卷积会产生与批处理输入上的convolve相同的结果。我们还可以看到,convolve (= 4.8s)比conv1d (= 0.28s)慢得多。下面我们评估convolve函数的慢部分,而不使用line_profiler进行批处理。

代码语言:javascript
复制
%load_ext line_profiler
%lprun -f convolve convolve(x, y)  # evaluated without batch-processing!

输出:

代码语言:javascript
复制
Timer unit: 1e-07 s

Total time: 0.0010383 s
File: C:\python_projects\pysumo\SO_CONVOLVE_QUESTION.py
Function: convolve at line 9

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     9                                           def convolve(a, v):
    10         1         68.0     68.0      0.7      if a.ndim == 1:
    11         1        271.0    271.0      2.6          a = a.view(1, -1)
    12         1         44.0     44.0      0.4          v = v.view(1, -1) 
    13                                           
    14         1         28.0     28.0      0.3      nrows, vcols = v.shape
    15         1         12.0     12.0      0.1      acols = a.shape[1]
    16                                           
    17         1       4337.0   4337.0     41.8      expanded = a.view((nrows, acols, 1)) * v.view((nrows, 1, vcols))
    18         1         12.0     12.0      0.1      noutdim = vcols + acols - 1
    19         1        127.0    127.0      1.2      out = torch.zeros((nrows, noutdim))
    20         6         32.0      5.3      0.3      for i in range(acols):  
    21        40        209.0      5.2      2.0          for j in range(vcols):
    22        35       5194.0    148.4     50.0              out[:, i+j] += expanded[:, i, j]  
    23         1         49.0     49.0      0.5      return out.squeeze()

显然,双循环和创建expanded张量的线是最慢的。如果有更好的代码设计,这些部件可以避免吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-04-05 12:04:12

事实证明,有一种方法可以通过沿着维度对输入进行分组,而不使用for-循环:

代码语言:javascript
复制
out = torch.conv1d(x_batch.unsqueeze(0), y_batch.unsqueeze(1).flip(2), padding=y_batch.size(1)-1, groups=x_batch.size(0))
print(torch.allclose(out, res1))  # True
票数 0
EN

Stack Overflow用户

发布于 2022-03-31 17:17:32

Pytorch有一个名为torch.nn.functional的批处理分析工具,在那里您有一个conv1d函数(很明显,也有2d,还有更多)。我们将使用conv1d

假设您想将在v1中给出的100个向量与v2中的另一个向量进行转换。v1有(小批处理,通道,权重)的维度,默认情况下您需要一个通道。此外,v2的维度为* (\text{out_channels},(out_channels,group/ in_channels,kW)*。您使用的是一个通道,因此是一个组,因此v1v2将由以下两种方式提供:

代码语言:javascript
复制
import torch
from torch.nn import functional as F

num_vectors = 100
len_vectors = 9
v1 = torch.rand((num_vectors, 1, len_vectors))
v2 = torch.rand(1, 1, 6)

现在,我们可以简单地计算必要的填充

代码语言:javascript
复制
padding = torch.min(torch.tensor([v1.shape[-1], v2.shape[-1]])).item() - 1

而卷积可以用

代码语言:javascript
复制
conv_result = temp = F.conv1d(v1, v2, padding=padding)

我没有计时,但它应该比您最初的双for循环要快得多。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71695862

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档