跳转至

P5-8.1 如何给目标函数加约束:正则化(regularization)

Section ID: P5-8.1 Version: v2026.07.20

在 P5-7 章里,我们已经看到 optimizer 是把 gradient 变成实际 update 的规则。但即使训练循环运转顺畅,也不意味着模型立刻就能在新数据上同样站得住。接下来的问题会马上出现。

如果模型在训练数据上拟合得很好,但在新数据上表现不好,该怎么办?

回答这个问题的核心概念之一,就是正则化(regularization)。第 8 章是在阅读:为了让学习循环更稳定,会额外加上哪些控制装置。而这一节先处理的是:应该给目标函数加上什么样的约束。

正则化,是在学习过程中加入约束或额外代价,好让模型不要只对训练数据过度贴合的想法。

如果之后又把过拟合抑制和 normalization 混在一起,更适合回到概念词汇表里的 regularization 条目,重新对齐边界。

regularization 给目标函数加上的问题

  • 为什么 regularization 会进入学习循环?
  • 它和过拟合(overfitting)有什么关系?
  • regularization 会怎样改变目标函数?
  • 为什么当模型规模和数据量一起看时,它会更重要?

更安全的读法,不是把这一节只看成optimizer 后面又多了一个设置项,而是把它看成:把update 规则让 update 更倾向哪类解的偏好条件分开阅读的一节。dropout 会在下一节 P5-8.2 作为结构层面的控制继续说明,而 training mode 与 evaluation mode 的计算差异,会在 P5-6.4 再次接回。

这一节要区分什么 为什么重要
optimizer 因为它负责看 gradient,并决定 update 实际用什么步幅移动。
regularization 因为它负责给移动加上限制,不让模型一路走向过于复杂的解。
normalization 因为它回答的不是过拟合抑制,而是怎样让数值尺度和分布更容易处理。

约束与泛化的判断标准

  • 能把 regularization 解释为为了降低过拟合而加入的约束
  • 能区分 optimizer 和 regularization 的角色。
  • 能说明为什么 regularization 和 normalization 回答的是不同问题。
  • 能说明 regularization 与 loss function、模型规模、数据量之间的关系。
  • 能说明 regularization 在第 8 章中承担的是目标函数控制装置的角色。
  • 能通过可执行的 Python 例子一起比较训练损失、验证损失和权重大小。

为什么 regularization 和 normalization 不一样

这一节说的正则化,是 regularization。但在实际语境里,normalizenormalization 这些词也会经常出现,所以初次阅读时很容易混在一起。

两个名字相近,但要回答的问题不同。

项目 regularization normalization
首先要回答的问题 怎样避免模型过度死记训练数据? 怎样让输入值或中间值的尺度更容易处理?
主要关注点 泛化(generalization)、过拟合抑制 数值范围、分布、学习稳定性
代表例子 L2 penalty、dropout、early stopping 输入归一化、batch normalization、layer normalization

也就是说,regularization 更接近让哪类解变得不那么受偏好,而 normalization 更接近什么样的数值范围和分布更方便计算处理

当然,在真实的深度学习里,两者并不是完全分开的。例如 batch normalization 更直接连到计算稳定性和学习速度,但结果上也可能观察到一些类似 regularization 的效果。即便如此,在入门阶段仍然更适合先这样拆开。

  • regularization:把模型从过度死记里拽回来的一类装置
  • normalization:让数值尺度和分布更容易处理的一类装置

为什么需要 regularization

深度学习模型的表达能力很强。这意味着它很有力量,但也意味着它可能把训练数据里的偶然模式和噪声(noise)也一起学进去。

例如:

  • 训练数据(training data)上的 loss 一直下降
  • 但验证数据(validation data)上的表现,到某个时刻开始不再变好,甚至反而变差

这个场景,直接连回 Part 4 里已经看到的过拟合(overfitting)。

regularization 正是在这里出现。它给模型加上一句约束:可以去拟合训练数据,但不要用过于复杂的方式去拟合。

如果把这个场景画成曲线会更容易读。训练损失持续下降,而验证损失从某个点开始重新上升时,模型可能正在越来越偏向于把训练数据的细节模式背得更牢。

