在Keras+TF环境中,用迁移学习和微调打造专属图像识别系统

上图,是CompCars数据集的示例图像,整个数据集包含163家汽车制造商,1713种车型。该怎样训练一个神经网络来区分这些车呢?这里就用到了迁移学习和微调。

@王小新 编译自 Deep Learning Sandbox
量子位 出品 | 公众号 QbitAI

量子位曾经编译过Greg Chu的一篇文章,介绍了如何用Keras+TF,来实现ImageNet数据集日常对象的识别。

但是,你要研究的物体,往往不在那个列表中。我们可能想要区分出不同型号的太阳镜、认出不同的鞋子、识别各种面部表情、说出不同汽车的型号、在X光影像下判定肺部疾病的类型,这时候该怎么办?

Greg Chu,博客Deep Learning Sandbox的作者,又写了一篇文章,教你在Keras +TensorFlow环境中,用迁移学习(transfer learning)和微调(fine-tuning)定制你专属的图像识别系统,来辨识特定的研究对象。

为什么要使用迁移学习和微调?

一般来说,从头开始训练一个卷积神经网络,不仅需要大规模的数据集,而且会占用大量的计算资源。比如,为了得到ImageNet ILSVRC模型,Google使用了120万张图像,在装有多个GPU的服务器上运行2-3周才完成训练。

在实际应用中,深度学习相关的研究人员和从业者通常运用迁移学习和微调方法,将ImageNet等数据集上训练的现有模型底部特征提取层网络权重传递给新的分类网络。这种做法并不是个例。

这种做法的效果很好。Razavian等人2014年发表的论文*表明,从ImageNet ILSVRC的训练模型中,简单地提取网络权重的初级特征,应用在多种图像分类任务中,都取得了与ImageNet网络相同或几乎相同的分类效果。

* CNN Features off-the-shelf: an Astounding Baseline for Recognition
arxiv.org/pdf/1403.6382

接下来我们介绍一下这两种方法:

迁移学习:在ImageNet上得到一个预训练好的ConvNet网络,删除网络顶部的全连接层,然后将ConvNet网络的剩余部分作为新数据集的特征提取层。这也就是说,我们使用了ImageNet提取到的图像特征,为新数据集训练分类器。

微调:更换或者重新训练ConvNet网络顶部的分类器,还可以通过反向传播算法调整预训练网络的权重。

该选择哪种方法?

有两个主要因素,将影响到所选择的方法:

1. 你的数据集大小;

2. 新数据集与预训练数据集的相似性,通常与ImageNet数据集相比。

新数据集相比于原数据集在样本量上更小,在内容上相似:如果数据过小,考虑到过拟合,这使用微调则效果不大好。因为新数据类似于原数据,我们希望网络中高级特征也与此数据集相关。因此,最好的思路可能是在ConvNet网络上重新训练一个线性分类器。上表指出了在如下4个场景下,该如何从这两种方法中做选择:

新数据集相比于原数据集在样本量上更小,且内容非常不同:由于数据较小,只训练一个线性分类器可能更好。但是数据集不同,从网络顶部开始训练分类器不是最好的选择,这里包含了原有数据集的高级特征。所以,一般是从ConvNet网络前部的激活函数开始,重新训练一个线性分类器。

新数据集相比于原数据集在样本量上较大,在内容上相似:由于我们有更多的数据,所以在我们试图微调整个网络,那我们有信心不会导致过拟合。

新数据集相比于原数据集在样本量上较大,但内容非常不同:由于数据集很大,我们可以尝试从头开始训练一个深度网络。然而,在实际应用中,用一个预训练模型的网络权重来初始化新网络的权重,仍然是不错的方法。在这种情况下,我们有足够的数据和信心对整个网络进行微调。

另外,在新数据集样本量较大时,你也可以尝试从头开始训练一个网络。

数据增强

数据增强方法能大大增加训练数据集的样本量和增大网络模型的泛化能力。实际上,在数据比赛中,每个获胜者的ConvNet网络一定会使用数据增强方法。在本质上,数据增强是通过数据转换来人为地增加数据集样本量的过程。

大多数深度学习框架具有一些基本函数,可以直接实现常用的数据转换。为了建立特定的图像识别系统,我们的任务是去确定对现有数据集有意义的转换方法。比如,不能对X射线图像旋转超过45度,因为这意味着在图像采集过程中出现错误。

图2:通过水平翻转和随机裁剪进行数据增强

常用转换方法:像素颜色抖动、旋转、剪切、随机裁剪、水平翻转、镜头拉伸和镜头校正等。

迁移学习和微调方法实现

数据准备

图3:Kaggle猫狗大赛的示例图像

我们将使用Kaggle猫狗大赛中提供的数据集,将训练集目录和验证集目录设置如下:

train_dir/
  dog/
  cat/
val_dir/
  dog/
  cat/

网络实现

让我们开始定义generators:

train_datagen =  ImageDataGenerator(
    preprocessing_function=preprocess_input,
    rotation_range=30,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True
)
test_datagen = ImageDataGenerator(
    preprocessing_function=preprocess_input,
    rotation_range=30,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True
)
train_generator = train_datagen.flow_from_directory(
  args.train_dir,
  target_size=(IM_WIDTH, IM_HEIGHT),
  batch_size=batch_size,
)
validation_generator = test_datagen.flow_from_directory(
  args.val_dir,
  target_size=(IM_WIDTH, IM_HEIGHT),
  batch_size=batch_size,
)

