resnet50转学习过程中的大量过度训练

时间:2018-05-16 07:24:33

标签: python tensorflow keras conv-neural-network resnet

这是我第一次尝试与CNN做某事,所以我可能做了一些非常愚蠢的事 - 但无法弄清楚我错在哪里......

模型似乎学得很好,但验证准确性没有提高(甚至在第一个时期之后),验证损失实际上随着时间的推移而增加。它看起来并不像我过度使用(在1个纪元之后?) - 我们必须以其他方式关闭。

typical network behaviour

我正在训练一个CNN网络 - 我有~100k各种植物的图像(1000个类),并希望微调ResNet50以创建一个muticlass分类器。图像有各种尺寸,我加载它们是这样的:

from keras.preprocessing import image                  

def path_to_tensor(img_path):
    # loads RGB image as PIL.Image.Image type
    img = image.load_img(img_path, target_size=(IMG_HEIGHT, IMG_HEIGHT))
    # convert PIL.Image.Image type to 3D tensor with shape (IMG_HEIGHT, IMG_HEIGHT, 3)
    x = image.img_to_array(img)
    # convert 3D tensor to 4D tensor with shape (1, IMG_HEIGHT, IMG_HEIGHT, 3) and return 4D tensor
    return np.expand_dims(x, axis=0)

def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in img_paths] #can use tqdm(img_paths) for data
    return np.vstack(list_of_tensors)enter code here

数据库很大(不适合内存)并且必须创建我自己的生成器以提供磁盘读取和扩充。 (我知道Keras有.flow_from_directory() - 但我的数据不是这样构建的 - 它只是一个100k图像与100k元数据文件混合的转储)。我可能应该创建一个脚本来更好地构造它们而不是创建我自己的生成器,但问题可能在其他地方。

下面的生成器版本暂时不进行任何扩充 - 只需重新缩放:

def generate_batches_from_train_folder(images_to_read, labels, batchsize = BATCH_SIZE):    

    #Generator that returns batches of images ('xs') and labels ('ys') from the train folder
    #:param string filepath: Full filepath of files to read - this needs to be a list of image files
    #:param np.array: list of all labels for the images_to_read - those need to be one-hot-encoded
    #:param int batchsize: Size of the batches that should be generated.
    #:return: (ndarray, ndarray) (xs, ys): Yields a tuple which contains a full batch of images and labels. 

    dimensions = (BATCH_SIZE, IMG_HEIGHT, IMG_HEIGHT, 3)

    train_datagen = ImageDataGenerator(
        rescale=1./255,
        #rotation_range=20,
        #zoom_range=0.2, 
        #fill_mode='nearest',
        #horizontal_flip=True
    )

    # needs to be on a infinite loop for the generator to work
    while 1:
        filesize = len(images_to_read)

        # count how many entries we have read
        n_entries = 0
        # as long as we haven't read all entries from the file: keep reading
        while n_entries < (filesize - batchsize):

            # start the next batch at index 0
            # create numpy arrays of input data (features) 
            # - this is already shaped as a tensor (output of the support function paths_to_tensor)
            xs = paths_to_tensor(images_to_read[n_entries : n_entries + batchsize])

            # and label info. Contains 1000 labels in my case for each possible plant species
            ys = labels[n_entries : n_entries + batchsize]

            # we have read one more batch from this file
            n_entries += batchsize

            #perform online augmentation on the xs and ys
            augmented_generator = train_datagen.flow(xs, ys, batch_size = batchsize)

        yield  next(augmented_generator)

这就是我定义模型的方式:

def get_model():

    # define the model
    base_net = ResNet50(input_shape=DIMENSIONS, weights='imagenet', include_top=False)

    # Freeze the layers which you don't want to train. Here I am freezing all of them
    for layer in base_net.layers:
        layer.trainable = False

    x = base_net.output

    #for resnet50
    x = Flatten()(x)
    x = Dense(512, activation="relu")(x)
    x = Dropout(0.5)(x)
    x = Dense(1000, activation='softmax', name='predictions')(x)

    model = Model(inputs=base_net.input, outputs=x)

    # compile the model 
    model.compile(
        loss='categorical_crossentropy',
        optimizer=optimizers.Adam(1e-3),
        metrics=['acc'])

    return model

因此,对于大约70k图像,我有1,562,088个可训练参数

然后我使用5倍交叉验证,但该模型不适用于任何折叠,因此我不会在此处包含完整代码,相关位是:

trial_fold = temp_model.fit_generator(
                train_generator,
                steps_per_epoch = len(X_train_path) // BATCH_SIZE,
                epochs = 50,
                verbose = 1,
                validation_data = (xs_v,ys_v),#valid_generator,
                #validation_steps= len(X_valid_path) // BATCH_SIZE,
                callbacks = callbacks,
                shuffle=True)

我做了各种各样的事情 - 确保我的发电机实际工作,试图通过减少完全连接层的大小来玩网络的最后几层,尝试增加 - 没有任何帮助......

我不认为网络中的参数数量太大 - 我知道其他人已经做了几乎相同的事情并且准确度接近0.5,但我的模型似乎过于拟合疯狂。关于如何解决这个问题的任何想法都将非常感激!

更新1:

我决定停止重新发送内容并按文件排序以使用.flow_from_directory()过程。为了确保我输入正确的格式(由下面的Ioannis Nasios评论引发) - 我确保来自keras的resnet50应用程序的preprocessing_unit()。

我还决定检查模型是否实际产生了一些有用的东西 - 我为我的数据集计算了botleneck特征,然后使用随机森林来预测类。它确实有效,我的准确度大约为0.4

