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

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

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

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

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

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

user2255757 08.04.2019 17:04
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
10
1
8 263
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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

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

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

Я сталкивался с похожими проблемами при работе с трехмерными облаками точек с такими моделями, как 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())

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.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

Вы также можете подтвердить наблюдение @RobinFrcd, что conv1d лучше, чем линейный? Спасибо

arash javanmard 11.11.2021 16:09

Другие вопросы по теме