查看全课大纲(第 14 / 82 节)
  1. 1.函数与数据泛化
  2. 2.向量:从几何直觉到高维空间
  3. 3.矩阵与张量
  4. 4.特征值与特征向量:矩阵变换的不动方向
  5. 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
  6. 6.概率与统计:机器学习的数学基础
  7. 7.独立性与随机变量:贝叶斯公式与二项分布
  8. 8.期望、方差与协方差:随机变量的数字画像
  9. 9.联合概率:当一条轴不够用的时候
  10. 10.概率分布(一):从伯努利到伽马函数
  11. 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
  12. 12.机器学习的模型:找输入与输出的关系
  13. 13.机器学习的应用
  14. 14.过拟合
  15. 15.机器学习的分类
  16. 16.有监督学习:从房价预测说起
  17. 17.损失函数
  18. 18.梯度下降(上):更新规则与学习速率
  19. 19.梯度下降(2)
  20. 20.牛顿方法
  21. 21.KNN简介
  22. 22.距离测算
  23. 23.朴素贝叶斯
  24. 24.朴素贝叶斯的实际应用
  25. 25.信息熵:决策树构建的度量基础
  26. 26.信息增益与决策树的构建
  27. 27.ID3算法
  28. 28.决策树节点的划分
  29. 29.CART树
  30. 30.Bagging
  31. 31.Boosting:自适应提升算法
  32. 32.Gradient Boosting
  33. 33.线性模型:回归与分类的同一副骨架
  34. 34.最小二乘:把最优参数直接解出来
  35. 35.最小二乘法的几何与概率视角
  36. 36.极大似然:给最小二乘一个概率出身
  37. 37.逻辑回归(1):把线性模型接到分类上
  38. 38.逻辑回归(2):损失函数与多分类
  39. 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
  40. 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
  41. 41.优化方法(1):坐标下降与前向选择
  42. 42.优化方法(2):前向策略与最小角回归
  43. 43.拉格朗日乘子法(1):从无约束到等式约束
  44. 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
  45. 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
  46. 46.最大间隔:把分类问题变成优化问题
  47. 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
  48. 48.支持向量:边界上的少数派
  49. 49.SVM 计算过程:一笔到底算完,再走进核方法
  50. 50.贝叶斯公式:概率即信念
  51. 51.隐含变量和概率模型:给看不见的原因建模
  52. 52.EM 算法:期望与最大化的迭代
  53. 53.琴生不等式:EM 的数学钥匙
  54. 54.三个硬币问题:EM 的复盘与衔接
  55. 55.高斯混合模型:用隐变量解释双峰数据
  56. 56.隐马尔可夫模型
  57. 57.隐马尔可夫模型(2):前向算法与动态混合模型
  58. 58.隐马尔可夫模型在 NLP 中的运用
  59. 59.EM 算法:把「为什么收敛」推到底
  60. 60.HMM 在 NLP 中的运用:从分词到机器翻译
  61. 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
  62. 62.高维下的高斯模型
  63. 63.主题模型:从词袋到隐含主题
  64. 64.Dirichlet 分布(1)
  65. 65.Dirichlet 分布(2)
  66. 66.统计学习内容回顾
  67. 67.K-means 算法
  68. 68.PCA 降维
  69. 69.SVD 降维
  70. 70.从聚类到流形
  71. 71.局部线性嵌入
  72. 72.随机邻域嵌入:SNE 与 t-SNE
  73. 73.无参模型:让数据自己说话
  74. 74.高斯过程:函数空间里的正态分布
  75. 75.高斯过程(2):噪声、正则化与核的线性组合
  76. 76.神经网络简介:从感知器到自编码器
  77. 77.反向传播:把误差一层层传回去
  78. 78.卷积神经网络(CNN):让特征自己学出来
  79. 79.循环神经网络(RNN):给网络装上记忆
  80. 80.深度学习回顾:静态一类,动态一类
  81. 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
  82. 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传

过拟合

约 27 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · 机器学习算法精讲

