1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

monitor_ui.gif

手のひらを近づけると後ろに下がるロボットを作りました。判断しているのは if (distance < 100) ではなく、FlyWire が公開したショウジョウバエ成虫の脳の全結線図から組んだ、13万9255ニューロン・1509万シナプスの leaky integrate-and-fire ネットワークです。

一番面白かったのはここです。手をゆっくり近づけると至近距離まで逃げないのに、素早く近づけると140 mm手前で逃げます。 そう実装したのではありません。ハエの配線をそのまま回したら出てきました。
demo_c_light.gif

この記事は、データを落としてくるところから実機が逃げるまでの全部です。コードはほぼ全部 Claude Code に書かせているので、そのやり方も書きます。


1. 何ができたか

[PC: 13万9255ニューロンのLIF] --UDP--> [M5StickC Plus2] --I2C--> [BugC2 4モータ]
                                             \--I2C--> [ToF測距 VL53L0X]
  • ToF測距センサの値から「見かけの大きさが広がる速さ」(looming)を計算する
  • それをハエの視覚投射ニューロン LC4 / LPLC2(314個)への発火レートに変換して脳モデルに入力する
  • 下行ニューロン DNp01(巨大繊維、ハエの逃避反応の司令塔)の発火を監視する
  • DNp01 が閾値を超えたら後退する

マイコン側は何も判断しません。距離を垂れ流し、PCから来たモータ指令をそのまま流すだけです。判断はすべてPCの中の、ハエの脳がやります。
raster.png


2. 脳の設計図が、公開されている

私は神経科学の人間ではありません。「ハエの脳の配線図が丸ごと公開されているらしい」という話を聞いたところから始めています。

ここは、同じくゼロから始める人向けの最小限です。知っている方は §3 まで飛ばしてください。

ニューロンとシナプス

ニューロンは細胞です。片側にたくさんの「入力端子」があり、反対側に1本の長い「出力線」が伸びている、と思ってください。

ニューロン同士は、出力線の先が別のニューロンの入力端子に触れる形で繋がります。この接点がシナプスです。

ここが大事なのですが、同じ相手に何本もシナプスを作ります。 「AからBへ147本」という数え方をします。この本数が、そのまま繋がりの太さです。

ニューロンが信号を出すことを発火といいます。電気のパルスで、全か無か。強い刺激を受けても「大きく」発火するのではなく、速く何度も発火します。だからこの記事の数字は全部 Hz(1秒あたり何回発火したか)です。

そして繋がりには2種類あります。

興奮性 相手を発火させる方向に押す
抑制性 相手の発火を抑える方向に引く

どちらになるかは、送る側のニューロンが何を出しているか(神経伝達物質)で決まります。アセチルコリン (ACH) なら興奮性、GABA とグルタミン酸 (GLUT) なら抑制性。

昆虫のグルタミン酸は抑制性です。 脊椎動物ではグルタミン酸が代表的な「興奮性」の伝達物質なので、直感と逆になります。ハエでは GluCl という塩素イオンを通す受容体が主で、抑制側に働きます。素人が最初に「符号を間違えたか?」と疑うところです。

細胞には名前と個数がついている

ハエの脳のニューロンは、形と繋がり方で分類されて名前がついています。そして左右の脳に、ほぼ同じ数ずつあります。

この記事に出てくるのはこれだけです。

名前 左 右 何をする細胞か
LC4 54 50 迫ってくる物の「接近の速さ」に反応する
LPLC2 108 102 迫ってくる物の「見かけの大きさ」に反応する
DNp01 1 1 巨大繊維。 逃避の号令をかける
DNa01 1 1 旋回の指令を出す
MDN 2 2 後退歩行の指令を出す
JO(聴覚) 213 177 触角の付け根で音と振動を拾う

注目してほしいのは DNp01 が左右に1個ずつしかないことです。13万9255個のうちの2個。ハエの逃避反応は、この2個が仕切っています。 ハエを叩こうとして逃げられた経験があるなら、あれです。

LC4 と LPLC2 が入口、DNp01 が出口になります。

コネクトームは「全結線図」のこと

ハエ1匹の脳を電子顕微鏡で薄く輪切りにして、全部撮る。そこに写っているニューロン1つ1つの形と、どこで誰と接触しているかを、人間とAIが何年もかけて追跡する。その結果が FlyWire で、2024年に Nature に出て、データが丸ごと公開されました。

中身は、乱暴にいえばこういう表です。

pre_root_id          post_root_id         syn_count  nt_type
720575940627652358   720575940635872101   147        ACH
720575940631384958   720575940627652358    23        GABA
...

「このニューロンから、このニューロンへ、147本のシナプス、伝達物質はACH(=興奮性)」。これが1509万行あります。

ニューロン 139,255
シナプス接続(pre→post ペア) 15,091,983

大事なのは、これが統計モデルでも「平均的なハエの脳」でもないことです。実在した1匹の写しです。 だから左右で数が揃っていなかったり、一部の配線が取れていなかったりします。§10 で、そのせいで片側だけ鳴らないニューロンを踏みます。

