Перейти к содержимому

NB-4 — IRT vs BKT

Вопрос на питче: «А почему не IRT? Это же стандарт в образовательном тестировании.»

IRTBKT
Что моделируетСпособность ученика + трудность задачиСостояние навыка во времени
ВремяСнэпшот (одно тестирование)Динамика (последовательность задач)
Скрытая переменнаяθ\theta — «уровень» (число)LtL_t — «состояние» (бит)
ИспользованиеSAT, GRE, диагностический тестAdaptive learning, tutoring
Что даёт«Иван набрал 78 на шкале»«Иван знает раскрытие скобок с P=0.42»
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize

Three-Parameter Logistic — самый общий вариант:

P(correctθ,a,b,c)=c+(1c)σ ⁣(a(θb))P(\text{correct} \mid \theta, a, b, c) = c + (1 - c) \cdot \sigma\!\left(a (\theta - b)\right)
  • θ\theta — способность ученика;
  • aa — discrimination (наклон, насколько задача различает уровни);
  • bb — difficulty (точка перегиба);
  • cc — guess parameter (нижняя асимптота).
def irt_3pl(theta, a, b, c):
return c + (1 - c) / (1 + np.exp(-a * (theta - b)))
# trio типичных задач
fig, ax = plt.subplots(figsize=(8, 4))
xs = np.linspace(-3, 3, 200)
for a, b, c, label in [
(1.0, -1.0, 0.1, 'легкая (b=−1)'),
(1.5, 0.0, 0.15, 'средняя (b=0)'),
(1.0, 1.5, 0.05, 'сложная (b=+1.5)'),
]:
ax.plot(xs, irt_3pl(xs, a, b, c), label=label)
ax.set_xlabel('θ — способность'); ax.set_ylabel('P(correct)')
ax.legend(); ax.grid(alpha=0.3)
plt.title('IRT 3PL — кривые задач')
plt.show()

Ученик с θ=0\theta = 0:

  • лёгкую решит с P ≈ 0.65;
  • среднюю с P ≈ 0.50;
  • сложную с P ≈ 0.13.
  • Тест на 30 минут: 20 задач, единый снэпшот → точная оценка θ\theta.
  • Стандартизация: все ученики получают сравнимый score.
  • Item bank calibration: задачи можно заранее «откалибровать» по трудности.
  1. Нет времени. θ\theta — константа в момент теста. После 10 правильных ответов IRT не «обновит знания», она лишь точнее оценит то же θ\theta.
  2. Нет навыков. Задача характеризуется числом bb, а не набором микро-навыков. «Раскрытие скобок» и «знаки» — для IRT это просто разные bb.
  3. Нет учёта обучения. Если ученик попрактиковался и подтянулся, IRT видит это как «поменялась θ\theta». BKT видит как «P(L) для соответствующего навыка вырос с 0.4 до 0.8».
  4. Сложнее объяснить учителю. «У Ивана θ=0.3\theta = 0.3» — что это? У нас — «P(L) для скобок = 0.42, для знаков = 0.83» — конкретно и действенно.

Симулируем ученика, который подтягивается на навыке за серию правильных ответов.

def bkt_update(pL, c, p={'pT':0.1,'pS':0.1,'pG':0.2}):
if c:
post = (pL*(1-p['pS'])) / (pL*(1-p['pS'])+(1-pL)*p['pG'])
else:
post = (pL*p['pS']) / (pL*p['pS']+(1-pL)*(1-p['pG']))
return post + (1-post)*p['pT']
def fit_irt_theta(answers, b=0.0, a=1.0, c=0.1):
"""МLE оценка theta при фиксированных параметрах задач."""
def neg_log_lik(theta):
p = irt_3pl(theta, a, b, c)
return -sum(np.log(p if x else 1-p) for x in answers)
res = minimize(neg_log_lik, x0=0.0, bounds=[(-4, 4)])
return res.x[0]
answers = [0, 1, 1, 0, 1, 1, 1, 1, 1, 1] # подтянулся
# BKT — состояние P(L) на каждом шаге
pL_traj = [0.2]
pL = 0.2
for a in answers:
pL = bkt_update(pL, a)
pL_traj.append(pL)
# IRT — оценка theta после 1, 2, ... наблюдений
theta_traj = []
for k in range(1, len(answers)+1):
theta_traj.append(fit_irt_theta(answers[:k]))
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))
ax1.plot(pL_traj, marker='o', color='#9333ea', linewidth=2)
ax1.set_title('BKT — P(L) растёт во времени')
ax1.set_xlabel('шаг'); ax1.set_ylabel('P(L)')
ax1.grid(alpha=0.3); ax1.set_ylim(0, 1)
ax2.plot(theta_traj, marker='s', color='#0ea5e9', linewidth=2)
ax2.set_title('IRT — θ-оценка плавает на одном уровне')
ax2.set_xlabel('после k наблюдений'); ax2.set_ylabel('θ MLE')
ax2.grid(alpha=0.3)
plt.tight_layout(); plt.show()

BKT покажет рост от 0.2 до ~0.85 — модель видит обучение. IRT покажет колебание оценки θ\theta вокруг константы — она не верит в изменение, считает все ответы реализациями одной и той же способности.

В реальных исследовательских системах (например, Cognitive Tutor от CMU) используют гибрид:

  • IRT для первоначальной диагностики при первом входе ученика;
  • BKT для отслеживания развития в процессе.

Для нашего MVP это избыточно. Старт с pInit=0.2 + BKT работает.

«IRT — для тестирования снэпшота, BKT — для отслеживания развития. Мы делаем adaptive learning, а не аттестацию. К тому же BKT работает по микро-навыкам, что позволяет учителю видеть конкретные пробелы, а не абстрактный ‘уровень’.»