flydnet
Use the real Drosophila connectome (FlyWire v783) as PyTorch layers. Pick any set of neurons by annotation (mushroom body, visual system, whole brain), and run them as a batched GPU spiking (LIF) or graded network whose wiring is the fly's actual synapse map. Layers are differentiable (surrogate gradients), so synapse strengths and per-cell-type parameters can be trained with backprop. Docs are in Korean.
Alpha (0.1). API가 바뀔 수 있다. 연구용 도구로 쓰고, 정확도 향상을 기대하지는 말 것 — 아래 "지금까지의 결론" 참고.
초파리 커넥톰(FlyWire v783)의 실제 배선을 신경망 층으로 쓰는 PyTorch 라이브러리. 텐서를 입력 뉴런의 발화율로 바꿔 넣고, 실제 배선을 따라 전파한 뒤 출력 뉴런의 활동을 특징으로 읽는다.
텐서 ─RateEncoder─▶ PN 발화율 ─ConnectomeLayer(실제 배선, LIF)─▶ KC/MBON 발화율 ─리드아웃─▶ 예측
- 회로 고르기: 주석(세포 유형·계열)으로 뉴런을 골라 그 사이 연결만 남김 — 버섯체, 시각계, 전체 뇌(13.9만 뉴런)
- 뉴런 모델: 스파이킹 LIF(Shiu et al. 2024 매개변수) 또는 연속값(graded) 뉴런
- 역전파 학습: 연결별 또는 연결 종류별 세기, 세포 유형별 bias·막 시간 상수. 전체 뇌도 12GB GPU에서 학습
- 대조군: 연결 수·차수를 유지한 무작위 배선, 시야 위치를 유지한 국소 무작위 배선
- 도파민 연합 학습 리드아웃: 역전파 없이 한 번 보고 학습, 연속 학습에 강함
- 데이터 도우미:
python -m flydnet download로 FlyWire·DoOR 데이터를 받음 (패키지에는 데이터 없음)
빠른 시작 (설치와 데이터 받기 후):
import torch, flydnet as fd
mb = fd.Circuit.from_flywire() # 오른쪽 버섯체: PN 344, KC 2597, APL 1, MBON 48
layer = fd.ConnectomeLayer(mb, "PN", "KC", t_ms=50, gains={"PN>KC": 2.0}, input_mode="regular")
kc = layer(torch.rand(8, 344) * 100) # 입력 PN 발화율(Hz) → (8, 2597) KC 발화율
연구 기록 전체(실험 10개, 방법, 한계)는 소스 저장소의 REPORT.md에 있다.
설치
# GPU(CUDA)로 쓰려면 PyTorch를 먼저 설치 (https://pytorch.org 에서 자기 CUDA 버전 명령 확인). 예:
pip install torch --index-url https://download.pytorch.org/whl/cu128
pip install flydnet
python -m flydnet download # 데이터 받기: FlyWire v783 연결·주석 + DoOR 냄새 데이터 (약 130MB)
python -m flydnet # 데이터 상태 확인
pip install flydnet만 하면 PyTorch는 CPU 버전이 설치된다. 데이터는 패키지에 들어 있지 않고 download()가
~/.flydnet/data에 받는다(위치는 fd.set_data_dir(...) 또는 환경변수 FLYDNET_FLYWIRE로 바꿀 수 있음).
개발용 (소스에서)
전용 가상환경 D:\flydnet\.venv (Python 3.11, torch 2.14.1+cu132)에 편집 모드로 설치되어 있다.
# 새로 만들 때
python -m venv .venv
.venv\Scripts\pip install torch==2.14.1 torchvision==0.29.1 --index-url https://download.pytorch.org/whl/cu132
.venv\Scripts\pip install -e ".[examples,dev]" # 편집 모드: 코드를 고치면 바로 반영
.venv\Scripts\python -m pytest -q # 테스트
examples/의 스크립트는 설치하지 않아도 src/를 직접 불러와서 실행된다.
사용
import flydnet as fd
mb = fd.Circuit.from_flywire() # 오른쪽 버섯체: PN 344, KC 2597, APL 1, MBON 48
enc = fd.RateEncoder(784, len(mb.groups["PN"])) # 픽셀 → PN 발화율 (고정 무작위 희소 투영)
layer = fd.ConnectomeLayer(mb, "PN", "KC", t_ms=100, gains={"PN>KC": 2.0}, input_mode="regular")
feats = fd.extract(layer, enc, images) # (n, 2597) KC 발화율
fd.train_linear(feats, y, feats_test, y_test) # 로지스틱 회귀
mb.shuffled(seed=0) # 무작위 배선 대조군 (연결 수·차수는 그대로)
PyTorch 층처럼 역전파로 학습
trainable=을 주면 배선은 고정하고 연결별 세기를 nn.Parameter로 학습한다. 스파이크는 대리 기울기
(순전파는 진짜 스파이크, 역전파는 빠른 시그모이드 기울기)로, 입력 스파이크는 straight-through로 미분되어
앞쪽 층까지 기울기가 흐른다. 부호(흥분/억제)는 바뀌지 않는다.
import torch, torch.nn as nn
import flydnet as fd
mb = fd.Circuit.from_flywire()
layer = fd.ConnectomeLayer(mb, "PN", "KC", t_ms=50, dt=0.5, gains={"PN>KC": 2.0},
input_mode="regular", trainable=True) # 또는 trainable=["KC>MBON"]
model = nn.Sequential(fd.RateEncoder(784, 344), layer, nn.Linear(layer.n_out, 10)).cuda()
opt = torch.optim.Adam([
{"params": [layer.log_scale], "lr": 3e-2}, # 커넥톰 연결 세기 (log 배율)
{"params": model[-1].parameters(), "lr": 3e-3},
])
loss = nn.functional.cross_entropy(model(x), y)
opt.zero_grad(); loss.backward(); opt.step()
layer.weights() # 현재 연결별 세기 (mV, 부호 포함), 순서는 layer.w_idx (post, pre)
버섯체 회로에서 배치 64, 50 ms(100스텝) 순전파 + 역전파가 RTX 5070 기준 약 0.2초, GPU 메모리 약 0.5GB.
데이터
fd.download() # 없는 파일만 받음: FlyWire 약 135 MB + DoOR 0.5 MB (python -m flydnet download)
fd.set_data_dir(flywire=r"D:\my\fw") # 이미 받아 둔 폴더를 쓰려면 (~/.flydnet/config.json에 저장)
fd.data_status() # 어디서 무엇을 찾았는지
위치를 찾는 순서: 함수에 준 경로 → 환경변수 FLYDNET_FLYWIRE / FLYDNET_DOOR (FLYDNET_DATA도 인정)
→ ~/.flydnet/config.json → ~/.flydnet/data/<flywire|door>. 받은 파일은 크기로 온전한지 확인한다.
출처: FlyWire v783 연결 파일(Shiu et al. 2024, MIT), 세포 주석(Schlegel et al. 2024), DoOR 2.0(CC BY-SA 4.0).
저장 / 불러오기
layer.save("mb.pt") # 배선 + 설정 + 학습한 연결 세기, 파일 하나 (버섯체 약 10 MB)
layer = fd.ConnectomeLayer.load("mb.pt") # FlyWire 데이터 없이도 다시 만들어짐
torch.save(model.state_dict(), "m.pt") # 표준 PyTorch 방식도 그대로
readout.save("r.pt"); fd.AssocReadout.load("r.pt")
모두 torch.load(weights_only=True)로 읽힌다 (텐서·기본 자료형만 저장).
메모리 절약: 그래디언트 체크포인팅
layer = fd.ConnectomeLayer(..., trainable=True, checkpoint_every=20) # 20스텝 구간마다 다시 계산
역전파 때 구간의 중간 상태를 다시 계산해 시간 방향 메모리를 줄인다. 출력은 완전히 같고, 기울기는 GPU 합산 순서 오차(상대 1e-7) 안에서 같다. 버섯체, 100 ms(200스텝): 배치 64에서 0.74 → 0.18 GB, 배치 256에서 1.97 → 0.51 GB, 계산은 1.3~2배.
큰 회로 학습: 연결 단위 역전파
torch.sparse.mm의 역전파는 연결 기울기를 뉴런 수 × 뉴런 수 크기로 만든다 (전체 뇌면 77 GB).
SparsePropagate는 필요한 연결 칸만 계산한다 (torch.sparse.sampled_addmm, 메모리 = 연결 수).
연결 세기도 순전파당 한 번만 계산한다. torch.sparse.mm과 같은 기울기를 내는 것을 gradcheck로 확인했다.
others = ["optic", "central", "visual_projection", "ascending", "descending",
"sensory_ascending", "visual_centrifugal", "motor", "endocrine"]
brain = fd.Circuit.from_flywire({"SENS": ("super_class", "sensory"), "REST": ("super_class", others)}, side=None)
layer = fd.ConnectomeLayer(brain, "SENS", "REST", t_ms=50, dt=0.5, input_mode="regular",
trainable=True, checkpoint_every=10)
학습 1스텝 (순전파 + 역전파 + Adam, 50 ms = 100스텝, RTX 5070):
| 회로 | 연결 | 배치 | GPU 메모리 | 시간 |
|---|---|---|---|---|
| 감각 → 중심 뇌, 4.9만 뉴런 | 486만 | 8 | 0.88 GB (이전 13.9 GB) | 0.8초 (이전 18초) |
| 전체 뇌, 13.9만 뉴런 | 1,509만 | 8 | 2.16 GB (이전 77 GB 요구) | 2.2초 |
| 전체 뇌 | 1,509만 | 32 | 3.65 GB | 2.6초 |
큰 회로를 시험할 때는 torch.cuda.set_per_process_memory_fraction(0.75)처럼 상한을 걸어 두면, GPU 메모리가
넘칠 때 Windows 가상 메모리(C 드라이브)로 흘러가지 않고 바로 오류가 난다.
구성
| 모듈 | 내용 |
|---|---|
circuit.py |
Circuit: 주석으로 뉴런 그룹을 골라 그 사이 연결만 남긴 회로, shuffled() 대조군, summary() |
encoders.py |
RateEncoder: 텐서 → 입력 뉴런 발화율 |
layers.py |
ConnectomeLayer: 배치 LIF 시뮬레이션 (희소행렬 곱, GPU). input_mode="regular"/"poisson" |
readout.py |
extract(): 데이터 → 발화율 특징, train_linear(): 리드아웃 학습 |
실험 ① 버섯체 저장소(reservoir) — MNIST
python examples/mnist_reservoir.py (train 60k / test 10k, RTX 5070에서 회로 하나당 약 2.5분)
설정: 100 ms, PN→KC 배율 2.0 (KC 약 7% 활성 = 실제 초파리 수준), 규칙적 입력 스파이크
| 특징 | 차원 | test |
|---|---|---|
| 픽셀 (기준선) | 784 | 92.72% |
| PN 발화율 (인코더 출력) | 344 | 92.41% |
| KC — 실제 배선 | 2597 | 89.56% |
| KC — 무작위 배선 ×3 | 2597 | 89.05 / 89.20 / 89.06% |
| MBON — 실제 배선 | 48 | 24.39% |
| MBON — 무작위 배선 ×3 | 48 | 26.97 / 35.10 / 32.02% |
해석
- KC 층은 픽셀보다 약 3%p 낮다. 고정된 스파이킹 층을 거치면서 정보가 줄어든다.
- 실제 배선이 무작위 배선보다 KC에서 0.4~0.5%p 높다. 무작위 대조군 3개 모두보다 높지만 차이가 작아 (테스트 1만 개의 표준오차 약 0.3%p) 확정적이지 않다.
- MBON 48개는 거의 쓸모없고 실제 배선이 오히려 낮다. MBON은 원래 학습된 KC→MBON 시냅스로 읽어야 하는 출력이라, 학습 없는 배선만으로는 의미 있는 판독이 안 되는 것으로 보인다 → 실험 ②의 동기.
개발 중 발견한 점
- 포아송(무작위) 입력, 100 ms에서는 같은 이미지를 다시 넣어도 켜지는 KC 집합이 26%만 겹쳐 리드아웃이 잡음을 외웠다 (test 54%). 규칙적 입력으로 바꿔 같은 입력 → 같은 반응이 되게 했다.
실험 ② 도파민 학습 리드아웃 (역전파 없음) — MNIST
python examples/mnist_dopamine.py (실험 ①의 KC 특징 캐시 사용, 1분 이내)
숫자마다 MBON 같은 출력 뉴런 하나. KC→출력 시냅스를 KC 활동 × 도파민 국소 규칙으로 학습 (fd.DopamineReadout).
bidir: 틀렸을 때 정답 출력 강화 + 이긴 오답 출력 약화 (양방향 도파민 가소성)assoc: 정답 출력만 그 클래스 평균 패턴으로 강화 + 출력별 시냅스 총량 정규화. 학습 순서와 무관ltd,ltd_err,ltp: 단방향 규칙. 전체 학습에서 37~73%로 약해 실험에서 제외
A. 전체 학습 (test, bidir은 3회 평균)
| 특징 | 역전파(로지스틱) | 도파민 bidir | 도파민 assoc |
|---|---|---|---|
| 픽셀 | 92.72% | 88.99 ± 0.63% | 82.16% |
| KC 실제 배선 | 89.46% | 84.77 ± 0.50% | 74.09% |
| KC 무작위 배선 | 89.06% | 84.39 ± 2.64% | 74.61% |
B. 연속 학습 (0/1 → 2/3 → 4/5 → 6/7 → 8/9 순서로 한 번씩, 지금까지 본 숫자 전체 정확도)
| 특징 | 방법 | 단계별 | 마지막 후 첫 과제(0/1) |
|---|---|---|---|
| KC 실제 배선 | 역전파 | 99.8 → 70.3 → 58.8 → 44.9 → 40.5 | 5.8% |
| KC 실제 배선 | 도파민 bidir | 99.8 → 47.4 → 30.4 → 24.8 → 19.1 | 0.0% |
| KC 실제 배선 | 도파민 assoc | 99.3 → 90.2 → 82.8 → 79.6 → 74.1 | 89.4% |
| 픽셀 | 도파민 assoc | 99.8 → 92.9 → 87.8 → 86.8 → 82.2 | 93.0% |
해석
- 오류를 고치는 규칙(역전파, bidir)은 전체 학습에서 강하지만 연속 학습에서 앞 과제를 거의 다 잊는다. bidir은 새 숫자를 배울 때 틀린 답이 대부분 옛 숫자라 옛 출력을 계속 약화시켜 역전파보다 더 잊는다.
- assoc는 다른 출력을 건드리지 않아 망각이 원리상 없다 → 연속 학습 최종 74% (역전파 40%). 대신 전체 학습 성능은 낮다 (74% vs 89%).
- 실제 배선 효과는 여기서도 없다: KC 실제 ≈ 무작위, 그리고 assoc는 KC보다 픽셀에서 더 좋다 (82% vs 74%). 이점은 버섯체 배선이 아니라 학습 규칙(순서 무관 연합 학습)에서 나온다.
실험 ③ 합성 냄새 과제 — 실제 배선 대 무작위 배선
python examples/odor_task.py (약 8분)
- 냄새 = 사구체 56개 활성 벡터.
fd.GlomerularEncoder가 같은 사구체의 단일 사구체형 PN(139개)에 같은 발화율을 넣음 (실제 더듬이엽 구조) - 클래스 = 냄새 원형 여러 개의 묶음 (
fd.synthetic_odors(protos_per_class=K)) → 사구체 공간에서 선형 분리가 어려움. 차원을 넓히는 KC 층이 이론적으로 유리한 과제 (Babadi & Sompolinsky 2014) - PN→KC 배율 3.0 (KC 약 5% 활성), 로지스틱 회귀 리드아웃, 과제 seed 5개 × 무작위 배선 5개
| 설정 (클래스 × 원형, 잡음, 학습/클래스) | 사구체 | KC 실제 | KC 무작위 (5개 범위) | 실제 − 무작위 | 실제 승률 |
|---|---|---|---|---|---|
| 20 × 5, 0.5, 20 | 68.6 | 76.7 | 75.1 (73.9~76.0) | +1.64 ± 1.36 | 84% |
| 20 × 10, 0.5, 20 | 41.5 | 52.4 | 50.5 (49.7~51.3) | +1.89 ± 0.68 | 100% |
| 10 × 20, 0.5, 40 | 38.0 | 54.2 | 53.8 (52.5~54.9) | +0.46 ± 1.73 | 48% |
| 50 × 5, 0.8, 20 | 53.5 | 47.7 | 46.0 (45.5~46.3) | +1.73 ± 0.65 | 96% |
전체 20개(설정 × seed) 중 18개에서 실제 배선이 무작위 평균보다 높음, 평균 +1.43%p. (같은 설정을 다시 돌리면 GPU 연산 순서 때문에 0.5%p 안팎으로 흔들린다)
정정: 처음 결과(+1.82%p, 19/20)는 무작위화 결함이 있던 버전이다. 섞을 때 같은 PN→KC 연결이 두 번 생기면 합쳐져서 무작위 회로의 KC가 실제보다 입력을 약 5% 적게 받았다 (PN→KC 중복 497개, KC→KC 1,557개).
shuffled()가 이제 중복·자기 연결을 맞바꿈으로 없앤다. 실험 ①의 무작위 대조군 숫자는 결함 버전 기준이다.
해석
- 실제 배선 효과는 결함을 고친 뒤에도 유지된다 (+1.43%p, 18/20). 원형 20개 설정에서는 차이가 없다.
- KC 활성 비율로는 설명되지 않는다: 실제 5.33%, 무작위 5.12~6.00%.
- 냄새가 무작위 합성이라 '자연 냄새 통계에 맞춰진 배선'이라는 설명은 아니다.
- 비선형 과제에서 KC 층은 사구체 값보다 8~16%p 낫다 (이론대로). 단, 잡음이 크고 클래스가 많으면 (마지막 행) 오히려 사구체 값이 낫다.
실험 ③-b 이점은 어느 연결에서 나오나
python examples/odor_ablation.py (약 11분). 한 종류의 연결만 섞고 나머지는 실제 그대로 둠.
손실 = 실제 − 해당 조건 (3설정 × 5seed, 조건마다 무작위 3개)
| 섞은 연결 | 손실 (%p) | 양수 |
|---|---|---|
| 전체 | +1.61 ± 0.48 | 12/15 |
| PN→KC | +0.66 ± 0.45 | 10/15 |
| 나머지 (MBON 관련, PN끼리 등) | +0.32 ± 0.28 | 11/15 |
| KC→KC | −0.48 ± 0.35 | 4/15 |
| APL ↔ KC | −0.50 ± 0.31 | 5/15 |
(± = 표준오차)
해석
- 전체를 섞으면 확실히 손해지만, 한 종류만 섞어서는 뚜렷한 손실이 없다. 이점이 한 연결에 있지 않고 여러 연결 구조가 함께 만드는 것으로 보인다 (손실이 더해지지 않음).
- KC→KC, APL은 섞으면 오히려 약간 좋아진다 → 이점의 출처가 아님.
- 결함 버전에서는 PN→KC 손실이 +1.17 (13/15)로 유일한 출처처럼 보였는데, 그중 상당 부분이 중복 연결로 무작위 KC 입력이 줄어든 탓이었다.
- 실제 PN→KC 배선은 무작위보다 같은 사구체 입력을 중복해 받는 KC가 많고 (14.7% 대 10.2%), KC 아형(γ, αβ, α'β')마다 받는 사구체 분포의 치우침이 크다. 이것이 성능에 기여하는지는 확인 못 함.
실험 ④ 실제 냄새 데이터 (DoOR 2.0)
python examples/door_task.py (약 5분)
데이터: DoOR.data의 door_response_matrix.csv, door_mappings.csv,
odor.csv를 data/door/에 둔다 (Münch & Galizia 2016, Sci Rep 6:21841, CC BY-SA 4.0. 저장소에는 포함하지 않음).
fd.door_odors()가 수용체 반응을 FlyWire 사구체 이름에 맞춰 사구체 벡터로 바꾼다 (자발 발화 빼고 0 아래는 0).
사구체 56개 중 47개가 측정됨. 사구체 20개 이상 측정된 냄새 154개 사용, 냄새당 켜진 사구체 평균 9.6개.
| 과제 | 사구체 | KC 실제 | KC 무작위 (5개 범위) | 실제 − 무작위 |
|---|---|---|---|---|
| A. 냄새 구별 (154개, seed 3개) | 59.4 | 52.2 | 52.4 (52.0~53.1) | −0.21 ± 0.20, 1/3 |
| B. 새 냄새의 화학 계열 (8계열 116개, 5겹 × seed 3개, 찍기 22%) | 55.1 | 51.5 | 51.5 (50.9~51.8) | −0.06 ± 0.56, 7/15 |
해석
- 실제 냄새에서는 실제 배선의 이점이 없다. 두 과제 모두 실제 ≈ 무작위.
- 실제 냄새에서는 KC 층이 사구체 값보다도 낮다 (3.6~7.2%p). 합성 과제에서 KC가 유리했던 건 클래스를 일부러 '원형 여러 개의 묶음'(선형 분리 어려움)으로 만들었기 때문이고, 실제 냄새 구별·계열 분류는 사구체 공간에서 이미 꽤 선형적인 것으로 보인다.
- 합성 과제의 작은 우위(+1.43%p)는 실제 냄새 통계로 옮겨지지 않았다. 그 우위는 실제 배선이 자연 냄새에 맞춰져 있어서가 아니라, 합성 과제의 특정 구조와 맞물린 결과일 가능성이 크다.
- 한계: 사구체 9개는 측정이 없어 0으로 들어가고, 측정된 칸도 86%가 빈 표에서 채운 값이다. PN→KC 배율(3.0)은 합성 과제 기준으로 맞춘 값이다.
실험 ⑤ 실제 냄새 혼합물 — 조건부 구별 (XOR형)
python examples/door_mixtures.py (약 6분)
DoOR 냄새 4개 (A, B, C, D)마다 AB+, CD+, AC−, BD− 를 학습 (Young et al. 2011의 형태 학습 과제). 모든 냄새가 보상·무보상에 한 번씩 들어가서, 반응이 더해지는 한 선형 분류기로는 풀 수 없다. 혼합물 = 포화(A + B), PN→KC 배율 3.0 (실제 냄새에서 KC 6% 활성), 묶음마다 따로 로지스틱 회귀. (A+, B+, AB− 부정 패턴은 '켜진 사구체가 많으면 무보상'이라는 선형 규칙으로 풀려서 쓰지 않았다.)
| 특징 | 정확도 (찍기 50%, 묶음 200개) |
|---|---|
| 사구체 | 61.6% |
| KC 실제 배선 | 80.5% |
| KC 무작위 배선 ×5 | 81.3% (80.5~81.8) |
실제 − 무작위 = −0.77 ± 0.33%p (표준오차), 실제가 나은 묶음 96 / 못한 묶음 101.
해석
- KC 층은 확실히 필요하다: 선형으로 못 푸는 과제에서 사구체 61.6% → KC 80.5% (+19%p).
- 하지만 실제 배선일 필요는 없다: 무작위 배선이 오히려 약간 낫다 (차이는 작음).
- 배선 가설에 대한 결론: 이점은 '확장 + 희소화'라는 일반 구조에서 나오고, FlyWire의 구체적인 PN→KC 배선이 추가로 주는 이점은 이 시험들에서 보이지 않는다 (MNIST, 합성 냄새 +1.4%p, 실제 냄새 구별·계열·혼합물 모두 차이 없음).
실험 ⑥ 연속 학습 — 도파민 연합 학습 대 역전파(재생 버퍼)
python examples/continual_mnist.py (약 1분, KC 특징은 실험 ① 캐시)
fd.AssocReadout(n_in, n_classes, per_class=k): 클래스마다 출력(원형) k개. 샘플이 오면 그 클래스 출력 중
가장 잘 맞는 하나에만 보상 도파민 → 그 출력이 받은 샘플들의 평균 패턴이 됨 (빈 출력부터 채움).
다른 클래스의 시냅스는 절대 바뀌지 않는다. k=1이면 실험 ②의 assoc와 같다.
전체 학습 (한 번 보기, 역전파 없음): k를 늘리면 픽셀 82.2 → 95.4% (k=50), KC 74.1 → 89.9%. 픽셀 k=50은 역전파 로지스틱 회귀(92.7%)보다 높다.
class-incremental split MNIST (0/1 → … → 8/9, 과제당 1에폭, 3회 평균, 최종 = 숫자 10개 전체)
| 특징 | 방법 (저장량: 클래스당 벡터 수) | 최종 | 마지막 후 0/1 |
|---|---|---|---|
| 픽셀 | 역전파 (0) | 37.8% | 2.1% |
| 픽셀 | 역전파 + 재생 20 | 74.8% | 76.7% |
| 픽셀 | 역전파 + 재생 50 | 82.2% | 86.4% |
| 픽셀 | 연합 20 | 93.9% | 98.8% |
| 픽셀 | 연합 50 | 95.2% | 99.2% |
| KC | 역전파 + 재생 50 | 75.6% | 84.1% |
| KC | 연합 50 | 89.6% | 98.2% |
해석
- 같은 저장량에서 연합 학습이 재생 버퍼 역전파보다 13~14%p 높고, 첫 과제를 거의 그대로 기억한다.
- 정직하게: 이 규칙은 머신러닝의 '클래스별 온라인 k-평균 원형 분류기'와 같고, 원형(평균) 기반 분류가 연속 학습에 강하다는 것은 알려져 있다 (예: iCaRL의 nearest-mean-of-exemplars). 비교한 역전파도 로지스틱 회귀 1에폭이라 약한 편이다. 새로운 건 이것을 버섯체 도파민 회로의 국소 규칙으로 해석·구현한 점이다.
- 여기서도 KC 특징이 픽셀보다 못하다 (89.6% 대 95.2%).
- (수정 기록: 처음 버전은 한 묶음의 같은 클래스 샘플이 모두 첫 빈 원형으로 들어가는 버그가 있었다. 고친 뒤 MNIST 숫자는 ±0.5%p 안에서만 바뀌었다.)
실험 ⑦ 연합 학습에 KC 층이 필요한가 — 실제 냄새 조건부 구별
python examples/door_assoc.py (약 6분). 실험 ⑤와 같은 XOR형 과제, 냄새 4개 묶음 200개, 묶음마다 따로 학습.
| 리드아웃 | 사구체 | KC 실제 | KC 무작위 |
|---|---|---|---|
| 로지스틱 (역전파, 선형) | 61.4 | 81.1 | 81.4 |
| MLP (역전파, 은닉 64) | 98.5 | 93.1 | 93.5 |
| 연합 k=1 | 58.4 | 62.8 | 64.1 |
| 연합 k=2 | 99.2 | 98.3 | 98.5 |
| 연합 k=4 | 98.8 | 98.1 | 98.2 |
| 연합 k=10 | 98.3 | 97.4 | 97.7 |
(찍기 50%, 표준오차 0.3~0.8%p)
해석
- 원형이 2개 이상인 연합 학습은 KC 없이 사구체 값만으로 XOR형 과제를 푼다 (99.2%). 원형 여러 개 = 그 자체로 비선형 분류기라서. KC를 거치면 오히려 약간 낮다 (98.3%).
- KC 층이 필요한 건 리드아웃이 선형일 때뿐이다 (로지스틱 61 → 81%). 역전파 MLP도 사구체에서 가장 좋다.
실험 ⑧ 커넥톰 층을 역전파로 학습 — MNIST
python examples/train_backprop.py (약 11분). 일반 PyTorch 학습 루프, 학습 2만 / 평가 1만, 3에폭,
dt 0.5 ms, 50 ms 창, 선형 층 학습률 3e-3, 커넥톰 연결 세기 학습률 3e-2.
| 설정 | 학습 파라미터 | 에폭별 test |
|---|---|---|
| 커넥톰 고정 + 선형 | 25,980 | 80.4 → 83.1 → 84.4 |
| 연결 19만 개 전부 학습 (KC 판독) | 216,836 | 84.2 → 85.1 → 85.9 |
| KC→MBON만 학습 (MBON 48개 판독) | 24,064 | 67.3 → 72.5 → 74.4 |
| 무작위 배선, 전부 학습 (KC 판독) | 216,836 | 84.8 → 84.2 → 85.9 |
해석
- 커넥톰 층을 통과하는 역전파가 동작한다. 연결 세기가 실제로 바뀌고(배율 5
95% 범위 0.24.3배), 고정 층보다 1.5%p 좋아졌다. - MBON 48개 판독은 고정이면 24%(실험 ①)였지만 **KC→MBON 연결만 학습하면 74%**가 된다. 초파리에서 학습이 일어나는 바로 그 자리만 바꿔도 판독이 쓸모 있어진다.
- 학습해도 실제 배선과 무작위 배선은 같다 (85.87% 대 85.91%).
- 설정이 실험 ①과 달라(dt 0.5, 50 ms, Adam 3에폭) 고정 층 숫자도 다르다 (84.4% 대 89.6%).
실험 ⑨ 시각계 — 전체 시야 운동 방향
python examples/visual_motion.py --circuit real (약 8분, --circuit shuffled). 오른쪽 시각계 전체
(fd.visual_circuit(), 뉴런 4.8만, 연결 429만, 세포 유형 584개), 연속값 뉴런(neuron="graded"), 연결 종류별
배율과 유형별 bias·시간 상수를 학습. 격자 8방향을 T4a–d·T5a–d 아형별 평균 8개로 맞힘.
| 정확도 (찍기 12.5%) | 뉴런별 방향 선택 지수 (중앙값) | |
|---|---|---|
| 실제 배선 | 100% | 0.003–0.014 |
| 무작위 배선 | 100% | 0.000–0.002 |
둘 다 100%지만 뉴런은 방향을 거의 가리지 않음(실제 T4/T5는 0.3–0.8). 수천 개 평균의 미세한 차이를 리드아웃이 증폭한 지름길 → 실험 ⑩으로.
실험 ⑩ 시각계 — 국소 운동 방향
python examples/visual_local_motion.py --circuit real|shuffled|local --seed 0 (약 6분). 운동 감지 경로만
(MOTION_PATHWAY, 뉴런 2.4만, 연결 43만). 시야를 칸 13개로 나눠 칸마다 다른 방향, 리드아웃은 모든 칸이 공유.
1500스텝, seed 3개씩.
| 회로 | 위치 대응 | 방향 구조 | 정확도 (찍기 12.5%) |
|---|---|---|---|
| 실제 배선 | 있음 | 있음 | 24.0 ± 8.3% (2/3 학습) |
국소 무작위 (shuffled(local=..., merge=...)) |
있음 | 없음 | 31.6 ± 2.8% (3/3) |
전체 무작위 (shuffled()) |
없음 | 없음 | 12.5 ± 0.2% (0/3) |
해석
- 전체 무작위가 못 배운 이유는 시야 위치 대응이 깨져서(입력이 1.5 대신 14기둥 밖에서 옴, 기울기 약 100배 작음).
- 위치 대응만 남기고 아형별 방향 구조를 지운 회로도 똑같이 배운다 → 이 과제에서 세부 배선의 이점은 없음.
vc = fd.visual_circuit().subset(fd.MOTION_PATHWAY) # 광수용체 → 라미나 → 메둘라 → T4/T5
xy = fd.column_map(fd.visual_circuit()) # 뉴런별 시야 좌표 (기둥 간격 단위)
layer = fd.ConnectomeLayer(vc.normalized(), fd.PHOTORECEPTORS, ["T4a", "T4b", "T4c", "T4d"],
neuron="graded", params={"w_syn": 3.0}, bias=0.2, t_ms=300, dt=2.0,
trainable=True, share="pair", train_neurons=True)
지금까지의 결론
| 질문 | 답 |
|---|---|
| 실제 FlyWire 배선이 무작위 배선보다 나은가 | 아니다. 합성 냄새에서만 +1.4%p, MNIST·실제 냄새 4가지 과제에서 차이 없음 |
| 시각계처럼 배선이 기능을 정하는 회로에서는 | 시야 위치 대응은 필수(없으면 학습 불가). 하지만 그 안의 세부 배선(아형별 방향 구조)은 학습으로 대체됨 |
| KC 층(확장 + 희소화)이 도움이 되나 | 리드아웃이 선형일 때만 (XOR형 과제 +19%p). 원형 연합 학습·MLP에는 불필요하거나 손해 |
| 도파민 연합 학습(AssocReadout)이 쓸모 있나 | 그렇다. 한 번 보기, 역전파 없음, 연속 학습에서 같은 저장량 재생 버퍼보다 13~14%p 높음. 단 알려진 원형 분류기와 같은 원리 |
다음 단계 아이디어
- 질문을 "커넥톰이 정확도를 높이는가"(10개 실험에서 아니다)에서 **"커넥톰이 어떤 해법을 찾게 하는가"**로 바꾸기: 학습된 T4/T5 아형별 선호 방향이 배선 속 방향 구조(Mi9→Mi4 어긋남)와 일치하는지, 국소 무작위에서는 제멋대로인지
- 적은 학습량에서의 차이 (타고난 방향 구조가 출발점으로 유리한지)
- 실제 측정과 비교 (T4/T5 선호 방향·시간 반응, Lappalainen et al. 2024 방식)
Metadata
Release files for flydnet 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flydnet-0.1.0.tar.gz | 60.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flydnet-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 105.7 kB
Release files / flydnet-0.1.0.tar.gz
| Download URL | flydnet-0.1.0.tar.gz |
|---|---|
| Size | 60.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
84dc6b836f844a959789e36372f85242bfb6954fd2c2fac4f8eda211df23df2b
|
|
BLAKE2b-256 checksum How to use checksums |
849da17242c4d30a83917258ae04dcfe48791f4c596843088d25bd46d1b0ca4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / flydnet-0.1.0-py3-none-any.whl
| Download URL | flydnet-0.1.0-py3-none-any.whl |
|---|---|
| Size | 45.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
81807421401fe0ab6e952aec2a742abcfa73e2e267b6801f69aacc12e5a77523
|
|
BLAKE2b-256 checksum How to use checksums |
1c594a414223ee2632f4b0dc12fde30cc00b268853bf52fb2243c83823e0099d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|