文章目录
在本教程中,我们将假设我们有一些非脉冲输入数据 (即MNIST数据集), 尝试使用几种不同的技术将这些数据编码为脉冲。
1. 配置MNIST数据集
1.1 配置库与环境
import snntorch as snn
import torch
# Training Parameters
batch_size=128
data_path='/tmp/data/mnist'
num_classes = 10 # MNIST has 10 output classes
# Torch Variables
dtype = torch.float
1.2 下载数据集
from torchvision import datasets, transforms
# Define a transform
transform = transforms.Compose([
transforms.Resize((28,28)),
transforms.Grayscale(),
transforms.ToTensor(),
transforms.Normalize((0,), (1,))])
mnist_train = datasets.MNIST(data_path, train=True, download=True, transform=transform)
使用date_subset函数裁剪数据集,当subset=10时,包含60,000个数据的训练集将减少到 6,000 个
from snntorch import utils
subset = 10
mnist_train = utils.data_subset(mnist_train, subset)
1.3 创建DataLoader
PyTorch 中的DataLoader是一个方便的接口,用于将数据传递到网络中。它们返回一个 被分成大小为 batch_size 的迭代器。
from torch.utils.data import DataLoader
train_loader = DataLoader(mnist_train, batch_size=batch_size, shuffle=True)
2.脉冲编码
脉冲神经网络(SNN) 旨在利用时变数据(time-varing data)。 然而, MNIST不是一个时变数据集。将MNIST与SNN一起使用有两种选择:
1.在每个时间段(time step)内,重复地将相同的训练样本 X传递给神经网络。 这就像把MNIST数据集转化为静态不变的视频。训练样本中的每个元素X可以在0和1之间归一化(Normalized)。
2.将输入(像素点)转换成长度为 num_steps 的脉冲序列
以下展开从数据到脉冲的转换(编码)
snntorch.spikegen 模块(脉冲生成模块)包含一系列可以简化这个转换过程的功能。 现在我们在 snntorch 库中有三个选择可用于脉冲编码:
1.脉冲率编码(Rate coding): spikegen.rate
2.延迟编码(Latency coding): spikegen.latency
3.增量调制(Delta modulation): spikegen.delta
以上方法不同之处:
脉冲率编码 用输入特征来确定 脉冲频率
延迟编码 利用输入特征来确定 脉冲时长
增量调制 利用输入特征的时态 变化 来生成脉冲
2.1 MNIST的脉冲率编码(Rate Coding)
脉冲发生的概率:
P(Rij=1)=Xij=1-P(Rij=0)
创建一个填充值为0.5的向量,并先应用上述伯努利实验的类比来进行概率编码:
# Temporal Dynamics
num_steps = 10
# 创建10个 0.5的向量
raw_vector = torch.ones(num_steps)*0.5
# 伯努利实验
rate_coded_vector = torch.bernoulli(raw_vector)
print(f"Converted vector: {rate_coded_vector}")
Converted vector: tensor([1., 1., 1., 0., 0., 1., 1., 0., 1., 0.])
print(f"The output is spiking {rate_coded_vector.sum()*100/len(rate_coded_vector):.2f}% of the time.")
The output is spiking 60.00% of the time.
增加 raw_vector 的长度,再试一次:
num_steps = 100
# 创建100个 0.5的向量
raw_vector = torch.ones(num_steps)*0.5
# 伯努利实验
rate_coded_vector = torch.bernoulli(raw_vector)
print(f"The output is spiking {rate_coded_vector.sum()*100/len(rate_coded_vector):.2f}% of the time.")
The output is spiking 48.00% of the time.
当num_steps趋向于无穷时,脉冲率将接近原始值
对于一个MNIST图像, 此概率意味着其像素的值。一个白色像素对应100%的脉冲概率, 而一个黑色像素对应0%。
以类似的方式 spikegen.rate 可以代替上述对伯努利试验的类比,生成概率编码数据样本。 由于MNIST的每个样本只是一个图像, 我们可以用 num_steps 来随着时间的推移重复它。
from snntorch import spikegen
# 通过 minibatches进行迭代
data = iter(train_loader)
data_it, targets_it = next(data)
# Spiking Data
spike_data = spikegen.rate(data_it, num_steps=num_steps)
2.3 MNIST的延迟编码(Latency coding)
时序编码能捕捉神经元精确发射时间的信息;与依赖发射频率的脉冲率编码相比, 单个脉冲的意义要大得多。虽然这样更容易受到噪声的影响, 但也能将运行 SNN 算法的硬件功耗降低几个数量级。
spikegen.latency 函数允许每个输入在整个扫描时间内最多触发 一次。 接近 1 的特征会更早触发,接近 0 的特征会更晚触发。 也就是说,在我们的 MNIST 案例中,亮像素会更早触发,暗像素会更晚触发。
大 输入意味着 早 触发脉冲; 小 输入意味着 晚 触发脉冲.
def convert_to_time(data, tau=5, threshold=0.01):
spike_time = tau * torch.log(data / (data - threshold))
return spike_time
以上函数将强度为[0,1]的输入特征转化为延迟编码响应
现在我们可以用这个函数来可视化输入特征强度和其对应的脉冲时间的关系。
raw_input = torch.arange(0, 5, 0.05) # tensor from 0 to 5
spike_times = convert_to_time(raw_input)
plt.plot(raw_input, spike_times)
plt.xlabel('Input Value')
plt.ylabel('Spike Time (s)')
plt.show()
可以看出,数值越小,脉冲发生的时间越晚,且呈指数关系。
向量 spike_times 包含脉冲触发的时间, 而不是包含脉冲本身(1 和 0)的稀疏张量。 在运行 SNN 仿真时,我们需要使用 1/0 表示来获得使用脉冲的所有优点。 整个过程可以使用 spikegen.latency 自动完成, 只需我们给 data_it
传递一个来自MNIST数据集的迷你批次:
spike_data = spikegen.latency(data_it, num_steps=100, tau=5, threshold=0.01)
tau: 电路的 RC 时间常数。默认情况下,输入特征被视为注入 RC 电路的恒定电流。 tau 越大,激活(firing)速度越慢
threshold: 膜电位点火阈值。低于该阈值的输入值没有闭式解(又称解析解),因为输入电流不足以驱动膜达到阈值。所有低于阈值的值都会被剪切并分配到最后一个时间段(time step)。