User-based коллаборативная фильтрация
Урок реализует user-based коллаборативную фильтрацию целиком: находим похожих пользователей и предсказываем оценки взвешенным усреднением их мнений.
User-based CF предсказывает оценку пользователя для товара как взвешенное среднее оценок похожих на него пользователей, где вес — мера похожести.
Алгоритм по шагам
Чтобы предсказать, как целевой пользователь оценит товар, который он ещё не видел, мы:
- считаем похожесть целевого пользователя со всеми остальными;
- среди тех, кто оценил этот товар, берём оценки;
- усредняем их с весами, равными похожести.
Формула прогноза: сумма по соседям sim(u, v) * rating(v, i), делённая на сумму sim(u, v). Чем больше похож сосед — тем сильнее его голос.
Полная реализация
import math
ratings = {
"Аня": {"Матрица": 5, "Титаник": 2, "Аватар": 4, "Шрек": 1},
"Борис": {"Матрица": 4, "Титаник": 1, "Аватар": 5},
"Вера": {"Матрица": 1, "Титаник": 5, "Шрек": 4},
"Глеб": {"Матрица": 5, "Аватар": 4, "Шрек": 1},
}
def cosine(a, b):
common = set(a) & set(b)
if not common:
return 0.0
num = sum(a[i] * b[i] for i in common)
na = math.sqrt(sum(v*v for v in a.values()))
nb = math.sqrt(sum(v*v for v in b.values()))
return num / (na * nb) if na and nb else 0.0
target = "Борис"
sims = {u: cosine(ratings[target], ratings[u]) for u in ratings if u != target}
print("Похожесть на Бориса:")
for u in sorted(sims, key=lambda x: -sims[x]):
print(f" {u}: {round(sims[u], 3)}")
# предсказываем оценки для невиденных Борисом фильмов
seen = set(ratings[target])
candidates = set()
for u in ratings:
if u != target:
candidates |= set(ratings[u])
candidates -= seen
preds = {}
for movie in candidates:
num = den = 0.0
for u in ratings:
if u != target and movie in ratings[u]:
num += sims[u] * ratings[u][movie]
den += sims[u]
if den > 0:
preds[movie] = num / den
print("\nПрогноз для Бориса:")
for m in sorted(preds, key=lambda x: -preds[x]):
print(f" {m}: {round(preds[m], 2)}")Вывод:
Похожесть на Бориса: Аня: 0.956 Глеб: 0.952 Вера: 0.214 Прогноз для Бориса: Шрек: 1.3
Борис ближе всего к Ане и Глебу, у которых «Шрек» получил низкие оценки, поэтому прогноз по «Шреку» низкий — мы его рекомендовать не станем.
Как работает под капотом
В чистом виде user-based CF плохо масштабируется: для миллионов пользователей считать попарную похожесть со всеми невозможно. На практике сужают круг — берут только top-k самых похожих соседей и/или ограничивают кандидатов теми, кто пересекается с целевым хотя бы по нескольким товарам. Ещё одна тонкость: профили пользователей быстро меняются (сегодня посмотрел детектив, завтра комедию), поэтому матрицу похожести приходится часто пересчитывать — это и есть слабое место user-based по сравнению с item-based.
Частые ошибки
- Не нормировать на сумму похожестей. Без деления на
sum(sim)прогноз раздувается и теряет шкалу. - Включать соседей с нулевой или отрицательной похожестью. Их вклад только зашумляет прогноз; отсекайте по порогу.
- Пересчитывать всё на каждый запрос. Матрицу похожести считают офлайн и кешируют.
Итоги
- User-based CF предсказывает оценку как взвешенное среднее по похожим пользователям.
- Вес соседа равен его похожести на целевого пользователя.
- Нормировка на сумму похожестей возвращает прогноз в исходную шкалу.
- Подход плохо масштабируется и чувствителен к смене вкусов — отсюда top-k и кеширование.