BrainX Ecosystem
Home
Platform
Full-Scale Simulation AI Compatibility Differentiable Optimization Powered by JAX
Ecosystem
Research
Papers about BrainX Papers using BrainX Papers citing BrainX
Documentation
brainpy (classic) ↗ brainpy.state braincell brainmass
brainunit brainevent braintrace braintools
brainstate
BrainX skill
Skip to main content
Ctrl+K
braintools - Home braintools - Home

Tutorials

  • Neural Data Analysis
    • Firing Rates and Population Synchrony
    • Spike-Train Distances and Synchrony
    • Local Field Potential Analysis
    • The analysis toolbox: spikes, spectra and populations
  • Cognitive Tasks
    • Tutorial 1: Quickstart with braintools.cogtask
    • Tutorial 2: Building custom tasks
    • Tutorial 3: Variable-length trial sequences
  • Synaptic Connectivity
    • Tutorial 1: Basic Connectivity Patterns
    • Tutorial 2: Spatial Connectivity
    • Tutorial 3: Network Topologies
    • Tutorial 4: Advanced Connectivity Patterns
    • Tutorial 5: Composing Connectors and Multi-Compartment Connectivity
    • Tutorial 6: Population Networks, Graph Measures, and Scale
  • File Processing
    • Save and Load Model States with msgpack Checkpointing
    • Read and Write MATLAB Files with load_matfile and save_matfile
  • Model Fitting
    • Fitting models to data
  • Initialization
    • Basic Distributions
    • Variance Scaling and Orthogonal Initialization
    • Distance Profiles
    • Composite and Distance-Modulated Initialization
  • Input Current
    • Introduction to braintools.input
    • The Functional API
    • The Composable API
    • Custom Inputs and Reusable Pipelines
  • Metric Function
    • Classification Losses
    • Regression Losses
    • Listwise Ranking Loss
    • Cosine Similarity: Aligned Pairs and Pairwise Matrices
  • Optimization
    • Gradient-Free Optimization with NevergradOptimizer
    • Bounded Optimization with ScipyOptimizer
    • Gradient-Based Optimizers: Getting Started
    • Learning-Rate Schedules
    • Advanced Optimizers
    • Readout learning, regression and multi-start search
  • Numerical Integration
    • ODE Integration
    • SDE Integration
    • IMEX and Delay Equations
    • Trajectories and adaptive integration
  • Spike Data
    • Spike trains: storage, encoding, and plotting
  • Surrogate Gradients
    • Surrogate Gradients: Basics
    • Custom Surrogate Gradients
    • Tuning Surrogate Hyperparameters
  • Training Loop
    • A training loop with braintools.trainer
  • Visualization
    • Visualizing Spiking Data
    • Networks, Tuning and Dynamics
    • Statistical Plots
    • Evaluating Models and Decoders
    • Interactive Figures
    • Three-Dimensional Plots
    • Styles, Palettes and Colormaps
    • Animations
  • Spike Encoding
    • Spike Encoding
  • Migrating to braintools 1.0

