Conv1D(filters=N, kernel_size=K) 与 Dense(output_dim=N) 层
Conv1D(filters=N, kernel_size=K) versus Dense(output_dim=N) layer
我有一个大小为 [batch_size=B, sequence_length=L, dim=K]
的输入张量 T。应用 N 个过滤器和内核大小 K 的一维卷积是否与应用输出维度为 N 的密集层相同?
例如在 Keras 中:
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 消耗,同时不重塑张量。
跟进说明:
这两个答案提供了执行一维卷积的两种不同方法。以下方法有何不同?:
方法一:
- 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]
方法二:
- 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]
我的理解是同一个操作(它们有相同的#parameters)。但是,我使用方法 1 的收敛速度更快。
Conv1D和Dense运算结果相同的情况有两种:
对于形状为(batch, length, channels)
的3D输入,这两个是相同的:
Conv1D(filters=N, kernel_size =1)
Dense(units=N)
这使用 Dense
层来模拟具有 kernel_size=1
的卷积。
Dense
无法通过这种方式实现更大的内核大小 (kernel_size > 1
)。
对于输入像 (batch, length, features)
的 Conv1D 和输入像 (batch, length * features)
这样的 Dense,这两个是相同的:
Conv1D(filters=N, kernel_size=length, padding='valid')
Dense(units=N)
这使用 Conv1D
层来模拟全连接层。
请注意,尽管两个层可能具有相同数量的参数,但卷积是完全不同的操作。如果您更改填充,您将在 Conv1D 中执行更多的乘法运算,并具有不同的输出。
关于速度,Dense
和Conv1D
是不同的算法,虽然在上面两种情况下它们的结果是一样的。不同的算法以不同的方式实现,所以不要指望完全相同的速度。
要使用 Conv1d 层实现与 Dense 层相同的行为,您需要确保 Conv1d 的任何输出神经元都连接到每个输入神经元。
对于大小为 [batch_size, L, K] 的输入,你的 Conv1d 需要有一个大小为 L 的内核和你想要的输出神经元数量的过滤器。要理解原因,让我们回到一维卷积或时间卷积的定义。
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 相同,但它们与将 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
关于速度,肯定是因为方法一只有一个点积来计算结果,而方法二有L+其他运算。
我有一个大小为 [batch_size=B, sequence_length=L, dim=K]
的输入张量 T。应用 N 个过滤器和内核大小 K 的一维卷积是否与应用输出维度为 N 的密集层相同?
例如在 Keras 中:
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 消耗,同时不重塑张量。
跟进说明:
这两个答案提供了执行一维卷积的两种不同方法。以下方法有何不同?:
方法一:
- 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]
方法二:
- 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]
我的理解是同一个操作(它们有相同的#parameters)。但是,我使用方法 1 的收敛速度更快。
Conv1D和Dense运算结果相同的情况有两种:
对于形状为
(batch, length, channels)
的3D输入,这两个是相同的:Conv1D(filters=N, kernel_size =1)
Dense(units=N)
这使用 Dense
层来模拟具有 kernel_size=1
的卷积。
Dense
无法通过这种方式实现更大的内核大小 (kernel_size > 1
)。
对于输入像
(batch, length, features)
的 Conv1D 和输入像(batch, length * features)
这样的 Dense,这两个是相同的:Conv1D(filters=N, kernel_size=length, padding='valid')
Dense(units=N)
这使用 Conv1D
层来模拟全连接层。
请注意,尽管两个层可能具有相同数量的参数,但卷积是完全不同的操作。如果您更改填充,您将在 Conv1D 中执行更多的乘法运算,并具有不同的输出。
关于速度,Dense
和Conv1D
是不同的算法,虽然在上面两种情况下它们的结果是一样的。不同的算法以不同的方式实现,所以不要指望完全相同的速度。
要使用 Conv1d 层实现与 Dense 层相同的行为,您需要确保 Conv1d 的任何输出神经元都连接到每个输入神经元。
对于大小为 [batch_size, L, K] 的输入,你的 Conv1d 需要有一个大小为 L 的内核和你想要的输出神经元数量的过滤器。要理解原因,让我们回到一维卷积或时间卷积的定义。
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 相同,但它们与将 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
关于速度,肯定是因为方法一只有一个点积来计算结果,而方法二有L+其他运算。