Помните тот момент, когда вы нашли видео на YouTube, которое было именно тем, что вам нужно? Это была не магия, а математика. И сегодня мы собираемся создать нечто удивительно похожее для онлайн-курсов. Если вы когда-нибудь задумывались, как такие платформы, как Coursera или Udemy, будто знают, какой курс вы захотите изучить следующим, пристегните ремни. Мы погружаемся в прекрасный мир коллаборативной фильтрации.

Почему рекомендательные системы важны (и почему это не просто мода)

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

Коллаборативная фильтрация особенно эффективна для онлайн-курсов, потому что модели обучения студентов удивительно предсказуемы. Если вы и другой обучающийся прошли одни и те же пять курсов и оценили их схоже, есть большая вероятность, что вам обоим понравится один и тот же новый курс. Это как найти своего двойника в обучении.

Понимание коллаборативной фильтрации: философия

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

Есть два основных подхода:

Коллаборативная фильтрация на основе пользователей объединяет похожих обучающихся вместе. Если ваша история обучения напоминает мою, и мне понравился курс по машинному обучению, система порекомендует его вам.

Коллаборативная фильтрация на основе предметов находит курсы, похожие на те, которые вам уже понравились. Если вам понравился курс «Python для начинающих», система может порекомендовать «JavaScript для начинающих», потому что обучающиеся обычно оценивают их схоже.

Оба подхода используются в матрице «пользователь-предмет» — представьте себе гигантскую таблицу, где строки — это студенты, столбцы — курсы, а ячейки содержат оценки или показатели вовлечённости. Большинство ячеек пустые (мы не взаимодействовали с большинством курсов), что является «проблемой разреженности», делающей эту задачу интересной.

Архитектура: как всё это сочетается

Позвольте мне показать вам, как эти части соединяются, прежде чем мы начнём кодировать:

graph LR A[Данные пользователей] --> D[Конвейер данных] B[Данные курсов] --> D C[Оценки/Взаимодействия] --> D D --> E[Матрица пользователь-предмет] E --> F[Вычисление сходства] F --> G{Механизм прогнозирования} G --> H[Рекомендуемые курсы] H --> I[Пользовательский интерфейс] style E fill:#e1f5ff style F fill:#fff3e0 style G fill:#f3e5f5

Давайте создадим: пошаговая реализация

Шаг 1: Подготовьте среду и загрузите зависимости

Сначала установите необходимые пакеты. Я использую scikit-surprise, потому что он создан специально для рекомендательных систем и берёт на себя множество сложностей.

pip install numpy pandas scikit-learn scikit-surprise matplotlib seaborn

Теперь давайте импортируем всё:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import StandardScaler
from sklearn.metrics.pairwise import cosine_similarity
from surprise import Dataset, Reader, KNNWithMeans, SVD
from surprise.model_selection import train_test_split
from surprise import accuracy
import warnings
warnings.filterwarnings('ignore')

Шаг 2: Подготовьте данные

Предполагая, что у вас есть три CSV-файла (что является обычной структурой для такого типа данных), давайте загрузим их:

# Загрузка наборов данных
students = pd.read_csv('students.csv')
courses = pd.read_csv('courses.csv')
ratings = pd.read_csv('ratings.csv')
# Давайте посмотрим, что у нас есть
print("Форма данных студентов:", students.shape)
print("Форма данных курсов:", courses.shape)
print("Форма данных оценок:", ratings.shape)
# Проверка качества данных
print("\nИнформация об оценках:")
print(ratings.info())
print("\nПример оценок:")
print(ratings.head())

Ваш файл с оценками должен содержать как минимум: student_id, course_id и rating. Оценка может быть явной (от 1 до 5 звёзд) или неявной (время, затраченное на прохождение, статус завершения).

Шаг 3: Очистка и предобработка данных

Здесь происходит негламурная работа, но она абсолютно важна:

# Удаление повторяющихся курсов (иногда они импортируются дважды)
courses_clean = courses.drop_duplicates(subset=['course_id'])
# Объединение оценок с метаданными курса
ratings_enriched = ratings.merge(courses_clean, on='course_id', how='left')
ratings_enriched = ratings_enriched.merge(students, on='student_id', how='left')
# Удаление строк с пропущенными оценками (иногда студенты не заполняют оценку)
ratings_enriched = ratings_enriched.dropna(subset=['rating'])
# Фильтрация по вовлечённости: оставляем студентов, которые оценили несколько курсов
student_course_count = ratings_enriched.groupby('student_id').size()
active_students = student_course_count[student_course_count >= 3].index
ratings_filtered = ratings_enriched[ratings_enriched['student_id'].isin(active_students)]
print(f"После фильтрации: {len(ratings_filtered)} оценок от {len(active_students)} студентов")
print(f"Разреженность: {1 - (len(ratings_filtered) / (len(active_students) * len(courses_clean))): .2%}")

Этот показатель разреженности tells you, сколько ячеек в вашей матрице «пользователь-предмет» пустые. В большинстве реальных систем разреженность составляет более 95%, и это нормально.

Шаг 4: Создание матрицы «пользователь-предмет»

Эта матрица — сердце коллаборативной фильтрации:

# Создание матрицы оценок
rating_matrix = ratings_filtered.pivot_table(
    index='student_id',
    columns='course_name',
    values='rating',
    fill_value=0
)
print(f"Форма матрицы: {rating_matrix.shape}")
print(f"Пример матрицы:\n{rating_matrix.iloc[:5, :5]}")
# Нормализация матрицы (важно для вычислений сходства)
# Нулевое среднее и единичное стандартное отклонение для каждого студента
rating_matrix_normalized = rating_matrix.copy()
for idx in rating_matrix_normalized.index:
    mean_val = rating_matrix_normalized.loc[idx].mean()
    std_val = rating_matrix_normalized.loc[idx].std()
    if std_val > 0:
        rating_matrix_normalized.loc[idx] = (rating_matrix_normalized.loc[idx] - mean_val) / std_val
    rating_matrix_normalized.loc[idx] = rating_matrix_normalized.loc[idx].fillna(0)

Шаг 5: Вычисление сходств

Здесь начинают формироваться фактические рекомендации. Мы вычисляем, насколько похожи студенты друг на друга:

# Вычисление сходства между пользователями с помощью косинусного сходства
user_similarity = cosine_similarity(rating_matrix_normalized)
user_similarity_df = pd.DataFrame(
    user_similarity,
    index=rating_matrix.index,
    columns=rating_matrix.index
)
# Проверка сходства для примера студента
sample_student = rating_matrix.index
similar_students = user_similarity_df[sample_student].sort_values(ascending=False)[1:6]
print(f"Студенты, наиболее похожие на {sample_student}:")
print(similar_students)

Шаг 6: Построение функции рекомендаций

Теперь самое интересное — функция, которая фактически генерирует рекомендации:

def recommend_courses(student_id, user_similarity_df, rating_matrix, num_recommendations=5, num_similar_users=10):
    """
    Рекомендовать курсы студенту на основе оценок похожих студентов.
    Args:
        student_id: ID студента, для которого нужно сделать рекомендации
        user_similarity_df: DataFrame сходства пользователей
        rating_matrix: Исходная матрица оценок
        num_recommendations: Сколько курсов рекомендовать
        num_similar_users: Сколько похожих студентов учитывать
    Returns:
        DataFrame с рекомендованными курсами и прогнозируемыми оценками
    """
    if student_id not in user_similarity_df.index:
        return pd.DataFrame({'ошибка': ['Студент не найден']})
    # Найти похожих студентов
    similar_users = user_similarity_df[student_id