Event-Driven Synaptic Plasticity#

This tutorial shows how spike events select sparse synapses for weight updates. It implements a minimal pair-based STDP example; it does not claim that this rule captures the full biological diversity of synaptic plasticity.

Contents#

  1. From Spike Events to Synaptic Updates

  2. Implement a Minimal STDP Rule

  3. Visualize the Learning Window

  4. Update CSR Weights from Pre- and Postsynaptic Events

  5. Apply Event-Driven Updates in a Network

  6. Summary and Next Steps

From Spike Events to Synaptic Updates#

Hebb’s Rule#

Hebbian ideas motivate activity-dependent weight changes, but a concrete implementation requires an explicit update rule, state variables, bounds, and a timing convention.

Spike-Timing-Dependent Plasticity#

Pair-based STDP uses decaying pre- and postsynaptic traces as summaries of recent events. A spike on one side triggers an update determined by the trace on the other side.

The Update Rule#

For an existing synapse from presynaptic neuron \(i\) to postsynaptic neuron \(j\), a pre-triggered update adds a scaled postsynaptic trace; a post-triggered update adds a scaled presynaptic trace. Signs determine potentiation or depression, and clipping enforces weight bounds.

BrainEvent Update Operations#

update_csr_on_binary_pre traverses outgoing CSR entries selected by presynaptic events. update_csr_on_binary_post uses a CSC index view plus a permutation back to CSR data order to traverse incoming entries selected by postsynaptic events.

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

Implement a Minimal STDP Rule#

tau_pre = 20.0
tau_post = 20.0
a_plus = 0.01
a_minus = 0.012
delta_t = jnp.linspace(-50.0, 50.0, 401)
learning_window = jnp.where(
    delta_t > 0,
    a_plus * jnp.exp(-delta_t / tau_post),
    -a_minus * jnp.exp(delta_t / tau_pre),
)

Visualize the Learning Window#

fig, ax = plt.subplots(figsize=(7, 3))
ax.plot(np.asarray(delta_t), np.asarray(learning_window))
ax.axhline(0.0, color="black", linewidth=0.8)
ax.axvline(0.0, color="black", linewidth=0.8)
ax.set(xlabel="relative event time", ylabel="weight change", title="Illustrative pair-based STDP window")
plt.tight_layout()
plt.show()
../../_images/31101bdf939c26663ee0f919160a10b6af4cc099ab8453fc729757840fbdbedc.png

Update CSR Weights from Pre- and Postsynaptic Events#

dense_weights = jnp.array([
    [0.20, 0.00, 0.40],
    [0.00, 0.30, 0.00],
], dtype=jnp.float32)
csr = brainevent.CSR.fromdense(dense_weights)

pre_spike = jnp.array([True, False])
post_trace = jnp.array([0.01, 0.02, 0.03])
after_pre = brainevent.update_csr_on_binary_pre(
    csr.data, csr.indices, csr.indptr, pre_spike, post_trace,
    0.0, 1.0, shape=csr.shape,
)

csc_indptr, csc_indices, weight_indices = brainevent.csr_to_csc_index(
    csr.indptr, csr.indices, shape=csr.shape
)
post_spike = jnp.array([False, True, True])
pre_trace = jnp.array([-0.01, -0.02])
after_post = brainevent.update_csr_on_binary_post(
    after_pre, csc_indices, csc_indptr, weight_indices, pre_trace, post_spike,
    0.0, 1.0, shape=csr.shape,
)
after_post = jax.block_until_ready(after_post)
print("initial CSR data:", csr.data)
print("after pre-triggered update:", after_pre)
print("after post-triggered update:", after_post)
initial CSR data: [0.2 0.4 0.3]
after pre-triggered update: [0.21000001 0.43       0.3       ]
after post-triggered update: [0.21000001 0.42000002 0.28      ]

Apply Event-Driven Updates in a Network#

The sparsity pattern is unchanged; only its stored weights change. The updated data can therefore be placed back into the same CSR structure and used immediately by an event-driven forward pass.

updated_csr = csr.with_data(after_post)
input_events = brainevent.BinaryArray(jnp.array([True, False]))
output = jax.block_until_ready(input_events @ updated_csr)
print("updated dense weights:\n", updated_csr.todense())
print("network output:", output)
updated dense weights:
 [[0.21000001 0.         0.42000002]
 [0.         0.28       0.        ]]
network output: [0.21000001 0.         0.42000002]

Summary and Next Steps#

BrainEvent separates the event trigger from the sparse connectivity data: pre- and postsynaptic events select which stored CSR weights receive trace-based updates. For storage details, continue with CSR and CSC Sparse Matrices; for task-oriented operator selection, see Apply event-driven synaptic plasticity.