braintools.analysis.population.decode#
- braintools.analysis.population.decode(x, y, n_classes=None, n_folds=5, model='lda', reg=0.001, key=None)#
Cross-validated linear decoding of integer labels from population activity.
The
Tsamples are split inton_foldscontiguous folds of equal sizem = T // n_folds; the trailingT mod n_foldssamples are dropped so that every fold has the same static shape. For each fold a linear classifier is fit in closed form on the othern_folds - 1folds and its accuracy is measured on the held-out fold. The folds are processed in parallel withjax.vmap.Two classifiers are available:
model='lda': shrinkage linear discriminant analysis [1] [2]. The pooled within-class covariance \(S\) (class covariances withddof=0, weighted by the class priors) is shrunk towards a scaled identity, \(\Sigma = (1 - \text{reg})\,S + \text{reg}\, \frac{\operatorname{tr} S}{D} I\), and the class scores are \(x^\top \Sigma^{-1}\mu_k - \tfrac12 \mu_k^\top\Sigma^{-1}\mu_k + \log \pi_k\) with empirical priors \(\pi_k\).model='ridge': one-vs-rest ridge regression [3] on+1/-1targets with an unpenalised intercept, \(W = (X_c^\top X_c + \text{reg}\,I)^{-1} X_c^\top Y_c\); the predicted class has the largest score.
- Parameters:
x (array_like or brainunit.Quantity) – Activity of shape
(T, D)(samples by features); a 1-D array is treated as(T, 1). Quantities are decoded on their mantissa (the accuracy is dimensionless; for'ridge'the penaltyregis expressed in the squared units of that mantissa).y (array_like of int) – Labels of shape
(T,)with values in[0, n_classes).n_classes (
int|None) – Number of classes (static).Nonemeansint(y.max()) + 1, which needs a concretey: underjax.jit/jax.vmapit must be given explicitly.n_folds (
int) – Number of folds (static,>= 2).model (
str) – Classifier.reg (float, default=1e-3) – Regularisation. For
'lda'it is the shrinkage intensity in[0, 1]; for'ridge'it is the ridge penaltyalpha >= 0.key (
Array|None) – PRNG key. If given, the samples are randomly permuted before folding (and before the trailing samples are dropped). IfNonethe folds are contiguous blocks in the given order.
- Returns:
Per-fold test accuracy, shape
(n_folds,).- Return type:
Array- Raises:
ValueError – If the shapes are inconsistent,
T < n_folds,modelis unknown, a concreteregis out of range, orn_classesisNonewhileyis traced.TypeError – If
yis not an integer array or a static argument is not an int.
See also
mutual_informationModel-free dependence between labels and responses.
pcaReduce dimensionality before decoding.
Notes
Correspondence with scikit-learn. On identical training/test splits the per-fold predictions equal those of
sklearn.discriminant_analysis.LinearDiscriminantAnalysis( solver='lsqr', shrinkage=reg)formodel='lda': sklearn’s fixed shrinkage appliesshrunk_covariance(empirical_covariance(X_k), reg)to every class and sums them weighted by the priors, which by linearity is exactly \(\Sigma\) above;sklearn.linear_model.RidgeClassifier(alpha=reg)formodel='ridge'(for two classes sklearn fits a single+1/-1column, whose sign decision is identical to the arg-max over the two complementary one-vs-rest columns used here).
Absent classes. A class that does not occur in a training fold cannot be predicted for that fold (its score is
-inf), as in scikit-learn, whose fittedclasses_only contain the training labels; nonanis produced. Labels outside[0, n_classes)are never predicted and always count as errors.The function is jittable and vmappable (with explicit
n_classes) but not differentiable (the prediction is an arg-max).References
Examples
>>> import jax >>> import jax.numpy as jnp >>> from braintools.analysis import population >>> k1, k2 = jax.random.split(jax.random.PRNGKey(0)) >>> y = jnp.arange(103) % 3 # 3 classes >>> x = jax.random.normal(k1, (103, 8)) + 3.0 * jax.nn.one_hot(y, 8) >>> acc = population.decode(x, y, n_folds=5) # 3 trailing samples dropped >>> acc.shape (5,) >>> bool(acc.mean() > 0.9) True >>> >>> # Under jit, pass the number of classes explicitly. >>> f = jax.jit(lambda x, y, k: population.decode(x, y, n_classes=3, ... model='ridge', key=k)) >>> bool(f(x, y, k2).mean() > 0.9) True