Skip to content

欠拟合、过拟合和正则化

欠拟合(Underfitting)

在机器学习中,欠拟合是指模型在训练数据上就表现得很差,更别说在测试数据上了。模型没有学到数据的模式和规律,可能是因为模型太简单,或者训练数据不足

解决方法

  1. 增加模型复杂度:如果模型太简单(比如用线性模型去解决复杂问题),就换一个更复杂的模型(比如用深度神经网络)
  2. 增加训练时间:让模型多学习一会儿,增加迭代次数,确保它有足够的时间去捕捉数据的模式
  3. 增加特征:给模型提供更多有用的信息,使用多项式或者增加参数
  4. 减少正则化:如果正则化太强,可能会限制模型的学习能力,可以适当放松约束
  5. 获取更多数据:如果数据量太少,模型可能学不到足够的模式,增加数据集大小

过拟合(Overfitting)

在机器学习中,过拟合是指模型在训练数据上表现得非常好,但是在新的、没见过的数据(测试数据)上表现很差。换句话说,模型过于“记住”了训练数据的细节和噪声,而没有学到数据的通用规律,一旦跳出训练集,对实际的数据预测效果很差

  1. 增加数据量:模型有了更多样化的训练数据,就不容易只记住某一类数据的细节。
  2. 数据增强:如果数据不够多,可以对现有数据做一些变化(比如旋转、缩放图片)
  3. 简化模型:如果模型太复杂(比如用了很深的神经网络),就容易记住细节。可以减少模型的参数或层数
  4. 正则化:给模型加一些“约束”,比如 L1/L2 正则化(有点像罚款),防止模型过于关注某些特定的特征,对应一些参数减小其数据量
  5. Dropout(随机丢弃):在训练神经网络时,随机“关掉”一些神经元,防止模型过于依赖某些特定的神经元
  6. 早停(Early Stopping):在训练过程中,如果发现模型在验证数据上的表现不再提升,就停止训练,避免模型继续“死记硬背”训练数据

正则化

在机器学习中,正则化通过对模型的复杂度施加“惩罚”,限制模型的参数(比如权重)变得过大,从而让模型更简单、更通用,避免它过于“记住”训练数据的细节和噪声

如何进行正则化

正则化的核心思想是在模型的损失函数(Loss Function)中加入一个“惩罚项”(Penalty Term)。损失函数是用来衡量模型预测结果和真实值之间差距的,而惩罚项的作用是限制模型的复杂度

模型训练时有两个目标:

  1. 尽量让预测解决接近真实值(减少误差)
  2. 尽量让模型简单,不要过于复杂(通过惩罚项实现)

常见的正则化方法有 L1 正则化L2 正则化

L1 正则化

  • 惩罚方式:对模型参数(权重 wj)的绝对值之和进行惩罚
  • 数学表达式:在损失函数中加上一个项:λ|wi|,其中 wi 是模型的权重,λ 是惩罚的强度
  • 效果:L1 正则化会将一些不重要的权重变得接近于 0,相当于砍掉一些特征的影响
  • 适用场景:模型参数很多,但是不知道要砍掉那些不必要的参数

原始损失函数(均方误差):

J(w,b)=1ni=1n(yiyi^)2

其中 yi 为真实值,yi^=w1x1i+w2x2i+b 为预测值,n 为样本数量

加入 L1 正则化后的损失函数:

J(w,b)+1ni=1n(yi+yi^)2+λ(|w1|+|w2|)

使用 Python 来展示一下线性回归的 L1 正则化:

python
import numpy as np
import matplotlib.pyplot as plt

# matplotlib.rcParams['font.sans-serif'] = ['Arial Unicode MS']  # macOS 可选

# 设置随机种子以保证结果可重复
np.random.seed(42)

# 1. 生成模拟数据
def generate_data(n_samples=100):
    """
    生成一个简单的线性数据集:y = 2 * x + 1 + 噪声
    """
    X = np.random.rand(n_samples, 1) * 10  # 随机生成 0 到 10 的 x 值
    y = 2 * X + 1 + np.random.normal(0, 1, (n_samples, 1))  # 线性关系 + 噪声
    return X, y