训练损失与验证损失分叉的过拟合场景

在这张图里,regularization 并不是只盯着训练损失的最低点。真正要一起看的,是新数据上的损失是否也在改善,还是训练数据与新数据之间的差距正在进一步扩大。

从入门读者的角度,更适合把这个场景再压成更短的三行。

先看到的数字 接着该追问的问题 regularization 出现的原因
训练损失一直下降 验证损失也一起变好吗? 因为不能把模型停留在只适合训练数据的解上。
训练准确率很高 输入稍微变化后,这个判断还站得住吗? 因为要让过度敏感的解变得不那么受偏好。
模型变得更复杂了 这种复杂度在新数据上也真的需要吗? 因为更大的权重和更复杂的规则可能通向过拟合。

regularization 想阻止什么

这里先用下面三行抓住 regularization 的目标就够了。

  • 不让模型过度依赖特别大的 parameter
  • 不让模型只去记住某些样本里的偶然模式
  • 帮助模型在新数据上也更稳定地工作

也就是说,regularization 不只是要把 loss 降下来,而是在限制:loss 应该以什么样的方式被降下来。

regularization 只意味着 penalty 吗

入门教材经常把 regularization 介绍成在 loss function 里再加一个 penalty 项。这个解释很重要,但单独拿出来又有些偏窄。

在深度学习里,把 regularization 看得更宽一些会更安全。

例如,下面这些也都可以作为广义的 regularization 来阅读。

  • 控制权重大小的 penalty
  • 像 dropout 这样随机切断部分连接的方式
  • 像 early stopping 这样避免训练过久的策略
  • 像 data augmentation 这样增加输入多样性的方式

所以 regularization 与其说是一个单独公式,不如说更接近为了减少过拟合而采用的一种设计哲学

它和损失函数有什么关系

regularization 经常和 loss function 一起出现。

\[ total\ loss = data\ loss + regularization\ term \]

这条式子读到下面这个程度就足够了。

  • data loss:预测和正确答案相差多少
  • regularization term:模型是不是正在走向过于复杂的方向

也就是说,regularization 不只是在加入答对题目的代价,还会再附加使用过多复杂度的代价

因此,optimizer 现在要减少的,就不再只是原始 loss,而是已经把 regularization 算进去之后的整个目标。

把这个连接再压短一点,大致就是下面这个流程。

flowchart TD
  A["模型预测"]
  B["数据损失"]
  C["正则化项"]
  D["总目标"]
  E["更偏好较不激进的参数"]
  F["更好的泛化方向"]

  A --> B
  B --> D
  C --> D
  D --> E
  E --> F

这张图里首先要确认的是:regularization 并不是代替误差计算的另一种 loss,而是贴在 data loss 旁边、改变整个目标函数,并因此让模型更倾向于较不激进解的一种装置。

它和模型规模、数据量有什么关系

regularization 更常需要出现的场景,大致可以读成下面这样。

  • 模型规模(model size)很大,表达能力很强
  • 数据量(data size)相对较少,或者
  • 数据里本来就混有不少偶然模式和噪声

这时模型很容易找到一个在训练数据上拟合得很好的解,但这个解能否在新数据上继续站得住,理由就没那么充分了。所以更准确的说法不是模型一大就一定要加 regularization,而是要一起看:相对于模型拥有的自由度,数据到底够不够。

反过来,如果数据更多,模式分布也更均匀,那么模型只靠记住某些样本里的偶然组合来拿到成绩的可能性就相对更低。因此 regularization 不该只被看成 loss function 旁边的 penalty 项,而更像是一起考虑模型规模数据量在新数据上的站得住程度的判断标准。

optimizer 和 regularization 有什么不同

读者会感觉 optimizer 和 regularization 都像是在调整学习。但两者角色并不一样。

项目 角色
optimizer 根据 gradient 决定怎样更新 parameter
regularization 给哪类解更受偏好、哪类复杂性要避开,加入约束

也就是说:

  • optimizer 处理的是该怎么移动
  • regularization 处理的是哪些方向应该少喜欢一点

先把这个区分固定住,后面再读 weight decay、dropout、early stopping 时,就更容易把它们放到同一个视角里。

