The Jerusalem PostBoy George announces second concert in Israel dedicated to Nova music festival survivorsCNN TürkTürkiye otomotiv ihracatı 9 ayda 31,1 milyar dolara ulaştıESPNThrough Dak Prescott's eyes: The Cowboys QB breaks down a play and all he seesPunchFirst Niger Bridge set to reopen soon – Federal controllerBollywood HungamaRhea Chakraborty launches ‘Caught Blushin’ with Staze Beauty, inspired by her viral The Traitors lookInquirerWalang pasok: Earthquake prompts work, class suspensions in GenSanCollider‘Twilight’ Star Jackson Rathbone Rejects “Soulless” AI-Generated Art [Exclusive]ZDF heuteAktuelle Pressemitteilungen des ZDFSouth China Morning PostIndia aims to anchor military ties with US as Indo-Pacific ‘resident power’Sky TG24L'esorcista: Martiri, il teaser trailer dell'horrorThe South AfricanTransfer boost: Bafana star wins award as England, Spain, Germany, France and Italy watchThe IndependentThree sisters who drowned off Brighton beach took their own lives, coroner rules
The Daily Newsstand · Free, Always
Thursday, October 8, 2026

Одно имя метрики, разные эксперименты: почему состав выборки надо хешировать

Translate

Рядом с метрикой принято хранить версию модели и версию данных. Этого мало. Между двумя запусками может смениться правило, по которому объекты попадают в оценку, и тогда одно и то же имя метрики описывает два разных эксперимента.

У меня это вылезло на прогнозе загруженности парковок. Часть датчиков молчит: вместо занятости они круглосуточно отдают одно и то же число. Такие парковки надо выкидывать и из обучения, и из замера, иначе модель учится предсказывать константу, а метрика выходит красивее заслуженного.

В скрипте обучения лежал список исключений: 39 идентификаторов, заведённых руками в 2025 году. Через год я сверил его с реальностью.

в списке                     : 39
реально мусорных сейчас      : 39
пересечение                  :  9

Размер совпал. Состав сменился на три четверти. Тридцать парковок исключались зря, тридцать других попадали в обучение, хотя не должны были.

Два способа, которыми расходится состав

Первый очевиден: правило записано перечислением. Список идентификаторов это снимок на один день, а данные живут дальше. Часть датчиков починили, часть парковок город завёл позже, и в список они не попали просто потому, что на момент его написания не существовали.

Второй способ не требует вообще никаких действий.

Правило может быть записано условием и всё равно давать разный ответ в разные дни. У меня критерий такой: «парковка не отдала ни одного свободного места за 30 дней». Текст условия неизменен, код неизменен, а состав ползёт сам, потому что ползёт календарь.

Я поймал это на второй день после того, как написал проверку. Вчера под условие попадало 39 парковок, сегодня 40. Разницу дала одна, у которой последнее свободное место пришло ровно 31 день назад. Никто ничего не трогал, порог просто истёк.

Что именно записывать

Напрашивается хранить текст правила. Это не работает: в обоих случаях выше текст совпадал, а результат нет.

Записывать надо, какие именно объекты дошли до оценки. Хранить их списком накладно, поэтому я складываю рядом хеш.

import hashlib

def fingerprint(object_ids, rule: str) -> dict:
    ids = sorted(int(i) for i in object_ids)
    return {
        'rule': rule,
        'n': len(ids),
        'sha256': hashlib.sha256(','.join(map(str, ids)).encode()).hexdigest()[:16],
    }

Три поля. Текст правила нужен человеку, чтобы понять, что имелось в виду. Размер ловит грубые расхождения глазом. Хеш ловит тонкие, когда размер совпал, а состав нет.

Тот самый случай с двумя списками по 39:

правило из кода 2025 года     n=39   sha256=7f0c85daeed23adb
то же правило, пересчитанное  n=40   sha256=e01745cc0f28e9d8

Сравнивать размеры бесполезно, они почти равны. Хеши расходятся сразу.

Считать по факту, а не по фильтру

Отпечаток надо снимать не с тех объектов, которые прошли условие, а с тех, которые реально дошли до расчёта метрики.

Разница тонкая, но существенная. В моём замере под условие «датчик живой» подходят 170 парковок, а в метрику попали 167. Три отвалились внутри самого расчёта: по ним не хватило истории для окна прогноза. Если хешировать результат фильтра, отпечаток опишет намерение, а не то, что посчиталось.

Поэтому накопление идёт по ходу цикла.

scored = {'live': [], 'silent': []}

for object_id in ids:
    df = load_history(object_id)
    if len(df) < required_window:
        continue              # не попал в метрику, значит не попадёт и в отпечаток
    ...
    scored[group].append(object_id)

cohorts = {
    'live': fingerprint(scored['live'], 'NOT feed_silent'),
    'silent': fingerprint(scored['silent'], 'feed_silent'),
}

Что показал следующий прогон

Я перезапустил тот же скрипт на следующий день. Веса модели те же, код тот же, данных прибавилось на сутки.

            вчера                      сегодня
live     n=168  MAE 8.80   →   n=167  MAE 8.79   sha256=d14e294f57dd149f
silent   n=39   MAE 1.18   →   n=40   MAE 1.07   sha256=e01745cc0f28e9d8

MAE сдвинулась на сотую. Без отпечатка это читается как «метрика стабильна, прогон воспроизвёлся». С отпечатком видно, что выборка другая: одна парковка переехала из живых в молчащие, и сравнивать эти два числа напрямую нельзя.

Разница копеечная, и именно поэтому пример хороший. Копеечную разницу никто не пойдёт расследовать. Она ляжет в таблицу результатов как подтверждение, что всё в порядке.

Чего это не решает

Отпечаток фиксирует состав и ничего не говорит о том, что внутри объектов. Если у половины выборки обновилась история за те же сутки, идентификаторы совпадут, хеш совпадёт, а данные будут другие. Для этого нужен отдельный отпечаток самих данных, и считается он дороже.

Ещё отпечаток не делает замеры сравнимыми, он только показывает, что они несравнимы. Чтобы сравнивать честно, надо либо зафиксировать состав на момент базового замера и гонять последующие на нём же, либо раскладывать изменение метрики на вклад модели и вклад состава. Второе требует прогона старой модели на новой выборке и новой на старой, то есть четырёх замеров вместо двух.

Хеш я обрезаю до 16 символов, чтобы читать его глазами в логе. Сравнивать полную сумму незачем: задача у него одна, показать, что два прогона с одинаковым именем метрики это разные прогоны.

Данные лежат открыто: 4,6 млн получасовых замеров загруженности парковок, CC BY 4.0. Признак молчащего датчика и дата последнего свободного места там есть, так что описанное воспроизводится.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.