Conv1D (filters = N, kernel_size = K) в сравнении с плотным (output_dim = N) уровнем - PullRequest
0 голосов
/ 22 января 2019

У меня есть входной тензор T размером [batch_size=B, sequence_length=L, dim=K]. Является ли применение одномерной свертки из N фильтров и размера ядра K таким же, как применение плотного слоя с выходным размером N?

Например, в Керасе:

Conv1D(filters=N, kernel_size=K)

против

Dense(units=N)

Примечание для Conv1D, я изменяю тензор T на [batch_size*sequence_length, dim=K, 1], чтобы выполнить свертку.

Оба результата дают обучаемые веса 20,480 + 256 (смещение). Тем не менее, использование Conv1D поначалу учит меня гораздо быстрее. Я не вижу, как Dense() отличается в этом случае, и я хотел бы использовать метод Dense(), чтобы иметь более низкое потребление vram, а не изменять форму тензора.


Последующее уточнение:

Два ответа предоставили два разных способа выполнения одномерной свертки. Чем отличаются следующие методы?

Метод 1:

- Reshape input to [batch_size * frames, frame_len]
- convolve with Conv1D(filters=num_basis, kernel_size=frame_len)
- Reshape the output of the convolution layer to [batch_size, frames, num_basis]

Метод 2:

- Convolve with Conv1D(filters=num_basis, kernel_size=1) on Input=[batch_size, frames, frame_len]. No input reshaping.
- No need to reshape output, it's already [batch_size, frames, num_basis]

Насколько я понимаю, это одна и та же операция (они имеют одинаковые параметры #). Тем не менее, я получаю более быструю конвергенцию с методом 1.

Ответы [ 2 ]

0 голосов
/ 23 января 2019

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

Для ввода размера [batch_size, L, K] ваш Conv1d должен иметь ядро ​​размера L и столько фильтров, сколько вы хотите вывести нейронов.Чтобы понять почему, давайте вернемся к определению 1d свертки или временной свертки.

Параметры слоя Conv1d состоят из набора обучаемых фильтров.Каждый фильтр обычно мал по времени и распространяется на всю глубину входного объема.Например, в вашей задаче типичный фильтр может иметь размер 5xK (т. Е. 5 шагов вашей последовательности, и K, потому что ваш вход имеет глубину K).Во время прямого прохода мы скользим (точнее, сворачиваем) каждый фильтр по различным шагам последовательности входного тома и вычисляем точечные произведения между записями фильтра и входом в любой позиции.Сдвигая фильтр, мы создадим одномерную карту активации, которая дает ответы этого фильтра в каждой пространственной позиции.

Теперь, если ваши фильтры имеют размер LxK, вы можете легко увидеть, что вы будетеиметь только одну возможную пространственную позицию (поскольку фильтр имеет тот же размер, что и последовательность), который будет точечным произведением между полным входным объемом и весами LxK для каждого фильтра.Различные фильтры, составляющие ваш Conv1d, теперь ведут себя так же, как и блоки, составляющие плотный слой: они полностью подключены к вашему входу.

Это поведение можно проверить с помощью следующего кода:

import tensorflow as tf
import numpy as np

l = 10
k = 2
n = 5

x = tf.placeholder(tf.float32, [None, l, k])
c = tf.layers.conv1d(inputs=x, strides=1, filters=n, kernel_size=l, kernel_initializer=tf.ones_initializer())
d = tf.layers.dense(inputs=tf.reshape(x, [-1, l*k]), units=n, kernel_initializer=tf.ones_initializer())

batch_size = 10

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    r_conv, r_dense = sess.run([c, d], {x: np.random.normal(size=[batch_size, l, k])})

print(r_conv.shape, r_dense.shape)
#(10, 1, 5) (10, 5)

print(np.allclose(r_conv.reshape([batch_size, -1]), r_dense.reshape([batch_size, -1])))
#True

При той же инициализации выходные данные действительно равны.

Что касается скорости, я полагаю, что одна из главных причин, почему Conv1d был быстрее и потреблял больше VRAM, была из-за изменения формы: вы фактически увеличивали размер пакета, улучшая распараллеливание за счет памяти.


Редактируйте после уточнения:

Возможно, я неправильно понял ваш вопрос.Метод 1 и метод 2 одинаковы, но они не совпадают с применением плотного слоя к Input = [B, LxK].

Здесь ваши выходы подключены к полному измерению K, и затем одни и те же веса используются для каждого временного шага вашей последовательности, что означает, что оба метода только полностью связаны с кадром, но не с последовательностью.Это на самом деле эквивалентно плотному слою на [BxL, K].

Вы можете проверить это поведение с помощью следующего кода:

l = 10
k = 2
n = 5

x = tf.placeholder(tf.float32, [None, l, k])
c2 = tf.layers.conv1d(inputs=x, strides=1, filters=n, kernel_size=1, kernel_initializer=tf.ones_initializer())
c3 = tf.layers.conv1d(inputs=tf.reshape(x, [-1, k, 1]), strides=1, filters=n, kernel_size=k, kernel_initializer=tf.ones_initializer())
d2 = tf.layers.dense(inputs=tf.reshape(x, [-1, k]), units=n, kernel_initializer=tf.ones_initializer())

batch_size = 10

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    r_d2, r_c2, r_c3 = sess.run([d2, c2, c3], {x: np.random.normal(size=[batch_size, l, k])})
    r_d2 = r_d2.reshape([10, 10, 5])
    r_c3 = r_c3.reshape([10, 10, 5])

print(r_d2.shape, r_c2.shape, r_c3.shape)
#(10, 10, 5) (10, 10, 5) (10, 10, 5)

print(np.allclose(r_d2, r_c2))
#True
print(np.allclose(r_d2, r_c3))
#True
print(np.allclose(r_c2, r_c3))
#True

Что касается скорости, то это должно быть потому, что в методе 1 для вычисления результата есть только одно точечное произведение, тогда как у вас есть Lв методе 2 + другие операции.

0 голосов
/ 22 января 2019

В двух случаях операции для Conv1D и Dense имеют одинаковые результаты:

  1. Для трехмерных входов с формой (batch, length, channels) эти два значения одинаковы:

    • Conv1D(filters=N, kernel_size =1)
    • Dense(units=N)

Используется слой Dense для имитации свертки с kernel_size=1.
Dense не может достичь больших размеров ядра (kernel_size > 1).

  1. Для Conv1D с входами типа (batch, length, features) и Dense с входами типа (batch, length * features) эти два значения одинаковы:

    • Conv1D(filters=N, kernel_size=length, padding='valid')
    • Dense(units=N)

Используется слой Conv1D для имитации полностью связанного слоя.
Обратите внимание, что хотя оба уровня могут иметь одинаковое количество параметров, свертка - это совершенно разная операция. Если вы измените отступ, вы будете выполнять намного больше умножений в Conv1D, имея другой выход.

Что касается скорости, Dense и Conv1D - это разные алгоритмы, хотя их результат будет одинаковым в двух вышеупомянутых случаях. Различные алгоритмы реализованы по-разному, поэтому не ожидайте одинаковой скорости.

...