ただし、配線図だけではシミュレーションできません。ニューロン1個がどう振る舞うかのモデルが、別に要ります。

ニューロンは「底に穴の空いたバケツ」でいい

今回使う leaky integrate-and-fire (LIF) は、名前のとおりのモデルです。ニューロン1個をバケツだと思ってください。

バケツ ニューロン
水が注がれる 興奮性の入力が来る integrate(溜める)
底の穴から漏れていく 入力がなければ元の状態に戻る leaky(漏れる)
縁を越えたら中身が全部こぼれる 発火して、空の状態に戻る fire(発火)
こぼした直後は蓋をする しばらく何が来ても反応しない 不応期

抑制性の入力は「水を汲み出す」に相当します。それだけのモデルです。

実際の値を入れるとこうなります。

                        縁(閾値)-45 mV
  ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
                            ╱|  ← ここを超えた瞬間に「発火」
        ╱‾╲          ╱‾╲  ╱  |
   v   ╱   ╲___     ╱   ╲╱   ↓ 空に戻る -52 mV
  ────╱         ╲__╱          └─ 2.2 ms は何が来ても無反応(不応期)
      ↑入力      ↑入力  ↑入力
      └─ 入力がないと 20 ms の時定数で元の水位へ(leaky)
値
水位 膜電位 v 何もなければ −52 mV
縁 閾値 −45 mV。差の 7 mV を埋めれば発火
漏れの速さ 膜時定数 20 ms
蓋の時間 不応期 2.2 ms
シナプス1本の水の量 0.275 mV

これらの値は私が決めたものではありません。 ショウジョウバエの全脳をこのモデルで動かした先行研究 Shiu et al. 2024(Nature) が使った値を、公開実装 philshiu/Drosophila_brain_model からそのまま転記しています。1つも推測していません。出典と転記箇所は §5 に、引用は §12 にあります。

この5行から、スケール感が計算で出ます。 シナプス1本は 0.275 mV ですが、これは水が入ってすぐまた漏れ始めるので、ピークでは約0.157倍しか水位が上がりません。7 mV を埋めるには:

7 mV ÷ (0.275 mV × 0.157) ≒ 162 本

同時に約162本のシナプスに叩かれて、ようやく1個のニューロンが発火します。 先ほどの「147本の繋がり」が太いというのは、こういうスケールの話です。逆に、1本や2本のシナプスはほとんど効きません。

あとはこれを13万9255個ぶん、0.1 ms刻みで回すだけです。§5 のコードがまさにそれです。

looming: ハエは距離を測っていない

ここが、この記事で一番おもしろい部分の下敷きになります。

ハエに測距センサはありません。 あるのは複眼だけで、そこに映る物の見かけの大きさが広がっていく速さを見ています。物が近づくと視野の中で急に膨らむ、あの感じです。専門用語で looming といいます。

数式にすると、相対拡大率です。

looming = -(距離の時間微分) / 距離
        = 1秒あたり何割ちぢんだか

たとえば 200 mm の距離が毎秒 100 mm の速さで縮んでいるなら、looming は 0.5 /秒。同じ速さでも 50 mm まで来ていれば 2.0 /秒で、4倍の強さの刺激になります。

だから ToF の距離をそのまま入れてはいけません。距離が縮む速さを、そのときの距離で割った量を入れます。

冒頭の「速く近づけると遠くで逃げる」は、この式から直接出てきます。速い物ほど、まだ遠い段階で分子が大きくなるからです。

配線図は、思ったより何も決めてくれない

素人としての最大の誤解がここでした。コネクトームがあれば脳が動く、わけではありません。

このプロジェクトで使っているものは、出どころで3層に分かれます。

層 中身 出どころ
① 結線 どのニューロンがどれに何本で繋がっているか、興奮性か抑制性か FlyWire の実測(139,255ニューロン / 1509万シナプス)
② 素子の値 閾値、時定数、シナプス1本の重み、不応期 Shiu et al. 2024 の論文値をそのまま転記
③ 入口と出口 何ミリのとき何ヘルツで刺激するか。何スパイクで「逃げる」と判断するか こちらで決めた。根拠はない

①と②は他人の測定値です。③だけが自分の裁量です。 そしてミリメートルもヘルツも車輪も、③にしか出てきません。

ここを曖昧にすると「ハエの脳でロボットが動きました」という誤解を招く記事になるので、③に何があるかを §11 に全部書き出しました。


3. 環境をつくる

部品: 2点、合計 8,470円

どちらもスイッチサイエンスです。

型番 商品名 価格
M5STACK-K033-C M5Stack BugC2(M5StickC Plus2 同梱) 8,800円(22%引きで 6,864円)
M5STACK-TOF-UNIT M5Stack用ToF測距センサユニット [U010] 1,606円

BugC2 のキットは「在庫限り」表記でした。 これから同じ構成を組む場合、入手できない可能性があります。StickC Plus2 と BugC2 を個別に調達するか、別の台車に読み替えてください。

StickC Plus2 はキットに同梱なので、単体で買う必要はありません。この1箱に脳以外の全部が入っています。

組み上がると、3つのデバイスがこう繋がります。

