# 培养神经元闭环奖惩训练仿真(DishBrain in silico) **Repository Path**: az13js/dishbrain-sim ## Basic Information - **Project Name**: 培养神经元闭环奖惩训练仿真(DishBrain in silico) - **Description**: 基于 Brian2 的 DishBrain 式仿真:200 个 LIF 神经元网络接收感觉刺激、输出放电,闭环系统按解码表现发放可预测奖励与不可预测惩罚,通过三因子奖励调制 STDP(Δw = η·E·R)学会目标放电模式。提供学习曲线、光栅图、权重演化 GIF 与自包含 HTML 报告,含完整测试与反作弊设计。 - **Primary Language**: Python - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-27 - **Last Updated**: 2026-09-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # DishBrain 式培养神经元闭环奖惩训练模拟(Brian2) 用纯软件模拟 **DishBrain 式闭环实验**:MEA(多电极阵列)上的 200 个 LIF 培养神经元接收电刺激、 输出放电,闭环系统按解码表现发放**奖励(可预测的规律刺激)**或**惩罚(不可预测的随机扰动)**, 经**奖励调制 STDP(三因子 Δw = η·E·R)**学会目标放电模式。 这不是"训练一个人工神经网络":网络初始权重完全随机、连接结构不利用任务信息, **任务结构只存在于外部刺激与解码规则里**,网络内部的结构性变化全部来自仿真涌现的 STDP 资格迹 + 全局奖惩信号。 --- ## 1. 与真实 DishBrain 设计的对应关系 | 真实 DishBrain(Cortical Labs, 2022) | 本模拟的对应实现 | | --- | --- | | 培养在 MEA 上的皮层神经元 | 200 个 LIF 神经元(80% 兴奋 / 20% 抑制),16 个指定为电极 | | 感觉区电刺激编码"世界状态" | S0-S3 / S4-S7 两组感觉电极分别接收 10Hz 规律脉冲(模式 A / B) | | 运动区放电被解码为"动作" | M0-M3 / M4-M7 两组运动电极,解码规则固定为放电率比较 | | **可预测刺激 = 奖励** | R=+1:感觉区 + 运动区 100Hz **规律**短爆发 200ms | | **不可预测刺激 = 惩罚** | R=-1:感觉区**随机泊松**电流(λ∈[5,50]Hz 随机)500ms | | 闭环实时反馈(≤50ms 延迟) | Brian2 `network_operation` 以 dt=1ms 步进实时判定并发放奖惩信号 | | 突触可塑性实现学习 | 资格迹 E(STDP 前后配对 + τ=200ms 衰减)× 全局奖惩 R | | 无监督、无标签、无反向传播 | 解码函数只接触放电计数;标签仅用于判定之后决定奖惩 | --- ## 2. 架构 ``` ┌──────────────────── 闭环(src/task.py, dt=1ms) ────────────────────┐ │ │ │ ① 模式选择(随机二选一) ② 解码窗口 [1s,2s) 计数 ③ 判定 + 奖惩 │ ▼ ▲ │ ┌───────────────────────┐ │ ▼ │ 刺激调度(plasticity)│ ┌────────┴─────────┐ ┌────────────────────┐ │ 规律/泊松电流脉冲 │ │ spike_count 门控 │ │ R = ±1 → 突触更新 │ └───────────┬───────────┘ └────────▲─────────┘ └─────────┬──────────┘ │ I_stim (16 电极) │ 放电 run_regularly ▼ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ Brian2 网络(src/network.py) │ │ │ 200 LIF 神经元:S0-S7(0-7) M0-M7(8-15) 中间神经元(16-199)│ │ │ 兴奋性突触 3184 条(可塑,U[0.05,0.5] 随机初值)+ 背景 OU 噪声 │ │ │ 抑制性突触 783 条(固定权重,I→I 权重 ×0.3) │◄─────────────────┘ └───────────────────────────────────────────────────────────┘ │ 放电序列 ▼ ┌───────────────────────────────────────────────────────────┐ │ 可视化(src/visualize.py):学习曲线 / 光栅对比 / 权重分布 │ │ / 权重演化 GIF / 自包含 HTML 报告 / --animate 实时动画 │ └───────────────────────────────────────────────────────────┘ ``` **三因子可塑性**(`src/plasticity.py`): ``` on_pre : I_syn_post += w·I_unit ; E -= A₋·apost ; apre += 1 on_post: E += A₊·apre ; apost += 1 run_regularly(dt=10ms): w = clip(w + η·E·R, w_min, w_max) ; E ← E·exp(-Δt/τ_E) ; R ← 0 ``` * `E` 是**资格迹**:pre 先于 post 放电写正、反之为负,以 τ_E=200ms 衰减 → 形成"credit assignment 时间窗"; * `R` 是**全局奖惩信号**,由闭环节点在判定后写入,被更新规则**一次性消费**(保证 Δw = η·E·R 的脉冲语义); * 只有兴奋性突触可塑;抑制性突触权重固定。 --- ## 3. 一键复现(从空目录到出图) ```bash # 0) 进入仓库(若从零开始:git clone 后进入该目录) cd dishbrain-sim # 1) 建虚拟环境并激活(Windows: .venv\Scripts\activate) python -m venv .venv source .venv/bin/activate # 2) 安装依赖(国内源;慢是正常的,耐心等待;仅当报错退出才回退官方 PyPI) pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --default-timeout=300 -r requirements.txt # 3) 跑测试(全部行为契约) pytest tests/ -v # 4) 运行主程序(默认模式:三阶段闭环训练 + 出图,< 5 分钟) python main.py # 可选: python main.py --verbose # DEBUG 级日志 python main.py --animate # 动画演示模式(仅前 50 个 trial) python main.py --self-check # 交付前完整自查清单(含 pytest 与幂等重跑比对) ``` > 说明:本机 pip 无 `ensurepip`(`python3-venv` 包缺失),本次交付是用 > `python -m venv --without-pip` + 从清华镜像下载 pip wheel 解包引导的; > 正常环境直接执行上面第 1 步即可。详见 `docs/tuning_log.md`。 **输出**(每次运行**整体覆盖**,不残留旧状态): | 文件 | 内容 | | --- | --- | | `figures/learning_curve.png` | 准确率 vs trial,标出基线/训练/测试三阶段 + 目标线 | | `figures/raster_before_after.png` | 训练前后同一模式(A)典型 trial 的光栅对比 | | `figures/weights.png` | 权重分布变化 + 分区平均权重矩阵(训练前后及差值) | | `figures/weights_evolution.gif` | 每 50 trial 一帧的权重演化网络图(兴奋绿 / 抑制红) | | `figures/report.html` | 自包含单文件报告(图片全部 base64 内嵌,无外链) | | `results.json` | 全部 trial 明细 + 分阶段准确率 + 参数摘要 + 达标判定 | `figures/` 与 `results.json` **选择入库**(便于直接查看交付结果);若希望仓库只留源码, 把 `.gitignore` 末尾两行注释取消即可。 --- ## 4. 目录结构 ``` dishbrain-sim/ ├── README.md 本文件 ├── AGENTS.md 给后续代理的继承说明(命令/规范/不变量/坑) ├── requirements.txt 锁定版本(brian2==2.9.0 等) ├── .gitignore ├── main.py 流程编排(不放算法)+ CLI + 自查清单 ├── src/ │ ├── config.py 唯一参数来源(dataclass + 防御式校验) │ ├── network.py Brian2 网络构建(LIF、突触矩阵、S/M 电极分区) │ ├── plasticity.py 三因子可塑性(Δw=η·E·R)与奖惩刺激发放 │ ├── task.py trial 编排、刺激时序、闭环解码、结果记录 │ ├── visualize.py 全部图表与动画(不碰仿真逻辑) │ └── utils.py 日志、随机种子、幂等输出、不变量断言 ├── tests/test_all.py 行为契约测试(37 项) ├── docs/tuning_log.md 调参记录(追加式) └── figures/ results.json 运行产物(整体覆盖) ``` --- ## 5. 参数表(`src/config.py` 为唯一来源) ### 5.1 网络与神经元 | 参数 | 取值 | 说明 | | --- | --- | --- | | `n_neurons` | 200 | 兴奋 160 / 抑制 40(80% / 20%) | | `connection_probability` | 0.1 | 全类型随机连接,排除自突触 | | `tau_m` / `v_rest` / `v_threshold` / `v_reset` | 20ms / −70mV / −50mV / −65mV | LIF 膜参数 | | `refractory_period` / `dt` | 2ms / 1ms | 不应期与仿真步长 | | `membrane_resistance` | 100 MΩ | 阈值电流 = 200 pA | | `synaptic_tau` / `synaptic_current_scale` | 10ms / 400pA | 单位权重对应约 1–6 mV EPSP | | `background_mean_current` / `background_noise_std` | 70pA / 75pA | 背景 OU 噪声(波动驱动工作点) | | `inhibitory_weight` / `inhibitory_to_inhibitory_scale` | 1.5 / 0.3 | 抑制权重固定;I→I 按比例缩放(建模选择) | | 初始权重 | U[0.05, 0.5] | **随机采样**,无任何任务结构预设 | ### 5.2 任务、电极与刺激 | 参数 | 取值 | 说明 | | --- | --- | --- | | `trial_duration` | 2s | 每 trial 刺激窗口 | | `decode_window_start/end` | 1s / 2s | 解码窗口(固定) | | `pattern_frequency` / `pattern_pulse_width` | 10Hz / 5ms | 模式 A:S0-S3;模式 B:S4-S7 | | `pattern_current` | 1.5 nA | 单脉冲可靠诱发一个动作电位 | | `feedback_delay` | 10 ms | 判定 → 奖惩刺激起始(不变量:≤ 50ms) | | `reward_burst_frequency/duration` | 100Hz / 200ms | **奖励**:规律短爆发(可预测),作用于 S+M | | `punishment_duration` / `punishment_rate_min/max` | 500ms / 5–50Hz | **惩罚**:随机泊松电流(不可预测),作用于 S | ### 5.3 可塑性与阶段 | 参数 | 取值 | 说明 | | --- | --- | --- | | `learning_rate` (η) | 0.3 | 三因子学习率(见调参记录第 3 轮) | | `eligibility_tau` | 200ms | 资格迹时间常数 | | `stdp_tau_pre/post`, `stdp_a_plus/minus` | 20ms, 0.01/0.0105 | 资格迹写入核 | | `plasticity_update_interval` | 10ms | 权重更新与资格迹衰减步长 | | `weight_min` / `weight_max` | 0.01 / 1.0 | 权重硬边界(不变量) | | `baseline_trials` / `training_trials` / `test_trials` | 50 / 250 / 50 | 见 `docs/tuning_log.md` 第 1、4 轮(原始 100/500/100 超时) | | `probe_interval` | 10 | 训练期每 10 trial 一次无反馈探测 | --- ## 6. 结果解读 固定随机种子(numpy/brian2 均为 20240927)下的一次完整运行(`python main.py`, **总耗时 3 分 30 秒**,其中仿真 892.9s 模型时间 / 350 个 trial): | 指标 | 数值 | | --- | --- | | 基线准确率(阶段 0,50 trial,零反馈) | **52.0%**(26/50) | | 训练准确率(阶段 1,250 trial,闭环奖惩) | 57.2%(143/250) | | ├ 训练中带反馈的 trial(225 个) | 59.1% | | └ 训练中无反馈的探测 trial(25 个) | 40.0%(见下方说明) | | 测试准确率(阶段 2,50 trial,零反馈) | **72.0%**(36/50) | | **提升** | **+20.0 个百分点**(要求 ≥ 15pp,**达标**) | | 正确方向放电偏置(正确组 − 错误组) | 基线 +0.28Hz → 训练 +0.37Hz → 测试 **+0.86Hz** | | 兴奋性突触权重 | 均值 0.2778 → 0.2854,标准差 0.1315 → 0.1370 | | 反馈刺激 | 奖励 133 次 / 惩罚 92 次,延迟恒为 **10.0ms**(上限 50ms) | | 测试阶段判定分布 | 判 A 58%(模式 A 占 50%)——恒定答案最多只能得 50%,故提升来自模式特异性 | **为什么这个结果说明"网络确实学到了东西"而不是统计巧合** 1. **三段式对照**:模式二选一、随机等概率,解码规则固定;基线期与测试期**零反馈**, 两者唯一的差别是中间 250 个 trial 的闭环奖惩,而准确率从 52.0% 升到 72.0%。 测试阶段 5 等分块准确率为 70/70/70/70/80%,非常稳定,不是个别 trial 拉高的。 2. **机制自洽**:奖励只发给"判定正确"的 trial,判定正确意味着被刺激的感觉通路 (S0-S3 或 S4-S7)到对应运动分组(M0-M3 或 M4-M7)的因果放电通路刚刚活跃, 其资格迹 E>0,于是 Δw = η·E·R > 0 被强化;判定错误时 R=−1,同一批通路被削弱。 正反馈使"被刺激分组 → 对应运动分组"的通路逐步占优。 3. **权重层面可见**:`figures/weights.png` 显示权重均值右移(0.2778→0.2854), 分区矩阵中 S0-S3 的输出通路普遍增强(+0.024~+0.059),而 S4-S7→M0-M3 被削弱(−0.032), 与"先纠正初始反向偏置、再强化正确通路"的过程一致。 4. **过程可见**:`figures/learning_curve.png` 下半 panel 显示权重均值在训练期单调上升后进入平台; `figures/weights_evolution.gif` 用它的小图同步展示这一演化。 **关于训练期探测准确率(40%)的说明(如实记录,不作粉饰)** 无反馈探测 trial 每 10 个 trial 一次,共 25 个(二项标准误 ~10%)。其轨迹为 `0% → 60% → 40% → 40% → 60%`:**前 5 个探测全部判错**——初始随机连接恰好给了一个 *反向* 的刺激-运动映射,训练前期必须先削弱这条已经"占优"的错误通路(惩罚起作用), 期间探测准确率会掉到随机水平以下;随后正确通路才被奖励强化起来。 25 个探测样本量小、且大量落在这一"纠偏期",因此均值偏低。 达标判据以 **50 个 trial 的独立零反馈测试阶段**为准(+20.0pp); 探测曲线作为过程参考,不用于达标判定。 **调参要点(详见 `docs/tuning_log.md`)** - 学习率存在明显最优区间:η=0.2 学不动(M 区脉冲太少、资格迹样本不足); η=0.3 最佳;η≥0.5 出现**非特异漂移**(网络退化成"恒判某一个答案",准确率回落到 50%)。 - 网络工作点必须落在"静默 ↔ 饱和"双稳态之间的窄窗口(平均放电率 1–10Hz)。 - trial 数从 100/500/100 降到 50/250/50:本机只有 numpy 后端(系统缺 Python 头文件, 无法启用 cython/C++ 后端),原始规模需约 10 分钟,违反 < 5 分钟硬约束。 ## 7. 反作弊与不变量 **反作弊(违反即回滚)** * 解码规则固定在 `src/task.py::decode_trial`:`mean(M0-M3) > mean(M4-M7) → A,否则 B`; 该函数的**签名与函数体都不接触任务标签/模式标识**,并有测试 `test_decode_has_no_access_to_labels` 扫描源码守门。 * 初始权重由固定种子的均匀分布 `U[0.05, 0.5]` 随机采样(`src/network.py`), 不存在任何手工构造的 S→M 定向连接;连接矩阵也来自随机概率 0.1。 * 调参上限 10 轮,只允许 η / 网络规模 / trial 数 / 刺激参数,全部记录在 `docs/tuning_log.md`。 * 不为通过测试而删改或弱化断言。 **仿真不变量(全程断言,违反立即报错,禁止静默容错)** * 兴奋性突触权重始终 ∈ [`weight_min`, `weight_max`]:每个 trial 结束复核 + 更新式内 `clip`; * 奖惩刺激延迟 ≤ 50ms 模拟时间:`feedback_delay=10ms`,且发放前用实际脉冲时刻复核; * 基线 / 测试 / 探测 trial **零反馈刺激**:`feedback_enabled=False` 时完全跳过反馈窗口; * 闭环回调步数与预期不符立即报错(防止调度与仿真步进错位); * 非奖惩时刻存在未消费的 R 立即报错; * numpy 与 brian2 随机种子固定于 config,运行期不得更改。 --- ## 8. 交付前自查清单 运行 `python main.py --self-check` 会在终端逐项打印;默认模式打印可自动复核的部分。 - [x] `pytest tests/` 全绿(37 项,无被删改的断言) - [x] 业务代码无魔法数字(扫描 `src/*.py` + `main.py`,0 处;命名常量与 config 除外) - [x] 日志统一用 `logging`(无 `print`),INFO 记阶段/trial 判定/奖惩发放,DEBUG 记细节 - [x] 不变量断言全程生效(见 §7) - [x] `requirements.txt` 锁版本;README 给出建 venv → 装依赖 → 测试 → 运行的完整命令 - [x] 重跑幂等(`--self-check` 实跑两遍并比对产物 SHA256) - [x] 逐张核对 `figures/` 5 个交付文件(坐标轴/图例/阶段标注/数据可见性/前后对比/GIF 帧数/HTML 无裂链) --- ## 9. 已知限制 1. **仿真后端**:本机缺 Python 开发头文件(`Python.h`)且无 sudo,Brian2 只能使用 `numpy` 代码生成后端(约 218µs/步)。若你的环境有 C++ 工具链, `prefs.codegen.target='cython'` 可获得 10 倍以上加速,届时可把 trial 数恢复到 X.md 原始的 100/500/100(原始规模在本机约需 10 分钟)。 2. **奖惩刺激与权重更新解耦**:按 X.md 语义,R 在判定瞬间写入并消费,奖惩刺激随后发放; 刺激本身的资格迹在下一个 trial 判定前已衰减到 e⁻¹⁰ 量级,不参与信用分配。 3. **LIF 是电流型突触**:抑制电流无反转电位,强抑制可把膜电位压到远低于静息;这属于 简化建模,参数已按 E/I 平衡标定(见 `docs/tuning_log.md` 第 0 阶段)。 4. **统计功效**:测试阶段 60 个 trial,准确率的标准误约 6.5 个百分点;提升结论以 `+15pp` 为目标线,并以学习曲线与权重分布作为旁证。