很多人第一次训练模型时,会把注意力放在“准确率是多少”。但要真正理解模型是否可靠,需要先知道训练到底在调整什么,损失函数表示什么,过拟合为什么会发生,以及测试集为什么必须保留。
这篇文章围绕模型训练与评估展开,重点讲清楚几个基础概念:参数、损失函数、训练轮次、过拟合、验证集、测试集和常见分类指标。
如果说上一篇机器学习流程解决的是“项目怎么组织”,这一篇解决的就是“训练过程是否可信”。
一、模型训练到底在训练什么
一个模型可以理解成一个带参数的函数:
预测结果 = model(输入特征, 参数)
训练前,参数可能是随机的,也可能有默认初始值。训练的目标就是不断调整这些参数,让模型输出更接近真实标签。
以一个非常简单的线性模型为例:
y = w1 * x1 + w2 * x2 + b
这里的 w1、w2 和 b 就是参数。训练过程会尝试找到更合适的参数组合。
二、损失函数是什么
模型需要一个标准来判断“预测得有多错”。这个标准就是损失函数。
对回归问题,常见损失可以是预测值和真实值之间的平方差:
loss = (y_true - y_pred) ** 2
对分类问题,常用的是交叉熵损失。你不需要一开始就推导公式,但要理解它的直觉:
模型越自信地给出错误答案,损失越大;模型越接近正确答案,损失越小。
训练模型时,算法会尝试让整体损失下降。
三、梯度下降的直觉
很多模型使用梯度下降或它的变体来更新参数。可以把它想成一个下山过程:
- 当前参数对应一个损失值
- 计算往哪个方向移动能让损失下降
- 参数朝这个方向移动一点
- 重复很多次
这里有一个重要超参数叫学习率。学习率太小,下降很慢;学习率太大,可能来回震荡甚至无法收敛。
new_weight = old_weight - learning_rate * gradient
这不是全部细节,但足够帮助你理解训练循环为什么需要反复执行。
四、epoch、batch 和 iteration
训练深度学习模型时,经常会看到三个词:
- epoch:完整看完一遍训练集
- batch:每次拿一小批样本计算损失和梯度
- iteration:一次参数更新
如果训练集有 1000 条样本,batch size 是 100,那么一个 epoch 里会有 10 次 iteration。
传统机器学习库不一定直接暴露这些词,但背后的思想相似:模型需要通过训练数据反复调整参数。
五、为什么会过拟合
过拟合指的是:模型在训练集上表现很好,但在新数据上表现明显变差。
常见原因包括:
- 模型太复杂,记住了训练数据里的细节和噪声
- 训练数据太少,无法代表真实场景
- 特征里包含了不该使用的信息,也就是数据泄漏
- 训练太久,但没有监控验证集表现
过拟合的危险在于:你只看训练集指标时会觉得模型很好,但上线或遇到新样本后效果很差。
六、训练集、验证集和测试集
为了更可靠地评估模型,通常会拆出三类数据:
- 训练集:用于训练参数
- 验证集:用于调参、选模型、观察是否过拟合
- 测试集:最后只用一次,用来估计最终泛化效果
如果数据量不大,也可以先用训练集和测试集两份数据。但要记住:测试集不应该被反复用于调参,否则它也会间接参与训练决策。
七、分类任务常见指标
分类问题不能只看准确率。下面几个指标经常一起出现:
- Accuracy:预测正确的比例
- Precision:预测为正类的样本中,有多少是真的正类
- Recall:真实正类中,有多少被模型找出来
- F1-score:precision 和 recall 的综合指标
如果是疾病筛查,漏掉真实阳性可能很严重,这时 recall 往往很重要。如果是自动封号,误伤正常用户代价很高,这时 precision 可能更重要。
八、混淆矩阵怎么读
混淆矩阵会把预测结果和真实标签交叉统计:
预测负类 预测正类
真实负类 TN FP
真实正类 FN TP
TP:正类预测对了TN:负类预测对了FP:负类被误判成正类FN:正类被误判成负类
看混淆矩阵的好处是,你不只知道模型错了多少,还能知道它主要错在哪一种方向。
九、评估模型时的检查清单
每次评估模型时,可以按下面几个问题检查:
- 测试集是否从训练中隔离出来
- 类别是否严重不平衡
- 是否只看了 accuracy
- 是否和简单基线模型比较过
- 错误样本是否被人工看过一部分
- 训练表现和测试表现差距是否过大
模型训练的核心不是把某个指标刷高,而是建立一个可信的判断过程。你需要知道模型为什么看起来有效,也要知道它在哪些情况下可能失效。
十、判断一次训练是否靠谱
一个比较靠谱的训练记录,至少应该包含这些信息:
- 训练集、验证集和测试集的划分方式
- 使用的模型、主要参数和随机种子
- 训练指标和测试指标,而不是只给一个最终分数
- 错误样本分析,尤其是代价最高的错误类型
- 和简单基线模型的比较
这些记录看起来琐碎,但它们能让你几天后重新检查实验时,仍然知道结果从哪里来。
十一、一个训练记录模板
为了让训练结果可复查,可以把每次实验记录成固定格式。这个模板适合小项目,也适合以后扩展到更复杂的训练平台。
任务: 鸢尾花三分类
数据版本: iris.csv sha256=...
划分方式: train/test=80/20, random_state=42, stratify=y
模型: LogisticRegression(max_iter=1000)
基线: 预测多数类
主要指标: accuracy, macro F1
错误分析: 查看混淆矩阵中错分最多的类别对
结论: 是否超过基线,是否存在类别偏差,下一步改什么
| 记录字段 | 为什么必须写 | 缺失后的风险 |
|---|---|---|
| 数据版本 | 确认训练材料没有变化 | 复现实验时结果对不上 |
| 随机种子 | 固定划分和初始化 | 一次偶然高分被误认为稳定提升 |
| 基线模型 | 给复杂模型建立比较对象 | 不知道提升是否真正有意义 |
| 错误样本 | 定位模型失效模式 | 只看到指标,看不到风险方向 |
十二、如何判断指标提升是否可信
如果一个模型从 91% 提升到 92%,不能立刻说明新模型更好。你需要检查测试集规模、随机划分差异、类别分布和错误类型是否变化。如果测试集只有几十条样本,1 个样本的变化就可能造成几个百分点的波动。
更稳妥的做法是重复多次划分或使用交叉验证,同时保留一个最终测试集只用于最后确认。指标提升必须和错误样本分析一起看:如果总体分数提高,但关键类别的召回下降,实际业务价值可能反而变差。
验证集用得太多,它就变成了第二个训练集
前面区分了训练集、验证集和测试集。有一个后果值得单独讲,因为它解释了为什么很多人「调了很久、验证分数很好」的模型,一到真实数据上就不行。
模型的参数是从训练集学到的,这一点很清楚。但超参数——学习率、层数、正则化强度、特征组合——是从验证集学到的。你每一次「看验证分数,然后改一个设置,再看分数」,都是在用验证集的信息更新你的选择。做上几百次,你其实是在用一个非常粗糙的优化器,把模型往验证集上拟合。
结果就是验证分数逐渐失去意义。它不再回答「这个模型在没见过的数据上表现如何」,而是回答「这个模型在这一份特定的验证集上表现如何」——而后者你已经优化过几百轮了。
标准做法是留出第三份数据,全程不看:
训练集 学参数 —— 模型直接使用
验证集 选超参数 —— 你反复查看,会被间接拟合
测试集 最终报告 —— 全程封存,只在最后看一次
关键在于「只看一次」这条纪律。如果你看了测试集分数、觉得不满意、回去继续调,那份测试集就作废了——它已经变成了第二个验证集。这时候诚实的做法是重新划分一份新的测试数据,或者接受当前结果并如实报告调整过程。
数据量小的时候留三份很奢侈,这时候用交叉验证代替固定验证集:把训练数据切成 k 份,轮流用其中一份做验证,取平均。这能让每条数据都参与训练,也让验证分数更稳定——单次划分的验证分数在小数据上波动很大,只看一个数字很容易被随机性误导。
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X_train, y_train, cv=5)
print(scores.mean(), scores.std()) # 标准差同样重要
标准差要和均值一起看。下面两组五折分数的均值完全相同,都是 0.896:
A: 0.91 0.89 0.90 0.90 0.88 均值 0.896 标准差 0.010
B: 0.98 0.76 0.97 0.78 0.99 均值 0.896 标准差 0.103
只报均值的话,这两个模型看起来一模一样。但 A 是稳定的,而 B 对数据怎么划分极度敏感——同样的模型、同样的数据,换一种切法结果能从 0.76 跳到 0.99。
遇到 B 这种情况,正确的动作不是报告 0.896,而是去查为什么某些折特别差。常见原因是某个类别的样本很少,随机划分时可能整批落进同一折;也可能数据里存在分组结构(同一个用户的多条记录、同一批次的多个样本),被拆散到训练和验证两侧造成了泄漏。这两种都要用分层划分或分组划分来解决,而不是接受那个均值。