如何使用 Keras 在 CNN 中处理可变大小的图像?

How to work with variable-sized image in CNNs using Keras?

我目前正在使用 CNN 处理图像以使用 keras 进行特征提取。所有图像为 276 行、x 列和 3 个颜色维度 (RGB)。 列数等于它应该生成的输出特征向量的长度。

输入数据表示 - 编辑:

图像的输入数据由图像的列切片组成。这意味着图像的实际输入是 (276,3) 并且列数等于它应该生成的特征长度。

我的初始模型是这样的:

    print "Model Definition"
    model = Sequential()

    model.add(Convolution2D(64,row,1,input_shape=(row,None,3)))
    print model.output_shape
    model.add(MaxPooling2D(pool_size=(1,64)))
    print model.output_shape
    model.add(Dense(1,activation='relu'))

我在中间打印了 output.shape,我似乎对输出有点困惑。

Model Definition
(None, 1, None, 64)
(None, 1, None, 64)

3D数据怎么变成4D了?并在 maxpoolling2d 层之后保持不变?

我的密集 layer/fully-connected 层给我带来了一些尺寸方面的问题:

Traceback (most recent call last):
  File "keras_convolutional_feature_extraction.py", line 466, in <module>
    model(0,train_input_data,output_data_train,test_input_data,output_data_test)
  File "keras_convolutional_feature_extraction.py", line 440, in model
    model.add(Dense(1,activation='relu'))
  File "/usr/local/lib/python2.7/dist-packages/keras/models.py", line 324, in add
    output_tensor = layer(self.outputs[0])
  File "/usr/local/lib/python2.7/dist-packages/keras/engine/topology.py", line 474, in __call__
    self.assert_input_compatibility(x)
  File "/usr/local/lib/python2.7/dist-packages/keras/engine/topology.py", line 415, in assert_input_compatibility
    str(K.ndim(x)))
Exception: Input 0 is incompatible with layer dense_1: expected ndim=2, found ndim=4

那么,为什么我无法从 3D 图像中将数据降为 1 个单一值。 ?

您正在使用 64 个卷积滤波器对 276 x None x 3 图像进行操作,每个滤波器的大小为 276 x 1(假设 rows = 276)。一个卷积滤波器将输出一个大小为 1 x None 的矩阵。阅读 this in detail if you do not know how convolutional filters work. So for 64 filters, (in Theano backend) you will get a matrix of size 64 x 1 x None. In Tensorflow backend, I think it will be 1 x None x 64. Now, first dimension for Keras-Theano is always samples. So, your final output shape will be None x 64 x 1 x None. For Tensorflow, it will be None x 1 x None x 64. Read this 了解有关 Keras 中不同后端的更多信息。

要消除密集层错误,我认为您需要在添加 Dense 层之前通过引入以下行来展平输出。

model.add(Flatten())

不过,这里的dense layer的用法我不是很懂。您必须知道,密集层仅接受固定大小的输入并提供固定大小的输出。因此,如果您希望您的网络 运行 不抛出错误,您的 None 维度将基本上限制为单个值。如果你想得到 1 x None 形状的输出,那么你不应该包括密集层,并在最后使用 average 池化来折叠对 1 x 1 x None 输出的响应。

编辑:如果您有一个大小为 276 x n x 3 的图像,其中它的列数可变,并且您想要一个大小为 1 x n 的输出, 那么你可以如下操作:

model = Sequential()
model.add(Convolution2D(64,row,1,input_shape=(row,None,3)))
model.add(Convolution2D(1,1,1))
print model.output_shape  # this should print `None x 1 x None x 1`
model.add(flatten())

现在,我怀疑这个网络是否会表现很好,因为它只有一层 64 个过滤器。感受野也太大(例如 276 - 图像的高度)。你可以做两件事:

  1. 减少感受野,即一次只对一列的 3 个像素进行卷积,而不是一次对图像的整个列进行卷积。
  2. 有多个卷积层。

下面我假设图片高度为50,那么你可以这样写一个网络:

model = Sequential()
model.add(Convolution2D(32,3,1,activation='relu',
          init='he_normal',input_shape=(row,None,3)))  # row = 50
model.add(Convolution2D(32,3,1,activation='relu',init='he_normal'))
model.add(MaxPooling2D(pool_size=(2,1), strides=(2,1), name='pool1'))
model.add(Convolution2D(64,3,1,activation='relu',init='he_normal'))
model.add(Convolution2D(64,3,1,activation='relu',init='he_normal'))
model.add(MaxPooling2D(pool_size=(2,1), strides=(2,1), name='pool2'))
model.add(Convolution2D(128,3,1,activation='relu',init='he_normal'))
model.add(Convolution2D(128,3,1,activation='relu',init='he_normal'))
model.add(Convolution2D(128,3,1,activation='relu',init='he_normal'))
model.add(MaxPooling2D(pool_size=(2,1), strides=(2,1), name='pool3'))
model.add(Convolution2D(1,1,1), name='squash_channels')
print model.output_shape  # this should print `None x 1 x None x 1`
model.add(flatten(), name='flatten_input')

您应该验证所有这些卷积层和最大池化层在最后一次最大池化之后将输入高度从 50 降低到 1。

如何处理可变大小的图片

一种方法是首先确定数据集的通用大小,例如224.然后如上图构建224 x n图像的网络(可能更深一点)。现在假设您得到一张不同尺寸的图像,例如 p x n',其中 p > 224n' != n。您可以对尺寸为 224 x n' 的图像进行中心裁剪,并将其穿过图像。你有你的特征向量。

如果您认为大部分信息没有集中在中心周围,那么您可以进行多次裁剪,然后对获得的多个特征向量进行平均(或最大池化)。使用这些方法,我认为您应该能够处理可变大小的输入。

编辑:

查看我使用 3 x 3 卷积定义的 CNN。假设输入的大小为 50 x n x 3。假设我们将大小为 p x q x r 的输入传递给具有 f 个过滤器的卷积层,每个过滤器的大小为 3 x 3,步幅为 1。输入没有填充。然后卷积层的输出大小为(p-2) x (q-2) x f,即输出的高度和宽度将比输入的小两个。我们的池化层大小为 (2,1),步幅为 (2,1)。他们会将 y 方向的输入减半(或将图像高度减半)。牢记这一点,下面的推导很简单(观察我在 CNN 中给出的层名称,它们在下面引用)。

CNN 输入:None x 50 x n x 3

pool1层的输入:None x 46 x n x 32
pool1层的输出:None x 23 x n x 32

pool2层的输入:None x 19 x n x 64
pool2 层的输出:None x 9 x n x 64 (我认为 Keras 池需要 floor,即 floor(19/2) = 9)

pool3层的输入:None x 3 x n x 128
pool3层的输出:None x 1 x n x 128

squash_channels层的输入:None x 1 x n x 128
squash_channels层的输出:None x 1 x n x 1

flatten_input层的输入:None x 1 x n x 1
flatten_input层的输出:None x n

我想这就是你想要的。希望现在晴了。