介绍
使用MobileNetV3(预训练好的)在120-breedDog数据集迁移学习,再微调。
迁移学习流程:去头,冻结权重,增加新层和分类头,在目的数据上进行新增层(包含分类头)的训练,再进行微调。
理解:原有权重已经学会了很多关于图像的基础知识,新训练的层只是告诉model,具体应用分类中哪些组合是是什么。
这就好像已经参加it工作的应届生,会写代码,但是不懂公司内部的具体业务,我们冻结权重(毕业了)+训练新层(入职培训)。
代码
去分类头、冻结参数、增加新层(包含分类头)、训练新层(包含分类头)
# 1.构建base模型
base_model = tf.keras.applications.MobileNetV3Large(
input_shape=(IMG_SIZE, IMG_SIZE, 3),
include_top=False,#去分类头(原有1000种分类)
weights='imagenet',
minimalistic=False # 标准版,包含高级激活函数,精度更高
)
base_model.trainable = False # 冻结,模型非新增层部分不参与训练
# 2.增加新层(包含匹配当前任务的分类头)
model = tf.keras.Sequential([
base_model,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dropout(0.2), # 防止过拟合
tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
])
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), # 初始大学习率
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 3.训练 Epochs
history_phase1 = model.fit(
train_ds,
validation_data=val_ds,
epochs=10 # 快速训练头部
)
微调
解冻基准模型(训练的梯度可以传递到基准模型来了),加入极小的学习率(原有的权重不宜大幅度修改,毕竟微调嘛)。
如果学习率太大(比如 $0.001$),一步迈得太快,就会瞬间把这些精细的特征提取能力冲垮,导致模型“失忆”。
目的:让基准模型更关注当前数据集的微小细节。
用术语来说,微调阶段完成以下两种任务:
| 任务 | 描述 |
|---|---|
| 特征对齐 | 让预训练的卷积核微调其参数,使其产生的特征图(Feature Map)能更清晰地传达给你新加的分类头。 |
| 适应特定分布 | 比如你的数据是在特殊光照、特定医疗设备(如你研究的 WSI 图像)下拍摄的,微调能让模型学会过滤掉这些特定环境带来的干扰。 |
base_model.trainable = True
# 保持底层的 BatchNorm 层冻结(非常重要,否则会破坏预训练特征的统计分布)
# 在 TF2 MobileNet 中,通常只要 set trainable=True,BN层的 inference mode 需注意
# 简单的做法是全解冻,但 LR 要极小
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5), # 极小学习率
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
history_fine = model.fit(
train_ds,
validation_data=val_ds,
epochs=20, # 根据 Val Loss 决定是否停止
callbacks=[
#只要验证集损失(Val Loss)连续 3 个 Epoch 不下降,就立刻停止训练并回退到表现最好的那个版本。这能完美防止过拟合。
tf.keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True),
#如果发现精度不再提升,就把学习率再打 2 折(变成 2e-6)。这就像是在登山接近顶峰时,步子迈得更小、更稳
tf.keras.callbacks.ReduceLROnPlateau(factor=0.2, patience=2)
]
)
整体代码
#对上面的代码进行总结,此代码就是完成的迁移学习:预训练+微调
import tensorflow as tf
# ===========================
# 1. 定义自动保存机制 (关键补充)
# ===========================
checkpoint_cb = tf.keras.callbacks.ModelCheckpoint(
filepath='best_dog_model.h5', # 保存的文件名
save_best_only=True, # True = 只保存最好的模型;False = 每轮都覆盖
monitor='val_accuracy', # 监控指标:验证集准确率
mode='max', # 准确率越高越好
verbose=1 # 打印保存日志
)
# 其他回调
early_stopping_cb = tf.keras.callbacks.EarlyStopping(
patience=5, restore_best_weights=True, monitor='val_loss'
)
reduce_lr_cb = tf.keras.callbacks.ReduceLROnPlateau(
factor=0.2, patience=2, monitor='val_loss'
)
# ===========================
# 2. 构建与编译模型
# ===========================
base_model = tf.keras.applications.MobileNetV3Large(
input_shape=(IMG_SIZE, IMG_SIZE, 3),
include_top=False,
weights='imagenet',
minimalistic=False
)
base_model.trainable = False # 冻结
model = tf.keras.Sequential([
base_model,
tf.keras.layers.GlobalAveragePooling2D(),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
])
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# ===========================
# 3. 第一阶段:冻结训练 (必须加入 callbacks)
# ===========================
print("🔥 [Phase 1] Start Training Head...")
history_phase1 = model.fit(
train_ds,
validation_data=val_ds,
epochs=10,
callbacks=[checkpoint_cb] # <--- 这里加上!
)
# ===========================
# 4. 第二阶段:微调训练
# ===========================
print("🧊 [Phase 2] Start Fine-tuning...")
base_model.trainable = True
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5), # 极小学习率
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
history_fine = model.fit(
train_ds,
validation_data=val_ds,
initial_epoch=history_phase1.epoch[-1] + 1, # 接续之前的轮次
epochs=30, # 总轮次
callbacks=[checkpoint_cb, early_stopping_cb, reduce_lr_cb] # <--- 这里也要加上!
)
# ===========================
# 5. (可选) 手动保存最终版
# ===========================
# 如果你想不管效果好坏,把最后一步训练完的模型也存一份:
model.save('final_dog_model.h5')
print("✅ 训练结束,最佳模型已保存为 best_dog_model.h5")
过程
服务器
客户端
实践中遇到的问题
训练好的模型部署到客户端,效果差异巨大
原因:训练预处理和部署时预处理不一致(客户端推理有问题)。
具体分析:
TensorFlow 的 Resize:TF 的
tf.image.resize(尤其是旧版本或默认配置)在处理像素坐标偏移(Coordinate Offset)时,与 OpenCV 或 PIL 的实现逻辑不同(例如half_pixel_centers的取值)。解决方案:
推理侧对齐:
在推理代码中,复刻训练时的 TF 逻辑。具体是在包装模型时,内置 resize(与训练完全一致的 bilinear, antialias=False)
理论中的问题
为什么不直接解冻、加头、然后一步到位开始训练呢?
新加的层和分类头的权重是随机的,如此时解冻基准模型,则计算梯度,会对原有预训练权重造成大的波动。
其次,让新加入的层理解基准模型提供的特征,eg:基准模型输出1280维视觉信号,新加入的层要正确的解读这些信号,来映射到分类上。说白了就是新增的层执行的是翻译的逻辑。
⚠️注意:等到它能给出 70% 或 80% 的准确率时,接下来的微调才是有意义的(会比较温和),这里也不必关注具体数值,下面给出两个参考。
新层的训练进入了瓶颈,即
Val Accuracy不再上升,Val Loss也不再下降时,说明新层已经把基座现有的特征理解到位了。准确率稳住不动。
后续
扩展猫猫分类,现有数据集Kaggle Cat Breeds;
对与新增分类,可采取在现有狗狗分类最佳权重的基础上进行迁移学习,在新数据集(狗+猫)上进行。
若分类的物种持续增多?
提出
A. 层次化分类 (Hierarchical Classification)
B. 向量检索架构 (Metric Learning / Embedding)
C. 持续学习 (Continual Learning / Incremental Learning)
| 类别规模 | 推荐方案 | 核心技术点 | 适用场景 |
|---|---|---|---|
| < 200 类 | 合并微调 (方案一) | 加权采样 (Weighted Sampler)、微调 | 你的 Android 比赛、小型 App |
| 200 - 1000 类 | 分阶段微调 | 知识蒸馏、学习率衰减 | 中型商业识别软件 |
| > 1000 类 | 特征向量检索 | Triplet Loss、向量数据库 (Faiss) | 拍照识花、万物识别、人脸库 |