部品 中身 役割
M5StickC Plus2 ESP32-PICO-V3-02 rev3.1 / 8MB flash / CH9102 USB-serial 中継のみ。WiFiと2本のI2Cを持つ
BugC2 4モータ台車。StickCのHATピン (G0/G26) にI2C 0x38 体。速度レンジ -100..100
ToF Unit (VL53L0X) Groveポート (G32/G33) にI2C 0x29 距離入力。視覚の代わり

実機まわりで先に知っておくと得なこと:

  • 速度30が下限であり、ほぼ上限でもある。 --speed 20 だと足が空回りして機体が進まない。30をだいぶ超えると4モータの同時起動で電源が落ち込み、StickCがリセットする。
  • まっすぐ走らない。しかも補正量は進行方向で符号が反転する。 前進は右側を6.5遅らせ、後退は左側を9遅らせるのが実測の最適。エンコーダなし。

ツールチェーン

Windows 11 + PowerShell / Git Bash。Python 3.10.11 に numpy 2.2.6, pandas 2.3.0, pyarrow 23.0.1, scipy 1.13.1。

winget install --id ArduinoSA.CLI -e --accept-package-agreements --accept-source-agreements

1.5.1 が C:\Program Files\Arduino CLI\arduino-cli.exe に入ります。すでに開いているシェルのPATHには載らないので、開き直すかフルパスで呼びます。

arduino-cli config init
arduino-cli config add board_manager.additional_urls \
  https://static-cdn.m5stack.com/resource/arduino/package_m5stack_index.json
arduino-cli core update-index
arduino-cli core install m5stack:esp32          # 3.3.9
arduino-cli board listall | grep -i stick       # -> m5stack:esp32:m5stack_stickc_plus2

ESP32コアのダウンロードは約1.8 GB、展開後およそ4.4 GBです。回線と気持ちの準備をしてから叩いてください。

arduino-cli lib install "M5Unified"     # 0.2.22 (M5GFX 0.2.29 を引き連れてくる)
arduino-cli lib install "VL53L0X"       # 1.3.1 Pololu

M5Hat-BugC は Arduino のライブラリインデックスに存在しません。 arduino-cli lib search BugC は何も返しません。手で入れる必要があります。

curl -sL -o bugc.zip https://github.com/m5stack/M5Hat-BugC/archive/refs/heads/main.zip
unzip -q bugc.zip
cp -r M5Hat-BugC-main "$HOME/Documents/Arduino/libraries/M5Hat-BugC"

この手順はスケッチ側からは完全に不可視です。別マシンでのビルドを静かに壊すので、READMEに書いておかないと半年後の自分が死にます。

arduino-cli compile --fqbn m5stack:esp32:m5stack_stickc_plus2 <sketch-dir>
arduino-cli upload -p COM5 --fqbn m5stack:esp32:m5stack_stickc_plus2 <sketch-dir>

書き込まずにボードの素性だけ確認したいとき:

esptool.exe --port COM5 flash-id
# Chip type: ESP32-PICO-V3-02 (revision v3.1), flash 8MB

esptool.exe はコアに同梱で、~/AppData/Local/Arduino15/packages/m5stack/tools/esptool_py/5.3.0/ にいます。

脳のデータを落とす

ここで手を抜くと全部やり直しになります。候補が2つあって、スキーマを読んでから選びました。

eonsystemspbc/fly-brain は不採用。その 2025_Connectivity_783.parquet の列は Presynaptic_Index, Postsynaptic_Index, Excitatory x Connectivity です。FlyWire の root_id ではなく連番インデックスをキーにしていて、しかも重みに興奮/抑制の符号がすでに焼き込まれている。 符号規約を自分で決められなくなります。

snedea/flybrain を採用。gzipヘッダを展開して中身を確認したところ pre_root_id,post_root_id,neuropil,syn_count,nt_type で、Codex の v783 エクスポートが無改変で入っていました。root_id をキーにし、シナプス数が生のまま残っているので、符号規約が独立した判断として残ります。

すべて不変のref(コミットハッシュ/タグ)にピン留めし、SHA-256を data/manifest.json に記録しています。HTTP Range で再開可能。

ファイル 出典 バイト
connections.csv.gz snedea/flybrain @ 9191824d 50,289,304
neurons.csv.gz 同上 1,679,884
classification.csv.gz 同上 934,402
Supplemental_file1_neuron_annotations.tsv flyconnectome/flywire_annotations @ v3.1.0 31,718,505
Connectivity_783.parquet philshiu/Drosophila_brain_model 100,804,642
Completeness_783.csv 同上 3,327,347
合計 180.01 MiB

知らないと事故る点が2つあります。

connections.csv.gz は (pre, post, neuropil) ごとに1行です。 3,869,878行が集約すると2,700,513エッジになります。集約せずに使うと同じエッジが重複します。

Codex は5シナプス未満を切り捨てています。 集約後の最小エッジ重みがぴったり5になることで確認できます。一方、論文のモデルが回しているのは 15,091,983本。つまり Codex エクスポートはその Connectivity >= 5 部分集合にちょうど一致し、本数比で18%しかありません。 私はしばらくこれに気づかず、エッジ数が82%足りない状態でモデルを回していました。

