Bayesian Difference Scaling
MLDS (maximum likelihood difference scaling; Maloney & Young, 2003) estimates parameter values for the difference scaling model by a maximum likelihood method. A Bayesian method for the difference scaling is presented here.
The difference scaling model sets the following:
where, denotes the difference of magnitudes and , of stimuli and , that is,
and denotes the random variable, which corresponds to the sensation of the difference of and . is assumed to have a normal distribution of mean and variance .
It is assumed that the difference is judged to be larger than the difference , when . Hence, we have
denotes the standard normal cumulative distribution.
To get MCMC sampling stable, the following restriction is set:
The origin and unit of the scale is set by the following:
When comparison of and is made times, and the number of judgments that is larger than is , the probability is given by
The likelihood for the results of the experiment is given by
Stan script for the above model is shown in Listing 1.
Listing 1. Stan script for Bayesian Difference Scaling (file name: ba_diff_s.stan)
data {
int N_data;
int N_st;
int<lower = 1, upper = N_st> ID_i[N_data];
int<lower = 1, upper = N_st> ID_j[N_data];
int<lower = 1, upper = N_st> ID_s[N_data];
int<lower = 1, upper = N_st> ID_t[N_data];
int<lower = 0> N[N_data];
int<lower = 1> TN[N_data];
}
parameters {
simplex[N_st - 1] theta;
real<lower
= 0.0> sgm;
}
transformed parameters {
real
psi[N_st];
psi[1]
= 0.0;
for
(i in 2:N_st)
psi[i] = psi[i
- 1] + theta[i - 1];
}
model {
for
(i in 1:N_data)
N[i] ~ binomial(TN[i],
Phi((fabs(psi[ID_i[i]] - psi[ID_j[i]])
- fabs(psi[ID_s[i]] - psi[ID_t[i]])) / sgm));
}
The Python script shown in Listing 2 uses the Stan script in the above. The script files and the sample data files are archived in the zip fileB_Diff_Scale.zip, which can be freely downloaded and used under the userfs responsibility. All rights are reserved.
An example of the input data for the script
is shown in Figure 1.
Figure
1
In the first row of the first column, the number of stimuli is put, e.g., in the Figure 1, 6.
In the second row, characters denoting stimuli are set, then characters N and TN, which denote and .
From the third row on, each data is set row by row.
Save the data with a file name of the extension g.csvh.
Run the script in Listing 2, the file name for the input data is required as is shown below:
(py39) PS D:\XXXXX\B_Diff_Scale>
python BDS.py
INFO:numexpr.utils:NumExpr defaulting to 8 threads.
Data(*.csv) = Data.csv
Set the input data file name, then press down the Enter key.
Calculation starts.
After MCMC sampling, the posterior distributions of are shown as in Figure 2.
Figure
2
Notice that are the assumption to set the origin and unit (Eq. (3)).
Close the window of Figure 2, then the posterior distribution of is displayed (Figure 3).
Figure
3
Close the window of Figure 3, lines, which show relations of the stimuli and sensations, are displayed (Figure 4).
Figure
4
Close the window in Figure 4, scatter gram
of data and probability estimated by the model is shown (figure 5).B
Figure
5
The correspondence of the model and data shown in Figure 5 is good.
Close the windows of Figure 5, then the program completes.
Maloney, L.
T., & Yang, J. N. (2003). Maximum likelihood difference scaling. Journal of Vision, 3, 573-585.
Listing 2 Python script for Bayesian Difference Scaling (file name: BDS.py)
import pystan
import numpy as np
import matplotlib.pyplot
as plt
import seaborn as sb
import scipy.stats
as ss
import csv
fin_nm = input('Data(*.csv)
= ')
with open(fin_nm,
'r') as f:
raw_data
= [v for v in csv.reader(f)]
N_st = int(raw_data[0][0])
N_data = 0
ID_i = []
ID_j = []
ID_s = []
ID_t = []
N = []
TN = []
for v in raw_data[2:]:
N_data
+= 1
ID_i.append(int(v[0]))
ID_j.append(int(v[1]))
ID_s.append(int(v[2]))
ID_t.append(int(v[3]))
N.append(int(v[4]))
TN.append(int(v[5]))
print('N_data = ',
N_data)
for i in range(N_data):
print("{0:>3}:
{1}, {2}, {3}, {4}/ {5}, {6}".
format((i + 1), ID_i[i], ID_j[i],
ID_s[i], ID_t[i], N[i], TN[i]))
Data = {'N_data':
N_data, 'N_st': N_st, \
'ID_i': ID_i, 'ID_j':
ID_j, 'ID_s': ID_s, 'ID_t':
ID_t, \
'N': N, 'TN': TN}
sm = pystan.StanModel(file
= 'ba_diff_s.stan')
fit = sm.sampling(data
= Data, n_jobs = 1)
print(fit)
Psi = fit['psi'].T
plt.plot([0, 0], [0, 10], label = '$\psi1$')
for i in
range(1, N_st-1):
sb.kdeplot(Psi[i], label = r'$\psi${}'.format(i+1))
plt.plot([1, 1], [0, 10], label =
r'$\psi${}'.format(N_st))
plt.legend()
plt.yticks([])
plt.xlabel('$\psi$', fontsize
= 16)
plt.title('Posterior Distributions', fontsize = 18)
plt.savefig('FigPsi.png')
plt.show()
Sgm = fit['sgm']
sb.kdeplot(Sgm)
plt.xlabel('$\psi$', fontsize
= 14)
plt.title('Posterir
distribution of $\sigma$', fontsize = 18)
plt.yticks([])
plt.savefig('FigSgm.png')
plt.show()
Sgm_med = np.percentile(Sgm, 50)
print('Sgm_med =',
Sgm_med)
Psi_med = []
Psi_025 = []
Psi_25 = []
Psi_75 = []
Psi_975 = []
for i in range(N_st):
q025, q25, med, q75, q975 = np.percentile(Psi[i], [2.5, 25, 50, 75, 97.5])
Psi_med.append(med)
Psi_025.append(q025)
Psi_25.append(q25)
Psi_75.append(q75)
Psi_975.append(q975)
stimuli = []
for i in range(N_st):
stimuli.append(i + 1)
plt.plot(stimuli, Psi_med,
'b-', linewidth = 2, label = 'Median')
plt.plot(stimuli, Psi_25, 'g-.', linewidth = 1,
label = '50% CI')
plt.plot(stimuli, Psi_75, 'g-.', linewidth = 1)
plt.plot(stimuli, Psi_025, 'y--', linewidth =
1, label = '95% CI')
plt.plot(stimuli, Psi_975, 'y--', linewidth =
1)
plt.legend()
plt.title('Sensation and Stimulus', fontsize = 18)
plt.xlabel('Stimulus', fontsize
= 14)
plt.ylabel('Sensation', fontsize
= 14)
plt.tight_layout()
plt.savefig('FigSummary.png')
plt.show()
Prop_data = np.empty(N_data)
Prop_model = np.empty(N_data)
for i in range(N_data):
Prop_data[i] = N[i] / TN[i]
Prop_model[i] = ss.norm(0, 1).cdf((abs(Psi_med[ID_i[i]-1]
- Psi_med[ID_j[i]-1]) -
abs(Psi_med[ID_s[i]-1] - Psi_med[ID_t[i]-1])) / Sgm_med)
plt.figure(figsize = (5,5))
plt.scatter(Prop_model, Prop_data)
plt.plot([0,1], [0,1])
plt.title('Scattergram
of (Prop_Data, Prob_Model)',
fontsize = 16)
plt.ylabel('N/TN', fontsize
= 14)
plt.xlabel('Prob.', fontsize
= 16)
plt.tight_layout()
plt.savefig('FigScatter.png')
plt.show()