Skip to content

Latest commit

 

History

History
458 lines (295 loc) · 10.5 KB

File metadata and controls

458 lines (295 loc) · 10.5 KB

第 4 章:四种输入机制分别适合什么实验

对应实验:lessons/04_inputs.py

输出图片:outputs/04_inputs.png

这一章解决什么问题

神经元很少在完全没有输入的情况下工作。输入可能来自:

  • 一个随时间变化的外部刺激。
  • 大量未显式建模的随机背景神经元。
  • 已知精确时刻的实验提示。
  • 另一组真正参与网络的脉冲神经元。

Brian2 提供不同对象,不是为了让 API 显得丰富,而是因为这些输入在建模含义和计算代价上不同。

本章比较:

机制 核心含义
TimedArray 已知的时间函数
PoissonInput 高效聚合的随机背景输入
SpikeGeneratorGroup 精确指定的脉冲序列
PoissonGroup + Synapses 可作为网络节点的随机脉冲源

1. 先理解靶神经元在做什么

方程:

dv/dt = (-v + stimulus(t)) / (10*ms)

它表示 v 以 10 ms 时间常数追踪当前刺激值 stimulus(t)

如果刺激在一段时间内固定为 S:

dv/dt = (S-v)/tau

那么 v 会指数靠近 S。

但本章还有三类脉冲输入会瞬时增加 v。因此完整轨迹由两种机制叠加:

  • 连续追踪时间刺激。
  • 离散脉冲造成瞬时跳变。

2. TimedArray:把离散采样值变成时间函数

代码:

stimulus = TimedArray(
    [0.1, 0.9, 0.2, 1.1, 0.1],
    dt=20 * ms,
)

它定义一个分段时间序列:

时间区间 stimulus(t)
0 至 20 ms 0.1
20 至 40 ms 0.9
40 至 60 ms 0.2
60 至 80 ms 1.1
80 至 100 ms 0.1

因此:

stimulus(45 * ms)

返回 0.2,因为 45 ms 落在第三个区间。

TimedArray 适合什么

  • 实验测得的刺激波形。
  • 分阶段任务信号。
  • 已知采样率的外部输入。
  • 多次实验中完全相同的时间协议。

它不是什么

TimedArray 不会自己产生脉冲,也不是一个神经元群。它只是一个可在方程中按时间查询的函数。

3. 为什么在方程中写 stimulus(t)

dv/dt = (-v + stimulus(t))/(10*ms) : 1

t 是 Brian2 提供的当前仿真时间。

每次更新状态时,方程会查询当前时间对应的刺激值。

如果写成普通 Python:

current = stimulus(45*ms)

只会在 Python 执行到这一行时得到一个固定值。把 stimulus(t) 放在方程中,才会让它随仿真时间变化。

4. Poisson 过程到底表示什么

后三种输入都涉及脉冲。先理解泊松脉冲的含义。

对频率 r 的齐次泊松过程,在很短的时间 dt 内:

发生一次事件的概率约为 r * dt

例如 r=10 Hz、dt=0.1 ms:

r * dt
= 10 / second * 0.0001 second
= 0.001

即每个时间步约 0.1% 概率发生事件。

长期平均频率接近 10 Hz,但:

  • 事件间隔不固定。
  • 短时间内可能一个也没有。
  • 也可能偶然连续出现。

泊松输入常用作“缺乏详细结构的随机背景”,不是说真实神经元一定严格服从泊松过程。

5. PoissonInput:不显式创建所有背景突触

代码:

poisson_input = PoissonInput(
    neurons,
    target_var="v",
    N=20,
    rate=10 * Hz,
    weight=0.04,
)

含义是每个靶神经元接收:

  • 20 路独立泊松输入。
  • 每一路平均频率 10 Hz。
  • 每个到达事件让 v 增加 0.04。

单个靶神经元每秒期望接收的总事件数约为:

20 * 10 Hz = 200 events/s

若忽略阈值、衰减和事件重叠,期望注入总量尺度约为:

200 * 0.04 = 8 / second

这只是理解驱动强度的粗略尺度,不等于 v 每秒净增加 8,因为 v 同时在衰减并被 reset。

为什么它高效

如果背景神经元本身没有需要记录或连接的状态,就没有必要显式创建:

  • 20 个源神经元。
  • 20 倍靶数量的突触。

PoissonInput 直接向目标变量添加统计等价的随机事件,节省对象和内存。

它的限制

你不能像操作普通 Synapses 那样:

  • 记录每个背景源的脉冲编号。
  • 为每条背景连接设置复杂可塑性。
  • 让同一个源再连接到其他网络对象。

6. SpikeGeneratorGroup:精确控制事件时刻

代码:

cue = SpikeGeneratorGroup(
    1,
    indices=[0, 0],
    times=[35, 75] * ms,
)

同一个 0 号提示源在 35 ms 和 75 ms 各放电一次。

随后:

cue_synapse = Synapses(
    cue,
    neurons,
    on_pre="v_post += 0.3",
)
cue_synapse.connect()

因为 cue 有 1 个神经元,neurons 有 20 个,无参数 connect() 会建立 20 条连接:

cue[0] -> neurons[0]
cue[0] -> neurons[1]
...
cue[0] -> neurons[19]

所以两个提示时刻会同时推动全部 20 个神经元。

它适合什么

  • 重放实验记录的脉冲。
  • 精确提示或同步信号。
  • 单元测试突触响应。
  • 构造已知输入输出关系。

7. PoissonGroup:把随机源作为真正的网络节点

代码:

background = PoissonGroup(
    5,
    rates="5*Hz + i*2*Hz",
)