上一节我们纵览了机器学习能解决的各种问题。本节回到一个更根本的问题:如果机器学习是一门科学,它到底是一门什么样的科学?围绕这个问题,我们走完机器学习”哲学篇”的最后一块拼图——科学发现的类比、泛化与特殊的平衡、过拟合、奥卡姆剃刀、维数灾难,以及对抗过拟合的四大策略。其中”过拟合”是贯穿整个机器学习实践的核心敌人,从本节起它会反复出场。

💡 核心导读

  • 机器学习的科学观:它是关于算法的科学,本质是用证据检验假设——一次自动化的科学发现。
  • 泛化与特殊的平衡:模型太宽泛会乱贴标签,太特殊会认死细节,好模型在两者之间。
  • 过拟合:模型把训练数据里的噪声当成了规律,训练误差近乎完美,测试误差一落千丈。
  • 奥卡姆剃刀与维数灾难:一条哲学原则、一个几何事实,都在提醒我们”简单”的价值与高维的代价。
  • 四大应对策略:更多数据、集成学习、正则化、交叉验证。

机器学习是关于算法的科学

机器学习研究者 Pat Langley 在 1986 年的《Human and Machine Learning》里说过一句常被引用的话:

If machine learning is a science, it is a science of algorithms.(如果机器学习是一门科学,它是一门关于算法的科学。)

而更早的 Herbert Simon 给出了学习的目的:通过学习,改进我们在某类任务上的表现(improve performance on some class of tasks)。你会发现,Langley、Simon、周志华……不同的人对机器学习下了不同的定义,没有一个人的定义是完美的。讲师在这里给了一个很”机器学习”的建议:多看几个人的定义,再抽象出你自己的理解。

这背后有一个朴素的认识论:所有的定义都是人们看了很多现象之后,从现象和数据里抽象出来的解释;先有现象,后有理论,而不是先有理论,后有现象。我们学习理论时为什么会学不懂?往往就是因为没见过它所解释的那些实际东西——看多了,自然就抽象出来了。

顺便解决一个实际问题:数据从哪里来?网上有大量公开的研究数据库——自然语言的、DNA 的、基因的、制药的,很多都是公开的,大家可以去搜。其中最著名的是 UCI Machine Learning Repository:1987 年起就向社区开放,是最早的机器学习数据库之一,里面有很多常用数据集。

像所有的科学一样,机器学习可以概括为一句话:It’s about hypothesis testing using evidence——用证据检验假设。

机器学习就是一次科学发现

回想人类的科学理论是怎么来的:先观察数据,大脑里产生对数据的假设——“也许物体越重,下落越快?“这不够,还需要验证:伽利略做实验,发现好像不是这么回事;可是羽毛确实下落得慢,为什么?也许存在空气浮力——那就需要更多实验证明空气浮力真的存在:充满氢气的气球能飞起来……所有的理论和解释,都是不断观察数据之后、从人脑中产生出来的。

机器学习做的是同款事情:给它很多图片,告诉它这些叫人、这些叫狗、这些叫猫;再给它新的图片,让它判断是什么。它需要在”机器的大脑”里构建出对这件事的解释和假设——满足什么情况,该是什么。这些要学习的对象就是数学模型,叫 model、mathematical model,所以机器学习是 model learning。

再具体一点:我们的大脑会构造出一个维度特别多、特别复杂的假设空间(hypothesis space),我们要在这个空间里不断搜索,也许找到一个特别好的点,它能解释你看到的所有数据——这就是我们对这个事情的假设。机器学习做的同样是:在一个机器所允许的假设空间里搜索(search in the hypothesis space)。

讲师举了个好玩的例子:某天你看见一只特别大的蜘蛛。怎么解释?假设空间瞬间收窄成两个:要么蜘蛛变大了,要么我们变小了。哪个正确?光看这一个证据,并不知道。但如果你看更多的数据——看看蜜蜂、看看蚂蚁是不是也变大了——就能判定。这些 evidence(证据)就是我们所说的训练数据:训练并检验我们的假设。

泛化与特殊的平衡