python scripts/fetch.py            # 180 MiB, 再開可能
python scripts/build.py            # -> connectome_csr.npz, annotations.parquet
python scripts/build_weights.py --edges shiu --sign shiu
python scripts/extract_groups.py   # -> neuron_groups.json
python scripts/check.py
python scripts/validate_reference.py

CSR形式(indptr int64 / targets int32 / weights int16)に落とします。

root_id は 2^53 を超えるので、JSON に書くときは10進の文字列にしてください。 数値のまま書くと JavaScript 側でも Python の json 経由でも精度が落ちます。


4. どう作ったか: Claude Code との二人三脚

先に白状しておくと、この記事に出てくるコードは、ほぼ全部 Claude Code が書いています。 私がやったのは、方針を決めること、実機を触ること、そしてAIの出した結論を疑うことでした。

自慢でも卑下でもなく、再現しようとする人に一番効く情報だと思うので書いています。 13万9255ニューロンのシミュレータも、UDPのブリッジも、ファームウェアも、私が1行ずつ書いていたら何日かかったか分かりません。

効いたのは「何をするな」の指示だった

振り返ると、成果を分けたのは技術的な指示ではありませんでした。

指示 効いた場面
ライブラリのAPIを推測で書くな。ソースを読め Wire1 のバス初期化。それでも推測で2回外しています
書き込む前に必ず確認を取れ 机の上でモータが暴れるのを何度も防ぎました
文献値と検算し、合わなければ採用せず報告しろ DNp09 / DNa01 の命名トラップを全部拾えました
仮定は ASSUMPTIONS.md に分離して書き出せ これがないと、何が脳由来で何が自分の作文か分からなくなります

特に最後です。「動いた」と「生物学的に正しい」は全く別で、その境界を毎回ファイルに書き出させないと、自分でも区別がつかなくなります。660行になりました。

AIが自信を持って間違えたもの

逆もかなりありました。質の違うものを2つだけ。

「発火が少ないのはエッジ不足だ」という見立て。 モデルで発火するニューロンが0.4%しかないのを見て、「Codex が5シナプス未満を捨てているせいだ」と診断しました。全エッジ(5.6倍)に差し替えて測り直したら、609個 対 567個。ほとんど変わりません。 弱い接続は1本あたり最大0.17 mVで、7 mVの閾値差に対して小さすぎたのです。エッジを揃える判断自体は正しかったのですが、理由は外していました。

用量反応曲線の流用。 200 msのパルスで測った曲線から、連続駆動する実機の定数を決めていました。3回チューニングして、最初の2回は何も変わっていません。これを暴いたのは「まだ300 mmや200 mmでも動きます」という実機からの報告です。 条件の違う測定値は使えない、というだけの話でした。

分担の結論

AIが強いのは、退屈で正確な作業と、大量の突き合わせでした。139,255ニューロンの集合が一致するか、エッジ数が1本単位で合うか、公表値と桁がずれていないか。人間がやると必ず手を抜くところです。

人間が強いのは、物理世界です。 足が空回りした、200 mmでも逃げてしまう、ゆっくり近づけると逃げない。この記事で一番おもしろい発見も、実機を触っていた側から出ています。

踏んだ地雷の全リストは別記事にしました → ハエの脳をロボットに繋いだときに踏んだ地雷、全部

Wire1 のバス初期化、DNp09 の命名トラップ、SO_REUSEADDR、%-3s に入れた4文字、フェイルセーフが自分のコントローラを撃っていた話など9件。症状で引ける形で並べてあります。


5. 脳に電源を入れる

パラメータは Shiu et al. 2024 のものをそのまま

推測した値は1つもありません。参照実装 philshiu/Drosophila_brain_model から転記しています。

V_REST      = -52.0 * mV
V_RESET     = -52.0 * mV
V_THRESHOLD = -45.0 * mV
T_MEMBRANE  =  20.0 * ms
TAU_SYN     =   5.0 * ms
T_REFRACTORY=   2.2 * ms
T_DELAY     =   1.8 * ms
W_SYN       = 0.275 * mV
R_POISSON   = 150.0          # Hz
F_POISSON   = 250.0
W_POISSON   = W_SYN * F_POISSON   # 68.75 mV
DT          = 0.1 * ms

符号規約

Shiu et al. に準拠して、シナプス前ニューロンの神経伝達物質で符号を決めます。

NT_SIGN = {"ACH": +1, "DA": +1, "OCT": +1, "SER": +1, "GABA": -1, "GLUT": -1}

ここで1つ、記事にする前に自分の主張を撤回した箇所があります。当初「Shiu et al. は接続ごとに符号を与えている」と書いていました。間違いです。 実際に数えたところ、138,005ニューロンすべてで、シナプス前ニューロンごとに符号は一定でした。データセットの nt_type 列は接続ごとに付いているので、それを素直に使うと 30.69%のニューロンが興奮性と抑制性の両方のシナプスを出すことになり、Dale の法則を破ります。実装には --sign connection としてその選択肢も残してありますが、docstring に「通常使用のためのものではない」と明記しています。

