Представьте себе систему рекомендаций, в которой очень важен реалтайм, т.е. быстрое обновление. Например, рекомендации новостей или постов. В ней очень важно быстро обновлять внутренние параметры, особенно те, которые соответствуют самим новостям. Это могут быть как обычные счетчики, выражающие CTR документов, так и обучаемые эмбеддинги для каждой новости, в которых в том числе и этот CTR будет зашит. Будем считать, что у нас есть модель, которая обучается в реальном времени и обновляет эти эмбеддинги.
А теперь давайте подумаем, в какой момент времени относительно времени показа (T_impression) и времени разных взаимодействий (T_click, etc.), о которых говорили в прошлом посте, наша модель должна получить новый сэмпл для обучения (т.е. каким должен быть T_ingest в обучатор модели). Конечно, нам хочется, чтобы это происходило как можно раньше, чтобы мы могли быстро выявлять очень популярные новости. Но сделать это прямо в момент показа мы не можем, так как еще не знаем, произойдёт ли клик.
Самый простой вариант — подождать X времени (скажем, несколько минут) после показа и соответствующим образом решить, положительный ли этот сэмпл или нет. Тогда модель будет предсказывать, с какой вероятностью пользователь кликнет/полайкает/… документ за время X. Если X будет слишком большим, реакция системы будет медленнее. Если X будет слишком маленьким, то мы упустим часть положительных взаимодействий из-за того, что они не успели произойти за X. Эта часть может оказаться совсем не нулевой, больше, чем кажется на первый взгляд, особенно для более сложных таргетов (например, подписался ли пользователь на источник новости). У каждого таргета есть своё вероятностное распределение времени, через которое он случается после показа. И у этих распределений тяжелые хвосты.
Есть ли способы эффективнее?
Например, можно было бы завести несколько разных событий с разными X: кликнул ли за 30 секунд, за 2 минуты, за 5 минут и т.д. Но тогда мы увеличим объём данных для обучения в несколько раз, хотя информации для обучения мы будем выдавать не сильно больше, чем раньше. Это очень большая нагрузка на инфраструктуру и на обучение.
Можно было бы сразу же в момент показа использовать негативный сэмпл, а потом в случае, если какое-то действие произойдёт, сделать новый сэмпл, «уточняющий» предыдущий. (А если еще одно действие произойдёт — то еще одно уточнение, и так далее.) Так как уточнений будет на порядок меньше, чем показов, то нагрузка на инфраструктуру возрастёт не слишком сильно. Понятно, как обрабатывать такие уточнения для счетчиков. А вот как это делать для обучающихся эмбеддингов? Т.е. как «отменить» действие предыдущего негативного сэмпла? Возможно, можно просто сделать обратный шаг — шаг по градиенту (т.е. шаг градиентного подъёма вместо градиентного спуска). Но параметры модели на этот момент уже не те, которые были при прямом шаге, поэтому это не будет совсем точной его отменой. Не разнесёт ли модель от этого? Кроме того, как только новость появляется, она сразу получает много негативных сэмплов, а позитивные придут только с задержкой, из-за чего получается сильное смещение в негативную сторону.
Можно также комбинировать этот способ с изначальным простым — сначала чуть-чуть подождать (маленькое X), использовать первый сэмпл и уже только после этого начинать делать уточнения. Это снизит степень проблем, связанных с уточнениями. Но решит ли полностью? Да и сложновато получается.
Наверно, стоит отметить, что если подумать не про сторону объектов (новостей), а про сторону пользователей, то эта проблема уже кажется не такой специфической — обновлять рекомендации для пользователя в реальном времени хотят все. И если используется обучаемый для каждого пользователя эмбеддинг, то проблема та же самая. Но, например, если использовать трансформер по пользовательской истории, то в него можно запихнуть всё как есть, а он уже сам разберётся, достаточно ли времени прошло от каждого показа, чтобы считать его полноценно отрицательным событием.
Что думаете?