Рынок подкастов значительно вырос за последние годы, при этом число слушателей во всем мире в последние годы увеличивалось на 20% ежегодно.

Стимулирует внедрение подкастов Spotify. За несколько коротких лет они стали бесспорными лидерами в подкастинге. Несмотря на то, что Spotify вступил в игру только в 2018 году, к концу 2021 года Spotify уже узурпировал Apple, многолетнего лидера в области подкастов, с более чем 28 миллионами ежемесячных слушателей подкастов.

Чтобы поддержать свои инвестиции в подкасты, Spotify работал над тем, чтобы сделать подкасты максимально удобными и доступными. От их универсального приложения для создания подкастов (Anchor) до API подкастов и новейшего поиска подкастов с поддержкой естественного языка.

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

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

Представьте, что мы хотели найти подкаст о здоровом питании в праздничные дни. Как бы мы это искали? Это может выглядеть примерно так:

Именно об этом говорится в подкасте. Его описание:

"Alex Straney chats to Dr. Preeya Alexander about how to stay healthy over Christmas and about her letter to patients."

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

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

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

Включить осмысленный поиск непросто, но если все сделано правильно, эффект может быть огромным. Как доказал Spotify, это может привести к гораздо лучшему пользовательскому опыту. Давайте углубимся в то, как Spotify построил поиск подкастов на естественном языке.

Семантический поиск

Технология, используемая в новом поиске подкастов Spotify, более известна как семантический поиск. Семантический поиск опирается на два столпа: естественный языкобработка языка (NLP) и векторный поиск.

Эти технологии действуют как два этапа в процессе поиска. Учитывая запрос на естественном языке, конкретная модель НЛП может закодировать его в внедрение вектора, также известное как плотный вектор. Эти плотные векторы могут численно представлять значение запроса. Мы можем визуализировать это поведение:

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

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

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

НЛП и векторный поиск существуют уже некоторое время, но недавние достижения послужили катализаторами в повышении производительности и последующем внедрении семантического поиска. В НЛП мы видели введение высокоэффективных моделей-трансформеров. В векторном поиске рост числа алгоритмов Aприближенных Nближайших Nсоседей (ANN).

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

Трансформеры

Модели-трансформеры стали стандартом в НЛП. Эти модели обычно состоят из двух компонентов: ядра, которое направлено на "понимание" значения языка и/или предметной области, и головы, которая адаптирует модель для конкретного варианта использования.

Есть только одна проблема: ядро ​​этих моделей требует огромных объемов данных и вычислительной мощности для предварительной подготовки.

Под предварительным обучением понимается этап обучения, применяемый к основному компоненту трансформатора. Затем следует этап тонкой настройки, на котором головная часть и/или ядро ​​дополнительно обучаются для конкретного варианта использования.

Одной из самых популярных моделей трансформаторов является BERT, и стоимость BERT составляет 2,5 000 – 50 000 000 на поезд; этот сдвиг составляет 2,5 000 000 000 на поезд; этот сдвиг составляет 80 000 000 000 000 000 000 000 000 на поезд;

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

Как мы обычно используем эти модели:

  1. Ядро модели-трансформера предварительно обучается за большие деньги такими компаниями, как Google, Microsoft и т. д.
  2. Это ядро ​​находится в открытом доступе.
  3. Другие организации берут ядро, добавляют "голову" для конкретной задачи и настраивают расширенную модель под задачу, связанную с предметной областью. Тонкая настройка менее затратна в вычислительном отношении и, следовательно, дешевле.
  4. Теперь модель готова к применению к конкретным задачам организации.

В случае построения модели поиска подкастов мы могли бы взять предварительно обученную модель, например bert-base-uncased. Эта модель уже «понимает» английский язык общего назначения.

Имея обучающий набор данных из запроса пользователя к парам эпизодов подкаста, мы могли бы добавить заголовок «объединения средних» в нашу предварительно обученную модель BERT. Как ядро, так и голову, мы в течение нескольких часов настраиваем его на наших данных о парах, чтобы создать преобразователь предложения, обученный идентифицировать похожие пары запрос-эпизод.

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

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

Преобразователи предложений добавляют «объединяющий слой» для преобразования множества вложений токенов, выдаваемых моделью преобразователя, в одно вложение предложения.

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

Встраивание предложения представляет собой плотный вектор, числовое представление смысла некоторого текста. Эти плотные векторы включают компонент семантического поиска векторный поиск.

ИНС поиск

Приблизительный поиск Nближайших Nсоседей (ANN) позволяет нам быстро сравнивать миллионы или даже миллиарды векторов. Он называется приближенным, поскольку не гарантирует, что мы найдем истинных ближайших соседей (наиболее похожие вложения).

Единственный способ гарантировать это — исчерпывающее сравнение каждого отдельного вектора. В масштабе это медленно.

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

При векторном поиске часто приходится принимать решение о том, следует ли отдавать предпочтение задержке или точности.

В любом случае требуется приблизительное решение для поддержания разумного времени запросов в масштабе.