创建 5 个源神经元。i 从 0 到 4,所以频率为:

i rate
0 5 Hz
1 7 Hz
2 9 Hz
3 11 Hz
4 13 Hz

这些源没有膜电位方程,但会产生可被 SpikeMonitor 记录、可通过普通 Synapses 传播的脉冲。

连接:

background_synapse.connect(p=0.25)

每个可能的 source-target 对以 25% 概率建立连接。

共有:

5 * 20 = 100

个可能连接,实际连接数会随机波动,期望约 25 条。

与 PoissonInput 的关键区别

PoissonGroup 的源是显式对象。因此你可以:

  • 记录每个源的脉冲。
  • 设置不同源频率。
  • 使用复杂突触模型。
  • 让源连接多个目标群体。
  • 在突触上加入延迟或可塑性。

代价是需要创建和调度更多对象。

8. 四种输入在同一个神经元上怎样叠加

本例的 v 同时受到:

  1. TimedArray 连续驱动。
  2. PoissonInput 小而频繁的随机跳变。
  3. cue 在 35 和 75 ms 的同步大跳变。
  4. background 稀疏随机突触跳变。
  5. 自身漏衰减。
  6. 达到阈值后的 reset。

不要试图把图上的每一个小折点都只归因于一种机制。正确方法是逐层建立对照:

  • 先只保留 TimedArray。
  • 再加入 PoissonInput。
  • 再加入 cue。
  • 最后加入 PoissonGroup。

这叫消融实验:逐项移除或加入机制,判断它对结果的贡献。

9. 为什么只记录 0 号神经元的 v

state = StateMonitor(neurons, "v", record=[0])

20 个神经元都接收相同 TimedArray 和 cue,但随机输入不同。

记录一个代表性神经元可以:

  • 减少数据量。
  • 让曲线容易阅读。
  • 与全群体脉冲栅格图互补。

但单个神经元不能代表整个群体统计。若要判断群体差异,应增加:

  • 多个代表神经元。
  • 群体平均。
  • 放电率分布。

10. 运行前应该预测什么

0 至 20 ms

TimedArray 只有 0.1,主要靠随机背景积累,活动较弱。

20 至 40 ms

刺激升至 0.9,v 更接近阈值。35 ms 的 cue 再让所有神经元增加 0.3,容易形成一批同步脉冲。

40 至 60 ms

刺激降至 0.2,活动应减弱。

60 至 80 ms

刺激升至 1.1,本身已高于阈值平衡点;75 ms cue 进一步推动群体,活动应最强。

80 至 100 ms

刺激回到 0.1,活动再次减弱。

具体脉冲数受随机种子影响,但时间结构应与刺激协议对应。

11. 怎样读两层图

上图:

  • 只显示 0 号神经元 v。
  • 可观察连续追踪、随机跳变、cue 跳变和 reset。

下图:

  • 每个点是任意神经元的一次脉冲。
  • 可观察 35 ms 和 75 ms 附近是否形成同步活动。
  • 可比较五个刺激阶段的活动密度。

把两图结合:

  • 上图解释单个状态如何接近阈值。
  • 下图验证这种机制是否在群体中形成脉冲。

12. 为什么精确总脉冲数不是本章重点

脚本打印:

print("总脉冲数:", spikes.num_spikes)

它用于快速确认网络是否有活动。

但总脉冲数把时间结构压缩成一个数字,无法回答:

  • 脉冲集中在哪个刺激阶段。
  • cue 是否造成同步。
  • 哪些神经元最活跃。

统计量必须与问题匹配。总数适合健康检查,不足以完成机制分析。

13. 常见误区

误区一:PoissonInput 的 N 是靶神经元数量

N 是每个靶神经元接收的独立泊松输入源数量。

误区二:PoissonGroup 和 PoissonInput 完全等价

它们可以产生相似统计输入,但前者是显式源对象,后者是聚合输入机制。

误区三:TimedArray 会在采样点之间自动平滑插值

本例按给定 dt 索引离散值,应把它理解为分段时间协议,而不是默认连续插值曲线。

误区四:connect(p=0.25) 恰好创建 25 条连接

25 是期望值。每次随机连接的实际数量会波动。

误区五:所有神经元接收相同输入,所以行为相同

它们共享 TimedArray 和 cue,但随机泊松事件及随机连接不同。

14. 动手实验

实验 A:逐个关闭输入源

依次注释掉:

  • PoissonInput。
  • cue 突触。
  • background 突触。

每次预测图会失去什么特征。

实验 B:只改变 cue 时刻

把 cue 放在弱刺激区间和强刺激区间,比较同样 0.3 增量造成的脉冲数。

这能展示“同样输入的效果依赖当前网络状态”。

实验 C:保持总泊松频率近似不变

比较:

N=20, rate=10 Hz, weight=0.04
N=200, rate=1 Hz, weight=0.04
N=20, rate=20 Hz, weight=0.02

虽然某些平均量接近,波动结构未必相同。

实验 D:记录 background

background 添加 SpikeMonitor,把源脉冲与 target 响应放在同一时间轴分析。

实验 E:把连接概率从 0.25 改到 1

预测不同靶神经元之间的输入差异会怎样变化。

本章小结

选择输入对象前,先问:

输入是连续时间函数还是离散脉冲?
脉冲时刻已知还是随机?
是否需要记录每个源?
是否需要复杂突触和连接结构?

下一章会专门处理“怎样记录”。你会看到 StateMonitor、SpikeMonitor 和 PopulationRateMonitor 记录的是三种不同层次的数据,不能互相替代。

官方参考