教程 2:任务生成与 CANN 仿真

Note

阅读时间: 20-25 分钟

难度: 初级

先决条件: 教程 1

本教程将指导您如何使用 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 参数说明

参数

类型

说明

cann_instance

BaseCANN1D

CANN 模型实例——任务将调用其 get_stimulus_by_pos() 方法

before_duration

float

刺激呈现前的持续时间(无输入阶段)

after_duration

float

刺激结束后持续时间(用于观察波包维持)

Iext

float

特征空间中的刺激位置,通常位于 [z_min, z_max] 范围内

duration

float

刺激呈现的持续时间

time_step

float

仿真时间步长——应与 bm.set_dt(...) 保持一致

这些参数的重要性:

  • cann_instance 是必需的,因为任务需调用模型的 get_stimulus_by_pos() 方法以生成合适的刺激

  • before_durationafter_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 仿真。您已学习:

  • 如何使用 SmoothTracking1DSmoothTracking2D 创建平滑追踪任务

  • 如何使用 OpenLoopNavigationTask 生成导航任务

  • 如何使用 bm.for_loop 实现高效仿真

  • 如何记录并分析网络状态随时间的变化

  • 开环(预生成轨迹)与闭环(交互式)任务的区别

您已掌握的内容

任务生成

您能够为 CANN 模型创建多种输入模式——从简单追踪到复杂导航轨迹。

仿真流程

您理解了标准仿真流程:初始化模型 → 创建任务 → 运行循环 → 分析结果。

性能优化

您掌握了使用 JIT 编译循环进行长时序仿真加速的方法。

数据组织

您能够结构化仿真输出,便于后续分析与可视化。

继续学习

请继续前往可视化与分析部分:

随后可继续学习:

  • 教程 4:参数影响——系统性探索参数对动力学的影响

  • 教程 5:分层路径积分——基于网格细胞的多尺度导航

  • 教程 6:Theta 扫描系统——Theta 调制的头方向细胞与网格细胞

或探索其他工作流:

  • 场景 2: 数据分析——分析实验神经记录数据

  • 场景 3: 类脑学习——使用赫布学习训练网络

  • 场景 4: 流水线——端到端研究工作流

关键要点

  1. 任务定义输入——任务模块生成驱动 CANN 动态的外部刺激

  2. 职责分离——模型定义动力学,任务定义输入,分析器负责可视化

  3. 灵活性——同一模型可配合不同任务用于多种应用场景

  4. 效率至关重要——生产级仿真应使用编译循环(bm.for_loop

最佳实践

运行仿真时请遵循以下建议:

  1. 从小规模开始——先用短时长测试,再逐步扩展

  2. 监控内存使用——长时程仿真中大量神经元可能消耗大量内存

  3. 保存中间结果——使用 np.save 对长仿真进行断点保存

  4. 记录参数——保存任务与模型参数以确保可复现性

下一节:教程 3:分析与可视化