Files
JspaceAI/main_language.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

223 lines
7.1 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.
#!/usr/bin/env python3
"""
JspaceAI 语言版 —— 自主进化实验
模型在 Shakespeare 文本上持续学习,边推理边进化。
观察:
1. loss 持续下降(在学习)
2. 生成文本从乱码逐渐变成类 Shakespeare 风格
3. 专家分工涌现(不同专家处理不同字符模式)
4. EWC + 经验回放防止灾难性遗忘
运行:
python main_language.py
python main_language.py --steps 300 --device mps
"""
from __future__ import annotations
import argparse
import torch
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
from jspaceai import (
LanguageConfig, JSpaceLanguageModel, EvolutionTrainer,
CharTokenizer, load_shakespeare,
)
def run_language_experiment(n_steps: int, device: str, outdir: Path):
outdir.mkdir(parents=True, exist_ok=True)
torch.manual_seed(42)
np.random.seed(42)
# 1. 数据
text = load_shakespeare()
tokenizer = CharTokenizer.from_text(text)
print(f"文本长度: {len(text)} 字符")
print(f"词汇表大小: {tokenizer.vocab_size}")
print(f"词汇表: {''.join(tokenizer.chars[:50])}...")
# 切成多段,模拟持续到来的文本流
chunk_size = 200
text_stream = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
print(f"文本流: {len(text_stream)} 段, 每段 {chunk_size} 字符")
# 2. 模型
config = LanguageConfig(
vocab_size=tokenizer.vocab_size,
embed_dim=16,
input_dim=8, # J-space 输入维度
workspace_dim=32, # 工作空间维度
expert_dim=16, # 每个专家内部状态
num_experts=5, # 5 个并行专家
num_wells=4, # 每个专家 4 个吸引子
ode_steps=3, # ODE 积分子步
dt=0.1,
tau_w=0.3,
jacobian_sparsity=8,
noise_std=0.005,
)
model = JSpaceLanguageModel(config)
n_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"\n模型参数量: {n_params:,}")
# 3. 自主进化训练器
trainer = EvolutionTrainer(
model, config,
lr=5e-3,
ewc_lambda=0.05, # 较小的 EWC 权重,让模型能学新东西
device=device,
)
# 4. 进化
print("\n" + "=" * 70)
print("自主进化开始")
print("=" * 70)
# 限制步数
history = trainer.evolve(
text_stream,
tokenizer,
seq_len=48,
batch_size=4,
consolidate_every=30,
generate_every=30,
max_steps=n_steps,
prompt_text="To be",
)
# 5. 总结
summary = trainer.get_evolution_summary()
print("\n" + "=" * 70)
print("进化总结")
print("=" * 70)
print(f"总步数: {summary['steps']}")
print(f"初始 loss: {summary['initial_loss']:.4f}")
print(f"最终 loss: {summary['final_loss']:.4f}")
print(f"最低 loss: {summary['min_loss']:.4f}")
print(f"loss 下降: {(1 - summary['final_loss']/summary['initial_loss'])*100:.1f}%")
print(f"\n专家最终使用率: {[f'{u:.3f}' for u in summary['expert_usage']]}")
# 专家专业化(每个专家最常处理的 top-5 字符)
print("\n专家专业化top-5 字符):")
for i, spec in enumerate(summary['expert_specialization']):
chars = tokenizer.decode([s[0] for s in spec])
weights = [f"{s[1]:.1f}" for s in spec]
display = ' '.join(f"{repr(c)}({w})" for c, w in zip(chars, weights))
print(f" 专家 {i}: {display}")
# 6. 最终生成对比
print("\n" + "=" * 70)
print("最终生成对比")
print("=" * 70)
for prompt in ["To be", "Romeo", "The "]:
prompt_ids = tokenizer.encode(prompt)
generated = model.generate(prompt_ids, n_new=100, temperature=0.7, top_k=5)
sample = prompt + tokenizer.decode(generated)
print(f"\n提示 '{prompt}':")
print(f" {sample}")
# 7. 可视化
print("\n生成可视化...")
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Loss 曲线
ax = axes[0, 0]
losses = [h['loss'] for h in history]
ax.plot(losses, alpha=0.3, linewidth=0.5, color='blue', label='raw')
if len(losses) > 10:
smoothed = np.convolve(losses, np.ones(10)/10, mode='valid')
ax.plot(smoothed, linewidth=2, color='blue', label='smoothed')
ax.set_xlabel('Evolution step')
ax.set_ylabel('Cross-entropy loss')
ax.set_title('Loss during self-evolution')
ax.legend()
ax.grid(True, alpha=0.3)
# 专家使用率演化
ax = axes[0, 1]
alpha_history = np.array([h['alpha_mean'] for h in history])
for i in range(config.num_experts):
ax.plot(alpha_history[:, i], label=f'Expert {i}', alpha=0.8)
ax.set_xlabel('Evolution step')
ax.set_ylabel('Attention weight (mean)')
ax.set_title('Expert usage during evolution')
ax.legend()
ax.grid(True, alpha=0.3)
# ||w|| 演化
ax = axes[1, 0]
w_norms = [h['w_norm_mean'] for h in history]
ax.plot(w_norms, linewidth=1.5, color='green')
ax.set_xlabel('Evolution step')
ax.set_ylabel('||w|| (mean)')
ax.set_title('Workspace norm during evolution')
ax.grid(True, alpha=0.3)
# 生成样本随时间演化
ax = axes[1, 1]
ax.axis('off')
samples = [(h['step'], h.get('sample', '')) for h in history if 'sample' in h]
text_lines = []
for step, sample in samples[:8]:
s = sample[:60].replace('\n', ' ')
text_lines.append(f"step {step:3d}: {s}")
ax.text(0.05, 0.95, '\n'.join(text_lines),
transform=ax.transAxes, fontsize=9, verticalalignment='top',
fontfamily='monospace',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
ax.set_title('Generated samples during evolution')
plt.tight_layout()
fig_path = outdir / 'language_evolution.png'
plt.savefig(fig_path, dpi=150, bbox_inches='tight')
print(f"可视化已保存: {fig_path}")
plt.close()
# 保存模型
torch.save({
'model': model.state_dict(),
'config': config,
'tokenizer_chars': tokenizer.chars,
'history': history,
'summary': summary,
}, outdir / 'language_model.pt')
print(f"模型已保存: {outdir / 'language_model.pt'}")
print("\n" + "=" * 70)
print("实验完成")
print("=" * 70)
def main():
parser = argparse.ArgumentParser(description='JspaceAI 语言版自主进化实验')
parser.add_argument('--steps', type=int, default=100,
help='文本流段数 (default: 100)')
parser.add_argument('--device', type=str, default='cpu',
help='设备 (cpu / cuda / mps / auto)')
parser.add_argument('--outdir', type=str, default='outputs',
help='输出目录')
args = parser.parse_args()
if args.device == 'auto':
if torch.cuda.is_available():
device = 'cuda'
elif torch.backends.mps.is_available():
device = 'mps'
else:
device = 'cpu'
else:
device = args.device
run_language_experiment(
n_steps=args.steps,
device=device,
outdir=Path(args.outdir),
)
if __name__ == '__main__':
main()