/offer_search

Project for CompTech 2019 "Offer search"

Primary LanguageJavaScript

Поиск по спецпредложениям

В мобильном приложении Тинькофф есть спецпредложения со скидками, и хочется находить по нетривиальному запросу самые лучшие. Например, по запросу где купить хороший велосипед со скидкой 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 - категория спецпредложения

Содержание

План решения или путь дата сатаниста

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

Препроцессинг

Из сырых данных нужно отфильтровать данные по категориям и создать некоторые соответствия между спецпредложениями и веб-сайтами.

Парсинг (Data mining)

Для каждого спецпредложения напарсить услуги и товары с веб-сайтов.

Определение намерения (Intent classification)

Запрос где купить хороший велосипед со скидкой 30% кешбэка и до 60к скорее относится к категории Спорт, а не Еда и продукты, поэтому нужно понимать какой запрос к чему относится.

кек

Заполнение слотов (Slotfilling)

Из запроса где купить хороший велосипед со скидкой 30% кешбэка и до 60к нас скорее интересует велосипед, чем купить (хотя вдруг кто-то хочет продать, но это уже другая задача :) А также 30% кешбэк и до 60к.

Ранжирование (Ranking)

На основе важных слов (слотов) сформировать запрос в поисковик и вуаля, получить лучшее спецпредложение!

Архитектура

architecture

  • Несколько человек занимаются парсингом веб-сайтов из спецпредложений для сбора данных (Data) вида (НАЗВАНИЕ_ТОВАРА, ОПИСАНИЕ, ДРУГИЕ_АТРИБУТЫ) для классификации и индексации.
  • Один человек отвечает за часть по обработке естественного языка (Natural Language Processing (NLP)), как классификация намерений (Intent Classification) и заполнение слотов (Slotfilling).
  • Один человек отвечает за построение индекса (Indexing) и ранжирования (Ranking).

Полезные ссылки

Для парсинга на python'е:

Для slotfilling'а:

Для ранжирования с помощью библиотеки Elasticsearch.

Процесс работы

День 1. 26.1.2019

Познакомились с командой и задачей. Распределили роли следующим образом:

  • @KinGelaim, @ArtemBoyarintsev и Елизавета парсят веб-сайты
  • @ameyuuno занимается классификацией намерений, индексацией и ранжированием
  • @NRshka занимается заполнением слотов

План на следующий день:

  • распарсить один сайт и посмотреть на данные (@KinGelaim и @ArtemBoyarintsev)
  • решить как генерить данные для классификации и слотфиллинга (@ameyuuno и @NRshka)
  • зафиксировать слоты (@NRshka)
  • накидать пайплайн всего процесса поиска, от запроса до выдачи

День 2. 27.1.2019

Формат результата парсинга веб-сайтов:

  • 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)

День 3. 28.1.2019

Зафиксированы сайты для спецпредложений для каждой из категорий Спорт 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 - описание спецпредложения

Первый прототип для классификации написан и проверен на простых примерах. На спарсенных трёх сайтах (два Спорт и один Еда и товары). Возникла проблема дисбаланса в сторону класса Еда и товары.

Также написан первый прототип для слотфиллинга и проверен на примерах, где товаром является велосипед. Для проверки на других примерах нужны названия всех имеющихся товаров и прочие атрибуты. Появится после парсинга сайтов.

План на следующий день:

  • прогнать некторые примеры через классификатор и слотфиллер

День 4. 29.1.2019

Проверили классификатор и слотфиллер. Классификатору требуется больше данных. Слотфиллеру нужен словарь значений слотов и больше правил. Также добавили простой постпроцессинг к результату слотфиллера, например, все числа вида 60к или 60 000 переводятся в 60000 для поисковика.

Парсить сайты стало утомительно. Решили остановиться на том, что успели.

Возможно, стоит перейти к нейросетевому слотфиллеру. Сгенерить данные на основе спарсенных слотов.

Елизавета предложила использовать сайт e-katalog для поиска товаров. По выдаче товаров среди веб-сайтов искать спецпредложения.

План на следующий день:

  • остановиться парсить сайты (@KinGelaim и @ArtemBoyarintsev)
  • обернуть текущую реализацию слотфиллинга и запушить в общий пайплан (@NRshka)
  • добавить модуль постпроцессинга слотов в корректный вид для поисковика (@NRshka)
  • начать писать поисковик (@ameyuuno)
  • попробовать сгенерить данные для нейросетевого слотфиллера и обучить его (@NRshka)

День 5. 30.1.2019

Написан первый прототип для поисковика. Осталось скормить ему данные для индексации.

Слотфиллинг обёрнут и добавлен в общий пайплайн. Нормализация добавлена.

Слотфиллер не выделяет слот велосипед и положил всё в атрибуты.

>>> 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)

День 6. 31.1.2019

Нейросетевой слотфиллинг не взлетел. Возможно мало данных.

Добавлены новые поля для запросов в эластике. Финальная структура 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)
  • сделать первый запуск всего проекта

День 7. 1.2.2019

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

>>> 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)
  • почистить словарь для слотов

День 8. 2.2.2019

Добавлен слот Offer_type - тип спецпредложения (с рассрочкой или без).

Протестирована система.

Планы на следующий день:

  • подготовиться к защите проекта

Что ещё можно сделать

  1. Улучшить классификатор намерений (некоторые фразы относит к неправильному классу)
  2. Улучшить слотфиллинг (плохо выделяем незнакомые слова)

Пререквизиты

  • git
  • python3
  • pip3
  • virtualenv
  • docker
  • docker-compose

Установка

Linux

Клониреум репозиторий

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 и вуаля.