基于第一性原理 + Anthropic 2026 J-space 论文实现的具身智慧系统。 核心架构: - ODE 动力系统 + 并行专家 + J-space 工作空间广播 - 12 个异构专家(视觉/屏幕/听觉/语言/鼠标/跨模态) - workspace 256 维 + LayerNorm + RK4 积分 自主心智(最重要的能力): - 好奇心驱动探索(内在奖励 + 世界模型) - 跨会话状态持久化(海洋不蒸发) - 自我模型(知道自己会什么不会什么) - 元学习(自适应学习率 + 策略选择) 具身 Agent(完整神经系统): - 感知层:摄像头 + 麦克风 + 屏幕 + 键盘 + 鼠标 - 大脑皮层(workspace)+ 小脑(运动控制)+ 中枢神经(门控) - 海马体(情景记忆)+ 基底神经节(动作选择) - 执行器:鼠标控制 + 键盘输出 + 音频播放 + 屏幕绘制 多模态支持: - 原生图像/音频/视频/文本/键盘/鼠标 6 种模态 - 跨平台(macOS/Windows/Linux) 外挂模块系统: - 可热插拔的外部能力(小模型/知识库/工具) - 核心心智不依赖外挂,断开后继续工作 守护进程: - 用户主动 start/stop(不自启) - 后台静默运行,持续感知学习 - 状态自动保存,跨会话继续 J-lens 可解释性: - 观测模型内部每个 ODE 子步的想法 - Directed Modulation 验证 workspace 因果作用 - Selectivity 验证(ablate workspace) 小模型蒸馏: - 接 GPT-2/Qwen 等迁移理解能力 - 蒸馏完成后小模型可断开 验证结果: - 连续序列:JSpace 胜 Flat 39.7% - 语言进化:loss 3.95→2.40 - workspace ||w||:v1 0.05 → v2 16.0 - 实时五通道感知 + 具身闭环运行
321 lines
12 KiB
Python
321 lines
12 KiB
Python
"""
|
||
核心架构:专家模块 + J-space 工作空间 + ODE 动力学
|
||
|
||
数学形式(每个 forward 时间步内做 Euler 积分若干子步):
|
||
|
||
专家 i 的状态 m_i:
|
||
dm_i/dt = -∇U_i(m_i) + J_i · w + P_i_in · x
|
||
|
||
U_i(m_i) = ½ ||m_i||² - ½ Σ_k softplus(a_ik · m_ik + b_ik)
|
||
(多井势能:阻尼项 + softplus 形成的局部吸引子)
|
||
|
||
工作空间 w:
|
||
τ_w · dw/dt = -w + Σ_i α_i · P_i_out(m_i)
|
||
α_i = softmax(<q, P_i_out(m_i)>) q = MLP(x, w)
|
||
|
||
Jacobian 路由 J_i: 稀疏线性映射,每个专家只对 w 的少数维度敏感
|
||
输出门控:当 ||w|| > θ 时触发输出 R(w)
|
||
|
||
所有参数都可 backprop 训练。学习目标是预测下一时刻的输入。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import torch
|
||
import torch.nn as nn
|
||
import torch.nn.functional as F
|
||
from dataclasses import dataclass
|
||
|
||
|
||
@dataclass
|
||
class JSpaceConfig:
|
||
"""模型超参,全部可调"""
|
||
input_dim: int = 8 # 输入 x 的维度
|
||
workspace_dim: int = 32 # 工作空间 w 的维度(J-space)
|
||
expert_dim: int = 16 # 每个专家内部状态 m_i 的维度
|
||
num_experts: int = 5 # 专家数量
|
||
num_wells: int = 4 # 每个专家势能景观的井数
|
||
ode_steps: int = 4 # 每个时间步内 ODE 积分子步数
|
||
dt: float = 0.1 # ODE 积分步长
|
||
tau_w: float = 0.3 # 工作空间时间常数
|
||
output_threshold: float = 0.5 # 输出门控阈值(船舶涌出阈值)
|
||
jacobian_sparsity: int = 8 # 每个 J_i 只保留前 k 大的连接
|
||
noise_std: float = 0.01 # 内部噪声 ξ(t) 的标准差
|
||
|
||
|
||
class Expert(nn.Module):
|
||
"""
|
||
单个专家模块。
|
||
|
||
状态:m_i ∈ R^{expert_dim}
|
||
势能:U_i(m_i) = ½||m_i||² - ½ Σ_k softplus(a_k · m_i + b_k) · w_k
|
||
- ½||m_i||² 是阻尼项(拉回原点)
|
||
- softplus 项创造多个局部吸引子(多井势能 → 内部"思考")
|
||
动力学:dm_i/dt = -∇U_i(m_i) + J_i · w + P_in · x + ξ
|
||
|
||
J_i 是稀疏 Jacobian:从工作空间 w 路由信息进来。
|
||
"""
|
||
|
||
def __init__(self, expert_dim: int, workspace_dim: int, input_dim: int,
|
||
num_wells: int, sparsity: int):
|
||
super().__init__()
|
||
self.expert_dim = expert_dim
|
||
self.workspace_dim = workspace_dim
|
||
self.num_wells = num_wells
|
||
|
||
# 势能景观参数:每个井是一个 softplus 形成的吸引子
|
||
# U(m) = 0.5||m||^2 - 0.5 * sum_k softplus(a_k @ m + b_k)
|
||
# ∇U(m) = m - 0.5 * sum_k sigmoid(a_k @ m + b_k) * a_k
|
||
self.well_a = nn.Parameter(torch.randn(num_wells, expert_dim) * 0.3)
|
||
self.well_b = nn.Parameter(torch.zeros(num_wells))
|
||
|
||
# P_in: 输入投影 x -> m_i 的扰动
|
||
self.P_in = nn.Linear(input_dim, expert_dim, bias=False)
|
||
|
||
# P_out: 模块输出到工作空间的投影
|
||
self.P_out = nn.Linear(expert_dim, workspace_dim, bias=False)
|
||
|
||
# J_i: 稀疏 Jacobian,从 w 路由信息到 m_i
|
||
# 用 top-k 稀疏:训练时学习一个 full matrix,但只激活 top-k
|
||
self.J_raw = nn.Parameter(torch.randn(expert_dim, workspace_dim) * 0.1)
|
||
self.sparsity = sparsity
|
||
|
||
# 注意:sparsity 通过 forward 时 top-k 选择实现,可微性通过稀疏 mask 保留
|
||
|
||
def get_sparse_J(self) -> torch.Tensor:
|
||
"""获取稀疏化的 Jacobian:每行只保留 top-k 元素"""
|
||
if self.sparsity >= self.workspace_dim:
|
||
return self.J_raw
|
||
# 对每行做 top-k(按绝对值)
|
||
abs_J = self.J_raw.abs()
|
||
topk_vals, topk_idx = abs_J.topk(self.sparsity, dim=-1)
|
||
mask = torch.zeros_like(self.J_raw)
|
||
mask.scatter_(-1, topk_idx, 1.0)
|
||
return self.J_raw * mask
|
||
|
||
def grad_potential(self, m: torch.Tensor) -> torch.Tensor:
|
||
"""计算势能梯度 ∇U_i(m)
|
||
U(m) = 0.5||m||^2 - 0.5 * sum_k softplus(a_k @ m + b_k)
|
||
∇U(m) = m - 0.5 * sum_k sigmoid(a_k @ m + b_k) * a_k
|
||
"""
|
||
# m: (batch, expert_dim)
|
||
# well_a: (num_wells, expert_dim)
|
||
# a_k @ m: (batch, num_wells)
|
||
am = F.linear(m, self.well_a, self.well_b) # (batch, num_wells)
|
||
sig = torch.sigmoid(am) # (batch, num_wells)
|
||
# sum_k sigmoid(...) * a_k: (batch, expert_dim)
|
||
# well_a: (num_wells, expert_dim), sig: (batch, num_wells) -> (batch, 1, num_wells)
|
||
# 用 matmul: sig @ well_a -> (batch, expert_dim)
|
||
grad_wells = torch.matmul(sig, self.well_a) # (batch, expert_dim)
|
||
return m - 0.5 * grad_wells
|
||
|
||
def forward(self, m: torch.Tensor, w: torch.Tensor, x: torch.Tensor,
|
||
dt: float, noise_std: float) -> tuple[torch.Tensor, torch.Tensor]:
|
||
"""一步 ODE 积分(Euler 法)
|
||
|
||
Args:
|
||
m: (batch, expert_dim) 当前状态
|
||
w: (batch, workspace_dim) 工作空间状态
|
||
x: (batch, input_dim) 输入
|
||
dt: 步长
|
||
noise_std: 噪声标准差
|
||
|
||
Returns:
|
||
m_next: (batch, expert_dim) 下一状态
|
||
contribution: (batch, workspace_dim) 对工作空间的贡献(pre-attention)
|
||
"""
|
||
# 动力学: dm/dt = -∇U(m) + J·w + P_in·x + ξ
|
||
J = self.get_sparse_J() # (expert_dim, workspace_dim)
|
||
w_proj = F.linear(w, J) # (batch, expert_dim)
|
||
x_proj = self.P_in(x) # (batch, expert_dim)
|
||
grad_U = self.grad_potential(m) # (batch, expert_dim)
|
||
|
||
noise = torch.randn_like(m) * noise_std if noise_std > 0 else 0.0
|
||
|
||
dm = -grad_U + w_proj + x_proj + noise
|
||
m_next = m + dt * dm
|
||
|
||
# 对工作空间的贡献
|
||
contribution = self.P_out(m_next) # (batch, workspace_dim)
|
||
return m_next, contribution
|
||
|
||
|
||
class JSpaceWorkspace(nn.Module):
|
||
"""
|
||
全局工作空间 w。
|
||
|
||
动力学: τ_w · dw/dt = -w + Σ_i α_i · P_i_out(m_i)
|
||
α_i = softmax(<q, P_i_out(m_i)>) q = MLP(x, w)
|
||
|
||
这个 α_i 是"注意力"——决定哪个专家的内容进入工作空间。
|
||
"""
|
||
|
||
def __init__(self, workspace_dim: int, input_dim: int, num_experts: int):
|
||
super().__init__()
|
||
self.workspace_dim = workspace_dim
|
||
|
||
# Query 生成器:从 (x, w) 生成 query 向量
|
||
self.query_gen = nn.Sequential(
|
||
nn.Linear(input_dim + workspace_dim, 32),
|
||
nn.Tanh(),
|
||
nn.Linear(32, workspace_dim),
|
||
)
|
||
|
||
def forward(self, w: torch.Tensor, x: torch.Tensor,
|
||
contributions: torch.Tensor, dt: float,
|
||
tau_w: float) -> tuple[torch.Tensor, torch.Tensor]:
|
||
"""一步工作空间演化
|
||
|
||
Args:
|
||
w: (batch, workspace_dim)
|
||
x: (batch, input_dim)
|
||
contributions: (batch, num_experts, workspace_dim) 各专家的贡献
|
||
dt: 步长
|
||
tau_w: 时间常数
|
||
|
||
Returns:
|
||
w_next: (batch, workspace_dim)
|
||
alpha: (batch, num_experts) 注意力权重(可解释性用)
|
||
"""
|
||
# 生成 query
|
||
q = self.query_gen(torch.cat([x, w], dim=-1)) # (batch, workspace_dim)
|
||
|
||
# 计算每个专家的注意力分数
|
||
# contributions: (batch, num_experts, workspace_dim)
|
||
# q: (batch, workspace_dim) -> (batch, 1, workspace_dim)
|
||
scores = (contributions * q.unsqueeze(1)).sum(dim=-1) # (batch, num_experts)
|
||
alpha = F.softmax(scores, dim=-1) # (batch, num_experts)
|
||
|
||
# 加权聚合
|
||
# alpha: (batch, num_experts, 1) * contributions: (batch, num_experts, workspace_dim)
|
||
aggregated = (alpha.unsqueeze(-1) * contributions).sum(dim=1) # (batch, workspace_dim)
|
||
|
||
# 动力学: τ_w · dw/dt = -w + aggregated
|
||
dw = (-w + aggregated) / tau_w
|
||
w_next = w + dt * dw
|
||
return w_next, alpha
|
||
|
||
|
||
class JSpaceModel(nn.Module):
|
||
"""
|
||
完整模型:N 个专家 + 工作空间 + 输出门控 + 预测头。
|
||
|
||
forward 流程(每个时间步):
|
||
1. 每个专家从 (m_i, w, x) 更新 m_i,产出对工作空间的贡献
|
||
2. 工作空间从 (w, x, contributions) 更新 w
|
||
3. (可选)当 ||w|| > threshold 时输出 R(w)
|
||
4. 预测头 Q(w) 预测下一时刻输入
|
||
|
||
时间序列处理:对长度 T 的输入序列,依次跑 T 步,返回每步的预测。
|
||
"""
|
||
|
||
def __init__(self, config: JSpaceConfig):
|
||
super().__init__()
|
||
self.config = config
|
||
|
||
self.experts = nn.ModuleList([
|
||
Expert(
|
||
expert_dim=config.expert_dim,
|
||
workspace_dim=config.workspace_dim,
|
||
input_dim=config.input_dim,
|
||
num_wells=config.num_wells,
|
||
sparsity=config.jacobian_sparsity,
|
||
)
|
||
for _ in range(config.num_experts)
|
||
])
|
||
|
||
self.workspace = JSpaceWorkspace(
|
||
workspace_dim=config.workspace_dim,
|
||
input_dim=config.input_dim,
|
||
num_experts=config.num_experts,
|
||
)
|
||
|
||
# 输出门控:R(w) → action(这里 action = 预测的下一时刻输入)
|
||
self.predictor = nn.Sequential(
|
||
nn.Linear(config.workspace_dim, 32),
|
||
nn.Tanh(),
|
||
nn.Linear(32, config.input_dim),
|
||
)
|
||
|
||
def init_state(self, batch_size: int, device: torch.device) -> dict:
|
||
"""初始化内部状态"""
|
||
return {
|
||
'w': torch.zeros(batch_size, self.config.workspace_dim, device=device),
|
||
'm': [torch.zeros(batch_size, self.config.expert_dim, device=device)
|
||
for _ in range(self.config.num_experts)],
|
||
}
|
||
|
||
def step(self, state: dict, x: torch.Tensor) -> tuple[dict, torch.Tensor, torch.Tensor, torch.Tensor]:
|
||
"""单时间步前向
|
||
|
||
Returns:
|
||
new_state: 更新后的状态
|
||
pred: (batch, input_dim) 预测的下一时刻输入
|
||
alpha: (batch, num_experts) 注意力权重(可解释性)
|
||
w_norm: (batch,) 工作空间范数(输出门控信号)
|
||
"""
|
||
w = state['w']
|
||
ms = state['m']
|
||
cfg = self.config
|
||
|
||
# ODE 子步积分
|
||
for _ in range(cfg.ode_steps):
|
||
# 1. 每个专家更新
|
||
contributions = []
|
||
new_ms = []
|
||
for i, expert in enumerate(self.experts):
|
||
m_next, contrib = expert(
|
||
ms[i], w, x,
|
||
dt=cfg.dt, noise_std=cfg.noise_std,
|
||
)
|
||
new_ms.append(m_next)
|
||
contributions.append(contrib)
|
||
contributions = torch.stack(contributions, dim=1) # (batch, num_experts, workspace_dim)
|
||
|
||
# 2. 工作空间更新
|
||
w, alpha = self.workspace(
|
||
w, x, contributions,
|
||
dt=cfg.dt, tau_w=cfg.tau_w,
|
||
)
|
||
ms = new_ms
|
||
|
||
# 3. 输出:预测下一时刻输入(船舶涌出,但这里为了训练简化为每步都预测)
|
||
pred = self.predictor(w)
|
||
w_norm = w.norm(dim=-1)
|
||
|
||
new_state = {'w': w, 'm': ms}
|
||
return new_state, pred, alpha, w_norm
|
||
|
||
def forward(self, xs: torch.Tensor, state: dict | None = None) -> tuple[torch.Tensor, dict]:
|
||
"""
|
||
Args:
|
||
xs: (batch, T, input_dim) 输入序列
|
||
state: 初始状态,None 则初始化
|
||
|
||
Returns:
|
||
preds: (batch, T, input_dim) 每步对下一时刻的预测
|
||
info: 包含注意力、w_norm 等可解释性信息
|
||
"""
|
||
batch_size, T, _ = xs.shape
|
||
device = xs.device
|
||
|
||
if state is None:
|
||
state = self.init_state(batch_size, device)
|
||
|
||
preds = []
|
||
alphas = []
|
||
w_norms = []
|
||
for t in range(T):
|
||
state, pred, alpha, w_norm = self.step(state, xs[:, t])
|
||
preds.append(pred)
|
||
alphas.append(alpha)
|
||
w_norms.append(w_norm)
|
||
|
||
preds = torch.stack(preds, dim=1) # (batch, T, input_dim)
|
||
info = {
|
||
'alpha': torch.stack(alphas, dim=1), # (batch, T, num_experts)
|
||
'w_norm': torch.stack(w_norms, dim=1), # (batch, T)
|
||
'final_w': state['w'],
|
||
'final_m': state['m'],
|
||
}
|
||
return preds, info
|