目的:
使用 TensorFlow 实践去训练一个自己的 AI 模型,数据源:绿豆 绿豆 - 豆芽的过程(因为时间短)。
模型选择:
这个项目是对图像的特征来分析,所以我选择使用 CNN (卷积神经网络) ,这是专门用于处理图像,它通过卷积层自动提取图像中的特征。
EfficientNetV2B3 作为特征提取器
计划:最初使用预训练的 CNN 模型(EfficientNet 或 ResNET) 来提取图像特征,然后将提取的特征输入到分类模型中,让它作为"特征提取器",然后在提取的特征上训练一个简单的分类器。
代码过程:
# 预训练模型 EfficientNetv2b3 做为 “base_model"
# 先提取特征
features = base_model.predict(preprocessed_4k_image)
# 然后,在上面构建一个简单分类器
classifier = Sequential([
Dense(512, activation='relu'),
Dense(num_classes, activation='softmax') # num_classes = 植物种类数量
])
classifier.compile(...)
# 训练分类器
classifier.fit(features, labels)
EfficientNetV2B3 的优势:
基于我的个人观点
在 ImageNet 中排名比较好,平衡精度与效率,也跟预训练过有关。
高效性,如:使用混合缩放方法,模型的不同阶段以不同的方式缩放深度、宽度和分辨率。上一代是统一缩放方法。
我的数据集大小。
其它模型也有优点,模型选择取决于具体的任务需求、数据集特点和计算资源。
规划:
- num_classes(类别数):要将图像分成的不同类别的总数量,参考:绿豆-豆芽生长过程。 要区分绿豆,发芽初期,发芽中期,成熟豆芽,4个不同的生长阶段,这个值就是 4,多个冗余 定义为5。
- image_size = 600 使用 600x600 像素的图像, RGB通道。
- 只加载预训练的 EfficientNetV2B3 模型,不含顶层
- 使用在 ImageNet 数据集上预训练的权重
- 做为分类器,移除模型顶部的全连接层
- 模型保存在 Google Drive 里
构建模型 - 分类器构建:
1. 挂载 Google Drive
from google.colab import drive
drive.mount('/content/drive')
2. 导入必要的库
import tensorflow as tf
from tensorflow.keras.applications import EfficientNetV2B3
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout
from tensorflow.keras.models import Model
3. 模型创建和保存
# 参数设置
num_classes = 5 # 替换你的类别数量
image_size = 600 # EfficientNetV2B3 默认图片尺寸是 224
# 加载预训练的 EfficientNetV2B3 模型(不包含顶层)
base_model = EfficientNetV2B3(weights='imagenet', include_top=False, input_shape=(image_size, image_size, 3))
# 构建分类器
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(1024, activation='relu')(x)
x = Dropout(0.5)(x) # 添加 Dropout 层
predictions = Dense(num_classes, activation='softmax')(x)
# 创建完整模型
model = Model(inputs=base_model.input, outputs=predictions)
# 冻结 EfficientNetV2B3 的层 (初始训练阶段)
for layer in base_model.layers:
layer.trainable = False
# 编译模型
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# 保存模型结构和权重
save_path = '/content/drive/My Drive/Projects_AI/vPlants/efficientnetv2b3_model.keras'
model.save(save_path)
print(f"模型已保存至:{save_path}")
解释:
构建分类器:获取 EfficientNetV2B3 模型的输出特征图,对它进行全局平均池化。
Robustness (“鲁棒性”):
抗干扰的能力。指在一个系统、模型或算法中,面对各种不利条件或异常情况时,保持其性能稳定可靠的能力。
GlobalAveragePooling2D 工作原理:
在 CNN 中,卷积层会生成一系列的特征图,比如这个特征图是对纹理敏感,另一个对颜色敏感,另一对边缘敏感等等;GlobalAveragePooling2D 层计算每个特征图所有像素的平均值 (全局平均池化),分配一个单一的数值;GlobalAveragePooling2D 层的输出是一个向量,向量的每个元素对应于一个特征图的平均值。
GlobalAveragePooling2D 层的输出向量 = 特征图的通道数
举个例子:
EfficientNetV2B3 模型在某个卷积层之后,输出了:20x20x1280
- 20x20 代表特征图的高度和宽度
- 1280 代表特征图的通道数 = GlobalAveragePooling2D 层的输出是一个1280的向量
Dropou = 0.5 :
经验值:被丢弃的神经元数量和保留的神经元数量大致相等,使得网络的结构在每次迭代中具有最大的随机性,阻止了神经元之间的 co-adaptation (“协同适应”)。
这个值以后可以依据模型表现调整,如:过拟合增加这个值, 欠拟合减少这个值。 在不同的层调整来微调模型。 前者要重新训练模型,微调要只要重新加载模型。
添加输出层:
predictions = Dense(num_classes, activation='softmax')(x)
添加输出层,包含 num_classes (5) 个神经元,使用 Softmax 激活函数。
比装 RedHat 5 简单多了。
4. 加载模型
import tensorflow as tf
from tensorflow.keras.models import load_model
# 加载保存的模型
load_path = '/content/drive/My Drive/Projects_AI/vPlants/efficientnetv2b3_model.keras'
model = load_model(load_path)
# model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
我第一次加载模型后,得到报错:
/usr/local/lib/python3.11/dist-packages/keras/src/saving/saving_lib.py:757: UserWarning: Skipping variable loading for optimizer 'adam', because it has 10 variables whereas the saved optimizer has 2 variables.
saveable.load_own_variables(weights_store.get(inner_path))
保存模型时 Adam 优化器具有 2 个变量,而当前环境中的 Adam 优化器具有 10 个变量。
查过后,可能原因:
- tensorflow 或 Keras 版本不匹配
- Adam 优化器状态变量问题
解决方法:
- 忽略警告
- 重新编译模型,使用在注释的行
- 保存模型,只保存权重 ("save_weights"),加载时只加载权重。
重新编译模型对已输入数据的影响:
- 模型结构和权重不受影响
- 优化器状态会被重置,之前训练过程中积累的动量和方差,这些自适应学习信息会丢失。造成影响训练稳定性;优化器状态的丢失,会使得模型重新回到初始的训练状态。
- 训练数据与编译无关