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

带类别权重的ResNet模型表现异常的原因及相关技术咨询

模型训练结果与技术疑问

带类别权重版本的准确率和损失曲线图

带类别权重的准确率曲线
带类别权重的损失曲线

无权重版本的准确率和损失曲线图

无权重版本的准确率曲线
无权重版本的损失曲线

实现代码

两个版本的唯一区别在于是否调用类别权重字典(本人刚入门,欢迎对整体架构提出建议):

from tensorflow import keras
from keras import optimizers
from keras.applications.resnet_v2 import ResNet50V2
from tensorflow.keras.preprocessing import image
from tensorflow.keras.models import Model
from tensorflow.keras import layers
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Rescaling, Conv2D, MaxPool2D, Flatten


#Create datasets
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    '/content/drive/MyDrive/Colab Notebooks/train/All classes/',
    labels="inferred",
    label_mode="int",
    validation_split=0.2,
    seed=1337,
    subset="training",
)

val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    '/content/drive/MyDrive/Colab Notebooks/train/All classes/',
    labels="inferred",
    label_mode="int",
    validation_split=0.2,
    seed=1337,
    subset="validation",
)

test_ds = tf.keras.preprocessing.image_dataset_from_directory(
    '/content/drive/MyDrive/Colab Notebooks/test/All classes/',
    labels="inferred",
    label_mode="int",
)

#Import ResNet
base_model = ResNet50V2(weights='imagenet', include_top=False)

#Create basic network to append to ResNet above
x = base_model.output
x = Rescaling(1.0 / 255)(x)
x = Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(256,256,3), padding="same")(x)
x = MaxPool2D(pool_size=(2, 2), strides=2)(x)
x = Conv2D(64, kernel_size=(3, 3), activation='relu')(x)
x = MaxPool2D(pool_size=(2, 2), strides=2)(x)
x = GlobalAveragePooling2D()(x)
predictions = Dense(units=5, activation='softmax')(x)

#Merge the models
model = Model(inputs=base_model.input, outputs=predictions)

#Freeze ResNet layers
for layer in base_model.layers:
    layer.trainable = False

#Compile
model.compile(optimizer=keras.optimizers.Adam(1e-3), loss='sparse_categorical_crossentropy', metrics=['accuracy'])

#These are the weights. They are derived here from their numbers in the train dataset -- there are 25,811 
#files in class 0, 2444 files in class 1, etc. This dictionary was not called for the unweighted version.
class_weight = {0: 1.0,
                1: 25811.0/2444.0,
                2: 25811.0/5293.0,
                3: 25811.0/874.0,
                4: 25811.0/709.0}

#Training the model
model_checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(
    filepath='/content/drive/MyDrive/Colab Notebooks/ResNet/',
    save_weights_only=False,
    mode='auto',
    save_best_only=True,
    save_freq= 'epoch')

history = model.fit(
          x=train_ds,
          epochs=30,
          class_weight=class_weight,
          validation_data=val_ds,
          callbacks=[model_checkpoint_callback]
)

#Evaluating
loss, acc = model.evaluate(test_ds)
print("Accuracy", acc)

技术疑问

  • metrics=['accuracy']是否应替换为metrics=['sparse_categorical_accuracy']?
  • class_weight=class_weight是否应替换为sample_weight=sample_weight?查阅文档未明确区别,但多数示例使用class_weight。
  • 仅在一个Conv2D层使用padding(为了编译通过的临时处理),是否应统一为所有Conv2D层添加padding?
  • 附加在ResNet后的简易CNN子模型(即predictions部分)是否有更合理的架构设计?
  • 未按照Keras官方ResNet指南预处理数据,是否值得优化?无权重模型准确率过高是否与此相关?

解答

1. 关于metrics选择

不用替换。Keras会根据你使用的损失函数自动匹配对应的准确率指标:当使用sparse_categorical_crossentropy时,metrics=['accuracy']会自动映射为sparse_categorical_accuracy,两者效果完全一致,直接写accuracy更简洁通用。

2. class_weight vs sample_weight

两者适用场景不同:

  • class_weight按类别分配权重,适合类别不平衡的场景,你当前类别样本数量差异极大,用这个完全正确,也是此类场景的常规选择。
  • sample_weight给每个样本单独分配权重,适合单类别内样本重要性不同的场景(比如部分样本标注质量低)。你现在的需求是平衡类别,用class_weight足够,无需替换。

3. Conv2D的padding问题

建议统一添加padding="same"。你当前仅给第一个Conv2D加padding,会导致特征图尺寸计算逻辑不一致,甚至可能在输入尺寸非偶数时出现后续层尺寸不匹配的问题。统一加padding能保证卷积后特征图尺寸不变,逻辑更清晰,也能避免潜在的编译问题。另外,第一个Conv2D上的input_shape=(256,256,3)参数可以删除,因为输入来自ResNet的输出,该参数会被忽略。

4. ResNet后附加子模型的优化

当前的简易CNN存在冗余:ResNet本身已经完成了大量高阶语义特征提取,后续无需再叠加Conv+MaxPool结构,建议调整为:

  • 直接在ResNet输出后接GlobalAveragePooling2D(),再连接全连接层,这样更高效且不会破坏ResNet提取的特征。
  • 若想微调特征,可以在GlobalAveragePooling2D后加1-2个全连接层(比如Dense(256, activation='relu')),同时加入Dropout层(比如Dropout(0.5))防止过拟合,最后接分类输出层。
  • 另外,Rescaling(1.0 / 255)可以放到数据集预处理阶段(比如用train_ds = train_ds.map(lambda x, y: (tf.keras.layers.Rescaling(1./255)(x), y))),让预处理在数据加载时完成,提升训练效率。

5. 数据预处理的优化

非常值得优化,这可能是无权重模型准确率异常高的核心原因之一:

  • ResNet50V2预训练时使用的是特定预处理逻辑(将像素值缩放到[-1,1]区间),你当前用的Rescaling(1.0/255)得到的是[0,1]区间,与预训练的特征分布不匹配,会削弱模型的特征提取能力。正确做法是使用tf.keras.applications.resnet_v2.preprocess_input对图像进行预处理。
  • 无权重模型准确率过高,本质是类别不平衡导致的:模型倾向于预测样本最多的类别(class 0),在准确率计算上得到高分,但实际泛化能力极差。你可以通过混淆矩阵验证这一点。使用类别权重后,模型会更关注少数类别,准确率可能下降,但F1-score、召回率等更能反映模型真实性能的指标会更合理。

额外架构建议

  • 可以尝试解冻ResNet的顶层几层(比如最后10层)进行微调:先训练完附加的全连接层,再降低学习率(比如1e-5)微调ResNet层,避免破坏预训练的通用特征。
  • 训练时加入数据增强(随机翻转、旋转、缩放等),能有效提升模型泛化能力,尤其缓解少数类别样本不足的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 00:55:14