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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Подаём на бой

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

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

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

3535
7 комментариев

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

4
Ответить

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

2
Ответить

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

2
Ответить

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

Ответить

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

Ответить