Conv1D с kernel_size = 1 интерпретация - PullRequest
0 голосов
/ 08 апреля 2019

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

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

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

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

Ответы [ 2 ]

1 голос
/ 09 апреля 2019

Я не согласен с ответом @ 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` «матрицы».Результирующее значение тогда уже находится в правильном формате вывода (1 значение).

neural net structure

С другой стороны, сверточный слой в Conv1d состоит изодной руды больше фильтры .Ради аргумента мы рассмотрим PyTorch Conv1d.Здесь мы видим, что мы можем указать in_channels и out_channels, но пока будем их игнорировать, поскольку в обоих случаях нам важен только один канал.

Более интересными являютсяпоследующие параметры: Как вы уже упоминали в своем вопросе, kernel_size будет равно 1 для вашего примера.Это означает, что мы берем «коробку» размером 1 x 1 (вторичное измерение - просто пустышка для сравнения с полностью подключенным слоем), а затем «сдвигаем» это по входу, снова сравниваем изображение ниже, которое имеет размер ядра2.

Вот здесь и происходит реальная разница! Вместо N различных весов для каждого отдельного входа предыдущего слоя, свертка делит один вес между всеми входами.Это означает, что выходное значение на самом деле является просто суммой Conv1d_weight * input[i].

. Конечно, MaxPool1d снова избавится от дополнительных измерений, но это происходит только путем выбора максимального значения, которое необязательно соотносится с тем, что имеет полностью связанный слой.

enter image description here

0 голосов
/ 20 июня 2019

Я не согласен с ответом @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
...