spikingjelly.activation_based.neuron.integrate_and_fire 源代码

import logging
import numbers
from typing import Optional, Union

import torch

from .. import base, surrogate
from .base_node import BaseNode, NonSpikingBaseNode, SimpleBaseNode

try:
    from ..cuda_kernel.auto_cuda import neuron_kernel as ac_neuron_kernel
    from ..cuda_kernel.auto_cuda import ss_neuron_kernel as ss_ac_neuron_kernel
except BaseException as e:
    logging.info(f"spikingjelly.activation_based.neuron: {e}")
    ac_neuron_kernel = None
    ss_ac_neuron_kernel = None

try:
    from .. import triton_kernel
except BaseException as e:
    logging.info(f"spikingjelly.activation_based.neuron: {e}")
    triton_kernel = None


__all__ = [
    "SimpleIFNode",
    "IFNode",
    "HalfThresholdIFNode",
    "ActivationAwareIFNode",
    "NonSpikingIFNode",
]


[文档] class SimpleIFNode(SimpleBaseNode): def __init__( self, v_threshold: float = 1.0, v_reset: Optional[float] = 0.0, surrogate_function: surrogate.SurrogateFunctionBase = surrogate.Sigmoid(), detach_reset: bool = False, step_mode="s", ): """ **API Language** - :ref:`中文 <SimpleIFNode.__init__-cn>` | :ref:`English <SimpleIFNode.__init__-en>` ---- .. _SimpleIFNode.__init__-cn: * **中文** :class:`IFNode` 的简化版实现。 :param v_threshold: 神经元阈值电压 :type v_threshold: float :param v_reset: 神经元重置电压 :type v_reset: Optional[float] :param surrogate_function: 替代梯度函数 :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: 是否在反向传播时分离 reset 计算图 :type detach_reset: bool :param step_mode: 步进模式,可为 ``"s"`` 或 ``"m"`` :type step_mode: str ---- .. _SimpleIFNode.__init__-en: * **English** A simple version of :class:`IFNode`. :param v_threshold: Threshold voltage of the neuron :type v_threshold: float :param v_reset: Reset voltage of the neuron :type v_reset: Optional[float] :param surrogate_function: Surrogate gradient function :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: Whether to detach reset graph in backward :type detach_reset: bool :param step_mode: Step mode, either ``"s"`` or ``"m"`` :type step_mode: str """ super().__init__( v_threshold, v_reset, surrogate_function, detach_reset, step_mode )
[文档] def neuronal_charge(self, x: torch.Tensor): r""" **API Language** - :ref:`中文 <SimpleIFNode.neuronal_charge-cn>` | :ref:`English <SimpleIFNode.neuronal_charge-en>` ---- .. _SimpleIFNode.neuronal_charge-cn: * **中文** 神经元充电的微分方程: .. math:: H[t] = V[t-1] + X[t] :param x: 输入电压 :type x: torch.Tensor :return: None(膜电位更新存储在 ``self.v`` 中) ---- .. _SimpleIFNode.neuronal_charge-en: * **English** The differential equation for neuronal charge: .. math:: H[t] = V[t-1] + X[t] :param x: Input voltage :type x: torch.Tensor :return: None (membrane potential is stored in ``self.v``) """ self.v = self.v + x
[文档] class IFNode(BaseNode): def __init__( self, v_threshold: float = 1.0, v_reset: Optional[float] = 0.0, surrogate_function: surrogate.SurrogateFunctionBase = surrogate.Sigmoid(), detach_reset: bool = False, step_mode="s", backend="torch", store_v_seq: bool = False, ): """ **API Language** - :ref:`中文 <IFNode.__init__-cn>` | :ref:`English <IFNode.__init__-en>` ---- .. _IFNode.__init__-cn: * **中文** Integrate-and-Fire 神经元模型,可以看作理想积分器,无输入时电压保持恒定,不会像 LIF 神经元那样衰减。其阈下神经动力学方程为: .. math:: H[t] = V[t-1] + X[t] :param v_threshold: 神经元的阈值电压 :type v_threshold: float :param v_reset: 神经元的重置电压。如果不为 ``None``,当神经元释放脉冲后,电压会被重置为 ``v_reset``; 如果设置为 ``None``,当神经元释放脉冲后,电压会被减去 ``v_threshold`` :type v_reset: Optional[float] :param surrogate_function: 反向传播时用来计算脉冲函数梯度的替代函数 :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: 是否将 reset 过程的计算图分离 :type detach_reset: bool :param step_mode: 步进模式,可以为 `'s'` (单步) 或 `'m'` (多步) :type step_mode: str :param backend: 使用哪种后端。不同的 ``step_mode`` 可能会带有不同的后端。可以通过打印 ``self.supported_backends`` 查看当前 使用的步进模式支持的后端。该参数是显式执行后端选择:设置为 ``'torch'``、``'cupy'`` 或 ``'triton'`` 时,将分别使用 对应后端,不会隐式切换到其他后端。在支持的情况下,使用 ``'cupy'`` 或 ``'triton'`` 后端通常更快。 :type backend: str :param store_v_seq: 在使用 ``step_mode = 'm'`` 时,给与 ``shape = [T, N, *]`` 的输入后,是否保存中间过程的 ``shape = [T, N, *]`` 的各个时间步的电压值 ``self.v_seq`` 。设置为 ``False`` 时计算完成后只保留最后一个时刻的电压,即 ``shape = [N, *]`` 的 ``self.v`` 。 通常设置成 ``False`` ,可以节省内存 :type store_v_seq: bool ---- .. _IFNode.__init__-en: * **English** The Integrate-and-Fire neuron, which can be seen as an ideal integrator. The voltage of the IF neuron will not decay as that of the LIF neuron. The sub-threshold neural dynamics of it is as followed: .. math:: H[t] = V[t-1] + X[t] :param v_threshold: threshold of this neurons layer :type v_threshold: float :param v_reset: reset voltage of this neurons layer. If not ``None``, the neuron's voltage will be set to ``v_reset`` after firing a spike. If ``None``, the neuron's voltage will subtract ``v_threshold`` after firing a spike :type v_reset: Optional[float] :param surrogate_function: the function for calculating surrogate gradients of the heaviside step function in backward :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: whether detach the computation graph of reset in backward :type detach_reset: bool :param step_mode: the step mode, which can be `s` (single-step) or `m` (multi-step) :type step_mode: str :param backend: backend for this neurons layer. Different ``step_mode`` may support different backends. Users can print ``self.supported_backends`` to check what backends are supported by the current ``step_mode``. This argument is an explicit execution-backend choice: ``'torch'``, ``'cupy'``, and ``'triton'`` each use their own backend and are not silently upgraded to another backend. If supported, ``'cupy'`` or ``'triton'`` is usually faster :type backend: str :param store_v_seq: when using ``step_mode = 'm'`` and given input with ``shape = [T, N, *]``, this option controls whether storing the voltage at each time-step to ``self.v_seq`` with ``shape = [T, N, *]``. If set to ``False``, only the voltage at last time-step will be stored to ``self.v`` with ``shape = [N, *]``, which can reduce the memory consumption :type store_v_seq: bool """ super().__init__( v_threshold, v_reset, surrogate_function, detach_reset, step_mode, backend, store_v_seq, ) @property def supported_backends(self): if self.step_mode == "s": return ("torch", "cupy") elif self.step_mode == "m": return ("torch", "cupy", "triton", "inductor") else: raise ValueError(self.step_mode)
[文档] def neuronal_charge(self, x: torch.Tensor): self.v = self.v + x
@staticmethod def _eval_single_step_forward( x: torch.Tensor, v: torch.Tensor, v_threshold: float, v_reset ): """Unified single-step eval (replaces jit_eval_single_step_forward_*).""" v = v + x spike = (v >= v_threshold).to(x) v = ( (v - spike * v_threshold) if v_reset is None else (v_reset * spike + (1.0 - spike) * v) ) return spike, v @staticmethod def _eval_multi_step_forward( x_seq: torch.Tensor, v: torch.Tensor, v_threshold: float, v_reset, store_v_seq: bool = False, ): """Unified multi-step eval (replaces jit_eval_multi_step_forward_*).""" T = x_seq.shape[0] spike_seq = torch.zeros_like(x_seq) v_seq = torch.zeros_like(x_seq) if store_v_seq else None soft_reset = v_reset is None _vr = 0.0 if soft_reset else v_reset for t in range(T): v = v + x_seq[t] spike = (v >= v_threshold).to(x_seq) v = ( (v - spike * v_threshold) if soft_reset else (_vr * spike + (1.0 - spike) * v) ) spike_seq[t] = spike if store_v_seq: v_seq[t] = v if store_v_seq: return spike_seq, v, v_seq return spike_seq, v # kept for subclass backward-compatibility @staticmethod def jit_eval_single_step_forward_hard_reset( x: torch.Tensor, v: torch.Tensor, v_threshold: float, v_reset: float ): v = v + x spike = (v >= v_threshold).to(x) v = v_reset * spike + (1.0 - spike) * v return spike, v @staticmethod def jit_eval_single_step_forward_soft_reset( x: torch.Tensor, v: torch.Tensor, v_threshold: float ): v = v + x spike = (v >= v_threshold).to(x) v = v - spike * v_threshold return spike, v def _build_inductor_multi_step_graph(self): store_v_seq = self.store_v_seq soft_reset = self.v_reset is None v_reset = 0.0 if soft_reset else self.v_reset surrogate_fn = self.surrogate_function v_threshold = self.v_threshold detach_reset = self.detach_reset def _graph(x_seq: torch.Tensor, v_init: torch.Tensor): v = v_init spike_seq = torch.empty_like(x_seq) if store_v_seq: v_seq = torch.empty_like(x_seq) for t in range(x_seq.shape[0]): v = v + x_seq[t] spike = surrogate_fn(v - v_threshold) spike_d = spike.detach() if detach_reset else spike if soft_reset: v = v - spike_d * v_threshold else: v = spike_d * v_reset + (1.0 - spike_d) * v spike_seq[t] = spike if store_v_seq: v_seq[t] = v if store_v_seq: return spike_seq, v, v_seq return spike_seq, v return _graph def _inductor_multi_step_forward(self, x_seq: torch.Tensor): self.v_float_to_tensor(x_seq[0]) x_seq = self._canonicalize_inductor_tensor(x_seq) v_init = self._canonicalize_inductor_tensor(self.v) graph = self._compile_inductor_graph( ( "if", self.store_v_seq, self.v_threshold, self.v_reset, self.detach_reset, self._surrogate_inductor_cache_key(), self._inductor_runtime_cache_key(x_seq, v_init), ), self._build_inductor_multi_step_graph(), ) out = graph(x_seq, v_init) if self.store_v_seq: spike_seq, self.v, self.v_seq = out else: spike_seq, self.v = out return spike_seq
[文档] def multi_step_forward(self, x_seq: torch.Tensor): if self.backend == "inductor": return self._inductor_multi_step_forward(x_seq) if self.training: if self.backend == "torch": return super().multi_step_forward(x_seq) elif self.backend == "cupy": hard_reset = self.v_reset is not None if x_seq.dtype == torch.float: dtype = "float" elif x_seq.dtype == torch.half: dtype = "half2" else: raise NotImplementedError(x_seq.dtype) if ( self.forward_kernel is None or not self.forward_kernel.check_attributes( hard_reset=hard_reset, dtype=dtype ) ): self.forward_kernel = ac_neuron_kernel.IFNodeFPTTKernel( hard_reset=hard_reset, dtype=dtype ) if ( self.backward_kernel is None or not self.backward_kernel.check_attributes( surrogate_function=self.surrogate_function.cuda_codes, hard_reset=hard_reset, detach_reset=self.detach_reset, dtype=dtype, ) ): self.backward_kernel = ac_neuron_kernel.IFNodeBPTTKernel( surrogate_function=self.surrogate_function.cuda_codes, hard_reset=hard_reset, detach_reset=self.detach_reset, dtype=dtype, ) self.v_float_to_tensor(x_seq[0]) spike_seq, v_seq = ac_neuron_kernel.multistep_if( x_seq=x_seq.flatten(1), v_init=self.v.flatten(0), v_threshold=self.v_threshold, v_reset=self.v_reset, detach_reset=self.detach_reset, surrogate_function=self.surrogate_function, forward_kernel=self.forward_kernel, backward_kernel=self.backward_kernel, ) spike_seq = spike_seq.reshape(x_seq.shape) v_seq = v_seq.reshape(x_seq.shape) if self.store_v_seq: self.v_seq = v_seq self.v = v_seq[-1].clone() return spike_seq elif self.backend == "triton": self.v_float_to_tensor(x_seq[0]) spike_seq, v_seq = triton_kernel.multistep_if( x_seq, self.v, self.v_threshold, self.v_reset, self.detach_reset, self.surrogate_function, ) if self.store_v_seq: self.v_seq = v_seq self.v = v_seq[-1].clone() return spike_seq else: raise ValueError(self.backend) else: self.v_float_to_tensor(x_seq[0]) if self.backend == "triton": if not getattr(self.surrogate_function, "spiking", True): raise NotImplementedError( "Triton backend only supports spiking surrogate functions. " "Use backend='torch' for non-spiking surrogate functions." ) spike_seq, v_seq = triton_kernel.multistep_if( x_seq, self.v, self.v_threshold, self.v_reset, self.detach_reset, self.surrogate_function, ) if self.store_v_seq: self.v_seq = v_seq self.v = v_seq[-1] else: self.v = v_seq[-1].clone() return spike_seq elif self.backend == "cupy": spike_seq, v_seq = ac_neuron_kernel.multistep_if( x_seq=x_seq.flatten(1), v_init=self.v.flatten(0), v_threshold=self.v_threshold, v_reset=self.v_reset, detach_reset=self.detach_reset, surrogate_function=self.surrogate_function, ) spike_seq = spike_seq.reshape(x_seq.shape) v_seq = v_seq.reshape(x_seq.shape) if self.store_v_seq: self.v_seq = v_seq self.v = v_seq[-1] else: self.v = v_seq[-1].clone() return spike_seq # torch backend: out = self._eval_multi_step_forward( x_seq, self.v, self.v_threshold, self.v_reset, store_v_seq=self.store_v_seq, ) if self.store_v_seq: spike_seq, self.v, self.v_seq = out else: spike_seq, self.v = out return spike_seq
[文档] def single_step_forward(self, x: torch.Tensor): if self.training: if self.backend == "torch": return super().single_step_forward(x) elif self.backend == "cupy": hard_reset = self.v_reset is not None if x.dtype == torch.float: dtype = "float" elif x.dtype == torch.half: dtype = "half2" else: raise NotImplementedError(x.dtype) if ( self.forward_kernel is None or not self.forward_kernel.check_attributes( hard_reset=hard_reset, dtype=dtype ) ): self.forward_kernel = ss_ac_neuron_kernel.IFNodeFPKernel( hard_reset=hard_reset, dtype=dtype ) if ( self.backward_kernel is None or not self.backward_kernel.check_attributes( surrogate_function=self.surrogate_function.cuda_codes, hard_reset=hard_reset, detach_reset=self.detach_reset, dtype=dtype, ) ): self.backward_kernel = ss_ac_neuron_kernel.IFNodeBPKernel( surrogate_function=self.surrogate_function.cuda_codes, hard_reset=hard_reset, detach_reset=self.detach_reset, dtype=dtype, ) self.v_float_to_tensor(x) spike, v = ss_ac_neuron_kernel.ss_if_step( x.flatten(0), self.v.flatten(0), self.v_threshold, self.v_reset, self.forward_kernel, self.backward_kernel, ) spike = spike.reshape(x.shape) v = v.reshape(x.shape) self.v = v return spike else: raise ValueError(self.backend) else: self.v_float_to_tensor(x) spike, self.v = self._eval_single_step_forward( x, self.v, self.v_threshold, self.v_reset, ) return spike
[文档] class HalfThresholdIFNode(BaseNode): def __init__( self, v_threshold: float = 1.0, surrogate_function: surrogate.SurrogateFunctionBase = surrogate.Sigmoid(), detach_reset: bool = False, step_mode="s", backend="torch", store_v_seq: bool = False, ): r""" **API Language** - :ref:`中文 <HalfThresholdIFNode.__init__-cn>` | :ref:`English <HalfThresholdIFNode.__init__-en>` ---- .. _HalfThresholdIFNode.__init__-cn: * **中文** 半阈值初始膜电位的 Integrate-and-Fire 神经元。每次调用 ``reset()`` 后膜电位会恢复为 ``v_threshold / 2``。单步前向中的脉冲后重置仍使用 标准软重置。除此之外,其充电、放电和重置动力学与软重置 IF 神经元一致: .. math:: H[t] = V[t-1] + X[t] .. math:: S[t] = \Theta(H[t] - V_{threshold}) .. math:: V[t] = H[t] - S[t] V_{threshold} 训练时使用 ``surrogate_function`` 为脉冲函数提供替代梯度;前向输出仍为 离散脉冲。 :param v_threshold: 神经元阈值电压,必须为正实数或单元素张量 :type v_threshold: float or torch.Tensor :param surrogate_function: 反向传播时用来计算脉冲函数梯度的替代函数 :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: 是否在反向传播时分离 reset 计算图 :type detach_reset: bool :param step_mode: 步进模式,可以为 ``"s"`` 或 ``"m"`` :type step_mode: str :param backend: 后端名称。当前实现支持 ``"torch"`` :type backend: str :param store_v_seq: 在 ``step_mode="m"`` 时是否保存每个时间步的膜电位序列 :type store_v_seq: bool :raises TypeError: 当 ``v_threshold`` 不是实数或张量时抛出 :raises ValueError: 当 ``v_threshold`` 不是单元素有限正数时抛出 ---- .. _HalfThresholdIFNode.__init__-en: * **English** An Integrate-and-Fire neuron with half-threshold initial membrane potential. After each explicit ``reset()``, its membrane potential is restored to ``v_threshold / 2``. The per-step post-spike reset still uses the standard soft reset. Apart from the initial reset value, its charge, fire, and reset dynamics are the same as a soft-reset IF neuron: .. math:: H[t] = V[t-1] + X[t] .. math:: S[t] = \Theta(H[t] - V_{threshold}) .. math:: V[t] = H[t] - S[t] V_{threshold} During training, ``surrogate_function`` provides surrogate gradients for the spike function; the forward output remains discrete spikes. :param v_threshold: Threshold voltage of the neuron, which must be a finite positive real number or a scalar tensor :type v_threshold: float or torch.Tensor :param surrogate_function: Surrogate gradient function for the spike function in backward propagation :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: Whether to detach the reset computation graph in backward propagation :type detach_reset: bool :param step_mode: Step mode, either ``"s"`` or ``"m"`` :type step_mode: str :param backend: Backend name. The current implementation supports ``"torch"`` :type backend: str :param store_v_seq: Whether to store membrane potentials at every time step when ``step_mode="m"`` :type store_v_seq: bool :raises TypeError: Raised when ``v_threshold`` is not a real number or tensor :raises ValueError: Raised when ``v_threshold`` is not scalar finite positive """ if isinstance(v_threshold, torch.Tensor): if v_threshold.numel() != 1: raise ValueError("v_threshold must be scalar finite positive.") v_threshold = float(v_threshold) elif not isinstance(v_threshold, numbers.Real): raise TypeError("v_threshold must be a real number.") v_threshold = float(v_threshold) if not torch.isfinite(torch.tensor(v_threshold)) or v_threshold <= 0.0: raise ValueError("v_threshold must be finite positive.") super().__init__( v_threshold=v_threshold, v_reset=None, surrogate_function=surrogate_function, detach_reset=detach_reset, step_mode=step_mode, backend=backend, store_v_seq=store_v_seq, ) half_threshold = self.v_threshold / 2.0 self.set_reset_value("v", half_threshold) self.v = half_threshold @property def supported_backends(self): return ("torch",)
[文档] def v_float_to_tensor(self, x: torch.Tensor): half_threshold = self.v_threshold / 2.0 if isinstance(self.v, float): self.v = torch.full_like(x, self.v, requires_grad=False) elif isinstance(self.v, torch.Tensor): if self.v.shape != x.shape: self.v = torch.full_like(x, half_threshold, requires_grad=False) elif self.v.dtype != x.dtype or self.v.device != x.device: self.v = self.v.to(dtype=x.dtype, device=x.device)
[文档] def neuronal_charge(self, x: torch.Tensor): self.v = self.v + x
[文档] class ActivationAwareIFNode(base.MemoryModule): def __init__( self, v_threshold: Union[float, torch.Tensor] = 1.0, v_offset: Union[float, torch.Tensor] = 0.0, channel_dim: int = -1, v_reset: Optional[float] = None, surrogate_function: surrogate.SurrogateFunctionBase = surrogate.Sigmoid(), detach_reset: bool = False, step_mode: str = "s", backend: str = "torch", store_v_seq: bool = False, ): r""" **API Language** - :ref:`中文 <ActivationAwareIFNode.__init__-cn>` | :ref:`English <ActivationAwareIFNode.__init__-en>` ---- .. _ActivationAwareIFNode.__init__-cn: * **中文** 实验性的 activation-aware IF 神经元,用于 ANN2SNN 中 Activation-Aware Redistribution (AAR) 风格的最小垂直切片。该神经元 支持标量或 1D channel-wise 的发放阈值 ``v_threshold`` 和膜电位偏移 ``v_offset``。当 ``v_threshold`` 或 ``v_offset`` 为 1D 张量时,会沿 ``channel_dim`` 广播到输入张量。 该类只支持 ``backend="torch"``,不继承 :class:`BaseNode`,也不改变 现有 :class:`IFNode` / :class:`BaseNode` 的标量 ``v_threshold`` 约定。 它面向研究和转换 POC,不表示默认 ANN2SNN 路径支持多元素阈值。 单步动力学为: .. math:: H[t] = V[t-1] + X[t] .. math:: S[t] = \Theta(H[t] + O - V_{th}) 其中 ``O`` 为 ``v_offset``。软复位时: .. math:: V[t] = H[t] - S[t] V_{th} 硬复位时: .. math:: V[t] = S[t] V_{reset} + (1 - S[t]) H[t] :param v_threshold: 发放阈值。必须为有限正标量,或有限正 1D 张量。 :type v_threshold: float or torch.Tensor :param v_offset: 膜电位偏移。必须为有限标量,或有限 1D 张量。 :type v_offset: float or torch.Tensor :param channel_dim: 1D ``v_threshold`` / ``v_offset`` 对应的输入通道维。 :type channel_dim: int :param v_reset: 硬复位电压。``None`` 表示软复位。 若不为 ``None``,``reset()`` 会将膜电位 ``v`` 恢复为 ``v_reset``, 与 :class:`BaseNode` 的硬复位语义一致。 :type v_reset: Optional[float] :param surrogate_function: 反向传播时使用的替代函数。 :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: 是否在反向传播时分离 reset 计算图。 :type detach_reset: bool :param step_mode: 步进模式,``"s"`` 为单步,``"m"`` 为多步。 :type step_mode: str :param backend: 仅支持 ``"torch"``。 :type backend: str :param store_v_seq: 多步模式下是否保存每个时间步的膜电位。 :type store_v_seq: bool :raises ValueError: 当 backend、step_mode、channel_dim、threshold 或 offset 非法时抛出。 ---- .. _ActivationAwareIFNode.__init__-en: * **English** Experimental activation-aware IF neuron for an ANN2SNN Activation-Aware Redistribution (AAR) style minimal vertical slice. This neuron supports scalar or 1D channel-wise firing threshold ``v_threshold`` and membrane offset ``v_offset``. A 1D ``v_threshold`` or ``v_offset`` is broadcast to the input tensor along ``channel_dim``. This class supports only ``backend="torch"``. It does not inherit from :class:`BaseNode` and does not change the scalar ``v_threshold`` convention of existing :class:`IFNode` / :class:`BaseNode`. It is meant for research and conversion POCs, and does not imply that the default ANN2SNN path supports multi-element thresholds. The single-step dynamics are: .. math:: H[t] = V[t-1] + X[t] .. math:: S[t] = \Theta(H[t] + O - V_{th}) where ``O`` is ``v_offset``. With soft reset: .. math:: V[t] = H[t] - S[t] V_{th} With hard reset: .. math:: V[t] = S[t] V_{reset} + (1 - S[t]) H[t] :param v_threshold: Firing threshold. It must be a finite positive scalar or a finite positive 1D tensor. :type v_threshold: float or torch.Tensor :param v_offset: Membrane offset. It must be a finite scalar or a finite 1D tensor. :type v_offset: float or torch.Tensor :param channel_dim: Input channel dimension for 1D ``v_threshold`` / ``v_offset``. :type channel_dim: int :param v_reset: Hard-reset voltage. ``None`` means soft reset. If it is not ``None``, ``reset()`` restores membrane voltage ``v`` to ``v_reset``, matching the hard-reset semantics of :class:`BaseNode`. :type v_reset: Optional[float] :param surrogate_function: Surrogate function used in backward. :type surrogate_function: surrogate.SurrogateFunctionBase :param detach_reset: Whether to detach the reset graph during backward. :type detach_reset: bool :param step_mode: Step mode, ``"s"`` for single-step and ``"m"`` for multi-step. :type step_mode: str :param backend: Only ``"torch"`` is supported. :type backend: str :param store_v_seq: Whether to store membrane voltage at each time step in multi-step mode. :type store_v_seq: bool :raises ValueError: If backend, step_mode, channel_dim, threshold, or offset is invalid. """ super().__init__() if backend != "torch": raise ValueError( f"ActivationAwareIFNode only supports backend='torch', got {backend!r}." ) if v_reset is not None and not isinstance(v_reset, float): raise ValueError( f"v_reset must be a float or None, got {type(v_reset).__name__}." ) if not isinstance(detach_reset, bool): raise ValueError("detach_reset must be bool.") if not isinstance(store_v_seq, bool): raise ValueError("store_v_seq must be bool.") if not isinstance(channel_dim, int): raise ValueError("channel_dim must be int.") threshold = torch.as_tensor(v_threshold) offset = torch.as_tensor(v_offset) self._check_threshold(threshold) self._check_offset(offset) self.register_buffer("v_threshold", threshold.clone().detach()) self.register_buffer("v_offset", offset.clone().detach()) self.channel_dim = channel_dim self.v_reset = v_reset self.detach_reset = detach_reset self.surrogate_function = surrogate_function self.store_v_seq = store_v_seq if v_reset is None: self.register_memory("v", 0.0) else: self.register_memory("v", v_reset) self.step_mode = step_mode self.backend = backend @staticmethod def _check_threshold(v_threshold: torch.Tensor) -> None: if v_threshold.dim() > 1: raise ValueError( "v_threshold must be a scalar or 1D tensor, " f"but got shape {tuple(v_threshold.shape)}." ) if not torch.is_floating_point(v_threshold): v_threshold = v_threshold.to(torch.float) if not torch.isfinite(v_threshold).all() or not (v_threshold > 0).all(): raise ValueError("v_threshold must contain finite positive values.") @staticmethod def _check_offset(v_offset: torch.Tensor) -> None: if v_offset.dim() > 1: raise ValueError( "v_offset must be a scalar or 1D tensor, " f"but got shape {tuple(v_offset.shape)}." ) if not torch.is_floating_point(v_offset): v_offset = v_offset.to(torch.float) if not torch.isfinite(v_offset).all(): raise ValueError("v_offset must contain finite values.") @property def supported_backends(self): return ("torch",) @property def store_v_seq(self): return self._store_v_seq @store_v_seq.setter def store_v_seq(self, value: bool): self._store_v_seq = value if value and not hasattr(self, "v_seq"): self.register_memory("v_seq", None) def _canonical_channel_dim(self, x: torch.Tensor) -> int: channel_dim = self.channel_dim if channel_dim < 0: channel_dim += x.dim() if channel_dim < 0 or channel_dim >= x.dim(): raise ValueError( f"channel_dim={self.channel_dim} is out of range for input " f"with {x.dim()} dimensions." ) return channel_dim def _broadcast_parameter( self, param: torch.Tensor, x: torch.Tensor, name: str ) -> torch.Tensor: param = param.to(device=x.device, dtype=x.dtype) if param.dim() == 0: return param channel_dim = self._canonical_channel_dim(x) if param.numel() != x.shape[channel_dim]: raise ValueError( f"{name} has length {param.numel()}, but input shape " f"{tuple(x.shape)} has {x.shape[channel_dim]} channels at " f"channel_dim={self.channel_dim}." ) shape = [1] * x.dim() shape[channel_dim] = param.numel() return param.view(shape)
[文档] def v_float_to_tensor(self, x: torch.Tensor) -> None: if isinstance(self.v, float): self.v = torch.full_like(x, self.v, requires_grad=False) elif isinstance(self.v, torch.Tensor): if self.v.shape != x.shape: fill_value = self.v_reset if self.v_reset is not None else 0.0 self.v = torch.full_like(x, fill_value, requires_grad=False) elif self.v.dtype != x.dtype or self.v.device != x.device: self.v = self.v.to(dtype=x.dtype, device=x.device)
[文档] def single_step_forward(self, x: torch.Tensor): self.v_float_to_tensor(x) threshold = self._broadcast_parameter(self.v_threshold, x, "v_threshold") offset = self._broadcast_parameter(self.v_offset, x, "v_offset") h = self.v + x spike = self.surrogate_function(h + offset - threshold) spike_d = spike.detach() if self.detach_reset else spike if self.v_reset is None: self.v = h - spike_d * threshold else: self.v = spike_d * self.v_reset + (1.0 - spike_d) * h return spike
[文档] def multi_step_forward(self, x_seq: torch.Tensor): T = x_seq.shape[0] y_seq = [] if self.store_v_seq: v_seq = [] for t in range(T): y = self.single_step_forward(x_seq[t]) y_seq.append(y) if self.store_v_seq: v_seq.append(self.v) if self.store_v_seq: self.v_seq = torch.stack(v_seq, dim=0) return torch.stack(y_seq, dim=0)
[文档] def extra_repr(self): return ( f"v_threshold_shape={tuple(self.v_threshold.shape)}, " f"v_offset_shape={tuple(self.v_offset.shape)}, " f"channel_dim={self.channel_dim}, v_reset={self.v_reset}, " f"detach_reset={self.detach_reset}, step_mode={self.step_mode}, " f"backend={self.backend}" )
[文档] class NonSpikingIFNode(NonSpikingBaseNode): def __init__(self, decode: Optional[str] = None): """ **API Language** - :ref:`中文 <NonSpikingIFNode.__init__-cn>` | :ref:`English <NonSpikingIFNode.__init__-en>` ---- .. _NonSpikingIFNode.__init__-cn: * **中文** 不发放脉冲的 IF 节点,输出膜电位(或根据 ``decode`` 进行解码)。 :param decode: 非脉冲输出解码方式,见 :class:`NonSpikingBaseNode` :type decode: Optional[str] ---- .. _NonSpikingIFNode.__init__-en: * **English** Non-spiking IF node that outputs membrane potential (or decoded outputs specified by ``decode``). :param decode: Decoding mode for non-spiking outputs, see :class:`NonSpikingBaseNode` :type decode: Optional[str] """ super().__init__(decode)
[文档] def neuronal_charge(self, x: torch.Tensor): self.v = self.v + x