原文:Building powerful image classification models using very little data

作者:Francois Chollet,2016.6.29

专有名词:

  1. train训练,validation验证,test测试。
  2. scratch,面向少年的简易编程工具。

文章目录

  • 1 简介
  • 2 我们的设置:仅2000个训练样本(每类1000个)
    • 2.1 论深度学习对小数据问题的相关性
  • 3 数据预处理和数据增强
  • 4 从头开始训练一个简单卷积神经网络:40行代码准确率达80%
  • 5 使用预训练网络的bottleneck features:训练一分钟准确率达到90%
  • 6 微调预训练网络的顶层

1 简介

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

我们将介绍以下内容

  • 从头开始训练小型网络(作为基础)
  • 使用预训练网络的bottleneck特征
  • 微调预训练网络的顶层

这将引导我们涵盖以下Keras功能

  • fit_generator使用Python数据生成器来训练Keras模型
  • ImageDataGenerator用于实时数据增强
  • 层冻结和模型微调

注意:所有代码示例已于2017年3月14日更新为Keras 2.0 API。您需要Keras 2.0.0或更高版本才能运行它们。

2 我们的设置:仅2000个训练样本(每类1000个)

我们将从以下设置开始:

  • 已安装Keras,SciPy,PIL。 如果你有NVIDIA GPU(并且安装了cuDNN)会更好。但因为我们只处理少量图片,所以那并不绝对需要。
  • 训练目录和验证目录,每个图像类包含一个子目录,填充.png或.jpg图像如下:
data/train/dogs/dog001.jpgdog002.jpg...cats/cat001.jpgcat002.jpg...validation/dogs/dog001.jpgdog002.jpg...cats/cat001.jpgcat002.jpg...

为了获得您感兴趣的类别的成百上千张训练图片,你可以使用Flickr API在友好许可下下载与给定标记匹配的图片。

本例我们将使用两组图片,从Kaggle获得:1000只猫和1000只狗(尽管原始数据集有12500只猫和12500只狗,我们取每个类的前1000张图片)。 我们还额外使用每个类的400个样本作为验证数据,以评估我们的模型。

猫狗数据集Kaggle地址:https://www.kaggle.com/c/dogs-vs-cats/data

对于一个不简单的分类问题,这些学习样本非常少。因此,这是一个具有挑战性的机器学习问题,但它也是一个现实的问题:在现实世界的一些案例中,即使是小规模的数据收集也可能非常昂贵或有时几乎不可能(如医学图像)。 能够充分利用非常少的数据是有能力的数据科学家的关键技能。

这个问题有多难? 当Kaggle开始举办猫与狗比赛(总共有25,000张训练图像)时,两年多前,它有以下声明:
(译者注:该比赛已是五年前。)

“多年前的一次非正式调查中,计算机视觉专家认为,如果没有现有技术的重大进步,准确度超过60%的分类器将会很困难。作为参考,60%的分类器提高了从1/4096到1/459的12幅图像HIP猜测概率。目前的文献表明,机器分类器在这项任务上的准确度可以达到80%以上。”
参考文献

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

2.1 论深度学习对小数据问题的相关性

我常听到“深度学习只有在你拥有大量数据时才有意义”。 虽然不完全错误,但有点误导。 当然,深度学习需要能够自动从数据中学习特征,这通常只有在有大量训练数据时才有可能——特别是对于输入样本非常高维的问题,如图像。 然而,即使只有很少的数据需要学习,卷积神经网络——深度学习的支柱算法——仍是解决大多数“感知”问题(例如图像分类)的最佳模型之一。 用scratch在小图像数据集上从头开始训练一个卷积神经网路仍然会产生合理的结果,而不需要任何自定义特征工程。卷积神经网络很简单。 它们是这项工作的正确工具。

但更重要的是,深度学习模型本质上是高度可再利用的:例如,您可以采用在大规模数据集上训练得到的图像分类或语音到文本模型,然后在一个显着不同的问题上重复使用它,只需进行微小的更改,正如即将本文中看到的。

特别在计算机视觉的情况下,许多预先训练的模型(通常在ImageNet数据集上训练)现在可以公开下载,并且可以用于从非常少的数据中引导强大的视觉模型。

3 数据预处理和数据增强

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

在Keras中,可以通过keras.preprocessing.image.ImageDataGenerator类完成。 该类能让你:

  • 配置在训练期间对图像数据执行的随机变换和标准化操作
  • 通过.flow(data, labels).flow_from_directory(directory)实例化增强图像批次(及其标签)的生成器
  • 这些生成器接受数据生成器作为输入,可以与Keras模型方法一起使用:fit_generatorevaluate_generatorpredict_generator

让我们马上看一个例子:

