forked from Dan4ick/Lidar_Muxa
2640 lines
202 KiB
Markdown
2640 lines
202 KiB
Markdown
# Эксперименты
|
||
|
||
Все числа в документе получены скриптами из `tools/` на предоставленных данных и
|
||
воспроизводятся командами, указанными в каждом разделе. Там, где результат оказался
|
||
хуже ожидаемого, он приведён как есть.
|
||
|
||
Машина разработки: Ryzen 5 7600X, 32 ГБ, RTX 5070 Ti. Стенд жюри слабее по CPU
|
||
(i7-9700E, 8 ядер, 2.6 ГГц), поэтому замеры задержки приведены с запасом и обсуждаются
|
||
отдельно в разделе 7.
|
||
|
||
---
|
||
|
||
## 1. Что на самом деле в данных
|
||
|
||
```bash
|
||
python tools/inspect_bags.py --root data/for_hackathon
|
||
```
|
||
|
||
| Бэг | Кадров | Топик | Точек в кадре | Валидных лучей |
|
||
|---|---|---|---|---|
|
||
| `doubleT_obstacle` | 201 | `/sensing/lidar/hesai128/pointcloud` | 921 600 | 37.6 % |
|
||
| `doubleT_platform` | 345 | `/lidar_points` | 307 200 | 60.5 % |
|
||
| `roundT_doubleT` | 252 | `/lidar_points` | 307 200 | 61.7 % |
|
||
| `roundT_pressureGate_roundT` | 268 | `/lidar_points` | 307 200 | 60.6 % |
|
||
| `roundT_squareT_pressureGate_squareT` | 545 | `/lidar_points` | 307 200 | 61.6 % |
|
||
| `squareT_platform_squareT_switch` | 877 | `/lidar_points` | 307 200 | 59.4 % |
|
||
| `new_data` (221 шард) | 11 271 | `/lidar_points` | 307 200 | — |
|
||
|
||
Три вещи, о которых README датасета молчит и которые ломают наивную обработку:
|
||
|
||
1. **Архивы — несжатый tar**, а не `.zst`.
|
||
2. **Раскладка скана различается**: 3600 азимутов на 360° против 1200 на 120°.
|
||
Ничего нельзя захардкодить.
|
||
3. **У каналов постоянный азимутальный сдвиг до 15.6°** (±78 столбцов при шаге 0.1°).
|
||
Без выпрямления «столбец» не является направлением, и все пространственные фильтры
|
||
считают мусор. Руководство Pandar128E3X это подтверждает прямо: «Each laser channel
|
||
has an intrinsic azimuth offset».
|
||
|
||
Различаются и условия съёмки: высота установки сенсора над головкой рельса составляет
|
||
1.31–1.33 м в пяти бэгах и 1.70 м в `doubleT_obstacle`. Решение калибруется по данным
|
||
и работает с обоими без правок.
|
||
|
||
---
|
||
|
||
## 2. Проверка калибровки по паспорту
|
||
|
||
```bash
|
||
python tools/extract_channel_table.py --pdf <руководство> --out .../pandar128_channels.csv
|
||
python tools/validate_calibration.py
|
||
```
|
||
|
||
Решётка лучей восстанавливается **из самих облаков точек**, паспортные углы нигде не
|
||
используются. Поэтому таблица каналов из Приложения A руководства служит независимой
|
||
проверкой:
|
||
|
||
| Величина | Измерено по данным | Паспорт | Расхождение |
|
||
|---|---|---|---|
|
||
| Разброс азимутального сдвига каналов | ±7.80° (размах 15.60°) | −7.811°…+7.804° (15.615°) | 0.015° |
|
||
| Диапазон углов места | −25.1°…+14.4° | −25.016°…+14.436° | < 0.1° |
|
||
| Угол места, поканально | — | — | медиана 0.065°, макс 0.123° |
|
||
| Азимутальный сдвиг, поканально | — | — | медиана 0.044°, макс 0.152° |
|
||
|
||
Расхождение одинаково на всех шести бэгах. Остаток объясняется тем, что в руководстве
|
||
приведены **проектные** значения, а каждый экземпляр прибора поставляется с собственным
|
||
файлом угловой коррекции — именно её и восстанавливает калибровка по данным.
|
||
|
||
Остаточная угловая ошибка после выпрямления образа: **p50 = 0.0000°, p99 = 0.028°,
|
||
макс 0.040°** — меньше половины шага решётки (0.05°).
|
||
|
||
---
|
||
|
||
## 3. Сколько тоннель вообще позволяет увидеть
|
||
|
||
```bash
|
||
python tools/analyze_corridor.py --frames 50
|
||
```
|
||
|
||
| Бэг | Радиус кривой | Прямая видимость (p99.9) |
|
||
|---|---|---|
|
||
| `doubleT_obstacle` | 1690 м | 167 м |
|
||
| `doubleT_platform` | 7310 м | 143 м |
|
||
| `roundT_doubleT` | 1310 м | 126 м |
|
||
| `roundT_pressureGate_roundT` | 2970 м | 121 м |
|
||
| `roundT_squareT_pressureGate_squareT` | 8660 м | 127 м |
|
||
| `squareT_platform_squareT_switch` | 2300 м | 132 м |
|
||
|
||
Максимальное эхо во всём датасете — **208.8 м**, что совпадает с паспортными
|
||
0.3…200 м. Но **реальная прямая видимость 121–167 м**: тоннели кривые, и линия взгляда
|
||
упирается в стену раньше, чем кончается дальнобойность прибора.
|
||
|
||
Это измерение, а не оправдание: заявленные в ТЗ «300 м → отлично» на предоставленных
|
||
участках недостижимы **никаким** алгоритмом, потому что от объекта за поворотом
|
||
до лидара не доходит ни одного фотона. Проверяется прямо: при вставке синтетического
|
||
предмета на 84 м в кадр, где фон в том же направлении стоит на 76 м, ни один луч
|
||
до предмета не доходит — он физически закрыт стеной.
|
||
|
||
---
|
||
|
||
## 4. Реальное препятствие
|
||
|
||
Единственный бэг с настоящим посторонним объектом — `doubleT_obstacle`. Поезд стоит,
|
||
объект **0.67 × 1.35 м на 54.7…56.9 м**, 47–69 лучей, медленно смещается поперёк пути
|
||
(с +1.16 м до −1.98 м и обратно за 20 с, примерно 0.2 м/с).
|
||
|
||
```bash
|
||
python tools/check_obstacle.py --memory artifacts/mushroom_body.npz
|
||
```
|
||
|
||
| Метрика | Значение |
|
||
|---|---|
|
||
| Попал в кандидаты | 100 % кадров |
|
||
| Подтверждён треком | 98.9 % кадров |
|
||
| Новизна (ответ MBON) | 0.62 при фоне 0.13 |
|
||
|
||
---
|
||
|
||
## 5. Обобщаемость: leave-one-bag-out
|
||
|
||
```bash
|
||
python tools/evaluate.py --device cuda
|
||
```
|
||
|
||
Память тоннеля обучается на всех данных **кроме проверяемого бэга** — иначе цифры лгут:
|
||
подавлять конструкции, которые сам же и запомнил, умеет кто угодно, а на приватном тесте
|
||
будет новый участок.
|
||
|
||
| Бэг | Путь | Кадров с тревогой | Разных ложных треков | На километр |
|
||
|---|---|---|---|---|
|
||
| `doubleT_platform` | 201 м | 18.8 % | 2 | 10.0 |
|
||
| `roundT_doubleT` | 201 м | 1.3 % | 1 | 5.0 |
|
||
| `roundT_pressureGate_roundT` | 247 м | 0.0 % | 0 | 0.0 |
|
||
| `roundT_squareT_pressureGate_squareT` | 274 м | 13.4 % | 1 | 3.7 |
|
||
| `squareT_platform_squareT_switch` | 271 м | 13.4 % | 2 | 7.4 |
|
||
| **Итого** | **1193 м** | **9.4 %** | **6** | **5.2** (медиана 5.0) |
|
||
|
||
Реальный объект в `doubleT_obstacle` при этом обнаруживается в **98.9 %** кадров.
|
||
|
||
Две метрики отличаются принципиально. «Кадров с тревогой» завышает картину: одна и та же
|
||
конструкция, попавшая в треки, видна сотню кадров подряд. Для эксплуатации важно другое —
|
||
сколько **разных** ложных объектов возникло, потому что именно столько раз поезд
|
||
затормозил бы напрасно.
|
||
|
||
### 5.1. Что дал разбор худшего бэга
|
||
|
||
`roundT_doubleT` давал 39.8 ложных трека на километр — втрое хуже любого другого. Разбор
|
||
(`tools/diagnose_fp.py`) показал, что пять из восьми треков — один и тот же тип объекта:
|
||
полоса шириной 0.6 м, ростом ровно с габарит, тёмная (интенсивность 9 из 255), с разрывом
|
||
дальности до фона 18–25 м, повторяющаяся вдоль тоннеля каждые 13–20 м.
|
||
|
||
Снятие ограничений по высоте показало, что это **колонна, идущая от полотна до свода**:
|
||
кластер тянется от −0.33 м до 4.45 м, и в габарит 0.28…2.30 попадает лишь **15 %** его
|
||
лучей. Признак «наполненность» этого не видел: контекст кластеризации обрывался на
|
||
`h_hi + 1.2 = 3.5` м — ровно в талии между колонной и сводом, — поэтому срез конструкции
|
||
выглядел целым предметом.
|
||
|
||
Контекст поднят до `h_hi + 4.0 = 6.3` м (параметр `ctx_up`). Результат:
|
||
|
||
| | контекст до 3.5 м | контекст до 6.3 м |
|
||
|---|---|---|
|
||
| `roundT_doubleT`, кадров с тревогой | 39.7 % | **1.3 %** |
|
||
| `roundT_doubleT`, ложных треков | 8 | **1** |
|
||
| Всего ложных треков на км | 12.4 | **5.2** |
|
||
| Реальный объект на 55 м | 98.9 % | **98.9 %** |
|
||
|
||
Значения 2.5, 4.0 и 12.0 дают одинаковый результат: контекст просто дотягивается до свода
|
||
и дальше упирается в пустоту. Взято 4.0 — с запасом на более высокий тоннель.
|
||
|
||
---
|
||
|
||
## 6. Абляция: что именно работает
|
||
|
||
```bash
|
||
python tools/ablation.py --device cuda
|
||
```
|
||
|
||
Каждый вариант отличается от полного ровно одним отключённым механизмом; память во всех
|
||
вариантах обучена без проверяемого бэга.
|
||
|
||
| Вариант | Кадров с ложной тревогой | Разных ложных треков | Объект на 55 м |
|
||
|---|---|---|---|
|
||
| полная система | 10.7 % | 6 | 98.9 % |
|
||
| − память тоннеля | 24.1 % | 16 | 98.9 % |
|
||
| − ось пути (прямой коридор) | 4.4 % | 3 | 98.9 % |
|
||
| − признаки формы | 30.5 % | 21 | 98.9 % |
|
||
| − накопление улик | 37.4 % | **81** | 100.0 % |
|
||
|
||
Что из этого следует.
|
||
|
||
**Накопление улик в центральном комплексе — самый весомый механизм.** Без него число
|
||
разных ложных объектов растёт в 13.5 раза (6 → 81): каждое случайное пятно немедленно
|
||
становится «обнаружением». Это прямое подтверждение того, что подтверждение по
|
||
нескольким кадрам должно быть не эвристическим фильтром, а накопителем.
|
||
|
||
**Грибовидное тело снижает ложные тревоги вдвое** (24.1 % → 10.7 %) и при этом **никак
|
||
не влияет на обнаружение реального объекта** (98.9 % в обоих случаях). Именно этого
|
||
от памяти и ждали: она гасит знакомое, не трогая незнакомое.
|
||
|
||
**Признаки формы** (целостность, компактность вдоль пути, опора снизу) дают почти такой
|
||
же вклад, как память, — втрое меньше ложных треков (21 → 6).
|
||
|
||
**Ось пути — единственный механизм, который сейчас стоит дороже, чем даёт.** Её
|
||
отключение снижает ложные тревоги вдвое (10.7 % → 4.4 %, 6 → 3 трека) и не трогает
|
||
обнаружение реального объекта. Так вышло потому, что в двухпутном тоннеле центр свода
|
||
смещён относительно пути: ось оценивается со сдвигом, и кривой габарит заводит в зону
|
||
поиска куски стены. Держим её ради кривых участков, где без неё объект уезжает из
|
||
габарита, — но это осознанная плата, а не выигрыш.
|
||
|
||
Из этого сделан вывод и изменено решение: габарит теперь **объединение** прямого и
|
||
кривого коридоров, а не замена одного другим. Система безопасности не имеет права
|
||
сужать зону поиска по неуверенной оценке. Итог замены на объединение:
|
||
|
||
| | ось заменяет прямой коридор | ось **дополняет** прямой |
|
||
|---|---|---|
|
||
| Реальный объект на 55 м | 75.3 % | **98.9 %** |
|
||
| Кадров с ложной тревогой | 10.6 % | 17.5 % |
|
||
| Разных ложных треков на км | 7.5 | 12.4 |
|
||
|
||
Размен сознательный: +23.6 п.п. обнаружения за +6.9 п.п. ложных тревог. Пропустить
|
||
человека на пути существенно хуже, чем лишний раз затормозить.
|
||
|
||
Таблица выше пересчитана уже на объединённом коридоре, поэтому «полная система»
|
||
показывает 98.9 % обнаружения.
|
||
|
||
---
|
||
|
||
## 7. Скорость
|
||
|
||
```bash
|
||
python tools/run_pipeline.py --all --memory artifacts/mushroom_body.npz --verbose
|
||
```
|
||
|
||
Медиана по стадиям на кадре 128 × 600 (сектор ±30°), машина разработки:
|
||
|
||
| Стадия | мс |
|
||
|---|---|
|
||
| retina (оконная проекция) | 7.0 |
|
||
| ламина | 6.4 |
|
||
| оценка движения (LPTC) | 5.5 |
|
||
| ось пути | 4.7 |
|
||
| лобула (кандидаты) | 4.5 |
|
||
| стабилизация | 3.5 |
|
||
| грибовидное тело | 1.2 |
|
||
| центральный комплекс | 0.2 |
|
||
| решение | 0.02 |
|
||
| **итого** | **p50 ≈ 35, p95 ≈ 45** |
|
||
|
||
Бюджет по ТЗ — 100 мс на кадр. Запас примерно двукратный, что важно: стенд жюри по CPU
|
||
слабее машины разработки. Если запаса не хватит, первыми кандидатами на перенос в
|
||
numba являются ламина и оценка движения — вместе это 12 мс почти чистой арифметики.
|
||
|
||
Отдельно измерена оптимизация ретины: на круговом скане (921 600 точек) оконная
|
||
проекция сократила стадию с **29 до 7 мс**, причём результат совпадает с полной
|
||
проекцией **побитово** — проверено сравнением массивов.
|
||
|
||
## 7.3. Почему далёкий предмет теряется — и что нужно для 200 м
|
||
|
||
ТЗ просит 300 м как «отлично» и 200 м как «очень хорошо». Разберём честно, чего
|
||
не хватает, потому что причина не та, которая кажется.
|
||
|
||
**Фотоны есть.** Вставленный человек на оси пути, по замерам полигона:
|
||
|
||
| Полоса | Лучей на кадр | Есть эхо | Кадров в полосе | Накоплено лучей | Обнаружено сейчас |
|
||
|---|---|---|---|---|---|
|
||
| 160–190 м | 5 | 100 % | 17 | ~84 | **0 %** |
|
||
| 135–160 м | 7 | 100 % | 17 | ~120 | **0 %** |
|
||
| 110–135 м | 10 | 94 % | 18 | ~176 | **0 %** |
|
||
| 90–110 м | 16 | 92 % | 14 | ~227 | 25 % |
|
||
| 70–90 м | 26 | 87 % | 15 | ~400 | 58 % |
|
||
|
||
На 170 м предмет освещён в **каждом** кадре и за проход набирает под сотню попаданий
|
||
в одну и ту же точку мира. Информация есть — мы её выбрасываем, решая покадрово.
|
||
|
||
**Кандидат при этом формируется.** Покадровый разбор (`doubleT_platform`, человек
|
||
от 200 м) показывает кандидата в большинстве кадров на 140–185 м: 4–9 лучей,
|
||
наполненность до 1.00, размер 0.4 × 1.5 м — верный. Но улика трека остаётся 0.00,
|
||
и виноват один множитель: **`gap` = 0.0 во всех кадрах без исключения**.
|
||
|
||
**Почему.** Кольцо окружения ламины берётся ±6 столбцов, то есть ±0.6°. На 170 м этот
|
||
угол отвечает боковому смещению 1.8 м, а стена тоннеля на таком смещении находится
|
||
на 172 м — там же, где предмет. Центр-окружение перестаёт работать, когда собственный
|
||
градиент тоннеля по глубине сравним с шагом от предмета: предмет не «ближе окружения»,
|
||
он «на той же дальности, что окружение». В `_quality` это даёт
|
||
`contrast = clip(0/3, 0.2, 1) = 0.2`, и улика не набирается ни за 17 кадров, ни за сто.
|
||
|
||
Это не настройка порога. Локальный контраст на больших дальностях в тоннеле физически
|
||
не несёт сигнала, и никакая подстройка ламины этого не изменит.
|
||
|
||
### Что сделано: накопление в координатах пути
|
||
|
||
Предмет неподвижен в мире, а тоннель проплывает мимо. Собственное движение мы уже
|
||
оцениваем, поэтому лучи из габарита складываются не в кадре, а в сетке, привязанной
|
||
к пройденному пути (`fan_body.py`, шаг 2 м вдоль пути × 0.2 м поперёк × 0.25 м по
|
||
высоте, забывание с полураспадом 23 кадра). Так устроено веерное тело центрального
|
||
комплекса мухи: оно копит вектор к цели в координатах мира, а не текущего кадра.
|
||
|
||
**Первая версия порождала собственных кандидатов — и это оказалось тупиком.** Ложных
|
||
треков стало 39 на километр вместо 5.2. Разбор показал, почему: по геометрии
|
||
накопленное скопление предмета и накопленный кусок конструкции тоннеля **неразличимы**.
|
||
Медианы (предмет / ложные), 145 против 761 скопления:
|
||
|
||
| признак | дальность | \|u\| | высота | h_min | ширина | протяжённость | опора | кадров |
|
||
|---|---|---|---|---|---|---|---|---|
|
||
| предмет | 88 | 1.16 | 1.28 | 0.28 | 0.80 | 4.0 | 1.31 | 22.1 |
|
||
| ложные | 100 | 1.10 | 1.27 | 0.28 | 0.80 | 4.0 | 1.44 | 18.5 |
|
||
|
||
Совпадает всё. Разделяет их только память тоннеля, а ей нужны признаки кадра —
|
||
контраст, интенсивность, тень, — которых у скопления нет по построению.
|
||
|
||
**Рабочая версия.** Накопитель не порождает кандидатов вовсе. Он отвечает на один
|
||
вопрос про **уже найденного покадрового кандидата** — возвращались ли лучи из этой
|
||
точки мира кадр за кадром — и эта опора подставляется в вес улики вместо недоступного
|
||
контраста (`contrast = max(по gap, по накоплению)`). Кандидат при этом остаётся под
|
||
судом грибовидного тела со всеми своими признаками, и штатные конструкции по-прежнему
|
||
подавляются.
|
||
|
||
Измеренный результат на вставленном человеке (доля кадров с обнаружением):
|
||
|
||
| Бэг | 55–75 м | 75–100 | 100–130 | 130–170 |
|
||
|---|---|---|---|---|
|
||
| `roundT_squareT_pressureGate_squareT` | 1.00 | 0.38 → **1.00** | 0.00 → **1.00** | 0.00 → **0.55** |
|
||
| `squareT_platform_squareT_switch` | 1.00 | 1.00 | 0.33 → **1.00** | 0.00 → 0.14 |
|
||
| `doubleT_platform` | 1.00 | 0.82 → **0.95** | 0.00 → 0.09 | 0.00 |
|
||
| `roundT_doubleT` | 0.00 | 0.00 | 0.00 | 0.00 |
|
||
| `roundT_pressureGate_roundT` | 0.00 | 0.00 | 0.00 | 0.00 |
|
||
|
||
Рабочая дальность там, где кандидат вообще формируется, **выросла вдвое**: с 75–100
|
||
до 130–170 м. Два круглых тоннеля накопление не спасает — там предмет слипается со
|
||
стеной в одну связную компоненту, кандидата нет, и поддерживать нечего (см. п. 9.3).
|
||
|
||
На полном полигоне (90 сценариев, 14 004 наблюдения):
|
||
|
||
| | без накопления | с накоплением |
|
||
|---|---|---|
|
||
| Рабочая дальность, человек стоя | 62 м | **100 м** |
|
||
| P@100 м, человек стоя | 0.24 | **0.57** |
|
||
| P@150 м, человек стоя | 0.00 | **0.10** |
|
||
| P@100 м, человек сидя | 0.19 | **0.33** |
|
||
| Ложных треков на км (leave-one-bag-out) | **5.2** | 9.1 |
|
||
| Кадров с тревогой | **9.4 %** | 17.2 % |
|
||
| Посторонних тревог на кадр (полигон) | **0.055** | 0.171 |
|
||
| Реальный объект на 55 м | 98.9 % | 98.9 % |
|
||
| Задержка, медиана | 32 мс | 33 мс |
|
||
|
||
**Включено по умолчанию.** Размен здесь принципиально лучше, чем у разделения фигуры
|
||
и фона (п. 9.4): там было вчетверо больше ложных за +29 % дальности, здесь — в 1.75
|
||
раза больше за +61 % рабочей дальности и рост обнаружения на 100 м в 2.4 раза. ТЗ
|
||
прямо оценивает дальность (100 м → «хорошо»), а «важно найти баланс между дальностью,
|
||
надёжностью и количеством ложных тревог» — этот баланс мы и выбираем осознанно.
|
||
Выключается одним параметром: `enable_accumulator: false` возвращает 5.2 ложных
|
||
трека на километр при рабочей дальности 62 м.
|
||
|
||
### Что ещё нужно
|
||
|
||
**Геометрическая карта линии.** Метро — неизменная среда: за несколько проездов
|
||
строится ожидаемый дальностный образ, привязанный к положению вдоль линии. Тогда
|
||
«препятствие» = «луч вернулся ближе, чем говорит карта», и это единственный способ
|
||
получить **и** дальность, **и** околонулевые ложные тревоги: всё постоянное в карте,
|
||
всё остальное — предмет. Грибовидное тело делает то же самое в пространстве
|
||
признаков; карта делает это в пространстве геометрии, где на 170 м ещё есть сигнал.
|
||
|
||
### Чего не будет никогда
|
||
|
||
* **На предоставленных участках 200 м недостижимы геометрически**: прямая видимость
|
||
121–167 м, дальше линия взгляда упирается в стену кривой. Это не свойство алгоритма.
|
||
* **Мелкие предметы на 200 м невозможны с этим сенсором**: ведро (0.1 м²) на 160–190 м
|
||
даёт 1 луч при видимости 2 %, каска и бутылка — ноль. Накопление не поможет там,
|
||
где фотонов нет.
|
||
* Реалистичная планка для предмета размером с человека на прямом участке — **около
|
||
200 м**, и путь к ней измерен выше: накопление плюс карта.
|
||
|
||
### 7.4. Стоит ли переносить вывод на GPU
|
||
|
||
На машине проверки RTX 4070 Ti Super, драйвер 580.173.02 (CUDA до 13.0), toolkit 12.9.
|
||
Рассогласование toolkit колёсам PyTorch не мешает — они несут свой CUDA runtime, — так
|
||
что технически перенос возможен. Вопрос в том, что он даёт. Время кадра по стадиям, один
|
||
процесс, свободная машина, 390 кадров трёх записей:
|
||
|
||
| стадия | мс | доля |
|
||
|---|---:|---:|
|
||
| lobula — кластеризация с учётом глубины | 15.3 | 35 % |
|
||
| ego — собственное движение | 6.4 | 15 % |
|
||
| lamina — центр-окружение на трёх масштабах | 6.0 | 14 % |
|
||
| retina — проекция облака на решётку | 5.3 | 12 % |
|
||
| corridor — ось пути | 4.5 | 10 % |
|
||
| stabilize | 4.0 | 9 % |
|
||
| память тоннеля, веерное тело, считывание MBON, треки, решение | 1.9 | 4 % |
|
||
| **всего, медиана / p95** | **43.1 / 49.0** | из 100 |
|
||
|
||
Под видеокарту годится только ламина — это фильтры по сетке 128 × 600. Остальные 80 %
|
||
времени — связные компоненты и подгонка геометрии к сотням точек: нерегулярная работа с
|
||
ветвлениями, которую GPU не ускоряет, а копирование туда-обратно только замедлит.
|
||
|
||
Ламину на CUDA проверили на 49 реальных кадрах (реализация сокомандника,
|
||
`F.avg_pool2d` вместо `uniform_filter`): 5.8 мс на процессоре, **2.9 мс** на RTX 5070 Ti
|
||
с копированием образа на карту и результата обратно. Совпадение не побитовое: ON-канал
|
||
расходится на 1·10⁻⁵, а в 60 пикселях из 3.76 млн (0.0016 %) выбирается другой масштаб —
|
||
при равных откликах решает порядок суммирования.
|
||
|
||
Итог: перенос сэкономил бы около 3 мс из 43 при бюджете 100, а взамен потребовал бы
|
||
другого контейнера (наш собран на `ros:humble-ros-base-jammy`, без CUDA), установленного
|
||
у жюри nvidia-container-toolkit и запуска с `--gpus all`. Отказ любого из трёх звеньев —
|
||
это не медленный кадр, а не запустившееся решение. **Вывод остаётся на CPU**, видеокарта
|
||
— для обучения.
|
||
|
||
---
|
||
|
||
## 8. Что не сработало
|
||
|
||
Раздел намеренно подробный: ТЗ п. 8.7 просит именно этого.
|
||
|
||
**Поиск рельсов по интенсивности.** Идея была привязать ось пути к колее 1520 мм.
|
||
Не вышло: медианная интенсивность на уровне головок рельсов равна 8 из 255, рельсы
|
||
ничем не выделяются на фоне полотна, и пара пиков на расстоянии 1.52 м находится
|
||
где попало — оценки прыгали от −1.18 до +1.37 м в соседних срезах одного кадра.
|
||
Отказались, ось пути оценивается по дрейфу центра свода.
|
||
|
||
**Обычная связность при кластеризации.** Соседние лучи объединялись без учёта глубины,
|
||
и предмет на 55 м слипался со стеной на 150 м в одно пятно размером 5 × 4 м. Реальный
|
||
объект обнаруживался в 16 кадрах из 20. После введения допуска по глубине, растущего
|
||
с расстоянием, — 20 из 20 и правильные габариты 0.67 × 1.35 м.
|
||
|
||
**Грибовидное тело с мушиными параметрами.** 2000 клеток Кеньона и 5 % активных
|
||
насыщаются после нескольких тысяч примеров: подавлено 98 % синапсов, новизна реального
|
||
препятствия падает до 0.001, и оно перестаёт обнаруживаться совсем. Потребовалось
|
||
увеличить популяцию до 50 000 и снизить разрежённость до 0.1 %, а темп депрессии
|
||
согласовать с размером обучающей выборки.
|
||
|
||
**Корреляция продольного профиля «в лоб».** Первая версия оценки скорости залипала
|
||
на нулевом сдвиге: в профиль входила ближняя зона, где на метр пути приходятся тысячи
|
||
лучей, и её вклад подавлял всё остальное. Помогло исключение ближней зоны и вычитание
|
||
скользящего среднего. Отдельно обнаружилась ошибка в перепроекции — использовалась
|
||
высота над рельсом вместо z сенсора, из-за чего согласие держалось на 0.12 вместо 0.9.
|
||
|
||
**Взвешивание срезов по числу точек при оценке оси пути.** У ближних срезов точек
|
||
в сотни раз больше, и подгонка полностью игнорировала дальние, где как раз содержится
|
||
кривизна. Кривые расходились от +10 до −10 м на 200 м в соседних кадрах. Равные веса
|
||
по срезам и линейная (а не квадратичная) экстраполяция за горизонт видимости решили
|
||
проблему.
|
||
|
||
**Оценка оси пути на станции.** Платформа делает сечение резко несимметричным, центр
|
||
свода «уезжает», и габарит заезжает прямо на платформу: радиус кривой падал с 999 до
|
||
225 м за 4.5 с. Это давало 54 % кадров с ложной тревогой на бэге с платформой и
|
||
стрелкой. Помогли два физических ограничения — минимальный радиус 300 м и предел
|
||
скорости изменения оси. Стало 5.4 %.
|
||
|
||
**Срыв оценки скорости на смене типа тоннеля.** На переходе круглого тоннеля
|
||
в двухпутный сопоставление кадров теряло опору и выдавало попеременно 0 и 70 км/ч.
|
||
Помог фильтр с физическим пределом ускорения 3 м/с²: поезд за 0.1 с так не разгоняется.
|
||
|
||
**Полигон без учёта кривизны.** Первая версия синтетических сценариев ставила предмет
|
||
в поперечных координатах сенсора, а не на ось пути. В кривой это уносило его в стену,
|
||
и «рабочая дальность» выходила 32 м вместо реальных 55+. Исправлено привязкой к оси.
|
||
|
||
---
|
||
|
||
## 9. Размеченный полигон: дальность обнаружения
|
||
|
||
```bash
|
||
python tools/make_benchmark.py --memory artifacts/mushroom_body.npz
|
||
python tools/plot_benchmark.py
|
||
```
|
||
|
||
Разметки в датасете нет, а организаторы предупредили, что приватный тест собран
|
||
добавлением синтезированных препятствий. Полигон строится тем же способом: в реальные
|
||
кадры пустого тоннеля трассировкой лучей вставляется предмет, стоящий **на оси пути**
|
||
в фиксированной точке тоннеля, поезд к нему подъезжает, и на каждом кадре известна
|
||
истинная дистанция.
|
||
|
||
Модель сенсора опирается на руководство: поканальная дальность из Приложения A
|
||
(каналы 34–65 берут 200 м, каналы 98–128 смотрят в землю и рассчитаны только на
|
||
ближнее поле), вероятность обнаружения на паспортной дальности PoD = 70 %, шум
|
||
дальности ±2 см, заполнение пятна луча для мелких предметов.
|
||
|
||
Проверка модели: настоящий объект 0.67 × 1.35 м на 55 м даёт 47–69 лучей; синтетический
|
||
человек 0.44 × 1.71 м на 60 м даёт 50 лучей. Совпадает.
|
||
|
||
Результаты приводятся в трёх разрезах, потому что смешивать их нельзя:
|
||
|
||
1. **видимость** — доля кадров, в которых до предмета дошёл хотя бы один луч;
|
||
за поворотом она падает до нуля независимо от алгоритма;
|
||
2. **обнаружение при условии видимости** — собственно качество алгоритма;
|
||
3. **обнаружение как есть** — произведение первых двух, эксплуатационная величина.
|
||
|
||
### 9.1. Результат
|
||
|
||
90 сценариев: 9 предметов × 2 поперечных смещения × 5 бэгов, 14 004 наблюдения
|
||
с известной истинной дистанцией.
|
||
|
||
| Предмет | Площадь | Рабочая дальность | P@50 м | P@100 м | P@150 м | Видимость |
|
||
|---|---|---|---|---|---|---|
|
||
| человек стоя | 0.75 м² | **100 м** | **0.70** | 0.53 | 0.12 | 92.5 % |
|
||
| человек сидя | 0.42 м² | 20 м | 0.62 | 0.34 | 0.00 | 89.3 % |
|
||
| ящик | 0.36 м² | 20 м | 0.49 | 0.00 | 0.00 | 88.1 % |
|
||
| чемодан | 0.25 м² | 62 м | 0.53 | 0.00 | 0.00 | 81.9 % |
|
||
| ведро | 0.10 м² | 8 м | 0.00 | 0.00 | 0.00 | 61.2 % |
|
||
| каска | 0.07 м² | — | 0.00 | 0.00 | 0.00 | 51.1 % |
|
||
| камень | 0.05 м² | — | 0.00 | 0.00 | 0.00 | 45.1 % |
|
||
| бутылка | 0.03 м² | — | 0.00 | 0.00 | 0.00 | 41.2 % |
|
||
| кабель | ~0 м² | — | 0.00 | 0.00 | 0.00 | 25.7 % |
|
||
|
||
Предыдущие замеры для сравнения. Без накопления в координатах пути (п. 7.3):
|
||
человек — рабочая дальность 62 м, P@50 = 0.56, P@100 = 0.24. С накоплением, но без
|
||
разреза по контрасту (п. 9.5): 100 м, P@50 = 0.56, P@100 = 0.57.
|
||
|
||
**«Рабочая дальность» у предметов около порога неустойчива** и её не надо читать как
|
||
физическую величину: метрика идёт от ближнего пояса и обрывается на первом, где доля
|
||
падает ниже 0.5, усредняя при этом два поперечных положения — на оси и со смещением
|
||
0.9 м к краю габарита. У сидящего человека и ящика пояс 25–40 м даёт 0.48 против
|
||
порога 0.50, и число падает с 62 до 20 м, хотя P@50 при этом не ухудшилось.
|
||
Содержательны таблицы по поясам (п. 9.2 и 9.5), а не это одно число.
|
||
|
||
Граница проходит по числу лучей: на 40–55 м человек даёт 68 лучей, чемодан 25, ведро 11,
|
||
каска 6, бутылка 3. Ниже примерно **десяти лучей предмет перестаёт отличаться от шума**
|
||
решётки, и никакая обработка этого не исправит — нужен либо более плотный сенсор, либо
|
||
подъезд ближе.
|
||
|
||
### 9.2. Почему рабочая дальность 62 м, а реальный объект виден на 98.9 %
|
||
|
||
Разброс по бэгам огромный, и он объясняет расхождение:
|
||
|
||
| Бэг (человек стоя, на оси) | 25–40 м | 40–55 м | 55–70 м | 70–90 м | лучей на 50 м |
|
||
|---|---|---|---|---|---|
|
||
| `doubleT_platform` | 1.00 | 1.00 | 1.00 | 1.00 | 68 |
|
||
| `roundT_squareT_pressureGate_squareT` | 1.00 | 1.00 | 1.00 | 0.67 | 68 |
|
||
| `squareT_platform_squareT_switch` | 0.00 | 0.18 | 1.00 | 1.00 | 56 |
|
||
| `roundT_pressureGate_roundT` | 0.73 | 0.00 | 0.00 | 0.00 | 65 |
|
||
| `roundT_doubleT` | 0.27 | 0.00 | 0.00 | 0.00 | 32 |
|
||
|
||
Не «плохо везде понемногу», а **идеально на одних участках и слепо на других**, причём
|
||
при 65 лучах на предмете. То есть дело не в видимости и не в размере.
|
||
|
||
> Таблица снята до накопления в координатах пути (п. 7.3) и до разреза по контрасту
|
||
> (п. 9.5). Актуальные цифры по тем же бэгам — в п. 9.5: `roundT_pressureGate_roundT`
|
||
> из полностью слепого за 40 м стал 0.45 / 1.00 / 0.57 на 40–90 м,
|
||
> `roundT_doubleT` за 40 м слепым остался.
|
||
|
||
### 9.3. Найденная причина слепоты: связная компонента течёт вдоль стены
|
||
|
||
Покадровый разбор провала (`roundT_pressureGate_roundT`, человек на 50 м, 65 лучей
|
||
вставлено) показал: кандидата нет вообще. В кадре всего 5 компонент, и одна из них —
|
||
**42 059 лучей, протянувшиеся по дальности от 4 до 99 м**, наполненность 0.05.
|
||
|
||
Кластеризация с разрывом по глубине объединяет соседние лучи, если их дальности
|
||
отличаются меньше чем на `0.06·R + 0.35` м. Вдоль гладкой стены тоннеля соседние лучи
|
||
отличаются на сантиметры, поэтому стена связна от ближнего поля до горизонта. Предмет,
|
||
стоящий у такой стены, попадает в ту же компоненту и **вместе с ней отбрасывается**
|
||
правилом «ни один предмет не тянется на 15 м вдоль пути».
|
||
|
||
Это не регрессия: мерж одинаков при любой верхней границе контекста, включая исходную.
|
||
|
||
**Попытка первая: разрезать по дальности.** Переглубокая компонента не выбрасывается,
|
||
а пересобирается с более строгим допуском. Предмет при этом действительно выделяется —
|
||
57 лучей, наполненность 1.00. Измеренный размен на `roundT_pressureGate_roundT`
|
||
(человек на 25…90 м) и глобально:
|
||
|
||
| Допуск разреза | Обнаружение | Ложных кадров (бэг) | Ложных треков (бэг) |
|
||
|---|---|---|---|
|
||
| выключен | 28.2 % | 0.0 % | 0 |
|
||
| 0.045 | 28.2 % | 27.2 % | 1 |
|
||
| 0.040 | 61.5 % | 21.5 % | 2 |
|
||
| **0.030** | **94.9 %** | 57.5 % | 5 |
|
||
| 0.015 | 94.9 % | 73.7 % | 16 |
|
||
| 0.006 | 94.9 % | 96.5 % | 47 |
|
||
|
||
Глобально при 0.030: ложных треков **25.8 на км против 5.2**, кадров с тревогой
|
||
**52 % против 9.4 %**. Половина кадров с тревогой — это непрерывное торможение, поэтому
|
||
разрез по умолчанию **выключен**.
|
||
|
||
Проверялось и то, можно ли отделить осколок стены от предмета по признакам: ни один
|
||
не разделяет их. Медианы (предмет / стена): ширина 0.21 / 0.20 м, наполненность
|
||
1.00 / 1.00, лучей 12 / 8, новизна 0.50 / 0.41. Строгий разрез делает стену
|
||
геометрически неотличимой от предметов — потому и цена такая. Этот вариант убран.
|
||
|
||
### 9.4. Разделение фигуры и фона по движению
|
||
|
||
Разрезать по дальности нельзя: предмет и стена рядом с ним стоят на одной дальности.
|
||
Зато они по-разному **приближаются**, и это чистая геометрия. Вдоль фиксированного луча
|
||
стена, параллельная движению, не приближается вовсе: поезд едет, точка пересечения
|
||
скользит по стене, дальность не меняется. Предмет, обращённый к поезду, приближается
|
||
ровно на пройденный путь.
|
||
|
||
Отсюда признак: `advance = (r_прошлый − r_текущий) / ds`. Ноль у фона, единица у фигуры.
|
||
Ничего перепроецировать не нужно — столбец решётки отвечает фиксированному азимуту, а
|
||
рысканье в кривой за кадр (0.06° при радиусе 1300 м) меньше шага решётки. Это тот самый
|
||
канал T4/T5 → LPTC: широкопольный поток задаёт ожидание, а что движется иначе — фигура.
|
||
|
||
Замер на вставленном человеке подтверждает физику: у предмета `advance` = 0.99…1.01,
|
||
у стены на той же дальности — 0.39…0.82 и падает по мере приближения.
|
||
|
||
Разрез по этому признаку (`Params.split_adv`, `lobula.split_by_figure`) не крошит стену:
|
||
из склеенной компоненты в 42 000 лучей остаётся 2.6–5.5 тысяч и **6–15 кандидатов**
|
||
вместо 1259 у разреза по дальности.
|
||
|
||
**Важно для честности замера.** Первая проверка дала 70 % ложных кадров, но она была
|
||
некорректной: память тоннеля обучена на кандидатах **старого** генератора, а разрез
|
||
порождает формы, которых она никогда не видела, — всё выглядит новым. После пересбора
|
||
кэша и переобучения памяти на тех же настройках (`tune_candidates_adv.npz`,
|
||
`new_data_candidates_adv.npz`, 70 273 кандидата) картина такая:
|
||
|
||
**Что это даёт — полигон:**
|
||
|
||
| | без разделения | с разделением |
|
||
|---|---|---|
|
||
| Рабочая дальность, человек стоя | 62 м | **80 м** |
|
||
| P@50 м | 0.56 | **0.71** |
|
||
| P@100 м | 0.24 | **0.36** |
|
||
| Чемодан, P@50 м | 0.57 | 0.67 |
|
||
|
||
По бэгам (человек на оси, доля кадров с обнаружением):
|
||
|
||
| Бэг | 25–40 м | 40–55 | 55–70 | 70–90 | 90–110 |
|
||
|---|---|---|---|---|---|
|
||
| `squareT_platform_squareT_switch` | 0.00 → **1.00** | 0.18 → **1.00** | 1.00 → 1.00 | 1.00 | 1.00 |
|
||
| `roundT_doubleT` | 0.27 → **0.64** | 0.00 | 0.00 | 0.00 | 0.00 |
|
||
| `roundT_pressureGate_roundT` | 0.73 → **0.80** | 0.00 | 0.00 | 0.00 | 0.00 |
|
||
| `roundT_squareT_pressureGate_squareT` | 1.00 | 1.00 | 1.00 | 0.67 → **0.89** | 0.00 |
|
||
| `doubleT_platform` | 1.00 | 1.00 | 1.00 | 1.00 | 0.25 |
|
||
|
||
**Что это стоит:**
|
||
|
||
| | без разделения | с разделением |
|
||
|---|---|---|
|
||
| Кадров с ложной тревогой | 9.4 % | 30.3 % |
|
||
| Разных ложных треков на км | **5.2** | **21.5** |
|
||
| Ложных тревог на полигоне, на кадр | 0.055 | 0.348 |
|
||
| Задержка, медиана | 31 мс | 39 мс |
|
||
| Реальный объект на 55 м | 98.9 % | 98.9 % |
|
||
|
||
**Порогом это не лечится.** Проверены значения 0.6 / 0.8 / 0.9: ложных треков
|
||
21.5 / 21.6 / 21.6 на километр, а доля кадров с тревогой только растёт (30 → 35 → 40 %).
|
||
Причина в том, что ложные срабатывания здесь — **не шум, а настоящие поверхности,
|
||
обращённые к поезду**: рамы гермозатворов, торцы, порталы. По одному признаку движения
|
||
они от предмета неотличимы, потому что физически ведут себя так же.
|
||
|
||
**Решение: разделение по умолчанию выключено** (`split_adv: 0.0`). 21.5 ложного трека
|
||
на километр — это напрасное торможение каждые 47 метров, система в таком виде
|
||
неработоспособна, и +18 м рабочей дальности этого не окупают. Два бэга из пяти
|
||
разделение к тому же не спасает: за 40 м они остаются слепыми.
|
||
|
||
**Чем это лечится по-настоящему.** Не порогом, а памятью: приближающиеся конструкции
|
||
тоннеля постоянны и повторяются от проезда к проезду. Здесь память обучена на пяти
|
||
бэгах и 20 минутах записи; на реальной линии с многими проездами грибовидное тело
|
||
подавило бы их так же, как подавляет всё остальное штатное. Это проверяемое
|
||
предсказание, а не надежда: переобучение памяти уже снизило ложные кадры с 70 % до
|
||
30 % — просто за счёт того, что она увидела эти формы один раз.
|
||
|
||
---
|
||
|
||
## 9.5. Разделение фигуры и фона по контрасту — третья попытка, и она работает
|
||
|
||
Разрез по допуску (п. 9.3) и разрез по движению (п. 9.4) вернули зрение и оба
|
||
оказались слишком дороги. Оставался третий признак фигуры, и он всё это время
|
||
уже вычислялся — просто не участвовал в сегментации.
|
||
|
||
**Физика.** Гладкая стена даёт **нулевой центр-окружение по построению**. Как бы
|
||
сильно дальность ни менялась вдоль стены, она меняется плавно: центр равен своему
|
||
окружению, и контраст равен нулю. Предмет на стене — это ступенька, и она видна.
|
||
Именно этим занята ламина, и её выход `gap` («насколько ближе окружения», м) наш
|
||
конвейер считал с самого начала — но использовал только как **признак кандидата**.
|
||
Сегментация же шла по связности с допуском по глубине, и переглубокая компонента
|
||
отбрасывалась целиком ещё до того, как признак кому-то пригождался.
|
||
|
||
**Замер разделимости** (вставленный человек, два круглых тоннеля, 55 кадров):
|
||
|
||
| Порог `gap` | Лучей предмета | Лучей фона | Компонент фона в кадре |
|
||
|---|---|---|---|
|
||
| 2 м | 90.8 % / 87.5 % | 4.15 % / 3.10 % | 24.6 / 16.3 |
|
||
| 4 м | 89.2 % / 83.4 % | 0.68 % / 0.62 % | 13.7 / 13.5 |
|
||
| **6 м** | **85.7 % / 75.1 %** | **0.13 % / 0.22 %** | **5.0 / 8.9** |
|
||
| 9 м | 80.7 % / 61.7 % | 0.02 % / 0.05 % | 0.7 / 2.6 |
|
||
|
||
Для сравнения: разрез по допуску давал 1259 кандидатов в кадре. Здесь фон
|
||
распадается на 5–9 компонент — на два порядка меньше, и это **настоящие выступы**,
|
||
которые память способна выучить, а не произвольные осколки гладкой стены.
|
||
|
||
Разделение держится до 75 м и разваливается к 90 м: доля лучей предмета, прошедших
|
||
порог 6 м, по полосам — 88 / 96 / 97 / 89 / 61 / 14 / 0 % на 15 / 30 / 45 / 60 / 75 /
|
||
90 / 105 м. Дальше 90 м кольцо окружения снова упирается в стену на той же
|
||
дальности (п. 7.3), и контраста нет.
|
||
|
||
### Почему первая версия всё равно была дорогой
|
||
|
||
Без ограничений разрез дал **9.4 ложных трека на км против 7.4** — и причина
|
||
нашлась замером, а не рассуждением. Медианы кандидатов на `roundT_doubleT`:
|
||
|
||
| признак | без разреза | с разрезом |
|
||
|---|---|---|
|
||
| протяжённость вдоль пути | 2.82 м | **1.00 м** |
|
||
| то же, дальше 55 м | 6.87 м | **0.81 м** |
|
||
| наполненность | 0.47 | 0.57 |
|
||
| контраст `gap` | 1.24 м | 3.29 м |
|
||
|
||
Разрез **отрезает фон**, и вместе с фоном исчезает протяжённость. В весе улики
|
||
(`central_complex._quality`) за неё отвечает множитель компактности
|
||
`clip(1.5 − depth/(3·span))`: при depth 6.9 м и размере 0.65 м он равен 0.1, при
|
||
depth 0.8 м — 1.0. То есть каждое наблюдение вырезанной фигуры стало весить
|
||
**вдесятеро больше**, и подавление протяжённых конструкций, работавшее до разреза,
|
||
выключилось.
|
||
|
||
Порог этого не лечит — проверено сквозным замером:
|
||
|
||
| Порог разреза | Ложных треков на км |
|
||
|---|---|
|
||
| выключен | 7.4 |
|
||
| 6 м | 9.4 |
|
||
| 9 м | 10.4 |
|
||
| 12 м | 9.4 |
|
||
|
||
### Что решило: одна фигура на компоненту
|
||
|
||
Раз каждая лишняя фигура стоит вдесятеро дороже прежнего, их число надо
|
||
ограничить. Из переглубокой компоненты выносится только **сильнейшая** фигура по
|
||
сумме превышения порога. Это тот же приём глобального торможения, которым APL
|
||
оставляет активными считанные проценты клеток Кеньона, а широкопольное торможение —
|
||
считанные колонки LC11.
|
||
|
||
| Фигур на компоненту | Ложных треков на км | Кадров с тревогой |
|
||
|---|---|---|
|
||
| без ограничения | 9.4 | 16.9 % |
|
||
| 2 | 7.4 | 15.5 % |
|
||
| **1** | **6.4** | **15.0 %** |
|
||
|
||
Дальность обнаружения при этом **не меняется вовсе**: и при одной фигуре, и при
|
||
двух, и без ограничения все три конфигурации дают одинаковые доли по полосам.
|
||
Лишние фигуры не добавляли зрения — только ложные тревоги.
|
||
|
||
Разрез вдобавок не применяется ближе 55 м (`split_near`): там покадровый тракт
|
||
видит предмет и без него (100 % на всех пяти бэгах до 70 м), а обстановки,
|
||
дающей контраст, в ближнем поле на порядок больше. Без этого ограничения 75 %
|
||
добавленных ложных треков приходили именно оттуда.
|
||
|
||
### Итог
|
||
|
||
Цифры ниже — leave-one-bag-out с пересобранным кэшем кандидатов и переобученной
|
||
памятью: без этого замер лжёт (п. 9.4).
|
||
|
||
| Конфигурация | Ложных треков на км | Кадров с тревогой | Объект 55 м |
|
||
|---|---|---|---|
|
||
| как было | 9.1 | 17.2 % | 98.9 % |
|
||
| **с разрезом по контрасту** | **7.5** | **16.7 %** | **98.9 %** |
|
||
|
||
То есть разрез не только вернул зрение там, где его не было, но и **снизил** ложные
|
||
тревоги — за счёт того, что переглубокая компонента перестала выбрасываться целиком
|
||
и вместо неё в память попадает одна осмысленная фигура, которую есть чему выучить.
|
||
|
||
Обнаружение вставленного человека на оси, доля кадров. Протокол полигона: предмет
|
||
стоит в точке тоннеля, до которой от начала записи 200 м, поезд подъезжает. Память
|
||
обучена и на этом бэге — как и во всём полигоне.
|
||
|
||
| Бэг | 15–25 м | 25–40 | 40–55 | 55–70 | 70–90 |
|
||
|---|---|---|---|---|---|
|
||
| `roundT_pressureGate_roundT` | 1.00 | 0.73 | 0.00 → **0.45** | 0.00 → **1.00** | 0.00 → **0.57** |
|
||
| `squareT_platform_squareT_switch` | 0.00 | 0.00 | 0.18 → **0.45** | 1.00 | 1.00 |
|
||
| `roundT_doubleT` | 1.00 | 0.27 | 0.00 | 0.00 | 0.00 |
|
||
|
||
**Это ровно тот бэг, который разбирался в п. 9.3.** `roundT_pressureGate_roundT` —
|
||
запись, где компонента из 42 000 лучей течёт вдоль стены от 4 до 99 м и уносит
|
||
предмет с собой. Разрез по контрасту превращает полностью слепую полосу 40–90 м в
|
||
0.45 / 1.00 / 0.57. Разрез по допуску (п. 9.3) и по движению (п. 9.4) добивались
|
||
там же 0.94 и 0.80 ценой 25.8 и 21.5 ложного трека на километр; здесь ложных треков
|
||
стало **меньше**, чем было до разреза.
|
||
|
||
**Второй слепой бэг разрез не спасает**, и причины там две, обе разобраны
|
||
покадрово в п. 9.6: за 50 м до предмета не доходит линия взгляда (98 % лучей
|
||
упираются в преграду ближе него), а на 35–52 м мешает уже наш собственный порог
|
||
`split_near`, снижать который оказалось слишком дорого.
|
||
|
||
**Осторожно с «рабочей дальностью».** Метрика в `plot_benchmark.py` идёт от ближнего
|
||
пояса и останавливается на первом, где доля падает ниже 0.5, а усредняет она два
|
||
поперечных положения сразу — на оси и со смещением 0.9 м к краю габарита. У предметов,
|
||
стоящих около порога, она поэтому скачет: у сидящего человека пояс 25–40 м даёт 0.48
|
||
против 0.50, и «рабочая дальность» падает с 62 до 20 м при том, что P@50 м
|
||
выросло с 0.58 до 0.62. Смотреть надо на таблицу по поясам, а не на одно число.
|
||
|
||
**Где разрез стоит денег — 1: холодный старт.** Всё сказанное верно, когда память
|
||
обучена. На совершенно новой линии, где памяти нет вовсе, разрез, наоборот, дорог:
|
||
21.8 → **31.8** ложных трека на километр. Это логично — он порождает кандидатов из
|
||
настоящих выступов тоннеля, и без памяти отличить их не от чего. Реальный сценарий —
|
||
именно с обученной памятью, она поставляется в образе; но если участок настолько
|
||
новый, что память к нему неприменима, `split_gap: 0.0` возвращает прежнее поведение.
|
||
|
||
**Где разрез стоит денег — 2: стоянка.** На записи со **стоящим** поездом (`doubleT_obstacle`)
|
||
он добавляет один устойчивый трек на 76.6 м, и доля кадров с посторонней тревогой
|
||
растёт с 63 % до 96 %. Это не случайность: при нулевом пройденном пути не работают
|
||
ни накопитель, ни привыкание — оба живут в координатах пути. Число разных ложных
|
||
треков на этом бэге растёт всего с 4 до 5; раздувается именно доля кадров, потому
|
||
что на стоянке ничто не уходит из поля зрения.
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## 9.6. Почему `roundT_doubleT` остаётся слепым за 40 м
|
||
|
||
Разрез по контрасту (п. 9.5) открыл `roundT_pressureGate_roundT`, но второй слепой
|
||
бэг не тронул. Покадровый разбор цепочки «лучи → кандидат → улика → тревога» на
|
||
штатной постановке полигона (предмет в точке, до которой от начала записи 200 м)
|
||
показал **две разные причины в двух разных полосах дальности**. Одна физическая,
|
||
вторая — наша.
|
||
|
||
### Дальше 50 м: смотреть не на что
|
||
|
||
Считаем, сколько лучей доходит до предмета, по этапам:
|
||
|
||
| Дальность до предмета | Геометрически попали | Пережили модель сенсора | Не заслонены |
|
||
|---|---|---|---|
|
||
| 55–105 м | 29 | **29** | **1** |
|
||
| 30–55 м | 109 | 109 | 86 |
|
||
|
||
Модель сенсора не теряет **ни одного** луча: предмет крупный, дальность паспортная.
|
||
Но за 55 м **98 % лучей упираются в эхо, которое ближе предмета**. Дело не в
|
||
отражательной способности и не в числе каналов — до предмета просто не доходит
|
||
линия взгляда.
|
||
|
||
Это не артефакт постановки. Предмет пробовали ставить четырьмя способами — на
|
||
оценённую ось коридора, прямо по оси сенсора, на половину смещения и со сдвигом
|
||
+0.8 м; заслонение одинаково во всех четырёх, и дальность преграды тоже одна и та же:
|
||
|
||
| Дальность до предмета | 104 | 94 | 84 | 74 | 64 | 54 | 45 |
|
||
|---|---|---|---|---|---|---|---|
|
||
| Преграда, м | 90 | 84 | 76 | 66 | 58 | 52 | **49** |
|
||
| Лучей видно (ось коридора) | 3/17 | 1/19 | 0/25 | 0/37 | 1/41 | 8/61 | **63/89** |
|
||
|
||
Преграда приближается вместе с поездом и в какой-то момент **обгоняет** предмет:
|
||
на 45 м обзор доходит до 49 м, предмет оказывается ближе преграды — и 63 луча из 89
|
||
приходят разом. Ровно с этого места и начинается обнаружение.
|
||
|
||
Для сравнения, на том же замере `roundT_pressureGate_roundT` преграда всегда **за**
|
||
предметом (предмет на 104 м — преграда на 113 м; предмет на 56 м — преграда на 70 м),
|
||
поэтому там предмет виден, и разрез по контрасту может ему помочь.
|
||
|
||
**Почему видимость на этом перегоне такая короткая.** По бэгу в целом вдоль оси пути
|
||
видно на 106 м (медиана), но полигон ставит предмет в точку за 200 м от начала
|
||
записи, а это самое начало проезда — как раз худший его участок. Плюс общий для всех
|
||
бэгов эффект: луч, идущий вдоль пути, **скользит по полотну**, и с какой-то дальности
|
||
прирельсовая зона перестаёт наблюдаться вовсе. Нижняя наблюдаемая точка у оси пути
|
||
(5-й процентиль высоты над головкой рельса):
|
||
|
||
| Дальность | 20 м | 40 | 60 | 80 | 90 | 100 |
|
||
|---|---|---|---|---|---|---|
|
||
| `roundT_doubleT` | −0.35 | −0.32 | −0.21 | −0.07 | **+0.36** | **+0.64** |
|
||
| `roundT_pressureGate_roundT` | −0.32 | −0.32 | −0.06 | +0.18 | +0.10 | −0.11 |
|
||
| `doubleT_platform` | −0.35 | −0.36 | −0.34 | −0.06 | +0.08 | +0.35 |
|
||
|
||
За 80–90 м самое низкое, что видно у оси, находится уже **выше рельса** на 0.1–0.6 м.
|
||
Это и есть предел скользящего луча, и он объясняет, почему у мелких лежащих предметов
|
||
видимость в полигоне 25–61 %: их просто нечем осветить.
|
||
|
||
Никакой обработкой это не лечится. Лечится геометрической картой линии (п. 7.3) —
|
||
или вторым сенсором, поднятым выше.
|
||
|
||
### 35–52 м: лучи есть, кандидата нет — и это наш порог
|
||
|
||
Здесь картина обратная: лучей 83…146, а кандидата нет.
|
||
|
||
| Дальность | 41.6 | 38.7 | 35.9 | 33.3 | 30.7 | 28.3 |
|
||
|---|---|---|---|---|---|---|
|
||
| Лучей на предмете | 83 | 101 | 126 | 146 | 168 | 200 |
|
||
| Кандидат при `split_near = 55` | нет | нет | нет | нет | есть | есть |
|
||
| Кандидат при `split_near = 20` | есть | есть | есть | есть | есть | есть |
|
||
|
||
Причина — тот же мерж со стеной, что в п. 9.3, и наш собственный порог: разрез по
|
||
контрасту по умолчанию не применяется ближе 55 м. Порог был введён из соображения
|
||
«ближе покадровый тракт видит предмет и сам» — на этом бэге это неверно.
|
||
|
||
Со сниженным порогом первая тревога наступает на **37.3 м вместо 28.3 м**, а доля
|
||
кадров без кандидата в окне 20–42 м падает с 44 % до 6 %.
|
||
|
||
**Порог всё равно оставлен 55 м.** Цена снижения измерена честно — с пересбором кэша
|
||
кандидатов при `split_near = 20` и переобучением памяти на нём:
|
||
|
||
| `split_near` | Ложных треков на км | Кадров с тревогой |
|
||
|---|---|---|
|
||
| **55 м** | **7.4** | **16.7 %** |
|
||
| 30 м | 14.1 | 25.6 % |
|
||
| 20 м (память не пересобрана) | 18.1 | 28.2 % |
|
||
| 20 м (**честно**, память переобучена) | 17.1 | 27.9 % |
|
||
|
||
Переобучение на этот раз почти ничего не вернуло (18.1 → 17.1): ближние вырезанные
|
||
фигуры от препятствий действительно неотличимы. А выигрыш — девять метров на
|
||
дальности, где он ничего не меняет: ТЗ оценивает 100 / 200 / 300 м, и поезд на
|
||
50 км/ч не останавливается ни за 28, ни за 37 м. Платить за это 2.3-кратным ростом
|
||
ложных тревог нельзя.
|
||
|
||
`split_near: 20.0` в конфиге доступен для линии, где обстановка беднее и ложные
|
||
тревоги дешевле.
|
||
|
||
|
||
---
|
||
|
||
## 10. Новый участок: привыкание внутри проезда — отрицательный результат
|
||
|
||
Обученная память отвечает на вопрос «этот тоннель я уже видел». На новом участке
|
||
она бесполезна по определению, и это измеренная величина, а не абстрактный риск:
|
||
|
||
| | Ложных треков на км | Кадров с тревогой |
|
||
|---|---|---|
|
||
| память обучена на других бэгах (leave-one-bag-out) | 9.1 | 17.2 % |
|
||
| **памяти нет вовсе (новая линия)** | **21.8** | **33.1 %** |
|
||
|
||
Приватный тест — это как раз новый участок, поэтому вопрос важный. Механизм был
|
||
сделан, доведён до работы и **отвергнут по результатам замера**. Ниже — почему,
|
||
потому что отрицательный результат здесь содержательнее положительного.
|
||
|
||
### Замысел
|
||
|
||
**Тоннельная обстановка повторяется вдоль пути, а посторонний предмет — нет.**
|
||
Кабельный кронштейн, рама крепи, стык тюбингов встречаются каждые несколько метров
|
||
в одном и том же виде; разбор худшего бэга (п. 5.1) прямо это показал — пять из
|
||
восьми ложных треков были колоннами, повторяющимися каждые 13–20 м. Упавший же
|
||
предмет лежит в одном месте.
|
||
|
||
Отсюда привыкание, считающее не по времени и не по числу кадров, а по **числу
|
||
разных точек пути**, где встретилась эта форма. Настоящий предмет виден сто кадров
|
||
подряд, но всё это время стоит в одной точке мира: его код депрессируется один раз
|
||
и остаётся новым до конца подъезда. Биологически это familiarity suppression
|
||
MBON-α′3, а момент депрессии разрешает координата пути из центрального комплекса —
|
||
роль, которую у мухи играют дофаминергические PPL1/PAM.
|
||
|
||
### Что пришлось починить, чтобы механизм вообще заработал
|
||
|
||
Обе ошибки найдены замером и сами по себе поучительны.
|
||
|
||
**Полный набор признаков не годится.** Первая версия кодировала кандидата тем же
|
||
дескриптором, что и долговременная память. За двенадцать разных мест новизна упала
|
||
с 1.000 до 0.981 — то есть ни на что. В дескрипторе есть дальность, число лучей,
|
||
угловой размер, интенсивность: код одного и того же кронштейна с 90 и с 40 м
|
||
разъезжается, повторы не узнаются, а подъезжающий предмет каждый кадр выглядит
|
||
новой формой и привыкает сам к себе. Переведено на десять признаков формы, не
|
||
зависящих от дальности.
|
||
|
||
**Нормировка обязана замирать.** Пока среднее и разброс пересчитываются, вместе с
|
||
ними плывёт код. У неподвижного предмета, чьи признаки формы не меняются вовсе,
|
||
набор активных клеток обновлялся настолько, что предмет засчитывался как
|
||
**двадцать шесть разных мест** и гасил сам себя. При пороге заморозки 4000
|
||
кандидатов эффект оставался живуч: в `roundT_doubleT` (около 570 кандидатов за
|
||
проезд) нормировка не замирала никогда, привыкание стояло на 0.20, медианная
|
||
новизна кандидата 1.00, и ложных треков было ровно столько же, сколько без него.
|
||
Порог снижен до 300.
|
||
|
||
### Почему всё равно отвергнуто
|
||
|
||
После починок механизм начал давать цифры: leave-one-bag-out 7.5 → **5.9** ложных
|
||
трека на км, новая линия 31.8 → **26.8**. Выглядело как успех — до проверки
|
||
обратной стороны.
|
||
|
||
**Привыкание глушило сам предмет.** Замер на вставленных предметах
|
||
(`doubleT_platform`, подъезд с 200 м):
|
||
|
||
| | Новизна кандидата, 20–60 м | Обнаружение, 70–90 м |
|
||
|---|---|---|
|
||
| привыкание выключено | 0.75 | 95 % |
|
||
| привыкание включено | **0.24** | **74 %** |
|
||
|
||
И это не «предмет заодно с обстановкой»: медианная новизна обычного кандидата в тех
|
||
же прогонах — 0.43…0.71, то есть **предмет подавлялся сильнее, чем тоннельная
|
||
обстановка**. Избирательность не просто мала, она отрицательна.
|
||
|
||
**Причина — насыщение популяции, а не узнавание повторов.** За проезд набирается
|
||
около 270 событий депрессии по 80 клеток каждое; при популяции 4000 это 5.4 попадания
|
||
на клетку, то есть подавлено всё. Ровно та же ошибка, что описана в п. 8 для
|
||
долговременной памяти с мушиными параметрами, — и проверяется она так же, поднятием
|
||
ёмкости:
|
||
|
||
| Ёмкость короткой памяти | Ложных треков на км (LOO) | Новизна предмета | Обнаружение 70–90 м |
|
||
|---|---|---|---|
|
||
| привыкание выключено | 7.5 | 0.75 | 95 % |
|
||
| 4 000 клеток | **5.9** | **0.24** | **74 %** |
|
||
| 20 000 клеток | 7.5 | 0.47 | 95 % |
|
||
| 50 000 клеток | 7.5 | 0.64 | 95 % |
|
||
|
||
При ёмкости, достаточной чтобы не насыщаться, привыкание не меняет **ничего**: 7.5
|
||
против 7.5 на обученной памяти и 31.8 против 31.8 на новой линии. Весь видимый
|
||
выигрыш был глобальным глушением — а его и так даёт порог решения, который у нас
|
||
уже есть отдельным параметром.
|
||
|
||
Пробовались два способа сделать отсчёт избирательным, оба измерены и оба ничего не
|
||
дали:
|
||
|
||
* **резкий отсчёт MBON**: вместо среднего по активным клеткам — квантиль 0.75 или
|
||
0.90, то есть «знакомо, только если подавлено не меньше трёх четвертей кода».
|
||
Новизну предмета это вернуло (0.75), но и весь эффект тоже (7.5 → 7.5);
|
||
* **огрубление признаков** (coarse coding) до половины и до целого разброса, чтобы
|
||
два похожих кронштейна давали буквально один и тот же код: 7.5 / 7.5 / 7.5.
|
||
|
||
### Что это на самом деле означает
|
||
|
||
Коды двух разных экземпляров одной и той же конструкции не перекрываются настолько,
|
||
чтобы второй узнавал первого, даже после огрубления до целого стандартного
|
||
отклонения. Проще говоря: **в этих данных штатная обстановка повторяется не так
|
||
буквально, как предполагалось**. Разброс между экземплярами одной конструкции —
|
||
по ракурсу, по числу лучей, по тому, какая часть попала в габарит — больше, чем
|
||
разрешение любого разумного кода формы.
|
||
|
||
Поэтому на новом участке работает то, что и работало: долговременная память,
|
||
обученная на других участках, снижает ложные тревоги с 21.8 до 9.1 трека на
|
||
километр. Признаки в дескрипторе намеренно смешаны — форма и угловой размер
|
||
переносятся на любой тоннель, положение в сечении запоминает конкретную обстановку,
|
||
и именно первая половина даёт этот перенос.
|
||
|
||
**Механизм оставлен в коде и выключен** (`enable_habituation: false`) со всеми
|
||
параметрами: ёмкость, квантиль отсчёта, огрубление, шаг «разных мест», путь
|
||
полузабывания. На линии, где обстановка стандартизована сильнее, чем в этих записях,
|
||
он может заработать — но проверять это надо замером на той линии, а не надеждой.
|
||
|
||
---
|
||
|
||
## 11. Обученное считывание MBON: метки из физики
|
||
|
||
До сих пор грибовидное тело училось **без учителя**: оно запоминало частоту
|
||
обстановки и отвечало «знакомо/ново». Вставка предметов трассировкой лучей даёт
|
||
то, чего в датасете нет, — **метку**. `synth.inject` возвращает индексы лучей,
|
||
которые он переписал, поэтому кандидат размечается точным пересечением: предмет,
|
||
если не меньше половины его лучей пришли от вставки (`tools/make_training_set.py`,
|
||
`MIN_OVERLAP = 0.5`). Слои остаются те же — разрежённый код клеток Кеньона,
|
||
торможение APL, один выход MBON, — меняется только учитель: вместо частоты
|
||
обстановки различение «предмет / тоннель» (`flyguard/mbon_readout.py`).
|
||
|
||
### 11.1. Сначала — найденный артефакт: интенсивность выдавала вставку
|
||
|
||
Первое же обучение дало подозрительно красивые цифры: leave-one-bag-out AUC
|
||
**0.9935**, по полосам дальности от 0.998 вблизи до 0.941 на 160–230 м. Разбор
|
||
важности перестановкой поставил наверх не форму, а **интенсивность**:
|
||
|
||
```
|
||
inten +0.0187 ← втрое больше следующего
|
||
elongation +0.0051
|
||
fill +0.0048
|
||
log_rays +0.0030
|
||
```
|
||
|
||
Проверка показала, откуда это взялось. Интенсивность вставки считалась по
|
||
ламбертовой модели ρ·cosθ/r², и произведение `intensity · r²` у вставленных
|
||
предметов держалось в пределах **1.8×** между p10 и p90, тогда как у настоящих
|
||
кандидатов разброс **64×**. Иначе говоря, вставка — и только она — точно
|
||
подчинялась учебниковому закону обратных квадратов, и это распознаётся надёжнее
|
||
любой формы.
|
||
|
||
Хуже того, у нижнего среза шкалы признак переворачивается. Разделяющая
|
||
способность **одной интенсивности** по полосам:
|
||
|
||
| полоса | intensity вставки | intensity обстановки | AUC по одной интенсивности |
|
||
|---|---:|---:|---:|
|
||
| 0–30 м | 37.2 | 5.0 | 0.956 |
|
||
| 30–55 м | 4.6 | 4.5 | 0.513 |
|
||
| 55–80 м | 1.7 | 4.0 | 0.287 |
|
||
| 80–110 м | 1.0 | 3.0 | 0.197 |
|
||
| 110–160 м | 1.0 | 5.0 | **0.049** |
|
||
| 160–230 м | 1.0 | 7.5 | **0.010** |
|
||
|
||
За 110 м расчётная яркость вставки падала ниже единицы, упиралась в срез шкалы —
|
||
и «тускло» становилось почти безошибочным признаком предмета. AUC 0.010 означает
|
||
почти идеальное разделение с обратным знаком.
|
||
|
||
**В реальных данных всё наоборот.** Медиана интенсивности по всему облаку,
|
||
20 кадров на бэг:
|
||
|
||
| бэг | 5–15 м | 15–30 | 30–50 | 50–80 | 80–120 | 120–170 | 170–230 |
|
||
|---|---:|---:|---:|---:|---:|---:|---:|
|
||
| `doubleT_obstacle` | 11 | 9 | 8 | 9 | 6 | 6 | 6 |
|
||
| `roundT_doubleT` | 10 | 8 | 8 | 4 | 2 | 3 | 4 |
|
||
| `squareT_platform_squareT_switch` | 8 | 8 | 7 | 3 | 2 | 2 | 4 |
|
||
|
||
Никакого 1/r². Прибор отдаёт не принятую энергию, а **отражательную способность
|
||
с компенсацией дальности** — то, что в руководстве Hesai называется
|
||
reflectivity. Ламбертова модель описывала физику фотона, а не то число, которое
|
||
лежит в поле `intensity`.
|
||
|
||
Настоящий предмет даёт точку привязки. В `doubleT_obstacle` он стоит на 55.9 м,
|
||
57 лучей, и его медианная интенсивность — **34.5** (p10…p90 = 31…39) при
|
||
медиане окружающих кандидатов 23.5. Старая формула на этой дальности дала бы
|
||
**1.45**, то есть в 24 раза тусклее настоящего.
|
||
|
||
**Первая попытка починки была неправильной.** Раз прибор компенсирует
|
||
дальность, яркость вставки сделали постоянной: `100 · ρ · √cosθ` со шкалой,
|
||
привязанной к настоящему предмету (ρ = 0.35 → около 34). Выборка пересобрана,
|
||
модель переобучена — и артефакт просто перевернулся:
|
||
|
||
| полоса | вставка | обстановка | AUC по одной интенсивности |
|
||
|---|---:|---:|---:|
|
||
| 0–30 м | 35.0 | 5.0 | 0.966 |
|
||
| 30–55 м | 35.2 | 4.5 | 0.960 |
|
||
| 55–80 м | 34.9 | 4.0 | 0.959 |
|
||
| 80–110 м | 35.1 | 3.0 | 0.970 |
|
||
| 110–160 м | 34.8 | 5.0 | 0.832 |
|
||
| 160–230 м | 34.2 | 7.5 | 0.798 |
|
||
|
||
Важность интенсивности при этом выросла втрое (+0.0533 против +0.0187), то есть
|
||
модель стала опираться на подсказку ещё сильнее.
|
||
|
||
**Настоящая причина: абсолютной шкалы в этих данных нет.** Медиана яркости
|
||
кандидатов обстановки по бэгам — 3.0, 4.0, 4.0, 4.0, 7.0, а в
|
||
`doubleT_obstacle`, где лежит настоящий предмет, 23.5 при 34.5 у самого
|
||
предмета. Разброс между записями впятеро больше, чем контраст предмета к фону
|
||
внутри записи. Любое абсолютное число, назначенное вставке, оказывается
|
||
подарком детектору — в ту или в другую сторону.
|
||
|
||
**Как сделано в итоге** (`synth.local_intensity` и `synth.IntensityEnv`):
|
||
вставка берёт яркость **реальных возвратов с тех же самых лучей** — того, что
|
||
предмет заслонил. Лучи, ушедшие в пустоту (за 100 м таких большинство),
|
||
получают образец из реальных возвратов **той же полосы дальности** в этом же
|
||
кадре; разложение кадра по дальности считается один раз и переиспользуется
|
||
всеми 135 сценариями. Ни отражательной способности, ни закона яркости от
|
||
дальности в функции нет; ρ осталась там, где ей место, — в вероятности, что
|
||
эхо вообще вернётся. Проверка вставками в реальные бэги:
|
||
|
||
| дальность | 30 м | 60 м | 100 м | 150 м | 200 м |
|
||
|---|---:|---:|---:|---:|---:|
|
||
| вставка | 8.8 | 6.9 | 4.3 | 4.3 | 10.1 |
|
||
| реальные возвраты рядом | 10.0 | 4.0 | 2.0 | 3.0 | 3.0 |
|
||
|
||
Это **заниженная** оценка: настоящий предмет в записи был в 1.47 раза ярче
|
||
окружения, и этот запас мы себе не засчитываем. На линии с откалиброванной
|
||
яркостью его можно вернуть — замером, а не верой. Тест
|
||
`test_injected_intensity_copies_the_surroundings` держит свойство: среди
|
||
аргументов нет отражения, а без разложения окружения дальность ничего не меняет.
|
||
|
||
Замечание, важное для оценки ущерба: складка, не видевшая `doubleT_obstacle`,
|
||
и с артефактом ставила настоящему предмету **1.000** (p10 = 0.998) против 0.393
|
||
у обстановки — на ближней дальности модель опиралась не только на подсказку.
|
||
Но полигонная дальность, измеренная на таких вставках, мерила бы признак,
|
||
которого в реальности нет. Поэтому все цифры ниже получены на выборке, где
|
||
яркость взята из самой записи.
|
||
|
||
Отдельный урок про метрику: таблицы выше потребовали починить подсчёт AUC.
|
||
Ранги раздавались по порядку в массиве, а не усреднялись на совпадениях, и
|
||
признак, у которого все значения равны, получал 0 или 1 вместо 0.5. Первым
|
||
ложным сигналом такого рода оказался контраст к фону за 160 м — там он
|
||
структурно равен нулю у всех кандидатов сразу. Теперь ранги средние, а сторож
|
||
«что делит выборку само по себе» встроен в `tools/train_mbon.py` и печатается
|
||
перед каждым обучением.
|
||
|
||
### 11.2. Что даёт обученное считывание
|
||
|
||
Обучение — полнопакетный логистический спуск по разрежённому коду: 4 000
|
||
клеток Кеньона, 100 активных после торможения APL, 23 признака кандидата,
|
||
185 252 примера, 19 445 из них предметы. Ёмкость выбрана развёрткой: 8 000 и
|
||
20 000 клеток дают ту же AUC (0.9859 и 0.9850 против 0.9860), а в худшем кадре
|
||
стоят 2.8 и 7.3 мс вместо 1.5 — платить не за что.
|
||
|
||
Позднее развёртку продолжили до 100 000 клеток на полной выборке (214 958
|
||
кандидатов), leave-one-bag-out:
|
||
|
||
| клеток | AUC | обстановки при 95 % предметов |
|
||
|---|---:|---:|
|
||
| **4 000** | **0.9829** | **9.89 %** |
|
||
| 20 000 | 0.9815 | 10.85 % |
|
||
| 50 000 | 0.9794 | 11.73 % |
|
||
| 100 000 | 0.9791 | 11.79 % |
|
||
|
||
Больше клеток — **хуже**, и монотонно. Активных после торможения APL всегда сто,
|
||
поэтому с ростом популяции каждая клетка становится специфичнее, и код превращается
|
||
в справочник по обучающим записям, который на невиданной записи обобщает хуже. Узкое
|
||
место не в ёмкости, а во входе: бустинг по тем же 23 признакам даёт 0.9927, то есть
|
||
запас есть, но лежит в признаках. Для сравнения, у мухи около двух тысяч клеток
|
||
Кеньона на полушарие.
|
||
|
||
Проверка — leave-one-bag-out, отдельная модель на каждую складку. Без этого
|
||
сквозные цифры были бы ложными: считывание увидело бы проверяемый бэг ровно
|
||
так же, как память тоннеля, обученная на всём подряд.
|
||
|
||
| бэг | AUC | обстановки при 95 % предметов | бустинг по сырым признакам |
|
||
|---|---:|---:|---:|
|
||
| `doubleT_platform` | 0.9923 | 3.54 % | 0.9951 |
|
||
| `roundT_doubleT` | 0.9939 | 2.79 % | 0.9987 |
|
||
| `roundT_pressureGate_roundT` | 0.9759 | 16.30 % | 0.9866 |
|
||
| `roundT_squareT_pressureGate_squareT` | 0.9828 | 11.65 % | 0.9930 |
|
||
| `squareT_platform_squareT_switch` | 0.9851 | 5.87 % | 0.9902 |
|
||
| **среднее** | **0.9860** | **8.03 %** | 0.9927 |
|
||
|
||
AUC по полосам дальности: 0.995 / 0.996 / 0.992 / 0.973 / 0.955 / 0.738 —
|
||
качество честно падает с дальностью, как и должно быть, когда от предмета
|
||
остаётся четыре луча.
|
||
|
||
Градиентный бустинг по тем же сырым признакам стабильно лучше на 0.005…0.01
|
||
AUC. Это цена разрежённого кода, и мы её знаем: взамен получаем схему, которая
|
||
считается за 1.5 мс на худшем кадре и держит непрерывное дообучение.
|
||
|
||
Наверху важности — форма, а не положение и не яркость:
|
||
|
||
```
|
||
fill +0.0079 abs_lat +0.0051
|
||
elongation +0.0076 acc_support +0.0046
|
||
h +0.0055 lat +0.0044
|
||
```
|
||
|
||
Положение в сечении (`abs_lat`, `lat`) стоит на 4–6 месте. Формально это
|
||
законный признак — опасны именно предметы в габарите, — но границу
|
||
«положительной» области задали наши же расстановки (0, ±0.6, ±1.2 м от оси),
|
||
и на защите это надо называть вслух, а не прятать.
|
||
|
||
**Сквозной результат, leave-one-bag-out:**
|
||
|
||
| | без считывания | со считыванием |
|
||
|---|---:|---:|
|
||
| разных ложных треков на километр | 7.5 | **3.3** |
|
||
| кадров с ложной тревогой | 16.7 % | **4.4 %** |
|
||
| реальный объект на 55 м | 98.9 % | **98.9 %** |
|
||
| задержка кадра, медиана | 33 мс | 35–41 мс |
|
||
|
||
**На размеченном полигоне** (модель по складкам, то есть для каждого бэга та,
|
||
что его не видела):
|
||
|
||
| человек стоя | без считывания | со считыванием |
|
||
|---|---:|---:|
|
||
| рабочая дальность «как есть» | 62 м | **80 м** |
|
||
| P@50 м | 0.70 | 0.70 |
|
||
| P@100 м | 0.19 | **0.31** |
|
||
| посторонних тревог на кадр | 0.169 | **0.030** |
|
||
|
||
Прибавка не только на человеке: ящик P@100 0.02 → 0.24, человек сидя
|
||
0.25 → 0.39, чемодан P@50 0.55 → 0.60, ведро впервые поднялось над нулём
|
||
(рабочая дальность 8 → 20 м).
|
||
|
||
Отдельно: прежние заявленные 100 м рабочей дальности и P@100 = 0.53 получены
|
||
на полигоне с артефактом яркости и **завышены**. Честные числа без считывания —
|
||
80 м и 0.19, со считыванием — 80 м и 0.31.
|
||
|
||
### 11.3. Сколько дальности покупается за ложные тревоги
|
||
|
||
Смысл обученного считывания не в том, чтобы просто показать меньшее число
|
||
ложных. Оно даёт **запас**, а запас надо тратить: поезд движется, и дальность
|
||
обнаружения — это время на торможение. Поэтому пороги решения вынесены в
|
||
`Params` и развёрнуты замером. Все строки — leave-one-bag-out по ложным и
|
||
полигон по дальности, модель по складкам:
|
||
|
||
| конфигурация | ложных треков/км | кадров с тревогой | реальный объект | P@50 | P@100 | тревог на кадр, полигон |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| без считывания | 7.4 | 16.65 % | 98.9 % | 0.70 | 0.19 | 0.169 |
|
||
| считывание, три подтверждения | 3.3 | 4.44 % | 98.9 % | 0.70 | 0.31 | 0.030 |
|
||
| **по умолчанию: считывание, два подтверждения** | **3.3** | **4.52 %** | **99.5 %** | **0.70** | **0.31** | **0.030** |
|
||
| тише: смешивание 0.7 | 2.5 | 4.85 % | 98.9 % | 0.70 | 0.23 | 0.016 |
|
||
| дальше: порог улики 0.40 | 5.3 | 6.19 % | 98.9 % | 0.71 | 0.34 | 0.039 |
|
||
| смешивание 0.7 + порог 0.32 | 4.3 | 7.28 % | 98.9 % | 0.71 | 0.31 | 0.055 |
|
||
| порог 0.30 | 7.8 | 8.37 % | — | — | — | — |
|
||
| порог 0.22 | 9.8 | 10.04 % | — | — | — | — |
|
||
|
||
Три вывода, каждый замером.
|
||
|
||
**Смешивание с ручной формулой — не способ купить дальность.** Строка
|
||
«смешивание 0.7 + порог 0.32» даёт ровно ту же P@100 = 0.31, что и чистая
|
||
модель при штатном пороге, но стоит 4.3 ложных трека вместо 3.3 и вдвое
|
||
больше посторонних тревог на полигоне. Смешивание полезно в другом качестве —
|
||
как **тихий режим**: при штатном пороге оно опускает ложные до 2.5 на км и
|
||
0.016 на кадр, отдавая дальность (P@100 0.31 → 0.23). Оно же — страховка на
|
||
случай, когда предмет не похож ни на что из каталога: ручная формула
|
||
продолжает работать по геометрии.
|
||
|
||
**Понижение порога работает, но дорого.** 0.50 → 0.40 добавляет 0.03 к P@100
|
||
человека и 0.07 ящику, а платит 3.3 → 5.3 ложных трека. Дальше кривая только
|
||
круче: 0.30 стоит 7.8, 0.22 — 9.8, то есть хуже, чем было без считывания
|
||
вообще. Оставлено пресетом, а не умолчанием.
|
||
|
||
**Два подтверждения вместо трёх оказались бесплатными.** Ложные не изменились
|
||
(3.3 на км, 4.44 → 4.52 % кадров), посторонние тревоги на полигоне не
|
||
изменились тоже (0.030 на кадр), а обнаружение выросло: реальный объект
|
||
98.9 → **99.5 %**, рабочая дальность человека сидя 20 → **80 м**, чемодана
|
||
«как есть» 20 → **62 м**. Причина видна из данных: на 60…100 м предмет
|
||
среднего размера виден через кадр, и третьего подтверждения приходится ждать
|
||
дольше, чем он успевает приблизиться. Это стало умолчанием.
|
||
|
||
Строки пресетов измерены при трёх подтверждениях — прежнем умолчании. В
|
||
базовой точке переход на два ложных не изменил, но для пресетов это отдельно
|
||
не перемерялось.
|
||
|
||
**Чего порогами не изменить.** P@150 равна нулю в любой конфигурации. Это не
|
||
алгоритм: прямая видимость в этих тоннелях 121–167 м, а прирельсовая зона за
|
||
80–90 м не наблюдается вовсе — луч скользит по полотну (п. 3 и п. 7.3).
|
||
Потолок здесь геометрический.
|
||
|
||
---
|
||
|
||
## 12. Воронка потерь: где именно пропадает видимый предмет
|
||
|
||
Кривая дальности говорит, сколько мы теряем, но не где. Поэтому полигон
|
||
инструментирован тремя ступенями: попали ли лучи в предмет (видимость),
|
||
появился ли кандидат, собрался ли трек, прошло ли решение. Считается доля
|
||
от **видимых** наблюдений, то есть от тех, где предмет физически освещён
|
||
лучами и претензии к алгоритму осмысленны.
|
||
|
||
Человек стоя, конфигурация по умолчанию:
|
||
|
||
| полоса | видимых | кандидат | трек | решение | нет кандидата | трек без решения |
|
||
|---|---:|---:|---:|---:|---:|---:|
|
||
| 6–30 м | 396 | 0.82 | 0.89 | 0.82 | 0.18 | 0.06 |
|
||
| 30–50 м | 184 | 0.55 | 0.64 | 0.59 | **0.45** | 0.05 |
|
||
| 50–70 м | 147 | 0.75 | 0.82 | 0.81 | 0.25 | 0.01 |
|
||
| 70–90 м | 131 | 0.73 | 0.68 | 0.62 | 0.27 | 0.06 |
|
||
| 90–120 м | 177 | 0.66 | 0.54 | 0.24 | 0.34 | **0.31** |
|
||
| 120–160 м | 251 | 0.62 | 0.37 | 0.00 | 0.38 | **0.37** |
|
||
|
||
Столбец «трек» бывает выше «кандидата»: трек живёт между кадрами и
|
||
переживает кадр без кандидата — это не ошибка таблицы, а работа накопителя.
|
||
|
||
Видны **две разные** болезни, и лечатся они по-разному.
|
||
|
||
### 12.1. Ближняя: 30–50 м, кандидат не рождается
|
||
|
||
Худшая полоса во всём диапазоне — хуже, чем 120–160 м. Причина известна:
|
||
разрез компоненты по контрасту запрещён ближе 55 м (`split_near`), и предмет,
|
||
слипшийся со стеной, уходит вместе с ней. Запрет стоял потому, что снятие
|
||
стоило слишком дорого по ложным. Обученное считывание удвоило бюджет, и
|
||
проверку повторили:
|
||
|
||
| не резать ближе | ложных треков/км | кадров с тревогой |
|
||
|---|---:|---:|
|
||
| **55 м (принято)** | **3.3** | **4.5 %** |
|
||
| 40 м | 6.3 | 7.5 % |
|
||
| 30 м | 7.3 | 9.6 % |
|
||
| 20 м | 8.3 | 12.1 % |
|
||
| резать везде | 8.3 | 12.7 % |
|
||
|
||
Весь выигрыш считывания съедается уже к 30 м, а на 20 м мы хуже, чем были
|
||
без модели вообще. Запрет оставлен. Дополнительный довод: при служебном
|
||
торможении с 60 км/ч тормозной путь с реакцией около 160 м, то есть полоса
|
||
30–50 м для полной остановки поздняя в любом случае — платить за неё
|
||
ложными тревогами на всей дистанции невыгодно.
|
||
|
||
### 12.2. Дальняя: за 90 м трек есть, решения нет
|
||
|
||
Здесь теряется 0.31 и 0.37 видимых наблюдений уже **после** того, как трек
|
||
собран. Первая догадка — порог. Проверена и отвергнута: порог тревоги,
|
||
линейно опущенный с 0.5 до 0.3 начиная с 90 м, не изменил ни ложных
|
||
(3.3 на км), ни обнаружения (P@100 = 0.31, решения за 120 м по-прежнему 0).
|
||
Порог новизны тоже ни при чём: с нулём вместо 0.10 всё то же самое.
|
||
|
||
Настоящая причина считается на бумаге. Улика растёт на `gain · качество` за
|
||
попадание и падает на `leak` за промах, при `gain` 0.34 и `leak` 0.12. При
|
||
доле попаданий p улика не убывает только если `p·gain·w > (1-p)·leak`. На
|
||
120–160 м кандидат появляется в 62 % наблюдений, откуда порог по качеству
|
||
**w > 0.22** — столько далёкое наблюдение из четырёх лучей не даёт никогда.
|
||
Поэтому улика далёкого трека не «чуть ниже порога», а колеблется около нуля,
|
||
и снижать порог бессмысленно.
|
||
|
||
Промах на 150 м, однако, не свидетельство отсутствия предмета, а свойство
|
||
решётки: там предмет виден через кадр. Ровно по этой причине число лучей уже
|
||
нормируется на ожидаемое для этой дальности (`_quality`), и промах надо
|
||
нормировать так же. Утечка сделана зависящей от дальности (`leak_far`).
|
||
Цена замерена сразу на двух сценах:
|
||
|
||
| утечка на дальнем краю | ложных/км, память обучена | то же на новой линии |
|
||
|---|---:|---:|
|
||
| 0.12 (было) | 3.3 | 20.6 |
|
||
| 0.06 | 3.3 | 21.6 |
|
||
| 0.03 | 3.3 | 21.6 |
|
||
| 0.01 | 3.3 | 21.6 |
|
||
|
||
Цена плоская: появление долгоживущих далёких треков стоит примерно одного
|
||
лишнего трека на километр на незнакомой линии и ничего — на знакомой.
|
||
|
||
### 12.3. Из чего на самом деле состоит улика далёкого предмета
|
||
|
||
Три ручки подряд ничего не дали, и это был знак, что ограничитель другой.
|
||
Поэтому вместо четвёртой развёртки — прямая трассировка: человек стоя
|
||
вставлен на 150 м, 178 кадров, и на каждом выписаны все множители улики.
|
||
|
||
| величина | медиана | p90 |
|
||
|---|---:|---:|
|
||
| лучей в предмете | 6 | 8 |
|
||
| кандидат найден | 164 из 178 кадров | |
|
||
| оценка MBON | **0.078** | 0.493 |
|
||
| новизна | **0.153** | 0.221 |
|
||
| качество улики (произведение) | **0.005** | 0.034 |
|
||
|
||
Новый трек выживает, только если `gain · качество > 0.02`, то есть при
|
||
качестве выше 0.059. Таких наблюдений **5.5 %**. Материал, стало быть, есть
|
||
— кандидат находится в 92 % кадров, — а улику копить нечем. Ни порогом, ни
|
||
утечкой это не лечится, потому что ноль на любой множитель остаётся нулём.
|
||
|
||
Дальше видно, какой из двух множителей виноват. На 120–185 м, 299
|
||
кандидатов, из них 231 — предмет:
|
||
|
||
| множитель | предмет | обстановка | AUC |
|
||
|---|---:|---:|---:|
|
||
| новизна | 0.150 | 0.199 | **0.293** |
|
||
| оценка MBON | 0.051 | 0.003 | 0.743 |
|
||
|
||
**Новизна на дальности перевёрнута.** Настоящий предмет выглядит более
|
||
знакомым, чем окружающая обстановка: на шести лучах дескриптор вырождается,
|
||
и память узнаёт в предмете любую далёкую конструкцию. Мы этим множителем
|
||
гасили собственный сигнал.
|
||
|
||
**Модель различает, но её ответ раздавлен редкостью.** Доля предметов среди
|
||
кандидатов падает с 42 % на 0–30 м до 3.1 % за 160 м, и калиброванная
|
||
апостериорная вероятность обязана падать вместе с ней — даже когда улика
|
||
по существу та же. Различие при этом сохраняется: 0.051 против 0.003, в
|
||
семнадцать раз.
|
||
|
||
Отсюда две правки, обе — нормировка на то, что достижимо на этой дальности,
|
||
ровно как уже сделано для числа лучей в `_quality`:
|
||
|
||
* **гашение знакомости с дальностью** (`nov_fade_from`): за 90 м вклад
|
||
новизны плавно сходит к нулю, вблизи остаётся как был;
|
||
* **поправка на распространённость** (`mbon_prior_from`): из логита модели
|
||
вычитается типичный для этой полосы, то есть апостериорная вероятность
|
||
превращается в отношение правдоподобий.
|
||
|
||
Поправка на распространённость **сама по себе не дала ничего**: ни ложных
|
||
(3.3 на км), ни обнаружения (P@100 = 0.31, за 120 м ноль), воронка не
|
||
сдвинулась ни на сотую. Это ожидаемо и поучительно: пока второй множитель
|
||
обнуляет произведение, правильная правка первого бесполезна.
|
||
|
||
Гашение знакомости дало главный результат дня:
|
||
|
||
| человек стоя | было | стало |
|
||
|---|---:|---:|
|
||
| P@50 | 0.70 | 0.70 |
|
||
| P@100 | 0.31 | **0.48** |
|
||
| P@150 | 0.00 | **0.32** |
|
||
| решений в полосе 120–160 м | 0.00 | **0.40** |
|
||
| ложных треков/км, память обучена | 3.3 | 5.6 |
|
||
| то же на новой линии | 21.6 | **21.6** |
|
||
| посторонних тревог на кадр, полигон | 0.030 | 0.047 |
|
||
|
||
Впервые предмет вообще обнаруживается на 150 м. Цена — полтора лишних
|
||
ложных трека на километр **на знакомой линии**; на незнакомой правка
|
||
бесплатна, потому что отказываться там не от чего. Приватный тест — это
|
||
именно незнакомая линия.
|
||
|
||
Отдельно про характер цены: лишние далёкие треки дают **предупреждения, а не
|
||
торможение**. Экстренный уровень требует `d ≤ max(тормозной путь, 25 м)`
|
||
(`descending.py`), поэтому далёкий ложный трек физически не может вызвать
|
||
экстренное торможение. Мы платим предупреждениями за дальность обнаружения —
|
||
для движущегося поезда это правильная сторона размена.
|
||
|
||
### 12.4. Что из этого принято
|
||
|
||
Обе правки и зависящая от дальности утечка проверены порознь и вместе,
|
||
каждая на двух сценах — линия с обученной памятью и первый проезд по
|
||
незнакомой:
|
||
|
||
| конфигурация | ложных/км, память | то же, новая линия | P@100 | P@150 | тревог на кадр |
|
||
|---|---:|---:|---:|---:|---:|
|
||
| как было | 3.3 | 20.6 | 0.31 | 0.00 | 0.030 |
|
||
| утечка 0.03 | 3.3 | 21.6 | 0.33 | 0.00 | 0.030 |
|
||
| поправка на распространённость | 3.3 | 20.6 | 0.31 | 0.00 | 0.030 |
|
||
| **гашение знакомости с 90 м** | **5.6** | **20.6** | **0.48** | **0.32** | 0.046 |
|
||
| гашение + утечка | 5.6 | 21.6 | 0.48 | 0.32 | 0.047 |
|
||
| гашение + утечка + поправка | 5.6 | 21.6 | 0.48 | 0.32 | 0.048 |
|
||
|
||
**Принято одно гашение знакомости.** Утечка добавляет к нему сотые доли
|
||
(человек сидя P@100 0.44 → 0.48, решений в полосе 120–160 м 0.37 → 0.40) и
|
||
стоит одного лишнего ложного трека на километр ровно на той сцене, которая
|
||
соответствует приватному тесту. Поправка на распространённость не даёт
|
||
ничего ни отдельно, ни в связке — таблицы обнаружения совпадают до сотой.
|
||
|
||
Обе отвергнутые правки **оставлены в коде выключенными** (`leak_far`,
|
||
`mbon_prior_from`, `warn_far`): они верны по сути, измеримы одним ключом и
|
||
могут пригодиться на линии с другой статистикой. Значения по умолчанию
|
||
выключают их полностью.
|
||
|
||
*Позднее:* `warn_far` перемерен на конфигурации после п. 12.3 и 12.5 и
|
||
**принят** — P@150 0.33 → 0.37 без потерь ближе. Почему отказ устарел —
|
||
п. 15.9.
|
||
|
||
Итоговая конфигурация и её цена:
|
||
|
||
| | без гашения | **принято** |
|
||
|---|---:|---:|
|
||
| реальный объект на 55 м | 99.5 % | **99.5 %** |
|
||
| P@50, человек стоя | 0.70 | **0.70** |
|
||
| P@100 | 0.31 | **0.48** |
|
||
| P@150 | 0.00 | **0.32** |
|
||
| ложных треков/км, знакомая линия | 3.3 | 5.6 |
|
||
| кадров с тревогой, знакомая линия | 4.5 % | 9.4 % |
|
||
| ложных треков/км, новая линия | 20.6 | **20.6** |
|
||
| задержка кадра, медиана | 33 мс | **32 мс** |
|
||
|
||
Размен честный и его надо называть вслух: на **знакомой** линии доля кадров
|
||
с тревогой удваивается. Взамен появляется обнаружение на 150 м, которого не
|
||
было вовсе, а на **незнакомой** линии — там, где будет приватный тест, —
|
||
плата нулевая. Кому дороже тишина, чем дальность, ставит `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 | полная |
|
||
|
||
### 12.7. Ближняя потеря: причина найдена, лечение отвергнуто
|
||
|
||
В воронке оставалась последняя необъяснённая дыра: на 6–30 м, где предмет
|
||
даёт сотни лучей, кандидат не появлялся в 18 % видимых наблюдений. Прямая
|
||
трассировка вставки на 6.5, 8, 10, 12, 20 и 28 м ничего не воспроизвела —
|
||
кандидат находился в **100 %** случаев (616 из 616). Ответ нашёлся в самих
|
||
данных полигона:
|
||
|
||
| смещение предмета от оси | наблюдений | без кандидата |
|
||
|---|---:|---:|
|
||
| 0.0 м | 198 | 0.01 |
|
||
| +0.9 м | 198 | **0.34** |
|
||
|
||
Хуже всего там, где есть платформа: `doubleT_platform` и
|
||
`squareT_platform_squareT_switch` по 0.27 против 0.11–0.14 у остальных.
|
||
Предмет у края габарита слипается с кромкой платформы в одну связную
|
||
компоненту и отбрасывается вместе с ней — **то же самое, что и в полосе
|
||
30–50 м**. Причина общая: разрез по контрасту запрещён ближе 55 м.
|
||
|
||
Комментарий в коде объяснял этот запрет тем, что вблизи предмет со стеной
|
||
не слипается. Замер показал, что это неверно, и объяснение исправлено.
|
||
|
||
Лечение пробовалось не «в лоб» (резать везде — уже отвергнуто в п. 12.1), а
|
||
избирательно: резать вблизи только компоненты, **слишком растянутые по
|
||
глубине**, чтобы быть предметом. Мерой служит разброс дальности внутри
|
||
компоненты: человек занимает десятки сантиметров, кромка платформы —
|
||
десятки метров. Две свёртки по меткам, доли миллисекунды.
|
||
|
||
| порог растянутости | ложных/км, знакомая | незнакомая |
|
||
|---|---:|---:|
|
||
| выключено | **8.0** | **20.3** |
|
||
| > 8 м | 10.0 | 25.2 |
|
||
| > 4 м | 12.0 | — |
|
||
| > 2 м | 12.0 | — |
|
||
|
||
Воронка при самом строгом пороге (решений от видимых наблюдений):
|
||
|
||
| полоса | без разреза | с разрезом |
|
||
|---|---:|---:|
|
||
| 6–30 м | 0.83 | **0.90** |
|
||
| 30–50 м | 0.59 | **0.68** |
|
||
| 50–70 м | **0.81** | 0.71 |
|
||
| 70–90 м | **0.62** | 0.54 |
|
||
|
||
**Отвергнуто.** Ближние полосы вылечились, но сломались средние: человек
|
||
стоя P@50 0.70 → 0.67, рабочая дальность «как есть» 80 → 62 м, посторонних
|
||
тревог на кадр 0.071 → 0.102. Механизм тот же, что и у тройки лучей вблизи:
|
||
разрез на 50–55 м дробит предмет на конкурирующие обрывки, и настоящий трек
|
||
проигрывает им в торможении по соседству.
|
||
|
||
Итог размена: мы вылечили бы полосу, которая при служебном торможении с
|
||
60 км/ч и так за тормозным путём, сломав ту, что внутри него, и заплатили бы
|
||
четвертью ложных треков на обеих сценах. Механизм оставлен в коде выключенным
|
||
(`near_long: 0`): на линии без платформ у самого габарита он может оказаться
|
||
дешевле — но проверять это надо замером на той линии.
|
||
|
||
## 13. Ось пути вдаль: устойчивость, горизонт и чего полигон не мерит
|
||
|
||
В общем чате организаторов участники пишут, что не могут устойчиво вести
|
||
путь с габаритом за 100 м — «облако очень разрежённое». Посылка верна:
|
||
рельсы на 100 м дают единицы точек. Мы их и не прослеживаем — ось берётся
|
||
из **дрейфа центра сечения тоннеля** с дальностью, а свод даёт тысячи точек
|
||
там, где рельсы не дают ничего.
|
||
|
||
Замер устойчивости: сдвиг оси в одной и той же точке пути между соседними
|
||
кадрами, 160 кадров на бэг.
|
||
|
||
| бэг | ось видна до | скачок @100 м, медиана/p90 | @150 м | ошибка касательной @150 |
|
||
|---|---:|---:|---:|---:|
|
||
| `doubleT_platform` | 86 м | 0.07 / 0.35 м | 0.10 / 0.67 | 0.16 м |
|
||
| `roundT_doubleT` | 107 м | 0.08 / 0.33 | 0.18 / 0.65 | 0.30 |
|
||
| `roundT_pressureGate_roundT` | 77 м | 0.07 / 0.31 | 0.11 / 0.85 | 0.51 |
|
||
| `roundT_squareT_pressureGate_squareT` | 69 м | 0.01 / 0.04 | 0.02 / 0.09 | 0.33 |
|
||
| `squareT_platform_squareT_switch` | 62 м | 0.04 / 0.28 | 0.05 / 0.57 | 0.91 |
|
||
|
||
На 100 м ось **не перескакивает**: худший p90 — 0.35 м при полуширине
|
||
габарита 1.6 м. Настоящее ограничение другое: сечение наблюдается только до
|
||
62–107 м, дальше ось продолжается касательной, и касательная расходится с
|
||
кривой до 1.19 м на 150 м.
|
||
|
||
### 13.1. Почему не продолжать кривизну
|
||
|
||
Напрашивается продолжать за горизонт не касательную, а измеренную кривизну:
|
||
железнодорожная кривая — длинная дуга постоянного радиуса. Проверено:
|
||
|
||
| бэг | разброс c₂ по кадрам (p10…p90) | расхождение на 150 м |
|
||
|---|---:|---:|
|
||
| `doubleT_obstacle` | 1.4·10⁻⁴ | 0.77 м |
|
||
| `doubleT_platform` | 3.9·10⁻⁴ | 0.70 м |
|
||
| `roundT_doubleT` | 10.3·10⁻⁴ | 1.87 м |
|
||
| `roundT_pressureGate_roundT` | 7.7·10⁻⁴ | **5.03 м** |
|
||
| `roundT_squareT_pressureGate_squareT` | 0.9·10⁻⁴ | 0.58 м |
|
||
| `squareT_platform_squareT_switch` | 8.0·10⁻⁴ | **6.25 м** |
|
||
|
||
Кривизна оценивается на коротком плече (до 62–107 м) и гуляет между кадрами;
|
||
её продолжение до 150 м даёт до 6 м расхождения против 0.16–1.19 м у
|
||
касательной. Нынешнее поведение `Corridor.centre` — линейное продолжение по
|
||
касательной — **лучшее из двух**, и теперь это подтверждено замером, а не
|
||
только рассуждением в комментарии.
|
||
|
||
### 13.2. Чего размеченный полигон не мерит
|
||
|
||
Предмет вставляется в точку `corridor.centre(d) + смещение`, то есть **на
|
||
нашу же оценку оси**. Поэтому полигон по построению не может обнаружить
|
||
ошибку оси: где бы мы ни считали путь, предмет окажется там же.
|
||
|
||
В эксплуатации это не так. Настоящий предмет стоит на настоящем пути, и на
|
||
150 м он может оказаться в 0.16–1.19 м от того места, где мы путь считаем —
|
||
при полуширине габарита 1.6 м этого хватает, чтобы предмет у края габарита
|
||
выпал за него. Дальние цифры полигона поэтому **оптимистичны по этой
|
||
причине**, и честно это назвать: не из-за алгоритма обнаружения, а из-за
|
||
того, что дальше 62–107 м мы путь не наблюдаем, а продолжаем.
|
||
|
||
Закрыть это можно только видимостью: накопление по кадрам не поможет,
|
||
потому что за горизонтом прямой видимости данных нет ни в одном кадре.
|
||
|
||
### 12.8. Где именно знакомость перестаёт работать
|
||
|
||
Граница гашения (90 м) была выбрана по одному замеру на полосе 120–185 м.
|
||
Проверка по всем полосам, два бэга, вставленный человек против окружающих
|
||
кандидатов:
|
||
|
||
| полоса | наблюдений | знакомость, AUC | модель, AUC |
|
||
|---|---:|---:|---:|
|
||
| 35–55 м | 484 | 0.953 | 0.966 |
|
||
| 55–75 м | 1068 | 0.765 | 0.783 |
|
||
| 75–95 м | 1143 | 0.741 | 0.778 |
|
||
| 95–120 м | 793 | **0.621** | 0.758 |
|
||
| 120–150 м | 569 | **0.376** | 0.734 |
|
||
| 150–200 м | 293 | **0.352** | 0.712 |
|
||
|
||
Граница подтвердилась: до 95 м знакомость ещё различает (0.74), дальше
|
||
проваливается и за 120 м переворачивается. Обученное считывание держит
|
||
0.71–0.78 на всей дистанции — оно и остаётся единственным работающим
|
||
признаком в дальней зоне.
|
||
|
||
Отсюда напрашивалось гасить знакомость **быстрее**: сейчас её вклад
|
||
обнуляется только к 160 м, то есть на 120 м остаётся 57 % влияния признака,
|
||
который там уже перевёрнут. Проверено — и отвергнуто:
|
||
|
||
| вклад обнуляется к | ложных/км, знакомая | кадров | незнакомая |
|
||
|---|---:|---:|---:|
|
||
| **160 м (принято)** | **8.0** | **11.0 %** | 20.3 |
|
||
| 140 м | 8.0 | 13.8 % | 20.3 |
|
||
| 120 м | 9.5 | 16.4 % | 20.3 |
|
||
| 105 м | 9.5 | 17.2 % | 20.3 |
|
||
|
||
Урок тут методический. AUC мерит **ранжирование**: 0.376 означает, что среди
|
||
далёких кандидатов предмет получает новизну ниже, чем обстановка. Но в весе
|
||
улики знакомость стоит **множителем**, и, ранжируя неверно, она продолжает
|
||
давить обстановку с высокой новизной. Для решения важно второе, а не первое —
|
||
поэтому выбирать параметр надо по сквозному замеру, а не по AUC признака,
|
||
каким бы убедительным он ни выглядел.
|
||
|
||
На незнакомой линии скорость гашения не влияет вовсе (20.3 во всех четырёх
|
||
случаях): там памяти нет, и гасить нечего.
|
||
|
||
### 12.9. Оговорка о подборе порогов
|
||
|
||
Память тоннеля и считывание MBON проверяются leave-one-bag-out. Для **порогов**
|
||
(с какой дальности гасить знакомость и как быстро, сколько лучей образуют
|
||
кандидата вблизи и вдали, где разрешён разрез по контрасту) отдельной
|
||
отложенной выборки нет: они выбраны на тех же пяти бэгах, на которых и
|
||
измерены. Это надо называть вслух.
|
||
|
||
Что смягчает: каждый порог выбран по **независимому** замеру, а не перебором
|
||
ради лучшей итоговой цифры. Граница гашения — по разделяющей способности
|
||
знакомости по полосам (п. 12.8). Порог по лучам — по тому, сколько лучей
|
||
предмет физически даёт на этой дальности (п. 12.5). Запрет резать вблизи —
|
||
по тому, где предмет слипается с фоном (п. 12.7). Порогов немного, каждый
|
||
имеет физический смысл, и отвергнутых значений больше, чем принятых.
|
||
|
||
Что не смягчает ничего: на приватном тесте будет другая линия, и сами пороги
|
||
там могут оказаться не оптимальными. Поэтому все они вынесены в `Params`
|
||
одним местом и меняются без правки кода.
|
||
|
||
---
|
||
|
||
## 14. Обобщение на форму, которой не было в обучении
|
||
|
||
На приватном тесте почти наверняка окажется предмет, которого нет в нашем
|
||
каталоге из девяти форм. Вопрос, выучило ли считывание **геометрию предмета**
|
||
или шесть конкретных силуэтов, решается замером: обучить без одного класса и
|
||
проверить на нём.
|
||
|
||
Из обучающей выборки выбрасываются только **предметы** этого вида; вся
|
||
обстановка остаётся. Проверка — на предметах этого вида против всей
|
||
обстановки.
|
||
|
||
| предмет | кандидатов | AUC, класс в обучении | AUC, класс исключён | потеря |
|
||
|---|---:|---:|---:|---:|
|
||
| бутылка | 312 | 0.999 | 0.984 | −0.016 |
|
||
| ведро | 2 091 | 0.999 | 0.997 | −0.002 |
|
||
| человек сидя | 4 479 | 0.996 | 0.993 | −0.003 |
|
||
| человек стоя | 5 668 | 0.993 | **0.965** | −0.028 |
|
||
| чемодан | 3 529 | 0.995 | 0.993 | −0.002 |
|
||
| ящик | 4 071 | 0.997 | 0.994 | −0.002 |
|
||
|
||
Потеря не превышает **0.028 AUC**, а обычно составляет 0.002–0.003.
|
||
Считывание опирается не на каталог, а на общую геометрию: предмет целиком
|
||
помещается в габарит, компактен вдоль пути, стоит на полотне и не тянется
|
||
дальше, как лоток или стена.
|
||
|
||
**Что это НЕ доказывает.** Все шесть форм собраны из одних примитивов
|
||
(параллелепипед, цилиндр, шар) и ставятся одинаково — на путь, вертикально,
|
||
без наклона. Это устойчивость внутри семейства компактных предметов на пути,
|
||
а не к чему угодно. Каска, камень и кабель в таблицу не попали: они дают
|
||
меньше двух лучей и кандидатов не образуют вовсе — об этом п. 9.
|
||
|
||
### 14.1. Почему не стали добавлять случайные формы
|
||
|
||
В [SynRailObs](https://arxiv.org/abs/2505.10784) — синтетическом датасете
|
||
препятствий для железной дороги — ровно эта проблема решается иначе: авторы
|
||
генерируют случайные полигоны, заливают их случайными текстурами и учат
|
||
отдельный класс «неизвестное препятствие». Приём напрашивался и у нас:
|
||
добавить в каталог составные тела случайных пропорций.
|
||
|
||
Замер выше показал, что это не нужно: привязки к каталогу нет, и добавлять
|
||
формы было бы решением несуществующей задачи. Полтора часа работы сэкономлены
|
||
одним прогоном на двадцать минут.
|
||
|
||
Попутно та же статья даёт точку сравнения по дальности. Их детекторы
|
||
(YOLOv5-m, nanodet, Faster-RCNN, RT-DETR), обученные с учителем на специально
|
||
сделанном датасете, на **открытом пути** дают mAP 72/69/62/52/44 на полосах
|
||
0–15, 15–30, 30–45, 45–60 и 60–75 м, и собственный вывод авторов — надёжная
|
||
работа в диапазоне **0–45 м**; на 70 м препятствие занимает меньше 20
|
||
пикселей. Сравнивать напрямую нельзя (mAP — не наша вероятность обнаружения в
|
||
кадре, открытый путь — не тоннель), но масштаб заявлений в поле понятен.
|
||
|
||
И отдельно: о ложных тревогах в той работе не сказано **ничего** — ни одного
|
||
упоминания false positive или false alarm. У нас они измерены на двух сценах
|
||
и выражены в треках на километр пути.
|
||
|
||
---
|
||
|
||
## 15. Решение по треку, а не по кадру
|
||
|
||
Воронка потерь (п. 12) разделила потери на две болезни, и вторая до сих пор не
|
||
лечилась: на 90…160 м трек **заводится, а решения не даёт**. В базовой настройке
|
||
это 0.20 и 0.24 от видимых наблюдений — больше, чем теряется на всех остальных
|
||
ступенях вместе.
|
||
|
||
Порогом это не чинится, и это уже измерено (п. 12.4): 0.5 → 0.3 не меняет
|
||
ничего, потому что улика у далёкого трека не «чуть ниже порога», а около нуля.
|
||
Значит, дело не в пороге, а в том, **чем** мы меряем трек.
|
||
|
||
### 15.1. Улика суммируется и упирается в потолок
|
||
|
||
Улика — это `min(1, Σ gain·w)` с утечкой за промахи. Два её свойства работают
|
||
против нас:
|
||
|
||
* она **суммируется**, то есть отвечает на вопрос «давно ли я на это смотрю», а
|
||
не «на что именно я смотрю»;
|
||
* она **обрезана единицей**, и у всего, что наблюдается дольше пары секунд, она
|
||
просто равна 1.
|
||
|
||
Второе проверяется не на синтетике, а на единственной записи с настоящим
|
||
объектом — `doubleT_obstacle`, 753 наблюдения трека, из них 188 приходится на
|
||
реальный объект 0.67 × 1.35 м на 55 м
|
||
(`tools/check_obstacle.py --tracks --memory ... --mbon artifacts/mbon_folds/mbon_roundT_doubleT.npz`):
|
||
|
||
| признак | объект | ложные треки | AUC |
|
||
|---|---|---|---|
|
||
| **улика** | **1.000** | **1.000** | **0.624** |
|
||
| средний вес наблюдения `w_mean` | 0.998 | 0.269 | 1.000 |
|
||
| средний отсчёт считывания `p_mean` | 0.998 | 0.582 | 1.000 |
|
||
| максимум отсчёта `p_max` | 1.000 | 0.782 | 1.000 |
|
||
| лучей к ожидаемым, в среднем | 6.483 | 1.384 | 1.000 |
|
||
| возраст трека, кадров | 94.5 | 52.0 | 0.679 |
|
||
|
||
Улика различает предмет и ложные треки с AUC 0.624 — чуть лучше монетки, — и
|
||
не потому, что сигнала нет, а потому что и у того, и у другого она упёрлась в
|
||
потолок. **Тот самый вес, из которого улика складывается, различает их
|
||
идеально.** Сумма потеряла то, что было в слагаемых.
|
||
|
||
**Где именно это бьёт — оговорка.** `doubleT_obstacle` записан со **стоящего**
|
||
поезда, и там наблюдений у каждого трека сотни, так что потолка достигают и
|
||
предмет, и обстановка. На полигоне поезд едет, ложный трек столько не живёт, и
|
||
картина другая: улика на потолке у 79 % наблюдений предмета в полосе 0…30 м и
|
||
лишь у 1.5 % ложных, на 80…110 м — 31 % против 3.4 %.
|
||
|
||
То есть насыщение само по себе ложных тревог не создаёт. Оно **обесценивает
|
||
улику как меру**: у предмета она перестаёт расти, и весь запас, который он
|
||
заработал качеством наблюдений, пропадает — вместо «этот трек вчетверо
|
||
убедительнее» остаётся «оба по единице». Хуже всего это там, где поезд стоит
|
||
или ползёт, то есть ровно в ситуации, когда препятствие опаснее всего.
|
||
|
||
Отсюда и решение: сравнивать с порогом не улику, а её геометрическую смесь со
|
||
средним весом наблюдения, `w_mean^b · улика^(1−b)`. При `b = 0` всё ровно как
|
||
раньше, так что размен меряется, а не объявляется.
|
||
|
||
Что смешивание делает с отсчётом, посчитано прямо по выборке. При `b = 0.3`
|
||
через порог 0.5 уходят вниз 180 предметных наблюдений и 668 ложных — на одно
|
||
потерянное обнаружение три и семь десятых убранных ложных. При `b = 0.2`
|
||
соотношение лучше: 73 против 348, то есть 4.8. При `b = 0.5` оно рушится до
|
||
1.3, и это то же колено, что видно в таблице п. 15.6.
|
||
|
||
Смешивание при этом **не только давит**: у 21 % наблюдений отсчёт растёт — это
|
||
молодые треки с хорошими наблюдениями, у которых улика ещё мала. Среди ложных
|
||
таких даже больше (25.5 % против 14.6 % среди предметных), и на незнакомой
|
||
линии, где память ничего не подавляет, это выходит боком (п. 15.8).
|
||
|
||
### 15.2. Выборка по трекам
|
||
|
||
Полигон научили попутно выкладывать описание каждого живого трека на каждом
|
||
кадре с меткой «это вставленный предмет» (`make_benchmark.py --tracks-out`).
|
||
Собирается это внутри полигона нарочно: та же память тоннеля, то же покадровое
|
||
считывание, те же пороги — обученное на другой обстановке считывание нечего и
|
||
мерить. Вышло 10 819 наблюдений трека, после отбора «не меньше двух
|
||
подтверждений» (столько же требует `descending.py`) — 9 299, предметных 42.1 %.
|
||
|
||
Что даёт каждый признак сам по себе, по полосам дальности:
|
||
|
||
| полоса | набл. | улика | `p_mean` | `p_max` | `w_mean` | `−s_std` |
|
||
|---|---|---|---|---|---|---|
|
||
| 0–30 м | 2208 | 0.962 | 0.954 | 0.956 | 0.946 | 0.876 |
|
||
| 30–55 м | 1216 | 0.892 | **0.989** | 0.924 | 0.965 | 0.769 |
|
||
| 55–80 м | 1474 | 0.983 | 0.949 | **0.994** | 0.914 | 0.562 |
|
||
| 80–110 м | 1777 | 0.688 | 0.801 | **0.845** | 0.832 | 0.459 |
|
||
| 110–160 м | 1977 | 0.724 | 0.849 | 0.815 | **0.900** | 0.694 |
|
||
| 160–230 м | 647 | 0.306 | 0.218 | 0.218 | 0.218 | **0.869** |
|
||
| всё | 9299 | 0.865 | 0.895 | 0.890 | 0.880 | 0.670 |
|
||
|
||
Главное здесь — полосы 80…160 м, ровно те, где воронка и теряет: улика 0.688 и
|
||
0.724, а история наблюдений 0.832 и 0.900.
|
||
|
||
### 15.3. Почему за 160 м всё переворачивается
|
||
|
||
В последней полосе AUC 0.218…0.306 — то есть признаки указывают **в обратную
|
||
сторону**. Разбор показывает, что дело не в признаках:
|
||
|
||
| | наблюдений | возраст | подтверждений | улика | `p_mean` |
|
||
|---|---|---|---|---|---|
|
||
| настоящий предмет | 114 | 2.0 | 2.0 | 0.094 | 0.104 |
|
||
| ложные треки | 533 | 5.0 | 4.0 | 0.234 | 0.351 |
|
||
|
||
На самом краю дальности предмет — **всегда самое молодое, что есть в сцене**:
|
||
он только что вошёл в зону видимости, а всё, что уже ведётся, ведётся дольше.
|
||
Любая величина, растущая со временем наблюдения, там обязана ранжировать
|
||
наоборот. Это не лечится ни моделью, ни порогом; это граница метода, и её надо
|
||
называть, а не прятать.
|
||
|
||
### 15.4. Привязка к месту: признак, который врёт на синтетике
|
||
|
||
Самым заманчивым признаком казался `s_std` — разброс места в тоннеле, которое
|
||
подразумевает наблюдение (пройденный путь плюс измеренная дальность). У
|
||
неподвижного предмета оно обязано быть постоянным. На полигоне признак и
|
||
выглядел блестяще: за 160 м AUC 0.869 при том, что все остальные там
|
||
перевёрнуты, разброс 0.01 м у предмета против 1.08 м у ложных треков.
|
||
|
||
На настоящем объекте он **перевёрнут**: 0.365 м у предмета против 0.013 м у
|
||
ложных треков, AUC 0.223.
|
||
|
||
Причина в устройстве вставки. Синтетический предмет ставится на дальность
|
||
`d_start − s(t)`, где `s(t)` — тот же самый проинтегрированный путь, который
|
||
двигает мировую координату треков. Ошибка оценки собственного движения входит в
|
||
обе величины и сокращается: у вставленного предмета привязка не дрожит **по
|
||
построению**. У настоящего объекта она дрожит на реальную ошибку одометрии, а
|
||
у ложных треков, прилипших к геометрии тоннеля, — не дрожит вовсе.
|
||
|
||
Это тот же класс ошибки, что артефакт яркости (п. 11.1), и найден он тем же
|
||
способом: цифру с полигона проверили на единственной реальной метке, какая
|
||
есть. Оба разброса (`s_std`, `u_std`) выброшены из обучения; в дескрипторе они
|
||
оставлены нарочно, чтобы проверку можно было повторить.
|
||
|
||
### 15.5. Обученное считывание по треку — отрицательный результат
|
||
|
||
Сделали то же, что с покадровым считыванием: те же клетки Кеньона, то же
|
||
торможение APL, один выход, вход — 21 признак трека
|
||
(`flyguard/track_readout.py`, `tools/train_track.py`). Проверка —
|
||
leave-one-bag-out.
|
||
|
||
| ёмкость | AUC |
|
||
|---|---|
|
||
| 500 клеток | 0.8713 |
|
||
| 1000 | 0.8641 |
|
||
| 2000 | 0.8651 |
|
||
| 4000 | 0.8730 |
|
||
| 4000, без обоих разбросов | 0.8801 |
|
||
|
||
Для сравнения: улика сама по себе даёт 0.865, `w_mean` — 0.880, `p_mean` —
|
||
0.895, произведение `p_mean · улика` — 0.904.
|
||
|
||
**Модель не обгоняет ни один из признаков, которые ей же и скормили.** По
|
||
полосам она выигрывает у улики на 30…55 и 80…110 м и проигрывает на 55…80 и
|
||
за 160 м. Причина понятна и её стоит назвать: наблюдений 9 299, но они не
|
||
независимы — это сотни кадров подряд по одному и тому же треку. Независимых
|
||
треков тут сотни, а не тысячи, и разрежённому коду на такой выборке учиться
|
||
нечему.
|
||
|
||
Вывод: считывание по трекам остаётся в репозитории как инструмент замера, в
|
||
решении **не участвует** (`track_score` по умолчанию `w_mean`, модель
|
||
включается только явным `--track-score model`). Цена вопроса — один вечер и
|
||
28 секунд обучения; сэкономлено то, что модель не поехала в продукт.
|
||
|
||
### 15.6. Размен: что стоит смешивание
|
||
|
||
Полигон, `человек_стоя`, память тоннеля на месте, считывание по складкам.
|
||
«Посторонних» — тревоги, не совпавшие с вставленным предметом, на 14 004
|
||
наблюдения.
|
||
|
||
| настройка | P@50 | P@100 | P@150 | посторонних |
|
||
|---|---|---|---|---|
|
||
| улика одна (`track_blend = 0`) | 0.70 | 0.53 | 0.33 | 999 |
|
||
| смешивание 0.3 | 0.71 | 0.43 | 0.26 | **354** |
|
||
| смешивание 0.5 | 0.63 | 0.17 | 0.10 | 305 |
|
||
| смешивание 0.7 | 0.63 | 0.17 | 0.10 | 296 |
|
||
|
||
Колено видно сразу: за 0.3 ложные почти не падают (354 → 305 → 296), а
|
||
обнаружение рушится. Причина арифметическая, и её стоит назвать: обе величины
|
||
меньше единицы, поэтому их произведение **всегда ниже улики**, и при
|
||
неподвижном пороге смешивание работает как его повышение. Сравнивать настройки
|
||
имеет смысл только на выровненной рабочей точке — то есть опустив порог на
|
||
столько, на сколько смешивание опустило отсчёт.
|
||
|
||
Полезная доля смешивания, таким образом, мала, и весь вопрос в том, на что
|
||
потратить освободившиеся три четверти ложных тревог.
|
||
|
||
### 15.7. Жёсткий порог вместо смешивания — отрицательный результат
|
||
|
||
Смешивание `w_mean^b · улика^(1−b)` опускает отсчёт **и предмету тоже**, потому
|
||
что обе величины меньше единицы. Напрашивается вместо него порог: трек с
|
||
низким средним весом просто не рассматривать, а прошедшему не отнимать ничего.
|
||
Тем более что на реальном объекте разделение выглядит идеальным — 0.998 против
|
||
0.269.
|
||
|
||
Замер (полигон, `человек_стоя`):
|
||
|
||
| настройка | P@50 | P@100 | P@150 | посторонних |
|
||
|---|---|---|---|---|
|
||
| база | 0.70 | 0.53 | 0.33 | 999 |
|
||
| порог 0.40 | 0.60 | **0.03** | 0.10 | 158 |
|
||
| порог 0.55 | 0.37 | **0.00** | 0.01 | 85 |
|
||
|
||
Ложные тревоги падают в шесть и в двенадцать раз, а обнаружение исчезает.
|
||
Воронка показывает где: в полосе 90…120 м до решения доходит **0.02** от
|
||
видимых наблюдений против 0.49 в базе.
|
||
|
||
Причина — та же, что у всех остальных порогов в этой работе: **неподвижный
|
||
порог слеп к дальности**. Средний вес наблюдения сам падает с расстоянием, и
|
||
сильно: в него входит контраст к фону, который за 140 м структурно равен нулю
|
||
(п. 7.3), и вероятность считывания, которая падает вместе с числом лучей.
|
||
Порог 0.55 отсекает не «плохие треки», а «всё, что дальше семидесяти метров».
|
||
|
||
Чтобы порог тут работал, его пришлось бы нормировать на ожидаемый для этой
|
||
дальности вес — то есть сделать ровно то, что уже сделано для числа лучей
|
||
(п. 12.5), для новизны (п. 12.3) и для утечки улики. Это возможно, но
|
||
смешивание даёт тот же эффект мягко и без ещё одной калибровочной кривой,
|
||
поэтому порог оставлен выключенным (`track_gate = 0`).
|
||
|
||
### 15.8. Смешивание меняет не в ту сторону
|
||
|
||
Ложные треки на километр, обе сцены, считывание по складкам:
|
||
|
||
| настройка | знакомая линия | незнакомая линия | объект на 55 м |
|
||
|---|---:|---:|---:|
|
||
| база | 8.0 | **20.3** | 99.5 % |
|
||
| один дальний порог 0.30 | **8.0** | 21.3 | 99.5 % |
|
||
| смешивание 0.3 + дальний порог с 60 м | 6.3 | **24.8** | 99.5 % |
|
||
| смешивание 0.2 + дальний порог с 60 м | 8.0 | 24.0 | 99.5 % |
|
||
|
||
Смешивание улучшает знакомую линию и **портит незнакомую**. Это не случайность,
|
||
и механизм виден из разбора п. 15.1: смешивание не только давит — у 21 %
|
||
наблюдений оно отсчёт поднимает, и среди ложных таких больше, чем среди
|
||
предметных (25.5 % против 14.6 %). Поднимаются молодые треки с хорошими
|
||
наблюдениями и малой уликой. На знакомой линии память тоннеля такие треки уже
|
||
погасила через новизну, а на незнакомой гасить нечем — и смешивание их
|
||
вытаскивает.
|
||
|
||
Приватный тест — это незнакомая линия. Значит, смешивание отвергается:
|
||
`track_blend = 0` по умолчанию.
|
||
|
||
И это ещё не всё. Смешивание надо сравнивать не с базой, а с **тем же разменом,
|
||
полученным простым поднятием порога** — иначе мы сравниваем две разные рабочие
|
||
точки и выдаём сдвиг по кривой за улучшение кривой. Контроль:
|
||
|
||
| настройка | P@50 | P@100 | P@150 | посторонних |
|
||
|---|---|---|---|---:|
|
||
| смешивание 0.2 + дальний порог с 60 м | 0.71 | 0.47 | 0.37 | 817 |
|
||
| **порог 0.60 + дальний порог, без смешивания** | 0.70 | **0.49** | 0.37 | **816** |
|
||
|
||
Одинаковое число посторонних тревог, и при нём простой порог даёт обнаружение
|
||
на 100 м **лучше**. Никакой новой кривой смешивание не открывает — оно двигает
|
||
по той же, что и ключ, который у нас уже был.
|
||
|
||
Итог: `track_blend = 0`, `track_gate = 0`. Накопители на треке и считывание по
|
||
трекам остаются в коде как инструмент замера — они и дали понимание, что улика
|
||
насыщается, — но в решении не участвуют.
|
||
|
||
### 15.9. Отрицательные результаты стареют
|
||
|
||
Выигрыш в этой главе дал не считывание по трекам, ради которого всё
|
||
затевалось, а ключ, **отвергнутый тремя разделами раньше**. П. 12.2 говорит
|
||
дословно: «порог тревоги, линейно опущенный с 0.5 до 0.3 начиная с 90 м, не
|
||
изменил ни ложных (3.3 на км), ни обнаружения (P@100 = 0.31, решения за 120 м
|
||
по-прежнему 0)».
|
||
|
||
Тогда это было правдой, и причина там же, в п. 12.3: улика далёкого трека
|
||
колебалась около нуля, потому что знакомость была на той дальности перевёрнута
|
||
и входила в вес **множителем**. Опускать порог под сигналом, который равен
|
||
нулю, бессмысленно — что замер и показал.
|
||
|
||
Потом приняли гашение знакомости с 90 м (п. 12.3) и порог по лучам,
|
||
нормированный на дальность (п. 12.5). Улика далёкого трека перестала быть
|
||
нулём. Тот же самый ключ, тот же самый замер — и P@150 растёт с 0.33 до 0.37
|
||
ценой нуля ложных треков на знакомой линии и одного на незнакомой.
|
||
|
||
Отсюда правило, которое стоит дороже самой правки: **отрицательный результат
|
||
верен только для той конфигурации, в которой он получен.** Каждое принятое
|
||
изменение обесценивает часть прежних отказов, и дешёвые из них надо
|
||
перепроверять. Здесь это стоило одного прогона на четыре минуты.
|
||
|
||
Варианты той же правки, полигон, `человек_стоя`:
|
||
|
||
| настройка | P@50 | P@100 | P@150 | посторонних |
|
||
|---|---|---|---|---:|
|
||
| база | 0.70 | 0.53 | 0.33 | 999 |
|
||
| **порог 0.30 с 90 м** | 0.70 | 0.53 | **0.37** | **1091** |
|
||
| порог 0.30 с 60 м | 0.70 | 0.53 | 0.37 | 1197 |
|
||
| порог 0.20 с 90 м | 0.70 | 0.53 | 0.38 | 1181 |
|
||
| порог 0.30 + утечка 0.03 | 0.70 | **0.48** | 0.40 | 1172 |
|
||
|
||
Начинать раньше незачем: обнаружение то же, ложных больше. Опускать до 0.20 —
|
||
сотая на 150 м за восемь процентов посторонних тревог. Утечку, отвергнутую
|
||
в п. 12.4, на нынешней базе тоже перемерили: теперь она даёт P@150 0.40, но
|
||
забирает 100 м (0.53 → 0.48) и рабочую дальность 100 → 80 м — отвергнута
|
||
снова, уже по другой причине. Правило «перепроверять» не означает
|
||
«перепринимать».
|
||
|
||
Принято: `warn_far = 0.30`, `warn_far_from = 90`. Итог на всех сценах:
|
||
|
||
| | было | **стало** |
|
||
|---|---:|---:|
|
||
| P@150, человек стоя | 0.33 | **0.37** |
|
||
| P@150, человек сидя | 0.22 | **0.24** |
|
||
| P@100 / P@150, ящик | 0.38 / 0.03 | **0.40 / 0.06** |
|
||
| P@50 и P@100, человек | 0.70 / 0.53 | 0.70 / 0.53 |
|
||
| решений в полосе 90–120 м / 120–160 м | 0.49 / 0.38 | **0.51 / 0.41** |
|
||
| ложных треков на км, знакомая линия | 8.0 | **8.0** |
|
||
| то же, незнакомая линия | 20.3 | 21.3 |
|
||
| настоящий объект на 55 м | 99.5 % | **99.5 %** |
|
||
|
||
Экстренного торможения правка не касается: у него свой порог и условие
|
||
`d ≤ max(тормозной путь, 25 м)`, поэтому далёкий ложный трек физически не
|
||
может его вызвать. Мы снова платим предупреждениями за дальность — для
|
||
движущегося поезда это правильная сторона размена (как и в п. 12.3).
|
||
|
||
## 16. Работа Zhirik1337: что перенесено и что из этого принято
|
||
|
||
22.09 в командный репозиторий пришло семь коммитов Zhirik1337: вывод на
|
||
видеокарте, пять правок габарита и решения, «человек лёжа» в каталоге,
|
||
случайные сценарии полигона, выгрузка рамок, ROS-узел и Docker. Всё перенесено
|
||
в основной проект, код детектора — за флагами, выключенными по умолчанию.
|
||
Потом каждая правка мерилась отдельно и на тех же вставках. Флаги
|
||
выставляются из командной строки без правки кода:
|
||
`--set k_sigma=0.75` у `make_benchmark.py`, `evaluate.py`, `make_training_set.py`
|
||
и `check_obstacle.py`.
|
||
|
||
### 16.1. Полигон шумит сильнее, чем меняют правки
|
||
|
||
Две реализации случайности полигона при **одних и тех же** настройках (зерна
|
||
12345 и 777) расходятся на P@150 у человека стоя 0.39 против 0.32, на P@100 у
|
||
ящика 0.40 против 0.29. Большинство правок меняют цифры меньше, поэтому
|
||
сравнивать два прогона по итоговой таблице нельзя: разница окажется шумом.
|
||
|
||
Сравнение ведётся **парно**. Генератор у каждого сценария свой (п. 16.2), и от
|
||
решений конвейера он не зависит, поэтому при другой настройке на вход идут те же
|
||
кадры с теми же вставками, до луча. Разница двух прогонов — только от настройки,
|
||
и её видно по переворотам отдельных наблюдений: сколько было 0 и стало 1 и
|
||
наоборот (`tools/compare_benchmark.py`). Если перевороты идут в одну сторону,
|
||
эффект настоящий, даже когда он меньше межзернового шума.
|
||
|
||
Так перепроверен и дальний порог из п. 15.9, на обоих зёрнах: 0.50 → 0.30
|
||
переворачивает 8 и 2 наблюдения человека стоя на 130–170 м в плюс и **ни одного**
|
||
в минус. Эффект маленький, но настоящий, и правка остаётся.
|
||
|
||
### 16.2. Новый предмет сдвигал цифры всех остальных
|
||
|
||
Генератор был один на запись, и сценарии тянули из него по очереди, кадр за
|
||
кадром. Когда в каталог добавили человека лёжа, поток сдвинулся для **всех**
|
||
сценариев: цифры по ящику менялись оттого, что где-то появился новый предмет.
|
||
Это проверено прямо: со старым каталогом перенесённый код давал полигон
|
||
побайтово, с новым цифры прежних предметов разошлись.
|
||
|
||
Теперь у каждого сценария свой генератор, зерно — из имени предмета и бокового
|
||
смещения через crc32 (`scenario_rng`; встроенный `hash()` солится в каждом
|
||
процессе заново). Случайные сценарии Zhirik1337 (`--augment`) переведены на него
|
||
же. Тест `test_scenario_stream_does_not_depend_on_the_rest_of_the_catalogue`.
|
||
|
||
База с новым каталогом (порог 0.30 за 90 м, как в п. 15.9):
|
||
|
||
| предмет | рабочая дальность | P@50 | P@100 | P@150 |
|
||
|---|---:|---:|---:|---:|
|
||
| человек стоя | 80 м | 0.70 | 0.48 | 0.39 |
|
||
| человек сидя | 100 м | 0.67 | 0.52 | 0.20 |
|
||
| **человек лёжа** | 20 м | **0.27** | **0.00** | 0.00 |
|
||
| ящик | 20 м | 0.58 | 0.40 | 0.06 |
|
||
|
||
Человек лёжа виден хуже всех, кроме совсем мелочи. Воронка (п. 12) показывает
|
||
почему: на 30–50 м кандидат рождается только в 35 % наблюдений. Высота 0.30 м,
|
||
а пол габарита стоит на 0.28 м, так что в габарит попадает верхушка в два
|
||
сантиметра.
|
||
|
||
### 16.3. Пол в колее: вдали выигрыш, вблизи рельсы
|
||
|
||
Правка: между рельсами (|u| ≤ 0.85 м) нижняя граница габарита опускается с
|
||
0.28 до 0.16 м, снаружи колеи остаётся прежней. Вдали она даёт много, но
|
||
вблизи обнаружение **падает вдвое**:
|
||
|
||
| полоса | человек стоя: база → пол 0.16 | лёжа | ящик |
|
||
|---|---|---|---|
|
||
| 0–15 м | 0.93 → 0.92 | 0.70 → **0.58** | 0.75 → **0.62** |
|
||
| 15–25 м | 0.71 → **0.37** | 0.55 → **0.27** | 0.56 → **0.34** |
|
||
| 25–40 м | 0.58 → **0.42** | 0.37 → 0.35 | 0.48 → **0.35** |
|
||
| 55–70 м | 0.83 → 0.83 | 0.17 → **0.62** | 0.70 → 0.71 |
|
||
| 110–135 м | 0.43 → 0.46 | 0.00 → 0.00 | 0.30 → **0.60** |
|
||
| 160–190 м | 0.07 → **0.27** | 0.03 → 0.03 | 0.04 → **0.12** |
|
||
|
||
Рабочая дальность у всех предметов стала 8 м, то есть обнаружение
|
||
проваливается уже на втором поясе.
|
||
|
||
**Причина.** Один и тот же кадр со вставкой прогнан через оба конвейера, и там,
|
||
где обычный видит кандидата, а опущенный нет, кандидаты пересчитаны без запрета
|
||
на длину. Во всех 12 потерях на 12–45 м одной записи предмет входит в
|
||
компоненту длиной 50–140 м, которая начинается **с 4 м** — с ближней границы
|
||
габарита. У разобранных подробно её нижняя точка лежит ровно на опущенном
|
||
полу, 0.16–0.18 м: это головки рельсов. Вблизи соседние кольца ложатся на
|
||
полотно плотнее допуска по глубине, и рельс собирается в одну компоненту. Компоненту
|
||
длиннее 15 м конвейер отбрасывает как «полотно или стену под скользящим углом»
|
||
и выбрасывает вместе с ней предмет. Разделение фигуры и фона могло бы вырезать
|
||
предмет, но ближе `split_near = 55 м` оно выключено — то же слипание, что с
|
||
кромкой платформы в п. 12.7.
|
||
|
||
**Лечение** — не опускать пол ближе заданной дальности (`core_from`). Почему
|
||
дальше рельсы предмет не губят, отдельно не разбирали; замер показывает, что
|
||
с 30 м опущенный пол вреда не делает (старые складки, база → пол с 30 м):
|
||
|
||
| полоса | человек стоя | лёжа | ящик |
|
||
|---|---|---|---|
|
||
| 0–15 м | 0.93 → 0.93 | 0.70 → 0.70 | 0.75 → 0.75 |
|
||
| 15–25 м | 0.71 → 0.71 | 0.55 → 0.55 | 0.56 → 0.56 |
|
||
| 25–40 м | 0.58 → 0.58 | 0.37 → **0.50** | 0.48 → 0.49 |
|
||
| 55–70 м | 0.83 → 0.83 | 0.17 → **0.62** | 0.70 → 0.71 |
|
||
| 110–135 м | 0.43 → 0.46 | 0.00 → 0.00 | 0.30 → **0.60** |
|
||
| 160–190 м | 0.07 → **0.27** | 0.03 → 0.03 | 0.04 → **0.12** |
|
||
|
||
Где ставить порог, мерилось тремя прогонами. Ближе 25 м ни один из них ничего
|
||
не теряет: из 3 469 наблюдений всех предметов в минус не перевернулось ни одно
|
||
(в плюс 8, 6 и 0 — треки, начатые дальше порога, доживают до ближней зоны).
|
||
Различаются пороги на мелком и низком:
|
||
|
||
| порог | ведро 25–40 м | ведро 40–55 м | лёжа 25–40 м | посторонних на полигоне |
|
||
|---|---:|---:|---:|---:|
|
||
| база (пол не опущен) | 0.23 | 0.02 | 0.37 | 1196 |
|
||
| **с 30 м** | **0.49** | **0.52** | **0.50** | **1313** |
|
||
| с 40 м | 0.49 | 0.52 | 0.45 | 1333 |
|
||
| с 55 м | 0.23 | 0.29 | 0.45 | 1336 |
|
||
|
||
Порог 30 м не хуже остальных ни в одной полосе и даёт меньше посторонних.
|
||
Парно против 40 м он добавляет 7 наблюдений лежачего и 2 ящика и не теряет
|
||
ни одного.
|
||
|
||
Парно, на тех же вставках (пол 0.16 с 30 м против базы, старые складки):
|
||
|
||
| предмет | полоса | было | стало | 0→1 | 1→0 |
|
||
|---|---|---:|---:|---:|---:|
|
||
| человек стоя | 130–170 м | 0.36 | 0.50 | 37 | 0 |
|
||
| человек сидя | 130–170 м | 0.19 | 0.47 | 68 | 0 |
|
||
| человек лёжа | 50–90 м | 0.13 | 0.46 | 74 | 0 |
|
||
| ящик | 90–130 м | 0.36 | 0.56 | 46 | 0 |
|
||
| чемодан | 130–170 м | 0.01 | 0.26 | 51 | 0 |
|
||
| ведро | 50–90 м | 0.00 | 0.39 | 85 | 0 |
|
||
|
||
На всех предметах и полосах вместе +677 наблюдений и −7. Вблизи с порогом по
|
||
дальности ничего не теряется (человек стоя на 6–50 м: +1 и −0). Цена:
|
||
|
||
Со старыми моделями пол обходится в два с лишним трека на километр на
|
||
знакомой линии и в четыре на незнакомой (таблица ниже, средний столбец).
|
||
Лишние треки — не сбой, а то, что пол открыл: низкие конструкции в колее. На
|
||
записи со стрелочным переводом появились три новых трека, и один из них —
|
||
предмет высотой 0.42 м прямо между рельсами (u = −0.40 м) на 78–104 м.
|
||
Лидар его видел и раньше, но он стоял ниже пола габарита.
|
||
|
||
Считывание таких кандидатов не видело никогда: оно обучалось на выборке,
|
||
собранной при прежнем поле. Поэтому, как в п. 12.6, выборка пересобрана на
|
||
новом полу — **333 739 кандидатов против 214 958**. Прибавка почти целиком
|
||
фоновая, это и есть низкие конструкции в колее, плюс человек лёжа, которого
|
||
раньше в каталоге не было (предметов 24 651 против 20 150). Складки
|
||
переобучены с прежней ёмкостью 4 000 клеток. AUC leave-one-bag-out **0.9856**
|
||
против 0.9829 на прежней выборке (по складкам 0.980–0.992): новые фоновые
|
||
кандидаты отделяются от предметов легко.
|
||
|
||
| | база | пол 30 м, старые складки | **пол 30 м, новые складки** |
|
||
|---|---:|---:|---:|
|
||
| ложных треков/км, знакомая линия | 8.0 | 10.2 | **8.0** |
|
||
| кадров с тревогой | 13.3 % | 19.3 % | 13.8 % |
|
||
| то же, незнакомая линия | 21.3 | 25.4 | **23.5** |
|
||
| кадров с тревогой | 28.3 % | 37.4 % | 34.6 % |
|
||
| посторонних на полигоне | 1196 | 1313 | **1226** |
|
||
| настоящий объект на 55 м | 99.5 % | 99.5 % | **99.5 %** |
|
||
| задержка кадра p50 / p95, мс (один процесс) | 33–45 / 37–49 | — | 35–42 / 41–52 |
|
||
|
||
Переобученное считывание убрало всю цену на знакомой линии и половину на
|
||
незнакомой. Из выигрыша в обнаружении отдана малая часть — у лежачего P@100
|
||
0.32 → 0.22 против старых складок, остальное в пределах сотых. Против базы:
|
||
|
||
| предмет | рабочая дальность | P@50 | P@100 | P@150 |
|
||
|---|---|---|---|---|
|
||
| человек стоя | 80 → 80 м | 0.70 → 0.71 | 0.48 → 0.48 | 0.39 → **0.49** |
|
||
| человек сидя | 100 → **148 м** | 0.67 → 0.67 | 0.52 → 0.52 | 0.20 → **0.49** |
|
||
| **человек лёжа** | 20 → **32 м** | 0.27 → **0.50** | 0.00 → **0.22** | 0.00 → 0.02 |
|
||
| ящик | 20 → 20 м | 0.58 → 0.54 | 0.40 → **0.50** | 0.06 → **0.26** |
|
||
| чемодан | 62 → **80 м** | 0.56 → 0.61 | 0.25 → **0.45** | 0.00 → **0.24** |
|
||
|
||
Парно против базы, все предметы:
|
||
|
||
| полоса | наблюдений | 0→1 | 1→0 |
|
||
|---|---:|---:|---:|
|
||
| 0–15 м | 2 158 | 0 | **16** |
|
||
| 15–40 м | 2 583 | 93 | 1 |
|
||
| 40–90 м | 2 644 | 224 | 7 |
|
||
| 90–200 м | 2 746 | 368 | 9 |
|
||
|
||
Шестнадцать ближних потерь — цена именно переобучения: со старыми складками
|
||
ближе 25 м не терялось ничего. Тринадцать из них — человек стоя у края
|
||
габарита (смещение 0.9 м) на 9–15 м, на трёх записях, две из них с платформой;
|
||
ещё сидящий там же и две бутылки на оси. Кандидат и трек у человека есть,
|
||
решения нет. Отдельно причину не разбирали. По месту это тот же случай, что в
|
||
п. 12.7: предмет у края габарита вблизи, где разрез фигуры выключен.
|
||
|
||
**Принято**: `h_lo_core = 0.16`, `core_from = 30`, выборка и складки
|
||
пересобраны. Самый важный для метро случай — человек, упавший на пути, — был
|
||
виден хуже всех (P@50 = 0.27, дальше 50 м почти никогда), теперь P@50 = 0.50 и
|
||
рабочая дальность 32 м. Цена — два трека на километр только на незнакомой линии
|
||
и 16 ближних наблюдений из двух тысяч у края габарита.
|
||
|
||
### 16.4. Лежащий предмет без штрафа — при обученном считывании не действует
|
||
|
||
`lying_exempt` снимает штраф за вытянутость по пути с низкого предмета в колее
|
||
(высота до 0.40 м, длина до 2.2 м): человек лёжа вдоль пути тянется на 1.8 м и
|
||
по ручной формуле похож на кусок лотка. Замер: полигон совпал с базой до
|
||
последнего наблюдения, и с опущенным полом тоже.
|
||
|
||
Причина не в правке, а в том, куда она встроена. Множитель компактности
|
||
живёт в ручной формуле веса, а в рабочей конфигурации `mbon_blend = 1.0`: вес
|
||
наблюдения целиком берёт обученное считывание, и ручная формула входит в него
|
||
в нулевой степени (`central_complex._quality`). Правка работает только там,
|
||
где модели нет. Флаг оставлен выключенным. Лежащему человеку помогает пол
|
||
(п. 16.3). Считывание, переобученное на выборке, где лежачий уже есть, ему не
|
||
помогло: P@100 0.32 → 0.22 против старых складок (п. 16.3).
|
||
|
||
### 16.5. Габарит в кривых, вырез платформы, дальний канал
|
||
|
||
Все три мерились одинаково: полигон парно против базы (сумма переворотов по
|
||
всем полосам) и ложные треки на двух сценах со старыми моделями.
|
||
|
||
| правка | человек стоя | сидя | лёжа | ящик | чемодан | всего | ложных/км, знакомая | незнакомая |
|
||
|---|---|---|---|---|---|---|---:|---:|
|
||
| база | | | | | | | 8.0 | 21.3 |
|
||
| `k_sigma = 0.75` | +0/−1 | +19/−0 | +0/−10 | +37/−0 | +0/−5 | +56/−16 | 9.5 | 25.7 |
|
||
| `platform_filter` | +9/−5 | +17/−3 | +0/−11 | +2/−7 | +1/−0 | +35/−26 | 10.2 | 25.2 |
|
||
| `far_channel` | +24/−0 | +21/−0 | +10/−0 | +43/−0 | +15/−0 | +115/−0 | 11.0 | 28.4 |
|
||
| для сравнения: пол с 30 м | +51/−1 | +92/−0 | +135/−0 | +108/−5 | +131/−0 | **+677/−7** | 10.2 | 25.4 |
|
||
|
||
**Габарит по неопределённости оси** (`k_sigma`) расширяет коридор там, где ось
|
||
пути известна хуже, — вдали и в кривых. Ящику это помогает, лежачему и
|
||
чемодану мешает: в широком габарите к низкому предмету чаще прилипает
|
||
посторонняя компонента. Ложные треки добавляются ровно там, где габарит
|
||
расширился, — на кривых записях, с медианной дальностью около 100 м. Отвергнуто.
|
||
|
||
**Вырез платформы** (`platform_filter`) удаляет из габарита горизонтальную
|
||
полосу 1.05–1.25 м у края (|u| ≥ 1.30 м) — настил платформы. На записях с
|
||
платформой ложных треков ровно столько же (1 → 1 и 2 → 2), а весь рост
|
||
приходится на `roundT_doubleT`, где платформы нет вовсе: 1 → 4 на знакомой
|
||
линии, 8 → 12 на незнакомой. Полоса, вырезанная из стены или лотка, режет
|
||
конструкцию на верхний и нижний обрывки, и каждый выглядит компактным
|
||
предметом. На полигоне перевороты в обе стороны почти поровну, то есть это
|
||
шум. Отвергнуто.
|
||
|
||
**Дальний канал** (`far_channel`) — отдельный путь к тревоге для треков дальше
|
||
90 м с послабленными условиями. Выигрыш настоящий (+115 и ни одного минуса), но
|
||
он решает ту же задачу, что и принятый дальний порог (п. 15.9), и стоит втрое
|
||
больше: +3.0 трека на км на знакомой линии и +7.1 на незнакомой, посторонних на
|
||
полигоне 1196 → 1676. Для сравнения, пол с 30 м даёт вшестеро больше
|
||
переворотов за меньшую цену. Отвергнуто; включать его вместе с дальним
|
||
порогом — ослаблять дальний край дважды.
|
||
|
||
### 16.6. Всё вместе, как было отправлено
|
||
|
||
Все пять правок разом плюс свои пороги дальнего края (`warn_far = 0.35`,
|
||
`leak_far = 0.06`): дальние цифры самые высокие из всех прогонов (P@150 у
|
||
человека сидя 0.56, у ящика 0.37), но посторонних тревог на полигоне **2299
|
||
против 1196** — почти вдвое, — и рабочая дальность 8 м у всех предметов из-за
|
||
рельсов (п. 16.3). Парно против базы:
|
||
|
||
| полоса | наблюдений | было | стало | 0→1 | 1→0 |
|
||
|---|---:|---:|---:|---:|---:|
|
||
| 6–40 м | 2 926 | 0.63 | **0.50** | 107 | **496** |
|
||
| 40–90 м | 1 982 | 0.47 | 0.61 | 271 | 3 |
|
||
| 90–200 м | 2 684 | 0.19 | 0.38 | 531 | 0 |
|
||
|
||
Дальше 40 м всё в плюс, ближе — всё в минус. А ближняя зона — это экстренное
|
||
торможение (у него условие `d ≤ max(тормозной путь, 25 м)`, п. 15.9) и вся
|
||
работа на малой скорости, у платформы и на подходе к ней: там других
|
||
дальностей просто нет. По отдельности видно, откуда что: дальний выигрыш почти
|
||
целиком даёт пол, ближний провал — он же без порога по дальности, а из
|
||
посторонних тревог больше всего добавляет дальний канал: +480 в одиночку, пол
|
||
без порога +99, остальные — десятки.
|
||
|
||
### 16.7. Выгрузка рамок, устройство, ROS-узел
|
||
|
||
* **Рамка в кривой стояла не там.** Боковое смещение трека отсчитано от оси
|
||
пути, а шло прямо в `x` сенсора: на радиусе 1300 м рамка уезжала на 1.2 м на
|
||
55 м и на 8.6 м на 150 м — в стену. Теперь `x = u + corridor.centre(d)`.
|
||
* **Поворот рамки был зеркальным**: `yaw = −atan(наклон)` вместо `+atan`, на
|
||
150 м это 13°. Оба случая проверяет `test_export_box_follows_a_curved_track`.
|
||
* **Устройство по умолчанию — процессор.** У Zhirik1337 `device=None` означал
|
||
«видеокарта, если есть», и обучение с выводом тихо уезжали на GPU. Вывод на
|
||
видеокарте ускоряет только ламину (п. 7.4). `auto` и `cuda` работают, если их
|
||
передать явно; Docker так и делает. Ошибка по дороге: `encode(device="auto")`
|
||
отдавал строку `auto` прямо в torch, падал, и падение навсегда помечало CUDA
|
||
сломанной.
|
||
* **ROS-узел `tools/flyguard_ros2_node.py`** отдаёт конвейеру массив numpy
|
||
вместо разобранного `PointCloud2`, публикует все треки, а не решение, и у
|
||
трека нет полей рамки. Узел ведёт интеграция, он оставлен за ними
|
||
(`TEAM_OWNED` в `export_team.py`). Рабочий узел — `ros2_ws/.../node.py`.
|
||
|
||
### 16.8. Итог
|
||
|
||
| правка | решение | почему |
|
||
|---|---|---|
|
||
| пол в колее `h_lo_core = 0.16` | **принято**, дальше 30 м (`core_from`) | лежачий P@50 0.27 → 0.50, P@150 у стоя 0.39 → 0.49; без порога по дальности вблизи провал вдвое |
|
||
| выборка и складки на новом полу | **принято** | цена пола на знакомой линии 2.2 → 0 треков на км, на незнакомой 4.1 → 2.2 |
|
||
| `lying_exempt` | не действует | правит ручную формулу, а вес целиком от модели |
|
||
| `k_sigma` | отвергнуто | +56/−16 на полигоне за +1.5 и +4.4 трека на км |
|
||
| `platform_filter` | отвергнуто | на платформах ничего, в тоннеле без платформы режет стену: +2.2 и +3.9 |
|
||
| `far_channel` | отвергнуто | та же задача, что `warn_far`, втрое дороже: +3.0 и +7.1 |
|
||
| человек лёжа в каталоге | **принято** | самый важный для метро случай и самый трудный |
|
||
| генератор на сценарий | **принято** | новый предмет больше не сдвигает цифры остальных |
|
||
| выгрузка рамок `export.py` | **принято** с двумя исправлениями | рамка в кривой и знак поворота |
|
||
| вывод на видеокарте | в коде, по умолчанию процессор | ускоряет только ламину (п. 7.4) |
|
||
|
||
Всё отвергнутое осталось в коде за флагами, с ценой в этом разделе:
|
||
отрицательный результат верен только для той конфигурации, в которой получен
|
||
(п. 15.9), и перепроверка любой правки стоит одного ключа `--set`.
|
||
|
||
**Что не сделано.** Память тоннеля (грибовидное тело) обучена на кандидатах,
|
||
собранных 19.09 при прежнем полу, и низких конструкций в колее не знает: на
|
||
знакомой линии их гасит только считывание. Выборку памяти надо собирать
|
||
проходом по 90 ГБ `new_data` — это следующий шаг, если понадобится ещё
|
||
снизить ложные на знакомой линии. *Сделано — п. 17.1.*
|
||
|
||
## 17. После переноса: память на новом полу, край габарита, вторая половина `new_data`
|
||
|
||
Три хвоста п. 16, каждый мерился так же: полигон парно, ложные треки на двух
|
||
сценах, настоящий объект.
|
||
|
||
### 17.1. Память тоннеля на новом полу
|
||
|
||
Кэш кандидатов памяти был собран 19.09, при старом полу, и низких конструкций
|
||
между рельсами память не знала (п. 16.8). Пересобран целиком: шесть записей
|
||
(`tune_memory.py --collect-only`) и весь `new_data` (`stream_new_data.py`,
|
||
39 707 кандидатов против 30 902), память переобучена.
|
||
|
||
| | было | новая память |
|
||
|---|---:|---:|
|
||
| ложных треков/км, знакомая линия | 8.0 | **6.5** |
|
||
| кадров с тревогой | 13.8 % | **9.0 %** |
|
||
| посторонних на полигоне | 1226 | 1166 |
|
||
| полигон парно | | +78 / −95 |
|
||
| настоящий объект | 99.5 % | 99.5 % |
|
||
|
||
Незнакомой линии память не касается — там её нет по построению.
|
||
|
||
При резкости считывания 1.5, принятой в п. 17.4, разница между памятями
|
||
исчезает: на знакомой линии 5.7 и 5.7 ложных трека на км, полигон парно
|
||
+90 / −86, на записи с настоящим объектом кадров с посторонней тревогой
|
||
80.5 % против 96.3 %. Принята новая: она собрана на том же полу габарита,
|
||
что и детектор, и знает низкие конструкции между рельсами, которые детектор
|
||
теперь видит.
|
||
|
||
Все ближние потери — один сценарий: на `roundT_pressureGate_roundT` поезд
|
||
стоит в 14.1 м от сидящего человека или ящика у края габарита, рядом
|
||
гермозатвор, и предмет не подтверждается: 26 кадров подряд у сидящего, 10 у
|
||
ящика. Кандидат там —
|
||
слипшееся пятно (≈950 лучей, высота 0.40 м), считывание в нём не уверено
|
||
(p = 0.23–0.30), и держится он только на новизне: у старой памяти 0.27–0.28,
|
||
у новой 0.20–0.21. При нижнем пороге новизны 0.15 вес наблюдения падает в
|
||
2.4 раза, улика 0.36–0.39 против 0.04–0.05. Случай пограничный при любой
|
||
памяти, новая переводит его через край. У настоящего объекта новизна тоже
|
||
ниже (медиана 0.63 → 0.51), но выше 0.50, где множитель новизны уже равен
|
||
единице.
|
||
|
||
### 17.2. Край габарита у платформы
|
||
|
||
Шестнадцать ближних потерь после переобучения (п. 16.3) разобраны. У края
|
||
габарита (0.9 м) рядом с платформой нижняя часть человека слипается с
|
||
кромкой, и от кадра к кадру кандидат то слипшееся пятно (p ≈ 0.0–0.2 у любых
|
||
складок), то «верх»: обрывок 0.2 × 0.2 м на высоте 1.5–1.7 м, целиком в
|
||
габарите, с нулевым дефицитом до пола — по признакам навесное оборудование.
|
||
Такой верх старые складки оценивали в 0.74–0.99, новые — в 0.43–0.84. Улика
|
||
не добирает до порога 0.5, и решение приходит на 8.8–8.9 м вместо 10.7–17 м.
|
||
|
||
В среднем новые складки на ближних предметах почти так же уверены, как
|
||
старые (отложенные записи, 0–15 м: медиана p 0.993 против 0.996, доля
|
||
p ≥ 0.9 — 0.864 против 0.891), а уверенного фона на 60–120 м у них вшестеро
|
||
меньше (0.1 % против 0.7 %). Отсюда и падение ложных тревог: новое считывание
|
||
строже в целом и к нетипичному обрывку в частности.
|
||
|
||
Попытка лечения — положение 0.9 м в обучающей выборке: полигон его проверяет,
|
||
а в выборке были только 0, ±0.6 и ±1.2 м. Выборка 466 715 кандидатов, AUC
|
||
0.9849. **Край это не вылечило**: человек стоя на 0–15 м +5 / −5, сидя
|
||
+0 / −4. Зато считывание стало строже к фону:
|
||
|
||
| | было | с ±0.9 |
|
||
|---|---:|---:|
|
||
| ложных треков/км, незнакомая линия | 23.5 | **21.9** |
|
||
| то же, знакомая (новая память) | 6.5 | 6.5 |
|
||
| посторонних на полигоне | 1226 | 1175 |
|
||
| полигон парно | | +9 / −25 |
|
||
| настоящий объект | 99.5 % | 99.5 % |
|
||
|
||
На второй половине `new_data` (п. 17.3) — ничего: 23.9 и 23.9 ложных трека
|
||
на км. Не принято.
|
||
|
||
### 17.3. Считывание, обученное на `new_data`
|
||
|
||
Пять коротких записей сняты в один день (02.09) и вместе дают чуть больше
|
||
километра пути. `new_data` — двадцать минут непрерывной езды 17.09: 11 271
|
||
кадр в 221 шарде, 90 ГБ несжатого tar. Места на диске меньше архива, поэтому
|
||
он читается кусками прямо из tar: `tools/_new_data.py` распаковывает по пять
|
||
шардов во временный каталог и удаляет за собой.
|
||
|
||
Делить пришлось по времени, а не по кадрам: вставки в первую половину
|
||
(шарды 0–109) идут в обучение, ложные тревоги меряются на второй (шарды
|
||
110–220, 3.27 км пути), которую модель не видела ни в каком виде. Памяти
|
||
тоннеля в этой проверке нет, как на незнакомой линии. Выборка — прежние
|
||
333 739 кандидатов плюс 827 314 из 22 кусков первой половины, предметов
|
||
5.4 %, AUC складок 0.981.
|
||
|
||
| считывание | вторая половина `new_data`, ложных/км | кадров с тревогой | незнакомая линия | знакомая линия |
|
||
|---|---:|---:|---:|---:|
|
||
| прежнее | 23.9 | 38.5 % | 23.5 | 6.5 |
|
||
| с ±0.9 (п. 17.2) | 23.9 | 39.0 % | 21.9 | 6.5 |
|
||
| с `new_data` | **13.2** | **20.2 %** | **13.2** | 6.2 |
|
||
|
||
Ложных тревог почти вдвое меньше, и прокси незнакомой линии совпал с
|
||
честной проверкой. Но полигон парно — +39 / −259: у стоящего человека P@50
|
||
0.71 → 0.62, у лежачего P@100 0.22 → 0.11, у ящика 0.53 → 0.40.
|
||
|
||
Воспроизводится так:
|
||
|
||
```bash
|
||
python tools/make_training_set.py --new-data 0:110 --out data/cache/training_set_nd.npz
|
||
python tools/train_mbon.py --device cuda --data data/cache/training_set.npz \
|
||
--data data/cache/training_set_nd.npz --train-only new_data_ \
|
||
--save-folds artifacts/mbon_folds_nd --out artifacts/mbon_readout_nd.npz
|
||
python tools/eval_new_data.py --shards 110: --readout было=artifacts/mbon_readout.npz \
|
||
--readout nd=artifacts/mbon_readout_nd.npz --out nd_eval.json
|
||
```
|
||
|
||
`--train-only new_data_` — куски `new_data` только учат и складок не
|
||
получают: проверяются по-прежнему пять записей.
|
||
|
||
### 17.4. Строже или умнее: сравнение при равной строгости
|
||
|
||
Модель, которая вдвое реже тревожится и заметно реже видит предмет, может
|
||
быть не лучше, а просто строже. Это проверяется сравнением при одинаковой
|
||
строгости, и ручка для неё есть с п. 11: `mbon_power`, вес наблюдения
|
||
p^power. Все строки — с новой памятью (п. 17.1).
|
||
|
||
| считывание, резкость | вторая половина `new_data` | незнакомая | знакомая | посторонних на полигоне | полигон парно к первой строке |
|
||
|---|---:|---:|---:|---:|---:|
|
||
| прежнее, 1.0 | 23.9 | 23.5 | 6.5 | 1166 | |
|
||
| **прежнее, 1.5** | 17.4 | 14.7 | **5.7** | **793** | **+1 / −142** |
|
||
| прежнее, 2.0 | 15.0 | 14.3 | 5.7 | 628 | +0 / −268 |
|
||
| `new_data`, 1.0 | **13.2** | **13.2** | 6.2 | 822 | +39 / −259 |
|
||
| `new_data`, 0.7 | 24.8 | 16.4 | 7.7 | 927 | +72 / −184 |
|
||
|
||
* **Больше половины выигрыша — строгость.** Прежнее считывание при
|
||
резкости 1.5 срезает ложные на незнакомой линии с 23.5 до 14.7, а на
|
||
полигоне теряет 142 наблюдения из 7 600: 39 вблизи у края габарита (тот же
|
||
пограничный случай, что в п. 17.1–17.2) и 82 за 90 м.
|
||
* **Но не весь.** При сопоставимых ложных на второй половине `new_data`
|
||
(15.0 и 13.2) модель на `new_data` теряет на полигоне меньше, чем прежняя
|
||
при 2.0 (−220 против −268 в сумме). Другой день записи действительно
|
||
научил её тому, чего в пяти записях нет. Ослаблять её бесполезно: при 0.7
|
||
ложные возвращаются к 24.8.
|
||
* **Между «прежнее, 1.5» и «`new_data`, 1.0» — размен, а не доминирование.**
|
||
Вторая на четверть реже тревожится на данных своего дня, первая держит
|
||
дальность: P@50 у стоящего 0.71 против 0.62, P@100 у лежачего 0.22 против
|
||
0.11, у ящика 0.50 против 0.40. На пяти записях без памяти, которых при
|
||
проверке не видела ни одна из двух, они почти равны (14.7 и 13.2), на
|
||
знакомой линии прежняя лучше (5.7 и 6.2).
|
||
|
||
**Принято: прежнее считывание, резкость 1.5.** ТЗ просит баланс дальности и
|
||
ложных тревог, постановщик — прежде всего не видеть того, чего нет; резкость
|
||
1.5 даёт второе почти без цены в первом. Модель на `new_data` остаётся
|
||
рецептом из п. 17.3: если появятся ещё записи других дней, учить на них
|
||
выгоднее, чем ужесточать.
|
||
|
||
### 17.5. Итог
|
||
|
||
Новая память (п. 17.1), прежнее считывание, резкость 1.5 (п. 17.4) — против
|
||
опубликованного 23.09:
|
||
|
||
| | 23.09 | сейчас |
|
||
|---|---:|---:|
|
||
| ложных треков/км, знакомая линия | 8.0 (13.8 % кадров) | **5.7** (8.8 %) |
|
||
| то же, незнакомая линия | 23.5 (34.6 %) | **14.7** (25.9 %) |
|
||
| то же, вторая половина `new_data` | 23.9 (38.5 %) | **17.4** (31.4 %) |
|
||
| посторонних тревог на полигоне | 1226 | **793** |
|
||
| настоящий объект | 99.5 % | 99.5 % |
|
||
| человек стоя: рабочая дальность; P@50 / P@100 / P@150 | 80 м; 0.71 / 0.48 / 0.49 | **100 м**; 0.71 / 0.50 / 0.46 |
|
||
| человек сидя | 148 м; 0.67 / 0.52 / 0.49 | 122 м; 0.67 / 0.54 / 0.39 |
|
||
| человек лёжа | 32 м; 0.50 / 0.22 / 0.02 | 20 м; 0.50 / 0.22 / 0.00 |
|
||
| ящик | 20 м; 0.54 / 0.50 / 0.26 | 20 м; 0.57 / 0.50 / 0.22 |
|
||
| чемодан | 80 м; 0.61 / 0.45 / 0.24 | 80 м; 0.61 / 0.46 / 0.24 |
|
||
|
||
Ложных тревог примерно на треть меньше на всех трёх сценах, ближняя и
|
||
средняя дальность на месте. Цена — хвост за 130 м у сидящего человека
|
||
(P@150 0.49 → 0.39) и рабочая дальность лежачего 32 → 20 м: доля в полосе
|
||
20–32 м у него ходит у самого порога 0.5, и сдвинула её новая память, а не
|
||
резкость (P@50 тот же, 0.50). Без обученного считывания при тех же
|
||
настройках было бы 11.9 и 36.1 ложных трека на км.
|
||
|
||
Отдельно найдено при проверке сдачи: образ Docker **не загружал**
|
||
считывание — `mbon_path` был пустым, файла модели в образе не было, и узел
|
||
молча работал на ручной формуле. Все цифры выше замерены со считыванием,
|
||
образ теперь тоже с ним (`docker/Dockerfile`, `detect.launch.py`), а
|
||
дымовой тест образа проверяет, что модель на месте.
|
||
|
||
### 17.6. Раздельный запуск: узел в контейнере, проигрывание снаружи
|
||
|
||
ТЗ описывает приёмку как `docker build → docker run → ros2 bag play`. Все
|
||
прежние замеры шли внутри одного контейнера — узел и проигрыватель вместе.
|
||
Раздельный запуск, как у инженера на стенде, проверен впервые (запись
|
||
`roundT_doubleT`, 252 кадра):
|
||
|
||
| узел | проигрыватель | принято кадров |
|
||
|---|---|---:|
|
||
| `--network host` | другой контейнер, наш профиль DDS | **0** |
|
||
| `--network host`, умолчания Fast DDS | другой контейнер, умолчания Fast DDS | **0** |
|
||
| `--network host --ipc host` | другой контейнер `--ipc host`, умолчания Fast DDS — как ROS на хосте | 252 |
|
||
| `--network host --ipc host` | другой контейнер `--ipc host`, наш профиль | 252 |
|
||
|
||
Причина: с `--network host` Fast DDS видит у собеседника тот же хост и шлёт
|
||
кадры через `/dev/shm`, а без `--ipc host` у контейнера она своя — сегмент
|
||
собеседника не виден, и кадры пропадают молча, без единой ошибки в журнале.
|
||
Это общая ловушка ROS 2 в Docker, наш профиль ни при чём: на умолчаниях то же.
|
||
|
||
Что сделано:
|
||
|
||
* `--ipc host` — во всех командах README;
|
||
* точка входа видит собственную `/dev/shm` (источник монтирования `shm`) и
|
||
переводит транспорт на UDP (`docker/fastdds_udp.xml`): проигрывание внутри
|
||
контейнера и второй контейнер из этого же образа тогда работают и без флага
|
||
(252 из 252). Проигрывателю на хосте со своими умолчаниями это не помогает —
|
||
он всё равно идёт через память (0 кадров), поэтому
|
||
* узел следит за входом: если издатель на топике есть, а кадров нет пять
|
||
секунд, он пишет в журнал, что контейнер нужно запускать с `--ipc host`.
|
||
|
||
Попутно: запуск одной командой (`bag:=`) проигрывал запись без
|
||
`--read-ahead-queue-size 10`, и на кадрах по 24 МБ доходило 119 из 201. Флаг и
|
||
задержка старта проигрывания на 4 с добавлены в launch — 201 из 201, с RViz и
|
||
без. Сквозная проверка после всех правок, `doubleT_obstacle` в реальном
|
||
времени: 201 кадр из 201, объект в 189 кадрах из 190 на 55.8 м, обработка кадра
|
||
33.4 / 40.3 мс (медиана / p95).
|