Как Spotify сделал это

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

  • Им нужна была модель, способная поддерживать многоязычные запросы; SBERT обучался только на английском языке.
  • Эффективность SBERT по разным темам без дальнейшей тонкой настройки оставляет желать лучшего [5].

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

Чтобы точно настроить свою модель USE для кодирования пар (запрос, эпизод) осмысленным образом, Spotify нужны были данные (запрос, эпизод). У них было четыре источника этого:

  1. Используя свои прошлые журналы поиска, они определили пары (запрос, эпизод) из успешных поисков.
  2. Они выявили неудачные поиски, за которыми последовал успешный поиск. Идея состоит в том, что неудачный запрос, скорее всего, будет более естественным запросом, который затем будет использоваться как пара (query_preor_to_successful_reformulation, Episode).
  3. Генерация синтетических запросов с использованием модели генерации запросов создает пары (synthetic_query, Episode).
  4. Небольшой набор кураторских запросов, написанных вручную для эпизодов.

Источники (1–3) уточняют модель ЕГЭ, оставляя некоторые образцы для оценки. Источник (4) использовался только для оценки.

К сожалению, у нас нет доступа к прошлым журналам поиска Spotify, поэтому мы мало что можем сделать для репликации источников (1–2). Однако мы можем воспроизвести подход источника построения (3), используя модели генерации запросов. И, конечно же, мы можем вручную писать запросы по источнику (4).

Предварительная обработка данных

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

Мы можем найти набор данных об эпизодах подкастов на Kaggle, который содержит записи для 881 тыс. эпизодов подкастов, т.е. Включая названия и описания эпизодов, а также названия и описания подкастов.

Мы используем API Kaggle для загрузки этих данных, установленных в Python с помощью pip install kaggle. Необходимы учетная запись и ключ API (найдите ключ API в Настройках учетной записи). Ключ API kaggle.json должен храниться в месте, отображаемом при попытке import kaggle. Если местоположение или ошибка не появляются, ключ API уже добавлен.

Затем мы аутентифицируем доступ к Kaggle.

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

И podcasts.csv, и episodes.csv будут загружены в виде ZIP-файлов, которые мы можем извлечь с помощью библиотеки zipfile.

У нас есть два CSV-файла, podcasts.csv, детализирующие подкасты, включая заголовки, описания и хосты. Данные episodes.csv включают данные из конкретных выпусков подкастов, включая название, описание и дату публикации.

Чтобы воспроизвести подход Spotify к объединению шоу подкастов и сведений об эпизодах, мы должны объединить два набора данных. Мы делаем это с помощью внутреннего соединения столбцов ID подкаста.

Прежде чем объединять нужные функции, давайте очистим данные. Мы удаляем лишние пробелы и удаляем строки, в которых какая-либо функция содержит нулевые значения.

Мы готовы к объединению, что дает нам нашу функцию эпизодов.

Генерация запроса

Теперь у нас есть эпизоды, но нет запросов, и нам нужны пары (запрос, эпизод) для точной настройки модели. Spotify сгенерировал синтетические запросы из текста эпизода, что мы и можем сделать.

Для этого они доработали модель BART генерации запросов, используя набор данных MS MARCO. Нам не нужно точно настраивать модель BART, так как многие доступные модели были настроены на точно такой же набор данных. Поэтому мы инициализируем одну из этих моделей с помощью библиотеки HuggingFace transformers.

Мы протестировали несколько моделей T5 и BART для генерации запросов на наших данных об эпизодах; результаты здесь. Модель doc2query/all-t5-base-v1 была выбрана, так как она выдавала более разумные запросы и имела некоторую многоязычную поддержку.

Пришло время генерировать запросы. Мы будем генерировать три запроса для каждого эпизода в соответствии с подходом, используемым методами GenQ и GPL.

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

Теперь у нас есть пары (synthetic_query, Episode), которые можно использовать для точной настройки модели преобразования предложений.

Модели и тонкая настройка

Как уже упоминалось, Spotify рассматривал возможность использования предварительно обученных моделей, таких как BERT и SBERT, но обнаружил, что производительность не подходит для их варианта использования. В конце концов они выбрали предварительно обученную модель Uuniversal Sentence Encoder (USE) от TFHub.

Мы будем использовать аналогичную модель под названием DistilUSE, поддерживаемую библиотекой sentence-transformers. Используя этот подход, мы можем использовать утилиты тонкой настройки модели sentence-transformers. После установки библиотеки с pip install sentence-transformers мы можем инициализировать модель следующим образом:

При тонкой настройке с помощью библиотеки преобразователей предложений нам нужно переформатировать наши данные в список InputExample объектов. Точный формат зависит от учебной задачи.

Мы будем использовать функцию ранжирования (подробнее об этом позже), поэтому мы должны включить два текстовых элемента, пары (запрос, эпизод).

Мы также взяли небольшой набор пар оценочных (eval_pairs) и тестовых наборов (test_pairs) для дальнейшего использования.

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

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

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

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

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

Библиотека преобразователей предложений решает проблему дублирования с помощью файла NoDuplicatesDataLoader. Как следует из названия, этот загрузчик данных гарантирует, что в обучающий пакет не попадут дубликаты.

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

