Up

信号検出理論

階層モデル

2025.11CmdStanPy対応に改訂

 

 

複数の条件における信号検出実験のデータを、階層モデルの下にまとめてベイズ分析を行う。

信号検出理論では、ノイズ刺激とシグナルを含むシグナル刺激の2つの刺激の弁別が扱われる(岡本、2025)。ノイズ刺激およびシグナル刺激によって惹き起こされる感覚をおよびで表し、それぞれ平均がおよび、分散がおよびの正規分布に従うものとする。すなわち、

 

 

である。ノイズ刺激とシグナル刺激の感覚の分散が等しい

 

 

等分散モデルについて考える。

 感覚の次元の単位と原点を決めるために、

 

 

とおく。

 等分散モデルのもとでは、平均値の差は、で表される。すなわち、

 

 

である。

 シグナル刺激を正しく「シグナル刺激である」と答える反応はHitと呼ばれ、ノイズ刺激を誤って「シグナル刺激である」と答える反応はFalse Alarmと呼ばれている。感覚がある基準値を越えたとき、「シグナル刺激である」と判断されるとすれば、HitFalse Alarmの確率は、次式で与えられる。

 

 

いま、信号検出理論の実験を4条件、A,B,C,D,で行い、表1の結果を得たとする。

 

表1

 

条件A

条件B

条件C

条件D

 

No

Yes

No

Yes

No

yes

No

Yes

ノイズ刺激

77

23

50

50

77

23

60

40

シグナル刺激

23

77

23

77

10

90

11

89

 

各条件においてノイズ刺激が100回、シグナル刺激が100回、ランダムに提示されたときのNo反応(「ノイズ刺激である」という判断)とYes反応(「シグナル刺激である」という判断)の頻度がまとめられている。条件Aを基準にして考えると、条件Bではシグナル刺激に対する反応の比率は条件Aと同じであるが、ノイズ刺激に対するYes反応が23回から50回に増えている。条件Cでは、ノイズ刺激に対する判断の比率は条件Aと同じであるが、シグナル刺激に対するYes反応が77回から90回に増えている。条件Dでは、条件Aと比べて、ノイズ刺激とシグナル刺激いずれに対してもYes反応が増えている。

 表1のデータを等分散ガウス型モデルで分析するが、4つの条件をまとめて一括して分析する。

条件ABCDにおけるdadbdcddで表すとき、これらは共通の分布に従うものとする。

条件ABCDにおける判断の基準値も、共通の分布に従うものとする。

ハイパーパラメータは、次のhyperprior分布に従うものとする。

 

 

