You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

我的回归模型每个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 18:15:38