Skip to content
ccbda3Public

About

零依赖的网页版训练监控:全量神经元图 / 特征图 / 梯度分布 / 损失曲面,训练脚本加几行即可接入

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

aimonitor

除 numpy 外零第三方依赖的网页版训练监控。训练脚本里加几行代码,就能在浏览器里看到神经网络的真实内部状态:每个神经元、每条连线、每张特征图、每个梯度分布。

python deps license

设计约定:页面上出现的每一个数字都来自真实运行时的张量,没有演示数据、没有占位图、没有抽样降采样。显示不出来的一律标注「无数据」,不做假。

训练概览

和 TensorBoard 有什么不同

aimonitor TensorBoard
依赖 只需 numpy TensorFlow / protobuf 等一大串
启动方式 训练进程内直接起 HTTP 服务,不用另开进程 训练写 event 文件,另起进程读
神经元级视图 全部神经元逐个显示 + 真实权重连线,3D 可旋转 无
特征图 每个卷积层的全部通道拼成一张图 需要自己写 image summary
梯度分布 每个张量的完整分布 需要自己写 histogram summary
损失曲面 内置,沿两个随机方向实测采样并叠加优化器轨迹 无
异常告警 内置 alert / watchdog / NaN 检测 无
前端 纯静态文件,改完刷新即生效 打包好的,改不动
生态 小 大,有大量插件与社区

如果你重度依赖 TF/Keras 生态、或者需要多人协作的实验管理,TensorBoard 更合适;如果你想要「开箱即见网络内部」且不想装一堆依赖,这个更顺手。

界面预览

下面是 examples/classify_demo.py 实际运行时抓下来的完整面板,全部为真实数据,不是示意图:

完整面板

图中依次是:顶部导航与启停按钮、实时指标卡片、训练曲线、训练健康度、真实图片/特征图、每类指标表与逐层权重统计、混淆矩阵与相关系数矩阵、嵌入散点投影、逐层梯度范数条图、实时日志流。

特性

模块 内容
实时指标 任意标量自动成卡片 + 折线图,历史存在服务端,刷新页面不丢
训练健康度 梯度范数、权重范数、梯度最大绝对值、学习率、训练/采样比、参数量
神经网络全量图 全部神经元逐个显示(512 个 FC 神经元就是 512 个),连线为真实权重矩阵,3D 可旋转 / 2D 平面 / 悬停查单个神经元
特征图热力图 每个卷积层的全部通道拼成一张图,原像素放大不插值
真实图片 网络输入输出按原始像素显示
权重/梯度直方图 每个张量的完整分布(800 万个数也是全量统计),用于判断梯度消失/爆炸
损失曲面 3D 在参数空间沿两个固定随机方向扰动采样,高度是实测损失,叠加真实优化器轨迹
指标明细表 任意表格:每类精确率/召回率/F1、top-k 预测、数据集统计,全量数值不四舍五入
矩阵热力图 混淆矩阵、注意力权重、相关性矩阵;颜色按真实值映射,悬停读单格
散点投影 嵌入降维(PCA / t-SNE)、预测-真值分布、PR 曲线,按类别着色 + 图例
对比条图 逐层梯度范数、各类准确率、任意分组对比,自动按绝对值排序
训练日志 实时日志流,attach_logger() 一行把 logging 接进来,自动标红 ERROR / 标黄 WARN
异常告警 alert() 自定义条件 + watchdog() 看门狗 + 指标 NaN/Inf 自动检测,顶部红条分级提示
曲线交互 悬停十字准线读单点数值、1/10/全部时间窗口、平滑、导出 CSV
训练控制 网页上直接「启动训练 / 结束训练并保存」

面板是注册制的:注册了就显示,没注册的区块自动隐藏。所以同一套前端既能当强化学习面板,也能当分类 / NLP / 检测的训练面板。

安装

pip install git+https://github.com/ccbda3/aimonitor

或者克隆后本地安装:

git clone https://github.com/ccbda3/aimonitor
cd aimonitor
pip install .

唯一的运行时依赖是 numpy。

快速开始

pip install numpy torch        # torch 只有示例需要
python examples/classify_demo.py

浏览器打开 http://127.0.0.1:8080。

示例里的数据、模型、张量全部现场真实计算(合成数据现场生成、每个数字现场前向),不是录制的假数据。

接入自己的项目

from aimonitor import Monitor

