Простые но Эффективные Модели

В этой главе мы рассмотрим две модели, которые доказывают, что простота может быть мощной: 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 регуляризацией.

Ключевые особенности

  1. L1 регуляризация → разреженная матрица (большинство весов = 0)
  2. L2 регуляризация → гладкие веса, устойчивость
  3. 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

Что дальше?

AI на дровах 🪵
Привет! Меня зовут Семён, я работаю в сфере ML и аналитики данных и пишу в блог nerdit.ru статьи о своем опыте и том, что может пригодиться начинающим в начале их пути изучения больших данных.

Подписаться на новости Nerd IT

Не пропустите последние выпуски. Зарегистрируйтесь сейчас, чтобы получить полный доступ к статьям.
jamie@example.com
Подписаться