Deep learning pipeline for detecting and grading diabetic retinopathy (DR) from retinal fundus images, built on the APTOS 2019 Blindness Detection Kaggle dataset. The project covers three progressively harder tasks:
- Binary classification — DR vs. no DR.
- Multiclass classification — DR severity grading (0–4) with self-supervised contrastive pre-training on an auxiliary ocular disease dataset.
- Membership inference attack — a privacy analysis that probes whether a sample was part of the training set using Monte Carlo augmentation features.
All notebooks are written to run on Google Colab with the dataset stored on Google Drive and a GPU runtime.
.
├── binary_classifier.ipynb # ResNet18 binary classifier (DR vs no DR)
├── multiclass_classifier.ipynb # SimCLR-style pre-training + 5-class fine-tuning
├── APTOS_membership_inference.ipynb # Monte Carlo membership inference attack
└── data/
├── train.csv # id_code, diagnosis (0–4) for 3,662 images
└── test.csv # id_code for held-out Kaggle test set
The image folders (train_images/, test_images/) and the original Kaggle zip are gitignored — you need to download them yourself (see Setup).
- Primary: APTOS 2019 Blindness Detection. 3,662 labeled training images graded on the international DR scale:
0— No DR1— Mild2— Moderate3— Severe4— Proliferative DR
- Auxiliary (pre-training): ODIR-5K Ocular Disease Recognition. A cropped subset of ~2,000 images is used for self-supervised pre-training of the backbone.
The label distribution is heavily imbalanced toward class 0, which motivated tackling the binary task first.
crop_retinaremoves the black border around each fundus image by thresholding the grayscale intensity and cropping to the non-black bounding box (with padding) — this prevents the model from learning border artifacts.- Images are resized to 224×224 and normalized with ImageNet statistics.
- Training-time augmentations: horizontal flip, small rotations, and color jitter.
- Labels remapped to
0(no DR) and1(any DR), which produces a roughly balanced dataset. - Stratified 64/16/20 train/val/test split (
random_state=42). - ResNet18 (ImageNet-pretrained), final FC replaced with a 2-way head.
- Trained end-to-end with Adam (
lr=1e-4), cross-entropy loss, 20 epochs, batch size 16. - Best checkpoint selected by validation loss; reported metrics include accuracy, ROC AUC, sensitivity, and specificity.
A two-stage pipeline designed to mitigate class imbalance and limited labeled data:
Stage 1 — Self-supervised pre-training (SimCLR-style).
- Backbone: ResNet18 with the FC layer removed, followed by a 2-layer projection head (
512 → 256 → 128). - Each image produces two strongly augmented views (random resized crop, flips, color jitter, grayscale, Gaussian blur). The NT-Xent contrastive loss pulls views of the same image together and pushes other views apart.
- Trained on ~2,000 cropped images from the ODIR-5K dataset for 10 epochs, Adam (
lr=1e-4), batch size 32.
Stage 2 — Supervised fine-tuning.
- Pre-trained backbone is loaded, parameters frozen, and a new
Linear(512, 5)head is trained on the APTOS labels. - Same 64/16/20 stratified split as the binary task, but stratified on the full 5-class label.
- Evaluation uses accuracy and Quadratic Weighted Kappa (the official APTOS competition metric), which penalizes predictions that are further from the true severity grade.
A black-box privacy analysis of the fine-tuned multiclass model:
- Members = the training split used to fit the model; non-members = the val + test splits.
- For each image,
Taugmented views are passed through the trained model and Monte Carlo statistics are computed:- Mean / std of the true-class softmax probability
- Mean / std of the predictive entropy
- Mean / std of the per-sample cross-entropy loss
- A logistic regression attack model is trained on these 6 features to predict membership. Performance is reported via accuracy, ROC AUC, and the full ROC curve, with per-feature histograms comparing member vs. non-member distributions.
The intuition: trained-on images tend to have higher confidence, lower entropy, and lower loss under augmentation than unseen images, and a simple classifier can exploit that gap.
The notebooks were developed against Google Colab. To run them:
-
Get the data
- Place a Kaggle API token at
~/.kaggle/kaggle.json(the first cells of each notebook handle uploading and permissions in Colab). - Download and unzip
aptos2019-blindness-detection.zipintodata/. - For Stage 1 pre-training, also download the ODIR-5K dataset and pre-crop a subset into
data/cropped_pretrain_images/usingcrop_retina.
- Place a Kaggle API token at
-
Mount Drive (Colab) The notebooks expect the project at
/content/drive/MyDrive/aptos_projectwith adata/subfolder. AdjustPROJECT_DIRif running locally. -
Dependencies (Colab has most of these pre-installed)
torch,torchvisionnumpy,pandas,scikit-learnopencv-python,Pillowmatplotlib,tqdm
-
Hardware A CUDA-capable GPU is strongly recommended. All notebooks auto-select
cudawhen available and fall back tocpu.
Run the notebooks in this order:
binary_classifier.ipynb— sanity-check that the pipeline works end-to-end on the balanced binary task. Savesbest_resnet18_binary.pth.multiclass_classifier.ipynb— runs Stage 1 (pre-training, savespretrain_model_weights.pth) and Stage 2 (fine-tuning, savesmodel_weights.pth), then evaluates on the held-out test split.APTOS_membership_inference.ipynb— loadsmodel_weights.pthand runs the attack pipeline.
Splits use random_state=42 throughout for reproducibility.
- The Kaggle test set has no public labels, so all reported metrics use an internal stratified split of the labeled training set.
- The membership inference attack uses a small sample (
max_samples=100per group by default) for speed — increaseTandmax_samplesfor tighter estimates. - The pre-training stage assumes you have manually cropped and saved the ODIR-5K subset to
data/cropped_pretrain_images/; the crop helper is the samecrop_retinadefined in each notebook.