Eksperymenty i Wyniki

Ten dokument zbiera najwazniejsze etapy eksperymentow wykonanych do tej pory.

1. Weryfikacja bazowa na CPU

Poczatkowo model byl sprawdzany lokalnie na CPU:

  • testy shape,
  • forward/backward,
  • prosty trening na sztucznym datasecie,
  • zapis i odczyt checkpointow.

To potwierdzilo, ze sama logika modelu i treningu dziala bez CUDA.

2. Penn-Fudan Pedestrian

Penn-Fudan byl pierwszym realnym datasetem:

  • maly,
  • prosty,
  • jedna klasa,
  • bardzo dobry do debugowania boxow i lossow.

Najwazniejsze obserwacje

Kluczowe poprawy jakosci przyszly z:

  • letterbox zamiast deformujacego resize,
  • lepszego postprocessu,
  • dluzszego treningu,
  • parity z RT-DETRv3 w query selection i denoising,
  • auxiliary branch w stylu PP-YOLOE.

Mocny punkt odniesienia

Najbardziej udany checkpoint Penn-Fudan po treningu na GPU dawal profil:

  • avg_best_iou ~= 0.824
  • pred_precision_proxy@0.50 ~= 0.984
  • gt_recall@0.50 ~= 0.639
  • duplicate_pair_ratio@0.40 = 0.000

W praktyce oznaczalo to:

  • bardzo dobre polozenie boxow,
  • brak duplikatow,
  • sensowny recall jak na maly eksperymentalny setup.

3. VOC 2007 - tylko klasa car

VOC car byl pierwszym sensownym testem poza jedna klasa pieszych.

Wczesne biegi

Pierwsze checkpointy pokazywaly, ze model:

  • poprawnie lokalizuje samochody,
  • jest raczej konserwatywny score'owo,
  • generalizuje na customowe zdjecia lepiej niz czysto zabawkowy baseline.

Przebudowa architektury

W kolejnych iteracjach dodano:

  • parity elementow transformera wzgledem ppdet,
  • nowy HybridEncoder,
  • auxiliary path w stylu PP-YOLOE,
  • training recipe z EMA, schedulerem i lepszym backbone.

Mocniejszy wariant VOC

Po dluzszym treningu z ResNet34, AMP i nowym training stackiem uzyskano profil:

  • avg_best_iou ~= 0.662
  • pred_precision_proxy@0.50 ~= 0.747
  • gt_recall@0.50 ~= 0.518
  • duplicate_pair_ratio@0.40 = 0.000
  • AP50 ~= 0.473
  • AP75 ~= 0.357
  • mAP@0.50:0.95 ~= 0.337

To jest obecny formalny punkt odniesienia dla samochodow.

4. COCO2017 traffic-5

Dodano standaryzowany subset COCO2017 jako bardziej rozpoznawalny benchmark wieloklasowy dla scen miejskich.

Klasy:

  • person
  • bicycle
  • car
  • bus
  • truck

Przygotowany wariant:

  • train = 5000
  • val = 1000
  • filtr bardzo malych obiektow: min_area_ratio = 0.0002
  • anotacje w formacie zgodnym z loaderem BDD-like,
  • root datasetu: data/coco_traffic5,
  • config treningowy: configs/coco_traffic5/resnet50.yaml.

Statystyki przygotowanego subsetu:

Split Obrazy Boxy Srednio boxow / obraz Mediana area ratio
train 5000 22836 4.57 0.01205
val 1000 4598 4.60 0.01193

Historyczny obiekt MinIO:

datasets/coco_traffic5/5k1k-seed0-area0002/coco_traffic5-5k1k-seed0-area0002.tar.gz

Stan aktualny:

  • obiekt zostal usuniety z MinIO 2026-05-10, zeby zwolnic miejsce,
  • lokalny katalog data/coco_traffic5 zostal usuniety,
  • wyniki treningowe z tego datasetu byly wykonane przed poprawka bledu DFL w auxiliary dense head, wiec nie powinny byc traktowane jako miarodajny wynik jakosciowy modelu.

Ten dataset nadal jest sensownym pomyslem na benchmark, ale powinien zostac odtworzony i przetrenowany dopiero po przejsciu sanity checku overfit na malym podzbiorze.

5. BDD100K vehicle-3

Dodano tez pipeline dla nowoczesniejszego datasetu drogowego.