mon = Monitor(title="我的模型训练监控", port=8080, interval=1.0, history=900)

# 1) 顶部信息条
mon.info(lambda: {"设备": "CUDA", "数据集": "cifar10"})

# 2) 标量指标:自动出卡片 + 曲线
mon.metric("loss", lambda: loss_value, label="Loss", fmt="f4", color="#ff8f5a")
mon.metric("acc",  lambda: acc,        label="准确率", fmt="pct")

# 3) 训练健康度
mon.health("grad_norm", lambda: grad_norm, label="梯度范数", fmt="sci")

# 4) 真实图片(numpy HxW 或 HxWx3,float 会自动归一化)
mon.image("input", lambda: batch_images[0], label="输入样本", cmap="gray", group="图片")

# 5) 特征图拼图(把所有通道铺成一张图)
from aimonitor import montage
mon.image("conv1", lambda: montage(list(featmaps[0]), cols=8),
          label="conv1 全部通道", cmap="viridis", group="特征图")

# 6) 权重/梯度直方图
for name, p in model.named_parameters():
    mon.histogram(name, (lambda n: (lambda: dict(model.named_parameters())[n].detach().cpu().numpy()))(name),
                  label=name + " 权重", bins=48)

# 7) 神经元全量图 + 精确连线矩阵
mon.graph(lambda: {
    "layers": [
        {"name": "输入", "detail": "784", "nodeCount": 784, "params": 0,
         "realCount": 784, "nodes": x.flatten().tolist()},
        {"name": "隐层", "detail": "256", "nodeCount": 256, "params": 200960,
         "realCount": 256, "nodes": h.tolist()},
        {"name": "输出", "detail": "10", "nodeCount": 10, "params": 2570,
         "realCount": 10, "nodes": out.tolist()},
    ],
    # edges[i][t][s] = 第 i 段里 目标神经元 t 与 源神经元 s 的真实权重
    "edges": [abs(w1.numpy()), abs(w2.numpy())],
})

# 8) 任意 3D 曲面(可选)
mon.surface(lambda: {"ready": 1, "res": 15, "ext": 0.5, "zmin": 0.1, "zmax": 0.4,
                     "values": grid_flat.tolist(), "path": [{"a": 0, "b": 0, "z": 0.2}]})

# 9) 参数表(可选)
mon.params(lambda: [{"title": "结构", "layers": [...], "rows": [["设备", "CUDA"]]}])

# 10) 网页上的启停按钮(可选)
mon.control(start=start_training, stop=stop_training)

# 11) 表格:每类精确率/召回率/F1
mon.table("report", lambda: [["cat", 0.91, 120], ["dog", 0.88, 96]],
          label="每类指标", columns=["类别", "精确率", "样本数"])

# 12) 矩阵热力图:混淆矩阵 / 注意力 / 相关性
mon.matrix("confusion", lambda: confusion_matrix(y_true, y_pred, labels=range(10)),
           label="混淆矩阵(行=真实 列=预测)", xlabels=class_names, ylabels=class_names, fmt="int")

# 13) 散点:嵌入投影(点 = [x, y, 类别],类别可以是字符串)
mon.scatter("emb", lambda: [[float(p[0]), float(p[1]), CLASSES[int(y[i])]]
                            for i, p in enumerate(project(features))],
            label="倒数第二层特征 PCA 投影", xlabel="主成分 1", ylabel="主成分 2")

# 14) 对比条图:逐层梯度范数(判断梯度消失/爆炸)
mon.bars("grad_norm", lambda: [[n, float(p.grad.norm())] for n, p in model.named_parameters()],
         label="逐层梯度范数")

# 15) 日志流:一行接管 logging
buf = mon.attach_logger(logger, key="train_log", label="训练日志", limit=400)
buf.write("这一行会出现在网页上")

# 16) 异常告警:fn() 返回字符串就弹红条,返回 None 表示正常
mon.alert("grad", lambda: ("范数 %.1f,疑似爆炸" % g) if g > 100 else None, label="梯度", level="error")

# 17) 看门狗:这个数字超过 timeout 秒没变化就报警
mon.watchdog("progress", counter.get, label="总步数", timeout=30.0)

port = mon.start()
print("监控面板 http://127.0.0.1:%d" % port)

面板交互

