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



Постановка задачи


Когда наша команда встретилась перед началом хакатона, нас смущало отсутствие чёткой постановки задачи или пожеланий. За неимением лучшего, мы начали генерировать идеи сами:


  • система обработки обращений клиентов;
  • рекомендательная система;
  • алгоритм для навигации по множеству товаров;
  • система сбора данных о товарах на других сайтах.

Мы мечтали о доказуемом увеличении бизнесовых метрик: просмотров, конверсии, среднего чека и лояльности. За несколько дней мы попытались вытащить информацию из инсайдера, но в результате только больше запутались. Когда мы приехали в офис М.Видео в субботу, у нас в голове было несколько вариантов того, что можно сделать — и ни один в итоге не совпал с тем, о чём нас действительно попросили.


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


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


Извлечение сжатых описаний товаров из отзывов


Первое из двух заданий хакатона была сформулировано как задача автоматической суммаризации текста. Нужно было создать алгоритм, который по множеству отзывов покупателей на товар генерирует один синтетический отзыв, содержащий самую важную информацию о товаре. Формальных требований к синтетическим отзывам не было, так что мы сами придумали свои хотелки:


  1. Синтетический отзыв состоит из отдельных утверждений о товаре (нужна же какая-то структура!);
  2. От каждого утверждения можно перейти к тем отзывам, на основе которых оно создано (чтобы им было проще поверить);
  3. Каждое утверждение должно быть по возможности основано более чем на одном реальном отзыве (любим достоверную информацию);
  4. Утверждения должны показывать, чем товар отличается от своих аналогов (иначе они бесполезны).

Из этих требований уже можно перейти к общей схеме алгоритма:


  1. Предобработка текста отзывов. Разбили каждый на слова, и каждое слово привели к нормальной форме (глаголы — к инфинитиву, существительные — к именительному падежу единственного числа, и т.п.) средствами pymorphy2. Это нужно, чтобы более корректно посчитать слова. Кроме того, мы ввели несколько специальных слов: последовательности цифр заменили на <ЧИСЛО>, смайлики с положительной и отрицательной окраской заменили на <SMILE_POS> и <SMILE_NEG>.
  2. Взвешивание слов. Посчитали, сколько раз каждое нормализованное слово встречается в отзывах на каждый товар. Каждому вхождению слова в отзыв присвоили вес, обратный доле отзывов, в которых оно используется, среди всех товаров данной категории. Таким образом мы постарались уменьшить влияние малоинформативных слов. Например, слово "НАУШНИКИ" в отзыве на наушники информационной нагрузки особо не несёт. Но словам, которые встречаются только в отзыве на данный товар, мы тоже присвоили вес 0, т.к. обычно это очень специфические понятия, по которым сложно сравнивать. Например, слово "JBLAWAREBLKI" в отзыве на те же наушники тоже не несёт смысла — это просто их название. Таким образом, мы исполнили хотелку (4).
  3. Нарезка каждого отзыва на утверждения. Для этого мы разбили его на предложения (просто по знакам препинания). Предложения разбили на всевозможные кусочки (n-граммы) по 2 и 3 слова. Утверждениями считали каждое предложение и каждую n-грамму.
  4. Перевод утверждений в числовую форму. Для каждого утверждения мы нашли среднее вложение входящих в него слов, взвешенное по весам из пункта 3. Вложение — это вектор из 100 чисел, выгруженных из обученной кем-то модели word2vec. Главное свойство вложений в том, что у похожих по смыслу слов вложения тоже обычно похожи, и наоборот. А мы посчитали "средневзвешенное слово" для каждого утверждения, надеясь, что оно как-то будет передавать смысл.
  5. Группировка. Для каждого продукта мы на всех векторизованных утверждениях об этом продукте запустили алгоритм кластеризации DBSCAN. Другими словами, мы нашли группы (кластеры) из утверждений, похожих на друг друга (т.е. описываемых похожими векторами-вложениями). Каждый найденный кластер по идее должен содержать какой-то важный факт о продукте. Утверждения, не вошедшие ни в один кластер (DBSCAN один из немногих алгоритмов, которые так могут), мы исключили из рассмотрения.
  6. Ранжирование кластеров. Для каждого кластера мы рассчитали его рейтинг — сумму весов слов всех входящих в него утверждений. К рейтингу кластеров, которые содержат утверждения только из одного отзыва, мы применили понижающий коэффициент, чтобы выполнить хотелку (3). Для каждого продукта мы взяли по 5 кластеров с максимальным рейтингом.
  7. Ранжирование утверждений внутри кластеров. В каждом из выбранных кластеров осталось найти то единственное утверждение, которое описывает его наилучшим образом. Для этого мы рассчитали рейтинг каждого утверждения в кластере как средний вес слов в этом утверждении, делённый на среднее расстояние от его вложения до вложений других утверждений в кластере. 1. Числитель отвечает за то, чтобы утверждение было информативным, а знаменатель — за то, чтобы оно достаточно хорошо соответствовало всему кластеру по смыслу. И для каждого из выбранных кластеров в синтетический отзыв мы вынесли утверждение с максимальным рейтингом, удовлетворив хотелки (1) и (2).

