Skip to content

Latest commit

 

History

History
436 lines (278 loc) · 9.83 KB

File metadata and controls

436 lines (278 loc) · 9.83 KB

第 3 章:脉冲怎样沿着突触从一个神经元影响另一个神经元

对应实验:lessons/03_synapses.py

输出图片:outputs/03_synapses.png

这一章解决什么问题

前两章的神经元彼此独立。即使把 100 个神经元画在同一张图上,它们也没有交换信息。

从这一章开始,我们加入网络最核心的关系:

突触前神经元产生脉冲,脉冲经过一段延迟到达突触,随后按权重改变突触后神经元。

你需要理解五个角色:

  1. source:脉冲从哪里来。
  2. target:脉冲要影响谁。
  3. Synapses:连接规则和突触状态。
  4. on_pre:前神经元脉冲到达时执行什么。
  5. delay:从源脉冲产生到突触事件执行要等待多久。

1. 为什么突触适合建模为“事件”

神经元内部状态通常持续变化,因此适合微分方程。

脉冲则只在少数时刻发生。如果每个时间步都检查每一条突触并计算一次完整作用,会浪费大量计算。

Brian2 的事件驱动思想是:

没有脉冲到达 -> 这条突触不执行 on_pre
脉冲到达     -> 执行一次 on_pre 中的语句

本章的突触规则是:

on_pre="v_post += w"

含义是:

当前突触的前神经元脉冲到达时,把该突触的权重 w 加到后神经元的 v。

这是一种瞬时跳变,不是连续微分方程。

2. 为什么先使用人工脉冲源

代码:

source = SpikeGeneratorGroup(
    5,
    indices=[0, 1, 2, 3, 4],
    times=[5, 10, 15, 20, 25] * ms,
)

SpikeGeneratorGroup 不模拟膜电位。它直接按照给定计划发出脉冲。

两个数组必须一一对应:

列表位置 indices times 含义
0 0 5 ms 0 号源神经元在 5 ms 放电
1 1 10 ms 1 号源神经元在 10 ms 放电
2 2 15 ms 2 号源神经元在 15 ms 放电
3 3 20 ms 3 号源神经元在 20 ms 放电
4 4 25 ms 4 号源神经元在 25 ms 放电

为什么不让源神经元自己积分到阈值?

因为本章只想研究:

  • 权重改变响应幅度。
  • 延迟改变响应时刻。

使用精确已知的脉冲时刻,可以把源神经元自身动力学这个变量暂时排除。

这是一种很重要的建模方法:为了理解一个机制,先控制其他机制。

3. 靶神经元为什么只会衰减

代码:

target = NeuronGroup(
    5,
    "dv/dt = -v/(8*ms) : 1",
    method="exact",
)

方程解析解是:

v(t) = v(0) * exp(-t / 8 ms)

它的平衡点是 0。

如果没有突触输入:

  • 初始 v 默认是 0。
  • 导数也是 0。
  • v 会一直保持 0。

突触事件到达时,v 被突然抬高。之后它再按指数衰减回 0。

因此图上的每个响应都容易解释:

突然上跳:突触事件
缓慢下降:靶神经元自身的漏衰减

4. 创建 Synapses 只是定义“可能怎样连接”

代码:

synapses = Synapses(
    source,
    target,
    "w : 1",
    on_pre="v_post += w",
)

四个部分:

source

突触前群体。事件从这里发出。

target

突触后群体。状态在这里被改变。

"w : 1"

声明每条突触自己的无量纲变量 w。

注意 w 属于突触,不属于 source 或 target。两条突触可以连接同一对群体,却有不同权重。

on_pre

定义突触前脉冲到达时执行的动作。

此时还没有真正创建任何连接。Synapses(...) 更像是定义了一种连接模板。

5. connect() 才决定哪些神经元真正相连

代码:

synapses.connect(
    i=[0, 1, 2, 3, 4],
    j=[0, 1, 2, 3, 4],
)

在 Synapses 中:

  • i 表示突触前神经元编号。
  • j 表示突触后神经元编号。

两个列表按位置配对:

i=0 -> j=0
i=1 -> j=1
i=2 -> j=2
i=3 -> j=3
i=4 -> j=4

因此只建立 5 条一对一连接,不是 25 条全连接。

如果直接写:

synapses.connect()

默认会连接每个 source 到每个 target,得到:

5 * 5 = 25 条连接

连接结构决定谁能影响谁,是网络模型的一部分,不能只把它当成程序配置。

6. v_post 后缀从哪里来

代码:

on_pre="v_post += w"

Brian2 用后缀区分变量属于哪一侧:

  • v_pre:突触前神经元的 v。
  • v_post:突触后神经元的 v。

本章 source 是 SpikeGeneratorGroup,它没有 v;target 才有 v,所以我们使用 v_post

如果不写 _post,Brian2 可能把名称理解为突触自身变量,或因为名称解析不清而报错。

类似地,在可塑性规则中经常会看到:

x_pre
y_post

