Я прогнал 60 тысяч составов продуктов через свой алгоритм. Вот что в еде на самом деле. Data Mining.. Data Mining. анализ данных.. Data Mining. анализ данных. здоровье.. Data Mining. анализ данных. здоровье. пищевые добавки.. Data Mining. анализ данных. здоровье. пищевые добавки. состав продуктов.

Я делаю сервис, который наводится камерой на штрих-код и отдаёт светофор по составу плюс одну фразу человеческим языком. Побочный эффект такой работы — база. Сейчас в ней около 60 тысяч товаров из российских магазинов с разобранными составами.

В какой-то момент я перестал смотреть на отдельные пачки и посмотрел на полку целиком. Ниже — цифры, грабли парсинга и два места, где алгоритм врал. Про механику, без ссылок и названий.

Что считает алгоритм

ИИ в оценке нет. Это арифметика.

На вход идут: сахар на 100 г, соль, насыщенные жиры, трансжиры, позиция сахара в списке ингредиентов и добавки из «списка внимания». Каждый показатель даёт штрафные баллы, баллы складываются, сумма сравнивается с двумя порогами.

score  = penalty_sugar(sugar_100g)
score += penalty_salt(salt_100g)
score += penalty_sat_fat(sat_fat_100g)
score += penalty_trans(trans_100g)
score += penalty_position(sugar_index)      # где сахар стоит в составе
score += sum(penalty_additive(c) for c in codes if c in WATCHLIST)

color = GREEN if score <= T1 else (YELLOW if score <= T2 else RED)

Языковая модель в системе есть, но стоит она после вердикта: пересказывает готовый результат одной короткой фразой. Цвет она не выбирает, на баллы не влияет. Формула открытая — это пригодится в разделе про пороги, где меня будут бить.

Что оказалось на полке

Распределение по всей базе:

  • зелёных — около 43%

  • жёлтых — около 38%

  • красных — около 18%

Я ждал худшего. Ощущение «в магазине всё плохо» на цифрах не подтверждается: больше 40% ассортимента — нормальная еда. Крупы, молочка, вода, чай, овощи. Скучные категории, которые никто не обсуждает, потому что обсуждать нечего.

Красная зона тоже не монолит:

  • ~40% красных — чистое питание: сахар, соль, жир. Добавок нет вообще.

  • ~36% — нутриенты в норме, красный приехал из-за одной добавки.

  • остальные — пограничные. Держатся на одном показателе, который чуть перевалил. Сдвинь порог на пару единиц — половина уедет в жёлтый.

Отдельно я посчитал товары, где сахар стоит в первой тройке ингредиентов. Состав пишется по убыванию массы, так что первая тройка — это буквально «из чего продукт в основном сделан». Таких товаров неприлично много. И примерно половина из них на десерт не похожа: соусы, готовые завтраки, «фруктовые» творожки, часть хлеба.

Для этого, кстати, никакой софт не нужен. Читается глазами прямо у полки.

Пороги

Самое уязвимое место всей затеи. Я их не выдумывал, но и готовыми не взял — пришлось калибровать.

Ориентиры:

  • рекомендации ВОЗ по свободным сахарам и соли;

  • светофор британского FSA, у него пороги на 100 г опубликованы;

  • ТР ТС 029/2012 по добавкам.

Дальше начинается инженерия. Если поставить порог по сахару строго по ВОЗ, в красное уезжает половина молочки, включая обычный питьевой йогурт. Формально всё верно. Практически бесполезно: когда красное почти всё, цвет перестаёт нести информацию. Если порог смягчить, зелёным становится почти всё, и сервис не нужен.

Я калибровал по распределению внутри категорий и проверял контрольную выборку руками. Это не наука, это компромисс. Готов спорить в комментариях — формула открыта, конкретные значения можно обсуждать предметно.

Е-коды и грабли парсинга

В базе встречается 400+ уникальных Е-кодов. Под реальным вниманием токсикологов — горстка: нитриты в колбасе, часть синтетических красителей, диоксид титана. И претензии там обычно к дозе.

Остальное — витамины, кислоты, загустители. Е300 — витамин C. Е330 — лимонная кислота. Пектин, лецитин, сода. Буква Е означает «на вещество есть регламент», а не «химия».

Но с кодами есть проблема, которая портит статистику.

