如何使用Keras处理CNN中的可变大小输入?

时间:2017-09-13 18:31:45

标签: python machine-learning neural-network deep-learning keras

我正在尝试对MNIST数据库执行通常的分类,但是使用随机裁剪的数字。 图像按以下方式裁剪:随机删除第一个/最后一个和/或行/列。

我想使用使用Keras(和Tensorflow后端)的卷积神经网络来执行卷积,然后进行常规分类。

输入大小可变,我无法让它发挥作用。

以下是我如何裁剪数字

import numpy as np
from keras.utils import to_categorical
from sklearn.datasets import load_digits

digits = load_digits()

X = digits.images
X = np.expand_dims(X, axis=3)

X_crop = list()
for index in range(len(X)):
    X_crop.append(X[index, np.random.randint(0,2):np.random.randint(7,9), np.random.randint(0,2):np.random.randint(7,9), :])
X_crop = np.array(X_crop)

y = to_categorical(digits.target)

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X_crop, y, train_size=0.8, test_size=0.2)

这是我想要使用的模型的架构

from keras.layers import Dense, Dropout
from keras.layers.convolutional import Conv2D
from keras.models import Sequential

model = Sequential()

model.add(Conv2D(filters=10, 
                 kernel_size=(3,3), 
                 input_shape=(None, None, 1), 
                 data_format='channels_last'))

model.add(Dense(128, activation='relu'))
model.add(Dropout(0.2))

model.add(Dense(10, activation='softmax'))


model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])

model.summary()

model.fit(X_train, y_train, epochs=100, batch_size=16, validation_data=(X_test, y_test))
  1. 有人知道如何在我的神经网络中处理可变大小的输入吗?

  2. 如何进行分类?

2 个答案:

答案 0 :(得分:18)

TL / DR - 转到第4点

所以 - 在我们谈到这一点之前 - 让我们解决你网络的一些问题:

  1. 您的网络因激活而无效categorical_crossentropy您需要softmax激活:

    model.add(Dense(10, activation='softmax'))
    
  2. 矢量化空间张量:,正如丹尼尔所说 - 你需要在某个阶段将矢量从空间(图像)切换到矢量化(矢量)。目前 - 将Dense应用于Conv2D的输出相当于(1, 1)卷积。所以基本上 - 你的网络输出是空间的 - 没有矢量化导致维度不匹配的原因(你可以通过运行你的网络或检查model.summary()来检查。为了改变你需要使用GlobalMaxPooling2DGlobalAveragePooling2D。例如:

    model.add(Conv2D(filters=10, 
                 kernel_size=(3, 3), 
                 input_shape=(None, None, 1),
                 padding="same",
                 data_format='channels_last'))
    model.add(GlobalMaxPooling2D())
    model.add(Dense(128, activation='relu'))
    model.add(Dropout(0.2))
    
    model.add(Dense(10, activation='softmax'))
    
  3. 连续numpy数组需要具有相同的形状:如果您检查X_crop的形状,您会发现它不是空间矩阵。这是因为你连接了不同形状的矩阵。可悲的是 - 由于numpy.array需要有固定的形状,所以无法克服这个问题。

  4. 如何使用不同形状的示例进行网络训练:这样做最重要的是要了解两件事。首先 - 在一个批次中,每个图像应该具有相同的大小。第二个 - 多次调用fit是一个坏主意 - 当你重置内部模型状态时。所以这是需要做的事情:

    一个。编写一个能够裁剪单批的函数 - 例如给定矩阵的get_cropped_batches_generator从中切出一批并随机裁剪。

    湾使用train_on_batch方法。这是一个示例代码:

    from six import next
    
    batches_generator = get_cropped_batches_generator(X, batch_size=16)
    losses = list()
    for epoch_nb in range(nb_of_epochs):
        epoch_losses = list()
        for batch_nb in range(nb_of_batches):
            # cropped_x has a different shape for different batches (in general)
            cropped_x, cropped_y = next(batches_generator) 
            current_loss = model.train_on_batch(cropped_x, cropped_y)
            epoch_losses.append(current_loss)
        losses.append(epoch_losses.sum() / (1.0 * len(epoch_losses))
    final_loss = losses.sum() / (1.0 * len(losses))
    
  5. 所以 - 上面代码的一些注释:首先,train_on_batch不使用好的keras进度条。它返回一个损失值(对于给定的批处理) - 这就是我添加逻辑来计算损失的原因。您也可以使用Progbar回调。第二 - 你需要实现get_cropped_batches_generator - 我没有编写代码来更清楚地回答我的问题。你可以问另一个关于如何实现它的问题。最后一件事 - 我使用six来保持Python 2Python 3之间的兼容性。

答案 1 :(得分:3)

通常,包含Dense层的模型不能具有可变大小的输入,除非输出也是可变的。但请参阅解决方法以及使用GlobalMaxPooling2D的其他答案 - 解决方法等同于GlobalAveragePooling2D。这些层可以在Dense图层之前消除可变大小并抑制空间维度。

对于图像分类案例,您可能需要调整模型外部的图像大小。

当我的图像处于numpy格式时,我会像这样调整它们的大小:

from PIL import Image
im = Image.fromarray(imgNumpy)
im = im.resize(newSize,Image.LANCZOS) #you can use options other than LANCZOS as well
imgNumpy = np.asarray(im)

<强>为什么吗

卷积层的权重为过滤器。有一个静态过滤器大小,并且反复对图像应用相同的过滤器。

但是,密集层的权重取决于输入。如果有1个输入,则有一组权重。如果有2个输入,你的重量是两倍。但是必须训练重量,改变重量肯定会改变模型的结果。

正如@Marcin评论的那样,当密集图层的输入形状有两个维度时,我所说的是正确的:(batchSize,inputFeatures)

但实际上keras密集层可以接受更多维度的输入。这些额外的尺寸(来自卷积层)可以在尺寸上变化。但这会使这些密集层的输出大小也不同。

尽管如此,最后你需要一个固定的分类大小:10个类就是这样。为了减少尺寸,人们经常使用Flatten图层,错误将显示在此处。

一种可能的可疑方法(未经测试):

在模型的卷积部分的末尾,使用lambda层来压缩固定大小张量中的所有值,可能采用边尺寸的平均值并保持通道(通道不可变)

假设最后一个卷积层是:

model.add(Conv2D(filters,kernel_size,...))
#so its output shape is (None,None,None,filters) = (batchSize,side1,side2,filters)

让我们添加一个lambda图层来压缩空间维度并仅保留过滤器维度:

import keras.backend as K

def collapseSides(x):

    axis=1 #if you're using the channels_last format (default)   
    axis=-1 #if you're using the channels_first format

    #x has shape (batchSize, side1, side2, filters)
    step1 = K.mean(x,axis=axis) #mean of side1
    return K.mean(step1,axis=axis) #mean of side2

    #this will result in a tensor shape of (batchSize,filters)

由于过滤器的数量是固定的(你已经推出了None尺寸),所以密集层应该可以工作:

model.add(Lambda(collapseSides,output_shape=(filters,)))
model.add(Dense.......)
.....

为了使其可行,我建议最后一个卷积层中的滤波器数量至少为10.

有了这个,你可以input_shape=(None,None,1)

如果您这样做,请记住您只能传输每批固定大小的输入数据。因此,您必须以较小的批次分隔整个数据,每个批次的图像都具有相同的大小。见这里:Keras misinterprets training data shape