BDD100K vehicle-3 nie jest pelnym oficjalnym benchmarkiem BDD100K. To roboczy subset przygotowany na potrzeby tego projektu:

  • klasy car
  • truck
  • bus
  • zrodlo lokalne: dostepne obrazy BDD 10k/train
  • split deterministyczny: 90/10, seed 0
  • train = 2665 obrazow
  • val = 297 obrazow

Ten split powstal dlatego, ze lokalne anotacje 100k/val nie pasowaly nazwami do lokalnych obrazow 10k/val. Uzycie ich bez kontroli dawalo puste val. Obecny subset jest wiec poprawny technicznie i nadaje sie do eksperymentow, ale w raporcie trzeba go opisac jako wybrany podzbior BDD100K, a nie jako pelny BDD100K.

Zakres zaimplementowany:

  • przygotowanie odfiltrowanego subsetu,
  • dataset loader,
  • integracja z MinIO,
  • cache po stronie Kaggle,
  • zdalny trening DDP na 2x Tesla T4,
  • checkpointy wracajace przez MinIO.

Historyczny obiekt datasetu w MinIO:

datasets/bdd100k_vehicle3/trainval90-seed0/bdd100k_vehicle3-trainval90-seed0.tar.gz

Stan aktualny:

  • obiekt zostal usuniety z MinIO 2026-05-10,
  • lokalny dataset BDD vehicle-3 nie jest obecnie aktywnym datasetem roboczym,
  • wyniki BDD ponizej sa wazne infrastrukturalnie, ale jakosciowo sa ograniczone, bo powstaly przed znalezieniem bledu DFL.

Smoke BDD na Kaggle

Pierwszy smoke potwierdzil:

  • pobieranie datasetu z MinIO,
  • rozpakowanie do data/bdd100k_vehicle3,
  • DDP na dwoch kartach: distributed=True world_size=2,
  • zapis checkpointu.

Przy runtime.amp=true GradScaler odrzucal kroki optymalizatora w bardzo krotkim smoke tescie:

  • optimizer_steps = 0

Dlatego kolejne biegi BDD byly prowadzone z runtime.amp=false. To jest wolniejsze, ale daje realne aktualizacje wag.

BDD + HGNet-V2-S, 3 epoki

Pierwszy pelniejszy bieg:

  • config: configs/bdd_vehicle3/hgnetv2_s.yaml
  • model: HGNet-V2-S
  • epoki: 3
  • batch per proces/GPU: 4
  • efektywny batch globalny: okolo 8
  • DDP: 2x T4
  • AMP: false
  • checkpoint: artifacts/remote/custom/artifacts/bdd_vehicle3_hgnetv2_s_3e.pt

Najwazniejsze obserwacje:

  • optimizer_steps = 334 w kazdej epoce,
  • loss spadl z okolo 123.6 do 94.0,
  • throughput okolo 10 img/s,
  • pamiec GPU okolo 1.54 GB,
  • mAP, AP50, AP75 nadal 0,
  • model po 3 epokach nie produkowal uzytecznych predykcji powyzej progu ewaluacji.

Ten bieg potwierdzil, ze pipeline trenuje poprawnie, ale 3 epoki sa niewystarczajace jakosciowo.

BDD + HGNet-V2-S, batch 16, 10 epok

Nastepny bieg zwiekszyl batch, bo poprzedni trening uzywal bardzo malo pamieci.

Ustawienia:

  • config: configs/bdd_vehicle3/hgnetv2_s.yaml
  • epoki: 10
  • batch per proces/GPU: 16
  • efektywny batch globalny: okolo 32
  • DDP: 2x T4
  • AMP: false
  • train_batches = 84
  • checkpoint: artifacts/remote/custom/artifacts/bdd_vehicle3_hgnetv2_s_b16_10e.pt

Wyniki techniczne:

  • batch 16 miesci sie bez problemu,
  • maksymalne zuzycie pamieci GPU: okolo 4.56 GB,
  • throughput: okolo 16.6 img/s,
  • optimizer_steps = 84 w kazdej epoce,
  • trening byl znacznie szybszy niz przy batch 4.

Wyniki jakosciowe po 10 epokach:

  • loss spadl z okolo 387.1 do 337.7, ale byl niestabilny,
  • mAP@0.50:0.95 = 0.0,
  • AP50 = 0.0,
  • AP75 = 0.0,
  • predykcje zaczely sie pojawiac w czesci epok, ale IoU pozostalo bardzo niskie,
  • najlepszy sygnal proxy byl w okolicy epoki 7: avg_best_iou ~= 0.0056, gt_recall@0.50 ~= 0.0023, nadal praktycznie zerowo.

