- BrainTools - https://www.braintools.ru -

Эволюция поиска вакансий в Авито Работе: ML-оптимизации и инсайты из АБ-тестов

Привет! Меня зовут Вадим Вахрушев, я старший DS-инженер в Авито [1], занимаюсь задачами поиска. В статье расскажу, как устроено ранжирование в Авито Работе: какие ML-модели помогают создавать выдачу вакансий, и какие инсайты мы вынесли из нескольких показательных АБ-тестов.

Думаю, статья будет в первую очередь интересна ML-инженерам, которым важно разобраться, как поисковое ранжирование адаптируется под конкретную бизнес-вертикаль. Если хочется узнать про поиск в Авито в целом, загляните в обзорную статью — «Как работает поисковое ранжирование для миллионов объявлений Авито» [2]. В этом материале я сфокусируюсь именно на специфике вакансий.

Эволюция поиска вакансий в Авито Работе: ML-оптимизации и инсайты из АБ-тестов - 1

Ключевые цели поисковой выдачи

Когда мы формируем результаты поиска для пользователя, преследуем сразу несколько целей:

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

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

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

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

Тут еще больше контента

Пирамида поиска: L1 → L2 → L3

Схему формирования выдачи можно представить как пирамиду из трёх этапов:

Пирамида поиска: на L1 отбираем кандидатов, на L2 применяем ML-модели, на L3 переранжируем блендером

Пирамида поиска: на L1 отбираем кандидатов, на L2 применяем ML-модели, на L3 переранжируем блендером

Ниже я разберу каждый уровень в подробностях, но общая суть этапов такая:

L1 — отбор кандидатов. Из более чем 1,5 млн вакансий отбираем 1 000 самых релевантных для пользователя.

L2 — вычисление интентов. Для отобранных кандидатов считаем интенты блендера с помощью продвинутых ML-моделей.

L3 — блендер. Перемешиваем кандидатов и формируем итоговую выдачу примерно из 200 вакансий.

L1: максимизируем полноту

Основная задача L1 — полнота. Нам надо отобрать все вакансии, которые потенциально могут быть релевантны пользователю. Здесь работают кандидатогенераторы (кандгены) и лёгкие ML-модели.

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

Горизонтальные, то есть общие для всего Авито:

— Полнотекстовое совпадение. Отбираем вакансии, где встретились все слова запроса. Это база поиска, но её часто недостаточно, если в запросе много слов или они редко встречаются.

— Семантический поиск. Отбираем вакансии, семантически похожие на запрос, даже если слова не совпадают.

— Расширенные регионы. Подмешиваем вакансии из соседних областей — полезно, например, когда пользователь живёт на их границе.

Допустим, получили поисковый запрос «учитель в Москве». Собираем кандидатов сначала из вакансий учителей в Москве, ровно как нас и просили. Ещё возьмём вакансии со словом «педагог» — с точки зрения [10] семантики это тот же «учитель» — плюс покажем учителей в Долгопрудном. Это смежный с Москвой город, славный своими школами.

Вертикальные, то есть специфичные для Авито Работы:

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

— Смежные профессии. Смежность мы определяем по бизнес-правилам и пользовательскому поведению [11]: анализируем, какие запросы делает человек и на что откликается, кластеризуем профессии. Это особенно важно для нашей основной аудитории — рабочих специальностей, которые готовы рассматривать несколько профессий сразу.

Избранные АБ на уровне L1 

Добавление кандгена удалённой работы

+7.4% пользователей с контактом поиска в сегменте офисной работы

+1.7% в тотале вакансий — решили проблему учёта локации при удалёночных запросах

Добавление кандгена смежных профессий

+2% пользователей с контактом из поиска — повысили разнообразие выдачи для людей, которые готовы рассматривать несколько профессий

Теперь к ML-моделям — на L1 их несколько.

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

Векторы вакансий считаются с задержкой в несколько минут. Модель получает на вход «заголовок» + «текст» + «параметры», например, зарплата, локация и опыт [12]

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

Архитектура векторного кандгена

Архитектура векторного кандгена

Clickpred — ранжирующая модель, которая, несмотря на название, учится и калибруется на контакты. Она лёгкая, пока неперсонализированная и использует базовые статистики по объявлению и продавцу (конверсия в контакт, в клик) и фичи запроса (BM25, TF-IDF).

RelPred — модель текстовой релевантности вакансии запросу. Работает на текстовых запросах и учится на LLM-разметке, потому что пользовательская слишком шумная, а нам нужна «идеализированная».

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

Итоговая формула отбора топ-1000 кандидатов:

score = sigmoid(w1 × clickpred + w2 × relpred + w3 × item_age)

Возраст вакансии (item_age) учитываем сознательно: хотим показывать свежие предложения. Во-первых, это обеспечивает справедливость — каждую свежую вакансию покажем хотя бы раз. Во-вторых, так успеваем накопить по ней статистику, которую потом используем в ранжировании.

Жми сюда!

L2: считаем интенты

На L2 вычисляются основные интенты блендера — BX и ECPI:

BX — баерское счастье — то, насколько вакансия-кандидат подходит соискателю, то есть вероятность мэтча.

ECPI — ожидаемая выручка от показа вакансии.

Он считается как произведение трёх скоров:

BX = cttr_pred × rel_predk × pess

Здесь RelPred приходит с L1 уровня в некоторой степени k, а pess — пессимизация: она занижает фродовые и накрученные вакансии. А CttrPred — скор CTTR-модели, про неё ниже.

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

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

