在许多情况下,我们没有足够的计算能力评估空间中所有n维像素的后验概率 。在这些情况下,我们倾向于利用称为Markov-Chain Monte Carlo 算法的程序 。此方法使用参数空间中的随机跳跃来(最终)确定后验分布(点击文末“阅读原文”获取完整代码数据)。
相关视频:马尔可夫链原理可视化解释与R语言区制转换Markov regime switching实例
马尔可夫链原理可视化解释与R语言区制转换Markov regime switching实例
拓端
,赞11
,时长07:25
相关视频
马尔可夫链蒙特卡罗方法MCMC原理与R语言实现
拓端
,赞26
,时长08:47
马尔科夫链蒙特卡洛方法
MCMC的关键如下:
跳跃概率的比例与后验概率的比例成正比。
跳跃概率可以表征为:
概率(跳跃)*概率(接受)
从长远来看,该链将花费大量时间在参数空间的高概率部分,从而实质上捕获了后验分布。有了足够的跳跃,长期分布将与联合后验概率分布匹配。
MCMC本质上是一种特殊类型的随机数生成器,旨在从难以描述(例如,多元,分层)的概率分布中采样。在许多/大多数情况下,后验分布是很难描述的概率分布。
MCMC使您可以从实际上不可能完全定义的概率分布中进行采样!
令人惊讶的是,MCMC的核心并不难于描述或实施。让我们看一个简单的MCMC算法。
Metropolis-Hastings算法
该算法与模拟退火算法非常相似。
MH算法可以表示为:
Prob(acceptB | A)= min(1,Posterior(B)Posterior(A)⋅Prob(b→a)Prob(a→b))
请注意,从本质上讲,这与“ Metropolis”模拟退火算法相同,后验概率代替了概率,并且 k 参数设置为1。
二元正态例子
请记住,MCMC采样器只是随机数生成器的一种。我们可以使用Metropolis-Hastings采样器来开发自己的随机数生成器,生成进行简单的已知分布。在此示例中,我们使用MH采样器从标准双变量正态概率分布生成随机数。
对于这个简单的示例,我们不需要MCMC采样器。一种实现方法是使用以下代码,该代码从具有相关参数ρ的双变量标准正态分布中绘制并可视化任意数量的独立样本。
1. #################
2. #MCMC采样的简单示例
3. #################
4. #########
5. # #首先,让我们构建一个从双变量标准正态分布生成随机数的函数
6. rbvn<-function (n, rho) #用于从二元标准正态分布中提取任意数量的独立样本。
7. {
8. x <- rnorm(n, 0, 1)
9. y <- rnorm(n, rho * x, sqrt(1 - rho^2))
10. cbind(x, y)
11. }
12. #########
13. # 现在,从该分布图中绘制随机抽样
14. bvn<-rbvn(10000,0.98)
15. par(mfrow=c(3,2))
16. plot(bvn,col=1:10000
1. ###############
2. # #Metropolis-Hastings双变量正态采样器的实现...
3. library(mvtnorm) # 加载一个包,该包使我们能够计算mv正态分布的概率密度
4. metropoli<- function (n, rho=0.98){ # 双变量随机数生成器的MCMC采样器实现
5. mat <- matrix(ncol = 2, nrow = n) # 用于存储随机样本的矩阵
6. x <- 0 # 所有参数的初始值
7. prev <- dmvnorm(c(x,y),mean=c(0,0),sig
8. # 起始位置分布的概率密度
9. mat[1, ] <- c(x, y) # 初始化马尔可夫链
10. newx <- rnorm(1,x,0.5) # 进行跳转
11. newprob <- dmvnorm(c(newx,newy),sigma =
12. # 评估跳转
13. ratio <- newprob/prev # 计算旧位置(跳出)和建议位置(跳到)的概率之比。
14. prob.accept <- min(1,ratio) # 决定接受新跳跃的概率!
15. if(rand<=prob.accept){
16. x=newx;y=newy # 将x和y设置为新位置
17. mat[counter,] <- c(x,y) # 将其存储在存储阵列中
18. prev <- newprob # 准备下一次迭代
然后,我们可以使用MH采样器从该已知分布中获取随机样本…
1. ###########
2. # 测试新的M-H采样器
3. bvn<-metropoli(10000,0.98)
4. par(mfrow=c(3,2))
5. plot(bvn,col=1:10000)
6. plot(bvn,type=
01
02
03
04
让我们尝试解决一个问题。
MCMC对粘液瘤病进行调查
1. ############
2. #黏液病示例的MCMC实现
3. ############
subset(MyxDat,grade==11. ## grade day titer
2. ## 1 1 2 5.207
3. ## 2 1 2 5.734
4. ## 3 1 2 6.613
5. ## 4 1 3 5.997
6. ## 5 1 3 6.612
7. ## 6 1 3 6.810
选择使用Gamma分布。这是经验分布:
1. ###########
2. # 第100次可视化粘液病数据
3. hist(Myx$titer,freq=FALSE)
我们需要估算最适合此经验分布的伽马速率和形状参数。这是适合此分布的Gamma的一个示例:
- #########
- # ...覆盖生成模型的数据(伽玛分布)
- curve(dgamma(x,shape=40,scale=0.15),add=T,col="red")
二维(对数)似然面:
1. ##############
2. # 定义二维参数空间
3. ##############
4. shapevec <- seq(3,100,by=0.1)
5. scalevec <- seq(0.01,0.5,by=0.001)
6. ##############
7. # #定义参数空间内此网格上的似然面
8. ##############
9. GammaLogLikelihoodFunction <- function(par
10. }
11. surface2D <- matrix(nrow=length(shapevec),ncol=length(scalevec)) #初始化存储变量
12. newparams <- c(sha
13. surface2D[i,j] <- GammaLogLikelihoodFunction(newparams)
14. ############
15. # 可视化似然面
16. ############
17. contour(x=shapevec,y=scalevec,z=surface2D,levels=c(-30,-40,-80,-500),add=T)
这是MH算法的实现,用于找到后验分布!
首先,我们需要一个似然函数–这次,我们将返回真实概率–而不是对数转换的概率
1. ############
2. #编写非对数转换的似然函数
3. GammaLike- function(params){
4. prod(dgamma(Myx$titer,shape=params['shape']
5. params <- c(shape=40,
6. ## shape scale
7. ## 40.00 0.15
GammaLike(params)## [1] 2.906766e-22GammaLogLike(params)## [1] -49.58983
然后,我们需要预先分配参数!在这种情况下,我们分配gamma(shape = 0.01,scale = 100)和gamma(shape = 0.1,scale = 10)的分布(均值为1且方差略低):
1. #############
2. # 函数返回参数空间中任意点的先验概率密度
3. GammaPriorFunction <- function(params){
4. prior <- c(shape=NA,scale=NA)
5. ],3,100)
6. # prior['scale'] <- dunif(params['
7. GammaLogPriorFunction <- function(params){
8. prior <- c(shape=NA,scale=NA)
9. '],shape=0.001,scale=1000,log=T)
10. # prior['shape'] <- dunif(params['shape'],3,100)
11. # prior['scale'] <- dunif(params['
12. curve(dgamma(x,shape=0.01,scale=1000),3,100)
params1. ## shape scale
2. ## 40.00 0.15
GammaPrior(params)## [1] 1.104038e-061. prior2D <- matrix(nrow=length(shapevec),ncol=length(scalevec)) # 初始化存储变量
2. newparams <- c(shape=50,scale=0
3. for(j in 1:length(scalevec)){
4. newparams['scale'] <- sca
5. ############
6. # 可视化似然面
7. ############
8. image(x=shapevec,y=scalevec,z=prior2D,zlim
contour(x=shapevec,y=scalevec,z=prior2D,levels=c(-30,-40,-80,-500),add=T)
我们假设形状和比例 在先验中是 独立的(联合先验的乘法概率)。然而,并没有对后验参数相关性提出相同的假设,因为概率可以反映在后验分布中。
然后,我们需要一个函数,该函数可以计算参数空间中任何给定跳转的后验概率比率。因为我们正在处理 后验概率的 比率,所以 我们不需要计算归一化常数。
无需归一化常数,我们只需要计算加权似然比(即先验加权的似然比)
1. ############
2. # 函数用于计算参数空间中任意两点之间的后验密度比
3. PosteriorRatio <- function(oldguess,newguess
4. oldLik <- max(1e-90,GammaLikelihoodFunction(oldguess)) # 计算旧猜测的可能性和先验密度
5. newLik <- GammaLikelihoodFunction(newguess) # 在新的猜测值下计算可能性和先验密度
6. return((newLik*newPrior)/(oldLik*oldPrior)) # 计算加权似然比
7. }
8. PosteriorRatio2 <- function(oldguess,newguess){
9. oldLogLik <- GammaLogLikelihoodFunction(oldguess) # 计算旧猜测的可能性和先验密度
10. newLogLik <- GammaLogLikelihoodFunction(newguess) # 在新的猜测值下计算可能性和先验密度
11. return(exp((newLogLik+newLogPrior)-(oldLogLik+oldLogPrior))) # 计算加权似然比
12. }
## [1] 0.01436301
PosteriorRatio2(oldguess,newguess)
然后,我们需要一个函数进行新的推测或在参数空间中跳转:
1. ############
2. # 为参数空间中的跳转定义:使用正态分布函数进行新的推测
3. newGuess <- function(oldguess)
4. jump <- c(shape=rnorm(1,mean=0,sd=sdshapejump),scale=rnorm(1,0,sdscalejump))
5. newguess <- abs(oldguess + ju
6. }
7. # 在原始推测附近设置新的推测
8. newGuess(oldguess=params)
9. ## shape scale
10. ## 35.7132110 0.1576337
newGuess(oldguess=params)
- ## shape scale
- ## 45.1202345 0.2094243
newGuess(oldguess=params)
- ## shape scale
- ## 42.87840436 0.08152061
现在,我们准备实现Metropolis-Hastings MCMC算法:
我们需要一个初始点:
- ##########
- # 在参数spacer中设置起点
- startingvals <- c(shape=75,scale=0.28) # 算法的起点
测试函数
1. ###########
2. # 尝试我们的新函数
3. newguess <- newGuess(startingvals) # 在参数空间中跳跃
4. newguess
5. ## shape scale
6. ## 73.9663949 0.3149796
PosteriorRatio2(startingvals,newguess) # 后验比例差异## [1] 2.922783e-57
现在让我们看一下Metropolis-Hastings:
1. ###############
2. #可视化Metropolis-Hastings
3. chain.length <- 11
4. gth,ncol=2)
5. colnames(guesses) <- names(startingvals)
6. guesses[1,] <- startingvals
7. counter <- 2
8. post.rat <- PosteriorRatio2(oldguess,newguess)
9. prob.accept <- min(1,post
10. oldguess <- newguess
11. guesses[coun
12. #可视化
13. contour(x=shapevec,y=scal
我们运行更长的时间
1. ##########
2. # 获取更多MCMC示例
3. chain.length <- 100
4. oldgu
5. counter <- 2
6. while(counter <= chain.length){
7. newguess <- newGuess(oldguess)
8. post.rat <- Posterio
9. rand <- runif(1)
10. if(rand<=prob.accept){
11. ewguess
12. counter=counte
13. #可视化
14. image(x=shapevec,y=scalevec,z=su
15. urface2D,levels=c(-30,-40,-80,-5
16. lines(guesses,col="red")
更长的时间
1. ############
2. #更长的时间
3. chain.length <- 1000
4. oldguess <- startingvals
5. chain.length,ncol=2)
6. colnames(guesses) <- names(startingvals)
7. guesses[1,] <- startingva
8. ess)
9. post.rat <- PosteriorRatio2(oldguess,newguess)
10. prob.accept <- min(1,post.rat)
11. rand <- runif(1)
12. guesse
13. #可视化
14. image(x=shapevec,y=scalevec,
15. rface2D,levels=c(-30,-40,-80,-500),a
看起来更好!搜索算法可以很好地找到参数空间的高似然部分!
现在,让我们看一下“ shape”参数的链
- #############
- # 评估MCMC样本的“轨迹图” ...
- ##### Shape 参数
- plot(1:chain.length,guesses[,'sha
-
对于比例参数
- ###### 比例参数
- plot(1:chain.length,guesses[,'scale'],type="l
-
我们可以说这些链已经收敛于形状参数的后验分布吗?
首先,链的起点“记住”起始值,因此不是固定分布。我们需要删除链的第一部分。
- ############
- # 删除预烧期(允许MCMC有一段时间达到后验概率)
- burn.in <- 100
- MCMCsamples <- guesses[-c(1:burn.in),]
但这看起来还不是很好。让我们运行更长的时间,看看是否得到的东西看起来更像是随机数生成器(白噪声)
- ##########
- # 再试一次-运行更长的时间
- chain.length <- 20000
- oldguess <- startingv
- o2(oldguess,newguess)
- prob.accept <- mi
让我们首先删除前5000个样本作为预烧期
- #############
- # 使用更长的“预烧”
- burn.in <- 5000
- MCMCsamples <- guesses[-c(1:bur
现在,让我们再次看一下链条
在评估这些迹线图时,我们希望看到看起来像白噪声的“平稳分布”。该轨迹图看起来可能具有一些自相关。解决此问题的一种方法是稀疏MCMC样本:
- ##########
- # “稀疏” MCMC样本
- thinnedMCMC <- MCMCsamples[seq(1,chain.length,by=5),]
现在我们可以检查我们的后验分布!
- # 可视化后验分布
- plot(density(thinnedMCMC[,'scale'])
我们可以像以前一样可视化。
- #########
- # 更多后验概率检察
- par(mfrow=c(3,2))
- plot(thinnedMCMC,col=1:10000)
- plot(thinnedMCMC,type="l")
可以修改Metropolis-Hastings MCMC方法来拟合任意模型的任意数量的自由参数。但是,MH算法本身不一定是最有效和灵活的。在实验中,我们使用吉布斯采样,大多采用建模语言 BUGS 。
注意:BUGS实现(例如JAGS)实际上倾向于结合使用MH和Gibbs采样,MH和Gibbs采样器并不是唯一的MCMC例程。例如,“ stan”使用MH采样的一种改进形式,称为“ Hamiltonian Monte Carlo”。
吉布斯Gibbs采样器
Gibbs采样器非常简单有效。基本上,该算法从完整的条件 概率分布(即, 在模型中所有其他参数的已知值作为条件的条件下,对任意参数i的后验分布)中进行 连续采样 。
在很多情况下,我们不能直接制定出我们的模型后验分布,但我们 可以 分析出条件后验分布。尽管如此,即使它在分析上不易处理,我们也可以使用单变量MH程序作为最后方法。
问:为什么Gibbs采样器通常比纯MH采样器效率更高?
二元正态例子
MCMC采样器只是随机数生成器的一种。我们可以使用Gibbs采样器来开发自己的随机数生成器,以实现相当简单的已知分布。在此示例中,我们使用Gibbs采样器从标准双变量正态概率分布生成随机数。注意,吉布斯采样器在许多方面都比MH算法更简单明了。
- #############
- #Gibbs采样器的简单示例
- #############
- ########
- # 首先,回顾一下我们简单的双变量正态采样器
- rbvn<-function (n, rho){ #f函数用于从双变量标准正态分布中提取任意数量的独立样本。
- x <- rnorm(n, 0, 1)
- y <- rnorm(n, rho * x, sqrt(1 - rho^2))
1. #############
2. # 现在构造一个吉布斯采样器
3. gibbs<-function (n, rho){ # 双变量随机数生成器的gibbs采样器实现
4. mat <- matrix(ncol = 2, nrow = n) # 用于存储随机样本的矩阵
5. mat[1, ] <- c(x, y) # initialize the markov chain
6. for (i in 2:n) {
7. x <- rnorm(1, rho * y, sqrt(1 - rho^2)) # 以y为条件的x中的样本
8. y <- rnorm(1, rho * x, sqrt(1 - rho^2)) # 以x为条件的y中的样本
9. mat[i, ] <- c(x, y)
然后,我们可以使用Gibbs采样器从该已知分布中获取随机样本…
1. ##########
2. # 测试吉布斯采样器
3. plot(ts(bvn[,2]))
4. hist(bvn[,1],40)
5. hist(bvn[,2],40)
在这里,马尔可夫链的样本中有很多明显的自相关。Gibbs采样器经常有此问题。
示例
BUGS语言
最后,让我们为我们最喜欢的粘瘤病示例创建一个Gibbs采样器,为此,我们将使用BUGS语言(在JAGS中实现)来帮助我们!
JAGS,全称是Just another Gibbs sampler,是基于BUGS语言开发的利用MCMC来进行贝叶斯建模的软件包。它没有提供建模所用的GUI以及MCMC抽样的后处理,这些要在其它的程序软件上来处理,比如说利用R包(rjags)来调用JAGS并后处理MCMC的输出。JAGS相对于WinBUGS/OpenBUGS的主要优点在于平台的独立性,可以应用于各种操作系统,而WinBUGS/OpenBUGS只能应用于windows系统;JAGS也可以在64-bit平台上以64-bit应用来进行编译。
BUGS语言看起来与R类似,但是有几个主要区别:
- 首先,BUGS是一种编译语言,因此代码中的操作顺序并不重要
- BUGS不是矢量化的-您需要使用FOR循环
- 在BUGS中,几个概率分布的参数差异很大。值得注意的是,正态分布通过均值和精度(1 / Variance )进行参数化。
这是用BUGS语言实现的粘液病示例:
1. model {
2. #############
3. # 似然
4. ############
5. for(obs in 1:n.observations){
6. titer[obs] ~ dgamma(shape,rate
7. #############
8. # 先验
9. ############
10. rate <- 1/scale # 将BUGS的scale参数转换为“ rate”
11. }
我们可以使用R中的“ cat”函数将此模型写到您的工作目录中的文本文件中:
1. ###########
2. # BUGS建模语言中的粘液瘤示例
3. ##########
4. # 将BUGS模型写入文件
5. cat("
6. model {
7. #############
8. # 似然
9. ############
10. for(obs in 1:n.observations){
11. titer[obs] ~ dgamma(shape,rate)
12. #############
13. # 先验
14. ############
15. shape ~ dgamma(0.001,0.001
16. file="BUGSmodel.txt")
现在我们已经将BUGS模型打包为文本文件,我们将数据捆绑到一个列表对象中,该列表对象包含BUGS代码中引用的所有相关数据:
1. ############
2. # 将数据封装到单个“列表”对象中
3. myx.data <- list(
4. n.observations = length(Myx$titer
5. ## $titer
6. ## [1] 5.207 5.734 6.613 5.997 6.612 6.810 5.930 6.501 7.182 7.292 7.819
7. ## [12] 7.489 6.918 6.808 6.235 6.916 4.196 7.682 8.189 7.707 7.597 7.112
8. ## [23] 7.354 7.158 7.466 7.927 8.499
9. ##
10. ## $n.observations
11. ## [1] 27
然后,我们需要为所有参数定义初始值。将其定义为一个函数很方便,因此可以使用不同的起始值来初始化每个MCMC链。
1. ###########
2. # 用于为所有自由参数生成随机初始值的函数
3. init.vals.for.bugs()
4. ## $shape
5. ## [1] 51.80414
6. ##
7. ## $scale
8. ## [1] 0.2202549
init.vals.for.bugs()1. ## $shape
2. ## [1] 89.85618
3. ##
4. ## $scale
5. ## [1] 0.2678733
init.vals.for.bugs()1. ## $shape
2. ## [1] 69.22457
3. ##
4. ## $scale
5. ## [1] 0.1728908
现在我们可以调用JAGS!
1. ###########
2. # 运行 JAGS
3. ##########
## Loading required package: rjags1. ## The following object is masked from 'package:coda':
2. ##
3. ## traceplot
jags.fit <- run.jags(model="BUGSmodel.txt",
1. ## Compiling rjags model...
2. ## Calling the simulation using the rjags method...
3. ## Adapting the model for 100 iterations...
4. ## Running the model for 5000 iterations...
5. ## Simulation complete
6. ## Finished running the simulation
jags.fit) # 转换为“ MCMC”对象(CODA包)
1. ##
2. ## Iterations = 101:5100
3. ## Thinning interval = 1
4. ## Number of chains = 3
5. ## Sample size per chain = 5000
6. ##
7. ## 1. Empirical mean and standard deviation for each variable,
8. ## plus standard error of the mean:
9. ##
10. ## Mean SD Naive SE Time-series SE
11. ## shape 51.6013 14.36711 0.1173070 2.216657
12. ## scale 0.1454 0.04405 0.0003597 0.006722
13. ##
14. ## 2. Quantiles for each variable:
15. ##
16. ## 2.5% 25% 50% 75% 97.5%
17. ## shape 28.76333 40.9574 50.1722 60.2463 82.7532
18. ## scale 0.08346 0.1148 0.1378 0.1687 0.2389
评估收敛
第一步是视觉检查-我们寻找以下内容来评估收敛性:
- 当视为“轨迹图”时,每个参数的链应看起来像白噪声(模糊的毛毛虫)或类似的噪声
- 多个具有不同起始条件的链条看起来应该相同
我们可能在这里可以做得更好的一种方法是使链条运行更长的时间,并丢弃初始样本我们还可以。
通过细化链来尝试减少自相关,我们每20个样本中仅保留1个。
1. ################
2. #运行链更长时间
3. jags.fit <-
4. sample = 10000,n.chains = 3,adapt = 1000,burnin = 1000,
5. summarise = FALSE,thin=20,method = "parallel" )
6. ## Calling 3 simulations using the parallel method...
7. ## Following the progress of chain 1 (the program will wait for all
8. ## chains to finish before continuing):
9. ## Welcome to JAGS 4.2.0 on Wed Oct 23 11:33:35 2019
10. ## JAGS is free software and comes with ABSOLUTELY NO WARRANTY
11. ## Loading module: basemod: ok
12. ## Loading module: bugs: ok
13. ## . . Reading data file data.txt
14. ## . Compiling model graph
15. ## Resolving undeclared variables
16. ## Allocating nodes
17. ## Graph information:
18. ## Observed stochastic nodes: 27
19. ## Unobserved stochastic nodes: 2
20. ## Total graph size: 37
21. ## . Reading parameter file inits1.txt
22. ## . Initializing model
23. ## . Adapting 1000
24. ## -------------------------------------------------| 1000
25. ## ++++++++++++++++++++++++++++++++++++++++++++++++++ 100%
26. ## Adaptation successful
27. ## . Updating 1000
28. ## -------------------------------------------------| 1000
29. ## ************************************************** 100%
30. ## . . . Updating 200000
31. ## -------------------------------------------------| 200000
32. ## ************************************************** 100%
33. ## . . . . Updating 0
34. ## . Deleting model
35. ## .
36. ## All chains have finished
37. ## Simulation complete. Reading coda files...
38. ## Coda files loaded successfully
39. ## Finished running the simulation
1. jagsfit.mcmc <- as.mcmc.list
2. # 转换为“ MCMC”对象(CODA包)
3. ##
4. ## Iterations = 2001:201981
5. ## Thinning interval = 20
6. ## Number of chains = 3
7. ## Sample size per chain = 10000
8. ##
9. ## 1. Empirical mean and standard deviation for each variable,
10. ## plus standard error of the mean:
11. ##
12. ## Mean SD Naive SE Time-series SE
13. ## shape 47.1460 12.9801 0.0749404 0.292218
14. ## scale 0.1591 0.0482 0.0002783 0.001075
15. ##
16. ## 2. Quantiles for each variable:
17. ##
18. ## 2.5% 25% 50% 75% 97.5%
19. ## shape 25.14757 37.9988 45.9142 55.1436 75.5850
20. ## scale 0.09147 0.1256 0.1509 0.1825 0.2773
从视觉上看,这看起来更好。现在我们可以使用更多的定量收敛指标。
Gelman-Rubin诊断
一种简单而直观的收敛诊断程序是 Gelman-Rubin诊断程序,该诊断程序基于简单的蒙特卡洛误差来评估链之间是否比应有的链距更大:
1. ##############
2. # 运行收敛诊断
3. gelman(jagsfit.mcmc)
4. ## Potential scale reduction factors:
5. ##
6. ## Point est. Upper C.I.
7. ## shape 1 1.00
8. ## scale 1 1.01
9. ##
10. ## Multivariate psrf
11. ##
12. ## 1
通常,1.1或更高的值被认为收敛不佳。为模型中的所有可用参数计算GR诊断。如果测试失败,则应尝试运行更长的链!
所以这个模型看起来不错!
获取全文完整代码数据资料。
本文选自《R语言BUGS/JAGS贝叶斯分析: 马尔科夫链蒙特卡洛方法(MCMC)采样》。