机器学习经常是一个 trade-off between generalization and specification:在”泛化”与”特殊”之间找平衡。

想判断一片叶子是不是柳树叶子:如果模型太宽泛,会觉得任何绿色的东西都是柳树叶子;如果太特殊,要求跟这片叶子的大小、长短、质地、重量,甚至上面有多少条叶脉、落了多少灰都一模一样——世界上没有两片一模一样的叶子,结果是没有任何东西能被认作柳树叶子。好的平衡是:形状像柳树叶子、也是绿色——不是杨树、不是松树。既 generalize 又不 too specific,还能做到一定的性能,这就是比较好的模型。

放到分类问题上看:模型特别简单(一条直线)的时候,会判错很多;但线特别复杂、把所有噪音都拟合进去的时候,就是过拟合了,同样不是好事。为什么”特别复杂”反而不好?这需要一个哲学原则来回答——

奥卡姆剃刀:如无必要,勿增实体

奥卡姆剃刀(Occam’s Razor)来自哲学:如果对同一件事存在两种解释,通常更简单的那个更好。放到机器学习里:如果两个模型的表现一样好,选简单的那个。

先讲讲师给的背景故事。奥卡姆的威廉是 14 世纪英国的方济会修士,讲师把他归入”牧师”这个人群——在欧洲,很多伟大的科学、哲学著作正是神职人员做出来的:贝叶斯是牧师,孟德尔也是。因为牧师在当时被认为是有知识的人,不需要劳动、不愁生计,于是有大量时间思考重要的问题。

顺着”牧师”再扯远一点:博士学位叫 PhD,全称 Doctor of Philosophy(哲学博士)——为什么物理、数学的博士都叫哲学博士?因为在早期的欧洲,学问简单分两种:一种是神学,剩下的统统叫哲学——生物学、博物学、数学、物理都属于哲学范畴。牛顿那本提出三定律的书为什么叫《自然哲学的数学原理》?不是牛顿故弄玄虚:他研究的是”描述自然运作的哲学”,用的工具是数学。想通了这个,PhD 的来历也就通了。

回到剃刀本身:razor 就是剃刀。假设奥卡姆去买剃刀,剃刀有各种功能形态,但他需要的唯一一件事是能把胡子刮掉——再复杂都是多余。以前打电话,所谓电话就是”能打电话”就行;今天手机已经完全逃脱了”电话”的含义。在所有能解决这个问题的答案里边,选择最简单的——这就是奥卡姆剃刀。

它和”先验”的关系

奥卡姆剃刀和我们先验(prior)的认知密切相关:先验认为哪种情况概率大,我们就倾向于相信哪种。讲师的经典例子:一家高科技公司门口走出一位二十六七岁、穿着时尚、黑丝袜高跟鞋的女士在路边打车——她在这家公司做什么工作?很多人猜 HR、行政、管理,很少有人猜程序员。凭什么程序员就不能穿黑丝袜、穿高跟鞋?没有任何逻辑上的理由,只是我们的先验如此。先验就像奥卡姆剃刀:面对复杂问题时,我们总按先验挑最简单的解释——所以它有可能是错的,要警醒。

Francis Crick 的警告

奥卡姆剃刀在物理科学里特别好用,但 DNA 双螺旋结构的发现者之一 Francis Crick 提醒过:它在生物学里可能非常危险——过度强调简单与优雅,而生物系统的真相可能非常 ugly(丑陋),但真实存在。对机器学习的警醒是:输入与输出之间的真实关系也许本来就是很复杂的,强迫自己认为它是个简单关系,有时候是错的。奥卡姆剃刀是有用的启发式原则,不是绝对真理。

过拟合:当模型”学得太好”

现在正式请出主角:Overfitting,过拟合——对数据的过拟合。

想象你去听一场交响乐,想把每一个音符都听进去,于是买了一副超级灵敏(super-sensitive)的麦克风和助听器。结果呢?除了音乐本身,你还听见了邻座在座位上翻节目单的窸窣声、乐手翻乐谱的声音、甚至指挥外套的摩擦声——你开始 overfitting:在交响乐之上,听到了噪声。(讲师顺口一提:我现在用的这个特别好的麦克风,就是小象给的。)

