1. 이벤트 기반 카메라(Event Camera)란?

전통적인 프레임 기반 카메라(ex: 웹캠, 스마트폰 카메라)는 초당 고정된 횟수(예: 30FPS, 60FPS)로 전체 픽셀 영역의 스냅샷을 찍습니다. 그러나 이 방식은 변동이 없는 배경 픽셀 정보까지 계속 저장하므로 데이터 중복성과 전력 소모가 매우 심합니다.

반면 이벤트 카메라 (DVS: Dynamic Vision Sensor)는 픽셀의 밝기(조도)가 '변화(Change)'할 때만 데이터를 기록합니다. 어떤 픽셀이 밝아지거나 어두워지는 순간에만 비동기적으로 스파이크(이벤트)를 발생시키므로 마이크로초($\mu s$) 단위의 초고속 응답 속도초저전력 특성을 자랑합니다. 이러한 특성 덕분에 SNN과 찰떡궁합을 이룹니다.

2. Neuromorphic Dataset과 Tonic 소개

이벤트 카메라로 촬영된 데이터셋(예: N-MNIST, DVS128 Gesture)은 전통적인 이미지 텐서 포맷이 아니라, 일련의 비동기적 이벤트 스트림 기록(시간, X좌표, Y좌표, 1 혹은 -1의 극성) 형태를 지닙니다.

이를 PyTorch 데이터 로더에서 손쉽게 가져와 전처리 작업을 해주는 라이브러리가 바로 Tonic입니다. Tonic은 torchvision의 이벤트 비전 버전이라고 생각하면 됩니다.

설치 방법

$ pip install tonic

3. N-MNIST 데이터셋 로드 및 변환하기

N-MNIST (Neuromorphic-MNIST) 데이터셋은 기존의 정적인 숫자 이미지(MNIST)를 화면에 띄우고 DVS 카메라를 움직이면서 촬영한 버전입니다.

import tonic
import torch
from torch.utils.data import DataLoader

# 1. 데이터셋 다운로드 및 로드 (사전 전처리 없음)
nmnist_train = tonic.datasets.NMNIST(save_to='./data', train=True)
events, target = nmnist_train[0]

# events는 구조화된 Numpy 배열: (x, y, t(Microseconds), p(Polarity: +1, -1))
print(f"Number of events: {len(events)}")

이벤트를 밀집 텐서(Dense Tensor)로 변환 (ToFrame)

초당 수백만 개의 이벤트 데이터 스트림을 SNN 네트워크의 입력 차원으로 전달하려면, 밀집된 스파이크 텐서 프레임으로 변환해야 합니다. Tonic의 ToFrame 트랜스폼을 사용하면 일정 시간(time window) 간격으로 이벤트를 모아 2D 프레임으로 생성할 수 있습니다.

import tonic.transforms as transforms

# 시뮬레이션 환경 (스텝 수 및 프레임 변환 설정)
time_window = 1000 # 1000 마이크로초(1ms) 마다 이벤트를 모아 프레임 1개 완성

# Tonic 변환 파이프라인 구성
sensor_size = tonic.datasets.NMNIST.sensor_size # (34, 34, 2)
transform = transforms.Compose([
    # event 스트림을 time_window 크기에 맞춰 프레임으로 누적 (Batch, Time, Channels, Height, Width)
    transforms.ToFrame(sensor_size=sensor_size, time_window=time_window) 
])

# 트랜스폼을 적용한 데이터셋
nmnist_train = tonic.datasets.NMNIST(save_to='./data', transform=transform, train=True)

# PyTorch 데이터 로더로 로드 (커스텀 collate_fn 권장)
from tonic import disk_cached_dataset
train_loader = DataLoader(nmnist_train, batch_size=64, collate_fn=tonic.collation.PadTensors())

# 이제 train_loader에서 텐서를 반복하며 SNN 네트워크의 입력으로 사용 가능!

요약

  • 이벤트 카메라는 동적 변화만 잡아내어 데이터 효율성을 극대화합니다.
  • Tonic 패키지를 통해 N-MNIST, DVS Gesture 등 복잡한 이벤트 데이터 포맷을 쉽게 파싱하고 로드할 수 있습니다.
  • ToFrame 변환을 적용해 순수한 이벤트 스트림 배열을 SNN PyTorch 모델에 주입하기 알맞은 (Time, Batch, Channel, H, W) 텐서 형태로 손쉽게 통합할 수 있습니다.

4. 주요 Neuromorphic 데이터셋 종류

데이터셋 원본 데이터 해상도 클래스 수 Tonic 클래스
N-MNIST MNIST (손글씨 숫자) 34×34 10 tonic.datasets.NMNIST
N-Caltech101 Caltech-101 (사물 분류) 240×180 101 tonic.datasets.NCALTECH101
DVS128 Gesture 손 제스처 (IBM 연구) 128×128 11 tonic.datasets.DVSGesture
CIFAR10-DVS CIFAR-10 (이미지 분류) 128×128 10 tonic.datasets.CIFAR10DVS