如果再慢半步看,optimizer 和 regularization 虽然都在同一个学习循环里,但读者注视的位置并不一样。

在学习循环里先看什么 接着看什么
optimizer 如何接收 gradient 并移动 parameter regularization 如何限制这种移动靠近的解的性质
有没有顺利下降 是不是正在朝过于激进的解下降

案例与示例

案例. 用不同标准重新阅读相同的训练性能

假设我们用一份小型表格数据来训练客户流失预测模型。两种模型在训练数据上都拟合得差不多好。但模型 A 只要某一列数值稍微变化,预测就会大幅波动;模型 B 在训练性能相近的同时,对输入变化的反应没有那么激烈。

一开始看起来,好像只要选择训练损失更低的那一个就够了。但如果我们想要的是在新数据上也站得住的模型,问题就必须变掉。不能只看它拟合得有多好,还得看为了得到这个结果,它用了多大的权重,以及多敏感的规则。regularization 就是在这里发挥作用:即使沿着相同的学习方向,也让更激进的解变得不那么受偏好。

这个案例里真正要确认的结果,并不是训练分数的最高点,而是:当有两个拟合程度相近的解时,是否会选择那个更可能在新数据上少一点摇晃、而不是更依赖大权重和高敏感度的解。

flowchart TD
  A["训练拟合程度相近"]
  B["旧读法:训练损失更低就够了"]
  C["检查权重大小"]
  D["检查预测敏感度"]
  E["regularization 视角"]
  F["更偏好较不激进的解"]
  G["确认验证损失与输入变化稳定性"]

  A --> B
  B --> C
  C --> D
  D --> E
  E --> F
  F --> G

如果按这个流程来读,regularization 和 normalization 的差别也会更不容易混淆。把输入列的单位对齐、把数值范围整理得更容易处理,更接近 normalization。相反,在当前这个案例里,regularization 看的不是把值变到什么范围,而是模型为了拟合训练数据,是否用上了过大的权重和过度敏感的规则。

如果把 regularization 的核心比较压成一个场景,就是:两边都在训练数据上拟合得差不多,但其中一边用了更大的权重和更复杂的路径。

比较问题 更激进的解 较不激进的解
对训练数据的拟合程度 拟合得差不多 拟合得差不多
权重大小与复杂度 更大 更小
对输入变化的敏感度 更高 更低
regularization 更偏好的那一边 不是
flowchart TD
  A["训练数据拟合程度相同"]

  subgraph HIGH["更激进的解"]
    B["更大的权重"]
    C["对输入变化更敏感"]
  end

  subgraph LOW["较不激进的解"]
    D["较小的权重"]
    E["对输入变化较不敏感"]
  end

  A --> B
  A --> D
  B --> C
  D --> E
  C --> F["较不受 regularization 偏好"]
  E --> G["更受 regularization 偏好"]

从这张比较图里,先要固定住下面几点。

  • regularization 不是在说不要去拟合正确答案,而是在两个拟合程度相近的解之间,让更激进的一边变得没那么受偏好。
  • 所以比较标准不能只剩下谁的误差更接近 0,还必须把为了制造这个误差,使用了多大的权重和多复杂的解也一起纳入。
  • 只有这个视角先固定住,下面的例子才不会被读成干扰 loss 下降的一项,而会被读成让模型更偏向较不激进解的一项

练习与例子

这个例子的目标,是把 regularization 读成让我们同时看验证损失和权重大小的约束,而不是让训练损失再低一点的技巧。这里把一小段训练日志放在 CSV 里,比较没有 regularization 的设置和使用 L2 regularization 的设置。

输入:

  • 训练日志 CSV:regularization-training-log.csv
  • model:没有 regularization 的设置,以及使用 L2 regularization 的设置
  • epoch:训练重复编号
  • train_lossvalidation_lossweight_size:训练损失、验证损失、权重大小

输出:

  • 每个模型的验证损失最低 epoch
  • 最后一个 epoch 的训练损失与验证损失差距
  • 随着学习推进,权重大小增加了多少