Interpretacja:

  • problemem nie jest juz infrastruktura, batch ani DDP,
  • model faktycznie aktualizuje wagi i korzysta z obu GPU,
  • obecny recipe nie daje jeszcze sensownej detekcji na BDD vehicle-3,
  • brak pretrainu dla HGNet-V2-S i maly subset BDD najpewniej utrudniaja start,
  • wysokie i niestabilne lossy sugeruja, ze trzeba dopracowac recipe przed dluzszym treningiem.

Wnioski dla BDD

To jest baza pod dalsze bardziej realistyczne eksperymenty wieloklasowe.

Najrozsadniejsze kolejne kroki:

  • uruchomic ewaluacje tego checkpointu z nizszym evaluation.score_threshold, np. 0.01, zeby zobaczyc, czy predykcje sa tylko zbyt nisko punktowane,
  • porownac HGNet-V2-S z ResNet50 lub ResNet34 na tym samym splicie,
  • sprobowac mniejszego LR dla batch 16, np. solver.lr=0.0001,
  • rozwazyc pretrain backbone'u albo import wag PPHGNetV2-S,
  • ograniczyc ewaluacje per epoka przy dluzszych biegach, np. eval_interval=2, bo walidacja 297 obrazow jest relatywnie kosztowna,
  • dopiero po stabilizacji recipe uruchamiac 20+ epok.

6. Vehicles3 - Roboflow vehicles v2 release

Aktualnym czystym datasetem roboczym jest vehicles3, przygotowany z archiwum:

data/vehicles.v2-release.coco.zip

To dataset Roboflow w formacie COCO, licencja CC BY 4.0, z obrazami 640x480. Oryginalne klasy byly zbyt szczegolowe i malo czytelne, dlatego zostaly zmapowane do trzech klas:

  • car
  • bus
  • truck

Mapowanie:

  • car -> car
  • big bus, bus-l-, bus-s-, small bus -> bus
  • big truck, mid truck, small truck, truck-l-, truck-m-, truck-s-, truck-xl- -> truck

Przygotowanie datasetu wykonuje:

.venv/bin/python scripts/prepare_dataset.py --dataset vehicles3

Lokalny root:

data/vehicles3

Config treningowy:

configs/vehicles3/resnet50.yaml

Statystyki po konwersji:

Split Obrazy Boxy car bus truck
train 2633 31905 19083 1134 11688
valid 963 13450 8537 342 4571
test 454 6222 4021 149 2052

Aktualny obiekt MinIO:

datasets/vehicles3/roboflow-v2-release/vehicles3-roboflow-v2-release.tar.gz

SHA256 archiwum:

df67861809359786237779affa2fe3f0334ab48f855494922199c9c3a065be56

Vehicles3 - probe 1 epoka

Pierwszy probe potwierdzil, ze dataset, MinIO, rozpakowanie i DDP dzialaja:

  • artifact: artifacts/remote/custom/artifacts/vehicles3_resnet50_remote_probe_1e.pt
  • DDP: world_size=2
  • data.max_train_samples=64
  • data.max_eval_samples=64
  • solver.epochs=1
  • solver.batch_size=4

Wyniki po 1 epoce byly bardzo slabe:

  • EMA AP50 ~= 0.000148
  • EMA mAP ~= 0.0000176
  • EMA avg_best_iou ~= 0.0788
  • RAW AP50 = 0.0
  • RAW avg_best_iou ~= 0.0089

Wniosek: pipeline dziala, ale model po 1 epoce nie wykrywa jeszcze niczego uzytecznego.

Krytyczna poprawka DFL

W easy_rtdetr/losses.py znaleziono blad w AuxiliaryDenseCriterion.forward().

Stara wersja mnozyla loss_dfl o ksztalcie [num_pos] przez bbox_weight.unsqueeze(-1) o ksztalcie [num_pos, 1]. Broadcasting tworzyl macierz [num_pos, num_pos], co sztucznie zawyzalo auxiliary DFL.

Efekt na probce przed poprawka:

  • loss_total ~= 364.3
  • loss_dfl_aux_o2m ~= 293.2

Efekt po poprawce:

  • loss_total ~= 71.6
  • loss_dfl_aux_o2m ~= 1.42

Dodano test regresyjny:

tests/test_engine_utils.py::test_auxiliary_dense_dfl_loss_does_not_broadcast_weights

