Simulations of the Autocorrelated Bayesian Sampler

This vignette provides a brief introduction of the Autocorrelated Bayesian Sampler (ABS; Zhu et al. (2024)) and the R scripts for running simulations of ABS using samplr package.

ABS is a sequential sampling model that assumes individuals draw autocorrelated samples from their memory of hypotheses based on their posterior beliefs, which is called “posterior of hypotheses”. These samples are subsequently integrated to perform various tasks: ABS is capable of generating estimates, confidence intervals, and response times for estimation tasks, as well as choices, confidence judgments, and response times for two-alternative force choice (2AFC) tasks. Notably, ABS employs different stopping rules depending on the type of tasks. In this vignette, we will outline the process of simulating ABS under both stopping rules. In addition, although ABS assume a normal distribution for the posterior of hypotheses, our package allows users to use custom distributions, which will also illustrated in this vignette.

Fixed stopping rule

A normal distribution for posterior of hypothesis

The fixed stopping rule means that a fixed number of samples are drawn to complete the tasks such as estimations and confidence intervals. This rule applies to tasks such as estimation tasks.

Estimation tasks involve participants providing estimates, such as the number of stimuli (e.g., dots) on a screen or offering confidence intervals of that counts at a specified level.

In this vignette, we will begin by generating several random numbers to represent the stimuli counts. We will assume the estimation task consists of 10 trials, wherein participants are tasked with estimating the number of dots displayed on the screen in each trial.

require(samplr)
#> Loading required package: samplr
set.seed(123)

trial_stim <- sample(20:25, 10, replace=TRUE)
print(trial_stim)
#>  [1] 22 25 22 21 21 25 22 24 23 25

ABS employs the [R6][R6::R6Class] object-oriented programming (OOP) system. Thus we need to construct a new object before running simulations. In the initialising step, we need to provide the values of the following arguments:

In this section, we employ the normal posterior distribution, which requires specifying the values of distr_name and distr_params. The distr_name argument should be set to “norm” to indicate the normal distribution. The distr_params argument specifies the standard deviation of the normal distribution. This can be either a single numeric value, indicating a fixed standard deviation across all trials, or a numeric vector of the same length as the stimuli, specifying the standard deviation for each trial. It is important to note that ABS assumes the stimulus value is the mean of the normal distribution, thus there is no need to specify the mean separately.

abs_model <- Zhu23ABS$new(
  width = 1, n_chains = 3, nd_time = 0.3, 
  s_nd_time = 0.2, lambda = 10, distr_name = 'norm', distr_params = 1
)

The simulation process is conducted using the simulate function, which requires two arguments: stopping_rule and start_point. Based on the assumptions of ABS, individuals employ a fixed stopping rule for the estimation task, meaning a predetermined number of samples are drawn. Therefore, stopping_rule is set to 'fixed'.

The start_point argument is set to NA by default, indicating that the starting point of the first trial for each MC3 chain is randomly selected from the posterior of hypotheses, and the starting points of subsequent trials for each chain are set to the last sample of the same chain in the previous trial. Alternatively, users have the option to specify starting points for each trial, ensuring that the length of starting points matches the length of trial_stim. It is important to note that when specified, all chains share the same starting point in each trial, and it will break the dependency of samples between adjacent trials.

To demonstrate the usage of start_point, we will run the simulation twice: once with the default settings and again with specified starting points.

Additionally, when stopping_rule = 'fixed', two further arguments are required:

abs_model$simulate(stopping_rule = 'fixed', 
                   n_sample = 5, trial_stim = trial_stim)

The results of the simulate method save in the field sim_results. Users can get access to the results by running abs_model$sim_results. It is important to note that ABS assumes the response time for each trial follows an Erlang distribution, with the shape parameter equal to the length of the samples that is determined by n_sample and the rate parameter lambda has been specified above. The response time value provided in the table is a random number drawn from the Erlang distribution.

The table below presents the simulation results, displaying only the samples from the cold chain of the MC3 sampler in the samples column. These samples will be used for generating the responses of ABS. With the default setting of start_point = NA, the starting point of the first trial was randomly drawn from the normal distribution N(22, 1). From the second trial, the starting point was set to the last point of the previous trial. For instance, the starting point of the second trial was 23.19899 in this simulation. It is important to note that when start_point = NA, only the starting point of the first trial was included in the samples of ABS. The starting points of subsequent trials served merely as initializers for the sampler and were excluded from the ABS samples.

