Простые но Эффективные Модели
В этой главе мы рассмотрим две модели, которые доказывают, что простота может быть мощной: EASE и SLIM. Обе модели основаны на линейных методах, но достигают результатов, сравнимых с гораздо более сложными deep learning подходами.
Простые но Эффективные Модели
В этой главе мы рассмотрим две модели, которые доказывают, что простота может быть мощной: EASE и SLIM. Обе модели основаны на линейных методах, но достигают результатов, сравнимых с гораздо более сложными deep learning подходами.
EASE - Embarrassingly Shallow Autoencoders
Теория
EASE (Embarrassingly Shallow Autoencoders for Sparse Data) - это линейная модель с закрытым решением, которая удивительно хорошо работает на implicit feedback данных.
Примеры использования
Базовый пример
from recommender import EASERecommender, load_movielens, InteractionDataset, Evaluator
# 1. Загрузка и подготовка данных
df = load_movielens(size='100k')
dataset = InteractionDataset(df, implicit=True, min_user_interactions=5)
train, test = dataset.split(test_size=0.2, strategy='random', seed=42)
# 2. Создание модели
model = EASERecommender(l2_reg=500.0)
# 3. Обучение (очень быстро!)
import time
start = time.time()
model.fit(train.data)
print(f"Обучение заняло {time.time() - start:.2f} секунд")
# 4. Генерация рекомендаций
user_ids = [1, 2, 3, 4, 5]
recommendations = model.recommend(user_ids, k=10, exclude_seen=True)
for user_id, items in recommendations.items():
print(f"User {user_id}: {[item_id for item_id, score in items[:5]]}")
# 5. Оценка модели
evaluator = Evaluator(metrics=['precision', 'recall', 'ndcg'], k_values=[10, 20])
results = evaluator.evaluate(model, test, task='ranking', train_data=train)
evaluator.print_results(results)
Подбор гиперпараметров
from recommender import EASERecommender, cross_validate
# Grid search по l2_reg
l2_values = [100, 200, 500, 800, 1000]
best_ndcg = 0
best_l2 = None
for l2_reg in l2_values:
print(f"\nТестирование l2_reg={l2_reg}")
model = EASERecommender(l2_reg=l2_reg)
model.fit(train.data)
results = evaluator.evaluate(model, test, task='ranking', train_data=train)
ndcg = results['ndcg@10']
print(f"NDCG@10: {ndcg:.4f}")
if ndcg > best_ndcg:
best_ndcg = ndcg
best_l2 = l2_reg
print(f"\n✅ Лучший l2_reg: {best_l2} (NDCG@10: {best_ndcg:.4f})")
Анализ item схожести
# Получить похожие items
model = EASERecommender(l2_reg=500.0)
model.fit(train.data)
# Анализ схожести для конкретного item
target_item = 50 # ID item'а
# Получить top-10 похожих items
similar_items = model.get_similar_items(target_item, k=10)
print(f"Items похожие на {target_item}:")
for item_id, similarity in similar_items:
print(f" Item {item_id}: similarity = {similarity:.4f}")
Сохранение и загрузка
# Сохранение
model.save('ease_model.pkl')
print("✅ Модель сохранена")
# Загрузка
loaded_model = EASERecommender()
loaded_model.load('ease_model.pkl')
# Проверка
recs = loaded_model.recommend([1, 2], k=5)
print("✅ Модель загружена и работает")
Производительность
Время обучения (Intel i7, 16GB RAM):
| Датасет | Пользователи | Items | Взаимодействия | Время |
|---|---|---|---|---|
| ML-100K | 943 | 1,682 | 100K | ~2 сек |
| ML-1M | 6,040 | 3,706 | 1M | ~5 сек |
| ML-10M | 69,878 | 10,677 | 10M | ~45 сек |
Качество (NDCG@10 на implicit feedback):
| Датасет | EASE | SLIM | ALS |
|---|---|---|---|
| ML-100K | 0.381 | 0.365 | 0.352 |
| ML-1M | 0.385 | 0.372 | 0.357 |
Когда использовать EASE
✅ Хорошо подходит:
- Implicit feedback (клики, просмотры, покупки)
- Нужен быстрый baseline
- Небольшие и средние датасеты (< 100K items)
- Когда важна скорость обучения
- Стабильный, воспроизводимый результат
❌ Не подходит:
- Explicit ratings (используйте SVD/SVD++)
- Очень большие датасеты (> 100K items, может быть медленно из-за матричной инверсии)
- Нужна интерпретируемость (используйте SLIM)
- Cold start для новых items (нет item features)
SLIM - Sparse Linear Methods
Теория
SLIM (Sparse Linear Methods) учит разреженную матрицу схожести items путём решения item-wise регрессионных задач с L1/L2 регуляризацией.
Ключевые особенности
- L1 регуляризация → разреженная матрица (большинство весов = 0)
- L2 регуляризация → гладкие веса, устойчивость
- Item-wise optimization → можно параллелить
Гиперпараметры
l1_reg (λ₁)
- Описание: L1 регуляризация для разреженности
- Диапазон: 0.01 - 10.0
- По умолчанию: 0.1
- Влияние:
- Малые (< 0.1): Плотная матрица, переобучение
- Средние (0.1-1.0): Хороший баланс
- Большие (> 1.0): Очень разреженная матрица, недообучение
l2_reg (λ₂)
- Описание: L2 регуляризация для гладкости
- Диапазон: 0.01 - 10.0
- По умолчанию: 0.1
- Влияние: Стабилизирует веса, предотвращает экстремальные значения
max_iter
- Описание: Максимум итераций для Elastic Net
- Диапазон: 50 - 1000
- По умолчанию: 100
- Влияние: Больше итераций → лучше сходимость, но медленнее
positive_only
- Описание: Ограничить веса положительными значениями
- Значения: True/False
- По умолчанию: True
- Рекомендация: Всегда True для интерпретируемости
Примеры использования
Базовый пример
from recommender import SLIMRecommender, load_movielens, InteractionDataset
# 1. Подготовка данных
df = load_movielens(size='100k')
dataset = InteractionDataset(df, implicit=True, min_user_interactions=5)
train, test = dataset.split(test_size=0.2, strategy='random', seed=42)
# 2. Создание и обучение SLIM
model = SLIMRecommender(
l1_reg=0.1,
l2_reg=0.1,
max_iter=100,
positive_only=True
)
print("Обучение SLIM (может занять несколько минут)...")
model.fit(train.data)
# 3. Рекомендации
recommendations = model.recommend([1, 2, 3], k=10, exclude_seen=True)
for user_id, items in recommendations.items():
print(f"User {user_id}: {[item_id for item_id, _ in items[:5]]}")
Анализ разреженности
# Обучение SLIM
model = SLIMRecommender(l1_reg=0.1, l2_reg=0.1)
model.fit(train.data)
# Анализ разреженности матрицы схожести
import numpy as np
sim_matrix = model.item_sim_matrix
n_total = sim_matrix.size
n_nonzero = np.count_nonzero(sim_matrix)
sparsity = 100 * (1 - n_nonzero / n_total)
print(f"Размер матрицы: {sim_matrix.shape}")
print(f"Ненулевых элементов: {n_nonzero:,} / {n_total:,}")
print(f"Разреженность: {sparsity:.2f}%")
# Распределение ненулевых весов
nonzero_weights = sim_matrix[sim_matrix > 0]
print(f"\nСтатистика ненулевых весов:")
print(f" Min: {nonzero_weights.min():.4f}")
print(f" Max: {nonzero_weights.max():.4f}")
print(f" Mean: {nonzero_weights.mean():.4f}")
print(f" Median: {np.median(nonzero_weights):.4f}")
Интерпретация похожих items
# SLIM особенно хорош для интерпретируемости
model = SLIMRecommender(l1_reg=0.5, l2_reg=0.1) # Более разреженная модель
model.fit(train.data)
# Получить похожие items с весами
target_item = 50
similar_items = model.get_similar_items(target_item, k=20)
print(f"Items наиболее похожие на item {target_item}:")
for item_id, weight in similar_items[:10]:
print(f" Item {item_id}: вес = {weight:.4f}")
# Можно объяснить рекомендацию!
# "Мы рекомендуем item X, потому что он похож на item Y (вес 0.85),
# с которым пользователь взаимодействовал"
Сравнение с EASE
from recommender import EASERecommender, SLIMRecommender, Evaluator
# Подготовка
evaluator = Evaluator(metrics=['precision', 'recall', 'ndcg'], k_values=[10])
# EASE
ease_model = EASERecommender(l2_reg=500.0)
ease_model.fit(train.data)
ease_results = evaluator.evaluate(ease_model, test, task='ranking', train_data=train)
# SLIM
slim_model = SLIMRecommender(l1_reg=0.1, l2_reg=0.1, max_iter=100)
slim_model.fit(train.data)
slim_results = evaluator.evaluate(slim_model, test, task='ranking', train_data=train)
# Сравнение
print("\nСравнение EASE vs SLIM:")
print(f"{'Метрика':<15} {'EASE':<10} {'SLIM':<10}")
print("-" * 35)
for metric in ['precision@10', 'recall@10', 'ndcg@10']:
ease_val = ease_results[metric]
slim_val = slim_results[metric]
print(f"{metric:<15} {ease_val:<10.4f} {slim_val:<10.4f}")
Производительность
Время обучения (Intel i7, 16GB RAM):
| Датасет | Пользователи | Items | Время |
|---|---|---|---|
| ML-100K | 943 | 1,682 | ~2 мин |
| ML-1M | 6,040 | 3,706 | ~15 мин |
Разреженность матрицы (при l1_reg=0.1):
| Датасет | Разреженность |
|---|---|
| ML-100K | ~95% |
| ML-1M | ~97% |
Когда использовать SLIM
✅ Хорошо подходит:
- Нужна интерпретируемость рекомендаций
- Важна разреженность модели (меньше памяти)
- Implicit feedback
- Средние датасеты (1K - 10K items)
- Когда можно подождать обучение
❌ Не подходит:
- Нужна максимальная скорость обучения (используйте EASE)
- Очень большие датасеты (> 10K items, слишком медленно)
- Explicit ratings (используйте SVD)
- Real-time адаптация модели
Сравнение EASE и SLIM
Таблица сравнения
| Аспект | EASE | SLIM |
|---|---|---|
| Скорость обучения | ⚡⚡⚡ Очень быстро (секунды) | ⚡ Медленно (минуты) |
| Качество | 🎯 Отлично | 🎯 Отлично |
| Интерпретируемость | ⭐⭐ Средняя | ⭐⭐⭐ Высокая |
| Разреженность | ❌ Плотная матрица | ✅ Разреженная |
| Память | 💾 Больше | 💾 Меньше |
| Гиперпараметры | 1 (простая настройка) | 4 (сложнее) |
| Масштабируемость | До 100K items | До 10K items |
| Стабильность | ✅ Детерминированный | ⚠️ Зависит от сходимости |
Когда что выбрать?
Выбирайте EASE если:
- Нужен быстрый и качественный baseline
- Ограничено время на обучение
- Датасет средний или большой
- Хотите простую настройку (1 гиперпараметр)
- Стабильность важнее интерпретируемости
Выбирайте SLIM если:
- Нужна интерпретируемость рекомендаций
- Важна разреженность модели (ограничена память)
- Есть время на подбор гиперпараметров
- Датасет небольшой или средний
- Планируете объяснять рекомендации пользователям
Попробуйте оба! На большинстве датасетов качество будет похожим, но каждая модель имеет свои преимущества.
Практические советы
1. Начните с EASE
# Быстрый baseline для оценки верхней планки качества
model = EASERecommender(l2_reg=500.0)
model.fit(train.data)
2. Если нужна интерпретируемость → SLIM
# Разреженная, интерпретируемая модель
model = SLIMRecommender(l1_reg=0.5, l2_reg=0.1)
model.fit(train.data)
3. Подбор l2_reg для EASE
# Попробуйте значения: [100, 200, 500, 800, 1000]
# Выберите по лучшему NDCG@10 на validation
4. Баланс L1/L2 для SLIM
# Равные веса - хорошее начало
model = SLIMRecommender(l1_reg=0.1, l2_reg=0.1)
# Больше L1 → более разреженная модель
model = SLIMRecommender(l1_reg=0.5, l2_reg=0.1)
# Больше L2 → более гладкие веса
model = SLIMRecommender(l1_reg=0.1, l2_reg=0.5)
5. Ensemble EASE + SLIM
from recommender.utils import ModelEnsemble
# Обучение обеих моделей
ease = EASERecommender(l2_reg=500.0)
ease.fit(train.data)
slim = SLIMRecommender(l1_reg=0.1, l2_reg=0.1)
slim.fit(train.data)
# Ensemble
ensemble = ModelEnsemble(
models=[ease, slim],
weights=[0.6, 0.4], # EASE обычно чуть лучше
strategy='weighted_average'
)
# Рекомендации от ensemble
recommendations = ensemble.recommend([1, 2, 3], k=10)
Научные статьи
EASE
- Название: Embarrassingly Shallow Autoencoders for Sparse Data
- Авторы: Harald Steck
- Конференция: WWW 2019
- Ссылка: https://arxiv.org/abs/1905.03375
SLIM
- Название: SLIM: Sparse Linear Methods for Top-N Recommender Systems
- Авторы: Xia Ning, George Karypis
- Конференция: ICDM 2011
- Ссылка: https://ieeexplore.ieee.org/document/6137254
Что дальше?
- Matrix Factorization модели - SVD, SVD++, ALS
- Нейронные модели - NCF, LightGCN, SASRec
- Обучение моделей - подбор гиперпараметров, early stopping

