数学背后的 1D 卷积与 TF 中的高级示例

`要手动计算 1D 卷积,可以在输入上滑动内核,计算逐元素乘法并求它们。

最简单的方法是填充= 0,stride = 1

因此,如果你的 input = [1, 0, 2, 3, 0, 1, 1]kernel = [2, 1, 3] 卷积的结果是 [8, 11, 7, 9, 4],这是通过以下方式计算的:

  • 8 = 1 * 2 + 0 * 1 + 2 * 3
  • 11 = 0 * 2 + 2 * 1 + 3 * 3
  • 7 = 2 * 2 + 3 * 1 + 0 * 3
  • 9 = 3 * 2 + 0 * 1 + 1 * 3
  • 4 = 0 * 2 + 1 * 1 + 1 * 3

TF 的 conv1d 函数分批计算卷积,所以为了在 TF 中执行此操作,我们需要以正确的格式提供数据(doc 解释输入应该在 [batch, in_width, in_channels] 中,它还解释了内核应该是什么样子)。所以

import tensorflow as tf
i = tf.constant([1, 0, 2, 3, 0, 1, 1], dtype=tf.float32, name='i')
k = tf.constant([2, 1, 3], dtype=tf.float32, name='k')

print i, '\n', k, '\n'

data   = tf.reshape(i, [1, int(i.shape[0]), 1], name='data')
kernel = tf.reshape(k, [int(k.shape[0]), 1, 1], name='kernel')

print data, '\n', kernel, '\n'

res = tf.squeeze(tf.nn.conv1d(data, kernel, 1, 'VALID'))
with tf.Session() as sess:
    print sess.run(res)

这将给你我们先前计算的相同答案:[ 8. 11. 7. 9. 4.]

用填充卷积

填充只是一种奇特的方式来告诉追加并在输入前添加一些值。在大多数情况下,此值为 0,这就是为什么大多数人将其命名为零填充。TF 支持’VALID’和’SAME’零填充,对于任意填充,你需要使用 tf.pad() 。 ‘VALID’填充意味着根本没有填充,这意味着输出将具有相同的输入大小。让我们在同一个例子中用 padding=1 计算卷积(请注意,对于我们的内核,这是’SAME’填充)。要做到这一点,我们只需在开头/结尾添加 1 个零的数组:input = [0, 1, 0, 2, 3, 0, 1, 1, 0]

在这里你可以注意到你不需要重新计算所有内容:除了第一个/最后一个元素之外,所有元素保持不变:

  • 1 = 0 * 2 + 1 * 1 + 0 * 3
  • 3 = 1 * 2 + 1 * 1 + 0 * 3

结果是 [1, 8, 11, 7, 9, 4, 3] 与 TF 计算的结果相同:

res = tf.squeeze(tf.nn.conv1d(data, kernel, 1, 'SAME'))
with tf.Session() as sess:
    print sess.run(res)

卷入大步

Strides 允许你在滑动时跳过元素。在我们之前的所有示例中,我们滑动了 1 个元素,现在你可以一次滑动 s 元素。因为我们将使用前面的例子,所以有一个技巧:通过 n 元素滑动相当于滑动 1 个元素并选择每个第 n 个元素。

因此,如果我们将前一个示例与 padding=1 一起使用并将 stride 更改为 2,则只需取前一个结果 [1, 8, 11, 7, 9, 4, 3] 并保留每个 2-nd 元素,这将导致 [1, 11, 9, 3]。你可以通过以下方式在 TF 中执行此操作:

res = tf.squeeze(tf.nn.conv1d(data, kernel, 2, 'SAME'))
with tf.Session() as sess:
    print sess.run(res)