Умный Поиск: Как Искусственный Интеллект Hh.ru Отбирает Вакансии Для Резюме

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

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



Умный поиск: как искусственный интеллект hh.ru отбирает вакансии для резюме

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

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

В этой статье я опишу, как мы сделали умный поиск — со всеми проблемами, тонкостями и компромиссами, на которые пришлось пойти.



Мы начали с системы рекомендаций

На hh.ru есть рассылки с подходящими вакансиями.

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

Чтобы разобраться в проблеме, мы записали, какие вакансии показывались пользователям и что пользователи с ними делали дальше.

Мы разработали систему a/b-тестов — инфраструктуру для использования машинного обучения для прогнозирования вероятности ответа по паре «резюме/вакансия».

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



Умный поиск: как искусственный интеллект hh.ru отбирает вакансии для резюме

Система стала приносить нам около 1,2 млн дополнительных ответов в месяц, это примерно 120 тысяч приглашенных на собеседование и 20 тысяч нанятых.

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

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

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



Поиск по пустому запросу

Мы начали с анализа поисковых запросов.

Выяснилось, что в 35% запросов пользователи, у которых есть резюме, оставляют строку поиска пустой.

Если учесть еще и анонимные запросы, то количество пустых поисковых запросов достигает 50%.

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

Никаких особых изменений не потребовалось и было сделано довольно быстро.

Использование ранга из рекомендательной системы давало несколько тысяч дополнительных ответов в день.

Но эффект оказался меньше ожидаемого.

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

Система показывала вверху подходящие «Премиумы», а затем неподходящие, даже если были более подходящие вакансии типа «Стандарт», «Стандарт+» и бесплатные.

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

, а затем все остальное в том же порядке.

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

В результате мы провели эксперимент и увидели рост откликов на все типы вакансий.



Производительность системы

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

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

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

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

Поэтому все решилось таблицей в PostgreSQL. Что пришлось добавить для достижения требуемой производительности от системы:

  1. Пересчет кэша при изменении функций.

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

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

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

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

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

  3. Мы сделали Failfast — быстрый ответ http 500 на базовые запросы, если при обработке запроса произошла ошибка.

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

    После этого мы сделали умозрительный повтор: если ответа от поиска по базе нет более 2/3 таймаута, то средний метапоиск заранее переходит к другому поиску по базе.

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



Умный поиск: как искусственный интеллект hh.ru отбирает вакансии для резюме

Потоки данных в системе:

  • красные стрелки, (1) – (15) – схема ответа на поисковый запрос, начинается автоматически при каждом поисковом запросе;
  • синие стрелки, (16) – (24) – схема индексации, запускается автоматически при появлении вакансий, резюме, смене компаний;
  • зеленые стрелки, (25) – (33) – схема машинного обучения, запускаемая вручную при каждом изменении моделей (изменения лингвистики, векторизации, признаков, целевых функций, моделей, просто переобучение моделей с использованием более актуальных данных);
  • фиолетовые стрелки (34) – (36) — контур расчета метрик в A/B-тестах и бизнес-метриках (запускается автоматически, один раз в сутки).

В процессе нам понадобилось добавить в кластер около 10 серверов, более мощных, чем те, что были там до сих пор.

Необходимо было рационально использовать свою власть.

Возросла вероятность того, что некоторые серверы будут недоступны.

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

Помимо использования новых серверов, это также позволило пережить внезапный сбой 20% кластера без каких-либо видимых последствий для пользователей.

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

Умный поиск: как искусственный интеллект hh.ru отбирает вакансии для резюме

Описанная выше балансировка работает между средними метапоисками (мета) и базовыми поисками (базовый поиск).

При этом мы постоянно совершенствовали систему рекомендаций.

Мы включили функции, основанные на текстовых взаимодействиях, градуированную целевую функцию, функции, основанные на «необработанных» векторах SVD, основанных на текстах, и метафункции, основанные на линейной регрессии по векторам tf/idf. Было еще одно улучшение: мы повторили выгрузку, очистку и объединение исходных данных для машинного обучения из логов и базы данных и сделали так, чтобы запускалось одной командой.



Поиск по непустому запросу: машинное обучение

Почти одновременно мы начали поиск непустого запроса.

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

Поэтому мы сделали специальную загрузку «запрос — резюме — вакансия — действие» и обучили две модели:

  1. линейный: используется для быстрого и с небольшой ресурсоемкостью отделения подходящих вакансий от неподходящих и грубого ранжирования неподходящих;
  2. XGBoost: используется для более точного ранжирования совпадений.



Умный поиск: как искусственный интеллект hh.ru отбирает вакансии для резюме

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

К обычным функциям мы добавили функции сравнения текстов с учетом текстовых взаимодействий.

Схематически работу машинного обучения можно изобразить следующим образом:

Умный поиск: как искусственный интеллект hh.ru отбирает вакансии для резюме

При расчете рекомендуемых вакансий и обработке поисковых запросов выполняется только зеленая часть; при изменении моделей (изменения в лингвистике, векторизации, признаках, целевых функциях, моделях, просто переобучение моделей на более актуальных данных) — и зеленый, и синий.

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

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

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

Мы измеряли качество моделей, выбирая локальные метрики ndcg и map для всех томов @10, @20, используя kfold по пользователям и проверку на основе времени.

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

Сначала мы попытались обучить линейную модель прогнозировать вероятность ответа для отдельных комбинаций «запрос – резюме – вакансия», но оказалось, что результат в a/b-тестах был лучше, когда линейная модель сравнивала вероятности для двух вакансии.

В такой конфигурации некоторые эксперименты уже дали статистически значимые положительные результаты.

Но все равно меньше, чем ожидалось.

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

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

, отмечая вакансии.

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

Времени оставалось совсем мало, поэтому мы выкатили большинству пользователей те настройки, которые лучше всего показали себя при разметке, и еще несколько вариантов по 5%, оставив только контрольное разделение без машинного обучения.

Оказалось, что маркировка прошла не зря; опция, которая была включена у большинства пользователей, действительно показала себя с лучшей стороны!

Новый интерфейс и реклама

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

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

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

Мы сделали новый графический дизайн, в котором реклама не занимала место по горизонтали вверху и по вертикали справа.

К рекламе можно относиться по-разному, но она приносит HeadHunter значительную часть прибыли.

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

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



Умный поиск: как искусственный интеллект hh.ru отбирает вакансии для резюме

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

Окончательно

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

Это был один из самых быстрых и спокойных проектов по запуску систем такого класса — по крайней мере, по моему опыту.

Во многом благодаря лучшей команде.

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

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

Кроме того, в HeadHunter есть еще множество областей, в которых было бы полезно применить ML, технологии поиска, а также метрики и a/b-тесты.

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

Мы набираем сотрудников, смотрите наши вакансии .

Теги: #ml #обработка данных #Машинное обучение #большие данные #Поисковые технологии #Машинное обучение

Вместе с данным постом часто просматривают: