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

如何提升皮肤病变分类CNN模型准确率?当前准确率仅约65%

皮肤病变分类CNN模型优化求助

我正在构建用于皮肤病变分类的CNN模型,为解决类别不平衡问题添加了加权交叉熵损失函数,但模型准确率仍仅约65%,且运行过程无报错。以下是相关代码,求可行的优化方案:

模型代码

#classification

def classi(input_shape):
    inputs = layers.Input(shape=input_shape)
    x = layers.Conv2D(64, 3, padding="same")(inputs)
    x = layers.Activation("relu")(x)
    x = layers.BatchNormalization()(x)
    #classi layers
    for filters in [96, 128, 256, 320, 512]:#, 1024, 2048]: #change # of filters??
        x = layers.Conv2D(filters, 3, padding="same")(x)
        x = layers.Activation("relu")(x)
        x = layers.BatchNormalization()(x)

        x = layers.Conv2D(filters, 3, padding="same")(x)
        x = layers.Activation("relu")(x)
        x = layers.BatchNormalization()(x)

        x = layers.MaxPool2D(3, strides=2, padding="same")(x)

    #output
    x = layers.Dropout(rate=0.1)(x)
    x = layers.Flatten()(x)
    x = layers.Dense(128, activation="relu")(x)
    #x = layers.Dense(64, activation="relu")(x)
    #x = layers.Dense(16, activation="sigmoid")(x)

    output = layers.Dense(7, activation=None)(x)

    model = k.Model(inputs=inputs, outputs=output, name="classification")
    return model

classification = classi((256,256,3))
classification.summary()

classification.save_weights("classification.h5")

加权损失函数(带权重的交叉熵损失)

#weighted binary loss
def get_weights(labels):
    cols = len(labels.columns)-2 #assumes 1 column for image ids
    pos_freqs = []
    neg_freqs = []
    pos_weights = []
    neg_weights = []
    for i in range(cols):
        pos_freqs.append(np.mean(labels[labels.columns[i+1]].tolist())) #get column values and sum
        neg_freqs.append(1-pos_freqs[i]) 
        pos_weights.append(neg_freqs[i]) 
        neg_weights.append(pos_freqs[i])
    
    return pos_weights, neg_weights


def weighted_cross_entropy_loss(y_true, y_pred):
    pos_weights, neg_weights = get_weights(pd.read_csv(cls_train_gt))
    #get frequencies to calculate weights
    loss = 0.0
    #print(k.backend.cast(-(neg_weights[0]*(1-y_true[:, 0])), 'float16'))
    for i in range(len(pos_weights)):
        loss += k.backend.mean(k.backend.cast(-(neg_weights[i]*(1-y_true[:, i])), 'float16')
                                 * k.backend.cast(k.backend.log((1-y_pred[:, i])), 'float16')
                                 + (k.backend.cast(pos_weights[i]*y_true[:, i], 'float16')  
                                    * k.backend.cast(k.backend.log((y_pred[:, i])), 'float16')))
    return loss

数据集加载代码

#For loading classification labels and images.

def load_images_and_labels(images_path, labels_path, batch_size, image_shape, verbose=False):
    ds_images = []
    ds_labels = []
    data_indexes = []
    labels = pd.read_csv(labels_path)
    images = os.listdir(images_path)
    if verbose:
        print(f"loading images from {images_path} and labels from {labels_path}")
    for i in range(batch_size):
        random_index = np.random.randint(0, len(images)-2)
        if random_index >= len(images):
            random_index -=1
        img = cv2.imread(os.path.join(images_path, images[random_index]))
        #print(random_index)
        #print(len(labels.columns))
        row = labels.iloc[random_index, 1:]

        if img is not None and row is not None:
            if random_index not in data_indexes:
                data_indexes.append(random_index)
                ds_images.append(np.array(cv2.resize(img, dsize=image_shape)))
                ds_labels.append(row.values)
    return np.array(ds_images).astype(np.int16), np.array(ds_labels).astype(np.int16)

