1. 从零开始:为什么你需要掌握多项式拟合?
如果你刚开始接触机器学习或者数据分析,可能会被那些听起来高大上的算法名字吓到。别担心,今天咱们聊的“多项式拟合”,其实是你入门路上最好的一块敲门砖。说白了,它就是一种用曲线去“描”数据点的方法。想象一下,你有一堆散落在纸上的点,你想画一条光滑的曲线,让它尽可能地从这些点中间穿过去,这条曲线就是“拟合”出来的。这条曲线如果是一个多项式(比如 y = a + bx + cx²),那这个过程就叫多项式拟合。
我刚开始学的时候,也觉得这玩意儿有啥用?后来在真实项目里踩过坑才发现,它的应用场景多到离谱。比如,你想预测一个产品未来几个月的销量趋势,手头只有过去一年的月度数据;或者,你在分析实验数据,想找到一个公式来描述温度和时间的关系。这些时候,一个简单的线性公式(一条直线)可能根本描述不了数据的弯曲变化,而多项式拟合就能派上大用场。它比那些复杂的深度学习模型要轻量、好理解得多,而且计算速度快,结果也直观,特别适合做快速原型验证和趋势分析。
那么,为什么我们要同时学 Numpy 和 Torch 两种实现方式呢?这就像你工具箱里得有螺丝刀和扳手一样,各有各的适用场景。Numpy 是 Python 科学计算的基石,它的实现方式非常直接,一两行代码就能出结果,适合快速验证想法、进行数据分析的前期探索。而 PyTorch 是当下深度学习领域的主流框架,用它来实现多项式拟合,看似“杀鸡用牛刀”,但实际上,这是你理解机器学习训练流程(准备数据、定义模型、计算损失、反向传播、更新参数)的绝佳起点。通过这个简单的例子,你能把深度学习的核心套路摸得一清二楚,以后再学复杂的神经网络,心里就有底了。
所以,这篇指南的目的,就是手把手带你用这两种工具,走完从生成数据、构建模型、训练优化到评估可视化的完整闭环。我会分享我实际编码时觉得好用的技巧,也会指出一些新手容易掉进去的坑。咱们不搞纯理论,就讲怎么用代码把它实实在在地做出来。
2. 用 Numpy 实现:五分钟搞定你的第一条拟合曲线
好了,理论不多说,咱们直接上手。用 Numpy 做多项式拟合,可以说是简单到令人发指。它的核心函数就是 np.polyfit,你几乎不用关心背后的数学(最小二乘法)是怎么算的,它帮你全包了。
2.1 核心三件套:生成、拟合、画图
我们先来复现并深入理解一下原始文章里的那个例子。这个过程我称之为“核心三件套”。
import numpy as np
import matplotlib.pyplot as plt
# 1. 生成模拟数据
np.random.seed(42) # 设置随机种子,确保每次运行结果一致,这是非常重要的调试习惯
x = np.sort(np.random.rand(100)) # 生成100个0到1之间的随机数,并排序
# 我们的目标是一条有起伏的曲线,这里用正弦函数加噪声来模拟真实世界的数据
y = np.sin(2 * np.pi * x) + 0.3 * np.random.randn(100)
# 2. 执行拟合 - 最关键的一行代码
# np.polyfit(x, y, 3) 的意思是:用3次多项式来拟合x和y的数据
# 它返回的是多项式各项的系数,从高次项到低次项
coefficients = np.polyfit(x, y, 3)
print(f"拟合得到的三次多项式系数为: {coefficients}")
# 输出可能类似:[ 10.2 -15.8 6.1 -0.2],对应 a*x^3 + b*x^2 + c*x + d
# 用np.poly1d将系数数组变成一个可调用的“函数”,方便后续计算y值
p = np.poly1d(coefficients)
# 3. 可视化对比
x_plot = np.linspace(0, 1, 200) # 为了画出光滑曲线,生成更密集的x值
plt.figure(figsize=(10, 6))
plt.scatter(x, y, alpha=0.6, label='原始数据 (含噪声)', color='blue')
plt.plot(x_plot, p(x_plot), 'r-', linewidth=2, label='三次多项式拟合曲线')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Numpy 多项式拟合演示')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.5)
plt.show()
跑完这段代码,你就能看到一张图,蓝色的散点是我们的原始数据,那条红色的光滑曲线就是拟合出来的三次多项式。怎么样,是不是感觉数据背后的“趋势”一下子就被抓住了?这里我想强调一个细节:np.sort 对x排序。这不是必须的,但排序后的x在画图时能保证曲线是连贯的,而不是来回穿梭的折线。这个小技巧能让你的可视化结果更专业。
2.2 深入参数:不只是换个次数那么简单
原始文章里只用了3次拟合,但 np.polyfit 的魅力在于你可以轻松尝试不同的多项式次数。这里就涉及到一个核心问题:到底选几次多项式合适?
# 尝试不同的多项式次数
degrees = [1, 3, 5, 10] # 分别尝试1次(线性),3次,5次,10次
plt.figure(figsize=(12, 10))
for i, deg in enumerate(degrees):
coeffs = np.polyfit(x, y, deg)
p_func = np.poly1d(coeffs)
plt.subplot(2, 2, i+1)
plt.scatter(x, y, alpha=0.3, s=20)
plt.plot(x_plot, p_func(x_plot), 'r-', linewidth=2)
plt.title(f'多项式次数 = {deg}')
plt.grid(True, linestyle='--', alpha=0.3)
# 计算并显示R-squared,这是一个衡量拟合好坏的常用指标
# 残差平方和
residuals = y - p_func(x)
ss_res = np.sum(residuals**2)
# 总平方和
ss_tot = np.sum((y - np.mean(y))**2)
r_squared = 1 - (ss_res / ss_tot)
plt.text(0.05, 0.9, f'$R^2$ = {r_squared:.4f}', transform=plt.gca().transAxes, fontsize=10)
plt.tight_layout()
plt.show()
运行这段代码,你会看到四张子图。可以明显观察到:
- 1次(线性):曲线太简单,很多数据点的波动都没捕捉到,这就是典型的“欠拟合”。
- 3次和5次:曲线弯曲程度适中,既跟随了数据的整体趋势,又没有过分扭曲,看起来比较“舒服”。
- 10次:曲线变得非常“崎岖”,几乎要穿过每一个数据点。它在训练数据上可能误差极小,但如果我在0到1之间新生成一些数据点,这条“崎岖”的曲线预测效果很可能很差。这就是“过拟合”。
所以,选择多项式次数是一个权衡。次数太低,模型太“笨”,学不到规律(欠拟合);次数太高,模型太“敏感”,连数据里的噪声都学会了(过拟合)。在实际项目中,我通常会画一个类似上面的对比图,并结合R平方等指标,选择一个在验证集上表现最好的次数。
2.3 实战技巧与避坑指南
用 Numpy 做拟合虽然简单,但也有一些小坑。这里分享几个我总结的实用技巧:
-
数据标准化:如果你的x值范围很大(比如从0到1000),直接拟合可能会导致数值计算不稳定(系数非常大或非常小)。一个很好的习惯是先对x进行标准化处理:
x_mean, x_std = np.mean(x), np.std(x) x_normalized = (x - x_mean) / x_std # 用标准化后的x进行拟合 coeffs_normalized = np.polyfit(x_normalized, y, 3) # 预测时,也需要先将新x值标准化,再用得到的多项式计算这样做能让拟合过程更稳定,系数也更容易解释。
-
评估拟合质量:不要只看图,要量化。除了上面代码中提到的R平方,还可以计算均方误差(MSE)或平均绝对误差(MAE)。
from sklearn.metrics import mean_squared_error, mean_absolute_error y_pred = p(x) mse = mean_squared_error(y, y_pred) mae = mean_absolute_error(y, y_pred) print(f"MSE: {mse:.4f}, MAE: {mae:.4f}")这些指标能给你一个更精确的判断。
-
外推的风险:这是多项式拟合的一个大忌!你拟合的曲线只在你的数据范围(比如x在0到1之间)内是可靠的。千万不要用它去预测远超出这个范围的值。例如,用0到1的数据拟合出的曲线,去预测x=10时的y值,结果往往会非常荒谬,因为多项式在边界外会急剧上升或下降。
Numpy 的方式快、准、狠,适合数据分析的初期。但如果你想真正理解机器学习模型是如何“学习”的,那就必须进入 PyTorch 的世界。
3. 用 PyTorch 实现:亲手搭建一个“学习机器”
用 PyTorch 做多项式拟合,我们就不再是简单地调用一个函数了。我们需要自己搭建一个微型的“机器学习系统”,明确地告诉计算机:这是数据,这是模型结构,这是衡量好坏的标准(损失函数),这是学习的方法(优化器)。这个过程能让你透彻理解深度学习训练的每一个环节。
3.1 重新定义问题与准备数据
在 Numpy 的例子中,我们的模型是 y = a + b*x + c*x² + d*x³。在 PyTorch 里,我们要自己构建这个计算图。一个巧妙的方法是:把多项式拟合看作一个特殊的线性回归。
对于三次多项式拟合,我们真正的模型是:
y = w0 * 1 + w1 * x + w2 * x² + w3 * x³
你看,如果我们把 1, x, x², x³ 看成是四个不同的“特征”,那么这个问题就变成了:寻找一组权重 [w0, w1, w2, w3],使得这组特征和y的线性组合最接近真实值。这样,我们就可以用 PyTorch 的 nn.Linear 层来解决它了!
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib.pyplot as plt
# 设置随机种子,保证可复现性
torch.manual_seed(42)
np.random.seed(42)
# 1. 准备数据 - 我们使用和Numpy例子相同的数据源
x_np = np.sort(np.random.rand(100))
y_np = np.sin(2 * np.pi * x_np) + 0.3 * np.random.randn(100)
# 关键步骤:构建多项式特征
# 对于每个x,我们生成它的0次幂到3次幂,作为4个特征
def make_poly_features(x, degree=3):
"""将一维x转换为多项式特征矩阵"""
# x的形状假设为 (n_samples,)
# 返回形状为 (n_samples, degree+1)
features = [x ** i for i in range(degree + 1)]
return np.column_stack(features) # 按列堆叠
X_poly_np = make_poly_features(x_np, degree=3) # 形状 (100, 4)
# 将NumPy数组转换为PyTorch张量
X_tensor = torch.from_numpy(X_poly_np).float() # 特征张量
y_tensor = torch.from_numpy(y_np).float().view(-1, 1) # 标签张量,并调整形状为(100, 1)
print(f"特征张量形状: {X_tensor.shape}") # 应为 torch.Size([100, 4])
print(f"标签张量形状: {y_tensor.shape}") # 应为 torch.Size([100, 1])
注意:这里的数据转换是关键。
view(-1, 1)是为了确保y的形状是(样本数, 1),这与模型输出的形状匹配,方便后续计算损失。
3.2 构建模型、定义损失与优化器
现在,我们来搭建这个“学习机器”。
# 2. 定义模型
class PolynomialRegression(nn.Module):
def __init__(self, input_dim):
super(PolynomialRegression, self).__init__()
# 输入维度是多项式的最高次数+1(例如3次多项式有4个特征:1, x, x^2, x^3)
# 输出维度是1(预测一个y值)
self.linear = nn.Linear(input_dim, 1)
def forward(self, x):
# 前向传播:就是一次线性变换
return self.linear(x)
# 我们的特征维度是4
model = PolynomialRegression(input_dim=4)
print(model)
# 3. 定义损失函数和优化器
criterion = nn.MSELoss() # 均方误差损失,这是回归问题的标配
optimizer = optim.SGD(model.parameters(), lr=0.01) # 随机梯度下降优化器,学习率设为0.01
# 我们可以看一下模型的初始参数(权重和偏置)
print("初始权重:", model.linear.weight)
print("初始偏置:", model.linear.bias)
这里我选择了 SGD(随机梯度下降)优化器,学习率设为 0.01。对于这个小模型,SGD 完全够用。你也可以尝试 Adam 优化器(optim.Adam(model.parameters(), lr=0.01)),它通常收敛得更快,但 SGD 更能让你理解梯度下降的基本原理。
3.3 训练循环:见证模型“学习”的过程
最激动人心的部分来了——训练。我们将亲眼看着模型的预测从一团糟慢慢变得准确。
# 4. 训练模型
num_epochs = 2000 # 训练轮数,可以设置大一些
loss_history = [] # 记录每轮的损失,方便后续画图看学习过程
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_tensor) # 用当前模型参数计算预测值
loss = criterion(outputs, y_tensor) # 计算预测值和真实值的差距
# 反向传播与优化
optimizer.zero_grad() # 清空上一轮累积的梯度,非常重要!
loss.backward() # 计算损失关于模型参数的梯度
optimizer.step() # 根据梯度更新模型参数
# 记录损失
loss_history.append(loss.item())
# 每500轮打印一次损失
if (epoch + 1) % 500 == 0:
print(f'Epoch [{epoch+1:04d}/{num_epochs}], Loss: {loss.item():.6f}')
# 训练完成后,查看学到的参数
print("\n训练完成!")
print(f"学到的权重 (对应1, x, x^2, x^3的系数): {model.linear.weight.data.squeeze().tolist()}")
print(f"学到的偏置: {model.linear.bias.item():.6f}")
# 绘制损失下降曲线
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(loss_history)
plt.xlabel('Epoch')
plt.ylabel('Loss (MSE)')
plt.title('训练损失下降曲线')
plt.grid(True, linestyle='--', alpha=0.5)
运行这段代码,你会看到控制台不断打印出越来越小的损失值。这意味着模型正在一步步调整它的权重,让它的预测越来越接近真实数据。画出损失曲线,你会看到一条典型的下降曲线,前期下降快,后期趋于平缓。
3.4 验证与对比:我们学得怎么样?
模型训练好了,是骡子是马拉出来遛遛。我们用它来预测一下,并和 Numpy 的结果做个对比。
# 5. 模型预测与可视化
with torch.no_grad(): # 关闭梯度计算,节省内存和计算资源
y_pred_tensor = model(X_tensor) # 用训练好的模型对训练数据做预测
# 转换为NumPy数组方便画图
y_pred_np = y_pred_tensor.squeeze().numpy()
# 为了画光滑曲线,生成密集的测试点
x_plot_np = np.linspace(0, 1, 200)
X_plot_poly = make_poly_features(x_plot_np, degree=3)
X_plot_tensor = torch.from_numpy(X_plot_poly).float()
with torch.no_grad():
y_plot_pred_tensor = model(X_plot_tensor)
y_plot_pred_np = y_plot_pred_tensor.squeeze().numpy()
# 用Numpy的polyfit再拟合一次,用于对比
coeffs_numpy = np.polyfit(x_np, y_np, 3)
p_numpy = np.poly1d(coeffs_numpy)
y_plot_numpy = p_numpy(x_plot_np)
plt.subplot(1, 2, 2)
plt.scatter(x_np, y_np, alpha=0.6, label='原始数据', s=20)
plt.plot(x_plot_np, y_plot_pred_np, 'r-', linewidth=3, label='PyTorch 拟合')
plt.plot(x_plot_np, y_plot_numpy, 'g--', linewidth=2, label='Numpy polyfit')
plt.xlabel('x')
plt.ylabel('y')
plt.title('拟合结果对比')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.show()
# 定量对比两种方法的系数
print("\n=== 系数对比 ===")
print(f"Numpy polyfit 系数 (高次到低次): {coeffs_numpy}")
# PyTorch模型的系数顺序是 [x^3, x^2, x, 常数项]?不,我们需要检查一下。
# 我们的特征矩阵是 [1, x, x^2, x^3],nn.Linear的权重对应这些特征。
torch_coeffs = model.linear.weight.data.squeeze().tolist()
torch_bias = model.linear.bias.item()
print(f"PyTorch 学到的权重 (对应[1, x, x^2, x^3]): {torch_coeffs}")
print(f"PyTorch 学到的偏置: {torch_bias}")
# 注意:Numpy的polyfit返回的系数顺序是 [最高次项, ..., 常数项]
# 而我们的PyTorch模型,linear.weight对应特征权重,bias对应额外的偏置。
# 实际上,对于我们的构造方式,PyTorch模型的最终公式是:
# y = bias + weight[0]*1 + weight[1]*x + weight[2]*x^2 + weight[3]*x^3
# 所以常数项是 (bias + weight[0])。对比时需要小心。
通过对比图,你会发现红色实线(PyTorch)和绿色虚线(Numpy)几乎完全重合!这说明我们亲手用梯度下降训练出来的模型,和 Numpy 用最小二乘法直接计算出来的最优解,结果是一致的。这给了我们巨大的信心:我们搭建的这个“学习机器”工作正常,并且学会了正确的知识。
4. 拟合程度的艺术:欠拟合、过拟合与刚刚好
通过前面的实践,你已经感受到了选择多项式次数的重要性。现在,我们系统地聊聊“拟合程度”这件事。这是机器学习中最核心的概念之一,直接决定了你的模型是“书呆子”还是“真学霸”。
4.1 可视化诊断:一眼看出模型的好坏
让我们用一个更综合的例子,把欠拟合、理想拟合和过拟合放在一起对比。这次我们生成一个更复杂的数据。
# 生成一个更复杂的非线性数据
np.random.seed(123)
x_complex = np.linspace(0, 2, 50)
y_true = np.sin(3 * np.pi * x_complex) * np.exp(-x_complex) # 真实的潜在规律
y_noisy = y_true + 0.2 * np.random.randn(len(x_complex)) # 加上噪声的观测值
degrees_to_try = [1, 4, 15] # 分别代表欠拟合、理想、过拟合
plt.figure(figsize=(15, 5))
for idx, deg in enumerate(degrees_to_try):
# 使用Numpy快速拟合
coeffs = np.polyfit(x_complex, y_noisy, deg)
p_func = np.poly1d(coeffs)
x_smooth = np.linspace(0, 2, 200)
y_smooth = p_func(x_smooth)
plt.subplot(1, 3, idx+1)
plt.scatter(x_complex, y_noisy, alpha=0.7, s=40, label='带噪声数据', color='lightblue', edgecolors='blue')
plt.plot(x_smooth, y_true, 'k--', linewidth=2, label='真实规律', alpha=0.8)
plt.plot(x_smooth, y_smooth, 'r-', linewidth=3, label=f'{deg}次拟合')
plt.fill_between(x_smooth, y_true - 0.2, y_true + 0.2, alpha=0.1, color='gray', label='噪声范围')
# 计算训练集上的误差
y_pred = p_func(x_complex)
train_mse = np.mean((y_pred - y_noisy) ** 2)
# 模拟一个测试集(从同一规律生成,但噪声不同)
x_test = np.linspace(0.1, 1.9, 30)
y_test_true = np.sin(3 * np.pi * x_test) * np.exp(-x_test)
y_test_noisy = y_test_true + 0.2 * np.random.randn(len(x_test))
y_test_pred = p_func(x_test)
test_mse = np.mean((y_test_pred - y_test_noisy) ** 2)
plt.title(f'多项式次数 = {deg}\n训练MSE: {train_mse:.4f}, 测试MSE: {test_mse:.4f}')
plt.xlabel('x')
plt.ylabel('y')
plt.legend(loc='upper right', fontsize='small')
plt.grid(True, linestyle='--', alpha=0.3)
plt.tight_layout()
plt.show()
在这张对比图中,你可以清晰地看到三种状态:
- 图1(1次,欠拟合):红色的拟合直线完全无法描述黑色虚线代表的真实规律。它在训练集和测试集上的误差(MSE)都很大。模型太简单,能力不足。
- 图2(4次,理想拟合):红色曲线大致跟随了真实规律的整体趋势,平滑地穿过了数据分布的中间区域。它没有刻意去穿过每一个蓝色数据点(因为那些是噪声),而是抓住了背后的主要模式。训练误差和测试误差都比较低,且相差不大。
- 图3(15次,过拟合):红色曲线变得极度扭曲,为了穿过每一个训练数据点(蓝色散点),产生了剧烈的震荡。注意看,在数据稀疏的区域(比如x接近2的地方),曲线出现了毫无道理的剧烈波动。虽然它在训练集上的误差可能极小(因为它几乎“记住”了每个点),但在测试集上的误差会显著增大。模型太复杂,学习了噪声。
4.2 理解过拟合:当模型成了“记忆大师”
过拟合的本质是模型**方差(Variance)**过高。它过度关注训练数据中的细节和噪声,导致学到的规则泛化能力极差。就像一个只靠死记硬背考高分的学生,题目稍一变化就不会了。
在多项式拟合中,过拟合的直接表现就是多项式系数变得非常大。高次项的系数巨大,使得曲线具备极强的弯曲能力去“贴合”噪声点。
# 查看过拟合模型的系数
coeffs_overfit = np.polyfit(x_complex, y_noisy, 15)
print("15次多项式拟合的部分高阶系数(通常绝对值非常大):")
for i, coeff in enumerate(coeffs_overfit[:5]): # 展示前5个最高次项系数
print(f" x^{15-i} 的系数: {coeff:12.6e}")
你会看到类似 x^15 的系数: 3.452e+08 这样的输出,系数巨大无比。这就是过拟合在数学上的特征。
如何在PyTorch中应对过拟合? 虽然我们的多项式拟合例子简单,但PyTorch提供了强大的正则化工具,这在复杂模型中至关重要。
- L2正则化(权重衰减):直接在优化器中设置
weight_decay参数。optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-5)weight_decay参数会给损失函数增加一个惩罚项,防止权重变得过大。 - 早停法(Early Stopping):这是我最常用也最有效的方法之一。在训练时,预留一部分数据作为验证集。每训练一定轮数,就在验证集上评估一次性能。当验证集上的误差不再下降反而开始上升时,就停止训练。
# 伪代码逻辑 best_val_loss = float('inf') patience = 50 # 容忍验证损失不下降的轮数 counter = 0 for epoch in range(num_epochs): # ... 训练步骤 ... current_val_loss = validate(model, val_loader) # 在验证集上计算损失 if current_val_loss < best_val_loss: best_val_loss = current_val_loss counter = 0 # 保存最佳模型 torch.save(model.state_dict(), 'best_model.pth') else: counter += 1 if counter >= patience: print(f'早停在第{epoch}轮') break # 训练结束后,加载效果最好的模型 model.load_state_dict(torch.load('best_model.pth'))
4.3 诊断欠拟合:当模型“还没开窍”
欠拟合则相反,是模型**偏差(Bias)**过高。模型本身太简单,无法捕捉数据中潜在的模式。就像用一个线性公式去拟合正弦波,再怎么优化参数,也画不出那条弯曲的线。
解决方法更直接:
- 增加模型复杂度:在多项式拟合中,就是增加次数。在神经网络中,可以增加层数或每层的神经元数量。
- 更好的特征工程:有时候不是模型不行,而是给模型的信息不够。在多项式拟合的语境下,如果你只用
x去拟合一个周期性数据,肯定欠拟合。但如果你聪明地加入了sin(x),cos(x)作为特征,可能用一个简单的线性模型就能拟合得很好。这就是特征工程的威力。 - 减少正则化:如果你不小心设置了过大的
weight_decay,也可能导致模型无法充分学习,表现为欠拟合。可以尝试减小正则化强度。 - 训练更长时间:有时候模型有潜力,只是还没训练到位。特别是使用SGD等优化器时,可以适当增加训练轮数 (
num_epochs)。
4.4 找到甜蜜点:偏差-方差权衡
机器学习的所有调参过程,几乎都是在进行偏差-方差权衡。我们的目标是在欠拟合(高偏差)和过拟合(高方差)之间找到一个平衡点,使模型在未知数据上的总误差最小。
一个实用的工作流是:
- 从简单模型开始:比如先用1次或2次多项式。
- 绘制学习曲线:绘制模型在训练集和验证集上的误差随模型复杂度(多项式次数)或训练轮数变化的曲线。
- 寻找拐点:理想情况下,随着复杂度增加,训练误差持续下降,而验证误差先下降后上升。那个验证误差最低点,就是我们要的“甜蜜点”。
- 交叉验证:对于小数据集,使用K折交叉验证来更稳健地评估不同复杂度模型的表现,避免因一次数据划分的偶然性而选错模型。
# 一个简单的模型复杂度选择示例(通过交叉验证)
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.metrics import make_scorer, mean_squared_error
degrees = range(1, 11) # 尝试1到10次
cv_scores = []
for deg in degrees:
# 构建一个“多项式特征+线性回归”的管道
model_pipeline = make_pipeline(PolynomialFeatures(degree=deg), LinearRegression())
# 进行5折交叉验证,负均方误差作为评分(sklearn约定:越大越好)
scores = cross_val_score(model_pipeline, x_complex.reshape(-1,1), y_noisy,
cv=5, scoring='neg_mean_squared_error')
cv_scores.append(-scores.mean()) # 转换为正的平均MSE
best_degree = degrees[np.argmin(cv_scores)]
print(f"通过5折交叉验证,最佳的多项式次数是: {best_degree}")
plt.figure(figsize=(8,5))
plt.plot(degrees, cv_scores, 'bo-', linewidth=2, markersize=8)
plt.xlabel('多项式次数')
plt.ylabel('交叉验证平均MSE')
plt.title('模型复杂度选择曲线')
plt.axvline(x=best_degree, color='r', linestyle='--', label=f'最佳次数={best_degree}')
plt.grid(True, linestyle='--', alpha=0.5)
plt.legend()
plt.show()
这段代码利用了 Scikit-learn 的交叉验证功能,自动为你找到了在给定数据上最合适的多项式次数。在实际项目中,这套组合拳(可视化诊断、学习曲线、交叉验证)能帮你做出更可靠的决定。
273



被折叠的 条评论
为什么被折叠?



