首页 > 其他分享 >【机器学习】深入浅出:多项式回归的全解析

【机器学习】深入浅出:多项式回归的全解析

时间:2024-12-06 17:33:03浏览次数:5  
标签:loss plt 多项式 模型 深入浅出 train test 解析 history

深入浅出:多项式回归的全解析


在机器学习与数据科学领域,多项式回归是一种强大的建模工具,用于处理输入变量和输出变量之间的非线性关系。它通过引入高次项特征来拟合复杂的数据模式,成为解决许多非线性问题的重要选择。本文将从多项式回归的基本原理出发,结合理论和实践案例,全面介绍其应用场景、实现方式及优势与挑战。


1. 多项式回归简介

1.1 什么是多项式回归?

多项式回归是线性回归的扩展形式,通过将输入变量的高次幂作为特征,构造出非线性模型。其目标是通过调整模型参数来最小化预测值与实际值之间的误差,从而实现对非线性关系的拟合。

多项式回归的通用形式为:

y = β 0 + β 1 x + β 2 x 2 + ⋯ + β d x d + ϵ y = \beta_0 + \beta_1x + \beta_2x^2 + \cdots + \beta_dx^d + \epsilon y=β0​+β1​x+β2​x2+⋯+βd​xd+ϵ

其中:

  • ( y ):目标变量(响应变量);
  • ( x ):输入特征;
  • ( d ):多项式的阶数;
  • ( \beta_0, \beta_1, \cdots, \beta_d ):回归系数;
  • ( \epsilon ):随机误差。

1.2 应用场景

多项式回归广泛应用于非线性关系建模场景,典型应用包括:

  1. 经济预测:预测商品价格与市场供需的非线性关系。
  2. 工程建模:拟合机械设备的性能曲线。
  3. 医学研究:分析药物剂量和疗效之间的关系。
  4. 自然现象模拟:研究温度变化与海洋潮汐的关联。

1.3 线性回归与多项式回归的区别

维度线性回归多项式回归
模型形式输出是输入特征的线性组合输出是输入特征的多项式组合
适用场景输入和输出变量呈线性关系输入和输出变量呈非线性关系
模型复杂度参数少,模型简单参数多,模型复杂
风险容易欠拟合容易过拟合

2. 多项式回归模型的构建

2.1 多项式回归的基本步骤

  1. 数据准备
    收集并清洗数据,确保其适合进行建模。

  2. 特征转换
    将原始输入特征提升为高次幂,生成多项式特征。例如,对于输入变量 ( x ),构造特征 ( [1, x, x^2, x^3, \dots, x^d] )。

  3. 模型训练
    利用最小二乘法拟合模型参数,最小化实际值和预测值之间的误差。

  4. 模型评估
    通过均方误差(MSE)、决定系数(( R^2 ))等指标评估模型性能。

2.2 特征转换与矩阵表示

多项式回归通过扩展特征空间将非线性问题转化为线性问题。例如,对于输入特征 ( x = [x_1, x_2, \dots, x_n] ):

  • 原始特征矩阵:( X = [x_1, x_2, \dots, x_n] )。
  • 转换后的特征矩阵:( X_{poly} = [1, x, x^2, \dots, x^d] )。

矩阵形式如下:
X p o l y = [ 1 x 1 x 1 2 ⋯ x 1 d 1 x 2 x 2 2 ⋯ x 2 d ⋮ ⋮ ⋮ ⋱ ⋮ 1 x n x n 2 ⋯ x n d ] X_{poly} = \begin{bmatrix} 1 & x_1 & x_1^2 & \cdots & x_1^d \\ 1 & x_2 & x_2^2 & \cdots & x_2^d \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & x_n & x_n^2 & \cdots & x_n^d \end{bmatrix} Xpoly​= ​11⋮1​x1​x2​⋮xn​​x12​x22​⋮xn2​​⋯⋯⋱⋯​x1d​x2d​⋮xnd​​

2.3 多项式阶数的选择

模型阶数 ( d ) 是多项式回归中的重要超参数:

  • 低阶多项式:模型简单,可能欠拟合。
  • 高阶多项式:模型复杂,可能过拟合。

通过交叉验证选择最优阶数是常见的策略。


3. 多项式回归的优缺点

3.1 优势

  1. 适应非线性数据
    能够捕捉输入变量和输出变量之间的复杂关系。

  2. 灵活性高
    随着阶数增加,可以拟合更加复杂的模式。

  3. 易于实现
    在现有的线性回归框架中稍作改动即可实现。

3.2 劣势

  1. 易过拟合
    高阶多项式可能导致模型对噪声过于敏感。

  2. 计算复杂度高
    阶数过高会显著增加模型训练时间。

  3. 可解释性差
    随着阶数增加,模型的可解释性显著降低。


4. 实例解析

4.1 数据拟合实例

假设我们希望预测房屋面积与价格的关系,数据如下:

面积 (m²)价格 (万元)
5080
6095
70110
80125
90140

代码实现

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

# 数据准备
X = np.array([50, 60, 70, 80, 90]).reshape(-1, 1)
y = np.array([80, 95, 110, 125, 140])

# 特征转换
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(X)

# 模型训练
model = LinearRegression()
model.fit(X_poly, y)

# 预测
X_test = np.linspace(40, 100, 100).reshape(-1, 1)
y_pred = model.predict(poly.transform(X_test))

# 可视化
plt.scatter(X, y, color='blue', label='Original Data')
plt.plot(X_test, y_pred, color='red', label='Polynomial Fit')
plt.xlabel('Area (m²)')
plt.ylabel('Price (万元)')
plt.legend()
plt.title('Polynomial Regression Fit')
plt.show()

4.2 模型评估与优化

模型评估

常用的评估指标包括:

  • 均方误差(MSE)
    M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE = \frac{1}{n}\sum_{i=1}^n (y_i - \hat{y}_i)^2 MSE=n1​i=1∑n​(yi​−y^​i​)2

  • 决定系数(( R^2 ))
    R 2 = 1 − ∑ i = 1 n ( y i − y ^ i ) 2 ∑ i = 1 n ( y i − y ˉ ) 2 R^2 = 1 - \frac{\sum_{i=1}^n (y_i - \hat{y}_i)^2}{\sum_{i=1}^n (y_i - \bar{y})^2} R2=1−∑i=1n​(yi​−yˉ​)2∑i=1n​(yi​−y^​i​)2​

正则化的引入

为了防止过拟合,可以使用正则化方法(如 L2 正则化)来约束模型复杂度。


5.生成带有随机噪声的二次曲线数据集

我们将假设数据符合一个特定的二次函数模型,并加入一些随机噪声来模拟现实世界中的数据波动。从而论证在多项式回归中的过拟合、欠拟合、正好情况!

假设我们的模型是:

y = 2 x 2 + 3 x + 4 + ϵ y=2x^2+3x+4+\epsilon y=2x2+3x+4+ϵ

其中 ϵ \epsilon ϵ 是一个随机噪声项,可以看作是正态分布的随机变量,具有零均值和一定的标准差。

数据生成

  • X X X 是从-10到10均匀分布的100个点。
  • n o i s e noise noise 是均值为0,标准差为10的正态分布随机噪声。
  • Y Y Y 是基于二次函数 y = 2 x 2 + 3 x + 4 y=2x^2+3x+4 y=2x2+3x+4 计算的结果加上噪声。

生成数据集的Python代码

import numpy as np
import matplotlib.pyplot as plt

# 设置随机种子以确保结果可复现
np.random.seed(0)

# 定义样本数量
n_samples = 100

# 生成x值,范围从-10到10
X = np.linspace(-10, 10, n_samples)

# 添加随机噪声,这里我们设定噪声的标准差为10
noise = np.random.normal(loc=0.0, scale=10, size=n_samples)

# 根据二次函数模型生成y值
Y = 2 * X**2 + 3 * X + 4 + noise

# 将数据存储在列表中
data = list(zip(X, Y))

# 输出前几个数据点
print("前几个数据点:")
for i in range(5):
    print(f"x: {X[i]}, y: {Y[i]}")

# 绘制数据点
plt.figure(figsize=(10, 6))
plt.scatter(X, Y, label='Data Points', color='blue')
plt.title('Generated Quadratic Data with Noise')
plt.xlabel('X values')
plt.ylabel('Y values')
plt.legend()
plt.grid(True)
plt.show()

# 打印整个数据列表
print("\n完整数据列表:")
print(data)

这段代码首先定义了一个包含100个均匀分布在-10到10之间的 x x x值数组。然后,我们生成了一个正态分布的噪声数组,并将其添加到了按照给定二次函数计算得到的 y y y值上,从而得到带有噪声的 y y y值。

结果

数据可视化

图一:产生的数据

根据图一我们可以看到,我们根据二次函数随机产生了十组数据,同时加入噪声,使得数据分布更真实,但是整体趋势还是符合二次函数图像。接下来我们定义模型进行训练,对这组数据进行拟合。

多项式回归——欠拟合

定义

欠拟合是指模型在训练数据上的表现较差,即模型未能很好地捕捉数据中的基本模式。这种情况通常发生在模型过于简单或参数设置不当的情况下。欠拟合会导致模型在训练数据上的误差较高,从而在新的未知数据上表现不佳。解决欠拟合的方法包括增加模型复杂度、引入更多特征或调整模型参数。

数学原理

线性回归模型试图找到一条直线来拟合数据点。这条直线的方程可以表示为:

y = a x + b y=ax+b y=ax+b

其中:

  • y y y 是预测值
  • x x x 是输入特征
  • a a a 是斜率
  • b b b 是 y y y轴截距。