API Reference

  • Release Notes
  • braintools module
    • braintools.spike_bitwise_or
    • braintools.spike_bitwise_and
    • braintools.spike_bitwise_iand
    • braintools.spike_bitwise_not
    • braintools.spike_bitwise_xor
    • braintools.spike_bitwise_ixor
    • braintools.spike_bitwise
  • braintools.analysis module
    • firing_rate
    • victor_purpura_distance
    • van_rossum_distance
    • spike_train_synchrony
    • burst_synchrony_index
    • phase_locking_value
    • spike_time_tiling_coefficient
    • correlation_index
    • cross_correlation
    • braintools.analysis.spike.isi
    • braintools.analysis.spike.cv
    • braintools.analysis.spike.cv2
    • braintools.analysis.spike.lv
    • braintools.analysis.spike.fano_factor
    • braintools.analysis.spike.psth
    • braintools.analysis.spike.spike_triggered_average
    • braintools.analysis.spike.cross_correlogram
    • braintools.analysis.spike.auto_correlogram
    • braintools.analysis.spike.isi_distance
    • braintools.analysis.spike.spike_distance
    • power_spectral_density
    • coherence_analysis
    • phase_amplitude_coupling
    • theta_gamma_coupling
    • spectral_entropy
    • lfp_phase_coherence
    • braintools.analysis.spectral.spectrogram
    • braintools.analysis.spectral.multitaper_psd
    • braintools.analysis.spectral.morlet_transform
    • braintools.analysis.spectral.bandpass
    • braintools.analysis.spectral.analytic_signal
    • braintools.analysis.spectral.band_power
    • braintools.analysis.spectral.plv
    • braintools.analysis.spectral.pli
    • braintools.analysis.spectral.wpli
    • unitary_LFP
    • current_source_density
    • voltage_fluctuation
    • functional_connectivity
    • functional_connectivity_dynamics
    • braintools.analysis.population.pca
    • braintools.analysis.population.participation_ratio
    • braintools.analysis.population.cca
    • braintools.analysis.population.rdm
    • braintools.analysis.population.rsa
    • braintools.analysis.population.decode
    • braintools.analysis.population.mutual_information
    • braintools.analysis.population.kuramoto_order_parameter
    • braintools.analysis.population.granger_causality
    • braintools.analysis.population.avalanches
    • braintools.analysis.population.branching_ratio
    • braintools.analysis.population.PCAResult
    • braintools.analysis.population.CCAResult
    • braintools.analysis.population.AvalancheResult
    • matrix_correlation
    • weighted_correlation
    • braintools.analysis.stats.jitter_spikes
    • braintools.analysis.stats.shuffle_isi
    • braintools.analysis.stats.phase_randomize
    • braintools.analysis.stats.bootstrap_ci
    • braintools.analysis.graph.degree_distribution
    • braintools.analysis.graph.clustering_coefficient
    • braintools.analysis.graph.characteristic_path_length
    • braintools.analysis.graph.modularity
    • braintools.analysis.graph.motif_counts
    • braintools.analysis.graph.spectral_radius
    • braintools.analysis.graph.small_world_index
    • braintools.analysis.graph.DegreeDistribution
    • braintools.analysis.graph.PathLengthResult
    • braintools.analysis.graph.MotifCounts
    • braintools.analysis.graph.SmallWorldResult
  • braintools.cogtask module
    • Task
    • Context
    • braintools.cogtask.create_task
    • Phase
    • DeclarativePhase
    • Sequence
    • Repeat
    • Parallel
    • Fixation
    • Stimulus
    • Delay
    • Response
    • Cue
    • Blank
    • Sample
    • Test
    • Recall
    • Match
    • Comparison
    • VariableDuration
    • braintools.cogtask.concat
    • braintools.cogtask.execute_phase
    • braintools.cogtask.execute_phase_packed
    • braintools.cogtask.phase_tree_is_variable
    • If
    • Switch
    • While
    • Feature
    • FeatureSet
    • CircleFeature
    • braintools.cogtask.is_feature
    • braintools.cogtask.as_feature
    • braintools.cogtask.one_hot
    • braintools.cogtask.identity
    • braintools.cogtask.circular
    • braintools.cogtask.von_mises
    • braintools.cogtask.cos_sin
    • braintools.cogtask.scalar
    • braintools.cogtask.gaussian
    • braintools.cogtask.ctx_value
    • braintools.cogtask.label
    • braintools.cogtask.match_label
    • braintools.cogtask.comparison_label
    • PerceptualDecisionMaking
    • PerceptualDecisionMakingDelayResponse
    • ContextDecisionMaking
    • SingleContextDecisionMaking
    • PulseDecisionMaking
    • MultiSensoryIntegration
    • EconomicDecisionMaking
    • DelayMatchSample
    • DualDelayMatchSample
    • DelayComparison
    • DelayMatchCategory
    • DelayPairedAssociation
    • GoNoGo
    • IntervalDiscrimination
    • PostDecisionWager
    • ReadySetGo
    • DelayDirectionReproduction
    • ImmediateDirectionReproduction
    • DelayDirectionClassification
    • ImmediateDirectionClassification
    • OneTwoThreeGo
    • HierarchicalReasoning
    • ProbabilisticReasoning
    • AntiReach
    • Reaching1D
    • EvidenceAccumulation
    • TruncExp
    • UniformDuration
    • Transform
    • TransformIT
    • braintools.cogtask.initialize
    • braintools.cogtask.initialize2
    • braintools.cogtask.interval_of
    • braintools.cogtask.period_to_arr
    • braintools.cogtask.rate_to_prob
    • braintools.cogtask.firing_rate
  • braintools.conn module
    • ConnectionResult
    • Connectivity
    • PointConnectivity
    • MultiCompartmentConnectivity
    • ScaledConnectivity
    • CompositeConnectivity
    • Random
    • FixedProb
    • AllToAll
    • OneToOne
    • FixedOutDegree
    • FixedInDegree
    • FixedTotalNum
    • Population
    • Projection
    • Network
    • DistanceDependent
    • Gaussian
    • Exponential
    • Ring
    • Grid2d
    • RadialPatches
    • ClusteredRandom
    • SmallWorld
    • ScaleFree
    • Regular
    • ModularRandom
    • ModularGeneral
    • HierarchicalRandom
    • CorePeripheryRandom
    • ExcitatoryInhibitory
    • Conv2dKernel
    • GaussianKernel
    • GaborKernel
    • DoGKernel
    • MexicanHat
    • SobelKernel
    • LaplacianKernel
    • CustomKernel
    • braintools.conn.SOMA
    • braintools.conn.BASAL_DENDRITE
    • braintools.conn.APICAL_DENDRITE
    • braintools.conn.AXON
    • CompartmentSpecific
    • AllToAllCompartments
    • CustomCompartment
    • SomaToDendrite
    • AxonToSoma
    • DendriteToSoma
    • AxonToDendrite
    • DendriteToDendrite
    • ProximalTargeting
    • DistalTargeting
    • BranchSpecific
    • MorphologyDistance
    • DendriticTree
    • BasalDendriteTargeting
    • ApicalDendriteTargeting
    • DendriticIntegration
    • AxonalProjection
    • AxonalBranching
    • AxonalArborization
    • TopographicProjection
    • SynapticPlacement
    • SynapticClustering
  • braintools.file module
    • load_matfile
    • save_matfile
    • msgpack_from_state_dict
    • msgpack_to_state_dict
    • msgpack_register_serialization
    • msgpack_save
    • msgpack_load
    • AsyncManager
    • AlreadyExistsError
    • InvalidCheckpointPath
    • save_hdf5
    • load_hdf5
    • save_nwb_units
    • load_nwb_units
    • save_nwb_timeseries
    • load_nwb_timeseries
    • to_neo
    • from_neo
  • braintools.fit module
    • Term
    • Objective
    • Fitter
    • FitResult
    • sweep
    • hessian_ci
    • braintools.fit.HessianCI
    • mse
    • rmse
    • mae
    • correlation_loss
    • fc_correlation_loss
    • fc_rmse
    • fcd_ks
    • fcd_wasserstein
    • ks_distance
    • wasserstein_1d
    • psd_log_distance
    • rate_loss
    • cv_loss
    • van_rossum_loss
  • braintools.init module
    • Initialization
    • braintools.init.param
    • braintools.init.Compose
    • Constant
    • ZeroInit
    • Uniform
    • Normal
    • LogNormal
    • TruncatedNormal
    • Gamma
    • Exponential
    • Beta
    • Weibull
    • VarianceScaling
    • KaimingUniform
    • KaimingNormal
    • XavierUniform
    • XavierNormal
    • LecunUniform
    • LecunNormal
    • Orthogonal
    • DeltaOrthogonal
    • Identity
    • Mixture
    • Conditional
    • Scaled
    • Clipped
    • DistanceModulated
    • DistanceProfile
    • GaussianProfile
    • ExponentialProfile
    • LinearProfile
    • StepProfile
    • PowerLawProfile
    • MexicanHatProfile
    • DoGProfile
    • SigmoidProfile
    • BimodalProfile
    • LogisticProfile
    • ComposedProfile
    • ClipProfile
    • ApplyProfile
    • PipeProfile
  • braintools.input module
    • braintools.input.section
    • braintools.input.constant
    • braintools.input.step
    • braintools.input.ramp
    • braintools.input.spike
    • braintools.input.gaussian_pulse
    • braintools.input.exponential_decay
    • braintools.input.double_exponential
    • braintools.input.burst
    • braintools.input.wiener_process
    • braintools.input.ou_process
    • braintools.input.poisson
    • braintools.input.sinusoidal
    • braintools.input.square
    • braintools.input.triangular
    • braintools.input.sawtooth
    • braintools.input.chirp
    • braintools.input.noisy_sinusoidal
    • braintools.input.Input
    • braintools.input.Composite
    • braintools.input.ConstantValue
    • braintools.input.Sequential
    • braintools.input.TimeShifted
    • braintools.input.Clipped
    • braintools.input.Smoothed
    • braintools.input.Repeated
    • braintools.input.Transformed
    • braintools.input.Section
    • braintools.input.Constant
    • braintools.input.Step
    • braintools.input.Ramp
    • braintools.input.Spike
    • braintools.input.GaussianPulse
    • braintools.input.ExponentialDecay
    • braintools.input.DoubleExponential
    • braintools.input.Burst
    • braintools.input.WienerProcess
    • braintools.input.OUProcess
    • braintools.input.Poisson
    • braintools.input.Sinusoidal
    • braintools.input.Square
    • braintools.input.Triangular
    • braintools.input.Sawtooth
    • braintools.input.Chirp
    • braintools.input.NoisySinusoidal
  • braintools.metric module
    • sigmoid_binary_cross_entropy
    • hinge_loss
    • perceptron_loss
    • softmax_cross_entropy
    • softmax_cross_entropy_with_integer_labels
    • multiclass_hinge_loss
    • multiclass_perceptron_loss
    • poly_loss_cross_entropy
    • kl_divergence
    • kl_divergence_with_log_targets
    • convex_kl_divergence
    • ctc_loss
    • ctc_loss_with_forward_probs
    • sigmoid_focal_loss
    • nll_loss
    • make_fenchel_young_loss
    • ranking_softmax_loss
    • squared_error
    • absolute_error
    • l1_loss
    • L1Loss
    • l2_loss
    • l2_norm
    • safe_norm
    • huber_loss
    • log_cosh
    • cosine_similarity
    • cosine_distance
    • pairwise_cosine_similarity
    • pairwise_cosine_distance
    • smooth_labels
  • braintools.optim module
    • Optimizer
    • OptaxOptimizer
    • OptimState
    • SGD
    • Momentum
    • MomentumNesterov
    • Adam
    • AdamW
    • Adagrad
    • Adadelta
    • RMSprop
    • Adamax
    • Nadam
    • RAdam
    • Lamb
    • Lars
    • Lookahead
    • Yogi
    • LBFGS
    • Rprop
    • Adafactor
    • AdaBelief
    • Lion
    • SM3
    • Novograd
    • Fromage
    • SOFO
    • SOFOScan
    • LRScheduler
    • StepLR
    • MultiStepLR
    • ConstantLR
    • LinearLR
    • ExponentialLR
    • PolynomialLR
    • ExponentialDecayLR
    • CosineAnnealingLR
    • CosineAnnealingWarmRestarts
    • WarmupCosineSchedule
    • CyclicLR
    • OneCycleLR
    • ReduceLROnPlateau
    • WarmupScheduler
    • PiecewiseConstantSchedule
    • ChainedScheduler
    • SequentialLR
    • ScipyOptimizer
    • NevergradOptimizer
    • multistart_minimize
    • multistart_init
    • braintools.optim.MultiStartResult
    • RLS
    • FORCE
    • LMS
    • linear_regression
    • ridge_regression
    • lasso_regression
    • elastic_net_regression
    • braintools.optim.RegressionResult
    • UniqueStateManager
  • braintools.quad module
    • odeint
    • solve_ivp
    • register_method
    • get_method
    • list_methods
    • braintools.quad.ODESolution
    • ode_euler_step
    • ode_rk2_step
    • ode_rk3_step
    • ode_rk4_step
    • ode_expeuler_step
    • ode_midpoint_step
    • ode_heun_step
    • ode_rk4_38_step
    • ode_rk45_step
    • ode_rk23_step
    • ode_dopri5_step
    • ode_rk45_dopri_step
    • ode_rkf45_step
    • ode_ssprk33_step
    • ode_dopri8_step
    • ode_rk87_dopri_step
    • ode_bs32_step
    • ode_ralston2_step
    • ode_ralston3_step
    • imex_euler_step
    • imex_ars222_step
    • imex_cnab_step
    • sde_euler_step
    • sde_milstein_step
    • sde_expeuler_step
    • sde_heun_step
    • sde_tamed_euler_step
    • sde_implicit_euler_step
    • sde_srk2_step
    • sde_srk3_step
    • sde_srk4_step
    • dde_euler_step
    • dde_heun_step
    • dde_rk4_step
    • dde_euler_pc_step
    • dde_heun_pc_step
  • braintools.spike module
    • SpikeTrains
    • LatencyEncoder
    • RateEncoder
    • PoissonEncoder
    • PopulationEncoder
    • BernoulliEncoder
    • DeltaEncoder
    • StepCurrentEncoder
    • SpikeCountEncoder
    • TemporalEncoder
    • RankOrderEncoder
    • braintools.spike.bitwise_or
    • braintools.spike.bitwise_and
    • braintools.spike.bitwise_iand
    • braintools.spike.bitwise_not
    • braintools.spike.bitwise_xor
    • braintools.spike.bitwise_ixor
    • braintools.spike.bitwise
  • braintools.surrogate module
    • braintools.surrogate.sigmoid
    • braintools.surrogate.piecewise_quadratic
    • braintools.surrogate.piecewise_exp
    • braintools.surrogate.soft_sign
    • braintools.surrogate.arctan
    • braintools.surrogate.nonzero_sign_log
    • braintools.surrogate.erf
    • braintools.surrogate.piecewise_leaky_relu
    • braintools.surrogate.squarewave_fourier_series
    • braintools.surrogate.s2nn
    • braintools.surrogate.q_pseudo_spike
    • braintools.surrogate.leaky_relu
    • braintools.surrogate.log_tailed_relu
    • braintools.surrogate.relu_grad
    • braintools.surrogate.gaussian_grad
    • braintools.surrogate.inv_square_grad
    • braintools.surrogate.multi_gaussian_grad
    • braintools.surrogate.slayer_grad
    • Surrogate
    • Sigmoid
    • PiecewiseQuadratic
    • PiecewiseExp
    • SoftSign
    • Arctan
    • NonzeroSignLog
    • ERF
    • PiecewiseLeakyRelu
    • SquarewaveFourierSeries
    • S2NN
    • QPseudoSpike
    • LeakyRelu
    • LogTailedRelu
    • ReluGrad
    • GaussianGrad
    • InvSquareGrad
    • MultiGaussianGrad
    • SlayerGrad
  • braintools.trainer module
    • LightningModule
    • Trainer
    • TrainerState
    • TrainOutput
    • EvalOutput
    • Callback
    • CallbackList
    • ModelCheckpoint
    • EarlyStopping
    • LearningRateMonitor
    • Timer
    • RichProgressBar
    • TQDMProgressBar
    • LambdaCallback
    • PrintCallback
    • GradientClipCallback
    • Logger
    • CSVLogger
    • TensorBoardLogger
    • WandBLogger
    • NeptuneLogger
    • MLFlowLogger
    • CompositeLogger
    • DataLoader
    • DistributedDataLoader
    • Dataset
    • ArrayDataset
    • DictDataset
    • IterableDataset
    • Sampler
    • RandomSampler
    • SequentialSampler
    • BatchSampler
    • DistributedSampler
    • braintools.trainer.create_distributed_batches
    • Strategy
    • SingleDeviceStrategy
    • DataParallelStrategy
    • ShardedDataParallelStrategy
    • FullyShardedDataParallelStrategy
    • AutoStrategy
    • braintools.trainer.get_strategy
    • braintools.trainer.all_reduce
    • braintools.trainer.broadcast
    • CheckpointManager
    • braintools.trainer.save_checkpoint
    • braintools.trainer.load_checkpoint
    • braintools.trainer.find_checkpoint
    • braintools.trainer.list_checkpoints
    • ProgressBar
    • SimpleProgressBar
    • TQDMProgressBarWrapper
    • RichProgressBarWrapper
    • braintools.trainer.get_progress_bar
  • braintools.tree module
    • braintools.tree.scale
    • braintools.tree.mul
    • braintools.tree.shift
    • braintools.tree.add
    • braintools.tree.sub
    • braintools.tree.dot
    • braintools.tree.sum
    • braintools.tree.squared_norm
    • braintools.tree.concat
    • braintools.tree.split
    • braintools.tree.idx
    • braintools.tree.expand
    • braintools.tree.take
    • braintools.tree.as_numpy
  • braintools.visualize module
    • spike_raster
    • population_activity
    • connectivity_matrix
    • neural_trajectory
    • spike_histogram
    • isi_distribution
    • firing_rate_map
    • phase_portrait
    • network_topology
    • tuning_curve
    • correlation_matrix
    • distribution_plot
    • qq_plot
    • box_plot
    • violin_plot
    • scatter_matrix
    • regression_plot
    • residual_plot
    • confusion_matrix
    • roc_curve
    • precision_recall_curve
    • learning_curve
    • interactive_spike_raster
    • interactive_line_plot
    • interactive_heatmap
    • interactive_3d_scatter
    • interactive_network
    • interactive_histogram
    • interactive_surface
    • interactive_correlation_matrix
    • dashboard_neural_activity
    • neural_network_3d
    • brain_surface_3d
    • connectivity_3d
    • trajectory_3d
    • volume_rendering
    • electrode_array_3d
    • dendrite_tree_3d
    • phase_space_3d
    • line_plot
    • raster_plot
    • animate_1D
    • animate_2D
    • animator
    • neural_style
    • publication_style
    • dark_style
    • colorblind_friendly_style
    • create_neural_colormap
    • brain_colormaps
    • apply_style
    • get_color_palette
    • set_default_colors
    • get_figure
    • remove_axis
  • .ipynb

