基于Keras进行迁移学习

2018 年 5 月 6 日 论智 Prakash Jay
来源: Prakash Jay
编译:weakish

编者按:数据科学家Prakash Jay介绍了迁移学习的原理,基于Keras实现迁移学习,以及迁移学习的常见情形。

Inception-V3

什么是迁移学习?

机器学习中的迁移学习问题,关注如何保存解决一个问题时获得的知识,并将其应用于另一个相关的不同问题。

为什么迁移学习?

  • 在实践中,很少有人从头训练一个卷积网络,因为很难获取足够的数据集。使用预训练的网络有助于解决大多数手头的问题。

  • 训练深度网络代价高昂。即使使用数百台配备了昂贵的GPU的机器,训练最复杂的模型也需要好多周。

  • 决定深度学习的拓扑/特色/训练方法/超参数是没有多少理论指导的黑魔法。

我的经验

不要试图成为英雄。

—— Andrej Karapathy

我面对的大多数计算机视觉问题没有非常大的数据集(5000-40000图像)。即使使用极端的数据增强策略,也很难达到像样的精确度。而在少量数据集上训练数百万参数的网络通常会导致过拟合。所以迁移学习是我的救星。

迁移学习为何有效?

让我们看下深度学习网络学习了什么,靠前的层尝试检测边缘,中间层尝试检测形状,而靠后的层尝试检测高层数据特征。这些训练好的网络通常有助于解决其他计算机视觉问题。

下面,让我们看下如何使用Keras实现迁移学习,以及迁移学习的常见情形。

