在前面的学习中,我们已经掌握了线性回归和逻辑回归这两个基本算法。但在实际应用中,我们常常会遇到一个棘手的问题:模型在训练数据上表现很好,但在新数据上表现很差。这就是过拟合(Overfitting)问题,也是机器学习实践中最常见的挑战之一。
想象你在备考,如果只是死记硬背练习题的答案,考试遇到原题就能答对,但遇到新题就不会做。机器学习模型也是如此——如果模型过于复杂,它可能“记住”了训练数据的细节和噪声,而没有学到真正的规律。正则化(Regularization)就是解决这个问题的重要技术。
正则化的核心思想是:在优化目标中增加对模型复杂度的惩罚,迫使模型保持简单,从而提高泛化能力。所以这个部分我们将深入理解过拟合的本质,学习正则化的原理和实现,并将其应用到线性回归和逻辑回归中。
让我们通过一个具体的例子来理解过拟合。假设我们要根据房屋面积预测房价,有6个训练样本。我们可以用不同复杂度的模型来拟合:

过拟合的根源是模型复杂度相对于数据量过高。导致过拟合的常见情况包括:
过拟合和欠拟合是模型复杂度与数据复杂度之间的权衡。理想的模型复杂度恰好能捕捉数据中的真实规律,既不过于简单(欠拟合),也不过于复杂(过拟合)。这个平衡点被称为偏差-方差权衡(Bias-Variance Tradeoff)。
如何发现过拟合?关键是在独立的测试集上评估模型。如果训练误差很小但测试误差很大,说明模型过拟合了。我们会在后续章节详细讨论如何评估模型和诊断问题。
解决过拟合有几种策略:
正则化的基本思想是:在原来的代价函数上加一个惩罚项,惩罚过大的参数值。
对于线性回归,正则化后的代价函数是:
前半部分是原来的均方误差,衡量模型对训练数据的拟合程度。后半部分是正则化项(也叫惩罚项),是所有参数(除了截距 )的平方和。 是正则化参数,控制正则化的强度。
理解这个公式:优化算法要最小化这个代价函数,就需要在两个目标之间权衡:一是拟合训练数据(第一项小),二是保持参数值小(第二项小)。 决定了这两个目标的相对重要性。
注意我们通常不惩罚截距项 ,因为它只是调整预测的整体水平,不影响模型的复杂度。
为什么惩罚参数的平方和能够减少过拟合?直观地说,较大的参数值会让模型对输入的变化很敏感,产生剧烈波动的预测函数(想象高次多项式的扭曲曲线)。通过限制参数大小,我们让模型变得"平滑",对噪声不那么敏感。
从另一个角度看,正则化相当于给参数施加了先验约束:"我们倾向于相信真实的参数值不会太大"。这种贝叶斯观点在统计学习理论中有深刻的根基。
将正则化应用到线性回归,我们需要修改梯度下降的更新规则。正则化代价函数对参数的梯度是:
注意 的梯度没有正则化项,其他参数的梯度多了一项 。
梯度下降的更新规则变为:
将后者改写一下:
这个形式很有启发性:每次更新, 首先乘以一个小于1的数(),即先"收缩"一点,然后再按照梯度调整。这就是为什么正则化有时也叫权重衰减(Weight Decay)。
实现代码:
|def computeCostRegularized(X, y, theta, lambda_reg): """计算正则化线性回归的代价函数""" m = len(y) h = X @ theta # 不惩罚theta[0] reg_term = (lambda_reg / (2*m)) * np.sum(theta[1:]**2) cost = (1/(2*m)) * np.sum((h -
正则化也适用于正规方程。正则化的正规方程解是:
其中 是一个 的矩阵,除了左上角的元素是0,对角线上其他元素都是1,其余元素都是0:
这个公式还有一个额外的好处:即使 不可逆(比如特征数多于样本数),只要 , 就是可逆的。所以正则化不仅能防止过拟合,还能解决矩阵不可逆的问题。
选择合适的 是正则化的关键。常用的方法是交叉验证:尝试一系列 值(比如0.01, 0.03, 0.1, 0.3, 1, 3, 10),在验证集上评估性能,选择表现最好的那个。我们会在后续学习详细讨论模型选择和交叉验证。
正则化同样适用于逻辑回归。正则化的代价函数是:
前半部分是原来的逻辑回归代价函数,后半部分是正则化项。
梯度的计算与线性回归完全类似:
注意虽然梯度的形式和线性回归一样,但 的定义不同——这里是 。
实现代码:
|def sigmoid(z): return 1 / (1 + np.exp(-z)) def computeCostRegularizedLogistic(X, y, theta, lambda_reg): """计算正则化逻辑回归的代价函数""" m = len(y) h = sigmoid(X @ theta) epsilon = 1e-10 # 逻辑回归的代价 cost = (-1/m) * (y.T
正则化逻辑回归在实践中非常常用。很多机器学习库(如scikit-learn)默认都会使用正则化。在使用这些库时,你会看到一个参数C,它是 的关系—— 越小,正则化越强。
到目前为止我们讨论的正则化使用参数的平方和作为惩罚,这称为L2正则化(也叫岭回归,Ridge Regression)。还有另一种常用的正则化方式,称为L1正则化(也叫Lasso)。
L1正则化使用参数绝对值的和作为惩罚:
L1和L2正则化有不同的特点:
L2正则化(Ridge):
L1正则化(Lasso):
L1正则化的特征选择性质很有用。如果我们有上千个特征但只有少数真正有用,L1正则化可以自动找出这些重要特征,把其他特征的系数设为0。这在高维数据分析中很有价值。
还有一种结合两者的方法,称为弹性网络(Elastic Net):
弹性网络同时具有L1的特征选择性和L2的稳定性。
在实践中如何选择:
为什么正则化能防止过拟合?让我们从几个角度理解:

