# mnist-dev **Repository Path**: oplus-data/mnist-dev ## Basic Information - **Project Name**: mnist-dev - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-21 - **Last Updated**: 2026-06-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ⚡ NN Training Dashboard — 训练脚手架 一个用于**从零手写机器学习算法**的可视化训练框架。 目标:让你只关注模型实现本身(`forward` / `backward`),其它一切——训练循环、指标记录、中间张量捕获、超参热更新、实时可视化——框架全部替你搞定。 > 适合场景:手写实现 Linear / MLP / CNN / RNN / LSTM / Attention / PagedAttention > 适合人群:学习者、面试准备、研究者、需要"看清模型到底在做什么"的人 --- ## 目录 - [特性一览](#特性一览) - [快速开始](#快速开始) - [项目结构](#项目结构) - [Dashboard 页面说明](#dashboard-页面说明) - [如何写一个模型](#如何写一个模型) - [如何训练并接入 Dashboard](#如何训练并接入-dashboard) - [Tracer / Probe 体系](#tracer--probe-体系) - [计算后端(CPU / GPU / 未来 Torch)](#计算后端cpu--gpu--未来-torch) - [配置文件](#配置文件) - [远程访问 / HTTP API](#远程访问--http-api) - [本地 Python 窗口 / Jupyter](#本地-python-窗口--jupyter) - [预置数据集与预置用例](#预置数据集与预置用例) - [常见问题](#常见问题) --- ## 特性一览 | 模块 | 能力 | |------|------| | **训练循环** | 后台线程、mini-batch shuffle、早停、epoch callback、热更新超参 | | **历史记录** | 线程安全的 `TrainingHistory`,自动保存所有 run,支持持久化 JSON | | **中间张量捕获** | 基于 tag 的 `Tracer/probe` 体系,全局单例、自动 step 编号、PyTorch 兼容 | | **多页 Dashboard** | Dash + Plotly,6 个页面:Training / Weights / Attention / Gradients / Sequences / Features | | **超参控制** | 6 个滑杆(学习率/批大小/轮数/动量/权重衰减/dropout)+ Apply & Retrain / Stop | | **远程监控** | 独立 HTTP/SSE 服务(`/stream`、`/status`、`/tracer/`),支持远程脚本订阅 | | **本地窗口** | `LiveWindow` 用 matplotlib 在桌面弹出,`SnapshotPlot` 静态导出,`JupyterLiveDisplay` inline 更新 | | **计算后端** | 自动 CuPy GPU ↔ NumPy CPU 切换,模型代码无需改动;可选强制 CPU | | **多种用例预设** | 4 个 YAML 配置文件:MNIST / CIFAR-10 / LFW 人脸 / Shakespeare 字符级 RNN | --- ## 快速开始 ### 1. 准备环境 ```bash # 已有 venv 在 ../venv;如未创建: python3 -m venv ../venv source ../venv/bin/activate # 安装依赖 pip install -r requirements.txt # (可选)GPU 加速 pip install cupy-cuda12x # 或 cupy-cuda11x ``` ### 2. 启动默认训练(MNIST + 占位模型) ```bash cd /mnt/sda6/opt/aidev/mnist source ../venv/bin/activate python main.py ``` 启动后控制台会打印: ``` [backend] 🔵 CPU | numpy | Intel(R) ... [StatusServer] http://0.0.0.0:8051 端点: /status /history /runs /stream(SSE) /tracer/ Dashboard: http://127.0.0.1:8050 SSE stream: http://0.0.0.0:8051/stream ``` 浏览器打开 `http://127.0.0.1:8050` 即可看到 Dashboard。 ### 3. 切换用例 ```bash # CIFAR-10 python main.py --config configs/cifar10.yaml # 人脸识别(LFW) python main.py --config configs/face.yaml # 文本 RNN(tiny-shakespeare) python main.py --config configs/text_rnn.yaml ``` > 如果当前 `main.py` 还不支持 `--config` 参数,编辑 `main.py` 第 123 行改 `open("configs/default.yaml")` 即可。 --- ## 项目结构 ``` mnist/ ├── main.py # 入口:加载数据 + 启动服务 + 启动训练 ├── requirements.txt ├── README.md # 你正在看的 │ ├── configs/ # 每个用例一个 YAML │ ├── default.yaml # MNIST │ ├── cifar10.yaml │ ├── face.yaml # LFW │ └── text_rnn.yaml # Shakespeare │ ├── models/ # ★ 在这里写你的模型 │ ├── base.py # BaseModel 抽象类 │ ├── attention_base.py # AttentionBase + scaled_dot_product_attention + KVCache │ └── __init__.py │ ├── utils/ # 工具模块(不需修改) │ ├── backend.py # CuPy/NumPy/Torch 透明后端 │ ├── data_loader.py # MNIST / CIFAR-10 / LFW / TextDataset │ ├── history.py # TrainingHistory(线程安全) │ ├── trainer.py # 通用训练循环 │ ├── tracer.py # 全局 Tracer / probe │ ├── probe_helpers.py # 标准化 probe 函数(per 模型类型) │ ├── metrics.py # softmax / cross_entropy / relu / accuracy ... │ ├── logger.py # Rich 日志 + 文件 │ ├── server.py # StatusServer (HTTP/SSE) │ └── gpu_check.py # GPU 探测 + 基准测试 │ ├── visualizer/ # ★ 可视化(不需修改) │ ├── app.py # 多页 Dash 应用 │ ├── window.py # LiveWindow / SnapshotPlot / JupyterLiveDisplay │ ├── dashboard.py # 旧版单页 Dashboard(保留兼容) │ ├── static_plots.py # 静态 matplotlib 工具 │ └── pages/ │ ├── training.py # /training │ ├── weights.py # /weights │ ├── attention.py # /attention │ ├── gradients.py # /gradients │ ├── sequences.py # /sequences │ ├── features.py # /features │ └── hyperparams.py # 左侧超参 sidebar │ ├── docs/ # 设计与需求文档 ├── data/ # 数据集(自动下载) │ ├── mnist/ # MNIST .gz │ ├── cifar10/ # cifar-10-batches-py │ ├── lfw/ # sklearn fetch │ └── text/shakespeare.txt │ └── logs/ # 训练日志(自动创建) ``` --- ## Dashboard 页面说明 访问 `http://127.0.0.1:8050`,顶部 6 个标签: ### 1. ⚡ Training(默认页) 实时训练指标,5 个 Tab: - **Loss** — 训练 / 验证 Loss 曲线 - **Accuracy** — 训练 / 验证准确率 - **Overfitting** — `val_loss − train_loss` 间隙图(>0 即过拟合) - **Grad Norms** — 各层梯度 L2 范数(从 `tracer` 读 `grad/*` tag) - **Run 对比** — 所有历史 Run 的最终 Loss 柱状图 数据源:`TrainingHistory`(线程安全)。 ### 2. 🔲 Weights 四种权重可视化模式: - **像素热图** — 全连接层权重按矩阵直接画出,CNN 权重展平 - **CNN 滤波器** — 每个滤波器单独显示 - **分布直方图** — 权重分布 + 正态参考线(检测 weight collapse / 爆炸) - **奇异值分解** — 奇异值衰减曲线(判断低秩结构) 数据源:`tracer.list_tags("weight/")`。 ### 3. 🎯 Attention 5 个 Tab: - **Attention Weights** — `(heads, seq_q, seq_k)` 热图,可切 head - **Q / K / V** — 三矩阵像素对比 - **Score 矩阵** — `Q·Kᵀ / √d_k`(可视化 softmax 之前) - **Entropy / Head** — 每个 head 的注意力熵(判断是否坍缩到单一 token) - **KV-Cache** — PagedAttention 块占用率 数据源:`tracer` 中以 `attn/*` 开头的 tag。 ### 4. ∇ Gradients - **Norm 趋势** — 步级 L2 范数,含消失(`<1e-5`)和爆炸(`>10`)自动告警 - **分布直方图** — 最新一步每层梯度分布 - **热图** — 单层梯度热图 - **梯度/权重比** — `‖grad‖/‖weight‖`(理想 ≈ `1e-3`;过大 → 训练发散;过小 → 学习停滞) 数据源:`tracer.list_tags("grad/")` + 同名 `weight/*`。 ### 5. 〰 Sequences(RNN / LSTM) - **Hidden State h** — `(T, hidden_dim)` 热图 - **Cell State c** — LSTM 细胞状态 - **LSTM Gates** — 4 个门(i/f/g/o)分别热图 - **Norm / Step** — `‖h‖`、`‖c‖` 随时间变化 - **Gradient Through Time** — `grad/_Wh` 等 数据源:`tracer` 中 `rnn//h_seq`、`c_seq`、`gates` 等。 ### 6. 🔍 Features(CNN / 嵌入) - **Feature Maps** — CNN 每层输出可视化(最多 64 通道) - **Embedding 2D** — t-SNE / PCA 降维散点图,按类别着色 - **距离矩阵** — 前 200 个样本的余弦距离矩阵 - **Triplet 空间** — 人脸识别专用,anchor/positive/negative 三元组连线 数据源:`tracer` 中 `cnn//fmap`、`embed/*`、`face/triplet/*`。 ### 左侧超参 Sidebar 所有页面共享的左侧栏,6 个滑杆: | 参数 | 类型 | 范围 | 默认 | |------|------|------|------| | Learning Rate | log | 1e-5 ~ 1 | 1e-2 | | Batch Size | int | 8 ~ 512 | 32 | | Epochs | int | 1 ~ 300 | 50 | | Momentum | float | 0 ~ 0.99 | 0.9 | | Weight Decay | log | 1e-6 ~ 1e-1 | 1e-4 | | Dropout | float | 0 ~ 0.9 | 0.0 | **▶ Apply & Retrain** 按钮:以新超参重启训练(自动记为新 run)。 **⏹ Stop** 按钮:温和停止当前训练。 --- ## 如何写一个模型 ### 最小模板 ```python # models/linear.py import numpy as np from models.base import BaseModel from utils.metrics import cross_entropy_loss, cross_entropy_grad, accuracy from utils.tracer import tick, probe from utils.backend import np as xp # 自动选 GPU/CPU class LinearClassifier(BaseModel): def __init__(self, input_dim: int, num_classes: int): rng = xp.random.default_rng(42) self.W = rng.standard_normal((input_dim, num_classes)).astype(xp.float32) * 0.01 self.b = xp.zeros(num_classes, dtype=xp.float32) def forward(self, X): return X @ self.W + self.b def backward(self, X, y, learning_rate): tick() # 每步 step +1 logits = self.forward(X) loss = cross_entropy_loss(logits, y) dL = cross_entropy_grad(logits, y) # (N, C) dW = X.T @ dL db = dL.sum(axis=0) self.W -= learning_rate * dW self.b -= learning_rate * db # ── 关键:把想看的张量注入 tracer ── probe("weight/W", self.W) probe("grad/W", dW) return {"loss": float(loss), "grad_norm": float(np.linalg.norm(dW))} ``` 在 `main.py` 中替换: ```python from models.linear import LinearClassifier model = LinearClassifier(ds["input_dim"], ds["num_classes"]) ``` 搞定。Dashboard 立即可看。 ### 用 probe_helpers 简化(推荐) ```python from utils.probe_helpers import probe_linear, probe_activation def backward(self, X, y, lr): tick() h1 = relu(X @ self.W1 + self.b1) # 隐藏层 logits = h1 @ self.W2 + self.b2 loss = cross_entropy_loss(logits, y) # 反向求梯度 ... dW1, db1, dW2, db2 = ... self.W1 -= lr * dW1 # ... # ── 一行完成:权重 + 梯度 + 激活值 ── probe_linear("layer1", self.W1, self.b1, dW1, db1) probe_linear("layer2", self.W2, self.b2, dW2, db2) probe_activation("relu1", h1) return {"loss": float(loss)} ``` ### 不同模型类型的 probe 模式 | 模型 | 用 | Tag 前缀 | |------|------|---------| | 全连接 | `probe_linear(name, W, b, dW, db)` | `weight/`, `grad/` | | 激活 | `probe_activation(name, act)` | `act/` | | 卷积 | `probe_conv(name, W, fmap, dW)` | `weight/`, `cnn//fmap`, `grad/` | | BatchNorm | `probe_bn(name, γ, β, mean, var)` | `weight/`, `act/` | | RNN 单步 | `probe_rnn_step(name, h, Wh, Wx, dWh, dWx, step)` | `rnn//h`, `weight/`, `grad/` | | RNN 整序列 | `probe_rnn_sequence(name, h_seq)` | `rnn//h_seq` | | LSTM 单步 | `probe_lstm_step(name, h, c, gates, step)` | `rnn//{h,c,gates}` | | LSTM 整序列 | `probe_lstm_sequence(name, h_seq, c_seq)` | `rnn//{h,c}_seq` | | Attention | `probe_attention(prefix, Q, K, V, weights, context)` | `attn//{Q,K,V,weights,context}` | | 嵌入 | `probe_embeddings(name, embeddings, labels)` | `embed/`, `embed/_labels` | | Triplet | `probe_triplet(anchor, positive, negative, name)` | `face/triplet/{anchor,positive,negative}` | Dashboard 自动按前缀路由到对应页面:**`weight/*` → Weights,`grad/*` → Gradients,`rnn/*` → Sequences,`attn/*` → Attention,`cnn/*` / `embed/*` / `face/*` → Features**。 ### MLP 完整示例 ```python class MLP(BaseModel): def __init__(self, in_dim, hidden, out_dim, dropout=0.0): rng = np.random.default_rng(0) self.W1 = rng.normal(0, 0.1, (in_dim, hidden)).astype(np.float32) self.b1 = np.zeros(hidden, dtype=np.float32) self.W2 = rng.normal(0, 0.1, (hidden, out_dim)).astype(np.float32) self.b2 = np.zeros(out_dim, dtype=np.float32) self.dropout = dropout def forward(self, X): return np.maximum(0, X @ self.W1 + self.b1) @ self.W2 + self.b2 def backward(self, X, y, lr): from utils.metrics import cross_entropy_loss, cross_entropy_grad, softmax from utils.tracer import tick, probe tick() z1 = X @ self.W1 + self.b1 h1 = np.maximum(0, z1) if self.dropout > 0 and self.training: mask = (np.random.rand(*h1.shape) > self.dropout).astype(np.float32) h1 *= mask logits = h1 @ self.W2 + self.b2 loss = cross_entropy_loss(logits, y) dL = cross_entropy_grad(logits, y) # (N, C) dW2 = h1.T @ dL db2 = dL.sum(0) dh1 = dL @ self.W2.T dz1 = dh1 * (z1 > 0) dW1 = X.T @ dz1 db1 = dz1.sum(0) self.W1 -= lr * dW1; self.b1 -= lr * db1 self.W2 -= lr * dW2; self.b2 -= lr * db2 probe("weight/W1", self.W1); probe("weight/W2", self.W2) probe("grad/W1", dW1); probe("grad/W2", dW2) probe("act/relu1", h1) return {"loss": float(loss)} ``` ### CNN 示例 ```python class SimpleCNN(BaseModel): def __init__(self): # 简化:1 个 conv + 1 个 FC rng = np.random.default_rng(0) self.Wc = rng.normal(0, 0.1, (16, 1, 3, 3)).astype(np.float32) # 16 个 3x3 filter self.bc = np.zeros(16, dtype=np.float32) self.Wf = rng.normal(0, 0.1, (16 * 26 * 26, 10)).astype(np.float32) self.bf = np.zeros(10, dtype=np.float32) from utils.probe_helpers import probe_conv self._probe_conv = probe_conv def forward(self, X): # X: (N, 1, 28, 28) N = X.shape[0] # 简单 im2col-free 实现 out = np.zeros((N, 16, 26, 26), dtype=np.float32) for i in range(26): for j in range(26): patch = X[:, :, i:i+3, j:j+3] # (N, 1, 3, 3) out[:, :, i, j] = (patch[:, None] * self.Wc).sum(axis=(2, 3, 4)) + self.bc h = np.maximum(0, out).reshape(N, -1) return h @ self.Wf + self.bf def backward(self, X, y, lr): from utils.tracer import tick, probe tick() # ... 反向实现 ... self._probe_conv("conv1", self.Wc, feature_map=out, dW=dWc) probe("weight/fc", self.Wf); probe("grad/fc", dWf) return {"loss": float(loss)} ``` ### LSTM 示例 ```python class LSTMClassifier(BaseModel): def __init__(self, vocab_size, embed_dim, hidden, out_dim): # Embedding + LSTM cell + output # ... (略) ... from utils.probe_helpers import probe_lstm_sequence self._probe_lstm = probe_lstm_sequence def forward(self, X_seq): # X_seq: (N, T) 整数 token # ... 逐时间步 LSTM ... return logits def backward(self, X, y, lr): from utils.tracer import tick tick() h_seq, c_seq = [], [] # ... BPTT ... # 整序列算完后一次性 probe self._probe_lstm("lstm0", np.stack(h_seq), np.stack(c_seq)) return {"loss": float(loss)} ``` ### Attention 示例 ```python from models.attention_base import AttentionBase from utils.probe_helpers import probe_attention class MyAttention(AttentionBase): def __init__(self, d_model, num_heads): super().__init__(d_model, num_heads, trace_prefix="layer0/attn") rng = np.random.default_rng(0) self.Wq = rng.normal(0, 0.1, (d_model, d_model)).astype(np.float32) self.Wk = rng.normal(0, 0.1, (d_model, d_model)).astype(np.float32) self.Wv = rng.normal(0, 0.1, (d_model, d_model)).astype(np.float32) self.Wo = rng.normal(0, 0.1, (d_model, d_model)).astype(np.float32) def _project_qkv(self, X): Q = (X @ self.Wq).reshape(*X.shape[:-1], self.num_heads, self.d_head).transpose(0, 2, 1, 3) K = (X @ self.Wk).reshape(*X.shape[:-1], self.num_heads, self.d_head).transpose(0, 2, 1, 3) V = (X @ self.Wv).reshape(*X.shape[:-1], self.num_heads, self.d_head).transpose(0, 2, 1, 3) return Q, K, V def _out_proj(self, context): # context: (N, heads, seq, d_head) → (N, seq, d_model) N, H, T, D = context.shape x = context.transpose(0, 2, 1, 3).reshape(N, T, H * D) return x @ self.Wo def backward(self, X, y, lr): from utils.tracer import tick tick() # forward 已经被父类实现并自动 probe 了 Q/K/V/weights/context logits = self.forward(X) # ... 计算 loss 和梯度 ... return {"loss": float(loss)} ``` `AttentionBase.forward` 会自动调用 `tracer.probe`,无需手动。 --- ## 如何训练并接入 Dashboard 最小可运行版本(替换 `main.py` 的模型段): ```python # main.py import sys, os sys.path.insert(0, os.path.dirname(__file__)) import yaml import numpy as np from utils.backend import backend_info from utils.data_loader import load_mnist, train_val_split from utils.history import TrainingHistory from utils.logger import get_logger, EpochLogger from utils.server import StatusServer from utils.tracer import get_tracer, reset_tracer from utils.trainer import Trainer from models.linear import LinearClassifier # ← 你的模型 from visualizer.app import build_app log = get_logger("main") cfg = yaml.safe_load(open("configs/default.yaml")) hp = cfg["hyperparams"] info = backend_info() log.info(f"后端: {info['backend']} | {info.get('device','')}") X_train, y_train, X_test, y_test = load_mnist(flatten=True) X_train, y_train, X_val, y_val = train_val_split(X_train, y_train, val_ratio=0.1) reset_tracer() tracer = get_tracer() history = TrainingHistory() model = LinearClassifier(784, 10) trainer = Trainer(model, history, hp) trainer.add_epoch_callback(EpochLogger(log, log_every=1)) # 后台 HTTP/SSE StatusServer(history, tracer, host="0.0.0.0", port=8051).start() # 后台训练 trainer.train(X_train, y_train, X_val, y_val, async_mode=True) # 前台 Dashboard def on_retrain(new_cfg): trainer.stop() trainer.update_config(new_cfg) trainer.train(X_train, y_train, X_val, y_val, async_mode=True) app = build_app(history, tracer, hp, on_retrain=on_retrain, on_stop=trainer.stop) app.run(host="127.0.0.1", port=8050, debug=False) ``` `Trainer` 已为你处理: - mini-batch shuffle(每个 epoch) - 验证集前向 + loss/acc 计算 - epoch callback - 后台线程 + 停止信号 - 超参热更新(`update_config`) --- ## Tracer / Probe 体系 ```python from utils.tracer import get_tracer, tick, probe, reset_tracer tracer = get_tracer() # 全局单例 reset_tracer() # 开始新一轮时清空 tick() # 每个 mini-batch 调一次,step +1 probe("weight/W", self.W) # 记录任意 ndarray arr = tracer.get_latest("weight/W") steps, vals = tracer.get_scalar_history("grad/W", reduce="norm") tracer.list_tags("weight/") # 所有以 weight/ 开头的 tag ``` 支持的 `reduce`:`mean` / `norm` / `max` / `std` `probe` 自动转换 PyTorch tensor(`tensor.detach().cpu().numpy()`) 自动丢弃超过 `max_history`(默认 200)的旧数据。 **约定 tag 命名空间:** ``` weight/ 权重矩阵 weight/_bias 偏置 grad/ 梯度 act/ 激活值 attn//{Q,K,V} Attention Q/K/V attn//weights Attention 权重 attn//context Attention 输出 rnn//h RNN 隐藏状态 rnn//c LSTM 细胞状态 rnn//gates LSTM 四门拼接 rnn//h_seq 整序列隐藏状态 cnn//fmap CNN 特征图 embed/ 嵌入向量 face/triplet/{a,p,n} Triplet Loss 三元组 kvcache/snapshot KV-Cache 快照 ``` **禁用 / 启用**(在验证阶段关掉以提速): ```python tracer.disable() # 所有 probe 变为 no-op tracer.enable() ``` --- ## 计算后端(CPU / GPU / 未来 Torch) ```python from utils.backend import np, to_numpy, to_device, is_gpu, backend_info ``` - 启动时自动检测:检测到 CuPy + CUDA → 用 GPU,否则用 NumPy - 启动时控制台会打印 `[backend] 🟢 GPU | cupy | ...` 或 `🔵 CPU | numpy` - 强制 CPU:`FORCE_NUMPY=1 python main.py` - 探测 GPU:`python utils/gpu_check.py`(输出设备信息 + 矩阵乘法基准 + 建议) **关键约定**:模型代码 **必须** 用 `from utils.backend import np as xp`,这样 GPU 加速无需任何额外改动。 数据从磁盘加载后用 `to_device(x)` 搬入 GPU;存盘 / 喂给 tracer 前用 `to_numpy(x)` 搬回 CPU。 未来加 PyTorch 支持:在 `utils/backend.py` 加一个 `set_backend("torch")` 分支即可,模型接口不变。 --- ## 配置文件 YAML 格式,每个用例一个: ```yaml # configs/default.yaml dataset: mnist # mnist | cifar10 | lfw | shakespeare val_ratio: 0.1 seed: 42 hyperparams: learning_rate: 0.01 batch_size: 32 epochs: 50 momentum: 0.9 weight_decay: 0.0001 dropout: 0.0 dashboard: host: "127.0.0.1" # 改成 0.0.0.0 允许外网访问 port: 8050 poll_interval_ms: 800 status_server: host: "0.0.0.0" port: 8051 backend: "" # 留空=自动;或 "numpy" 强制 CPU log_dir: "logs" ``` 预置 4 个:`default.yaml`(MNIST)、`cifar10.yaml`、`face.yaml`(LFW)、`text_rnn.yaml`(Shakespeare)。 --- ## 远程访问 / HTTP API `StatusServer` 与 Dashboard 独立运行(端口 8051 / 8050),可任一组合。 ### 浏览器 ``` # 训练机本机 http://127.0.0.1:8050 # 同网段另一台机器 http://<训练机IP>:8050 # 前提:configs/default.yaml 里 dashboard.host=0.0.0.0 ``` ### 端点 | 路径 | 说明 | |------|------| | `GET /status` | 当前 step/epoch/loss/acc(JSON) | | `GET /stream` | **SSE 长连接**,每 500ms 推一次 | | `GET /history` | 当前 run 全部 epoch 数据 | | `GET /runs` | 所有 run 摘要 | | `GET /tracer/tags` | tracer 中所有 tag | | `GET /tracer/` | 最新 ndarray(base64 编码 + shape/stats) | ### Python 客户端示例 ```python import requests, base64, io, numpy as np # 拉取训练状态 r = requests.get("http://:8051/status") print(r.json()) # → {'status': 'training', 'step': 234, 'epoch': 4, 'train_loss': 0.18, ...} # 拉取 Attention Q 矩阵 r = requests.get("http://:8051/tracer/attn/layer0/Q") payload = r.json() Q = np.load(io.BytesIO(base64.b64decode(payload["data_b64"]))) print(Q.shape, Q.mean(), Q.std()) ``` ### SSE 订阅 ```python from utils.server import stream_status stream_status("http://:8051/stream") # [TRAINING] ep=4 train_loss=0.1812 val_loss=0.2205 # [TRAINING] ep=5 train_loss=0.1644 val_loss=0.2118 # ... ``` --- ## 本地 Python 窗口 / Jupyter 不需要 Web,matplotlib 桌面窗口直接看: ```python from visualizer.window import LiveWindow, SnapshotPlot, JupyterLiveDisplay # ── 方式 1:后台线程 LiveWindow ── win = LiveWindow(history, tracer, refresh_sec=1.0, mode="full") win.start() # 不阻塞 trainer.train(...) win.stop() # ── 方式 2:训练结束后一次性静态图 ── SnapshotPlot(history, tracer).show(save_path="report.png") # ── 方式 3:Jupyter inline ── display = JupyterLiveDisplay(history, tracer) display.show() trainer.add_epoch_callback(lambda ep, m: display.update()) ``` `mode` 可选:`training`(3 列)/ `weights`(2 列)/ `sequences`(2 列)/ `full`(2×3 网格) --- ## 预置数据集与预置用例 | 数据集 | 加载函数 | 规模 | 用途 | |--------|----------|------|------| | MNIST | `load_mnist(flatten=True/False)` | 60K + 10K | 入门分类 | | CIFAR-10 | `load_cifar10(flatten=True/False)` | 50K + 10K | 图像分类 | | LFW | `load_lfw(min_faces=20)` | ~3K 人 | 人脸识别 / Triplet | | Shakespeare | `TextDataset.from_shakespeare()` | 1MB 字符 | 字符级 RNN/LSTM | 数据首次运行自动下载到 `data/` 目录。 --- ## 常见问题 **Q:Dashboard 打不开,浏览器连接被拒?** A:默认绑定 `127.0.0.1` 仅本机。改 `configs/.yaml` 中 `dashboard.host: 0.0.0.0` 并重启。`StatusServer` 默认就是 `0.0.0.0`。 **Q:训练时浏览器页面不刷新?** A:检查 `poll_interval_ms`(默认 800ms);如果是公司网络,浏览器可能需要勾选"忽略证书"或允许 mixed content。 **Q:怎么判断 GPU 加速生效了?** A:启动时控制台打印 `[backend] 🟢 GPU | cupy | <设备名>`。或跑 `python utils/gpu_check.py`。 **Q:可视化页面都是空的?** A:模型没调用 `probe` / `tick`。看 `docs/spec.md` 的 tag 约定。每个 mini-batch 至少调一次 `tick()` + 想看的张量调 `probe()`。 **Q:多个 run 之间数据会冲突吗?** A:不会。`TrainingHistory` 保留所有 run 的 list;tracer 在 `main.py` 入口调 `reset_tracer()` 清空。 **Q:想保存模型参数?** A:在 `BaseModel` 子类里实现 `get_params()` / `set_params()`,框架已留好接口。 **Q:想接 PyTorch?** A:把所有 `import numpy as np` 换成 `from utils.backend import np`(自动选 CuPy),未来要换 torch 时改 `utils/backend.py` 一处即可。`probe` 已自动处理 `torch.Tensor` → `numpy.ndarray`。 **Q:能在 Colab / Jupyter 里跑吗?** A:可以。`JupyterLiveDisplay` 给 inline 实时;Dashboard 也支持 `app.run(mode="inline")`(需 `jupyter_dash`)。 --- ## 接下来:实现你的第一个模型 ```bash $ touch models/linear.py # 粘贴上面的 LinearClassifier 模板 # 编辑 main.py: from models.linear import LinearClassifier $ python main.py # 浏览器 → 看到 W1 热图、loss 下降、grad norm 趋势 ``` Happy hacking. 🚀