具有kernel_size = 1解释的Conv1D

时间:2019-04-08 14:58:56

标签: python conv-neural-network pytorch

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

我后来尝试将Conv1dkernel_size=1MaxPool1d结合使用,并且该网络的工作情况稍好(准确度为96.4%)。这两种实现有何不同?单位为Conv1d的{​​{1}}是否应该与线性图层相同?我尝试了多次运行,CNN总是会产生更好的结果。

4 个答案:

答案 0 :(得分:4)

在使用 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())

Fig.1 Histogram of forward tensor between nn.Conv1d and nn.Linear

  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())

Fig.2 Histogram of backward tensor between nn.Conv1d and nn.Linear

  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

答案 1 :(得分:1)

我不同意@ user2255757在评论中的回答,即应该产生相同的答案。
TL; DR :完全连接的层对单独的通道具有单独的权重,而卷积层仅共享其内核的权重。

让我解释一下:

为了这个论点,我假设我们正在处理一些中间层结果h,它是维数1 x N的向量(否则整个连接层的整体不会一开始,以及您使用Conv1d)。

从您的描述中,我还阅读到您希望有一个目标输出,可能仅限于[0,1]。相应的完全连接层将具有类似于下图中第二个隐藏层和输出层之间的连接的结构。
如您所见,这恰好包含四个连接,每个连接都有各自的权重。这意味着您可以通过矩阵乘法来表示在这一层中发生的计算,其中权重矩阵的形状为[output dimension, input dimension], i.e. in our case this would be a N x 1`“ matrix”。然后,结果值已经是正确的输出格式(1个值)。

neural net structure

另一方面,Conv1d中的卷积层由一个或多个过滤器组成。为了争辩,我们将看一下PyTorch's Conv1d
在这里,我们可以看到可以指定in_channelsout_channels,但是现在我们将忽略它们,因为在这两种情况下我们只关心单个通道。

更有趣的是后续参数:正如您在问题中已经提到的,kernel_size在示例中等于1。这意味着我们将一个尺寸为1 x 1的“盒子”(次级尺寸只是一个虚拟对象,用于与完全连接的层进行比较),然后在输入中“移动”它,再次比较下图,该图片具有内核大小2。

这是真正的区别所在!
卷积并没有为上一层的每个单独输入具有N个不同的权重,而是在所有输入之间共享一个权重。这意味着输出值实际上只是Conv1d_weight * input[i]的和。

当然,MaxPool1d会再次摆脱其他尺寸,但是只能通过选择最大值来实现,而不必与完全连接的层具有相关性。

enter image description here

答案 2 :(得分:0)

我不同意@dennlinger的回答。 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

答案 3 :(得分:0)

是的,它们是不同的。我假设您使用的是Pytorch API,请阅读Pytorch的{​​{3}}。老实说,如果将运算符作为矩阵乘积,则内核大小= 1的Conv1d确实会产生与线性层相同的结果。但是,应该指出,Conv1d中使用的运算符是2D Conv1d,用于测量两个序列的相似性。我认为您的数据集将从这种机制中受益。