実装

brian2 の method='linear' に相当する指数厳密解で2変数線形ODEを解きます。dt を固定すれば係数は3つの定数に畳めます。

self._a = float(np.exp(-self.dt / P.T_MEMBRANE))
self._b = float(np.exp(-self.dt / P.TAU_SYN))
self._c = float(P.TAU_SYN / (P.TAU_SYN - P.T_MEMBRANE) * (self._b - self._a))

内側ループは、不応期のマスク代入をやめて全要素をin-placeで更新し、不応期の数個だけ書き戻す方法で 46秒 → 13.9秒 に縮みました(1試行1秒あたり、出力はビット単位で一致)。

ref_idx = np.flatnonzero(refractory)
if ref_idx.size:
    v_hold = v[ref_idx]; g_hold = g[ref_idx]
np.multiply(g, self._c, out=tmp)
v -= P.V_REST; v *= self._a; v += tmp; v += P.V_REST
g *= self._b
if ref_idx.size:
    v[ref_idx] = v_hold; g[ref_idx] = g_hold; refractory[ref_idx] -= 1

発火するのは1.5%程度なので、ボトルネックはスパイクの伝播ではなく13万9255要素に対する毎ステップの算術です。だからエッジ数を5.6倍にしても実行時間は8%しか増えませんでした。

1試行 1000 ms, 13万9255ニューロン, 1509万シナプス 10〜14秒
30試行 約7分(1コア)

実時間の10〜14倍遅い。 この数字が後のロボット設計を全部規定します(§8)。

自作実装を信じる前に、公表値と突き合わせる

ここは飛ばさないほうがいいです。この検証を通すまでに、上のエッジ82%不足が見つかっています。

参照リポジトリが結果アーカイブなしに主張している定量的な値は、チュートリアル中の2つだけです。曰く "more than 400 000 spikes were generated by activating the sugar neurons (30 trials, 1 s each)" と "only about 400 neurons show activity"。

彼らの v630 ネットワークに彼らのパラメータで、この NumPy 実装を走らせた結果:

参照実装 この実装
スパイク数 (30試行×1秒) more than 400,000 509,803
一度でも発火したニューロン about 400 431

緩い主張なので「ビット一致の再現」ではなく「整合している」程度の話です。差が測れるのは活動ニューロン数だけで、431 対 約400 の +8%。スパイク数のほうは "more than" 付きの下限なので、そもそも差を計算できません。それでも符号規約、遅延、閾値、重みスケールのどれかを間違えていれば、数%ではなく桁でずれます。

符号規約の独立した裏取り: 生のシナプス数とニューロンごとの伝達物質から自力で構築した符号付き重みのレンジが -2405 .. 1897 で、参照実装の Excitatory x Connectivity 列と完全に一致しました。


6. 何をどこに繋いだか

脳が動くようになったので、あとは入口と出口を決めます。ここが §2 でいう③、全部こちらで決めた部分です。

  ToF (VL53L0X)  20 ms 周期
        │ 距離 [mm]
        ▼
  looming = -(dd/dt)/d          直近5サンプル(100ms)の線形回帰
        │ 0 〜 450 Hz に写像     ← ゲイン 0.07、これは我々が決めた値
        ▼
 ┌────────────── ハエの脳 139,255 ニューロン / 15,091,983 シナプス ──────────────┐
 │                                                                              │
 │   LC4    104個 ┐                                                             │
 │                ├─ Poisson 入力 ─→ … 実測の結線をそのまま伝播 … ─→ DNp01   2個 │
 │   LPLC2  210個 ┘                                          ├─→ DNa01   2個 │
 │                                                            ├─→ DNa02   2個 │
 │                                                            └─→ MDN     4個 │
 └──────────────────────────────────────────────────────────────────────────────┘
        │ DNp01 の発火率
        ▼
  400 Hz 超(25 ms チャンクで10スパイク)→「逃げる」
        │
        ▼
  後退 0.3 秒、速度30、クールダウン 0.4 秒

入口は LC4 と LPLC2 の合計314個。ハエで実際に looming を検出している細胞そのものです。出口は DNp01、巨大繊維。

LC4 と LPLC2 を区別せず、同じ強度で刺激しています。 本来この2つは役割が違います(LC4は接近の速度、LPLC2は角サイズ)。ToF 1個からは「縮む速さ」しか取れないので、LPLC2 に入れるべき量が存在しない。 生物学的な判断ではなくセンサの制約による妥協です。

プロトコルとフェイルセーフ

平文1行1パケットのUDP。Wiresharkでもnetcatでもそのまま読めます。

StickC -> PC    S <seq> <millis> <dist_mm> <enabled> <rssi> <supply_mv> 0
PC -> StickC    M <m0> <m1> <m2> <m3> <ttl_ms>

StickC は 200 ms 指令が来なければモータを止めます。PCは50 msごとに送り続けます。PCのハング、WiFi断、プロセスkill、この3つはStickCから見て区別がつかず、そして3つとも止まるべきです。


