Представьте: вы только что создали идеальный плейлист с треками в стиле synthwave, и вдруг приложение предлагает вам «How You Remind Me» уже в третий раз за неделю. Давайте создадим что-то получше, используя коллаборативную фильтрацию — ту же технологию, что лежит в основе Spotify Discover Weekly (но, надеюсь, без Чада Крюгера). К концу этого руководства вы будете рекомендовать музыку настолько персонализированно, что ваши пользователи подумают, будто вы установили жучок в их AirPods.
От микстейпов к матрицам: основы коллаборативной фильтрации
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)
Заключение: ваш билет к тому, чтобы стать следующим Даниэлем Эком (без подкастных драм)
Вы создали рекомендательную систему, которая потенциально может предлагать исполнителей, которых ваши пользователи ещё даже не открыли для себя. Для улучшения:
- Добавьте временные аспекты (потому что никто не хочет, чтобы их фаза My Chemical Romance преследовала их)
- Включите аудиохарактеристики (относитесь к BPM как к секретному соусу)
- Реализуйте гибридную фильтрацию (музыкальный эквивалент хорошо смешанного коктейля) Полный код доступен на GitHub. Помните — отличные рекомендательные системы похожи на хороших диджеев: они играют то, что люди хотят, прежде чем они сами это поймут. Теперь идите и рекомендуйте ответственно!