在上篇文章中,我们已经强调了在图像识别中预处理环节的重要性。从keras.applications.inception_v3模块中引出参数preprocess_input,进而设置preprocessing_function = preprocess_input。

为了实现数据增强,还定义了旋转、移动、剪切、缩放和翻转操作的参数范围。

接下来,我们从keras.applications模块中引出InceptionV3网络。

base_model = InceptionV3(weights='imagenet', include_top=False)

设置了标志位include_top = False,去除ImageNet网络的全连接层权重,因为这是针对ImageNet竞赛的1000种日常对象预先训练好的网络权重。因此,我们将添加一个新的全连接层,并进行初始化。

def add_new_last_layer(base_model, nb_classes):
  """Add last layer to the convnet
  Args:
    base_model: keras model excluding top
    nb_classes: # of classes
  Returns:
    new keras model with last layer
  """
  x = base_model.output
  x = GlobalAveragePooling2D()(x)
  x = Dense(FC_SIZE, activation='relu')(x) 
  predictions = Dense(nb_classes, activation='softmax')(x) 
  model = Model(input=base_model.input, output=predictions)
  return model

全局平均初始化函数GlobalAveragePooling2D将MxNxC张量转换后输出为1xC张量,其中C是图像的通道数。然后我们添加一个维度为1024的全连接层Dense,同时加上一个softmax函数,得到[0,1]之间的输出值。

在这个项目中,我将演示如何实现迁移学习和微调。当然你可以在以后的项目中自由选用。

1. 迁移学习:除去倒数第二层,固定所有其他层的参数,并重新训练最后一层全连接层。

2. 微调:固定用来提取低级特征的底部卷积层,并重新训练更多的网络层。

这样做,将确保更稳定和全局一致的训练网络。因为如果不固定相关层,随机初始化网络权重会导致较大的梯度更新,进一步可能会破坏卷积层中的学习权重。我们应用迁移学习,训练得到稳定的最后全连接层后,可以再通过微调的方法训练更多的网络层。

迁移学习

def setup_to_transfer_learn(model, base_model):
  """Freeze all layers and compile the model"""
  for layer in base_model.layers:
    layer.trainable = False
  model.compile(optimizer='rmsprop',    
                loss='categorical_crossentropy', 
                metrics=['accuracy'])

微调

def setup_to_finetune(model):
   """Freeze the bottom NB_IV3_LAYERS and retrain the remaining top 
      layers.
   note: NB_IV3_LAYERS corresponds to the top 2 inception blocks in 
         the inceptionv3 architecture
   Args:
     model: keras model
   """
   for layer in model.layers[:NB_IV3_LAYERS_TO_FREEZE]:
      layer.trainable = False
   for layer in model.layers[NB_IV3_LAYERS_TO_FREEZE:]:
      layer.trainable = True
   model.compile(optimizer=SGD(lr=0.0001, momentum=0.9),   
                 loss='categorical_crossentropy')

在微调过程中,最重要的是与网络从头开始训练时所使用的速率相比(lr = 0.0001),要降低学习率,否则优化过程可能不稳定,Loss函数可能会发散。

网络训练

现在我们开始训练,使用函数fit_generator同时实现迁移学习和微调。

history = model.fit_generator(
  train_generator,
  samples_per_epoch=nb_train_samples,
  nb_epoch=nb_epoch,
  validation_data=validation_generator,
  nb_val_samples=nb_val_samples,
  class_weight='auto')

model.save(args.output_model_file)

我们将使用AWS上的EC2 g2.2xlarge实例进行网络训练。

我们可以使用对象history,绘制训练准确率和损失曲线。

def plot_training(history):
  acc = history.history['acc']
  val_acc = history.history['val_acc']
  loss = history.history['loss']
  val_loss = history.history['val_loss']
  epochs = range(len(acc))

  plt.plot(epochs, acc, 'r.')
  plt.plot(epochs, val_acc, 'r')
  plt.title('Training and validation accuracy')

  plt.figure()
  plt.plot(epochs, loss, 'r.')
  plt.plot(epochs, val_loss, 'r-')
  plt.title('Training and validation loss')

  plt.show()

模型预测

现在我们通过keras.model保存训练好的网络模型,通过修改predict.py中的predict函数后,只需要输入本地图像文件的路径或是图像的URL链接即可实现模型预测。

python predict.py --image dog.001.jpg --model dc.model
python predict.py --image_url https://goo.gl/Xws7Tp --model dc.model

完工

作为例子,我将猫狗大赛数据集中的24000张图像作为训练集,1000张图像作为验证集。从结果中,可以看出训练迭代2次后,准确率已经相当高了。

图4:经过2次迭代后的输出日志

测试

python predict.py --image_url https://goo.gl/Xws7Tp --model dc.model

图5:猫的图片和类别预测

python predict.py --image_url https://goo.gl/6TRUol --model dc.model

图6:狗的图片和类别预测

将上述代码组合起来,你就创建了一个猫狗识别系统。

该项目的完整程序请查看GitHub链接:

DeepLearningSandbox/DeepLearningSandbox

其他相关链接:

原文:deeplearningsandbox.com

Kaggle猫狗数据集:Dogs vs. Cats | Kaggle

训练好的模型:drive.google.com/file/d

编辑于 2017-05-03