差尺度法の実験
感覚の強さの測定法としては、マグネチュード推定法あるいはカテゴリ判断法がよく知られている。これらでは、提示された個々の評定刺激の感覚の評価が求められる。Maloney & Yang (2003)の最尤法による差尺度法では、4つの刺激i, j, s,tが提示されて、対(i, j)における刺激iと刺激jの感覚の差と、対(s, t)における刺激sと刺激tの感覚の差の比較が求められ、差の大きい方の対がどちらの対であるかの比較判断が求められる。この比較判断課題は、評定課題よりも容易であるように思われる。図1に4つの円盤が表示された例を示す。
図1
観察者は、上の2枚の円盤の大きさの差と、下の2枚の円盤の大きさの差を比較して、差が大きい方の対が求められる。
実験の実施も、PCを利用すれば、4つの刺激の選択提示と比較判断の記録は自動で行うことができることが、本ウェブサイトのスクリプト例により可能である。実験結果の分析もPCにより簡単にできるが、分析のスクリプト例は別のウェブサイトに示した。
実験実施のためのスクリプト例は、リスト1に示したが、このスクリプトでは提示刺激はディスプレイ上の円盤になっている。円盤以外の刺激、あるいはディスプレイ画面以外での刺激提示の実験のためにリスト1を書き換えることは簡単である。円盤の代わりに提示刺激の番号を提示し、実験者が提示された刺激番号に合わせて刺激を提示すればよい。観察者の判断は実験者がキーを押して入力すればよい。リスト1では、提示刺激は1からの整数値で表されている。刺激の総数がSであれば、1からSまでの整数値で表され、刺激値の強さと刺激値を表す整数値は単調関係にあるものとされている。リスト1のスクリプトでは、提示される4つの刺激の組、i、j、s、t、はリストで表されている。
"""
提示刺激対の組 [[i,j],[s,t]] のリスト
"""
L_trials = [[[1,2],[3,4]],
[[2,3],[4,5]],
[[1,2],[3,5]], [[1,3],[4,5]],
[[1,3],[2,4]], [[2,4],[3,5]],
[[1,4],[2,5]], [[1,4],[3,5]],
[[2,5],[1,3]]]
リストL_trialsは、1ブロック分であり、各ブロックの開始時にシャフリングされる。
rng.shuffle(L_trials)
# リストのシャフリング
ブロック内においては、各試行において、上の対であるか、下の対であるかのシャフリングと、各対内での左右の提示位置のシャフリングが行われる。
st =
copy.deepcopy(L_trials[i_subs])
rng.shuffle(st)
# 提示刺激対の上下のシャフリング
rng.shuffle(st[0]) # 上の対の左右のシャフリング
rng.shuffle(st[1]) # 下の対の左右のシャフリング
リストL_trialsは、刺激値数が5の場合のものであるが、5以外の場合はそれに合わせて作成すればよい。4つのものからなるすべての組を作成する必要はない。刺激の感覚の差の比較に必要な情報が得られれば良い。
スクリプト中では、整数値で刺激値を表しているが、刺激値の間隔は任意である。スクリプトファイルなどはまとめて圧縮ファイルexp_files.zipとした。
リスト1のスクリプトによる実験は、以下のように進む。
リスト1のスクリプトを実行すると、図2の画面になる。
図2
Enterキーの押下で、図3の画面になる。
実験中の画面であり、図3の場合、画面上部に「1/270」と表示されているのは、総試行数270試行における第1試行であることを示している。
図3
スペースキーを押すと、刺激図形が表示され、図4のようになる。
図4
上の対の方が大きさの差が大きいと思えば、上矢印キー↑、下の対に方が大きさの差が大きいと思えば、下矢印キー↓を押す。
矢印キーを押すと、円盤は消えて、図5のような画面になる。
図5
Enterキーを押すと4つの円盤が提示されて対の差の比較判断が求められる。以下、図3から図5までが同様に繰り返される。
すべての試行(総試行数は270試行である)が終わると、刺激提示画面は消えて、データの出力用ファイル名の設定が求めるメッセージが端末上に表示される。データはCSV形式のファイルに保存されるので、データの出力用ファイル名の拡張子は「.csv」を指定する。次の例では、raw_disc.csvが設定されている。
Output file
name (*.csv) = raw_disc.csv
CSV形式のファイルについては、岡本(2019)、pp.33-35で説明している。
保存されたデータraw_disc.csvの分析は、別のウェブサイトで説明している。
Maloney, L. T., & Yang, J. N. (2003). Maximum likelihood difference scaling. Journal of Vision, 3, 573-585.
岡本安晴(2019)いまさら聞けないPythonでデータ分析.丸善出版
import pygame
import sys
import numpy as np
import copy
import pickle
pygame.init()
desk_w, desk_h =
pygame.display.get_desktop_sizes()[0]
print(desk_w, ', ', desk_h)
my_font = pygame.font.SysFont(None,int(desk_h/30))
rng = np.random.default_rng()
screen =
pygame.display.set_mode((desk_w, desk_h))
# Full screen
"""
教示の表示
"""
msg_img =
pygame.image.load('msg_intro.png')
msg_w, msg_h = msg_img.get_size()
print(msg_w, ', ', msg_h)
screen.fill((255,255,255))
screen.blit(msg_img, ((desk_w -
msg_w)//2, (desk_h - msg_h)//2))
pygame.display.flip()
"""
Enterキーが押されるまで表示
"""
ck = True
while ck:
for e in pygame.event.get():
if
e.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif
e.type == pygame.KEYDOWN:
if e.key == pygame.K_ESCAPE: # Escapeキーを押すと強制終了
pygame.quit()
sys.exit()
elif e.key == pygame.K_RETURN: # Enter key pressed?
ck = False
break
screen.fill((0,0,0))
scl_img = int(desk_h / 5)
# パッチサイズ
"""
大きさの異なる5枚の円盤のイメージをリストL_patchesに格納
"""
L_patches = []
for ip in range(5):
temp_s =
pygame.Surface((scl_img, scl_img))
c_r = scl_img*0.5*0.2 + 0.8
* scl_img * 0.5 * ip/4
pygame.draw.circle(temp_s,
(255,255,255), (scl_img/2, scl_img/2), c_r)
L_patches.append(temp_s)
"""
提示刺激対の組 [[i,j],[s,t]] のリスト
"""
L_trials = [[[1,2],[3,4]],
[[2,3],[4,5]],
[[1,2],[3,5]], [[1,3],[4,5]],
[[1,3],[2,4]], [[2,4],[3,5]],
[[1,4],[2,5]], [[1,4],[3,5]],
[[2,5],[1,3]]]
n_subtrials = len(L_trials)
# 1ブロックの試行数
n_subs = 30
# ブロック数
n_wholetrials = n_subs *
n_subtrials # 総試行数
L_res = [] # 提示刺激組と反応のリスト
i_trial = 0 # 試行数
for i_tsubs in range(n_subs):
rng.shuffle(L_trials)
# リストのシャフリング
for i_subs in
range(n_subtrials):
i_trial += 1
st =
copy.deepcopy(L_trials[i_subs])
rng.shuffle(st)
# 提示刺激対の上下のシャフリング
rng.shuffle(st[0]) # 上の対の左右のシャフリング
rng.shuffle(st[1]) # 下の対の左右のシャフリング
screen.fill((0,0,0))
srfc_str = my_font.render(
f'Difference Scaling: {i_trial}/{n_wholetrials}',
True, (255,255,255))
str_w, str_h = srfc_str.get_size()
screen.blit(srfc_str, ((desk_w - str_w)//2, 5))
pygame.display.flip()
"""
スペースキーの押下を待つ
"""
ck =
True
while ck:
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif e.type == pygame.KEYDOWN:
if e.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
elif e.type == pygame.KEYUP:
if
e.key == pygame.K_SPACE:
ck = False
break
"""
刺激組の提示
"""
screen.blit(L_patches[st[0][0]-1],
(int(desk_w/2 - scl_img*1.2), int(scl_img)))
screen.blit(L_patches[st[0][1]-1],
(int(desk_w/2 + scl_img*0.2), int(scl_img)))
screen.blit(L_patches[st[1][0]-1],
(int(desk_w/2 - scl_img*1.2), int(3*scl_img)))
screen.blit(L_patches[st[1][1]-1], (int(desk_w/2 + scl_img*0.2),
int(3*scl_img)))
pygame.display.flip()
X =
None
ck =
True
while ck:
for e in pygame.event.get():
#print('e =',e)
if e.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif e.type == pygame.KEYDOWN:
if e.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
elif e.key == pygame.K_UP:
X = 1
screen.fill((0,0,0))
pygame.display.flip()
ck = False
break
elif e.key == pygame.K_DOWN:
X = 0
screen.fill((0,0,0))
pygame.display.flip()
ck = False
break
if ck == False:
break
L_res.append([st,X]) # 提示刺激の組と反応の記録
pygame.quit()
print(L_res)
fout_csv_nm = input('Output file name
(*.csv) = ')
with open(fout_csv_nm, 'w') as f:
f.write(',i,j,s,t,X\n')
for i,v in enumerate(L_res):
print(i, v)
f.write(f'{i},{v[0][0][0]},{v[0][0][1]},' +
f'{v[0][1][0]},{v[0][1][1]},{v[1]}\n')
print('\n' + fout_csv_nm + ' was
saved.\n')