对应实验:lessons/00_environment.py
很多教程一上来就让你输入 pip install,然后立即创建神经元。这样虽然可能跑得起来,但一旦报错,你会完全不知道问题属于哪一层。
这一章先建立一张“运行地图”:
- Python 负责执行你写的教程脚本。
- Brian2 负责理解神经元方程、检查单位并组织仿真。
- NumPy、Cython 或 C++ 负责执行 Brian2 生成的数值计算代码。
- 编译器只在选择需要编译的后端时才需要。
你应该在本章结束后说清楚:
- “Brian2 版本”和“Python 版本”为什么是两回事。
device与codegen target分别控制什么。- 没有 C++ 编译器为什么仍然可以学习本教程。
- 环境错误应该从哪一层开始排查。
下面这类 Brian2 代码看上去像普通 Python:
neurons = NeuronGroup(
100,
"dv/dt = -v/(10*ms) : 1",
method="exact",
)
run(100*ms)但 Brian2 并不是让 Python 在每个时间步里逐个神经元执行一次字符串。
大致过程是:
- Python 创建
NeuronGroup对象。 - Brian2 解析方程字符串。
- Brian2 检查变量和物理单位。
- Brian2 根据积分方法生成更新状态的代码。
- 所选执行后端运行生成的代码。
Monitor把你要求的数据记录回来。
因此,Brian2 同时扮演了三种角色:
- 模型描述器:你写方程、阈值、重置和突触事件。
- 实验调度器:它决定每个时间步中谁先更新、谁后更新。
- 代码生成器:它把高层模型翻译成可执行计算。
理解这三层后,你就不会把所有错误都归结为“Python 写错了”。
实验开头导入:
import brian2
import numpyPython 是语言和解释器。它负责:
- 导入模块。
- 创建 Brian2 对象。
- 执行实验控制代码。
- 调用绘图和文件保存逻辑。
不同 Python 版本会影响哪些 Brian2 版本和二进制依赖可以安装。
Brian2 是脉冲神经网络仿真框架。它负责:
- 解析微分方程。
- 检查单位。
- 创建神经元、突触、输入源和监视器。
- 生成并调度仿真代码。
NumPy 是 Python 中的数值数组库。Brian2 的 NumPy 后端会用向量化数组运算更新整群神经元。
“NumPy 后端”不意味着模型变成了普通 NumPy 教程。模型仍由 Brian2 定义,只是底层计算交给 NumPy 执行。
实验代码:
print("Python:", platform.python_version())
print("Brian2:", brian2.__version__)
print("NumPy:", numpy.__version__)版本信息是实验可复现性的一部分。
同一份代码在不同机器上失败,常见原因不是模型思想不同,而是:
- Python 版本不同。
- Brian2 API 或依赖要求发生变化。
- NumPy 删除了旧接口。
- 编译器或操作系统不同。
本项目当前验证环境是:
Python: 3.11.9
Brian2: 2.9.0
NumPy: 1.26.4
它们不是“永远最好的版本”,而是一组已经共同验证过的版本。
代码:
from brian2 import get_device
print("当前设备:", type(get_device()).__name__)这里的“设备”不是显卡型号,而是 Brian2 组织整个仿真的方式。
默认的 RuntimeDevice 表示:
- 在当前 Python 进程中创建对象。
- 调用
run()时立即生成并执行所需代码。 - 运行结束后立刻可以在 Python 中读取结果。
以后会接触 cpp_standalone。它的思路不同:
- Python 先描述完整实验。
- Brian2 生成一个独立 C++ 工程。
- 编译并运行该工程。
- 再把结果读回。
可以把两者理解为:
- Runtime:一边写实验,一边运行。
- Standalone:先把整份实验编译成独立程序,再运行。
初学阶段使用 Runtime 更容易调试。
代码:
from brian2 import prefs
print("默认代码生成目标:", prefs.codegen.target)codegen target 决定 Runtime 模式下,一段状态更新代码由谁执行。
常见目标:
| 目标 | 大致机制 | 优点 | 代价 |
|---|---|---|---|
numpy |
生成 NumPy 数组运算 | 无需编译器,启动简单 | 大模型可能较慢 |
cython |
生成并编译 Cython/C++ 扩展 | 循环密集型计算通常更快 | 首次运行需要编译 |
auto |
Brian2 自动选择 | 配置省事 | 环境问题不够直观 |
本教程的共享工具把正式实验固定到 numpy,目的是先减少环境变量。等你确认模型正确,再学习性能优化。
这体现一个重要工程原则:
先让模型正确、可解释、可复现,再让它更快。
代码:
compiler = shutil.which("cl") or shutil.which("g++")shutil.which() 会在系统的 PATH 中寻找可执行程序:
cl通常是 Microsoft C/C++ 编译器。g++通常是 GNU C++ 编译器。
如果打印“未发现”,你仍然可以:
- 使用 NumPy 目标。
- 创建神经元和突触。
- 运行本教程的全部默认示例。
- 画图并分析结果。
你暂时不能顺利使用的主要是:
- Cython 目标的本地编译。
- C++ standalone。
- 依赖本地编译器的加速方案。
所以“没有编译器”和“Brian2 完全不能运行”是两件不同的事。
import platform
import shutilplatform用来查询 Python 和操作系统信息。shutil.which用来查询命令是否存在。
import brian2
import numpy
from brian2 import get_device, prefs这里没有使用 from brian2 import *,因为本章只需要两个明确对象。后续实验为了让方程代码紧凑,会使用星号导入;在大型工程中通常更推荐显式导入。
其余代码只读取环境,不会启动仿真。因此本章:
- 不需要
start_scope()。 - 不需要
NeuronGroup。 - 不需要
run()。
在仓库根目录执行:
.\.venv\Scripts\python.exe lessons\00_environment.py不要在 lessons 目录内部运行整套教程。后面的脚本会按仓库根目录寻找共享工具和输出目录。
输出示例:
Python: 3.11.9
Brian2: 2.9.0
NumPy: 1.26.4
当前设备: RuntimeDevice
默认代码生成目标: auto
C++ 编译器: 未发现
逐项解释:
- 前三行回答“当前依赖组合是什么”。
RuntimeDevice回答“仿真是在当前 Python 进程运行,还是先生成独立工程”。auto回答“Runtime 代码优先尝试什么执行目标”。- “未发现”只说明当前终端找不到 C++ 编译器。
导入成功只证明基础模块可加载。某些功能还可能需要:
- SciPy。
- Matplotlib。
- C++ 编译器。
- 与当前 Python 匹配的二进制包。
因此环境检查要分功能层次进行。
小实验中,编译时间可能比仿真本身还长。开发阶段使用 NumPy,最终批量实验再切换后端,通常更合理。
科研代码首先追求的是可复现。未经验证地升级某一个依赖,可能破坏整组兼容关系。
- 运行脚本,把输出保存到实验记录中。
- 在 Python 中执行
import sys; print(sys.executable),确认当前解释器来自.venv。 - 执行
where.exe python,观察系统可能同时存在多少个 Python。 - 把
prefs.codegen.target临时打印为"numpy",理解“偏好设置”和“当前检测结果”的区别。 - 查找本机是否存在
clang++,但不要急着修改教程配置。
你还没有模拟神经元,但已经建立了后续排错所需的地图:
教程脚本
-> Brian2 解析模型和调度实验
-> 代码生成目标执行数值更新
-> Monitor 返回结果
下一章开始建立第一个神经元。届时最重要的问题不是记住 API,而是理解:一条连续微分方程怎样与离散脉冲事件组合起来。