- PVSM.RU - https://www.pvsm.ru -
Одна из больших задач приложения для хранения и анализа покупок — поиск одинаковых или очень близких продуктов в базе данных, где собраны разномастные и непонятные наименования продуктов, полученные из чеков. Есть два вида входного запроса:
Запросы первого вида как правило исходят из продуктов в самом чеке, когда пользователю нужно подыскать продукты подешевле. Наша задача заключается в том, чтобы подобрать максимально похожий аналог товара из чека в других магазинах поблизости. Здесь важно подобрать наиболее соответствующую марку продукта и по возможности объём.
Запрос второго вида — это простой запрос пользователя на поиск определённого продукта в ближайшем магазине. Запрос может быть очень общий, не уникально описывающий продукт. Здесь возможны небольшие отклонения от запроса. Например, если пользователь ищет молоко 3.2%, а у нас в базе только молоко 2.5%, то мы все равно хотим показать хотя бы этот результат.
Информация в чеке о продукте далеко не идеальна. В ней много не всегда понятных сокращений, грамматических ошибок, опечаток, разнообразных транслитов, латинских букв посреди кириллицы и наборов символов, имеющих смысл только для внутренней организации в определенном магазине.
Например, детское яблочно-грушевое пюре с творогом запросто может быть записано в чеке вот так:
Elasticsearch — достаточно популярная и доступная технология для имплементации поиска. Это поисковый движок с JSON REST API, использующий Lucene и написанный на Java. Основными преимуществами Elastic’а являются скорость, масштабируемость и отказоустойчивость. Подобные движки используются при сложном поиске по базе документов. Например, поиск с учетом морфологии языка или поиск по geo координатам.
Чтобы понять, как можно улучшить поиск, надо разобрать систему поиска на составляющие настраиваемые компоненты. Для нашего случая структура системы выглядит так.
Из устройства поиска можно выделить три отдельно настраиваемых компонента, в каждом из которых можно выделить свои пути и способы улучшения.
Далее рассмотрим каждый компонент в отдельности и разберём конкретные настройки параметров, которые помогли улучшить поиск в случае с названиями продуктов.
Чтобы понять, что мы можем улучшить в запросе, приведем пример изначального запроса.
{
"query": {
"multi_match": {
"query": "кетчуп хайнц 105г",
"type": "most_fields",
"fields": ["name"],
"minimum_should_match": "70%"
}
},
“size”: 100,
“min_score”: 15
}
Мы используем тип запроса most_fields, так как предвидим необходимость в комбинации нескольких анализаторов для поля “название продукта”. Данный тип запроса позволяет комбинировать результаты поиска по разным атрибутам объекта, содержащих один и тот же текст, проанализированный разными способами. Альтернативой этому подходу являются использование запросов best_fields или cross_fields, но они не подходят для нашего случая, так как рассчитаны поиск среди разных атрибутов объекта (н-р: название и описание). Перед нами же стоит задача учесть разные аспекты одного атрибута — названия продукта.
Что можно настроить:
Анализатор анализирует входную строку в три этапа и на выходе выдает токены — поисковые единицы:
Сначала происходят изменения на уровне символов строки. Это может быть замена, удаление или добавление символов в строку. Затем в игру вступает tokenizer, который призван разделить строку на токены. Стандартный токенизатор разделяет строку на токены согласно знакам препинания. Последним этапом полученные токены фильтруются и обрабатываются.
Рассмотрим, какие вариации этапов стали полезными в нашем случае.
Модель релевантности нужна для того, чтобы определить в какой мере совпадение одного токена влияет на релевантность объекта из базы по отношению к запросу. Допустим, если в запросе и продукте из базы совпадает токен ‘для’ — это конечно, неплохо, но о соответствии продукта к запросу говорит мало. Таким образом, совпадение разных токенов несет различную ценность. Для того, чтобы это учитывать и нужна модель релевантности. Elastic предоставляет множество различных моделей. Если правда в них разобраться, то можно подобрать очень специфичную и подходящую под конкретный случай модель. Выбор может основываться на количестве часто употребляемых слов (по типу того же токена ‘для’), оценке важности длинных токенов (длиннее — значит лучше? Хуже? Не имеет значения?), какие результаты мы хотим видеть в конце и тд. Примерами моделей, которые предложены в Elastic’е могут стать TF-IDF (самая простая и понятная модель), Okapi BM25 [3] (улучшенный TF-IDF, модель по умолчанию), Divergence from randomness, Divergence from independence и тд. У каждой модели также имеются настраиваемые параметры. После нескольких экспериментов с моделью, лучший результат показала модель по умолчанию Okapi BM25, но с отличными от предустановленных параметрами.
По ходу работы с поиском стала доступна очень важная дополнительная информация о продукте — его категория. Подробнее о том, как мы реализовывали категоризацию, можно почитать в статье Как я понял, что ем много сладкого, или классификация товаров по чекам в приложении [4]. До этих пор мы основывали наш поиск только на сопоставлении наименований товаров, теперь стало возможно сравнивать категорию запроса и товаров в базе.
Возможные варианты использования этой информации — обязательное совпадение по полю категория (оформляется как фильтр результатов), дополнительное преимущество в виде очков товарам с совпадающей категорией (как в случае с цифрами) и сортировка результатов по категории (сначала совпадающая, затем все остальные). Для нашего случая последний вариант сработал лучше всего. Это связано с тем, что наш алгоритм категоризации слишком хорош, чтобы использовать второй способ, и недостаточно хорош, чтобы использовать первый. Мы достаточно уверены в алгоритме и хотим, чтобы совпадение по категории было большим преимуществом. В случае добавления дополнительных очков к скору (второй способ) товары с совпадающей категорией будут подниматься по списку, но все так же будут проигрывать некоторым товарам, которые больше совпали по названию. Однако первый способ нам тоже не подходит, так как ошибки в категоризации все-таки не исключены. Иногда запрос может содержать недостаточную информацию для корректного определения категории, или товаров в этой категории в ближайшем радиусе пользователя слишком мало. В этом случае мы все же хотим показать результаты с другой категорией, но все так же релевантные по названию продукта.
Второй способ также хорош тем, что ‘не портит’ скоры продуктов в результате поиска, и позволяет без препятствий продолжать использовать вычисляемый минимальный скор.
Конкретную имплементацию сортировки можно увидеть в финальном запросе.
Подбор финальной модели поиска включал в себя генерации различных поисковых систем, их оценка и сравнение. Чаще всего сравнение проходило по одному из параметров. Допустим в одном конкретном случае нам необходимо было вычислить лучший размер для токенизатора edgeNgram (то есть подобрать наиболее эффективный диапазон N). Для этого мы генерировали одинаковые поисковые системы с одним лишь различием в размере этого диапазона. После этого было возможно выявить лучшее значение для этого параметра.
Оценка моделей производилась с помощью метрики nDCG (normalized discounted cumulative gain) — метрики для оценивания качества ранжирования. В каждую поисковую модель отправлялись заранее установленные запросы, после чего по полученным результатам поиска рассчитывалась метрика nDCG.
Анализаторы, которые вошли в финальную модель:
В качестве модели релевантности была выбрана модель по умолчанию (Okapi — BM25) с параметром b = 0.5. Значение по умолчанию — 0.75. Этот параметр показывает в какой степени длина названия продукта нормализует tf (term frequency) переменную. Меньшее число в нашем случае работает лучше, так как длинное название очень часто не влияет на значимость отдельно взятого слова. То есть слово “шоколад” в названии “шоколад с дробленым фундуком в упаковке 25шт” не теряет своей ценности от того, что длина названия достаточно большая.
Финальный запрос выглядит так:
Экспериментальным путем была выявлена лучшая комбинация анализаторов и весов.
В результате сортировки в начало результатов выходят продукты с совпадающей категорией, а затем все остальные. Сортировка по количеству очков (скору) сохраняется внутри каждого подмножества.
Для простоты в данном запросе указан порог для скора в 15, хотя в нашей системе мы используем вычисляемый параметр, который был описан ранее.
Есть мысли по улучшению поиска путем решения одной из самых смущающих наш алгоритм проблем, которая заключается в существовании миллиона и одного различного способа сократить слово из 5 букв. Решать её можно путем первоначальной обработки названий с целью раскрыть сокращения. Один из способов решения — попытка сопоставления сокращенного названия из нашей базы с одним из названий из базы с “правильными” полными названиями. Это решение встречает свои определенные препятствия, но кажется многообещающим изменением.
Автор: almiradreamer
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/data-mining/302103
Ссылки в тексте:
[1] TF/IDF: https://en.wikipedia.org/wiki/Tf%E2%80%93idf
[2] ICU Analysis Plugin: https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu.html
[3] Okapi BM25: https://en.wikipedia.org/wiki/Okapi_BM25
[4] Как я понял, что ем много сладкого, или классификация товаров по чекам в приложении: https://habr.com/post/430216/
[5] Источник: https://habr.com/post/433070/?utm_campaign=433070
Нажмите здесь для печати.