最近はGaussianRandomWalkを使った時系列ベイズモデルの推定に挑戦していたが、あまりうまくいかなかったので一旦「Pythonで体験するベイズ推論」に戻ろうと思う。

今回は「Pythonで体験するベイズ推論」の「2.2.27 例題 カンニングした学生の割合」をJuliaで実装した内容を紹介する。

まずはライブラリのインポート

using Distributed

addprocs(3)
using CSV
using DataFrames
using HTTP
using LaTeXStrings
using LinearAlgebra
@everywhere using Mamba
using Plots

データの加工はこのような形で行った。DataFrameInt64 にパースしたい行にMissing valueやNaNがあるとき、convertではエラーになるので、 パースできない場合は missing になる tryparse を使って、その後 nothing になる行を削除して、Union{Nothing, Int64} から Int64 にもう一度変換している。

r = HTTP.request("GET", "https://git.io/vXknD");

challengers_data = CSV.read(IOBuffer(r.body))
names!(challengers_data, [:date, :temperature, :incident])
# incidentのパース
challengers_data[:incident] = tryparse.(Int64, challengers_data[:incident])
# NaNを削除
challengers_data = challengers_data[challengers_data[:incident] .!= nothing, :]
challengers_data[:incident] = convert.(Int64, challengers_data[:incident])
disallowmissing!(challengers_data)

データの図示をする。weighted_color_mean を使って、マーカーの色を青から赤にグラデーションさせた。

temperature = challengers_data[:temperature]
color_weight = (temperature .- minimum(temperature)) ./ (maximum(temperature) .- minimum(temperature))
wcolor = weighted_color_mean.(color_weight, colorant"red", colorant"blue")
scatter(challengers_data.temperature, challengers_data.incident, 
        markercolor = wcolor, 
        xlabel = "Temprature (F)", ylabel = "O-ring failure", label = "")

Oリングと破損の関係

破損発生の有無を表す確率変数 \( D_i \) は、ベルヌーイ分布とロジスティック関数を用いて

\( \begin{aligned} D_i &\sim \text{Bernoulli}(p(t_i)), \\ p(t) &= \frac{1}{1 +\exp(\beta t + \alpha)}, \end{aligned} \)

\( t \) : 温度 とモデリングされる。今回は、あとでサンプルされた p の値を使ってデータのシミュレーションを行うために、下のように pLogical ノードを割り当てたが、

model = Model(
    
    observed = Stochastic(1,
        p -> UnivariateDistribution[Bernoulli(x) for x in p],
        false
    ),
    
    p = Logical(1,
        (alpha, beta, temperature) 
            -> @.(1.0 / (1.0 + exp(beta * temperature + alpha)))
    ),
    
    alpha = Stochastic(() -> Normal(0, sqrt(1000))),
    beta = Stochastic(() -> Normal(0, sqrt(1000))),
)

observedのモデリングだけであればpを経由せずに直接モデリングすることもできる。

model = Model(
    
    observed = Stochastic(1,
        (alpha, beta, temperature) -> 
            UnivariateDistribution[
                Bernoulli(1.0 / (1.0 + exp(beta * x + alpha)))
            for x in temperature],
        false
    ),
    
    alpha = Stochastic(() -> Normal(0, sqrt(1000))),
    beta = Stochastic(() -> Normal(0, sqrt(1000))),

)

モデルパラメータ alpha, beta の事後分布は次のようになった。

data = Dict{Symbol, Any}(
    :observed => challengers_data[:incident],
    :temperature => challengers_data[:temperature],
)
inits = [
    Dict{Symbol, Any}(
        :observed => challengers_data[:incident],
        :alpha => 0,
        :beta => 0,
    ) for _ in 1:3
]
scheme = [AMWG([:alpha, :beta], 0.1)]
setsamplers!(model, scheme)
sim = mcmc(model, data, inits, 200000, burnin = 50000, thin = 50, chains = 3)
p = Mamba.plot(sim[:, [:alpha, :beta], :], legend = true)
Mamba.draw(p, nrow = 2, ncol = 2)

Value, Density

p = Mamba.plot(sim[:, [:alpha, :beta], :], [:autocor, :mean], legend=true)
Mamba.draw(p, nrow = 2, ncol = 2)

Autocorrelation, Mean

alpha に対して beta をプロットすると、次のような原点を通る直線上のグラフになる。\( p(t) = 0.5 \) となるのが \( t = -\alpha / \beta \) となるので、故障するかしないか半々となる温度はシミュレーションで大きく変化しないことがわかる。

alpha_samples = sim[:, [:alpha], :].value[:]
beta_samples = sim[:, [:beta], :].value[:]
scatter(alpha_samples, beta_samples, label = "", markersize = 3)

Relation between alpha and beta

破損確率の事後期待値と、サンプルから選んでプロットする。

function logistic(x, alpha, beta)
    1.0 ./ (1.0 .+ exp.(beta * x .+ alpha))
end

xs = collect((minimum(temperature) - 5):0.1:(maximum(temperature) + 5))
p_t = logistic(transpose(xs), alpha_samples, beta_samples)

Plots.plot(xs, vec(mean(p_t, dims=1)), linewidth = 2, label = "Average posterior probability")
Plots.plot!(xs, p_t[1, :], linewidth = 2, linestyle = :dash, label = "Realization from posterior")
Plots.plot!(xs, p_t[end-2, :], linewidth = 2, linestyle = :dash, label = "Realization from posterior")
scatter!(challengers_data.temperature, challengers_data.incident, 
        markercolor = weighted_color_mean.(color_weight, colorant"blue", colorant"red"), 
        xlabel = "Temprature (F)", ylabel = "O-ring failure", label = "")

Realization

破損確率の事後期待値と、95%信頼区間をプロットする

p_t_ci = mapslices(x -> quantile(x, [0.025, 0.975]), p_t, dims = 1)

Plots.plot(xs, p_t_ci[2, :], linewidth = 0, 
    fillrange = p_t_ci[1, :], fillalpha = 0.4,
    label = "95% Confidence interval")
Plots.plot!(xs, vec(mean(p_t, dims=1)), linewidth = 2, label = "Average posterior probability")
scatter!(challengers_data.temperature, challengers_data.incident, 
        markercolor = weighted_color_mean.(color_weight, colorant"blue", colorant"red"), 
        xlabel = "Temprature (F)", ylabel = "O-ring failure", label = "")

Confidence interval