Я ответил на похожий вопрос ранее .Здесь можно использовать многие вещи, которые я упомянул (не вычисление текущей вероятности на каждой итерации, предварительное вычисление случайных инноваций и т. Д.).
Другими улучшениями для вашей реализации было бы не использование списка для храненияваши образцы.Вместо этого вам следует предварительно выделить память для образцов и сохранить их в виде массива.Что-то вроде samples = np.zeros(n_samples)
будет более эффективным, чем добавление в список на каждой итерации.
Вы уже упоминали, что пытались повысить эффективность, не записывая записанные пробы.Это хорошая идея.Вы также можете сделать аналогичный трюк с прореживанием, записывая только каждый m-й семпл, так как в любом случае вы отбрасываете их в своем выражении возврата с np.array(sample_list)[::m]
.Вы могли бы сделать это, изменив:
# do not add burn-in samples
if n_sampled > burn_in:
sample_list.append(z)
на
# Only keep iterations after burn-in and for every m-th iteration
if n_sampled > burn_in and n_sampled % m == 0:
samples[(n_sampled - burn_in) // m] = z
Также стоит отметить, что вам не нужно вычислять min(1, p(cand) / p(z))
и вы можете обойтись только вычислениями p(cand) / p(z)
.Я понимаю, что формально min
необходим (чтобы гарантировать, что вероятности ограничены между 0 и 1).Но в вычислительном отношении нам не нужен минимум, так как если p(cand) / p(z) > 1
, то p(cand) / p(z)
всегда больше u
.
Собирая все это вместе, а также предварительно вычисляяслучайные инновации, вероятность принятия u
и вычисление вероятностей только тогда, когда вам действительно нужно, я придумал:
def my_Metropolis_Gaussian(p, z0, sigma, n_samples=100, burn_in=0, m=1):
# Pre-allocate memory for samples (much more efficient than using append)
samples = np.zeros(n_samples)
# Store initial value
samples[0] = z0
z = z0
# Compute the current likelihood
l_cur = p(z)
# Counter
iter = 0
# Total number of iterations to make to achieve desired number of samples
iters = (n_samples * m) + burn_in
# Sample outside the for loop
innov = np.random.normal(loc=0, scale=sigma, size=iters)
u = np.random.rand(iters)
while iter < iters:
# Random walk innovation on z
cand = z + innov[iter]
# Compute candidate likelihood
l_cand = p(cand)
# Accept or reject candidate
if l_cand / l_cur > u[iter]:
z = cand
l_cur = l_cand
# Only keep iterations after burn-in and for every m-th iteration
if iter > burn_in and iter % m == 0:
samples[(iter - burn_in) // m] = z
iter += 1
return samples
Если мы посмотрим на производительность, то получим, что эта реализация равна 2xбыстрее , чем оригинал, что неплохо для нескольких незначительных изменений.
In [1]: %timeit Metropolis_Gaussian(pdf_t, 3, 1, n_samples=100, burn_in=100, m=10)
205 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [2]: %timeit my_Metropolis_Gaussian(pdf_t, 3, 1, n_samples=100, burn_in=100, m=10)
102 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)