Files
JspaceAI/jspaceai/evolution.py
XiuchengWu e782ef4db1 feat: JspaceAI 自主智慧架构
基于第一性原理 + 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
- 实时五通道感知 + 具身闭环运行
2026-07-07 09:20:15 +08:00

217 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
自主进化训练器
核心:模型在推理的同时持续学习。每处理一段文本,参数就更新一次。
进化循环:
1. 喂入新文本片段
2. forward + 计算 next-token loss
3. EWC 优化器更新参数(保护旧知识)
4. 经验回放:当前片段存入 buffer定期回放旧片段
5. 周期性 consolidate EWC更新 Fisher 信息和锚点)
6. 追踪专家可塑性统计
7. 定期生成样本,观察进化效果
这个循环可以无限运行——模型永远不会"训练完成",它一直在进化。
"""
from __future__ import annotations
import torch
import torch.nn.functional as F
from typing import Callable
from .language_model import (
JSpaceLanguageModel, LanguageConfig,
ExperienceReplay, EWCOptimizer, ExpertPlasticity,
)
class EvolutionTrainer:
"""自主进化训练器"""
def __init__(self, model: JSpaceLanguageModel, config: LanguageConfig,
lr: float = 1e-3, ewc_lambda: float = 0.1,
device: str = 'cpu'):
self.model = model.to(device)
self.config = config
self.device = device
# 三大自主进化机制
self.ewc_optimizer = EWCOptimizer(model, lr=lr, ewc_lambda=ewc_lambda)
self.replay_buffer = ExperienceReplay(capacity=500, seq_len=64)
self.plasticity = ExpertPlasticity(num_experts=config.num_experts)
# 进化历史追踪
self.history: list[dict] = []
def learn_step(self, token_seq: torch.Tensor) -> dict:
"""单步学习
Args:
token_seq: (batch, T) token indices
Returns:
stats: 包含 loss、注意力、专家统计等
"""
token_seq = token_seq.to(self.device)
# 1. Forward
logits, info = self.model(token_seq)
# 2. Next-token prediction loss
# logits[:, t] 预测 token_seq[:, t+1]
pred_logits = logits[:, :-1] # (batch, T-1, vocab)
targets = token_seq[:, 1:] # (batch, T-1)
loss = F.cross_entropy(
pred_logits.reshape(-1, self.config.vocab_size),
targets.reshape(-1),
)
# 3. 经验回放:如果有足够样本,混入旧数据
replay_loss = torch.tensor(0.0, device=self.device)
if len(self.replay_buffer.buffer) >= 8:
replay_seq = self.replay_buffer.sample(4)
if replay_seq is not None:
replay_seq = replay_seq.to(self.device)
replay_logits, _ = self.model(replay_seq)
replay_pred = replay_logits[:, :-1]
replay_targets = replay_seq[:, 1:]
replay_loss = F.cross_entropy(
replay_pred.reshape(-1, self.config.vocab_size),
replay_targets.reshape(-1),
)
# 4. 总 loss + EWC 正则 + 经验回放
total_task_loss = loss + 0.5 * replay_loss
total_loss = self.ewc_optimizer.step(total_task_loss)
# 5. 更新专家可塑性统计
self.plasticity.update(info['alpha'].detach(), token_seq.detach())
# 6. 存入经验回放
self.replay_buffer.push(token_seq.detach().cpu())
stats = {
'loss': loss.item(),
'replay_loss': replay_loss.item() if isinstance(replay_loss, torch.Tensor) else replay_loss,
'total_loss': total_loss,
'w_norm_mean': info['w_norm'].mean().item(),
'alpha_mean': info['alpha'].mean(dim=(0, 1)).detach().cpu().tolist(),
}
return stats
def consolidate(self, data_sample: torch.Tensor):
"""周期性 consolidate EWC——更新参数重要性锚点
在学完一段文本后调用,把当前知识"固化"
"""
self.ewc_optimizer.consolidate(data_sample.to(self.device), n_samples=20)
def evolve(self, text_stream: list[str], tokenizer,
seq_len: int = 64, batch_size: int = 4,
consolidate_every: int = 20,
generate_every: int = 50,
max_steps: int | None = None,
prompt_text: str = "To be",
on_progress: Callable | None = None) -> list[dict]:
"""
持续进化主循环
Args:
text_stream: 文本片段列表(模拟持续到来的数据流)
tokenizer: CharTokenizer
seq_len: 序列长度
batch_size: 每次喂入的 batch 大小
consolidate_every: 每隔多少步 consolidate EWC
generate_every: 每隔多少步生成样本观察
prompt_text: 生成样本的提示词
on_progress: 回调函数,返回当前进度
Returns:
history: 进化历史
"""
step = 0
all_tokens = []
# 把所有文本编码成 token 流
for text in text_stream:
tokens = tokenizer.encode(text)
all_tokens.extend(tokens)
# 用滑动窗口在完整 token 流上采样 batch
# 每个 batch 包含 batch_size 条序列,每条长 seq_len
# 相邻 batch 之间步进 stride 个序列
stride = batch_size # 每个 batch 用 batch_size 个新起点
n_possible_starts = max(0, len(all_tokens) - seq_len - 1)
n_batches = max(0, n_possible_starts // stride)
print(f"自主进化开始:{len(all_tokens)} tokens, {len(text_stream)} 段文本")
print(f"配置: seq_len={seq_len}, batch_size={batch_size}, "
f"stride={stride}, n_batches={n_batches}")
print("=" * 70)
for batch_idx in range(n_batches):
# 取一个 batch 的序列(滑动窗口)
batch_tokens = []
for i in range(batch_size):
start = batch_idx * stride + i
if start + seq_len >= len(all_tokens):
batch_tokens.append(all_tokens[-seq_len:])
else:
batch_tokens.append(all_tokens[start:start + seq_len])
if any(len(b) < seq_len for b in batch_tokens):
continue
token_seq = torch.tensor(batch_tokens, dtype=torch.long)
stats = self.learn_step(token_seq)
stats['step'] = step
stats['batch_idx'] = batch_idx
self.history.append(stats)
# 周期性 consolidate
if step > 0 and step % consolidate_every == 0:
self.consolidate(token_seq)
# 周期性生成 + 报告
if step % generate_every == 0 or step == n_batches - 1:
prompt_ids = tokenizer.encode(prompt_text)
generated = self.model.generate(
prompt_ids, n_new=80, temperature=0.8, top_k=5
)
sample = prompt_text + tokenizer.decode(generated)
stats['sample'] = sample
print(f"\n[step {step:4d}] loss={stats['loss']:.4f} "
f"replay={stats['replay_loss']:.4f} "
f"||w||={stats['w_norm_mean']:.3f}")
print(f" 专家使用率: {[f'{u:.2f}' for u in self.plasticity.usage.tolist()]}")
print(f" 生成样本: {repr(sample[:120])}...")
if on_progress:
on_progress(stats)
step += 1
if max_steps is not None and step >= max_steps:
break
print("\n" + "=" * 70)
print("自主进化完成")
return self.history
def get_evolution_summary(self) -> dict:
"""获取进化总结"""
if not self.history:
return {}
losses = [h['loss'] for h in self.history]
return {
'steps': len(self.history),
'final_loss': losses[-1],
'initial_loss': losses[0],
'min_loss': min(losses),
'expert_usage': self.plasticity.usage.tolist(),
'expert_specialization': self.plasticity.get_stats()['top_specialization'],
'samples': [h.get('sample', '') for h in self.history if 'sample' in h],
}