操作 效果
悬停曲线 十字准线 + 显示该采样点的时间与精确数值
曲线右上按钮 切换 1 分钟 / 10 分钟 / 全部时间窗口
平滑 对曲线做指数滑动平均,看趋势用
导出 CSV 把所有曲线导成宽表 CSV(UTF-8 BOM,Excel 直接打开)
点击区块标题 折叠 / 展开区块,状态记在浏览器里
顶部导航 按「训练概览 / 模型内部 / 诊断分析 / 配置」分组跳转
顶部告警条 有告警才出现,红色 = 严重,黄色 = 警告

异常告警

告警由服务端每秒评估一次,前端只在有告警时显示顶部红条,不需要人工盯着。

来源 触发条件
watchdog 监控的数字在 timeout 秒内没有变化(训练线程 / 采集线程卡死、进程僵死)。训练停止时自动不报警
alert 自定义条件,fn() 返回非空字符串即告警
指标非法值 任何 metric 从有限值变成 NaN / Inf 时自动告警一次,恢复正常后自动解除

SDK API

方法 说明
Monitor(title, host, port, interval, history) interval 采样间隔秒;history 服务端保留的采样点数;默认 127.0.0.1:80
info(fn) 顶部信息条,fn() 返回 {键: 值}
metric(key, fn, label, color, fmt, chart) 标量指标;chart=False 只出卡片不出曲线
health(key, fn, label, unit, fmt) 健康度卡片
image(key, fn, label, cmap, scale, group) 真实图片;cmap 为空按灰度 / 原色,group 决定归到「特征图」还是「图片」
histogram(key, fn, label, bins, refresh) 直方图;refresh 是重算间隔秒(大张量建议 ≥5)
table(key, fn, label, columns) 表格;返回行列表 [[...], ...] 或 {"columns": [...], "rows": [...]},行不足自动补空
matrix(key, fn, label, cmap, xlabels, ylabels, fmt, refresh) 二维矩阵热力图;fn() 返回 2D 数组(numpy / list 均可),含负数时自动用发散色图
scatter(key, fn, label, xlabel, ylabel) 散点图;返回 [[x, y, 类别], ...] 或 {"groups": [...], "points": [...]},上限 4 万点
bars(key, fn, label) 条图;返回 [[标签, 数值], ...] / [数值, ...] / [{"label": .., "value": ..}, ...]
log(key, fn, label, limit) 日志流;fn() 返回字符串列表或 LogBuffer.get()
attach_logger(logger, key, label, level, limit) 把某个 logging.Logger 的输出实时镜像到网页,返回可继续 write() 的 LogBuffer
alert(key, fn, label, level) 异常告警;fn() 返回字符串即告警,返回 None / False 表示正常;level 取 "warn" / "error"
watchdog(key, fn, label, timeout, level) 看门狗;fn() 返回的数字在 timeout 秒内没有变化就告警
graph(fn) 返回 {"layers": [...], "edges": [...]};edges[i] 形状必须是 (目标层节点数, 源层节点数)
surface(fn) 损失曲面数据
params(fn) 底部参数表,返回 [{"title": ..., "layers": [...], "rows": [[键, 值]]}]
control(start, stop) 网页启停按钮回调,返回 True 表示状态真的变了
set_training(bool) / is_training() 手动同步 / 查询训练状态(用 control 时自动维护)
start() 启动服务,返回实际端口(端口被占用会自动顺延)
stop() 关闭服务

fmt 可选:auto / int / f1~f6 / pct / sci。

辅助函数:

函数 说明
montage(maps, cols, pad) 把多张特征图铺成一张
normalize(a, lo, hi) 归一化到 0~255 uint8,返回 (数组, lo, hi)
colormap(u8, name) 给 uint8 图上色,内置 viridis / magma / blue / red / diverging / gray
encode_png(arr) 无依赖编码 PNG 字节
to_png_data_uri(arr, cmap) 直接转成 data:image/png;base64, 字符串
LogBuffer(capacity) 环形日志缓冲,支持 write() / get() / clear()

约定:key 相同的面板会被视为同一个;所有 fn 都在采样线程里调用,异常会被吞掉(页面保持上一帧),不影响训练。

数据协议

前端是纯静态文件,只依赖下面几个接口。你也可以自己写前端,只要遵守协议。

接口 频率 说明
GET /api/state 高频(默认 1 秒) 指标、曲线、图片、直方图、表格、矩阵、散点、条图、日志、损失曲面、参数表、当前激活值
GET /api/graph 低频 全量神经元与连线矩阵,仅当 graph.version 变化时才需要请求
GET /api/ping 按需 探活,返回 {"ok": true, "title": ...}
POST /api/control/start 手动 触发 control(start=...) 回调
POST /api/control/stop 手动 触发 control(stop=...) 回调