Readout learning, regression and multi-start search

Contents

  • 1. FORCE learning on a chaotic rate network
  • 2. Online least squares: RLS, LMS and ridge_regression
  • 3. Sparse regression: lasso and elastic net
  • 4. Multi-start minimization of a multimodal function
  • Summary

Readout learning, regression and multi-start search#

Besides gradient-based optimizers, braintools.optim has tools for problems with more structure:

  1. FORCE learning (FORCE): train the linear readout of a chaotic recurrent rate network online, so that the network produces a target signal on its own.

  2. Online least squares (RLS, LMS) against the closed-form ridge_regression on the same linear problem.

  3. Sparse regression (lasso_regression, elastic_net_regression): how the number of selected features shrinks as the penalty alpha grows.

  4. Multi-start minimization (multistart_minimize): a local optimizer run from hundreds of starting points at once on a multimodal function.

Every repeated model step below runs inside brainstate.transform.for_loop, which compiles the whole time loop into one XLA program.

import brainstate
import jax
import jax.numpy as jnp
import matplotlib.pyplot as plt
import numpy as np

import braintools
from braintools.optim import (
    FORCE, LMS, RLS,
    elastic_net_regression, lasso_regression, ridge_regression,
    multistart_init, multistart_minimize,
)

1. FORCE learning on a chaotic rate network#

