Как построить рекомендательную систему с цензурированными данными

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

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

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

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

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

import numpy as np
user_ids, item_ids, ratings, timestamps = zip(*[i.strip().split('\t') for i in open("./ml-100k/u.data").readlines()])
user_ids = np.array([int(u) for u in list(user_ids)])
item_ids = np.array([int(i) for i in list(item_ids)])
timestamps = np.array([int(s) for s in list(timestamps)])
interactions = Interactions(user_ids=user_ids, item_ids=item_ids, timestamps=timestamps)
train, test = random_train_test_split(interactions)

Набор данных 100k Movielens содержит 943 пользователя, 1682 фильма и 100 000 взаимодействий между пользователями и фильмами. Взаимодействия разделены в соотношении 80/20 для обучения и тестирования. Взаимодействия в поезде разделены на три последовательности: список идентификаторов пользователей, список идентификаторов элементов, с которыми взаимодействует пользователь, и отметка времени этого взаимодействия. В обычном сценарии все взаимодействия между пользователями и элементами фиксируются и сохраняются на стороне сервера. В нашей системе пользователям разрешено отправлять поддельные взаимодействия с условием, что они взяты из однородного шума.

Я готовлю три сценария шума: 25%, 50% и 75%, имитируя соответствующее количество данных о поездах, заменяемых шумом в наборе данных.

import random
preserving_25_percent_items = []
preserving_50_percent_items = []
preserving_75_percent_items = []
vmin = train.item_ids.min()
vmax = train.item_ids.max()
for real_item_idx in train.item_ids:
    random_item_idx = random.randint(vmin, vmax)
    sampling_threshold = random.random()
    if sampling_threshold < .25:
        preserving_25_percent_items.append(real_item_idx)
    else:
        preserving_25_percent_items.append(random_item_idx)
    if sampling_threshold < .5:
        preserving_50_percent_items.append(real_item_idx)
    else:
        preserving_50_percent_items.append(random_item_idx)
    if sampling_threshold < .75:
        preserving_75_percent_items.append(real_item_idx)
    else:
        preserving_75_percent_items.append(random_item_idx)

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

from matplotlib import pyplot
plt = pyplot.figure(figsize=(16,10))
pyplot.subplot(221)
pyplot.hist(item_ids, bins=50, alpha=0.7, label='100% item preserving', color='red')
pyplot.legend(loc='upper right')
pyplot.subplot(222)
pyplot.hist(preserving_25_percent_items, bins=50, alpha=0.7, color='green', 
            label='25% item preserving, 75% random noise' )
pyplot.legend(loc='upper right')
pyplot.subplot(223)
pyplot.hist(preserving_50_percent_items, bins=50, alpha=0.7, color='blue', 
            label= '50% item preserving, 50% random noise')
pyplot.legend(loc='upper right')
pyplot.subplot(224)
pyplot.hist(preserving_75_percent_items, bins=50, alpha=0.7, 
            label='75% item preserving, 25% random noise')
pyplot.legend(loc='upper right')
pyplot.show()

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

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

user_ids = train.user_ids
timestamps = train.timestamps
preserving_25_percent_train = Interactions(
  user_ids=user_ids,
  item_ids=np.asarray(preserving_25_percent_items),
  timestamps=timestamps)
preserving_50_percent_train = Interactions(
  user_ids=user_ids,                                    item_ids=np.asarray(preserving_50_percent_items),
  timestamps=timestamps)
preserving_75_percent_train = Interactions(
  user_ids=user_ids,                               item_ids=np.asarray(preserving_75_percent_items),
  timestamps=timestamps)

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

from spotlight.sequence.implicit import ImplicitSequenceModel
model = ImplicitSequenceModel(embedding_dim=128)
preserving_25_percent_model = ImplicitSequenceModel(embedding_dim=128)
preserving_50_percent_model = ImplicitSequenceModel(embedding_dim=128)
preserving_75_percent_model = ImplicitSequenceModel(embedding_dim=128)

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

model.fit(train.to_sequence(), verbose=True)
preserving_25_percent_model.fit(preserving_25_percent_train.to_sequence(), verbose=True)
preserving_50_percent_model.fit(preserving_50_percent_train.to_sequence(), verbose=True)
preserving_75_percent_model.fit(preserving_75_percent_train.to_sequence(), verbose=True)

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

from spotlight.evaluation import mrr_score
train_mrrs = mrr_score(model, train)
preserving_25_train_mrrs = mrr_score(preserving_25_percent_model, preserving_25_percent_train)
preserving_50_train_mrrs = mrr_score(preserving_50_percent_model, preserving_50_percent_train)
preserving_75_train_mrrs = mrr_score(preserving_75_percent_model, preserving_75_percent_train)
test_mrrs = mrr_score(model, test)
preserving_25_test_mrrs = mrr_score(preserving_25_percent_model, test)
preserving_50_test_mrrs = mrr_score(preserving_50_percent_model, test)
preserving_75_test_mrrs = mrr_score(preserving_75_percent_model, test)
print('For 100% preserving interactions')
print('Train MRRS {:.3f}, test MRRS {:.3f}'.format(train_mrrs.sum(), test_mrrs.sum()))
print('For 25% preserving interactions')
print('Train MRRS {:.3f}, test MRRS {:.3f}'.format(preserving_25_train_mrrs.sum(), preserving_25_test_mrrs.sum()))
print('For 50% preserving interactions')
print('Train MRRS {:.3f}, test MRRS {:.3f}'.format(preserving_50_train_mrrs.sum(), preserving_50_test_mrrs.sum()))
print('For 75% preserving interactions')
print('Train MRRS {:.3f}, test MRRS {:.3f}'.format(preserving_75_train_mrrs.sum(), preserving_75_test_mrrs.sum()))

Для 100 % сохранения взаимодействий
Обучите MRRS 9.566, проверьте MRRS 10.132
Для 25 % сохранения взаимодействий
Обучите MRRS 6.064, проверьте MRRS 11.379
Для 50 % сохранения взаимодействий
Обучить MRRS 8.932, протестировать MRRS 12.749
Для 75% сохранения взаимодействий
Обучить MRRS 9.836, протестировать MRRS 12.488

И результаты довольно удивительны: с сохранением элементов на 25%, 50% и 75% эти модели работают лучше на тестовом наборе соответственно 11,379, 12,488 и 12,749 по сравнению с 10,132 для модели с полными данными. Даже если случайно получилось лучше, можно подтвердить, что рекомендательная модель все еще работает с зашумленными данными.

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

Вот полный основной код, просто скачайте и извлеките набор данных в папку «./ml-100k», после чего результат можно будет проверить.

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

Наше решение для искусственного интеллекта имеет открытый исходный код и активно разрабатывается в рамках проекта Telar, а также несколько интересных статей от нас:

👋 Для получения дополнительной информации посетите официальный сайт Телар Пресс или команды Красное Золото.

Все виды обратной связи всегда приветствуются. Спасибо за ваше время!