@@ -337,41 +337,21 @@ def _posterior_to_trace(self, chain=0) -> NDArray:
337
337
class IMH (SMC_KERNEL ):
338
338
"""Independent Metropolis-Hastings SMC kernel"""
339
339
340
- def __init__ (self , * args , n_steps = 25 , tune_steps = True , p_acc_rate = 0.85 , ** kwargs ):
340
+ def __init__ (self , * args , correlation_threshold = 0.01 , ** kwargs ):
341
341
"""
342
342
Parameters
343
343
----------
344
- n_steps: int
345
- The number of steps of each Markov Chain. If ``tune_steps == True`` ``n_steps`` will be used
346
- for the first stage and for the others it will be determined automatically based on the
347
- acceptance rate and `p_acc_rate`, the max number of steps is ``n_steps``.
348
- tune_steps: bool
349
- Whether to compute the number of steps automatically or not. Defaults to True
350
- p_acc_rate: float
351
- Used to compute ``n_steps`` when ``tune_steps == True``. The higher the value of
352
- ``p_acc_rate`` the higher the number of steps computed automatically. Defaults to 0.85.
353
- It should be between 0 and 1.
344
+ correlation_threshold: float
345
+ The lower the value the higher the number of IMH steps computed automatically.
346
+ Defaults to 0.01. It should be between 0 and 1.
354
347
"""
355
348
super ().__init__ (* args , ** kwargs )
356
- self .n_steps = n_steps
357
- self .tune_steps = tune_steps
358
- self .p_acc_rate = p_acc_rate
349
+ self .correlation_threshold = correlation_threshold
359
350
360
- self .max_steps = n_steps
361
- self .proposed = self .draws * self .n_steps
362
351
self .proposal_dist = None
363
352
self .acc_rate = None
364
353
365
354
def tune (self ):
366
- # Tune n_steps based on the acceptance rate (skip in first iteration)
367
- if self .tune_steps and self .iteration > 1 :
368
- acc_rate = max (1.0 / self .proposed , self .acc_rate )
369
- self .n_steps = min (
370
- self .max_steps ,
371
- max (2 , int (np .log (1 - self .p_acc_rate ) / np .log (1 - acc_rate ))),
372
- )
373
- self .proposed = self .draws * self .n_steps
374
-
375
355
# Update MVNormal proposal based on the mean and covariance of the
376
356
# tempered posterior.
377
357
cov = np .cov (self .tempered_posterior , ddof = 0 , rowvar = 0 )
@@ -384,34 +364,42 @@ def tune(self):
384
364
385
365
def mutate (self ):
386
366
"""Independent Metropolis-Hastings perturbation."""
387
- ac_ = np .empty ((self .n_steps , self .draws ))
388
- log_R = np .log (self .rng .random ((self .n_steps , self .draws )))
389
-
390
- # The proposal is independent from the current point.
391
- # We have to take that into account to compute the Metropolis-Hastings acceptance
392
- # We first compute the logp of proposing a transition to the current points.
393
- # This variable is updated at the end of the loop with the entries from the accepted
394
- # transitions, which is equivalent to recomputing it in every iteration of the loop.
395
- backward_logp = self .proposal_dist .logpdf (self .tempered_posterior )
396
- for n_step in range (self .n_steps ):
367
+ self .n_steps = 1
368
+ old_corr = 2
369
+ corr = Pearson (self .tempered_posterior )
370
+ ac_ = []
371
+ while True :
372
+ log_R = np .log (self .rng .random (self .draws ))
373
+ # The proposal is independent from the current point.
374
+ # We have to take that into account to compute the Metropolis-Hastings acceptance
375
+ # We first compute the logp of proposing a transition to the current points.
376
+ # This variable is updated at the end of the loop with the entries from the accepted
377
+ # transitions, which is equivalent to recomputing it in every iteration of the loop.
397
378
proposal = floatX (self .proposal_dist .rvs (size = self .draws , random_state = self .rng ))
398
379
proposal = proposal .reshape (len (proposal ), - 1 )
399
- # We then compute the logp of proposing a transition to the new points
380
+ # To do that we compute the logp of moving to a new point
400
381
forward_logp = self .proposal_dist .logpdf (proposal )
401
-
382
+ # And to going back from that new point
383
+ backward_logp = self .proposal_dist .logpdf (self .tempered_posterior )
402
384
ll = np .array ([self .likelihood_logp_func (prop ) for prop in proposal ])
403
385
pl = np .array ([self .prior_logp_func (prop ) for prop in proposal ])
404
386
proposal_logp = pl + ll * self .beta
405
- accepted = log_R [ n_step ] < (
387
+ accepted = log_R < (
406
388
(proposal_logp + backward_logp ) - (self .tempered_posterior_logp + forward_logp )
407
389
)
408
390
409
- ac_ [n_step ] = accepted
410
391
self .tempered_posterior [accepted ] = proposal [accepted ]
411
392
self .tempered_posterior_logp [accepted ] = proposal_logp [accepted ]
412
393
self .prior_logp [accepted ] = pl [accepted ]
413
394
self .likelihood_logp [accepted ] = ll [accepted ]
414
- backward_logp [accepted ] = forward_logp [accepted ]
395
+ ac_ .append (accepted )
396
+ self .n_steps += 1
397
+
398
+ pearson_r = corr .get (self .tempered_posterior )
399
+ if np .mean ((old_corr - pearson_r ) > self .correlation_threshold ) > 0.9 :
400
+ old_corr = pearson_r
401
+ else :
402
+ break
415
403
416
404
self .acc_rate = np .mean (ac_ )
417
405
@@ -429,36 +417,39 @@ def sample_settings(self):
429
417
stats .update (
430
418
{
431
419
"_n_tune" : self .n_steps , # Default property name used in `SamplerReport`
432
- "tune_steps" : self .tune_steps ,
433
- "p_acc_rate" : self .p_acc_rate ,
420
+ "correlation_threshold" : self .correlation_threshold ,
434
421
}
435
422
)
436
423
return stats
437
424
438
425
426
+ class Pearson :
427
+ def __init__ (self , a ):
428
+ self .l = a .shape [0 ]
429
+ self .am = a - np .sum (a , axis = 0 ) / self .l
430
+ self .aa = np .sum (self .am ** 2 , axis = 0 ) ** 0.5
431
+
432
+ def get (self , b ):
433
+ bm = b - np .sum (b , axis = 0 ) / self .l
434
+ bb = np .sum (bm ** 2 , axis = 0 ) ** 0.5
435
+ ab = np .sum (self .am * bm , axis = 0 )
436
+ return np .abs (ab / (self .aa * bb ))
437
+
438
+
439
439
class MH (SMC_KERNEL ):
440
440
"""Metropolis-Hastings SMC kernel"""
441
441
442
- def __init__ (self , * args , n_steps = 25 , tune_steps = True , p_acc_rate = 0.85 , ** kwargs ):
442
+ def __init__ (self , * args , correlation_threshold = 0.01 , ** kwargs ):
443
443
"""
444
444
Parameters
445
445
----------
446
- n_steps: int
447
- The number of steps of each Markov Chain.
448
- tune_steps: bool
449
- Whether to compute the number of steps automatically or not. Defaults to True
450
- p_acc_rate: float
451
- Used to compute ``n_steps`` when ``tune_steps == True``. The higher the value of
452
- ``p_acc_rate`` the higher the number of steps computed automatically. Defaults to 0.85.
453
- It should be between 0 and 1.
446
+ correlation_threshold: float
447
+ The lower the value the higher the number of MH steps computed automatically.
448
+ Defaults to 0.01. It should be between 0 and 1.
454
449
"""
455
450
super ().__init__ (* args , ** kwargs )
456
- self .n_steps = n_steps
457
- self .tune_steps = tune_steps
458
- self .p_acc_rate = p_acc_rate
451
+ self .correlation_threshold = correlation_threshold
459
452
460
- self .max_steps = n_steps
461
- self .proposed = self .draws * self .n_steps
462
453
self .proposal_dist = None
463
454
self .proposal_scales = None
464
455
self .chain_acc_rate = None
@@ -484,14 +475,6 @@ def tune(self):
484
475
# Interpolate between individual and population scales
485
476
self .proposal_scales = 0.5 * (chain_scales + chain_scales .mean ())
486
477
487
- if self .tune_steps :
488
- acc_rate = max (1.0 / self .proposed , self .chain_acc_rate .mean ())
489
- self .n_steps = min (
490
- self .max_steps ,
491
- max (2 , int (np .log (1 - self .p_acc_rate ) / np .log (1 - acc_rate ))),
492
- )
493
- self .proposed = self .draws * self .n_steps
494
-
495
478
# Update MVNormal proposal based on the covariance of the tempered posterior.
496
479
cov = np .cov (self .tempered_posterior , ddof = 0 , rowvar = 0 )
497
480
cov = np .atleast_2d (cov )
@@ -502,9 +485,12 @@ def tune(self):
502
485
503
486
def mutate (self ):
504
487
"""Metropolis-Hastings perturbation."""
505
- ac_ = np .empty ((self .n_steps , self .draws ))
506
- log_R = np .log (self .rng .random ((self .n_steps , self .draws )))
507
- for n_step in range (self .n_steps ):
488
+ self .n_steps = 1
489
+ old_corr = 2
490
+ corr = Pearson (self .tempered_posterior )
491
+ ac_ = []
492
+ while True :
493
+ log_R = np .log (self .rng .random (self .draws ))
508
494
proposal = floatX (
509
495
self .tempered_posterior
510
496
+ self .proposal_dist (num_draws = self .draws , rng = self .rng )
@@ -514,13 +500,20 @@ def mutate(self):
514
500
pl = np .array ([self .prior_logp_func (prop ) for prop in proposal ])
515
501
516
502
proposal_logp = pl + ll * self .beta
517
- accepted = log_R [ n_step ] < (proposal_logp - self .tempered_posterior_logp )
503
+ accepted = log_R < (proposal_logp - self .tempered_posterior_logp )
518
504
519
- ac_ [n_step ] = accepted
520
505
self .tempered_posterior [accepted ] = proposal [accepted ]
521
506
self .prior_logp [accepted ] = pl [accepted ]
522
507
self .likelihood_logp [accepted ] = ll [accepted ]
523
508
self .tempered_posterior_logp [accepted ] = proposal_logp [accepted ]
509
+ ac_ .append (accepted )
510
+ self .n_steps += 1
511
+
512
+ pearson_r = corr .get (self .tempered_posterior )
513
+ if np .mean ((old_corr - pearson_r ) > self .correlation_threshold ) > 0.9 :
514
+ old_corr = pearson_r
515
+ else :
516
+ break
524
517
525
518
self .chain_acc_rate = np .mean (ac_ , axis = 0 )
526
519
@@ -539,8 +532,7 @@ def sample_settings(self):
539
532
stats .update (
540
533
{
541
534
"_n_tune" : self .n_steps , # Default property name used in `SamplerReport`
542
- "tune_steps" : self .tune_steps ,
543
- "p_acc_rate" : self .p_acc_rate ,
535
+ "correlation_threshold" : self .correlation_threshold ,
544
536
}
545
537
)
546
538
return stats
0 commit comments