哈喽,大家好~
偏差与方差:模型越复杂,效果反而越差?
最近,很多同学也会发现:模型太简单学不会,模型太复杂又容易在测试集上表现变差。
这篇文章我们就用一个简单的多项式回归案例,把机器学习里的偏差、方差和过拟合一次讲清楚~
什么是偏差和方差?
假设我们要用模型拟合一组带噪声的数据。
如果模型过于简单,比如只用一条直线拟合明显弯曲的数据,它连训练集规律都没有学好,这就是高偏差。
如果模型过于复杂,比如用一条剧烈波动的曲线穿过训练集的每一个点,它把噪声也当成了规律,这就是高方差。
你可以这样理解:
-
偏差:模型的“平均预测”离真实规律有多远。 -
方差:模型换一批训练数据后,预测结果变化有多大。
模型误差通常可以拆成:
其中,偏差描述模型本身是否过于简单,方差描述模型是否对训练数据过于敏感。
也就是,偏差高,模型学不够;方差高,模型学过头。**
02多项式回归
我们构造这样一条真实规律:
这里的 是真实函数, 是随机噪声。
接下来分别使用一次、四次和十五次多项式进行拟合:
越小,模型越简单; 越大,模型表达能力越强。
但表达能力强不等于泛化能力强。十五次多项式可以把训练样本拟合得非常漂亮,却容易在样本点之间剧烈波动,这就是典型的过拟合。
Python实现
我们最后的数据分析题,第一张图对比不同复杂度模型的拟合曲线,观察欠拟合和过拟合。
第二张图统计不同多项式阶数下的偏差平方、方差和预测误差,观察模型复杂度变化带来的影响。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
rng = np.random.default_rng(42)
def true_func(x):
return np.sin(x)
# 一组训练数据
x_train = np.sort(rng.uniform(-3, 3, 25))
y_train = true_func(x_train) + rng.normal(0, 0.25, 25)
x_test = np.linspace(-3, 3, 400)
y_true = true_func(x_test)
def build_model(degree):
return make_pipeline(
PolynomialFeatures(degree),
LinearRegression()
)
degrees_show = [1, 4, 15]
colors = ["#00B4D8", "#FF7A00", "#E63946"]
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 图1:不同复杂度的拟合曲线
axes[0].scatter(
x_train, y_train, color="#8338EC",
s=45, label="训练数据"
)
axes[0].plot(
x_test, y_true, color="#222222",
linewidth=3, label="真实规律"
)
for degree, color in zip(degrees_show, colors):
model = build_model(degree)
model.fit(x_train.reshape(-1, 1), y_train)
axes[0].plot(
x_test, model.predict(x_test.reshape(-1, 1)),
color=color, linewidth=2.5,
label=f"{degree}次多项式"
)
axes[0].set_title("模型复杂度与拟合曲线")
axes[0].set_xlabel("x")
axes[0].set_ylabel("y")
axes[0].legend()
axes[0].grid(alpha=0.25)
# 图2:重复采样,估计偏差平方和方差
n_rounds = 100
bias2_list, variance_list, error_list = [], [], []
for degree in range(1, 16):
predictions = []
for _ in range(n_rounds):
x = rng.uniform(-3, 3, 25)
y = true_func(x) + rng.normal(0, 0.25, 25)
model = build_model(degree)
model.fit(x.reshape(-1, 1), y)
predictions.append(model.predict(x_test.reshape(-1, 1)))
predictions = np.array(predictions)
mean_prediction = predictions.mean(axis=0)
bias2 = np.mean((mean_prediction - y_true) ** 2)
variance = np.mean((predictions - mean_prediction) ** 2)
error = np.mean((predictions - y_true) ** 2)
bias2_list.append(bias2)
variance_list.append(variance)
error_list.append(error)
axes[1].plot(
range(1, 16), bias2_list,
marker="o", color="#00B4D8",
linewidth=2.5, label="偏差平方"
)
axes[1].plot(
range(1, 16), variance_list,
marker="s", color="#FF006E",
linewidth=2.5, label="方差"
)
axes[1].plot(
range(1, 16), error_list,
marker="^", color="#FFBE0B",
linewidth=2.5, label="总预测误差"
)
axes[1].set_title("偏差-方差随模型复杂度的变化")
axes[1].set_xlabel("多项式阶数")
axes[1].set_ylabel("误差")
axes[1].legend()
axes[1].grid(alpha=0.25)
plt.tight_layout()
plt.show()
第一张图中,一次多项式基本是一条直线,无法描述正弦曲线的弯曲变化,所以属于高偏差、欠拟合。
四次多项式通常能抓住整体趋势,在训练数据和真实规律之间取得比较好的平衡。
十五次多项式会努力穿过每一个训练点,曲线在边缘位置出现明显波动。它在当前训练集上表现很好,但换一批数据后,曲线很容易发生变化,这就是高方差、过拟合。
第二张图展示了更完整的变化过程。随着阶数增加,偏差平方通常下降,因为模型越来越有能力拟合真实规律;但方差会逐渐上升,因为模型开始依赖具体训练样本。
真正理想的模型,不是让偏差或方差单独达到最低,而是让两者之和尽量小。图中的“总预测误差”最低点,就是一个比较合适的复杂度区域。
实验中注意点
当模型出现高偏差时,可以增加特征、提高模型复杂度,或者减少正则化。
当模型出现高方差时,可以增加训练数据、降低模型复杂度,或者加强正则化。交叉验证也很重要,它能帮助我们选择更稳定的模型参数。
这里还要注意,训练误差很低并不代表模型真的好。我们最终关心的是验证集和测试集上的表现,也就是模型面对新数据时能不能继续做出准确预测。
总结
偏差告诉我们模型有没有学到足够的规律,方差告诉我们模型是否过度依赖某一批训练数据。模型选择的核心,就是在欠拟合和过拟合之间找到平衡。
大家可以继续尝试修改噪声大小、训练样本数量和正则化参数,观察偏差与方差曲线如何变化。也可以把多项式回归替换成决策树,看看树深度增加后,模型又会呈现怎样的过拟合现象。

