Files
caption-test/CLAUDE.md
pikaliov abb3337f8d Rewrite: GatedFusion architecture + UAV-GeoLoc dataset
Architecture v2:
- Query branch: drone + text -> GatedFusion -> proj -> query_emb
- Gallery branch: satellite -> proj -> gallery_emb
- Single InfoNCE loss (asymmetric 0.6/0.4)
- GatedFusion: learnable gated addition (sigma(alpha)*img + (1-sigma(alpha))*text)
- Baseline mode: gate=1.0 (text ignored)

Dataset:
- UAV-GeoLoc loader with template captions from path metadata
- 27 terrain types with predefined features
- Random positive crop sampling per epoch

Configs: balanced (gate=0.7), baseline (no text), text_heavy (gate=0.3)

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

209 lines
11 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
## Архитектура системы (v2, 2026-04-17)
```
QUERY BRANCH (drone + caption):
drone_img --> GeoRSCLIP ViT-B/32 (frozen) --> drone_feat [B,512]
caption --> GeoRSCLIP Text (partial unfreeze) --> text_feat [B,512]
|
GatedFusion: q = sigma(alpha)*drone + (1-sigma(alpha))*text
|
proj_query (Linear 512->512) --> L2-norm --> query [B,512]
GALLERY BRANCH (satellite only):
sat_img --> GeoRSCLIP ViT-B/32 (frozen) --> sat_feat [B,512]
|
proj_gallery (Linear 512->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: ~1.2M из ~151M (proj_query + proj_gallery + fusion alpha + text last_block)
## Ключевые файлы
| Файл | Назначение |
|------|-----------|
| `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) |
## Backbone: GeoRSCLIP ViT-B/32
- **Checkpoint:** `checkpoints/RS5M_ViT-B-32.pt` (скачать с github.com/om-ai-lab/RS5M)
- **Image encoder:** ViT-B/32, 224x224, 512-dim, ~86M params — frozen
- **Text encoder:** CLIP text transformer, 77 tokens, 512-dim — partial unfreeze (last_block + text_projection)
- **Throughput:** ~4000 img/s на RTX 4090 (AMP, batch 128)
- **Выбран вместо SigLIP 2** (ViT-SO400M, 384px, ~400M): в 7-10x быстрее, domain-specific (обучен на 5M RS-изображений), больше batch = больше негативов в InfoNCE
## 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 логируется каждую эпоху для интерпретации вклада текста
## Датасет: UAV-GeoLoc
- **Путь:** `/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)
- **Структура:** `Terrain/{type}/{scene}/query/height{N}_rot{M}/footage/{file}.jpeg`
- **Index:** `Index/train_query.txt``query_path 0 pos_crop1 pos_crop2 ...`
### Template captions (из path metadata)
Формат: `"Aerial view at {height}m facing {heading} over {terrain} terrain near {scene}. Plan-view features: {features}."`
Пример: `"Aerial view at 100m facing northwest over volcanic terrain near KilaueaVolcano. Plan-view features: lava flows, crater edges, volcanic rock."`
Metadata извлекается из пути:
- `Terrain/Volcano/KilaueaVolcano/query/height100_rot315/...` → terrain=Volcano, scene=KilaueaVolcano, height=100, heading=northwest
- 27 terrain типов с predefined features (Volcano, Mountain, Hill, Desert, Plain, ...)
- Country subset: features = "buildings, roads, urban blocks, rooftops, intersections"
## Конфигурации (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 |
Общие параметры: 10 epochs, batch 128, lr=1e-4, AMP, cosine LR schedule, eval every 2 epochs.
## Запуск
```bash
# 1. Baseline (no text)
python -m src.training.train --config conf/baseline_no_text.gin
# 2. With captions (primary test)
python -m src.training.train --config conf/balanced.gin
# 3. Text-heavy (stress test)
python -m src.training.train --config conf/text_heavy.gin
# 4. Compare
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 (query -> gallery)
| 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 |
**Logged per epoch:** loss, temperature (tau), gate value (sigma(alpha)), lr
**Eval metrics:** R@1, R@5, R@10 для query->gallery и gallery->query
## Бюджет времени (RTX 4090, 24 GB)
| Фаза | Время |
|------|-------|
| Один 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`
- **Статус:** код готов, ещё не запускался
- **Задача:** нарезка satellite кропов 512x512, stride 256 для UAV-VisLoc dataset
- **Подробности:** см. ниже
---
## Датасеты (справочник)
### 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`
- **Статус:** код готов, ещё не запускался
### Запуск
```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 256
```
### Pipeline
1. Resize drone -> 256x256 (JPEG, quality=95)
2. Stitch satellite tiles для маршрута 09 (4 тайла -> 44800x33280)
3. Нарезка satellite -> кропы 512x512, stride 256, resize -> 256x256 (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) |
### Ожидаемые объёмы
- Drone: 6744 (без маршрута 07: 30 images excluded)
- Satellite кропов: ~74,807
- Память: до ~4.5 GB RAM (маршрут 09 stitched 44800x33280)
### Ревью и исправления (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)
- Большие спутниковые карты загружаются целиком в RAM (до 4.5 GB для route 09)