上の階層モデルをリスト1のようにStanスクリプトで表した。Stanについての説明は、岡本(2019で行っている。

 

 

 

リスト1 信号検出理論の階層モデルのStanスクリプト(sdt_h.stan

 

 

data {

    int NFAa; int NCRa;  int NHa;  int NMa;

    int NFAb; int NCRb;  int NHb;  int NMb;

    int NFAc; int NCRc;  int NHc;  int NMc;

    int NFAd; int NCRd;  int NHd;  int NMd;   

}

parameters {

    real dmu_h;

    real Cmu_h;

    real<lower = 0> dsgm_h;

    real<lower = 0> Csgm_h;

    real<lower=0, upper=10.0> da;

    real Ca;

    real<lower=0, upper=10.0> db;

    real Cb;

    real<lower=0, upper=10.0> dc;

    real Cc;

    real<lower=0, upper=10.0> dd;

    real Cd;

}

model {

    dmu_h ~ normal(0, 5);

    Cmu_h ~ normal(0, 5);

    dsgm_h ~ exponential(0.5); 

    Csgm_h ~ exponential(0.5); 

    da ~ normal(dmu_h, dsgm_h); 

    db ~ normal(dmu_h, dsgm_h); 

    dc ~ normal(dmu_h, dsgm_h); 

    dd ~ normal(dmu_h, dsgm_h); 

    Ca ~ normal(Cmu_h, Csgm_h);

    Cb ~ normal(Cmu_h, Csgm_h);

    Cc ~ normal(Cmu_h, Csgm_h);

    Cd ~ normal(Cmu_h, Csgm_h);

    NFAa ~ binomial(NFAa+NCRa, 1.0 - normal_cdf(Ca | 0.0, 1.0));

    NHa ~ binomial(NHa+NMa, 1.0 - normal_cdf(Ca | da, 1.0));

    NFAb ~ binomial(NFAb+NCRb, 1.0 - normal_cdf(Cb | 0.0, 1.0));

    NHb ~ binomial(NHb+NMb, 1.0 - normal_cdf(Cb | db, 1.0));

    NFAc ~ binomial(NFAc+NCRc, 1.0 - normal_cdf(Cc | 0.0, 1.0));

    NHc ~ binomial(NHc+NMc, 1.0 - normal_cdf(Cc | dc, 1.0));

    NFAd ~ binomial(NFAd+NCRd, 1.0 - normal_cdf(Cd | 0.0, 1.0));

    NHd ~ binomial(NHd+NMd, 1.0 - normal_cdf(Cd | dd, 1.0));

}

 

 

 

リスト1のStanスクリプトを用いて表1のデータを分析するPythonスクリプトを、リスト2のように用意した。これらのスクリプトファイルは圧縮ファイルfilesSDT_h.zipにまとめたので、ダウンロード・展開すれば自由に利用できる。

 

 

 

リスト2 表1のデータを分析するスクリプト(sdt_eqvar_h.py

 

 

import numpy as np

import scipy.stats as ss

import matplotlib.pyplot as plt

import seaborn as sb

import arviz as az

from cmdstanpy import CmdStanModel

 

model = CmdStanModel(stan_file='sdt_h.stan')

 

NFAa = 23; NCRa = 77;

NHa = 77;  NMa = 23;

NFAb = 50; NCRb = 50;

NHb = 77;  NMb = 23;

NFAc = 23; NCRc = 77;

NHc = 90;  NMc = 10;

NFAd = 40; NCRd = 60;

NHd = 89;  NMd = 11;

sdt_data = {'NFAa' : NFAa, 'NCRa': NCRa,

            'NHa': NHa, 'NMa': NMa,

            'NFAb': NFAb, 'NCRb': NCRb,

            'NHb': NHb, 'NMb': NMb,

            'NFAc': NFAc, 'NCRc':NCRc,

            'NHc': NHc, 'NMc': NMc,

            'NFAd': NFAd, 'NCRd': NCRd,

            'NHd': NHd, 'NMd':NMd}

 

fit = model.sample(data=sdt_data, adapt_delta=0.9999,max_treedepth=20,

                   iter_warmup=10_000)

print(fit.diagnose())

print(fit.summary())

 

inf_data = az.from_cmdstanpy(fit)      #   Arviz DataFrame型に変換

az.plot_trace(inf_data, figsize=(12,8),

              var_names=['da','Ca','db','Cb','dc','Cc','dd','Cd']) 

plt.tight_layout()

plt.show()

 

fit = fit.draws_pd()                   #   Pandas DataFrame型に変換

 

 

da_med = np.median(fit['da'])

db_med = np.median(fit['db'])

dc_med = np.median(fit['dc'])

dd_med = np.median(fit['dd'])

sb.kdeplot(fit['da'], label = 'da')

sb.kdeplot(fit['db'], label = 'db')

sb.kdeplot(fit['dc'], label = 'dc')

sb.kdeplot(fit['dd'], label = 'dd')

plt.legend()

plt.title("Distributions of d'" +

          '\nda_med = {0:.2f}, db_med = {1:.2f}, dc_med = {2:.2f}, dd_med = {3:.2f}'.format(

          da_med, db_med, dc_med, dd_med))

plt.savefig('Figdprime.png')

plt.show()

 

Ca_med = np.median(fit['Ca'])

Cb_med = np.median(fit['Cb'])

Cc_med = np.median(fit['Cc'])

Cd_med = np.median(fit['Cd'])

sb.kdeplot(fit['Ca'], label = 'Ca')

sb.kdeplot(fit['Cb'], label = 'Cb')

sb.kdeplot(fit['Cc'], label = 'Cc')

sb.kdeplot(fit['Cd'], label = 'Cd')

plt.legend()

plt.title('Distributions of C' +

         '\nCa_med = {0:.2f}, Cb_med = {1:.2f}, Cc_med = {2:.2f}, Cd_med = {3:.2f}'.format(

         Ca_med, Cb_med, Cc_med, Cd_med))

plt.savefig('FigC.png')

plt.show()

 

dmu_h_med = np.median(fit['dmu_h'])

Cmu_h_med = np.median(fit['Cmu_h'])

sb.kdeplot(fit['dmu_h'], label = 'dmu_h')

sb.kdeplot(fit['Cmu_h'], label = 'Cmu_h')

plt.legend()

plt.title('dmu_h_med = {0:.2f}, Cmu_h_med = {1:.2f}'.format(dmu_h_med, Cmu_h_med))

plt.savefig('Figdmu_h.png')

plt.show()

 

dsgm_h_med = np.median(fit['dsgm_h'])

Csgm_h_med = np.median(fit['Csgm_h'])

sb.kdeplot(fit['dsgm_h'], label = 'dsgm_h')

sb.kdeplot(fit['Csgm_h'], label = 'Csgm_h')

plt.legend()

plt.title('dsgm_h_med = {0:.2f}, Csgm_h_med = {1:.2f}'.format(dsgm_h_med, Csgm_h_med))

plt.savefig('Figdsgm_h.png')

plt.show()

 

plt.hist(fit['dsgm_h'], density=True, label = 'dsgm_h', alpha = 0.3)

plt.hist(fit['Csgm_h'], density=True, label = 'Csgm_h', alpha = 0.3)

plt.ylabel('Density', fontsize=14)

plt.legend()

plt.title('dsgm_h_med = {0:.2f}, Csgm_h_med = {1:.2f}'.format(dsgm_h_med, Csgm_h_med))

plt.savefig('Figdsgm_h_hist.png')

plt.show()

 

 

 

リスト1のStanスクリプトファイル(sdt_h.stan)とリスト2のPythonスクリプトファイル(sdt_eqvar_h.py)を同じフォルダに置く。そのフォルダにカレントディレクトリ(フォルダ)を移し、次のコマンドをCmdStanPyのインストールされた環境において実行する。CmdStanPyの簡単な説明を、このウェブサイトに用意した。

 

(stan) *****/filesSDT_h$ python sdt_eqvar_h.py

 

上のコマンドを実行すると、ファイルが読み込まれ、Stanスクリプトのコンパイルが始まる。コンパイルには多少時間が掛かる。コンパイルが終了すると、MCMCサンプリングが始まり、終了後、図1のトレース図が表示される。

 

図1

 

左に事後分布のグラフが表示され、右にMCMCサンプリングのトレース図が表示されている。

図1のWindowを閉じると、図2のグラフが表示される。

 

図2

 

、すなわちdadbdcddの事後分布のグラフと、事後分布の中央値が表示されている。Gelmanら(2021は、事後分布の代表値として、平均値より安定した中央値を勧めている。

条件AとDの事後分布が重なっており、条件Bより右側、条件Cより左側に位置している。条件Cでの弁別力d’が最も高いと言える。

図2のWindowを閉じると、図3のグラフが表示される。

 

図3

 

判断の基準値の事後分布のグラフと中央値が表示されている。条件Bの事後分布が最も左側にあり、その右に条件Dの事後分布が位置している。条件ACの基準値の事後分布が重なって一番右側に位置している。条件Bでの判断基準が最も厳しいと言える。

図3のWindowを閉じると、図4のグラフが表示される。

 

図4

 

ハイパーパラメータdmu_hCmu_hの事後分布のグラフと中央値が表示されている。基準値のハイパー分布の平均値は、弁別力のハイパー分布の平均値より負の側に寄っている。

図4のWindowを閉じると、図5のグラフが表示される。

 

図5

 

ハイパーパラメータdsgmCsgmの事後分布のグラフと中央値が表示されている。これらのパラメータは負の値を取らないはずであるが、グラフが0より左側、負の領域にはみ出しているのは、滑らかなグラフを描くためにKDEを用いているからである。。

図5のフォームを閉じると、ヒストグラムのグラフが表示される(図6)。

 

図6

 

事後分布が正の領域に収まっていることが判る。

 

図6のWindowを閉じると、スクリプトの実行終了である。

 

 

 

参考文献

Gelman, A., Carlin, J. B., Stern, H. S., Dunson, D. B., Vehtari, A., & Rubin, D. B. (2014). Bayesian data analysis, 3rd. Ed. CRC Press.

Gelman, A., Hill, J., & Vehtari, A. (2021). Regression and other stories. Cambridge University

岡本安晴(2025) 感覚・知覚測定法.和氣典二・重野純・村上郁也(編)感覚・知覚心理学ハンドブック 第三版(第2章)、誠信書房

 

 

Up