from keras.preprocessing.image import ImageDataGeneratordatagen = 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_range0-180的值,在该角度范围内随机旋转图片
  • width_shift_rangeheight_shift在该范围内垂直或水平地随机平移图像
  • rescale缩放因子,在其他处理之前与该数据相乘。(原始图像的RGB范围是0-255,这些值对于学习模型来说太高了,所以将目标值缩放成0-1之间)
  • shear_range随机应用剪切影射
  • zoom_range随机缩放图片内部
  • horizontal_flip水平随机翻转一半图像
  • fill_mode填充新像素的策略,在旋转或偏移后填充。

剪切影射维基百科:https://zh.wikipedia.org/wiki/%E9%94%99%E5%88%87

现在让我们使用这个工具生成图片并保存到临时目录中,这样就可以了解数据增强的原理:

from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_imgdatagen = 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)# .flow()函数批量生成随机转换的图像,并保存到preview目录
i = 0
for batch in datagen.flow(x, batch_size=1,save_to_dir='preview', save_prefix='cat', save_format='jpeg'):i += 1if i > 20:break  # otherwise the generator would loop indefinitely

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

图像分类工作的正确工具是卷积神经网络,让我们试着在我们的数据上训练作为基础。由于只有很少的例子,我们首要担心的是过拟合。

当少量样本的模式学习没有推广到新数据时,即当模型开始使用不相关的特征进行预测时,就会发生过拟合。 例如,你作为一个人,只看到三张伐木工人的图像,三张水手的图像。其中只有一个伐木工人戴着帽子,你便开始认为戴帽子是一名伐木工人而不是一名水手的标志,然后你便成为了一个非常糟糕的伐木工人/水手的分类器。

数据增强是预防过拟合的一种方法,但这还不够,因为我们增强的样本仍是高度相关的。 预防过拟合的焦点应该是模型的熵容量——您的模型可以存储多少信息。 可以存储大量信息的模型可能更加准确,但同时存储不相关的功能也存在风险。 因此,只能存储一些特征的模型必须关注数据中最重要的特征,这些特征更有可能真正相关并更好地泛化。

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

本例将采用一个简单卷积神经网路,每层有少量层和过滤器,以及数据增强和Dropout。Dropout也有助于减少过拟合,防止层看到两次完全的相同模式,达到类似数据增强的效果(你可以说Dropout和数据增强都会破坏数据出现的随机关联)。

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

完整代码:https://gist.github.com/fchollet/0830affa1f7f19fd47b06d4cf89ed44d

from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Densemodel = 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 directorytarget_size=(150, 150),  # all images will be resized to 150x150batch_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次后达到0.79-0.81的验证准确度val-acc(这是一个任意挑选的数字——因为模型很小并且使用较大的dropout,到那时它似乎不会过拟合)。 因此,放在当时的Kaggle比赛,该模型是最领先的——仅有8%的数据,且没有努力优化架构或超参数。 事实上,在Kaggle比赛中,这个模型将进入前100名(共215名)。 我想至少有115名参赛者没有使用深度学习)

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

5 使用预训练网络的bottleneck features:训练一分钟准确率达到90%

更精确的方法是利用在大型数据集上预先训练的网络。 这样的网络已经学习了对大多数计算机视觉问题有用的特征,在仅有少量数据的情况下,利用这些特征比用任何方法都使我们获得更好的准确性。

VGG16架构如下:

策略如下:仅实例化模型的卷积部分,都是全连接层。 然后在训练和验证数据上运行此模型一次,记录输出(“bottleneck features”从已有特征的顶部开始)。

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

代码:https://gist.github.com/fchollet/f35fbc80e066a49d65f1688a7e99f069
权重:https://drive.google.com/file/d/0Bz7KyqmuGsilT0J5dmRCM0ROVHc/view

本例不会回顾模型的构建和加载方式——这已经包含在多个Keras示例中。 而让我们来看看如何使用图像数据生成器记录bottleneck features:

batch_size = 16generator = 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 labelsshuffle=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上也可以非常快速地进行训练(每代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的验证准确度:一点也不差。 达到这效果主要原因是基础模型早在具有狗和猫(含数百个其他类别)的数据集上进行训练的结果。

6 微调预训练网络的顶层

为了进一步改进我们之前的结果,我们可以尝试与顶级分类器一起“微调”VGG16模型的最后一个卷积块。 微调是从训练过的网络开始,然后使用非常小的权重更新在新数据集上重新训练它。 本例通过3个步骤完成:

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


注意:

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

代码:https://gist.github.com/fchollet/7eb39b44eb9e16e59632d25fb3119975

在实例化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,

这种方法使我们在迭代50次后达到0.94的验证准确度val-acc。 巨大的成功!

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

  • 更激进的数据增强
  • 更激进的dropout
  • 使用L1和L2正则化(也称为“权重衰减”)
  • 微调多一个卷积块(同时更大的正则化)

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

  • 从头开始训练卷积神经网络
  • Bottleneck features
  • 微调

如果您对此帖子有任何评论或有关未来主题的任何建议,您可以在Twitter上联系。

Keras少量样本训练强大图像分类模型相关推荐

  1. Keras 的预训练权值模型用来进行预测、特征提取和微调(fine-tuning)

    转至:Keras中文文档 https://keras.io/zh/applications/ 应用 Applications Keras 的应用模块(keras.applications)提供了带有预 ...

  2. 老板让我用少量样本 finetune 模型,我还有救吗?急急急,在线等!

    文 | ???????????????? 编 | 王苏 今天介绍的是一篇 NAACL'21 新鲜出炉的工作!NAACL 上周四出的结果,这篇工作本周一上传 arxiv,周二被王苏小哥哥发现,周三拜读了 ...

  3. python实现人脸检测及识别(2)---- 利用keras库训练人脸识别模型

    前面已经采集好数据集boss文件夹存放需要识别的对象照片,other存放其他人的训练集照片,现在,我们终于可以尝试训练我们自己的卷积神经网络模型了.CNN擅长图像处理,keras库的tensorflo ...

  4. 七、图像分类模型的部署(Datawhale组队学习)

    文章目录 前言 ONNX简介 应用场景 部署ImageNet预训练图像分类模型 导出ONNX模型 推理引擎ONNX Runtime部署-预测单张图像 前期准备 ONNX Runtime预测 推理引擎O ...

  5. 利用Keras使用非常少的数据建立强大的图像分类模型

    博客原文 在本教程中,我们将介绍一些简单而有效的方法,您可以使用这些方法来构建强大的图像分类器,仅使用极少数的训练实例 - 只需从您想要识别的每个类别中挑选几百或几千张图片即可. 我们将会涵盖以下内容 ...

  6. ML.NET 示例:图像分类模型训练-首选API(基于原生TensorFlow迁移学习)

    ML.NET 版本 API 类型 状态 应用程序类型 数据类型 场景 机器学习任务 算法 Microsoft.ML 1.5.0 动态API 最新 控制台应用程序和Web应用程序 图片文件 图像分类 基 ...

  7. 把一个dataset的表放在另一个dataset里面_现在开始:用你的Mac训练和部署一个图像分类模型...

    可能有些同学学习机器学习的时候比较迷茫,不知道该怎么上手,看了很多经典书籍介绍的各种算法,但还是不知道怎么用它来解决问题,就算知道了,又发现需要准备环境.准备训练和部署的机器,啊,好麻烦. 今天,我来 ...

  8. 【神经网络与深度学习】CIFAR10数据集介绍,并使用卷积神经网络训练图像分类模型——[附完整训练代码]

    [神经网络与深度学习]CIFAR-10数据集介绍,并使用卷积神经网络训练模型--[附完整代码] 一.CIFAR-10数据集介绍 1.1 CIFAR-10数据集的内容 1.2 CIFAR-10数据集的结 ...

  9. Keras快速上手——打造个人的第一个“圣诞老人”图像分类模型

    首发地址:https://yq.aliyun.com/articles/288077 2017年已到最后一个月的尾巴,那圣诞节还会远吗?不知道各位对于圣诞节有什么安排或一些美好的回忆,我记得最清楚的还 ...

最新文章

  1. 一文概览深度学习中的激活函数
  2. 【Codevs1346】HelloWorld编译器
  3. 设置行间距_[EndNote]如何修改文末参考文献行间距?
  4. Linux下Nagios的安装与配置
  5. HTTP 方法:GET 对比 POST
  6. python多线程网络编程_python网络编程之线程
  7. mxnet基础到提高(51)-Activation激活函数
  8. 「pip」常用pip镜像地址
  9. 黄聪: 50 个 Bootstrap 插件
  10. OpenFileDialog对话框Filter属性
  11. Jackson学习笔记
  12. 程序人生:提高代码运行效率的9个技巧
  13. python qt5 designer 免费安装_PyCharm离线安装PyQt5_tools(QtDesigner)
  14. 一 c语言程序设计 张玉生版
  15. 微信小程序--点餐系统(本地服务器+源码分享)
  16. 艺术名家|翰墨异彩“三分书”——访知名书法家蔡爱军
  17. Python爬虫(1)
  18. yarn : 无法加载文件 D:\Development\nvm\node_global\yarn.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.c
  19. rsa java模数_公钥/私钥中的RSA密钥值和模数
  20. sql语句中的子查询

热门文章

  1. oracle crs 4563,重启机器后,节点2无法启动crs,Oracle你生产的RAC为何如此的脆弱,有图有真相。求解...
  2. 3.摄像模组之Golden模组
  3. 2565: 最长双回文串
  4. Windows Server 2008 R2下安装卸载Oracle 11g
  5. LoadRunner12 进行协议顾问分析时浏览器选择问题
  6. 复变函数(1)-复数及其几何属性
  7. 玩客云刷入openwrt系统
  8. 技术分享 | 基于 Alertmanager 告警系统的改造
  9. 计算机底层——计算机的发展史
  10. 计算机主板 辐射,一直被忽视!电脑电磁辐射危害及机箱选择