我是靠谱客的博主 文艺黑夜,这篇文章主要介绍迁移学习和微调( 以Xception为基础模型在Kaggle Dogs vs. Cats 猫狗分类问题上为例) 简介冻结层迁移学习微调一个完整可复现的端到端的例子:参考资料注意事项,现在分享给大家,希望可以做个参考。

 

目录

简介

冻结层

迁移学习工作流

微调

参考

注意事项


简介

当前目标的数据集数据太少,无法训练一个有效的模型时,可以使用迁移学习。因为一般情况下,在某种大型数据集上训练出来的模型,含有大量的特征,这些特征可用于类似的新问题。深度学习中迁移学习的一般流程:

   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
25
import 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
32
base_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
8
model.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
12
base_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 猫狗分类问题上为例) 简介冻结层迁移学习微调一个完整可复现的端到端的例子:参考资料注意事项的全部内容,更多相关迁移学习和微调(内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部