模型训练代码

datagen = ImageDataGenerator(rescale=1./255,
                             rotation_range=0.1,
                             horizontal_flip=True,
                             vertical_flip=True,
                             )

classification.load_weights('classification.h5') #reset weights
optimizer = tf.keras.optimizers.SGD(learning_rate=0.2)
classification.compile(optimizer=optimizer, loss=weighted_cross_entropy_loss, metrics=["binary_accuracy", 'MeanSquaredError', 'AUC']) 

callback_list = [tf.keras.callbacks.EarlyStopping(patience=1.5)] #can adjust to improve accuracy
batch_size=16
spe = 4 #steps per epoch
epochs = 80 
seed = 123

cls_val = r'validation/ISIC2018_Task3_Validation_Input/'
cls_val_gt = "validation_ground_truth/ISIC2018_Task3_Validation_GroundTruth/ISIC2018_Task3_Validation_GroundTruth.csv"

cls_train = r'train/ISIC2018_Task3_Training_Input/'#r"classi/ISIC2018_Task3_Training_Input/ISIC2018_Task3_Training_Input/" 
cls_train_gt = 'train_ground_truth/ISIC2018_Task3_Training_GroundTruth/ISIC2018_Task3_Training_GroundTruth.csv'#("classi/ISIC2018_Task3_Training_GroundTruth/ISIC2018_Task3_Training_GroundTruth/ISIC2018_Task3_Training_GroundTruth.csv")

#organize_images_to_classes(class_train_gt, class_train)

