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

В этом году мы хотели помочь еще большему количеству людей, поэтому моя команда решила создать программу, которая могла бы рекомендовать костюм пользователю по SMS. Кто-то предложил использовать смайлики в качестве входных данных, и нам всем понравилась эта идея. Таким образом, у нас было смутное представление о системе, которую мы должны были построить: что-то, что могло бы сопоставить строку смайликов с костюмом на Хэллоуин.

смайлики в костюмы?

Есть несколько возможных решений этой проблемы, у каждого из которых есть свои плюсы и минусы. Одним из простых решений было бы классифицировать смайлики вручную (например, «😂» можно поместить в «забавное» ведро), аналогичным образом классифицировать костюм вручную, а затем рекомендовать случайный костюм, соответствующий классу. Это было бы очень легко реализовать, но это может потребовать большого количества ручного труда для определения классов, особенно если мы хотим разрешить ввод более одного смайлика.

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

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

Естественно, мы выбрали третий вариант.

Введите word2vec

Чтобы сделать простой прототип, нам нужен был способ извлечь семантическое значение из списка слов и сравнить его значение со списком смайликов. К счастью, что-то подобное можно сделать с помощью модели word2vec.

word2vec - это модель машинного обучения, впервые разработанная Google, которая изучает семантические значения слов. Это обученная модель нейронной сети, которая пытается предсказать данное пропущенное слово, учитывая окно окружающих слов. Модель должна знать, где слова «подходят», а где нет, и изучение числового представления слова помогает в решении этой задачи.

Вы можете загрузить предварительно обученную модель word2vec, которая может преобразовывать слово в 300-мерный вектор с плавающей запятой. Например:

Как видите, слово «страшно» преобразовано в серию цифр. Эти 300 чисел кодируют контекстное значение слова «страшно». Другими словами, эти 300 чисел описывают место слова «scary» в семантическом ландшафте английского языка. (Довольно аккуратное) следствие этого состоит в том, что если вы думаете об этих 300 числах как о точке на 300-мерном графике, синонимы слова «страшно» будут ближе к этой точке, чем другие слова! Итак, если мы сможем аналогичным образом закодировать смайлы, возможно, мы сможем найти смайлики и костюмы, которые являются «синонимами» друг друга, и порекомендовать их.

Остальные части

Удивительно, но модель смайликов, подобная word2vec, уже существует, и называется она emoji2vec (в каком мире мы живем). Используя эту модель, мы можем закодировать смайлик в 300-мерный вектор для использования с word2vec. Это был момент, когда мы поняли, что действительно можем это сделать.

Используя word2vec и emoji2vec, мы смогли перевести как слова, так и смайлы в общее семантическое пространство. Следующий шаг - сопоставить их вместе. Этого можно достичь, вычислив евклидово расстояние между представлением входных смайлов и представлением всех костюмов и выбрав костюм с наименьшим расстоянием. Чтобы вычислить это быстро, я использовал дерево k-d для хранения векторов, которое может запрашивать ближайшую точку за время O (log n).

Внимательные читатели, возможно, заметили одну деталь, которую я пропустил: если word2vec работает с одним словом за раз, а emoji2vec работает с одним смайликом за раз, как это может может быть расширен, чтобы можно было использовать костюмы из нескольких слов и вводить несколько смайлов? Мы можем использовать word2vec и emoji2vec для кодирования каждого из различных входных данных, но нам нужен способ свернуть их в один вектор, чтобы мы могли выполнять поиск. Подход, который я выбрал, заключался в использовании среднего векторного. Следовательно, модель действительно сопоставляет средний вход с ближайшим средним костюмом (где "средний" костюм в этом смысле означает среднее значение всех слов в описании данный костюм). Есть более изощренные подходы, но это показалось хорошим местом для начала.

Доказательство концепции

Мы знали, что в конечном итоге захотим составить собственный список костюмов, но в качестве доказательства концепции модели я написал скрипт для извлечения названий и описаний костюмов с веб-сайта костюмов на Хэллоуин, собрав около 900 костюмов. Первый шаг, который я сделал, - это токенизация слов в описании. Это избавило от знаков препинания и заглавных букв и превратило описание в список слов. Затем каждое слово в описании пропускалось через word2vec, чтобы получить матрицу с 300 столбцами на слово. Затем эта матрица была свернута в вектор длиной 300 посредством векторного усреднения.

Этот процесс повторялся для всех костюмов, поэтому в конце с каждым костюмом был связан 300-мерный вектор. Это были очки, с которыми мы сравнивали.

На рисунке ниже показан процесс для примера строки эмодзи «👽🤖». На первом этапе каждый смайлик («👽» и «🤖») отдельно передается через emoji2vec, и создается матрица размером 2 x 300. Затем эта матрица сворачивается в вектор размером 1 x 300, взяв среднее значение по строкам.

Затем модель берет этот вектор и вычисляет ближайшую точку в списке всех костюмов. Затем он ищет название костюма, связанное с этим костюмом, и в этом случае возвращается «Трансформеры». Трансформер - это буквально инопланетный робот, так что мы можем что-то понять с этим подходом! Ниже приведены еще несколько примеров выходных данных.

😨😱 -> "Saw" Movie Costume
💪 -> Inflatable Muscles Costume
💀💤 -> Giant Skull Costume

Создание рабочей модели смайлика

Затем мы попросили сотрудников помочь с новыми идеями костюмов, и в итоге получили список из более чем 300 костюмов! Для каждого костюма мы также попросили список тегов. Эти теги служили «описанием» костюма для модели, а теги содержали любые слова, которые приходили на ум для данного названия костюма. Например:

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

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

Возвращение к среднему значению

У меня было подозрение, что виной всему было векторное усреднение всех слов в описании костюмов. В описаниях костюмов было много слов, часто охватывающих самые разные темы. Таким образом, чем больше слов в описании, тем больше усреднение «ограничивает» результирующий вектор, заставляя его вести себя больше как средний вектор.

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

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

При обработке естественного языка один из способов добиться этого - взвесить каждый термин, используя tf-idf или частоту термина, обратную документу. Идея состоит в том, что более редкие слова более характерны для документа. Таким образом, если в описании Гондольер используются слова человек и лодка, вычисляется распространенность слов человек и лодка среди других описаний. Поскольку лодка встречается реже среди других описаний костюмов, tf-idf может дать лодке более высокий вес, чем человек в описании для гондольера.

Полученные результаты

Взвешивание тегов с описанием костюмов помогло, и теперь рекомендации стали намного разнообразнее. Ниже приведен пример того, что происходит при запросе с «🎸🏈😎»:

Заключение

Исследования в области машинного обучения быстро развиваются. Этот проект был бы передовым исследованием пять лет назад, а теперь его можно реализовать за несколько дней работы. Будет интересно увидеть, что будет просто собрать через несколько лет.

Вы можете написать 68848, пока не закончится Хэллоуин, и посмотреть, что вам рекомендует костюм Warby Parker Halloween! 🎉