请问有人能够清楚地解释卷积神经网络(在深度学习中)中的1D、2D和3D卷积之间的区别,并且用例子加以说明吗?
请问有人能够清楚地解释卷积神经网络(在深度学习中)中的1D、2D和3D卷积之间的区别,并且用例子加以说明吗?
本文将通过C3D的图片进行解释。
简而言之,卷积方向和输出形状非常重要!
↑↑↑↑↑ 1D卷积 - 基础版 ↑↑↑↑↑
import tensorflow as tf
import numpy as np
sess = tf.Session()
ones_1d = np.ones(5)
weight_1d = np.ones(3)
strides_1d = 1
in_1d = tf.constant(ones_1d, dtype=tf.float32)
filter_1d = tf.constant(weight_1d, dtype=tf.float32)
in_width = int(in_1d.shape[0])
filter_width = int(filter_1d.shape[0])
input_1d = tf.reshape(in_1d, [1, in_width, 1])
kernel_1d = tf.reshape(filter_1d, [filter_width, 1, 1])
output_1d = tf.squeeze(tf.nn.conv1d(input_1d, kernel_1d, strides_1d, padding='SAME'))
print sess.run(output_1d)
↑↑↑↑↑ 2D卷积基础 ↑↑↑↑↑
ones_2d = np.ones((5,5))
weight_2d = np.ones((3,3))
strides_2d = [1, 1, 1, 1]
in_2d = tf.constant(ones_2d, dtype=tf.float32)
filter_2d = tf.constant(weight_2d, dtype=tf.float32)
in_width = int(in_2d.shape[0])
in_height = int(in_2d.shape[1])
filter_width = int(filter_2d.shape[0])
filter_height = int(filter_2d.shape[1])
input_2d = tf.reshape(in_2d, [1, in_height, in_width, 1])
kernel_2d = tf.reshape(filter_2d, [filter_height, filter_width, 1, 1])
output_2d = tf.squeeze(tf.nn.conv2d(input_2d, kernel_2d, strides=strides_2d, padding='SAME'))
print sess.run(output_2d)
↑↑↑↑↑ 3D卷积 - 基础 ↑↑↑↑↑
ones_3d = np.ones((5,5,5))
weight_3d = np.ones((3,3,3))
strides_3d = [1, 1, 1, 1, 1]
in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_3d = tf.constant(weight_3d, dtype=tf.float32)
in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])
in_depth = int(in_3d.shape[2])
filter_width = int(filter_3d.shape[0])
filter_height = int(filter_3d.shape[1])
filter_depth = int(filter_3d.shape[2])
input_3d = tf.reshape(in_3d, [1, in_depth, in_height, in_width, 1])
kernel_3d = tf.reshape(filter_3d, [filter_depth, filter_height, filter_width, 1, 1])
output_3d = tf.squeeze(tf.nn.conv3d(input_3d, kernel_3d, strides=strides_3d, padding='SAME'))
print sess.run(output_3d)
↑↑↑↑↑ 使用3D输入的2D卷积 - LeNet, VGG, ..., ↑↑↑↑↑
in_channels = 32 # 3 for RGB, 32, 64, 128, ...
ones_3d = np.ones((5,5,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae with in_channels
weight_3d = np.ones((3,3,in_channels))
strides_2d = [1, 1, 1, 1]
in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_3d = tf.constant(weight_3d, dtype=tf.float32)
in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])
filter_width = int(filter_3d.shape[0])
filter_height = int(filter_3d.shape[1])
input_3d = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_3d = tf.reshape(filter_3d, [filter_height, filter_width, in_channels, 1])
output_2d = tf.squeeze(tf.nn.conv2d(input_3d, kernel_3d, strides=strides_2d, padding='SAME'))
print sess.run(output_2d)
in_channels = 32 # 3 for RGB, 32, 64, 128, ...
out_channels = 64 # 128, 256, ...
ones_3d = np.ones((5,5,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae x number of filters = 4D
weight_4d = np.ones((3,3,in_channels, out_channels))
strides_2d = [1, 1, 1, 1]
in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_4d = tf.constant(weight_4d, dtype=tf.float32)
in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])
filter_width = int(filter_4d.shape[0])
filter_height = int(filter_4d.shape[1])
input_3d = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_4d = tf.reshape(filter_4d, [filter_height, filter_width, in_channels, out_channels])
#output stacked shape is 3D = 2D x N matrix
output_3d = tf.nn.conv2d(input_3d, kernel_4d, strides=strides_2d, padding='SAME')
print sess.run(output_3d)
↑↑↑↑↑ CNN中的奖励1x1卷积 - GoogLeNet,...,↑↑↑↑↑
in_channels = 32 # 3 for RGB, 32, 64, 128, ...
out_channels = 64 # 128, 256, ...
ones_3d = np.ones((1,1,in_channels)) # input is 3d, in_channels = 32
# filter must have 3d-shpae x number of filters = 4D
weight_4d = np.ones((3,3,in_channels, out_channels))
strides_2d = [1, 1, 1, 1]
in_3d = tf.constant(ones_3d, dtype=tf.float32)
filter_4d = tf.constant(weight_4d, dtype=tf.float32)
in_width = int(in_3d.shape[0])
in_height = int(in_3d.shape[1])
filter_width = int(filter_4d.shape[0])
filter_height = int(filter_4d.shape[1])
input_3d = tf.reshape(in_3d, [1, in_height, in_width, in_channels])
kernel_4d = tf.reshape(filter_4d, [filter_height, filter_width, in_channels, out_channels])
#output stacked shape is 3D = 2D x N matrix
output_3d = tf.nn.conv2d(input_3d, kernel_4d, strides=strides_2d, padding='SAME')
print sess.run(output_3d)
in_channels = 32 # 3, 32, 64, 128, ...
out_channels = 64 # 3, 32, 64, 128, ...
ones_4d = np.ones((5,5,5,in_channels))
weight_5d = np.ones((3,3,3,in_channels,out_channels))
strides_3d = [1, 1, 1, 1, 1]
in_4d = tf.constant(ones_4d, dtype=tf.float32)
filter_5d = tf.constant(weight_5d, dtype=tf.float32)
in_width = int(in_4d.shape[0])
in_height = int(in_4d.shape[1])
in_depth = int(in_4d.shape[2])
filter_width = int(filter_5d.shape[0])
filter_height = int(filter_5d.shape[1])
filter_depth = int(filter_5d.shape[2])
input_4d = tf.reshape(in_4d, [1, in_depth, in_height, in_width, in_channels])
kernel_5d = tf.reshape(filter_5d, [filter_depth, filter_height, filter_width, in_channels, out_channels])
output_4d = tf.nn.conv3d(input_4d, kernel_5d, strides=strides_3d, padding='SAME')
print sess.run(output_4d)
sess.close()
根据@runhani的回答,我补充了一些细节,以使解释更加清晰,并尝试更详细地解释这个问题(当然包括TF1和TF2的示例)。
我要包含的主要内容如下:
tf.Variable
以下是您可能使用TF 1和TF 2进行1D卷积的方式。
具体而言,我的数据具有以下形状:
[批大小,宽度,通道数]
(例如1, 5, 1
)[宽度,通道数,输出通道数]
(例如5, 1, 4
)[批大小,宽度,输出通道数]
(例如1, 5, 4
)import tensorflow as tf
import numpy as np
inp = tf.placeholder(shape=[None, 5, 1], dtype=tf.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5, 1, 4]), dtype=tf.float32)
out = tf.nn.conv1d(inp, kernel, stride=1, padding='SAME')
with tf.Session() as sess:
tf.global_variables_initializer().run()
print(sess.run(out, feed_dict={inp: np.array([[[0],[1],[2],[3],[4]],[[5],[4],[3],[2],[1]]])}))
import tensorflow as tf
import numpy as np
inp = np.array([[[0],[1],[2],[3],[4]],[[5],[4],[3],[2],[1]]]).astype(np.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5, 1, 4]), dtype=tf.float32)
out = tf.nn.conv1d(inp, kernel, stride=1, padding='SAME')
print(out)
TF2相比其他技术,例如不需要使用Session
和variable_initializer
等,工作量更少。
让我们通过一个信号平滑处理的例子来理解它。左边是原始数据,右边是具有3个输出通道的Convolution 1D的输出结果。
多通道基本上是输入的多个特征表示。在这个例子中,你有三个通过三个不同滤波器获得的表示。第一个通道是等权重平滑滤波器。第二个是将滤波器中间的权重比边界更高的滤波器。最后一个滤波器与第二个相反。因此,您可以看到这些不同的滤波器带来不同的效果。
1D卷积已成功用于句子分类任务。
接下来是2D卷积。如果你是一个深度学习人员,你没有接触过2D卷积的几率就…嗯,大约为零。它被用于CNNs进行图像分类、目标检测等,以及涉及图像的NLP问题(例如图像字幕生成)。
让我们试一个例子,我有一个具有以下滤波器的卷积核:
而且具体来说,我的数据有以下形状:
[batch_size, height, width, 1]
(例如1, 340, 371, 1
)[height, width, in channels, out channels]
(例如3, 3, 1, 3
)[batch_size, height, width, out_channels]
(例如1, 340, 371, 3
)import tensorflow as tf
import numpy as np
from PIL import Image
im = np.array(Image.open(<some image>).convert('L'))#/255.0
kernel_init = np.array(
[
[[[-1, 1.0/9, 0]],[[-1, 1.0/9, -1]],[[-1, 1.0/9, 0]]],
[[[-1, 1.0/9, -1]],[[8, 1.0/9,5]],[[-1, 1.0/9,-1]]],
[[[-1, 1.0/9,0]],[[-1, 1.0/9,-1]],[[-1, 1.0/9, 0]]]
])
inp = tf.placeholder(shape=[None, image_height, image_width, 1], dtype=tf.float32)
kernel = tf.Variable(kernel_init, dtype=tf.float32)
out = tf.nn.conv2d(inp, kernel, strides=[1,1,1,1], padding='SAME')
with tf.Session() as sess:
tf.global_variables_initializer().run()
res = sess.run(out, feed_dict={inp: np.expand_dims(np.expand_dims(im,0),-1)})
import tensorflow as tf
import numpy as np
from PIL import Image
im = np.array(Image.open(<some image>).convert('L'))#/255.0
x = np.expand_dims(np.expand_dims(im,0),-1)
kernel_init = np.array(
[
[[[-1, 1.0/9, 0]],[[-1, 1.0/9, -1]],[[-1, 1.0/9, 0]]],
[[[-1, 1.0/9, -1]],[[8, 1.0/9,5]],[[-1, 1.0/9,-1]]],
[[[-1, 1.0/9,0]],[[-1, 1.0/9,-1]],[[-1, 1.0/9, 0]]]
])
kernel = tf.Variable(kernel_init, dtype=tf.float32)
out = tf.nn.conv2d(x, kernel, strides=[1,1,1,1], padding='SAME')
在这里,您可以看到上述代码生成的输出。第一张图片是原始图像,顺时针方向依次为第1个滤波器、第2个滤波器和第3个滤波器的输出。
在2D卷积的背景下,理解这些多个通道要容易得多。比如你正在进行人脸识别。可以将(这是一个非常不切实际的简化,但能够说明问题)每个滤波器视为眼睛、嘴巴、鼻子等。因此,每个特征映射都是图像中是否存在该特征的二进制表示。我认为对于人脸识别模型来说,这些特征非常有价值。更多信息请参见article。
这是我试图表达的内容的插图。
在深度学习领域,2D卷积非常普遍。
CNN(卷积神经网络)几乎在所有计算机视觉任务中使用2D卷积操作(例如图像分类、目标检测、视频分类)。
随着维度数量的增加,说明正在发生的事情变得越来越困难。但是,如果能够很好地理解1D和2D卷积的工作原理,则可以将这种理解推广到3D卷积。因此,接下来就是3D卷积。
具体而言,我的数据具有以下形状:
[batch size, height, width, depth, in channels]
(例如1, 200, 200, 200, 1
)[height, width, depth, in channels, out channels]
(例如5, 5, 5, 1, 3
)[batch size, width, height, width, depth, out_channels]
(例如1, 200, 200, 2000, 3
)import tensorflow as tf
import numpy as np
tf.reset_default_graph()
inp = tf.placeholder(shape=[None, 200, 200, 200, 1], dtype=tf.float32)
kernel = tf.Variable(tf.initializers.glorot_uniform()([5,5,5,1,3]), dtype=tf.float32)
out = tf.nn.conv3d(inp, kernel, strides=[1,1,1,1,1], padding='SAME')
with tf.Session() as sess:
tf.global_variables_initializer().run()
res = sess.run(out, feed_dict={inp: np.random.normal(size=(1,200,200,200,1))})
import tensorflow as tf
import numpy as np
x = np.random.normal(size=(1,200,200,200,1))
kernel = tf.Variable(tf.initializers.glorot_uniform()([5,5,5,1,3]), dtype=tf.float32)
out = tf.nn.conv3d(x, kernel, strides=[1,1,1,1,1], padding='SAME')
在开发涉及三维数据的机器学习应用中,使用了三维卷积来处理LIDAR(光探测与测距)数据。
好的,你快到了。那么我们来看看步幅和填充是什么。如果你认真思考,它们就很直观。
如果你跨越一条走廊,你会更快地到达目的地,但这也意味着你观察到的周围环境比你穿过整个房间时要少。现在让我们通过一个漂亮的图片来加强对二维卷积的理解。
tf.nn.conv2d
时,例如,你需要将它设置为一个4个元素的向量。没有理由感到害怕。它只包含以下顺序的步幅。
2D卷积 - [batch stride, height stride, width stride, channel stride]
。在这里,批次步幅和通道步幅只需设置为1(我已经实现了5年的深度学习模型,从未将它们设置为除1以外的任何值)。所以你只需要设置2个步幅。
3D卷积 - [batch stride, height stride, width stride, depth stride, channel stride]
。在这里,你只需要关注高度/宽度/深度步幅。
现在,你会注意到,无论步幅有多小(即1),在卷积过程中都会发生不可避免的尺寸缩小(例如,在卷积4个单位宽度的图像后,宽度变成了3)。这是不可取的,特别是在构建深度卷积神经网络时。这就是填充的作用。有两种最常用的填充类型。
SAME
和 VALID
下面你可以看到它们的区别。
最后一句话: 如果你非常好奇,你可能会想知道。我们刚刚对整个自动降维进行了重大突破,现在又谈论不同的步幅。但步幅的最好之处在于,您可以控制何时、何地和如何降低维度。
CNN 1D、2D 或 3D 是指卷积方向,而不是输入或过滤器维度。
对于单通道输入,当卷积内核长度等于输入长度时,CNN2D 等同于 CNN1D。(1个卷积方向)