X, y = generate_data()

# 2. 定义带 L1 正则化的线性回归类
class LassoRegression:
    def __init__(self, learning_rate=0.01, lambda_reg=0.1, n_iterations=1000):
        self.learning_rate = learning_rate  # 学习率
        self.lambda_reg = lambda_reg        # L1 正则化惩罚系数
        self.n_iterations = n_iterations    # 迭代次数
        self.weights = None                 # 权重
        self.bias = None                    # 偏置
        self.loss_history = []              # 记录损失变化

    def fit(self, X, y):
        """
        训练模型,使用梯度下降法优化参数
        """
        n_samples, n_features = X.shape
        
        # 初始化权重和偏置为 0
        self.weights = np.zeros((n_features, 1))
        self.bias = 0
        
        # 梯度下降迭代
        for _ in range(self.n_iterations):
            # 预测值
            y_pred = np.dot(X, self.weights) + self.bias
            
            # 计算损失(均方误差 + L1 正则化项)
            mse_loss = (1 / n_samples) * np.sum((y_pred - y) ** 2)
            l1_loss = self.lambda_reg * np.sum(np.abs(self.weights))
            total_loss = mse_loss + l1_loss
            self.loss_history.append(total_loss)
            
            # 计算梯度
            dw = (2 / n_samples) * np.dot(X.T, (y_pred - y))  # 均方误差部分的梯度
            dw += self.lambda_reg * np.sign(self.weights)      # L1 正则化部分的梯度
            db = (2 / n_samples) * np.sum(y_pred - y)          # 偏置的梯度
            
            # 更新参数
            self.weights -= self.learning_rate * dw
            self.bias -= self.learning_rate * db
    
    def predict(self, X):
        """
        使用训练好的模型进行预测
        """
        return np.dot(X, self.weights) + self.bias

# 3. 训练模型
model = LassoRegression(learning_rate=0.01, lambda_reg=0.1, n_iterations=1000)
model.fit(X, y)

# 4. 预测并可视化结果
y_pred = model.predict(X)

plt.scatter(X, y, color='blue', label='数据点', alpha=0.5)
plt.plot(X, y_pred, color='red', label='拟合直线')
plt.xlabel('X')
plt.ylabel('y')
plt.title('L1 正则化线性回归结果')
plt.legend()
plt.show()

# 5. 绘制损失变化曲线
plt.plot(model.loss_history, color='green', label='损失变化')
plt.xlabel('迭代次数')
plt.ylabel('损失值')
plt.title('训练过程中的损失变化')
plt.legend()
plt.show()

# 打印最终的权重和偏置
print("训练后的权重:", model.weights)
print("训练后的偏置:", model.bias)

除了损失函数和梯度计算的变化,正则化对线性回归的其他部分几乎没有影响:

python
# 计算损失(均方误差 + L1 正则化项)
            mse_loss = (1 / n_samples) * np.sum((y_pred - y) ** 2)
            l1_loss = self.lambda_reg * np.sum(np.abs(self.weights))
            total_loss = mse_loss + l1_loss
            self.loss_history.append(total_loss)

在这里加上了正则化的损失

下面就要开始计算梯度了

python
# 计算梯度
            dw = (2 / n_samples) * np.dot(X.T, (y_pred - y))  # 均方误差部分的梯度
            dw += self.lambda_reg * np.sign(self.weights)      # L1 正则化部分的梯度
            db = (2 / n_samples) * np.sum(y_pred - y)          # 偏置的梯度

其中,dw 的梯度由原本的均方误差和 L1 部分的梯度共同组成:

dw = MSE部分 + lambda_reg * np.sign(weights)

最后更新参数:

python
# 更新参数
            self.weights -= self.learning_rate * dw
            self.bias -= self.learning_rate * db

最后更新于:

Released under the MIT License.