16031aae96
CPU tests Workflow / Testing (ubuntu-latest, 3.12) (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.13) (push) Failing after 0s
Mypy Type Check / Type Check (push) Failing after 0s
Docs/Test WorkFlow / Test docs build (push) Failing after 1s
PR Conflict Labeler / labeling (push) Failing after 1s
Dependency resolution / Resolve [tflite] extra — Python 3.12 (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.10) (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.13) (push) Failing after 1s
CPU tests Workflow / build-pkg (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.10) (push) Failing after 0s
CPU tests Workflow / Testing (ubuntu-latest, 3.11) (push) Failing after 0s
Smoke Tests / try-all-models (macos-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (macos-latest, 3.13) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / testing-guardian (push) Has been cancelled
GPU tests Workflow / Testing (push) Has been cancelled
452 lines
16 KiB
Plaintext
452 lines
16 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "1777c5f9",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Training with PyTorch Lightning\n",
|
|
"\n",
|
|
"[](https://colab.research.google.com/github/roboflow/rf-detr/blob/develop/docs/cookbooks/pytorch-lightning.ipynb)\n",
|
|
"\n",
|
|
"**RF-DETR** is a real-time object detection model that combines the accuracy of\n",
|
|
"transformer-based detectors with inference speeds suitable for production.\n",
|
|
"The training stack is built on **PyTorch Lightning** — giving you\n",
|
|
"composable building blocks you can adopt incrementally, without changing your\n",
|
|
"existing code.\n",
|
|
"\n",
|
|
"| Building block | Role |\n",
|
|
"|---|---|\n",
|
|
"| `RFDETRModelModule` | `LightningModule` — model, loss, optimizer, scheduler |\n",
|
|
"| `RFDETRDataModule` | `LightningDataModule` — datasets and dataloaders |\n",
|
|
"| `build_trainer()` | Factory that assembles a `Trainer` with all RF-DETR callbacks |\n",
|
|
"\n",
|
|
"**Key design principle:** start simple, then pick up building blocks without losing\n",
|
|
"your trained weights.\n",
|
|
"\n",
|
|
"- **Phase 1** — `model.train()` one-liner (`EPOCHS_PHASE_1` epochs)\n",
|
|
"- **Phase 2** — swap in the PTL components and continue for `EPOCHS_PHASE_2` more\n",
|
|
" epochs from the same checkpoint, same output folder — no conversion required\n",
|
|
"- **End** — full training curve, single-image inference, and batch inference with\n",
|
|
" `trainer.predict()`"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "a750078f",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 1. Install RF-DETR 1.6.0\n",
|
|
"\n",
|
|
"`rfdetr[train,loggers]` pulls in PyTorch Lightning, torchmetrics, and the full\n",
|
|
"callback stack. `roboflow` downloads the demo dataset."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "a4688278",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"!pip install -q rfdetr[train,loggers]==1.6.0 roboflow"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "77fab4e5",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 2. Config\n",
|
|
"\n",
|
|
"All notebook-level knobs in one place. Adjust `EPOCHS_PHASE_*` and `BATCH_SIZE`\n",
|
|
"to match your hardware — every downstream cell reads from these variables.\n",
|
|
"\n",
|
|
"`num_workers` is set to `os.cpu_count()` inside a Jupyter/Colab kernel where\n",
|
|
"process forking is safe, and to `0` when running as a plain Python script.\n",
|
|
"On macOS and Windows, spawn-based multiprocessing would otherwise re-import\n",
|
|
"this module as `__main__` and retrigger training."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "174445e1",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import os\n",
|
|
"from pathlib import Path\n",
|
|
"\n",
|
|
"DATASET_DIR = os.environ.get(\"DATASET_DIR\", \"\")\n",
|
|
"OUTPUT_DIR = \"output\"\n",
|
|
"EPOCHS_PHASE_1 = 20\n",
|
|
"EPOCHS_PHASE_2 = 10\n",
|
|
"BATCH_SIZE = 12\n",
|
|
"THRESHOLD = 0.3\n",
|
|
"\n",
|
|
"os.makedirs(OUTPUT_DIR, exist_ok=True)\n",
|
|
"\n",
|
|
"try:\n",
|
|
" from IPython import get_ipython\n",
|
|
"\n",
|
|
" _in_notebook = get_ipython() is not None\n",
|
|
"except Exception:\n",
|
|
" _in_notebook = False\n",
|
|
"\n",
|
|
"# Outside a notebook kernel, macOS/Windows spawn-based multiprocessing will\n",
|
|
"# re-import this script as __main__, triggering training again.\n",
|
|
"# Use 0 workers in that case; inside a kernel the usual forking rules apply safely.\n",
|
|
"num_workers = (os.cpu_count() or 0) if _in_notebook else 0"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3a43c723",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 3. Dataset\n",
|
|
"\n",
|
|
"[Aquarium Combined](https://universe.roboflow.com/brad-dwyer/aquarium-combined)\n",
|
|
"— 638 images across 7 classes (`fish`, `jellyfish`, `penguin`, `puffin`,\n",
|
|
"`shark`, `starfish`, `stingray`). It is small enough to complete a demo run\n",
|
|
"in a few minutes yet diverse enough to produce meaningful detection results.\n",
|
|
"\n",
|
|
"Set `ROBOFLOW_API_KEY` as a Colab secret (Secrets panel, key icon) or as an\n",
|
|
"environment variable before running this cell. The dataset is downloaded in\n",
|
|
"COCO format, which RF-DETR reads natively."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "34478ada",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import json\n",
|
|
"\n",
|
|
"from roboflow import Roboflow\n",
|
|
"\n",
|
|
"try:\n",
|
|
" from google.colab import userdata # type: ignore[import]\n",
|
|
"\n",
|
|
" API_KEY = userdata.get(\"ROBOFLOW_API_KEY\")\n",
|
|
"except Exception:\n",
|
|
" API_KEY = os.environ[\"ROBOFLOW_API_KEY\"]\n",
|
|
"\n",
|
|
"rf = Roboflow(api_key=API_KEY)\n",
|
|
"dataset = rf.workspace(\"brad-dwyer\").project(\"aquarium-combined\").version(1).download(\"coco\", location=\"datasets\")\n",
|
|
"DATASET_DIR = dataset.location\n",
|
|
"\n",
|
|
"with open(Path(DATASET_DIR) / \"train\" / \"_annotations.coco.json\") as f:\n",
|
|
" _ann = json.load(f)\n",
|
|
"\n",
|
|
"CLASS_NAMES = [c[\"name\"] for c in sorted(_ann[\"categories\"], key=lambda c: c[\"id\"])]\n",
|
|
"NUM_CLASSES = len(CLASS_NAMES)\n",
|
|
"print(f\"Dataset : {DATASET_DIR}\")\n",
|
|
"print(f\"Classes : {NUM_CLASSES} — {CLASS_NAMES}\")\n",
|
|
"\n",
|
|
"with open(Path(DATASET_DIR) / \"valid\" / \"_annotations.coco.json\") as f:\n",
|
|
" _val_ann = json.load(f)\n",
|
|
"\n",
|
|
"val_images_dir = Path(DATASET_DIR) / \"valid\"\n",
|
|
"val_image_files = [img[\"file_name\"] for img in _val_ann[\"images\"]]"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "44bbcce0",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 4. Phase 1 — `model.train()` one-liner\n",
|
|
"\n",
|
|
"The same high-level API that has been in RF-DETR since v1.0 — nothing here\n",
|
|
"changes from previous releases. If you have existing training scripts, they\n",
|
|
"keep working without modification.\n",
|
|
"\n",
|
|
"`pretrain_weights=\"rf-detr-medium.pth\"` downloads COCO-pretrained backbone\n",
|
|
"weights automatically on first run and caches them locally. `use_ema=True`\n",
|
|
"maintains an exponential moving average of the weights to stabilise validation\n",
|
|
"metrics. `run_test=False` skips the final test-set evaluation to keep Phase 1\n",
|
|
"fast; Phase 2 turns it back on.\n",
|
|
"\n",
|
|
"After this cell completes, `OUTPUT_DIR/checkpoint_best_total.pth` holds the\n",
|
|
"best weights seen so far — the starting point for Phase 2."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "bf7b136e",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from rfdetr import RFDETRMedium\n",
|
|
"\n",
|
|
"model = RFDETRMedium(num_classes=NUM_CLASSES, pretrain_weights=\"rf-detr-medium.pth\")\n",
|
|
"model.train(\n",
|
|
" dataset_dir=DATASET_DIR,\n",
|
|
" epochs=EPOCHS_PHASE_1,\n",
|
|
" batch_size=BATCH_SIZE,\n",
|
|
" grad_accum_steps=4,\n",
|
|
" lr=1e-4,\n",
|
|
" num_workers=num_workers,\n",
|
|
" output_dir=OUTPUT_DIR,\n",
|
|
" use_ema=True,\n",
|
|
" run_test=False,\n",
|
|
" progress_bar=\"rich\",\n",
|
|
" tensorboard=True,\n",
|
|
" seed=42,\n",
|
|
")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "178dd044",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 5. Phase 2 — PTL building blocks\n",
|
|
"\n",
|
|
"Pick up the three PTL components and call `trainer.fit()` pointing at the Phase 1\n",
|
|
"checkpoint. No weight conversion is needed — `RFDETRModelModule.on_load_checkpoint`\n",
|
|
"detects the `.pth` format and remaps keys automatically.\n",
|
|
"\n",
|
|
"A lower learning rate (`5e-5`) is used because the model is already partially\n",
|
|
"converged. `epochs=EPOCHS_PHASE_1 + EPOCHS_PHASE_2` sets the *absolute* epoch\n",
|
|
"ceiling; because the loaded checkpoint records the last completed epoch, PTL\n",
|
|
"runs exactly `EPOCHS_PHASE_2` additional epochs before stopping. The same\n",
|
|
"`OUTPUT_DIR` is reused so checkpoints and metrics all land in one place."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "d4526ab0",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import pandas as pd\n",
|
|
"\n",
|
|
"from rfdetr import RFDETRDataModule, RFDETRModelModule, build_trainer\n",
|
|
"from rfdetr.config import RFDETRMediumConfig, TrainConfig\n",
|
|
"\n",
|
|
"# Read Phase 1 metrics before Phase 2 overwrites the CSV.\n",
|
|
"df1 = pd.read_csv(f\"{OUTPUT_DIR}/metrics.csv\")\n",
|
|
"\n",
|
|
"model_config = RFDETRMediumConfig(\n",
|
|
" num_classes=NUM_CLASSES,\n",
|
|
" pretrain_weights=\"rf-detr-medium.pth\",\n",
|
|
")\n",
|
|
"\n",
|
|
"# epochs = EPOCHS_1 + EPOCHS_2 so PTL (which resumes the epoch counter from the\n",
|
|
"# checkpoint) runs exactly EPOCHS_2 additional epochs before reaching max_epochs.\n",
|
|
"train_config = TrainConfig(\n",
|
|
" dataset_dir=DATASET_DIR,\n",
|
|
" epochs=EPOCHS_PHASE_1 + EPOCHS_PHASE_2,\n",
|
|
" batch_size=BATCH_SIZE,\n",
|
|
" grad_accum_steps=4,\n",
|
|
" lr=5e-5,\n",
|
|
" num_workers=num_workers,\n",
|
|
" output_dir=OUTPUT_DIR,\n",
|
|
" use_ema=True,\n",
|
|
" run_test=True,\n",
|
|
" progress_bar=\"tqdm\",\n",
|
|
" tensorboard=True,\n",
|
|
" seed=42,\n",
|
|
")\n",
|
|
"\n",
|
|
"module = RFDETRModelModule(model_config=model_config, train_config=train_config)\n",
|
|
"datamodule = RFDETRDataModule(model_config=model_config, train_config=train_config)\n",
|
|
"trainer = build_trainer(train_config, model_config)\n",
|
|
"\n",
|
|
"# Resume directly from the Phase 1 .pth — no conversion needed.\n",
|
|
"trainer.fit(module, datamodule, ckpt_path=f\"{OUTPUT_DIR}/checkpoint_best_total.pth\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "344147ee",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 6. Training curve\n",
|
|
"\n",
|
|
"Phase 1 and Phase 2 each emit their own `metrics.csv` (Phase 2 overwrites Phase 1's\n",
|
|
"file when it starts). We captured the Phase 1 copy before fitting so we can\n",
|
|
"concatenate both DataFrames and plot a single continuous curve across all epochs."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "751e8277",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%matplotlib inline\n",
|
|
"\n",
|
|
"import matplotlib.pyplot as plt\n",
|
|
"import numpy as np\n",
|
|
"import supervision as sv\n",
|
|
"from IPython.display import display\n",
|
|
"from PIL import Image\n",
|
|
"\n",
|
|
"from rfdetr.visualize.training import plot_metrics\n",
|
|
"\n",
|
|
"df2 = pd.read_csv(f\"{OUTPUT_DIR}/metrics.csv\")\n",
|
|
"\n",
|
|
"combined_csv = f\"{OUTPUT_DIR}/metrics_combined.csv\"\n",
|
|
"pd.concat([df1, df2], ignore_index=True).to_csv(combined_csv, index=False)\n",
|
|
"print(f\"Combined CSV: {combined_csv} ({len(df1) + len(df2)} rows)\")\n",
|
|
"\n",
|
|
"fig = plot_metrics(combined_csv)\n",
|
|
"display(fig)\n",
|
|
"print(f\"Saved: {OUTPUT_DIR}/metrics_plot.png\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "d3f29ab1",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 7. Single-image inference — `model.predict()`\n",
|
|
"\n",
|
|
"`RFDETRMedium` can be instantiated directly from a checkpoint path for inference\n",
|
|
"— no training config needed. `model.predict()` accepts a PIL `Image`, runs\n",
|
|
"preprocessing, the forward pass, and postprocessing internally, and returns a\n",
|
|
"`supervision.Detections` object that is ready to annotate and display."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "90e06abb",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%matplotlib inline\n",
|
|
"\n",
|
|
"model = RFDETRMedium(pretrain_weights=f\"{OUTPUT_DIR}/checkpoint_best_total.pth\", num_classes=NUM_CLASSES)\n",
|
|
"\n",
|
|
"image = Image.open(val_images_dir / val_image_files[0])\n",
|
|
"detections = model.predict(image, threshold=THRESHOLD)\n",
|
|
"\n",
|
|
"annotated = sv.BoxAnnotator().annotate(image.copy(), detections)\n",
|
|
"annotated = sv.LabelAnnotator().annotate(annotated, detections, labels=[CLASS_NAMES[c] for c in detections.class_id])\n",
|
|
"\n",
|
|
"plt.figure(figsize=(10, 7))\n",
|
|
"plt.imshow(np.array(annotated))\n",
|
|
"plt.axis(\"off\")\n",
|
|
"plt.show()\n",
|
|
"\n",
|
|
"print(f\"Detected {len(detections)} object(s)\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "32baa2d8",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 8. Batch inference — `trainer.predict()`\n",
|
|
"\n",
|
|
"Instead of calling `model.predict()` one image at a time, `trainer.predict()`\n",
|
|
"streams the entire validation set through the model in batches and collects\n",
|
|
"all results — useful for dataset-level evaluation or offline export pipelines.\n",
|
|
"\n",
|
|
"**What happens under the hood:**\n",
|
|
"\n",
|
|
"1. PTL calls `datamodule.setup(\"predict\")` — this builds `_dataset_val` if it\n",
|
|
" does not exist yet. Because `trainer.fit()` already ran above, the dataset\n",
|
|
" is already in memory and `setup` is a no-op.\n",
|
|
"2. PTL calls `datamodule.predict_dataloader()` — this returns the *validation*\n",
|
|
" dataset wrapped in a `SequentialSampler` (no shuffle, no augmentation),\n",
|
|
" identical to `val_dataloader`.\n",
|
|
"3. For each batch, `RFDETRModelModule.predict_step()` runs a forward pass under\n",
|
|
" `torch.no_grad()` and returns a list of `{\"scores\", \"labels\", \"boxes\"}`\n",
|
|
" dicts — one dict per image in the batch.\n",
|
|
"4. `trainer.predict()` collects all batch results into a\n",
|
|
" `List[List[dict]]` (outer = batches, inner = images).\n",
|
|
"\n",
|
|
"Flatten, apply a confidence threshold, and wrap in `sv.Detections`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "7d7543ff",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%matplotlib inline\n",
|
|
"\n",
|
|
"import itertools\n",
|
|
"\n",
|
|
"# Returns List[List[dict]] — outer: batches, inner: one dict per image\n",
|
|
"# Each dict has keys: \"scores\" (N,), \"labels\" (N,), \"boxes\" (N, 4) — all tensors\n",
|
|
"all_preds = trainer.predict(module, datamodule)\n",
|
|
"\n",
|
|
"# Flatten the batch dimension → one result dict per validation image\n",
|
|
"flat_preds = [img_result for batch in all_preds for img_result in batch]\n",
|
|
"print(f\"Ran predict on {len(flat_preds)} validation images\")"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "ed942d37",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Build sv.Detections from raw tensors and visualise the first four images\n",
|
|
"annotated_images = []\n",
|
|
"for img_file, result in itertools.islice(zip(val_image_files, flat_preds), 4):\n",
|
|
" keep = result[\"scores\"] > THRESHOLD\n",
|
|
" detections = sv.Detections(\n",
|
|
" xyxy=result[\"boxes\"][keep].cpu().float().numpy(),\n",
|
|
" confidence=result[\"scores\"][keep].cpu().float().numpy(),\n",
|
|
" class_id=result[\"labels\"][keep].cpu().long().numpy(),\n",
|
|
" )\n",
|
|
" image = Image.open(val_images_dir / img_file)\n",
|
|
" annotated = sv.BoxAnnotator().annotate(image.copy(), detections)\n",
|
|
" annotated = sv.LabelAnnotator().annotate(\n",
|
|
" annotated, detections, labels=[CLASS_NAMES[c] for c in detections.class_id]\n",
|
|
" )\n",
|
|
" annotated_images.append(np.array(annotated))\n",
|
|
" print(f\" {img_file}: {len(detections)} detection(s)\")\n",
|
|
"\n",
|
|
"fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n",
|
|
"for ax, img in zip(axes.flat, annotated_images):\n",
|
|
" ax.imshow(img)\n",
|
|
" ax.axis(\"off\")\n",
|
|
"plt.tight_layout()\n",
|
|
"plt.show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3387e060",
|
|
"metadata": {},
|
|
"source": [
|
|
"## 9. Next steps\n",
|
|
"\n",
|
|
"You have now seen the complete 1.6.0 stack — from a one-liner `model.train()`\n",
|
|
"through composable PTL components to batch inference. From here:\n",
|
|
"\n",
|
|
"- [PyTorch Lightning training docs](https://rfdetr.roboflow.com/1.6.0/reference/training/) — custom callbacks, multi-GPU, mixed precision\n",
|
|
"- [Advanced training options](https://rfdetr.roboflow.com/1.6.0/learn/train/advanced/) — augmentations, EMA, learning rate schedules\n",
|
|
"- [Logger integrations (ClearML, MLflow, W&B)](https://rfdetr.roboflow.com/1.6.0/learn/train/loggers/) — experiment tracking\n",
|
|
"- [Export your model](https://rfdetr.roboflow.com/1.6.0/learn/export/) — ONNX, TensorRT, CoreML"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"jupytext": {
|
|
"cell_metadata_filter": "-all",
|
|
"formats": "ipynb,py",
|
|
"main_language": "python"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|