После прошлого поста про 243 добавки мне написали человек десять с вопросом, как вообще устроен светофор в приложении: почему один товар зелёный, а соседний с виду такой же — красный. Отвечаю честно, заодно расскажу, как чуть не отправил в чёрный список сыр, орехи и оливковое масло. Спойлер: отправил.
Коротко про механику. Приложение сканирует штрих-код и по составу с этикетки красит товар в зелёный, жёлтый или красный. Никаких нейросетей, обычная система штрафных баллов: сахар, соль, насыщенные жиры, трансжиры, позиция сахара в списке ингредиентов, проблемные Е-добавки. Сумма штрафов 3 и больше — красный, 1-2 — жёлтый, 0 — зелёный. Логика прозрачная, я ей гордился.
Потом сел проверять алгоритм на локальном мастер-файле почти из 60 тысяч товаров с составом — хотел посмотреть общую картину по цветам. И зацепился взглядом: в красном слишком много того, что интуитивно вредным не выглядит. Сыр. Грецкие орехи. Оливковое масло. Полез разбираться.
Вот в чём была засада.
Далеко не все производители пишут на этикетке строкой «в том числе насыщенные жиры» — часто указан только общий жир. Когда такой строки не было, алгоритм не сдавался и досчитывал насыщенные сам: брал общий жир и умножал на 0,30. Число не с потолка — это реальная доля насыщенных жиров у сливочного масла.
Проблема в том, что 30% — это профиль конкретно сливочного масла, а не жира вообще. У других продуктов доля насыщенных в общем жире совсем другая:
— лосось — около 20%
— оливковое масло — около 14%
— грецкий орех — около 9%
— миндаль — около 8%
То есть оливковому маслу мы приписывали вдвое больше насыщенных жиров, чем в нём реально есть. А миндалю и грецкому ореху — почти вчетверо больше.
Дальше по цепочке было ещё хуже. Порог штрафа стоял на 5 г насыщенных на 100 г продукта. Если насыщенные считаются как 30% от общего жира, то любой товар жирнее примерно 17 г на 100 г автоматически огребал минус два балла — независимо от того, что это вообще за жир. Под раздачу попали сыр, орехи, жирная рыба, оливковое масло, сырники, майонез, творожные сырки. Не потому что там много «плохого» жира, а просто потому что жирный.
Мне не столько не понравилась завышенная цифра, сколько то, как она была устроена внутри формулы. Оценка «мы предполагаем» и измерение «мы точно знаем из этикетки» шли туда с одинаковым весом. Алгоритм не различал «у меня есть точные данные» и «у меня нет данных, но я сейчас что-то придумаю похожее на правду».
Прогнал по базе — 6245 товаров были красными исключительно из-за этой догадки. Это 43% всех красных вердиктов, почти половина.
Чинится это просто, обидно, что не сразу пришло в голову: за догадку — штраф -1, за реальные данные с этикетки — как и было, -2.
Пока копался в этом месте, нашёл вторую дыру, и она мне нравится ещё меньше, потому что я сам её туда вкрутил на прошлой итерации. Штраф за проблемную добавку был бинарным: нашли в составе что-то из списка «плохих» Е — минус три балла, сразу красный. При актуализации справочника под этот флаг закономерно попали фосфаты и глутаматы — не подарок, но встречаются в половине обработанных продуктов на полке. С ними доля красных в базе улетела до 28%.
И тут стало ясно: если каждый третий товар в магазине красный, красный цвет перестаёт что-либо значить. Светофор, где половину времени горит красный, бесполезен — глаз перестаёт на него реагировать.
Развели добавки по степени риска. Высокий риск — нитриты, диоксид титана — минус три балла, как раньше. Умеренный риск — фосфаты, глутаматы — минус один.
Что получилось по итогам обеих правок на всей базе:
Было: 37% зелёных, 39% жёлтых, 24% красных.
Стало: 43% зелёных, 38% жёлтых, 18% красных.
Из красного ушли сыр, сырники, майонез, крекеры, творожные сырки. Остались колбасы с нитритом, бекон, шоколад, торты, лапша быстрого приготовления — то есть ровно то, к чему вопросы были и без всех этих перерасчётов.
Отдельно скажу, что я сознательно не стал трогать, хотя соблазн был. Есть правило: если сахар — первый ингредиент в составе, продукт автоматически красный, независимо от остальных баллов. При полном пересчёте под раздачу опять попадают шоколад и торты, и было желание их пожалеть. Но здесь смягчать нечего: позиция ингредиента в составе — прямой факт с этикетки, а не оценка алгоритма. Раз сахара больше всего остального — значит больше всего, тут нечего интерпретировать.
Из всей этой истории вынес одну вещь. В системе, где смешаны факты и предположения, самое опасное — не сами предположения, а то, что их слишком легко перепутать с фактами, если не помечать разницу явно. Одна строчка кода, тихо округлявшая неизвестное до среднего по больнице, почти держала половину продуктовой полки в статусе «вредно» без единого реального основания.
А вам не кажется, что чем детерминированнее и «прозрачнее» выглядит алгоритм — без нейросетей, чистая арифметика, — тем проще в нём просмотреть такую тихую подмену? С нейросетью хотя бы сразу понятно, что это чёрный ящик, которому нельзя доверять слепо. А тут вроде всё на ладони, код читается как учебник, и именно поэтому ошибку так долго никто не замечал — включая меня.