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

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

Контентная фильтрация против совместной фильтрации

Вообще говоря, существует два основных подхода к рекомендательным системам: основанный на содержании и совместный. Есть, конечно, гибридные подходы, и якобы существует система, основанная на знаниях. См. Ниже: https://pdfs.semanticscholar.org/7e41/3d5661f185b4f19825da9220535cc04388ae.pdf

Не заходя в ненужные кроличьи норы, основанный на содержании означает рекомендовать вам контент на основе его сходства с другим контентом без учета предпочтений других пользователей. Сотрудничество - обратное, оно не занимается не абстрактными характеристиками элемента, а скорее использует мудрость толпы для выработки рекомендаций. Примером первой является система рекомендаций Pandora. Они (кропотливо) создали музыкальный геном из ~ 400 характеристик. И песни рекомендуются пользователям в зависимости от того, в какой степени они демонстрируют эти функции. (Подумайте о жанре, длине песни, возрасте исполнителя, расе исполнителя и т. Д.) Совместная фильтрация обнаруживает закономерности в предпочтениях элементов пользователя и использует эти закономерности для выработки рекомендаций. В этой статье мы сосредоточимся только на совместной фильтрации.

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

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

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

Обратите внимание, что каждая ячейка как в матрице пользовательских характеристик, так и в матрице элементов-характеристик является параметром. (называемые весами на жаргоне нейронных сетей). Нам необходимо обновлять эти параметры итеративно с помощью некоторой функции стоимости. Но прежде чем мы сможем углубиться в это, нам необходимо знать ячейки user-item, в которые вносятся вклады любая заданная ячейка пользовательской функции и ячейка элемента функции. Например, U1F1 и U1F2 вносят вклад в U1I1, U1I2 и U1I3. Умножение матриц выполняется как скалярное произведение строки левой матрицы на столбец правой матрицы. Как вы увидите, индексы строки и столбца матрицы пользовательских элементов определяют целую строку и столбец, выбранные из разложенных матриц.

Рассмотрим другой пример. Обратите внимание, что ячейки пользовательских элементов, на которые я обратил ваше внимание, образуют один столбец, не строку. Это связано с тем, что ячейки F1I1 и F2I2 вносят вклад в три разные ячейки в матрице пользовательских элементов.

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

В приведенном ниже примере показан только один из трех градиентов, необходимых для обновления U1F1; обратите внимание, что два других градиента U1I2 и U1I3, каждый (конечно) по отношению к U1F1. Когда мы обновляем U1F1, мы усредним все три этих градиента. Интуиция здесь заключается в том, что, преследуя один градиент за раз, мы можем никогда не прийти к оптимальным значениям параметров. Усредняя градиенты, мы можем одновременно сделать три шага к оптимальному решению.

Вы не поверите, но теперь у нас есть все строительные блоки, необходимые для кодирования решателя матричной факторизации на Python! Посмотрим код. Я прикрепил блокнот Google Colab ниже, где вы можете увидеть и запустить код, чтобы поэкспериментировать с результатами! Вскоре мы разберем код метод за методом в архитектуре класса.



Во-первых, давайте создадим объект класса и инициализируем его функции. Я принял дизайнерское решение инициализировать параметры значениями по умолчанию от 0,1 до 0,9. Кроме того, мы определим функцию MSE, как подробно описано ранее.

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

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

Наконец, нам нужно определить метод, обучающий модель. По умолчанию мы установили скорость обучения 0,1, а количество итераций - 1000. Это можно изменить в Google Colab, упомянутом выше. Вы заметите, что значения характеристик элемента увеличиваются на каждой итерации. Разве градиент не был отрицательным? Да! Но при градиентном спуске обычно используется обозначение w -= learning_rate * gradient. В нашем случае двойные отрицания просто уравновешиваются.

Теперь давайте проверим, что наш код работает! Как видите, имея всего 2 скрытых функции, мы так близко подошли к оптимальному решению. Ошибка не превышает ~ 3,54.

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

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