AI-система персонализации учебного пути
Персонализация учебного пути — задача рекомендательной системы для образовательного контента. Отличие от Netflix: важна не только релевантность, но и педагогическая последовательность. Нельзя рекомендовать advanced-модуль студенту без базы, даже если модель считает его «интересным».
Граф знаний и навигация по нему
import networkx as nx
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
class KnowledgeGraph:
"""Граф зависимостей учебных модулей"""
def __init__(self):
self.graph = nx.DiGraph()
def add_module(self, module_id: str, metadata: dict):
self.graph.add_node(module_id, **metadata)
def add_prerequisite(self, module_id: str, prerequisite_id: str):
"""prerequisite → module (нужно изучить prerequisite перед module)"""
self.graph.add_edge(prerequisite_id, module_id)
def get_unlocked_modules(self, completed_modules: set[str]) -> list[str]:
"""Модули, доступные после прохождения completed"""
unlocked = []
for node in self.graph.nodes():
if node in completed_modules:
continue
predecessors = set(self.graph.predecessors(node))
if predecessors.issubset(completed_modules):
unlocked.append(node)
return unlocked
def get_shortest_path_to_goal(self, start_modules: set[str],
goal_module: str) -> list[str]:
"""Минимальный путь к цели через граф зависимостей"""
# Виртуальный стартовый узел
self.graph.add_node('__start__')
for m in start_modules:
self.graph.add_edge('__start__', m)
try:
path = nx.shortest_path(self.graph, '__start__', goal_module)
return [p for p in path if p != '__start__']
except nx.NetworkXNoPath:
return []
finally:
self.graph.remove_node('__start__')
class LearningPathRecommender:
"""Персонализированный учебный маршрут"""
def __init__(self, knowledge_graph: KnowledgeGraph):
self.kg = knowledge_graph
self.completion_predictor = GradientBoostingClassifier(
n_estimators=100, random_state=42
)
def recommend_path(self, student: dict,
goal: str,
max_modules: int = 10) -> list[dict]:
"""
Рекомендация учебного пути к цели.
Учитывает: завершённые модули, скорость обучения, предпочтения формата.
"""
completed = set(student.get('completed_modules', []))
unlocked = self.kg.get_unlocked_modules(completed)
# Фильтруем только модули на пути к цели
path_modules = self.kg.get_shortest_path_to_goal(completed, goal)
relevant = [m for m in unlocked if m in path_modules]
if not relevant:
relevant = unlocked[:max_modules]
# Скоринг каждого модуля
scored = []
for module_id in relevant[:20]: # Ограничиваем перебор
module = self.kg.graph.nodes[module_id]
score = self._score_module(module, student)
scored.append({
'module_id': module_id,
'title': module.get('title', ''),
'type': module.get('type', 'video'),
'duration_min': module.get('duration_min', 30),
'difficulty': module.get('difficulty', 3),
'score': score,
'reason': self._explain_score(module, student, score)
})
scored.sort(key=lambda x: -x['score'])
return scored[:max_modules]
def _score_module(self, module: dict, student: dict) -> float:
"""Скоринг: релевантность + формат + сложность"""
# Сложность относительно уровня студента
student_level = student.get('avg_score', 0.6)
module_difficulty = module.get('difficulty', 3) / 5 # 0-1
difficulty_fit = 1.0 - abs(student_level - 0.7 - (module_difficulty - 0.5) * 0.4)
# Соответствие предпочтениям формата
preferred_types = student.get('preferred_content_types', ['video'])
format_score = 1.2 if module.get('type') in preferred_types else 0.8
# Популярность (социальное доказательство)
completion_rate = module.get('completion_rate', 0.5)
# Оценка ценности с учётом карьерной цели
goal_relevance = module.get('goal_tags', {}).get(student.get('career_goal', ''), 0.5)
return difficulty_fit * 0.3 + format_score * 0.2 + completion_rate * 0.2 + goal_relevance * 0.3
def _explain_score(self, module: dict, student: dict, score: float) -> str:
reasons = []
if module.get('completion_rate', 0) > 0.8:
reasons.append(f"{module['completion_rate']:.0%} студентов завершили")
if module.get('type') in student.get('preferred_content_types', []):
reasons.append(f"Ваш предпочтительный формат: {module['type']}")
if not reasons:
reasons.append("Рекомендовано на основе прогресса")
return '; '.join(reasons)
class StudyScheduler:
"""Планировщик учебного расписания"""
def create_schedule(self, path: list[dict],
available_hours_per_week: float,
deadline_weeks: int = None) -> pd.DataFrame:
"""Распределение модулей по неделям"""
total_hours = sum(m['duration_min'] / 60 for m in path)
if deadline_weeks:
required_hours_per_week = total_hours / deadline_weeks
if required_hours_per_week > available_hours_per_week * 1.2:
# Нереалистичный план — предупреждаем
return pd.DataFrame({'warning': [
f'Для достижения цели к дедлайну нужно {required_hours_per_week:.1f} ч/нед, '
f'у вас только {available_hours_per_week} ч/нед'
]})
schedule = []
current_week = 1
week_hours = 0
for module in path:
module_hours = module['duration_min'] / 60
if week_hours + module_hours > available_hours_per_week and week_hours > 0:
current_week += 1
week_hours = 0
schedule.append({
'week': current_week,
'module_id': module['module_id'],
'title': module['title'],
'duration_hours': round(module_hours, 1)
})
week_hours += module_hours
return pd.DataFrame(schedule)
Система персонализации учебных путей сокращает среднее время до первой компетенции на 30-40% за счёт исключения ненужных модулей и правильного порядка изучения. Ключевой KPI — time-to-competency, а не completion rate.







