您当前的位置:首页 > IT编程 > 数据集
| C语言 | Java | VB | VC | python | Android | TensorFlow | C++ | oracle | 学术与代码 | cnn卷积神经网络 | gnn | 图像修复 | Keras | 数据集 | Neo4j | 自然语言处理 | 深度学习 | 医学CAD | 医学影像 | 超参数 | pointnet | pytorch |

小样本数据集图像分类模型的方法

51自学网 2020-12-08 10:04:49
  数据集

用小样本数据集构建强大的图像分类模型

 

在本教程中,我们将介绍一些简单而有效的方法,可以使用这些方法构建一个功能强大的图像分类器,只使用很少的训练数据 —— 每类几百或几千张图片。

 

将介绍以下内容:

  • 从头开始训练小型网络(作为基线)
  • 使用预先训练的网络的瓶颈功能
  • 微调预训练网络的top layers

 

我们将使用到以下Keras的features:

  • fit_generator 使用Python数据生成器,训练Keras模型
  • ImageDataGenerator 用于实时数据增强
  • 层冻结(layer freezing)和模型fine-tuning

 

注意:需要Keras 2.0.0或更高版本方可运行。


开工:2000个训练样本(每类1000个)

我们将从以下设置开始:

  • 安装了Keras,SciPy,PIL的电脑(当然如果有Nvidia 的GPU最好了)。
  • 训练数据集和验证数据集,目录如下:
data/ 
    train/ 
        dog/ 
            dog001.jpg 
            dog002.jpg 
            ... 
        cats / 
            cat001.jpg 
            cat002.jpg 
            ... 
    validation / 
        dogs / 
            dog001.jpg 
            dog002.jpg 
            ... 
        cats / 
            cat001.jpg 
            cat002.jpg 
            ...

 

本实验中,我们从Kaggle获得1000只猫和1000只狗的图像(原始数据集有12,500只猫和12,500只狗,各取了前1000张)作为训练集,额外的400张作为验证集。

对于深度学习来说,这是一个很小的数据集。用小样本训练深度学习模型是一个非常有挑战性的问题,但它也是一个现实的问题:在许多现实世界的使用案例中,即使是小规模的数据收集也可能非常昂贵或有时几乎不可能(例如在医学成像中)。能够充分利用非常少的数据是有能力的数据科学家的关键技能。

 

 

这个问题有多难?两年前的Kaggle猫狗比赛(共计25,000张训练图像),有以下声明:

“在多年前进行的非正式民意调查中,计算机视觉专家认为,如果没有现有技术的重大进步,精度高于60%的分类器将很难实现。目前的文献表明,机器分类器在此任务上的准确度可以达到80%以上[参考]。“

在最终的比赛中,顶级参赛者通过使用现代深度学习技术获得了超过98%的准确率。在我们的例子中,因为我们仅将自己限制在数据集的8%,所以问题要困难得多。

深度学习与小数据

我们经常听到的一条信息是“深度学习只有在拥有大量数据时才有意义”。当然,深度学习需要能够从数据中自动学习特征,这通常只有在有大量训练数据可用时才有可能 ,特别是对于输入样本非常高维的问题,如图像。然而,卷积神经网络 —— 深度学习的支柱算法 —— 是大多数“感知”问题(例如图像分类)的最佳模型之一,即使只有很少的训练数据,依然可以训练一个不错的模型,而不需要任何自定义特征工程。

更重要的是,深度学习模型本质上是高度可再利用的:例如,您可以采用在大规模数据集上训练的图像分类或语音到文本模型,然后在一个不同的任务重复使用它,只需进行微小的更改,如我们将在这篇文章中看到。特别是在计算机视觉的任务中,许多预先训练的模型(通常在ImageNet数据集上训练)现在可以公开下载,并且可以用于从非常少的数据中推导强大的视觉模型。


数据预处理和数据增强

为了充分利用训练样本,我们将通过一系列随机变换来“扩充”它们,这样模型就不会看到完全相同的两次图像。这有助于防止过拟合,并增强模型的泛化性能。

在Keras中可以通过keras.preprocessing.image.ImageDataGenerator来完成。这个类允许我们:

  • 配置训练过程中图像的各种变换和归一化操作
  • 通过调用.flow(data, labels)或者.flow_from_directory(directory)方法,返回上述操作的图像batch生成器。这个生成器可以和Keras的模型方法(fit_generator,evaluate_generator和predict_generator)一起使用,作为他们的输入。

我们马上看一个例子:

from keras.preprocessing.image  import ImageDataGenerator 

datagen  =  ImageDataGenerator (
        rotation_range = 40 ,
        width_shift_range = 0.2 ,
        height_shift_range = 0.2 ,
        rescale= 1 / 255 ,
        shear_range = 0.2 ,
        zoom_range = 0.2 ,
        horizontal_flip = True,
        fill_mode = 'nearest' )

