
文章目录
-
- [1. 课前导读](#1. 课前导读)
-
- [1.1 本节课学习目标](#1.1 本节课学习目标)
- [1.2 知识重难点](#1.2 知识重难点)
- [1.3 学习前置条件](#1.3 学习前置条件)
- [1.4 学完可掌握能力](#1.4 学完可掌握能力)
- [1.5 行业应用场景](#1.5 行业应用场景)
- [2. 核心理论精讲](#2. 核心理论精讲)
-
- [2.1 `tf.keras.applications` 预训练模型库](#2.1
tf.keras.applications预训练模型库) - [2.2 微调的最佳实践](#2.2 微调的最佳实践)
- [2.3 防止过拟合的技巧](#2.3 防止过拟合的技巧)
- [2.4 TensorFlow Hub简介](#2.4 TensorFlow Hub简介)
- [2.1 `tf.keras.applications` 预训练模型库](#2.1
- [3. 环境搭建与工具配置](#3. 环境搭建与工具配置)
- [4. 代码实战教学](#4. 代码实战教学)
-
- [4.1 使用 EfficientNetB0 进行微调(猫狗分类)](#4.1 使用 EfficientNetB0 进行微调(猫狗分类))
- [4.2 使用 TensorFlow Hub 加载预训练模型(EfficientNet)](#4.2 使用 TensorFlow Hub 加载预训练模型(EfficientNet))
- [4.3 多分类案例:花朵识别(5类)](#4.3 多分类案例:花朵识别(5类))
- [5. 案例实操演练](#5. 案例实操演练)
-
- [5.1 数据集准备(模拟)](#5.1 数据集准备(模拟))
- [6. 常见坑点与排错总结](#6. 常见坑点与排错总结)
-
- [6.1 预处理不匹配](#6.1 预处理不匹配)
- [6.2 冻结与解冻策略](#6.2 冻结与解冻策略)
- [6.3 训练稳定性](#6.3 训练稳定性)
- [6.4 TensorFlow Hub坑点](#6.4 TensorFlow Hub坑点)
- [7. 知识点总结 + 课后作业](#7. 知识点总结 + 课后作业)
-
- [7.1 核心知识点梳理](#7.1 核心知识点梳理)
- [7.2 基础作业](#7.2 基础作业)
- [7.3 进阶实操作业](#7.3 进阶实操作业)
- [7.4 思考拓展题](#7.4 思考拓展题)
- [🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航](#🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航)
1. 课前导读
1.1 本节课学习目标
- 掌握使用
tf.keras.applications加载多种预训练模型(ResNet、EfficientNet、MobileNet等)。 - 理解TensorFlow Hub的作用,并能够从TF Hub加载预训练模型进行迁移学习。
- 熟练掌握微调训练的标准流程:加载预训练基、冻结、添加分类头、编译、训练分类头、解冻部分层、降低学习率继续训练。
- 学会为不同预训练模型设置正确的预处理函数。
- 能够评估迁移学习模型的性能,并与从头训练模型对比。
- 通过实践案例,掌握在小数据集上利用预训练模型达到高准确率的技巧。
1.2 知识重难点
| 类别 | 内容 |
|---|---|
| 重点 | tf.keras.applications中预训练模型的加载与参数配置;微调的分阶段训练策略;TensorFlow Hub的使用 |
| 难点 | 不同预训练模型的输入尺寸与预处理差异;微调时学习率的选择与层解冻策略;避免过拟合(小数据集+增强) |
| 易混淆点 | weights='imagenet'与weights=None的区别;include_top=False与全局池化的配合;TF Hub中模型签名的处理 |
1.3 学习前置条件
- 已完成第33课,掌握迁移学习的基本原理。
- 熟悉TensorFlow的数据加载和模型训练流程。
- 能够使用
ImageDataGenerator或tf.data加载图像数据。
1.4 学完可掌握能力
- 独立使用官方预训练模型进行图像分类任务的迁移学习。
- 根据任务和数据量选择合适的预训练模型和微调策略。
- 利用TensorFlow Hub快速获取最新预训练模型(如EfficientNet、BERT)。
- 解决微调过程中的过拟合和训练不稳定问题。
1.5 行业应用场景
- 医疗影像分类:利用在ImageNet上预训练的模型微调肺结节分类。
- 遥感图像分析:使用预训练模型识别卫星图像中的建筑物。
- 缺陷检测:工业产品表面缺陷分类。
- 自然语言处理:使用BERT预训练模型进行情感分类、命名实体识别。
2. 核心理论精讲
2.1 tf.keras.applications 预训练模型库
TensorFlow内置了多种在ImageNet上预训练的模型,包括:
- VGG16/VGG19:经典但参数量大(138M),速度慢。
- ResNet50/101/152:引入残差连接,支持更深网络。
- InceptionV3/V4:使用Inception模块,计算高效。
- MobileNetV1/V2/V3:轻量级,适合移动端。
- EfficientNetB0-B7:基于神经架构搜索,精度和速度平衡最佳。
每个模型可通过tf.keras.applications.{model_name}加载,常用参数:
weights:'imagenet'(预训练权重)或None(随机初始化)。include_top:是否包含顶部的全连接分类器(ImageNet的1000类)。迁移学习时通常设为False。input_shape:可选输入尺寸,需与模型兼容。
2.2 微调的最佳实践
根据目标数据集大小和与ImageNet的相似度,建议采用以下流程:
- 数据准备 :确保数据预处理与预训练模型要求一致(尺寸、归一化方式)。使用模型的
preprocess_input函数。 - 加载预训练基 :设置
include_top=False,weights='imagenet'。 - 冻结预训练基 :
base_model.trainable = False。 - 添加分类头 :通常包含全局池化(
GlobalAveragePooling2D)、Dropout、全连接层(输出类别数)。 - 第一阶段训练(特征提取):只训练分类头,使用中等学习率(如0.001),训练若干轮(10-20)。
- 解冻部分顶层 :设置
base_model.trainable = True,并选择性解冻最后几层(如最后2-3个卷积块)。 - 第二阶段训练(微调):使用非常小的学习率(如1e-5),继续训练10-20轮,同时可降低批次大小。
为什么分阶段:如果一开始就解冻所有层,随机初始化的分类头会产生较大梯度,破坏预训练特征。
2.3 防止过拟合的技巧
小数据集迁移学习时,过拟合风险高。可采取:
- 大幅数据增强(旋转、翻转、缩放、亮度调整)。
- Dropout(在分类头中,率0.2~0.5)。
- 早停(EarlyStopping)。
- L2正则化(在全连接层)。
- 减少微调的层数或降低学习率。
2.4 TensorFlow Hub简介
TensorFlow Hub是一个预训练模型库,包含更多模型(如EfficientNet、BERT、Universal Sentence Encoder)。使用方式:
python
import tensorflow_hub as hub
model = tf.keras.Sequential([
hub.KerasLayer("https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/classification/5", trainable=False),
layers.Dense(num_classes, activation='softmax')
])
TF Hub模型通常已经包含预处理(归一化),但需确认输入范围。
3. 环境搭建与工具配置
沿用第33课环境,额外安装tensorflow-hub。
bash
conda activate tf213
pip install tensorflow-hub
下载示例数据集(猫狗分类、花朵分类)。可用TensorFlow内置的tf.keras.utils.get_file或手动下载。
4. 代码实战教学
4.1 使用 EfficientNetB0 进行微调(猫狗分类)
python
import tensorflow as tf
from tensorflow.keras import layers, models, optimizers, callbacks
from tensorflow.keras.applications import EfficientNetB0
from tensorflow.keras.applications.efficientnet import preprocess_input
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import matplotlib.pyplot as plt
import os
# 设置路径
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')
train_dir = os.path.join(PATH, 'train')
val_dir = os.path.join(PATH, 'validation')
# 数据增强与预处理
train_datagen = ImageDataGenerator(
preprocessing_function=preprocess_input,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
val_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)
batch_size = 32
img_size = 224 # EfficientNetB0 默认输入224
train_generator = train_datagen.flow_from_directory(
train_dir, target_size=(img_size, img_size), batch_size=batch_size, class_mode='binary')
val_generator = val_datagen.flow_from_directory(
val_dir, target_size=(img_size, img_size), batch_size=batch_size, class_mode='binary')
# 加载预训练基(不包含顶层)
base_model = EfficientNetB0(weights='imagenet', include_top=False, input_shape=(img_size, img_size, 3))
base_model.trainable = False # 冻结
# 构建模型
inputs = tf.keras.Input(shape=(img_size, img_size, 3))
x = base_model(inputs, training=False)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.2)(x)
outputs = layers.Dense(1, activation='sigmoid')(x)
model = tf.keras.Model(inputs, outputs)
model.compile(optimizer=optimizers.Adam(0.001), loss='binary_crossentropy', metrics=['accuracy'])
# 第一阶段:特征提取
early_stop = callbacks.EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
history1 = model.fit(train_generator, epochs=10, validation_data=val_generator, callbacks=[early_stop], verbose=1)
# 第二阶段:微调(解冻顶层)
base_model.trainable = True
# 冻结除最后50层之外的所有层(EfficientNetB0约237层,解冻最后50层)
for layer in base_model.layers[:-50]:
layer.trainable = False
# 重新编译,使用更小学习率
model.compile(optimizer=optimizers.Adam(1e-5), loss='binary_crossentropy', metrics=['accuracy'])
history2 = model.fit(train_generator, epochs=10, validation_data=val_generator, callbacks=[early_stop], verbose=1)
# 最终评估
val_loss, val_acc = model.evaluate(val_generator)
print(f"Validation accuracy after fine-tuning: {val_acc:.4f}")
# 绘制训练曲线
acc = history1.history['accuracy'] + history2.history['accuracy']
val_acc = history1.history['val_accuracy'] + history2.history['val_accuracy']
plt.plot(acc, label='Train acc')
plt.plot(val_acc, label='Val acc')
plt.axvline(x=len(history1.history['accuracy']), color='r', linestyle='--', label='Start fine-tune')
plt.legend()
plt.title('Transfer Learning with EfficientNetB0')
plt.show()
4.2 使用 TensorFlow Hub 加载预训练模型(EfficientNet)
python
import tensorflow_hub as hub
# 加载TF Hub模型(特征提取版本,不包含分类头)
feature_extractor_url = "https://tfhub.dev/tensorflow/efficientnet/b0/classification/1"
# 或者使用特征向量版本(无分类头)
feature_vector_url = "https://tfhub.dev/tensorflow/efficientnet/b0/feature-vector/1"
# 创建模型
def build_model_from_hub(num_classes):
model = tf.keras.Sequential([
hub.KerasLayer(feature_vector_url, trainable=True, input_shape=(224,224,3)),
layers.Dense(num_classes, activation='softmax')
])
return model
model_hub = build_model_from_hub(2)
model_hub.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 注意:TF Hub模型通常已经包含预处理(将输入从[0,255]映射到[-1,1]),但需确认。
# 使用时直接传入原始像素值(0-255)即可,无需额外预处理。
# 训练代码同上(需调整生成器的class_mode为'sparse')
4.3 多分类案例:花朵识别(5类)
使用TensorFlow内置的花朵数据集(来自tf.keras.preprocessing.image_dataset_from_directory)。
python
import pathlib
dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file(origin=dataset_url, fname='flower_photos', extract=True)
data_dir = pathlib.Path(data_dir).parent / 'flower_photos'
batch_size = 32
img_height = 224
img_width = 224
# 使用image_dataset_from_directory
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
data_dir, validation_split=0.2, subset='training', seed=123,
image_size=(img_height, img_width), batch_size=batch_size)
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
data_dir, validation_split=0.2, subset='validation', seed=123,
image_size=(img_height, img_width), batch_size=batch_size)
class_names = train_ds.class_names
num_classes = len(class_names)
# 应用预处理(ResNet50的预处理)
from tensorflow.keras.applications.resnet50 import preprocess_input
def preprocess(image, label):
image = tf.cast(image, tf.float32)
image = preprocess_input(image)
return image, label
train_ds = train_ds.map(preprocess).cache().prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.map(preprocess).cache().prefetch(tf.data.AUTOTUNE)
# 加载ResNet50
base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False, input_shape=(224,224,3))
base_model.trainable = False
inputs = tf.keras.Input(shape=(224,224,3))
x = base_model(inputs, training=False)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.2)(x)
outputs = layers.Dense(num_classes, activation='softmax')(x)
model_resnet = tf.keras.Model(inputs, outputs)
model_resnet.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 特征提取阶段
history = model_resnet.fit(train_ds, validation_data=val_ds, epochs=15, verbose=1)
# 微调:解冻最后3个卷积块(ResNet50的卷积块名称: conv2_block, conv3_block, conv4_block, conv5_block)
base_model.trainable = True
for layer in base_model.layers:
if 'conv5_block' in layer.name or 'conv4_block' in layer.name:
layer.trainable = True
else:
layer.trainable = False
model_resnet.compile(optimizer=tf.keras.optimizers.Adam(1e-5),
loss='sparse_categorical_crossentropy', metrics=['accuracy'])
history_finetune = model_resnet.fit(train_ds, validation_data=val_ds, epochs=10, verbose=1)
val_loss, val_acc = model_resnet.evaluate(val_ds)
print(f"Flower classification accuracy: {val_acc:.4f}")
5. 案例实操演练
案例:使用EfficientNetB3进行细粒度图像分类(狗品种识别)
使用Stanford Dogs数据集(120个品种),演示迁移学习在细粒度分类中的效果。
5.1 数据集准备(模拟)
由于完整数据集较大,我们模拟数据集结构并演示代码框架。
python
# 假设数据目录结构: dogs/train/breed1, dogs/val/breed1, ...
# 使用tf.keras.preprocessing.image_dataset_from_directory加载
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
'dogs/train', image_size=(300,300), batch_size=32, label_mode='categorical')
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
'dogs/val', image_size=(300,300), batch_size=32, label_mode='categorical')
# 使用EfficientNetB3(输入尺寸300x300)
from tensorflow.keras.applications import EfficientNetB3
from tensorflow.keras.applications.efficientnet import preprocess_input
def preprocess(image, label):
image = tf.cast(image, tf.float32)
image = preprocess_input(image)
return image, label
train_ds = train_ds.map(preprocess).cache().prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.map(preprocess).cache().prefetch(tf.data.AUTOTUNE)
base_model = EfficientNetB3(weights='imagenet', include_top=False, input_shape=(300,300,3))
base_model.trainable = False
inputs = tf.keras.Input(shape=(300,300,3))
x = base_model(inputs, training=False)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.3)(x)
outputs = layers.Dense(120, activation='softmax')(x)
model = tf.keras.Model(inputs, outputs)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# 训练
model.fit(train_ds, validation_data=val_ds, epochs=20)
# 微调
base_model.trainable = True
for layer in base_model.layers[:-30]:
layer.trainable = False
model.compile(optimizer=tf.keras.optimizers.Adam(1e-5), loss='categorical_crossentropy', metrics=['accuracy'])
model.fit(train_ds, validation_data=val_ds, epochs=10)
6. 常见坑点与排错总结
6.1 预处理不匹配
-
坑1 :使用
preprocess_input时,有些模型要求输入为0,255的像素值,有些要求-1,1或0,1。EfficientNet的preprocess_input将输入从0,255转换为-1,1。- 解决:查看文档,确保数据生成器不额外除以255。
-
坑2:输入尺寸与模型不匹配,例如ResNet50要求至少32×32,但最佳224×224。
- 解决 :使用
target_size调整。
- 解决 :使用
6.2 冻结与解冻策略
-
坑3 :忘记在特征提取阶段将
base_model的trainable=False,导致预训练权重被破坏。 -
坑4:微调时学习率过高导致损失震荡。
- 解决:从1e-5开始,若震荡则降低。
-
坑5:解冻的层数过多,小数据集过拟合。
- 策略:数据量<1000时,只解冻最后2-3层;数据量>5000时可解冻更多。
6.3 训练稳定性
- 坑6 :BatchNormalization层在
training=False和training=True下的行为不同。特征提取阶段应设为False,微调阶段可设为True。 - 坑7 :使用
ImageDataGenerator的flow_from_directory时,class_mode应匹配分类头输出(二分类用binary,多分类用categorical或sparse)。
6.4 TensorFlow Hub坑点
- 坑8:TF Hub模型可能已包含Flatten或分类头,需确认是否需要添加自己的层。
- 坑9 :从TF Hub加载的模型默认不可训练(
trainable=False),需设置trainable=True才能微调。
7. 知识点总结 + 课后作业
7.1 核心知识点梳理
- 预训练模型库 :
tf.keras.applications支持VGG、ResNet、EfficientNet等;TensorFlow Hub提供更丰富模型。 - 迁移学习流程 :加载预训练基(
include_top=False)→ 冻结 → 添加分类头 → 训练分类头 → 解冻顶层 → 低学习率微调。 - 数据预处理 :使用对应模型的
preprocess_input,保证输入范围正确。 - 防止过拟合:数据增强、Dropout、早停、分层微调。
7.2 基础作业
- 使用MobileNetV2作为预训练模型,在猫狗数据集上进行特征提取,报告验证准确率。
- 修改EfficientNetB0的微调解冻层数(解冻最后20层、50层、全部层),比较最终准确率。
- 在花朵数据集上,不使用数据增强,重复微调实验,观察过拟合现象。
7.3 进阶实操作业
任务:在CIFAR-10上使用EfficientNetB0进行迁移学习并对比
要求:
- 由于CIFAR-10图像尺寸小(32×32),需调整输入尺寸或使用
layers.Resizing上采样到224×224。 - 比较三种方法:从头训练小CNN、特征提取(冻结)、微调。
- 分析领域差异对迁移效果的影响(自然图像 vs 小尺寸物体)。
7.4 思考拓展题
-
如果你的目标数据集与ImageNet的类别完全不相关(例如医学MRI图像),迁移学习还有效吗?如何改进?
-
在微调阶段,为什么通常解冻靠近输出的层,而不是靠近输入的层?请从特征通用性角度解释。
-
当目标数据集非常大(如百万级)且与ImageNet差异大时,从头训练与迁移学习哪个更优?为什么?
下一课预告:模型轻量化优化------我们将学习量化、剪枝、蒸馏等模型压缩技术,帮助你在资源受限设备上部署深度学习模型。
🔗《TensorFlow2.x: 深度学习入门到高阶实战教程》系列课程导航
第一部分:基础入门(1-10 课)
第二部分:神经网络核心(11-25 课)
第三部分:进阶网络与框架高阶(26-40 课)
第四部分:企业实战与项目落地(41-50 课)
🌟 感谢您耐心阅读到这里!💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~