The network has \(N\) rate units with state \(x\), rates \(r = \tanh(x)\) and a linear readout \(z = w^\top r\) that is fed back into every unit:

\[\tau \dot x = -x + J r + w_{fb}\, z .\]

The recurrent weights \(J_{ij} \sim \mathcal N(0, g^2/N)\) with gain \(g = 1.5 > 1\) make the network chaotic when the readout is silent. FORCE learning (Sussillo and Abbott, 2009) trains only \(w\), by recursive least squares at every time step, so that \(z\) follows a target \(f(t)\). Because the RLS update is strong from the first step, the error stays small throughout, the fed-back signal is close to the target, and the network’s chaos is tamed into the periodic activity that generates \(f\).

The target here is a sum of three sines with periods of 240, 120 and 60 ms. The state lives in brainstate states: x is a HiddenState, the readout w a ParamState registered with the optimizer.

N, g, dt, tau = 400, 1.5, 1.0, 10.0          # time in ms
k_J, k_fb, k_x = jax.random.split(jax.random.PRNGKey(0), 3)
J = g * jax.random.normal(k_J, (N, N)) / jnp.sqrt(N)
w_fb = jax.random.uniform(k_fb, (N,), minval=-1.0, maxval=1.0)

x = brainstate.HiddenState(0.5 * jax.random.normal(k_x, (N,)))
w = brainstate.ParamState(jnp.zeros((N, 1)))
force = FORCE(alpha=1.0)
force.register_trainable_weights({'readout': w})