5. Tonic + snnTorch 통합 학습 예시 (N-MNIST)

N-MNIST 데이터를 로드하여 snnTorch SNN 모델을 학습하는 완전한 파이프라인입니다.

import torch
import torch.nn as nn
import tonic
import tonic.transforms as transforms
from tonic import DiskCachedDataset
from torch.utils.data import DataLoader
import snntorch as snn
from snntorch import surrogate, utils, functional as SF

# ── 설정 ─────────────────────────────────────────────
batch_size = 64
num_steps  = 30   # N-MNIST는 ms 단위이므로 30ms = 30 프레임
beta       = 0.9
num_epochs = 5
lr         = 1e-3
spike_grad = surrogate.fast_sigmoid(slope=25)

# ── 데이터 로드 (Tonic) ───────────────────────────────
sensor_size = tonic.datasets.NMNIST.sensor_size   # (34, 34, 2)
time_window = 1000  # 1ms씩 프레임으로 누적

frame_transform = transforms.Compose([
    transforms.Denoise(filter_time=10000),         # 노이즈 제거
    transforms.ToFrame(sensor_size=sensor_size,
                       time_window=time_window),
])

# 디스크 캐시를 활용해 전처리 결과 재사용
nmnist_train = DiskCachedDataset(
    tonic.datasets.NMNIST(save_to='./data', train=True,
                          transform=frame_transform),
    cache_path='./cache/nmnist_train'
)
nmnist_test = DiskCachedDataset(
    tonic.datasets.NMNIST(save_to='./data', train=False,
                          transform=frame_transform),
    cache_path='./cache/nmnist_test'
)

train_loader = DataLoader(nmnist_train, batch_size=batch_size,
                          collate_fn=tonic.collation.PadTensors(batch_first=False),
                          shuffle=True)
test_loader  = DataLoader(nmnist_test,  batch_size=batch_size,
                          collate_fn=tonic.collation.PadTensors(batch_first=False),
                          shuffle=False)

# ── 모델 ─────────────────────────────────────────────
# N-MNIST 입력: (T, B, 2채널, 34, 34)
net = nn.Sequential(
    nn.Conv2d(2, 16, kernel_size=3, padding=1),    # (B, 16, 34, 34)
    nn.MaxPool2d(2),                               # (B, 16, 17, 17)
    snn.Leaky(beta=beta, spike_grad=spike_grad,
              init_hidden=True),
    nn.Conv2d(16, 32, kernel_size=3, padding=1),   # (B, 32, 17, 17)
    nn.MaxPool2d(2),                               # (B, 32, 8, 8)
    snn.Leaky(beta=beta, spike_grad=spike_grad,
              init_hidden=True),
    nn.Flatten(),
    nn.Linear(32 * 8 * 8, 10),
    snn.Leaky(beta=beta, spike_grad=spike_grad,
              init_hidden=True, output=True),
)

optimizer = torch.optim.Adam(net.parameters(), lr=lr)
loss_fn   = SF.ce_rate_loss()

# ── 학습 루프 ─────────────────────────────────────────
for epoch in range(num_epochs):
    net.train()
    for data, targets in train_loader:
        # data: (T, B, C, H, W) - PadTensors가 길이를 맞춰줌
        T = data.shape[0]     # 실제 타임 스텝 수 (가변)

        optimizer.zero_grad()
        spk_rec = []
        utils.reset(net)

        for t in range(T):
            frame = data[t].float()     # (B, C, H, W)
            spk_out, _ = net(frame)
            spk_rec.append(spk_out)

        spk_rec = torch.stack(spk_rec)  # (T, B, 10)
        loss_val = loss_fn(spk_rec, targets)
        loss_val.backward()
        optimizer.step()

    # 간단한 에폭별 출력
    print(f"Epoch {epoch+1} complete")

print("훈련 완료! N-MNIST SNN 분류기 학습 성공.")

6. 디스크 캐시(DiskCachedDataset) 활용 팁

Tonic의 ToFrame 변환은 매 배치마다 이벤트 스트림을 프레임으로 변환하므로 CPU 연산이 많이 필요합니다. DiskCachedDataset은 첫 번째 변환 결과를 디스크에 저장하여 이후 에폭에서 빠르게 로드합니다. 데이터셋 규모가 클수록 학습 속도가 대폭 향상됩니다.

  • 처음 실행 시 변환 및 캐시 저장 (시간이 걸림)
  • 2번째 에폭부터 캐시에서 직접 로드 (매우 빠름)
  • 캐시 경로: cache_path 인수로 지정
목록으로 돌아가기