Представьте: вы только что создали идеальный плейлист с треками в стиле synthwave, и вдруг приложение предлагает вам «How You Remind Me» уже в третий раз за неделю. Давайте создадим что-то получше, используя коллаборативную фильтрацию — ту же технологию, что лежит в основе Spotify Discover Weekly (но, надеюсь, без Чада Крюгера). К концу этого руководства вы будете рекомендовать музыку настолько персонализированно, что ваши пользователи подумают, будто вы установили жучок в их AirPods.

От микстейпов к матрицам: основы коллаборативной фильтрации

graph LR A[Пользователь 1 любит
Daft Punk] --> B[Матрица пользователь-объект] C[Пользователь 2 обожает
Justice] --> B D[Пользователь 3 поклоняется
Kavinsky] --> B B --> E[Обнаружение закономерностей] E --> F[Рекомендовать
Gesaffelstein?]

Коллаборативная фильтрация работает как тот друг, который настаивает: «Если тебе нравится X, ты ПОЛЮБИШЬ Y!» — и на самом деле угадывает. Мы будем использовать набор данных Last.fm — музыкальный эквивалент винтажного музыкального магазина, который до сих пор пахнет пачули.

Сначала настроим наш инструментарий:

pip install implicit scikit-learn pandas numpy

Шаг 1: Обработка данных как у дорожного техника

import pandas as pd
from scipy.sparse import csr_matrix
# Загрузка данных с соответствующей обработкой ошибок
try:
    user_artists = pd.read_csv('user_artists.dat', sep='\t')
    artists = pd.read_csv('artists.dat', sep='\t', usecols=['id', 'name'])
except FileNotFoundError as e:
    print(f"Набор данных не найден! {str(e)}")
    raise
# Создание разреженной матрицы пользователь-объект (микстейп нашей мечты)
user_items = csr_matrix(
    (user_artists['weight'], 
     (user_artists['userID'], user_artists['artistID']))
)
print(f"Форма матрицы: {user_items.shape}")
print(f"Не нулевые элементы: {user_items.nnz}")

Это создаёт нашу музыкальную Розеттскую скрижаль — разреженную матрицу, где каждая строка представляет пользователя, а каждый столбец — исполнителя. Значения? Сколько раз они включали этого исполнителя. Про tip: если ваша матрица недостаточно разреженная, вы, вероятно, включаете слишком много слушателей Nickelback.

Шаг 2: Обучение модели, которая работает (но не в стиле бизнес-кэжуал)

Мы будем использовать Alternating Least Squares (ALS) из библиотеки implicit — не путать с собранием поддержки по боковому амиотрофическому склерозу вашей тёти.

from implicit.als import AlternatingLeastSquares
from implicit.nearest_neighbours import bm25_weight
# Преобразование в взвешенную матрицу (потому что некоторые воспроизведения важнее других)
weighted = bm25_weight(user_items, K1=100, B=0.8)
# Инициализация и обучение нашей музыкальной свахи
model = AlternatingLeastSquares(factors=50, iterations=10, regularization=0.01)
model.fit(weighted)
# Вспомогательная функция для получения имени исполнителя по ID
def get_artist_name(artist_id):
    return artists[artists['id'] == artist_id]['name'].values

Почему factors=50? Это как выбрать, сколько музыкальных измерений учитывать — больше, чем у укулеле, но меньше, чем у полного оркестра.

Шаг 3: Рекомендации, которые не разочаруют

def recommend(user_id, known_artists=[], n=10):
    if user_id < user_items.shape:
        # Для существующих пользователей
        ids, scores = model.recommend(
            user_id, 
            user_items[user_id], 
            N=n, 
            filter_already_liked_items=True
        )
    else:
        # Для новых пользователей на основе их любимых исполнителей
        artist_ids = [get_artist_id(name) for name in known_artists]
        ids, scores = model.similar_items(artist_ids, N=n)
    return [(get_artist_name(idx), score) for idx, score in zip(ids, scores)]
# Пример использования
print("Рекомендации для пользователя 42 (вероятно, любит synthwave):")
for artist, score in recommend(42):
    print(f"🎧 {artist} (уверенность: {score:.2f})")

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

Шаг 4: Тестирование вашей музыкальной свахи

Разделите данные, как будто вы делите последний винил в музыкальном магазине:

from sklearn.model_selection import train_test_split
# Разделение индексов пользователей
train_users, test_users = train_test_split(
    range(user_items.shape), 
    test_size=0.2,
    random_state=42
)
# Расчёт точности@k для наших рекомендаций
def precision_at_k(model, test_users, k=10):
    total = 0
    for user in test_users:
        recommended = set(model.recommend(user, user_items[user], N=k))
        actual = set(user_items[user].indices)
        total += len(recommended & actual) / k
    return total / len(test_users)
print(f"Точность@10: {precision_at_k(model, test_users):.2f}")

Если ваш результат низкий, не паникуйте — возможно, у ваших пользователей просто ужасный вкус в музыке.

Подготовка к использованию в продакшене (потому что хакатоны — это не реальная жизнь)

Перед развёртыванием учтите:

  • Проблемы с холодным стартом (обрабатывайте новых пользователей так же внимательно, как радиозапросы)
  • Масштабирование с помощью приближённых ближайших соседей (Annoy или FAISS)
  • Регулярное переобучение (музыкальные вкусы меняются быстрее, чем тренды в TikTok)
graph TD A[Новый пользователь] --> B{Есть история прослушиваний?} B -->|Да| C[Персонализированные рекомендации] B -->|Нет| D[Популярные/по жанрам] C --> E[Сохранить обратную связь] D --> E E --> F[Переобучать модель еженедельно]

Заключение: ваш билет к тому, чтобы стать следующим Даниэлем Эком (без подкастных драм)

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

  1. Добавьте временные аспекты (потому что никто не хочет, чтобы их фаза My Chemical Romance преследовала их)
  2. Включите аудиохарактеристики (относитесь к BPM как к секретному соусу)
  3. Реализуйте гибридную фильтрацию (музыкальный эквивалент хорошо смешанного коктейля) Полный код доступен на GitHub. Помните — отличные рекомендательные системы похожи на хороших диджеев: они играют то, что люди хотят, прежде чем они сами это поймут. Теперь идите и рекомендуйте ответственно!