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

基于DenseNet的胸片疾病分类模型验证精度瓶颈优化咨询

胸片疾病分类模型优化求助

本人刚入门AI,基于NIH ChestXray数据集构建胸片疾病分类模型,选用DenseNet121架构(对比EfficientNet、MobileNet表现最优),调整Adam优化器学习率(当前为1e-5)与clipnorm(当前为20.0),每轮epoch保存模型,但验证精度始终徘徊在0.44左右。尝试将学习率降至1e-6后,首epoch验证精度达0.457,但后续又回落至0.44左右,认为继续降学习率不可行,此前因出现Nan损失使用clipnorm,本次尝试已移除。

已针对数据集类别不平衡问题,通过计算类别权重惩罚少数类,代码如下:

clasification_data = pd.read_csv("/mnt/d/CXR8/PruneCXR/miccai2023_nih-cxr-lt_labels_train.csv",sep=",").values.tolist()
training_frequencies = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
for clases in clasification_data:
    for i in range(0,20):
        training_frequencies[i] = training_frequencies[i] + clases[i+1]

training_labels = []
for clases in clasification_data:
    training_labels.append(clases[1:-1])

total_samples = sum(training_frequencies)

# Calculate weights (inverse of frequency)
weights = [total_samples / freq for freq in training_frequencies]

# Normalize weights (optional)
normalized_weights = np.array(weights) / sum(weights) * len(training_frequencies)

class_weight_dict = {i: weight for i, weight in enumerate(normalized_weights)}

print("Class Weights:", class_weight_dict)

模型结构与编译代码

model = DenseNet121(weights=None, include_top=False)
model = Model(inputs=model.input, outputs=Dense(20, activation="sigmoid")(GlobalAveragePooling2D()(model.output)))

model.load_weights('./checkpoints/weights_epoch_24.weights.h5')
adam_optimizer = Adam(
    learning_rate=0.00001,   # Default: 0.001
    clipnorm = 20.0
)
model.compile(optimizer=adam_optimizer, loss='binary_crossentropy', metrics=[Precision(),Accuracy()])

数据加载代码

def create_dynamic_loaded_data(csv_path):
    dummy = pd.read_csv(csv_path,sep=",").values.tolist()
    img_names = tf.convert_to_tensor([label[0] for label in dummy])
    dummy = np.array([np.array(label[1:-1]) for label in dummy], dtype=np.float32)

    general_img_path = "/mnt/d/CXR8/images/"

    def preprocess_image(img_name, label):
        image = tf.io.read_file(general_img_path + img_name)
        image = tf.image.decode_jpeg(image, channels=3)  # Ensure 3 channels
        image = tf.image.resize(image, [IMG_SIZE, IMG_SIZE])      # Resize to match input size
        image = image / 255.0                           # Normalize to [0, 1]
        return image, label

    #create shuffle
    dummy_data = tf.data.Dataset.from_tensor_slices((img_names, dummy))
    dummy_data = dummy_data.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)
    dummy_data = dummy_data.shuffle(buffer_size=1000, seed=42)
    dummy_data = dummy_data.batch(20).prefetch(tf.data.AUTOTUNE)

    return dummy_data


train_dataset = create_dynamic_loaded_data("/mnt/d/CXR8/PruneCXR/miccai2023_nih-cxr-lt_labels_train.csv")
validation_dataset = create_dynamic_loaded_data("/mnt/d/CXR8/PruneCXR/miccai2023_nih-cxr-lt_labels_val.csv")

训练代码

checkpoint_dir = "./checkpoints"
os.makedirs(checkpoint_dir, exist_ok=True)

# Define the checkpoint callback
checkpoint_path = os.path.join(checkpoint_dir, "weights_epoch_{epoch:02d}.weights.h5")
checkpoint_callback = ModelCheckpoint(
    filepath=checkpoint_path,
    save_weights_only=True,  # Save only the weights
    save_best_only=False,    # Save after every epoch
    verbose=1
)

history = []
with tf.device('/GPU:0'):
    history = model.fit(train_dataset,
          validation_data=validation_dataset,
          epochs=128,
          verbose=1,
          class_weight=class_weight_dict,
          callbacks=[checkpoint_callback],
          initial_epoch=24
          )

可尝试的泛化能力提升方案

1. 数据增强优化

当前数据预处理仅做了resize和归一化,可针对胸片图像添加针对性增强:

  • 几何变换:左右翻转(符合胸片医学逻辑)、小范围平移、±10°以内轻微旋转
  • 像素变换:随机亮度/对比度调整、高斯噪声注入(模拟采集噪声)
  • 集成到预处理的示例代码:
def preprocess_image(img_name, label):
    image = tf.io.read_file(general_img_path + img_name)
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [IMG_SIZE, IMG_SIZE])
    # 仅训练阶段应用增强
    if tf.keras.backend.learning_phase():
        image = tf.image.random_flip_left_right(image)
        image = tf.image.random_brightness(image, max_delta=0.1)
        image = tf.image.random_contrast(image, lower=0.9, upper=1.1)
    image = image / 255.0
    return image, label

2. 迁移学习优化

当前模型未使用预训练权重,DenseNet121在ImageNet上的预训练权重可大幅提升特征提取能力:

model = DenseNet121(weights='imagenet', include_top=False)
# 冻结前N层,先训练顶层,后续再解冻微调
for layer in model.layers[:-10]:
    layer.trainable = False

3. 正则化策略补充

  • Dropout层:在全局平均池化后添加Dropout层抑制过拟合:
model = Model(inputs=model.input, outputs=Dense(20, activation="sigmoid")(Dropout(0.5)(GlobalAveragePooling2D()(model.output))))
  • L2正则化:在输出层添加权重正则项:
Dense(20, activation="sigmoid", kernel_regularizer=tf.keras.regularizers.l2(1e-4))

4. 学习率动态调度

放弃固定学习率,用调度器根据验证指标自动调整:

lr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(
    monitor='val_precision',
    factor=0.5,  # 学习率减半
    patience=5,  # 5轮无提升则调整
    min_lr=1e-7
)
# 训练时加入该回调
history = model.fit(..., callbacks=[checkpoint_callback, lr_scheduler])

5. 损失函数调整

针对多标签不平衡问题,用Focal Loss替代二元交叉熵,降低易分类样本权重:

def focal_loss(gamma=2., alpha=0.25):
    def focal_loss_fixed(y_true, y_pred):
        pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))
        pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))
        return -K.sum(alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1)) - K.sum((1 - alpha) * K.pow(pt_0, gamma) * K.log(1. - pt_0))
    return focal_loss_fixed

model.compile(optimizer=adam_optimizer, loss=focal_loss(gamma=2), metrics=[Precision(),Accuracy()])

6. 数据采样优化

除类别权重外,可尝试加权随机采样、过采样少数类或欠采样多数类,进一步平衡训练分布。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 07:43:10