模型同理。看那组蓝点红点的分类数据:某个蓝点其实只是被噪声污染、跑错了位置;模型为了把蓝和红完美地分开,画出的分界线过于曲折——它拟合的是噪声,付出的是泛化性能。什么叫”过”?over-sensitive、over-fitting——过了这个头。

复杂度与那条著名的曲线

模型的复杂度可以是什么?神经网络的层数、决策树的层数,或者多项式的指数:

  • f(x)=ax+bf(x) = ax + b:一阶,2 个参数;
  • f(x)=ax2+bx+cf(x) = ax^2 + bx + c:3 个参数;
  • f(x)=ax3+bx2+cx+df(x) = ax^3 + bx^2 + cx + d:4 个参数……

多项式随着阶数变复杂,模型的复杂度、自由度不断增加。我们用代码把这条经典的”复杂度-误差”曲线亲手算出来:训练数据取自一条正弦曲线加手工噪声;测试点直接用真实函数值,相当于请一位不带噪声的裁判:

# 演示一:多项式阶数(模型复杂度)与训练/测试误差的关系
import math

# 训练数据:sin(2πx) 采样加手工噪声
train_data = [
    (0.0, 0.15), (0.1, 0.41), (0.2, 1.07), (0.3, 0.75), (0.4, 0.80),
    (0.5, -0.05), (0.6, -0.78), (0.7, -1.05), (0.8, -0.80), (0.9, -0.47),
    (1.0, 0.10),
]
# 测试数据:直接取真实函数值(不带噪声的裁判)
test_data = [(0.05 + 0.1 * i, math.sin(2 * math.pi * (0.05 + 0.1 * i))) for i in range(10)]

def solve(A, b):
    """高斯消元(列主元)解线性方程组 Ax = b"""
    n = len(A)
    M = [row[:] + [b[i]] for i, row in enumerate(A)]
    for col in range(n):
        pivot = max(range(col, n), key=lambda r: abs(M[r][col]))
        M[col], M[pivot] = M[pivot], M[col]
        for r in range(col + 1, n):
            f = M[r][col] / M[col][col]
            for c in range(col, n + 1):
                M[r][c] -= f * M[col][c]
    x = [0.0] * n
    for r in range(n - 1, -1, -1):
        x[r] = (M[r][n] - sum(M[r][c] * x[c] for c in range(r + 1, n))) / M[r][r]
    return x

def poly_fit(data, degree):
    """最小二乘拟合 degree 阶多项式:解正规方程 (X^T X) a = X^T y"""
    m = degree + 1
    A = [[0.0] * m for _ in range(m)]
    b = [0.0] * m
    for xi, yi in data:
        powers = [xi ** d for d in range(m)]
        for r in range(m):
            for c in range(m):
                A[r][c] += powers[r] * powers[c]
            b[r] += powers[r] * yi
    return solve(A, b)

def mse(data, coeffs):
    err = 0.0
    for xi, yi in data:
        pred = sum(c * xi ** d for d, c in enumerate(coeffs))
        err += (pred - yi) ** 2
    return err / len(data)

print("多项式阶数 | 训练MSE | 测试MSE")
print("-" * 40)
for d in [1, 3, 5, 9]:
    coeffs = poly_fit(train_data, d)
    print(f"    {d}      | {mse(train_data, coeffs):.4f} | {mse(test_data, coeffs):.4f}")

# 过拟合模型的"相貌":看九阶多项式的参数摆动有多剧烈
coeffs9 = poly_fit(train_data, 9)
print(f"九阶多项式的最大系数绝对值: {max(abs(c) for c in coeffs9):.1f}")
多项式阶数 | 训练MSE | 测试MSE
----------------------------------------
    1      | 0.2903 | 0.2163
    3      | 0.0382 | 0.0076
    5      | 0.0134 | 0.0093
    9      | 0.0021 | 0.1396
