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

NB-2 — Parameter sensitivity

Дефолты 0.2 / 0.1 / 0.1 / 0.2 для (P(L0),P(T),P(S),P(G))(P(L_0), P(T), P(S), P(G)) — не магия и не «подгонка под результат». В этом ноутбуке мы сломаем модель в каждом параметре по очереди и покажем, что именно сломается.

import numpy as np
import matplotlib.pyplot as plt
from itertools import product
def bkt_update(pL, observed, p):
pS, pG, pT = p['pSlip'], p['pGuess'], p['pTransit']
if observed:
post = (pL * (1 - pS)) / (pL * (1 - pS) + (1 - pL) * pG)
else:
post = (pL * pS) / (pL * pS + (1 - pL) * (1 - pG))
return post + (1 - post) * pT
def trajectory(p, answers, pL0=None):
pL = pL0 if pL0 is not None else p['pInit']
trace = [pL]
for a in answers:
pL = bkt_update(pL, a, p)
trace.append(pL)
return trace

Эксперимент 1 — «доверчивая» модель (P(G)=0.5P(G) = 0.5)

Заголовок раздела «Эксперимент 1 — «доверчивая» модель (P(G)=0.5P(G) = 0.5P(G)=0.5)»

Ученик отвечает 10 раз правильно подряд.

defaults = {"pInit": 0.2, "pTransit": 0.1, "pSlip": 0.1, "pGuess": 0.2}
broken_g = {**defaults, "pGuess": 0.5}
answers = [True] * 10
t_def = trajectory(defaults, answers)
t_brk = trajectory(broken_g, answers)
print(f"После 10 ✓ — defaults: P(L) = {t_def[-1]:.3f}")
print(f"После 10 ✓ — P(G)=0.5: P(L) = {t_brk[-1]:.3f}")
# defaults: 0.881 ← модель «поверила» в обучение
# P(G)=0.5: 0.612 ← модель «угадывание объясняет всё», ученик не учится

При P(G)=0.5P(G) = 0.5 модель не верит правильным ответам — даже после 10 успехов P(L)P(L) не дотягивает до 0.7. Ученика будут гонять одни и те же задачи бесконечно.

Эксперимент 2 — «недоверчивая» модель (P(S)=0.5P(S) = 0.5)

Заголовок раздела «Эксперимент 2 — «недоверчивая» модель (P(S)=0.5P(S) = 0.5P(S)=0.5)»

Тот же ученик, но 10 раз ошибается.

broken_s = {**defaults, "pSlip": 0.5}
answers = [False] * 10
t_def = trajectory(defaults, answers)
t_brk = trajectory(broken_s, answers)
print(f"После 10 ✗ — defaults: P(L) = {t_def[-1]:.3f}")
print(f"После 10 ✗ — P(S)=0.5: P(L) = {t_brk[-1]:.3f}")
# defaults: 0.072 ← модель видит «он не знает»
# P(S)=0.5: 0.181 ← модель «слипа объясняет всё», даёт сложные

При P(S)=0.5P(S) = 0.5 модель не верит ошибкам — даже после 10 провалов считает, что ученик «просто немного оплошал». Слабым ученикам будут давать слишком сложные задачи. Это деморализует.

Эксперимент 3 — «неподвижный» ученик (P(T)=0P(T) = 0)

Заголовок раздела «Эксперимент 3 — «неподвижный» ученик (P(T)=0P(T) = 0P(T)=0)»
no_transit = {**defaults, "pTransit": 0.0}
answers = [True] * 50
t_def = trajectory(defaults, answers)
t_brk = trajectory(no_transit, answers)
print(f"50 ✓ — defaults: P(L) = {t_def[-1]:.3f}")
print(f"50 ✓ — P(T)=0: P(L) = {t_brk[-1]:.3f}")
# defaults: 0.999 ← ученик может «учиться», достигает мастерства
# P(T)=0: 0.998 ← BTW даже без transit достигает, через демонстрацию...
# но: разница в скорости — defaults быстрее

Тонкий момент: P(T)=0P(T) = 0 не означает «ученик не достигнет P(L)=1P(L) = 1». Он достигнет — просто через демонстрацию знания. Но скорость ниже, и в начале (когда P(L)P(L) маленький) траектория плоская.

fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(t_def, label='defaults (P(T)=0.1)', color='#9333ea', linewidth=2)
ax.plot(t_brk, label='P(T)=0', color='#ef4444', linewidth=2, linestyle='--')
ax.set_xlabel('шаг'); ax.set_ylabel('P(L)')
ax.legend(); ax.grid(alpha=0.3)
plt.show()

Эксперимент 4 — «быстрый» ученик (P(T)=0.5P(T) = 0.5)

Заголовок раздела «Эксперимент 4 — «быстрый» ученик (P(T)=0.5P(T) = 0.5P(T)=0.5)»
fast_transit = {**defaults, "pTransit": 0.5}
answers = [True] * 5
t_def = trajectory(defaults, answers)
t_brk = trajectory(fast_transit, answers)
print(f"5 ✓ — defaults: P(L) = {t_def[-1]:.3f}")
print(f"5 ✓ — P(T)=0.5: P(L) = {t_brk[-1]:.3f}")
# defaults: 0.628
# P(T)=0.5: 0.981 ← осваивает за 3-4 правильных ответа

P(T)=0.5P(T) = 0.5 — нереалистично для школьной математики. Подходит для быстрого освоения простых задач взрослыми (например, «как пользоваться калькулятором»), не для развития навыков.

patterns = {
'все правильно': [True] * 10,
'все ошибки': [False] * 10,
'через один': [True, False] * 5,
'старт ошибками': [False] * 5 + [True] * 5,
'старт верно': [True] * 5 + [False] * 5,
}
configs = {
'defaults': defaults,
'P(G)=0.5': {**defaults, 'pGuess': 0.5},
'P(S)=0.5': {**defaults, 'pSlip': 0.5},
'P(T)=0': {**defaults, 'pTransit': 0.0},
'P(T)=0.5': {**defaults, 'pTransit': 0.5},
}
mat = np.zeros((len(configs), len(patterns)))
for i, (cn, cp) in enumerate(configs.items()):
for j, (pn, ans) in enumerate(patterns.items()):
mat[i, j] = trajectory(cp, ans)[-1]
fig, ax = plt.subplots(figsize=(9, 4))
im = ax.imshow(mat, cmap='RdYlGn', vmin=0, vmax=1, aspect='auto')
ax.set_xticks(range(len(patterns))); ax.set_xticklabels(patterns.keys(), rotation=20)
ax.set_yticks(range(len(configs))); ax.set_yticklabels(configs.keys())
for i in range(mat.shape[0]):
for j in range(mat.shape[1]):
ax.text(j, i, f'{mat[i,j]:.2f}', ha='center', va='center', fontsize=9)
plt.colorbar(im, label='итоговый P(L)')
plt.title('Финальный P(L) после 10 ответов: параметры × паттерн')
plt.tight_layout(); plt.show()

Виджет ниже — тот же pSolve(pL) график, что и в главе 4. Подвигай ползунки и проверь интуицию: P(S)+P(G)>1P(S) + P(G) > 1 означает, что прямая идёт вниз — чем больше ученик «знает», тем меньше шанс решить. Бессмысленно.

Прямая. P(solve) при P(L)=0 равно P(G), при P(L)=1 равно 1−P(S). Наклон = 1−P(S)−P(G).

ПараметрСлишком низкоСлишком высоко
P(L0)P(L_0)новые ученики кажутся слабыми, ZPD-подбор уводит в простоепервые задачи — сложные, фрустрация
P(T)P(T)модель «не учится», вечная стагнациянереалистично быстрое мастерство
P(S)P(S)модель доверяет всем правильным ответам — путает с угадываниеммодель не доверяет ошибкам — слабых гонит вверх
P(G)P(G)модель не верит ни одному правильному → бесконечная тренировкамодель «оправдывает» всё угадыванием

Почему дефолты работают: в исследованиях Corbett & Anderson 1995 показано, что для широкого класса предметов оптимальные параметры, найденные через EM, лежат в диапазоне 0.1-0.3 для каждого. Дефолты 0.2 / 0.1 / 0.1 / 0.2 — это центр распределения.

  • NB-3 — EM fitting (скоро): как именно эти параметры найти на реальных данных без угадывания.
  • NB-1 — BKT from scratch — модель в 30 строк, основа всех экспериментов.