CNN眼部影像分类模型损失与精度无明显提升问题及精度优化咨询
我正在尝试使用2030张预处理眼部影像训练CNN模型,输入数据形状为(2030, 200, 200, 1)。初始数据集规模为1527张,随后我使用imblearn.over_sampling.RandomOverSampler对数据集进行过采样以扩大规模。我基于Keras构建了模型,模型结构如下:
model = Sequential() model.add(Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=(img_cols, img_rows, 1))) model.add(Conv2D(32, (3, 3), padding='same', activation='relu')) model.add(MaxPooling2D(pool_size=(2,2))) model.add(Dropout(0.25)) model.add(Conv2D(64, (5, 5), padding='same', activation='relu')) model.add(Conv2D(64, (5, 5), padding='same', activation='relu')) model.add(MaxPooling2D(pool_size=(3,3))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(512, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(1, activation='sigmoid')) optimizer = tf.keras.optimizers.SGD(learning_rate=0.000001) model.compile(optimizer=optimizer,loss='binary_crossentropy', metrics=[tf.keras.metrics.SpecificityAtSensitivity(0.5), tf.keras.metrics.SensitivityAtSpecificity(0.5),'accuracy']) # Augmentation train_datagen = ImageDataGenerator( rescale=1./255, horizontal_flip=True, vertical_flip=True, width_shift_range=0.3, height_shift_range=0.5, rotation_range=10, zoom_range=0.2 ) test_datagen = ImageDataGenerator(rescale=1./255) train_data = train_datagen.flow(x_train, y_train) test_data = test_datagen.flow(x_test, y_test) reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.9, patience=2, min_lr=0.0000000000000000001) history=model.fit(train_data, epochs=10, batch_size=32, validation_data=test_data, callbacks=[reduce_lr])
我尝试了多种参数组合进行训练:包括32、64、128、256、512、1024不同的batch size;添加128和256神经元的卷积层;调整学习率;使用回调函数;将全连接层规模调整为32、64……1024等。但无论如何调整,模型的训练过程中损失与精度均无明显变化,训练过程如下:
Epoch 1/10 51/51 [] - 14s 238ms/step - loss: 0.6962 - specificity_at_sensitivity_15: 0.4548 - sensitivity_at_specificity_15: 0.4777 - accuracy: 0.4969 - val_loss: 0.6957 - val_specificity_at_sensitivity_15: 0.4112 - val_sensitivity_at_specificity_15: 0.3636 - val_accuracy: 0.4852 - lr: 1.0000e-04
Epoch 2/10 51/51 [] - 12s 226ms/step - loss: 0.6945 - specificity_at_sensitivity_15: 0.4829 - sensitivity_at_specificity_15: 0.4615 - accuracy: 0.5018 - val_loss: 0.6949 - val_specificity_at_sensitivity_15: 0.4467 - val_sensitivity_at_specificity_15: 0.3206 - val_accuracy: 0.4877 - lr: 1.0000e-04
Epoch 3/10 51/51 [] - 12s 227ms/step - loss: 0.6955 - specificity_at_sensitivity_15: 0.4328 - sensitivity_at_specificity_15: 0.4082 - accuracy: 0.5043 - val_loss: 0.6945 - val_specificity_at_sensitivity_15: 0.5584 - val_sensitivity_at_specificity_15: 0.5167 - val_accuracy: 0.4852 - lr: 1.0000e-04
Epoch 4/10 51/51 [] - 12s 226ms/step - loss: 0.6971 - specificity_at_sensitivity_15: 0.4034 - sensitivity_at_specificity_15: 0.4256 - accuracy: 0.5049 - val_loss: 0.6941 - val_specificity_at_sensitivity_15: 0.4010 - val_sensitivity_at_specificity_15: 0.3923 - val_accuracy: 0.4852 - lr: 1.0000e-04
Epoch 5/10 51/51 [] - 12s 226ms/step - loss: 0.6954 - specificity_at_sensitivity_15: 0.4670 - sensitivity_at_specificity_15: 0.4640 - accuracy: 0.4969 - val_loss: 0.6938 - val_specificity_at_sensitivity_15: 0.5584 - val_sensitivity_at_specificity_15: 0.5407 - val_accuracy: 0.4729 - lr: 1.0000e-04
Epoch 6/10 51/51 [] - 12s 227ms/step - loss: 0.6972 - specificity_at_sensitivity_15: 0.4352 - sensitivity_at_specificity_15: 0.3883 - accuracy: 0.4791 - val_loss: 0.6935 - val_specificity_at_sensitivity_15: 0.4772 - val_sensitivity_at_specificity_15: 0.3206 - val_accuracy: 0.4729 - lr: 1.0000e-04
Epoch 7/10 51/51 [] - 12s 227ms/step - loss: 0.6943 - specificity_at_sensitivity_15: 0.4474 - sensitivity_at_specificity_15: 0.4814 - accuracy: 0.5031 - val_loss: 0.6933 - val_specificity_at_sensitivity_15: 0.3604 - val_sensitivity_at_specificity_15: 0.4880 - val_accuracy: 0.4729 - lr: 1.0000e-04
Epoch 8/10 51/51 [] - 12s 225ms/step - loss: 0.6974 - specificity_at_sensitivity_15: 0.4609 - sensitivity_at_specificity_15: 0.4355 - accuracy: 0.4926 - val_loss: 0.6930 - val_specificity_at_sensitivity_15: 0.5279 - val_sensitivity_at_specificity_15: 0.5885 - val_accuracy: 0.4655 - lr: 1.0000e-04
Epoch 9/10 51/51 [] - 12s 226ms/step - loss: 0.6945 - specificity_at_sensitivity_15: 0.4425 - sensitivity_at_specificity_15: 0.4777 - accuracy: 0.5031 - val_loss: 0.6929 - val_specificity_at_sensitivity_15: 0.4619 - val_sensitivity_at_specificity_15: 0.3876 - val_accuracy: 0.4655 - lr: 1.0000e-04
Epoch 10/10 51/51 [] - 12s 226ms/step - loss: 0.6977 - specificity_at_sensitivity_15: 0.4389 - sensitivity_at_specificity_15: 0.4367 - accuracy: 0.4766 - val_loss: 0.6927 - val_specificity_at_sensitivity_15: 0.6091 - val_sensitivity_at_specificity_15: 0.5024 - val_accuracy: 0.4951 - lr: 1.0000e-04
使用占2030张影像2%的x_test数据进行测试,结果如下:
13/13 [==============================] - 1s 69ms/step - loss: 0.6927 - specificity_at_sensitivity_15: 0.6091 - sensitivity_at_specificity_15: 0.5024 - accuracy: 0.4951
Accuracy score is : 0.4950738847255707
我尝试了各种可能的调整方式,但模型精度最高仅能达到53%,而我在网络上看到的同类方案精度可达76%。这是一个医学影像项目,我需要获得更高的精度,请问如何才能提升模型的精度?
首先看你的训练数据,损失一直在0.69左右徘徊,这其实是二元分类中模型随机猜测的典型表现(因为ln(0.5)≈0.693),说明模型根本没学到有效特征,或者训练过程中有严重的阻碍因素。下面是针对性的优化方向:
1. 先排查数据与采样的核心问题
- 过采样时机错误:你用
RandomOverSampler直接在整个数据集上采样了,但正确的做法应该是只对训练集过采样,测试集绝对不能碰。如果测试集也被过采样,模型评估完全失效;就算没碰测试集,提前对全量数据采样再划分训练测试,也会导致数据泄露,模型泛化能力极差。 - 测试集规模太小:2%的2030张只有40多张样本,这个规模的测试结果完全不具备统计意义,波动会非常大,根本没法准确评估模型性能。建议把测试集比例调到15%-20%,至少要有300+样本,这样评估结果才靠谱。
- 数据预处理检查:随机可视化几张训练样本,确认眼部影像预处理是否正确——比如有没有归一化到正确范围?是否过度裁剪、模糊导致关键病变特征丢失?
2. 优化模型训练的核心参数
- 学习率严重过低:你用的SGD学习率是
1e-6,这几乎是冻结模型的程度!SGD的合理初始学习率一般在1e-3到1e-2之间,这个数值比正常小了3-4个数量级,模型根本没法更新权重。建议先把学习率调到1e-3,搭配ReduceLROnPlateau动态调整,或者直接换用Adam optimizer(自适应学习率,对医学影像任务更友好,初始值1e-3即可)。 - 训练轮数不足:你只训练了10轮,对于CNN来说完全不够。建议先训练50-100轮,观察loss和精度的变化趋势,再用EarlyStopping回调(比如
patience=10)防止过拟合,而不是固定10轮就停。 - Batch Size的合理性:Batch Size太大(比如512、1024)会导致梯度更新不充分,太小(比如32)可能波动大,但你的问题核心不是Batch Size,先把学习率和轮数调好再调整这个。
3. 模型结构的优化
- 引入预训练模型:2000张数据量不算大,直接从零训练CNN很难学到通用特征。建议用迁移学习,比如基于
VGG16、ResNet50这类在ImageNet预训练的模型,去掉顶层后冻结前几层,只训练顶层。预训练模型已经学到了边缘、纹理等通用视觉特征,针对眼部数据微调即可大幅提升精度。 - 调整卷积层设计:在卷积层后添加BatchNormalization,它能加速收敛,稳定训练过程;MaxPooling的池化核大小可以调整为更常见的2x2,避免过度压缩特征;全连接层512对于200x200的输入来说参数太多,容易过拟合,可以先降到256甚至128,同时配合BatchNorm使用。
- 损失函数调整:医学影像任务中,类别不平衡是常见问题,除了过采样,还可以用加权二元交叉熵(给少数类更高权重)或Focal Loss(降低易分类样本权重,聚焦难分类样本),更适合对漏诊误诊敏感的医学场景。
这里给一个迁移学习的简单示例:
from tensorflow.keras.applications import VGG16 # 因为预训练模型输入是3通道,把单通道复制为3通道 x_train_3ch = np.repeat(x_train, 3, axis=-1) x_test_3ch = np.repeat(x_test, 3, axis=-1) base_model = VGG16(weights='imagenet', include_top=False, input_shape=(200,200,3)) # 冻结预训练层 for layer in base_model.layers: layer.trainable = False # 构建顶层 model = Sequential() model.add(base_model) model.add(Flatten()) model.add(Dense(256, activation='relu')) model.add(BatchNormalization()) model.add(Dropout(0.5)) model.add(Dense(1, activation='sigmoid')) model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss='binary_crossentropy', metrics=['accuracy', tf.keras.metrics.SpecificityAtSensitivity(0.5), tf.keras.metrics.SensitivityAtSpecificity(0.5)])
4. 数据增强的优化
- 当前增强可能过度:
width_shift_range=0.3和height_shift_range=0.5的偏移量太大,可能导致眼部区域移出图像,破坏有效特征。建议把偏移量降到0.1-0.2,旋转角度调到15-20度,同时添加shear_range=0.1这样的轻度变换,增强数据多样性但不破坏特征。 - 增强只作用于训练集:确认
train_datagen只用于训练数据,测试集只做归一化,不要做任何增强。
5. 评估指标的合理性
医学影像任务中,Accuracy不是最优指标,如果类别不平衡,Accuracy会误导判断。你已经用了Specificity和Sensitivity,还可以加上F1-Score、AUC-ROC这些指标,更全面评估模型性能。
按照这些方向调整,应该能看到模型性能的明显提升。优先从数据采样和学习率这两个最关键的问题入手,这是导致你模型随机猜测的核心原因。
内容的提问来源于stack exchange,提问作者Ulaş Filiz

