『モンテカルロ統計計算』章末問題を Julia で解く:第2章「乱数」
Julia 言語と MCMC の勉強のため、R 向けに書かれている教科書『モンテカルロ統計計算』(講談社、鎌谷研吾 著、駒木文保 編)の章末問題を解いていこうかと思います。また、単に問題解くだけでは面白くなかったりもするので、適宜 Python との比較などを追加しているものもあります。
今回は、第2章「乱数」です。
2.1 一様乱数のプロット
using Plots function scatter_2d(x) reshaped = reshape(x, (2, length(x) ÷ 2))' scatter(reshaped[:, 1], reshaped[:, 2]) end x = rand(1000) scatter_2d(x)
2.2 線形合同法による一様乱数の生成
function linear_congruential_generator(a, b, y, n, n_samples) x = zeros(n_samples) for i in 1:n_samples y = (a * y + b) % n x[i] = y / n end x end a = 13 b = 0 n = 67 y_init = 1234 n_samples = 1000 x = linear_congruential_generator(a, b, y_init, n, n_samples) scatter_2d(x) savefig("2.2.png")
2.3 逆変換法によるロジスティック乱数の生成
using BenchmarkTools, Distributions function gen_logistic_variables(n_samples) x = -log.(1 ./ rand(n_samples) .- 1) end n_samples = 1000 dlogistic = Logistic(0, 1) a = @benchmark gen_logistic_variables(n_samples) b = @benchmark rand(dlogistic, n_samples) println(a) println(b) # Trial(10.565 μs) # Trial(17.482 μs)
ちなみに全く同じ実装を Python (Numpy) でやると:
In [1]: import numpy as np In [2]: def gen_logistic_variables(n_samples): ...: return -np.log(1 / np.random.rand(n_samples) - 1) ...: In [3]: %timeit gen_logistic_variables(1000) 14.5 µs ± 55.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
となり、逆変換法を Julia で実装したもののほうが速い。また、np.random.logistic
についても、
In [6]: %timeit np.random.logistic(0, 1, 1000) 17.6 µs ± 74.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
となり、手元の環境では Julia の逆変換法による実装が最速ということになった。なお numpy は conda 経由で入れており MKL にリンクされている。
2.4 逆変換法によるパレート乱数の生成
パレート分布の確率密度関数が なので、 から までの累積分布関数は となる。したがって逆変換法は、 を一様乱数として とすればよい。コードは以下の通り:
function gen_pareto_variables(a, b, n_samples) u = rand(n_samples) x = similar(u) @. x = b * u ^ (-1 / a) end
2.5 逆変換法によるレイリー乱数の生成
レイリー分布の確率密度関数が なので、 での累積分布関数は となる。したがって逆変換法は、 を一様乱数として とすればよい。コードは以下の通り:
function gen_rayleigh_variables(σ, n_samples) u = rand(n_samples) x = similar(u) @. x = sqrt(-2σ^2 * log(u)) end
2.6 逆変換法によるワイブル乱数の生成
ワイブル分布の確率密度関数が なので、 での累積分布関数は となる。したがって逆変換は、 を一様乱数として とすればよい。コードは以下の通り:
function gen_weibull_variables(a, b, n_samples) u = rand(n_samples) x = similar(u) @. x = b * (-log(u)) ^ (1 / a) end
2.7 逆変換法による二項乱数の生成
using Distributions, Plots function gen_binomial_variables(N, θ, n_samples) x = zeros(Int, n_samples) for i in 1:n_samples u = rand() n = 0 F = (1 - θ) ^ N while u > F n = n + 1 F = F + binomial(N, n) * θ ^ n * (1 - θ) ^ (N - n) end x[i] = n end x end N = 10 θ = 0.3 n_samples = 1000 dbinom = Binomial(N, θ) a = gen_binomial_variables(N, θ, n_samples) b = rand(dbinom, n_samples) histogram(a, label = "inversion") histogram!(b, label = "Distributions", alpha = 0.5) savefig("2.7.png")
2.8 負の二項分布の生成
次の手順により負の二項分布に従う乱数を生成する:
using Distributions, Plots function gen_negative_binomial(r, θ, n_samples) dgamma = Gamma(r, (1 - θ) / θ) x = zeros(n_samples) @. x = rand(Poisson(rand(dgamma))) end r = 10 θ = 0.5 n_samples = 1000 x = gen_negative_binomial(r, θ, n_samples) y = rand(NegativeBinomial(r, θ), n_samples) histogram(x, normalized = true, label = "implemented") histogram!(y, normalized = true, label = "Distributions", alpha = 0.5) savefig("2.8.png")
この方法でよい理由は次の通り:上記の式は および を意味するので( とした)、 となり負の二項分布が得られる。
2.9 TBA
2.10 ラプラス分布を提案分布とする標準正規乱数の棄却法による生成
なので、対数をとり微分した式=0を の符号に気をつけて解くと が得られる。したがって となる。さらに の対数をとり について微分したものをゼロとおくと となるが、 のため となる。結局、棄却法で用いる定数の最小値は である。