机器学习的三个基本问题
机器学习要解决的问题可以归结为一句话:给定一批数据,找到一个函数,使它在没见过的数据上也能给出正确答案。围绕“正确答案长什么样”,任务被分成三类。
- 监督学习:数据带标签,目标是预测标签。房价预测(回归)、垃圾邮件判定(分类)都属此类。
- 无监督学习:数据没有标签,目标是发现结构。聚类、降维、异常检测属于这一类。
- 强化学习:没有标准答案,只有与环境交互后的奖励信号,目标是学到策略。
几乎所有落地项目的价值,都取决于数据质量与问题定义,而不是模型的复杂度。
本文聚焦监督学习,因为它是工程岗位最常接触、也最容易见效的一类。把它学扎实,再去看深度学习或强化学习,概念会顺很多。
监督学习:从标注数据里找规律
监督学习的数学表达很简洁:给定样本 (x, y),学习一个映射 f,使得 f(x) 尽可能接近 y。这里的 x 是特征向量(把一条记录转成数字数组),y 是标签。
- 训练集用来拟合模型参数,模型“见过”这些数据。
- 验证集用来调超参数与选模型,不能用来报最终成绩。
- 测试集只在最后用一次,代表对未知数据的真实表现。
- 特征工程把原始数据转成模型可用的数值,常比换模型更有效。
特征工程常被低估。缺失值怎么填、类别变量怎么编码、数值怎么标准化,这些决定往往比“用线性模型还是神经网络”影响更大。经验法则是:先把特征处理干净,再谈模型选择。
损失函数与梯度下降
模型好不好,需要一个可优化的数字指标,这就是损失函数。回归常用均方误差(MSE),分类常用交叉熵(Cross Entropy)。损失越小,模型在当前数据上拟合得越好。
把损失看成参数空间里的一个曲面,训练就是沿着梯度下降的方向一步步走,试图找到谷底。学习率决定每步走多远:太小收敛慢,太大可能直接跨过谷底甚至发散。批量大小则影响每次更新所用样本的数量——批量越大梯度越稳,但内存开销也越大。
两个常见现象值得记住:损失突然变成 NaN,通常是学习率过大或数据没归一化;训练损失下降而验证损失上升,说明开始过拟合。这两种情况都能从训练日志里第一时间发现,前提是你真的在看损失曲线。
原理不难,难在手感。下面用纯 NumPy 实现一次一元线性回归的梯度下降:不用任何机器学习框架,几十行就能看到损失如何一步步变小,也能亲手把学习率调大,观察发散的过程。
import numpy as np
def mse(y_true, y_pred):
return float(np.mean((y_true - y_pred) ** 2))
rng = np.random.default_rng(7)
x = rng.uniform(-3, 3, size=200) # 特征
y = 2.5 * x + 1.0 + rng.normal(0, 0.8, size=200) # 真实规律 + 噪声
w, b, lr = 0.0, 0.0, 0.05 # 参数与学习率
for epoch in range(1, 301):
y_pred = w * x + b
# 两个参数的偏导(对 w 与 b 分别求梯度)
gw = float(np.mean(2 * (y_pred - y) * x))
gb = float(np.mean(2 * (y_pred - y)))
w -= lr * gw
b -= lr * gb
if epoch % 100 == 0:
print("epoch=%3d loss=%.4f w=%.3f b=%.3f" % (epoch, mse(y, y_pred), w, b))
# epoch=100 loss=0.642 w=2.472 b=1.043
# epoch=300 loss=0.617 w=2.494 b=1.088 -> 接近真实值 w=2.5, b=1.0
把 lr 从 0.05 调到 0.5 再跑一次,会看到损失在第 2 到 3 轮直接爆成巨大的数甚至 NaN——这正是训练日志里最常见的失败信号。亲手复现一次,比看十遍公式有用。
过拟合与正则化
过拟合是监督学习最核心的敌人:模型把训练数据里的噪声也当成了规律,导致在新数据上表现变差。判断方法很简单——训练集表现远好于验证集。
对抗手段主要有四种。增加数据量(最有效);简化模型(减少参数或层数);正则化(L1/L2 惩罚过大的权重);早停与 Dropout(训练中随机关闭一部分单元,迫使模型不依赖单一路径)。
与过拟合相对的是欠拟合:训练集和验证集都差,说明模型容量不够或特征信息不足。这时应该换更强的模型或补特征,而不是继续调正则化——方向搞反了,只会越调越差。
评估指标:别只看准确率
准确率(Accuracy)是最直观也最容易骗人的指标。当类别极不平衡时——比如 1000 条里只有 10 条是欺诈——一个把所有样本都判为“正常”的模型也能拿到 99% 准确率,却毫无用处。
| 指标 | 含义 | 适用场景 |
|---|---|---|
| 精确率 Precision | 判为正的样本里有多少真的是正 | 误报代价高,如垃圾邮件拦截 |
| 召回率 Recall | 真正的正样本有多少被找出来 | 漏报代价高,如疾病筛查 |
| F1 | 精确率与召回率的调和平均 | 需要单值综合衡量时 |
| AUC | 排序能力,与阈值无关 | 不平衡数据、排序场景 |
回归任务则看 MAE(平均绝对误差,与业务单位一致,易解释)和 RMSE(对大误差更敏感)。选指标的原则是:先想清楚哪种错误更不可接受,再选对应的指标。
经典算法速览与选型
经典算法至今仍是很多项目的首选,因为它们训练快、可解释、在小数据上表现稳健。
- 线性/逻辑回归:基线首选,系数可直接解释方向与强度。
- 决策树与随机森林:能处理非线性与特征交互,对缺失值和量纲不敏感。
- 梯度提升树(XGBoost / LightGBM):表格数据上的常胜方案,多数竞赛与业务的默认选择。
- K 近邻与朴素贝叶斯:实现极简,适合快速验证思路。
- 支持向量机:小样本高维场景仍有优势。
- 神经网络:特征为图像、语音、文本等非结构化数据时才明显占优。
选型顺序建议是:逻辑回归做基线 → 梯度提升树做主力 → 只有在非结构化数据或数据量极大时才上深度学习。先跑通基线,永远比一上来就调大模型更划算。
用 scikit-learn 跑通第一个分类任务
下面用 scikit-learn 在一个经典数据集上跑通完整流程:划分数据、标准化、训练、评估。
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y)
clf = make_pipeline(StandardScaler(),
LogisticRegression(max_iter=2000, C=1.0))
clf.fit(X_train, y_train)
pred = clf.predict(X_test)
prob = clf.predict_proba(X_test)[:, 1]
print(classification_report(y_test, pred, digits=3))
print("AUC =", round(roc_auc_score(y_test, prob), 4))
把 LogisticRegression 换成 RandomForestClassifier 或 GradientBoostingClassifier,只需改一行,就能快速比较不同模型。注意 StandardScaler 必须放进 Pipeline:如果先在全量数据上标准化再划分,测试集的信息会泄漏到训练过程,评估结果会虚假偏高。
再进一步,用 GridSearchCV 搜索 C 或树的数量,观察验证集指标随超参数的变化曲线。这一步能让你直观看到“训练分数高、验证分数低”的过拟合现象。
从模型到工程:数据才是胜负手
最后回到工程现实。一个模型从实验到上线,通常要经历:数据采集与清洗 → 特征管道固化 → 离线评估 → 小流量上线 → 监控指标漂移 → 定期重训。其中真正花时间的往往是第一项。
三个实践建议:把数据处理写成可复现的脚本而不是 Notebook 里的零散单元格;为每次实验记录数据版本、超参数与指标,否则一周后你无法复现最好的那次结果;上线前就设计好监控——输入分布是否偏移、预测分布是否异常、关键业务指标是否变化。
模型会过时,数据管道不会。把数据质量当成长期资产经营,收益会随时间累积。
如果你准备继续深入,下一步是学 Pandas 做数据清洗,把真实的表格数据变成模型能吃的格式——这正是下一篇文章的主题。