class_val = r"classi/ISIC2018_Task3_Validation_Input/ISIC2018_Task3_Validation_Input/" 
class_val_gt = pd.read_csv("classi/ISIC2018_Task3_Validation_GroundTruth/ISIC2018_Task3_Validation_GroundTruth/ISIC2018_Task3_Validation_GroundTruth.csv")
organize_images_to_classes(class_val_gt, class_val)
"""


for i in range(epochs):
    train_ds, train_gt = load_images_and_labels(cls_train, cls_train_gt, batch_size, (256,256), True)
    val_ds, val_gt = load_images_and_labels(cls_val, cls_val_gt, batch_size, (256,256), True)
    
    #print(train_ds)
    #print(train_gt)

    print(f"train_ds len: {len(train_ds)}, train labels len: {len(train_gt)}")
    cls_train_gen = datagen.flow(x=train_ds, y=train_gt, seed=seed, batch_size=batch_size)
    val_train_gen = datagen.flow(x=val_ds, y=val_gt, seed=seed, batch_size=batch_size)

    history = classification.fit(x=cls_train_gen.x, y=cls_train_gen.y, steps_per_epoch=spe, callbacks=callback_list, verbose=1)#, validation_data=val_dataset, validation_batch_size=16)
    print(f"--------------- Done epoch {i+1} -----------------")

classification.save_weights("final_class.h5")

优化方案

1. 修复数据集加载的核心问题

  • 当前问题:load_images_and_labels每次仅加载batch_size张图片,随机索引逻辑易导致数据重复/遗漏,训练时每个epoch仅用64张样本,完全未利用全量训练数据。
  • 优化措施:
    • 替换自定义加载函数,改用tf.keras.utils.flow_from_dataframe实现全量数据的高效加载,确保每个epoch遍历所有训练样本。
    • 移除手动随机索引,依赖内置数据打乱机制避免采样偏差。
    • 图片存储改用float32格式,避免int16带来的精度损失。

2. 修正加权损失函数的实现

  • 当前问题:
    • 每次计算损失都重新读取CSV文件,拖慢训练速度且易引发IO不稳定。
    • 手动循环计算多分类交叉熵,存在数值不稳定风险(如y_pred接近0时log值爆炸),且输出层未加激活函数。
  • 优化措施:
    • 提前计算类别权重,在fit时传入class_weight参数,无需自定义损失函数。
    • 输出层添加softmax激活,改用内置CategoricalCrossentropy损失:
      # 提前计算类别权重
      train_labels = pd.read_csv(cls_train_gt)
      class_counts = train_labels.iloc[:,1:].sum(axis=0).values
      total = class_counts.sum()
      class_weights = total / (7 * class_counts)
      
      # 修改模型输出层
      output = layers.Dense(7, activation="softmax")(x)
      
      # 编译模型
      classification.compile(
          optimizer=optimizer,
          loss=tf.keras.losses.CategoricalCrossentropy(),
          metrics=["categorical_accuracy", 'AUC'],
          class_weight=class_weights
      )
      

3. 调整模型结构与正则化

  • 当前问题:
    • 卷积块无残差连接,深层网络易出现梯度消失,特征提取能力受限。
    • Dropout率仅0.1,正则化不足易过拟合。
    • 全连接层仅128单元,拟合复杂特征的能力不足。
  • 优化措施:
    • 为卷积块添加残差连接:
      for filters in [96, 128, 256, 320, 512]:
          shortcut = x
          x = layers.Conv2D(filters, 3, padding="same")(x)
          x = layers.Activation("relu")(x)
          x = layers.BatchNormalization()(x)
          x = layers.Conv2D(filters, 3, padding="same")(x)
          x = layers.Activation("relu")(x)
          x = layers.BatchNormalization()(x)
          # 通道数不一致时用1x1卷积调整shortcut
          if shortcut.shape[-1] != filters:
              shortcut = layers.Conv2D(filters, 1, strides=2, padding="same")(shortcut)
          x = layers.Add()([x, shortcut])
          x = layers.MaxPool2D(3, strides=2, padding="same")(x)
      
    • 提升Dropout率至0.3-0.5,或为卷积层/全连接层添加L2正则化:
      x = layers.Conv2D(filters, 3, padding="same", kernel_regularizer=tf.keras.regularizers.l2(1e-4))(x)
      
    • 增加全连接层复杂度,比如将128单元改为256,或添加64单元的隐藏层。

4. 优化训练策略

  • 当前问题:
    • SGD学习率0.2过高,训练易震荡无法收敛。
    • EarlyStopping的patience=1.5无效(需为整数),且未监控验证集指标。
    • 数据增强幅度极小,几乎无效果。
    • 训练未使用验证集,无法评估泛化能力。
  • 优化措施:
    • 改用Adam优化器,初始学习率设为1e-4,配合学习率衰减:
      optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
      lr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3)
      
    • 修正EarlyStopping配置:
      callback_list = [
          tf.keras.callbacks.EarlyStopping(monitor='val_categorical_accuracy', patience=5, restore_best_weights=True),
          lr_scheduler
      ]
      
    • 增强数据增强幅度:
      datagen = ImageDataGenerator(
          rescale=1./255,
          rotation_range=30,
          width_shift_range=0.2,
          height_shift_range=0.2,
          horizontal_flip=True,
          vertical_flip=True,
          zoom_range=0.2
      )
      
    • 训练时传入验证集,监控泛化能力:
      history = classification.fit(
          cls_train_gen,
          steps_per_epoch=len(train_ds)//batch_size,
          validation_data=val_train_gen,
          validation_steps=len(val_ds)//batch_size,
          callbacks=callback_list,
          epochs=epochs,
          verbose=1
      )
      

5. 其他建议

  • 对输入图片做针对性预处理:比如RGB转HSV调整对比度,或使用预训练模型的标准化逻辑。
  • 尝试迁移学习:用ResNet50、EfficientNetB0等预训练模型作为特征提取器,冻结部分层后微调,可快速提升准确率。
  • 校验数据集标签:确认图片与标签的对应关系无误,避免标签错误导致的训练偏差。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 04:38:09