问题场景:

  • 如果只看训练损失,没有 regularization 的一边可能看起来更好。
  • 但如果验证损失重新上升、权重大小持续变大,就需要重新追问这个解能否在新数据上站得住。

要确认的概念:

  • regularization 会让我们不只看训练损失一个数字
  • 需要同时看验证损失最低的时点和最后时点
  • 即使学习方向相似,能保持较小权重的解也可能是较不激进的解

输入(input):

CSV 的一行,是某个模型设置在一个 epoch 结束后记录的一条摘要。这里不使用真实的深度学习库,而是读取已经记录好的训练日志,只确认判断标准。

在看代码之前,可以先预测哪一边的训练损失更低,哪一边在验证损失和权重大小上更稳定。

比较项目 可以先预测的比较 预测理由
最后的训练损失 没有 regularization 的一边可能更低 因为没有约束时,可以更强地贴合训练数据。
最后的验证损失 使用 L2 regularization 的一边可能更低 因为它让大权重和激进的解变得不那么受偏好。
权重大小增加 没有 regularization 的一边可能更大 因为使用复杂解时不会额外付出代价。

这张表的目的,是把训练损失验证损失权重大小一起读出来。

# 这个例子读取 CSV 训练日志,比较有无 regularization 时的训练损失、验证损失和权重大小。
from csv import DictReader
from pathlib import Path

csv_path = Path("docs/assets/part-05/chapter-08/regularization-training-log.csv")

rows = []
with csv_path.open(encoding="utf-8") as file:
    for row in DictReader(file):
        rows.append(
            {
                "model": row["model"],
                "epoch": int(row["epoch"]),
                "train_loss": float(row["train_loss"]),
                "validation_loss": float(row["validation_loss"]),
                "weight_size": float(row["weight_size"]),
                "regularization_strength": float(row["regularization_strength"]),
            }
        )

models = ["without_regularization", "with_l2_regularization"]

for model in models:
    model_rows = [row for row in rows if row["model"] == model]
    first = model_rows[0]
    last = model_rows[-1]
    best_validation = min(model_rows, key=lambda row: row["validation_loss"])

    validation_gap = last["validation_loss"] - last["train_loss"]
    validation_rebound = last["validation_loss"] - best_validation["validation_loss"]
    weight_growth = last["weight_size"] - first["weight_size"]

    print(f"[{model}]")
    print("regularization_strength =", last["regularization_strength"])
    print("best_validation_epoch =", best_validation["epoch"])
    print("best_validation_loss =", round(best_validation["validation_loss"], 3))
    print("last_train_loss =", round(last["train_loss"], 3))
    print("last_validation_loss =", round(last["validation_loss"], 3))
    print("last_validation_gap =", round(validation_gap, 3))
    print("validation_rebound_after_best =", round(validation_rebound, 3))
    print("weight_growth =", round(weight_growth, 3))
    print()

读输出时,不要先只看最后的训练损失,而要先确认验证损失什么时候最低、之后又重新上升了多少。

[without_regularization]
regularization_strength = 0.0
best_validation_epoch = 8
best_validation_loss = 0.55
last_train_loss = 0.19
last_validation_loss = 0.74
last_validation_gap = 0.55
validation_rebound_after_best = 0.19
weight_growth = 4.9

[with_l2_regularization]
regularization_strength = 0.08
best_validation_epoch = 12
best_validation_loss = 0.45
last_train_loss = 0.33
last_validation_loss = 0.47
last_validation_gap = 0.14
validation_rebound_after_best = 0.02
weight_growth = 1.5
  • 没有 regularization 的一边,最后训练损失更低,为 0.19
  • 但最后验证损失上升到 0.74,训练损失与验证损失的差距也扩大到 0.55
  • 使用 L2 regularization 的一边,训练损失更高,为 0.33,但最后验证损失是 0.47,权重增加也更小

把这些数字再画成曲线,regularization 要我们读取的比较轴会更清楚。

有无 regularization 时的训练损失与验证损失

第一张图里,不能只选择训练损失更低的那条线。没有 regularization 的设置中,训练损失持续下降,但验证损失在第 8 个 epoch 之后重新上升。使用 L2 regularization 的设置虽然没有那么激进地降低训练损失,却把验证损失的反弹保持得更小。

