【Deep Learning with Python】C5 迁移学习两种方法
迁移学习
这本书写得很好,我学到了很多keras迁移学习的技巧。
- 特征提取的迁移学习
- 迁移学习方法二
- 微调
其中有很多非常值得参考的东西,为了表达方便,我直接用书中的例子,一步步对模型进行改进。
前置背景
前面已经建立了一个识别猫和狗的模型,只使用了2000个样本!
前面一步中,原始卷积神经网络识别结果是,val 0.7,超级过拟合,原因是数据实在太少。
引入数据增广,val 0.8+,已经改善了很多。
下面使用迁移学习的方式,对上述进行改善。下面是keras中获取训练好的模型的方法。
from keras.applications import VGG16
# 使用VGG16进行特征抽取,这里获得部分模型
conv_base = VGG16(weights='imagenet',
include_top=False,
input_shape=(150,150,3) ) # 注意声明输入大小,很好奇这个是怎么指定输入大小的,通过加一层卷积?不指定的话,网络允许任何大小输入
notes:
尽量避免使用全连接层,简单来说,卷积层特征更有泛化性。
- 全连接层的特征更接近输出结果
- 全连接层并没有新的信息
- 全连接层会失去特征在卷积层的位置信息
特征提取
说起来思路很简单,用部分已经训练好的模型将数据转为特征(特征提取),特征提取之后,再放到dnn中训练。
- 获取已经训练的模型的一部分
- 做特征提取——用部分模型predit
- 将predit结果输入新网络
下面实现之所以显得复杂,是因为其中很大部分是结合了数据生成的代码。
# 特征提取,其实就是运行部分模型得到权重
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = '/content/data/cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')
datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20
def extract_features(directory, sample_count): # 输入文件夹地址,还有采样数量
features = np.zeros(shape=(sample_count, 4, 4, 512))# 这个大小注意,需要查看模型输出的特征大小
labels = np.zeros(shape=(sample_count))
generator = datagen.flow_from_directory( # 从目标文件夹中进行采样
directory,
target_size=(150, 150),
batch_size=batch_size,
class_mode='binary')
i = 0
for inputs_batch, labels_batch in generator: # 使用conv_base对采样的数据集进行预测,得到所谓的特征
features_batch = conv_base.predict(inputs_batch)
features[i * batch_size : (i + 1) * batch_size] = features_batch
labels[i * batch_size : (i + 1) * batch_size] = labels_batch
i += 1
if i * batch_size >= sample_count:
break
return features, labels
train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)
# 模拟flatten,输入全连接层之前,需要对其进行维度调整
train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))
# 跑模型, 不需要再模型中体现, 只需要在fit中作为输入, 这是啥, 这的意思是, 其实我们用已经有的网络把数据集变成了特征.
# 用特征向量再进行deep learning
from keras import models
from keras import layers
from keras import optimizers
model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim = 4*4*512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer=optimizers.RMSprop(lr=2e-5),# 学习率???
loss = 'binary_crossentropy',
metrics=['acc'])
history = model.fit(train_features, train_labels,
epochs=30,
batch_size=20,
validation_data = (validation_features, validation_labels))
结果如下:
对以上结果分析:
- 特征提取的结果非常好。准确率直接从0.7飙升到0.9,注意,这里并没有使用数据增广。
- 其实按道理说,使用数据增广还是可能的,只是得准备两个生成器。
- 从图像来看,从一开始就过拟合,尽管dropout已经很大了。
- 这是因为,数据太少了!小图像数据集中的数据增广非常重要。
迁移学习二
就是比较常见的方式,取出部分模型,在后面添加上自己设计的输出层。
# 冻结
from keras import models
from keras import layers
model = models.Sequential()
conv_base.trainable = False
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
这是迁移学习+数据增广,结果相当好,val 从0.9直接到0.96+。
微调
微调的方式之一,就是解冻已有模型一部分层,和输出一起训练。相当于对后面相对抽象的特征进行进一步的训练。
# 微调, 微调后三层
conv_base.trainable = True
set_trainable = False
for layer in conv_base.layers:
if layer.name == 'block5_conv1':
set_trainable = True
if set_trainable:
layer.trainable = True
else:
layer.trainable = False
微调注意,
- 加入新的输出层后先进行训练,训练完再解冻后n层,再进行整体微调。
原因很简单,如果你加入初试输出层进行训练,那么输出层必定距离”正确“相差甚远,前面的层级会根据后面的错误进行调整(反向传播),那么前面的层级已经学到的特征就可能丢失。
为什么微调只对后几层进行比较好?作者给出了两个理由。
- 前面的层次的特征比较通用,后面的特征相对特殊,所以训练前面的层次回报不高。
- 训练越多参数,越容易过拟合。这个本身就是一个小数据集,训练太多参数,有非常严重的过拟合危险。
以下是训练结果。
可以看到,微调之后,区间基本集中在0.97+,也就是说,比起上个状态已经提升了1%。
看到这里有一个比较奇怪的地方,损失并没有减少,甚至明显升高,而准确率却比较平稳, 有时甚至还增加了。
作者的解释:
我们可视化的损失是每个epoch的平均损失,但是模型关心的实际上是损失的分布。
另外一个原因是,我们分类问题其实有一个阈值,比如二分类,大于阈值直接设定为1. 所以意味着,损失并不一定反映在准确率上。比如0.6升到0.8,对于准确率毫无影响,0.4到0.6却有影响。
总结
keras利用已训练模型进行特征提取,迁移和微调。
- 小数据集,基础卷积网络,val 0.7
- 数据增广,val 0.8
- 迁移学习方法一,特征提取,val 0.9
- 迁移学习方法二,迁移+数据增广,val 0.96
- 微调,训练,解冻,训练,val 0.97
粗调和微调,我感觉这个原理相当重要。
Reference
Main2