Python Multiprocessing Programming for MCMC
A sample program was written in Python, using multiprocessing, so that multiple chains in MCMC were run concurrently. A model for one variable normal distribution was employed, that is, it was assumed that data s were sampled from a normal distribution
Hence, we have
is called a likelihood function and denoted by , when it is considered to be a function of parameters and with data s given and fixed. That is,
Hence, the posterior distribution is given by the following equation
Uniform distribution was used as a prior distribution
where,
For equation (1), the following algorithm, which is known as Metropolis-within-Gibbs, was employed.
Proposal distributions were chosen as follows
and were adjusted in an adapting stage before the main MCMC stage as follows (see the code in mainprg.py below)
v,
ck = scale_sgm(r_acpt_mean, ck)
params['genMuSgm'] = params['genMuSgm'] * v
v,
ck = scale_sgm(r_acpt_sd, ck)
params['genSDSgm']
= params['genSDSgm'] * v
params['genMuSgm']
and params['genSDSgm'] represent and ,
r_acpt_mean and r_acpt_sd
are acceptance rates for parameters and The function scale_sgm is given
def
scale_sgm( acpt_r, ck ):
v = 1.0
if (acpt_r > 0.5):
ck
+= 1
v =
1.0 + 2.0 * (acpt_r - 0.4) / 0.6
elif (acpt_r < 0.3):
ck
+= 1
v =
1.0 / (1.0 + 2.0 * (0.4 - acpt_r) / 0.4)
return v, ck
The
function scale_sgm adjusts and ,
so that acceptance rates becomes from
0.3 to 0.5
Cycles of
MCMC were executed according to the following steps
Step 0
Set ,and for the first MCMC, the initial values and are set to be the mean and SD of the data. The initial values and are also given based on the SD of the data. For MCMC after the first MCMC, the initial values are set based on samples from the previous MCMC.
Step 1
As a candidate for , sample y as
Set the acceptance probability as follows
Set
with probability
or
with probability
Step 2
As a candidate for , sample y as
Set acceptance probability as follows
Set
with probability
or
with probability
Step 3
Set
If t reaches the maximum number of cycles, then exit. Otherwise, jump back to step 1.
Some tips on calculation are given below.
The following term in the equation in step1
is given by
because
When we calculate a ratio of probabilities
Values of probability may become 0, because of the range, which type double can represent. To consider this possibility, the following code is prepared.
v = 1.0
Lv = 0.0
for i in
range(0, 325):
v *= 0.1
print("%d v = %0.17e"%((i + 1),
v))
Run this
code, then we have the results in Figure 1.
Figure 1
Beyond
the value , v becomes 0. To avoid to get
value 0 for probability, calculations are based on log-transformed value. That
is, the following transformation was employed.
In this
case, we have
To
restore the log-transformed value to the not-transformed one, the function exp
may be used. But, as to the function exp, some caution should be needed.
Consider the following code.
import math
v = 700.0
for i in
range(0, 10):
try:
print("exp(%f) = %0.15e exp(%f) = %0.15e"%(\
v, math.exp(v), -v, math.exp(-v)))
v +=
3.0
except Exception as e:
print(e)
print("v = %f (exp overflow) exp(%f) =
%0.15f"%(\
v, -v, math.exp(-v)))
Run this
code, then we get the results in Figure 2.
Figure 2
For a
large value v, exp(v) causes overflow error.
Acceptance
probability need not become larger than 1 (see the code of the function mcmc_func
in subprg.py below), so the following function exp0 was prepared:
def exp0( x
):
if (x > 0.0):
return 1.0
else:
return math.exp(x)
In step
2, log-transformed terms are given as follows.
For a candidate y such as
we have
In this case, the acceptance probability is set to be -1, which means that actual acceptance probability is 0 (see the code of the function mcmc_func in subprg.py below).
The algorithm of the MCMC is implemented as the function mcmc_func in the following code file subprg.py.
================= subprg.py ==========================
import math
from RNGen import
RNp2to191 as RN
def sqr( v ):
return v * v
def exp0( x ):
if (x > 0.0):
return 1.0
else:
return math.exp(x)
def scale_sgm( acpt_r,
ck ):
v = 1.0
if (acpt_r > 0.5):
ck
+= 1
v = 1.0
+ 2.0 * (acpt_r - 0.4) / 0.6
elif (acpt_r < 0.3):
ck
+= 1
v =
1.0 / (1.0 + 2.0 * (0.4 - acpt_r) / 0.4)
return v, ck
def mcmc_func( params
):
"""
params = {'jump': 'n': 'x': 'NSimu':
'init_mean': 'init_sd': 'genMuSgm': 'geSDSgm': }
"""
jump = params['jump']
n
= params['n']
x
= params['x']
NSimu = params['NSimu']
init_mean =
params['init_mean']
init_sd = params['init_sd']
genMuSgm = params['genMuSgm']
genSDSgm = params['genSDSgm']
sumX = 0.0
ssumX = 0.0
for i in range(0, n):
sumX
+= x[i]
ssumX += x[i] ** 2.0
mean = [init_mean]
sd = [init_sd]
acpt_mean = 0
acpt_sd = 0
rn = RN()
#
# Independent sequences can be
obtained for different values of jump
#
for i in range(0, jump):
rn.jump(100)
#
# NSimu
samples from MCMC
#
for t in range(0, NSimu):
if
(t % 1000) == 0:
print("t/NSimu
= %d/%d for jump =
%d"%(t, NSimu, jump))
y =
rn.normalMS(mean[t], genMuSgm)
log_a = (-2.0 * sumX * (y - mean[t]) + n * (sqr(y) - sqr(mean[t]))) /
(-2.0 * sqr(sd[t]))
a =
exp0(log_a)
if
(rn.uni() < a):
mean.append(y)
acpt_mean += 1
else:
mean.append(mean[t])
y =
rn.normalMS( sd[t], genSDSgm )
a =
0.0
if
(y <= 0.0):
a = -1.0
else:
log_a = n * math.log(sd[t] / y) \
+ 0.5 * ((1.0 / sqr(sd[t])) - (1.0 / sqr(y))) \
* (ssumX - 2.0 * mean[t + 1] * sumX + n * sqr(mean[t + 1]))
a = exp0(log_a)
if
(rn.uni() < a):
sd.append(y)
acpt_sd += 1
else:
sd.append(sd[t])
return mean, acpt_mean, sd,
acpt_sd
====================================================
The following code makes Markov chains of child processes independent of each other.
for i in range(0, jump):
rn.jump(100)
As to the method jump, visit <this website>.
Sampling distributions from the posterior distributions are displayed as histograms (Figure 3 for ; Figure 4 for ).
Figure 3
Figure 4
Quartile values and 95% CIs are written in the output file (Figure 5).
Figure 5
The main program code is shown below.
================= mainprg.py ============================
from
multiprocessing.pool import Pool
import matplotlib.pyplot
as plt
import math
from subprg import
scale_sgm, mcmc_func
if __name__ ==
"__main__":
#
# Prepare
the input data file
#
s_in = input("Input
data file = ")
f_in = open(s_in,
"r")
#
# Set the
contents of the input data file in the object data
#
data_f = f_in.readlines()
f_in.close()
#
# Prepare
the output file
#
s_out = input("Output
data file = ")
f_out = open(s_out,
"w")
f_out.write("Input data
file = " + s_in + "\n\n")
#
#
Set the data in list x
#
pos = 0
while True:
if
data_f[pos].find("/", 0) == 0: break
pos
+= 1
n = 0
x = []
while True:
pos
+= 1
if
data_f[pos].find("/", 0) == 0: break
x.append(float(data_f[pos]))
n +=
1
print("X...")
i = 1
for v in x:
f_out.write("%5d:"%i + " %f\n"%v)
i+=1
f_out.write("\n")
sum = 0.0
for v in x:
sum
+= v
mean = sum / n
print("Mean =
%f"%mean)
f_out.write("Mean =
%f\n"%mean)
ssum = 0.0
for v in x:
ssum
+= (v - mean) ** 2.0
var = ssum / n
sd = math.sqrt(var)
print("Variance =
%f"%var + "
sd = %f"%sd)
f_out.write("Variance =
%f"%var + "
sd = %f\n"%sd)
uvar = ssum / (n - 1.0)
sd_uvar = math.sqrt(uvar)
print("Unbiased Var. =
%f"%uvar + "
sd(uvar) = %f"%sd_uvar)
f_out.write("Unbiased
Var. = %f"%uvar + " sd(uvar) =
%f\n"%sd_uvar)
NSimu = 3000
#
# Initial
values of the parameters of MCMC
#
params = {'jump': 0,
'n': n,
'x': x,
'NSimu': NSimu,
'init_mean': mean,
'init_sd': sd,
'genMuSgm': 0.1 * sd,
'genSDSgm':
0.1 * sd }
#
#
Adjust the parameters of MCMC
#
ck = -1
n_adap = 0
while True:
print("ck = %d
n_adap = %d"%(ck, n_adap))
L_mean, acpt_mean, L_sd, acpt_sd = mcmc_func( params )
sum_mean = 0.0
sum_sd = 0.0
for
t in range(1, NSimu + 1):
sum_mean += L_mean[t]
sum_sd += L_sd[t]
params['init_mean'] = sum_mean / NSimu
params['init_sd'] = sum_sd / NSimu
r_acpt_mean = acpt_mean / float(NSimu)
r_acpt_sd = acpt_sd / float(NSimu)
ck =
0
v,
ck = scale_sgm(r_acpt_mean, ck)
params['genMuSgm'] = params['genMuSgm'] * v
v,
ck = scale_sgm(r_acpt_sd, ck)
params['genSDSgm'] = params['genSDSgm'] * v
if
(ck == 0):
print("ck = %d"%ck)
break
n_adap +=1
#
#
The main stage of MCMC
#
pool = Pool()
NSimu = 10000
params['NSimu'] = NSimu
n_mcmc = 4 # The number of concurrent MCMCs
ary_params = []
for i in range(0, n_mcmc):
temp
= params.copy()
temp['jump'] = i + 1
ary_params.append(temp)
#
# Run the
processes of MCMCs
#
print("The main MCMCs
started.")
results =
pool.map(mcmc_func, ary_params)
print("The main MCMCs
ended.")
#
# Merge the
samples from MCMCs
#
mrg_mean = []
mrg_sd = []
for t_mean, t_acpt_mean,
t_sd, t_acpt_sd in results:
print("t_acpt_mean = %d t_acpt_sd =
%d"%(t_acpt_mean, t_acpt_sd))
mrg_mean += t_mean[1:NSimu+1]
mrg_sd += t_sd[1:NSimu+1]
#
#
Calculate the quantiles
#
mrg_mean.sort()
L_mean = mrg_mean[int(NSimu *
n_mcmc * 0.025)]
Q1_mean = mrg_mean[int(NSimu
* n_mcmc * 0.25)]
Med_mean =
mrg_mean[int(NSimu * n_mcmc * 0.5)]
Q3_mean = mrg_mean[int(NSimu
* n_mcmc * 0.75)]
U_mean = mrg_mean[int(NSimu
* n_mcmc * 0.975)]
print("\nQ1_mean =
%f Med_mean =
%f Q3_mean =
%f"%(Q1_mean, \
Med_mean, Q3_mean))
print("95%%CI = [%f,
%f]"%(L_mean, U_mean))
f_out.write("\nQ1_mean
= %f Med_mean =
%f Q3_mean =
%f\n"%(Q1_mean, \
Med_mean,
Q3_mean))
f_out.write("95%%CI =
[%f, %f]\n"%(L_mean, U_mean))
plt.figure(figsize = (12,
9))
plt.title("Posterior
Distribution of the Mean")
plt.hist(mrg_mean, bins =
50)
plt.show()
mrg_sd.sort()
L_sd = mrg_sd[int(NSimu *
n_mcmc * 0.025)]
Q1_sd = mrg_sd[int(NSimu *
n_mcmc * 0.25)]
Med_sd = mrg_sd[int(NSimu *
n_mcmc * 0.5)]
Q3_sd = mrg_sd[int(NSimu *
n_mcmc * 0.75)]
U_sd = mrg_sd[int(NSimu *
n_mcmc * 0.975)]
print("\nQ1_sd =
%f Med_sdn =
%f Q3_sd =
%f"%(Q1_sd, \
Med_sd, Q3_sd))
print("95%%CI = [%f,
%f]"%(L_sd, U_sd))
f_out.write("\nQ1_sd =
%f Med_sd =
%f Q3_sd = %f\n"%(Q1_sd,
\
Med_sd, Q3_sd))
f_out.write("95%%CI =
[%f, %f]\n"%(L_sd, U_sd))
plt.figure(figsize = (12,
9))
plt.title("Posterior
Distribution of the SD")
plt.hist(mrg_sd, bins = 50)
plt.show()
f_out.close()
print(s_out + " was
saved.")
=================================================
Proposal
distributions and of the MCMC are adjusted automatically by
the following code.
ck = -1
n_adap = 0
while True:
print("ck = %d
n_adap = %d"%(ck, n_adap))
L_mean, acpt_mean, L_sd, acpt_sd = mcmc_func( params )
sum_mean = 0.0
sum_sd = 0.0
for
t in range(1, NSimu + 1):
sum_mean +=
L_mean[t]
sum_sd += L_sd[t]
params['init_mean'] = sum_mean / NSimu
params['init_sd'] = sum_sd / NSimu
r_acpt_mean = acpt_mean / float(NSimu)
r_acpt_sd = acpt_sd / float(NSimu)
ck =
0
v,
ck = scale_sgm(r_acpt_mean, ck)
params['genMuSgm'] = params['genMuSgm'] * v
v,
ck = scale_sgm(r_acpt_sd, ck)
params['genSDSgm'] = params['genSDSgm'] * v
if
(ck == 0):
print("ck = %d"%ck)
break
n_adap +=1
Multiprocessing is implemented for the main MCMC using the class Pool as follows. Multiprocessing by the class Pool is explained at the website <Multiprocessing by the Class Pool of Python>.
#
#
The main stage of MCMC
#
pool = Pool()
NSimu = 10000
params['NSimu'] = NSimu
n_mcmc = 4
# The number of concurrent MCMCs
ary_params = []
for i in range(0, n_mcmc):
temp
= params.copy()
temp['jump'] = i + 1
ary_params.append(temp)
#
# Run the
processes of MCMCs
#
print("The main MCMCs started.")
results =
pool.map(mcmc_func, ary_params)
print("The main MCMCs
ended.")
Run the program, the name of the input data file is asked (Figure 6).
Figure 6
Data in the input data file are enclosed by the lines with slash / at the heads as shown in Figure 7.
Figure 7
Set the name of the input data file, and press the Enter key. A name of the output file will be asked (Figure 8).
Figure 8
The name of the output file is an arbitrary text file name. After setting the output file name, calculation starts. When the main MCMC ends, the histogram of the samples from the posterior distribution of will be presented (Figure 3). When the window of the histogram of is closed by clicking the X icon at the upper right corner, the window of the histogram for will be shown (Figure 4).
Close the window of the histogram for , then the program ends.
Output by the function print in child processes are not displayed in the window of the IDLE shell. When the program is executed in a console window (a terminal window), outputs by the child processes will be displayed (Figure 9).
Figure 9
Program files are archived in the zip file mcmcMultiProcPrg.zip , which can be downloaded by clicking the name mcmcMultiProcPrg.zip .