Conv1D с kernel_size = 1 против линейного уровня

#python #conv-neural-network #pytorch

#python #conv-нейронная сеть #pytorch

Вопрос:

Я работаю с очень разреженными векторами в качестве входных данных. Я начал работать с простыми Linear (плотными / полностью связанными слоями), и моя сеть дала довольно хорошие результаты (давайте возьмем точность в качестве моего показателя здесь, 95,8%).

Позже я попытался использовать Conv1d с kernel_size=1 и a MaxPool1d , и эта сеть работает немного лучше (точность 96,4%).

Вопрос: Чем отличаются эти две реализации? Разве Conv1d с модулем kernel_size не должен делать то же самое, что и Linear слой?

Я пробовал несколько запусков, CNN всегда дает немного лучшие результаты.

Комментарии:

1. Да, Conv1d делает то же самое, предполагая, что реализации верны (например, для запуска CNN вдоль правой оси по сравнению с линейным широковещательным слоем, который вам нужно вызвать inputs.transpose(1,2) ), вероятно, все сводится к MaxPool1d

Ответ №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
  

Как показывает ответ Ханчена, результаты могут очень незначительно отличаться из-за числовой точности.

Ответ №2:

Я сталкивался с подобными проблемами при работе с облаками 3d-точек с такими моделями, как PointNet (CVPR’17). Поэтому я сделал еще несколько интерпретаций, основанных на ответах 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.Conv1d и nn.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 Гистограмма прямого тензора между nn.Conv1d и nn.Линейный

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

Рис.2 Гистограмма обратного тензора между nn.Conv1d и nn.Линейный

  1. Скорость вычислений на графическом процессоре. nn.Linear немного быстрее, чем nn.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. можете ли вы также подтвердить наблюдение @RobinFrcd, что conv1d лучше, чем linear? Спасибо

2. @arashjavanmard Да, мы нашли, что это лучше.

Ответ №3:

Да, они разные. Я предполагаю, что вы используете Pytorch API, и, пожалуйста, ознакомьтесь с Conv1d Pytorch. Честно говоря, если вы берете оператор как матричное произведение, Conv1d с размером ядра = 1 генерирует те же результаты, что и линейный слой. Однако следует отметить, что оператор, используемый в Conv1d, является 2D-оператором взаимной корреляции, который измеряет сходство двух рядов. Я думаю, что ваш набор данных выигрывает от этого механизма.