В итоге получили примерно такие синтетические отзывы, как этот (на телевизор):


  • По горизонтали углы обзора великолепные
  • Хорошее качество изображения
  • Картинка отличная
  • Купили на кухню
  • Звук хороший

По первому же примеру видны плюсы и минусы нашего подхода. Плюс — что отзыв читабельный. Минус — например, что 2 и 3 утверждения можно было бы объединить, но наша кластеризация оказалась слишком детальной. К сожалению, никакого способа проверить качество генерации синтетических отзывов, кроме метода пристального взгляда, мы не придумали. А метод пристального (но беглого) взгляда показал, что отзывам недостаёт структуры, но в целом выглядят адекватно.



Извлечение важных свойств товаров из отзывов


Другая ветка работы была связана с эмоциональной окраской (тональностью) отзывов. При большей части отзывов была прикреплена оценка товара от 1 до 5. Ещё на отборочном туре мы решали задачу определения тональности — прогнозирования её по тексту отзыва. Для этого каждое нормализованное слово было превращено в бинарную переменную, и на них была обучена линейная регрессия. Большой положительный коэффициент перед словом в такой модели означает, что оно обычно встречается в позитивных отзывах, а отрицательный — что в негативных. Чем больше коэффициент (по модулю), тем сильнее влияет слово на оценку товара.


Для каждого товара мы построили такую регрессию на всех отзывах на него (если их достаточно много. Отсортировав коэффициенты, можно найти самые "позитивные" и "негативные" слова об этом товаре. Среди них, правда, много неинформативных, таких, как "отличный" или "отвратительный". Чтобы их исключить, пришлось составить специальный чёрный список. Для этого мы просто построили модель тональности на абсолютно всех продуктах, и вытащили самые значимые коэффициенты из неё. Они как раз в основном соответствовали неинформативным оценочным суждениям, типа "хороший/плохой".


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



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


Поиск важных признаков по поведению пользователей


Кроме отзывов, у нас была информация о просмотрах товаров и добавлении их в корзину. Мы решили выяснить, какими свойствами продукта определяется конверсия просмотров в покупки. Для любого продукта у нас были характеристики из справочника: габариты, разрешение экрана, марка процессора, и так далее. Мы преобразовали их в бинарные признаки. На них для каждой категории товаров построили логистическую регрессию, предсказывающую вероятность добавления товара в корзину. Полученные коэффициенты при свойствах товаров тоже можно интерпретировать как важность этих свойств при принятии решения о покупке. Дальше мы поступили с ними примерно так же, как со словами в отзывах: перевзвесили важности по частоте, и отобрали самый важный признак из каждой смысловой группы. В итоге для каждого товара мы получили самые важные его характеристики с точки потребительского поведения, но ни сопоставить их с результатами анализа отзывов, ни тем более объединить с ними мы не успели.


Глубокомысленные выводы


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



С точки зрения организации хакатона не хватило только объявленных с самого начала критериев качества, по которым бы сравнивались решения. Максимизировать неизвестную функцию — не самое продуктивное занятие. Ещё не хватило понятной постановки задачи до начала хакатона — фразы “агрегация отзывов на сайте” и “помощник продавца по подбору товаров в магазине” можно было трактовать как угодно.


Задним числом уже понятно, что даже в таких условиях нашей команде не стоило бросаться в разные стороны, как лебедь, рак и щука. Стоило сразу определить, как должен выглядеть то идеальное описание товара, которое мы хотим получить на выходе. Мы могли бы с первых часов хакатона сконцентрироваться на каком-нибудь одном направлении (не столь важно, каком именно), и к вечеру иметь работающий прототип. Тогда за второй день совместной отладки мы бы довели его до уже неплохо работающего продукта.


В любом случае, мы получили код, который можно переиспользовать, большое количество мерча, и, главное, опыт. Для решения простых задач магазина понадобилось применить едва ли не половину вводного курса по машинному обучение. Но решили всё, как обычно, не познания в программировании и математике, а организационные способности.

Комментарии (2)


  1. Roosh
    23.10.2017 08:42

    Кто-нибудь пробовал какие-нибудь GAN'ы делать для такой задачи? А если да, то что получалось?


    1. cointegrated Автор
      23.10.2017 10:24

      Насколько я знаю, GAN'ы пока плохо с текстом работают. В этом посте (правда, год назад) Ян Гудфеллоу популярно объясняет, почему. Может быть, с тех пор что-то сдвинулось, но вряд ли сильно