所以,我猜我的图像输入格式确实存在问题。下一步,我将微调模型(使用新的顶层)以查看问题是否仍然存在......

更新2:

我认为问题出在图像预处理上。 我最终没有进行微调,只是提取了botleneck层并训练了linear_SVC() - 得到了大约60%的列车精度和大约45%的测试数据集。

6 个答案:

答案 0 :(得分:3)

我实现了各种用于迁移学习的体系结构,并发现包含BatchNorm层的模型(例如,Inception,ResNet,MobileNet)在评估(验证/测试)期间的表现比模型差很多(〜30%,而测试准确度则为> 95%)在我的自定义数据集上没有BatchNorm图层(例如VGG)。此外,在保存瓶颈功能并将其用于分类时不会发生此问题。关于此主题,已经有一些博客条目,论坛主题,问题和请求请求,事实证明,冻结时BatchNorm层不使用新数据集的统计信息,而是使用原始数据集(ImageNet)的统计信息:

假设您正在构建计算机视觉模型,但是没有足够的数据,那么您决定使用Keras的预训练CNN之一并对其进行微调。不幸的是,这样做不能保证BN层中新数据集的均值和方差与原始数据集的均值和方差相似。请记住,目前,在训练期间,您的网络将始终使用小批量统计信息,无论BN层是否冻结。同样在推断过程中,您将使用先前学习的冻结BN层统计信息。因此,如果您微调顶层,则它们的权重将被调整为新数据集的均值/方差。但是,在推理过程中,由于将使用原始数据集的均值/方差,因此他们将收到缩放比例不同的数据。

引自http://blog.datumbox.com/the-batch-normalization-layer-of-keras-is-broken/

对我而言,解决此问题的方法是冻结所有图层,然后解冻所有BatchNormalization图层,以使它们使用新数据集的统计信息而不是原始统计信息:

# build model
input_tensor = Input(shape=train_generator.image_shape)
base_model = inception_v3.InceptionV3(input_tensor=input_tensor,
                                      include_top=False,
                                      weights='imagenet',
                                      pooling='avg')
x = base_model.output

# freeze all layers in the base model
base_model.trainable = False

# un-freeze the BatchNorm layers
for layer in base_model.layers:
    if "BatchNormalization" in layer.__class__.__name__:
        layer.trainable = True

# add custom layers
x = Dense(1024, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(train_generator.num_classes, activation='softmax')(x)

# define new model
model = Model(inputs=input_tensor, outputs=x)

这也解释了在使用冻结层训练模型并使用验证/测试集对其进行评估以及保存瓶颈特征(使用model.predict内部后端标志set_learning_phase设置为{{1 }}),并在缓存的瓶颈功能上训练分类器。

更多信息在这里:

请求更改此行为(不接受):https://github.com/keras-team/keras/pull/9965

相似线程:https://datascience.stackexchange.com/questions/47966/over-fitting-in-transfer-learning-with-small-dataset/72436#72436

答案 1 :(得分:1)

我也正在处理一个非常小的数据集,虽然训练准确度越来越高,但遇到了同样的问题,即验证准确性仍然存在。我还注意到,随着时间的推移,我的验证损失也越来越高。仅供参考,我使用的是Resnet 50和InceptionV3模型。

在互联网上进行了一些挖掘后,我发现了一个关于github的讨论,该讨论将这个问题与Keras中的Batch Normalization图层的实现联系起来。在应用转移学习和微调网络时遇到上述问题。我不确定您是否遇到同样的问题,但我已将下面的链接添加到Github,您可以在其中阅读有关此问题的更多信息,并尝试应用一些测试,以帮助您了解是否受到同一问题的影响。

Github link to the pull request and discussion

答案 2 :(得分:1)

您需要在ImageDataGenerator中使用preprocessing_function参数。

if((isset($row['rdocket'])) && ($row['rdocket'] != NULL)){
echo "<td> <input pattern='.{9}' class='c-font-sm' value='$row[rdocket]' readonly></input> </td>";
}
else
{
echo "<td> <input pattern='.{9}' id='test' class='c-font-sm' value='$row[rdocket]' name='p_$row[id]'></input> </td>";
}

这将确保对所使用的经过预先训练的网络按预期对图像进行预处理。

答案 3 :(得分:1)

您是否已解决所有问题?如果不是,那么这可能是您的resnet中的批处理规范层的问题。我也遇到过类似的问题,例如在keras批处理规范层中,在培训和测试期间的行为有很大不同。因此,您可以通过以下方式冻结所有bn层:

BatchNorm()(training=False)

,然后尝试再次使用相同的数据集重新训练您的网络。还有一件事情要记住,在训练过程中,您应该将训练标志设置为

import keras.backend as K K.set_learning_phase(1)

,并在测试过程中将此标志设置为0。我认为在进行上述更改后,它应该可以工作。

如果您找到了其他解决问题的方法,请在此处发布,以便其他人可以从中受益。

谢谢。

答案 4 :(得分:0)

问题是每个类的数据集太小。 100k示例/ 1000个类=每个类约100个示例。 这个数量太少了。您的网络可以记住权重矩阵中的所有示例,但为了概括,您应该有更多示例。尝试只使用最常见的类,并弄清楚发生了什么。

答案 5 :(得分:0)

根据斯坦福大学,这里有关于微调和转学的一些解释

  1. 非常不同的数据集和来自image-net的非常少的数据集 数据集 - 尝试不同阶段的线性分类器
  2. 总结

    由于数据集非常小,您可能希望从早期图层中提取特征并在其上训练分类器并检查问题是否仍然存在。