这些只是一些可用选项(更多信息,请参阅文档)。让我们快速回顾一下我们刚写的内容:

  • rotation_range 是一个度数(0-180)的值,表示随机旋转图片的范围
  • width_shift和height_shift是在垂直或水平方向上随机平移图片的范围(作为总宽度或高度的一部分)
  • rescale是一个值,对图像的亮度值进行缩放,这个操作在所有操作之前,例如rescale为1/255时,表示把RGB的值从0-255转换到0-1之间。
  • shear_range用于随机应用剪切变换
  • zoom_range 用于随机缩放
  • horizontal_flip 50%的随机概率对图像进行水平翻转
  • fill_mode 像素填充策略,在进行旋转或平移之后,需要对图像像素进行填充。

现在让我们开始使用这个工具生成一些图片并将它们保存到临时目录中,这样我们就可以了解我们的增强策略正在做什么 —— 禁用rescale以保持图像可显示:

from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img

datagen = ImageDataGenerator(
        rotation_range=40,
        width_shift_range=0.2,
        height_shift_range=0.2,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True,
        fill_mode='nearest')

img = load_img('data/train/cats/cat.0.jpg')  # this is a PIL image
x = img_to_array(img)  # this is a Numpy array with shape (3, 150, 150)
x = x.reshape((1,) + x.shape)  # this is a Numpy array with shape (1, 3, 150, 150)

# the .flow() command below generates batches of randomly transformed images
# and saves the results to the `preview/` directory
i = 0
for batch in datagen.flow(x, batch_size=1,
                          save_to_dir='preview', save_prefix='cat', save_format='jpeg'):
    i += 1
    if i > 20:
        break  # otherwise the generator would loop indefinitely

下图是我们的数据增强策略的样子。

 

 


从头开始训练一个小卷积神经网络:40行代码,准确率达到80%

卷积神经网络是图像分类任务的不二选择,所以让我们从训练一个小网络开始,作为初始基线。由于只有少量训练数据,我们的头号问题应该是过拟合。当暴露于太少示例的模型学习不能推广到新数据的模式时,即当模型开始使用不相关的特征进行预测时,就会发生过度拟合。例如,如果你作为一个人,只能看到三个伐木工人的图像,三个是水手人的图像,其中只有一个伐木工人戴着帽子,你可能会开始认为戴帽子是一个作为一名伐木工人的标志,而不是一名水手。然后你会做一个非常糟糕的伐木工/水手分类器。

数据增加是对抗过度拟合的一种方法,但这还不够,因为我们的增强样本仍然是高度相关的。过度拟合的主要焦点应该是模型的熵能力 - 我们的模型可以存储多少信息。通过利用更多功能,可以存储大量信息的模型可能更加准确,但开始存储不相关的功能也存在风险。同时,只能存储一些功能的模型必须关注数据中发现的最重要的功能,这些功能更有可能真正相关并更好地推广。

调制熵容量有不同的方法。主要的是选择模型中的参数数量,即层数和每层的大小。另一种方法是使用权重正则化,例如L1或L2正则化,其包括迫使模型权重接受较小的值。

在我们的例子中,我们将使用一个非常小的convnet,每层有少量层和少量过滤器,以及数据增加和dropout。Dropout还有助于减少过度拟合,防止图层看到完全相同模式的两倍,从而以类似于数据增强的方式运行(您可以说丢失和数据增加都会破坏数据中出现的随机关联)。

下面的代码片段是我们的第一个模型,一个包含3个卷积层的简单堆栈,其中包含ReLU激活,然后是最大池化层。这与Yann LeCun在20世纪90年代提倡的用于图像分类的架构(ReLU除外)非常相似。

可以在此处找到此实验的完整代码。

from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(3, 150, 150)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

# the model so far outputs 3D feature maps (height, width, features)

在它的顶部,我们粘贴两个完全连接的层。我们用一个单元和一个sigmoid激活结束模型,这对于二进制分类是完美的。为此,我们还将使用binary_crossentropy损失来训练我们的模型。

