Как приготовить 10 000 меню за три недели, когда есть Data Science

В период перехода на удалёнку команда Data Science 2ГИС придумала решение, как быстро приготовить красивые меню для 10 000 ресторанов и кафе более чем в 100 городах.

Придумываем блюдо

В конце 2019 года мы добавили в 2ГИС товарный поиск по строительным рубрикам и шинам. Сделали глубокий рубрикатор строительных категорий, узнали про балясину и RunFlat и научились извлекать из названий и описаний необходимые для выбора атрибуты товаров.

С RunFlat можно ехать даже после прокола

Оценив востребованность, решили развивать поиск в новых рубриках.

Основной камень преткновения — полнота данных. Чтобы поиск давал исчерпывающие результаты, нужны цены очень многих компаний. И желательно в машиночитаемом формате. Но у одних компаний нет сайта — значит, они не могут дать ссылку на прайс-лист. У других не настроена автоматическая выгрузка из 1С. Решение с нашим FTP тоже не взлетело, так как многие срезались на стадии формата файлов.

Тогда мы решили увеличить полноту, снизив порог входа даже для самых неподготовленных фирм — предоставили им возможность самостоятельно вносить цены в Личном кабинете при помощи CRUD. А заодно — добавлять фотографии товаров.

Весной этого года мы готовились к релизу. Оставалось несколько недель работы, как пришёл COVID-19 и последующий за ним карантин. И исходя из новых условий, мы сфокусировались на самых востребованных категориях: «еда» и «лекарства».

Отправляем мини-паука

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

В 2ГИС несколько десятков тысяч сайтов заведений общепита. И примерно треть — с ценами на блюда. И было очень логично взять цены с этих сайтов.

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

Классифицируем данные

Задачу сформулировали так: на странице с меню должно быть название блюда, а где-то визуально рядом с названием указана цена. Поэтому нам нужен классификатор текста на три класса:

  • название блюда,
  • описание или ингредиенты,
  • другая строка.

Цену нужно отличать от граммовки, а фотография блюда всегда находится рядом с описанием.

В одном из лучших мест Новосибирска пицца бывает не только с ананасами

При помощи headless-браузера мы скачали несколько сотен гигабайт страниц с сайтов общепита и сохранили computed style каждого html-элемента. Так мы получили возможность определять взаимное расположение элементов для необходимых строк.

Классификатор названий блюд сделали из двух моделей бинарных классификаторов. Первая модель определяла, является ли строка блюдом или каким-то другим текстом, а вторая разделяла блюда и описания.

Готовим датасет

Датасет собирали самостоятельно.

Класс «название»
Названия взяли из рецептов блюд в различных сборниках. Выучили простой классификатор, а на основе скачанных данных делали разметку.

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

Салат — это блюдо или ингредиент? А моцарелла с томатами — описание или название?

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

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

Замешиваем обучение

Пробовали различные варианты моделей: от классических регрессий до претрейна Bert. Самым стабильным и предсказуемым вариантом на наших данных оказался vowpal wabbit на различных текстовых фичах и их комбинациях.

Когда модель начала давать приемлемое качество, сделали кластеризацию найденных строк по визуальному расположению и обязательному наличию цифрового токена, похожего на цену. Кому интересны значения, «приемлемое качество» — F1 0.90+.

Для классификации «цена»/«не цена» использовали набор эвристик: название html-класса, наличие символов, указывающих на валюту, и исключение токенов с явным указанием граммовки и калорий.

Так получили большой набор кандидатов на название блюда с указанием цены.

Убираем алкоголь

Выпускать в таком виде мы не могли из-за различного мусора и запрещённых товаров вроде алкоголя.

Потребовалось сделать ещё две модели. Для мусора — классификатор мусора, где мы улучшали бинарную классификацию «блюдо»/«не блюдо». А для алкоголя… классификатор алкоголя.

Датасет по алкоголю мы собрали ещё до этого по данным модерации товаров из «Чека». Но так как в некоторых ресторанах довольно редкие вина или виски, его оказалось недостаточно. И чтобы его расширить, взяли прайс-листы крупных оптовых поставщиков алкоголя.

Раскладываем на категории

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

Как и с другими задачами — разметили данные и итеративно улучшали метрики и «внешний вид» меню.

Добавляем фото

Отдельная задача — выбор фотографии для блюда.

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

Все выкаченные фотографии отфильтровали по размеру: слишком маленькие и слишком большие. А также профильтровали на количество цветов, чтобы убрать плейсхолдеры.

Подаём на бой

После этого мы залили красивые меню в Личные кабинеты и сделали рассылку, что всё уже готово — компаниям осталось только проверить цены и актуальность.

Грузинская кухня — приятный способ приобретения антитела

Подход сработал. За несколько дней компании загрузили ещё примерно такое же количество своих прайсов, сколько мы заранее подготовили. А предмет нашей отдельной гордости и показатель популярности этого раздела — владельцы киоска в спальном районе Новосибирска добавили прайс на беляши.

0
7 комментариев
Написать комментарий...
Жан Грудачёв

Команда, вы супер! Гениально, мощно, очень быстро. Лучшее MVP, что я знаю из такого обьема 🔥

Ответить
Развернуть ветку
Жан Грудачёв

Есть ли официально сам 2Gis на VC? Узнал о посте через ваш канал: https://www.instagram.com/p/CHxI2xgFs4c

Ответить
Развернуть ветку
Sergey Kolomenkin
Автор

Пока нет. Прощупываем )

Ответить
Развернуть ветку
Сергей Ерёмин

Как вам идея добавить в 2 гис функционал аналога приложения Citizen, чтобы пользователи могли добавлять контент с мест событий?

Ответить
Развернуть ветку
Sergey Kolomenkin
Автор

Пока не планировали.

Ответить
Развернуть ветку
Сергей Ерёмин

Планировали добавить функции трекинга, чтобы можно было увидеть пройденный маршрут?

Ответить
Развернуть ветку
Sergey Kolomenkin
Автор

В ближайших планах не планировали.

Ответить
Развернуть ветку
4 комментария
Раскрывать всегда