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

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

🐸 x 🧀

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

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

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

Теперь вы можете увидеть одну важную вещь: этот процесс заполнил любую ячейку, в которой изначально не было рейтинга. Мы используем скрытые особенности пользователя и предмета, чтобы приблизительно оценить их оценку предмета. Если бы мы порекомендовали Гонзо сыр, зная, что он уже оценил «Swiss & Roquefort», мы увидим, что Гауда имеет наивысшую оценку из тех продуктов, которых у него не было, - твердый выбор!

Магия извлечения признаков

У нас получилось, можем порекомендовать сыры Маппетам. НО ... как мы рекомендуем сыр доктору Зубу? Самокат? Крыса Риццо !? Как мы могли его забыть! Нам нужно помочь получить больше кукол, сыров, которые им больше всего подходят.

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

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

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

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

dr_teeth = np.array([[5],[5]])
dr_teeth.shape # (2, 1)

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

select_cheese = items.iloc[0:2, 2:12].to_numpy()
select_cheese.shape # (2, 10)

Затем мы будем использовать решение линейной алгебры для наименьших квадратов для решения пользовательских функций Dr. Teeth:

dr_teeth_features = np.linalg.lstsq(select_cheese, dr_teeth, rcond=None)
dr_teeth_features = dr_teeth_features[0].reshape((10,))
dr_teeth_features
###
array([1.94412615, 0.        , 0.        , 1.49873073, 0.20873594,
       0.        , 0.        , 0.42289857, 0.36965752, 0.19383103])

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

dr_teeth_scores = [np.dot(dr_teeth_features, row) for row in items.iloc[:, 2:12].to_numpy()]
for row in zip(items['cheese'], dr_teeth_scores):
    print(row)
###
('gouda', 4.9999999999999964)
('cheddar', 5.000000000000001)
('brie', 4.270218133363084)
('swiss', 2.505755675697055)
('roquefort', 3.5066194383500755)

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

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