Ta poprawka jest istotna: wyniki BDD/COCO/vehicles sprzed niej nie powinny byc uzywane jako ostateczny argument, ze architektura nie dziala.

Vehicles3 - ResNet50 po poprawce DFL, 5 epok

Po poprawce DFL uruchomiono test:

  • config: configs/vehicles3/resnet50.yaml
  • artifact: artifacts/remote/custom/artifacts/vehicles3_resnet50_dflfix_5e.pt
  • epoki: 5
  • data.max_eval_samples=128
  • DDP: 2x Tesla T4
  • EMA wlaczona z ema_decay=0.999

Najwazniejsze metryki:

Epoka RAW AP50 RAW mAP RAW avg_best_iou RAW recall proxy EMA AP50 EMA mAP
1 ~0.000519 ~0.000091 brak pelnego odczytu brak pelnego odczytu ~0.000028 bardzo niskie
3 ~0.001663 ~0.000286 brak pelnego odczytu ~0.166 brak pelnego odczytu brak pelnego odczytu
5 ~0.002563 ~0.000462 ~0.0735 ~0.1719 ~0.000175 ~0.000034

Interpretacja:

  • poprawka DFL usunela realny blad w lossie,
  • metryki po 5 epokach nadal sa zbyt slabe do demo i raportowania jako skuteczny model,
  • RAW model uczy sie szybciej niz EMA, co jest normalne w krotkim treningu,
  • 5 epok z cosine schedulerem jest zlym testem finalnej jakosci, bo LR dochodzi do zera bardzo szybko,
  • przed dlugim treningiem potrzebny jest maly overfit sanity check.

Rekomendowany kolejny test:

  • 32-64 obrazy z train,
  • 100-200 epok,
  • staly LR 1e-4,
  • runtime.amp=false albo nizszy runtime.amp_init_scale,
  • solver.use_ema=false na czas diagnostyki,
  • ewaluacja na tym samym malym train subset,
  • oczekiwany sygnal: wyrazny wzrost AP50/IoU. Jesli overfit nadal nie dziala, problem jest w modelu, lossach, assignment albo postprocessie.

7. Lokalny benchmark wideo na CPU

Dodano prosty benchmark inferencji na lokalnym pliku wideo:

scripts/benchmark_video.py

Material testowy:

  • lokalny plik: artifacts/videos/youtube_Gr0HpDM8Ki8.mp4
  • rozdzielczosc: 640x360
  • FPS zrodlowy: 25
  • czas: 30 s
  • liczba klatek: 750

Wyniki na lokalnym CPU laptopa, torch_threads=4:

Model Checkpoint image_size prog score FPS modelu FPS pipeline Srednio detekcji/klatke Interpretacja
VOC car ResNet34 voc_car_kaggle.pt 256 0.18 3.80 3.77 1.08 najlepszy obecny model do demo detekcji samochodow na filmie
BDD ResNet50 overfit64 bdd_vehicle3_resnet50_overfit64_512_200e.pt 512 0.3 1.14 1.13 0.84 wykrywa pojazdy, ale CPU nie jest realtime
BDD ResNet50 v2 b8 20e best bdd_vehicle3_resnet50_v2_b8_20e.pt 640 0.05 0.91 0.91 40.20 najnowszy artefakt z Kaggle, ale checkpoint ma epoch=2 i jest bardzo zaszumiony
BDD HGNet-V2-S b16 10e bdd_vehicle3_hgnetv2_s_b16_10e.pt 256 0.05 4.89 4.85 0.00 szybszy, ale ten checkpoint nie daje uzytecznych detekcji

Zapisany podglad wideo z bboxami:

artifacts/videos/youtube_Gr0HpDM8Ki8_voc_car_r34_cpu_preview.mp4
artifacts/videos/youtube_Gr0HpDM8Ki8_bdd_resnet50_cpu_preview.mp4
artifacts/videos/youtube_Gr0HpDM8Ki8_bdd_resnet50_v2_cpu_preview.mp4

Wniosek:

  • do demonstracji samochodow najlepszy jest obecnie checkpoint VOC car, bo faktycznie uczy jedna klase car i daje najbardziej czytelny podglad,
  • na CPU laptopa obecny ResNet50 jest za wolny do realtime,
  • wariant ResNet50 v2 przy 640 jest jeszcze wolniejszy i w aktualnym checkpointcie generuje duzo nisko punktowanych predykcji,
  • lzejszy backbone jest szybszy, ale wymaga lepszego treningu/checkpointu,
  • BDD overfit jest dobrym dowodem poprawnosci pipeline'u, ale nie powinien byc przedstawiany jako model generalizujacy.

