我的回归模型每个epoch损失值几乎不变,该如何排查?
问题现象
训练用于预测边界框坐标(top_x, top_y, bottom_x, bottom_y)的回归模型时,各epoch的训练损失与验证损失几乎没有下降,始终维持在较高数值。
数据生成器代码
datagen = ImageDataGenerator(rescale=1./255, validation_split=0.1) train_generator = datagen.flow_from_dataframe( df, directory=img_data_dir, x_col="image_name", y_col=["top_x", "top_y", "bottom_x", "bottom_y"], target_size=(WIDTH, HEIGHT), batch_size=32, class_mode="other", subset="training") validation_generator = datagen.flow_from_dataframe( df, directory=img_data_dir, x_col="image_name", y_col=["top_x", "top_y", "bottom_x", "bottom_y"], target_size=(WIDTH, HEIGHT), batch_size=32, class_mode="other", subset="validation")
模型定义代码
model = Sequential() model.add(VGG16(weights="imagenet", include_top=False, input_shape=(HEIGHT, WIDTH, CHANNEL))) model.add(Flatten()) model.add(Dense(128, activation="relu")) model.add(Dense(64, activation="relu")) model.add(Dense(64, activation="relu")) model.add(Dense(4, activation="sigmoid")) model.layers[-6].trainable = False model.summary()
训练参数与执行代码
STEP_SIZE_TRAIN = int(np.ceil(train_generator.n / train_generator.batch_size)) STEP_SIZE_VAL = int(np.ceil(validation_generator.n / validation_generator.batch_size)) print("Train step size:", STEP_SIZE_TRAIN) print("Validation step size:", STEP_SIZE_VAL) train_generator.reset() validation_generator.reset()
adam = Adam(lr=1e-4) model.compile(optimizer=adam, loss="mse")
history = model.fit(train_generator, steps_per_epoch=STEP_SIZE_TRAIN, validation_data=validation_generator, validation_steps=STEP_SIZE_VAL, epochs=10)
训练结果
Epoch 1/10
20/20 [] - 376s 18s/step - loss: 436570.7812 - val_loss: 524766.6875
Epoch 2/10
20/20 [] - 14s 732ms/step - loss: 436464.6250 - val_loss: 524765.2500
Epoch 3/10
20/20 [] - 14s 721ms/step - loss: 436464.2188 - val_loss: 524765.1250
Epoch 4/10
20/20 [] - 14s 721ms/step - loss: 436464.1875 - val_loss: 524765.0625
Epoch 5/10
20/20 [] - 14s 722ms/step - loss: 436464.1875 - val_loss: 524765.0625
Epoch 6/10
20/20 [] - 14s 707ms/step - loss: 436464.1875 - val_loss: 524765.0625
Epoch 7/10
20/20 [] - 14s 715ms/step - loss: 436464.1875 - val_loss: 524765.0000
Epoch 8/10
20/20 [] - 14s 713ms/step - loss: 436464.1875 - val_loss: 524765.0000
Epoch 9/10
20/20 [] - 15s 741ms/step - loss: 436464.1250 - val_loss: 524765.0000
Epoch 10/10
20/20 [] - 17s 827ms/step - loss: 436464.0625 - val_loss: 524765.0000
问题根源与解决办法
1. 输出层激活函数不匹配
输出层使用sigmoid激活,其输出范围为[0,1],但如果边界框坐标是原始像素值(如几百甚至上千),模型输出被限制在0-1区间,与真实标签差距极大,导致损失居高不下且参数无法有效更新。
解决:
- 若坐标为原始像素值,移除输出层的
sigmoid激活,改用线性激活(直接写Dense(4)即可,Keras默认线性激活); - 若要保留
sigmoid,需将标签归一化到[0,1]范围(例如除以图像的宽度/高度)。
2. 标签未归一化
仅对输入图像做了rescale=1./255归一化,但标签(边界框坐标)仍为原始数值,输入与输出的数值尺度差异过大,模型难以学习。
解决:
生成数据前对标签做归一化处理:
# 假设图像原始宽高为ORIG_WIDTH、ORIG_HEIGHT def normalize_labels(df): df['top_x'] = df['top_x'] / ORIG_WIDTH df['top_y'] = df['top_y'] / ORIG_HEIGHT df['bottom_x'] = df['bottom_x'] / ORIG_WIDTH df['bottom_y'] = df['bottom_y'] / ORIG_HEIGHT return df df = normalize_labels(df)
3. 冻结层设置错误
代码中model.layers[-6].trainable = False的写法无法正确冻结整个VGG16预训练层,若仅冻结单一层,其余VGG层的预训练权重会被更新,干扰模型学习。
解决:
若要冻结整个VGG16的预训练权重,直接设置:
vgg = VGG16(weights="imagenet", include_top=False, input_shape=(HEIGHT, WIDTH, CHANNEL)) vgg.trainable = False # 冻结全部VGG16层 model = Sequential() model.add(vgg) # 后续Dense层保持不变
4. 学习率可能过低
当前使用lr=1e-4,若头部Dense层需要学习的尺度差异较大,该学习率可能不足以驱动参数更新。可尝试调高至1e-3,观察损失变化。
5. 模型结构优化
VGG16特征图Flatten后直接接入128神经元的Dense层,可能存在特征压缩过度的问题。可在Flatten层后加入Dropout层防止过拟合,或增加Dense层的神经元数量。
验证建议
修改后先运行1-2个epoch,观察损失是否有明显下降。若仍无变化,需检查:
- 生成器是否正确加载标签,打印
train_generator.next()[1]查看标签数值范围; - 模型输出形状是否与标签形状匹配;
- 尝试改用SGD优化器对比效果。
内容的提问来源于stack exchange,提问作者19F046 - SHANMUGAM R