九阶多项式的最大系数绝对值: 313672.5

读这张表:训练误差随阶数单调下降,到 9 阶只剩 0.0021——11 个点、10 个参数,它几乎穿过了每一个点,连噪声都不放过;而测试误差先降后升,3 阶最优(0.0076),9 阶暴涨到 0.1396,是最优点的 18 倍。训练数据上的完美解释者,恰好是新数据上的灾难。再看最后一行:这个九阶多项式的最大系数绝对值超过 31 万,而数据本身只在 −1 到 1 之间——模型靠剧烈的参数摆动去够每一个噪声点,这就是过拟合模型的典型相貌。

(补一个名字:模型太简单、连基本规律都抓不住、判错一片的现象,通常称为欠拟合——正是曲线左端一阶的样子。)

神经网络的过拟合简史

80 年代神经网络就火过一轮,但当时它饱受过拟合之苦,后来渐渐淡出了主流研究领域。直到深度学习出现:模型的复杂度虽然大增,数据量也在同步大增,反而训练得更好、没有过拟合——深度学习由此重回人们的视野。可见复杂度本身不是原罪,复杂度与数据量的配比才是。

为什么需要测试数据

正因为如此,我们要找的不是训练数据的最优点,而是测试数据的最优点——训练误差一路下降的尽头,往往正是测试误差开始爬升的悬崖。这也是我们需要测试数据的原因。

对抗过拟合的四大策略

1. 收集更多数据

最朴素也最有效。数据的真实分布往往是未知的:只有三个数据点时,你可能很有理由地认为规律是某条曲线;第四个点一出现,才发现真实趋势可能完全不同。尤其在分布稀疏的地方多采一些数据,有助于真正了解这个分布。今天这件事已经有了名字——Big Data:所有的公司都以”有数据”为荣,数据就是最重要的部分。

2. 集成学习(ensemble)

把给定的数据训练出很多个模型,再求这些模型的均值。多个模型在不同侧面犯错,平均之后相互抵消,整体更稳。这个方法我们讲完决策树之后细讲——本课程第 7 章的 Bagging、Boosting、随机森林都是它的代表。

3. 正则化(regularization)

对复杂度加约束、加惩罚(penalty complexity):比如对参数整体保证稀疏性,或者让参数的 L2 范数保持最小——相当于在方程上乘一个 λ\lambda 项,使模型不会过拟合。细节等我们讲到线性模型的正则化时展开(本课程第 9 章:岭回归与 LASSO)。

4. n 折交叉验证(n-fold cross-validation)

这是本节要当场讲透的工具,也是数据量有限时最实用的武器。以 10 折为例:把数据分成十小块,每次留一块做测试、其余九块做训练,轮流十次——每次都是 90% 训练、10% 测试。把十次测试的准确率都算出来,求均值和方差,用来比较客观地衡量一个算法的好坏。

关键的判读规则来了:把每个算法的性能画成”均值 ± 标准差”的区间,然后两两比较:

  • 区间有重叠 → 不能说谁在统计意义上更好(comparable,可比较但不分高下);
  • 一个的下沿比另一个的上沿还要好 → 这时候才敢说绝对地好(statistically significant)。

我们用一组手算可复核的数据,把这个判读法完整跑一遍:数据大致沿一条直线、带少量手工噪声;三个候选模型分别是”最简单的常数""合适的一阶多项式""自由度拉满的九阶多项式”:

# 演示二:10 折交叉验证 + "均值±标准差"区间判读
import statistics

# 数据大致沿直线 y = 1 + 0.8x,带少量手工噪声
cv_data = [
    (0.05, 1.11), (0.10, 1.05), (0.15, 1.06), (0.20, 1.20), (0.25, 1.14),
    (0.30, 1.28), (0.35, 1.24), (0.40, 1.36), (0.45, 1.30), (0.50, 1.44),
    (0.55, 1.42), (0.60, 1.55), (0.65, 1.48), (0.70, 1.61), (0.75, 1.57),
    (0.80, 1.70), (0.85, 1.64), (0.90, 1.76), (0.95, 1.71), (1.00, 1.82),
]