knitr::kable(abs_model$sim_results)
trial samples stimulus rt point_est
1 22.46092, 21.19585, 23.16227, 23.07685, 23.19899 22 0.6962331 23.19899
2 22.81852, 22.94063, 24.40037, 24.06716, 23.57613 25 0.5775903 23.57613
3 22.02688, 21.93149, 22.02688, 22.06456, 21.75045 22 0.8583808 21.75045
4 21.21486, 21.34514, 21.53813, 21.27593, 21.27593 21 0.8593033 21.27593
5 21.83892, 20.57877, 20.56013, 20.27643, 20.27643 21 0.8591766 20.27643
6 21.03049, 21.03049, 21.03049, 20.56761, 20.56761 25 0.6687111 20.56761
7 20.56761, 21.79455, 21.05602, 21.59945, 21.59945 22 0.7996729 21.59945
8 22.63067, 22.63067, 22.63067, 23.84692, 23.84692 24 0.8465396 23.84692
9 22.77392, 22.77392, 21.72141, 22.47818, 22.47818 23 1.026247 22.47818
10 22.47818, 22.47818, 22.47818, 24.14924, 24.35889 25 0.7949772 24.35889

In the upcoming simulations, we will run the simulation with specified start_point. To proceed, let us generate some starting points for the simulation.

start_point <- runif(length(trial_stim), 20, 25)
print(start_point)
#>  [1] 23.15387 22.60421 23.29811 23.64680 22.43411 21.92228 20.03417 20.01842
#>  [9] 24.97468 20.53943

It is worth noticing that before rerunning the simulation, users should either create a new object or reset the sim_results using the reset_sim_results method.

abs_model$reset_sim_results()
abs_model$simulate(stopping_rule = 'fixed', 
                   start_point = start_point, 
                   n_sample = 5, 
                   trial_stim = trial_stim)

The following table shows the simulation results with specified starting points. We notice that the first sample of each trial follows the order of start_point and is included in the samples of ABS.

knitr::kable(abs_model$sim_results)
trial samples stimulus rt point_est
1 23.15387, 22.94857, 23.32674, 22.86275, 22.84844 22 0.9391273 22.84844
2 22.60421, 23.98278, 23.16726, 23.83018, 24.05747 25 0.960213 24.05747
3 23.29811, 23.29811, 23.25266, 21.96929, 22.97895 22 0.8303294 22.97895
4 23.64680, 23.64680, 20.38653, 20.90503, 20.90503 21 0.843562 20.90503
5 22.43411, 22.43411, 23.02500, 22.82066, 22.82066 21 0.7319466 22.82066
6 21.92228, 22.28465, 22.17285, 23.14012, 24.37679 25 0.7435707 24.37679
7 20.03417, 20.16216, 20.61667, 20.93413, 21.34805 22 0.6252088 21.34805
8 20.01842, 20.01842, 19.95175, 19.91607, 21.29672 24 0.7063606 21.29672
9 24.97468, 23.03316, 22.99983, 22.99983, 22.99983 23 0.5919077 22.99983
10 20.53943, 20.53943, 22.07215, 22.38586, 22.43730 25 0.5465358 22.43730

In addition to performing point estimation, ABS can also simulate confidence interval estimation by the confidence_interval method.

abs_model$confidence_interval(0.5)

The following table shows the interval estimation on the level of 0.5. conf_interval_l and conf_interval_u represent the lower and the upper bounds.