基于Keras的简单实现


  
  
    
  1. from keras import applications

  2. from keras.preprocessing.image import ImageDataGenerator

  3. from keras import optimizers

  4. from keras.models import Sequential, Model

  5. from keras.layers import Dropout, Flatten, Dense, GlobalAveragePooling2D

  6. from keras import backend as k

  7. from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard, EarlyStopping

  8. img_width, img_height = 256, 256

  9. train_data_dir = "data/train"

  10. validation_data_dir = "data/val"

  11. nb_train_samples = 4125

  12. nb_validation_samples = 466

  13. batch_size = 16

  14. epochs = 50

  15. model = applications.VGG19(weights = "imagenet", include_top=False, input_shape = (img_width, img_height, 3))

  16. """

  17. 层 (类型)                 输出形状              参数数量  

  18. =================================================================

  19. input_1 (InputLayer)         (None, 256, 256, 3)       0        

  20. _________________________________________________________________

  21. block1_conv1 (Conv2D)        (None, 256, 256, 64)      1792      

  22. _________________________________________________________________

  23. block1_conv2 (Conv2D)        (None, 256, 256, 64)      36928    

  24. _________________________________________________________________

  25. block1_pool (MaxPooling2D)   (None, 128, 128, 64)      0        

  26. _________________________________________________________________

  27. block2_conv1 (Conv2D)        (None, 128, 128, 128)     73856    

  28. _________________________________________________________________

  29. block2_conv2 (Conv2D)        (None, 128, 128, 128)     147584    

  30. _________________________________________________________________

  31. block2_pool (MaxPooling2D)   (None, 64, 64, 128)       0        

  32. _________________________________________________________________

  33. block3_conv1 (Conv2D)        (None, 64, 64, 256)       295168    

  34. _________________________________________________________________

  35. block3_conv2 (Conv2D)        (None, 64, 64, 256)       590080    

  36. _________________________________________________________________

  37. block3_conv3 (Conv2D)        (None, 64, 64, 256)       590080    

  38. _________________________________________________________________

  39. block3_conv4 (Conv2D)        (None, 64, 64, 256)       590080    

  40. _________________________________________________________________

  41. block3_pool (MaxPooling2D)   (None, 32, 32, 256)       0        

  42. _________________________________________________________________

  43. block4_conv1 (Conv2D)        (None, 32, 32, 512)       1180160  

  44. _________________________________________________________________

  45. block4_conv2 (Conv2D)        (None, 32, 32, 512)       2359808  

  46. _________________________________________________________________

  47. block4_conv3 (Conv2D)        (None, 32, 32, 512)       2359808  

  48. _________________________________________________________________

  49. block4_conv4 (Conv2D)        (None, 32, 32, 512)       2359808  

  50. _________________________________________________________________

  51. block4_pool (MaxPooling2D)   (None, 16, 16, 512)       0        

  52. _________________________________________________________________

  53. block5_conv1 (Conv2D)        (None, 16, 16, 512)       2359808  

  54. _________________________________________________________________

  55. block5_conv2 (Conv2D)        (None, 16, 16, 512)       2359808  

  56. _________________________________________________________________

  57. block5_conv3 (Conv2D)        (None, 16, 16, 512)       2359808  

  58. _________________________________________________________________

  59. block5_conv4 (Conv2D)        (None, 16, 16, 512)       2359808  

  60. _________________________________________________________________

  61. block5_pool (MaxPooling2D)   (None, 8, 8, 512)         0        

  62. =================================================================

  63. 总参数: 20,024,384.0

  64. 可训练参数: 20,024,384.0

  65. 不可训练参数: 0.0

  66. """

  67. # 冻结不打算训练的层。这里我冻结了前5层。

  68. for layer in model.layers[:5]:

  69.    layer.trainable = False

  70. # 增加定制层

  71. x = model.output

  72. x = Flatten()(x)

  73. x = Dense(1024, activation="relu")(x)

  74. x = Dropout(0.5)(x)

  75. x = Dense(1024, activation="relu")(x)

  76. predictions = Dense(16, activation="softmax")(x)

  77. # 创建最终模型

  78. model_final = Model(input = model.input, output = predictions)

  79. # 编译最终模型

  80. model_final.compile(loss = "categorical_crossentropy", optimizer = optimizers.SGD(lr=0.0001, momentum=0.9), metrics=["accuracy"])

  81. # 数据增强

  82. train_datagen = ImageDataGenerator(

  83. rescale = 1./255,

  84. horizontal_flip = True,

  85. fill_mode = "nearest",

  86. zoom_range = 0.3,

  87. width_shift_range = 0.3,

  88. height_shift_range=0.3,

  89. rotation_range=30)

  90. test_datagen = ImageDataGenerator(

  91. rescale = 1./255,

  92. horizontal_flip = True,

  93. fill_mode = "nearest",

  94. zoom_range = 0.3,

  95. width_shift_range = 0.3,

  96. height_shift_range=0.3,

  97. rotation_range=30)

  98. train_generator = train_datagen.flow_from_directory(

  99. train_data_dir,

  100. target_size = (img_height, img_width),

  101. batch_size = batch_size,

  102. class_mode = "categorical")

  103. validation_generator = test_datagen.flow_from_directory(

  104. validation_data_dir,

  105. target_size = (img_height, img_width),

  106. class_mode = "categorical")

  107. # 保存模型

  108. checkpoint = ModelCheckpoint("vgg16_1.h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)

  109. early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')

  110. # 训练模型

  111. model_final.fit_generator(

  112. train_generator,

  113. samples_per_epoch = nb_train_samples,

  114. epochs = epochs,

  115. validation_data = validation_generator,

  116. nb_val_samples = nb_validation_samples,

  117. callbacks = [checkpoint, early])

迁移学习的常见情形

别忘了,靠前的层中的卷积特征更通用,靠后的层中的卷积特征更针对原本的数据集。迁移学习有4种主要场景:

1. 新数据集较小,和原数据集相似

如果我们尝试训练整个网络,容易导致过拟合。由于新数据和原数据相似,因此我们期望卷积网络中的高层特征和新数据集相关。因此,建议冻结所有卷积层,只训练分类器(比如,线性分类器):

  
  
    
  1. for layer in model.layers:

  2.   layer.trainable = False

2. 新数据集较大,和原数据集相似

由于我们有更多数据,我们更有自信,如果尝试对整个网络进行精细调整,不会导致过拟合。

  
  
    
  1. for layer in model.layers:

  2.   layer.trainable = True

其实默认值就是True,上面的代码明确指定所有层可训练,是为了更清楚地强调这一点。

由于开始的几层检测边缘,你也可以选择冻结这些层。比如,以下代码冻结VGG19的前5层:

  
  
    
  1. for layer in model.layers[:5]:

  2.   layer.trainable = False

3. 新数据集很小,但和原数据很不一样

由于数据集很小,我们大概想要从靠前的层提取特征,然后在此之上训练一个分类器:(假定你对h5py有所了解)

  
  
    
  1. from keras import applications

  2. from keras.preprocessing.image import ImageDataGenerator

  3. from keras import optimizers

  4. from keras.models import Sequential, Model

  5. from keras.layers import Dropout, Flatten, Dense, GlobalAveragePooling2D

  6. from keras import backend as k

  7. from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard, EarlyStopping

  8. img_width, img_height = 256, 256

  9. ### 创建网络

  10. img_input = Input(shape=(256, 256, 3))

  11. x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(img_input)

  12. x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x)

  13. x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)

  14. # 块2

  15. x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x)

  16. x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x)

  17. x = MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)

  18. model = Model(input = img_input, output = x)

  19. model.summary()

  20. """

  21. _________________________________________________________________

  22. 层 (类型)                 输出形状              参数数量  

  23. =================================================================

  24. input_1 (InputLayer)         (None, 256, 256, 3)       0        

  25. _________________________________________________________________

  26. block1_conv1 (Conv2D)        (None, 256, 256, 64)      1792      

  27. _________________________________________________________________

  28. block1_conv2 (Conv2D)        (None, 256, 256, 64)      36928    

  29. _________________________________________________________________

  30. block1_pool (MaxPooling2D)   (None, 128, 128, 64)      0        

  31. _________________________________________________________________

  32. block2_conv1 (Conv2D)        (None, 128, 128, 128)     73856    

  33. _________________________________________________________________

  34. block2_conv2 (Conv2D)        (None, 128, 128, 128)     147584    

  35. _________________________________________________________________

  36. block2_pool (MaxPooling2D)   (None, 64, 64, 128)       0        

  37. =================================================================

  38. 总参数:260,160.0

  39. 可训练参数:260,160.0

  40. 不可训练参数:0.0

  41. """

  42. layer_dict = dict([(layer.name, layer) for layer in model.layers])

  43. [layer.name for layer in model.layers]

  44. """

  45. ['input_1',

  46. 'block1_conv1',

  47. 'block1_conv2',

  48. 'block1_pool',

  49. 'block2_conv1',

  50. 'block2_conv2',

  51. 'block2_pool']

  52. """

  53. import h5py

  54. weights_path = 'vgg19_weights.h5' # ('https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg19_weights_tf_dim_ordering_tf_kernels.h5)

  55. f = h5py.File(weights_path)

  56. list(f["model_weights"].keys())

  57. """

  58. ['block1_conv1',

  59. 'block1_conv2',

  60. 'block1_pool',

  61. 'block2_conv1',

  62. 'block2_conv2',

  63. 'block2_pool',

  64. 'block3_conv1',

  65. 'block3_conv2',

  66. 'block3_conv3',

  67. 'block3_conv4',

  68. 'block3_pool',

  69. 'block4_conv1',

  70. 'block4_conv2',

  71. 'block4_conv3',

  72. 'block4_conv4',

  73. 'block4_pool',

  74. 'block5_conv1',

  75. 'block5_conv2',

  76. 'block5_conv3',

  77. 'block5_conv4',

  78. 'block5_pool',

  79. 'dense_1',

  80. 'dense_2',

  81. 'dense_3',

  82. 'dropout_1',

  83. 'global_average_pooling2d_1',

  84. 'input_1']

  85. """

  86. # 列出模型中的所有层的名称

  87. layer_names = [layer.name for layer in model.layers]

  88. """

  89. # 提取`.h5`文件中每层的模型权重

  90. >>> f["model_weights"]["block1_conv1"].attrs["weight_names"]

  91. array([b'block1_conv1/kernel:0', b'block1_conv1/bias:0'],

  92.      dtype='|S21')

  93. # 将这一数组分配给weight_names

  94. >>> f["model_weights"]["block1_conv1"]["block1_conv1/kernel:0]

  95. <HDF5 dataset "kernel:0": shape (3, 3, 3, 64), type "<f4">

  96. # 列表推导(weights)储存层的权重和偏置

  97. >>>layer_names.index("block1_conv1")

  98. 1

  99. >>> model.layers[1].set_weights(weights)

  100. # 为特定层设置权重。

  101. 使用for循环我们可以为整个网络设置权重。

  102. """

  103. for i in layer_dict.keys():

  104.    weight_names = f["model_weights"][i].attrs["weight_names"]

  105.    weights = [f["model_weights"][i][j] for j in weight_names]

  106.    index = layer_names.index(i)

  107.    model.layers[index].set_weights(weights)

  108. import cv2

  109. import numpy as np

  110. import pandas as pd

  111. from tqdm import tqdm

  112. import itertools

  113. import glob

  114. features = []

  115. for i in tqdm(files_location):

  116.        im = cv2.imread(i)

  117.        im = cv2.resize(cv2.cvtColor(im, cv2.COLOR_BGR2RGB), (256, 256)).astype(np.float32) / 255.0

  118.        im = np.expand_dims(im, axis =0)

  119.        outcome = model_final.predict(im)

  120.        features.append(outcome)

  121. ## 收集这些特征,创建一个dataframe,在其上训练一个分类器