8. Jak interpretowac score modelu

Score przy boxie:

  • jest scorem po sigmoid,
  • sluzy do rankingu i filtrowania,
  • nie jest idealnie skalibrowanym prawdopodobienstwem.

Dlatego w repo dodano tez prosty etap kalibracji precision. Dzieki temu wizualizacje moga pokazywac:

  • raw=... jako surowy score modelu,
  • p=... jako oszacowana precyzja empiryczna na zbiorze ewaluacyjnym.

9. Diagnostyka vehicles3 po poprawkach postprocessingu

Dodano narzedzie diagnostyczne:

scripts/diagnose_validation.py

Uruchomienie dla aktualnego checkpointu HGNet-V2-S, z pelnym audytem datasetu i metrykami/obrazkami na 64 obrazach walidacyjnych:

.venv/bin/python scripts/diagnose_validation.py \
  --config configs/vehicles3/hgnetv2_s.yaml \
  --checkpoint artifacts/remote/custom/artifacts/vehicles3_hgnetv2_s_vfl_fullval_5e.pt \
  --output-dir artifacts/diagnostics/vehicles3_hgnetv2_s_vfl_fullval_5e \
  --set runtime.device=cpu \
  --set runtime.num_workers=0 \
  --set runtime.pin_memory=false \
  --set solver.eval_batch_size=4 \
  --diagnostic-max-eval-samples 64 \
  --max-visuals 24 \
  --visual-max-preds 30 \
  --visual-score-threshold 0.05

Artefakty:

artifacts/diagnostics/vehicles3_hgnetv2_s_vfl_fullval_5e/dataset_audit.json
artifacts/diagnostics/vehicles3_hgnetv2_s_vfl_fullval_5e/validation_metrics.json
artifacts/diagnostics/vehicles3_hgnetv2_s_vfl_fullval_5e/visuals/

Audyt datasetu:

Split Obrazy Obiekty car bus truck Puste obrazy Bledne boxy
train 2633 31905 19083 1134 11688 0 0
valid 963 13450 8537 342 4571 0 0

Wniosek z audytu:

  • dataset nie wyglada na technicznie uszkodzony: brak pustych obrazow, brak blednych etykiet i brak blednych boxow,
  • klasy sa mocno niezbalansowane: bus to tylko okolo 3.6% obiektow w train i 2.5% w valid,
  • wiekszosc obiektow jest mala: w train 23844/31905, a w valid 10366/13450 boxow ma powierzchnie ponizej 1% obrazu.

Metryki na 64 obrazach walidacyjnych:

Metryka Wartosc
AP50 0.5805
AP75 0.2378
mAP@0.50:0.95 0.2883
pred_precision_proxy@0.50 0.2858
gt_recall@0.50 0.9035
duplicate_pair_ratio@0.40 0.0193

Metryki per klasa na 64 obrazach:

Klasa AP50 mAP predykcje GT TP FP FN precision recall
car 0.6742 0.3089 2976 648 563 2413 85 0.1892 0.8688
bus 0.4401 0.2900 672 28 25 647 3 0.0372 0.8929
truck 0.6272 0.2659 2752 371 336 2416 35 0.1221 0.9057

Wniosek z diagnostyki:

  • model ma wysoki recall, czyli znajduje wiekszosc obiektow,
  • glowny problem to za duzo false positives i slaba kalibracja/ranking score,
  • bus jest najslabsza klasa w precyzji, prawdopodobnie przez mala liczbe przykladow i mylenie z truck,
  • kolejny techniczny kierunek to poprawa scoringu/assignment/klasyfikacji, a nie dalsze krecenie samym top-k.

10. Quality-aware scoring head

Po diagnostyce false positives dodano opcjonalny quality_score_head w glownym dekoderze DETR.

Mechanizm:

  • kazda warstwa dekodera moze przewidywac dodatkowy quality_logit,
  • targetem dla matched query jest IoU predykcji z dopasowanym GT,
  • targetem dla unmatched query jest 0,
  • loss jest varifocal-like,
  • w inferencji score jest mnozony przez przewidywana jakosc:
final_score = sigmoid(class_logit) * sigmoid(quality_logit)