def target_fn(t):
    return (jnp.sin(2 * jnp.pi * t / 240.) + 0.5 * jnp.sin(2 * jnp.pi * t / 120.)
            + 0.25 * jnp.sin(2 * jnp.pi * t / 60.)) / 1.5


def step(target, train):
    # One Euler step of the network; with train=True, one FORCE update of the readout.
    r = jnp.tanh(x.value)
    z = r @ w.value                                  # readout, shape (1,)
    x.value = x.value + dt / tau * (-x.value + J @ r + w_fb * z[0])
    if train:
        w_old = w.value
        force.step(r[None], z[None], target[None])   # inputs, current output, target
        dw = jnp.linalg.norm(w.value - w_old)
    else:
        dw = jnp.zeros(())
    return z[0], r[:5], dw

The run has three phases, each a single for_loop over the target samples: 1 s of spontaneous activity (the readout is still zero, so there is no feedback), 6 s of training, and 2 s of testing with learning switched off, where the network runs on its own feedback.

T_pre, T_train, T_test = 1000, 6000, 2000
t = jnp.arange(T_pre + T_train + T_test) * dt
f = target_fn(t)[:, None]
f_pre, f_train, f_test = f[:T_pre], f[T_pre:T_pre + T_train], f[T_pre + T_train:]

z_pre, r_pre, _ = brainstate.transform.for_loop(lambda y: step(y, False), f_pre)
z_train, r_train, dw_train = brainstate.transform.for_loop(lambda y: step(y, True), f_train)
z_test, r_test, _ = brainstate.transform.for_loop(lambda y: step(y, False), f_test)

def mse(z, y):
    return float(jnp.mean((z - y[:, 0]) ** 2))

print('target variance:            %.4f' % float(jnp.var(f)))
print('training MSE, first 500 ms: %.2e' % mse(z_train[:500], f_train[:500]))
print('training MSE, last 500 ms:  %.2e' % mse(z_train[-500:], f_train[-500:]))
print('test MSE (no learning):     %.2e' % mse(z_test, f_test))
target variance:            0.2916
training MSE, first 500 ms: 1.43e-03
training MSE, last 500 ms:  7.02e-06
test MSE (no learning):     1.19e-04
segments = [('before: spontaneous', t[:T_pre], r_pre, z_pre, f_pre),
            ('during: first 1 s of training', t[T_pre:T_pre + 1000], r_train[:1000], z_train[:1000], f_train[:1000]),
            ('after: learning off', t[T_pre + T_train:], r_test, z_test, f_test)]
fig, axes = plt.subplots(2, 3, figsize=(12, 5), sharey='row')
for col, (title, ts, rates, z, y) in enumerate(segments):
    axes[0, col].plot(ts, rates, lw=0.8)
    axes[0, col].set_title(title)
    axes[1, col].plot(ts, y[:, 0], 'k--', lw=1.2, label='target')
    axes[1, col].plot(ts, z, color='tab:red', lw=1, label='readout z')
    axes[1, col].set_xlabel('time (ms)')
axes[0, 0].set_ylabel('rates of 5 units')
axes[1, 0].set_ylabel('output')
axes[1, 0].legend(loc='lower left', fontsize=8)
plt.tight_layout()
../_images/2f7d158ae0b8e578a3432ebab4f85163a534b94f1b73c3e6e55d6ebf5363be04.png

Before training the readout is zero and the units fluctuate irregularly: this is the chaotic regime of \(g = 1.5\). Within a few steps of training the readout follows the target closely (a training MSE of 1.4e-3 over the first 500 ms, against a target variance of 0.29), and the unit activity becomes periodic, locked to the 240 ms period of the target. By the end of training the error has fallen to 7.0e-6. With learning switched off, the network keeps generating the target from its own feedback, with a test MSE of 1.2e-4.

The size of each readout update, \(\|\Delta w\|\), shows the learning settling down, while the error stays small throughout, so FORCE never has to correct large deviations:

err_train = np.abs(np.asarray(z_train) - np.asarray(f_train[:, 0]))
dw = np.asarray(dw_train)
window = np.ones(100) / 100                       # 100-ms moving average
t_ms = np.asarray(t[T_pre:T_pre + T_train])
fig, axes = plt.subplots(1, 2, figsize=(10, 3))
axes[0].semilogy(t_ms, dw, color='0.8', lw=0.4, label='per step')
axes[0].semilogy(t_ms, np.convolve(dw, window, mode='same'), lw=1.5, label='100-ms average')
axes[0].set_xlabel('time (ms)'); axes[0].set_ylabel(r'$\|\Delta w\|$'); axes[0].legend(fontsize=8)
axes[1].semilogy(t_ms, err_train, color='0.8', lw=0.4, label='per step')
axes[1].semilogy(t_ms, np.convolve(err_train, window, mode='same'), lw=1.5, label='100-ms average')
axes[1].set_xlabel('time (ms)'); axes[1].set_ylabel('|z - f| during training'); axes[1].legend(fontsize=8)
plt.tight_layout()
print('mean |dw| over the first / last 100 steps: %.1e / %.1e'
      % (float(dw_train[:100].mean()), float(dw_train[-100:].mean())))
