Приложение для подсчёта калорий по фото — идея распространённая, но большинство решений плохо справляются с русской кухней. Разработчик, строящий собственный сервис, решил не полагаться на ощущение «кажется, работает» и провёл структурированный бенчмарк на 66 фотографиях блюд из трёх кухонь: русской, азиатской и европейской.
Для оценки он ввёл три категории ответов: correct — блюдо названо верно (уточнения вроде «борщ с говядиной» вместо «борщ» ошибкой не считаются), wrong-but-close — другое, но похожее блюдо той же категории, которое пользователь может исправить парой тапов, и wrong — принципиально другое блюдо, после которого доверие к приложению не восстановить. Критическая планка — доля wrong ниже 20%.
| Кухня | Правильно распознано | Всего | Точность |
|---|---|---|---|
| Русская (RU) | 14 | 14 | 100,0% |
| Азиатская (ASIA) | 21 | 22 | 95,5% |
| Европейская (EU) | 27 | 30 | 90,0% |
Модель — Gemini 2.5 Flash через OpenRouter — показала 84,8% точных ответов, 9,1% близких и 6,1% неисправимых ошибок. Восстановимых случаев в сумме 93,9% — запас до критической планки значительный. Оценку вердиктов автоматизировали через LLM-as-judge: та же модель получала ground truth и предсказание и отвечала одним словом при temperature=0. Подход спорный из-за возможного self-serving bias, но ручная проверка части результатов явных расхождений не выявила.
Распределение по кухням оказалось контринтуитивным. Русская еда — 14 из 14, 100%. Азиатская — 21 из 22, 95,5%. Европейская — 27 из 30, 90%. Ожидалось обратное: западных блюд в обучающих данных заведомо больше. Объяснение нашлось при разборе четырёх провалов: жареный рис принят за омлет, лазанья дважды — за тефтели в соусе и жульен, курица терияки — за лосось на гриле. Все четыре — составные или запечённые блюда, где ключевой ингредиент скрыт под сыром, соусом или корочкой. Открытые блюда — борщ, суп, тарелка с гарниром — читаются без проблем. Граница проходит не по кухне, а по визуальной доступности ингредиентов.
Главный неожиданный вывод касается поля confidence. Gemini возвращает значение от 0 до 1, и разработчик планировал показывать пользователю предупреждение при значении ниже 0.85. Анализ распределения разрушил эту идею: 49 из 66 ответов получили ровно 0.90. За весь датасет модель опустилась ниже порога один раз. Ни одна из четырёх неисправимых ошибок предупреждения не получила — все четыре пришли с пометкой high. Confidence не коррелирует с правильностью; это почти константа, которую модель выставляет по умолчанию. Строить на нём UX-логику («перефотографируйте, мы не уверены») бессмысленно.
Отдельный блок бенчмарка — калории. Из правильно распознанных блюд лишь около половины попали в референсный диапазон. Причина очевидна: LLM интерполирует значения КБЖУ по памяти, не имея структурированной базы данных. Решение — справочник Скурихина (советская база состава пищевых продуктов) плюс часть USDA для зарубежных позиций, итого 226 записей. Резолвер работает в два уровня: сначала точный поиск по нормализованному ключу, затем полнотекстовый поиск MySQL с биграммным ре-ранжированием (коэффициент Дайса, порог 0.85). Нормализация убирает стоп-слова — «варёный», «жареный», «свежий» — чтобы «варёная говядина» и «говядина» совпадали на первом уровне. Если совпадение не найдено, остаются значения от LLM.
Методологически проект демонстрирует рабочий подход к оценке vision-моделей в продуктовом контексте: небольшой, но размеченный датасет, чёткое разделение ошибок по критичности для пользователя и автоматизированная оценка через LLM-as-judge с возможностью ручной правки вердиктов без повторного прогона распознавания. Для масштабирования до тысяч строк автор рекомендует заменить судью на отдельную, более дешёвую модель.

