Commit Graph

86 Commits

Author SHA1 Message Date
pikaliov
7bbf67c465 claude_refactor_v3: Removed old conf with gins 2026-05-04 11:25:42 +03:00
pikaliov
89cb8ab0f7 claude_refactor_v3: New train_gtauav.py, added entry point main.py, added utils 2026-05-04 11:20:14 +03:00
pikaliov
08d328db09 checkpoint 30_04 2026-04-30 16:30:34 +03:00
pikaliov
4b441279e0 claude_refactor_v3: De-duplicate gin-configs, + "in/config_files" with common and cpecific experiment-presets gins 2026-04-30 16:22:04 +03:00
pikaliov
e8a0de7ad3 claude_refactor_v3: Added .py-confs and all presets (nx5 .gin files). TODO: common gins-mapping and prepare to next step 2026-04-30 12:02:15 +03:00
pikaliov
db2b5b32f4 checkpoint 29_04 2026-04-29 16:38:20 +03:00
pikaliov
af207d302c claude_refactor_v3: Added models configs except sofiav71 - need extra presets for M, L, Tiny 2026-04-29 16:28:28 +03:00
pikaliov
ce9e3dc94b claude_refactor_v3: Added plan.md, new conf dirictories 2026-04-29 15:50:36 +03:00
pikaliov
bf5e417094 Add new pre_refactor analysis.md 2026-04-29 15:15:40 +03:00
pikaliov
d42ef94821 clean up to baseline 2026-04-29 14:45:31 +03:00
pikaliov
6b928634f4 clean up 2026-04-29 14:43:42 +03:00
pikaliov
3275a41b36 claude_refactor_v2: remove extra configs additions 2026-04-29 14:40:40 +03:00
pikaliov
4bbe1dda1d Added tmp extras before any code restructurig 2026-04-29 12:17:03 +03:00
pikaliov
5c48b6c8fd claude_refactor_v2: temp changes before src changes 2026-04-29 11:55:38 +03:00
pikaliov
0d8c82acc3 add sofia models 2026-04-29 08:04:33 +03:00
pikaliov
a27b5a7357 Init refactor branch 2026-04-27 11:57:29 +03:00
pikaliov
c6fcd2222c Add stripnet_freeze flag for full StripNet fine-tune mode
When stripnet_freeze=False, all StripNet backbone params train end-to-end
with a separate optimizer group at lr * stripnet_backbone_lr_factor (default
0.1, so 1e-5 with default learning_rate=1e-4) — typical fine-tuning practice
for ImageNet-pretrained CNNs to avoid catastrophic forgetting.

Conv-MONA is now optional (stripnet_mona_last_n_stages=0 disables it). Three
modes are now supported:
  - frozen + MONA: PEFT-style (~1.2M trainable, original default)
  - unfrozen, no MONA: full fine-tune (~13.85M, all backbone params)
  - unfrozen + MONA: hybrid (~14.5M, backbone + extra adapters)

_build_param_groups: new "backbone" group identifies image_encoder.backbone.*
params (excluding mona_*) when backbone="stripnet"; assigned lr factor
controls fine-tune step size independently from text/MONA groups.

conf/gtauav_balanced_stripnet_unfrozen.gin + baseline variant: ready-to-use
configs for full fine-tune experiment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:04:24 +03:00
pikaliov
d4cb2dd300 Add StripNet backbone option with Conv-MONA adaptation
StripNet-small (Strip-R-CNN, HVision-NKU) as alternative image encoder to
DINOv3 ViT-L/16. ~28M params (10x smaller). Output 512-dim from stage 4
projected to 1024 to keep retrieval space compatible with DINOv3 configs.

- src/models/stripnet/: self-contained backbone (model.py, conv_mona.py).
  State-dict naming follows upstream Strip-R-CNN repo (conv_spatial1/2);
  ImageNet-1K pretrained head dropped on load.
- Conv-MONA: 2D adaptation of MONA (CVPR 2025) for CNN blocks. BN → 1x1
  Down(C->bn) → multi-scale DWConv {3,5,7} mean → +residual → GELU →
  1x1 Up(bn->C) with channel-wise layer scale γ init 1e-6. Two adapters
  per StripNet Block (post-attn, post-mlp); injected into deepest N stages.
- StripNetEncoder: GAP + Linear(512->1024). Overrides train() to keep
  frozen BatchNorm stats stable across mode flips.