mean |dw| over the first / last 100 steps: 3.6e-03 / 6.3e-05
../_images/6bc93f852e2c43b25e2f6b1282c597535fa0e84e62a8059b14179500610e57d7.png

2. Online least squares: RLS, LMS and ridge_regression#

FORCE is RLS applied inside a recurrent network. On a plain linear problem \(y = X w + \varepsilon\), RLS with forgetting factor 1 does something exact: after any number of samples its weights equal the ridge solution

\[\arg\min_W \|Y - X W\|^2 + \alpha \|W\|^2\]

over the samples seen so far (with zero initial weights and \(P_0 = I/\alpha\)). Here 400 noisy samples with 20 features are fed to RLS one at a time inside a for_loop, recording the weights after every sample:

rng = np.random.default_rng(0)
n, d, alpha = 400, 20, 5.0
X = rng.normal(size=(n, d)).astype(np.float32)
w_true = rng.normal(size=(d, 1)).astype(np.float32)
Y = X @ w_true + 0.5 * rng.normal(size=(n, 1)).astype(np.float32)

w_rls = brainstate.ParamState(jnp.zeros((d, 1)))
rls = RLS(alpha=alpha)
rls.register_trainable_weights({'w': w_rls})

def rls_step(x_i, y_i):
    rls.step(x_i[None], None, y_i[None])     # outputs=None: computed as x @ w
    return w_rls.value[:, 0]

w_history = brainstate.transform.for_loop(rls_step, jnp.asarray(X), jnp.asarray(Y))

ridge_all = ridge_regression(X, Y, alpha=alpha, fit_intercept=False)
ridge_50 = ridge_regression(X[:50], Y[:50], alpha=alpha, fit_intercept=False)
print('max |RLS - ridge|, all 400 samples:   %.1e' % float(jnp.max(jnp.abs(w_rls.value - ridge_all.coef))))
print('max |RLS - ridge|, first 50 samples:  %.1e' % float(jnp.max(jnp.abs(w_history[49] - ridge_50.coef[:, 0]))))
max |RLS - ridge|, all 400 samples:   1.8e-06
max |RLS - ridge|, first 50 samples:  8.9e-07

The two agree to float32 rounding (about 1e-6), after all 400 samples and after the first 50 alike: RLS needs no learning rate, and after every sample it holds the best regularised fit to the data seen so far. The price is the \(d \times d\) matrix \(P\) that it updates at every step.

LMS (Widrow-Hoff) is the first-order counterpart: \(w \leftarrow w - \eta\, x\, e\) per sample, with no \(P\) matrix. It is cheaper per step but needs a learning rate, and it is stable only for \(\eta < 2/\|x\|^2\), about \(2/d = 0.1\) here. Each run below is one for_loop over the same samples:

def run_lms(lr):
    w_lms = brainstate.ParamState(jnp.zeros((d, 1)))
    lms = LMS(lr=lr)
    lms.register_trainable_weights({'w': w_lms})

    def lms_step(x_i, y_i):
        lms.step(x_i[None], None, y_i[None])
        return jnp.linalg.norm(w_lms.value[:, 0] - w_true[:, 0])

    return np.asarray(brainstate.transform.for_loop(lms_step, jnp.asarray(X), jnp.asarray(Y)))


lms_rates = [0.002, 0.01, 0.05, 0.12]
lms_errors = {lr: run_lms(lr) for lr in lms_rates}
rls_error = np.asarray(jnp.linalg.norm(w_history - w_true[:, 0], axis=1))
ridge_error = float(jnp.linalg.norm(ridge_all.coef - w_true))

print('|w - w_true| after 400 samples:')
print('  ridge (closed form): %.3f' % ridge_error)
print('  RLS:                 %.3f' % rls_error[-1])
for lr in lms_rates:
    print('  LMS lr=%-5g          %.3g' % (lr, lms_errors[lr][-1]))

fig, ax = plt.subplots(figsize=(6.5, 3.5))
samples = np.arange(1, n + 1)
ax.semilogy(samples, rls_error, 'k', lw=2, label='RLS')
for lr in lms_rates:
    ax.semilogy(samples, lms_errors[lr], lw=1, label=f'LMS lr={lr}')
ax.axhline(ridge_error, color='k', ls=':', label='ridge (all samples)')
ax.set_ylim(0.05, 50); ax.set_xlabel('samples seen'); ax.set_ylabel(r'$\|w - w_{true}\|$')
ax.legend(fontsize=8, ncol=2); plt.tight_layout()
|w - w_true| after 400 samples:
  ridge (closed form): 0.094
  RLS:                 0.094
  LMS lr=0.002          1.89
  LMS lr=0.01           0.238
  LMS lr=0.05           0.66
  LMS lr=0.12           1.54e+05
../_images/3d995ac9c831ec1273ed683536f4966af70fbc21ec572b4a5393f8a2883ad281.png

RLS ends at the ridge solution, 0.094 away from the true weights; that remaining error comes from the measurement noise and cannot be removed. The LMS runs show the learning-rate trade-off. With lr=0.002 LMS is still far off after 400 samples (1.89). lr=0.05 descends fastest at first but then stalls at a noise floor (0.66 at the end), because with a constant learning rate every noisy sample keeps pushing the weights around. lr=0.01 is the best of these rates (0.238). lr=0.12 is above the stability limit and diverges (1.5e5).

3. Sparse regression: lasso and elastic net#