Dlaczego:

  • aktualny model ma wysoki recall, ale niska precyzje,
  • zwykly class score nie wystarcza do rankingu detekcji,
  • VarifocalNet opisuje IoU-aware classification score jako sposob laczenia obecnosci obiektu i jakosci lokalizacji,
  • RT-DETR/D-FINE w praktyce korzystaja z Varifocal Loss jako glownego elementu klasyfikacji/rankingu.

Wazne ustawienia:

model:
  use_quality_score_head: true
  quality_loss_weight: 1.0

Zmiana jest kompatybilna wstecz: stare checkpointy nie maja tej glowy, wiec domyslne use_quality_score_head=false pozwala je dalej ladowac.

Test 5 epok na Kaggle

Uruchomiono krotki test:

configs/vehicles3/hgnetv2_s.yaml
solver.epochs=5
solver.eval_interval=5
solver.save_interval=5

Trening dzialal na 2x Tesla T4 przez DDP. Dataset byl staged przez MinIO:

datasets/vehicles3/quality-run/vehicles3.tar.gz

Artefakty:

artifacts/custom/vehicles3_hgnetv2_s_quality_5e.pt
artifacts/custom/vehicles3_hgnetv2_s_quality_5e_raw.pt

Wazne: wersja quality_5e.pt zawiera tez EMA, ale EMA po 5 epokach byla praktycznie bezuzyteczna. Do dalszych testow nalezy uzywac quality_5e_raw.pt albo ladowac model z use_ema=False.

Metryki RAW po 5 epokach:

Metryka Wartosc
AP50 0.5598
AP75 0.2418
mAP@0.50:0.95 0.2767
pred_precision_proxy@0.50 0.3572
gt_recall@0.50 0.8851
avg_preds 98.73

Per klasa RAW:

Klasa AP50 mAP precision@0.50 recall@0.50
car 0.7006 0.3442 0.1942 0.8584
bus 0.2941 0.1563 0.0170 0.9123
truck 0.6847 0.3294 0.1065 0.9070

Porownanie do poprzedniego HGNet-V2-S 5e bez osobnej quality head:

  • AP50: 0.5611 -> 0.5598, praktycznie bez zmiany,
  • mAP: 0.2845 -> 0.2767, lekko gorzej,
  • precision proxy: 0.2132 -> 0.3572, wyraznie lepiej,
  • recall: 0.9239 -> 0.8851, spadek recall w zamian za mniej false positives.

Wniosek: quality head robi to, czego oczekiwalismy kierunkowo: poprawia ranking/precyzje, ale po 5 epokach kosztuje recall i lekko mAP. To nie jest jeszcze finalny model; nastepny test powinien isc bez EMA i z dluzszym treningiem albo z mniejsza waga quality_loss_weight, np. 0.5.

11. Eksperyment P2 - 4 poziomy feature maps (negatywny wynik)

W odpowiedzi na obserwacje, ze wiekszosc obiektow vehicles3 ma area < 1% obrazu, sprobowano dodac poziom P2 (stride 4) do feature pyramid. Wariant pelny: backbone zwraca 4 poziomy (P2, P3, P4, P5), encoder + decoder operuja na wszystkich czterech.

Zmiany w kodzie:

  • TorchvisionResNetBackbone i HGNetV2Backbone dostaly opcjonalny flag return_p2
  • build_backbone przekazuje go gdy w feat_strides jest 4
  • model.py automatycznie wykrywa return_p2 = 4 in config.feat_strides
  • konfiguracja YAML: num_feature_levels: 4, feat_strides: [4, 8, 16, 32], hybrid_encoder_use_idx: [3]

Realny koszt na 640x640:

  • token count rosnie z 8.4k do ~34k (~4x wiecej w deformable encoder)
  • na T4 16GB pojawil sie OOM przy batch_size=4
  • przy batch_size=2 + grad_accum_steps=2 trening sie miesci, ale memory peak 13.8GB

Wyniki po 5 epokach (HGNet-V2-S, P2, vehicles3)

Metryka P3 baseline P2 Zmiana
AP50 0.560 0.00054 -99.9%
mAP@0.50:0.95 0.277 0.0001 -99.96%
gt_recall@0.50 0.885 0.084 -91%
pred_precision@0.50 0.357 0.022 -94%

Najbardziej diagnostyczny sygnal byl per-klasa: model zaczal kolapsowac na klase bus (3.6% datasetu) i przewidywal 52544 busow na 342 prawdziwe (153x nadprodukcja).

Interpretacja

