如何构建 CANN 模型?

目标:完成本指南后,您将能够创建并运行一个基础的 CANN 模型。

预计阅读时间:10 分钟


介绍

在本库中构建 CANN 模型非常简单,这得益于与 BrainPy 的集成,BrainPy 是基于 JAX 构建的动力系统框架。本指南将向您展示如何:

  1. 设置 BrainPy 环境

  2. 创建 CANN1D 模型实例

  3. 初始化模型状态

  4. 运行简单的前向传播

基础知识:BrainPy 框架

CANN 模型使用 BrainPy 构建,它提供:

  • 通过 bm.set_dt()统一时间步管理

  • 用于管理神经动力学的状态容器bm.Variable

  • 通过 bm.for_loopJIT 编译以获得高性能

  • 梯度分析的自动微分支持

所有 CANN 模型都继承自 bp.DynamicalSystem,这意味着它们在库中遵循一致的接口。

逐步指南:创建您的第一个 CANN

1. 设置时间步

在创建任何模型之前,您必须设置模拟时间步:

[ ]:
import brainpy.math as bm

# 设置时间步为 0.1 ms(或您偏好的值)
bm.set_dt(0.1)

为什么这很重要:时间步 dt 控制您模拟的粒度。您会话中的所有模型都将使用此值进行其动力学更新。

2. 导入并创建模型

[2]:
from canns.models.basic import CANN1D

# 创建有 512 个神经元的 1D CANN
cann = CANN1D(num=512)

这里发生了什么

  • num=512 指定网络中的神经元数量

  • 该模型自动设置连接权重、神经元位置和动力学参数

  • 使用默认参数(例如连接强度 k、时间常数 tau),除非您指定其他参数

4. 运行前向传播

现在您可以调用模型来更新其状态:

[4]:
import jax.numpy as jnp

# 创建简单的外部输入(在位置 0 的刺激)
external_input = jnp.zeros(512)

# 运行一个时间步
cann(external_input)

# 访问模型的当前状态
print("突触输入:", cann.u.value)
print("神经活动:", cann.r.value)
突触输入: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0.]
神经活动: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0. 0. 0. 0. 0. 0. 0. 0.]

发生了什么

  • 模型接收外部输入并更新其内部动力学

  • cann.u 存储突触输入(膜电位)

  • cann.r 存储神经放电速率(活动)

  • 每次调用 cann(...) 将模型推进一个时间步(dt

完整的工作示例

这是一个最小的、可运行的示例,将它们全部整合在一起:

[ ]:
import brainpy.math as bm
import jax.numpy as jnp
from canns.models.basic import CANN1D

# 步骤 1:设置时间步
bm.set_dt(0.1)

# 步骤 2:创建模型
cann = CANN1D(num=512)

# 步骤 3:创建以位置 0 为中心的高斯凸起刺激
positions = cann.x  # 来自 -pi 到 pi 的神经元位置
stimulus = jnp.exp(-0.5 * (positions - 0.0)**2 / 0.25**2)

# 步骤 4:运行多个前向传播
cann(stimulus)
cann(stimulus)
cann(stimulus)

# 步骤 6:检查输出
print(f"活动形状:{cann.r.value.shape}")
print(f"最大活动:{jnp.max(cann.r.value)}")
[6]:
cann = CANN1D(
    num=512,           # 神经元数量
    k=1.0,             # 全局连接强度
    tau=1.0,           # 时间常数(毫秒)
    a=0.5,             # 兴奋性连接宽度
    A=10.0,            # 兴奋性连接幅度
    J0=1.0,         # 外部输入强度
)

关键参数

  • num:神经元数量(更高 = 更精细的空间分辨率,但速度更慢)

  • k:控制总体连接强度(更高 = 更强的自组织)

  • tau:动力学的时间常数(更高 = 变化更慢)

  • a:连接核的宽度(控制凸起宽度)

  • A:连接的幅度(影响稳定性)

对于大多数应用,默认值表现很好。我们将在核心概念部分探索参数调整。

运行多个时间步

在实践中,您将在循环中运行许多时间步。BrainPy 为此提供了优化的工具:

[ ]:
# 初始化状态

def step_function(t, stimulus):
    """运行模型的一个时间步。"""
    cann(stimulus)
    return cann.r.value  # 返回每个步骤的活动

# 为 100 个时间步创建刺激(这里是恒定刺激)
stimuli = jnp.tile(stimulus, (100, 1))

# 使用进度条运行优化循环
activities = bm.for_loop(
    step_function,
    operands=(jnp.arange(0, 100), stimuli),
    pbar=bm.ProgressBar(10)  # 显示进度
)

print(f"记录的活动形状:{activities.shape}")  # (100, 512)

❌ 错误 1:未设置时间步

[10]:
from canns.models.basic import CANN1D
cann = CANN1D(num=512)  # 使用之前设置的任何 dt(或默认值)

✅ 解决方案:在脚本开始时显式设置 dt

[ ]:
import brainpy.math as bm
bm.set_dt(0.1)  # 首先设置 dt
cann = CANN1D(num=512)

❌ 错误 2:输入维度错误

[12]:
cann = CANN1D(num=512)
try:
    cann(jnp.zeros(256))  # 错误!输入大小与神经元数量不匹配
except Exception as e:
    print(f"按预期捕获错误:{e}")
按预期捕获错误:add got incompatible shapes for broadcasting: (512,), (256,).

✅ 解决方案:输入必须与 num 大小相同:

[13]:
cann = CANN1D(num=512)
cann(jnp.zeros(512))  # 正确的大小

关于 2D CANN?

相同的原理也适用于 2D 模型:

[ ]:
from canns.models.basic import CANN2D

bm.set_dt(0.1)

# 创建有 32x32 个神经元的 2D CANN
cann2d = CANN2D(32)

# 输入必须是 (32, 32) 形状
stimulus_2d = jnp.zeros((32, 32))
cann2d(stimulus_2d)

print(f"2D 活动形状:{cann2d.r.value.shape}")  # (32, 32)

API 几乎相同——只需调整您的输入维度!

后续步骤

现在您知道如何创建和运行 CANN 模型,您已准备好:

  1. 生成任务数据 - 学习如何创建平滑跟踪输入、导航任务等

  2. 探索模型集合 - 发现其他模型变体(SFA-CANN、分层网络、脑启发模型)

  3. 学习 BrainPy 基础 - 如果您想构建自定义模型或深入理解框架


快速参考

[ ]:
# 创建任何 CANN 模型的模板
import brainpy.math as bm
from canns.models.basic import CANN1D

bm.set_dt(0.1)      # 1. 设置时间步
cann = CANN1D(num=512)              # 2. 创建模型
cann(stimulus)                      # 4. 运行前向传播
result = cann.r.value               # 5. 访问活动

有疑问?查看FAQ或打开GitHub Discussion