Проект заключался в реализации алгоритма малорангового восстановления матрицы. Вообще, это довольно полезная штука: например, чтобы Кинопоиск мог предложить вам какой-то фильм, ему нужно знать, как вы оценивали другие, уже просмотренные фильмы. Из оценок можно сформировать матрицу
(пользователи, фильмы), где на пересечении строки с пользователем A и фильмом B будет стоять оценка, которую пользователь A выставил фильму B. Это будет очень большая и жутко разреженная матрица, ведь каждый пользователь мог просмотреть лишь малое число от всех фильмов, которые есть у Кинопоиска. Ну вот, если вдруг мы хотим предлагать пользователю какие-то фильмы, которые ему наверное понравятся, нам бы хотелось заранее знать, какую оценку он мог бы поставить фильму, который он еще не смотрел. Но как это сделать?
Хорошая новость заключается в том, что, хоть матрица
(пользователи, фильмы) и большая, но скорее всего она малоранговая: вероятно, существует какой-то небольшой набор фильмов, оценки которых будут давать оценки всем другим фильмам. Как это понимать? Об этом можно думать так: все фильмы можно разбить по жанрам: детективы, комедии, хорроры. Если пользователю
A нравятся детективы (например, всем просмотренным детективам он выставил высокую оценку), но не нравятся хорроры (аналогично, всем просмотренным хоррорам он выставил низкую оценку), то вполне вероятно, что какому-то другому детективу он также выставит высокую оценку, а значит и предлагать ему надо какой-то детектив. В этом и заключается малоранговость матрицы (пользователи, фильмы): можно найти небольшое число фильмов (скажем, по одному ключевому фильму в каждом жанре), пользовательские оценки которых будут определять оценки других фильмов.Допустим, это так. Выходит, чтобы приблизить нашу исходную матрицу
(пользователи, фильмы), нам нужно искать среди матриц такого же размера, но у которых маленький ранг (например, ранг k). Это как-то облегчает нашу участь? О да: множество матриц фиксированного размера и фиксированного ранга, на самом деле, являются гладким многообразием! Более того, оно погружено в Евклидово пространство, а значит можно наследовать риманову метрику с объемлющего пространства на наше многообразие. А тогда мы можем заниматься всякой гладкой оптимизацией, ведь у нас теперь есть возможность посчитать градиент.Рассмотрим такую функцию
f(index; A) = A[index], которая по заданным индексам и матрице возвращает значения матрицы в этих индексах. Пусть index будет теперь означать индексы исходной матрицы (пользователи, фильмы), в которых стоят оценки. Тогда, можно рассмотреть квадратичную функцию ошибки на многообразии матриц фиксированного ранга: (f(index; (пользователи, фильмы)) - f(index; A))**2Ее мы можем минимизировать тем же градиентным спуском, и тогда мы получим приближение нашей исходной матрицы. То есть у нас получится найти такую матрицу
A, у которой на пересечении известных оценок будут, собственно, эти самые оценки, а для тех фильмов, оценки которых мы еще не знаем, мы получим какое-то приближение. Но мы ведь этого и хотели! Теперь мы примерно знаем, что конкретный пользователь может поставить фильму, который он еще не смотрел — надо просто глянуть на нужную ячейку в матрице
A. Ну и дальше уже думать, предлагать ли фильм или нет.Вообщем, просмотреть все детали и код можно в этой тетрадке.