7. ゆっくり近づけると、逃げない

run.png

動かしてみて、一番よかったのはこれでした。

手をゆっくり近づけると、かなり至近距離まで反応しません。素早く近づけると、140 mm手前で逃げます。

距離の閾値で判定していたら、絶対にこうはなりません。速かろうが遅かろうが100 mmで逃げる。

これは、そう実装したのではありません。「速いときだけ遠くで逃げる」なんてコードはどこにも書いていない。looming を入力にして、あとはハエの実際の配線をそのまま流しただけです。速く近づくものほど膨らむのが速いので、遠い段階で閾値を越える。配線がそういう形をしていた、というだけの話です。

実測の対応はこうなりました。

手の速さ 300 mm 200 mm 100 mm 50 mm
0.2 m/s — — — 逃避
0.4 m/s — — 逃避 逃避
0.8 m/s — 逃避 逃避 逃避
1.5 m/s 逃避 逃避 逃避 逃避

机の上で手を振っていると、これがけっこう気味が悪いです。生き物っぽさは複雑さから来ているのではなく、「こちらの動かし方に応じて反応の仕方が変わる」というところから来ているのだと思いました。


8. 1秒遅れる脳

正直に書いておくと、この脳はとても遅いです。実時間の10〜14倍。

ルール制御 脳制御
PC側レイテンシ 中央値 15 ms 992 ms
最小 / 最大 0 / 16 ms 906 / 1281 ms
制御周期 16.0 Hz 1.8 Hz → 3.8 Hz
判断時間 中央値/p90/最大 0 / 0 / 0 ms 234 / 485 / 641 ms

ルール制御の約66倍。 約1秒の遅延の内訳は、判断そのものと、判断に使ったサンプルの古さで概ね半々でした。

これは体感でどうなのか、実機を触って確かめました。返ってきた答えは「ほとんど気にならない。ルール制御との差は微差」。

意外でしたが、理由はたぶんあります。この脳は「近いこと」ではなく「近づいてくる速さ」で反応するので、手が近づききる前に発火している。 その先行ぶんが、処理の遅れをちょうど相殺しているらしい。

遅いシステムを速く感じさせる方法として、これは偶然拾ったものですが示唆的でした。早く気づけば、遅く動いてもいい。

取り逃しの診断

初期実装では接近の33〜52%を取り逃していました。原因は2つ。

  1. 脳の評価が毎秒1.8回。一方、接近は0.47秒しか続かない
  2. コントローラが最新サンプルだけを読み、51 Hzのセンサ列の97%を捨てていた

チャンクを半分(制御周期 1.8 → 3.8 Hz)にし、前回の判断以降の全サンプルから looming のピークを取るように変えました。

改善後の取り逃し率は測っていません。 制御周期が2倍になったことと、51 Hz のサンプルを全部使うようになったことは実測で確認済みですが、「33〜52% がいくつになったか」は未検証です。ここは主張しません。


9. 予想が外れて、そのほうがよかった実験

ロボットとは別に、画面の中だけで1つ実験をしました。

生き物には「慣れ」があります。同じ刺激を繰り返すと、だんだん反応が鈍くなる。ハエも、何度も脅かされると逃げなくなります。

これがこのモデルで起きるのか。起きるはずがない、という予想もできました。 慣れには普通シナプス可塑性が必要で、このモデルにはそれが入っていないからです。ただ、膜とシナプスには時定数があるので、刺激をものすごく詰めて打てば前の残りが効くのではないかと思っていました。

LC4 + LPLC2 を 150 Hz・200 ms のパルスで20回、刺激間隔を4条件振りました。

DNp01左の、パルスごとのスパイク数(全条件で同一):

52 50 51 51 51 49 49 53 48 52 52 50 51 52 52 51 51 51 52 50
刺激間隔 シミュレート時間 実時間 結果
100 ms 36.5 s 12.2 min 変化なし
500 ms 13.8 s 2.3 min 変化なし
2 s 42.4 s 5.3 min 変化なし
10 s 194.3 s 55.5 min 変化なし

4条件がビット単位で同一出力でした。刺激窓が同じ順序で同じ数の乱数を消費するので、出力が完全一致するということはパルス間にネットワークの状態が完全に減衰しきっていることの証明になります。モデル中の最長時定数は膜の20 msで、100 msはその5倍です。

予想は外れました。でもこれは、外れたほうがずっと価値のある結果です。床がちょうどゼロだと確定したので、今後シナプス可塑性を足したときに見える慣れは、全部それに帰属できます。 ここに「もともと0.3くらいの慣れがあった」という汚れが残っていたら、何を足しても結論が出ません。

何も起きなかったことを確かめるのに55分かかりましたが、払う価値のある55分でした。


10. 片方の耳が聞こえないハエ

途中で、気味の悪い結果が出ました。

音の刺激(ハエの聴覚器 = Johnston器官、JO_auditory 390個)を左右均等に入れたのに、DNp01 が右だけ鳴って、左はぴくりとも動かない。

刺激 DNp01 左 DNp01 右
JO_auditory 両側 150 Hz 0.0 Hz 27.8 Hz

