第一次看到龙格现象的实验结果时,我正对着屏幕上的曲线发愣——明明用7次多项式完美穿过了所有样本点,为什么在区间外会像脱缰野马一样疯狂偏离真实函数?这让我想起刚入行时犯过的错误:用一个复杂的深度学习模型完美拟合了训练数据,结果在实际应用中输出了荒谬的预测。
让我们用Python重现这个经典实验。假设真实函数是y=1/(1+25x²),在[-2,2]区间均匀取7个点:
python复制import numpy as np
import matplotlib.pyplot as plt
# 真实函数
x = np.linspace(-2, 2, 1000)
y = 1/(1 + 25*x**2)
# 采样点
testx = np.linspace(-2, 2, 7)
testy = 1/(1 + 25*testx**2)
# 不同次数拟合
degrees = [3, 5, 7]
models = [np.poly1d(np.polyfit(testx, testy, d)) for d in degrees]
当把拟合结果绘制在[-10,10]区间时,7次多项式在|x|>2的区域出现了剧烈的震荡,振幅随着|x|增大呈指数级增长。这就像用记忆代替理解的学生——能在模拟考中拿满分,遇到新题型却一败涂地。
龙格现象生动展示了模型复杂度的双刃剑特性。低次多项式(如3次)虽然不能精确穿过每个样本点,但整体趋势与真实函数相近;而高次多项式在样本点处误差为零,在其他位置却误差爆炸。这对应着机器学习中的偏差-方差分解:
总误差 = (偏差)² + 方差 + 不可约误差
python复制# 添加噪声观察模型稳定性
noisy_testy = testy + np.random.normal(0, 0.05, size=testy.shape)
noisy_models = [np.poly1d(np.polyfit(testx, noisy_testy, d)) for d in degrees]
实验显示,带噪声数据下高次多项式的震荡会更加剧烈,这正是方差过大的典型特征。
在训练集上,随着多项式次数增加,均方误差(MSE)单调递减:
code复制3次多项式MSE: 0.0085
5次多项式MSE: 0.0021
7次多项式MSE: 0.0000
但测试集的MSE却呈现U型曲线:
code复制3次多项式测试MSE: 0.0102
5次多项式测试MSE: 0.0257
7次多项式测试MSE: 1.86e+06
这解释了为什么单纯追求训练误差最小化是危险的——就像根据模拟考成绩选拔学生,可能选出只会死记硬背的应试高手。
针对龙格现象,数学家提出了两种经典解决方案:
对应到机器学习中,这正是:
python复制# 使用岭回归(Ridge)实现L2正则化
from sklearn.linear_model import Ridge
# 构造多项式特征
X_train = np.column_stack([testx**i for i in range(8)])
X_test = np.column_stack([x**i for i in range(8)])
model = Ridge(alpha=1.0).fit(X_train, testy)
ridge_pred = model.predict(X_test)
加入L2正则后,虽然训练集MSE上升到0.0012,但测试集MSE从百万级降至0.0098,显著改善了泛化能力。
龙格现象告诉我们,在模型选择时:
实践中建议:
python复制from sklearn.model_selection import cross_val_score
scores = []
for d in range(1, 8):
model = np.poly1d(np.polyfit(testx, testy, d))
# 留一法交叉验证
loo_loss = -cross_val_score(
lambda x: model(x),
testx.reshape(-1,1),
testy,
cv=len(testx),
scoring='neg_mean_squared_error'
).mean()
scores.append(loo_loss)
龙格函数在|x|>0.5时导数急剧增大,这意味着:
这对应机器学习中的关键认知:数据质量决定模型上限。在图像识别中,与其盲目增加CNN层数,不如先:
龙格现象为"如无必要,勿增实体"提供了数学验证。模型复杂度选择应当:
python复制# 早停策略实现
best_val_loss = float('inf')
best_degree = 1
for d in range(1, 10):
model = np.poly1d(np.polyfit(trainx, trainy, d))
val_loss = np.mean((model(valx) - valy)**2)
if val_loss < best_val_loss:
best_val_loss = val_loss
best_degree = d
else:
break # 误差开始上升,停止增加复杂度
在真实项目中,这种渐进式方法帮我避免了许多过拟合陷阱。记得有一次构建用户流失预测模型,当把XGBoost的max_depth从6增加到7时,虽然AUC提升了0.003,但跨时间验证集的表现下降了0.015,果断选择了较浅的树深度。