Parameters and priors#

The parameters of a model are described with Parameters. It holds two kinds of things:

  • free parameters, which are estimated from the data. Each is a Value with a starting value, lower and upper bounds, and a prior distribution.

  • everything else the model needs to start, such as the initial values of the stimuli. These are plain numbers or arrays, without a prior.

import numpy as np

from cpm.generators import Parameters, Value

parameters = Parameters(
    alpha=Value(
        value=0.5,
        lower=1e-10,
        upper=1,
        prior="truncated_normal",
        args={"mean": 0.5, "sd": 0.25},
    ),
    temperature=Value(
        value=1,
        lower=0,
        upper=10,
        prior="truncated_normal",
        args={"mean": 5, "sd": 2.5},
    ),
    values=np.array([0.25, 0.25, 0.25, 0.25]),
)

A parameter is free if, and only if, it has a prior. free() lists the free parameters, and bounds() their bounds, which the optimisers use:

parameters.free()
['alpha', 'temperature']
parameters.bounds()
(array([1.e-10, 0.e+00]), array([ 1, 10]))

Inside a model function, parameters behave like numbers:

parameters.alpha * 2
1.0

Priors#

prior is the name of a distribution, and args its arguments. The distributions are those of scipy.stats:

prior

args

"uniform"

none: uniform between lower and upper

"truncated_normal"

mean, sd

"norm"

mean, sd

"beta"

a, b, and mean and sd as its location and scale

"gamma"

a, and mean and sd as its location and scale

"truncated_exponential"

mean and sd as its location and scale, truncated at upper

You can also pass a scipy.stats distribution itself, such as scipy.stats.multivariate_normal: cpm calls it with args as keyword arguments.

The priors are used in three places:

  • when fitting with prior=True, the optimisers maximise the posterior instead of the likelihood,

  • sample() draws parameters from their priors, for example to simulate data,

  • hierarchical methods (cpm.hierarchical) update the priors to the estimated group distribution.

parameters.sample(size=3)
[{'alpha': np.float64(0.31009860467551054),
  'temperature': np.float64(8.333882539594498)},
 {'alpha': np.float64(0.6145645170274359),
  'temperature': np.float64(3.1935234940021315)},
 {'alpha': np.float64(0.603999424362693),
  'temperature': np.float64(6.788947830551595)}]

PDF() gives the joint prior density of the current values, or its logarithm with log=True:

parameters.PDF(log=True)
np.float64(-2.5547376125562042)

A density can be larger than 1: unlike a probability, it is only bounded by the requirement that it integrates to 1 over the whole range of the parameter.

Updating priors#

update_prior() changes the arguments of the priors, one keyword argument per parameter:

parameters.update_prior(alpha={"mean": 0.3, "sd": 0.1})
parameters.alpha.prior.mean()
np.float64(0.3004437839046317)

Parameters on a transformed scale#

Some optimisers work better on an unbounded scale. LogParameters stores the parameters on a logit scale within their bounds, and transforms them back for the model.

Next: Models.