迁移学习和微调( 以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 个不可训练权重

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/

例子:

#应用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=...)

微调

最后一个可选步骤是微调,需要解冻上面获得的整个模型(或模型的一部分),然后在新数据上以极低的学习率对该模型进行重新训练(否则容易过拟合)。

例子:

# 解冻基础模型
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.首先获取数据集,划分训练集验证集测试集:

#取数据 划分数据集
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.标准化数据

这一步包括图像大小的调整,数据批处理,使用缓存和预提取、数据扩充等。

#将图像的大小调整为 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.构建模型

    在构建自己的新模型时,需要根据情况确定迁移学习方式(上文提到),这里为第一种。

   一般情况下,需要去掉基础模型的最后一层,这一层通常是为不同任务需要而设置的;另外,在基础模型的头部,根据情况是否添加归一化层,以将输入数据符合基础模型的要求。

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编译训练

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的均值和方差,请查看文章尾部的注意事项。


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

 

 

 

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alocus_

如果我的内容帮助到你,打赏我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值