knitr::kable(abs_model$sim_results)
trial samples stimulus rt point_est conf_interval_l conf_interval_u
1 23.15387, 22.94857, 23.32674, 22.86275, 22.84844 22 0.9391273 22.84844 22.86275 23.15387
2 22.60421, 23.98278, 23.16726, 23.83018, 24.05747 25 0.960213 24.05747 23.16726 23.98278
3 23.29811, 23.29811, 23.25266, 21.96929, 22.97895 22 0.8303294 22.97895 22.97895 23.29811
4 23.64680, 23.64680, 20.38653, 20.90503, 20.90503 21 0.843562 20.90503 20.90503 23.64680
5 22.43411, 22.43411, 23.02500, 22.82066, 22.82066 21 0.7319466 22.82066 22.43411 22.82066
6 21.92228, 22.28465, 22.17285, 23.14012, 24.37679 25 0.7435707 24.37679 22.17285 23.14012
7 20.03417, 20.16216, 20.61667, 20.93413, 21.34805 22 0.6252088 21.34805 20.16216 20.93413
8 20.01842, 20.01842, 19.95175, 19.91607, 21.29672 24 0.7063606 21.29672 19.95175 20.01842
9 24.97468, 23.03316, 22.99983, 22.99983, 22.99983 23 0.5919077 22.99983 22.99983 23.03316
10 20.53943, 20.53943, 22.07215, 22.38586, 22.43730 25 0.5465358 22.43730 20.53943 22.38586

An advantage of R6 is that it allows method chaining, which means that we can simulate the point and confidence interval estimation in one line of code.

abs_model$reset_sim_results()
abs_model$simulate(
  stopping_rule = 'fixed', 
  n_sample = 5, 
  trial_stim = trial_stim, 
  start_point=start_point)$confidence_interval(0.5)
knitr::kable(abs_model$sim_results)
trial samples stimulus rt point_est conf_interval_l conf_interval_u
1 23.15387, 23.15387, 21.32755, 21.17221, 22.25114 22 0.7928794 22.25114 21.32755 23.15387
2 22.60421, 23.58632, 24.49342, 25.26377, 25.25655 25 0.9453281 25.25655 23.58632 25.25655
3 23.29811, 23.29811, 23.01077, 21.48146, 22.46782 22 0.9608318 22.46782 22.46782 23.29811
4 23.64680, 23.64680, 23.64680, 23.86076, 22.98811 21 1.022691 22.98811 23.64680 23.64680
5 22.43411, 21.75967, 21.57249, 20.92830, 20.56626 21 0.9489973 20.56626 20.92830 21.75967
6 21.92228, 23.13268, 24.05023, 23.89194, 22.83065 25 1.087054 22.83065 22.83065 23.89194
7 20.03417, 21.18261, 22.23694, 20.48587, 20.77174 22 1.15924 20.77174 20.48587 21.18261
8 20.01842, 20.41099, 20.70444, 20.39197, 21.92217 24 0.5480482 21.92217 20.39197 20.70444
9 24.97468, 24.97468, 24.96424, 24.19963, 23.86320 23 0.6626406 23.86320 24.19963 24.97468
10 20.53943, 20.97900, 20.93043, 24.20136, 24.15534 25 0.9091687 24.15534 20.93043 24.15534

A custom posterior of hypotheses

In this section, we will illustrate how to employ custom distributions to ABS with a fixed stopping rule, using the same experimental scenario and the same stimuli. First, we specify our custom posterior function.

 custom_post_func <- function(x){
  if (x >= 19 & x < 22){
    return(0.3)
  } else if (x >= 22 & x < 24) {
    return(0.6)
  } else if (x >= 24 & x < 26) {
    return(0.1)
  } else {
    return(0)
  }
}

To employ a custom posterior of hypotheses, two special arguments are required in the initialisation step: custom_distr and custom_start. The custom_distr argument accepts a list of custom posterior functions, one for each trial, matching the length of the stimuli. The custom_start argument specifies the first starting point of the Zhu23ABS sampler, i.e., the initial sample of the entire simulation.

It is important to distinguish custom_start from start_point in the simulate() function. custom_start is only necessary when providing a custom posterior of hypotheses, whereas start_point can be used for both Gaussian and custom posteriors. custom_start initializes the Zhu23ABS sampler without breaking the dependency of samples between trials. In contrast, start_point sets the starting point for each trial, thus breaking the dependency between samples of adjacent trials. Additionally, if users provide a vector of start_point, a value for custom_start is still required as a placeholder and will be overwritten by start_point.

custom_func_list <- replicate(
  length(trial_stim), custom_post_func, simplify = FALSE
)
abs_model <- Zhu23ABS$new(
  width = 1, n_chains = 3, nd_time = 0.3, 
  s_nd_time = 0.2, lambda = 10, 
  custom_distr = custom_func_list, custom_start = 23
)
abs_model$simulate(
  stopping_rule = 'fixed', 
  n_sample = 5, 
  trial_stim = trial_stim
)

