目录
简介
冻结层
迁移学习工作流
微调
参考
注意事项
简介
当前目标的数据集数据太少,无法训练一个有效的模型时,可以使用迁移学习。因为一般情况下,在某种大型数据集上训练出来的模型,含有大量的特征,这些特征可用于类似的新问题。深度学习中迁移学习的一般流程:
1从某训练好的模型中获取层并冻结
2在其顶部添加一些可训练的层,这些层能够将模型适应你的新的数据集
3训练你添加的新层
4最后可选地进行微调(微调会对整体模型进一步训练)
下面以tensorflow keras为例进一步说明。
冻结层
层和模型具有布尔特性 trainable
。此特性的值可以更改。将 layer.trainable
设置为 False
会将层的所有权重从可训练移至不可训练。这一过程称为“冻结”层:已冻结层的状态在训练期间不会更新(无论是使用 fit()
进行训练,还是使用依赖于 trainable_weights
来应用梯度更新的任何自定义循环进行训练时)。一般而言,所有权重都是可训练权重。唯一具有不可训练权重的内置层是 BatchNormalization
层(参考文章末尾的链接)。另外注意,如果在模型或具有子层的任何层上设置 trainable = False
,则所有子层也将变为不可训练。
如下代码所示:
Dense 层具有 2 个可训练权重(内核与偏差)在训练期间,它使用不可训练权重跟踪其输入的平均值和方差。
BatchNormalization 层具有 2 个可训练权重和 2 个不可训练权重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import numpy as np import tensorflow as tf from tensorflow import keras #Dense 层具有 2 个可训练权重(内核与偏差)在训练期间,它使用不可训练权重跟踪其输入的平均值和方差。 layer = keras.layers.Dense(3) layer.build((None, 4)) # Create the weights print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights)) #BatchNormalization 层具有 2 个可训练权重和 2 个不可训练权重 layer = keras.layers.BatchNormalization() layer.build((None, 4)) # Create the weights print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights)) #实施冻结 layer = keras.layers.Dense(3) layer.build((None, 4)) # Create the weights layer.trainable = False # Freeze the layer print("weights:", len(layer.weights)) print("trainable_weights:", len(layer.trainable_weights)) print("non_trainable_weights:", len(layer.non_trainable_weights))
迁移学习
迁移学习一般有两种:
一个是将基础模型的所有层冻结后,将其中的某些层的输出嵌入到你的新模型中,最后在你的数据集中进行训练。
另外一个是将基础模型的所有层冻结后,输入你的数据,将某些层的输出(即特征)作为你的新模型的输入,最后在你的数据集中进行训练。
keras中可用模型见https://keras.io/api/applications/
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#应用Xception模型 base_model = keras.applications.Xception( weights='imagenet', # Load weights pre-trained on ImageNet. input_shape=(150, 150, 3), include_top=False) # Do not include the ImageNet classifier at the top. #冻结模型 base_model.trainable = False inputs = keras.Input(shape=(150, 150, 3)) #通过training=False确保基础模型在推断模式下运行。否则就失去了迁移学习和微调的意义 x = base_model(inputs, training=False) #添加的你的新模型 x = keras.layers.GlobalAveragePooling2D()(x) outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs) #编译并拟合模型 model.compile(optimizer=keras.optimizers.Adam(), loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()]) model.fit(new_dataset, epochs=20, callbacks=..., validation_data=...)
微调
最后一个可选步骤是微调,需要解冻上面获得的整个模型(或模型的一部分),然后在新数据上以极低的学习率对该模型进行重新训练(否则容易过拟合)。
例子:
1
2
3
4
5
6
7
8
9
10# 解冻基础模型 base_model.trainable = True #记得重新编译你的模型,且以极低的学习率进行拟合 model.compile(optimizer=keras.optimizers.Adam(1e-5), # Very low learning rate loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()]) #端到端进行训练,及时停止训练,防止过拟合 model.fit(new_dataset, epochs=10, callbacks=..., validation_data=...)
一个完整可复现的端到端的例子:
我们加载在 ImageNet 上预训练的 Xception 模型,并将其用于 Kaggle Dogs vs. Cats 分类数据集:(我的环境为最新版tensorflow2.4)
1.首先获取数据集,划分训练集验证集测试集:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#取数据 划分数据集 import tensorflow_datasets as tfds import tensorflow as tf tfds.disable_progress_bar() train_ds, validation_ds, test_ds = tfds.load( "cats_vs_dogs", # Reserve 10% for validation and 10% for test split=["train[:40%]", "train[40%:50%]", "train[50%:60%]"], as_supervised=True, # Include labels ) print("Number of training samples: %d" % tf.data.experimental.cardinality(train_ds)) print( "Number of validation samples: %d" % tf.data.experimental.cardinality(validation_ds)) print("Number of test samples: %d" % tf.data.experimental.cardinality(test_ds))
2.标准化数据
这一步包括图像大小的调整,数据批处理,使用缓存和预提取、数据扩充等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#将图像的大小调整为 150x150: size = (150, 150) train_ds = train_ds.map(lambda x, y: (tf.image.resize(x, size), y)) validation_ds = validation_ds.map(lambda x, y: (tf.image.resize(x, size), y)) test_ds = test_ds.map(lambda x, y: (tf.image.resize(x, size), y)) #对数据进行批处理并使用缓存和预提取来优化加载速度 batch_size = 32 train_ds = train_ds.cache().batch(batch_size).prefetch(buffer_size=10) validation_ds = validation_ds.cache().batch(batch_size).prefetch(buffer_size=10) test_ds = test_ds.cache().batch(batch_size).prefetch(buffer_size=10) #使用随机数据扩充,数据增强,增添数据 对称旋转等 from tensorflow import keras from tensorflow.keras import layers data_augmentation = keras.Sequential( [ layers.experimental.preprocessing.RandomFlip("horizontal"), layers.experimental.preprocessing.RandomRotation(0.1), ] )
3.构建模型
在构建自己的新模型时,需要根据情况确定迁移学习方式(上文提到),这里为第一种。
一般情况下,需要去掉基础模型的最后一层,这一层通常是为不同任务需要而设置的;另外,在基础模型的头部,根据情况是否添加归一化层,以将输入数据符合基础模型的要求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32base_model = keras.applications.Xception( weights="imagenet", # Load weights pre-trained on ImageNet. input_shape=(150, 150, 3), include_top=False, ) # 去掉基础模型的最后一层用于imagenet的分类器,并在后面添加你的新模型 #冻结基础模型 base_model.trainable = False # 开始创建新模型 inputs = keras.Input(shape=(150, 150, 3)) x = data_augmentation(inputs) # Apply random data augmentation #由于Xception模型要求输入不是(0,255),而是(-1,1)。 #添加 Normalization 层以将输入值(最初在 [0, 255] 范围内)缩放到 [-1, 1] 范围。 #即outputs = (inputs - mean) / sqrt(var) norm_layer = keras.layers.experimental.preprocessing.Normalization() mean = np.array([127.5] * 3) var = mean ** 2 x = norm_layer(x) norm_layer.set_weights([mean, var]) # 基础模型中包含batchnorm层,冻结它以使其在推理模式下运行(值不变) #在微调阶段进行解冻,再对模型以极低的学习率进行训练 x = base_model(x, training=False) x = keras.layers.GlobalAveragePooling2D()(x) x = keras.layers.Dropout(0.2)(x) # Regularize with dropout outputs = keras.layers.Dense(1)(x) model = keras.Model(inputs, outputs) model.summary()
4编译训练
1
2
3
4
5
6
7
8model.compile( optimizer=keras.optimizers.Adam(), loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()], ) epochs = 20 model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
5微调
注意这里的微调,官方的具体解释为:
重要的是,尽管基础模型变得可训练,但在构建模型过程中,由于我们在调用该模型时传递了 training=False
,因此它仍在推断模式下运行。这意味着内部的批次归一化层不会更新其批次统计信息。如果它们更新了这些统计信息,则会破坏该模型到目前为止所学习的表示。这里面批归一化层中Batch的均值和方差,请查看文章尾部的注意事项。
1
2
3
4
5
6
7
8
9
10
11
12base_model.trainable = True model.summary() model.compile( optimizer=keras.optimizers.Adam(1e-5), # 低学习率 loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()], ) epochs = 10 model.fit(train_ds, epochs=epochs, validation_data=validation_ds)
为节约时间。迭代了2次,准确率提升可观。
参考资料
tensorflow官方文档
kaggle
注意事项
(以下对理解BN层冻结问题十分重要,建议浏览一遍)
K.learning_phase() 和 training=False/True ,BN层和Dropout层在迁移学习中冻结(Frozen)的注意事项:
中文讲解:
https://zhuanlan.zhihu.com/p/56225304
英文原文详解(英文好的看原版)
http://blog.datumbox.com/the-batch-normalization-layer-of-keras-is-broken/#comment-22015
最后
以上就是文艺黑夜最近收集整理的关于迁移学习和微调( 以Xception为基础模型在Kaggle Dogs vs. Cats 猫狗分类问题上为例) 简介冻结层迁移学习微调一个完整可复现的端到端的例子:参考资料注意事项的全部内容,更多相关迁移学习和微调(内容请搜索靠谱客的其他文章。
发表评论 取消回复