P2 dodaje ~25600 anchorow na poziomie stride 4. W 5 epokach model nie zdazyl nauczyc sie sensownie korzystac z nowych, losowo zainicjalizowanych features na tym poziomie. Auxiliary dense head i assigner taskAligned konkurowaly o sygnal z duza liczba slabych pozycji, co zdestabilizowalo klasyfikacje.

Wniosek

P2 mogloby zadzialac przy:

  • dluzszym treningu (30+ epok), aby P2 features mialy szanse sie zsyntonizowac,
  • mocniejszym pretrenowaniu backbone'u, zeby stage 0 nie startowal od zera,
  • rozdzielonej krzywej lr dla nowych poziomow.

W obecnym setupie wniosek jest jednoznaczny: architektura nie byla waskim gardlem na 5 epok. Ograniczeniem byl recipe (pretrain, dlugosc treningu, class balance). Pelny P2 zostal cofniety w configach vehicles3. Kod-level wsparcie pozostalo jako opt-in (feat_strides: [4, 8, 16, 32]), zeby ulatwic kolejne probe w pelniejszym setupie.

12. HGNet-V2-B0 z pretrainem + class reweighting

Po negatywnym eksperymencie P2 wrocono do P3 i skupiono sie na recipe. Wprowadzono dwie zmiany jednoczesnie:

  1. Mniejszy backbone hgnetv2_b0 (~5M params vs ~23M dla "S") z importowanym pretrainem z PaddleClas (PPHGNetV2_B0_ssld_stage1_pretrained.pdparams).
  2. Class reweighting w SetCriterion.VarifocalLoss z wagami inverse-sqrt-frequency.

Wagi klas dla vehicles3

Klasa Liczba GT (train) Waga (inverse-sqrt, znormalizowana)
car 19083 0.47
bus 1134 1.93
truck 11688 0.60

Wzor: w_c = (1 / sqrt(n_c)) / mean_c (1 / sqrt(n_c)). Bus dostaje ~2x wieksza wage zarowno na positives jak i negatives w VarifocalLoss. To rownoczesnie nagradza model za TP na bus, jak i karze za FP. Wagi sa wczytywane jako buffer w SetCriterion przez nowe pole config cls_class_weights.

Implementacja w kodzie

  • easy_rtdetr/config.py: cls_class_weights: tuple[float, ...] | None = None
  • easy_rtdetr/losses.py: SetCriterion.__init__ przyjmuje class_weights, rejestrowane jako buffer; _varifocal_loss_with_logits mnozy element-wise weight tensor przez class_weights[None, None, :]
  • easy_rtdetr/model.py: przekazuje config.cls_class_weights do SetCriterion

Wsparcie dla B0 w backbone

Dodano nowy wariant arch B0 w _HGNET_V2_ARCH_CONFIGS (stem 16/16, stages mid 16/32/64/128). build_backbone rozumie nazwe hgnetv2_b0. scripts/import_hgnetv2.py przyjmuje teraz --arch B0 obok S.

Trening i wyniki

Konfiguracja configs/vehicles3/hgnetv2_s.yaml (mimo nazwy uzywa teraz B0 przez backbone_name: hgnetv2_b0):

  • backbone_pretrained_path: artifacts/hgnetv2_b0_pretrained.pt
  • cls_class_weights: [0.47, 1.93, 0.60]
  • image_size: 640, batch_size: 8, epochs: 5, lr: 2e-4
  • use_quality_score_head: true

Trening na 2x T4 przez DDP, ~1.5h, throughput 6.96 img/s, memory peak 10.2GB.

Wyniki na pelnym valid (963 obrazow):

Metryka Baseline (S 5e, no pretrain) B0 5e, pretrain + reweight Zmiana
AP50 0.560 0.758 +35%
AP75 0.242 0.490 +102%
mAP@0.50:0.95 0.277 0.458 +65%
gt_recall@0.50 0.885 0.917 +4%
pred_precision@0.50 0.357 0.506 +42%
avg_preds/image 98 72 -27%

Per klasa (AP50):

Klasa Baseline B0 + reweight Zmiana
car 0.701 0.775 +11%
bus 0.294 0.662 +125%
truck 0.685 0.836 +22%

Mean score per klasa: car 0.214, bus 0.067, truck 0.178. Bus ma teraz najnizszy mean score, co znaczy ze reweight + pretrain zatrzymaly nadprodukcje bus. Recall na wszystkich klasach > 0.89.

Wniosek