Выбрать ответ из ста образцов сложнее, чем из четырех. Имея это в виду, чем выше batch_size, тем выше производительность моделей.

Теперь мы инициализируем функцию потерь. Поскольку мы используем ранжирование, мы выбираем MultipleNegativesRankingLoss, обычно называемое потери MNR.

Пакетная оценка

Spotify описывает два этапа оценки. Первый можно реализовать перед тонкой настройкой с использованием внутрипакетных метрик. Здесь они рассчитали две метрики на уровне партии (в нашем случае с использованием 64 выборок за раз); это:

  • Recall@k сообщает нам, находится ли правильный ответ на первых k позициях.
  • Mean Reciprocal Rank (MRR) вычисляет средний обратный ранг правильного ответа.

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

Перед инициализацией этого оценщика нам нужно удалить дубликаты из данных eval.

Затем мы переформатируем данные в список словарей, содержащих запрос, его положительный эпизод (с которым он связан), а затем все остальные эпизоды как негативные.

Мы устанавливаем показатель MRR@5, то есть, если положительный эпизод возвращается в пределах пяти лучших результатов, мы возвращаем положительную оценку. В противном случае оценка была бы нулевой.

Если правильный эпизод появился на третьей позиции, обратный рейтинг этой выборки будет рассчитан как 1/3. В первой позиции мы бы вернули 1/1.

Поскольку мы вычисляем среднее обратное значение, мы берем все выборочные баллы и вычисляем среднее, что дает нам окончательный балл MRR@5.

Используя наш оценщик, мы сначала вычисляем производительность MRR@5 без какой-либо тонкой настройки.

Получив значение MRR@5, равное 0,68, мы сравним его с показателем MRR@5 после тренировки.

Тонкая настройка

Когда наш оценщик готов, мы можем точно настроить нашу модель. Статья Spotify не дает никакой информации об используемых ими параметрах, поэтому мы будем придерживаться довольно типичных параметров обучения для моделей преобразования предложений с использованием потери MNR. Мы тренируем одну эпоху и «разогреваем» скорость обучения для первых 10 % шагов обучения.

После тонкой настройки модель будет сохранена в каталог, указанный output_path. В distiluse-podcast-nq мы увидим все необходимые файлы модели и каталог с именем eval. Здесь мы находим показатель MRR@5 после тренировки 0,89, значительное улучшение на 21 балл по сравнению с предыдущим MRR@5 0,68.

Эта оценка выглядит многообещающе, но необходимо провести дальнейшую оценку.

Оценка

Мы хотим сымитировать более реальный сценарий на заключительном этапе оценки. Вместо того, чтобы вычислять MRR@5 для небольших пакетов данных (как это делалось ранее), мы должны проиндексировать много эпизодов и пересчитать некоторые показатели поиска.

Spotify детализирует свои показатели настроек полного извлечения, используя Recall@30 и MRR@30, выполняемые как для запросов из набора eval, так и для их курируемого набора данных.

Наш eval набор невелик, поэтому мы можем от него отказаться. Вместо этого мы будем использовать гораздо больший набор тестов test_pairs.

Как и прежде, мы должны дедуплицировать эпизоды из набора данных.

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

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

Вернувшись в Python, мы гарантируем, что клиент Pinecone установлен с pip install pinecone-client. Затем мы инициализируем наше соединение с Pinecone и создаем новый векторный индекс.

В векторном индексе мы будем хранить все наши вложения эпизодов. Мы должны закодировать текст эпизода, используя нашу доработанную модель distiluse-podcast-nq, и вставить вложения в наш индекс.

Мы рассчитаем показатель Recall@K, который немного отличается от показателя MRR@K, как если бы совпадение появилось в первых K возвращаемых результатах, мы набираем 1 ; в противном случае мы получаем 0. Как и раньше, мы берем все оценки запросов и вычисляем среднее значение.

Пока это выглядит великолепно; В 88 % случаев мы возвращаем точные положительные эпизоды из первых 30 результатов. Но это предполагает, что наши синтетические запросы идеальны, а это не так.

Мы должны измерять производительность модели на более реалистичных запросах, как это сделал Spotify со своим тщательно отобранным набором данных. В этом примере мы выбрали серию эпизодов и написали вручную запросы, соответствующие эпизоду.

Используя эти отобранные образцы, мы получили более низкую оценку 0,57. По сравнению с 0,88 это кажется низким, но мы должны помнить, что, вероятно, есть и другие эпизоды, соответствующие этим запросам. Это означает, что мы рассчитываем отзыв, предполагая, что других релевантных запросов нет.

Что мы можем сделать, так это: сравнить эту оценку с оценкой модели перед тонкой настройкой. Мы создаем новый индекс Pinecone и повторяем те же шаги, но с использованием преобразователя предложений distiluse-base-multilingual-cased-v2. Вы можете найти полный сценарий здесь.

Используя эту модель, мы возвращаем оценку всего 0,29. Благодаря точной настройке модели на данных этого эпизода, несмотря на отсутствие пар запросов, мы повысили производительность поиска эпизода на 28 баллов.

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