教程 2:任务生成与 CANN 仿真¶
本教程将指导您如何使用 Task 模块生成任务数据,并使用 CANN 模型运行仿真。
1. 任务模块概述¶
CANNs 的 Task 模块用于生成实验范式与输入数据。Task 与 Model 的关系如下:
Task: 生成外部刺激序列(输入数据)
Model: 消费输入数据,并在仿真循环中运行
任务类别¶
CANNs 提供两类主要任务:
追踪任务:
PopulationCoding1D/2D——群体编码TemplateMatching1D/2D——模板匹配SmoothTracking1D/2D——平滑追踪
导航任务:
ClosedLoopNavigation——闭环导航OpenLoopNavigation——开环导航
Note
本教程以最简单的 PopulationCoding1D 为例进行演示。其他任务的使用模式类似,仅初始化参数不同。我们将在后续教程中展示更多任务类型。
2. PopulationCoding1D 详解¶
PopulationCoding1D 是一个简单的群体编码任务:无刺激 → 刺激 → 无刺激。该任务用于测试网络形成并维持记忆波包(bump)的能力。
2.1 导入与创建任务¶
[ ]:
from canns.task.tracking import PopulationCoding1D
from canns.models.basic import CANN1D
import brainpy.math as bm # :cite:p:`wang2023brainpy`
# First create model instance
bm.set_dt(0.1)
model = CANN1D(num=256, tau=1.0, k=8.1, a=0.5, A=10, J0=4.0)
# Create task
task = PopulationCoding1D(
cann_instance=model, # CANN model instance
before_duration=10.0, # Duration before stimulus
after_duration=50.0, # Duration after stimulus
Iext=1.0, # Stimulus position in feature space
duration=10.0, # Stimulus duration
time_step=0.1, # Time step
)
2.2 参数说明¶
参数 |
类型 |
说明 |
|---|---|---|
|
BaseCANN1D |
CANN 模型实例——任务将调用其 |
|
float |
刺激呈现前的持续时间(无输入阶段) |
|
float |
刺激结束后持续时间(用于观察波包维持) |
|
float |
特征空间中的刺激位置,通常位于 |
|
float |
刺激呈现的持续时间 |
|
float |
仿真时间步长——应与 |
这些参数的重要性:
cann_instance是必需的,因为任务需调用模型的get_stimulus_by_pos()方法以生成合适的刺激before_duration和after_duration用于观察波包的形成与维持过程Iext决定了记忆波包形成的位置所有持续时间均使用与
time_step相同的单位
2.3 获取任务数据¶
创建任务后,调用 get_data() 以生成输入数据并存储于 task.data 中:
[2]:
# Generate task data
task.get_data()
# Access task properties
print(f"Total time steps: {task.total_steps}")
print(f"Total duration: {task.total_duration}")
print(f"Data shape: {task.data.shape}")
<PopulationCoding1D>Generating Task data(No For Loop)
Total time steps: 700
Total duration: 70.0
Data shape: (700, 256)
Important
get_data() 不返回任何值——而是就地修改 task.data。请通过 task.data 访问生成的数据。
3. 使用 bm.for_loop 运行仿真¶
3.1 为何使用 for_loop?¶
BrainPy [18] 提供了 bm.for_loop 以实现高效的仿真循环。相较于 Python 原生 for 循环,它具备以下优势:
JIT 编译:整个循环被编译为高效机器码
GPU 加速:自动利用 GPU 资源
自动向量化:优化内存访问模式
See also
详见 BrainPy 循环教程 了解 for_loop 的详细用法。
3.2 基本用法¶
[3]:
import brainpy.math as bm # :cite:p:`wang2023brainpy`
# Define step function
def run_step(t, inp):
"""
Single simulation step.
Args:
t: Current time step index
inp: Input data at current time step
Returns:
State variables to record
"""
model(inp) # Or model.update(inp)
return model.u.value, model.r.value
# Run simulation using task.data
results = bm.for_loop(
run_step, # Step function
operands=(task.run_steps, task.data), # Number of time steps and input data
progress_bar=10 # Optional progress bar (updates every 10 steps)
)
3.3 处理返回值¶
for_loop 返回的值对应于步长函数的返回值:
[4]:
# results is a tuple of return values across all time steps
u_history, r_history = results
print(f"Membrane potential history shape: {u_history.shape}") # (run_steps, num)
print(f"Firing rate history shape: {r_history.shape}") # (run_steps, num)
Membrane potential history shape: (700, 256)
Firing rate history shape: (700, 256)
3.4 JIT 编译的优势¶
首次运行包含编译耗时(数秒),但后续运行速度显著提升:
[9]:
import time
model = CANN1D(num=256, tau=1.0, k=8.1, a=0.5, A=10, J0=4.0)
# Create a new longer task
task = PopulationCoding1D(
cann_instance=model, # CANN model instance
before_duration=10000.0, # Duration before stimulus
after_duration=10000.0, # Duration after stimulus
Iext=1.0, # Stimulus position in feature space
duration=10000.0, # Stimulus duration
time_step=0.1, # Time step
)
task.get_data()
# First run (includes compilation)
start = time.time()
results = bm.for_loop(run_step, operands=(task.run_steps, task.data))
print(f"First run: {time.time() - start:.2f}s")
# Second run (already compiled)
start = time.time()
results = bm.for_loop(run_step, operands=(task.run_steps, task.data))
print(f"Second run: {time.time() - start:.2f}s")
<PopulationCoding1D>Generating Task data(No For Loop)
First run: 0.75s
Second run: 0.24s
4. 完整示例¶
以下是从模型创建到仿真的完整示例:
[10]:
import brainpy.math as bm # :cite:p:`wang2023brainpy`
from canns.models.basic import CANN1D
from canns.task.tracking import PopulationCoding1D
# ============================================================
# Step 1: Setup environment and create model
# ============================================================
bm.set_dt(0.1)
model = CANN1D(num=256, tau=1.0, k=8.1, a=0.5, A=10, J0=4.0)
# ============================================================
# Step 2: Create task
# ============================================================
task = PopulationCoding1D(
cann_instance=model,
before_duration=10.0,
after_duration=50.0,
Iext=0.0,
duration=10.0,
time_step=0.1,
)
# Get task data
task.get_data()
print("Task Information:")
print(f" Total time steps: {task.total_steps}")
print(f" Total duration: {task.total_duration}")
print(f" Data shape: {task.data.shape}")
# ============================================================
# Step 3: Define simulation step function
# ============================================================
def run_step(t, inp):
model.update(inp)
return model.u.value, model.r.value
# ============================================================
# Step 4: Run simulation
# ============================================================
u_history, r_history = bm.for_loop(
run_step,
operands=(task.run_steps, task.data),
)
# ============================================================
# Step 5: Inspect results
# ============================================================
print("\nSimulation Results:")
print(f" Membrane potential history shape: {u_history.shape}")
print(f" Firing rate history shape: {r_history.shape}")
# Check states at different phases
before_steps = int(10.0 / 0.1) # Before stimulus
stim_end = int(20.0 / 0.1) # End of stimulus
after_steps = int(70.0 / 0.1) # End of simulation
print(f"\nBefore stimulus (t={before_steps-1}) max firing rate: {bm.max(r_history[before_steps-1]):.6f}")
print(f"During stimulus (t={stim_end-1}) max firing rate: {bm.max(r_history[stim_end-1]):.6f}")
print(f"After stimulus (t={after_steps-1}) max firing rate: {bm.max(r_history[after_steps-1]):.6f}")
<PopulationCoding1D>Generating Task data(No For Loop)
Task Information:
Total time steps: 700
Total duration: 70.0
Data shape: (700, 256)
Simulation Results:
Membrane potential history shape: (700, 256)
Firing rate history shape: (700, 256)
Before stimulus (t=99) max firing rate: 0.000000
During stimulus (t=199) max firing rate: 0.002426
After stimulus (t=699) max firing rate: 0.002348
预期输出:
刺激前:放电率 ≈0
刺激期间:放电率上升(波包形成)
刺激后:放电率保持稳定(记忆持续存在)
5. 后续步骤¶
恭喜!您现已掌握如何生成任务与运行 CANN 仿真。您已学习:
如何使用
SmoothTracking1D和SmoothTracking2D创建平滑追踪任务如何使用
OpenLoopNavigationTask生成导航任务如何使用
bm.for_loop实现高效仿真如何记录并分析网络状态随时间的变化
开环(预生成轨迹)与闭环(交互式)任务的区别
您已掌握的内容¶
- 任务生成
您能够为 CANN 模型创建多种输入模式——从简单追踪到复杂导航轨迹。
- 仿真流程
您理解了标准仿真流程:初始化模型 → 创建任务 → 运行循环 → 分析结果。
- 性能优化
您掌握了使用 JIT 编译循环进行长时序仿真加速的方法。
- 数据组织
您能够结构化仿真输出,便于后续分析与可视化。
继续学习¶
请继续前往可视化与分析部分:
教程 3:分析与可视化——学习如何绘制能量景观、放电场图与动画
随后可继续学习:
教程 4:参数影响——系统性探索参数对动力学的影响
教程 5:分层路径积分——基于网格细胞的多尺度导航
教程 6:Theta 扫描系统——Theta 调制的头方向细胞与网格细胞
或探索其他工作流:
场景 2: 数据分析——分析实验神经记录数据
场景 3: 类脑学习——使用赫布学习训练网络
场景 4: 流水线——端到端研究工作流
关键要点¶
任务定义输入——任务模块生成驱动 CANN 动态的外部刺激
职责分离——模型定义动力学,任务定义输入,分析器负责可视化
灵活性——同一模型可配合不同任务用于多种应用场景
效率至关重要——生产级仿真应使用编译循环(
bm.for_loop)
最佳实践¶
运行仿真时请遵循以下建议:
从小规模开始——先用短时长测试,再逐步扩展
监控内存使用——长时程仿真中大量神经元可能消耗大量内存
保存中间结果——使用
np.save对长仿真进行断点保存记录参数——保存任务与模型参数以确保可复现性
下一节:教程 3:分析与可视化