-
Notifications
You must be signed in to change notification settings - Fork 6
/
16-bayes-mcmc.Rmd
1837 lines (1460 loc) · 70.3 KB
/
16-bayes-mcmc.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Métodos de Cadenas de Markov Monte Carlo
```{r setup, include=FALSE, message=FALSE}
library(tidyverse)
library(patchwork)
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning=FALSE, fig.align = 'center', fig.width = 5, fig.height=3, cache = TRUE)
comma <- function(x) format(x, digits = 2, big.mark = ",")
theme_set(theme_minimal())
```
Hasta ahora, hemos considerado modelos bayesianos *conjugados*, donde la
posterior tiene una forma conocida. Esto nos permitió simular directamente
de la posterior usando las rutinas estándar de `R`, o utilizar cálculos teóricos
o funciones estándar de `R` para calcular resúmenes de interés, como medias o
medianas posteriores o intervalos de credibilidad.
Sin embargo, en aplicaciones rara vez es factible este tipo de análisis tan
simple, pues:
1. Los modelos que estamos considerando son más complejos y la distribución posterior
conjunta de los parámetros no tiene una forma simple conocida.
2. Queremos usar distribuciones iniciales que no son conjugadas para utilizar correctamente
nuestra información inicial.
Recordamos que tenemos expresiones explícitas para la inicial $p(\theta)$ y la verosimilitud
$p(x|\theta)$, así que conocemos explícitamente la posterior, módulo la constante de
normalización,
$$p(\theta|x) \propto p(x|\theta) \, p(\theta).$$
Supongamos por ejemplo que quisiéramos calcular las medias posteriores de los
parámetros $\theta$. En teoría, tendríamos que calcular
$$\hat \theta = \mathbb{E}[{\theta}\, |\, x] = \int \theta \, p(\theta|x) \, d\theta$$
Entonces es necesario calcular también $p(x)$, que resulta de la integral
$$p(x) = \int p(x|\theta) \, p(\theta)\, d\theta$$
Si no tenemos expresiones analíticas simples, tendremos que aproximar numéricamente
estas integrales de alguna forma.
1. Si la posterior tiene una forma conocida, podemos calcular cantidades de interés usando
fórmulas o rutinas de simulación de distribuciones conocidas que producen muestras independientes.
Cuando la posterior no tiene una forma conocida, sin embargo:
2. Podemos intentar usar integración numérica usual. Como veremos, este enfoque no es muy escalable.
3. Podemos usar simulaciones bajo cadenas de Markov (**Markov Chain Monte Carlo**, MCMC),
que es un enfoque más escalable.
Mucho del uso generalizado actual de la estadística bayesiana se debe a que gracias al
poder de cómputo disponible y los métodos MCMC, no estamos restringidos al uso de 1 y 2,
que tienen desventajas grandes. Primero mostraremos cómo el método de integración
por subdivisión no es escalable.
## Integrales mediante subdivisiones {-}
Como tenemos una expresión analítica para el integrando, podemos intentar una
rutina numérica de integración. Una vez calculada, podríamos entonces
usar otra rutina numérica para calcular las medias posteriores $\hat{\theta}$.
Las rutinas usuales de integración pueden sernos útiles cuando el número de parámetros
es chico. Consideremos primero el caso de 1 dimensión, y supongamos que $a\leq\theta\leq b$.
Si dividimos el rango de $\theta$ en intervalos determinados
por $a = \theta^1<\theta^2<\cdots \theta^M =b$, tales que $\Delta\theta = \theta^{i+1} -\theta^{i}$,
podríamos aproximar con
$$p(x) \approx \sum_{i=1}^M p(x|\theta^i)p(\theta^i) \Delta\theta$$
Lo que requiere $M$ evaluaciones del factor $p(x|\theta)p(\theta)$. Podríamos usar
por ejemplo $M=100$ para tener precisión razonable.
### Ejemplo: estimación de una proporción {-}
Teníamos que $p(S_n = k|\theta) \propto \theta^k(1-\theta)^{n-k}$ cuando observamos $k$ éxitos
en $n$ pruebas independientes. Supongamos que nuestra inicial es $p(\theta) = 2\theta$
(checa que es una densidad), es decir, creemos que es más probable a priori
observar proporciones altas. Podemos integrar numéricamente
```{r}
crear_log_post <- function(n, k){
function(theta){
verosim <- k * log(theta) + (n - k) * log(1 - theta)
inicial <- log(theta)
log_p_factor <- verosim + inicial
log_p_factor
}
}
# observamos 3 éxitos en 4 pruebas:
log_post <- crear_log_post(4, 3)
prob_post <- function(x) { exp(log_post(x))}
# integramos numéricamente
p_x <- integrate(prob_post, lower = 0, upper = 1, subdivisions = 100L)
p_x
```
Y ahora podemos calcular la media posterior:
```{r}
media_funcion <- function(theta){
theta * prob_post(theta) / p_x$value
}
integral_media <- integrate(media_funcion, lower = 0, upper = 1, subdivisions = 100L)
media_post <- integral_media$value
media_post
```
Podemos verificar nuestro trabajo pues sabemos que la posterior es $\mathsf{Beta}(5, 2)$
cuya media es
```{r}
5/(2+5)
```
Y podríamos intentar una estrategia similar, por ejemplo, para calcular intervalos
de credibilidad. Sin embargo, veremos abajo que este método no escala con el número de
parámetros.
### Más de un parámetro {-}
Ahora supongamos que tenemos $2$ parámetros. Dividiríamos cada parámetro
en 100 intervalos, y luego tendríamos que calcular
$$p(x) \approx \sum_{i=1}^M \sum_{j=1}^M p(x|\theta_1^i, \theta_2^j)p(\theta_1^i, \theta_2^j) \Delta\theta_1\Delta\theta_2$$
Y esto requeriría $M^2 = 10,000$ evaluaciones de $p(x|\theta)p(\theta)$.
Si tenemos $p$ parámetros, entonces tendríamos que hacer $M^p$ evaluaciones de la
posterior. Incluso cuando $p=10$, **esta estrategia es infactible**, pues tendríamos
que hacer más de millones de millones de millones de evaluaciones de la posterior. Si sólo
tenemos esta técnica disponible, el análisis bayesiano está considerablemente
restringido. Regresión bayesiana con unas 10 covariables por ejemplo, no podría hacerse.
De modo que tenemos que replantearnos cómo atacar el problema de calcular o aproximar
estas integrales.
## Métodos Monte Carlo {-}
En varias ocasiones anteriormente hemos usado el método Monte Carlo para
aproximar integrales: por ejemplo, para calcular medias posteriores.
Supongamos que tenemos una densidad $p(\theta)$.
```{block2, type='mathblock'}
**Integración Monte Carlo**. Supongamos que queremos calcular el valor esperado de
$g(X)$, donde $X\sim p(X\,|\,\theta).$ Es decir, la variable aleatoria $X$ se
distribuye de acuerdo al modelo probabilistico $p(X \, | \, \theta),$ de tal forma que
lo que nos interesa calcular es
$$\mathbb{E}[g(X)] = \int g(x) p(x|\theta)\, dx.$$
Si tomamos una muestra
$x^{(1)},x^{(2)}, \ldots x^{(N)} \overset{iid}{\sim} p(x|\theta)$, entonces
$$\mathbb{E}[g(X)] \approx \, \frac1N \, \sum_{n = 1}^N g(x^{(n)})$$
cuando $N$ es grande.
```
Esto es simplemente una manera de escribir la ley de los grandes números, y hemos
aplicado este teorema en varias ocasiones. Nos ha sido útil cuando
**sabemos cómo simular de distribución** $p(\theta | x)$ (usando alguna rutina de `R`, por
ejemplo, o usando un método estándar como inversión de la función de distribución acumulada).
### Ejemplo {-}
En este ejemplo repetimos cosas que ya hemos visto. En el caso de estimación
de una proporción $\theta$, tenemos como inicial
$p(\theta) \propto \theta$, que es $\mathsf{Beta}(2,1)$. Si observamos 3 éxitos en 4 pruebas,
entonces sabemos que la posterior es $p(\theta|x)\propto \theta^4(1-\theta)$, que
es $\mathsf{Beta}(5, 2)$. Si queremos calcular media y segundo momento posterior para $\theta$,
en teoría necesitamos calcular
$$\mu = \int_0^1 \theta p(\theta|X = 3)\, d\theta,\,\, \mu_2=\int_0^1 \theta^2 p(\theta|X = 3)\, d\theta$$
integramos con Monte Carlo
```{r}
theta <- rbeta(10000, 5, 2)
media_post <- mean(theta)
momento_2_post <- mean(theta^2)
c(media_post, momento_2_post)
```
Y podemos aproximar de esta manera cualquier cantidad de interés que esté basada
en integrales, como probabilidades asociadas a $\theta$ o cuantiles asociados.
Por ejemplo, podemos aproximar fácilmente $P(e^{\theta}> 2|x)$ haciendo
```{r}
mean(exp(theta) > 2)
```
y así sucesivamente.
Este enfoque, sin embargo, es mucho más flexible y poderoso.
### Ejemplo: varias pruebas independientes {-}
Supongamos que probamos el nivel de gusto para 4 sabores distintos de una paleta. Usamos
4 muestras de aproximadamente
50 personas diferentes para cada sabor, y cada uno evalúa si le gustó mucho o no.
Obtenemos los siguientes resultados:
```{r}
datos <- tibble(
sabor = c("fresa", "limón", "mango", "guanábana"),
n = c(50, 45, 51, 50), gusto = c(36, 35, 42, 29)) %>%
mutate(prop_gust = gusto / n)
datos
```
Usaremos como inicial $\mathsf{Beta}(2, 1)$ (pues hemos obervado cierto sesgo de
cortesía en la calificación de sabores, y no es tan probable tener valores muy
bajos) para todos los sabores, es decir $p(\theta_i)$ es la funcion de densidad
de una $\mathsf{Beta}(2, 1)$. La inicial conjunta la definimos entonces, usando
idependiencia inicial, como
$$p(\theta_1,\theta_2, \theta_3,\theta_4) = p(\theta_1)p(\theta_2)p(\theta_3)p(\theta_4).$$
Pues inicialmente establecemos que ningún parámetro da información sobre otro:
saber que mango es muy gustado no nos dice nada acerca del gusto por fresa. Bajo
este supuesto, y el supuesto adicional de que las muestras de cada sabor son
independientes, podemos mostrar que las posteriores son independientes:
$$p(\theta_1,\theta_2,\theta_3, \theta_4|k_1,k_2,k_3,k_4) = p(\theta_4|k_1)p(\theta_4|k_2)p(\theta_4|k_3)p(\theta_4|k_4)$$
De forma que podemos trabajar individualmente con cada muestra. Calculamos los parámetros de las posteriores individuales:
```{r}
datos <- datos %>%
mutate(a_post = gusto + 2, b_post = n - gusto + 1)
datos
```
Ahora nos preguntamos, ¿cuál es la probabilidad posterior de que mango sea el sabor
más preferido de la población? Conocemos la posterior para cada parámetro, y sabemos
que los parámetros son independientes para la posterior. Eso quiere decir
que podemos simular de cada parámetro independientemente para obtener simulaciones
de la conjunta posterior.
```{r}
simular_conjunta <- function(rep, datos){
datos %>% mutate(valor_sim = map2_dbl(a_post, b_post, ~ rbeta(1, .x, .y))) %>%
select(sabor, valor_sim)
}
simular_conjunta(1, datos)
```
```{r}
# esta no es una manera muy rápida, podríamos calcular todas las
# simulaciones de cada parámetro de manera vectorizada
sims_posterior <- tibble(rep = 1:5000) %>%
mutate(sims = map(rep, ~ simular_conjunta(.x, datos))) %>%
unnest(cols = sims)
sims_posterior
```
Y ahora podemos aproximar fácilmente la probabilidad de interés:
```{r}
sims_posterior %>%
group_by(rep) %>%
mutate(sabor = sabor[which.max(valor_sim)]) %>%
group_by(sabor) %>%
count() %>%
ungroup() %>%
mutate(prop = n / sum(n))
```
Y vemos que los mejores sabores son mango y limón. La probabilidad posterior de
que mango sea el sabor preferido por la población es de 66%. La integral correspondiente
no es trivial.
```{block2, type='ejercicio'}
- ¿Cuáles son las probabilidades a priori de que cada sabor sea el preferido
por la población?
- ¿Cuál es la integral correspondiente a las probabilidades que acabamos de calcular?
¿Qué tan fácil es hacer esta integral de manera analítica?
- Calcula la probabilidad de que mango sea preferido a limón?
- ¿Qué conclusión práctica sacas de estos resultados?
```
```{r, echo = FALSE}
# esta no es una manera muy rápida, podríamos calcular todas las
# simulaciones de cada parámetro de manera vectorizada
sims <- tibble(rep = 1:3000) %>%
mutate(sims_posterior = map(rep, ~ simular_conjunta(.x, datos)),
sims_prior = map(rep, ~ rbeta(4, 2, 1))) %>%
unnest(cols = c(sims_posterior, sims_prior))
# ¿Cuáles son las probabilidades a priori de que cada sabor sea el preferido por la población?
sims %>%
group_by(rep) %>%
mutate(sabor = sabor[which.max(sims_prior)]) %>%
group_by(sabor) %>%
count(sabor) %>%
ungroup() %>%
mutate(prop = n / sum(n))
# Calcula la probabilidad de que mango sea preferido a limón?
sims %>%
filter(sabor %in% c("mango", "limón")) %>%
group_by(rep) %>%
mutate(sabor = sabor[which.max(sims_prior)]) %>%
group_by(sabor) %>%
count(sabor) %>%
ungroup() %>%
mutate(prop = n / sum(n))
```
## Simulando de la posterior {-}
Hemos establecido que podemos contestar varias preguntas de inferencia
usando simulación Monte Carlo, y que este enfoque es potencialmente
escalable (en contraste con métodos de integración numérica por cuadrícula). Ahora
el problema que necesitamos resolver es el siguiente:
- Conocemos $p(\theta |x)$ módulo una constante de integración.
- En general, $p(\theta|x)$ no tiene una forma reconocible que corresponda a un
simulador estándar.
- ¿Cómo simulamos de esta posterior cuando sólo sabemos calcular $p(x|\theta)p(\theta)$?
Hay varias maneras de hacer esto. Presentaremos los algoritmos en términos
de una distribución cualquiera $p(\theta) = K f(\theta)$, donde sólo conocemos
la función $f(\theta)$.
## Método de Metrópolis {-}
Veremos un ejemplo relativemente simple que nos puede ayudar
a ganar un poco de intuición acerca de este algoritmo.
Supongamos que un vendedor de *Yakult* trabaja a lo largo de una cadena de
islas:
* Constantemente viaja entre las islas ofreciendo sus productos;
* Al final de un día de trabajo decide si permanece en la misma isla o se
transporta a una de las $2$ islas vecinas;
* El vendedor ignora la distribución de la población en las islas y el número
total de islas; sin embargo, una vez que se encuentra en una isla puede
investigar la población de la misma y también de la isla a la que se propone
viajar después.
* El objetivo del vendedor es visitar las islas de manera proporcional a la
población de cada una. Con esto en mente el vendedor utiliza el siguiente
proceso:
1) Lanza un volado, si el resultado es águila se propone ir a la isla
del lado izquierdo de su ubicación actual y si es sol a la del lado derecho.
2) Si la isla propuesta en el paso anterior tiene población mayor a la
población de la isla actual, el vendedor decide viajar a ella. Si la isla vecina
tiene población menor, entonces visita la isla propuesta con una probabilidad que
depende de la población de las islas. Sea $P^*$ la población de la isla
propuesta y $P_{t}$ la población de la isla actual. Entonces el vendedor
cambia de isla con probabilidad
$$q_{mover}=P^*/P_{t}$$
A la larga, si el vendedor sigue la heurística anterior la probabilidad de que
el vendedor este en alguna de las islas coincide con la población relativa de
la isla.
```{r, fig.height=6, fig.width=3.5}
islas <- tibble(islas = 1:10, pob = 1:10)
camina_isla <- function(i){ # i: isla actual
u <- runif(1) # volado
v <- ifelse(u < 0.5, i - 1, i + 1) # isla vecina (índice)
if (v < 1 | v > 10) { # si estas en los extremos y el volado indica salir
return(i)
}
u2 <- runif(1)
p_move = min(islas$pob[v] / islas$pob[i], 1)
if (p_move > u2) {
return(v) # isla destino
}
else {
return(i) # me quedo en la misma isla
}
}
pasos <- 100000
iteraciones <- numeric(pasos)
iteraciones[1] <- sample(1:10, 1) # isla inicial
for (j in 2:pasos) {
iteraciones[j] <- camina_isla(iteraciones[j - 1])
}
caminata <- tibble(pasos = 1:pasos, isla = iteraciones)
plot_caminata <- ggplot(caminata[1:1000, ], aes(x = pasos, y = isla)) +
geom_point(size = 0.8) +
geom_path(alpha = 0.5) +
coord_flip() +
labs(title = "Caminata aleatoria") +
scale_y_continuous(expression(theta), breaks = 1:10) +
scale_x_continuous("Tiempo")
plot_dist <- ggplot(caminata, aes(x = isla)) +
geom_histogram() +
scale_x_continuous(expression(theta), breaks = 1:10) +
labs(title = "Distribución objetivo",
y = expression(P(theta)))
plot_caminata / plot_dist
```
Notemos que para aproximar la distribución objetivo debemos permitir que el vendedor
recorra las islas durante una sucesión larga de pasos y registramos sus visitas. Y nuestra aproximación de la distribución es justamente el registro de sus
visitas.
¿Cómo se relaciona el ejemplo con la distribución posterior? Las distribución
de la cuál simularemos será justamente la posterior. Ahora pasaremos a un ejemplo donde
buscamos simular de la distribución posterior del parámetro $\theta$.
Al igual que en el ejemplo del vendedor, comenzamos
con un valor inicial de los parámetros $\theta^{(0)}$ en el soporte de $p(\theta)$,
es decir $p(\theta^{(0)})>0.$
Para $i=1, \ldots, M$, hacemos:
1. Partiendo de $\theta^{(i)}$, hacemos un salto
corto en una dirección al azar para obtener una propuesta $\theta^* \sim q(\theta \, |\, \theta^{(i)}).$
2. Aceptamos or rechazamos el salto:
- Si $\alpha = \frac{f(\theta^*)}{f(\theta^{(i)})} \geq 1$, aceptamos el salto
y ponemos $\theta^{(i+1)}=\theta^*$. Regresamos a 1 para la siguiente
iteración $i\leftarrow i + 1.$
- Si $\alpha = \frac{f(\theta^*)}{f(\theta^{(i)})} < 1$, entonces aceptamos
con probabilidad $\alpha$ el salto, ponemos $\theta^{(i+1)}=\theta^*$ y
regresamos a 1 para la siguiente iteración $i \leftarrow i + 1$. Si rechazamos
el salto, ponemos entonces $\theta^{(i+1)}=\theta^{(i)}$ y regresamos a 1 para
la siguiente iteración $i\leftarrow i + 1.$
Requerimos también que la función que propone los saltos sea simétrica: es
decir, $q(\theta^*|\theta^{(i)})$ debe ser igual a $q(\theta^{(i)}|\theta^*)$.
Se puede modificar el algoritmo para tratar con una propuesta que no sea
simétrica.
Una elección común es escoger $q(\theta^* |\theta^{(i)})$ como
una $\mathsf{N}(\theta^{(i)}, \sigma_{salto})$.
```{block,type="comentario"}
En este curso, escribiremos varios métodos de cadenas de Markov para estimación
Monte Carlo (*Markov Chain Monte Carlo*, MCMC) desde cero para entender los
básicos de cómo funciona. Sin embargo, **en la práctica no hacemos esto**, sino
que usamos software estándar (Stan, JAGS, BUGS, etc.) para hacer este trabajo.
**Expertos** en MCMC, métodos numéricos, y estadística a veces escriben partes
de sus rutinas de simulación, y pueden lograr mejoras de desempeño
considerables. Excepto para modelos simples, esto no es trivial de hacer
garantizando resultados correctos.
```
En resumen, todo el código de esta sección es de carácter ilustrativo.
Utiliza implementaciones establecidas en las aplicaciones.
Abajo implementamos el algoritmo con un salto de tipo normal:
```{r}
crear_metropolis <- function(fun_log, sigma_salto = 0.1){
# la entrada es la log posterior
iterar_metropolis <- function(theta_inicial, n){
p <- length(theta_inicial)
nombres <- names(theta_inicial)
iteraciones <- matrix(0, nrow = n, ncol = p)
colnames(iteraciones) <- nombres
iteraciones[1,] <- theta_inicial
for(i in 2:n){
theta <- iteraciones[i - 1, ]
theta_prop <- theta + rnorm(p, 0, sigma_salto)
# exp(log(p) - log(q)) = p/q
cociente <- exp(fun_log(theta_prop) - fun_log(theta))
if(cociente >= 1 || runif(1,0,1) < cociente){
iteraciones[i, ] <- theta_prop
} else {
iteraciones[i, ] <- theta
}
}
iteraciones_tbl <- iteraciones %>%
as_tibble() %>%
mutate(iter_num = row_number()) %>%
select(iter_num, everything())
iteraciones_tbl
}
iterar_metropolis
}
```
E intentamos simular de una exponencial no normalizada:
```{r}
exp_no_norm <- function(x) {
z <- ifelse(x > 0, exp(-0.5 * x), 0)
log(z)
}
iterador_metro <- crear_metropolis(exp_no_norm, sigma_salto = 0.25)
sims_tbl <- iterador_metro(c(theta = 0.5), 50000)
ggplot(sims_tbl, aes(x = theta)) + geom_histogram()
```
Ahora probemos con una $\mathsf{Beta}(3, 2):$
```{r}
beta_no_norm <- function(x) {
z <- ifelse(x > 0 && x < 1, (x^2)*(1-x), 0)
log(z)
}
iterador_metro <- crear_metropolis(beta_no_norm, sigma_salto = 0.04)
sims_metro_tbl <- iterador_metro(c(theta = 0.5), 50000)
sims_indep_tbl <- tibble(iter_num = 1:30000, theta = rbeta(30000, 3, 2))
g_1 <- ggplot(sims_metro_tbl, aes(x = theta)) + geom_histogram() +
labs(subtitle = "Metrópolis")
g_2 <- ggplot(sims_indep_tbl, aes(x = theta)) +
geom_histogram() +
labs(subtitle = "rbeta")
g_1 + g_2
```
Y vemos que esto funciona. Revisa el ejemplo de las islas en @Kruschke (7.2) para
tener más intuición de cómo funciona este algoritmo.
Nótese sin embargo un aspecto de estas simulaciones que no habíamos encontrado
en el curso. Aunque la distribución final de las simulaciones es muy cercana
a la de la distribución que queremos simular, lo cual era nuestro propósito,
las **simulaciones no son extracciones independientes** de esa distribución.
La construcción del algoritmo muestra eso, pero podemos también graficar las
simulaciones:
```{r, fig.width = 8}
g_metropolis <- sims_metro_tbl %>%
filter(iter_num < 500) %>%
ggplot(aes(x = iter_num, y = theta)) +
geom_line() + labs(subtitle = "Metrópolis")
g_indep <- sims_indep_tbl %>%
filter(iter_num < 500) %>%
ggplot(aes(x = iter_num, y = theta)) +
geom_line() + labs(subtitle = "Independientes")
g_metropolis + g_indep
```
Donde vemos claramente que las simulaciones de metropolis están autocorrelacionadas:
la siguiente simulación depende de la anterior. Esto define una cadena de Markov.
En cualquiera de los dos casos, como vimos en los histogramas de arriba,
las simulaciones "visitan" cada parte [0,1] de manera proporcional a la densidad,
de manera que podemos usar ambos tipos de simulaciones para aproximar la integral
o cantidad que nos interesa. Por ejemplo, la media posterior es:
```{r}
media_1 <- sims_metro_tbl %>% summarise(media_post = mean(theta)) %>% pull(media_post)
media_2 <- sims_indep_tbl %>% summarise(media_post = mean(theta)) %>% pull(media_post)
media_exacta <- 3/(3 + 2)
tibble(metodo = c("sim Metrópolis", "sim Independiente", "exacto"),
media_post = c(media_1, media_2, media_exacta))
```
```{block, type='mathblock'}
Supongamos que queremos simular de una distribución $p(\theta)$,
pero sólo conocemos $p(\theta)$ módulo una constante. Bajo ciertas condiciones de regularidad:
El **algoritmo Metrópolis** para la distribución $p(\theta)$ define una **cadena
de Markov** cuya distribución a largo plazo es $p(\theta)$. Esto implica que si
$\theta^{(1)},\theta^{(2)}, \ldots, \theta^{(M)}$ es una simulación de esta
cadena, y $M$ es suficientemente grande
1. La distribución de las $\theta^{(i)}$ es aproximadamente $p(\theta)$,
2. Tenemos que
$$ \frac1M \sum_{m = 1}^M h(\theta^{(m)}) \to \int h(\theta)p(\theta)\, d\theta$$
cuando $M\to \infty$
```
**Observaciones**:
1. Aunque hay distintas *condiciones de regularidad* que pueden funcionar,
generalmente el supuesto es que la cadena de Markov construída es
[ergódica](https://en.wikipedia.org/wiki/Ergodic_theory), y hay varias
condiciones que garantizan esta propiedad. Una condición simple, por ejemplo, es
que el soporte de la distribución $p(\theta)$ es un conjunto conexo del espacio
de parámetros.
2. Más crucialmente, este resultado no dice qué tan grande debe ser $M$ para que
la aproximación sea buena. Esto depende de cómo es $p(\theta)$, y de la
distribución que se utiliza para obtener los saltos propuestos. Dependiendo de
estos dos factores, la convergencia puede ser rápida (exponencial) o tan lenta
que es infactible usarla. Más adelante veremos diagnósticos para descartar los
peores casos de falta de convergencia.
## Ajustando el tamaño de salto {-}
En el algoritmo Metrópolis, generalmente es importante escoger la
dispersión de la distribución que genera propuestas con cuidado.
- Si la dispersión de la propuesta es demasiado grande, tenderemos a rechazar mucho,
y la convergencia será lenta.
- Si la dispersión de la propuesta es demasiado chica, tardaremos mucho tiempo
en explorar las distintas partes de la distribución objetivo.
### Ejemplo {-}
Supongamos que queremos simular usando metróplis de una distribución
$\textsf{Gamma}(20, 100)$. Abajo vemos la forma de esta distribución:
```{r}
sim_indep <- tibble(theta = rgamma(10000, 20, 100))
ggplot(sim_indep, aes(x = theta)) + geom_histogram()
```
```{r}
# logaritmo de densidad no normalizada
log_f_dist <- function(x) 210 + dgamma(x, 20, 100, log = TRUE)
# iterar
iterador_metro_chico <- crear_metropolis(log_f_dist, sigma_salto = 0.001)
sims_chico_tbl <- iterador_metro_chico(c(theta = 0.02), 50000)
g_sim <- ggplot(sims_chico_tbl %>% filter(iter_num < 3000), aes(x = iter_num, y = theta)) + geom_line() + ylim(c(0, 0.5))
dist_bplot <- ggplot(tibble(x = rgamma(10000, 20, 100)), aes(y = x, x = "a")) + geom_violin() + ylab("") + ylim(0, 0.5)
g_sim + dist_bplot + plot_layout(widths = c(5, 1))
```
Nótese que después de 5 mil iteraciones estamos muy lejos de tener una muestra
que se aproxime a la distribución objetivo. Empezamos en un lugar bajo, y la
cadena sólo ha ido lentamente hacia las zonas de alta densidad. *Cualquier
resumen con esta cadena estaría fuertemente sesgado* al valor donde iniciamos la
iteración. Decimos que la cadena todavía no *mezcla* en las primeras 5 mil
iteraciones.
Ahora vemos qué pasa si ponemos el tamaño de salto demasiado grande:
```{r}
set.seed(831)
iterador_metro_grande <- crear_metropolis(log_f_dist, sigma_salto = 20)
sims_grande_tbl <- iterador_metro_grande(c(theta = 0.02), 50000)
g_sim <- ggplot(sims_grande_tbl %>% filter(iter_num < 3000), aes(x = iter_num, y = theta)) + geom_line() + ylim(c(0, 0.5))
g_sim + dist_bplot + plot_layout(widths = c(5, 1))
```
En este caso, la cadena se *atora* muchas veces, pues las propuestas tienen
probabilidad muy baja, y tendemos a tener una tasa de rechazos muy alta. Esto
quiere decir que la información que tenemos acerca de la posterior es
relativamente poca, pues muchos datos son repeticiones del mismo valor.
*Cualquier resumen con esta cadena podría estar muy lejos del verdadero valor,*
pues su varianza es alta - otra corrida se "atoraría" en otros valores
distintos.
Nótese que cualquiera de estas cadenas, si la corremos suficientemente tiempo,
nos daría resultados buenos. Sin embargo, el número de simulaciones puede ser
infactible.
Un valor intermedio nos dará mucho mejores resultados:
```{r}
set.seed(831)
iterador_metro_apropiada <- crear_metropolis(log_f_dist, sigma_salto = 0.1)
sims_tbl <-iterador_metro_apropiada(c(theta = 0.02), 50000)
g_sim <- ggplot(sims_tbl %>% filter(iter_num < 3000),
aes(x = iter_num, y = theta)) + geom_line() + ylim(c(0, 0.5))
g_sim + dist_bplot + plot_layout(widths = c(5, 1))
```
Donde vemos que esta cadena parece mezclar bien (está explorando la totalidad
de la distribución objetivo), y también parece estar en un estado estable.
Comparemos cómo saldría por ejemplo la media posterior aproximada según
los tres métodos:
```{r}
estimaciones_media <- map_dfr(
list(sims_chico_tbl, sims_grande_tbl, sims_tbl),
~ filter(.x, iter_num < 3000) %>%
summarise(media = mean(theta))) %>%
mutate(tipo = c("salto chico", "salto grande", "salto apropiado"))
estimaciones_media %>% bind_rows(tibble(tipo = "exacta", media = 20/100)) %>%
select(tipo, media)
```
Veamos otra corrida:
```{r}
set.seed(6222131)
sims_chica_tbl <- iterador_metro_chico(c(theta = 0.02), 5000)
sims_grande_tbl <- iterador_metro_grande(c(theta = 0.02), 5000)
estimaciones_media <- map_dfr(
list(sims_chica_tbl, sims_grande_tbl, sims_tbl),
~ filter(.x, iter_num < 3000) %>%
summarise(media = mean(theta))) %>%
mutate(tipo = c("salto chico", "salto grande", "salto apropiado"))
estimaciones_media %>% bind_rows(tibble(tipo = "exacta", media = 20/100)) %>%
select(tipo, media)
```
```{block, type='ejercicio'}
Repite este proceso varias veces. Verifica que:
- Si el tamaño de paso es muy chico, las estimaciones de la media tienen sesgo alto.
- Si el tamaño de paso es muy grande, las estimaciones tienen varianza alta.
- Si el tamaño de paso es adecuado, obtenemos buena precisión en la estimación de la media posterior.
- Explica estos tres casos en términos de la convergencia de las realizaciones
de la cadena de Markov. Explica cómo afecta a cada caso el valor inicial de las
simulaciones de Metrópolis.
- Repite para otra estadística, como la desviación estándar o el rangon intercuartil.
```
## ¿Por qué funciona Metrópolis? {-}
Retomemos el ejemplo del vendedor de *Yakult*,
```{r, fig.height=6, fig.width=3.5}
islas <- tibble(islas = 1:10, pob = 1:10)
camina_isla <- function(i){ # i: isla actual
u <- runif(1) # volado
v <- ifelse(u < 0.5, i - 1, i + 1) # isla vecina (índice)
if (v < 1 | v > 10) { # si estas en los extremos y el volado indica salir
return(i)
}
u2 <- runif(1)
p_move = min(islas$pob[v] / islas$pob[i], 1)
if (p_move > u2) {
return(v) # isla destino
}
else {
return(i) # me quedo en la misma isla
}
}
pasos <- 100000
iteraciones <- numeric(pasos)
iteraciones[1] <- sample(1:10, 1) # isla inicial
for (j in 2:pasos) {
iteraciones[j] <- camina_isla(iteraciones[j - 1])
}
caminata <- tibble(pasos = 1:pasos, isla = iteraciones)
plot_caminata <- ggplot(caminata[1:1000, ], aes(x = pasos, y = isla)) +
geom_point(size = 0.8) +
geom_path(alpha = 0.5) +
coord_flip() +
labs(title = "Caminata aleatoria") +
scale_y_continuous(expression(theta), breaks = 1:10) +
scale_x_continuous("Tiempo")
plot_dist <- ggplot(caminata, aes(x = isla)) +
geom_histogram() +
scale_x_continuous(expression(theta), breaks = 1:10) +
labs(title = "Distribución objetivo",
y = expression(P(theta)))
plot_caminata / plot_dist
```
Entonces:
* Para aproximar la distribución objetivo debemos permitir que el vendedor
recorra las islas durante una sucesión larga de pasos y registramos sus visitas.
* Nuestra aproximación de la distribución es justamente el registro de sus
visitas.
* Más aún, debemos tener cuidado y excluir la porción de las visitas que se
encuentran bajo la influencia de la posición inicial. Esto es, debemos excluir
el **periodo de calentamiento**.
* Una vez que tenemos un registro _largo_ de los viajes del vendedor (excluyendo
el calentamiento) podemos aproximar la distribución objetivo
simplemente contando el número relativo de veces que el vendedor visitó
dicha isla.
```{r, warning=FALSE, message=FALSE, fig.width=8, fig.height=7.5}
t <- c(1:10, 20, 50, 100, 200, 1000, 5000)
plots_list <- map(t, function(i){
ggplot(caminata[1:i, ], aes(x = isla)) +
geom_histogram() +
labs(y = "", x = "", title = paste("t = ", i, sep = "")) +
scale_x_continuous(expression(theta), breaks = 1:10, limits = c(0, 11))
})
wrap_plots(plots_list)
```
Escribamos el algoritmo, para esto indexamos las islas por el valor
$\theta$, es así que la isla del extremo oeste corresponde a $\theta=1$ y la
población relativa de cada isla es $P(\theta)$:
1. El vendedor se ubica en $\theta^{(i)}$ y propone moverse a la izquierda
o derecha con probabilidad $0.5$.
El rango de los posibles valores para moverse, y la probabilidad de proponer
cada uno se conoce como **distribución propuesta**, en nuestro ejemplo sólo
toma dos valores cada uno con probabilidad $0.5$.
2. Una vez que se propone un movimiento, decidimos si aceptarlo. La decisión de
aceptar se basa en el valor de la distribución **objetivo** en la posición
propuesta, relativo al valor de la distribución objetivo en la posición actual:
$$\alpha=\min\bigg\{\frac{P(\theta^*)}{P(\theta^{(i)})},1\bigg\},$$
donde $\alpha$ denota la probabilidad de hacer el cambio de isla.
Notemos que la distribución objetivo $P(\theta)$ no necesita estar normalizada,
esto es porque lo que nos interesa es el cociente $P(\theta^*)/P(\theta^{(i)})$.
3. Una vez que propusimos un movimiento y calculamos la probabilidad de aceptar
el movimiento aceptamos o rechazamos el movimiento generando un valor de una
distribución uniforme, si dicho valor es menor a la probabilidad de cambio,
$\alpha,$ entonces hacemos el movimiento.
Entonces, para utilizar el algoritmo necesitamos ser capaces de:
* Generar un valor de la distribución propuesta, que hemos denotado por $q,$
(para crear $\theta^*$).
* Evaluar la distribución objetivo en cualquier valor propuesto (para calcular
$P(\theta^*)/P(\theta^{(i)})$).
* Generar un valor uniforme (para movernos con probabilidad $\alpha$).
Las $3$ puntos anteriores nos permiten generar muestras aleatorias de la
distribución objetivo, sin importar si esta está normalizada. Esta técnica es
particularmente útil cuando cuando la distribución objetivo es una posterior
proporcional a $p(x|\theta)p(\theta)$.
Para entender porque funciona el algoritmo de Metrópolis hace falta entender $2$
puntos, primero que la distribución objetivo es **estable**: si la probabilidad
_actual_ de ubicarse en una posición coincide con la probabilidad en la
distribución objetivo, entonces el algoritmo preserva las probabilidades.
```{r, warning=FALSE, message=FALSE, fig.width=8, fig.height=7.5}
library(expm)
transMat <- function(P){ # recibe vector de probabilidades (o población)
T <- matrix(0, 10, 10)
n <- length(P - 1) # número de estados
for (j in 2:n - 1) { # llenamos por fila
T[j, j - 1] <- 0.5 * min(P[j - 1] / P[j], 1)
T[j, j] <- 0.5 * (1 - min(P[j - 1] / P[j], 1)) +
0.5 * (1 - min(P[j + 1] / P[j], 1))
T[j, j + 1] <- 0.5 * min(P[j + 1] / P[j], 1)
}
# faltan los casos j = 1 y j = n
T[1, 1] <- 0.5 + 0.5 * (1 - min(P[2] / P[1], 1))
T[1, 2] <- 0.5 * min(P[2] / P[1], 1)
T[n, n] <- 0.5 + 0.5 * (1 - min(P[n - 1] / P[n], 1))
T[n, n - 1] <- 0.5 * min(P[n - 1] / P[n], 1)
T
}
T <- transMat(islas$pob)
w <- c(0, 1, rep(0, 8))
t <- c(1:10, 20, 50, 100, 200, 1000, 5000)
expT <- map_df(t, ~data.frame(t = ., w %*% (T %^% .)))
expT_long <- expT %>%
gather(theta, P, -t) %>%
mutate(theta = parse_number(theta))
ggplot(expT_long, aes(x = theta, y = P)) +
geom_bar(stat = "identity", fill = "darkgray") +
facet_wrap(~ t) +
scale_x_continuous(expression(theta), breaks = 1:10, limits = c(0, 11))
```
El segundo punto es que el proceso converge a la distribución objetivo.
Podemos ver, (en nuestro ejemplo sencillo) que sin importar el punto de inicio
se alcanza la distribución objetivo.
```{r, warning=FALSE, message=FALSE, fig.width=8, fig.height=7.5}
inicio_p <- function(i){
w <- rep(0, 10)
w[i] <- 1
t <- c(1, 10, 50, 100)
exp_t <- map_df(t, ~ data.frame(t = .x, inicio = i, w %*% (T %^% .))) %>%
gather(theta, P, -t, -inicio) %>%
mutate(theta = parse_number(theta))
exp_t
}
exp_t <- map_df(c(1, 3, 5, 9), inicio_p)
ggplot(exp_t, aes(x = as.numeric(theta), y = P)) +
geom_bar(stat = "identity", fill = "darkgray") +
facet_grid(inicio ~ t) +
scale_x_continuous(expression(theta), breaks = 1:10, limits = c(0, 11))
```
## Metrópolis con varios parámetros {-}
Ahora aplicaremos el algoritmo Metrópolis cuando tenemos varios parámetros. La idea
es la misma, pero nuestra distribución de salto debe ser multivariada. Una selección
usual es usando saltos normales independientes para cada parámetro, es decir, la
normal multivariada con matriz de varianza y covarianza diagonal.
### Ejemplo: el modelo normal {-}
Veremos cómo simular con Metrópolis para el problema de los cantantes.
Sabemos como calcular la posterior:
```{r}
crear_log_posterior_norm <- function(x = datos, m_0, n_0, a, b){
# calcula log_posterior
log_posterior <- function(mu, sigma){
log_verosim <- sum(dnorm(x, mu, sigma, log = TRUE))
tau <- 1 / sigma^2
log_inicial <-
dgamma(tau, a, b, log = TRUE) +
dnorm(mu, mu_0, sigma/sqrt(n_0), log = TRUE)
log_p <- log_verosim + log_inicial
log_p
}
log_posterior
}
```
```{r}
# parametros de inicial y datos
a <- 3
b <- 140
mu_0 <- 175
n_0 <- 5
set.seed(3413)
cantantes <- lattice::singer %>%
mutate(estatura_cm = round(2.54 * height)) %>%
filter(str_detect(voice.part, "Tenor")) %>%
sample_n(20)
```
Vemos cómo se ven las primeras iteraciones de nuestra cadena de Markov:
```{r}
log_p <- crear_log_posterior_norm(cantantes$estatura_cm, mu_0, n_0, a, b)
log_post <- function(pars) { log_p(pars[1], pars[2]) }
set.seed(823)
metro_normal <- crear_metropolis(log_post, sigma_salto = 0.5)
sim_tbl <- metro_normal(c(mu = 172, sigma = 3), 50000)
ggplot(sim_tbl %>% filter(iter_num < 100),
aes(x = mu, y = sigma)) +
geom_path() +
geom_point()
```
Y ahora vemos todas las simulaciones:
```{r}
g_normal <- ggplot(sim_tbl, aes(x = mu, y = sigma)) +
geom_point(alpha = 0.05)+ coord_equal() + ylim(c(0, 14))
g_normal
```
Y las medias posteriores son:
```{r}
sim_tbl %>% summarise(across(is_double, mean))
```
### Ejemplo: observaciones normales, no conjugado {-}
Arriba repetimos el análisis conjugado usando Metrópolis. Aunque
ya no es necesario usar el modelo conjugado, y podemos poner
iniciales que sean más intuitivas y acorde con nuestro conocimiento
existente.
Por ejemplo, podemos poner $p(\mu, \sigma) = p(\mu)p(\sigma)$, donde la densidad
de $\mu \sim \mathsf{N}(175, 2)$ y $\sigma \sim \mathsf{U}[2, 20].$ Igual que
antes, la verosimilitud $p(x|\mu, \sigma)$ es normal con media $\mu$ y
desviación estándar $\sigma.$
Escribimos la posterior:
```{r}
crear_log_posterior <- function(x, m_0, sigma_0, inf, sup){
# calcula log_posterior
log_posterior <- function(mu, sigma){
log_verosim <- sum(dnorm(x, mu, sigma, log = TRUE))
log_inicial <-
dunif(sigma, inf, sup, log = TRUE) +
dnorm(mu, mu_0, sigma_0, log = TRUE)
log_p <- log_verosim + log_inicial
log_p
}
log_posterior
}
```
```{r}
log_p <- crear_log_posterior(cantantes$estatura_cm, 175, 3, 2, 20)
log_post <- function(pars) { log_p(pars[1], pars[2]) }
```
```{r, fig.width = 7}
set.seed(8231)
metro_normal <- crear_metropolis(log_post, sigma_salto = 0.5)
sim_tbl <- metro_normal(c(mu = 172, sigma = 5), 50000)
g_normal_2 <- ggplot(sim_tbl, aes(x = mu, y = sigma)) +
geom_point(alpha = 0.05) + coord_equal() + ylim(c(0, 14))
g_normal + g_normal_2
```
Los resultados son similares, pero en
nuestras estimaciones bajo el segundo modelo, la $\sigma$ está
concentrada en valores un poco más bajos que el modelo normal-gamma inversa.
Las medias posteriores son:
```{r}
sim_tbl %>% summarise(across(is.numeric, mean))
```
Nótese que la inicial para el modelo normal-gamma inversa pone muy poca
probabilidad para valores bajos de $\sigma$, mientras que el segundo
modelo hay un 10% de probabilidad de que la $\sigma$ sea menor que 4.
```{r}
tau <- rgamma(5000, 3, 150)
sigma <- 1/sqrt(tau)
quantile(sigma, c(0.01,0.1, 0.9, 0.99))
quantile(runif(5000, 2, 25), c(0.01,0.1, 0.9, 0.99))
```
### Ejemplo: exámenes {-}
Recordamos un ejemplo que vimos en la sección de máxima verosimilitud.