把神经网络训练日志(
step/loss/val_loss/accuracy/learning_rate…)当作统计样本, 用假设检验与稳健统计给出可复现、可量化的结论,而不是依赖肉眼看曲线。
TensorBoard 是训练过程可视化的事实标准,但它不是统计推断工具。当结论需要写进实验记录、 论文或工程决策时,可视化留下的问号无法回答:
| 维度 | TensorBoard 能提供 | 其局限(本文档要解决的问题) |
|---|---|---|
| 两组结果对比 | 把两条 loss 曲线叠在同一张图上 | 只能看出「看起来更低」,无法回答差异是否显著。0.03 的差距究竟是超参有效还是随机种子噪声?没有 p 值、没有效应量 |
| 曲线平滑 | 提供 EMA / 滑动平均开关 | 平滑参数由人眼主观选择,掩盖了方差结构;平滑后的「更低」不能反推原始尺度的差异大小 |
| 单点取值 | 可读最后一步或最小 loss | 单点取值忽略波动,选到运气好的 step 就会误判;没有置信区间说明这个数字有多稳定 |
| 多 run 统计 | 支持多 run 叠加显示 | 多组比较不控制 I 类错误(family-wise error rate),也没有方差齐性/正态性前提的检查 |
| 「收敛」判断 | 依赖研究者目视曲线变平 | 「收敛」缺乏量化定义,无法自动判定平台期起点,也无法复核 |
| 报告与审计 | 截图 / 可分享的 dashboard | 截图不可复现:缺少检验名称、样本量 n、统计量、p 值、效应量,审稿人或同事无法重算 |
- 从「看起来」到「差多少、有多确定」:
p = 3.2e-05,Cohen's d = -0.83,95% CI [-0.041, -0.019]比「调参后 loss 更低」信息量大一个数量级,而且可被第三方重算。 - 让方法选择有依据而非习惯:先做正态性检验,再决定用 Welch t 检验还是 Mann-Whitney U, 把「为什么用这个检验」写进流水线而不是留在脑子里。
- 把「异常」和「收敛」变成可量化的判定规则:稳健离群点检测(MAD / Tukey 围栏 / 滑动窗口) 与平台期检测(滑动窗口极差)都能给出具体的 step 编号与容差,从而可纳入自动化早停与告警。
定位说明:NeuroStat-Logger 不替代 TensorBoard 的可视化能力,而是补上「可视化之后的那一步」—— 把日志读数变成带前提检查、效应量与置信区间的统计结论,并可导出为 JSON 报告。
| 能力 | 说明 |
|---|---|
| 多格式日志解析 | CSV / TSV / JSON / JSONL / Python dict 记录列表 / 现成 DataFrame,按后缀自动分派 |
| 容错解析 | JSONL 坏行跳过并记录行号;无法解析的单元格记为 NaN 并汇总为解析告警,不让整份日志失败 |
| 自动规范化 | 列名统一为小写 snake_case;step 强制 int64 并升序;缺失 step 时按行号补全 |
| 多 run 友好 | 同一 step 重复出现默认保留(每个 run 都从 step 0 开始);完全重复行自动去除 |
| 分组列保留 | run_name / seed / config 等文本列保留为分组变量,用于跨 run 差异检验 |
| 能力 | 核心函数 / 方法 |
|---|---|
| 正态性检验 | test_normality() — Shapiro-Wilk / D'Agostino K² / KS,auto 按样本量自动切换 |
| ADF 单位根检验(平稳性) | test_stationarity() — 增广 Dickey-Fuller,判断指标是否存在随机趋势 |
| 效应量计算 | cohens_d()(参数)、秩二列相关(非参数),符号约定统一 |
| 描述性统计 | describe() — 均值 / 标准差 / 变异系数 / 偏度 / 峰度 / 分位数 / 缺失值 |
| 区间估计 | confidence_interval() — 均值的 t 分布置信区间;均值差用 Welch–Satterthwaite 自由度 |
| 组间差异检验 | compare_groups() — Welch t / Mann-Whitney U / 单因素 ANOVA / Kruskal-Wallis |
| 相关性分析 | correlation() — Pearson / Spearman / Kendall,成对删除缺失值 |
| 趋势建模 | trend() — 对 step 的 OLS 线性回归(斜率 + R² + 斜率置信区间) |
| 异常检测 | detect_anomalies() — 稳健 MAD 修正 z 分数 / 经典 z 分数 / Tukey IQR 围栏,支持中心化滑动窗口做局部检测 |
| Plateau(平台期)检测 | detect_plateau() — 滑动窗口极差相对容差,返回平台期起点 step |
| 平滑趋势 | rolling_summary() — 任意窗口的均值 / 标准差 / 中位数 / 极值 |
| 汇总报告 | summary_report() — 描述统计 + 正态性 + 平稳性 + 趋势,一键导出 JSON |
- Streamlit 界面:4 个标签页(数据总览 / 描述性统计 / 推断检验 / 趋势分析),统计失败以告警降级,不会白屏
- 导出:描述性统计表导出 CSV;完整统计报告导出 JSON
- 77 个单元测试:覆盖解析、描述统计、正态性、ADF、效应量、异常检测、趋势、平台期与全部错误路径,固定随机种子、无网络依赖
- Python 3.12+(本项目在 Python 3.12.10 上验证通过)
- 依赖及其用途:
| 依赖 | 最低版本 | 在本项目中的用途 |
|---|---|---|
streamlit |
1.63 | 交互界面与前端渲染 |
pandas |
2.0 | 日志解析、表格运算、滑动窗口统计 |
numpy |
1.26 | 数值计算与稳健统计量 |
scipy |
1.11 | 假设检验、分布函数、分位数 |
statsmodels |
0.14 | OLS 线性趋势回归、ADF 单位根检验 |
plotly |
5.18 | 交互式图表 |
cd NeuroStat-Logger
# 创建虚拟环境(推荐)
py -3.12 -m venv .venv
# 安装依赖
.\.venv\Scripts\python.exe -m pip install -r requirements.txt # Windows (PowerShell)
# source .venv/bin/activate && pip install -r requirements.txt # macOS / Linux.\.venv\Scripts\streamlit.exe run app.py # Windows (PowerShell)
# streamlit run app.py # 已激活虚拟环境的任意平台浏览器访问 http://localhost:8501。未上传文件时会自动加载内置的 200 step 演示日志,
所有统计功能都可直接试用。
.\.venv\Scripts\python.exe -m unittest discover -s tests -t . -v
# 期望输出:Ran 77 tests ... OK当前机器上默认的 python 指向 MSYS2 mingw64 的 Python 3.14
(D:\msys64\mingw64\bin\python.exe,平台标签 mingw_x86_64_msvcrt_gnu,不带 pip),
它无法安装 PyPI 上的 Windows wheel,import pandas 会直接失败。
请统一使用项目内的 .venv,或显式指定 py -3.12。
提示:请把脚本保存在项目根目录(例如
demo.py)后运行 ——from src.stats_engine import ...要求项目根目录位于sys.path;也可以直接在项目根目录用python -c "..."执行。
import numpy as np
from src.stats_engine import StatsEngine
# 1) 构造 120 step 的指数衰减 loss,并在 step 80 注入一次尖峰(模拟数值不稳定)
loss = 2.0 * np.exp(-np.arange(120) / 25.0) + 0.1
loss[80] = 1.5
engine = StatsEngine.from_records([{"step": i, "loss": float(v)} for i, v in enumerate(loss)])
# 2) 描述性统计
print(engine.describe("loss")[["count", "mean", "std", "cv"]].round(4))
# 3) ADF 平稳性检验(H0:存在单位根,即非平稳)
adf = engine.test_stationarity("loss")
print(f"ADF: stat={adf.statistic:.4f} p={adf.p_value:.3e} stationary={adf.extra['is_stationary']}")
# 4) 训练前期 vs 后期的差异检验 + 效应量
diff = engine.compare_groups("loss")
print(f"diff: {diff.test_name} p={diff.p_value:.3e} d={diff.effect_size:.3f}")
# 5) 异常点检测:全局 MAD 会被衰减趋势放大,改用 21 步滑动窗口
global_anomalies = engine.detect_anomalies("loss", method="mad")
window_anomalies = engine.detect_anomalies("loss", method="mad", window=21)
print(f"anomalies(global): {int(global_anomalies['is_anomaly'].sum())} / {len(global_anomalies)}")
print(f"anomalies(window=21): {window_anomalies.loc[window_anomalies['is_anomaly'], 'step'].tolist()}")
# 6) 平台期检测
print("plateau:", engine.detect_plateau("loss", rel_tol=0.01, patience=5))实测输出(逐字复制,Python 3.12.10 / pandas 3.0.5 / scipy 1.18.1 / statsmodels 0.15.0):
count mean std cv
loss 120 0.5325 0.5147 0.9665
ADF: stat=-4.6971 p=8.520e-05 stationary=True
diff: mann_whitney_u p=4.423e-20 d=-0.972
anomalies(global): 18 / 120
anomalies(window=21): [80]
plateau: None
结果解读
describe:120 个观测,均值 0.5325,变异系数 CV = 0.9665,说明指标波动相对其水平很大。ADF:p = 8.5e-05 < 0.05,拒绝「存在单位根」的原假设 → 序列平稳(不含随机趋势)。 注意这不代表 loss 不再下降:ADF 检验的是随机趋势,确定性衰减要靠第 4~6 步的斜率与平台期回答。diff:p = 4.4e-20,前后期差异极显著;d = -0.972为负,表示后期 loss 整体低于前期(接近大效应量)。anomalies:全局 MAD 把整段衰减当成尺度,只把 18 个点标为异常(绝大多数是趋势造成的误报); 改为 21 步滑动窗口后,只剩[80]—— 正是人工注入的尖峰。这正是「局部异常检测」的价值。plateau:None,因为该曲线到第 120 步仍以超过 1% / 5 steps 的速度下降,尚未进入平台期。
| 格式 | 扩展名 | 说明 |
|---|---|---|
| CSV / TSV | .csv / .tsv |
表头即列名;.tsv 自动使用制表符分隔 |
| JSON Lines | .jsonl / .ndjson |
每行一个 JSON 对象;坏行会跳过并记录行号,不会整份失败 |
| JSON | .json |
顶层数组,或 {"logs": [...]} / {"records": [...]} / {"data": [...]} / {"history": [...]} 包装结构 |
| 内存对象 | — | StatsEngine.from_records(records)(dict 列表/生成器)、StatsEngine(frame)(现成 DataFrame) |
界面侧支持在侧边栏直接上传 csv / tsv / json / jsonl 文件。
CSV
step,loss,val_loss,accuracy,run_name
0,2.10,2.15,0.310,baseline
1,1.98,2.05,0.352,baseline
2,1.87,1.97,0.391,tunedJSONL
{"step": 0, "loss": 2.10, "val_loss": 2.15, "run_name": "baseline"}
{"step": 1, "loss": 1.98, "val_loss": 2.05, "run_name": "baseline"}JSON
{"logs": [{"step": 0, "loss": 2.10, "val_loss": 2.15}, {"step": 1, "loss": 1.98, "val_loss": 2.05}]}| 列 | 是否必需 | 约定与行为 |
|---|---|---|
step |
否 | 训练步。缺失时按行号自动生成 0..n-1 并写入解析告警;存在时强制为 int64、按升序排序 |
loss、val_loss、accuracy、val_accuracy、learning_rate |
否 | DEFAULT_METRICS 中列出的常见指标名,默认参与分析;任何 数值列都会被自动识别为可分析指标 |
任意文本列(run_name、seed、config …) |
否 | 不会被当作指标,而是保留为分组列,供 compare_groups(metric, group_column) 做跨 run 检验 |
score / is_anomaly / direction |
— | 这三个名字由 detect_anomalies() 输出,请不要与输入列重名 |
列名不敏感:"Train Loss"、"train-loss"、"TRAIN_LOSS" 均规范化为 train_loss;
"Val Loss" → val_loss,因此大部分框架(PyTorch Lightning、Keras History、HuggingFace Trainer)
导出的 CSV 无需手工改名。
-
列名:统一为小写
snake_case(空格 / 连字符 → 下划线)。 -
类型:数值列尽力转为数值,无法解析的单元格记为
NaN;若文本列全部无法解析为数值,则原样保留(例如run_name)。 -
重复 step 默认保留:多 run 日志中每个 run 都从
step = 0开始,按 step 去重会静默丢掉除最后一个 run 外的全部数据。 需要「同一 step 只留最后一条」时显式声明:parser = TrainingLogParser(dedupe_by_step=True)
-
完全重复的行(所有列取值相同)会被自动去除。
-
列名冲突:规范化后若出现重复列名(同时存在
"Train Loss"与"train_loss"),直接抛ValueError,避免静默取错列。 -
解析告警:坏行、缺失
step、非法数值等非致命问题都记录在engine.parse_warnings中,并在界面「数据总览」页展示。
from src.stats_engine import (
StatsEngine, TrainingLogParser, test_stationarity, cohens_d, compare_groups, fit_trend,
)
engine = StatsEngine.from_file("logs/run.csv") # 按后缀分派 .csv/.tsv/.json/.jsonl
engine = StatsEngine.from_records(records) # dict 列表或生成器
engine = StatsEngine(frame) # 现成 DataFrame| 方法 | 返回 | 说明 |
|---|---|---|
metrics (property) |
List[str] |
所有可分析的数值列(不含 step) |
parse_warnings (property) |
List[str] |
解析阶段的非致命告警 |
steps() |
np.ndarray |
步数列(float 数组) |
values(metric, dropna=True) |
np.ndarray |
取出某指标的一维样本 |
describe(metrics=None, percentiles=(0.05,0.25,0.5,0.75,0.95)) |
DataFrame |
描述性统计表(接受单个名字或名字列表) |
rolling_summary(metric, window=10, aggregations=("mean","std")) |
DataFrame |
滑动窗口统计(mean/std/min/max/median) |
test_normality(metric, method="auto") |
NormalityReport |
正态性检验,auto/shapiro/dagostino/ks |
test_stationarity(metric, regression="c", autolag="AIC", alpha=0.05) |
StatResult |
ADF 单位根检验;regression 可选 c/ct/n |
confidence_interval(metric, confidence=0.95) |
(low, high) |
均值的 t 分布置信区间 |
compare_groups(metric, group_column=None, parametric=None, alpha=0.05) |
StatResult |
组间差异检验;group_column=None 时按 step 中位数切 early / late |
correlation(metric_x, metric_y, method="pearson") |
StatResult |
pearson/spearman/kendall,成对删除缺失 |
trend(metric, alpha=0.05) |
StatResult |
对 step 做 OLS;statistic 即斜率 |
detect_anomalies(metric, method="mad", threshold=None, window=None) |
DataFrame |
异常点检测;window 给值时做局部检测 |
detect_plateau(metric, rel_tol=0.01, patience=5) |
int | None |
平台期检测,返回平台期起始 step |
summary_report(metrics=None, alpha=0.05) |
dict |
描述统计 + 正态性 + 平稳性 + 趋势,可直接 JSON 导出 |
StatResult(
test_name="welch_t_test", # 检验名称
statistic=3.42, # 检验统计量(趋势分析中为斜率)
p_value=6.1e-05, # 双尾 p 值
effect_size=-0.83, # Cohen's d 或秩二列相关(不适用时为 None)
confidence_interval=(-0.51, -0.22), # 均值差 / 斜率置信区间
extra={"metric": "loss", ...}, # 组标签、组样本量、R²、滞后阶数、临界值等
)
result.is_significant(alpha=0.05) # -> bool
result.as_dict(alpha=0.05) # -> 扁平 dict(含 significant / ci_low / ci_high),便于 JSON 导出
NormalityReport(metric, test_name, statistic, p_value, sample_size) # .is_normal / .as_dict()| 异常 | 触发场景 |
|---|---|
KeyError |
引用了不存在的列(错误信息会列出可用列) |
TypeError |
目标列不是数值列,无法参与检验 |
ValueError |
样本量不足(正态性 < 3、ADF < 8、置信区间 < 2、趋势 < 3、异常检测 < 3) |
ValueError |
分组数 < 2、window < 2(滚动统计)/ window < 3(异常检测)、threshold <= 0 |
ValueError |
非法的 method / confidence / regression;常量序列做 ADF;规范化后列名重复;空日志 |
符号约定:
α为显著性水平(默认 0.05);所有检验均为双尾;两组比较中a为标签升序在前的组。 判定规则统一为:p < α时拒绝H0。
| 方法 | 目的 | Python 实现(库函数) | 原假设 H0 |
备择假设 H1 |
拒绝 H0 的含义 |
|---|---|---|---|---|---|
| Shapiro-Wilk | 正态性检验 | scipy.stats.shapiro |
样本来自正态分布 | 样本不服从正态分布 | 不满足正态性前提 → 改用非参数方法 |
| D'Agostino-Pearson K² | 正态性检验(大样本) | scipy.stats.normaltest |
样本来自正态分布 | 样本不服从正态分布 | 同上(n > 5000 时自动替代 Shapiro-Wilk) |
| Kolmogorov-Smirnov | 正态性检验(指定参考分布) | scipy.stats.kstest |
样本服从给定的正态分布 | 样本不服从该分布 | 同上 |
| Welch t 检验 | 两组均值差异 | scipy.stats.ttest_ind(equal_var=False) |
μ_a = μ_b |
μ_a ≠ μ_b |
两组均值差异显著(不要求方差齐性) |
| Mann-Whitney U | 两组位置差异(非参数) | scipy.stats.mannwhitneyu |
两组分布相同(P(X>Y) = 1/2) |
一组取值系统性更大/更小 | 两组存在位置差异 |
| 单因素 ANOVA | ≥ 3 组均值差异 | scipy.stats.f_oneway |
μ₁ = μ₂ = … = μ_k |
至少一对均值不等 | 至少一组与其他组不同(定位需事后检验) |
| Kruskal-Wallis H | ≥ 3 组位置差异(非参数) | scipy.stats.kruskal |
各组分布相同 | 至少一组位置不同 | 同上 |
| Pearson 相关 | 线性相关 | scipy.stats.pearsonr |
ρ = 0(无线性相关) |
ρ ≠ 0 |
存在显著线性相关 |
| Spearman 秩相关 | 单调相关 | scipy.stats.spearmanr |
ρ_s = 0 |
ρ_s ≠ 0 |
存在显著单调相关(对非线性单调关系更稳健) |
| Kendall τ | 秩相关(并列/小样本稳健) | scipy.stats.kendalltau |
τ = 0 |
τ ≠ 0 |
存在显著秩相关 |
| OLS 线性趋势 | 指标随 step 的斜率 |
statsmodels.api.OLS |
β₁ = 0(无线性趋势) |
β₁ ≠ 0 |
存在线性趋势;斜率符号给出方向 |
| ADF 单位根检验 | 序列平稳性 | statsmodels.tsa.stattools.adfuller |
序列存在单位根(非平稳,含随机趋势) | 序列平稳(无单位根) | 序列平稳,可用均值/方差概括 |
| 方法 | 含义 | Python 实现 | 有无 H0 |
判读标准 |
|---|---|---|---|---|
| Cohen's d | 两组均值差的标准化效应量(合并标准差) | 自实现(numpy,pooled SD) |
无(点估计量) | |d|:0.2 小 / 0.5 中 / 0.8 大(Cohen, 1988) |
秩二列相关 r_rb |
非参数路径的效应量,基于 Mann-Whitney 的 U |
自实现(调用 scipy.stats.mannwhitneyu 取 U) |
无 | |r_rb| → 1 表示两组完全分离;符号约定同 Cohen's d |
| 均值置信区间 | 总体均值区间估计 | 自实现(numpy + scipy.stats.t.ppf) |
无 | 用 ddof=1 的样本标准差与 t(n−1) 分位数构造 |
| 均值差置信区间 | 两组差值的区间估计 | 自实现(Welch–Satterthwaite 自由度 + scipy.stats.t.ppf) |
无 | 区间不含 0 ⇔ 同 α 下两组差异显著(双尾) |
| OLS 斜率置信区间 | 趋势斜率的区间估计 | statsmodels 的 model.conf_int(alpha) |
无 | 区间不含 0 ⇔ 趋势显著 |
符号与口径约定
- 效应量与均值差的符号统一为
mean(group_b) − mean(group_a)方向:compare_groups("loss")中a = early、b = late,因此「后期 loss 更低」得到负值; 非参数路径的秩二列相关采用同一符号约定,避免「参数与非参数结论方向相反」的误读。 - 组标签顺序:按标签字符串升序排序后依次作为
a、b(["baseline","tuned"]→baseline为a)。 - 方差口径:描述统计与检验一律使用样本方差
ddof=1;Cohen's d 使用合并标准差。 - 缺失值:
describe/values按列删除;correlation/trend按成对删除(pairwise deletion)。
以下方法不是假设检验(不产出 p 值),而是基于稳健统计量的确定性判定规则:
| 方法 | 判定统计量 | 判定规则 | 默认阈值 | 适用场景与注意 |
|---|---|---|---|---|
MAD 修正 z 分数(method="mad") |
z_mod = 0.6745·(x − median) / MAD |
|z_mod| > threshold |
3.5 |
推荐默认。中位数与 MAD 对离群点不敏感;Iglewicz & Hoaglin (1993) 建议 3.5 作为 3σ 的稳健等价 |
经典 z 分数(method="zscore") |
z = (x − mean) / std |
|z| > threshold |
3.0 |
计算最快,但 mean/std 会被离群点本身污染(掩盖效应/淹没效应),适合快速筛查 |
Tukey IQR 围栏(method="iqr") |
Q1, Q3, IQR = Q3 − Q1 |
x < Q1 − k·IQR 或 x > Q3 + k·IQR |
k = 1.5 |
非参数、不依赖分布假设;当 IQR 极小(近常数序列)时会标出较多点,需结合业务判断 |
| 滑动窗口(三种方法通用) | 以中心化窗口(min_periods=3)内的中位数/MAD/均值/标准差/分位数替代全局统计量 |
同上,阈值作用于窗口内标准化偏离度 | 同上 | 用于局部突变检测。训练指标常带长期趋势与噪声水平变化,全局尺度会被放大从而漏检局部尖峰(见 Quick Start 示例:全局标出 18 点,窗口法仅剩 [80])。复杂度 O(n·window);窗口内取值完全相同时 score 为 NaN |
| Plateau 检测 | 滑动窗口极差的相对幅度 | (max − min) / |窗口首值| ≤ rel_tol 且持续 patience 步 |
rel_tol=0.01、patience=5 |
用于量化「是否收敛」。返回窗口起点的 step;对周期性波动敏感,且不等价于最优早停点 |
当 parametric=None(默认)时,compare_groups 的执行流程:
- 对每一组做正态性检验(
n ≤ 5000用 Shapiro-Wilk,更大的样本自动切到 D'Agostino-Pearson K²); - 所有组的
p > α才使用参数检验(Welch t / ANOVA),否则自动降级为非参数检验(Mann-Whitney U / Kruskal-Wallis); - 实际路径记录在
result.extra["parametric"]中,便于复核与论文写作时说明。
| 分析 | 最小样本量 | 说明 |
|---|---|---|
| 正态性检验 | 3 | scipy 的硬性下限 |
| ADF 单位根检验 | 8 | 过短序列的检验结果不可信 |
| 均值置信区间 | 2 | 需要至少一个自由度 |
| OLS 趋势拟合 | 3 | 且自变量不能为常数 |
| 异常检测 | 3 | 滑动窗口模式要求 window ≥ 3 |
| 分组差异检验 | 每组 ≥ 2,且分组数 ≥ 2 | 单组或单观测直接抛 ValueError |
| 平台期检测 | patience + 1 |
否则返回 None |
- ADF 检验的是随机趋势,不是确定性趋势。 这是最容易误读的一条:本项目中平滑指数衰减的 loss
做 ADF 得到
p = 0.0056(拒绝单位根 → 判为「平稳」),但它的均值明显仍随step下降。 两者并不矛盾 —— ADF 的H0是「存在单位根(累积随机冲击)」,而非「均值恒定」。 因此:「是否收敛」请用trend()的斜率与detect_plateau()回答;ADF 用于回答「该指标能否用均值 ± 标准差概括」。 指标带明显长期趋势时,建议使用regression="ct"并保证足够样本量。 - ADF 在短序列上功效偏低。 测试校准中观察到:长度 200 的随机游走在部分随机种子下会给出
p ≈ 0.025, 即错误地拒绝单位根。样本量较小时,「平稳」结论只能作为参考,不宜单独用于重大判断。 - 「显著」不等于「重要」。
p值随样本量增大而单调变小;请同时报告效应量与置信区间 (本项目所有两组检验都同时输出effect_size与confidence_interval)。 - 未做多重比较校正。 同时对多个指标 / 多个分组检验时,family-wise error rate 会膨胀; 本工具如实给出每个检验的原始 p 值,需要时请自行应用 Holm-Bonferroni 等校正。
- 非参数检验功效较低。 样本量小且分布非正态时,Mann-Whitney / Kruskal-Wallis 的效应量与区间估计精度有限。
- 平台期判定依赖窗口参数。
rel_tol与patience会改变起点位置,且返回的是窗口起点而非数学最优点; 若用于自动早停,建议保留保守余量。 - 线性趋势对指数衰减拟合有限。 见 Quick Start 示例与
R²值:早期下降快、后期平缓的曲线 用单一线性模型会低估后期收敛速度,必要时改为对log(metric)建模。 - 异常检测阈值是经验值。
3.5 / 3.0 / 1.5分别来自稳健统计与 Tukey 的经典建议, 实际任务应结合噪声水平调整;滑动窗口法复杂度O(n·window),超长日志建议先降采样。 - 相关不等于因果。 Pearson / Spearman / Kendall 只度量线性或单调关联;
训练指标间高度共线(如
loss与accuracy)属正常现象,不代表其中一个是另一个的原因。
- Shapiro, S. S., & Wilk, M. B. (1965). An analysis of variance test for normality (complete samples). Biometrika, 52(3–4), 591–611.
- D'Agostino, R. B., & Pearson, E. S. (1973). Tests for departure from normality. Biometrika, 60(3), 613–622.
- Welch, B. L. (1947). The generalization of "Student's" problem when several different population variances are involved. Biometrika, 34(1–2), 28–35.
- Mann, H. B., & Whitney, D. R. (1947). On a test of whether one of two random variables is stochastically larger than the other. Annals of Mathematical Statistics, 18(1), 50–60.
- Kruskal, W. H., & Wallis, W. A. (1952). Use of ranks in one-criterion variance analysis. JASA, 47(260), 583–621.
- Cohen, J. (1988). Statistical Power Analysis for the Behavioral Sciences (2nd ed.). Lawrence Erlbaum Associates.
- Tukey, J. W. (1977). Exploratory Data Analysis. Addison-Wesley.
- Iglewicz, B., & Hoaglin, D. C. (1993). How to Detect and Handle Outliers. ASQC Quality Press.
- Kendall, M. G. (1938). A new measure of rank correlation. Biometrika, 30(1–2), 81–93.
- Dickey, D. A., & Fuller, W. A. (1979). Distribution of the estimators for autoregressive time series with a unit root. JASA, 74(366), 427–431.
- Said, S. E., & Dickey, D. A. (1984). Testing for unit roots in autoregressive-moving average models of unknown order. Biometrika, 71(3), 599–607.
- MacKinnon, J. G. (2010). Critical values for cointegration tests. Queen's Economics Department Working Paper No. 1227.
- Seabold, S., & Perktold, J. (2010). statsmodels: Econometric and statistical modeling with Python. Proceedings of the 9th Python in Science Conference.
- Virtanen, P., et al. (2020). SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17, 261–272.
- McKinney, W. (2010). Data structures for statistical computing in Python. Proceedings of the 9th Python in Science Conference.
NeuroStat-Logger/
├── app.py # Streamlit 主界面:侧边栏 + 4 个标签页(仅交互与绘图)
├── requirements.txt # 运行依赖(streamlit/pandas/numpy/scipy/statsmodels/plotly)
├── README.md # 本文档
├── LICENSE # MIT 许可证(Copyright (c) 2026 Xinhang Yu)
├── CITATION.cff # 引用元数据(GitHub「Cite this repository」按钮)
├── .gitignore # 忽略 __pycache__ / .venv / 工具缓存
├── src/
│ ├── __init__.py # 包导出:StatsEngine、TrainingLogParser、test_stationarity …
│ └── stats_engine.py # 统计推断核心(解析器 + 引擎 + 检验函数)
└── tests/
├── __init__.py
└── test_stats.py # 77 个 unittest 用例(4 个测试类)
| 模块 | 职责 |
|---|---|
TrainingLogParser |
CSV / TSV / JSON / JSONL / 记录列表解析;列名与类型规范化;多 run 兼容(重复 step 默认保留);解析告警收集 |
StatsEngine |
统计分析门面:描述性统计、正态性、ADF 平稳性、置信区间、组间差异、相关性、趋势、异常检测、平台期、汇总报告 |
StatResult / NormalityReport |
结果容器(dataclass):统一携带统计量、p 值、效应量、置信区间与 extra,as_dict() 可直接序列化为 JSON |
cohens_d / compare_groups / fit_trend / test_stationarity |
与引擎解耦的纯函数,可在 notebook、脚本或 CI 中单独复用 |
app.py |
只负责交互与可视化;所有统计计算都调用 src.stats_engine,保证结论可脱离界面复现 |
日志文件 (CSV/JSON/JSONL)
│ TrainingLogParser.parse()
▼
规范化 DataFrame(列名 snake_case、step 升序、类型统一)
│ StatsEngine
▼
StatResult / NormalityReport / DataFrame
├─► app.py 渲染(指标卡 / 表格 / Plotly 图 / 异常点标注)
└─► summary_report() → JSON 报告导出(可复现、可审计)
| 测试类 | 用例数 | 覆盖内容 |
|---|---|---|
TestTrainingLogParser |
11 | 列名/类型规范化、缺 step 自动补全、重复 step 默认保留、dedupe_by_step 开关、CSV / JSONL(含坏行)/ JSON 包装格式、重复列名报错、不支持后缀报错 |
TestDescriptiveStats |
11 | metrics 过滤、values 取值与未知列报错、describe 列与分位数(含传单个名字)、滑动统计、非法窗口、空 DataFrame |
TestInference |
32 | 正态性(多方法 + 大样本自动切换)、ADF 平稳性(白噪声/随机游走/趋势序列/常量报错/与确定性趋势的区分)、置信区间、early-late 与跨 run 分组、非参数路径、多组 ANOVA、相关性三方法、OLS 趋势、异常检测(MAD/z/IQR、方向标记、干净序列、滑动窗口救回漏检尖峰、恒定窗口 NaN、非法参数)、平台期、汇总报告 |
TestModuleFunctions |
23 | cohens_d(幅值/符号/配对/NaN/长度校验)、compare_groups(Welch 区间、多组标签、非参数效应量符号、组数不足)、fit_trend(斜率还原、非有限值、样本不足、常数 x)、test_stationarity(白噪声/随机游走/ct/非有限值/样本不足/常量)、StatResult 与默认阈值 |
| 合计 | 77 | 断言确定性(固定随机种子)、无网络、无外部文件依赖 |
本项目采用 MIT License 发布,全文见仓库根目录的 LICENSE 文件。
Copyright (c) 2026 Xinhang Yu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
| 权限 | 条件 | 限制 |
|---|---|---|
| ✅ 商业使用、修改、分发、私用、再许可 | 保留版权声明与许可声明 | ❌ 作者不提供任何担保,不承担任何责任 |
版权持有人:Xinhang Yu(2026)。LICENSE、本节与 CITATION.cff 三处保持一致;
若日后以组织名义重新发布,请同步更新这三处。
若本工具对你的研究或工程有所帮助,请按以下格式引用:
@software{yu2026neurostat,
title = {NeuroStat-Logger: 神经网络训练日志统计特征分析工具},
author = {Xinhang Yu},
year = {2026},
version = {0.1.0},
license = {MIT},
note = {基于统计推断的训练日志分析:ADF 单位根检验、正态性检验、效应量与置信区间、
组间差异检验、稳健异常检测(MAD/z-score/IQR)与 Plateau 检测}
}正文(Acknowledgement / Methods)中引用示例:
Training-log statistics in this work were computed with NeuroStat-Logger (Xinhang Yu, 2026), an open-source toolkit for statistical inference on neural-network training logs, released under the MIT License.
仓库根目录的 CITATION.cff 与上述 BibTeX 一致;若代码已发布到 GitHub,
建议在其中补上 url 字段(https://github.com/<用户名>/NeuroStat-Logger),
GitHub 会自动显示「Cite this repository」按钮。