This document describes how to run CVRKD-IQA (this repository) in a reproducible, operationally safe way: pinned environments, canonical data layout, deterministic training runs, checkpoint hygiene, evaluation, and serving the trained student model behind an inference API.
It complements the quick start in the README.
| Item | Assumption |
|---|---|
| Runtime | Python 3.9, Linux, NVIDIA GPU with CUDA |
| Training data | KADID-10K (distorted) + DIV2K (HQ references) |
| Eval data | LIVE, CSIQ, TID2013, KonIQ-10K, LIVEC, BID, PIQ23 |
| Weights | Stored under model_zoo/, loaded by path from CLI flags |
| Tracking | Config snapshot auto-saved to checkpoint_DistillationIQA/setting.txt |
The reference IQA baselines in models/ (CNNIQA, HyperIQA, WaDIQaM, …) are
research comparators, not part of the production student pipeline.
Use an isolated, reproducible environment and install exact wheels for the GPU.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# Pin exact torch builds for your CUDA driver, e.g. cu118:
pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cu118For a fully hermetic setup, containerize:
FROM pytorch/pytorch:2.1.2-cuda12.1-cudnn8-runtime
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "test_DistillationIQA_single.py"]Freeze all transitive deps before a release:
pip freeze > constraints.txtand install withpip install -c constraints.txt.
Datasets are gitignored; download them once and keep the layout stable.
dataset/
├── kadid10k/
│ ├── dmos.csv
│ └── images/
├── DIV2K_ref/
│ ├── train_HR/ # HQ references used during training
│ └── val_HR/ # HQ references used during testing
├── LIVE/ CSIQ/ TID2013/ LIVEC/ koniq-10k/ BID/ PIQ23/
Dataset root paths are hard-coded in each script's folder_path / img_num
dicts (e.g. train_DistillationIQA.py, test_DistillationIQA.py) — keep the
default ./dataset/<NAME> layout or edit those dicts consistently.
Before a training run, verify every index range in the script matches the number of
samples you downloaded (see the img_num dicts) and that each CSV/MAT label file is
present:
ls dataset/kadid10k/dmos.csv dataset/LIVE/dmos_realigned.mat \
dataset/TID2013/mos_with_names.txtMissing a label file fails silently later — check logs for empty epochs.
- Every knob is an argparse flag in
option_train_DistillationIQA*.py. - The effective config is snapshotted automatically to
checkpoint_DistillationIQA/setting.txt— store it with the run artifacts. - Checkpoints are written each epoch as
checkpoint_DistillationIQA/models/Distillation_inner_<epoch>_saved_model.pth.
export CUDA_VISIBLE_DEVICES=0
python -m torch.distributed.run --nproc_per_node=1 train_DistillationIQA.py \
--train_dataset kadid10k \
--test_dataset tid2013 \
--self_patch_num 10 \
--patch_size 224 \
--batch_size 32 \
--epochs 100 \
--lr 2e-5 \
--feature_stacking False \
--repeatable_loss FalseThe training loop seeds only the train-index shuffle, so bit-exact reproducibility
requires explicit seeding. Before solver.train() add:
import random, numpy, torch
random.seed(seed); numpy.random.seed(seed); torch.manual_seed(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = FalseRecord the seed and the exact commit (git rev-parse HEAD) alongside the setting.txt
snapshot in your experiment log. Note: the test code draws random crops
(RandomCrop) per image at inference, so average over several runs when comparing models.
runs/<run_id>/
├── setting.txt # auto-snapshot (copy after run starts)
├── seed.txt # manual
├── git_commit.txt # manual
├── models/ # checkpoints per epoch
└── logs/ # train/val acc + loss curves (auto-written)
Training already writes loss curves to checkpoint_DistillationIQA/log/*.txt
(train_acc, test_acc, pred_loss, feature_loss, loss). Keep the newest
best checkpoint per fold and prune the rest.
| Role | Expected path (CLI default) |
|---|---|
| FR-teacher | model_zoo/FR_teacher_cross_dataset.pth |
| NAR-student | model_zoo/NAR_student_cross_dataset.pth |
Rules:
*.pth/*.ptare gitignored — weights are never committed to the repo.- State dicts are loaded via the model's tolerant
_load_state_dict; a dimension mismatch on a key fails loudly (good). Check that the loaded dict matches the variant you instantiate (orgvsstackingV1vsstackingV2) — they are not interchangeable. - For releases, publish weights with metadata: model variant, patch size, distillation layer, epoch, and the exact metrics from the run that produced them.
python test_DistillationIQA.py # runs LIVE + LIVEC folds by default- Reports SRCC / PLCC / KRCC. PLCC can be computed after a 4-parameter logistic
fit to the MOS scale (
--use_fitting_prcc_srcc, defaultTrue); SRCC/KRCC are monotonic and need no fitting. - Predictions and ground truth are dumped to
pred_*.txt/gt_*.txt.
Prefer the class API in test_DistillationIQA_single.py (import it; it is a module,
not a CLI):
from test_DistillationIQA_single import DistillationIQASolver
solver = DistillationIQASolver(
student_address="./model_zoo/NAR_student_cross_dataset.pth",
net_mode="org", # org | stackingV1 | stackingV2
)
score = solver.cvr_on_single_image("lq.png", "ref_hq.png") # content-variant ref OKThe returned score is on the model's arbitrary scale. Map it to a target MOS range
with the logistic fit in tools.py::convert_obj_score before exposing it to users.
Compare every candidate model against the previous best on the same folds of all held-out sets and require an improvement (or no regression) on SRCC before promotion. Average ≥ 3 repetitions to damp the random-crop variance.
The student forward pass is: LQ image + a content-variant HQ reference → quality score. Wrap it behind an idempotent API; below is a minimal FastAPI sketch (concept only — no new files are added by this document).
# app.py (illustrative)
import io
from fastapi import FastAPI, UploadFile
from PIL import Image
from test_DistillationIQA_single import DistillationIQASolver
app = FastAPI()
solver = DistillationIQASolver("model_zoo/NAR_student_cross_dataset.pth", "org")
@app.post("/score")
def score(lq: UploadFile, ref: UploadFile):
lq_img, ref_img = Image.open(lq.file).convert("RGB"), Image.open(ref.file).convert("RGB")
lq_img.save("/tmp/lq.png"); ref_img.save("/tmp/ref.png")
return {"quality_score": solver.cvr_on_single_image("/tmp/lq.png", "/tmp/ref.png")}Operational checklist:
- Model warm-up & single instance: load the checkpoint once at startup; don't reload per request.
- Batch requests (server needs higher throughput) by routing many LQ/ref pairs
through
solver.test, which already batches patches per image. - GPU isolation: one worker process per GPU; set
CUDA_VISIBLE_DEVICES. - Input policy: fixed patch size (default
224), reject/resize=Truefor smaller images, define your MOS-scale mapping response contract. - Observability: log score distribution, p95 latency, and input dimensions;
alert on GPU OOM (patches are shaped
[1, self_patch_num, 3, H, W]per image). - Fallbacks: return a clear error for unreadable images; cap concurrent requests to available VRAM.
| Symptom | Likely cause / fix |
|---|---|
| Empty epochs or 0 samples | Dataset layout / label file mismatch — check img_num vs. downloaded files |
RuntimeError: dimension mismatch ... in checkpoint |
Checkpoint built for a different variant (org/stackingV1/stackingV2) or self_patch_num |
| Different scores run-to-run | RandomCrop at test time — average ≥ 3 runs or use fixed center patches |
| Slow first epoch | ResNet-50 ImageNet weights are downloaded via model_zoo.load_url at first use — cache them |
| OOM on inference | Reduce --self_patch_num or batch images one at a time |
| Settings written to wrong path | --checkpoint_dir default ./checkpoint_DistillationIQA/; set per-run with the run id |
-
requirements.txtresolved to a frozenconstraints.txton the target GPU image. - Datasets verified (layout + index counts), documented hashes stored off-repo.
- Best student checkpoint published to
model_zoo/(gitignored) or external storage with full metadata (variant, epoch, config, metrics, commit, seed). - Config snapshot (
setting.txt) and git commit SHA archived with the run. - Cross-dataset eval reproduced ≥ 3×; mean ± std reported.
- Single-image smoke test passed on synthetic and authentic samples.
- Serving path tested for warm start, error handling, and MOS-scale mapping.
- LICENSE/attribution for CVRKD-IQA, HyperIQA, and all benchmark datasets confirmed.