- AsymmetricEncoder: new `backbone="stripnet"` option (always shared).
- TrainConfigGTAUAV: backbone, stripnet_path, stripnet_mona_last_n_stages.
- conf/gtauav_balanced_stripnet.gin + gtauav_baseline_stripnet.gin.

Smoke test: forward [2,3,256,256] -> [2,1024]. Trainable: 1.2M baseline
(8.27%), 4.76M with text (3.35% of 142M).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:34:53 +03:00
pikaliov
814586ce3b Add g2q eval metrics and asymmetric MONA-24 configs
- _evaluate: compute R@K + AP for both directions (q2g and g2q) via inverted
  ground truth; g2q denominator counts only sat-tiles with at least one positive
  drone in the (sub)sampled query set. Surfaces in train.csv, val.csv,
  train_recall.csv, W&B summary, and final log.
- conf/gtauav_balanced_asym.gin: asymmetric WEB+SAT encoders, MONA in all 24
  ViT blocks (~17.6M trainable / ~733M total).
- conf/gtauav_baseline_asym.gin: same architecture, baseline_mode=True for
  Δ R@1 against balanced_asym.
- CLAUDE.md / README.md: document new configs, clarify that g2q is now
  computed (was claimed but missing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:47:02 +03:00
pikaliov
8f8cbb14dd Diagnostic baseline v2: also disable MoCo queue
The mutex-only run still collapsed at epoch 1 — same pattern as the DSS
run. Val loss locks to log(8) ≈ 2.08 (uniform over the in-batch sat),
train loss grows monotonically (4.80 → 5.56), train R@1 drops 7.79% →
0.34%. Mode collapse, not sampler-induced.

The smoking gun is the queue: WeightedInfoNCELoss (the OLD-run loss)
silently ignored `queue_negatives`, so the OLD-run effective task was
in-batch-only contrast against 8 negatives. Switching to InfoNCELoss
made the queue active — 4096 stale embeddings without a momentum encoder
to keep them consistent with the live model. With the trimmed adapter
surface (MONA in last 12/24 blocks → 3.5M trainable), the model can't
reconcile fresh representations against stale negatives and collapses.

Disable the queue entirely (`neg_bank_size = 0`). Matches OLD's effective
setup — same 8 in-batch negatives, but with the new SymmetricInfoNCE +
mutex sampler + tau clamp 0.1 + per-sample mask + full-gallery eval.

Output → `out/gtauav/baseline_inbatch` (separate from the failed mutex run).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 05:57:14 +03:00
pikaliov
c25bd64357 Diagnostic baseline: disable DSS + hard mining, fresh output dir
Previous baseline run collapsed to ~random retrieval (R@1 0.6% at epoch 8,
train loss trending up 4.2 → 4.5). Hypothesis: at bs=8, DSS packs
visually-identical drones into a batch where InfoNCE asks the model to
discriminate them, and the hard-mining queue amplifies that hardness —
together they prevent convergence from a near-random start.

Override the new baseline config to run with the simpler regime:
  sampler_type = "mutex"    (disable DSS, keep only the no-false-negative guarantee)
  hard_mining_k = 0         (use full queue as uniform negatives, no per-query top-K)

Fresh `out/gtauav/baseline_mutex` output dir so results stay separate from
the failed run's mixed logs.

Other architecture changes (shared DINOv3 WEB, MONA in last 12 blocks,
grad_accum=8) kept — verify they work with simple sampling before
layering DSS/mining back on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:30:30 +03:00
pikaliov
9a7fbff743 Fix plot_combined: fallback from 'total' to 'train_loss'
Commit e0db8d2 renamed the `total` column in train.csv to `train_loss`.
`plot_per_epoch` already handled this via a fallback, but `plot_combined`
was still hard-coded to `y="total"` and crashed during training:

  ValueError: Could not interpret value `total` for `y`.

Mirror the same fallback there and guard against the column being absent
entirely (first-epoch edge case before any loss is logged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:24:30 +03:00
pikaliov
70f1617317 Fix autograd in-place error: move memory-bank enqueue after backward
Hard mining (c307269) uses `queue.t()` as a view directly in the
similarity matmul, so the autograd graph now holds a reference to the
memory-bank buffer's storage. The previous code enqueued fresh gallery
embeddings BEFORE `backward()`, which mutates that same buffer in place
and triggers:

  RuntimeError: one of the variables needed for gradient computation has
  been modified by an inplace operation: [torch.cuda.FloatTensor [1024, 8]]
  is at version 2; expected version 1 instead.

The older InfoNCE path called `torch.cat([emb_b, queue], 0)`, which
materialises a fresh contiguous tensor and severs the alias — so the
same enqueue order worked. After the mining refactor, we need the
buffer to stay stable until backward completes.

Moving the enqueue to after backward is semantically identical: the
queue state observed by the next training step is the same either way
(FIFO, not involved in the just-completed backward).

`smoke_train.py` already had the correct order and didn't catch the
regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:40:58 +03:00
pikaliov
8197ab2407 Fix training loop: only pass positive_weights to WeightedInfoNCELoss
InfoNCELoss.forward() doesn't accept `positive_weights` — that kwarg is
specific to WeightedInfoNCELoss's adaptive label-smoothing path. After
switching the default `loss_type` to "symmetric", training crashed with
`TypeError: unexpected keyword argument 'positive_weights'`.

Build the kwargs dict conditionally: add `positive_weights` only when
the loss is an instance of WeightedInfoNCELoss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:38:16 +03:00
pikaliov
cb477f4b40 Simplify model: shared DINOv3 WEB + MONA in last 12/24 blocks
Three related architecture changes, driven by a cost/simplicity trade-off:

1. **Shared encoder**: one DINOv3 LVD-1689M (WEB) processes both drone
   and satellite images. Previously asymmetric — separate WEB (drone) and
   SAT-493M (satellite) encoders. Saves ~303M frozen params and halves
   VRAM for the image tower. Expected to lose some satellite-domain
   inductive bias; MONA adapters pick up the slack.

2. **MONA in last 12/24 blocks**: adapters injected only in the top half
   of the ViT. The lowest 12 blocks keep their pretrained features
   untouched. Trainable MONA count drops from 14.0M (48 adapters × 2
   encoders) to 3.5M (24 adapters × 1 encoder).

3. **No DINO_SAT**: `nn_models/DINO_SAT` is no longer loaded by the
   default config. It stays on disk and the path param is kept for
   backward compat with asymmetric checkpoints.

Parameter counts (with text fusion + LoRA + gates):
  Before: 17.6M trainable / 733M total (2.35%)
  After:   7.06M trainable / 434M total (1.63%)

Also fixes a pre-existing resume bug: checkpoints now record
`shared_encoder`, `baseline_mode`, `mona_bottleneck`, `mona_last_n_blocks`
so `AsymmetricEncoder.load_checkpoint` can rebuild the right architecture.
Old checkpoints still load (missing keys fall back to asymmetric defaults
via `ckpt.get(..., <default>)`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:26:17 +03:00
pikaliov
d98d853455 DSS pipeline: GPU kNN, LSH index, embedding cache
Three upgrades to the DynamicSimilaritySampler infrastructure:

1. **GPU kNN** (`dss_knn_device="cuda"`, default):
   Moves the per-seed similarity matmul to the GPU. At 25K train items
   this cuts per-epoch sampler generation from 17s to 1.6s — a 10.8x
   speedup. Negligible VRAM (100MB for the [N, 1024] embedding tensor).

2. **LSH index** (`src/datasets/lsh_index.py`, opt-in via `dss_use_lsh=True`):
   Random-projection cosine-LSH with H tables of B bits each. When enabled,
   the sampler narrows the candidate pool per seed via hash-bucket lookup
   before exact refinement. At 25K it's a wash (pool already fits in VRAM)
   but provides a scaling path for 100K+ where the N² similarity matrix
   would stop fitting. Default off.

3. **Embedding cache** (`src/datasets/embedding_cache.py`, `dss_cache_dir` config):
   Disk-backed cache for drone query embeddings, keyed by epoch. Skips
   re-embedding on --resume and lets ablations replay from a snapshot.
   Atomic writes via `.tmp` → `.replace`.

Measured on 25K train entries, 1024-dim random embeddings:
  CPU kNN:      17.44s
  GPU kNN:       1.62s  (10.8x)
  GPU + LSH:     1.42s  (LSH candidate pool 0.05% of N)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:20:48 +03:00
pikaliov
f8e0631210 Add DynamicSimilaritySampler — embedding-kNN batches with mutex constraint
Batches assembled from visually-similar drone queries pressure the model to
learn finer discriminative features. Random mutex batches average ~0.26
pairwise cosine similarity in query embedding space; DSS batches average
~0.71 — confirming the lookalikes grouping works as intended.

Algorithm per batch:
  1. Pick a random seed drone from the remaining pool.
  2. Rank the entire remaining pool by cosine similarity to the seed.
  3. Walk the ranking in descending order; add items whose sat_candidates
     don't collide with the batch's already-claimed set.
  4. Drop the seed if no valid batch can be assembled (rare mutex deadlock).

Inherits MutuallyExclusiveSampler semantics — no false negatives. Degrades
gracefully to mutex-only when no embeddings are set (warmup epochs, or if
`sampler_type="mutex"` is chosen).

Integration in `train_gtauav.py`:
  - New `_embed_drone_queries` helper: model.encode_query forwarded over
    GTAUAVDroneQuery, returns [N, D] CPU tensor. ~13s per 1024 queries on
    a 4090 → ~5 min for the full 25K train set.
  - Epoch loop re-embeds every `dss_reembed_every` epochs after a `dss_warmup_epochs`
    warmup (first epochs use mutex-only since untrained embeddings aren't
    informative for kNN).
  - Config: `sampler_type` ∈ {"mutex", "dss"}. Default flipped to "dss".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:12:34 +03:00
pikaliov
c30726998b Add per-query hard negative mining to InfoNCELoss
New `hard_mining_k` parameter on InfoNCELoss. When >0 and queue is non-empty,
each query row keeps only its K highest-similarity queue entries (via
`torch.topk`) as negatives, instead of the full queue. Fully vectorized —
no Python loop, no extra forward pass.

Rationale: the memory bank grows to 4096 detached gallery embeddings, but
most are easy negatives that contribute almost nothing to the gradient.
Hard mining focuses compute on the small subset that actually shapes the
decision boundary. +2-3% R@1 in similar contrastive setups.

Edge cases:
- K=0: mining disabled, full queue used (original behavior).
- K >= queue size: falls back to full queue (e.g. warmup when queue is small).
- Queue empty: in-batch only, no changes.

Default in `gtauav_balanced.gin`: K=512 (1/8 of queue). Smoke-train updated
to exercise the full memory-bank + mining path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:05:32 +03:00
pikaliov
a499fcfd65 Fix GTA-UAV eval + training pipeline: full gallery, mutex sampler, per-sample mask
Six critical fixes to the caption-test training/eval stack:

1. **IndentationError blocker** (train_gtauav.py:765-766)
   Unparseable file — train-recall LOGGER.info block was orphaned outside
   its `if eval_every` guard. Wrapped in `if train_recall:` so val eval
   and Grad-CAM only run on eval epochs.

2. **Full satellite gallery in `_evaluate`**
   Old code assembled gallery from DataLoader batches (one random sat per
   drone), producing an incomplete gallery of size ≈ N_query instead of
   N_unique_sat. Metrics were inflated because retrieval was against a
   subset that always contained the target.
   New `GTAUAVSatGallery` / `GTAUAVDroneQuery` iterate all unique tiles
   and queries independently; full-gallery multi-match R@K + MRR.

3. **Per-sample caption mask** (`AsymmetricEncoder._fuse_with_mask`)
   Mixed batches (some samples have captions, some don't) previously
   encoded empty strings through DGTRS and mixed the noise output into
   every sample via scalar gate. New `encode_query`/`encode_gallery` use
   `torch.where` to fall back to pure image features for empty-caption
   samples. Training `forward()` routes through the same helper so
   training and eval share code.

4. **Symmetric InfoNCE as primary loss** (multi_infonce.InfoNCELoss)
   Switched gin default from `WeightedInfoNCELoss` (adaptive label
   smoothing — not the Game4Loc soft-IoU target it claimed) to the
   existing symmetric InfoNCE with q2g=0.6/g2q=0.4 weighting. Loss type
   now selectable via `cfg.loss_type ∈ {"symmetric", "weighted"}`.

5. **MutuallyExclusiveSampler** (new file)
   BatchSampler that greedily packs drones whose `sat_candidates` sets
   are pairwise disjoint within a batch. Eliminates false negatives from
   the semi-positive graph without needing soft-label losses.
   At bs=8 keeps 100% of 24,891 train entries; at bs=64 keeps 92.6%.
   `set_epoch()` for reproducibility + different batches per epoch.

6. **Temperature clamp [0.01, 0.1]** (both loss modules)
   Old tau_max=0.5 allowed the logit distribution to collapse into a
   near-uniform softmax. Tightened to the CLIP-standard range.

Also:
- Added `scripts/smoke_eval.py` / `scripts/smoke_train.py` for fast
  regression checks (eval in ~2 min, 2 train steps in ~1 min on RTX 4090).
- CLAUDE.md updated to reflect the new pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:58:27 +03:00
pikaliov
ce7892926f Add Recall@K and AP panels to train_metrics.png
train_metrics.png now has 6 panels (2x3):
  Row 1: Train Loss, Train R@1/5/10, Train AP
  Row 2: Temperature, Gates, Learning Rate

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 13:01:51 +03:00
pikaliov
e0db8d2726 Rename 'total' to 'train_loss' in train.csv
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 12:59:47 +03:00
pikaliov
536f904842 Remove eval_loss from train.csv (already in val.csv)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 12:57:37 +03:00
pikaliov
d9c795d9ae Add train recall/AP to train.csv (merged with loss metrics per epoch)
train.csv now includes eval_loss, r@1_q2g, r@5_q2g, r@10_q2g, ap_q2g
alongside training loss/temperature/gates when eval runs that epoch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 12:55:36 +03:00
pikaliov
b6dccbba7b Fix GTA-UAV evaluation and loss (critical: false negatives + wrong R@K)
PROBLEM: GTA-UAV has overlapping satellite crops (partial IoU).
Standard InfoNCE with diagonal targets treated valid matches as negatives.
R@K checked only diagonal — missed valid matches, artificially low recall.

FIXES:
1. WeightedInfoNCE loss (src/losses/weighted_infonce.py):
   - Per-sample adaptive label smoothing from positive_weights (IoU)
   - Higher weight → sharper target, lower → softer (semi-positive tolerance)
   - Based on Game4Loc reference implementation

2. Multi-match R@K evaluation:
   - Uses dataset.get_all_valid_sat_names() to get ALL valid matches per query
   - R@K counts hit if ANY valid satellite is in top-K (not just diagonal)
   - AP computed as MRR over first valid match

3. Dataset returns positive_weight per sample:
   - Sampled satellite weight passed to loss for adaptive smoothing
   - All valid satellite candidates exposed for evaluation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 12:40:10 +03:00
pikaliov
04d5307221 Asymmetric encoders + MONA all 24 blocks + 1024-dim + hard negatives
Architecture changes:
- Asymmetric DINOv3: WEB (drone) + SAT (satellite) with separate MONA
- MONA on all 24 blocks per encoder (was last 12)
- Remove projection, native 1024-dim retrieval space (was 512)
- Total: 748M params, 17.6M trainable (2.35%)

Hard negative memory bank:
- MoCo-style FIFO queue of 4096 detached gallery embeddings
- Each batch: B in-batch + Q queue negatives in InfoNCE
- Queue updated after each forward pass

Training config:
- batch_size=8, grad_accum=8, effective_batch=64
- eval_every=1 (eval + train recall every epoch)
- Max bs=24 with grad checkpointing on RTX 4090

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 08:47:33 +03:00
pikaliov
75a4350d18 Update README: AP metric, train/val eval, CSV/plot inventory
- Document AP (MRR) metric computed on both train and val
- Add output CSVs table (train.csv, val.csv, train_recall.csv, train_batches.csv)
- Add plots table (train_metrics, val_metrics with AP panel, overview)
- Update diagnostics table with recall CSV and plots
- Note overfitting detection via train vs val comparison

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 08:27:41 +03:00
pikaliov
4a05d05ccd Add Average Precision (AP) metric for train and val
- Compute AP (Mean Reciprocal Rank) in _evaluate() for both q→g and g→q
- AP saved in val.csv and train_recall.csv alongside R@K
- New AP plot panel in val_metrics.png (train vs val, both directions)
- Log AP in console output for train-recall and val epochs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 08:08:48 +03:00
pikaliov
df60e83ead Add val/train loss to evaluation and plots
- _evaluate() now computes per-batch loss when loss_fn is provided
- Val loss and train recall loss saved in val.csv and train_recall.csv
- Overview plot shows train vs val loss curves side by side
- Helps detect overfitting: val loss diverging from train loss

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 08:05:12 +03:00
pikaliov
93ad66810d Add train recall evaluation (R@K on train subset each epoch)
- Evaluate R@K on train set (subset matching test size) alongside val
- New train_recall.csv with per-epoch train R@1/R@5/R@10
- Plot train vs val recall on same chart (solid=val, dashed=train)
- Helps detect overfitting: train R@1 up + val R@1 flat = overfit
- Train eval uses clean transforms (no augmentation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 08:01:09 +03:00
pikaliov
66c83469ef Remove per-epoch train/val CSV files (redundant with cumulative CSVs)
epoch_000_train.csv and epoch_000_val.csv removed — all data is in
train.csv and val.csv which persist across resume.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:57:37 +03:00
pikaliov
1638b250a4 CSVLogger: deduplicate epochs on resume (replace, not append duplicates)
On resume, the same epoch may be re-run. Now log_train/log_val remove
existing entries for the same epoch before appending, preventing
duplicate rows in train.csv/val.csv.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:54:33 +03:00
pikaliov
f382325ac6 CSVLogger: load previous epochs on resume for continuous plots
On init, load existing train.csv/val.csv so that epoch-level metrics
and plots include the full training history after checkpoint resume.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:41:32 +03:00
pikaliov
a32cb8ac6c Fix Grad-CAM numpy() on tensor with grad (add detach)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 07:02:12 +03:00
pikaliov
b0e87c07ef Fix Grad-CAM crash in shared encoder mode
Access model.image_encoder instead of model.drone_encoder/sat_encoder
when shared_encoder=True. Caused AttributeError at eval epoch 4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 00:36:06 +03:00
pikaliov
7cc068e18c Update README: full architecture refresh (shared DINOv3, MONA bf16 top-12, 512-dim)
- Rewrite architecture diagram: shared DINOv3 WEB, projection 1024→512,
  MONA bf16 last 12 blocks, gradient checkpointing
- Update VRAM table: bs=48 at 21.8 GB (was bs=24)
- Update model summary: 432M total, 5.6M trainable, retrieval dim=512
- Update workflow: bs=48, 30 epochs, eval every epoch
- Document bf16 vs fp16 NaN issue (gamma=1e-6 underflow)
- Document MONA vs LoRA rationale for CVGL
- Update optimizer section: gradient accumulation, mixed precision details

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 22:44:47 +03:00
pikaliov
e55734425c MONA bfloat16: safe low-precision (gamma=1e-6 needs bf16 exponent range)
- Switch MONA from fp32 to bfloat16 (same exponent range as fp32, no underflow)
- fp16 causes NaN: gamma=1e-6 falls into subnormal range (min normal ~6.1e-5)
- bf16 min normal ~1.2e-38, so 1e-6 is safe
- RTX 4090 supports bf16 natively
- Document bf16 vs fp16 vs fp32 comparison in README
- Update model summary: 3.5M MONA (last 12 blocks), 5.6M total trainable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 22:13:35 +03:00
pikaliov
9200772bea Fix NaN loss: revert MONA to fp32, fix loss logging
- MONA fp16 causes NaN (gamma=1e-6 underflows in fp16 min subnormal ~6e-8)
- Revert MONA forward to fp32 with autocast(enabled=False), cast output back
- Fix loss CSV: save raw_loss before backward() (tensor consumed after backward)
- Verified: loss=3.78, no NaN, bs=48 peak=21.4 GB

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 22:09:49 +03:00
pikaliov
916854f124 MONA only on last 12 blocks (skip early low-level blocks)
- Add last_n_blocks parameter to inject_mona_into_dinov3 (default=12)
- Blocks 0-11: pure frozen DINOv3 (low-level features, domain-agnostic)
- Blocks 12-23: MONA adapted (high-level semantic features)
- MONA params: 3.5M (was 6.85M, -49%)
- Total trainable: ~5.7M with text (was ~9.0M)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:51:52 +03:00
pikaliov
4a0336e0ff Update README: MONA vs LoRA rationale, projection 1024→512, model summary
- Document why MONA (spatial inductive bias) over LoRA for DINOv3
- Add MONA vs LoRA comparison table for CVGL
- Document projection head (1024→512) and retrieval space change
- Update model summary: 436M total, 9.0M trainable (2.06%), dim=512
- Note MONA fp16, gradient checkpointing, shared encoder

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:50:47 +03:00
pikaliov
471bc6444a Restore conv7x7 in MONA (full 3x3+5x5+7x7 multi-scale)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 21:45:27 +03:00