The following table shows the simulation results with a custom posterior. We notice that the first sample of the first trial equals to the value of custom_start.

knitr::kable(abs_model$sim_results)
trial samples stimulus rt point_est
1 23.00000, 21.65776, 24.54276, 23.28013, 21.58476 22 0.939751 21.58476
2 20.19480, 20.98478, 22.94399, 23.09041, 23.87754 25 0.7334943 23.87754
3 22.45328, 23.07457, 20.38108, 20.66667, 21.56507 22 1.064708 21.56507
4 25.33200, 19.58683, 19.06603, 20.86223, 20.86223 21 0.7530445 20.86223
5 25.98513, 23.84136, 24.07200, 19.73874, 20.51456 21 0.621633 20.51456
6 23.48351, 21.85166, 20.35451, 21.06815, 19.97855 25 0.5564545 19.97855
7 19.64951, 19.10088, 19.21834, 19.15238, 20.56728 22 1.020738 20.56728
8 19.99142, 20.41566, 20.41566, 19.60917, 22.16439 24 1.090832 22.16439
9 21.46472, 22.29925, 23.05005, 22.20548, 22.20548 23 0.6287007 22.20548
10 22.75063, 22.26817, 20.41036, 20.14076, 20.76399 25 0.6757852 20.76399

Relative stopping rule

A normal distribution for posterior of hypothesis

The relative stopping rule means that the model counts the difference in evidence between the two hypotheses, and terminates the sampling process whenever the accumulated difference exceeds a threshold. This rule applies to tasks such as 2AFC.

2AFC is a cognitive task that asks participants to make judgments between two alternatives. For instance, in the random dot motion (RDM) task, participants are presented with a screen where most dots move coherently in either the left or right direction, and they’re asked to perceive the correct direction.

ABS is able to describe and simulate this cognitive process. Similarly, we will begin by randomly generating 10 directions from the set c('left', 'right') to represent the stimuli in the RDM task.

trial_stim <- factor(sample(c('left', 'right'), 10, TRUE))

In 2AFC, ABS employs a sampling process and converts the samples into “evidence” supporting either the left or right responses. Specifically, if the sample falls below the decision boundary, it supports the first level in trial_stim, which in our example is “left”; otherwise, the sample will support the second level, which is “right”. According to the assumptions of ABS, it employs a ‘relative’ stopping rule: It counts the difference in evidence between the two responses, and terminates the sampling process whenever the accumulated difference exceeds a threshold.

To simulate the 2AFC of ABS, we need to initialize a new ABS model and then use the simulate method with stopping_rule = 'relative'. The posterior of hypotheses will be normal distribution. The following arguments are required:

To demonstrate the usage of these arguments, we will also run the simulation twice: once with the default settings and again with some of the settings modified.

abs_model2 <- Zhu23ABS$new(
  width=1, n_chains = 3, nd_time = 0.3, s_nd_time = 0.2, 
  lambda = 10, distr_name = 'norm', distr_params = 1
)
abs_model2$simulate(
  stopping_rule = 'relative', delta = 4, dec_bdry = 0, 
  discrim = 1, trial_stim = trial_stim
)

The table below presents the simulation results, including the simulated response, response time, and confidence. It is important to note that in the simulation of 2AFC, the length of the sample sequences may vary due to ABS utilizing a relative stopping rule. To illustrate its mechanism, let us examine the sequences in the first two trials as an example.