GET /api/state 返回结构(节选):

{
  "title": "...", "interval": 1.0, "training": true,
  "info": {"设备": "CUDA"},
  "metrics":   [{"key":"loss","label":"Loss","value":0.12,"fmt":"f4","color":"#ff8f5a","chart":true}],
  "series":    {"loss": [[1712345678.1, 0.15], [1712345679.1, 0.12]]},
  "health":    [{"key":"grad_norm","label":"梯度范数","value":0.19,"unit":"","fmt":"sci"}],
  "images":    [{"key":"input","label":"输入","png":"data:image/png;base64,...","w":32,"h":32,"scale":6,"group":"图片","lo":0,"hi":255}],
  "histograms":[{"key":"fc.weight","label":"fc.weight 权重","counts":[...],"edges":[...],"min":-0.1,"max":0.1,"mean":0.0,"std":0.02,"n":8388608}],
  "tables":    [{"key":"report","label":"每类指标","columns":["类别","精确率"],"rows":[["cat",0.91]],"n":1}],
  "matrices":  [{"key":"cm","label":"混淆矩阵","h":10,"w":10,"data":[[205,37],[51,186]],"min":2,"max":208,
                 "xlabels":["cat","dog"],"ylabels":["cat","dog"],"cmap":null,"fmt":"int"}],
  "scatters":  [{"key":"emb","label":"特征投影","xlabel":"主成分 1","ylabel":"主成分 2",
                 "points":[[2.02,-0.36,0]],"groups":["螺旋 A","螺旋 B"],"n":1}],
  "bars":      [{"key":"grad","label":"逐层梯度范数","bars":[{"label":"body.0.weight","value":0.42}],"n":1}],
  "logs":      [{"key":"train_log","label":"训练日志","lines":[{"i":10,"t":1712345678.1,"text":"step 100 loss 0.8"}],"n":1}],
  "surface":   {"ready":1,"res":15,"ext":0.05,"zmin":0.3,"zmax":0.6,"values":[...],"path":[{"a":..,"b":..,"z":..}]},
  "params":    [{"title":"网络结构","layers":[{"name":"FC","detail":"512","params":8389120,"realCount":512}],"rows":[["设备","CUDA"]]}],
  "graph":     {"version": 3790918552, "summary": "680 神经元 / 41088 连线"},
  "acts":      [[...每层节点激活...]]
}

自定义前端

aimonitor/assets/ 下的文件(index.html / style.css / app.js)每次请求都从磁盘读取,改完刷新浏览器即可生效,不用重启训练进程。

这三个文件之间没有构建步骤,直接编辑即可。

目录结构

aimonitor/
├── aimonitor/
│   ├── __init__.py        导出 Monitor / LogBuffer / 图像辅助函数
│   ├── sdk.py             Monitor 类、注册式 API、采样线程、告警与看门狗
│   ├── server.py          HTTP 服务(/api/state、/api/graph、/api/control/*)
│   ├── imaging.py         无依赖 PNG 编码、色图、拼图
│   └── assets/            前端静态文件(改完刷新即生效)
│       ├── index.html
│       ├── style.css
│       └── app.js
├── examples/
│   └── classify_demo.py   图像分类任务接入示例
├── docs/                  README 使用的面板截图
├── pyproject.toml
└── LICENSE

实现说明

  • 零第三方依赖:只用标准库 + numpy。PNG 编码、色图、直方图全部自己实现,不需要 matplotlib / Pillow
  • 前端可热改:assets/ 下文件每次请求都从磁盘读取,改完刷新浏览器即可,不用重启训练进程
  • 对大张量友好:连线矩阵按「模型版本」缓存,只在模型结构变化时传输;直方图有独立刷新间隔;非线性数值一律转成 null 而不是输出非法 JSON 的 NaN
  • 不阻塞训练:所有采样都在独立线程里做,训练主循环不参与
  • 端口自适应:端口被占用时会自动顺延,并在 start() 的返回值里给出实际端口

许可

MIT,详见 LICENSE

About

零依赖的网页版训练监控:全量神经元图 / 特征图 / 梯度分布 / 损失曲面,训练脚本加几行即可接入

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages