Neural Network는 예를 들어 이 예제 처럼 선형회귀(Linear Regression)으로 분류하기 어려운 분류를 수행 할 수 있다.
베이즈 추론과 샘플링으로 얻고 싶은것은 신경망의 가중치와 편향에 대한 사후분포를 얻고 이를 통해 신경망을 재구성 하여 선형회귀에서 분류하기 어려운 분류 문제를 해결한다.
이 튜토리얼에서는 머신러닝 도구 제품군인 Turing과 Flux의 조합을 사용해 베이지안 신경망을 구현하는 방법을 보여드립니다. 분류 알고리즘을 구현하는 것을 목표로 Flux를 사용하여 신경망의 레이어를 지정하고 Turing을 사용하여 확률적 추론을 구현할 것입니다.
Bayesian Neural Network¶
이 튜토리얼에서는 머신러닝 도구 제품군인 Turing과 Flux의 조합을 사용해 베이지안 신경망을 구현하는 방법을 보여드립니다. 분류 알고리즘을 구현하는 것을 목표로 Flux를 사용하여 신경망의 레이어를 지정하고 Turing을 사용하여 확률적 추론을 구현할 것입니다.
using Turing
using FillArrays
using Flux
using ReverseDiff
using LinearAlgebra
using Random
using Plots
using JLD2
using DataFrames
using StatsPlots
# Use reverse_diff due to the number of parameters in neural networks.
Turing.setadbackend(:reversediff)
:reversediff
목표¶
아래 그림의 데이터 포인트를 분류한다.
여기서 우리의 목표는 베이지안 신경망을 사용하여 인공 데이터 세트의 포인트를 분류하는 것입니다. 아래 코드는 상자 모양의 패턴으로 배열된 데이터 포인트를 생성하고 작업할 데이터 세트의 그래프를 표시합니다.
데이터 포인트 및 레이블 생성¶
데이터포인트 생성:
총 N개의 데이터 포인트를 4개의 구획으로 나눠서 생성한다.
1,2,3,4 분면에 각각 20개의 데이터 포인트 생성
* 1사분면 : 0.5 < x < 5, 0.5 < y < 5 , 빨간점
* 3사분면 : -5 < x < -0.5, -5 < y < -0.5 , 빨간점
* 4사분면 : 0.5 < x < 5, -5 < y < -0.5 , 파란점
* 2사분면 : -5 < x < -0.5, 0.5 < y < 5 , 파란점
분류 카테고리 생성:
빨간점 : 1
파란점 : 0
# Number of point to generate.
N = 80
M = round(Int, N/4)
Random.seed!(1234)
# Generate artificial data.
# ----------------------------------
# 1사 분면의 빨강 데이터 포인트 생성
# 0.5 < x < 5, 0.5 < y < 5
# -----------------------------------
x1s = rand(M) * 4.5 # 0 ~ 4.5
x2s = rand(M) * 4.5 # 0 ~ 4.5
# ([0.5 ~ 5, 0.5 ~ 5]+)
xt1s = Array([[x1s[i] + 0.5; x2s[i] + 0.5] for i in 1:M])
# ----------------------------------
# 3사 분면의 빨강 데이터 포인트 생성
# -5 < x < 0.5, -5 < y < 0.5
# -----------------------------------
x1s = rand(M) * 4.5
x2s = rand(M) * 4.5
# ([-5 ~ -0.5]+)
append!(xt1s,Array([[x1s[i]-5; x2s[i]-5] for i in 1:M]))
# ----------------------------------
# 4사 분면의 파랑 데이터 포인트 생성
# 0.5 < x < 5, -5 < y < -0.5
# -----------------------------------
x1s = rand(M) * 4.5
x2s = rand(M) * 4.5
# ([0.5 ~ 5, -5 ~ -0.5]+)
xt0s = Array([[x1s[i] + 0.5; x2s[i] - 5] for i in 1:M])
# ----------------------------------
# 2사 분면의 파랑 데이터 포인트 생성
# -5 < x < -0.5, 0.5 < y < 5
# -----------------------------------
x1s = rand(M) * 4.5
x2s = rand(M) * 4.5
append!(xt0s, Array([[x1s[i] - 5, x2s[i] + 0.5 ] for i in 1:M]))
# Store all the data for later
# 빨강, 파랑 데이터 병합
xs = vcat(xt1s,xt0s)
# 분류카테로리 생성
# 빨간점(1), 파란점(0)의 레이블 생성
ts = vcat(ones(2*M), zeros(2*M))
# Plot data points
function plot_data()
x1 = map(e->e[1], xt1s)
y1 = map(e->e[2], xt1s)
x2 = map(e->e[1], xt0s)
y2 = map(e->e[2], xt0s)
Plots.scatter(x1,y1; color=:red)
return Plots.scatter!(x2,y2; color=:blue,legend=false)
end
plot_data()
Flex 신경망의 데이터는 Float32 이므로 기본 Float64를 Float32로 바꿔 주는것이 성능이 좋음 (GPU 사용하는 경우도 마찬가지)
# const F = Float32
# xs = map(e->F.(e),xs)
# ts = map(e->F.(e),ts);
Building a Neural Netrwork¶
다음 단계는 기존 신경망처럼 매개변수를 단일점이 아닌 분포로 표현하는 피드포워드 신경망을 정의하는 것입니다. 이를 위해 Flux의 신경망 기본 요소인 Dense
를 사용하여 선형 계층(linear layer)을 정의하고 Chain
을 통해 구성할 것입니다. 아래 그림과 같이 우리가 만든 네트워크 nn_initial
에는 tanh
활성화가 있는 두 개의 숨겨진 레이어와 시그모이드(σ
) 활성화가 있는 하나의 출력 레이어가 있습니다.
nn_initial
은 함수처럼 작동하며 데이터를 입력으로 받아 예측을 출력할 수 있는 인스턴스입니다. 신경망 매개변수에 대한 분포를 정의하고 Flux의 destructure
를 사용하여 매개변수를 parameters_initial
로 추출합니다. destructure
는 또한 (새로운) 매개변수를 받아 nn_initial
과 아키텍처는 동일하지만 매개변수가 업데이트된 신경망 인스턴스를 반환할 수 있는 또 다른 함수 reconstruct
를 반환합니다.
$ \text{parameters_initial} = [w^1_{11},w^1_{12},w^1_{13},…,w^1_{23},b^1_1,b^1_2,b^1_3,w^2_{11},w^2_{12},…,w^2_{32},b^2_1,b^2_2,w^3_{11},w^3_{21},b^3_1] $
Flux.destructure : 주어진 신경망의 모든 가중치($w$)와 편향($b$)을 추출하고, 이러한 가중치와 편향을 사용하여 원래의 신경망 구조로 재구성할 수 있는 결과를 반환
parameters_initial : 신경망의 초기 가중치와 편향을 담고 있는 배열
reconstruct : 이러한 가중치와 편향을 사용하여 원래의 신경망 구조로 재구성 할 수 있는 함수
# Construct a neural network using Flux
nn_initial = Chain(Dense(2, 3, tanh),
Dense(3, 2, tanh),
Dense(2, 1, σ))
# Extract weights and a helper function to reconstruct NN from weights
parameters_initial, reconstruct = Flux.destructure(nn_initial)
(parameters_initial, reconstruct) |> display
# number of parameters in NN
length(parameters_initial) |> display
(Float32[0.003838873, -0.69605756, 0.65243447, -0.9733285, -0.076712295, -0.98042494, 0.0, 0.0, 0.0, -0.92877734, 1.0588763, 0.17422558, 0.8207938, -0.7399675, 0.15133403, 0.0, 0.0, 0.6228594, 0.05872354, 0.0], Restructure(Chain, ..., 20))
20
Flux에서 데이터를 Float32를 기본적으로 취급하기 때문에 Float32로 바꿔 준다
x = map(e->Float32.(e), hcat(xs...))
y = map(e->Float32.(e), ts);
let
pred = nn_initial(x)
end
1×80 Matrix{Float32}: 0.4649 0.620925 0.618656 0.62601 … 0.638107 0.634906 0.638755
신경망의 가중치와 편향(Parameters)에 대한 Bayesian 추론 모델 작성¶
아래의 확률적 모델 사양은 IID 정규 분포 확률 변수가 있는 parameters
변수를 생성합니다. parameters
벡터는 신경망의 모든 파라미터(가중치 및 편향)를 나타냅니다.
신경망의 parameters(즉 w,b) 각각이 정규분포를 이루는 확률변수로 보고 이들은 parameters의 각 값을 평균(μ)으로 하고 1/$\alpha$ 을 공분산행렬(covariance matrix:Σ)로 설정 한다.
사전분포 정의:
각 파라미터(w,b)는 파라미터값을 중심으로 정규분포를 이룬다고 가정
우도(Likelihood):
분류확률은 신경망에서 예측한 각 점에 대한 분류 확률로 베르누이 분포를 따름
@model function bayes_nn(xs, ts, nparameters, reconstruct; α=0.09)
# Create the weights and bias vector.
parameters ~ MvNormal(Zeros(nparameters), I / α)
# Construct NN from parameters
# 새로운 파라미터로 신경망을 재구성
nn = reconstruct(parameters)
# Forward NN to make predictions
preds = nn(xs)
# Observe each prediction.
for i in 1:length(ts)
ts[i] ~ Bernoulli(preds[i])
end
end
bayes_nn (generic function with 2 methods)
이제 샘플을 호출하여 추론을 수행할 수 있습니다. 여기서는 NUTS 해밀턴 몬테카를로 샘플러를 사용합니다.
xs의 데이터 포멧을 column vector 포맷으로 바꿔야 함
신경망에 입력은 column vector
xs =
80-element Vector{Vector{Float64}}:
[3.109379540603596, 0.875480402444553]
[2.3508235307743273, 2.8660804874110517]
[4.874612371049609, 4.282884137652053]
column vector
hcat(xs...) =
2×80 Matrix{Float64}:
3.10938 2.35082 4.87461 0.56709 … -3.2496 -1.42898 -4.15106
0.87548 2.86608 4.28288 2.85777 1.12361 3.10967 4.59778
# 베이즈 추론 시행
n_thread = Threads.nthreads()
N = 5000
n_sample = div(N, n_thread)
m = bayes_nn(x, y, length(parameters_initial), reconstruct)
ch = sample(m, NUTS(), MCMCThreads(),n_sample,n_thread)
┌ Info: Found initial step size └ ϵ = 0.2 ┌ Info: Found initial step size └ ϵ = 0.4 ┌ Info: Found initial step size └ ϵ = 0.8 ┌ Info: Found initial step size └ ϵ = 0.4 ┌ Info: Found initial step size └ ϵ = 0.4 ┌ Info: Found initial step size └ ϵ = 0.05 ┌ Info: Found initial step size └ ϵ = 0.05 ┌ Info: Found initial step size └ ϵ = 0.4 ┌ Info: Found initial step size └ ϵ = 0.4 ┌ Info: Found initial step size └ ϵ = 0.21640625000000002 ┌ Info: Found initial step size └ ϵ = 0.8 ┌ Info: Found initial step size └ ϵ = 0.4 ┌ Info: Found initial step size └ ϵ = 0.4 ┌ Info: Found initial step size └ ϵ = 0.0484375 ┌ Info: Found initial step size └ ϵ = 0.39990234375 ┌ Info: Found initial step size └ ϵ = 0.03125 Sampling (16 threads): 100%|████████████████████████████| Time: 0:05:24
Chains MCMC chain (312×32×16 Array{Float64, 3}): Iterations = 157:1:468 Number of chains = 16 Samples per chain = 312 Wall duration = 826.41 seconds Compute duration = 11192.32 seconds parameters = parameters[1], parameters[2], parameters[3], parameters[4], parameters[5], parameters[6], parameters[7], parameters[8], parameters[9], parameters[10], parameters[11], parameters[12], parameters[13], parameters[14], parameters[15], parameters[16], parameters[17], parameters[18], parameters[19], parameters[20] internals = lp, n_steps, is_accept, acceptance_rate, log_density, hamiltonian_energy, hamiltonian_energy_error, max_hamiltonian_energy_error, tree_depth, numerical_error, step_size, nom_step_size Summary Statistics parameters mean std mcse ess_bulk ess_tail rhat ⋯ Symbol Float64 Float64 Float64 Float64 Float64 Float64 ⋯ parameters[1] 0.1546 3.4528 0.4921 49.9153 145.1910 1.2387 ⋯ parameters[2] 1.0427 3.4304 0.4239 64.3732 157.4950 1.1919 ⋯ parameters[3] -0.0770 3.9476 0.6594 35.9270 187.7273 1.3772 ⋯ parameters[4] 0.5137 3.9479 0.6770 35.2276 117.1930 1.3805 ⋯ parameters[5] 0.1675 3.8163 0.5821 44.8605 85.4270 1.2767 ⋯ parameters[6] -0.0910 3.2386 0.4294 54.8255 127.5765 1.2635 ⋯ parameters[7] 0.1628 2.5750 0.1391 347.4933 889.0700 1.0377 ⋯ parameters[8] -0.2693 2.6979 0.1997 175.4675 716.6765 1.0662 ⋯ parameters[9] 0.1384 2.3972 0.0925 717.8933 440.1522 1.0213 ⋯ parameters[10] -0.2081 3.5279 0.5366 46.6756 432.7114 1.2528 ⋯ parameters[11] -0.2471 3.3511 0.4741 52.6409 513.5498 1.2214 ⋯ parameters[12] 0.1926 3.4427 0.4563 62.4289 626.6283 1.1818 ⋯ parameters[13] 0.6787 3.4776 0.4709 57.5466 470.8515 1.2081 ⋯ parameters[14] -0.2204 3.6568 0.5043 57.7363 481.8551 1.1978 ⋯ parameters[15] 0.5018 3.5921 0.5028 56.9850 565.6284 1.1970 ⋯ parameters[16] 0.4972 3.8963 0.8458 26.6858 421.1597 1.6449 ⋯ parameters[17] -0.0600 3.8601 0.8357 26.6036 577.7419 1.6346 ⋯ parameters[18] -0.4443 5.6422 1.3373 25.6788 269.2036 1.6823 ⋯ parameters[19] 0.0404 5.6609 1.3585 25.2032 207.0638 1.7224 ⋯ parameters[20] -1.0537 4.9407 1.1657 26.1533 169.2818 1.6545 ⋯ 1 column omitted Quantiles parameters 2.5% 25.0% 50.0% 75.0% 97.5% Symbol Float64 Float64 Float64 Float64 Float64 parameters[1] -7.0350 -1.3898 0.0502 1.7004 7.6664 parameters[2] -6.1759 -0.5834 0.3549 3.1386 8.3968 parameters[3] -7.8482 -2.6145 -0.0729 2.1262 7.6693 parameters[4] -7.7854 -1.2576 0.1925 3.2401 8.0020 parameters[5] -7.7179 -1.5415 0.1358 2.4509 7.4884 parameters[6] -7.0489 -1.2593 -0.0792 0.9019 7.0117 parameters[7] -4.8500 -1.4015 0.1389 1.6419 5.5794 parameters[8] -5.6327 -1.9319 -0.2002 1.3163 5.5201 parameters[9] -4.7680 -1.1862 0.1242 1.4579 5.4378 parameters[10] -6.2271 -3.0134 -0.4625 2.6360 6.3334 parameters[11] -6.3347 -2.8296 -0.3839 2.3910 5.8521 parameters[12] -6.2369 -2.5175 0.5199 2.8463 6.1262 parameters[13] -6.1294 -2.0009 1.1477 3.2994 6.6373 parameters[14] -6.5645 -3.0929 -0.3645 2.7194 6.4987 parameters[15] -6.0796 -2.4911 1.0792 3.3558 6.6376 parameters[16] -6.6460 -2.9220 1.6796 3.6060 6.7747 parameters[17] -6.3914 -3.3815 -0.1131 3.1963 6.4821 parameters[18] -7.9063 -5.4175 -3.6641 5.2265 8.0685 parameters[19] -7.8541 -5.3287 0.0948 5.3811 8.0630 parameters[20] -7.4320 -5.0559 -3.6068 4.3279 7.0206
이제 샘플링된 체인에서 매개변수 샘플을 세타(n_thread x n_sample x 20 크기, 여기서 n_thread x n_sample은 반복 횟수, 20은 매개변수 수)로 추출합니다. 이 샘플은 주로 모델의 분류기가 얼마나 좋은지 판단하는 데 사용됩니다.
sampling 결과 save & load¶
@save "bayes_nn_samples.jld2" ch
@load "bayes_nn_samples.jld2" ch
1-element Vector{Symbol}: :ch
# df = DataFrame(ch)
# plot(ch)
여러 sample에 있는 parameter들을 하나로 합치기¶
theta,lp = let
params = MCMCChains.group(ch, :parameters).value
_,_,n = size(params)
theta = vcat([params[:,:,i] for i in 1:n]...)
lps = MCMCChains.group(ch, :lp).value
lp = vcat([lps[:,:,i] for i in 1:n]...)
theta, lp
end
([3.5171679776053835 -0.9596372644349761 … -6.0924936373184355 6.047657338663431; 5.037591208545007 -1.1063193382489742 … -5.038118829941873 7.25731220690677; … ; -5.680101618153894 4.771512342796615 … 5.2549205099170235 -3.426074813990592; -2.9224796942447853 7.19441000252973 … 4.212654073919177 -4.865727837337831], [-55.622668058247896; -61.7161545745407; … ; -60.79717928864991; -60.22461074899916;;])
Prediction Visualization¶
MAP(Maximum a Posterior) 추정을 사용하여 가장 높은 로그 사후분포(posterior)를 제공한 가중치 집합을 사용하여 모집단을 분류할 수 있습니다.
# A helper to create NN from weights `θ` and run it through data `x`
nn_forward(x, θᵢ) = reconstruct(θᵢ)(x)
nn_forward (generic function with 1 method)
let
θᵢ = theta[1,:]
nn_forward(x,θᵢ)
end
1×80 Matrix{Float32}: 0.989521 0.989531 0.991785 … 0.00579351 0.00599253 0.00599403
# Find the index that provided the highest log posterior in the chain
i = argmax(lp)
i |> display
# Extract the max row value from i
i = i.I[1]
θₘₐₚ = theta[i,:]
println("MAP 추정 최적 파리미터 : θₘₐₚ = $θₘₐₚ")
# 반복작업시 성능을 위해 MAP 추정결과 theta[i,:]를 고정
nn_forward_map(x) = reconstruct(θₘₐₚ)(x)
# MAP으로 추정한 theta를 사용한 x의 분류 결과
"MAP으로 추정한 파라미터 θₘₐₚ를 사용한 x의 분류 결과" |> display
nn_forward2(x) |> display
# Plot the posterior distribution with contour plot
x1_range = Float32.(collect(range(-6; stop=6, length=25)))
x2_range = Float32.(collect(range(-6; stop=6, length=25)))
Z = [nn_forward_map([x1, x2])[1] for x1 in x1_range, x2 in x2_range]
plot_data()
contour!(x1_range,x2_range,Z)
CartesianIndex(135, 1)
MAP 추정 최적 파리미터 : θₘₐₚ = [0.8640540809571358, -0.14663568688546055, -3.495132454721974, 2.997271215464062, -2.110583352609562, 0.0694509980479484, 1.3307833957246198, 0.05268024674726891, -1.8745139040591927, -2.5434329945680583, 0.9400456978922308, 0.5297885282669079, -1.2166826424902661, 2.343275454019229, -2.656676466187899, 3.4320310656152584, 1.7135439109110557, -4.112167441252718, -4.628350312189436, 3.9488920277727897]
"MAP으로 추정한 파라미터 θₘₐₚ를 사용한 x의 분류 결과"
1×80 Matrix{Float32}: 0.963621 0.963744 0.963744 0.963711 … 0.0206274 0.0177642 0.0179179
위의 등고선 플롯은 MAP 메서드가 데이터를 분류하는 데 그리 나쁘지 않다는 것을 보여줍니다.
let
# Plot the posterior distribution with contour plot
colors = vec(nn_forward2(x))
plot_data()
scatter!(x[1,:],x[2,:], zcolor=colors , legend=false, colorbar=true,
markersize=10, marker=:cross,
background_color=:grey)
end
이제 예측을 시각화할 수 있습니다.
베이즈 신경망의 예측 분포¶
이 수식은 베이즈 신경망의 예측을 위한 것입니다.
$$ p(\tilde{x} | X,\alpha) = \int_\theta p(\tilde{x} | \theta) p(\theta | X,\alpha) \approx \sum_{\theta \sim p(\theta | X,\alpha) } f_\theta(\tilde{x}) $$$p(\tilde{x} | X,\alpha)$는 학습데이터 $X$와 하이퍼파라미터 $\alpha$가 주어 졌을 때 새로운 데이터 포인트$\tilde{x}$에 대한 예측 확률을 나타냅니다.
여기서 $p(\tilde{x} | \theta)$는 파라미터(가중치와 편향) $\theta$로 구성된신경망 모델을 통해 주어진 입력 $\tilde{x}$에 대한 예측을 나타 냅니다. 신경망은 파라미터 $\theta$를 사용하여 입력 데이터를 변환하며, 이를 통해 얻어진 출력을 $f_\theta(\tilde{x})$로 표현 할 수 있습니다. 따라서 $p(\tilde{x} | \theta)$와 $f_\theta(\tilde{x})$는 동일 하다고 볼 수 있습니다.
$p(\theta | X,\alpha)$는 학습데이터 $X$를 사용하여 베이즈 추론으로 얻은 파라미터 $\theta$의 사후분포 입니다.
이 예제에서 $X$는 데이터 포인트 xs
와 타켓(또는 카테고리, 레이블) ts
를 포함 합니다. 즉 $X = \{xs,ts\}$
위의 수식에서 $p(\tilde{x} | X,\alpha)$을 적분으로 직접 얻는것은 일반적으로 계산적으로 어렵거나 불가능 하기 때문에 근사적 방법을 사용하게 됩니다.
$\sum_{\theta \sim p(\theta | X,\alpha) } f_\theta(\tilde{x})$는 이러한 근사적 방법 중 하나입니다. 여기서는 $p(\theta | X,\alpha)$의 확률분포에서 샘플링된 특정한 $\theta$ 값들에 대해서만 $f_\theta(\tilde{x})$를 계산하여 평균을 취합니다. 이런 방식으로, 전체 $\theta$의 공간을 대신해서 특정한 샘플링된 $\theta$값들만을 사용하여 적분을 근사하게 됩니다.
이러한 근사 방식을 몬테카를로 적분(Monte Carlo integration)이라 불리며, 높은 차원의 적분 문제에 흔희 사용됩니다.
따라서 $p(\tilde{x} | X,\alpha)$에 대한 적분이 $\sum_{\theta \sim p(\theta | X,\alpha) } f_\theta(\tilde{x})$에 가깝다는 것은, 적분 값을 근사하기 위해 몬테카를로 방법을 사용한다는 것을 의미 합니다.
$\theta$의 사후 분포 $p(\theta | X,\alpha)$ 구하기¶
$p(\theta | X,\alpha) \propto p(X|\theta) p(\theta|\alpha)$
- $p(X | \theta)$ : 우도 (likelihood)
- $p(\theta|\alpha)$ : 파라미터 $\theta$의 사전분포
1. 사전분포(prior)¶
- 수식 : $p(\theta | \alpha)$
하이퍼파라미터 $\alpha$가 주어 졌을때 파라미터 $\theta$의 사전분포
- 코드:
parameters ~ MvNormal(Zeros(nparameters), I / α)
- 해당 코드는 신경망의 가중치와 편향에 대한 사전분포를 정규분포로 설정합니다. 여기서 $\alpha$는 정규분포의 공분산을 조절하는 하이퍼파라미터 입니다.
- $\alpha$ : 정규분포의 공분산 행렬의 정밀도를 결정하는 하이퍼 파라미터로서 값이 크면 사전분포의 분산이 작아져 파라미터의 값이 0 (또는 평균값) 주변에 더 집중되게 되며 반대로 값이 작으면 파라미터의 가능한 값의 범위가 넓어진다. 수식과 코드에서 $\alpha$는 동일한 개념을 나타내며, 베이지안 접근법에서 모델의 파라미터에 대한 사전 지식을 나타내는 정밀도(precision) 또는 불확실성(uncertainty)를 조절하는 역할을 합니다.
2. 신경망 예측 모델¶
파라미터 $\theta$가 주어 졌을 때 신경망의 예측 함수 : $f_{\theta}$
- 수식(1) : $f_{\theta}(X)$
- $X$ : 코드(1)에서 사용된 xs
- 아래 코드(1)에서
preds
가 $f_{\theta}(X)$에 해당
- 코드(1): 수식(1)에 해당 하는 코드
nn = reconstruct(parameters)
preds = nn(xs)
- 수식(2) : $f_{\theta_i}(\tilde{x})$
- $\theta_i$ : 코드(2)에서 사용된
theta[i, :]
- $\tilde{x}$ : 코드(2)에서 사용된
x1
,x2
- 코드(2): 수식(2)에 해당 하는 코드
nn_forward(x, theta) = reconstruct(theta)(x)
nn_forward([x1, x2], theta[i, :])
수식(3) : $\sum_{\theta \sim p(\theta | X,\alpha) } f_\theta(\tilde{x})$
코드(3): 수식(3)에 해당 하는 코드
function nn_predict(x, theta, num)
return mean([nn_forward(x, theta[i, :])[1] for i in 1:10:num])
end;
3. 우도(Likelihood)¶
- 수식 : $p(X | \theta)$
- 파라미터(여기서는 신경망의 가중치와 편향) $\theta$가 주어졌을 때 새로운 데이터 포인트 $X$의 분포를 나타냅니다.
- $X$ = {
xs
,ts
} - $p(X | \theta) = p(\text{ts} | \text{xs}, \theta) = \prod_{i=1}^N p(t_i | x_i,\theta) = \prod_{i=1}^N \text{Bernoulli}(t_i;f_{\theta}(x_i))$
- 신경망의 출력인
preds
와 실제 타켓(레이블)값ts
간의 관계는 Bernoulli분포를 사용하여 모델링 됩니다.
즉ts[i] ~ Bernoulli(preds[i])
에서,
$p(t_i | x_i,\theta) = \text{Bernoulli}(t_i;f_{\theta}(x_i))$
에 해당 합니다.
여기서 $t_i$ =ts[i]
, $x_i$=xs[i]
, $f_{\theta}(x_i)$ =pred[i]
-$f_{\theta}(x_i)$ = preds[i] = nn(xs[i]) = reconstruct(parameters)(xs[i])
- 이렇게 표현된 우도는 신경망의 예측이 얼마나 실제 타겟값과 일치 하는지 나타냅니다.
- 코드:
nn = reconstruct(parameters)
preds = nn(xs)
for i in 1:length(ts)
ts[i] ~ Bernoulli(preds[i])
end
- 주어진
parameters
(수식에서 $\theta$ 해당)를 사용하여 신경망을 재구성(reconstruct
)하고, - 이 신경망을 사용해 입력 데이터
xs
에 대한 예측preds
를 생성합니다.
4. 사후분포(posterior)¶
- 수식: $p(\theta | X,\alpha)$
- 사후분포의 정확한 형태는 계산적으로 어려울 수 있기 때문에 종종 MCMC(Markov Chain Monte Carlo)와 같은 샘플링 기법을 사용하여 이 분포에서 샘플을 얻습니다.
ch = sample(m, NUTS(), N)
- 사후분포에서 샘플링 : $\theta \sim p(\theta | X,\alpha)$,
theta = MCMCChains.group(ch, :parameters).value
- 사후분포는 계산의 편의를 위해
log
를 취한log
사후분포로 계산 한다
- 코드:
## 사후분포를 계산하고 사후 분포에서 샘플링
## ch : 파라미터 샘플
ch = sample(m, NUTS(), N)
## 샘플에서 𝜃를 추출
theta = MCMCChains.group(ch, :parameters).value
## 샘플에서 로그 사후분포를 추출
lp = ch[:lp]
또는 여러개의 샘플 chain을 생성하고 하나로 합쳐서 𝜃와 로그 사후분포를 추출한다
ch = sample(m, NUTS(), MCMCThreads(),n_sample,n_thread)
theta,lp = let
params = MCMCChains.group(ch, :parameters).value
_,_,n = size(params)
theta = vcat([params[:,:,i] for i in 1:n]...)
lps = MCMCChains.group(ch, :lp).value
lp = vcat([lps[:,:,i] for i in 1:n]...)
theta, lp
end
예측 함수 만들기¶
아래 수식에서 예측에 해당하는 $p(\tilde{x} | X,\alpha)$를 위한 $p(\theta | X,\alpha)$과 $f_{\theta}$ 를 전부 구했다. $ p(\tilde{x} | X,\alpha) = \int_\theta p(\tilde{x} | \theta) p(\theta | X,\alpha) \approx \sum_{\theta \sim p(\theta | X,\alpha) } f_{\theta}(\tilde{x}) $
- 수식 : $p(\tilde{x} | X,\alpha) \approx \sum_{\theta \sim p(\theta | X,\alpha) } f_{\theta}(\tilde{x})$
$f_{\theta}(\tilde{x})$ =
nn_forward(x, theta) = reconstruct(theta)(x)
$\sum_{\theta \sim p(\theta | X,\alpha) } f_{\theta}(\tilde{x})$ =
nn_predict(x, theta, num)
- 코드 :
nn_forward(x, theta) = reconstruct(theta)(x)
function nn_predict(x, theta, num)
return mean([nn_forward(x, theta[i, :])[1] for i in 1:10:num])
end;
nn_predict
함수는 MCMC 체인에서 도출된 가중치로 매개변수화된 네트워크에서 평균 예측값을 가져옵니다.
예측 예시¶
n_end = 1500
x1_range = collect(range(-6; stop=6, length=25))
x2_range = collect(range(-6; stop=6, length=25))
Z = [nn_predict([x1, x2], theta, n_end)[1] for x1 in x1_range, x2 in x2_range]
여기서 새로운 데이터 $\tilde{x}$는 x1_range
와 x2_range
로 표현
Z가 $p(\tilde{x} | X,\alpha)$에 해당
평균예측함수 : $p(\tilde{x} | X,\alpha) \approx \sum_{\theta \sim p(\theta | X,\alpha) } f_{\theta}(\tilde{x})$
# Return the average predicted value across multiple weight
function nn_predict(x, theta,num)
return mean([nn_forward(x, theta[i, :])[1] for i in 1:10:num])
end
nn_predict (generic function with 1 method)
다음으로, nn_predict
함수를 사용하여 x1
및 x2
좌표가 -6에서 6 사이의 범위인 지점 샘플에서 값을 예측합니다. 아래에서 볼 수 있듯이 여전히 데이터에 만족스럽게 잘 맞으며, 더 중요한 것은 신경망의 예측이 불확실한 부분, 즉 클러스터 경계 사이의 영역도 훨씬 쉽게 확인할 수 있다는 것입니다.
# Plot the average prediction
n_end = 1500
x1_range = collect(range(-6; stop=6, length=25))
x2_range = collect(range(-6; stop=6, length=25))
Z = [nn_predict([x1, x2], theta, n_end)[1] for x1 in x1_range, x2 in x2_range]
plot_data()
contour!(x1_range, x2_range, Z)
베이지안 신경망의 예측력이 샘플 간에 어떻게 변화했는지 알고 싶다고 가정해 보겠습니다. 이 경우 다음 그래프는 샘플 1에서 1,000까지의 네트워크 가중치에서 생성된 윤곽선 플롯의 애니메이션을 표시합니다.
# # Number of iteration to plot
# n_end = 500
# anim = @gif for i in 1:n_end
# plot_data()
# Z = [nn_forward([x1,x2],theta[i,:])[1] for x1 in x1_range, x2 in x2_range]
# contour!(x1_range, x2_range, Z; title="Iteration $i", clim = (0,1))
# end every 5
ROC Curve¶
using PyCall
# return : optimal_threshold
function plot_roc_curve(fpr,tpr,thresholds,optimal_auc)
J_values = tpr - fpr
idx = argmax(J_values)
optimal_threshold = thresholds[idx]
plot(fpr,tpr, title="ROC Curve",
label="ROC Curve(AUC=$(round(optimal_auc,digits=2)))",
xlabel = "False Positive Rate(FPR)",
ylabel = "True Positive Rate(TPR)")
scatter!([fpr[idx]],[tpr[idx]], color=:red,
label="Optimal threshold=$(round(optimal_threshold,digits=4))\nSensitivity=$(round(tpr[idx],digits=2))\nSpecificity=$(round(1 - fpr[idx],digits=2))")
plot!([0, 1], [0, 1], linestyle=:dash, color=:black, label="Random Classifier")|>display
optimal_threshold
end
plot_roc_curve (generic function with 1 method)
X 에 대한 ROC Curve¶
optimal_threshold = let
n_end = 1500
_,n = size(x)
category_pred = map(i->nn_predict([x[1,i],x[2,i]], theta, n_end) , 1:n)
category_true = Int.(y)
sklearn = pyimport("sklearn.metrics")
fpr, tpr, thresholds = sklearn.roc_curve(category_true,category_pred)
optimal_auc = sklearn.auc(fpr, tpr)
plot_roc_curve(fpr,tpr,thresholds,optimal_auc)
end
0.95259494f0