Дальность: порог по лучам зависит от дальности, выборка пересобрана
This commit is contained in:
parent
5f6eb5eb52
commit
8a0badbc20
9 changed files with 181 additions and 41 deletions
10
README.md
10
README.md
|
|
@ -126,13 +126,13 @@ python tools/plot_benchmark.py # кривые и граф
|
|||
| Метрика | Значение | Чем измерено |
|
||||
|---|---|---|
|
||||
| Реальный объект 0.67 × 1.35 м на 55 м | **99.5 %** кадров | `tools/check_obstacle.py` |
|
||||
| Ложные тревоги, leave-one-bag-out | **5.6 трека на км**, 9.4 % кадров | `tools/evaluate.py --mbon-dir` |
|
||||
| То же без обученного считывания | 7.4 на км, 16.7 % кадров | там же, без `--mbon-dir` |
|
||||
| На незнакомой линии (памяти нет) | **20.6 на км**; без считывания 33.6 | `tools/evaluate.py --no-memory` |
|
||||
| Дальность (полигон, 14 004 наблюдения) | человек стоя P@50 = 0.70, **P@100 = 0.48, P@150 = 0.32** | `tools/plot_benchmark.py` |
|
||||
| Ложные тревоги, leave-one-bag-out | **8.0 трека на км**, 11.0 % кадров | `tools/evaluate.py --mbon-dir` |
|
||||
| То же без обученного считывания | 12.9 на км, 25.9 % кадров | там же, без `--mbon-dir` |
|
||||
| На незнакомой линии (памяти нет) | **20.3 на км**; без считывания 33.6 | `tools/evaluate.py --no-memory` |
|
||||
| Дальность (полигон, 14 004 наблюдения) | рабочая дальность **100 м**, P@50 = 0.70, **P@100 = 0.53, P@150 = 0.33** | `tools/plot_benchmark.py` |
|
||||
| Обработка кадра | медиана 33 мс из бюджета 100 мс | `tools/run_pipeline.py` |
|
||||
| Разделение «знакомое / новое» (без учителя) | ROC AUC 0.905 | `tools/tune_memory.py` |
|
||||
| Считывание MBON «предмет / тоннель» (с учителем) | ROC AUC **0.986** | `tools/train_mbon.py` |
|
||||
| Считывание MBON «предмет / тоннель» (с учителем) | ROC AUC **0.983** | `tools/train_mbon.py` |
|
||||
|
||||
Проверка всегда **leave-one-bag-out**: память обучается на всех записях, кроме
|
||||
проверяемой. Иначе цифры лгут — подавлять конструкции, которые сам же и
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -5,13 +5,13 @@
|
|||
"path_m": 0.0,
|
||||
"alarm_frames": 183,
|
||||
"alarm_rate": 0.9631578947368421,
|
||||
"fp_objects": 334,
|
||||
"fp_objects": 518,
|
||||
"fp_tracks": 4,
|
||||
"fp_per_km": NaN,
|
||||
"fp_median_d": 76.66390740207035,
|
||||
"fp_median_d": 106.83187139894102,
|
||||
"obj_rate": 0.9947368421052631,
|
||||
"ms_p50": 31.554799997138616,
|
||||
"ms_p95": 32.83338500204991,
|
||||
"ms_p50": 32.263549999697716,
|
||||
"ms_p95": 33.94519999383192,
|
||||
"train_size": 37073
|
||||
},
|
||||
{
|
||||
|
|
@ -25,23 +25,23 @@
|
|||
"fp_per_km": 4.981343439320496,
|
||||
"fp_median_d": 33.983066409463774,
|
||||
"obj_rate": null,
|
||||
"ms_p50": 34.39600000092469,
|
||||
"ms_p95": 37.78021999751218,
|
||||
"ms_p50": 34.84300000491203,
|
||||
"ms_p95": 38.588240001990926,
|
||||
"train_size": 36151
|
||||
},
|
||||
{
|
||||
"bag": "roundT_doubleT",
|
||||
"frames": 239,
|
||||
"path_m": 200.9598713551979,
|
||||
"alarm_frames": 0,
|
||||
"alarm_rate": 0.0,
|
||||
"fp_objects": 0,
|
||||
"fp_tracks": 0,
|
||||
"fp_per_km": 0.0,
|
||||
"fp_median_d": NaN,
|
||||
"alarm_frames": 3,
|
||||
"alarm_rate": 0.012552301255230125,
|
||||
"fp_objects": 3,
|
||||
"fp_tracks": 1,
|
||||
"fp_per_km": 4.976117835149752,
|
||||
"fp_median_d": 20.107822042272232,
|
||||
"obj_rate": null,
|
||||
"ms_p50": 33.786500001951936,
|
||||
"ms_p95": 40.69248000432708,
|
||||
"ms_p50": 34.14939999493072,
|
||||
"ms_p95": 40.86443000596773,
|
||||
"train_size": 36584
|
||||
},
|
||||
{
|
||||
|
|
@ -53,40 +53,40 @@
|
|||
"fp_objects": 25,
|
||||
"fp_tracks": 2,
|
||||
"fp_per_km": 8.104919303770778,
|
||||
"fp_median_d": 160.97122011941988,
|
||||
"fp_median_d": 160.95257704675265,
|
||||
"obj_rate": null,
|
||||
"ms_p50": 39.27589999693737,
|
||||
"ms_p95": 40.91062000243255,
|
||||
"ms_p50": 39.37539999969886,
|
||||
"ms_p95": 41.14257999899564,
|
||||
"train_size": 36718
|
||||
},
|
||||
{
|
||||
"bag": "roundT_squareT_pressureGate_squareT",
|
||||
"frames": 239,
|
||||
"path_m": 273.9439574444854,
|
||||
"alarm_frames": 32,
|
||||
"alarm_rate": 0.13389121338912133,
|
||||
"fp_objects": 32,
|
||||
"fp_tracks": 2,
|
||||
"fp_per_km": 7.3007633337022915,
|
||||
"fp_median_d": 102.23183508833142,
|
||||
"alarm_frames": 35,
|
||||
"alarm_rate": 0.14644351464435146,
|
||||
"fp_objects": 35,
|
||||
"fp_tracks": 4,
|
||||
"fp_per_km": 14.601526667404583,
|
||||
"fp_median_d": 105.12193877726114,
|
||||
"obj_rate": null,
|
||||
"ms_p50": 39.65690000222821,
|
||||
"ms_p95": 41.665320002539374,
|
||||
"ms_p50": 40.1726000054623,
|
||||
"ms_p95": 42.31233000136854,
|
||||
"train_size": 36083
|
||||
},
|
||||
{
|
||||
"bag": "squareT_platform_squareT_switch",
|
||||
"frames": 239,
|
||||
"path_m": 270.98139848673645,
|
||||
"alarm_frames": 27,
|
||||
"alarm_rate": 0.11297071129707113,
|
||||
"fp_objects": 27,
|
||||
"alarm_frames": 40,
|
||||
"alarm_rate": 0.16736401673640167,
|
||||
"fp_objects": 40,
|
||||
"fp_tracks": 2,
|
||||
"fp_per_km": 7.380580405772363,
|
||||
"fp_median_d": 103.27156681153457,
|
||||
"fp_median_d": 104.76858112973235,
|
||||
"obj_rate": null,
|
||||
"ms_p50": 36.54640000058862,
|
||||
"ms_p95": 39.32138000236591,
|
||||
"ms_p50": 37.06099999908474,
|
||||
"ms_p95": 39.86911000647524,
|
||||
"train_size": 33658
|
||||
}
|
||||
]
|
||||
Binary file not shown.
|
|
@ -1416,3 +1416,101 @@ h +0.0055 lat +0.0044
|
|||
было вовсе, а на **незнакомой** линии — там, где будет приватный тест, —
|
||||
плата нулевая. Кому дороже тишина, чем дальность, ставит `nov_fade_from: 0`
|
||||
и возвращается к 3.3 трека на километр.
|
||||
|
||||
### 12.5. Порог по числу лучей не должен быть одинаковым на всех дальностях
|
||||
|
||||
После того как знакомость перестала гасить далёкую улику, в воронке осталась
|
||||
вторая дыра: за 120 м **0.38 видимых наблюдений не дают кандидата вовсе**.
|
||||
Причина прямая — кандидатом считается компонента из четырёх лучей и больше,
|
||||
а на 150 м у человека их медианно шесть, и кадры с тремя выпадают целиком.
|
||||
|
||||
Разрешить тройки везде — плохая замена. Вблизи обрывок из трёх лучей это
|
||||
кусок чего-то большего, он дробит настоящий предмет и растаскивает
|
||||
сопоставление между треками:
|
||||
|
||||
| человек стоя | 4 луча | 3 луча везде | **4 вблизи, 3 за 90 м** |
|
||||
|---|---:|---:|---:|
|
||||
| P@50 | **0.70** | 0.64 | **0.70** |
|
||||
| P@100 | 0.48 | 0.48 | 0.48 |
|
||||
| P@150 | 0.32 | 0.37 | **0.37** |
|
||||
| человек сидя, рабочая дальность | 80 м | 100 м | **100 м** |
|
||||
| человек сидя, P@100 | 0.44 | 0.53 | **0.53** |
|
||||
| ящик, P@100 | 0.31 | 0.36 | **0.36** |
|
||||
| ложных треков/км, знакомая линия | 5.6 | 6.3 | **5.6** |
|
||||
| то же, незнакомая линия | 20.6 | 21.3 | **20.6** |
|
||||
| кадров с тревогой, знакомая линия | 9.4 % | 11.4 % | 10.7 % |
|
||||
|
||||
Порог, зависящий от дальности, забирает **весь** дальний выигрыш тройки и не
|
||||
стоит ни одного лишнего ложного трека ни на одной из двух сцен. Это третий
|
||||
случай подряд, когда правильным оказалось не «подобрать значение», а
|
||||
**нормировать требование на то, что физически достижимо на этой дальности**
|
||||
— как уже сделано для числа лучей в весе улики и для знакомости.
|
||||
|
||||
Чего порог не забирает: ведро на 50 м (0.02 против 0.20 при тройке везде) и
|
||||
рабочую дальность чемодана (62 м против 80 м). Это ближние эффекты, и
|
||||
приходят они в комплекте с потерей 0.06 у человека стоя на 50 м — размен,
|
||||
который мы не берём.
|
||||
|
||||
Оговорка к цифрам выше: они получены моделью, обученной на выборке с порогом
|
||||
в четыре луча, то есть далёких кандидатов из трёх лучей считывание в
|
||||
обучении не видело ни одного. Это **нижняя** оценка; после пересборки
|
||||
выборки на итоговой конфигурации цифры в п. 11.2 и в README обновлены.
|
||||
|
||||
### 12.6. Пересборка выборки и итоговая конфигурация
|
||||
|
||||
Порог по лучам, зависящий от дальности, меняет **состав кандидатов**, а
|
||||
считывание обучалось на прежнем: далёких компонент из трёх лучей в выборке
|
||||
не было ни одной. Поэтому выборка пересобрана на итоговой конфигурации —
|
||||
214 958 кандидатов против 185 252, предметов 20 150 (9.4 %). Прибавка почти
|
||||
в 30 тысяч — это и есть далёкая мелочь, которую модель раньше не видела.
|
||||
|
||||
Переобучение дало AUC **0.9829** против 0.9860: задача стала труднее, потому
|
||||
что в неё добавили самые бедные наблюдения. По полосам дальности
|
||||
0.995 / 0.996 / 0.992 / 0.968 / 0.950 / 0.758.
|
||||
|
||||
Результат пересборки — **размен, а не чистый выигрыш**:
|
||||
|
||||
| человек стоя | модель на прежней выборке | модель на новой |
|
||||
|---|---:|---:|
|
||||
| рабочая дальность | 80 м | **100 м** |
|
||||
| P@100 | 0.48 | **0.53** |
|
||||
| P@150 | 0.37 | 0.33 |
|
||||
| чемодан, P@100 | 0.13 | **0.21** |
|
||||
| ложных треков/км, знакомая линия | 5.6 | 8.0 |
|
||||
| то же, незнакомая линия | 20.6 | **20.3** |
|
||||
|
||||
Ложные на знакомой линии выросли до 8.0, и это сначала выглядело как
|
||||
поражение: опора без считывания была 7.4. Но опора мерилась при **других**
|
||||
настройках. При итоговых та же оценка без считывания даёт **12.9 трека на
|
||||
км и 25.9 % кадров**, то есть считывание по-прежнему срезает больше трети.
|
||||
Сравнивать надо равное с равным — это стоило одного лишнего прогона и
|
||||
спасло от ложного вывода.
|
||||
|
||||
Разложение цены на знакомой линии (новая модель):
|
||||
|
||||
| конфигурация | ложных/км | кадров |
|
||||
|---|---:|---:|
|
||||
| без считывания вовсе | 12.9 | 25.9 % |
|
||||
| считывание, без гашения знакомости | 3.5 | 5.0 % |
|
||||
| + гашение, 4 луча везде | 6.5 | 8.7 % |
|
||||
| **+ гашение, 3 луча за 90 м (принято)** | **8.0** | **11.0 %** |
|
||||
|
||||
Промежуточный вариант (гашение, четыре луча) измерен полностью: на человеке
|
||||
стоя он неотличим (рабочая дальность 100 м, P@50 0.70, P@100 0.53,
|
||||
P@150 0.32), а на всём, что меньше, — заметно хуже: человек сидя 80 м против
|
||||
100 м рабочей дальности и P@100 0.44 против 0.53, ящик 0.32 против 0.38,
|
||||
чемодан 0.12 против 0.21. На незнакомой линии варианты неразличимы
|
||||
(20.5 и 20.3).
|
||||
|
||||
**Принят дальнобойный.** Упавший или сидящий человек — самый тяжёлый по
|
||||
наблюдаемости и самый важный по безопасности случай, и рабочая дальность по
|
||||
нему растёт с 80 до 100 м. Полтора лишних ложных трека в километре платятся
|
||||
только там, где линия уже знакома; на незнакомой разницы нет.
|
||||
|
||||
Лестница режимов, все три измерены:
|
||||
|
||||
| режим | как включить | ложных/км | дальняя зона |
|
||||
|---|---|---:|---|
|
||||
| тихий | `nov_fade_from: 0` | 3.5 | за 90 м почти нет |
|
||||
| средний | `min_rays_far: 0` | 6.5 | человек стоя есть, мелочь хуже |
|
||||
| **дальнобойный (умолчание)** | — | 8.0 | полная |
|
||||
|
|
|
|||
|
|
@ -228,7 +228,9 @@ def gauge_mask(tf: TrackFrame, corridor: Corridor, *, half_width: float,
|
|||
def find_candidates(tf: TrackFrame, lam: LaminaOutput, corridor: Corridor, *,
|
||||
half_width: float = 1.7, h_lo: float = 0.28, h_hi: float = 2.3,
|
||||
d_min: float = 4.0, d_max: float = 220.0,
|
||||
min_rays: int = 4, max_candidates: int = 64,
|
||||
min_rays: int = 4, min_rays_far: int = 0,
|
||||
min_rays_far_from: float = 90.0,
|
||||
max_candidates: int = 64,
|
||||
dilate_cols: int = 2, max_depth: float = 15.0,
|
||||
ctx_up: float = 4.0, split_adv: float = 0.0,
|
||||
split_gap: float = 0.0, split_near: float = 0.0,
|
||||
|
|
@ -287,7 +289,22 @@ def find_candidates(tf: TrackFrame, lam: LaminaOutput, corridor: Corridor, *,
|
|||
counts[0] = 0
|
||||
inner = np.bincount(flat, weights=inside.ravel().astype(np.float64), minlength=n + 1)
|
||||
inner[0] = 0
|
||||
keep = np.flatnonzero(inner >= min_rays)
|
||||
# Сколько лучей достаточно, чтобы считать компоненту кандидатом. Вблизи
|
||||
# обрывок из трёх лучей — это кусок чего-то большего, и он только
|
||||
# дробит настоящий предмет: замер даёт P@50 у человека 0.70 → 0.64,
|
||||
# если разрешить тройки везде. Вдали три луча — всё, что вообще есть
|
||||
# (медиана на 150 м — шесть), и запрет отсекает предмет целиком.
|
||||
# Поэтому порог зависит от дальности, как и нормировка числа лучей в
|
||||
# `_quality`. 0 — не различать (EXPERIMENTS п. 12.5).
|
||||
need = np.full(inner.shape, float(min_rays))
|
||||
if min_rays_far > 0:
|
||||
din = np.where(inside, np.nan_to_num(tf.d, nan=0.0, posinf=0.0), 0.0)
|
||||
dsum = np.bincount(flat, weights=din.ravel().astype(np.float64),
|
||||
minlength=n + 1)
|
||||
dmean = dsum / np.maximum(inner, 1.0)
|
||||
need = np.where(dmean >= min_rays_far_from, float(min_rays_far),
|
||||
float(min_rays))
|
||||
keep = np.flatnonzero(inner >= need)
|
||||
if keep.size == 0:
|
||||
return []
|
||||
if keep.size > max_candidates:
|
||||
|
|
@ -306,7 +323,7 @@ def find_candidates(tf: TrackFrame, lam: LaminaOutput, corridor: Corridor, *,
|
|||
for lbl in keep:
|
||||
ctx_idx = order[starts[lbl]:starts[lbl + 1]]
|
||||
sel = ctx_idx[inside_flat[ctx_idx]] # признаки считаем по ядру в габарите
|
||||
if sel.size < min_rays:
|
||||
if sel.size < need[lbl]:
|
||||
continue
|
||||
ii, jj = np.divmod(sel, labels.shape[1])
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,16 @@ class Params:
|
|||
leak_far_from: float = 90.0 # с какой дальности она начинает падать
|
||||
d_min: float = 4.0
|
||||
d_max: float = 220.0
|
||||
# Сколько лучей образуют кандидата. Вблизи обрывок из трёх лучей — это
|
||||
# кусок чего-то большего, и он дробит настоящий предмет: разрешить
|
||||
# тройки везде стоит P@50 у человека 0.70 → 0.64. Вдали три луча —
|
||||
# всё, что есть (медиана на 150 м — шесть). Порог по дальности берёт
|
||||
# дальний выигрыш целиком (P@150 0.32 → 0.37, человек сидя 80 → 100 м
|
||||
# рабочей дальности) и не стоит ни одного лишнего ложного трека ни на
|
||||
# знакомой линии, ни на незнакомой. EXPERIMENTS п. 12.5.
|
||||
min_rays: int = 4
|
||||
min_rays_far: int = 3 # 0 — не различать
|
||||
min_rays_far_from: float = 90.0
|
||||
calib_frames: int = 12
|
||||
novelty_gate: float = 0.0 # ниже этой новизны кандидат отбрасывается сразу
|
||||
enable_motion: bool = True
|
||||
|
|
@ -310,7 +319,9 @@ class FlyGuard:
|
|||
split_near=self.p.split_near,
|
||||
split_top=self.p.split_top, advance=advance,
|
||||
d_min=self.p.d_min,
|
||||
d_max=self.p.d_max, min_rays=self.p.min_rays)
|
||||
d_max=self.p.d_max, min_rays=self.p.min_rays,
|
||||
min_rays_far=self.p.min_rays_far,
|
||||
min_rays_far_from=self.p.min_rays_far_from)
|
||||
|
||||
with t("mushroom"):
|
||||
cands = self.memory.annotate(cands)
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ def main() -> None:
|
|||
help="ниже этой новизны трек не считается")
|
||||
ap.add_argument("--min-rays", type=int, default=None,
|
||||
help="сколько лучей минимум образуют кандидата")
|
||||
ap.add_argument("--min-rays-far", type=int, default=None,
|
||||
help="порог по лучам за `--min-rays-far-from`; 0 — не различать")
|
||||
ap.add_argument("--min-rays-far-from", type=float, default=None)
|
||||
ap.add_argument("--leak-far", type=float, default=None,
|
||||
help="утечка улики на дальнем краю; ниже обычной — далёкий трек прощает промахи")
|
||||
ap.add_argument("--leak-far-from", type=float, default=None,
|
||||
|
|
@ -176,6 +179,10 @@ def main() -> None:
|
|||
over["novelty_floor"] = args.novelty_floor
|
||||
if args.min_rays is not None:
|
||||
over["min_rays"] = args.min_rays
|
||||
if args.min_rays_far is not None:
|
||||
over["min_rays_far"] = args.min_rays_far
|
||||
if args.min_rays_far_from is not None:
|
||||
over["min_rays_far_from"] = args.min_rays_far_from
|
||||
if args.leak_far is not None:
|
||||
over["leak_far"] = args.leak_far
|
||||
if args.leak_far_from is not None:
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ def main() -> None:
|
|||
help="ниже этой новизны трек не считается")
|
||||
ap.add_argument("--min-rays", type=int, default=None,
|
||||
help="сколько лучей минимум образуют кандидата")
|
||||
ap.add_argument("--min-rays-far", type=int, default=None,
|
||||
help="порог по лучам за `--min-rays-far-from`; 0 — не различать")
|
||||
ap.add_argument("--min-rays-far-from", type=float, default=None)
|
||||
ap.add_argument("--leak-far", type=float, default=None,
|
||||
help="утечка улики на дальнем краю; ниже обычной — далёкий трек прощает промахи")
|
||||
ap.add_argument("--leak-far-from", type=float, default=None,
|
||||
|
|
@ -185,6 +188,10 @@ def main() -> None:
|
|||
over["novelty_floor"] = args.novelty_floor
|
||||
if args.min_rays is not None:
|
||||
over["min_rays"] = args.min_rays
|
||||
if args.min_rays_far is not None:
|
||||
over["min_rays_far"] = args.min_rays_far
|
||||
if args.min_rays_far_from is not None:
|
||||
over["min_rays_far_from"] = args.min_rays_far_from
|
||||
if args.leak_far is not None:
|
||||
over["leak_far"] = args.leak_far
|
||||
if args.leak_far_from is not None:
|
||||
|
|
|
|||
Loading…
Reference in a new issue