When only a few of many features matter, an \(L_1\) penalty sets the other coefficients exactly to zero. lasso_regression minimises the objective of scikit-learn’s Lasso,

\[\frac{1}{2n}\|y - X w - b\|^2 + \alpha \|w\|_1 ,\]

and elastic_net_regression adds an \(L_2\) term, \(\alpha\left(\rho \|w\|_1 + \tfrac{1-\rho}{2}\|w\|^2\right)\) with \(\rho\) = l1_ratio. Both are solved by FISTA inside a lax.while_loop, so they can be jax.vmap-ped over alpha to compute a whole regularisation path in one call.

The problem has 80 samples and 40 features, of which only the first 5 have non-zero true coefficients:

rng = np.random.default_rng(1)
n2, d2 = 80, 40
X2 = rng.normal(size=(n2, d2)).astype(np.float32)
coef_true = np.zeros(d2, np.float32)
coef_true[:5] = [3.0, -2.0, 1.5, -1.0, 0.5]
y2 = X2 @ coef_true + 0.3 * rng.normal(size=n2).astype(np.float32)

alphas = jnp.logspace(-3, 0.5, 30)
lasso_path = jax.vmap(lambda a: lasso_regression(X2, y2, alpha=a).coef)(alphas)
enet_path = jax.vmap(lambda a: elastic_net_regression(X2, y2, alpha=a, l1_ratio=0.5).coef)(alphas)

nnz_lasso = np.asarray((jnp.abs(lasso_path) > 0).sum(axis=1))
nnz_enet = np.asarray((jnp.abs(enet_path) > 0).sum(axis=1))
for i in [0, 10, 15, 20, 25, 29]:
    print('alpha = %7.4f   non-zero coefficients: lasso %2d, elastic net %2d'
          % (float(alphas[i]), nnz_lasso[i], nnz_enet[i]))

fit = lasso_regression(X2, y2, alpha=0.1)
print('\nlasso, alpha=0.1: first 8 coefficients', np.round(np.asarray(fit.coef[:8]), 3))
print('                  non-zero:', int((fit.coef != 0).sum()), ' true:', coef_true[:8])

true_support = coef_true != 0
exact = np.all((np.asarray(lasso_path) != 0) == true_support, axis=1)
print('\nalphas where the lasso support is exactly the true one: %.3f to %.3f (%d of 30 grid values)'
      % (float(alphas[exact].min()), float(alphas[exact].max()), exact.sum()))
print('elastic net, fewest non-zero coefficients below alpha = 1:', nnz_enet[np.asarray(alphas) < 1].min())
alpha =  0.0010   non-zero coefficients: lasso 40, elastic net 40
alpha =  0.0161   non-zero coefficients: lasso 22, elastic net 32
alpha =  0.0646   non-zero coefficients: lasso  8, elastic net 17
alpha =  0.2593   non-zero coefficients: lasso  5, elastic net  7
alpha =  1.0405   non-zero coefficients: lasso  3, elastic net  6
alpha =  3.1623   non-zero coefficients: lasso  0, elastic net  1
lasso, alpha=0.1: first 8 coefficients [ 2.851 -1.887  1.347 -0.942  0.395  0.    -0.     0.   ]
                  non-zero: 5  true: [ 3.  -2.   1.5 -1.   0.5  0.   0.   0. ]

alphas where the lasso support is exactly the true one: 0.113 to 0.452 (6 of 30 grid values)
elastic net, fewest non-zero coefficients below alpha = 1: 6
fig, axes = plt.subplots(1, 3, figsize=(13, 3.6))
a = np.asarray(alphas)
for ax, path, title in [(axes[0], lasso_path, 'lasso'), (axes[1], enet_path, 'elastic net (l1_ratio=0.5)')]:
    path = np.asarray(path)
    ax.semilogx(a, path[:, 5:], color='0.75', lw=0.8)
    for j in range(5):
        ax.semilogx(a, path[:, j], lw=2, label=f'w{j} (true {coef_true[j]:g})')
    ax.axhline(0, color='k', lw=0.5)
    ax.set_xlabel('alpha'); ax.set_ylabel('coefficient'); ax.set_title(title)
axes[0].legend(fontsize=7)
axes[2].semilogx(a, nnz_lasso, 'o-', ms=3, label='lasso')
axes[2].semilogx(a, nnz_enet, 's-', ms=3, label='elastic net')
axes[2].axhline(5, color='k', ls=':', label='true support (5)')
axes[2].set_xlabel('alpha'); axes[2].set_ylabel('non-zero coefficients'); axes[2].legend(fontsize=8)
plt.tight_layout()
../_images/d991e558c712417f35364ef6e1825abfc100637f92c49f60a23287880d78af1e.png

At the smallest alpha every coefficient is non-zero (40 of 40): with 80 samples for 40 features, the fit also follows the noise. As alpha grows the irrelevant coefficients drop out first, and between alpha = 0.113 and 0.452 the lasso selects exactly the 5 true features. Their estimates are shrunk towards zero: at alpha=0.1, for example, 2.851 for a true 3. Larger penalties then remove true features too, the smallest one (w4, true value 0.5) first. The elastic net keeps at least as many coefficients at every alpha (its \(L_1\) part is only half as strong, and its \(L_2\) part shrinks coefficients without setting them to zero); below alpha = 1 it never gets under 6 non-zero coefficients, so in that range it never isolates the true support.

4. Multi-start minimization of a multimodal function#

The 2-D Rastrigin function

\[f(p) = 20 + \sum_{i=1}^{2} \left(p_i^2 - 10 \cos 2\pi p_i\right), \qquad p \in [-5.12, 5.12]^2,\]

has a local minimum near every integer point of the grid and its global minimum \(f = 0\) at the origin. A local optimizer finds the minimum of whichever basin it lands in, so it is run from many starts: multistart_init draws them uniformly in the box, and multistart_minimize runs L-BFGS from all of them at once (a vmap over a lax.while_loop, compiled into one program). With bounds, every start is optimised in an unconstrained space through a sigmoid, so the iterates never leave the box.

