本示例展示了如何使用岭回归来近似一个函数,通过多项式和样条插值方法。将展示两种不同的方法来处理一维点的n_samples数据:使用PolynomialFeatures生成所有单项式直到degree,以及使用SplineTransformer生成B样条基函数。
PolynomialFeatures可以生成所有单项式直到degree,这会给一个所谓的Vandermonde矩阵,它有n_samples行和degree+1列。这个矩阵可以被解释为伪特征矩阵(点提升到某些幂)。而SplineTransformer生成B样条基函数,一个B样条基函数是一个只在degree+1个连续结点之间非零的分段多项式函数。给定n_knots个结点,这将导致一个有n_samples行和n_knots+degree-1列的矩阵。
这两种变换器非常适合使用线性模型来模拟非线性效应,通过管道添加非线性特征。核方法扩展了这个想法,可以诱导非常高(甚至是无限)维度的特征空间。
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures, SplineTransformer
# 定义要近似的函数并准备绘图
def f(x):
""“多项式插值的目标函数。”""
return x * np.sin(x)
# 绘图范围
x_plot = np.linspace(-1, 11, 100)
# 为了使事情有趣,只提供一小部分点进行训练
x_train = np.linspace(0, 10, 100)
rng = np.random.RandomState(0)
x_train = np.sort(rng.choice(x_train, size=20, replace=False))
y_train = f(x_train)
# 创建这些数组的2D版本以提供给变换器
X_train = x_train[:, np.newaxis]
X_plot = x_plot[:, np.newaxis]
# 现在可以创建多项式特征和样条,拟合训练点并展示它们如何插值
lw = 2
fig, ax = plt.subplots()
ax.set_prop_cycle(color=["black", "teal", "yellowgreen", "gold", "darkorange", "tomato"])
ax.plot(x_plot, f(x_plot), linewidth=lw, label="真实情况")
ax.scatter(x_train, y_train, label="训练点")
# 多项式特征
for degree in [3, 4, 5]:
model = make_pipeline(PolynomialFeatures(degree), Ridge(alpha=1e-3))
model.fit(X_train, y_train)
y_plot = model.predict(X_plot)
ax.plot(x_plot, y_plot, label=f"degree {degree}")
# B样条,有4 + 3 - 1 = 6个基函数
model = make_pipeline(SplineTransformer(n_knots=4, degree=3), Ridge(alpha=1e-3))
model.fit(X_train, y_train)
y_plot = model.predict(X_plot)
ax.plot(x_plot, y_plot, label="B样条")
ax.legend(loc="lower center")
ax.set_ylim(-20, 10)
plt.show()
这个图表很好地展示了更高阶的多项式可以更好地拟合数据。但同时,过高的幂次可能会显示出不想要的振荡行为,并且对于超出拟合数据范围的外推特别危险。这是B样条的优势。它们通常可以像多项式一样拟合数据,并且显示出非常好和平滑的行为。它们还有很好的选项来控制外推,默认情况下是继续以常数进行。通常,宁愿增加结点的数量,但保持degree=3。
为了更深入地了解生成的特征基,分别绘制了两种变换器的所有列。在左图中,认出了从x**0到x**3的简单单项式对应的线条。在右图中,看到了六个degree=3的B样条基函数,以及在fit期间选择的四个结点位置。注意,有degree数量的额外结点在拟合区间的左侧和右侧。这些结点出于技术原因存在,所以避免显示它们。每个基函数都有局部支持,并且在拟合范围之外以常数继续。这种外推行为可以通过参数extrapolation来改变。
在前一个例子中,看到了多项式和样条在训练观测范围之外进行外推的局限性。在某些设置中,例如具有季节性效应的情况下,期望底层信号有周期性的延续。这种效应可以使用周期性样条来建模,它们在第一个和最后一个结点处具有相等的函数值和相等的导数。在下面的例子中,展示了周期性样条如何在训练数据的范围内外提供更好的拟合,这是由于额外的周期性信息。样条周期是第一个和最后一个结点之间的距离,手动指定。
周期性样条对于自然周期性特征(如一年中的一天)也很有用,因为边界结点的平滑性可以防止转换值的跳跃(例如从12月31日到1月1日)。对于这种自然周期性特征或更一般地,对于已知周期的特征,建议通过设置结点手动将此信息传递给SplineTransformer。
def g(x):
""“要通过周期性样条插值近似的函数。”""
return np.sin(x) - 0.7 * np.cos(x * 3)
y_train = g(x_train)
# 将测试数据扩展到未来:
x_plot_ext = np.linspace(-1, 21, 200)
X_plot_ext = x_plot_ext[:, np.newaxis]
lw = 2
fig, ax = plt.subplots()
ax.set_prop_cycle(color=["black", "tomato", "teal"])
ax.plot(x_plot_ext, g(x_plot_ext), linewidth=lw, label="真实情况")
ax.scatter(x_train, y_train, label="训练点")
for transformer, label in [
(SplineTransformer(degree=3, n_knots=10), "样条"),
(SplineTransformer(degree=3, knots=np.linspace(0, 2 * np.pi, 10)[:, None], extrapolation="periodic"), "周期性样条"),
]:
model = make_pipeline(transformer, Ridge(alpha=1e-3))
model.fit(X_train, y_train)
y_plot_ext = model.predict(X_plot_ext)
ax.plot(x_plot_ext, y_plot_ext, label=label)
ax.legend()
fig.show()
fig, ax = plt.subplots()
knots = np.linspace(0, 2 * np.pi, 4)
splt = SplineTransformer(knots=knots[:, None], degree=3, extrapolation="periodic").fit(X_train)
ax.plot(x_plot_ext, splt.transform(X_plot_ext))
ax.legend(ax.lines, [f"样条{n}" for n in range(3)])
plt.show()
在图表中,可以看到周期性样条在训练数据的范围内外都提供了更好的拟合。这种拟合不仅在训练数据范围内表现良好,而且在范围之外也表现出了周期性的延续。