以上代码提取block2_pool特征。通常而言,由于这层有64 x 64 x 128特征,在其上训练一个分类器可能于事无补。我们可以加上一些全连接层,然后在其基础上训练神经网络。

  • 增加少量全连接层和一个输出层。

  • 为靠前的层设置权重,然后冻结。

  • 训练网络。

4. 新数据集很大,和原数据很不一样

由于你有一个很大的数据集,你可以设计你自己的网络,或者使用现有的网络。

你可以基于随机初始化权重或预训练网络权重初始化训练网络。一般选择后者。

你可以使用不同的网络,或者基于现有网络做些改动。

参考

  1. cs231n课程中关于“迁移学习”的内容

  2. Keras官网

有任何想法,或基于迁移学习做了什么有趣的事情,都欢迎留言。

登录查看更多
12

相关内容

基于深度学习的手语识别综述
专知会员服务
45+阅读 · 2020年5月18日
【Amazon】使用预先训练的Transformer模型进行数据增强
专知会员服务
56+阅读 · 2020年3月6日
《强化学习—使用 Open AI、TensorFlow和Keras实现》174页pdf
专知会员服务
136+阅读 · 2020年3月1日
【模型泛化教程】标签平滑与Keras, TensorFlow,和深度学习
专知会员服务
20+阅读 · 2019年12月31日
Keras François Chollet 《Deep Learning with Python 》, 386页pdf
专知会员服务
143+阅读 · 2019年10月12日
【教程】TensorFlow2 最新迁移学习教程和实战
初学者的 Keras:实现卷积神经网络
Python程序员
24+阅读 · 2019年9月8日
基于TensorFlow和Keras的图像识别
Python程序员
16+阅读 · 2019年6月24日
使用LSTM模型预测股价基于Keras
量化投资与机器学习
34+阅读 · 2018年11月17日
预训练模型迁移学习
极市平台
10+阅读 · 2018年11月6日
【干货】基于Keras的注意力机制实战
专知
58+阅读 · 2018年5月4日
keras系列︱深度学习五款常用的已训练模型
数据挖掘入门与实战
10+阅读 · 2018年3月27日
迁移学习在图像分类中的简单应用策略
炼数成金订阅号
4+阅读 · 2018年1月4日
深度学习实战(二)——基于Keras 的深度学习
乐享数据DataScientists
15+阅读 · 2017年7月13日
Compositional Generalization in Image Captioning
Arxiv
3+阅读 · 2019年9月16日
Multi-task Deep Reinforcement Learning with PopArt
Arxiv
4+阅读 · 2018年9月12日
ViZDoom Competitions: Playing Doom from Pixels
Arxiv
5+阅读 · 2018年9月10日
Few Shot Learning with Simplex
Arxiv
5+阅读 · 2018年7月27日
Arxiv
7+阅读 · 2018年5月23日
Arxiv
8+阅读 · 2018年1月12日
Arxiv
4+阅读 · 2016年12月29日
VIP会员
相关资讯
【教程】TensorFlow2 最新迁移学习教程和实战
初学者的 Keras:实现卷积神经网络
Python程序员
24+阅读 · 2019年9月8日
基于TensorFlow和Keras的图像识别
Python程序员
16+阅读 · 2019年6月24日
使用LSTM模型预测股价基于Keras
量化投资与机器学习
34+阅读 · 2018年11月17日
预训练模型迁移学习
极市平台
10+阅读 · 2018年11月6日
【干货】基于Keras的注意力机制实战
专知
58+阅读 · 2018年5月4日
keras系列︱深度学习五款常用的已训练模型
数据挖掘入门与实战
10+阅读 · 2018年3月27日
迁移学习在图像分类中的简单应用策略
炼数成金订阅号
4+阅读 · 2018年1月4日
深度学习实战(二)——基于Keras 的深度学习
乐享数据DataScientists
15+阅读 · 2017年7月13日
相关论文
Compositional Generalization in Image Captioning
Arxiv
3+阅读 · 2019年9月16日
Multi-task Deep Reinforcement Learning with PopArt
Arxiv
4+阅读 · 2018年9月12日
ViZDoom Competitions: Playing Doom from Pixels
Arxiv
5+阅读 · 2018年9月10日
Few Shot Learning with Simplex
Arxiv
5+阅读 · 2018年7月27日
Arxiv
7+阅读 · 2018年5月23日
Arxiv
8+阅读 · 2018年1月12日
Arxiv
4+阅读 · 2016年12月29日
Top
微信扫码咨询专知VIP会员