前 15 天我们一直在讲 LLM。但 LLM 是金字塔尖,底座是 1950–2000 年沉淀下来的经典 ML。今天回到地基:四个至今仍在生产系统里大规模运行的算法。它们的共同点——可解释、便宜、在表格数据上常常打败深度学习。理解它们,你才真正懂「机器学习」这四个字到底学了什么。
线性回归就是一个权重可学习的打分公式。你做容量规划时手写过 cost = 0.3·CPU + 0.5·内存 + 0.2·IO 这种加权和——那些系数是你拍脑袋定的。线性回归做的事一模一样,只是让数据自己算出最优系数,目标是让预测误差最小。
痛点:你有一堆「输入特征 → 数值结果」的历史样本(房子面积→价格、请求数→延迟),想找一条规律去预测新样本。线性回归假设结果是输入的加权和:
其中 xᵢ 是特征(面积、房间数),wᵢ 是权重(这个特征多重要),b 是偏置(基准值)。怎么找最优的 w?定义一个「错得多严重」的度量——均方误差(MSE):把每个样本的预测值减真实值、平方、求平均。
为什么平方而不是绝对值?三个原因:① 平方处处可导,能用梯度下降优化;② 大误差被放大(错 10 的惩罚是错 1 的 100 倍),逼模型别犯大错;③ 数学上有闭式解——直接一个矩阵公式 w = (XᵀX)⁻¹Xᵀy 算出答案,不用迭代。这就是「最小二乘法」,1805 年勒让德就在用了,比神经网络早 150 年。
from sklearn.linear_model import LinearRegression import numpy as np # 特征:[面积, 房间数] 标签:价格(万) X = np.array([[50, 1], [80, 2], [120, 3], [200, 4]]) y = np.array([300, 480, 700, 1100]) model = LinearRegression() model.fit(X, y) # 一行求解最优权重(闭式解) print(model.coef_) # 每个特征的权重 w print(model.intercept_) # 偏置 b print(model.predict([[100, 2]])) # 预测 100㎡/2 室的价格
逻辑回归 = 线性回归 + 一个把任意数值压成「概率」的阀门。它像一个带校准概率的准入控制器(admission controller):不只输出「放行/拒绝」,而是给出「这个请求有 87% 概率是正常流量」。名字里有「回归」,干的却是分类的活。
痛点:很多任务要的是是/否而非数值——这封邮件是垃圾吗?这个用户会流失吗?线性回归输出 (−∞, +∞),没法当概率。逻辑回归在加权和外面套一个 Sigmoid 函数把它挤进 (0, 1):
直觉:z 很大 → p 趋近 1(几乎确定是正类);z 很小(很负)→ p 趋近 0;z=0 → p=0.5(最不确定)。这条 S 形曲线把「线性打分」平滑映射成「概率」。背后的数学身份是对数几率(log-odds):wᵀx+b = log(p/(1−p))——模型其实在线性地预测「赢面的对数」。
训练不用 MSE,而用交叉熵 / 对数损失:真实是正类时惩罚 −log(p),真实是负类时惩罚 −log(1−p)。为什么换损失?因为 Sigmoid 套 MSE 会让损失函数变「非凸」(坑坑洼洼,梯度下降卡在局部最优);交叉熵恰好让它重新变成凸函数,且等价于最大似然估计——数学上最自然的选择。
from sklearn.linear_model import LogisticRegression # 特征:[邮件长度, 含'免费'次数] 标签:1=垃圾 0=正常 X = [[20, 0], [15, 3], [200, 0], [30, 5]] y = [0, 1, 0, 1] clf = LogisticRegression() clf.fit(X, y) # predict_proba 给的是概率,不是硬标签——这才是逻辑回归的价值 print(clf.predict_proba([[18, 4]])) # [[P(正常), P(垃圾)]] print(clf.predict([[18, 4]])) # 默认 0.5 阈值后的硬标签
决策树就是一棵自动生成的嵌套 if-else 路由表——和你写过的规则引擎一模一样,只是规则由数据自己学出来。随机森林则是用 quorum 投票替代单点决策:种几百棵互不相同的树,让它们投票,靠多数表决消除单棵树的随机偏差——和分布式系统用副本冗余对抗单点故障是同一个思想。
痛点:线性模型要你手动设计特征、还假设关系是线性的。决策树不假设任何形状,它贪心地反复切分数据:每一步问「按哪个特征、哪个阈值切,能让两边最‘纯’?」纯度用 基尼不纯度(Gini)衡量:
pₖ 是某节点里第 k 类的占比。全是同一类 → Gini=0(最纯);五五开 → Gini=0.5(最乱)。树就是不断选「切完后加权 Gini 下降最多」的切法,直到叶子足够纯。这给你一个完全可读的模型——能画出来给非技术的人看。
但单棵树有致命伤:方差极高,换几个样本就长成完全不同的树,容易过拟合。随机森林(Breiman 2001)用两层随机性解耦这些树:
关键数学直觉:平均 N 个近似独立、各自有噪声的预测,方差约降到 1/N(和你重复测量取均值降噪是同一个统计原理)。bagging 让树彼此独立,投票就把单棵树的过拟合「平均掉」了——这就是集成学习以弱胜强的核心。
from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_iris X, y = load_iris(return_X_y=True) # n_estimators=树的数量;越多越稳,但收益递减 rf = RandomForestClassifier(n_estimators=300, max_depth=5) rf.fit(X, y) # 几乎免费的特征重要性——森林天然能排序哪个特征最有用 for name, imp in zip(["花萼长","花萼宽","花瓣长","花瓣宽"], rf.feature_importances_): print(f"{name}: {imp:.2f}")
SVM 找的是两类数据之间最宽的「隔离带 / DMZ」。别的分类器随便画一条能分开两边的线就满足了;SVM 偏要画那条离两边都最远的线——隔离带越宽,对新数据越鲁棒。而决定这条线的,只有贴着边界的少数几个关键样本(支持向量),就像一个优化问题里真正「绷紧」的那几条约束。
痛点:能分开两类的直线有无数条,哪条泛化最好?SVM 给出明确答案——最大化间隔(margin),即决策边界到最近样本的距离。几何上,间隔等于 2/‖w‖,所以「最大化间隔」等价于「最小化 ‖w‖」,在「所有点都被正确分到隔离带外」的约束下求解:
直觉:‖w‖ 越小,隔离带越宽;约束保证每个样本都站在自己一侧、且离边界至少「一个单位」。最优解只由那几个压在边界上的支持向量决定——删掉其它样本,边界纹丝不动。这让 SVM 在高维小样本下特别稳。
但数据常常线性不可分(一条直线分不开)。SVM 的杀手锏是核技巧(kernel trick):把数据隐式映射到更高维空间,让原本绕成一团的两类变得能用平面切开——类比你给 SQL 查询加一个计算列(比如 x²+y²),原本分不开的环形数据立刻可分。妙处在于核函数不用真的算高维坐标,只算样本间的相似度,省掉天文数字的计算:
γ 控制「影响半径」:大 γ → 每个点只管附近,边界弯曲(易过拟合);小 γ → 影响范围大,边界平滑。这是 SVM 最关键的旋钮。
from sklearn.svm import SVC from sklearn.datasets import make_circles # 环形数据:内圈一类、外圈一类,直线绝对分不开 X, y = make_circles(n_samples=200, noise=0.1, factor=0.4) # 用 RBF 核:隐式升维,把环形变得可分 clf = SVC(kernel="rbf", C=1.0, gamma="scale") clf.fit(X, y) print("支持向量数量:", len(clf.support_)) # 只有少数样本决定边界 print("准确率:", clf.score(X, y))