def rastrigin(p):
    return 20.0 + jnp.sum(p ** 2 - 10.0 * jnp.cos(2 * jnp.pi * p))


box = (jnp.full(2, -5.12), jnp.full(2, 5.12))
x0 = multistart_init(jax.random.PRNGKey(0), box, n_starts=512)
res = multistart_minimize(rastrigin, x0, method='lbfgs', bounds=(-5.12, 5.12), max_iter=100)

fun = np.asarray(res.fun)
print('starts:', x0.shape[0], ' converged:', int(res.converged.sum()),
      ' iterations: median %d, max %d' % (np.median(res.n_iter), int(res.n_iter.max())))
print('best start: #%d, f = %.2e at p = %s' % (int(res.best_index), float(res.best_fun), np.round(np.asarray(res.best_x), 4)))
print('it started at', np.round(np.asarray(x0[res.best_index]), 3))
print('starts reaching f < 1e-3:', int((fun < 1e-3).sum()))
levels, counts = np.unique(np.round(fun, 3), return_counts=True)
print('lowest final values (number of starts):', ', '.join(f'{l:.3f} ({c})' for l, c in zip(levels[:5], counts[:5])))
in_central = np.all(np.abs(np.asarray(x0)) < 0.5, axis=1)
print('starts inside the central basin |p_i| < 0.5:', int(in_central.sum()),
      ' of which reached f < 1e-3:', int((fun[in_central] < 1e-3).sum()))
print('starts needed before the global minimum is found:', int(np.argmax(np.minimum.accumulate(fun) < 1e-3)) + 1)
starts: 512  converged: 512  iterations: median 7, max 16
best start: #465, f = 0.00e+00 at p = [0. 0.]
it started at [0.235 0.186]
starts reaching f < 1e-3: 1
lowest final values (number of starts): 0.000 (1), 0.995 (19), 1.990 (25), 3.980 (29), 4.975 (43)
starts inside the central basin |p_i| < 0.5: 5  of which reached f < 1e-3: 1
starts needed before the global minimum is found: 466
fig, axes = plt.subplots(1, 3, figsize=(14, 4))

grid = jnp.linspace(-5.12, 5.12, 300)
P = jnp.stack(jnp.meshgrid(grid, grid), axis=-1)
F = jax.vmap(jax.vmap(rastrigin))(P)
axes[0].contourf(grid, grid, F, levels=30, cmap='Greys')
x_end = np.asarray(res.x)
axes[0].scatter(*np.asarray(x0).T, s=4, color='tab:blue', alpha=0.5, label='starts')
axes[0].scatter(*x_end.T, s=10, c=fun, cmap='autumn', label='end points')
axes[0].plot(*np.asarray(res.best_x), 'g*', ms=16, label='best')
axes[0].set_aspect('equal'); axes[0].set_title('starts and end points'); axes[0].legend(fontsize=7, loc='upper right')

history = np.asarray(res.history)
axes[1].plot(history.T, color='0.6', lw=0.4)
axes[1].plot(history[int(res.best_index)], 'g', lw=2, label=f'best start (#{int(res.best_index)})')
axes[1].set_xlim(0, int(res.n_iter.max()) + 2); axes[1].set_xlabel('iteration'); axes[1].set_ylabel('f')
axes[1].set_title('per-start convergence'); axes[1].legend(fontsize=8)

best_so_far = np.minimum.accumulate(fun)
axes[2].semilogx(np.arange(1, len(fun) + 1), best_so_far, 'k')
axes[2].set_xlabel('number of starts used'); axes[2].set_ylabel('best f found')
axes[2].set_title('best value vs. number of starts')
plt.tight_layout()
../_images/3995b652fef6f41595d4112b4dee190823a997d1137f8f7cfef2707fd364e678.png

All 512 starts converged within 16 iterations (median 7), but each one only to a nearby local minimum: the end points sit on the integer grid (left panel), and the final values form discrete levels (0.995, 1.990, 3.980, …). Only one start, #465, reached the global minimum \(f = 0\) at the origin. It started at (0.235, 0.186), inside the central basin. Of the 5 starts drawn in that basin, the other 4 ended in neighbouring minima: an early L-BFGS line-search step can carry a start over a basin boundary. Because the starts are independent, the best value among the first \(S\) starts (right panel) shows what a smaller run would have found: it falls in steps as \(S\) grows, and this draw needs 466 starts before the global minimum is found.

Summary#

  • FORCE (and RLS) update registered readout weights from (inputs, outputs, targets); the update is a brainstate state change, so the whole training run is one for_loop.

  • With forgetting factor 1, RLS reproduces ridge_regression on the samples seen so far; LMS is cheaper but slower, has a learning-rate stability limit, and keeps a noise floor at a constant learning rate.

  • lasso_regression and elastic_net_regression are jit- and vmap-compatible, so a regularisation path over alpha is a single jax.vmap.

  • multistart_minimize runs a local optimizer from many starts in one compiled program and reports every start’s result, iteration count and loss history, with the best start picked out.

previous

Advanced Optimizers

next

Numerical Integration

Contents
  • 1. FORCE learning on a chaotic rate network
  • 2. Online least squares: RLS, LMS and ridge_regression
  • 3. Sparse regression: lasso and elastic net
  • 4. Multi-start minimization of a multimodal function
  • Summary

By BrainTools Developers

© Copyright 2024, braintools.

Last updated on Oct 20, 2026.

BrainX Ecosystem

An open, differentiable ecosystem for brain simulation. We build the foundations the next decade of computational neuroscience will run on.

Modeling

  • brainpy (classic) ↗
  • brainpy.state
  • braincell
  • brainmass

Infrastructure

  • brainunit
  • brainevent
  • braintrace
  • braintools

Compilation

  • brainstate

Vibe coding

  • BrainX skill

Research

  • Papers about BrainX
  • Papers using BrainX
  • Papers citing BrainX
© 2026 BrainX Developers How to cite Digital Brain Laboratory ↗