最初は自分のバグを疑いました。符号の規約を別の方法で導出し直しても同じパターン。左右のニューロン数を数えると、鳴らないほうが数は多い(213対177)。校正の完全性もほぼ全部「完了」の印つき(左213中212、右177中175)。符号の不一致2,528件にも JO_auditory の390ニューロンは1つも含まれない。

配線を直接数えて、はっきりしました。

  JO-L 213個 ──12エッジ /  36シナプス──→ DNp01-L   同側 +6.45 mV  → 発火せず
                                                  (閾値まで 7 mV)
  JO-R 177個 ──24エッジ / 147シナプス──→ DNp01-R   同側 +28.12 mV → 80 Hz

  対側への直接結合は、左右ともゼロ本

4倍違います。 左は駆動を上げても届きません(400 Hzで2 Hz、600 Hzでも2 Hz。同条件で右は150 Hzで82 Hz)。

では左のDNp01は壊れた細胞なのか。視覚(looming)で叩いてみたら、左右ほぼ同じだけ鳴りました。

刺激 DNp01 左 DNp01 右
LC4+LPLC2 両側 150 Hz 102.2 Hz 117.8 Hz

非対称度0.07、10試行中10試行で発火。細胞は正常です。JOからの経路だけが壊れています。

文献を当たって決着しました。巨大繊維回路は左右対称であることが知られていて、しかも JO-A/JO-B は巨大繊維に電気シナプスと化学シナプスの混合で接続します。そしてこのモデルにはギャップ結合が1本も入っていません。

つまりこれは、ハエの性質ではなく再建の欠損です。左のDNp01は「聞こえない」のではなく、「聞こえている経路がデータに写っていない」。

このモデルにはギャップ結合がありません。 電気的結合を経由する経路は軒並み過小評価され、しかもその失敗は「接続が弱い」ように見えて「機構が欠けている」ようには見えません。足りないものは、足りない形では現れない。 「この機構が欠けています」というエラーは出ず、ただ静かに片側が弱いだけに見えます。

旋回信号は存在する。ただし対側性

同じ視覚経路で、旋回を司る DNa01 / DNa02 はきれいに交差します。

  片側だけ looming を入れると:

     LC4/LPLC2-L ──✕─────────→ DNa01-L     0.0 Hz   (同側はゼロ)
                 └───交差───→ DNa01-R    13.4 Hz   (対側だけ鳴る)

     LC4/LPLC2-R ──✕─────────→ DNa01-R     0.0 Hz
                 └───交差───→ DNa01-L    15.2 Hz

  両側同時だと打ち消し合う → DNa01 0.8 / 0.4 Hz、DNa02 はゼロ

旋回指令は、脅威が左右非対称なときにだけ現れます。 理にかなっています。

対側 DNa01 の用量反応:

25 50 100 150 200 300 450 Hz
左刺激 → 右 0.4 11.6 12.8 16.4 14.8 18.4 24.4
右刺激 → 左 0.0 4.8 8.4 15.2 18.0 19.6 23.2
同側 (両方) 0.0 0.0 0.0 0.0 0.0 0.0 0.0

単調で、左右刺激で対称。一方 DNa02 は段階的な信号としては使えません。100 Hz付近でピークを打って落ちるうえ、左右で倍近く違います。

ToF を2個にすれば旋回が成立します。 ただし今回は使っていません。理由は §11 に書きます。

脳が実際にやっていること

0.1 msごとに更新するニューロン 139,255
シナプス 15,091,983
刺激を入れるニューロン 314 (0.23%)
強い looming で発火するニューロン 2,077 (1.49%)
一度も発火しないニューロン 137,178 (98.5%)

このモデルには自発発火がありません。 参照実装は Network(neu, syn, spk_mon, *poi_inp) を、明示的に指定したニューロンにだけ入力を付けて構築し、静止電位とリセット電位が等しい。沈黙は設計です。 「98.5%が黙っている」はバグではありません。

間引きがないことも確認済み: ニューロン集合はCodexの139,255とぴったり一致、エッジ数もソースファイルと1本単位まで一致。接続を1本も持たないニューロンが616個(0.44%)ありますが、これはニューロンリストとエッジリストの出所がわずかに違うためです。


11. この「脳」は、何ではないか

ここが一番大事な節です。飛ばすと、ただの誇大広告になります。

コネクトームが教えてくれるのは「どのニューロンがどれに繋がっているか」だけです。ミリメートルもヘルツも車輪も、そこには一切入っていません。

こちらで決めた部分(根拠のない自由パラメータ):

  • 距離 → looming の計算窓(5サンプル、100 ms)と閾値(0.5 /s)
  • looming → 刺激レートへのゲイン(0.07)と上限(450 Hz)
  • DNp01 の発火 → 「逃げる」という判断の閾値(400 Hz)
  • 後退の速度(30)と時間(0.3秒)
  • LC4 と LPLC2 に同じ強度を入れていること(§6)

