Image-to-Text
PyTorch
Safetensors
PEFT
English
remote-sensing
satellite-imagery
earth-observation
change-detection
visual-grounding
image-captioning
visual-question-answering
optical-sar-fusion
sar
multimodal
lora
Instructions to use thundercode/SatQuery with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use thundercode/SatQuery with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
release: add docs/TRAINING.md
Browse files- docs/TRAINING.md +205 -0
docs/TRAINING.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Training
|
| 2 |
+
|
| 3 |
+
**Status tags:** `IMPLEMENTED` · `VERIFIED` · `MEASURED` · `ATTEMPTED` · `NOT RUN` · `REJECTED`.
|
| 4 |
+
|
| 5 |
+
The project trains **small modules on frozen backbones**. No backbone is fine-tuned end-to-end. Every
|
| 6 |
+
hyperparameter lives in `configs/base.yaml` (no magic numbers in Python), and every trained artifact
|
| 7 |
+
records the frozen config hash it was trained against.
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 1. Overview
|
| 12 |
+
|
| 13 |
+
| Artifact | Backbone (frozen) | Where trained | Selection signal |
|
| 14 |
+
|---|---|---|---|
|
| 15 |
+
| `router` adapter | `all-MiniLM-L6-v2` | local CPU | val (group-split) |
|
| 16 |
+
| `grounding` head | RemoteCLIP ViT-B/32 | local | val fraction 0.10 |
|
| 17 |
+
| `change` head | STANet ResNet-18 + PAM | local | val (LEVIR split) |
|
| 18 |
+
| `optical_sar` fusion head | CROMA-base | local, **seed sweep** | held-out test (pre-registered) |
|
| 19 |
+
| `change_vqa` head | over cached change features | **external GPU (Kaggle)** | val answer accuracy |
|
| 20 |
+
| `vlm` LoRA adapter | SmolVLM-500M-Instruct | **external GPU** | frozen 1000-question subset |
|
| 21 |
+
|
| 22 |
+
**CPU-first.** The router and every head except the VLM adapter train on CPU. The VLM LoRA adapter
|
| 23 |
+
requires a GPU (T4-class).
|
| 24 |
+
|
| 25 |
+
## 2. Router adapter
|
| 26 |
+
|
| 27 |
+
A **50,822-parameter** adapter over the frozen MiniLM encoder.
|
| 28 |
+
|
| 29 |
+
| Hyperparameter | Value |
|
| 30 |
+
|---|---|
|
| 31 |
+
| epochs | 60 |
|
| 32 |
+
| batch size | 64 |
|
| 33 |
+
| learning rate | 0.001 |
|
| 34 |
+
| weight decay | 0.01 |
|
| 35 |
+
| task loss weight | 1.0 |
|
| 36 |
+
| modality loss weight | 0.3 |
|
| 37 |
+
| binary loss weight | 0.5 |
|
| 38 |
+
| val ratio | 0.15 |
|
| 39 |
+
| hard negatives to test | true |
|
| 40 |
+
|
| 41 |
+
**Finding F4-2 — the encoder is frozen, so embeddings are cached** and the adapter trains on cached
|
| 42 |
+
vectors. **Measured: 20 epochs / 4,096 vectors in 0.28 s on CPU.** No GPU is required.
|
| 43 |
+
|
| 44 |
+
**Finding F4-3 — splits are by GROUP** (template / hard-negative family), never by example.
|
| 45 |
+
Hard-negative families are placed in the **test** split so their accuracy measures generalisation
|
| 46 |
+
rather than memorisation.
|
| 47 |
+
|
| 48 |
+
> **Status:** the adapter is trained and shipped. Its measured number (0.965116) is **validation-only,
|
| 49 |
+
> ungated, n = 86**; the router **test split was NOT RUN**.
|
| 50 |
+
|
| 51 |
+
## 3. Grounding head
|
| 52 |
+
|
| 53 |
+
A trainable head over the frozen RemoteCLIP ViT-B/32 encoder.
|
| 54 |
+
|
| 55 |
+
| Hyperparameter | Value |
|
| 56 |
+
|---|---|
|
| 57 |
+
| learning rate | 0.0001 |
|
| 58 |
+
| batch size | 16 |
|
| 59 |
+
| epochs | 20 |
|
| 60 |
+
| weight decay | 0.0001 |
|
| 61 |
+
| warmup ratio | 0.05 |
|
| 62 |
+
| grad clip | 1.0 |
|
| 63 |
+
| val fraction | 0.10 |
|
| 64 |
+
| save every steps | 500 |
|
| 65 |
+
| box loss weight | 0.5 |
|
| 66 |
+
| GIoU loss weight | 0.3 |
|
| 67 |
+
| confidence loss weight | 0.2 |
|
| 68 |
+
| `positive_confidence_weight` | **20.0** |
|
| 69 |
+
|
| 70 |
+
**Architecture constraint (enforced, not documented).** Per-cell feature is
|
| 71 |
+
`concat([patch, text, patch·text, global_pool]) = 4 × 512 = 2048`. `core/config.py` **rejects any
|
| 72 |
+
value other than** `4 × grounding.encoder_projected_dim` at load time, and the specialist asserts the
|
| 73 |
+
same 512 against the real model — because a mismatch is a *silent* shape error that torch only raises
|
| 74 |
+
at the similarity step, after patch features are already cached.
|
| 75 |
+
|
| 76 |
+
**Why `positive_confidence_weight = 20.0`.** Objectness BCE sees ~1 positive cell out of 49.
|
| 77 |
+
Unweighted, the optimum is "no object" everywhere; the weight is what stops that collapse.
|
| 78 |
+
|
| 79 |
+
**Resolution is frozen at 224.** 448 was evaluated and **REJECTED** (paired test: mean diff −0.0147,
|
| 80 |
+
95 % CI [−0.0160, −0.0134], t = −22.63, at 1.59× latency).
|
| 81 |
+
|
| 82 |
+
## 4. Change head
|
| 83 |
+
|
| 84 |
+
STANet-style Siamese detector.
|
| 85 |
+
|
| 86 |
+
| Hyperparameter | Value |
|
| 87 |
+
|---|---|
|
| 88 |
+
| encoder | ResNet-18 |
|
| 89 |
+
| self-attention | **PAM** (BAM alternative not used) |
|
| 90 |
+
| tile size | 256 |
|
| 91 |
+
| tile overlap | 0 |
|
| 92 |
+
| threshold | 0.50 |
|
| 93 |
+
| min component pixels | 32 |
|
| 94 |
+
| learning rate | 0.001 |
|
| 95 |
+
| batch size | 8 |
|
| 96 |
+
| BCE weight | 0.5 |
|
| 97 |
+
| Dice weight | 0.5 |
|
| 98 |
+
|
| 99 |
+
Trained on LEVIR-CD-256 (train 7120 / val 1024 / test 2048). **This is the only task with a
|
| 100 |
+
`VERIFIED` headline metric** (pooled IoU 0.8122 on the immutable test split).
|
| 101 |
+
|
| 102 |
+
## 5. Optical-SAR fusion head
|
| 103 |
+
|
| 104 |
+
| Hyperparameter | Value |
|
| 105 |
+
|---|---|
|
| 106 |
+
| input dim | 2318 = 3 × 768 + 12 + 2 |
|
| 107 |
+
| hidden dim | 512 |
|
| 108 |
+
| dropout | 0.2 |
|
| 109 |
+
| num classes | 19 (BigEarthNet CLC) |
|
| 110 |
+
|
| 111 |
+
**Seed sweep.** Training was run as two arms (**armA**, **armB**) × five seeds (**100–104**), with a
|
| 112 |
+
per-arm variance report (`armA_seed_variance_report.json`). The **production head** is a distinct,
|
| 113 |
+
frozen artifact (`fusion_head_production_v001/head.pt`) with its own
|
| 114 |
+
`production_head_record.json` and a `phase12_rerun_verification.json`.
|
| 115 |
+
|
| 116 |
+
**Pre-registration.** The headline metric is a **pre-registered** 115-class protocol
|
| 117 |
+
(`pre_registered_11.5`) computed over the 19-class label space on the held-out test split. The
|
| 118 |
+
metric JSON records `is_deciding_statistic: False`, i.e. it is a reported measurement, not a
|
| 119 |
+
decision statistic.
|
| 120 |
+
|
| 121 |
+
**Feature caches.** Training consumes cached CROMA features
|
| 122 |
+
(`fusion_features/`, `fusion_features_armB/`, ~231 MB each). These caches are **reproducible** and are
|
| 123 |
+
not released as model weights.
|
| 124 |
+
|
| 125 |
+
## 6. Change-VQA head — trained externally
|
| 126 |
+
|
| 127 |
+
The change-VQA head was trained **outside this repository**, on an external GPU (Kaggle), following
|
| 128 |
+
`docs/R02_KAGGLE_TRAINING_GUIDE.md`. That guide's status on entry was
|
| 129 |
+
`IMPLEMENTATION_READY_FOR_EXTERNAL_TRAINING`, and its explicit contract is:
|
| 130 |
+
|
| 131 |
+
> **Training produces an artifact, not a verified capability, and the run record says
|
| 132 |
+
> `TRAINED_UNVERIFIED`.**
|
| 133 |
+
|
| 134 |
+
The returned checkpoint was **promoted** through a byte-identity gate
|
| 135 |
+
(`artifacts/change_vqa/run/PROMOTION.json`):
|
| 136 |
+
|
| 137 |
+
| Property | Value |
|
| 138 |
+
|---|---|
|
| 139 |
+
| sha256 | `cfae5e43b97ca930f568dc5b8ae4f36b24e9ff717af226159802206ffd63a82a` |
|
| 140 |
+
| bytes | 5,822,809 |
|
| 141 |
+
| architecture | `change_vqa_head_v1` |
|
| 142 |
+
| parameters | 1,453,912 |
|
| 143 |
+
| non-finite tensors | **0** |
|
| 144 |
+
| weights modified during promotion | **false** |
|
| 145 |
+
| byte-identical to source | **true** |
|
| 146 |
+
| hash agrees across | `model_metadata.json`, `run_record.json`, `hashes.json` |
|
| 147 |
+
|
| 148 |
+
**Selection:** epoch **8**, chosen on **Val answer accuracy = 0.700018**, stopped by early stopping.
|
| 149 |
+
Seed 42. It trains on **cached change + text features** (specs `change_feat_v1`,
|
| 150 |
+
`change_cache_spec c801326f85a185f8`, `text_cache_spec d2801ea1a314354a`), not on raw imagery.
|
| 151 |
+
|
| 152 |
+
> **Note.** The raw CDVQA loader (see [`DATASETS.md`](DATASETS.md)) loads examples but has **no
|
| 153 |
+
> training loop**; the shipped head is a cached-feature model. These are different paths and are not
|
| 154 |
+
> conflated.
|
| 155 |
+
|
| 156 |
+
## 7. VLM LoRA adapter — trained externally
|
| 157 |
+
|
| 158 |
+
A PEFT LoRA adapter on **frozen** `HuggingFaceTB/SmolVLM-500M-Instruct`.
|
| 159 |
+
|
| 160 |
+
| Hyperparameter | Value |
|
| 161 |
+
|---|---|
|
| 162 |
+
| PEFT version | **0.19.1** |
|
| 163 |
+
| `r` (rank) | 16 |
|
| 164 |
+
| `alpha` | 32 |
|
| 165 |
+
| `dropout` | 0.05 |
|
| 166 |
+
| target modules | `model.text_model.*.{q,k,v,o,gate,up,down}_proj` |
|
| 167 |
+
| precision | **fp16** (finding C-6: T4 is SM 7.5 → **fp16, NOT bf16**) |
|
| 168 |
+
| batch size | 2 |
|
| 169 |
+
| gradient accumulation | 8 |
|
| 170 |
+
| learning rate | 0.0002 |
|
| 171 |
+
| epochs | 1 |
|
| 172 |
+
| gradient checkpointing | true |
|
| 173 |
+
| save every steps | 500 |
|
| 174 |
+
|
| 175 |
+
**Finding F5-2 (cost).** The processor's default `longest_edge` is 2048, which upscales 512-px tiles
|
| 176 |
+
4× and then splits them into **17 sub-images** (`pixel_values (1, 17, 3, 512, 512)`, 1142 prompt
|
| 177 |
+
tokens). Pinning `processor_longest_edge: 512` yields `pixel_values (1, 1, 3, 512, 512)`. The plan
|
| 178 |
+
estimated a 4× cost overrun; the **measured** figure is ~17×.
|
| 179 |
+
|
| 180 |
+
**Finding F5-3.** SmolVLM requires one `<image>` token per image in the prompt; hand-written prompt
|
| 181 |
+
strings raise `ValueError`. Prompts are always built through `processor.apply_chat_template()`.
|
| 182 |
+
|
| 183 |
+
**Outcome:** metrics usable (exact_match 0.963, F1 0.96432, +49.5 pp) but the artifact is
|
| 184 |
+
**ACCEPTANCE-REJECTED** for promotion. The deployed caption/VQA path uses the **unadapted** model.
|
| 185 |
+
See [`MODELS.md`](MODELS.md) §3.6.
|
| 186 |
+
|
| 187 |
+
## 8. Reproducibility contract for training
|
| 188 |
+
|
| 189 |
+
- **Seed 42** everywhere (`project.seed`).
|
| 190 |
+
- **Precision `fp16`** (T4 constraint), never bf16.
|
| 191 |
+
- Every artifact records the **frozen config hash** `78f1e3700da15aa1`; a config edit moves the hash
|
| 192 |
+
and invalidates the artifact.
|
| 193 |
+
- `save_every_steps: 500`; checkpoints are archived as provenance, not released.
|
| 194 |
+
- Training guides state their own entry status and **never** claim a trained artifact is a verified
|
| 195 |
+
capability.
|
| 196 |
+
|
| 197 |
+
## 9. What was NOT trained
|
| 198 |
+
|
| 199 |
+
| Item | State |
|
| 200 |
+
|---|---|
|
| 201 |
+
| Backbone fine-tuning (any) | **NOT DONE** — all backbones frozen |
|
| 202 |
+
| Router on the test split | **NOT RUN** |
|
| 203 |
+
| Any end-to-end / joint training | **NOT RUN** |
|
| 204 |
+
| Re-training of the change head at a second resolution | **NOT RUN** |
|
| 205 |
+
| Benchmark adapters | **NOT RUN** |
|