ベイズ
解説
ベイズではパラメータの事後分布を求めるか、その事後分布からのサンプリングを行うことでパラメータの分布を考えます。その際、推定値として何を採用するかが問題となってきます。事後平均、事後の中央値、最頻値等色々考えられます。
事前分布の選択も重要です。
Rstanによる実装(非ベクトル化編)
Stanによるモデリングを行います。
まず、普通にベクトル化せずに書いてみます。
Stanではパラメータに対して分布を設定しておかなかった場合、勝手に無情報事前分布が割り当てられます。
library(rstan)
model2="
data{
int N;
int K;
real X[N,(K-1)];
real Y[N];
}
parameters{
real a[K];
real<lower=0> sigma;
}
transformed parameters{
real mu[N];
for(n in 1:N){
mu[n]=a[1];
for(k in 2:K){
mu[n]=mu[n]+a[k]*X[n,(k-1)];
}
}
}
model{
for(n in 1:N){
Y[n] ~ normal(mu[n],sigma);
}
}
"
data_list=list(N=n,K=k,X=cbind(X1,X2),Y=y)
fit=stan(model_code=model2,data=data_list,seed=1234)
出力
> fit
Inference for Stan model: c126dea47c42ac03e7ce323de93ea362.
4 chains, each with iter=2000; warmup=1000; thin=1;
post-warmup draws per chain=1000, total post-warmup draws=4000.
mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat
a[1] 10.26 0.04 1.66 6.95 9.17 10.28 11.40 13.47 1487 1
a[2] 0.40 0.00 0.01 0.38 0.40 0.40 0.41 0.42 2709 1
a[3] 5.20 0.00 0.04 5.12 5.17 5.20 5.22 5.28 1502 1
sigma 1.74 0.00 0.17 1.45 1.62 1.73 1.85 2.11 2438 1
今回は事後平均を推定値として採用するとします。
この場合推定値は
より効率の良いコード(ベクトル化編)
Stanはベクトル化した方が計算速度が速くなることがあります。
ベクトル化したコードをもう一度書いてみます。
model3="
data{
int N;
int K;
matrix[N,K] X;
vector[N] Y;
}
parameters{
vector[K] a;
real<lower=0> sigma;
}
transformed parameters{
vector[N] mu;
mu = X*a;
}
model{
Y~normal(mu,sigma);
}
"
data_list2=list(N=n,K=k,X=X,Y=y)
fit2=stan(model_code=model3,data=data_list2,seed=1234)
計算速度を調べるためにはproc.time関数を使います。
t=proc.time()
fit=stan(model_code=model2,data=data_list,seed=1234)
proc.time()-t
t=proc.time()
fit2=stan(model_code=model3,data=data_list2,seed=1234)
proc.time()-t
model2の出力
> proc.time()-t
ユーザ システム 経過
3.92 0.56 4.59
model3の出力
> proc.time()-t
ユーザ システム 経過
3.55 0.17 3.80
経過時間がmodel3の方が早くなっていることが確認出来ます。
ベイズの特徴
・最尤法の一般化概念と捉えることも出来る
・最尤法との違いは、最尤法はmonotonicな変数変換に対して不変なのに対して、ベイズは事後平均等を用いるため変化してしまう
・階層モデル等に拡張することも出来る
・事後の条件付き分布を見ているにすぎないことに注意
スポンサーリンク
3つの手法の比較
実際は背後の真のパラメータの値によりけりなので、それぞれの特徴を考慮した上で方法を選ぶことになるわけですが、今回はシミュレーション出来るので、サンプルサイズを変えて比較してみました。
N=10:300
a0=10
a1=0.4
a2=5.2
sigma=sqrt(3)
a0hat1=c()
a1hat1=c()
a2hat1=c()
a0hatB=c()
a1hatB=c()
a2hatB=c()
sigmahat1=c()
sigmahat2=c()
sigmahatB=c()
for(nn in N){
X1=sample(1:1000,nn,replace=FALSE)
X2=sample(300:500,nn,replace=TRUE)
y=a0+a1*X1+a2*X2+rnorm(nn,0,sigma)
model1=lm(y~X1+X2)
n=length(y)
k=3
X=cbind(1,X1,X2)
a0hat1[which(N==nn)]=coef(model1)[1]
a1hat1[which(N==nn)]=coef(model1)[2]
a2hat1[which(N==nn)]=coef(model1)[3]
sigmahat1[which(N==nn)]=sqrt(t(y-X%*%a_hat1)%*%(y-X%*%a_hat1)/(nn-k))
sigmahat2[which(N==nn)]=sqrt(t(y-X%*%a_hat1)%*%(y-X%*%a_hat1)/nn)
data_list2=list(N=n,K=k,X=X,Y=y)
fit2=stan(model_code=model3,data=data_list2,seed=1234,chains=1)
sigmahatB[which(N==nn)]=mean(rstan::extract(fit2)$sigma)
a0hatB[which(N==nn)]=mean((rstan::extract(fit2)$a)[,1])
a1hatB[which(N==nn)]=mean((rstan::extract(fit2)$a)[,2])
a2hatB[which(N==nn)]=mean((rstan::extract(fit2)$a)[,3])
}
par(mfcol=c(2,2))
plot(N,a0hat1,xlab="sample size",ylab="",main="a0",col="red",type="l")
points(N,rep(a0,length(N)),col="black",type="l")
points(N,a0hatB,col="green",type="l")
legend("topright", legend = c("OLS","Bayes"), col = c("red","green"),lty=c("solid","solid"))
plot(N,a1hat1,xlab="sample size",ylab="",main="a1",col="red",type="l")
points(N,rep(a1,length(N)),col="black",type="l")
points(N,a1hatB,col="green",type="l")
legend("topright", legend = c("OLS","Bayes"), col = c("red","green"),lty=c("solid","solid"))
plot(N,a2hat1,xlab="sample size",ylab="",main="a2",col="red",type="l")
points(N,rep(a2,length(N)),col="black",type="l")
points(N,a2hatB,col="green",type="l")
legend("topright", legend = c("OLS","Bayes"), col = c("red","green"),lty=c("solid","solid"))
plot(N,sigmahat1,xlab="sample size",ylab="",main="sigma",col="red",type="l")
points(N,rep(sigma,length(N)),col="black",type="l")
points(N,sigmahatB,col="green",type="l")
points(N,sigmahat2,col="yellow",type="l")
legend("topright", legend = c("OLS","Bayes","MLE"), col = c("red","green","yellow"),lty=c("solid","solid","solid"))
出力はこんな感じ。
の推定値が推定方法によって収束スピードが異なっている様子が見て取れます。
試しにもう少し比較してみると
par(mfcol=c(2,2))
plot(N,sigmahat1,xlab="sample size",ylab="",main="sigmaOLS",col="red",type="l",ylim=c(1,3))
points(N,rep(sigma,length(N)),col="black",type="l")
plot(N,sigmahat2,xlab="sample size",ylab="",main="sigmaMLE",col="red",type="l",ylim=c(1,3))
points(N,rep(sigma,length(N)),col="black",type="l")
plot(N,sigmahatB,xlab="sample size",ylab="",main="sigmaBayes",col="red",type="l",ylim=c(1,3))
points(N,rep(sigma,length(N)),col="black",type="l")
プロットをした印象としては、ある程度サンプルサイズの大きくなると、事後分布からの事後平均を使ったものが安定していそうな気がします。
真の値との誤差も求めてみます。
今回求めた全ての推定値から誤差を求めた場合
> sum((sigmahat1-sigma)^2)
[1] 9.220136
> sum((sigmahat2-sigma)^2)
[1] 6.285238
> sum((sigmahatB-sigma)^2)
[1] 6.807877
最尤推定量を使った値が最も誤差の総和が小さい
一方、サンプルサイズが50より大きい所で見た場合
> sum((sigmahat1[40:length(N)]-sigma)^2)
[1] 4.474911
> sum((sigmahat2[40:length(N)]-sigma)^2)
[1] 3.742564
> sum((sigmahatB[40:length(N)]-sigma)^2)
[1] 3.059328
ベイズ事後平均が最も誤差の総和が小さくなっています。
よって、今回の場合、高々サンプルサイズ300程度の推定では、誤差項の標準偏差はベイズ事後平均を使うのが良かったということになりました(結果論ですが。)
注意
・まず、線形回帰分析の場合はベイズを使えといった類の話ではありませんので、注意してください。今回のケースを試しに1回の実験で比較してみたら、偶然ベイズが勝ったというそれだけの話です。
・今回の比較は少々不偏推定量には都合の悪い物だったようにも思います(不偏推定量は平均的に真の値に近づく)
・正直、各サンプルサイズごとに1回こっきりの推定しかしていないので、各サンプルサイズにおいて何度も推定を行って比較を行うと、また話が変わるような気がします(多分サンプルサイズが小さいところでは、不偏推定量が勝つような気がする)。