【问题标题】:Conv1D with kernel_size=1 vs Linear layer具有 kernel_size=1 的 Conv1D 与线性层
【发布时间】:2019-08-29 18:33:29
【问题描述】:

我正在处理非常稀疏的向量作为输入。我开始使用简单的Linear(密集/全连接层),我的网络产生了非常好的结果(让我们以准确度作为我的指标,95.8%)。

我后来尝试将Conv1dkernel_size=1MaxPool1d 一起使用,这个网络工作得稍微好一些(96.4% 的准确率)。

问题:这两种实现有何不同? Conv1d 与单元 kernel_size 不应该与 Linear 层一样吗?

我尝试了多次运行,CNN 总是产生稍微好一点的结果。

【问题讨论】:

  • 是的 Conv1d 做同样的假设实现是正确的(例如,沿右轴运行 CNN 与您需要调用 inputs.transpose(1,2) 的线性广播层),可能都归结为MaxPool1d

标签: python conv-neural-network pytorch


【解决方案1】:

nn.Conv1d 内核大小为 1 和 nn.Linear 给出基本相同的结果。唯一的区别是初始化过程和如何应用操作(这对速度有一些影响)。请注意,使用线性层应该更快,因为它是作为简单的矩阵乘法实现的(+ 添加广播的偏置向量)

@RobinFrcd 由于MaxPool1d 或由于不同的初始化过程,您的答案会有所不同。

这里有一些实验来证明我的主张:

def count_parameters(model):
    """Count the number of parameters in a model."""
    return sum([p.numel() for p in model.parameters()])

conv = torch.nn.Conv1d(8,32,1)
print(count_parameters(conv))
# 288

linear = torch.nn.Linear(8,32)
print(count_parameters(linear))
# 288

print(conv.weight.shape)
# torch.Size([32, 8, 1])
print(linear.weight.shape)
# torch.Size([32, 8])

# use same initialization
linear.weight = torch.nn.Parameter(conv.weight.squeeze(2))
linear.bias = torch.nn.Parameter(conv.bias)

tensor = torch.randn(128,256,8)
permuted_tensor = tensor.permute(0,2,1).clone().contiguous()

out_linear = linear(tensor)
print(out_linear.mean())
# tensor(0.0067, grad_fn=<MeanBackward0>)

out_conv = conv(permuted_tensor)
print(out_conv.mean())
# tensor(0.0067, grad_fn=<MeanBackward0>)

速度测试:

%%timeit
_ = linear(tensor)
# 151 µs ± 297 ns per loop

%%timeit
_ = conv(permuted_tensor)
# 1.43 ms ± 6.33 µs per loop

正如 Hanchen 的回答所示,由于数值精度,结果可能会略有不同。

【讨论】:

    【解决方案2】:

    是的,它们是不同的。我假设您使用 Pytorch API,请阅读 Pytorch 的 Conv1d。老实说,如果您将运算符视为矩阵乘积,则内核大小=1 的 Conv1d 确实会产生与线性层相同的结果。但是,需要指出的是,Conv1d 中使用的运算符是一个 2D cross-correlation operator,它衡量两个系列的相似性。我认为您的数据集受益于这种机制。

    【讨论】:

      【解决方案3】:

      在使用 PointNet (CVPR'17) 等模型处理 3d 点云时,我遇到了类似的问题。因此,我根据Yann Dubois 的回答做出了更多解释。我们首先定义了一些实用函数,然后报告我们的发现:

      import torch, timeit, torch.nn as nn, matplotlib.pyplot as plt
      
      
      def count_params(model):
          """Count the number of parameters in a module."""
          return sum([p.numel() for p in model.parameters()])
      
      
      def compare_params(linear, conv1d):
          """Compare whether two modules have identical parameters."""
          return (linear.weight.detach().numpy() == conv1d.weight.detach().numpy().squeeze()).all() and \
                 (linear.bias.detach().numpy() == conv1d.bias.detach().numpy()).all()
      
      
      def compare_tensors(out_linear, out_conv1d):
          """Compare whether two tensors are identical."""
          return (out_linear.detach().numpy() == out_conv1d.permute(0, 2, 1).detach().numpy()).all()
      
      1. 在输入和参数相同的情况下,nn.Conv1dnn.Linear 在算术上预计会产生相同的正向结果,但实验表明存在不同。我们通过绘制数值差异的直方图来展示这一点。 请注意,随着网络的深入,这个数值差异会增加。
      conv1d, linear = nn.Conv1d(8, 32, 1), nn.Linear(8, 32)
      
      # same input tensor
      tensor = torch.randn(128, 256, 8)
      permuted_tensor = tensor.permute(0, 2, 1).clone().contiguous()
      
      # same weights and bias
      linear.weight = nn.Parameter(conv1d.weight.squeeze(2))
      linear.bias = nn.Parameter(conv1d.bias)
      print(compare_params(linear, conv1d))  # True
      
      # check on the forward tensor
      out_linear = linear(tensor)  # torch.Size([128, 256, 32])
      out_conv1d = conv1d(permuted_tensor)  # torch.Size([128, 32, 256])
      print(compare_tensors(out_linear, out_conv1d))  # False
      plt.hist((out_linear.detach().numpy() - out_conv1d.permute(0, 2, 1).detach().numpy()).ravel())
      

      1. 反向传播中的梯度更新也会在数值上有所不同。
      target = torch.randn(out_linear.shape)
      permuted_target = target.permute(0, 2, 1).clone().contiguous()
      
      loss_linear = nn.MSELoss()(target, out_linear)
      loss_linear.backward()
      loss_conv1d = nn.MSELoss()(permuted_target, out_conv1d)
      loss_conv1d.backward()
      
      plt.hist((linear.weight.grad.detach().numpy() - 
          conv1d.weight.grad.permute(0, 2, 1).detach().numpy()).ravel())
      

      1. GPU 上的计算速度。 nn.Linearnn.Conv1d 快一点
      # test execution speed on CPUs
      print(timeit.timeit("_ = linear(tensor)", number=10000, setup="from __main__ import tensor, linear"))
      print(timeit.timeit("_ = conv1d(permuted_tensor)", number=10000, setup="from __main__ import conv1d, permuted_tensor"))
      
      # change everything in *.cuda(), then test speed on GPUs
      

      【讨论】:

      • 你能确认@RobinFrcd 的观察,conv1d 比线性更好吗?谢谢
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-31
      • 2020-03-12
      • 2018-12-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多