Conv1D(filters=N, kernel_size=K) 与 Dense(output_dim=N) 层

Art*_*ash 4 convolution deep-learning keras tensorflow

我有一个大小为 T 的输入张量[batch_size=B, sequence_length=L, dim=K]。应用 N 个滤波器和内核大小 K 的一维卷积与应用输出维度为 N 的密集层相同吗?

例如在 Keras 中:

Conv1D(filters=N, kernel_size=K)
Run Code Online (Sandbox Code Playgroud)

对比

Dense(units=N)
Run Code Online (Sandbox Code Playgroud)

请注意Conv1D,我将张量 T 整形[batch_size*sequence_length, dim=K, 1]以执行卷积。

两者都导致可学习的权重为 20,480 + 256(偏差)。然而,Conv1D最初对我来说使用学习速度要快得多。我看不出Dense()在这种情况下有什么不同,我想使用Dense()方法来降低 vram 消耗以及不重塑张量。


后续澄清:

这两个答案提供了两种不同的方式来执行一维卷积。以下方法有何不同?:

方法一:

- 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]
Run Code Online (Sandbox Code Playgroud)

方法二:

- 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]
Run Code Online (Sandbox Code Playgroud)

我的理解是它是相同的操作(它们具有相同的#parameters)。但是,我使用方法 1 的收敛速度越来越快。

Oli*_*ene 7

要使用 Conv1d 层实现与 Dense 层相同的行为,您需要确保来自 Conv1d 的任何输出神经元都连接到每个输入神经元。

对于大小为 [batch_size, L, K] 的输入,您的 Conv1d 需要有一个大小为 L 的内核和尽可能多的过滤器输出神经元。为了理解原因,让我们回到一维卷积或时间卷积的定义。

Conv1d 层的参数由一组可学习的过滤器组成。每个过滤器在时间上通常都很小,并延伸到输入体积的整个深度。例如,在您的问题中,典型过滤器的大小可能为 5xK(即序列的 5 个步骤,而 K 因为您的输入具有深度 K)。在前向传递期间,我们在输入卷序列的不同步骤中滑动(更准确地说,卷积)每个过滤器,并计算过滤器条目和任何位置输入之间的点积。当我们滑动过滤器时,我们将产生一个一维激活图,它给出了过滤器在每个空间位置的响应。

现在,如果您的过滤器的大小为 LxK,您可以很容易地看到,您将只有一个可能的空间位置(因为过滤器与序列的大小相同),这将是完整输入体积和权重 LxK 之间的点积对于每个过滤器。现在,构成 Conv1d 的不同过滤器的行为与构成 Dense 层的单元相同:它们完全连接到您的输入。

您可以使用以下代码验证此行为:

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
Run Code Online (Sandbox Code Playgroud)

对于相同的初始化,输出确实相等。

关于速度,我认为 Conv1d 更快并占用更多 VRAM 的主要原因之一是因为您的重塑:您实际上增加了批量大小,以内存为代价提高了并行化。


跟进澄清后编辑:

也许我误解了你的问题。方法 1 和方法 2 相同,但它们与将 Dense 层应用于 Input=[B, LxK] 不同。

在这里,您的输出连接到全维 K,然后对序列的每个时间步使用相同的权重,这意味着这两种方法都只完全连接到帧而不是序列。这实际上相当于 [BxL, K] 上的 Dense 层。

您可以使用以下代码验证此行为:

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
Run Code Online (Sandbox Code Playgroud)

关于速度,一定是因为方法1中只有一个点积来计算结果,而方法2中的L +其他操作。