Я работаю над очень разреженными векторами в качестве входных данных. Я начал работать с простым Linear
(плотные/полностью связанные слои), и моя сеть дала довольно хорошие результаты (давайте возьмем точность в качестве моей метрики здесь, 95,8%).
Позже я попытался использовать Conv1d
с kernel_size=1
и MaxPool1d
, и эта сеть работает немного лучше (точность 96,4%).
Вопрос: Чем отличаются эти две реализации? Разве Conv1d
с единицей kernel_size
не должен делать то же самое, что и слой Linear
?
Я пробовал несколько запусков, CNN всегда дает немного лучшие результаты.
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()
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())
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())
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 лучше, чем линейный? Спасибо
Да,
Conv1d
делает то же самое при условии, что реализации верны (например, для запуска CNN вдоль правой оси по сравнению с линейным широковещательным слоем, который вам нужно вызватьinputs.transpose(1,2)
), вероятно, все сводится кMaxPool1d