fangwei123456 / spikingjelly

SpikingJelly is an open-source deep learning framework for Spiking Neural Network (SNN) based on PyTorch.
https://spikingjelly.readthedocs.io
Other
1.39k stars 242 forks source link

关于自定义cupy神经元的问题 #426

Open youlingforever0328 opened 1 year ago

youlingforever0328 commented 1 year ago

作者您好,我想请教一个问题 在教程里关于自定义CUPY神经元的部分 class CUPYIFNode(base.MemoryModule): def init(self, v_threshold: float = 1., v_reset: float or None = 0., surrogate_function: Callable = surrogate.Sigmoid(), detach_reset: bool = False): super().init() self.v_threshold = v_threshold self.v_reset = v_reset self.surrogate_function = surrogate_function self.detach_reset = detach_reset self.step_mode = 'm' if v_reset is not None: self.register_memory('v', v_reset) else: self.register_memory('v', 0.)

def multi_step_forward(self, x_seq: torch.Tensor):

    if isinstance(self.v, float):
        self.v = torch.zeros_like(x_seq[0])

    hard_reset = self.v_reset is not None
    if x_seq.dtype == torch.float:
        dtype = 'float'
    elif x_seq.dtype == torch.half:
        dtype = 'half2'

    forward_kernel = IFNodeFPTTKernel(hard_reset=hard_reset, dtype=dtype)
    backward_kernel = IFNodeBPTTKernel(surrogate_function=self.surrogate_function.cuda_codes, hard_reset=hard_reset, detach_reset=self.detach_reset, dtype=dtype)

    # All tensors wil be regard as 2D or 1D. Thus, we use flatten
    spike_seq, v_seq = IFNodeATGF.apply(x_seq.flatten(1), self.v.flatten(), self.v_threshold, self.v_reset, forward_kernel, backward_kernel)

    spike_seq = spike_seq.view(x_seq.shape)
    self.v = v_seq[-1].view(x_seq.shape[1:])

    return spike_seq

CUPYIFNode的输出只有spike_seq,然后他被用来计算loss(在教程里是直接对spike_seq求和),然后反向传播,这个loss的计算并没有用到v_seq,那么请问为什么教程里提到损失会对v_seq有梯度呢?可能我学的还不够深入,不太明白这里loss是只用spike_seq计算的,为什么loss对v_seq会有梯度【grad_v_seq】呢?因为我自己用cuda实现了一个类似的过程,其中loss对v_seq的梯度都是0,不太明白为什么,还请多多指教 @staticmethod def backward(ctx, grad_spike_seq: torch.Tensor, grad_v_seq: torch.Tensor):

    backward_kernel, blocks, threads, py_dict = NeuronATGFBase.pre_backward(ctx, grad_spike_seq, grad_v_seq)
    backward_kernel((blocks,), (threads,), py_dict)

    return py_dict['grad_x_seq'], py_dict['grad_v_init'], None, None, None, None
fangwei123456 commented 1 year ago

在pytorch中实现autograd function时,有几个输入就要返回几个梯度,由于v是前向传播的输入,因此grad_v也一定需要成为反向传播的输出

v_seq是可能参与loss计算的,所以在SJ框架里面也给它计算了梯度

fangwei123456 commented 1 year ago

如果你把神经元设计成 输入: v[-1], x_seq 输出: v[T-1], s_seq

在这种情况下你就只需要回传grad_v而不是grad_v_seq了。但在这种情况下无法获取到中间的v_seq并进行某些计算,比如v_seq参与计算损失

youlingforever0328 commented 1 year ago

在pytorch中实现autograd function时,有几个输入就要返回几个梯度,由于v是前向传播的输入,因此grad_v也一定需要成为反向传播的输出

v_seq是可能参与loss计算的,所以在SJ框架里面也给它计算了梯度

所以说如果输出的v_seq并未参与计算的话,损失对v_seq的导数是0对吗? 比如在"编写CUPY神经元"这一教程最后的损失是只用了spike对其求和,并没有用到v_seq, 所以我发现损失对v_seq的导数都是0.那么这个时候在反向传播公式里面 image 中的L对v_t的导数这一项都是0是吗?关于这块还不是很懂,想请教下作者

fangwei123456 commented 1 year ago

aL/aV是0(V不直接参与loss计算),但dL/dV不是0(V通过S参与loss计算)

youlingforever0328 commented 1 year ago

aL/aV是0(V不直接参与loss计算),但dL/dV不是0(V通过S参与loss计算)

也就是说,上述公式里aL/aV[t]这一项,无论是v_seq有没有参与损失计算,都是一直存在的吗,并且若v_seq没有参与损失计算,那么就无法通过pytorch中autograd function显式得到aL/aV[t](我自己打印出来都是0),只能通过自己在底层反向传播时手动求出aL/aV[t]是这样吗(通过V和S的关系递推)

fangwei123456 commented 1 year ago

是的