Recipe (pretrain + class reweight) byl waskim gardlem, nie architektura. Dwie niezalezne zmiany dlugosci tygodnia kodu daly ~2x lepsze metryki niz rozbudowywanie architektury (P2 dalo regresje 1000x).

Domain shift jako otwarta sprawa

Pomimo dobrych metryk na valid, model jest mocno specyficzny dla rodzaju kamery z vehicles3 (klatki CCTV/dashcam z kilku sesji nagraniowych: aditganteng_mp4, malam_04112021_mp4, etc.). Na zdjeciach z innej dystrybucji (np. lokalne pliki street_of_cars.jpeg) detekcje sa zauwazalnie slabsze. To nie jest blad modelu - to natura datasetu. Lek na to to mocniejsza augmentacja (mosaic, random_scale_crop, perspective transforms) lub mieszanie z innym datasetem (BDD100K, COCO traffic-5).

13. Jetson TensorRT benchmark - openimages_traffic5_raw

Przetestowano eksportowany model:

easy_rtdetr_openimages_traffic5_raw.onnx

Model byl zapisany jako ONNX z external data, wiec do konwersji potrzebny byl komplet:

easy_rtdetr_openimages_traffic5_raw.onnx
easy_rtdetr_openimages_traffic5_raw.onnx.data

Srodowisko:

  • host: Jetson Orin, GPU Orin, compute capability 8.7
  • TensorRT: 10.3.0
  • obraz Docker: 10.10.0.1:5000/camera-worker:latest-jetson
  • input tensor: images
  • input shape: 1x3x640x640
  • precyzja builda: FP32+FP16 przez trtexec --fp16

Uruchomienie:

cd /data/test_model

IMAGE=10.10.0.1:5000/camera-worker:latest-jetson \
./scripts/jetson_tensorrt_benchmark.sh easy_rtdetr_openimages_traffic5_raw.onnx

Wynik konwersji:

Metryka Wartosc
Czas budowy engine'a 1516.5 s
Rozmiar engine'a 48.86 MiB
Total weights memory 47.43 MiB
Total activation memory 104.17 MiB
Execution context device memory 99.34 MiB

Wyjscia engine'a:

Tensor Shape dtype
logits 1x300x5 float32
boxes 1x300x4 float32
quality 1x300x1 float32

Wyniki trtexec:

Metryka Wartosc
Throughput 13.4816 qps
Latency mean 74.403 ms
Latency median 74.4094 ms
Latency p90 74.8887 ms
Latency p95 75.1406 ms
Latency p99 75.4912 ms
GPU Compute Time mean 73.9811 ms
H2D Latency mean 0.4138 ms
D2H Latency mean 0.0081 ms

Wyniki TensorRT Python API + CuPy:

Metryka Wartosc
Warmup 50 iteracji
Iteracje mierzone 500
Mean latency 74.1474 ms
FPS 13.4867
Copy outputs false

Interpretacja:

  • wynik trtexec i wynik Python API sa praktycznie identyczne, wiec surowa inferencja engine'a jest stabilna,
  • realny limit samego modelu na tym Jetsonie to okolo 13.5 FPS / 74 ms dla 1x3x640x640,
  • wejscie i wyjscia sa float32, ale TensorRT wykonuje kompatybilne warstwy w FP16 w ramach builda FP32+FP16,
  • ten benchmark mierzy tylko surowy engine; pelny camera-worker bedzie wolniejszy o RTSP/decode, preprocessing, postprocessing, rysowanie i WebRTC,
  • czas budowy engine'a jest wysoki (~25 minut), dlatego zbudowany .engine powinien byc traktowany jako artefakt runtime, a nie cos budowanego przy starcie workerow.

14. Wnioski z wynikow

Najwazniejsze wnioski praktyczne:

  • model jest juz uzywalnym research prototype, a nie tylko szkicem,
  • pipeline dziala lokalnie i zdalnie,
  • Penn-Fudan pokazal mocna jakosc lokalizacji,
  • VOC car pokazal sensowna generalizacje i daje juz formalne mAP,
  • BDD100K i COCO traffic-5 byly wartosciowe infrastrukturalnie, ale ich stare wyniki jakosciowe sa mniej wiarygodne po znalezieniu bledu DFL,
  • aktualnym czystym datasetem roboczym jest vehicles3,
  • najwazniejszy kolejny krok to overfit sanity check po poprawce DFL, a dopiero potem dluzszy trening na pelnym datasecie.