# 第一次真实训练：25参数CPU网络

Python 3.11+，标准库，无模型/API/框架依赖。`train.py`真实计算2→6→1 tanh网络、sigmoid二分类、二元交叉熵、手写反向与momentum SGD；不是预先编好的loss动画。课程在`/learn/deep-learning/case.html`。

## 数据与协议

三个CSV分别有train=80、validation=24、test=24个合成样本。每个特征是二维坐标，标签为两轴同号时1、异号时0。生成种子2044，四象限各32点，在象限内按20/6/6固定划分后打乱并落盘；不适用于真实世界质量声明。测试文件不在训练/模型选择函数中打开。阈值0.5、seed7、LR0.05、momentum0.8、400步预算在运行前固定；不是看test挑最佳配置。

## 先预测再跑

多数类在验证集应为50%；非线性网络是否能超过它？没有step与没有backward会有什么不同信号？

```bash
cd cpu-mlp
python3 -B train.py train --steps 400 --out runs/base
python3 -B -m unittest discover -s tests -v
```

应有12条测试通过。默认验证loss约从0.744076降到0.061626，accuracy=1，混淆矩阵TN=12、TP=12、FP=FN=0。这个小集可记忆、边界很简单，不证明泛化到自然数据。细微浮点差允许，主要看方向、同协议与实际输出。

`runs/base/report.json`保存验证指标与训练/验证哈希；`history.jsonl`每20步保存train_loss、validation、grad_norm、parameter_delta；`checkpoint.json`保存参数、momentum速度、梯度buffer、步数、数据顺序/游标、RNG及超参。400是预先固定的训练预算；此实验不声称自动选择了最佳验证checkpoint。

## 保存与新进程恢复

```bash
python3 -B train.py train --steps 200 --out runs/part
python3 -B train.py train --resume runs/part/checkpoint.json --steps 400 --out runs/resumed
```

`--steps`表示最终总步数，不是再加400。与完整400步的params、velocity、step、cursor、order和rng_state比较；同Python环境应一致。删去momentum状态后恢复会偏离，测试专门验证。改变训练/验证数据后拒绝沿旧checkpoint恢复，不能把两次不同实验拼成一次。

方案冻结后才可执行最终测试：

```bash
python3 -B train.py evaluate --checkpoint runs/base/checkpoint.json --split test --out runs/base/test-report.json
```

test-report.json含实际test文件hash与checkpoint hash，evaluate不更新参数。保存结果后不再依据这份test调参；要继续调整应新建开发/保留协议。预测时用forward读取概率、按已定阈值比较；evaluate不更新参数。

## 故障实验

```bash
python3 -B train.py train --steps 40 --fault no-step --out runs/no-step
python3 -B train.py train --steps 40 --fault no-backward --out runs/no-backward
python3 -B train.py train --steps 40 --fault tiny-lr --out runs/tiny
python3 -B train.py train --steps 40 --fault accumulate --out runs/accumulate
```

默认第40步的实际信号：

| 故障 | grad_norm | parameter_delta | train_loss |
| --- | --- | --- | --- |
| no-step | 约0.28927 | 0 | 约0.70028 |
| no-backward | 0 | 0 | 约0.70028 |
| tiny-lr | 约0.28927 | 约2.68e-9 | 约0.70028 |
| accumulate | 约3.50099 | 约0.80231 | 约1.31260 |

前三条损失看起来都平，根因不同；忘清零直接是梯度累加，本例变得更差，不能把它固定配对“卡住”。这是此数据/seed下的日志，不是所有网络都遵守的曲线口诀。

## 改一个条件，再作诊断

1. 换seed为11，仍按同划分/预算训练，保存均值与差异；不得只展示最好一次。
2. 在自己的数据副本把train/validation/test标签都改为`x1>0`，明确这是新的任务。多数类基线、网络、错误切片重新计算；不要恢复旧checkpoint。
3. 故意移除step，先保留失败日志再恢复，验收需梯度非零但参数不动→修复后参数变化。若另一个故障使梯度也为零，说明检查不能只靠loss。

交付：协议卡、三份数据hash、基线/验证报告、两条恢复日志、一条故障与修复证据、最终test报告及“不适用于何种输入”说明。参考答案：模型有25个参数（12+6+6+1）；BCE对logit导数为p−y；tanh局部导数为1−h²；所有参数用同一轮旧值计算梯度后同时更新。完整验证集的平均loss和混淆矩阵对排列不敏感，前提是确定前向与正确样本加权聚合。

## 排错

- Python低于3.11：升级解释器；无需pip。
- empty dataset/nonfinite input：核对CSV表头、标签0/1与有限数值。
- 验证指标异常：先比较数据hash、seed与预算；再看第一步grad_norm/parameter_delta，别直接加步数。
- 恢复报data changed：这是有意阻断协议混用；新数据从新state开始。

下一步把同一数学机制接到PyTorch：zero_grad清旧梯度、backward填新梯度、step更新参数。参考[Autograd官方教程](https://docs.pytorch.org/tutorials/beginner/introyt/autogradyt_tutorial.html)与[checkpoint官方教程](https://docs.pytorch.org/tutorials/beginner/saving_loading_models)。本包没有dropout/BatchNorm；接入它们后还需train/eval模式与相应状态。