def solve(A, b):
    """高斯消元(列主元)解线性方程组 Ax = b"""
    n = len(A)
    M = [row[:] + [b[i]] for i, row in enumerate(A)]
    for col in range(n):
        pivot = max(range(col, n), key=lambda r: abs(M[r][col]))
        M[col], M[pivot] = M[pivot], M[col]
        for r in range(col + 1, n):
            f = M[r][col] / M[col][col]
            for c in range(col, n + 1):
                M[r][c] -= f * M[col][c]
    x = [0.0] * n
    for r in range(n - 1, -1, -1):
        x[r] = (M[r][n] - sum(M[r][c] * x[c] for c in range(r + 1, n))) / M[r][r]
    return x

def poly_fit(data, degree):
    """最小二乘拟合 degree 阶多项式"""
    m = degree + 1
    A = [[0.0] * m for _ in range(m)]
    b = [0.0] * m
    for xi, yi in data:
        powers = [xi ** d for d in range(m)]
        for r in range(m):
            for c in range(m):
                A[r][c] += powers[r] * powers[c]
            b[r] += powers[r] * yi
    return solve(A, b)

def mse(data, coeffs):
    err = 0.0
    for xi, yi in data:
        pred = sum(c * xi ** d for d, c in enumerate(coeffs))
        err += (pred - yi) ** 2
    return err / len(data)

# 分十折:第 i 个点进第 i%10 折,每折 2 个点
K = 10
folds = [[] for _ in range(K)]
for i, point in enumerate(cv_data):
    folds[i % K].append(point)

models = [
    ("常数模型(预测均值)", lambda tr: [sum(y for _, y in tr) / len(tr)]),
    ("一阶多项式(直线)", lambda tr: poly_fit(tr, 1)),
    ("九阶多项式(自由度拉满)", lambda tr: poly_fit(tr, 9)),
]

for name, trainer in models:
    fold_errs = []
    for k in range(K):
        test = folds[k]
        train = [p for j in range(K) if j != k for p in folds[j]]
        coeffs = trainer(train)
        fold_errs.append(mse(test, coeffs))
    m = statistics.mean(fold_errs)
    s = statistics.stdev(fold_errs)
    print(f"{name} 每折MSE: {[round(e, 4) for e in fold_errs]}")
    print(f"{name} 均值 {m:.4f} 标准差 {s:.4f} 区间 [{m - s:.4f}, {m + s:.4f}]")
    print()
常数模型(预测均值) 每折MSE: [0.0545, 0.0809, 0.0726, 0.0424, 0.0518, 0.0498, 0.0404, 0.0635, 0.0505, 0.0895]
常数模型(预测均值) 均值 0.0596 标准差 0.0165 区间 [0.0430, 0.0761]

一阶多项式(直线) 每折MSE: [0.0039, 0.003, 0.0036, 0.0024, 0.0029, 0.0029, 0.0023, 0.0018, 0.0045, 0.0011]
一阶多项式(直线) 均值 0.0028 标准差 0.0010 区间 [0.0018, 0.0039]

九阶多项式(自由度拉满) 每折MSE: [0.0089, 0.005, 0.0037, 0.007, 0.006, 0.0044, 0.0069, 0.0052, 0.0139, 0.1689]
九阶多项式(自由度拉满) 均值 0.0230 标准差 0.0514 区间 [-0.0284, 0.0743]

三组结果把判读规则的三种情形全演了一遍:

  • 一阶 vs 常数:一阶的区间上沿 0.0039,远低于常数的下沿 0.0430——彻底分离,一阶在统计意义上绝对地好。
  • 一阶 vs 九阶:九阶的区间把一阶的整个罩住了——尽管一阶的均值低了 8 倍(0.0028 对 0.0230),按判读规则也不能下结论。“均值低就赢了”是直觉,区间不重叠才是规则。
  • 九阶 vs 常数:区间同样重叠——欠拟合与过拟合这两个极端,统计意义上竟然难分高下。

