如何提升皮肤病变分类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 )
- 改用Adam优化器,初始学习率设为1e-4,配合学习率衰减:
5. 其他建议
- 对输入图片做针对性预处理:比如RGB转HSV调整对比度,或使用预训练模型的标准化逻辑。
- 尝试迁移学习:用ResNet50、EfficientNetB0等预训练模型作为特征提取器,冻结部分层后微调,可快速提升准确率。
- 校验数据集标签:确认图片与标签的对应关系无误,避免标签错误导致的训练偏差。
内容的提问来源于stack exchange,提问作者Quasont
相关产品推荐
相关产品推荐