这些后缀让一条突触规则可以同时读取两端状态。

7. 字符串赋值怎样给每条突触不同权重

代码:

synapses.w = "0.2 + 0.1*i"

Brian2 对每条已创建的突触计算表达式。

因为本章每条突触的 i 分别是 0 到 4:

i w
0 0.2
1 0.3
2 0.4
3 0.5
4 0.6

所以越靠后的 source 神经元,其突触造成的 v 跳变越大。

这里使用 i 而不是 j,在一对一连接中结果相同。但在一般网络中二者含义不同:

  • 依赖 i:同一个前神经元发出的连接具有某种共同结构。
  • 依赖 j:指向某个后神经元的连接具有某种共同结构。

8. 延迟改变的是到达时间,不是源脉冲时间

代码:

synapses.delay = "i*ms"

得到:

i 源脉冲时刻 delay on_pre 执行时刻
0 5 ms 0 ms 5 ms
1 10 ms 1 ms 11 ms
2 15 ms 2 ms 17 ms
3 20 ms 3 ms 23 ms
4 25 ms 4 ms 29 ms

SpikeGeneratorGroup 仍在原定时刻发出脉冲。delay 表示突触事件排队多久后才执行。

因此:

  • SpikeMonitor(source) 会看到原始发放时刻。
  • StateMonitor(target) 会看到延迟后的响应时刻。

这一区分在研究传播时间、同步和振荡时非常重要。

9. 运行前完整预测五条曲线

对于第 k 条一对一连接:

  1. 5 + 6k ms 左右发生跳变。
  2. 跳变高度为 0.2 + 0.1k
  3. 之后按 8 ms 时间常数衰减。

更明确地说:

target 响应时刻 瞬时增量
0 5 ms 0.2
1 11 ms 0.3
2 17 ms 0.4
3 23 ms 0.5
4 29 ms 0.6

由于每个 target 只接收一次输入,五条曲线不会发生多次叠加。

10. 为什么图上可能不是数学意义上的完美竖线

状态只在离散调度点被记录。一次时间步中可能依次发生:

  1. 状态更新。
  2. 阈值检测。
  3. 突触事件。
  4. 状态记录。

具体记录到事件前还是事件后的值取决于对象调度位置。

所以绘图时不要过度解读一个时间步内的像素差异。真正重要的是:

  • 响应顺序。
  • 延迟差。
  • 跳变幅度。
  • 衰减时间尺度。

需要精确分析调度时,可以查看对象的 whenorder,第 7 章会专门讨论。

11. len(synapses) 为什么等于 5

len(synapses) 返回实际创建的突触数量。

它不是:

  • source 神经元数量。
  • target 神经元数量。
  • 可能连接数量。

在随机连接中,实际数量会围绕期望值波动。记录它是检查网络结构的第一步。

12. 突触权重不是天然等于生物电压

本章 v 和 w 都无量纲:

w : 1
v : 1

所以:

v_post += w

单位一致。

更生物物理的模型可能使用:

  • 电流型突触:事件改变突触电流。
  • 电导型突触:事件改变电导,再由反转电位计算电流。
  • 带上升和衰减过程的突触:事件改变中间状态,状态再连续演化。

本章选择瞬时无量纲跳变,是为了先隔离连接、权重和延迟的基本语义。

13. 常见误区

误区一:创建 Synapses 后就已经全连接

必须调用 connect() 才会真正创建连接。

误区二:w 属于前神经元

w 默认是每条突触的变量。一个前神经元发出的不同突触可以有不同 w。

误区三:delay 会推迟前神经元放电

delay 只推迟突触事件到达,不改变源脉冲记录。

误区四:曲线高度等于当前权重的永久影响

事件只瞬间增加 v,之后 v 会继续按自身方程衰减。

误区五:一对一连接写成 connect() 也一样

不一样。无参数 connect() 是全连接。

14. 动手实验

实验 A:去掉延迟

设置:

synapses.delay = 0*ms

预测五条曲线的跳变时刻,再运行验证。

实验 B:让所有源连接所有靶

改为:

synapses.connect()

需要同时重新思考权重表达式与延迟表达式。观察每个 target 会接收多少次输入。

实验 C:改成依赖 j 的权重

synapses.w = "0.2 + 0.1*j"

在一对一连接中结果不变。然后改成全连接,观察 i 依赖和 j 依赖的区别。

实验 D:制造输入叠加

让多个 source 连接到同一个 target。预测在前一次响应尚未衰减完时,下一次输入如何叠加。

实验 E:加入阈值

给 target 添加:

threshold="v > 0.7"
reset="v = 0"

判断哪些权重或输入组合足以使 target 放电。

本章小结

突触传播可以拆成四个问题:

结构:i 连接到哪个 j
状态:每条突触有什么 w
事件:前脉冲到达时执行什么 on_pre
时间:事件延迟多久执行

下一章不再只使用一组手工脉冲,而是比较四种常见输入:时间函数、聚合泊松输入、精确脉冲序列和显式泊松神经元群。

官方参考