knitr::kable(abs_model2$sim_results)
trial samples response stimulus accuracy rt confidence
1 0.4275131, 0.1622293, 0.3256617, 0.2739435 right right 1 0.8842087 0.8333333
2 -0.9458666, -1.9165161, -1.0475625, -1.4366053, -1.4366053 left left 1 0.6003210 0.7500000
3 -0.9621143, -0.9621143, -1.0762124 left left 1 0.4214597 0.8333333
4 -0.2486073, -0.2486073, -0.2486073 left right 0 0.5587766 0.8333333
5 0.9018419, 1.6166903, 1.6817583 right right 1 0.7195611 0.8333333
6 1.6817583, 1.9277007, -0.7676286, 2.3873046, 0.9870808 right right 1 0.8551096 0.7500000
7 0.9870808, 0.9870808, 0.9870808 right right 1 0.5159783 0.8333333
8 -0.41717131, -0.70702914, 2.21601114, 1.39502444, 1.76113883, 1.76113883, -0.05608772, -1.00232885, -0.58802069, 0.52335972, 0.46143706, 0.52596694, 0.52596694 right right 1 1.4518855 0.6250000
9 0.2868992, -0.9103864, -0.9103864, -0.9103864, -0.7006005, -0.9896238, -0.9896238 left left 1 1.1940603 0.7000000
10 -1.5721525, 0.7261261, 0.4822092, 0.4822092, -1.7215719, -1.7215719, -0.3113327, -0.2768050, -0.2768050 left left 1 1.6029330 0.6666667

The prior on responses, set to c(1, 1), corresponds to an unbiased Beta distribution Beta(1, 1). Let us consider the first trial: the initial sample, 0.4275131, falls above the decision boundary of 0, supporting “right”. Consequently, the posterior on responses shifts to Beta(1, 2). With a relative difference of 1 between the amounts of evidence supporting both stimuli, which is lower than the relative stopping rule, the sampling process continues. Subsequent samples are analysed similarly: the posterior adjusts according to the samples until the relative difference meets the stopping rule. In this case, the last sample supporting “right” results in a posterior of Beta(1, 5), satisfying the stopping rule and prompting ABS to return a “right” response.

With the prior_depend=TRUE argument, the prior on responses for the second trial depends on the stimulus of the first trial. Given that “right” was the correct response in the first trial, the prior on responses for the second trial is Beta(1, 2). Since no starting points are provided, ABS began with 0.2739435, the last sample of the first trial, but this sample is excluded from the samples and from the calculation of the posterior on responses. The process then proceeds similarly to the first trial. In this instance, after eight samples in total, the posterior reaches Beta(6, 2), satisfying the stopping rule and resulting in a “left” response.

It is important to clarify two points. Firstly, the prior on responses is not accumulated when prior_depend=TRUE. In the example above, the prior on responses for the third trial is Beta(2, 1) rather than Beta(2, 2). Secondly, it’s crucial to distinguish the starting points of ABS from those in the drift diffusion model (DDM; Ratcliff (1978), Ratcliff & McKoon (2008)). The bias of the starting points in ABS is independent from the bias of the responses, which is captured by the prior on responses.

In the upcoming simulations, we will run the simulation again with two arguments changed: start_point and prior_depend. To proceed, let us generate some starting points for the simulation.

start_point <- runif(length(trial_stim), -3, 3)
print(start_point)
#>  [1]  2.49825567 -2.57474596  1.86057651 -0.08457317 -1.16859165  0.70724300
#>  [7] -1.08447977  0.91402088  2.49508984  0.41447419

Next, we will put these starting points into the ABS model and rerun the simulation. It is worth noting that in this simulation, the starting point of each trial precisely matches what we provided, and all starting points are included in the calculation of the posterior of responses. Additionally, it is important to observe that the prior on responses resets to Beta(1, 1) at the start of every trial.

abs_model2$reset_sim_results()
abs_model2$simulate(
  stopping_rule = 'relative', delta = 4, dec_bdry = 0, 
  discrim = 1, trial_stim = trial_stim, start_point = start_point, 
  prior_depend = FALSE
)
knitr::kable(abs_model2$sim_results)
trial samples response stimulus accuracy rt confidence
1 2.498256, 2.498256, 2.498256, 4.653175 right right 1 0.5254800 0.8333333
2 -2.574746, -1.481331, -1.887003, -2.847988 left left 1 0.5513093 0.8333333
3 1.8605765, 0.8811842, 0.6171134, 0.0614605 right left 0 0.7555300 0.8333333
4 -0.08457317, 0.25636442, 0.69083272, -0.97940277, 0.96035322, 1.21691271, 1.21691271, 0.59006379 right right 1 1.1302366 0.7000000
5 -1.1685916, -0.9746845, -1.2742347, -0.3545086 left right 0 0.9798226 0.8333333
6 0.7072430, 0.7072430, 0.6659027, 1.1382033 right right 1 0.5360159 0.8333333
7 -1.08447977, -0.29601230, 0.93947644, -0.06303025, -1.35547336, -1.35547336 left right 0 0.7949616 0.7500000
8 0.9140209, 0.9140209, 0.9140209, 0.9140209 right right 1 0.9697987 0.8333333
9 2.495090, 2.400942, 2.569325, 2.569325 right left 0 0.9800438 0.8333333
10 0.4144742, -0.4003186, 0.9795133, -0.7381408, -0.7385936, -0.7385936, 2.2072473, -2.5076334, -2.5076334, -2.5076334 left left 1 1.6487114 0.6666667