有无 regularization 时的权重大小增加

第二张图里,要看的是在同样的学习推进中,哪一种解更依赖较大的权重。没有 regularization 的设置中,权重大小持续变大;使用 L2 regularization 的设置中,增长幅度则平缓得多。

比较 现在要读的核心
without_regularization 对训练数据贴合得更强,但验证损失重新上升,权重大小也大幅增加。
with_l2_regularization 如果只看训练损失最低值,会显得不够好;但在验证损失和权重大小上更稳定。

即使在读输出数字时,也需要把误差下降更偏好较不激进的解分开来看。

比较 输出里首先看到的 只看误差时容易留下的解读 把 regularization 一起算进去之后会改变的解读
without_regularization 最后的训练损失最低。 容易把它看成训练得最好的模型。 如果一起看验证损失反弹和较大的权重增加,它可能是过度贴合训练数据的解。
with_l2_regularization 最后的训练损失更高。 容易把它看成故意降低性能的模型。 如果一起看验证损失和权重大小,它是偏好较不激进解、因而更可能在新数据上站得住的设置。

也就是说,在这个例子里,读者真正要抓住的问题不是regularization 会不会阻止训练损失下降,而是在降低训练损失的过程中,它是否也让验证损失和权重大小一起站得住。

regularization 也和深度学习之前的统计学习理论(statistical learning theory)紧密相连。模型一旦过于复杂,就可能在训练数据上拟合得很好,却在泛化上表现更差,这个问题本来就是长期以来的核心主题。

到了深度学习时代,regularization 变得更重要的理由也很清楚。

  • 模型容量(capacity)变得非常大
  • 数据分布里的偏差和噪声问题并没有消失
  • 训练性能高,本身并不能保证模型就一定好

从课程结构上看,这一节放在 optimizer 后面也很自然。

  • 紧前面的 P5-7.1、P5-7.2 讨论的是应该怎样往下降
  • optimizer 负责的是怎样更好地下降
  • regularization 负责的是应该允许下降到什么程度,以及更偏好哪类解

也就是说,这两节都在调整学习,但回答的问题并不一样。

在学习循环里应该把 regularization 放在哪里读

学习进行得很好开始和只是对训练数据拟合得很好混在一起时,就该把这一节拿出来。regularization 不是学习循环外的一种装饰,而是应该和 optimizer 并排放着,但按不同角色来读的一种控制装置。

首先出现的问题场景 为什么此时 regularization 视角更有用 接着会连到哪里
训练性能很高,但验证性能在摇晃 它让我们可以把泛化问题和是不是拟合得更好分开来读 会继续连到 P5-8.2 里通过摇动结构本身的 dropout
optimizer 和 normalization 看起来都像某种调整装置 它能把 update 规则、数值尺度调整、泛化约束这几类问题拆开 之后还要在 P5-8.2、P5-8.3 里进一步看控制位置的差异
大模型似乎过度依赖某一个特征 它能先固定住:应该让哪类解变得不那么受偏好,这种 regularization 直觉 接下来还需要看 penalty 之外的结构型 regularization
数据越少为什么越要小心,看起来不够清楚 它能说明:为什么在小数据场景里,过拟合抑制装置会变得更重要 后面还要继续看 dropout、early stopping 等实践形式

检查清单

  • 能说明 regularization 是一种降低过拟合的视角吗?
  • 能区分 optimizer 和 regularization 回答的是不同问题吗?
  • 能说明 regularization 是通过增加约束或代价来减少过拟合的想法吗?
  • 能说明为什么 optimizer 工作得好,并不意味着泛化会自动变好吗?
  • 能把 regularization 和 normalization 区分成过拟合抑制整理数值尺度和分布吗?
  • 能说明 regularization 不只可以看成 penalty 公式,也可以看成更广的设计哲学吗?
  • 当 optimizer 运转正常但验证性能摇晃时,能先从 regularization 视角想到这是泛化问题吗?
  • 能理解这一节在第 8 章里承担的是目标函数控制,下一节则会转向通过摇动结构的 dropout 吗?

出处与参考资料