一、什么是拟合(Fit)
拟合 = 让模型学会数据里的规律。
- 欠拟合(Underfitting)
模型太简单,连基本趋势都没学会。 - 过拟合(Overfitting)
模型太复杂,把训练数据的噪声都记住了,导致新数据表现很差。
欠拟合 = 学得太少
过拟合 = 学得太死
二、直观理解(用曲线拟合作类比)
假设真实曲线很复杂(例如 S 型曲线):
- 欠拟合(模型太简单) :
用一条直线去拟合 S 型 → 明显不行。 - 正常拟合:
用一个适度的多项式 → 刚好贴近真实趋势。 - 过拟合(模型太复杂) :
用 15 次多项式逼近 10 个点 → 训练误差小,但曲线左右乱跳。
三、经典 Python 代码示例(Polynomial Regression)
以下示例用正弦波 + 噪声的数据来演示:
- degree = 1 → 欠拟合
- degree = 4 → 正常
- degree = 15 → 过拟合
📦 依赖
pip install scikit-learn matplotlib numpy
🧪 完整代码
# 导入必要的Python库
# numpy: 用于数值计算的基础库
# matplotlib.pyplot: 用于数据可视化的绘图库
# PolynomialFeatures: scikit-learn中的多项式特征生成器
# LinearRegression: scikit-learn中的线性回归模型
# mean_squared_error: scikit-learn中的均方误差计算函数
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# ==================== 数据生成阶段 ====================
# 设置随机种子为0,确保每次运行程序时生成的随机数序列相同,使实验结果可重现
np.random.seed(0)
# 在[0, 2π]区间内生成30个均匀分布的点,作为输入特征X
# np.linspace(start, stop, num): 在指定区间内生成指定数量的等间距点
X = np.linspace(0, 2 * np.pi, 30)
# 生成目标变量y:基于正弦函数并添加高斯白噪声
# np.sin(X): 计算X中每个元素的正弦值,构成基本的正弦波形
# np.random.randn(30): 生成30个服从标准正态分布(均值为0,标准差为1)的随机数
# 0.2 * np.random.randn(30): 将标准正态分布的随机数缩放0.2倍,作为噪声项
# 最终y是真实的正弦函数值加上小幅度的随机噪声,模拟现实世界中的测量数据
y = np.sin(X) + 0.2 * np.random.randn(30)
# 将一维数组X重塑为二维数组,每行一个样本,每列一个特征
# reshape(-1, 1): -1表示自动推断该维度的大小,1表示每行只有一个特征
# 这是scikit-learn算法要求的数据格式
X = X.reshape(-1, 1)
# ==================== 定义拟合和绘图函数 ====================
def fit_and_plot(degree, subplot):
"""
使用指定degree阶的多项式对数据进行拟合并可视化结果
参数说明:
degree: int类型,表示多项式的最高次数
subplot: int类型,表示在图形中的子图位置(1, 2, 或 3)
"""
# 创建多项式特征转换器对象,指定多项式的最高次数
# PolynomialFeatures(degree=d)会将原始特征x转换为[1, x, x^2, ..., x^d]的特征向量
poly = PolynomialFeatures(degree=degree)
# 对原始特征X进行多项式特征转换
# fit_transform方法首先学习数据的统计特性,然后执行特征转换
# X_poly是一个二维数组,形状为[n_samples, degree+1]
X_poly = poly.fit_transform(X)
# 创建线性回归模型对象
# LinearRegression实现了普通最小二乘法线性回归
model = LinearRegression()
# 使用多项式特征X_poly和目标值y训练线性回归模型
# 模型会学习到一个线性组合的权重系数,使得预测值尽可能接近真实值
model.fit(X_poly, y)
# ==================== 模型预测阶段 ====================
# 生成更密集的测试数据点用于绘制平滑的拟合曲线
# 在[0, 2π]区间内生成200个点,比原始数据点更密集
X_test = np.linspace(0, 2 * np.pi, 200).reshape(-1, 1)
# 使用之前训练好的多项式特征转换器对测试数据进行特征转换
# 注意这里使用的是transform而不是fit_transform,因为我们不需要重新学习统计特性
X_test_poly = poly.transform(X_test)
# 使用训练好的线性回归模型对测试数据进行预测
# predict方法返回模型对输入特征的预测值
y_pred = model.predict(X_test_poly)
# ==================== 结果可视化阶段 ====================
# 在指定的子图位置激活绘图区域
# subplot(1, 3, subplot)表示创建1行3列的子图布局,subplot指定当前操作的子图索引
plt.subplot(1, 3, subplot)
# 绘制原始数据的散点图
# scatter方法用于绘制散点图,X是横坐标,y是纵坐标
# color="black"指定散点颜色为黑色,label="data"设置图例标签
plt.scatter(X, y, color="black", label="data")
# 绘制模型的预测曲线
# plot方法用于绘制连续曲线,X_test是横坐标,y_pred是纵坐标
# label=f"degree={degree}"使用f-string格式化字符串设置图例标签
plt.plot(X_test, y_pred, label=f"degree={degree}")
# 设置当前子图的标题
# f"degree={degree}"使用f-string格式化字符串显示多项式的阶数
plt.title(f"degree={degree}")
# 显示图例,自动使用之前设置的label参数
plt.legend()
# ==================== 创建图形窗口 ====================
# 创建一个新的图形窗口并设置图形尺寸
# figsize=(15, 4)指定图形的宽度为15英寸,高度为4英寸
plt.figure(figsize=(15, 4))
# ==================== 分别展示三种拟合情况 ====================
# 第一幅子图:欠拟合情况 - 使用1次多项式(线性模型)
# 1次多项式只能拟合直线,对于非线性的正弦函数会产生较大的偏差
# subplot参数为1,表示在第一幅子图中绘制结果
fit_and_plot(1, 1)
# 第二幅子图:良好拟合情况 - 使用4次多项式
# 4次多项式具有足够的灵活性来逼近正弦函数的形状
# subplot参数为2,表示在第二幅子图中绘制结果
fit_and_plot(4, 2)
# 第三幅子图:过拟合情况 - 使用15次多项式
# 15次多项式过于灵活,会过度拟合训练数据中的噪声
# subplot参数为3,表示在第三幅子图中绘制结果
fit_and_plot(15, 3)
# 显示所有绘制的图形
# show方法将图形渲染到屏幕上
plt.show()
效果
四、训练效果对比
| 模型复杂度 | 结果 | 原因 |
|---|---|---|
| degree = 1 | 欠拟合 | 模型太简单 |
| degree = 4 | 正常拟合 | 复杂度刚好 |
| degree = 15 | 过拟合 | 学到了噪声 |
五、工程实践中如何避免过拟合?
- 降低模型复杂度(减少多项式次数、降低树深度等)
- 正则化 L1/L2
- 交叉验证(k-fold)
- 增加数据量
- 早停法(Early stopping,神经网络常用)
- Dropout(深度学习)