7問でスコアが収束する軽量アイテム反応理論 — IRT をスマホローカルで動かす

| 開発記録 | アルゴリズム

タグ: #アルゴリズム #統計 #Flutter #個人開発

1日7問・約5分のテストで意味のある能力推定をしたい。項目反応理論(IRT)の Rasch モデルを簡略化し、正誤と回答速度から能力値θを逐次更新する軽量スコアリングをスマホローカルに実装した話です。

「毎日7問・5分」で能力を測れるか

脳トレ系の新作アプリを設計したとき、最初に決めたのは「1 日 1 セット、7 問、約 5 分」という制約でした。毎日続けてもらうには短さが正義です。しかし 7 問という少なさで、意味のあるスコアを出せるのか。100 問のテストなら正答率で十分ですが、7 問の正答率は 0/7 〜 7/7 の 8 段階しかありません。

ここで使ったのが**項目反応理論(IRT)**の考え方です。

Rasch モデルを簡略化する

IRT の最もシンプルな形である Rasch モデルでは、能力値 θ の人が難易度 b の問題に正答する確率を

P(正答) = 1 / (1 + exp(-(θ - b)))

とモデル化します。本格的な IRT は全回答データから最尤推定しますが、スマホローカルで毎回推定計算を回すには重すぎるし、そもそも個人の回答履歴しかありません。

そこで逐次更新に落としました。1 問回答するたびに、実際の正誤と予測確率の差分で θ を勾配的に動かします。

q = 実際の正誤(0/1) - P(正答)
θ += learningRate × q

難しい問題に正答すれば θ は大きく上がり、簡単な問題に正答してもほとんど動かない。誤答はその逆。予測どおりの結果なら微動で済みます。学習率は 0.6 とかなり大きめに設定しました。試行した結果、この値だと7 問でほぼ収束するからです。少問数を学習率の大きさで補う、理論的には粗いが実用上は素直なトレードオフです。

正誤だけでなく速さも使う

7 問しか情報源がないので、正誤以外の情報も絞り出します。各問題に難易度に応じた期待回答時間を設定し、

速度係数 = 期待時間 / 実際の経過時間(0.5〜1.5 に clamp)

を θ の更新に掛け合わせました。同じ正解でも、素早く解けたなら能力の証拠として重く、時間切れ寸前ならば軽く扱う。clamp を入れているのは、たまたま指が滑って即答した場合などの外れ値に推定を引きずられないためです。

最後に θ を「平均 100・標準偏差 15」の慣習的なスコアスケール(100 + 15θ)に線形変換して表示します。

問題も自前で作る

もうひとつの設計判断は、公開されている既存テストの問題を一切使わないことでした。著作権の問題もありますし、ネットに答えがある問題では測定になりません。数列補完・図形回転・行列推論など 6 系統の問題を、難易度パラメータ付きのジェネレータで動的生成しています。難易度が上がると数列の規則が 1 次式から 2 次式になり、制限時間も 45 秒から 15 秒へ絞られる、という具合に、難易度 b をジェネレータの出力と直結させました。IRT の前提(問題ごとに既知の難易度がある)を、生成側でコントロールして満たす構成です。

収束の様子を可視化して決めた

学習率 0.6 という値は、シミュレーションで決めました。仮想の能力値を持つ回答者を乱数で作り、7 問回答させて θ の推定値が真値にどう近づくかを学習率を変えながらプロットする。小さい学習率(0.2 など)では 7 問で全然追いつかず、大きすぎる(1.0 超)と最後の 1 問で推定が暴れます。0.6 前後が「7 問で十分近づき、かつ 1 問の重みが支配的にならない」バランスでした。パラメータを感覚で決めずに、安いシミュレーションでも良いから根拠を持つ。統計モデルを簡略化するときほど大事な習慣だと思います。

まとめ

統計理論は「そのまま実装」しなくても役に立ちます。Rasch モデルの骨格だけ借りて、推定は逐次更新で近似し、少問数は学習率と速度係数で補う。理論の正しさと実装の軽さの間に、個人開発にちょうどいい落とし所がありました。