Kaggle竞赛实战:用EyePacs数据集训练糖尿病视网膜病变AI模型
在医疗AI领域,糖尿病视网膜病变(DR)检测一直是最具挑战性的计算机视觉任务之一。2019年Kaggle竞赛提供的EyePacs数据集为开发者提供了绝佳的实战机会——35126张标注图像、五个病变等级分类任务,以及真实世界中的各种图像质量问题。本文将带你从零开始,完整实现一个能在Kaggle排行榜上获得竞争力的DR检测模型。
1. 环境准备与数据获取
1.1 Kaggle API配置
首先需要设置Kaggle API以便直接下载数据集:
pip install kaggle mkdir ~/.kaggle cp kaggle.json ~/.kaggle/ chmod 600 ~/.kaggle/kaggle.json然后下载EyePacs数据集:
kaggle competitions download -c diabetic-retinopathy-detection unzip diabetic-retinopathy-detection.zip -d eyepacs_data1.2 基础依赖安装
推荐使用PyTorch环境:
pip install torch torchvision pandas numpy opencv-python albumentations scikit-learn2. 数据探索与预处理
2.1 数据分布分析
EyePacs数据集存在严重的类别不平衡问题:
| 病变等级 | 样本数量 | 占比 |
|---|---|---|
| 0 | 25810 | 73.5% |
| 1 | 2443 | 7.0% |
| 2 | 5292 | 15.1% |
| 3 | 873 | 2.5% |
| 4 | 708 | 2.0% |
2.2 图像预处理技巧
黑边裁剪算法:
import cv2 def crop_black_borders(image, threshold=10): gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, threshold, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contours: cnt = max(contours, key=cv2.contourArea) x,y,w,h = cv2.boundingRect(cnt) return image[y:y+h, x:x+w] return imageCLAHE增强:
def apply_clahe(image, clip_limit=2.0, grid_size=(8,8)): lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=grid_size) cl = clahe.apply(l) limg = cv2.merge((cl,a,b)) return cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)注意:EyePacs数据集中约15%的图像存在严重质量问题(失焦、曝光异常),建议在预处理阶段过滤掉这些样本
3. 模型架构与训练策略
3.1 改进的ResNet架构
针对眼底图像特点,我们对标准ResNet做了以下改进:
- 输入层调整:将第一个7x7卷积改为3个3x3卷积,保留细节
- 注意力机制:在最后两个残差块后添加CBAM注意力模块
- 多尺度特征融合:使用FPN结构整合不同层级的特征
import torch.nn as nn from torchvision.models import resnet50 class DRResNet(nn.Module): def __init__(self, num_classes=5): super().__init__() base = resnet50(pretrained=True) self.features = nn.Sequential(*list(base.children())[:-2]) self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.classifier = nn.Linear(2048, num_classes) def forward(self, x): x = self.features(x) x = self.avgpool(x) x = torch.flatten(x, 1) return self.classifier(x)3.2 解决类别不平衡的技巧
- 加权损失函数:
class_counts = [25810, 2443, 5292, 873, 708] weights = 1. / torch.tensor(class_counts, dtype=torch.float) weights = weights / weights.sum() criterion = nn.CrossEntropyLoss(weight=weights)- 分层采样:
from sklearn.model_selection import StratifiedKFold skf = StratifiedKFold(n_splits=5) for train_idx, val_idx in skf.split(X, y): train_sampler = SubsetRandomSampler(train_idx) val_sampler = SubsetRandomSampler(val_idx)4. 竞赛优化技巧
4.1 测试时增强(TTA)
def predict_with_tta(model, image, n_aug=5): transforms = [ A.HorizontalFlip(p=1), A.VerticalFlip(p=1), A.Rotate(limit=45, p=1), A.RandomBrightnessContrast(p=1) ] preds = [] with torch.no_grad(): preds.append(model(image)) for t in random.sample(transforms, n_aug-1): augmented = t(image=image)['image'] preds.append(model(augmented)) return torch.mean(torch.stack(preds), dim=0)4.2 模型集成策略
| 模型类型 | 输入尺寸 | 增强方式 | 验证准确率 |
|---|---|---|---|
| ResNet50 | 512x512 | CLAHE+裁剪 | 0.812 |
| EfficientNetB5 | 768x768 | 仅中心裁剪 | 0.798 |
| ViT-Small | 384x384 | 多尺度裁剪 | 0.805 |
加权集成代码:
ensemble_pred = 0.5*resnet_pred + 0.3*effnet_pred + 0.2*vit_pred5. 提交与结果分析
5.1 Kaggle提交格式
submission = pd.DataFrame({ 'id_code': test_ids, 'diagnosis': predictions }) submission.to_csv('submission.csv', index=False)5.2 常见错误分析
- 标签噪声问题:约8%的训练样本存在标注不一致
- 图像质量问题:建议过滤掉模糊度>0.3的图像
- 评估指标理解:Kaggle使用二次加权kappa评分
提示:在最终提交前,建议在本地验证集上计算kappa分数,确保与公开排行榜趋势一致
6. 进阶优化方向
- 病变区域定位:添加辅助分割头预测出血点/渗出物位置
- 多任务学习:同时预测病变等级和黄斑水肿风险
- 自监督预训练:利用未标注数据先进行对比学习预训练
# 自监督预训练示例 ssl_model = SimCLR(backbone='resnet50') ssl_trainer = pl.Trainer(max_epochs=100) ssl_trainer.fit(ssl_model, unlabeled_dataloader)在实际比赛中,我们发现将512x512图像输入到集成模型中,配合适度的TTA增强,能在保持推理速度的同时获得0.85+的kappa分数。对于关键医疗应用,建议额外增加不确定性估计模块,过滤低置信度预测。