Пара инсайтов из АБ-тестов с CTTR-моделью. Небольшая ремарка про методологию: все тесты мы гоняем через внутреннюю систему [14]. Она позволяет точно конфигурировать долю трафика под эксперимент — обычно мы берём не 100%, а, например, 50% и делим контроль и тест 1:1, то есть по 25% на ветку.

💡 Как подобрали таргет. Остановились на связке «контакт + целевой контакт», причём целевой с бóльшим весом: +2,7% пользователей с целевым контактом с поиска.

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

💡 Как изменились показатели после добавления разнообразных фичей:

+2,3% соискателей с целевым контактом дал косинус между векторами соискателя и вакансии из рекомендательной DL-модели. Хороший пример переиспользования фичей из соседних команд.

+1% — интересы пользователя к атрибутам вакансии.

+1,1% — конверсии в целевой контакт по объявлению и работодателю.

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

Один и тот же признак за разные окна работает в разные стороны: за месяц — в плюс, за неделю — в минус

Один и тот же признак за разные окна работает в разные стороны: за месяц — в плюс, за неделю — в минус

L3: блендер и многокритериальная оптимизация

Полученные на L2 интенты нужно как-то учесть вместе. Формирование выдачи — это задача многокритериальной оптимизации, и в вакансиях мы решаем её жадным алгоритмом.

Интентов на этом уровне четыре:

BX — удовлетворённость пользователя (вероятность, что по вакансии случится мэтч)

ECPI — матожидание выручки от показа (используются свои ML-модели)

ST — свежесть: новые вакансии поднимаем не только на уровне кандгенов, но и здесь, чтобы они не терялись при смешивании

RP — репутация: деприоритизируем недобросовестных работодателей — например, тех, кто не отвечает пользователям

Сам алгоритм можно представить как цикл:

  1. Вычисляем интенты для каждой вакансии.

  2. Нормируем значения от 0 до 1, чтобы их можно было складывать.

  3. Считаем скор как взвешенную сумму: score = w1 × RelIntent1 + w2 × RelIntent2 + …

  4. Берём топ-1 по скору и добавляем в выдачу.

  5. Дисконтируем веса интентов пропорционально релевантности интентов у отобранной вакансии — и повторяем [15].

Жадный алгоритм блендера: вычисляем интенты, нормируем, считаем взвешенный скор, берём топ-1 и дисконтируем веса

Жадный алгоритм блендера: вычисляем интенты, нормируем, считаем взвешенный скор, берём топ-1 и дисконтируем веса

Интуиция [16] на пятом шаге такая: если мы только что отобрали вакансию с высокой релевантностью к ECPI, нужно уменьшить вес этого интента, чтобы следующим показать вариант, с которым у пользователя, скорее всего, случится мэтч, потом — что-то свежее, и так далее.

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

Выводы

Вертикализация поиска может принести много профита на всех этапах формирования выдачи — от кандгенов до блендера.

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

Грамотная балансировка интентов — ключ к успешному поиску, от которого хорошо всем: и работодателю, и соискателю, и нам как бизнесу.

На этом всё. Если есть вопросы или свои истории про ранжирование поиска — буду рад обсудить в комментариях.

А если вам не хватило контента от нас, приходите в телеграм-канал «Доска AI-объявлений» [17] — там и тексты покороче, и вакансии к нам в команды есть, и с инженерами Авито можно в комментах пообщаться. Например, вот о чём уже писали: 

📎 Рассуждали, как AI-агенты интегрируются в медицину [18]

📎 Искали ответ на вопрос: куда расти Data Science-инженеру, если он уже синьор? [19]

📎Обсуждали, как изменилась стабильность новых агентов на объёмных задачах [20]

Кликни здесь и узнаешь

Автор: Vadvak

Источник [22]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/33833

URLs in this post:

[1] Авито: https://clc.to/LV3FBg

[2] «Как работает поисковое ранжирование для миллионов объявлений Авито»: https://habr.com/ru/companies/avito/articles/846832/

[3] Ключевые цели поисковой выдачи: #section1

[4] Пирамида поиска: L1 → L2 → L3: https://section2

[5] L1: максимизируем полноту: https://section3

[6] L2: считаем интенты: https://section4

[7] L3: блендер и многокритериальная оптимизация: https://section5

[8] Выводы: #section6

[9] Тут еще больше контента: https://telegram.me/+ShQQPXymxoViNzFi

[10] зрения: http://www.braintools.ru/article/6238

[11] поведению: http://www.braintools.ru/article/9372

[12] опыт: http://www.braintools.ru/article/6952

[13] Жми сюда!: https://clc.to/MDY_jw

[14] через внутреннюю систему: https://trisigma.io/

[15] повторяем: http://www.braintools.ru/article/4012

[16] Интуиция: http://www.braintools.ru/article/6929

[17] «Доска AI-объявлений»: https://t.me/DS_avitotech

[18] Рассуждали, как AI-агенты интегрируются в медицину: https://t.me/DS_avitotech/342

[19] Искали ответ на вопрос: куда расти Data Science-инженеру, если он уже синьор?: https://t.me/DS_avitotech/339

[20] Обсуждали, как изменилась стабильность новых агентов на объёмных задачах: https://t.me/DS_avitotech/334

[21] Кликни здесь и узнаешь: https://clc.to/vtMlJg

[22] Источник: https://habr.com/ru/companies/avito/articles/1064546/?utm_campaign=1064546&utm_source=habrahabr&utm_medium=rss

www.BrainTools.ru

Rambler's Top100