要使用 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
对于相同的初始化,输出确实是相等的。
关于速度,我认为 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
关于速度,一定是因为方法1中只有一个点积可以计算结果,而方法2中只有L+其他操作。