A custom posterior of hypotheses

In this section, we will illustrate how to employ custom distributions to ABS with a relative stopping rule, using the RDM task with the same stimuli. First, we specify two custom posterior functions for the “left” and “right” stimuli, and create a list of the custom posterior function according to the stimuli.

custom_post_left <- function(x){
  if (x >= -3 & x < -1){
    return(0.25 * x + 0.75)
  } else if (x >= -1 & x < 0) {
    return(-0.25 * x + 0.25)
  } else {
    return (0)
  }
}

custom_post_right <- function(x){
  if (x >= -1 & x < 1){
    return(0.25 * x + 0.25)
  } else if (x >= 1 & x < 3) {
    return(-0.25 * x + 0.75)
  } else {
    return (0)
  }
}

custom_func_list <- lapply(trial_stim, function(stim) ifelse(stim=='left', custom_post_left, custom_post_right))

Then, we initialise a Zhu23ABS object with a list of custom_func_list and a value of custom_start.

abs_model2 <- Zhu23ABS$new(
  width=1, n_chains = 3, nd_time = 0.3, s_nd_time = 0.2, 
  lambda = 10, custom_distr = custom_func_list, custom_start = -0.1
)
abs_model2$simulate(
  stopping_rule = 'relative', delta = 4, dec_bdry = 0, 
  discrim = 1, trial_stim = trial_stim
)

The following table shows the simulation results with a custom posterior. We notice that the first sample of the first trial equals to the value of custom_start.

knitr::kable(abs_model2$sim_results)
trial samples response stimulus accuracy rt confidence
1 -0.1000000, -0.1000000, 1.1016574, -0.4119108, 0.4092011, 0.1937227, 0.1727152, 0.7248535, 1.5414128, 1.4869701 right right 1 1.3962562 0.6666667
2 1.48697, 1.48697, 1.48697 right left 0 0.7224212 0.8333333
3 1.48697011, -0.06471386, -0.06471386, -0.06471386, -0.99778854 left left 1 0.8641009 0.7500000
4 -0.41446080, -0.03089549, 1.42896914, 1.42896914, 2.20108267, 1.42896914, 0.21296796, 1.49176136, -0.57370256, 0.88807227, 0.98585835 right right 1 1.6384660 0.6428571
5 1.6889577, 1.8963487, 0.5086753 right right 1 0.6274639 0.8333333
6 0.30462902, 1.41494998, 0.03257175 right right 1 0.4416011 0.8333333
7 -0.32957081, -0.16841672, 0.04310975, 0.48995349, 0.11556503, 0.16657204, 0.70833944 right right 1 1.4056088 0.7000000
8 0.4987224, 0.5140892, 0.8615744 right right 1 0.9689298 0.8333333
9 0.8615744, -0.5686874, -0.3232613, -1.6409327, -1.1384310, -1.1384310, -0.8294954 left left 1 1.0342848 0.7000000
10 -0.8531594, -1.2648573, -1.2744982 left left 1 1.1686782 0.8333333

References

Ratcliff, R. (1978). A theory of memory retrieval. Psychological Review, 85(2), 59–108. https://doi.org/10.1037/0033-295X.85.2.59
Ratcliff, R., & McKoon, G. (2008). The diffusion decision model: Theory and data for two-choice decision tasks. Neural Computation, 20(4), 873–922. https://doi.org/10.1162/neco.2008.12-06-420
Zhu, J.-Q., Sundh, J., Spicer, J., Chater, N., & Sanborn, A. N. (2024). The autocorrelated Bayesian sampler: A rational process for probability judgments, estimates, confidence intervals, choices, confidence judgments, and response times. Psychological Review, 131(2), 456–493. https://doi.org/10.1037/rev0000427