再细看九阶那一行,有两个值得咀嚼的细节:其一,它的逐折 MSE 里前九折都还算体面(0.004~0.014),第十折却爆炸到 0.1689——过拟合的坏,很多时候就坏在”不可预测”;其二,它的区间下沿是负数(−0.0284)——MSE 不可能为负,这个越界本身就是警报:折与折之间忽好忽坏,波动幅度已经和均值同一量级。顺带说一句:逐折细看的话,一阶其实十折全胜;但”均值±标准差”这个课堂判读法在方差悬殊时非常保守、宁可不下结论——它惩罚的正是不稳定。

维数灾难:高维空间的稀疏困境

讨论复杂度时还有一个警告级的概念:curse of dimensionality,维数的诅咒(维数灾难)。同样数量的数据,维度一增加,问题就会变得非常困难——因为数据在高维空间里会非常稀疏。

一个非常简单的算术:在长度为 1 的线段上撒 10 个点,相邻点的平均距离大约是 0.1——无论怎么撒,总有挨得很近的点。把空间扩成边长为 1 的平面,还是这 10 个点,密度立刻稀疏了很多;要想维持同样的密度,需要约 100 个点(平方关系)。再扩到三维的立方体,就需要 1000 个点。反过来,把 10 个点扔进三维立方体,点与点之间的距离就变得非常大:它们也许各有相似的特征,但在高维空间里每一个都离得很远,特征显得非常不明显——想找”哪些数据近、哪些数据远”都变得困难。

讲师在这里留了一道思考题:高维空间的”球”是什么样子?线段是一维空间的”球”,圆是二维的,球是三维的——再往上想,高维的球有什么神奇特点?讲师先剧透了一半:它的质量(体积)都集中在表面。为什么?留给你琢磨(练一练里我们算一笔账)。

📝 动手练一练

  1. 高维球的”薄壳”账本:讲师说高维球的体积集中在表面。请不动用编程、纯手算回答:一个半径为 1 的 d 维球,只剥掉最外层 5% 的厚度(剩下半径 0.95 的内球),内球体积占整球的比例是多少?分别算 d=1、2、10、100。
👉 点击查看参考答案

体积与半径的 d 次方成正比,所以内球占比就是 0.95d0.95^d:

  • d=1:0.95——薄壳只占 5%;
  • d=2:约 0.9025——薄壳约 9.75%;
  • d=10:约 0.5987——薄壳约 40.1%;
  • d=100:约 0.0059——薄壳约 99.4%。

维度到了 100,99% 以上的体积都挤在最外层 5% 的薄壳里——这就是”质量都在表面”。对机器学习的含义:高维空间里的数据点几乎都落在边缘和角落而非中心,彼此遥远、分布稀疏,学习任务因此变得异常困难。

  1. 代码实践:把演示一的阶数搜索从 [1, 3, 5, 9] 细化为 1~9 全序列,找出测试误差最低的阶数,并观察最优点附近的”地形”。
👉 点击查看参考答案
# 练习:阶数搜索细化为 1~9 全序列
import math

train_data = [
    (0.0, 0.15), (0.1, 0.41), (0.2, 1.07), (0.3, 0.75), (0.4, 0.80),
    (0.5, -0.05), (0.6, -0.78), (0.7, -1.05), (0.8, -0.80), (0.9, -0.47),
    (1.0, 0.10),
]
test_data = [(0.05 + 0.1 * i, math.sin(2 * math.pi * (0.05 + 0.1 * i))) for i in range(10)]

def solve(A, b):
    """高斯消元(列主元)解线性方程组 Ax = b"""
    n = len(A)
    M = [row[:] + [b[i]] for i, row in enumerate(A)]
    for col in range(n):
        pivot = max(range(col, n), key=lambda r: abs(M[r][col]))
        M[col], M[pivot] = M[pivot], M[col]
        for r in range(col + 1, n):
            f = M[r][col] / M[col][col]
            for c in range(col, n + 1):
                M[r][c] -= f * M[col][c]
    x = [0.0] * n
    for r in range(n - 1, -1, -1):
        x[r] = (M[r][n] - sum(M[r][c] * x[c] for c in range(r + 1, n))) / M[r][r]
    return x