正则化是机器学习实践中最重要的技术之一。几乎所有现代机器学习算法都有某种形式的正则化。在深度学习中,除了L1/L2正则化,还有Dropout、Batch Normalization等其他正则化技术。但核心思想是一致的:控制模型复杂度,提高泛化能力。
在下一节课程中,我们将学习神经网络的基本结构。神经网络是更强大的模型,可以学习复杂的非线性关系。但同时,神经网络也更容易过拟合,正则化在神经网络训练中扮演着关键角色。我们会看到,逻辑回归实际上是最简单的神经网络。
理解正则化参数的作用:考虑一个多项式回归模型,数据有10个样本。你尝试了不同的正则化参数 ,得到以下结果:
分析每个 值对应的模型状态(欠拟合/过拟合/合适),并选择最佳的 值。
答案:
分析各个 值的状态:
(无正则化):
(轻度正则化):
(适度正则化):
实现L2正则化的梯度:给定正则化的代价函数,推导并实现正则化线性回归的梯度。
正则化代价函数:
答案:
梯度推导:
对 求偏导(不正则化):
(过度正则化):
最佳选择:
原因:
正则化参数选择的一般规律:
|λ 太小 → 过拟合 → 训练误差很低,验证误差很高 λ 适中 → 最优 → 验证误差最低 λ 太大 → 欠拟合 → 训练误差和验证误差都很高
Python实现参数选择:
|import numpy as np import matplotlib.pyplot as plt # 尝试不同的lambda值 lambdas = [0, 0.1, 1, 10, 100, 1000] train_errors = [] val_errors = [] for lam in lambdas: # 训练模型(伪代码) model = train_with_regularization(X_train, y_train, lambda=lam) # 计算误差 train_err = compute_error(model, X_train, y_train) val_err = compute_error(model, X_val, y_val) train_errors.append(train_err) val_errors.append(val_err) # 绘制学习曲线 plt.plot(lambdas, train_errors, label='训练误差') plt.plot(lambdas, val_errors, label='验证误差') plt.xscale('log') plt.xlabel('正则化参数 λ') plt.ylabel('误差') plt.legend() plt.show() # 选择最佳lambda best_lambda = lambdas[np.argmin(val_errors)] print(f"最佳λ: {best_lambda}")
实践建议:
注意: 不参与正则化。
推导梯度表达式,并用Python实现。
对 () 求偏导(需要正则化):
向量化形式:
设 是 的设计矩阵, 是 的参数向量。
完整的梯度向量:
或者写成:
Python实现:
|import numpy as np def compute_cost_regularized(X, y, theta, lambda_reg): """ 计算正则化的代价函数 """ m = len(y) # 预测值 h = X @ theta # 误差 errors = h - y # 基础代价(MSE) cost_mse = (1 / (2*m)) * (errors.T @ errors) # 正则化项(不包括theta[0]) reg_term = (lambda_reg / (2*m)) * (theta[1:].T @ theta[1:]) # 总代价 cost = cost_mse + reg_term return cost def compute_gradient_regularized(X, y, theta, lambda_reg): """ 计算正则化的梯度 """ m = len(y) # 预测值 h = X @ theta # 误差 errors = h - y # 基础梯度 gradient = (1/m) * (X.T @ errors) # 正则化项(theta[0]不正则化) reg_term = (lambda_reg/m) * theta reg_term[0] = 0 # 不正则化theta_0 # 总梯度 gradient = gradient + reg_term return gradient def gradient_descent_regularized(X, y, theta, alpha, lambda_reg, num_iters): """ 正则化梯度下降 """ m = len(y) J_history = [] for i in range(num_iters): # 计算梯度 gradient = compute_gradient_regularized(X, y, theta, lambda_reg) # 更新参数 theta = theta - alpha * gradient # 记录代价 cost = compute_cost_regularized(X, y, theta, lambda_reg) J_history.append(cost) return theta, J_history # 测试示例 np.random.seed(42) m, n = 100, 5 X = np.random.randn(m, n) X = np.hstack([np.ones((m, 1)), X]) # 添加偏置列 true_theta = np.array([1, 2, -1, 0.5, 0, -0.5]) y = X @ true_theta + np.random.randn(m) * 0.1 # 初始化参数 theta_init = np.zeros(n + 1) # 训练(有正则化) theta_reg, J_history_reg = gradient_descent_regularized( X, y, theta_init.copy(), alpha=0.01, lambda_reg=1.0, num_iters=1000 ) # 训练(无正则化) theta_no_reg, J_history_no_reg = gradient_descent_regularized( X, y, theta_init.copy(), alpha=0.01, lambda_reg=0.0, num_iters=1000 ) print("真实参数:", true_theta) print("正则化参数:", theta_reg) print("无正则化参数:", theta_no_reg)
关键点:
正则化的效果: