LesionShiftAI is a research benchmark for cross-dataset skin lesion classification under dataset shift. The project trains models on ISIC 2019 and evaluates external generalization on HAM10000.
The repository currently supports three training/evaluation pipelines:
- Baseline CNN
- Ensemble of CNNs (k-fold members + aggregate predictions)
- Vision Transformer (ViT-B16)
Each training script performs:
- ISIC training
- ISIC validation
- HAM10000 external test
- Artifact export (checkpoints, predictions, metrics, ROC/PR curves, generalization gap)
LesionShiftAI/
|- config/ # baseline_cnn.yml and vit_b16.yml
|- scripts/
| |- hpc/ # SLURM launch scripts for Monsoon
| |- train_baseline_cnn.py
| |- train_ensemble_member_cnn.py
| |- train_vit.py
|- src/lesionshiftai/ # core, data, models, train, eval modules
|- tests/ # unit and integration tests
|- .github/workflows/ci.yml
|- pyproject.toml
|- environment.yml
`- dist/lesionshiftai.pyz # zipapp launcher used on HPC
LesionShiftAI expects these files/directories:
ISIC 2019/
|- train-metadata.csv
`- train images/
`- <isic_id>.jpg
HAM10000/
|- GroundTruth.csv
`- images/
`- <image>.jpg
Use Python 3.12 locally.
uv sync --extra dev --python 3.12Run commands without activating the virtual environment:
uv run python --version
uv run pytest -m unit --no-covCreate a shared environment in scratch (recommended path matches SLURM scripts):
module purge
module load anaconda3
source "$(conda info --base)/etc/profile.d/conda.sh"
conda env create -f environment.yml -p /scratch/$USER/conda/envs/lesionshiftai
conda activate /scratch/$USER/conda/envs/lesionshiftaiIf the environment already exists:
conda env update -f environment.yml -p /scratch/$USER/conda/envs/lesionshiftai --pruneThe SLURM scripts in scripts/hpc invoke lesionshiftai.pyz and config files by filename only, so stage them in your working directory before submitting jobs:
uv run python scripts/build_pyz.py
scp dist/lesionshiftai.pyz <USER>@monsoon.hpc.nau.edu:~/lesionshiftai
scp config/baseline_cnn.yml <USER>@monsoon.hpc.nau.edu:~/lesionshiftai
scp config/vit_b16.yml <USER>@monsoon.hpc.nau.edu:~/lesionshiftai
scp scripts/hpc/train_baseline_cnn.sh <USER>@monsoon.hpc.nau.edu:~/lesionshiftai
scp scripts/hpc/train_ensemble_cnn.sh <USER>@monsoon.hpc.nau.edu:~/lesionshiftai
scp scripts/hpc/train_vit.sh <USER>@monsoon.hpc.nau.edu:~/lesionshiftai
ssh <USER>@monsoon.hpc.nau.edu # make sure you are connected to NAU VPN if not on campus
cd lesionshiftai
dos2unix *.sh # convert to Linux format if coming from Windows machine
chmod +x *.shEdit the staged baseline_cnn.yml and vit_b16.yml before launching jobs.
Required fields to set:
experiment_name: unique name for each run familyoutput_root: recommended/scratch/$USER/lesionshiftai/outputsdata.isic_root: absolute path to ISIC 2019 rootdata.ham_root: absolute path to HAM10000 root
Common tuning fields:
data.batch_size,data.num_workers,data.image_sizetrain.epochs,train.lr,train.weight_decay- ViT only:
train.warmup_epochs,train.min_lr
All commands below should be run from the directory that contains:
lesionshiftai.pyzbaseline_cnn.ymlvit_b16.ymlscripts/hpc/
sbatch train_baseline_cnn.shPrimary artifacts:
checkpoints/best.ptpredictions/val_final.csvpredictions/ham_test.csvmetrics/val_metrics.jsonmetrics/test_metrics.jsonmetrics/generalization_gap.json
ENSEMBLE_RUN_ID is required and must be shared across fold jobs.
Run all folds in one job:
export ENSEMBLE_RUN_ID=ens_$(date +%Y%m%d_%H%M%S)
sbatch train_ensemble_cnn.shRun a single fold (for retry/debug):
export ENSEMBLE_RUN_ID=ens_20260429_rerun
FOLD_INDEX=0 sbatch train_ensemble_cnn.shOptional fold count override:
ENSEMBLE_NUM_FOLDS=5 sbatch train_ensemble_cnn.shPrimary artifacts:
members/fold_<k>/...for each memberensemble/predictions/isic_val_aggregate_predictions.csvensemble/predictions/ham_test_aggregate_predictions.csvensemble/metrics/isic_val_aggregate_metrics.jsonensemble/metrics/ham_test_aggregate_metrics.jsonensemble/metrics/generalization_gap.json
sbatch train_vit.shPrimary artifacts:
checkpoints/best.ptcheckpoints/last.ptpredictions/val_final.csvpredictions/ham_test.csvmetrics/val_metrics.jsonmetrics/test_metrics.jsonmetrics/generalization_gap.json
For local smoke runs (single process):
uv run python scripts/train_baseline_cnn.py --config config/baseline_cnn.yml --threshold 0.5
uv run python scripts/train_ensemble_member_cnn.py --config config/baseline_cnn.yml --num-folds 5 --ensemble-run-id local_smoke --threshold 0.5
uv run python scripts/train_vit.py --config config/vit_b16.yml --threshold 0.5Fast feedback:
uv run pytest -m unit --no-covPipeline smoke tests:
uv run pytest -m integration --no-covFull validation with coverage gate:
uv run pytestThe project enforces --cov-fail-under=85 through pyproject.toml.
GitHub Actions workflow: .github/workflows/ci.yml
The CI job runs on:
- Pull requests targeting
main - Manual trigger (
workflow_dispatch)
CI steps:
- Install package + test dependencies
- Run unit tests (
pytest -m unit --no-cov) - Run integration tests (
pytest -m integration --no-cov) - Run full suite with coverage gate (
pytest)
Jeffrey Hoelzel Jr.
LesionShiftAI is a research benchmarking project and is not a clinical diagnostic tool.