さらに構造的な限界:

  • これは脳だけのコネクトームです。 腹側神経索(VNC)がありません。つまり下行ニューロンには運動の出力先がない。実際、どの刺激条件でも110個の運動ニューロンは1つも発火しません。「DNp01が鳴ったら下がる」の**「たら下がる」の部分は、ハエではなく私が決めています。**
  • ギャップ結合がありません(§10)。
  • DNa がどちら向きの旋回に対応するかはデータセットに存在しません。 それはVNCの情報だからです。だから §10 で旋回信号を見つけても使っていません。符号を間違えると、自信を持って物にぶつかっていくロボットができます。

リポジトリには ASSUMPTIONS.md を660行置いてあります。後から覆った推論も、覆ったまま残してあります。 そのほうが、あとで読む人の役に立つので。


12. ライセンスと引用

FlyWire のデータは CC BY-NC 4.0、つまり非商用限定です。 これを使って作ったものには、ライセンスがデータに追随します。

FlyWire の Terms of Service (https://flywire.ai/tos) より:

You agree that they will be made freely available under a CC-BY-NC 4.0 license, following procedures defined by the community.

なお flyconnectome/flywire_annotations には LICENSE ファイルがありません。READMEは引用要件を述べていますが再配布条件は述べていない。元になっているデータはFlyWireのものなので CC BY-NC 4.0 の下にあると考えられますが、ここは解決したふりをせず、穴があると書いておくべき箇所です。

各リポジトリのライセンス(そのコードにかかるもので、中のFlyWireデータにかかるものではありません): snedea/flybrain は MIT、philshiu/Drosophila_brain_model は MIT、eonsystemspbc/fly-brain は GPL-2.0。

データをリポジトリに入れないでください。 代わりに scripts/fetch.py を配ります。不変のrefにピン留めしてチェックサムを記録しているので、読者は同一バイト列を再現できます。

引用

Dorkenwald, S., Matsliah, A., Sterling, A.R. et al. Neuronal wiring diagram of an adult brain. Nature 634, 124–138 (2024). https://doi.org/10.1038/s41586-024-07558-y

Schlegel, P., Yin, Y., Bates, A.S. et al. Whole-brain annotation and multi-connectome cell typing of Drosophila. Nature 634, 139–152 (2024). https://doi.org/10.1038/s41586-024-07686-5

Matsliah, A., Yu, S.-C., Kruk, K. et al. Neuronal parts list and wiring diagram for a visual system. Nature 634, 166–180 (2024). https://doi.org/10.1038/s41586-024-07981-1

Berg, S., Beckett, I.R., Costa, M. et al. Sexual dimorphism in the complete Drosophila male central nervous system connectome. Cell 189 (2026). https://doi.org/10.1016/j.cell.2026.08.015

Shiu, P.K., Sterne, G.R., Spiller, N. et al. A Drosophila computational brain model reveals sensorimotor processing. Nature 634, 210–219 (2024). https://doi.org/10.1038/s41586-024-07763-9

Shiu et al. は bioRxiv 版と Nature 版でタイトルが違います。 プレプリントは "A leaky integrate-and-fire computational model based on the connectome of the entire adult Drosophila brain reveals insights into sensorimotor processing"、掲載版は上記。検索すると別論文に見えますが同じものです。Berg et al. も 2025年の bioRxiv プレプリントが 2026年に Cell に載っているので、引用するなら掲載版を使ってください。

アノテーション v3.1.0 を使っている場合、>= 3.0.0 のリリースは Berg et al. と Matsliah et al. を取り込んでいるので、上の最初の4本すべてが該当します。

謝辞(FlyWire の引用ガイドラインが逐語で求めているもの)

We thank the Princeton FlyWire team and members of the Murthy and Seung labs, as well as members of the Allen Institute for Brain Science, for development and maintenance of FlyWire (supported by BRAIN Initiative grants MH117815 and NS126935 to Murthy and Seung). We also acknowledge members of the Princeton FlyWire team and the FlyWire consortium for neuron proofreading and annotation.


まとめ

ハエの脳の配線図を落としてきて、そのとおりに電気を流したら、机の上の4輪車が、近づく手から、近づく速さに応じて逃げました。

  • ハエの全脳コネクトームは誰でもダウンロードできて、NumPyで回せます。 13万9255ニューロン・1509万シナプスで、1秒のシミュレーションに10〜14秒。
  • 自作実装は、公表値と突き合わせてから信じてください。 これをやったことでエッジ数82%不足が見つかりました。
  • 一番時間を食ったのは神経科学ではありません。 %-3s に入れた4文字と、条件の違う測定値を流用したことでした。
  • 他人の測定値と、自分で決めた値の境界を書き残してください。 コネクトームが決めるのは配線だけです。

やっていることの大半は「ハエの脳がすごい」という話ではありません。むしろ逆で、配線図というのは思ったより何も決めていない、という話でした。ミリメートルもヘルツもモータも、全部こちらが決める必要があった。

配線図が決めていたのは、「速いものには遠くで、遅いものには近くで反応する」という振る舞いの形だけです。でも、その形は、私が思いつかなかった形でした。それを思いつかないまま手に入れられたのが、このプロジェクトで一番よかったところです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?