我们的目标是找到最优的 a a a 和 b b b 值,使得预测值与实际值之间的误差最小。

损失函数

我们使用均方误差(Mean Squared Error, MSE)作为损失函数:

M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE=\frac{1}{n} \sum_{i=1}^n (y_i-\hat{y}_i)^2 MSE=n1​i=1∑n​(yi​−y^​i​)2

其中:

  • n n n 是数据点的数量
  • y i y_i yi​ 是实际值
  • y ^ i \hat{y}_i y^​i​ 是预测值
梯度下降

为了最小化损失函数,我们使用梯度下降算法。梯度下降的更新规则如下:

a ← a − α ∂ M S E ∂ a a \leftarrow a-\alpha \frac{\partial MSE}{\partial a} a←a−α∂a∂MSE​

b ← b − α ∂ M S E ∂ b b \leftarrow b-\alpha \frac{\partial MSE}{\partial b} b←b−α∂b∂MSE​

其中:

  • α \alpha α 是学习率
  • ∂ M S E ∂ a \frac{\partial MSE}{\partial a} ∂a∂MSE​ 和 ∂ M S E ∂ b \frac{\partial MSE}{\partial b} ∂b∂MSE​ 分别是损失函数对 a a a 和 b b b 的偏导数
实现思路
  • 数据准备:
    • 将给定的数据点分为训练集和测试集
    • 训练集用于模型训练,测试集用于评估模型性能
  • 模型定义:
    • 创建一个 LinearModel 类,包含参数 a a a 和 b b b
    • 实现 predict 方法用于预测
    • 实现 loss 方法计算均方误差
    • 实现 gradient_descent 方法更新模型参数
  • 模型训练:
    • 使用梯度下降算法迭代更新模型参数
    • 记录每次迭代的训练损失和测试损失
  • 可视化:
    • 绘制训练损失和测试损失随迭代次数的变化曲线
    • 绘制数据点和模型预测线
  • 分析:
    • 观察损失曲线,评估模型是否收敛
    • 比较训练损失和测试损失,判断是否存在过拟合或欠拟合
    • 观察预测线是否合理拟合数据点

这个实现允许我们直观地观察线性回归模型的训练过程和最终效果,有助于理解模型的性能和潜在的问题。

代码实现
import numpy as np
import matplotlib.pyplot as plt

data_list = [(-10.0, 98.64052345967664), (-7.777777777777778, 49.939843688610495),
             (-5.555555555555555, 30.540466260810476), (-3.333333333333333, 27.853376436459023),
             (-1.1111111111111107, 18.68792558051202), (1.1111111111111107, -5.315988675307321),
             (3.333333333333334, 28.278661953033676), (5.555555555555557, 41.46173655899835),
             (7.777777777777779, 76.01719419811381), (10.0, 125.10598501938372)]

class LinearModel:
    def __init__(self):
        self.a = 0.0
        self.b = 0.0
        self.learning_rate = 0.001

    def predict(self, x):
        """给定输入x,返回模型的预测值"""
        return self.a * x + self.b

    def loss(self, y_true, y_pred):
        """计算预测值与真实值之间的均方误差"""
        return np.mean((y_true - y_pred) ** 2)

    def gradient_descent(self, x, y_true, y_pred):
        """使用梯度下降法更新模型参数"""
        da = -2 * np.mean((y_true - y_pred) * x)
        db = -2 * np.mean(y_true - y_pred)
        self.a -= self.learning_rate * da
        self.b -= self.learning_rate * db

def process_data(data_list):
    """从数据列表中提取x和y值"""
    X = [point[0] for point in data_list]
    Y = [point[1] for point in data_list]
    return np.array(X), np.array(Y)

def train_model(model, X_train, Y_train, X_test, Y_test, epochs):
    """训练模型的函数"""
    train_loss_history = []
    test_loss_history = []
    for epoch in range(epochs):
        y_pred_train = model.predict(X_train)
        current_train_loss = model.loss(Y_train, y_pred_train)
        model.gradient_descent(X_train, Y_train, y_pred_train)

        y_pred_test = model.predict(X_test)
        current_test_loss = model.loss(Y_test, y_pred_test)

        train_loss_history.append(current_train_loss)
        test_loss_history.append(current_test_loss)

        if epoch % 100 == 0:
            print(f"Epoch {epoch}, Train Loss: {current_train_loss}, Test Loss: {current_test_loss}")

    return train_loss_history, test_loss_history

