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

Keras ResNet图像价格回归预测label_mode配置及报错排查

问题核心原因

你当前做的是图像回归预测任务(输出连续的价格值),之前的报错和损失为nan是几个配置错误叠加导致的:

  • 误用了分类场景的损失函数(交叉熵类损失仅适用于离散类别预测,完全不匹配连续值回归需求)
  • image_dataset_from_directory的label_mode参数选择错误,int/categorical/binary都是为分类任务设计的,会对标签做类别编码转换,无法保留浮点型价格的原始数值
  • 未对价格标签做归一化,数值跨度过大容易引发梯度爆炸导致损失为nan
  • 输出层激活函数选择错误,且未对输入图像做ResNet对应的预处理
  • 额外加了多余的Flatten层,且优化器、学习率选择不合适
分步修复方案

1. 修正数据集加载逻辑

image_dataset_from_directory没有专门的回归标签模式,传入自定义连续值标签时需要将label_mode设为None,此模式下函数不会对标签做分类编码转换,会保留传入的原始数值格式。
需要特别注意:该函数默认会按文件名字典序遍历文件夹内的图像,你提取标签时的遍历顺序必须和函数内部顺序完全一致,否则会出现图像和标签错配的问题。最稳妥的实现方式是先获取排序后的文件列表,再按该顺序提取标签:

import os
import numpy as np
import tensorflow as tf
img_height, img_width = 160, 160
batch_size = 32
data_dir = "你的图像存储文件夹路径"

# 获取和函数内部加载顺序完全一致的文件列表
valid_exts = ('.png', '.jpg', '.jpeg', '.bmp')
file_list = sorted([
    os.path.join(data_dir, f) for f in os.listdir(data_dir)
    if f.lower().endswith(valid_exts)
])
# 按文件列表顺序从文件名提取价格标签,替换为你自己的价格提取逻辑
labels = [float(os.path.basename(f).split('_')[0]) for f in file_list]

# 加载数据集,label_mode设为None
train_ds = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    labels=labels,
    validation_split=0.1,
    subset="training",
    seed=123,
    label_mode=None,
    image_size=(img_height, img_width),
    batch_size=batch_size
)

val_ds = tf.keras.utils.image_dataset_from_directory(
    data_dir,
    labels=labels,
    validation_split=0.1,
    subset="validation",
    seed=123,
    label_mode=None,
    image_size=(img_height, img_width),
    batch_size=batch_size
)

2. 对价格标签做归一化

价格数值如果跨度较大(比如从几元到几千元),直接用原始值训练很容易因为数值过大引发梯度爆炸,导致损失为nan。需要先把所有标签缩放到0~1区间,预测完成后再还原为真实价格:

labels_arr = np.array(labels)
label_min = labels_arr.min()
label_max = labels_arr.max()
eps = 1e-8 # 避免除零错误

# 归一化:原始价格 -> 0~1区间
def normalize_label(y):
    return (y - label_min) / (label_max - label_min + eps)

# 反归一化:模型输出 -> 真实价格
def denormalize_label(y_norm):
    return y_norm * (label_max - label_min + eps) + label_min

# 将归一化操作映射到数据集
train_ds = train_ds.map(lambda x, y: (x, normalize_label(y)))
val_ds = val_ds.map(lambda x, y: (x, normalize_label(y)))

3. 修正模型结构与训练配置

  • ResNet预训练权重是在经过特定预处理的ImageNet数据集上训练的,直接喂原始0~255的像素值会导致特征分布不匹配,需要调用配套的预处理函数
  • 你之前在池化层后加Flatten是多余的:设置pooling="avg"后,ResNet的输出已经是一维特征向量,不需要再展平
  • 回归任务的输出层不需要加激活函数,用线性输出即可;之前用relu会截断所有负值输出,容易导致梯度传递异常
  • 回归任务不要用accuracy作为评估指标(该指标仅适用于分类任务),换成MAE(平均绝对误差)更能直观反映预测价格和真实价格的偏差
  • 初始训练用小学习率的Adam优化器比SGD更稳定,不容易出现梯度爆炸

修正后的代码如下:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.applications.resnet50 import preprocess_input

# 给数据集加ResNet预处理、开启预取优化加速训练
train_ds = train_ds.map(lambda x, y: (preprocess_input(x), y)).prefetch(tf.data.AUTOTUNE)
val_ds = val_ds.map(lambda x, y: (preprocess_input(x), y)).prefetch(tf.data.AUTOTUNE)

resnet_model = Sequential()
pretrained_model = ResNet50(
    include_top=False,
    input_shape=(160, 160, 3),
    pooling="avg",
    weights="imagenet"
)
# 初始阶段冻结预训练层,先训头部全连接层
for layer in pretrained_model.layers:
    layer.trainable = False

resnet_model.add(pretrained_model)
resnet_model.add(Dense(512, activation="relu"))
resnet_model.add(Dense(1)) # 线性输出,无激活函数

# 编译:回归任务用MSE做损失
resnet_model.compile(
    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),
    loss="mse",
    metrics=["mae"]
)

4. 训练注意事项

  • 训练前先检查提取的标签有没有脏数据:比如非数值、空值、负价格的异常值,这类脏数据也会导致损失为nan
  • 等头部全连接层训练到验证集损失稳定下降后,再尝试解冻ResNet最后几层的参数,用1e-5级别的更小学习率微调,不要一开始就全量训练,否则会破坏预训练权重导致不收敛
  • 模型推理得到的归一化预测值,必须用之前定义的denormalize_label函数还原,才能得到真实的价格结果

内容的提问来源于stack exchange,提问作者zueri95

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 12:57:21