Files
caption-test/CLAUDE.md
pikaliov 905b9867c8 Add 80/20 random split (replaces cross-area 46/54 split)
- scripts/make_split.py: merges cross-area train+test (33,708 pairs),
  shuffles with seed=42, splits 80/20
- meta/train_80.json (26,966) + meta/test_20.json (6,742)
- After seg filter: 24,891 train / 6,252 test
- Default paths in train_gtauav.py updated to use new split

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 18:19:37 +03:00

360 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Caption Quality Test for Cross-View Geo-Localization
## Архитектура системы (v3, 2026-04-21) — GTA-UAV эксперимент
```
QUERY BRANCH (drone + L1/L2/L3 captions):
drone_img --> DINOv3 ViT-L/16 LVD-1689M (frozen) --> CLS [B,1024]
|
proj_drone (1024->512)
|
L1 (overview) --> LRSCLIP (248 tok) --> z_L1 [768] --\
L2 (full desc) --> LRSCLIP (248 tok) --> z_L2 [768] ---+-- concat [B,2304]
L3 (fingerprint) --> LRSCLIP (248 tok) --> z_L3 [768] --/ |
MLP(2304->768->512)
|
GatedFusion(drone_512, text_512) --> L2-norm --> query [B,512]
GALLERY BRANCH (satellite only):
sat_img --> DINOv3 ViT-L/16 SAT-493M (frozen) --> CLS [B,1024]
|
proj_sat (1024->512) --> L2-norm --> gallery [B,512]
LOSS: InfoNCE(query, gallery) — symmetric, asymmetric weights (0.6 q->g, 0.4 g->q)
BASELINE: gate = 1.0 (text ignored)
```
### Trainable parameters: 10.9M из 733M (1.49%)
- proj_drone: 1024x512 = ~524K
- proj_sat: 1024x512 = ~524K
- TextFusionMLP: 2304->768->512 = ~2.2M
- gate alpha: 1 scalar
- LRSCLIP partial unfreeze (last block + ln_final + text_projection): ~7.6M
- Backbones DINOv3 x2 (303M each): frozen
### Image input: 256x256
DINOv3 ViT-L/16 с patch_size=16 → 16x16=256 patches на 256x256.
Resize(256) + CenterCrop(256) + ImageNet normalization.
### Предыдущая архитектура (v2) — UAV-GeoLoc эксперимент
Использовала GeoRSCLIP ViT-B/32 (512-dim) для обеих веток + template captions.
Код в `src/models/dual_encoder.py`, `src/datasets/visloc_with_captions.py`.
## Ключевые файлы
### V2 (UAV-GeoLoc, GeoRSCLIP)
| Файл | Назначение |
|------|-----------|
| `src/models/dual_encoder.py` | GeoRSCLIP + GatedFusion + projection heads |
| `src/losses/multi_infonce.py` | InfoNCE с cosine temperature schedule |
| `src/datasets/visloc_with_captions.py` | UAV-GeoLoc loader + template captions из path metadata |
| `src/training/train.py` | Training loop, логирование loss/gate/tau |
| `src/eval/evaluate.py` | R@K metrics, delta_r_at_1 |
| `scripts/compare_runs.py` | Markdown/JSON сравнение baseline vs caption runs |
| `scripts/generate_captions.py` | Offline caption generation (template/VLM/hybrid) |
### V3 (GTA-UAV, DINOv3 + LRSCLIP) — DONE
| Файл | Назначение |
|------|-----------|
| `src/models/asymmetric_encoder.py` | DINOv3ViT + LRSCLIPTextEncoder + TextFusionMLP + AsymmetricEncoder + GatedFusion |
| `src/datasets/gtauav_dataset.py` | GTA-UAV-LR loader + L1/L2/L3 caption parsing из VLM JSON |
| `src/training/train_gtauav.py` | Training loop с eval, AMP, CLI args (--baseline, --filter-meta) |
| `scripts/filter_segmentation.py` | Scan segm masks, output meta JSON (exclude >=90% bg+water) |
## Backbones (v3)
### DINOv3 ViT-L/16 — Drone (web pretrained)
- **Checkpoint:** `nn_models/DINO_WEB/dinov3-vitl16-pretrain-lvd1689m.pth`
- **Arch:** ViT-L/16, 24 layers, 16 heads, hidden=1024, MLP=4096, 303M params
- **Input:** 256x256, ImageNet normalization, patch=16 → 256 patches
- **Register tokens:** 4, RoPE theta=100.0
- **Status:** frozen
### DINOv3 ViT-L/16 — Satellite (sat pretrained)
- **Checkpoint:** `nn_models/DINO_SAT/model.safetensors`
- **HuggingFace:** `facebook/dinov3-vitl16-pretrain-sat493m`
- **Arch:** идентична DINO_WEB (ViT-L/16, hidden=1024, 303M params)
- **Input:** 256x256
- **Config:** `nn_models/DINO_SAT/config.json` — BROKEN (auth error), используем конфиг от DINO_WEB
- **Status:** frozen
### DGTRS-CLIP ViT-L-14 (LRSCLIP) — Text encoder
- **Checkpoint:** `nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt`
- **Text dim:** 768, max tokens: 248 (KPS stretched from CLIP's 77)
- **Содержит:** полную CLIP модель (visual + text), используем только text encoder
- **GitHub:** `github.com/MitsuiChen14/DGTRS`
- **Status:** partial unfreeze (last block + text_projection)
### GeoRSCLIP ViT-B/32 (v2, legacy)
- **Checkpoint:** `checkpoints/RS5M_ViT-B-32.pt`
- **Image encoder:** ViT-B/32, 224x224, 512-dim, ~86M params — frozen
- **Text encoder:** 77 tokens, 512-dim — partial unfreeze
## GatedFusion
- `query = sigma(alpha) * drone_feat + (1 - sigma(alpha)) * text_feat`
- `alpha` — один learnable scalar в logit-space
- `init_gate = 0.7` → начальный вес image = 70%, text = 30%
- `baseline_mode = True` → gate = 1.0, text полностью игнорируется
- Gate value логируется каждую эпоху для интерпретации вклада текста
## Text Hierarchy (L1/L2/L3)
Три уровня описаний из VLM-generated captions:
| Уровень | Контент | Длина | Источник |
|---|---|---|---|
| L1 overview | Краткое описание сцены | <=30 tok | Конденсация P1 |
| L2 full description | Детальное описание через Qwen3-VL | <=200 tok | Полный P1+P2 |
| L3 fingerprint | Ключевые landmark'ы | <=30 tok | Конденсация P3 |
Все три уровня кодируются одним LRSCLIP (248 tok max).
Альтернатива (Stage 2): RemoteCLIP для L1/L3 + LRSCLIP для L2.
## Датасет: GTA-UAV-LR
- **RGB:** `/home/servml/Документы/datasets/GTA-UAV-LR/`
- Drone: 33,763 PNG (512x384), altitudes 100-600m
- Satellite: 14,640 PNG (256x256 RGBA)
- Pairs: `cross-area-drone2sate-{train,test}.json` (primary split)
- Metadata: `*_drone_meta.csv` (height, yaw, roll, pitch)
- Origin: GTA V simulation (Los Santos)
- **Captions:** `/home/servml/Документы/datasets/GTA-UAV-LR-captions/`
- Drone: 33,411 JSON (32,635 multi-paragraph P1/P2/P3 + 776 short water-only)
- Satellite: 6,546 JSON (все multi-paragraph)
- Формат: 3 абзаца (P1 Inventory + P2 Spatial + P3 Fingerprint)
- Token counts: ~430 output tokens per caption
- **Segmentation:** `/home/servml/Документы/datasets/GTA-UAV-LR-aug/`
- 48,403 images, 17 классов (background, building, road, vegetation, water, ...)
- Modalities: segm/, depth/, edge/, chm/, safetensors/
- Query: 512x512, DB: 256x256
### Фильтрация сегментации
Meta-файл `meta/seg_filter.json`: исключение изображений с >=90% background(class 0) + water(class 4).
- **Total:** 48,403 → **Passed:** 37,498 (77.5%) / **Excluded:** 10,905 (22.5%)
- Drone: 31,188 passed / 2,575 excluded
- Satellite: 6,310 passed / 8,330 excluded (преимущественно open water tiles)
## Датасет: UAV-GeoLoc (v2, legacy)
- **Путь:** `/mnt/data1tb/cvgl_datasets/UAV-GeoLoc/`
- **Train:** 206,108 queries, 94,709 DB crops (140 scenes, Terrain split)
- **Val:** 62,368 queries, 26,597 DB crops (40 scenes)
- **Test:** 33,472 queries, 11,684 DB crops (20 scenes)
- **Index:** `Index/train_query.txt``query_path 0 pos_crop1 pos_crop2 ...`
## Конфигурации
### V3 (GTA-UAV)
Параметры:
- 10 epochs, batch 64, AMP, image 256x256
- **Optimizer:** AdamW, per-group LR: proj=1e-4, text=1e-5 (10x lower)
- **Scheduler:** linear warmup (2 epochs) + cosine annealing (per-step)
- **Loss:** InfoNCE с learnable temperature (CLIP logit_scale), init=0.07, clamp [0.01, 0.5]
- **Augmentations:**
- Drone: RandomResizedCrop(0.7-1.0), HFlip, Rotation(15°), ColorJitter, Grayscale(5%), GaussianBlur
- Satellite: RandomResizedCrop(0.7-1.0), HFlip, ColorJitter, Grayscale(5%)
- Eval: Resize+CenterCrop (clean, no augmentation)
- **Split:** 80/20 random из всех 33,708 пар (`meta/train_80.json` / `meta/test_20.json`)
- Train: 26,966 → 24,891 after seg filter
- Test: 6,742 → 6,252 after seg filter
- Скрипт: `python -m scripts.make_split --ratio 0.8 --seed 42`
### V2 (UAV-GeoLoc, gin)
| Конфиг | Gate init | Описание |
|--------|-----------|----------|
| `conf/balanced.gin` | 0.7 (30% text) | **Primary test** |
| `conf/baseline_no_text.gin` | 1.0 (no text) | Reference baseline |
| `conf/text_heavy.gin` | 0.3 (70% text) | Stress test |
## Запуск
### V3 (GTA-UAV)
```bash
# 1. Filter segmentation (exclude 90%+ background/water)
python -m scripts.filter_segmentation --output meta/seg_filter.json
# 2. Baseline (no text)
python -m src.training.train_gtauav --baseline --filter-meta meta/seg_filter.json
# 3. With captions (L1/L2/L3)
python -m src.training.train_gtauav --filter-meta meta/seg_filter.json
# 4. Compare
python -m scripts.compare_runs \
--baseline_report out/gtauav/baseline/eval_report.json \
--full_report out/gtauav/with_text/eval_report.json \
--output out/gtauav/comparison.md
```
### V2 (UAV-GeoLoc)
```bash
python -m src.training.train --config conf/baseline_no_text.gin
python -m src.training.train --config conf/balanced.gin
python -m scripts.compare_runs \
--baseline_report out/caption_test/baseline_no_text/eval_report.json \
--full_report out/caption_test/balanced/eval_report.json \
--output out/caption_test/comparison.md
```
## Метрики и Decision rule
**Primary metric:** Delta R@1 (drone -> satellite)
| Delta R@1 | Verdict |
|-----------|---------|
| >= +3% | PASS — captions informative, proceed to production |
| +1% to +3% | MARGINAL — add VLM refinement, re-run |
| 0 to +1% | WEAK — redesign caption pipeline |
| < 0 | HARMFUL — critical bug |
**Eval metrics:** R@1, R@5, R@10 для drone->satellite и satellite->drone
**Splits (GTA-UAV):** cross-area (primary, harder) и same-area (sanity check)
**Logged per epoch:** loss, temperature (tau), gate value (sigma(alpha)), lr
## Бюджет времени (RTX 4090, 24 GB)
### V3 (GTA-UAV, DINOv3 ViT-L/16, 256x256)
| Фаза | Оценка |
|------|--------|
| VRAM: 2x DINOv3-L + LRSCLIP + batch 64 | ~18-22 GB |
| GPU mem (smoke test, batch 4) | 3.1 GB |
| Batch size | 64 (default) |
| Total params | 733M (10.9M trainable, 1.49%) |
### V2 (UAV-GeoLoc, GeoRSCLIP)
| Фаза | Время |
|------|-------|
| Один training run (10 epochs, 206K queries, batch 128) | ~15-30 мин |
| Full test (3 варианта) | ~1-1.5 ч |
| Evaluation per run | ~2-5 мин |
## Связанные проекты
### Text Annotation Pipeline
- **Путь:** `/home/servml/Документы/pikaliov_obsidian/(Полякова ВЕ_Система для генерации текстовых описаний для БПЛА)/2_work/2_text_annotation/code/`
- **VLM:** Qwen3-VL-8B AWQ (1.68 s/img)
- **Scoring:** SigLIP 2 (drone P3) + CLIP-RSICD (satellite P1+P2)
- **Формат описаний:** 3 абзаца (P1 Inventory + P2 Spatial Map + P3 Fingerprint)
- **Метрики:** FDR, FNR, NumAcc, LLaVA-Critic C1-C6
### UAV-VisLoc Prepare
- **Путь:** `/home/servml/Документы/code/Yaroslav/UAV-VisLoc-prepare/scripts/prepare_dataset.py`
- **Статус:** выполнен (2026-04-17), данные в `/home/servml/Документы/datasets/UAV_VisLoc_processed/` (25 GB)
- **Задача:** нарезка satellite кропов 512x512, stride 256 + resize drone -> 512x512
- **Подробности:** см. ниже
---
## Downstream: NADEZHDA Teacher (DINOv3 + Multi-FiLM)
caption-test — первый этап валидации. При Δ R@1 >= +3% переход к полному teacher'у:
```
Этап 1 (caption-test): GeoRSCLIP + GatedFusion(text) → валидация текста
Этап 2 (teacher): DINOv3-L + Multi-FiLM(depth, seg, CHM, normals, text)
Этап 3 (distillation): teacher ~300M → student ~5M → Jetson Orin NX
```
### Auxiliary modalities (предвычисляются из 512x512 офлайн)
| Модальность | Модель | Формат для teacher | Каналы |
|---|---|---|---|
| Depth | DepthAnything V2 | continuous, log(1+d) | 1 |
| Normals | Sobel от depth | continuous | 3 |
| Segmentation | SegFormer-B5 | binary per-class masks (top-K) | 16-17 |
| Canopy Height | Meta HRCH | binary bins (1-5m, 5-15m, >15m) + occupancy | 4-5 |
| Text | Qwen3-VL-8B / MobileCLIP2 | embedding | - |
### Асимметрия sat/drone
- CHM: только satellite (модель обучена на nadir, на oblique drone не работает)
- Satellite: ~27 aux каналов, Drone: ~21 aux каналов
### Fusion: Multi-FiLM
```
aux_features → FiLM(γ, β) → γ * DINOv3_tokens + β
```
Binary masks — natural FiLM gates. Modality dropout: text 0.3, CHM 0.5, seg 0.15, depth 0.1.
### Планируемый эксперимент H5
Сравнение T_bin (binary masks) vs T_pyr (native feature pyramids) vs T_hybrid.
Прогноз: binary masks лучше на cross-domain из-за робастности к aux-model artifacts.
---
## Датасеты (справочник)
### UAV-VisLoc
- **Путь:** `/home/servml/Документы/datasets/UAV_VisLoc_dataset/`
- **Структура:** 11 маршрутов (папки `01`-`11`), каждая содержит:
- `drone/` — drone-снимки (`XX_NNNN.JPG`)
- `satelliteXX.tif` — спутниковая карта
- `XX.csv` — GPS-метаданные drone (num, filename, date, lat, lon, height, Omega, Kappa, Phi1, Phi2)
- **Исключение:** маршрут `09` — спутник разбит на 4 тайла (`satellite09_01-01.tif` и т.д.)
- **Satellite coordinates:** `satellite_ coordinates_range.csv` — bbox каждой карты (LT_lat_map, LT_lon_map, RB_lat_map, RB_lon_map)
- **Splits:** `visloc_train.csv`, `visloc_test.csv` — списки drone-снимков (TSV, full absolute paths)
- **Размеры:** Drone 3976x2652 / 3000x2000 (6774 снимков), Satellite от 3000x170 до 43421x38408
- **GSD спутника:** ~0.30 м/px (единый zoom level Google Earth для всех карт). GSD по долготе варьируется 0.23-0.27 м/px из-за косинусного эффекта широты (40°N vs 25°N), но это не разная высота съёмки. Кроп 512x512 покрывает ~154x154 м везде.
### UAV-GeoLoc
- **Путь:** `/mnt/data1tb/cvgl_datasets/UAV-GeoLoc/`
- **Подмножества:** Country (171 scene), Terrain (200 scenes), Rot (1 scene)
- **Формат пар:** `positive.json`, `semi_positive.json`, `db_postion.txt`
- **Index:** `train_query.txt``query_path label pos_crop1 pos_crop2 ...`
- **Drone:** синтетика Google Earth Studio 3D, 512x512, FOV 30deg, heights 100/125/150m
- **Satellite:** кропы из merge.tif, доминирующий размер 200x200
## Скрипт подготовки UAV-VisLoc
- **Путь:** `/home/servml/Документы/code/Yaroslav/UAV-VisLoc-prepare/scripts/prepare_dataset.py`
- **Статус:** выполнен (2026-04-17)
### Запуск
```bash
python scripts/prepare_dataset.py \
--src /home/servml/Документы/datasets/UAV_VisLoc_dataset \
--dst /home/servml/Документы/datasets/UAV_VisLoc_processed \
--crop-size 512 --stride 256 --target-size 512
```
### Pipeline
1. Resize drone -> 512x512 (JPEG, quality=95)
2. Stitch satellite tiles для маршрута 09 (4 тайла -> 44800x33280)
3. Нарезка satellite -> кропы 512x512, stride 256, сохранение без downscale (PNG)
4. GPS для каждого кропа из bbox карты + позиция в grid
5. Match drone->crop через vectorized haversine (positive = ближайший, semi-positive = +/-1 в grid)
6. Metadata: `positive.json`, `semi_positive.json`, `db_postion.txt` (per route)
7. Index: `train_query.txt`, `test_query.txt`, `train_db.txt`, `test_db.txt`, `all_db.txt`
### Форматы выходных файлов (совместимость с UAV-GeoLoc)
| Файл | Формат |
|------|--------|
| `positive.json` | `{frame_id: [crop_name]}`, ключ = frame ID без route prefix (`"0001"`) |
| `semi_positive.json` | `{frame_id: [crop1, crop2, ...]}`, соседи +/-1 в grid |
| `db_postion.txt` | tab-separated: `name\tlon\tlat\tscale_lon\tscale_lat` |
| `train_query.txt` | `route/drone/file.JPG 0 route/DB/img/crop1.png ...` |
| `train_db.txt` / `test_db.txt` | все кропы всех маршрутов (gallery одинаковая, split по query) |
### Результаты (target-size 512)
- Drone: 6,744 images 512x512 (без маршрута 07: 30 excluded)
- Satellite кропов: 74,807 (512x512, без downscale)
- Размер на диске: 25 GB
- Median distance drone->crop: 25.9m, P99: 45.7m
- Память при генерации: до ~8.7 GB RAM (маршрут 09 stitched 44800x33280)
- Разрешение 512 для downstream задач (сегментация, depth, normals); resize до 256x256 в dataloader
### Ревью и исправления (2026-04-17)
1. `train_db.txt`/`test_db.txt` содержали только matched кропы -> теперь все ~74K (полная gallery)
2. `db_position.txt` -> `db_postion.txt` (совместимость с UAV-GeoLoc), добавлены scale_lon/scale_lat, tab-separator
3. `positive.json` ключи были filename (`01_0001.JPG`) -> теперь frame_id (`0001`)
4. Semi-positive поиск O(n) -> O(1) через dict lookup по (x,y) grid
5. Удален мёртвый код (`haversine_m`, `defaultdict` import)
### Известные ограничения
- Нет val split (только train/test, как в оригинальном UAV-VisLoc)
- 6 drone в маршруте 06 (06_0093-06_0098) за пределами спутниковой карты (distance >1000m)
- Большие спутниковые карты загружаются целиком в RAM при генерации (до 8.7 GB для route 09)