{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "17b82d8d",
   "metadata": {},
   "source": [
    "# Training a Spiking Neural Network"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81e90c48",
   "metadata": {},
   "source": [
    "A spiking network unrolled over time *is* a recurrent network, so it can be trained with\n",
    "backpropagation through time. The one obstacle is the spike itself: a threshold function has zero\n",
    "gradient almost everywhere, so gradients cannot flow back through it. The standard remedy is a\n",
    "**surrogate gradient** — use the hard threshold on the forward pass, but a smooth approximation\n",
    "for the backward pass.\n",
    "\n",
    "This tutorial trains a small spiking classifier end to end: define the network with a surrogate\n",
    "spike function, generate a synthetic spike dataset, and optimise it with `braintools.optim` and\n",
    "`brainstate.transform.grad`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8bd13a74",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-30T16:51:28.762634Z",
     "iopub.status.busy": "2026-05-30T16:51:28.762369Z",
     "iopub.status.idle": "2026-05-30T16:51:37.075064Z",
     "shell.execute_reply": "2026-05-30T16:51:37.074400Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'0.4.0'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import brainunit as u\n",
    "import jax.numpy as jnp\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "import brainstate\n",
    "import brainpy\n",
    "import braintools\n",
    "\n",
    "brainstate.random.seed(0)\n",
    "brainstate.environ.set(dt=1.0 * u.ms)\n",
    "\n",
    "num_inputs, num_hidden, num_outputs = 100, 4, 2\n",
    "num_steps, batch_size = 100, 128\n",
    "brainstate.__version__"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b21fef44",
   "metadata": {},
   "source": [
    "## The network"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e4cf4da",
   "metadata": {},
   "source": [
    "The classifier has three stages: an input projection (`Linear` followed by an `Expon` synapse), a\n",
    "recurrent `LIF` layer, and a readout (`Linear` into an `Expon` output). The crucial detail is\n",
    "`spk_fun=braintools.surrogate.ReluGrad()` on the `LIF` layer — this is what makes the spike\n",
    "differentiable for training. Weights are initialised with `braintools.init`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "59c1074d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-30T16:51:37.077300Z",
     "iopub.status.busy": "2026-05-30T16:51:37.076905Z",
     "iopub.status.idle": "2026-05-30T16:51:37.819667Z",
     "shell.execute_reply": "2026-05-30T16:51:37.818761Z"
    }
   },
   "outputs": [],
   "source": [
    "class SNN(brainstate.nn.Module):\n",
    "    def __init__(self, n_in, n_rec, n_out):\n",
    "        super().__init__()\n",
    "        decay = 1 - u.math.exp(-brainstate.environ.get_dt() / (1 * u.ms))\n",
    "        self.i2r = brainstate.nn.Sequential(\n",
    "            brainstate.nn.Linear(\n",
    "                n_in, n_rec,\n",
    "                w_init=braintools.init.KaimingNormal(scale=7 * decay, unit=u.mA),\n",
    "                b_init=braintools.init.ZeroInit(unit=u.mA),\n",
    "            ),\n",
    "            brainpy.state.Expon(n_rec, tau=10. * u.ms,\n",
    "                                g_initializer=braintools.init.Constant(0. * u.mA)),\n",
    "        )\n",
    "        self.r = brainpy.state.LIF(\n",
    "            n_rec, tau=20 * u.ms, V_rest=0 * u.mV, V_reset=0 * u.mV, V_th=1. * u.mV,\n",
    "            spk_fun=braintools.surrogate.ReluGrad(),   # surrogate gradient for the spike\n",
    "        )\n",
    "        self.r2o = brainstate.nn.Linear(n_rec, n_out, w_init=braintools.init.KaimingNormal())\n",
    "        self.o = brainpy.state.Expon(n_out, tau=10. * u.ms,\n",
    "                                     g_initializer=braintools.init.Constant(0.))\n",
    "\n",
    "    def update(self, spike):\n",
    "        # one time step: input projection -> recurrent spikes -> readout\n",
    "        return self.o(self.r2o(self.r(self.i2r(spike))))\n",
    "\n",
    "net = SNN(num_inputs, num_hidden, num_outputs)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f396d4d2",
   "metadata": {},
   "source": [
    "## A synthetic dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e7814b4",
   "metadata": {},
   "source": [
    "Each example is a `num_steps x num_inputs` array of Poisson spikes; the label is a random binary\n",
    "class. The data is intentionally trivial — the point is the training mechanics, not the task."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "079022cb",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-30T16:51:37.822272Z",
     "iopub.status.busy": "2026-05-30T16:51:37.822013Z",
     "iopub.status.idle": "2026-05-30T16:51:38.515286Z",
     "shell.execute_reply": "2026-05-30T16:51:38.514774Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input spikes: (100, 128, 100) | labels: (128,)\n"
     ]
    }
   ],
   "source": [
    "firing_rate = 5 * u.Hz\n",
    "x_data = brainstate.random.rand(num_steps, batch_size, num_inputs) < firing_rate * brainstate.environ.get_dt()\n",
    "y_data = u.math.asarray(brainstate.random.rand(batch_size) < 0.5, dtype=int)\n",
    "print('input spikes:', x_data.shape, '| labels:', y_data.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c50688c8",
   "metadata": {},
   "source": [
    "## Loss, optimizer, and the training step"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "00fd4f35",
   "metadata": {},
   "source": [
    "The loss runs the network over all time steps with `for_loop`, averages the readout over time,\n",
    "and applies a cross-entropy. The training step re-initialises the network state for each batch,\n",
    "differentiates the loss with respect to the parameters (backpropagation through time), and\n",
    "applies an Adam update — the whole thing wrapped in `jit`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "7af3da4a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-30T16:51:38.517444Z",
     "iopub.status.busy": "2026-05-30T16:51:38.517229Z",
     "iopub.status.idle": "2026-05-30T16:51:38.973000Z",
     "shell.execute_reply": "2026-05-30T16:51:38.972392Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "accuracy before training: 0.438\n"
     ]
    }
   ],
   "source": [
    "def accuracy():\n",
    "    brainstate.nn.init_all_states(net, batch_size=batch_size)\n",
    "    outs = brainstate.transform.for_loop(net.update, x_data)\n",
    "    pred = u.math.argmax(u.math.max(outs, axis=0), axis=1)   # max over time, argmax over class\n",
    "    return float(u.math.mean(pred == y_data))\n",
    "\n",
    "print(f'accuracy before training: {accuracy():.3f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "49257fb4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-30T16:51:38.974542Z",
     "iopub.status.busy": "2026-05-30T16:51:38.974397Z",
     "iopub.status.idle": "2026-05-30T16:51:39.055810Z",
     "shell.execute_reply": "2026-05-30T16:51:39.054874Z"
    }
   },
   "outputs": [],
   "source": [
    "optimizer = braintools.optim.Adam(lr=3e-3)\n",
    "optimizer.register_trainable_weights(net.states(brainstate.ParamState))\n",
    "\n",
    "def loss_fn():\n",
    "    outs = brainstate.transform.for_loop(net.update, x_data)\n",
    "    outs = u.math.mean(outs, axis=0)\n",
    "    return braintools.metric.softmax_cross_entropy_with_integer_labels(outs, y_data).mean()\n",
    "\n",
    "@brainstate.transform.jit\n",
    "def train_step():\n",
    "    brainstate.nn.init_all_states(net, batch_size=batch_size)\n",
    "    grads, loss = brainstate.transform.grad(\n",
    "        loss_fn, net.states(brainstate.ParamState), return_value=True)()\n",
    "    optimizer.update(grads)\n",
    "    return loss"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13eb0bd2",
   "metadata": {},
   "source": [
    "## Training"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df06cec9",
   "metadata": {},
   "source": [
    "Each call to `train_step` is one epoch over the batch. The loss falls steadily as the surrogate\n",
    "gradient drives the weights."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "156ab7df",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-30T16:51:39.058281Z",
     "iopub.status.busy": "2026-05-30T16:51:39.058115Z",
     "iopub.status.idle": "2026-05-30T16:51:39.402935Z",
     "shell.execute_reply": "2026-05-30T16:51:39.402044Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  100 | loss 0.5279\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  200 | loss 0.3692\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  300 | loss 0.2669\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  400 | loss 0.1996\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  500 | loss 0.1493\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  600 | loss 0.1111\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  700 | loss 0.0834\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch  800 | loss 0.0628\n"
     ]
    }
   ],
   "source": [
    "losses = []\n",
    "for epoch in range(1, 801):\n",
    "    losses.append(float(train_step()))\n",
    "    if epoch % 100 == 0:\n",
    "        print(f'epoch {epoch:4d} | loss {losses[-1]:.4f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8c6536c2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-30T16:51:39.405779Z",
     "iopub.status.busy": "2026-05-30T16:51:39.405513Z",
     "iopub.status.idle": "2026-05-30T16:51:39.614727Z",
     "shell.execute_reply": "2026-05-30T16:51:39.614042Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "accuracy after training: 0.844\n"
     ]
    },
    {
     "data": {
      "image/png": "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Y8dq2zt16qTff/O/bdu2rTaXsF27dtWOTUpKwoQJE2Brawtzc3M4ODigf//+AID8/Pw71n/p0iXk5+fD0dERDg4OVV5FRUX6m+Hy8/ORnp6uf+Xk5Ohrlcvl1c63plpvqqlvcnJyMGPGDDg5OcHExAQODg76djfPIysrC6WlpWjfvn2142vq15rOdefOndXOMyQkBED1G/9uDcsA9KG2rnOHayKXyzF16lRERkYiOzsbW7duxaOPPoq///4bzzzzzG2PmzFjBqytres8V/Zuwm9dGBkZoU2bNtW23+t1+O++Bm70d136ui7HJiYm1nhN1nadErVEnCNLZOCUSiV69uyJnj17okOHDpg4cSI2bNiABQsW3PZGHa1We9v3u91KBE2xQoFWq8WgQYOQk5ODN954A76+vjAzM0NqaiomTJhQ5Qam29HpdHB0dMQvv/xS4/6bN//MmDEDP/zwg357//7973qJppr6ZuTIkTh69Chee+01+Pv7w9zcHDqdDo888kidzqMudDodBg0ahNdff73G/R06dKjy9e3ukBe3zIW+F3Z2dhg6dCiGDh2KAQMG4MCBA0hMTKxxeambwXThwoX1GpX94osvsGjRomrzf++WSqWCXF51TKchrsN76evG/nsiakkYZIlakMDAQABAWloagH9G3PLy8qq0uzlSeq88PT0RGxtbbfvFixf1+2/+98KFCxBCVAnXly9frnJcdHQ04uLi8MMPP2DcuHH67Xv27Kn2GbcL6W3btsXevXvRt2/fWsP366+/jjFjxui/vtlXnp6e0Ol0SEhIqDJS+u9aa5Obm4uwsDAsWrQI8+fP12+/dOlSlXYODg4wMTGpth1Ajf36b23btkVRUZF+BLYh3O1Tqv4tMDAQBw4cQFpa2m3XSZ05cyYWL16MRYsW1bgCx7/dGn7Hjx9fpzru5nzqcx1KxdPTs8Zrsj7XKVFLwKkFRAZo3759NY7O7NixA8A/v5a2tLSEvb19tbmSX331VYPU8dhjjyEiIgLh4eH6bcXFxVi1ahW8vLzQuXNnAMDgwYORmppaZf5uWVlZtcX9b45E3XpuQogaH/Jwc63Pf4f0kSNHQqvV4t133612TGVlpb59586dERISon8FBAToawWq91F9nphW03kAqDaKqFAoMHjwYGzZsgVJSUn67TExMdi1a9cdP2fkyJEIDw+vsW1e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7ty54sCBAyIhIUGcPXtWzJ07V8hkMrF7924hROvtl5rcumqBEK2zb+bMmSP2798vEhISxJEjR0RISIiwt7cXmZmZQojW2Sc3RURECCMjI/H++++LS5cuiV9++UWYmpqKn3/+Wd+mtX4P1mq1wsPDQ7zxxhvV9rXWa2b8+PHCzc1Nv/zW77//Luzt7cXrr7+ub2MI1wuDbCP78ssvhYeHh1AqlaJXr17i2LFjUpfUqPbt2ycAVHuNHz9eCHFjOY+3335bODk5CZVKJR566CERGxtb5T2uX78unn32WWFubi4sLS3FxIkTRWFhoQRn03Bq6hMA4vvvv9e3KS0tFS+//LKwsbERpqamYvjw4SItLa3K+1y9elU8+uijwsTERNjb24s5c+aIioqKJj6bhjVp0iTh6ekplEqlcHBwEA899JA+xArRevulJv8Osq2xb0aNGiVcXFyEUqkUbm5uYtSoUVXWSW2NfXKr7du3i65duwqVSiV8fX3FqlWrquxvrd+Dd+3aJQBUO1chWu81U1BQIGbMmCE8PDyEWq0WPj4+4q233qqypJghXC8yIW55hAMRERERkYHgHFkiIiIiMkgMskRERERkkBhkiYiIiMggMcgSERERkUFikCUiIiIig8QgS0REREQGiUGWiIiIiAwSgywRERERGSQGWSKiVmj//v2QyWTIy8uTuhQiorvGIEtEREREBolBloiIiIgMEoMsEZEEdDodQkND4e3tDRMTE/j5+WHjxo0A/vm1/59//onu3btDrVajd+/eOHfuXJX32LRpE7p06QKVSgUvLy989tlnVfaXl5fjjTfegLu7O1QqFdq1a4dvv/22SpvIyEgEBgbC1NQUffr0QWxsbOOeOBFRA2KQJSKSQGhoKH788UesWLEC58+fx6xZszBmzBgcOHBA3+a1117DZ599hhMnTsDBwQGPP/44KioqANwIoCNHjsQzzzyD6OhoLFy4EG+//TbWrFmjP37cuHH47bffsHTpUsTExGDlypUwNzevUsdbb72Fzz77DCdPnoSRkREmTZrUJOdPRNQQZEIIIXURREStSXl5OWxtbbF3714EBwfrt7/wwgsoKSnBiy++iIEDB2Lt2rUYNWoUACAnJwdt2rTBmjVrMHLkSIwePRpZWVnYvXu3/vjXX38df/75J86fP4+4uDh07NgRe/bsQUhISLUa9u/fj4EDB2Lv3r146KGHAAA7duzAkCFDUFpaCrVa3ci9QER07zgiS0TUxC5fvoySkhIMGjQI5ubm+tePP/6IK1eu6NvdGnJtbW3RsWNHxMTEAABiYmLQt2/fKu/bt29fXLp0CVqtFlFRUVAoFOjfv3+ttXTv3l3/ZxcXFwBAZmbmPZ8jEVFTMJK6ACKi1qaoqAgA8Oeff8LNza3KPpVKVSXM3i0TE5M6tTM2Ntb/WSaTAbgxf5eIyBBwRJaIqIl17twZKpUKSUlJaNeuXZWXu7u7vt2xY8f0f87NzUVcXBw6deoEAOjUqROOHDlS5X2PHDmCDh06QKFQoFu3btDpdFXm3BIRtTQckSUiamIWFhZ49dVXMWvWLOh0Otx///3Iz8/HkSNHYGlpCU9PTwDAO++8Azs7Ozg5OeGtt96Cvb09hg0bBgCYM2cOevbsiXfffRejRo1CeHg4li1bhq+++goA4OXlhfHjx2PSpElYunQp/Pz8kJiYiMzMTIwcOVKqUycialAMskREEnj33Xfh4OCA0NBQxMfHw9raGj169MCbb76p/9X+hx9+iBkzZuDSpUvw9/fH9u3boVQqAQA9evTA+vXrMX/+fLz77rtwcXHBO++8gwkTJug/4+uvv8abb76Jl19+GdevX4eHhwfefPNNKU6XiKhRcNUCIqJm5uaKArm5ubC2tpa6HCKiZotzZImIiIjIIDHIEhEREZFB4tQCIiIiIjJIHJElIiIiIoPEIEtEREREBolBloiIiIgMEoMsERERERkkBlkiIiIiMkgMskRERERkkBhkiYiIiMggMcgSERERkUFikCUiIiIig/T/BP85RHdShnkAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 700x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "print(f'accuracy after training: {accuracy():.3f}')\n",
    "\n",
    "plt.figure(figsize=(7, 4))\n",
    "plt.plot(losses)\n",
    "plt.xlabel('epoch')\n",
    "plt.ylabel('training loss')\n",
    "plt.title('Surrogate-gradient SNN training')\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa7a1dbe",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "- A spiking network is a recurrent network in time; train it with backpropagation through time.\n",
    "- The non-differentiable spike is handled by a **surrogate gradient**, supplied to the neuron\n",
    "  model as `spk_fun=braintools.surrogate.ReluGrad()` (other surrogates are available).\n",
    "- The loop over time is a `brainstate.transform.for_loop`; `grad` differentiates through it, and a\n",
    "  `braintools.optim` optimizer applies the updates — exactly the [training pattern from the core\n",
    "  track](../core/07_training_and_metrics.ipynb), now unrolled over time.\n",
    "\n",
    "### See also\n",
    "\n",
    "- [Building a spiking neural network](04_building_an_snn.ipynb) — simulating (not training) an SNN.\n",
    "- [Training and metrics](../core/07_training_and_metrics.ipynb) — the underlying optimization loop.\n",
    "- The [brain-dynamics gallery](../../examples/brain_dynamics/index.rst) — complete SNN applications."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
