fly-instinct 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fly_instinct/__init__.py +29 -0
- fly_instinct/__main__.py +12 -0
- fly_instinct/datafetch.py +106 -0
- fly_instinct/engine.py +197 -0
- fly_instinct/loader.py +130 -0
- fly_instinct-0.1.0.dist-info/METADATA +187 -0
- fly_instinct-0.1.0.dist-info/RECORD +11 -0
- fly_instinct-0.1.0.dist-info/WHEEL +5 -0
- fly_instinct-0.1.0.dist-info/entry_points.txt +2 -0
- fly_instinct-0.1.0.dist-info/licenses/LICENSE.md +71 -0
- fly_instinct-0.1.0.dist-info/top_level.txt +1 -0
fly_instinct/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fly-instinct — 基于真实果蝇连接组的“本能引擎” (Instinct Engine)
|
|
3
|
+
================================================================
|
|
4
|
+
|
|
5
|
+
一个可复用、非学习(权重永久冻结)的“本能”信号源:
|
|
6
|
+
把输入刺激打散成高维、非平凡、结构化的反应信号。
|
|
7
|
+
它既不是白噪声(死的),也不是脚本规则(可预测),
|
|
8
|
+
而是“像活物一样对刺激有反应”。
|
|
9
|
+
|
|
10
|
+
快速开始:
|
|
11
|
+
# 1) 替身模式(零依赖数据,直接跑)
|
|
12
|
+
>>> from fly_instinct import FlyInstinct
|
|
13
|
+
>>> fly = FlyInstinct(n_neurons=150, seed=7)
|
|
14
|
+
>>> reaction, spikes = fly.react(stimulus)
|
|
15
|
+
|
|
16
|
+
# 2) 真实 MaleCNS 连接组模式
|
|
17
|
+
>>> python -m fly_instinct fetch-data --out data # 一次性下载 25MB 数据
|
|
18
|
+
>>> fly = FlyInstinct.from_malecns("data/edges.csv")
|
|
19
|
+
>>> reaction, spikes = fly.react(stimulus)
|
|
20
|
+
|
|
21
|
+
数据出处:MaleCNS v1.0, Janelia FlyEM (CC-BY-4.0)
|
|
22
|
+
"""
|
|
23
|
+
from .engine import FlyInstinct
|
|
24
|
+
from .loader import load_malecns
|
|
25
|
+
from .datafetch import fetch_data, DATA_URLS, EXPECTED_SIZES
|
|
26
|
+
|
|
27
|
+
__version__ = "0.1.0"
|
|
28
|
+
__all__ = ["FlyInstinct", "load_malecns", "fetch_data",
|
|
29
|
+
"DATA_URLS", "EXPECTED_SIZES", "__version__"]
|
fly_instinct/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""支持:
|
|
2
|
+
python -m fly_instinct fetch-data [--out data] [--force]
|
|
3
|
+
python -m fly_instinct [--out data] [--force] # 省略子命令亦可
|
|
4
|
+
"""
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .datafetch import main
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
if len(sys.argv) > 1 and sys.argv[1] == "fetch-data":
|
|
11
|
+
sys.argv.pop(1)
|
|
12
|
+
main()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
datafetch.py — 下载 MaleCNS 真实连接组子集(25MB,3 个 CSV)
|
|
3
|
+
============================================================
|
|
4
|
+
|
|
5
|
+
数据出处(CC-BY-4.0,发布时须署名):
|
|
6
|
+
- 上游:MaleCNS v1.0, Janelia FlyEM, https://male-cns.janelia.org
|
|
7
|
+
- 子集:nosuchstudios/fruit-fly-brain-runtime (HuggingFace)
|
|
8
|
+
9,999 节点 / 1,587,930 条边,从 CX/MBON/descending 等核心类抽取。
|
|
9
|
+
|
|
10
|
+
用法:
|
|
11
|
+
python -m fly_instinct fetch-data --out data
|
|
12
|
+
# 或安装后:
|
|
13
|
+
fly-instinct-fetch-data --out data
|
|
14
|
+
|
|
15
|
+
只标准库(urllib),无额外依赖;支持断点续传(Range 头)。
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
import argparse
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import urllib.request
|
|
22
|
+
|
|
23
|
+
_BASE = ("https://hf-mirror.com/datasets/nosuchstudios/"
|
|
24
|
+
"fruit-fly-brain-runtime/resolve/main/artifacts/malecns-v1.0")
|
|
25
|
+
|
|
26
|
+
# 文件名 -> (URL, 预期字节数;与上游 PROVENANCE.md 一致)
|
|
27
|
+
DATA_URLS = {
|
|
28
|
+
"edges.csv": f"{_BASE}/edges.csv",
|
|
29
|
+
"annotations.csv": f"{_BASE}/annotations.csv",
|
|
30
|
+
"neurotransmitters.csv": f"{_BASE}/neurotransmitters.csv",
|
|
31
|
+
}
|
|
32
|
+
EXPECTED_SIZES = {
|
|
33
|
+
"edges.csv": 25_207_711,
|
|
34
|
+
"annotations.csv": 283_053,
|
|
35
|
+
"neurotransmitters.csv": 364_160,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_UA = "Mozilla/5.0 (fly-instinct datafetch)"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _download_one(url: str, dest: str) -> int:
|
|
42
|
+
"""下载单个文件(已存在且大小正确则跳过;否则断点续传)。返回最终字节数。"""
|
|
43
|
+
expected = EXPECTED_SIZES.get(os.path.basename(dest))
|
|
44
|
+
if os.path.exists(dest):
|
|
45
|
+
sz = os.path.getsize(dest)
|
|
46
|
+
if expected is None or sz == expected:
|
|
47
|
+
print(f" [skip] {os.path.basename(dest)} 已存在 ({sz:,} B)")
|
|
48
|
+
return sz
|
|
49
|
+
|
|
50
|
+
req = urllib.request.Request(url, headers={"User-Agent": _UA})
|
|
51
|
+
# 断点续传:若已有部分文件
|
|
52
|
+
start = os.path.getsize(dest) if os.path.exists(dest) else 0
|
|
53
|
+
if start > 0:
|
|
54
|
+
req.add_header("Range", f"bytes={start}-")
|
|
55
|
+
|
|
56
|
+
with urllib.request.urlopen(req, timeout=60) as resp, \
|
|
57
|
+
open(dest, "ab" if start > 0 else "wb") as f:
|
|
58
|
+
total = start + int(resp.headers.get("Content-Length") or 0)
|
|
59
|
+
got = start
|
|
60
|
+
chunk = 1024 * 256
|
|
61
|
+
while True:
|
|
62
|
+
b = resp.read(chunk)
|
|
63
|
+
if not b:
|
|
64
|
+
break
|
|
65
|
+
f.write(b)
|
|
66
|
+
got += len(b)
|
|
67
|
+
if total:
|
|
68
|
+
pct = 100.0 * got / total
|
|
69
|
+
sys.stdout.write(f"\r {os.path.basename(dest)}: "
|
|
70
|
+
f"{got/1e6:.1f}/{total/1e6:.1f} MB "
|
|
71
|
+
f"({pct:.0f}%)")
|
|
72
|
+
sys.stdout.flush()
|
|
73
|
+
sys.stdout.write("\n")
|
|
74
|
+
sz = os.path.getsize(dest)
|
|
75
|
+
if expected is not None and sz != expected:
|
|
76
|
+
raise IOError(f"{dest}: 大小 {sz:,} != 预期 {expected:,},"
|
|
77
|
+
f"文件可能损坏,请删除后重试")
|
|
78
|
+
print(f" [ok] {os.path.basename(dest)} ({sz:,} B)")
|
|
79
|
+
return sz
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def fetch_data(out_dir: str, force: bool = False) -> dict:
|
|
83
|
+
"""下载全部 3 个 CSV 到 out_dir,校验大小。返回 {文件名: 字节数}。"""
|
|
84
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
85
|
+
print(f"下载 MaleCNS 子集数据 -> {os.path.abspath(out_dir)}")
|
|
86
|
+
sizes = {}
|
|
87
|
+
for name, url in DATA_URLS.items():
|
|
88
|
+
dest = os.path.join(out_dir, name)
|
|
89
|
+
if force and os.path.exists(dest):
|
|
90
|
+
os.remove(dest)
|
|
91
|
+
sizes[name] = _download_one(url, dest)
|
|
92
|
+
print("完成。用法:FlyInstinct.from_malecns('<out_dir>/edges.csv')")
|
|
93
|
+
return sizes
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv=None):
|
|
97
|
+
p = argparse.ArgumentParser(
|
|
98
|
+
description="下载 MaleCNS 真实连接组子集 (25MB, 3 个 CSV)")
|
|
99
|
+
p.add_argument("--out", default="data", help="输出目录 (默认 ./data)")
|
|
100
|
+
p.add_argument("--force", action="store_true", help="删除已有文件重新下载")
|
|
101
|
+
a = p.parse_args(argv)
|
|
102
|
+
fetch_data(a.out, force=a.force)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
main()
|
fly_instinct/engine.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fly_instinct.py — “本能引擎” (Instinct Engine)
|
|
3
|
+
================================================
|
|
4
|
+
|
|
5
|
+
一个可复用、非学习的“本能”信号源。
|
|
6
|
+
|
|
7
|
+
核心思想(储备池计算 / Reservoir Computing):
|
|
8
|
+
用一个【权重冻结、不学习】的递归网络,把输入刺激打散成
|
|
9
|
+
高维、非平凡、结构化的“本能反应”。它既不是白噪声(死的),
|
|
10
|
+
也不是脚本规则(可预测),而是“像活物一样对刺激有反应”。
|
|
11
|
+
|
|
12
|
+
真实完整版会加载 MaleCNS 连接组(~16.67 万神经元)的某个采样子图
|
|
13
|
+
作为冻结网络;本 PoC 用一个【结构匹配的冻结递归网络】扮演这个角色,
|
|
14
|
+
接口与行为完全一致,后续可直接替换为真实连接组子图。
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
from fly_instinct import FlyInstinct
|
|
18
|
+
fly = FlyInstinct(n_neurons=150, seed=7)
|
|
19
|
+
reaction, spikes = fly.react(stimulus) # stimulus: 1D/2D 数组
|
|
20
|
+
|
|
21
|
+
设计原则:
|
|
22
|
+
- 权重在 __init__ 时生成后【永久冻结】,react() 不做任何学习。
|
|
23
|
+
- 给定 seed + 刺激,输出是【确定性的】(除非显式注入噪声)。
|
|
24
|
+
- 噪声是可选的:真实动物的本能也带一点热噪声,可加可不加。
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FlyInstinct:
|
|
32
|
+
"""冻结递归网络 + LIF 动力学 组成的“本能”信号源。"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, n_neurons: int = 150, n_input: int = 1,
|
|
35
|
+
n_output: int = 1, seed: int = 0,
|
|
36
|
+
tau: float = 20.0, dt: float = 1.0,
|
|
37
|
+
v_th: float = 1.0, v_reset: float = 0.0,
|
|
38
|
+
gain: float = 6.0, spectral_radius: float = 0.95):
|
|
39
|
+
self.n = n_neurons
|
|
40
|
+
self.n_in = n_input
|
|
41
|
+
self.n_out = n_output
|
|
42
|
+
self.tau = tau
|
|
43
|
+
self.dt = dt
|
|
44
|
+
self.v_th = v_th
|
|
45
|
+
self.v_reset = v_reset
|
|
46
|
+
self.gain = gain
|
|
47
|
+
|
|
48
|
+
rng = np.random.default_rng(seed)
|
|
49
|
+
|
|
50
|
+
# 递归权重:冻结的“本能回路”。缩放到临界点附近以保留丰富动力学。
|
|
51
|
+
W = rng.normal(0.0, 1.0, size=(n_neurons, n_neurons))
|
|
52
|
+
W *= spectral_radius / np.linalg.norm(W, ord=2)
|
|
53
|
+
|
|
54
|
+
# 输入权重:刺激 -> 网络。
|
|
55
|
+
Win = rng.normal(0.0, 1.0, size=(n_neurons, n_input)) * (1.0 / np.sqrt(n_input + 1.0))
|
|
56
|
+
|
|
57
|
+
# 读出权重:网络活动 -> 单一“反应”标量(冻结,不训练)。
|
|
58
|
+
Wout = rng.normal(0.0, 1.0, size=(n_output, n_neurons)) / np.sqrt(n_neurons)
|
|
59
|
+
|
|
60
|
+
# —— 冻结:之后不再改变 ——
|
|
61
|
+
self.W = W
|
|
62
|
+
self.Win = Win
|
|
63
|
+
self.Wout = Wout
|
|
64
|
+
self._rng = np.random.default_rng(seed + 1)
|
|
65
|
+
|
|
66
|
+
self._v = np.zeros(n_neurons)
|
|
67
|
+
self._sp_prev = np.zeros(n_neurons)
|
|
68
|
+
self._reset_internal()
|
|
69
|
+
|
|
70
|
+
def _reset_internal(self):
|
|
71
|
+
self._v = np.zeros(self.n)
|
|
72
|
+
self._sp_prev = np.zeros(self.n)
|
|
73
|
+
|
|
74
|
+
def reset(self):
|
|
75
|
+
"""清空网络内部状态(保留冻结权重)。"""
|
|
76
|
+
self._reset_internal()
|
|
77
|
+
|
|
78
|
+
def step(self, stimulus: np.ndarray, noise: float = 0.0) -> np.ndarray:
|
|
79
|
+
"""推进一个时间步,返回该步的“反应”标量数组(长度 n_out)。"""
|
|
80
|
+
stim = np.asarray(stimulus, dtype=float).ravel()
|
|
81
|
+
I = self.W @ self._sp_prev + self.Win @ stim
|
|
82
|
+
if noise and noise > 0.0:
|
|
83
|
+
I = I + self._rng.normal(0.0, noise, size=self.n)
|
|
84
|
+
self._v = self._v + (self.dt / self.tau) * (0.0 - self._v) + self.dt * (I * self.gain)
|
|
85
|
+
sp = (self._v >= self.v_th).astype(float)
|
|
86
|
+
self._v = np.where(sp == 1.0, self.v_reset, self._v)
|
|
87
|
+
self._sp_prev = sp
|
|
88
|
+
return (self.Wout @ sp.T).ravel()
|
|
89
|
+
|
|
90
|
+
def react(self, stimulus: np.ndarray, noise: float = 0.0,
|
|
91
|
+
smooth: int = 1):
|
|
92
|
+
"""
|
|
93
|
+
对一段刺激序列做“本能反应”。
|
|
94
|
+
|
|
95
|
+
参数
|
|
96
|
+
----
|
|
97
|
+
stimulus : 1D 或 2D 数组,形状 (T,) 或 (T, n_input)
|
|
98
|
+
noise : 注入的网络内噪声幅度(0=确定性)
|
|
99
|
+
smooth : 输出滑动平均窗宽(1=不平滑)
|
|
100
|
+
|
|
101
|
+
返回
|
|
102
|
+
----
|
|
103
|
+
reaction : (T,) 反应强度序列(静息基线对齐 0、峰值对齐 1;
|
|
104
|
+
若网络被抑制到低于静息水平会出现小幅负值)
|
|
105
|
+
spikes : (n, T) 每个神经元的发放(0/1),供可视化
|
|
106
|
+
"""
|
|
107
|
+
stimulus = np.asarray(stimulus, dtype=float)
|
|
108
|
+
if stimulus.ndim == 1:
|
|
109
|
+
stimulus = stimulus[:, None]
|
|
110
|
+
T = stimulus.shape[0]
|
|
111
|
+
self.reset()
|
|
112
|
+
|
|
113
|
+
# 先测静息读出(零输入,待动力学稳定后取均值),作为反应基线
|
|
114
|
+
for _ in range(max(10, int(self.tau))):
|
|
115
|
+
self.step(np.zeros(self.n_in), noise=0.0)
|
|
116
|
+
resting = float((self.Wout @ self._sp_prev).ravel()[0])
|
|
117
|
+
|
|
118
|
+
reaction = np.zeros(T)
|
|
119
|
+
spikes = np.zeros((self.n, T))
|
|
120
|
+
for t in range(T):
|
|
121
|
+
reaction[t] = self.step(stimulus[t], noise=noise)[0] - resting
|
|
122
|
+
spikes[:, t] = self._sp_prev
|
|
123
|
+
|
|
124
|
+
if smooth > 1:
|
|
125
|
+
k = min(smooth, T)
|
|
126
|
+
kernel = np.ones(k) / k
|
|
127
|
+
reaction = np.convolve(reaction, kernel, mode="same")
|
|
128
|
+
|
|
129
|
+
# 归一化:静息基线对齐 0,峰值对齐 1(便于跨项目复用)
|
|
130
|
+
peak = reaction.max()
|
|
131
|
+
if peak > 0:
|
|
132
|
+
reaction = reaction / peak
|
|
133
|
+
return reaction, spikes
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_malecns(cls, edges_path, ann_path=None, nt_path=None,
|
|
137
|
+
seed=0, in_neurons=800, tau=20.0, dt=1.0,
|
|
138
|
+
v_th=1.0, v_reset=0.0, gain=1.0, spectral_radius=0.9):
|
|
139
|
+
"""
|
|
140
|
+
用【真实 MaleCNS 稀疏连接组】作为冻结递归核心构造本能引擎。
|
|
141
|
+
|
|
142
|
+
- 递归权重 W:真实连接组(按递质赋兴奋/抑制符号,谱半径归一到临界点),永久冻结。
|
|
143
|
+
- 输入接口 Win:刺激 -> 顶层 out-degree 神经元的固定兴奋投影(冻结、非学习)。
|
|
144
|
+
- 读出接口 Wout:全部神经元 -> 单一反应标量的固定随机投影(冻结、非学习)。
|
|
145
|
+
(该子集未标注明确的感受/运动神经元,用冻结投影是诚实且非学习的做法。)
|
|
146
|
+
|
|
147
|
+
与替身版接口完全一致:fly.react(stimulus) -> (reaction, spikes)
|
|
148
|
+
"""
|
|
149
|
+
from scipy import sparse
|
|
150
|
+
from .loader import load_malecns
|
|
151
|
+
|
|
152
|
+
d = load_malecns(edges_path, ann_path, nt_path,
|
|
153
|
+
spectral_radius=spectral_radius)
|
|
154
|
+
W = d["W"]
|
|
155
|
+
n = d["n"]
|
|
156
|
+
|
|
157
|
+
obj = cls.__new__(cls)
|
|
158
|
+
obj.n = n
|
|
159
|
+
obj.n_in = 1
|
|
160
|
+
obj.n_out = 1
|
|
161
|
+
obj.tau = tau
|
|
162
|
+
obj.dt = dt
|
|
163
|
+
obj.v_th = v_th
|
|
164
|
+
obj.v_reset = v_reset
|
|
165
|
+
obj.gain = gain
|
|
166
|
+
obj.W = W # 冻结:真实连接组
|
|
167
|
+
|
|
168
|
+
rng = np.random.default_rng(seed)
|
|
169
|
+
|
|
170
|
+
# 输入接口:刺激投到顶层 out-degree 神经元(兴奋驱动)
|
|
171
|
+
coo = W.tocoo()
|
|
172
|
+
outdeg = np.bincount(coo.col, minlength=n)
|
|
173
|
+
order = np.lexsort((np.arange(n), -outdeg)) # out-degree 降序,同度按 idx 升序
|
|
174
|
+
top = order[:min(in_neurons, n)]
|
|
175
|
+
in_w = rng.uniform(0.6, 1.2, size=top.size)
|
|
176
|
+
Win = sparse.csr_matrix((in_w, (top, np.zeros(top.size, dtype=int))),
|
|
177
|
+
shape=(n, 1))
|
|
178
|
+
|
|
179
|
+
# 读出接口:固定随机投影 -> 单一反应
|
|
180
|
+
Wout = sparse.csr_matrix(rng.normal(0.0, 1.0, (1, n)) / np.sqrt(n))
|
|
181
|
+
|
|
182
|
+
obj.Win = Win
|
|
183
|
+
obj.Wout = Wout
|
|
184
|
+
obj._rng = np.random.default_rng(seed + 1)
|
|
185
|
+
obj._v = np.zeros(n)
|
|
186
|
+
obj._sp_prev = np.zeros(n)
|
|
187
|
+
|
|
188
|
+
obj.is_real = True
|
|
189
|
+
obj.meta = {
|
|
190
|
+
"n_nodes": n,
|
|
191
|
+
"n_edges": d["n_edges"],
|
|
192
|
+
"inhibit_frac": d["inhibit_frac"],
|
|
193
|
+
"sr_raw": d["sr_raw"],
|
|
194
|
+
"sr_target": spectral_radius,
|
|
195
|
+
"in_neurons": int(top.size),
|
|
196
|
+
}
|
|
197
|
+
return obj
|
fly_instinct/loader.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
malecns_loader.py — 读取 MaleCNS 真实连接组子集,产出“冻结”稀疏权重矩阵
|
|
3
|
+
=======================================================================
|
|
4
|
+
|
|
5
|
+
只做数据这一件事:
|
|
6
|
+
- 读 edges.csv (pre, post, weight) -> 节点 ID 映射 -> scipy 稀疏矩阵 W[post, pre]
|
|
7
|
+
- 用 neurotransmitters.csv 给突触赋符号:GABA/glycine = 抑制(-),其余 = 兴奋(+)
|
|
8
|
+
* 注意(PROVENANCE 明确):递质符号是“先验预测”,不是 ground truth
|
|
9
|
+
- 按谱半径把 |W| 归一到临界点附近,保证递归网络有丰富而不爆炸的动力学
|
|
10
|
+
- 权重一经生成即视为“冻结”(本模块不做任何学习)
|
|
11
|
+
|
|
12
|
+
不依赖引擎代码,可单独测试。返回一个 dict(W + 元信息),交给 FlyInstinct 组装。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
import csv
|
|
16
|
+
import os
|
|
17
|
+
import numpy as np
|
|
18
|
+
from scipy import sparse
|
|
19
|
+
|
|
20
|
+
# 抑制性递质(小写比对)
|
|
21
|
+
INHIBIT = {"gaba", "glycine"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _spectral_radius(M, iters: int = 60, seed: int = 0) -> float:
|
|
25
|
+
"""用幂迭代近似 |M| 的谱半径(M 为稀疏矩阵,速度快)。"""
|
|
26
|
+
n = M.shape[0]
|
|
27
|
+
v = np.abs(np.random.default_rng(seed).random(n)) + 1e-9
|
|
28
|
+
v /= np.linalg.norm(v)
|
|
29
|
+
sr = 0.0
|
|
30
|
+
for _ in range(iters):
|
|
31
|
+
w = np.abs(M @ v)
|
|
32
|
+
nr = np.linalg.norm(w)
|
|
33
|
+
if nr < 1e-12:
|
|
34
|
+
return 0.0
|
|
35
|
+
v = w / nr
|
|
36
|
+
sr = float(nr)
|
|
37
|
+
return sr
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def load_malecns(edges_path: str, ann_path: str | None = None,
|
|
41
|
+
nt_path: str | None = None,
|
|
42
|
+
spectral_radius: float = 0.9) -> dict:
|
|
43
|
+
"""
|
|
44
|
+
加载 MaleCNS 真实连接组子集。
|
|
45
|
+
|
|
46
|
+
参数
|
|
47
|
+
----
|
|
48
|
+
edges_path : edges.csv,列 pre,post,weight
|
|
49
|
+
ann_path : annotations.csv(root_id,cell_type,superclass),可选
|
|
50
|
+
nt_path : neurotransmitters.csv(root_id,transmitter,confidence),可选
|
|
51
|
+
spectral_radius : 目标谱半径(递归强度的标尺,近临界 ~0.9)
|
|
52
|
+
|
|
53
|
+
返回
|
|
54
|
+
----
|
|
55
|
+
dict: W(稀疏 CSR, post x pre, 带符号已缩放), n, n_edges, node_ids,
|
|
56
|
+
id2idx, inhibit_frac, sr_raw, sr_target, cell_type
|
|
57
|
+
"""
|
|
58
|
+
# 1) 读边表,收集节点
|
|
59
|
+
rows = []
|
|
60
|
+
ids = set()
|
|
61
|
+
with open(edges_path, newline="") as f:
|
|
62
|
+
r = csv.reader(f)
|
|
63
|
+
next(r) # header
|
|
64
|
+
for row in r:
|
|
65
|
+
if len(row) < 3:
|
|
66
|
+
continue
|
|
67
|
+
pre, post, w = int(row[0]), int(row[1]), float(row[2])
|
|
68
|
+
rows.append((pre, post, w))
|
|
69
|
+
ids.add(pre)
|
|
70
|
+
ids.add(post)
|
|
71
|
+
|
|
72
|
+
# 2) 读细胞类型注释(顺带把注释里的节点也纳入,保证 ID 覆盖)
|
|
73
|
+
cell_type = {}
|
|
74
|
+
if ann_path and os.path.exists(ann_path):
|
|
75
|
+
with open(ann_path, newline="") as f:
|
|
76
|
+
r = csv.reader(f)
|
|
77
|
+
next(r)
|
|
78
|
+
for row in r:
|
|
79
|
+
if len(row) < 2:
|
|
80
|
+
continue
|
|
81
|
+
rid = int(row[0])
|
|
82
|
+
ids.add(rid)
|
|
83
|
+
ct = row[1]
|
|
84
|
+
sup = row[2] if len(row) > 2 else ""
|
|
85
|
+
cell_type[rid] = (ct, sup)
|
|
86
|
+
|
|
87
|
+
id2idx = {i: k for k, i in enumerate(sorted(ids))}
|
|
88
|
+
n = len(id2idx)
|
|
89
|
+
|
|
90
|
+
# 3) 每个(突触前)神经元的符号
|
|
91
|
+
sign = np.ones(n) # 默认兴奋
|
|
92
|
+
if nt_path and os.path.exists(nt_path):
|
|
93
|
+
with open(nt_path, newline="") as f:
|
|
94
|
+
r = csv.reader(f)
|
|
95
|
+
next(r)
|
|
96
|
+
for row in r:
|
|
97
|
+
if len(row) < 2:
|
|
98
|
+
continue
|
|
99
|
+
rid = int(row[0])
|
|
100
|
+
tr = row[1].strip().lower()
|
|
101
|
+
if rid in id2idx and tr in INHIBIT:
|
|
102
|
+
sign[id2idx[rid]] = -1.0
|
|
103
|
+
|
|
104
|
+
# 4) 组装 W[post, pre]
|
|
105
|
+
ii, jj, data = [], [], []
|
|
106
|
+
for pre, post, w in rows:
|
|
107
|
+
if pre in id2idx and post in id2idx:
|
|
108
|
+
ii.append(id2idx[post])
|
|
109
|
+
jj.append(id2idx[pre])
|
|
110
|
+
data.append(w * sign[id2idx[pre]])
|
|
111
|
+
W = sparse.csr_matrix((data, (ii, jj)), shape=(n, n))
|
|
112
|
+
W.sum_duplicates()
|
|
113
|
+
|
|
114
|
+
# 5) 谱半径归一到临界点附近
|
|
115
|
+
sr_raw = _spectral_radius(W, seed=0)
|
|
116
|
+
if sr_raw > 0:
|
|
117
|
+
W = W * (spectral_radius / sr_raw)
|
|
118
|
+
|
|
119
|
+
n_inhib = int((sign < 0).sum())
|
|
120
|
+
return {
|
|
121
|
+
"W": W,
|
|
122
|
+
"n": n,
|
|
123
|
+
"n_edges": len(rows),
|
|
124
|
+
"node_ids": sorted(ids),
|
|
125
|
+
"id2idx": id2idx,
|
|
126
|
+
"inhibit_frac": n_inhib / n,
|
|
127
|
+
"sr_raw": sr_raw,
|
|
128
|
+
"sr_target": spectral_radius,
|
|
129
|
+
"cell_type": cell_type,
|
|
130
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fly-instinct
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Instinct Engine: a frozen, non-learning 'instinct' signal source driven by a real fruit-fly (MaleCNS) connectome
|
|
5
|
+
Author: roblitz
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/roblitz-spec/fly-instinct
|
|
8
|
+
Project-URL: Data, https://male-cns.janelia.org
|
|
9
|
+
Keywords: connectome,fruit-fly,neuroscience,reservoir-computing,spiking-neurons,generative
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE.md
|
|
18
|
+
Requires-Dist: numpy>=1.24
|
|
19
|
+
Requires-Dist: scipy>=1.10
|
|
20
|
+
Provides-Extra: demo
|
|
21
|
+
Requires-Dist: matplotlib>=3.6; extra == "demo"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# fly-instinct · 果蝇连接组“本能引擎”
|
|
25
|
+
|
|
26
|
+
一个**可复用、非学习、纯本地**的“本能”信号源:把输入刺激喂进一个**权重冻结、不训练**的递归网络(核心是**真实果蝇 MaleCNS 连接组子图**),得到一个“像活物本能反应”的信号——**既不是白噪声(死的),也不是脚本规则(可预测)**。
|
|
27
|
+
|
|
28
|
+
> 定位一句话:它是一个**有结构的、非学习的反应算子**,用于给“需要‘活的反应’”的项目提供信号。
|
|
29
|
+
> 它**不是智能、不是大脑、没有意识、不会学习**。
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 文件结构
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
fly-instinct/
|
|
37
|
+
├── pyproject.toml # 打包配置(pip install . 或从 PyPI 安装)
|
|
38
|
+
├── fly_instinct/ # Python 包(发布进 PyPI 的部分)
|
|
39
|
+
│ ├── engine.py # 引擎(FlyInstinct):冻结递归网络 + LIF 动力学
|
|
40
|
+
│ ├── loader.py # 数据加载:真实 MaleCNS 子集 -> 冻结稀疏矩阵
|
|
41
|
+
│ ├── datafetch.py # 数据下载工具(25MB 子集,断点续传+校验)
|
|
42
|
+
│ └── __main__.py # python -m fly_instinct fetch-data 入口
|
|
43
|
+
├── examples/
|
|
44
|
+
│ ├── poc_demo.py # PoC(替身网络版,150 节点,无需数据)
|
|
45
|
+
│ └── poc_real.py # PoC(真实 MaleCNS 连接组版)
|
|
46
|
+
├── data/ # 真实 MaleCNS 子集(~25 MB,不进 pip 包)
|
|
47
|
+
│ ├── edges.csv # 连接权重图 pre,post,weight(1,587,930 条边)
|
|
48
|
+
│ ├── annotations.csv # 细胞类型/超类(root_id,cell_type,superclass)
|
|
49
|
+
│ └── neurotransmitters.csv# 递质(root_id,transmitter,confidence)
|
|
50
|
+
├── instinct_poc.png # 替身版三方对比图
|
|
51
|
+
├── instinct_poc_real.png # 真实数据三方对比图
|
|
52
|
+
├── README.md # 本文件(操作文档)
|
|
53
|
+
├── MODEL.md # 模型说明(架构/数据出处/完整连接组接入)
|
|
54
|
+
├── LICENSE.md # 许可与署名
|
|
55
|
+
└── PUBLISH.md # 发布清单(署名/合规/商用边界)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 安装
|
|
61
|
+
|
|
62
|
+
**方式 A:pip 安装(发布后)**
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install fly-instinct # 核心(numpy + scipy)
|
|
66
|
+
pip install fly-instinct[demo] # 含 matplotlib,可跑 examples 出图
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**方式 B:从本仓库安装**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd fly-instinct
|
|
73
|
+
pip install . # 或 pip install .[demo]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**下载数据(一次性,25MB)**
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
python -m fly_instinct fetch-data --out data
|
|
80
|
+
# 或安装后的命令行入口:
|
|
81
|
+
fly-instinct-fetch-data --out data
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> 数据是 **CC-BY 4.0 的 MaleCNS 子集**(上游 Janelia),体积 25MB 故不进 pip 包;
|
|
85
|
+
> 下载走 HuggingFace 镜像(国内可达),断点续传 + 大小校验,已存在则自动跳过。
|
|
86
|
+
|
|
87
|
+
**纯本地、无 API、无密钥、数据不外传。** 计算全部在本机 CPU 完成(唯一联网动作是上面这一次性数据下载)。
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 快速开始:跑 PoC
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
cd fly-instinct
|
|
95
|
+
|
|
96
|
+
# 真实连接组版(推荐,需先 fetch-data 到 data/)
|
|
97
|
+
python examples/poc_real.py # 自动选增益 + 三方对比 + 输出 instinct_poc_real.png
|
|
98
|
+
|
|
99
|
+
# 替身版(不需要数据,150 节点,秒级)
|
|
100
|
+
python examples/poc_demo.py # 输出 instinct_poc.png
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`poc_real.py` 在同一“威胁逼近”刺激下对比三种反应:
|
|
104
|
+
|
|
105
|
+
| 反应 | 响应延迟* | lag-1 自相关(结构) | 与刺激相关 | 威胁期发放率 |
|
|
106
|
+
|---|---|---|---|---|
|
|
107
|
+
| 纯随机(白噪声·死的) | 47 | ≈0 | ≈0 | — |
|
|
108
|
+
| 纯脚本(规则·可预测) | 131 | 1.000 | 0.998 | — |
|
|
109
|
+
| **真实本能(MaleCNS·非学习)** | 128 | 0.551 | −0.204 | 15.9% |
|
|
110
|
+
|
|
111
|
+
\* 响应延迟 = 相对刺激前基线上升超过 0.25 的第一个时间步(威胁在 t=120 出现)。
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 作为插件使用(一行)
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from fly_instinct import FlyInstinct
|
|
119
|
+
|
|
120
|
+
# 真实连接组(推荐)
|
|
121
|
+
fly = FlyInstinct.from_malecns(
|
|
122
|
+
"data/edges.csv",
|
|
123
|
+
ann_path="data/annotations.csv",
|
|
124
|
+
nt_path="data/neurotransmitters.csv",
|
|
125
|
+
seed=7, in_neurons=800, gain=2.0, spectral_radius=0.9,
|
|
126
|
+
)
|
|
127
|
+
reaction, spikes = fly.react(stimulus) # stimulus: 1D/2D 数组
|
|
128
|
+
# reaction: (T,) 本能反应强度(静息基线=0,峰值=1;被抑制时可能有小幅负值)
|
|
129
|
+
# spikes: (n, T) 每个神经元发放(0/1),供可视化
|
|
130
|
+
|
|
131
|
+
# 或替身版(不需要数据)
|
|
132
|
+
fly = FlyInstinct(n_neurons=150, seed=7, gain=6.0)
|
|
133
|
+
reaction, spikes = fly.react(stimulus)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**接口约定**
|
|
137
|
+
- `fly.react(stimulus, noise=0.0, smooth=1) -> (reaction, spikes)`
|
|
138
|
+
- 权重在构造时**永久冻结**,`react()` 不做任何学习;给定 `seed`+刺激,输出**确定性**(除非显式 `noise>0`)。
|
|
139
|
+
- `noise>0` 可叠加一点“热噪声”,更接近真实生物(可选)。
|
|
140
|
+
|
|
141
|
+
**典型用途**:游戏 NPC 本能行为、生成艺术/ComfyUI 的有机扰动、交互装置(“碰它,它有本能反应”)、科普 demo。
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 接入完整版 MaleCNS(1.05 GB)
|
|
146
|
+
|
|
147
|
+
当前 `data/` 是**真实子集**(约 1 万节点,够跑、够真)。要升级到**完整校对版**(16.67 万神经元 / 2560 万条连接),用官方 **1.05 GB** 连接权重文件:
|
|
148
|
+
|
|
149
|
+
**下载地址(官方,CC-BY 4.0)**
|
|
150
|
+
- 官方下载页:<https://male-cns.janelia.org/download/>
|
|
151
|
+
- 直链(GCS,可断点续传):
|
|
152
|
+
`https://storage.googleapis.com/flyem-male-cns/v1.0/connectome-data/flat-connectome/connectome-weights-male-cns-v1.0-minconf-0.5.feather`
|
|
153
|
+
- 文件:`connectome-weights-male-cns-v1.0-minconf-0.5.feather`(feather 格式,列同 `pre,post,weight`)
|
|
154
|
+
|
|
155
|
+
> 说明:`storage.googleapis.com` 在部分网络(含中国大陆)可能不可达;届时可用可达的镜像/代理下载该文件,**只要列名仍是 pre/post/weight 即可**。
|
|
156
|
+
|
|
157
|
+
**接入步骤**
|
|
158
|
+
1. 下载 `.feather` 到本地。
|
|
159
|
+
2. 转成 CSV(或扩展 loader 直接读 feather):
|
|
160
|
+
```python
|
|
161
|
+
import pandas as pd
|
|
162
|
+
df = pd.read_feather("connectome-weights-male-cns-v1.0-minconf-0.5.feather")
|
|
163
|
+
df.to_csv("full_edges.csv", index=False) # 列:pre,post,weight
|
|
164
|
+
```
|
|
165
|
+
3. 用同一接口加载(完整版的 `annotations`/`neurotransmitters` feather 同理转 CSV 后传入):
|
|
166
|
+
```python
|
|
167
|
+
from fly_instinct import FlyInstinct
|
|
168
|
+
fly = FlyInstinct.from_malecns("full_edges.csv",
|
|
169
|
+
ann_path="full_annotations.csv",
|
|
170
|
+
nt_path="full_neurotransmitters.csv",
|
|
171
|
+
seed=7, in_neurons=2000, gain=2.0, spectral_radius=0.9)
|
|
172
|
+
reaction, spikes = fly.react(stimulus)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**代价与注意**
|
|
176
|
+
- 完整版稀疏矩阵约 2560 万非零元,加载后内存数百 MB;单次 `react(T)` 计算量约为当前子集的 ~16 倍,建议 T 不要太大、机器内存 ≥ 8 GB。
|
|
177
|
+
- 完整版的**递质符号仍是先验预测**(见 MODEL.md 诚实边界)。
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 许可与署名(必须)
|
|
182
|
+
|
|
183
|
+
底层连接组数据为 **MaleCNS v1.0(Janelia FlyEM),CC-BY 4.0,需署名**;本包代码为你可自定授权的衍生作品。商用/分发前请阅读 `LICENSE.md` 与 `PUBLISH.md`。
|
|
184
|
+
|
|
185
|
+
## 诚实声明
|
|
186
|
+
|
|
187
|
+
本引擎输出的是“**冻结结构对刺激给出的、非学习的本能式反应**”。它**不具备智能、记忆、学习或意识**;递质兴奋/抑制符号是**先验预测**而非实验测定。请勿以“果蝇大脑 / 有意识 / 能学习”等措辞宣传。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
fly_instinct/__init__.py,sha256=na_eFwO0sz4Ukvr5E1iIFMzU47lNNuBEsSLNp1MMIZw,1196
|
|
2
|
+
fly_instinct/__main__.py,sha256=60iZgZylcTsxajUXvcO0P4e7TsxXWHcP55JT2mDu0xs,315
|
|
3
|
+
fly_instinct/datafetch.py,sha256=dBm59pb0-EFzSdBI_3SNqYPJyRDRm-gArtE9Imqj5oo,3896
|
|
4
|
+
fly_instinct/engine.py,sha256=xwbpXEzpb5kXGdCKMO0X3hnVVMaUIZvNoQbA_PC-Me0,7833
|
|
5
|
+
fly_instinct/loader.py,sha256=_Ipf0_lgu0TbShpUPCbSgu8_wKnnsBR3ULNlQb4huNc,4388
|
|
6
|
+
fly_instinct-0.1.0.dist-info/licenses/LICENSE.md,sha256=92tQJS2lLU-sZaYPKf1bNb9tKIAYcuZ3gb3VYlLGj9g,3548
|
|
7
|
+
fly_instinct-0.1.0.dist-info/METADATA,sha256=TIaQJXS0G9wG6KB4FXeoSJiSh4f1EVJerZHKY3chQng,8520
|
|
8
|
+
fly_instinct-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
fly_instinct-0.1.0.dist-info/entry_points.txt,sha256=qy0X0fciswZNsYe_NnYK30VFRT-EmO9aRJdnSFFThyI,72
|
|
10
|
+
fly_instinct-0.1.0.dist-info/top_level.txt,sha256=kK7lE3K_j9VBDnA3EDG2xWXe1sBo5UQzh7RwH7rrXs8,13
|
|
11
|
+
fly_instinct-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# LICENSE.md · 许可与署名
|
|
2
|
+
|
|
3
|
+
本包由**两部分**组成,许可不同,发布时**都要带上**。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## A. 连接组数据 —— CC-BY 4.0(不可改、必须署名)
|
|
8
|
+
|
|
9
|
+
`data/` 下的连接组数据源自 **MaleCNS v1.0(Janelia FlyEM)**,许可为 **CC-BY 4.0**。
|
|
10
|
+
CC-BY 4.0 **允许**:商用、修改、分发、做衍生作品。
|
|
11
|
+
CC-BY 4.0 **要求**:署名;且**不得**施加比 CC-BY 更严格的限制(例如不能禁止他人再分发数据)。
|
|
12
|
+
|
|
13
|
+
**发布时必须包含的署名块(可直接复制):**
|
|
14
|
+
|
|
15
|
+
> 本软件使用的果蝇连接组数据来自 **MaleCNS v1.0**(成年雄性果蝇中枢神经连接组),
|
|
16
|
+
> 由 Janelia Research Campus / Howard Hughes Medical Institute 提供,
|
|
17
|
+
> 来源 <https://male-cns.janelia.org/>,许可 **CC-BY 4.0**。
|
|
18
|
+
> 本包内的 25 MB 子集取自 <https://huggingface.co/datasets/nosuchstudios/fruit-fly-brain-runtime>(CC-BY 4.0)。
|
|
19
|
+
|
|
20
|
+
> ⚠️ 如果你把 `data/` 里的 CSV 随包一起分发,这些文件本身仍是 CC-BY 4.0,**不能**用“禁止再分发”之类的条款限制它们。
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## B. 本包代码 —— 你的衍生作品(可自定授权)
|
|
25
|
+
|
|
26
|
+
`fly_instinct/` 包(`engine.py`、`loader.py`、`datafetch.py`、`__init__.py`、`__main__.py`)与 `examples/` 下的 PoC 脚本,是**独立编写的衍生代码**,
|
|
27
|
+
**你可以自由选择授权方式**:
|
|
28
|
+
|
|
29
|
+
- **想开源 / 攒口碑 / 招人**:用 **MIT**(最宽松,允许商用、闭源集成)。
|
|
30
|
+
- **想闭源卖产品 / 卖服务**:可用**专有许可**(保留所有权利,仅授予使用许可),
|
|
31
|
+
但**数据部分(A)仍须保持 CC-BY 4.0 + 署名**,不能把数据锁死。
|
|
32
|
+
|
|
33
|
+
### 代码默认授权(MIT,可替换)
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
MIT License
|
|
37
|
+
|
|
38
|
+
Copyright (c) 2026 roblitz
|
|
39
|
+
|
|
40
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
41
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
42
|
+
in the Software without restriction, including without limitation the rights
|
|
43
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
44
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
45
|
+
furnished to do so, subject to the following conditions:
|
|
46
|
+
|
|
47
|
+
The above copyright notice and this permission notice shall be included in all
|
|
48
|
+
copies or substantial portions of the Software.
|
|
49
|
+
|
|
50
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
51
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
52
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
53
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
54
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
55
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
56
|
+
SOFTWARE.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> 本包代码采用 **MIT**(署名 roblitz)。若日后改为专有许可,替换上面的 MIT 段即可,
|
|
60
|
+
> 但**必须保留 A 部分的数据 CC-BY 4.0 署名**。
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 商用边界(一句话)
|
|
65
|
+
|
|
66
|
+
- ✅ 可以:把“引擎 + 接口 + 体验”做成产品/插件/SaaS 并收费(你卖的是你的工程与产品)。
|
|
67
|
+
- ✅ 必须:数据保持 CC-BY 4.0 + 署名;不得夸大(见 MODEL.md 诚实边界)。
|
|
68
|
+
- ❌ 不能:宣称拥有/独占“果蝇大脑”数据;用更严条款锁死 CC-BY 数据;以“有意识/能学习”等虚假措辞宣传。
|
|
69
|
+
|
|
70
|
+
> 本文件不构成法律意见。正式上架收费前,建议花小钱做一次快速合规审查,
|
|
71
|
+
> 重点核对署名条款与“不得再限制”的表述。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fly_instinct
|