백엔드 개발자가 Brain-Computer Interface(BCI)와 신경공학을 처음부터 따라갈 수 있도록 만든 학습 가이드. 환경 셋업부터 SNN, EEG 분석, 비침습 자극 시뮬레이션까지 다룹니다.
학습 방식: 각 Step은 ① 개념 영상 → ② 코드 골격(
TODO로 일부 비어 있음) → ③ 직접 채우기 → ④ 확장 과제(Challenges) 4단계입니다. 빈칸을 채워보고 막히면 References 영상을 다시 보세요.
- Overview
- How to Use
- Prerequisites
- Installation
- Glossary
- Curriculum
- Final Project Ideas
- Datasets
- Library Cheat Sheet
- Troubleshooting
- References
| Part | Topic | Steps |
|---|---|---|
| 1 | Foundations (neuroscience, environment) | 2 |
| 2 | EEG & Motor Imagery BCI | 4 |
| 3 | Spiking Neural Networks | 3 |
| 4 | Circuit Decoding | 2 |
| 5 | Deep Learning for EEG | 2 |
| 6 | Non-invasive Stimulation | 3 |
| 7 | Real-time Closed-loop | 3 |
각 Step의 코드 블록은 완성본이 아니라 골격입니다. TODO와 ...을 직접 채우세요.
- Watch — 영상으로 개념을 머리에 넣습니다.
- Code skeleton — 빈칸(
TODO,...)을 채워 실행 가능한 코드로 만듭니다. - Run & Verify — 결과가 영상에서 본 것과 같은지 확인합니다.
- Challenges — 변형·확장 과제를 풀어 응용력을 만듭니다. 최소 1개, 가능하면 3개 이상 풀기를 권장.
- Notes —
notes/stepNN.md에 막힌 부분과 해결법을 기록하세요. 이게 가장 큰 자산이 됩니다.
💡 빈칸을 어떻게 채울지 모를 때: 해당 라이브러리 공식 문서 검색 → API signature 확인 → 영상의 코드와 비교.
- 기본적인 Python 사용 경험
- 고등학교 수준 미적분 (미분방정식 개념)
- 선형대수 기초 (벡터·행렬)
- Git 사용 가능
- OS: Windows 10+, macOS 11+, Ubuntu 20.04+
# Anaconda: https://www.anaconda.com/download
conda create -n bci python=3.11 -y
conda activate bci# 공통
pip install numpy scipy matplotlib pandas jupyter scikit-learn seaborn
# EEG 분석
pip install mne moabb braindecode
# 딥러닝 + SNN
pip install torch torchvision
pip install snntorch brian2bci_study/
├── step01_neuroscience/
├── step02_mne_first/
├── ...
├── data/ # 모든 데이터셋 저장
└── notes/ # 개념 정리 노트 (필수)
cd ~/bci_study
jupyter notebookpython -c "import mne, torch, snntorch, brian2; print('OK')"| 용어 | 한 줄 설명 |
|---|---|
| EEG | Electroencephalography. 두피 전극으로 측정하는 뇌 표면 전기 신호 (μV 단위). |
| BCI | Brain-Computer Interface. 뇌 신호 → 컴퓨터 명령 변환 시스템. |
| SNN | Spiking Neural Network. 실제 뉴런처럼 스파이크(0/1)로 통신하는 신경망. |
| LIF Neuron | Leaky Integrate-and-Fire. 전압을 누적하다가 임계값을 넘으면 스파이크 발사, 평소엔 leak. |
| STDP | Spike-Timing-Dependent Plasticity. 스파이크 발생 순서에 따라 시냅스 가중치가 변하는 학습 규칙. |
| Euler Method | 미분방정식 수치해법 — 다음값 = 현재값 + 기울기 × dt. |
| ICA | Independent Component Analysis. EEG 잡음 성분(눈깜빡임·EMG) 분리. |
| ERD/ERS | Event-Related De-/Synchronization. 운동 상상 시 특정 주파수 전력 변화. |
| P300 | 자극 후 ~300ms에 나타나는 양의 EEG 정점. |
| CSP | Common Spatial Pattern. 운동 상상 EEG의 클래시컬 특징 추출법. |
| tDCS / tACS / TI | 두피 전기 자극 — 직류 / 교류 / Temporal Interference. |
| MOABB | Mother of All BCI Benchmarks. BCI 데이터셋 통합 인터페이스. |
| EEGNet | 모터 이미저리 EEG 분류용 경량 CNN. BCI 표준 베이스라인. |
| Place Cell / RSC | 해마·뇌량팽대후피질의 공간 정보 담당 뉴런. 2014 노벨상. |
| Surrogate Gradient | 스파이크 함수의 미분 불가능을 우회하는 SNN 학습 기법. |
Goal: 뉴런이 어떻게 신호를 보내는지 한 문장으로 설명할 수 있다.
Watch
Concepts: membrane potential, action potential, synapse, excitation/inhibition.
Challenges
- 뉴런이 신호를 보내는 과정을 5개 단어(예: depolarization, threshold, …)로만 요약해서 노트에 적기.
- 흥분성/억제성 시냅스의 차이를 본인 회사 시스템(서비스 호출·차단)에 비유해서 설명.
- EEG가 측정하는 것이 단일 뉴런인가, 집단인가? 왜 그런지 노트에 정리.
Goal: 오일러법으로 미분방정식을 풀 수 있다. SNN 시뮬레이션의 기반.
Watch
Concepts: dt (step size), forward/backward Euler, truncation error.
Code skeleton — 02_euler.ipynb (지수 감쇠 dy/dt = -k·y 풀기)
import numpy as np
import matplotlib.pyplot as plt
k = 0.5
y0 = 10.0
t_end = 10.0
dt = 0.1
# TODO: forward Euler 루프 작성
# y[i+1] = y[i] + dt * (-k * y[i])
n = int(t_end / dt)
t = np.linspace(0, t_end, n)
y = np.zeros(n); y[0] = y0
for i in range(n - 1):
y[i+1] = ...
# 해석해와 비교
y_exact = y0 * np.exp(-k * t)
plt.plot(t, y, label='Euler'); plt.plot(t, y_exact, '--', label='Exact')
plt.legend(); plt.show()Challenges
dt를 0.5, 0.1, 0.01로 바꾸면서 해석해와 오차가 어떻게 변하는지 표로 정리.- 같은 ODE를 backward Euler로 다시 풀고 두 결과를 한 그래프에 그리기.
- 진자 운동
d²θ/dt² = -(g/L)·sin(θ)을 2차 시스템 → 1차 두 개로 분해해서 풀고 시각화.
Goal: EEG raw 데이터를 열어서 채널 수와 샘플링 주파수를 확인하고 그래프를 그린다.
Watch
Dataset: MNE 샘플 (자동 다운로드, ~1GB)
Code skeleton — 01_first_eeg.ipynb
import mne
sample_path = mne.datasets.sample.data_path()
raw_fname = sample_path / 'MEG' / 'sample' / 'sample_audvis_raw.fif'
raw = mne.io.read_raw_fif(raw_fname, preload=True)
print(raw.info)
# TODO: 처음 10초 / 20채널만 시각화
raw.plot(duration=..., n_channels=...)
# TODO: 1~40 Hz 대역통과 필터 적용 (raw.filter)
...
# TODO: 필터 전/후 차이를 비교하려면? raw.copy()로 원본을 보존하고 두 객체를 따로 plotChallenges
raw.info['sfreq'],raw.info['nchan']을 변수로 저장하고 출력.- EEG 채널만 골라서(
raw.pick_types(eeg=True, meg=False)) 다시 그려보고 채널 수가 얼마나 줄었는지 비교. - notch filter 60Hz를 적용한 후 PSD(
raw.plot_psd(fmax=80))를 그려서 전원 잡음이 사라졌는지 확인. preload=False로 로드하면 어떤 차이가 있는지 적어보기.
Goal: 4-class 운동 상상 EEG 데이터를 받고 trial 단위로 본다.
Watch
- 🇺🇸 Talha Anwar — Read Motor Imagery EEG BCI Competition IV 2a
- 🇺🇸 Ollie D'Amico — Motor Imagery & CSP
Dataset: BCI Competition IV 2a
- 자동: MOABB docs
- 수동: bbci.de download (420MB)
Code skeleton — 02_motor_imagery.ipynb
from moabb.datasets import BNCI2014_001
from moabb.paradigms import MotorImagery
import matplotlib.pyplot as plt
dataset = BNCI2014_001()
dataset.subject_list = [1]
paradigm = MotorImagery(n_classes=4, fmin=8, fmax=30)
X, y, metadata = paradigm.get_data(dataset=dataset, subjects=[1])
print("Shape:", X.shape)
print("Classes:", set(y))
# TODO: 첫 trial의 첫 채널을 그려보기
plt.plot(...)
plt.title(f"Class: {y[0]}"); plt.show()Challenges
- 클래스별 trial 개수를
collections.Counter로 출력. left_hand라벨의 trial 5개를 subplot으로 한 figure에 그리기.- 클래스별 평균 파형(채널 1개)을 그려서 차이를 눈으로 확인.
subject_list = [1, 2, 3]으로 늘리고 X 모양이 어떻게 변하는지 적기.fmin/fmax를 (1, 40)으로 바꾸면 결과가 어떻게 달라지는지 관찰하고 이유 정리.
Goal: ICA로 눈깜빡임 잡음을 식별·제거한다.
Watch
Code skeleton — 03_filter_ica.ipynb
import mne
sample = mne.datasets.sample.data_path()
raw = mne.io.read_raw_fif(sample/'MEG'/'sample'/'sample_audvis_raw.fif', preload=True)
# TODO: 60Hz notch + 1~40Hz bandpass 적용
...
ica = mne.preprocessing.ICA(n_components=20, random_state=42)
ica.fit(raw)
ica.plot_components()
# TODO: EOG 성분 자동 검출 → ica.exclude에 넣고 apply
eog_indices, eog_scores = ica.find_bads_eog(raw)
print("EOG components:", eog_indices)
# ica.exclude = ...
# raw_clean = ...Challenges
ica.plot_sources(raw)로 시간축 성분을 그려서 눈깜빡임의 큰 점프가 어디에 있는지 직접 찾기.- 자동 검출 결과와 본인이 눈으로 고른 인덱스를 비교.
- ICA 적용 전/후 raw 데이터를 같은 구간에서 그려 잡음이 줄었는지 확인.
n_components를 10, 30으로 바꾸면 결과가 어떻게 달라지는지 관찰.
Goal: 자극 시점의 EEG 평균(evoked) 응답을 시각화한다.
Watch
Code skeleton — epochs_erp.ipynb
import mne
sample = mne.datasets.sample.data_path()
raw = mne.io.read_raw_fif(sample/'MEG'/'sample'/'sample_audvis_raw.fif', preload=True)
raw.filter(1., 40.)
events = mne.find_events(raw, stim_channel='STI 014')
event_id = {'auditory/left': 1, 'auditory/right': 2}
# TODO: 자극 전 200ms ~ 자극 후 500ms 윈도우로 Epochs 생성
epochs = mne.Epochs(raw, events, event_id, tmin=..., tmax=...,
preload=True, baseline=(None, 0))
# TODO: auditory/left 평균 evoked 그리기 + 토포맵
evoked = epochs[...].average()
# evoked.plot()
# evoked.plot_topomap(times=[...])Challenges
auditory/left와auditory/right의 evoked를 같은 figure에 겹쳐 그리기 (mne.viz.plot_compare_evokeds).- visual 자극(event id 3, 4)도 추가하고 청각 vs 시각 토포맵 차이 비교.
- epoch reject 기준(
reject=dict(eeg=150e-6))을 추가해 몇 개 trial이 버려지는지 확인. - 특정 시점(P100, N170)의 토포맵을 저장해 노트에 첨부.
Goal: 오일러법으로 LIF 뉴런 하나를 직접 시뮬레이션한다.
Watch
- 🇺🇸 Gerstner Lab — CNS1.3 LIF Model
- 🇺🇸 NPTEL — Neuromorphic LIF
- 🇺🇸 Arnau Blanco — Integrate-and-Fire in Python
Code skeleton — 04_lif_neuron.ipynb
import numpy as np
import matplotlib.pyplot as plt
# 파라미터
dt, T = 0.1, 200 # ms
tau = 10.0
V_rest, V_th, V_reset = -70, -55, -75
R, I = 10.0, 1.6
n_steps = int(T / dt)
V = np.zeros(n_steps); V[0] = V_rest
spikes = []
# TODO: dV/dt = (-(V - V_rest) + R*I) / tau 를 Euler로 풀기
for t in range(1, n_steps):
dV = ...
V[t] = V[t-1] + dV * dt
# TODO: V가 임계값을 넘으면 spikes에 시각(t*dt) 기록하고 V[t] = V_reset
if ...:
...
plt.plot(np.arange(n_steps) * dt, V)
plt.scatter(spikes, [V_th]*len(spikes), color='red', marker='|', s=200)
plt.xlabel("Time (ms)"); plt.ylabel("V (mV)"); plt.show()
print(f"Total spikes: {len(spikes)}")Challenges
- 입력 전류
I를 0.5, 1.0, 1.6, 2.5 nA로 바꿔가며 firing rate(spikes/sec) 곡선을 그리기 (F-I curve). tau를 5, 10, 20 ms로 바꾸면 발사 빈도가 어떻게 달라지는지 관찰.- 시간에 따라 변하는 입력(
I = 1.6 * np.sin(2*np.pi*5*t/1000))을 주입해서 막전위 동기화 시각화. - 불응기(refractory period) 2ms를 구현 — 스파이크 직후 일정 시간 V를 V_reset에 고정.
- 뉴런 100개를 동시에 시뮬레이션하고 raster plot 그리기 (뉴런마다 다른
I부여).
Goal: 스파이크 타이밍에 따라 시냅스 가중치가 어떻게 변하는지 이해한다.
Watch
Concepts: pre/post-synaptic spike, LTP, LTD, weight update window.
Code skeleton — 08_stdp.ipynb (STDP 학습 윈도우 함수 그리기)
import numpy as np
import matplotlib.pyplot as plt
A_plus, A_minus = 0.1, 0.12
tau_plus, tau_minus = 20.0, 20.0 # ms
dt = np.linspace(-50, 50, 200) # post - pre 시각차
# TODO: STDP 함수 작성
# dt > 0 → Δw = A_plus * exp(-dt / tau_plus)
# dt < 0 → Δw = -A_minus * exp( dt / tau_minus)
dw = np.where(dt > 0, ..., ...)
plt.axhline(0, color='gray'); plt.axvline(0, color='gray')
plt.plot(dt, dw); plt.xlabel("t_post - t_pre (ms)"); plt.ylabel("Δw"); plt.show()Challenges
tau_plus,tau_minus를 5, 20, 50 ms로 바꿔가며 윈도우 모양 비교.- 두 LIF 뉴런(Step 7) 사이에 STDP 시냅스를 만들고 100 trial 동안 weight 변화를 그리기.
- anti-STDP(부호 반전) 규칙으로 바꾸면 가중치가 어떻게 발산/수렴하는지 관찰.
Goal: PyTorch 위에서 2-layer SNN을 만든다.
Watch
Code skeleton — 05_snn_basic.ipynb
import snntorch as snn
import torch
import torch.nn as nn
beta = 0.95
# TODO: 입력 4 → hidden 8 → 출력 2 인 2-layer SNN
# 힌트: nn.Linear → snn.Leaky → nn.Linear → snn.Leaky(output=True)
net = nn.Sequential(
...
)
x = torch.randn(1, 4)
spk_rec = []
for step in range(25):
spk, mem = net(x)
spk_rec.append(spk)
spk_out = torch.stack(spk_rec)
print("Output spikes:", spk_out.shape)
# TODO: 두 클래스의 스파이크 합을 비교해 더 많이 발사한 쪽을 "예측"으로 출력Challenges
beta를 0.5 / 0.9 / 0.99로 바꿔가며 hidden 뉴런 막전위 trace 시각화.- snntorch 공식 튜토리얼의 Training SNN on MNIST를 끝까지 돌리기.
- surrogate gradient를
fast_sigmoid대신atan으로 바꿔 학습 곡선 비교. - 본인이 만든 LIF(Step 7)와 snntorch
Leaky의 출력을 같은 입력·파라미터에서 비교.
Goal: 공간 정보 처리 뉴런과 RSC의 역할을 1분 설명한다.
Watch
Concepts: place field, grid cell, head-direction cell, retrosplenial cortex.
Optional Code skeleton — place_field.ipynb (가상 place field 시뮬레이션)
import numpy as np
import matplotlib.pyplot as plt
# 2m × 2m 환경, 격자 50×50
grid = np.linspace(-1, 1, 50)
X, Y = np.meshgrid(grid, grid)
# TODO: 중심 (cx, cy), 폭 sigma인 가우시안 place field 만들기
cx, cy, sigma = 0.3, -0.2, 0.2
firing_rate = ...
plt.imshow(firing_rate, extent=[-1, 1, -1, 1], origin='lower'); plt.colorbar(); plt.show()Challenges
- place cell 3개를 다른 위치에 놓고 한 figure에 3개 firing map 그리기.
- 동물 trajectory(랜덤 워크 1000step)를 만들고 각 시점에 firing rate 샘플링 → spike 시뮬레이션.
- grid cell 패턴(6각 격자)을 sinusoid 합으로 근사해 시각화.
- RSC가 BCI와 어떤 관련이 있을지 본인 생각을 노트에 3줄로 적기.
Goal: N개 뉴런의 동시 활동에서 자극 정보를 분류 알고리즘으로 읽는다.
Watch
Code skeleton — 06_population_decoding.ipynb
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
np.random.seed(0)
n_trials, n_neurons = 100, 50
# TODO: 가상 데이터 만들기
# X: shape (n_trials, n_neurons), 표준정규
# y: 절반 0, 절반 1
# class 1에서만 처음 10개 뉴런에 +1.0 bias (signal)
X = ...
y = ...
clf = LogisticRegression(max_iter=1000)
scores = cross_val_score(clf, X, y, cv=5)
print(f"Decoding accuracy: {scores.mean():.2%} ± {scores.std():.2%}")Challenges
- 신호를 받는 뉴런 개수를 10 → 3, 1로 줄이면 정확도가 어떻게 떨어지는지 그래프로 그리기.
- bias 크기를 0.1 ~ 2.0 범위에서 sweep하고 SNR과 정확도 관계 시각화.
LogisticRegression대신RandomForestClassifier,SVC(kernel='rbf')로 성능 비교.- confusion matrix를
sklearn.metrics.confusion_matrix로 출력. - (응용) Step 4의 BCI 데이터를 trial 평균 전력으로 요약한 후 같은 디코더로 분류 시도.
Goal: 가장 널리 쓰이는 EEG 분류 CNN인 EEGNet을 학습시킨다.
Watch
Reference: braindecode 공식 예제 — Trialwise Decoding on BCIC IV 2a
Code skeleton — 08_eegnet.ipynb
from braindecode.datasets import MOABBDataset
from braindecode.preprocessing import preprocess, Preprocessor, exponential_moving_standardize
from braindecode.models import EEGNetv4
dataset = MOABBDataset(dataset_name="BNCI2014001", subject_ids=[3])
# TODO: preprocessor 파이프라인 구성
# 1) EEG 채널만 선택, 2) V→μV 스케일, 3) 4~38Hz 필터, 4) exponential standardize
preprocessors = [
...
]
preprocess(dataset, preprocessors)
# TODO: EEGNetv4 인스턴스 (22ch, 4 class, 1125 samples)
model = ...
print(model)
print(f"Parameters: {sum(p.numel() for p in model.parameters())}")Challenges
- 공식 예제를 끝까지 돌리고 cross-subject vs within-subject 정확도 차이를 표로 정리.
ShallowFBCSPNet도 같은 데이터로 학습해 두 모델 파라미터·정확도 비교.- subject 3 외에 9명 전체에 대해 학습 → subject-wise 정확도 분포(boxplot) 그리기.
- train/val loss 곡선을 저장하고 overfitting 시점 표시.
- (응용) 학습된 모델 weight를
torch.save로 저장 후 Step 18 서버에서 로드해 추론에 사용.
Goal: 딥러닝과 비교할 클래시컬 BCI 파이프라인을 구축한다.
Code skeleton — 10_csp_lda.ipynb
from sklearn.pipeline import Pipeline
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.model_selection import cross_val_score
from mne.decoding import CSP
from moabb.datasets import BNCI2014_001
from moabb.paradigms import LeftRightImagery
paradigm = LeftRightImagery(fmin=8, fmax=30)
X, y, _ = paradigm.get_data(dataset=BNCI2014_001(), subjects=[1])
# TODO: CSP(6 components) → LDA 파이프라인 만들기
clf = Pipeline([
...
])
# TODO: 5-fold 평균 정확도 출력
scores = ...
print(f"CSP+LDA accuracy: {scores.mean():.2%}")Challenges
n_components를 2, 4, 6, 8, 10으로 sweep하고 정확도 vs 컴포넌트 수 그래프 그리기.- 주파수 대역을 (8,12) μ rhythm만, (13,30) β만, (8,30) 합칠 때 정확도 비교.
- CSP 필터의 spatial pattern을
csp.plot_patterns(epochs.info)로 시각화. - Step 12 EEGNet 정확도와 같은 피험자에서 직접 비교 표 만들기.
- classifier만
LogisticRegression,SVC로 교체해서 비교.
Goal: 비침습 자극의 종류와 TI의 원리(envelope frequency)를 설명한다.
Watch
Code skeleton — ti_envelope.ipynb (TI envelope 개념 수치 확인)
import numpy as np
import matplotlib.pyplot as plt
fs = 10000
t = np.linspace(0, 1, fs)
# TODO: 두 고주파 사인파 만들기
# f1 = 2000 Hz, f2 = 2010 Hz → 차주파수 10 Hz envelope
sig1 = ...
sig2 = ...
sum_sig = sig1 + sig2
plt.plot(t[:500], sum_sig[:500])
plt.title("Sum of 2000Hz + 2010Hz → 10Hz envelope")
plt.show()Challenges
- 두 주파수 차이를 5 / 10 / 40 Hz로 바꿔가며 envelope 주기 확인.
- envelope을 추출하려면 어떤 신호처리가 필요할지 적어보고
scipy.signal.hilbert로 구현. - TI가 일반 tACS보다 깊은 영역을 자극할 수 있는 이유를 노트에 3줄로 정리.
Goal: SimNIBS로 두피 전극 위치를 잡고 tDCS 시뮬레이션을 실행한다.
Watch
Install
Dataset: m2m_ernie 예제 (~500MB) — SimNIBS Datasets
Challenges
- GUI에서 motor cortex(M1) 위 전극(C3) → 반대편(Fp2) tDCS 1mA 시뮬레이션을 끝까지 실행.
- 결과 .msh 파일을 Gmsh로 열어 전기장 강도를 색깔로 확인.
- 전류 강도를 0.5 / 1 / 2 mA로 바꿔가며 최대 E-field 변화 기록.
Goal: Python 스크립트로 두 전극쌍에 다른 주파수를 인가하여 TI envelope을 만든다.
Reference: SimNIBS Scripting tutorial, TI 예제 simnibs/examples/simulations/TI.py
Challenges
- TI.py를 복사해서 두 전극쌍 위치를 본인이 정한 좌표로 바꿔 실행.
- envelope amplitude가 가장 큰 voxel의 위치를 출력.
- 전극 위치를 1cm 옮기면 envelope hotspot이 얼마나 이동하는지 비교.
- (응용) 결과를 numpy array로 export해서 matplotlib slice view 만들기.
Watch
Concepts: latency, sliding window, online inference.
Challenges
- closed-loop BCI에서 latency가 100ms 넘으면 어떤 문제가 생기는지 영상에서 찾아 정리.
- 본인이 만들 시스템의 latency budget을 component별로 분배 (acquisition + filtering + inference + stimulation).
- 본인이 일했던 백엔드 시스템에서 "실시간" 정의를 어떻게 했었는지 BCI 맥락에 비유.
Goal: FastAPI + WebSocket으로 EEG 추론 서버를 만든다.
Code skeleton — 11_realtime_server.py
from fastapi import FastAPI, WebSocket
import numpy as np
import torch
app = FastAPI()
window = [] # 250 samples = 1초 @ 250Hz
# TODO: Step 12에서 저장한 EEGNet weight를 torch.load로 로드
# model = ...
# model.eval()
@app.websocket("/eeg")
async def eeg_endpoint(ws: WebSocket):
await ws.accept()
while True:
sample = await ws.receive_json() # {"channels": [...]}
window.append(sample["channels"])
if len(window) >= 250:
X = np.array(window[-250:])
# TODO: X를 (1, n_channels, n_times) tensor로 reshape
# TODO: model(X) → argmax → 클래스 이름
prediction = "left_hand" # placeholder
await ws.send_json({"class": prediction})pip install fastapi uvicorn websockets
uvicorn 11_realtime_server:app --reloadChallenges
- 클라이언트 스크립트(
client.py)로 BCI Competition trial을 250ms 간격으로 WebSocket에 전송하고 예측을 받아 정확도 측정. - sliding window를 250 → 125 samples(50% overlap)로 바꾸고 latency 차이 측정(
time.time()). - 추론 결과를 SQLite에 로그하고
/stats엔드포인트에서 클래스별 분포 반환. - 동시 접속 N명까지 처리 가능한지
wrk또는locust로 부하 테스트. - (응용) Redis Pub/Sub으로 추론 결과를 다른 서비스(예: 자극 생성기)에 broadcast — 본인 백엔드 강점 활용.
┌──────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────┐
│ EEG 장비 │ -> │ Decoder │ -> │ Stimulator │ -> │ 뇌 │
│ (OpenBCI)│ │ (EEGNet/SNN)│ │ (TI/tDCS) │ │ │
└──────────┘ └─────────────┘ └──────────────┘ └──────┘
▲ │
└───────────────────────────────────────────────────┘
(closed loop)
Challenges
- 위 다이어그램에서 각 박스의 latency 예상치를 적어보기 (영상에서 본 수치 기준).
- 본인 시스템에서 어떤 곳이 bottleneck이 될지 예상하고 측정 계획 적기.
- failure mode(센서 disconnect, 추론 오류, 자극 over-current) 3가지를 골라 fallback 전략 설계.
1주~1개월 분량 — 한 가지를 골라 끝까지 완성해보기를 권장합니다.
- Realtime Motor Imagery Demo: BCI Competition 데이터를 실시간 스트림처럼 재생 → EEGNet 추론 → 화면에 클래스 표시.
- SNN vs CNN Benchmark: 같은 데이터셋에서 snntorch SNN과 EEGNet의 정확도·파라미터·추론시간 비교 보고서.
- TI Stimulation Planner: SimNIBS Python API로 타깃 영역(좌표) 입력 → 최적 전극 위치 추천 스크립트.
- Decoder API as a Service: FastAPI + Redis + Docker로 EEG 추론 마이크로서비스를 패키징하고 GitHub에 공개.
- Custom STDP Visualizer: brian2로 50개 뉴런 네트워크에 STDP 적용 → 가중치 행렬 변화를 GIF로 저장.
| Dataset | Use | Access | Size |
|---|---|---|---|
| MNE Sample (sample_audvis) | EEG/MEG intro | mne.datasets.sample.data_path() |
~1GB |
| BCI Competition IV 2a | 4-class motor imagery | MOABB auto / bbci.de | 420MB |
| Allen Brain Observatory | Circuit decoding (optional) | pip install allensdk |
varies |
| SimNIBS m2m_ernie | TI simulation | SimNIBS Datasets | ~500MB |
| Library | One-liner | Key APIs |
|---|---|---|
| MNE-Python | EEG/MEG 표준 분석 | mne.io.read_raw_fif, raw.filter, mne.Epochs, mne.preprocessing.ICA |
| MOABB | BCI 데이터셋 통합 | BNCI2014_001(), paradigm.get_data() |
| Braindecode | EEG 딥러닝 | EEGNetv4, MOABBDataset, EEGClassifier |
| snntorch | PyTorch SNN | snn.Leaky(beta), surrogate.fast_sigmoid() |
| brian2 | 생물물리 SNN | NeuronGroup, Synapses, run() |
| scikit-learn | 클래시컬 ML | LogisticRegression, cross_val_score, Pipeline |
| SimNIBS | 비침습 자극 | GUI + Python simnibs.run_simnibs() |
| FastAPI | 실시간 서버 | WebSocket, uvicorn |
| Symptom | Cause | Fix |
|---|---|---|
ImportError: No module named mne |
conda 환경 비활성 | conda activate bci |
| MOABB 다운로드 멈춤 | 방화벽 | VPN 또는 수동 다운로드 |
CUDA out of memory |
GPU 부족 | .to('cpu') 또는 batch size 절반 |
| matplotlib 창 안 뜸 | backend 문제 | Jupyter: %matplotlib inline / 스크립트: plt.savefig('out.png') |
| SimNIBS GUI 안 열림 | 설치 문제 | 공식 설치 가이드 재실행 |
mne.find_events 빈 결과 |
stim 채널명 오류 | raw.ch_names 확인 후 stim_channel 인자 수정 |
- EBSi — 자극의 전달
- 북툰 — 뇌과학 15분
- 칸아카데미 한국어 — 오일러법
- 피토스터디 7-2 — 전진/후진 오일러
- 피토스터디 7-3 — 수정 오일러
- 기계TV — MATLAB 미분방정식
- Gerstner CNS1.3 LIF Model
- Gerstner NDC6.5 STDP
- Neuromatch — Biological Neuron Models
- NPTEL Neuromorphic LIF
- Jason Eshraghian Training SNN
- Dan Goodman Cosyne 2022 SNN
- Arnau Blanco IF Python
- Berdakh L8 MNE Part 1
- Armin Abdollahi MNE Tutorial
- Pybrain MNE Workshop
- Talha Anwar — Motor Imagery EEG
- Ollie D'Amico — CSP
- Ollie D'Amico — EEGNet
- Basics of EEG BCI
- Microsoft Research Closed-loop BCI
- sentdex Python OpenBCI
- gtec Closed-loop EEG
- BrainDecode 소개
MIT