【问题标题】:Conv1D(filters=N, kernel_size=K) versus Dense(output_dim=N) layerConv1D(filters=N, kernel_size=K) 与 Dense(output_dim=N) 层
【发布时间】:2019-01-22 10:19:14
【问题描述】:

我有一个大小为 [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 的收敛速度更快。

【问题讨论】:

    标签: tensorflow keras deep-learning convolution


    【解决方案1】:

    要使用 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+其他操作。

    【讨论】:

    • 能否请您看一下我编辑的后续问题。我看不出您的方法与丹尼尔的回答所推荐的唯一方法有何不同。然而,这两者对我来说表现不同。
    • 编辑了我的答案。
    • 感谢您的详细解答!对于c3卷积,为什么会有strides=k?根据我的测试,该卷积忽略了步幅值。它是否在第一个维度 (batch_size * l) 上以 1 的步幅滑动?
    • 更改值时出错。它不会改变结果,因为只有一个可能的位置,所以确实忽略了步幅值。感谢您接听!
    【解决方案2】:

    Conv1D 和 Dense 的运算有两种结果相同的情况:

    1. 对于形状为 (batch, length, channels) 的 3D 输入,这两个是相同的:

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

    这使用Dense 层来模拟与kernel_size=1 的卷积。
    Dense 无法通过这种方式获得更大的内核大小 (kernel_size > 1)。

    1. 对于带有(batch, length, features) 等输入的Conv1D 和带有(batch, length * features) 等输入的Dense,这两个是相同的:

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

    这使用Conv1D 层来模拟全连接层。
    请注意,尽管两个层可能具有相同数量的参数,但卷积是完全不同的操作。如果您更改填充,您将在 Conv1D 中执行更多的乘法运算,并具有不同的输出。

    关于速度,DenseConv1D 是不同的算法,尽管在上述两种情况下它们的结果是相同的。不同的算法以不同的方式实现,所以不要期望完全相同的速度。

    【讨论】:

    • 您的意思是Conv1D(filters = N, kernel_size = L) 吗?如果不是,你的陈述是错误的。另外,我真的不知道“Dense 无法实现更大的内核大小”是什么意思。相反,根据定义,密集层是最大的内核,因为它连接到整个输入。
    • 密集层根本不执行卷积。我的说法不假。但我添加了第二个我以前没有意识到的案例。你可以模拟kernel size = 1,就像我在 Dense 中提到的那样。或者你可以模拟kernel_size = L,只要你理解输入的形状不同。
    • 我只是说如果你想在两个内核之间做一个并行(就像你做的那样),那么密集层将是一个卷积,因为它是连接的最大可能的内核到整个输入。此外,如果您从代码方面考虑,是的,您的陈述是正确的。但是,虽然Dense([B, L, K], N) 会运行,但它与严格意义上的 Dense/FC 层不同,因为每个输出神经元都没有连接到整个输入,而只是连接到 K 维。 “密集”层在此设置中非常稀疏...
    • 我明白了...问题是我在谈论“用密集层模拟卷积”(案例 1),而不是“用卷积模拟密集层”(案例 2) .
    猜你喜欢
    • 1970-01-01
    • 2019-08-29
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 2013-02-21
    • 1970-01-01
    • 2013-11-08
    相关资源
    最近更新 更多