Производитель вправе писать добавку названием, а не кодом. Строка «стабилизатор полифосфат натрия, фиксатор окраски нитрит натрия» — это Е452 и Е250, и ни одного кода в ней нет. Парсер по кодам такое не видит.

Хуже, что недосчёт асимметричный. Он сильнее там, где производителю есть что прятать: «нитрит натрия» на этикетке звучит спокойнее, чем «Е250».

Лечится словарём синонимов. Беда в том, что имён у одного вещества много:

SYNONYMS = {
    "E300": ["аскорбиновая кислота", "витамин c", "аскорбат"],
    "E322": ["лецитин", "соевый лецитин", "лецитины"],
    "E452": ["полифосфаты", "пирофосфат", "полифосфат натрия"],
    "E250": ["нитрит натрия", "нитритная соль"],
}

А потом приходит засада с подстрокой:

# первая версия, выглядит разумно
if re.search(r"нитр", text):
    codes.add("E250")
# ловит заодно нитраты, Е251 и Е252. Другие вещества, другой профиль риска.

# вторая версия
if re.search(r"нитрит", text):
    codes.add("E250")
# «нитритная соль» — поймали, и правильно
# «нитрат натрия» — мимо, и тоже правильно

Такая ошибка не падает с исключением. Она просто тихо тащит лишние товары в статистику по нитритам.

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

Два места, где алгоритм врал

Насыщенные жиры. В таблице на упаковке отдельной строки по ним часто нет. В первой версии я прикидывал их долей от общего жира. Логика вроде здравая. Результат — в красное уехали сыр, орехи, жирная рыба и оливковое масло.

Допущение я убрал. Несколько тысяч товаров честно вышли из красного. Правило, которое из этого осталось: пропуск в данных — не значение. Штрафовать за то, чего не знаешь, нельзя.

Вода. Этот баг нашли читатели, уже после публикации.

Питьевая вода получала зелёный и подпись «сбалансированный состав». В комментариях воду назвали цельнозерновой и предложили сварить на ней кашу. Скриншот собрал 90+ плюсов. Заслуженно.

Формально алгоритм прав: добавок нет, штрафов ноль, сумма ноль, зелёный. Ошибка смысловая. Отсутствие претензий к составу — не то же самое, что подтверждённая польза. Тот же баг сидел на соли.

Чинил в три захода.

  1. Отправить все однокомпонентные товары в нейтральный статус. Неверно: гречке и молоку зелёный подходит нормально, менять надо было формулировку, а не цвет.

  2. Отправить в нейтральный соль и сахар. Тоже неверно: так снимается единственное полезное предупреждение, которое там вообще есть.

  3. Рабочий вариант: цвет отвечает на вопрос, превышает ли норму по этому веществу обычный человек на практике.

Как это ложится на примеры:

  • Соль. Норма ВОЗ — 5 г в сутки, в России реально едят около 11. Жёлтый.

  • Лимонная кислота. ADI не установлена, упереться в опасное количество нереально. Зелёный.

  • Нитриты. Есть механизм вреда, а не только вопрос дозы. Красный.

Штрих-коды съели больше времени, чем формула

Я думал, что сложное здесь — оценка. Нет. Сложное — идентификация товара.

Что вылезло:

  • Коды, начинающиеся с 2, — внутренние весовые коды магазина. В них зашит вес. Один и тот же окорок превращается в десятки «разных товаров».

  • Один товар живёт под несколькими кодами: другой объём, другой завод, ребрендинг.

  • Реже наоборот: один код на разные товары, код переиспользовали.

  • Ведущие нули. 0000050761166 и 50761166 — это один товар. Но если хранить код числом, они разъезжаются. Дубли пришлось схлопывать отдельным проходом.

Стек при этом намеренно скучный: Python, PostgreSQL, никакого ML в оценке. Весь «интеллект» лежит в правилах и словарях, которые можно открыть и прочитать. Страницы отдаются с сервера без JS — их 60+ тысяч, они должны индексироваться.

Общий итог по данным получается такой: пугают не тем. Страх сфокусирован на букве Е, а основной вклад в красную зону дают сахар и соль. Сахар при этом ещё и прячется под десятком имён, а соль не прячется вовсе — написана русским языком в начале состава.

Формула открыта, вопросы по порогам принимаю.

Автор: Valery247500

Источник