使用Keras CV微调DeepLabV3Plus语义分割模型的精度与警告问题
问题解决:DeepLabV3Plus微调精度无提升及形状警告修复
问题背景
使用Open Images V7数据集(通过FiftyOne访问),基于KerasCV库以MobileNetV3Small为骨干网络微调DeepLabV3Plus模型时,出现以下问题:
- 训练过程中训练集精度完全固定不变,验证集精度也无任何提升
- 训练时弹出多个形状相关警告
- 提示数据集提前耗尽,中断训练流程
错误分析
从输出日志和代码逻辑来看,核心问题集中在以下几点:
- 单通道误用Softmax激活:当
num_classes=1时,KerasCV的DeepLabV3Plus默认使用Softmax激活,但单通道的Softmax输出恒为1,完全无法学习有效特征,这是精度不提升的核心原因。 - 输入结构不匹配:模型预期输入为命名Keras张量结构,但数据集输出是普通张量,导致框架抛出结构不匹配警告。
- 数据集提前耗尽:训练集的batch数量不足,每个epoch仅迭代13次就耗尽数据,且未设置重复生成,影响训练稳定性。
- 图像未归一化:输入图像未做0-1范围归一化,而MobileNetV3的预训练权重基于归一化数据训练,特征分布不匹配会严重阻碍模型收敛。
- Mask处理隐患:遍历检测框取第一个车牌mask的逻辑可能遗漏无车牌样本,且mask未确保严格二值化(0/1),会干扰损失计算。
修复步骤
- 替换激活函数为Sigmoid:二分类分割任务中,单通道输出应使用Sigmoid激活,而非Softmax。
- 添加图像归一化:将输入图像除以255,缩放到0-1范围,匹配预训练权重的输入分布。
- 修复数据集输入结构:调整模型初始化逻辑,使其兼容普通张量输入,避免结构不匹配警告。
- 设置数据集重复:在数据集后添加
.repeat(),避免每个epoch提前耗尽数据,同时指定steps_per_epoch控制迭代次数。 - 确保Mask二值化:处理mask时将值严格转为0(背景)和1(目标),避免模糊值影响损失计算。
- 优化Mask提取逻辑:处理无车牌的样本,避免报错或返回无效mask。
修改后的完整代码
数据集准备代码
import tensorflow as tf import numpy as np def preprocess_sample(sample): # 图像读取与预处理 img = tf.io.read_file(sample["filepath"]) img = tf.image.decode_jpeg(img, channels=3) img.set_shape([None, None, 3]) img = tf.image.resize(img, (512, 512)) img = img / 255.0 # 必须添加归一化,匹配预训练权重的输入分布 # Mask提取与二值化处理 mask = np.zeros((img.shape[0], img.shape[1]), dtype=np.float32) for detection in sample.ground_truth.detections: if detection.label == 'Vehicle registration plate': mask = detection.mask.astype(np.float32) # 确保Mask严格二值化 mask = np.where(mask > 0.5, 1.0, 0.0) break mask = tf.expand_dims(mask, axis=-1) mask.set_shape([None, None, 1]) mask = tf.image.resize(mask, (512, 512), method="nearest") return img, mask # 转换为TF Dataset tf_train_dataset = tf.data.Dataset.from_generator( lambda: (preprocess_sample(s) for s in train_dataset), output_signature=( tf.TensorSpec(shape=(512, 512, 3), dtype=tf.float32), tf.TensorSpec(shape=(512, 512, 1), dtype=tf.float32), ) ) tf_val_dataset = tf.data.Dataset.from_generator( lambda: (preprocess_sample(s) for s in val_dataset), output_signature=( tf.TensorSpec(shape=(512, 512, 3), dtype=tf.float32), tf.TensorSpec(shape=(512, 512, 1), dtype=tf.float32), ) ) # 数据集增强:shuffle、batch、repeat、prefetch tf_train_dataset = tf_train_dataset.shuffle(32).batch(8).repeat().prefetch(tf.data.AUTOTUNE) tf_val_dataset = tf_val_dataset.batch(8).repeat().prefetch(tf.data.AUTOTUNE)
微调代码
import keras_cv import tensorflow as tf # 初始化模型,直接指定sigmoid激活 model = keras_cv.models.DeepLabV3Plus.from_preset( "mobilenet_v3_small", input_shape=(512, 512, 3), num_classes=1, activation="sigmoid" # 替换默认的softmax,适配二分类任务 ) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4), loss="binary_crossentropy", metrics=["binary_accuracy"] ) # 训练时指定steps_per_epoch,避免无限迭代 model.fit( tf_train_dataset, validation_data=tf_val_dataset, epochs=5, steps_per_epoch=len(train_dataset)//8, # 根据训练集大小计算每个epoch的迭代次数 validation_steps=len(val_dataset)//8, callbacks=[ tf.keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True), ], )
关键说明
- 激活函数修改:直接在
from_preset中指定activation="sigmoid",既解决了单通道Softmax的问题,也避免了后续添加层导致的输入结构不匹配。 - 数据集重复与step控制:添加
.repeat()后必须指定steps_per_epoch,否则训练会无限进行,同时保证每个epoch遍历完整的训练集。 - Mask二值化:严格的0/1值是二元交叉熵损失有效计算的前提,避免模糊值导致模型学习偏差。
- 图像归一化:预训练模型微调的必要步骤,能让模型快速利用预训练特征,加速收敛。
内容的提问来源于stack exchange,提问作者Amir
相关产品推荐
相关产品推荐

