В мобильном приложении Тинькофф есть спецпредложения со скидками, и хочется находить по нетривиальному запросу самые лучшие. Например, по запросу где купить хороший велосипед со скидкой 30% кешбэка и до 60к выдать наилучшее спецпредложение (см. короткую презентацию).
В каталоге data
присутствуют два xlsx
файла current_offers
и offers_with_categories
. В файле current_offers
содержаться данные по спецпредложениям (на 23 января 2019 года) со следующими полями:
- NAME - название организации, предоставляющее спецпредложение
- WEB - веб-сайт организации
- CASH_BACK_HEIGHT - кешбэк в процентах (если значение больше 100, то в рублях)
- TRANCHE_STMT_COUNT - длина рассрочки в месяцах
- OFFER_TYPE - тип спецпредложения (STANDARD - кешбэк и SPECIAL_CREDIT - рассрочка)
- ADVERT_TEXT - описание спецпредложения
В файле offers_with_categories
:
- OFFER_ID - идентификационный номер спецпредложения
- NAME - название организации, предоставляющее спецпредложение
- CASH_BACK_HEIGHT - кешбэк в процентах (если значение больше 100, то в рублях)
- TRANCHE_STMT_COUNT - длина рассрочки в месяцах
- OFFER_TYPE - тип спецпредложения (STANDARD или SPECIAL_CREDIT)
- ADVERT_TEXT - описание спецпредложения
- CATEGORY_NAME - категория спецпредложения
- Данные
- Содержание
- План решения
или путь дата сатаниста - Архитектура
- Полезные ссылки
- Процесс работы
- Что ещё можно сделать
- Пререквизиты
- Установка
- Использование
Поскольку задача звучит как написать свой гугл за неделю, введём некоторые ограничения, а именно будем искать только среди двух категориях Спорт и Еда и продукты и по ограниченному количеству услуг и товаров.
Из сырых данных нужно отфильтровать данные по категориям и создать некоторые соответствия между спецпредложениями и веб-сайтами.
Для каждого спецпредложения напарсить услуги и товары с веб-сайтов.
Запрос где купить хороший велосипед со скидкой 30% кешбэка и до 60к скорее относится к категории Спорт, а не Еда и продукты, поэтому нужно понимать какой запрос к чему относится.
Из запроса где купить хороший велосипед со скидкой 30% кешбэка и до 60к нас скорее интересует велосипед, чем купить (хотя вдруг кто-то хочет продать, но это уже другая задача :) А также 30% кешбэк и до 60к.
На основе важных слов (слотов) сформировать запрос в поисковик и вуаля, получить лучшее спецпредложение!
- Несколько человек занимаются парсингом веб-сайтов из спецпредложений для сбора данных (Data) вида
(НАЗВАНИЕ_ТОВАРА, ОПИСАНИЕ, ДРУГИЕ_АТРИБУТЫ)
для классификации и индексации. - Один человек отвечает за часть по обработке естественного языка (Natural Language Processing (NLP)), как классификация намерений (Intent Classification) и заполнение слотов (Slotfilling).
- Один человек отвечает за построение индекса (Indexing) и ранжирования (Ranking).
Для парсинга на python'е:
Для slotfilling'а:
Для ранжирования с помощью библиотеки Elasticsearch.
- Elasticsearch tutorial for beginners using Python
- Getting started with ElasticSearch-Python :: Part Two
- Python + Elasticsearch. First steps.
- Twitter Sentiment Analysis – Python, Docker, Elasticsearch, Kibana
- Elastic docs
Познакомились с командой и задачей. Распределили роли следующим образом:
- @KinGelaim, @ArtemBoyarintsev и Елизавета парсят веб-сайты
- @ameyuuno занимается классификацией намерений, индексацией и ранжированием
- @NRshka занимается заполнением слотов
План на следующий день:
- распарсить один сайт и посмотреть на данные (@KinGelaim и @ArtemBoyarintsev)
- решить как генерить данные для классификации и слотфиллинга (@ameyuuno и @NRshka)
- зафиксировать слоты (@NRshka)
- накидать пайплайн всего процесса поиска, от запроса до выдачи
Формат результата парсинга веб-сайтов:
Path
: полный путь к продукту (велосипеды ~ горный ~ Merida 500)Description
: некоторое описание, если оно естьPrice
: стоимость продукта
Решили зафиксировать следующие слоты:
Item
: наименования товара (велосипед)Attributes
: атрибуты (горный, детский, и т. д.)Price_[from, to]
: стоимость как интервал от и доinstallment
(под вопросом): рассрочкаis
: есть или нетperiod
: на какое времяpercent
: процент
Cashback
: значение кешбэка
Написаны, но не протестированы слоты на правилах
Item
: наименования товара (велосипед)Attributes
: атрибуты (горный, детский, и т. д.)Price_[from, to]
: стоимость как интервал от и доCashback
: значение кешбэка
План на следующий день:
- нагенерить данные для классификации и обучить простой классификатор (@ameyuuno)
- протестировать работоспособность слотфиллера (@NRshka)
- продолжить парсить сайты (@KinGelaim и @ArtemBoyarintsev)
Зафиксированы сайты для спецпредложений для каждой из категорий Спорт sport и Еда и товары food. Эти файлы получены с помощью merge. Также создан файл с пересекающимися спецпредложениями по категориям intersect.
Появились данные по Спорт и Еда и продукты по парсингу. Результатом парсинга является два файла data.xls
и meta.xls
и помещаются в папку с названием как сайт у спецпредложения. В data.xls
хранится следующая информация:
- Название: полный путь к продукту (велосипеды ~ горный ~ Merida 500)
- Описание: некоторое описание, если оно есть
- Цена: стоимость продукта
В meta.xls
хранится следующая информация:
- NAME - название организации, предоставляющее спецпредложение
- WEB - веб-сайт организации
- CASH_BACK_HEIGHT - кешбэк в процентах (если значение больше 100, то в рублях)
- TRANCHE_STMT_COUNT - длина рассрочки в месяцах
- OFFER_TYPE - тип спецпредложения (STANDART - кешбэк и SPECIAL_CREDIT - рассрочка)
- ADVERT_TEXT - описание спецпредложения
Первый прототип для классификации написан и проверен на простых примерах. На спарсенных трёх сайтах (два Спорт и один Еда и товары). Возникла проблема дисбаланса в сторону класса Еда и товары.
Также написан первый прототип для слотфиллинга и проверен на примерах, где товаром является велосипед. Для проверки на других примерах нужны названия всех имеющихся товаров и прочие атрибуты. Появится после парсинга сайтов.
План на следующий день:
- прогнать некторые примеры через классификатор и слотфиллер
Проверили классификатор и слотфиллер. Классификатору требуется больше данных. Слотфиллеру нужен словарь значений слотов и больше правил. Также добавили простой постпроцессинг к результату слотфиллера, например, все числа вида 60к
или 60 000
переводятся в 60000
для поисковика.
Парсить сайты стало утомительно. Решили остановиться на том, что успели.
Возможно, стоит перейти к нейросетевому слотфиллеру. Сгенерить данные на основе спарсенных слотов.
Елизавета предложила использовать сайт e-katalog для поиска товаров. По выдаче товаров среди веб-сайтов искать спецпредложения.
План на следующий день:
- остановиться парсить сайты (@KinGelaim и @ArtemBoyarintsev)
- обернуть текущую реализацию слотфиллинга и запушить в общий пайплан (@NRshka)
- добавить модуль постпроцессинга слотов в корректный вид для поисковика (@NRshka)
- начать писать поисковик (@ameyuuno)
- попробовать сгенерить данные для нейросетевого слотфиллера и обучить его (@NRshka)
Написан первый прототип для поисковика. Осталось скормить ему данные для индексации.
Слотфиллинг обёрнут и добавлен в общий пайплайн. Нормализация добавлена.
Слотфиллер не выделяет слот велосипед и положил всё в атрибуты.
>>> sf.fill('где купить велосипед', '0')
{'Cashback': 'NaN',
'Price': {'From': 0, 'To': 0},
'Attributes': ' [где купить велосипед] ',
'Item': 'NaN'}
>>> sf.fill('велосипед за 300', '0')
{'Cashback': 'NaN',
'Price': {'From': 0, 'To': 0},
'Attributes': ' велосипед за 300 ',
'Item': 'велосипед'}
Интентклассифаер неправильно отнёс к классу:
>>> ic.predict('где купить бургеры')
'sport'
>>> ic.predict('где купить еду')
'sport'
Скорее всего проблема в недостатке значений слотов. Ждём данные.
Собрали значения слотов (Спорт и Еда и продукты).
План на следующий день:
- написать постпроцессор для выдачи ранкера (он работает так, что индексирует все товары для каждого сайта; нужно будет сгруппировать все товары из одного сайта в один результат и выдать уже список спецпредложений) (@ameyuuno)
- обернуть ранкер и запушить в мастер (@ameyuuno)
- добавить словарь для слотфиллинга и протестировать его (@NRshka)
Нейросетевой слотфиллинг не взлетел. Возможно мало данных.
Добавлены новые поля для запросов в эластике. Финальная структура json
:
{
'Item': 'name of item',
'Attributes': 'description',
'Price': 'price',
'Offer': 'offer name',
'Web': 'web site of offer',
'Cashback': 'cashback',
'Period': 'period of credit',
'Offer_type': 'type offer,
'Advert_text': 'advert text'
}
План на следующий день:
- написать программу, которая заполнит файл
preset.json
для инициализации ранкера (@KinGelaim) - сделать первый запуск всего проекта
Собрали датасет и проиндексировали. Сделали первый запуск. Появились проблемы со слотфиллингом.
>>> sf.fill('где купить велосипед с 30кб и до 60к', intent)
{'Cashback': '30',
'Price from': 0,
'Price to': 60000,
'Attributes': 'где купить велосипед с и к',
'Item': 'велосипед с и'}
Из-за того, что выделен слот 'Item': 'велосипед с и'
, поисковик ничего не находит. Нужно подумать как решить эту проблему.
Также надена проблема с добавлением прилагательных в название товара:
sf.fill('где купить горный велосипед', intent)
{'Cashback': 0,
'Price_from': 0,
'Price_to': 999999999,
'Attributes': 'горный велосипед',
'Item': 'горный велосипед'
}
Из Item
нужно удалять прилагательные, а из Attributes
нужно удалять сам Item
.
@KinGelaim начал писать веб-сервис.
Сделали первый запуск поисковика. Выявились некоторые проблемы с классификатором (бородинский хлеб относит к Спорту) и слотфиллингом (не выделяет слот сноуборд). Нужно подумать как улучшить.
Планы на следующий день:
- дописать веб-сервис (@KinGelaim)
- почистить словарь для слотов
Добавлен слот Offer_type
- тип спецпредложения (с рассрочкой или без).
Протестирована система.
Планы на следующий день:
- подготовиться к защите проекта
- Улучшить классификатор намерений (некоторые фразы относит к неправильному классу)
- Улучшить слотфиллинг (плохо выделяем незнакомые слова)
git
python3
pip3
virtualenv
docker
docker-compose
Клониреум репозиторий
git clone https://github.com/kbrodt/offer_search.git
Заходим в проект
cd offer_search
Запускаем сервер с эластиком и веб сервисом
docker-compose up
Откроем новую вкладку в терминале и создадим виртуальное окружение с Python3.6
virtualenv vos
Зайдём в только что созданное виртуальное окружение
source vos/bin/activate
Установим необходимые библиотеки для питона
pip install -r requirements.txt
python run.py
Заходим в http://0.0.0.0:8080 и вуаля.