model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(1))
model.add(Activation('sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])

让我们准备我们的数据。我们将.flow_from_directory()直接从各自文件夹中的jpgs生成批量图像数据(及其标签)。

batch_size = 16

# this is the augmentation configuration we will use for training
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

# this is the augmentation configuration we will use for testing:
# only rescaling
test_datagen = ImageDataGenerator(rescale=1./255)

# this is a generator that will read pictures found in
# subfolers of 'data/train', and indefinitely generate
# batches of augmented image data
train_generator = train_datagen.flow_from_directory(
        'data/train',  # this is the target directory
        target_size=(150, 150),  # all images will be resized to 150x150
        batch_size=batch_size,
        class_mode='binary')  # since we use binary_crossentropy loss, we need binary labels

# this is a similar generator, for validation data
validation_generator = test_datagen.flow_from_directory(
        'data/validation',
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode='binary')

我们现在可以使用这些发电机来训练我们的模型。每个纪元在GPU上需要20-30秒,在CPU上需要300-400秒。因此,如果您不赶时间,在CPU上运行此模型绝对可行。

model.fit_generator(
        train_generator,
        steps_per_epoch=2000 // batch_size,
        epochs=50,
        validation_data=validation_generator,
        validation_steps=800 // batch_size)
model.save_weights('first_try.h5')  # always save your weights after training or during training

这种方法使我们在50个epoch之后达到0.79-0.81的验证准确度(这个数字是任意选择的 - 因为模型很小并且使用了非常激进的dropout,到那时它似乎不会过度拟合)。因此,在推出Kaggle比赛时,我们已经成为“最先进的” - 拥有8%的数据,并且没有努力优化我们的架构或超参数。事实上,在Kaggle比赛中,这个模型将进入前100名(215名参赛者中)。我想至少有115名参赛者没有使用深度学习;)

请注意,验证准确度的方差相当高,因为准确度是一个高方差度量,因为我们只使用800个验证样本。在这种情况下,一个很好的验证策略是进行k折交叉验证,但这需要在每轮评估中训练k个模型。


使用预先训练的网络的瓶颈功能:一分钟内准确率达到90%

更精确的方法是利用在大型数据集上预先训练的网络。这样的网络已经学习了对大多数计算机视觉问题有用的特征,并且利用这些特征将使我们能够比仅依赖于可用数据的任何方法获得更好的准确性。

我们将使用VGG16架构,该架构在ImageNet数据集上进行了预训练 - 这是此博客之前的模型。由于ImageNet数据集在其总共1000个类中包含多个“猫”类(波斯猫,暹罗猫......)和许多“狗”类,因此该模型已经学习了与我们的分类问题相关的特征。实际上,仅仅记录模型的softmax预测而不是瓶颈特征就足以解决我们的狗与猫的分类问题。然而,我们在这里提出的方法更有可能很好地推广到更广泛的问题,包括ImageNet中缺少类的问题。

这就是VGG16架构的样子:

 

 

我们的策略如下:我们只会实例化模型的卷积部分,直到完全连接的层。然后,我们将在训练和验证数据上运行此模型一次,在两个numpy阵列中记录输出(来自VGG16模型的“瓶颈特征”:完全连接的层之前的最后一个激活映射)。然后,我们将在存储的功能之上训练一个小的完全连接模型。

我们之所以离线存储这些功能而不是直接在冻结的卷积基础上添加我们的完全连接模型并运行整个功能,是因为计算效率。运行VGG16很昂贵,特别是如果你正在使用CPU,我们只想做一次。请注意,这会阻止我们使用数据扩充。

您可以在此处找到此实验的完整代码。你可以从Github获得权重文件。我们不会检查模型的构建和加载方式 - 这已经在多个Keras示例中介绍过了。但是让我们来看看如何使用图像数据生成器记录瓶颈特征:

batch_size = 16

generator = datagen.flow_from_directory(
        'data/train',
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode=None,  # this means our generator will only yield batches of data, no labels
        shuffle=False)  # our data will be in order, so all first 1000 images will be cats, then 1000 dogs
# the predict_generator method returns the output of a model, given
# a generator that yields batches of numpy data
bottleneck_features_train = model.predict_generator(generator, 2000)
# save the output as a Numpy array
np.save(open('bottleneck_features_train.npy', 'w'), bottleneck_features_train)

generator = datagen.flow_from_directory(
        'data/validation',
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode=None,
        shuffle=False)
bottleneck_features_validation = model.predict_generator(generator, 800)
np.save(open('bottleneck_features_validation.npy', 'w'), bottleneck_features_validation)

然后我们可以加载我们保存的数据并训练一个小的完全连接的模型:

train_data = np.load(open('bottleneck_features_train.npy'))
# the features were saved in order, so recreating the labels is easy
train_labels = np.array([0] * 1000 + [1] * 1000)

validation_data = np.load(open('bottleneck_features_validation.npy'))
validation_labels = np.array([0] * 400 + [1] * 400)

model = Sequential()
model.add(Flatten(input_shape=train_data.shape[1:]))
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model.fit(train_data, train_labels,
          epochs=50,
          batch_size=batch_size,
          validation_data=(validation_data, validation_labels))
model.save_weights('bottleneck_fc_model.h5')

由于它的体积小,这种型号甚至可以在CPU(每个epoch 1秒)上快速训练:

Train on 2000 samples, validate on 800 samples
Epoch 1/50
2000/2000 [==============================] - 1s - loss: 0.8932 - acc: 0.7345 - val_loss: 0.2664 - val_acc: 0.8862
Epoch 2/50
2000/2000 [==============================] - 1s - loss: 0.3556 - acc: 0.8460 - val_loss: 0.4704 - val_acc: 0.7725
...
Epoch 47/50
2000/2000 [==============================] - 1s - loss: 0.0063 - acc: 0.9990 - val_loss: 0.8230 - val_acc: 0.9125
Epoch 48/50
2000/2000 [==============================] - 1s - loss: 0.0144 - acc: 0.9960 - val_loss: 0.8204 - val_acc: 0.9075
Epoch 49/50
2000/2000 [==============================] - 1s - loss: 0.0102 - acc: 0.9960 - val_loss: 0.8334 - val_acc: 0.9038
Epoch 50/50
2000/2000 [==============================] - 1s - loss: 0.0040 - acc: 0.9985 - val_loss: 0.8556 - val_acc: 0.9075

我们达到0.90-0.91的验证准确度:一点也不差。这在一定程度上部分原因在于基础模型是在已经具有狗和猫(其他数百个类别)的数据集上进行训练的。


微调预训练网络的top layers

为了进一步改进之前的结果,我们可以对VGG16模型的最后一个卷积块和最终的全连接层进行Fine-tuning。分以下3步完成:

  • 实例化VGG16卷积网络,并加载预训练权重
  • 在顶部添加我们先前定义的完全连接模型,并加载其权重
  • 冻结VGG16模型的层到最后一个卷积块

 

注意:

  • 为了进行微调,所有层都应该从训练有素的权重开始:例如,你不应该在预先训练好的卷积基础上打一个随机初始化的全连接网络。这是因为由随机初始化的权重触发的大梯度更新将破坏卷积基础中的学习权重。在我们的例子中,这就是为什么我们首先训练顶级分类器,然后才开始微调卷积权重。
  • 我们选择仅微调最后的卷积块而不是整个网络以防止过度拟合,因为整个网络将具有非常大的熵容量并因此具有过度拟合的强烈倾向。低级卷积块学习的特征比较高级的卷积块更加通用,不那么抽象,所以保持前几个块固定(更一般的特征)并且只调整最后一个块(更专业的特征)是明智的 )。
  • 微调应该以非常慢的学习速率完成,通常使用SGD优化器而不是适应性学习速率优化器,例如RMSProp。这是为了确保更新的幅度保持非常小,以免破坏以前学过的功能。

这里实验的完整代码。

在实例化VGG基础并加载其权重后,我们在顶部添加了之前训练有素的完全连接分类器:

# build a classifier model to put on top of the convolutional model
top_model = Sequential()
top_model.add(Flatten(input_shape=model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(1, activation='sigmoid'))

# note that it is necessary to start with a fully-trained
# classifier, including the top classifier,
# in order to successfully do fine-tuning
top_model.load_weights(top_model_weights_path)

# add the model on top of the convolutional base
model.add(top_model)

然后我们继续将所有卷积层冻结到最后一个卷积块:

# set the first 25 layers (up to the last conv block)
# to non-trainable (weights will not be updated)
for layer in model.layers[:25]:
    layer.trainable = False

# compile the model with a SGD/momentum optimizer
# and a very slow learning rate.
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=['accuracy'])

最后,我们开始训练整个事情,学习速度非常慢:

batch_size = 16

# prepare data augmentation configuration
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        train_data_dir,
        target_size=(img_height, img_width),
        batch_size=batch_size,
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_data_dir,
        target_size=(img_height, img_width),
        batch_size=batch_size,
        class_mode='binary')

# fine-tune the model
model.fit_generator(
        train_generator,
        steps_per_epoch=nb_train_samples // batch_size,
        epochs=epochs,
        validation_data=validation_generator,
        validation_steps=nb_validation_samples // batch_size)

这种方法使我们在50个epoch之后达到0.94的验证准确度。巨大的成功!

以下是一些您可以尝试达到0.95以上的方法:

  • 更具侵略性的数据扩充
  • 更积极的辍学
  • 使用L1和L2正则化(也称为“重量衰减”)
  • 微调一个卷积块(同时更大的正则化)

 

这篇文章在这里结束!回顾一下,在这里您可以找到我们三个实验的代码:

注:本文章翻译自:

Building powerful image classification models using very little data
blog.keras.io图标
疟疾数据集
三维深度学习PASCAL3D+ 3D 物体检测和姿态识别数据集
51自学网,即我要自学网,自学EXCEL、自学PS、自学CAD、自学C语言、自学css3实例,是一个通过网络自主学习工作技能的自学平台,网友喜欢的软件自学网站。
京ICP备13026421号-1