def poly_fit(data, degree):
    """最小二乘拟合 degree 阶多项式"""
    m = degree + 1
    A = [[0.0] * m for _ in range(m)]
    b = [0.0] * m
    for xi, yi in data:
        powers = [xi ** d for d in range(m)]
        for r in range(m):
            for c in range(m):
                A[r][c] += powers[r] * powers[c]
            b[r] += powers[r] * yi
    return solve(A, b)

def mse(data, coeffs):
    err = 0.0
    for xi, yi in data:
        pred = sum(c * xi ** d for d, c in enumerate(coeffs))
        err += (pred - yi) ** 2
    return err / len(data)

best_degree, best_err = None, float("inf")
print("阶数 | 训练MSE | 测试MSE")
print("-" * 40)
for d in range(1, 10):
    coeffs = poly_fit(train_data, d)
    tr = mse(train_data, coeffs)
    te = mse(test_data, coeffs)
    print(f"  {d}   | {tr:.4f} | {te:.4f}")
    if te < best_err:
        best_degree, best_err = d, te
print(f"测试误差最低的阶数: {best_degree} (测试MSE = {best_err:.4f})")
阶数 | 训练MSE | 测试MSE
----------------------------------------
  1   | 0.2903 | 0.2163
  2   | 0.2874 | 0.2182
  3   | 0.0382 | 0.0076
  4   | 0.0382 | 0.0074
  5   | 0.0134 | 0.0093
  6   | 0.0132 | 0.0088
  7   | 0.0125 | 0.0084
  8   | 0.0073 | 0.0299
  9   | 0.0021 | 0.1396
测试误差最低的阶数: 4 (测试MSE = 0.0074)

测试误差最低的是 4 阶(0.0074),但它和 3 阶(0.0076)只在小数点后第四位分胜负——最优点附近是平坦的,3 阶和 4 阶本质上是同一个答案;真正醒目的是 8、9 阶的悬崖(0.0299 → 0.1396)。模型选择的实践要点由此而来:与其精细搜索那个”最优点”,不如远远避开悬崖区。

还有一个耐人寻味的细节:2 阶几乎没比 1 阶强(训练 0.2874 对 0.2903)。因为 sin⁡(2πx)\sin(2\pi x) 关于中点 x=0.5x=0.5 是奇对称的,偶次项本来就没多少可贡献——每一阶新参数”值不值”,数据自己会说话。

本章小结

本节补完了机器学习”哲学篇”的最后一块:它是一门关于算法的科学,本质是用证据检验假设、在巨大假设空间里搜索的自动化科学发现。好模型在泛化与特殊之间取平衡;越过平衡冲向”特殊”那一端,就把噪声当成了规律——训练误差近乎完美、测试误差一落千丈的过拟合。奥卡姆剃刀(以及它与先验的关联、Crick 的警告)给了我们”优先选简单”的哲学依据;维数灾难则从几何上说明了高维数据为什么格外难学。对抗过拟合的四大策略——更多数据、集成学习、正则化、交叉验证——会在后面的章节逐一展开:第 7 章的集成学习、第 9 章的正则化都是它们的正片。下一节我们把机器学习问题按”监督信号的有无”分成四大类,随后正式进入算法世界。

📋 行动清单

学完本节,你可以立即:

  • 逛数据入口:去 UCI Machine Learning Repository 找一个你感兴趣的数据集,记下它的样本量和维度——这是讲师亲荐的数据来源。
  • 换个函数重跑:把演示一的训练数据换成别的函数(如 exe^x 采样加噪声),重跑复杂度曲线,亲眼看一次 U 型。
  • 找一个自己的例子:想一个你生活中”凭先验选了最简单解释”的案例(就像黑丝袜女士那个例子),想想它在什么情况下会出错。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第3章课件:机器学习的哲学
    下载