def plot_loss(train_loss_history, test_loss_history):
    """绘制训练集和测试集的损失曲线"""
    plt.figure(figsize=(10, 5))
    plt.plot(train_loss_history, label='Train Loss')
    plt.plot(test_loss_history, label='Test Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.title('Training and Test Loss Over Time')
    plt.legend()
    plt.show()

def plot_data_and_prediction(X_train, Y_train, X_test, Y_test, model):
    """绘制数据点和模型预测线"""
    plt.figure(figsize=(10, 5))
    plt.scatter(X_train, Y_train, color='blue', label='Train Data')
    plt.scatter(X_test, Y_test, color='green', label='Test Data')

    X_all = np.concatenate((X_train, X_test))
    X_plot = np.linspace(X_all.min(), X_all.max(), 100)
    plt.plot(X_plot, model.predict(X_plot), color='red', label='Prediction Line')

    plt.xlabel('X')
    plt.ylabel('Y')
    plt.title('Data and Model Prediction')
    plt.legend()
    plt.show()

def process_and_train(data_list, epochs=1000):
    """处理数据并训练模型"""
    X, Y = process_data(data_list)

    # 分割训练集和测试集
    split = int(0.8 * len(X))
    X_train, X_test = X[:split], X[split:]
    Y_train, Y_test = Y[:split], Y[split:]

    model = LinearModel()
    train_loss_history, test_loss_history = train_model(model, X_train, Y_train, X_test, Y_test, epochs)

    # 绘制损失曲线
    plot_loss(train_loss_history, test_loss_history)

    # 绘制数据点和模型预测线
    plot_data_and_prediction(X_train, Y_train, X_test, Y_test, model)

    return model, train_loss_history, test_loss_history

# 训练模型
model, train_loss_history, test_loss_history = process_and_train(data_list)
结果

欠拟合

图二:欠拟合损失函数图
欠拟合结果

图三:欠拟合模型曲线及数据图

根据图二的图像可以看出,随着训练轮数的增加,模型在训练集和测试集上的损失值一直下降。根据图三结果可以看出,使用一次多项式回归去训练,并不能够完美拟合我们生成的数据,即发生了欠拟合现象。

多项式回归——正好

定义

刚好拟合是指模型既不过于简单也不过于复杂,能够在训练数据上表现良好,并且在新的未知数据上也有较好的泛化能力。这种状态表明模型既没有欠拟合也没有过拟合,能够较好地捕捉数据的基本模式。实现刚好拟合通常需要仔细选择模型复杂度、调整超参数并使用合适的正则化技术。

数学原理

二次多项式回归模型试图用一条抛物线来拟合数据点。这条抛物线的方程可以表示为:

y = a x 2 + b x + c y=ax^2+bx+c y=ax2+bx+c

其中:

  • y y y 是预测值
  • x x x 是输入特征
  • a , b , c a, b, c a,b,c 是模型参数

我们的目标是找到最优的 a , b , c a, b, c a,b,c 值,使得预测值与实际值之间的误差最小。

损失函数

我们使用均方误差(Mean Squared Error, MSE)作为损失函数:

M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE=\frac{1}{n} \sum_{i=1}^n (y_i-\hat{y}_i)^2 MSE=n1​i=1∑n​(yi​−y^​i​)2

梯度下降

为了最小化损失函数,我们使用梯度下降算法。梯度下降的更新规则如下:

a ← a − α ∂ M S E ∂ a a \leftarrow a-\alpha \frac{\partial MSE}{\partial a} a←a−α∂a∂MSE​

b ← b − α ∂ M S E ∂ b b \leftarrow b-\alpha \frac{\partial MSE}{\partial b} b←b−α∂b∂MSE​

c ← c − α ∂ M S E ∂ c c \leftarrow c-\alpha \frac{\partial MSE}{\partial c} c←c−α∂c∂MSE​

实现思路
  • 数据准备:
    • 对输入数据进行最小-最大归一化处理
    • 将归一化后的数据分为训练集和测试集
  • 模型定义:
    • 创建一个 QuadraticModel 类,包含参数 a , b , c a, b, c a,b,c
    • 实现 predict 方法用于预测
    • 实现 loss 方法计算均方误差
    • 实现 gradient_descent 方法更新模型参数
  • 模型训练:
    • 使用梯度下降算法迭代更新模型参数
    • 记录每次迭代的训练损失和测试损失
  • 可视化:
    • 绘制训练损失和测试损失随迭代次数的变化曲线
    • 绘制原始数据点和模型预测曲线
  • 分析:
    • 观察损失曲线,评估模型是否收敛
    • 比较训练损失和测试损失,判断是否存在过拟合或欠拟合
    • 观察预测曲线是否合理拟合数据点
代码实现
import numpy as np
import matplotlib.pyplot as plt

data_list = [(-10.0, 98.64052345967664), (-7.777777777777778, 49.939843688610495),
             (-5.555555555555555, 30.540466260810476), (-3.333333333333333, 27.853376436459023),
             (-1.1111111111111107, 18.68792558051202), (1.1111111111111107, -5.315988675307321),
             (3.333333333333334, 28.278661953033676), (5.555555555555557, 41.46173655899835),
             (7.777777777777779, 76.01719419811381), (10.0, 125.10598501938372)]

class QuadraticModel:
    def __init__(self):
        self.a = 0.0
        self.b = 0.0
        self.c = 0.0
        self.learning_rate = 0.5

    def predict(self, x):
        """给定输入x,返回模型的预测值"""
        return self.a * x ** 2 + self.b * x + self.c

    def loss(self, y_true, y_pred):
        """计算预测值与真实值之间的均方误差"""
        return np.mean((y_true - y_pred) ** 2)

    def gradient_descent(self, x, y_true, y_pred):
        """使用梯度下降法更新模型参数"""
        n = len(x)
        da = -2 * np.sum(x ** 2 * (y_true - y_pred)) / n
        db = -2 * np.sum(x * (y_true - y_pred)) / n
        dc = -2 * np.sum(y_true - y_pred) / n
        self.a -= self.learning_rate * da
        self.b -= self.learning_rate * db
        self.c -= self.learning_rate * dc

def normalize_data(data):
    """对数据进行最小-最大归一化"""
    X, Y = zip(*data)
    X = np.array(X)
    Y = np.array(Y)
    X_min, X_max = X.min(), X.max()
    Y_min, Y_max = Y.min(), Y.max()
    X_normalized = (X - X_min) / (X_max - X_min)
    Y_normalized = (Y - Y_min) / (Y_max - Y_min)
    return X_normalized, Y_normalized, (X_min, X_max), (Y_min, Y_max)

def denormalize_data(normalized_value, min_max):
    """将归一化的数据还原回原始范围"""
    min_val, max_val = min_max
    return normalized_value * (max_val - min_val) + min_val

def train_model(model, X_train, Y_train, X_test, Y_test, epochs):
    """训练模型的函数"""
    train_loss_history = []
    test_loss_history = []
    for epoch in range(epochs):
        y_pred_train = model.predict(X_train)
        current_train_loss = model.loss(Y_train, y_pred_train)
        model.gradient_descent(X_train, Y_train, y_pred_train)

        y_pred_test = model.predict(X_test)
        current_test_loss = model.loss(Y_test, y_pred_test)

        train_loss_history.append(current_train_loss)
        test_loss_history.append(current_test_loss)

        if epoch % 100 == 0:
            print(f"Epoch {epoch}, Train Loss: {current_train_loss}, Test Loss: {current_test_loss}")

    return train_loss_history, test_loss_history

def plot_loss(train_loss_history, test_loss_history):
    """绘制训练集和测试集的损失曲线"""
    plt.figure(figsize=(10, 5))
    plt.plot(train_loss_history, label='Train Loss')
    plt.plot(test_loss_history, label='Test Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.title('Training and Test Loss Over Time')
    plt.legend()
    plt.show()

def plot_data_and_prediction(X_train, Y_train, X_test, Y_test, model, X_original_range, Y_original_range):
    """绘制数据点和模型预测曲线"""
    plt.figure(figsize=(10, 5))

    # 绘制训练数据点
    X_train_original = denormalize_data(X_train, X_original_range)
    Y_train_original = denormalize_data(Y_train, Y_original_range)
    plt.scatter(X_train_original, Y_train_original, color='blue', label='Train Data')

    # 绘制预测曲线
    x_plot = np.linspace(X_original_range[0], X_original_range[1], 1000)
    x_plot_normalized = (x_plot - X_original_range[0]) / (X_original_range[1] - X_original_range[0])
    y_plot_normalized = model.predict(x_plot_normalized)
    y_plot = denormalize_data(y_plot_normalized, Y_original_range)
    plt.plot(x_plot, y_plot, color='red', label='Prediction Curve')

    plt.xlabel('X')
    plt.ylabel('Y')
    plt.title('Data and Model Prediction')
    plt.legend()
    plt.show()

def process_and_train(data_list, epochs=1000):
    """处理数据并训练模型"""
    X, Y, X_original_range, Y_original_range = normalize_data(data_list)

    # 分割训练集和测试集
    split = int(0.8 * len(X))
    X_train, X_test = X[:split], X[split:]
    Y_train, Y_test = Y[:split], Y[split:]

    model = QuadraticModel()
    train_loss_history, test_loss_history = train_model(model, X_train, Y_train, X_test, Y_test, epochs)

    # 绘制损失曲线
    plot_loss(train_loss_history, test_loss_history)

    # 绘制数据点和模型预测曲线
    plot_data_and_prediction(X_train, Y_train, X_test, Y_test, model, X_original_range, Y_original_range)

    return model, train_loss_history, test_loss_history

# 训练模型
model, train_loss_history, test_loss_history = process_and_train(data_list)

这个实现允许我们直观地观察二次多项式回归模型的训练过程和最终效果,有助于理解模型的性能和潜在的问题。通过运行这段代码,我们可以得到损失曲线和数据拟合图,从而分析模型的表现。与线性回归相比,二次多项式回归模型能够捕捉数据中的非线性关系,可能会提供更好的拟合效果。

结果

训练正好损失函数图
图四:训练正好损失函数图
正好模型曲线及数据图

图五:正好模型曲线及数据图

根据图四、图五的结果图,我们可以清晰的看到,二次项回归模型的训练结果可以很好拟合数据集,曲线可以穿过大部分的数据点,这也符合我们的预期。

多项式回归——过拟合

定义

过拟合是指模型在训练数据上表现非常好,但在新的未知数据上表现较差。这是因为模型过度学习了训练数据中的细节和噪声,从而失去了泛化能力。过拟合通常发生在模型过于复杂或训练数据量不足的情况下。解决过拟合的方法包括简化模型、增加正则化项、使用交叉验证或增加训练数据量。

数学原理

多项式回归模型试图用一个高阶多项式来拟合数据点。这个多项式的一般形式为:

y = a n x n + a n − 1 x n − 1 + ⋯ + a 1 x + a 0 y=a_n x^n+a_{n-1} x^{n-1}+\cdots+a_1 x+a_0 y=an​xn+an−1​xn−1+⋯+a1​x+a0​

其中:

  • y y y 是预测值
  • x x x 是输入特征
  • a n , a n − 1 , ⋯   , a 1 , a 0 a_n,a_{n-1},\cdots,a_1,a_0 an​,an−1​,⋯,a1​,a0​ 是模型参数
  • n n n 是多项式的阶数

在这个实现中,我们使用了3阶多项式 ( n = 3 n=3 n=3):

y = a x 3 + b x 2 + c x + d y=ax^3+bx^2+cx+d y=ax3+bx2+cx+d

损失函数

我们使用均方误差(Mean Squared Error, MSE)作为损失函数:

M S E = 1 m ∑ i = 1 m ( y i − y ^ i ) 2 MSE=\frac{1}{m} \sum_{i=1}^m (y_i-\hat{y}_i)^2 MSE=m1​i=1∑m​(yi​−y^​i​)2

梯度下降

为了最小化损失函数,我们使用梯度下降算法。梯度下降的更新规则如下:

a j ← a j − α ∂ M S E ∂ a j a_j \leftarrow a_j-\alpha \frac{\partial MSE}{\partial a_j} aj​←aj​−α∂aj​∂MSE​

其中:

  • α \alpha α 是学习率
  • ∂ M S E ∂ a j \frac{\partial MSE}{\partial a_j} ∂aj​∂MSE​ 是损失函数对参数 a j a_j aj​ 的偏导数
实现思路
  • 数据准备:
    • 对输入数据进行最小-最大归一化处理
    • 将归一化后的数据分为训练集和测试集
  • 模型定义:
    • 创建一个 PolynomialModel 类,包含参数 a , b , c , d a, b, c, d a,b,c,d
    • 实现 predict 方法用于预测
    • 实现 loss 方法计算均方误差
    • 实现 gradient_descent 方法更新模型参数
  • 模型训练:
    • 使用梯度下降算法迭代更新模型参数
    • 记录每次迭代的训练损失和测试损失
  • 可视化:
    • 绘制训练损失和测试损失随迭代次数的变化曲线
    • 绘制原始数据点和模型预测曲线
  • 分析:
    • 观察损失曲线,评估模型是否收敛
    • 比较训练损失和测试损失,判断是否存在过拟合或欠拟合
    • 观察预测曲线是否合理拟合数据点
代码实现
import numpy as np
import matplotlib.pyplot as plt

data_list = [(-10.0, 98.64052345967664), (-7.777777777777778, 49.939843688610495),
             (-5.555555555555555, 30.540466260810476), (-3.333333333333333, 27.853376436459023),
             (-1.1111111111111107, 18.68792558051202), (1.1111111111111107, -5.315988675307321),
             (3.333333333333334, 28.278661953033676), (5.555555555555557, 41.46173655899835),
             (7.777777777777779, 76.01719419811381), (10.0, 125.10598501938372)]

class PolynomialModel:
    def __init__(self):
        self.a = 0.0
        self.b = 0.0
        self.c = 0.0
        self.d = 0.0
        self.learning_rate = 0.01

    def predict(self, x):
        """给定输入x,返回模型的预测值"""
        return self.a * x**3 + self.b * x**2 + self.c * x + self.d

    def loss(self, y_true, y_pred):
        """计算预测值与真实值之间的均方误差"""
        return np.mean((y_true - y_pred) ** 2)

    def gradient_descent(self, x, y_true, y_pred):
        """使用梯度下降法更新模型参数"""
        n = len(x)
        da = -2 * np.sum(x**3 * (y_true - y_pred)) / n
        db = -2 * np.sum(x**2 * (y_true - y_pred)) / n
        dc = -2 * np.sum(x * (y_true - y_pred)) / n
        dd = -2 * np.sum(y_true - y_pred) / n
        self.a -= self.learning_rate * da
        self.b -= self.learning_rate * db
        self.c -= self.learning_rate * dc
        self.d -= self.learning_rate * dd

def normalize_data(data):
    """对数据进行最小-最大归一化"""
    X, Y = zip(*data)
    X = np.array(X)
    Y = np.array(Y)
    X_min, X_max = X.min(), X.max()
    Y_min, Y_max = Y.min(), Y.max()
    X_normalized = (X - X_min) / (X_max - X_min)
    Y_normalized = (Y - Y_min) / (Y_max - Y_min)
    return X_normalized, Y_normalized, (X_min, X_max), (Y_min, Y_max)

def denormalize_data(normalized_value, min_max):
    """将归一化的数据还原回原始范围"""
    min_val, max_val = min_max
    return normalized_value * (max_val - min_val) + min_val

def train_model(model, X_train, Y_train, X_test, Y_test, epochs):
    """训练模型的函数"""
    train_loss_history = []
    test_loss_history = []
    for epoch in range(epochs):
        y_pred_train = model.predict(X_train)
        current_train_loss = model.loss(Y_train, y_pred_train)
        model.gradient_descent(X_train, Y_train, y_pred_train)

        y_pred_test = model.predict(X_test)
        current_test_loss = model.loss(Y_test, y_pred_test)

        train_loss_history.append(current_train_loss)
        test_loss_history.append(current_test_loss)

        if epoch % 100 == 0:
            print(f"Epoch {epoch}, Train Loss: {current_train_loss}, Test Loss: {current_test_loss}")

    return train_loss_history, test_loss_history

def plot_loss(train_loss_history, test_loss_history):
    """绘制训练集和测试集的损失曲线"""
    plt.figure(figsize=(10, 5))
    plt.plot(train_loss_history, label='Train Loss')
    plt.plot(test_loss_history, label='Test Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.title('Training and Test Loss Over Time')
    plt.legend()
    plt.show()

def plot_data_and_prediction(X_train, Y_train, X_test, Y_test, model, X_original_range, Y_original_range):
    """绘制数据点和模型预测曲线"""
    plt.figure(figsize=(10, 5))

    # 绘制训练数据点
    X_train_original = denormalize_data(X_train, X_original_range)
    Y_train_original = denormalize_data(Y_train, Y_original_range)
    plt.scatter(X_train_original, Y_train_original, color='blue', label='Train Data')

    # 绘制测试数据点
    X_test_original = denormalize_data(X_test, X_original_range)
    Y_test_original = denormalize_data(Y_test, Y_original_range)
    plt.scatter(X_test_original, Y_test_original, color='green', label='Test Data')

    # 绘制预测曲线
    x_plot = np.linspace(X_original_range[0], X_original_range[1], 1000)
    x_plot_normalized = (x_plot - X_original_range[0]) / (X_original_range[1] - X_original_range[0])
    y_plot_normalized = model.predict(x_plot_normalized)
    y_plot = denormalize_data(y_plot_normalized, Y_original_range)
    plt.plot(x_plot, y_plot, color='red', label='Prediction Curve')

    plt.xlabel('X')
    plt.ylabel('Y')
    plt.title('Data and Model Prediction')
    plt.legend()
    plt.show()

def process_and_train(data_list, epochs=10000):
    """处理数据并训练模型"""
    X, Y, X_original_range, Y_original_range = normalize_data(data_list)

    # 分割训练集和测试集
    split = int(0.8 * len(X))
    X_train, X_test = X[:split], X[split:]
    Y_train, Y_test = Y[:split], Y[split:]

    model = PolynomialModel()
    train_loss_history, test_loss_history = train_model(model, X_train, Y_train, X_test, Y_test, epochs)

    # 绘制损失曲线
    plot_loss(train_loss_history, test_loss_history)

    # 绘制数据点和模型预测曲线
    plot_data_and_prediction(X_train, Y_train, X_test, Y_test, model, X_original_range, Y_original_range)

    return model, train_loss_history, test_loss_history

# 训练模型
model, train_loss_history, test_loss_history = process_and_train(data_list)

这个实现允许我们直观地观察3阶多项式回归模型的训练过程和最终效果,有助于理解模型的性能和潜在的问题。通过运行这段代码,我们可以得到损失曲线和数据拟合图,从而分析模型的表现。

与线性回归和二次多项式回归相比,3阶多项式回归模型能够捕捉数据中更复杂的非线性关系,可能会提供更好的拟合效果。然而,需要注意的是,高阶多项式也可能导致过拟合,特别是在数据量较少的情况下。因此,在实际应用中,需要仔细权衡模型的复杂度和泛化能力。

结果

过拟合模型损失函数图

图六:过拟合模型损失函数图
过拟合模型曲线及数据图
图七:过拟合模型曲线及数据图

根据图六的结果可以看出,模型在训练过程中,在测试集上的损失值先逐渐下降后逐渐上升,这是非常突出的过拟合的现象。接着我们看图七,训练好的曲线并不能够反应数据真正的分布特征,拟合结果差,这就是典型的过拟合现象。

总结与实践建议

  1. 多项式回归适用于非线性数据建模,通过适当选择阶数,可以在模型复杂度和泛化性能之间找到平衡。

  2. 正则化方法是防止过拟合的有效工具,在高维场景中尤为重要。

  3. 实践中的注意事项

    • 确保数据标准化,以减少特征尺度的影响。
    • 使用交叉验证选择最优模型超参数。
    • 在非线性建模时,结合多项式回归与其他算法(如决策树)可能获得更优的结果。

标签:loss,plt,多项式,模型,深入浅出,train,test,解析,history
From: https://blog.csdn.net/m0_74882984/article/details/144175428

相关文章

  • 智慧园区算法视频分析服务器垃圾桶溢满检测算法:DDNS动态域名解析在安防场景中有哪些应
    随着互联网技术的不断进步,动态域名解析(DDNS)曾经是安防视频监控领域中一个不可或缺的技术。尽管在今天,DDNS的使用频率有所下降,尤其是在P2P技术逐渐普及的背景下,但它仍然在某些场景下发挥着重要作用。本文将带您回顾DDNS的定义、原理以及它在安防领域的应用,帮助您理解这项技术如何......
  • 深入解析 Nginx:基础介绍到原理分析及案例实践
    引言Nginx(“Engine-X”)是一个高性能的HTTP和反向代理服务器,广泛应用于Web服务、负载均衡、API网关、反向代理、静态资源服务器等多种场景。由于其高效的性能、低资源消耗和灵活的配置,Nginx成为众多互联网公司、企业以及开发者的首选。本文将从Nginx的基本介绍入......
  • 【深度学习】深入解析长短期记忆网络(LSTMs)
    长短期记忆网络(LongShort-TermMemorynetworks,LSTMs)是一种特殊的递归神经网络(RNN),专门设计用来解决标准RNN在处理长序列数据时的梯度消失和梯度爆炸问题。LSTMs在许多序列数据任务中表现出色,如自然语言处理、时间序列预测和语音识别等。本文将深入探讨LSTMs的基本......
  • MQ消息乱序问题解析与实战解决方案
    1.背景在分布式系统中,消息队列(MQ)是实现系统解耦、异步通信的重要工具。然而,MQ消费时出现的消息乱序问题,经常会对业务逻辑的正确执行和系统稳定性产生不良影响。本文将详细探讨MQ消息乱序问题的根源,并提供一系列在实际应用中可行的解决方案。2.MQ消息乱序问题分析常见的MQ消息......
  • Java-19 深入浅出 MyBatis - 用到的设计模式 源码剖析 代理设计模式
    点一下关注吧!!!非常感谢!!持续更新!!!大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html目前已经更新到了:MyBatis(正在更新)代理模式概念介绍代理模式(ProxyPattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用,代理模式的英文名字叫Prox......
  • 探索 Vue 的 API 风格:选项式 API 与组合式 API 全解析
    目录探索Vue的API风格:选项式API与组合式API全解析一、VueAPI风格概述(一)选项式API(二)组合式API二、组合式API详细解析(一)使用前提(二)声明响应式变量(三)声明函数(四)声明钩子函数(五)将数据绑定到HTML元素(六)处理事件绑定(七)易错点提醒三、组合式API示例演......
  • Optional的使用与解析
    引言今天在项目中看到了大量Optional的使用,之前我也了解过Optional,是Java8中的新特性,并且便利地为空指针问题提供了处理方法,可以避免繁琐的if/else。但是并没有真正在项目中使用过Optional,现在就来详细地学习一下Optional的用法以及源码实现。构造器方法Optional.of(Tt):......
  • 2023年12月GESPC++二级真题解析
    一、单选题(每题2分,共30分)题目123456789101112131415答案CADDDADCDBCDCBB1.以下不可以做为C++变量的是()。A.FiveStarB.fiveStarC.5StarD.Star5【答案】C【考纲知识点】变量的定义与使用(二级考纲知识点范畴),具体涉及到变量名的命名规则。在C++语言中,变量名有严格......
  • ArrayList源码解析-JDK18
    引言ArrayList在JDK1.7和1.8中的差距并不大,主要差距以下几个方面:JDK1.7在JDK1.7中,使用ArrayListlist=newArrayList()创建List集合时,底层直接创建了长度是10的Object[]数组elementData;在接下来调用add()方法向集合中添加元素时,如果本次的添加导致底层elementData数组容量不......
  • web.xml 中 url-pattern 设置解析
    在web.xml中使用filter-mapping、servlet-mapping节点下的子节点url-pattern配置映射。Pattern匹配规则精确匹配配置和请求的URL完全相同。<servlet-mapping><servlet-name>indexServlet</servlet-name><url-pattern>/index</url-pattern></servlet-mapping......