Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

名義反応モデル

順序のないカテゴリカル変数を名義尺度(nominal scale)という。

名義尺度の反応を扱えるのが 名義反応モデル(nominal response model: NRM) softmax関数の形で多値化する。

名義カテゴリモデル(nominal categories model) という呼ばれ方もする

同値変換

NRMのモデル

P(yij=k)=softmax(ajkθi+bjk)P(y_{ij} = k) = \operatorname{softmax}(a_{jk} \theta_i + b_{jk})

  • ajk=ajka_{jk}' = -a_{jk}

  • θi=θi\theta_i' = -\theta_i

と置き換えても

ajkθi+bjk=(ajk)(θi)+bjk=ajkθi+bjka_{jk}' \theta_i' + b_{jk} = (-a_{jk}) (-\theta_i) + b_{jk} = a_{jk} \theta_i + b_{jk}

となるため、推定に使うライブラリの違いなどで推定結果が真逆になることがある

多次元バージョン

Thissen et al. (2010) の General-Purpose Multidimensional Nominal Model (GPMNM) は名義反応モデルで多次元の特性パラメータθ\boldsymbol{\theta}と識別力ajk\mathbf a_{jk}を扱えるよう一般化したモデル

実装例

# データを生成
import numpy as np
import pandas as pd

def simulate_nrm(N=1000, J=10, K=4, seed=42):
    rng = np.random.default_rng(seed)
    theta = rng.normal(0, 1, size=N)

    # カテゴリ0を参照カテゴリ (a=0, b=0) とする
    a_free = np.sort(rng.normal(0, 1, size=(J, K - 1)), axis=1)
    b_free = rng.normal(0, 1, size=(J, K - 1))

    a_full = np.zeros((J, K))
    a_full[:, 1:] = a_free
    b_full = np.zeros((J, K))
    b_full[:, 1:] = b_free

    U = np.zeros((N, J), dtype=int)
    for i in range(N):
        for j in range(J):
            logits = a_full[j] * theta[i] + b_full[j]
            logits -= logits.max()
            P = np.exp(logits)
            P /= P.sum()
            U[i, j] = rng.choice(K, p=P)
    return U, theta, a_free, b_free

num_users = 1000
num_items = 20
U, true_theta, true_a, true_b = simulate_nrm(N=num_users, J=num_items)
df = pd.DataFrame(
    U,
    index=[f"user_{i+1}" for i in range(num_users)],
    columns=[f"question_{j+1}" for j in range(num_items)],
)
df.head()
# 縦持ちへ変換
df_long = pd.melt(
    df.reset_index(),
    id_vars="index",
    var_name="item",
    value_name="response",
).rename(columns={"index": "user"}).astype({"user": "category", "item": "category"})

df_long.head()
import pymc as pm
import pytensor.tensor as pt

user_idx = df_long["user"].cat.codes.to_numpy()
users = df_long["user"].cat.categories.to_numpy()
item_idx = df_long["item"].cat.codes.to_numpy()
items = df_long["item"].cat.categories.to_numpy()
responses = df_long["response"].to_numpy().astype("int64")

K = int(responses.max() + 1)
n_free = K - 1
n_items = len(items)

coords = {
    "user": users,
    "item": items,
    "category_free": np.arange(n_free),
    "obs_id": np.arange(len(df_long)),
}

with pm.Model(coords=coords) as model:
    user_idx_ = pm.Data("user_idx", user_idx, dims="obs_id")
    item_idx_ = pm.Data("item_idx", item_idx, dims="obs_id")

    theta = pm.Normal("theta", 0.0, 1.0, dims="user")
    a_free = pm.Normal("a_free", 0.0, 1.0, dims=("item", "category_free"))
    b_free = pm.Normal("b_free", 0.0, 2.0, dims=("item", "category_free"))

    # 参照カテゴリ(0列目)にゼロを付与
    a_full = pt.concatenate([pt.zeros((n_items, 1)), a_free], axis=1)
    b_full = pt.concatenate([pt.zeros((n_items, 1)), b_free], axis=1)

    # logits[n, k] = a_{j,k} * theta_i + b_{j,k}
    logits = a_full[item_idx_] * theta[user_idx_][:, None] + b_full[item_idx_]
    p = pm.math.softmax(logits, axis=1)

    pm.Categorical("obs", p=p, observed=responses, dims="obs_id")

推定

%%time
with model:
    idata = pm.sample(random_seed=0, draws=1000)

EAP推定量

post_mean = idata.posterior.mean(dim=["chain", "draw"])

import matplotlib.pyplot as plt

fig, axes = plt.subplots(figsize=[12, 4], ncols=3)

ax = axes[0]
ax.scatter(true_theta, post_mean["theta"])
ax.plot(true_theta, true_theta, color="gray")
_ = ax.set(xlabel="true_theta", ylabel="theta_hat")

ax = axes[1]
ax.scatter(true_a.flatten(), post_mean["a_free"].to_numpy().flatten())
ax.plot(true_a.flatten(), true_a.flatten(), color="gray")
_ = ax.set(xlabel="true_a", ylabel="a_hat")

ax = axes[2]
ax.scatter(true_b.flatten(), post_mean["b_free"].to_numpy().flatten())
ax.plot(true_b.flatten(), true_b.flatten(), color="gray")
_ = ax.set(xlabel="true_b", ylabel="b_hat")

参考文献

  • Lipovetsky, S. (2021). Handbook of Item Response Theory, Volume 1, Models. Technometrics, 63(3), 428-431.