【机器学习】欠拟合与过拟合 02

目录

1 提升扩展阶数产生的现象

2 常用的正则化方法

2.1 正则化对权重的影响:以L2正则化为例

2.2 正则化对权重的影响:以L1正则化为例

2.3 三种正则化方法解决过拟合:

 3 交叉验证调整超参数


#学习记录#

1 提升扩展阶数产生的现象

过拟合现象

通过之前的程序我们发现,使用多项式扩展完美的解决了欠拟合问题。如果我们使用更多阶的多项式,甚至可以将拟合度提高为1,但是问题来了,是否阶数越多越好呢?

我们通过代码来看一下:

from sklearn.model_selection import train_test_split
def true_fun(x):
    """数据分布的函数,用于生成由x -> y的映射。
    根据x数组中的每个元素,返回该元素的余弦计算值y。
    Parameters
    ----------
    x : array-like
    训练数据集。
    Returns
    -------
    y : array-like
    x对应的余弦映射结果。
    """
    return np.cos(1.5 * np.pi * x)
def fit_and_plot(model):
    """用来训练模型,并且绘制模型的拟合效果。
    Parameters
    ----------
    model : object
    模型对象。
    """
    np.random.seed(0)
    x = np.random.rand(50)
    # 在映射函数上,增加一定的误差(噪声)。这样更符合现实中数据的分布。
    # 误差服从正态分布。
    y = true_fun(x) + np.random.randn(len(x)) * 0.1
    X = x[:, np.newaxis]
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5,
    random_state=0)
    model.fit(X_train, y_train)
    train_score = model.score(X_train, y_train)
    test_score = model.score(X_test, y_test)
    plt.scatter(X_train, y_train, c="g", label="训练集")
    plt.scatter(X_test, y_test, c="orange", marker="D", label="测试集")
    s = np.linspace(0, 1, 100).reshape(-1, 1)
    plt.plot(s, model.predict(s), c="r", label="拟合线")
    plt.xlabel("x")
    plt.ylabel("y")
    plt.xlim((0, 1))
    plt.ylim((-2, 2))
    plt.legend(loc="upper center")
    plt.title(f"训练集:{train_score:.3f} 测试集:{test_score:.3f}")
# 定义多项式扩展的阶数。
degrees = [1, 3, 8, 15]
plt.figure(figsize=(15, 12))
for i, n in enumerate(degrees):
    plt.subplot(2, 2, i + 1)
    pipe = Pipeline([("poly", PolynomialFeatures(degree=n, include_bias=False)),("lr", LinearRegression())])
    fit_and_plot(pipe)
    # named_steps返回字典对象,提供流水线中每个步骤的名称(key)与对象(value)的映射。
    print(pipe.named_steps["lr"].coef_)

输出:

2 常用的正则化方法

背景: 在进行线性回归等模型训练时,过度复杂的模型往往表现为参数值过大。这不仅会导致过拟合现象,也使模型难以泛化到新数据上。为了缓解这一问题,正则化技术应运而生。

定义: 正则化是一种在损失函数中加入额外惩罚项的技术,旨在限制模型参数的大小,减少模型复杂度。这种额外的惩罚项,也称为正则项,有助于降低过拟合的风险。

常见类型:

  1. L2正则化(岭回归): 在损失函数中添加参数权重的平方和作为惩罚项。它倾向于使参数值较小,但不会将它们完全减至零。
  2. L1正则化(套索回归): 损失函数中包含参数的绝对值和。这种方法除了惩罚大的参数值外,还可能将某些参数完全压缩为零,从而实现特征选择的效果。
  3. Elastic Net: 是L1和L2正则化的组合,包含两者的惩罚项。这种方法结合了岭回归和套索回归的优点,既可以压缩参数,也可以完全消除某些参数。

2.1 正则化对权重的影响:以L2正则化为例

概念: L2正则化(也称为岭回归)通过在损失函数中添加一个正则项,该正则项是模型权重的平方和与一个正则化系数(α)的乘积,来减少模型参数的复杂度。这种做法旨在减少模型对训练数据的过度拟合,同时保留其预测能力。

权重影响: 在L2正则化中,正则化系数α的选择对模型的权重有显著影响:

  • 较小的α值: 当α值较小,正则化的影响减少,模型倾向于增加权重的复杂度,从而更好地拟合训练数据。这可能会导致过拟合。
  • 较大的α值: 反之,较大的α值增强了正则化的作用,迫使模型权重变得更小,减少了模型的复杂度。这有助于防止过拟合,但如果α过大,可能会导致模型欠拟合,即在训练数据上的表现也不佳。

我们通过代码来观察:

from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
# 注意:当坐标轴使用对数比例后,这里需要改成英文字体,否则无法正常显示。
plt.rcParams["font.family"] = "serif"
# 创建回归数据集。
# n_samples:样本数量。
# n_features:特征数量。
# coef:是否返回权重。默认为False。
# random_state:随机种子。
# bias:偏置。
# noise:增加的噪声干扰,值越大,干扰越大。
X, y, w = make_regression(n_samples=10, n_features=10, coef=True,
random_state=1, bias=3.5, noise=0.0)
alphas = np.logspace(-4, 4, 200)
# 定义列表,用来保存在不同alpha取值下,模型最优的权重(w)值。
coefs = []
# 创建岭回归对象。
ridge = Ridge()
for a in alphas:
    # alpha:惩罚力度,值越大,惩罚力度越大。
    ridge.set_params(alpha=a)
    ridge.fit(X, y)
    # 将每个alpha取值下,Ridge回归拟合的最佳解(w)加入到列表中。
    coefs.append(ridge.coef_)
# gca get current axes 获取当前的绘图对象。
ax = plt.gca()
# 当y是二维数组时,每一列会认为是一个单独的数据集。
ax.plot(alphas, coefs)
# 设置x轴的比例。(对数比例)
ax.set_xscale("log")
# 设置x轴的标签。
ax.set_xlabel("alpha")
# 设置y轴的标签。
ax.set_ylabel("weights")

输出:

这张图展示的是在不同α值(正则化强度)下,L2正则化对模型权重的影响。我们可以看到,随着α值的增加(从左至右),各个权重的绝对值普遍减小。这是因为较高的正则化强度对模型参数施加了更严格的约束,迫使模型偏向于更小的权重值。

在α值较小的左侧区域,权重值较大,模型可能会更加复杂,有较大的过拟合风险。而在α值增大到某个程度之后,权重逐渐趋于平稳,这有助于降低过拟合的风险,但也可能导致欠拟合,特别是当α值非常大时。

2.2 正则化对权重的影响:以L1正则化为例

L1正则化,也被称为套索回归(Lasso Regression),它通过惩罚模型的损失函数中权重的绝对值来工作。与L2正则化不同,L1正则化倾向于产生稀疏的权重矩阵,即模型的许多权重将被设置为零。这种特性使得L1正则化能够进行特征选择,因为它可以减少不重要特征的权重到零。

代码:

from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
# 注意:当坐标轴使用对数比例后,这里需要改成英文字体,否则无法正常显示。
plt.rcParams["font.family"] = "serif"
# 创建回归数据集。
# n_samples:样本数量。
# n_features:特征数量。
# coef:是否返回权重。默认为False。
# random_state:随机种子。
# bias:偏置。
# noise:增加的噪声干扰,值越大,干扰越大。
X, y, w = make_regression(n_samples=10, n_features=10, coef=True,
random_state=1, bias=3.5, noise=0.0)
alphas = np.logspace(-4, 4, 200)
# 定义列表,用来保存在不同alpha取值下,模型最优的权重(w)值。
coefs = []
# 创建Lasso回归对象。
lasso = Lasso()
for a in alphas:
    # alpha:惩罚力度,值越大,惩罚力度越大。
    lasso.set_params(alpha=a)
    lasso.fit(X, y)
    # 将每个alpha取值下,Lasso回归拟合的最佳解(w)加入到列表中。
    coefs.append(lasso.coef_)
# gca get current axes 获取当前的绘图对象。
ax = plt.gca()
# 当y是二维数组时,每一列会认为是一个单独的数据集。
ax.plot(alphas, coefs)
# 设置x轴的比例。(对数比例)
ax.set_xscale("log")
# 设置x轴的标签。
ax.set_xlabel("alpha")
# 设置y轴的标签。
ax.set_ylabel("weights")

 输出:

  • 在α较小(接近0)的区域,大多数权重都远离零点,表明正则化的影响较小,模型可能会保留更多的特征。
  • 随着α值的增加,曲线急剧下降,权重迅速减小到零,这反映了L1正则化对于特征选择的效果。在某些权重上这一点尤为明显,我们可以看到它们突然降到零,这意味着对应的特征被模型彻底排除。
  • 在α继续增加时,几乎所有的权重都趋近于零,这可能意味着模型已经非常简化,可能会导致欠拟合。

2.3 三种正则化方法解决过拟合:

现在我们分别使用三种正则化方法,尝试解决过拟合问题并进行对比分析:

from sklearn.linear_model import ElasticNet
# 将字体改回中文字体。
plt.rcParams["font.family"] = "SimHei"
np.random.seed(0)
x = np.random.rand(50)
y = true_fun(x) + np.random.randn(len(x)) * 0.1
X = x[:, np.newaxis]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)
models = [("线性回归(无正则化)", LinearRegression()), ("L1正则化:", Lasso(alpha=0.02)),
("L2正则化", Ridge(alpha=0.02)), ("弹性网络", ElasticNet(alpha=0.02,
l1_ratio=0.5))]
plt.figure(figsize=(15, 12))
for i, (name, model) in enumerate(models):
    plt.subplot(2, 2, i + 1)
    pipe = Pipeline([("poly", PolynomialFeatures(degree=15, include_bias=False)),
    ("model", model)])
    fit_and_plot(pipe)
    print(model.coef_)

输出:

 3 交叉验证调整超参数

通过刚刚的试验,我们已经发现过拟合得到一定程度的解决,但是效果并不是非常好,因为我们的a值设置的比较随意。如果使用合适的a值,或许我们还能提高模型的效果,我们用交叉验证来调整参数a的值

在sklearn中,LassoCVRidgeCVElasticNetCV是三个用于执行带有交叉验证的线性模型的类。它们分别对应于L1正则化、L2正则化和弹性网(Elastic Net)正则化,并内置了交叉验证功能,可以自动选择最佳的正则化参数α。这些类非常方便,因为它们简化了正则化强度的选择过程,使用内置的交叉验证来确定α值,从而避免了手动调整α值并运行多次模型以确定最佳值的需要。

代码如下:

from sklearn.linear_model import LassoCV,RidgeCV,ElasticNetCV

alphas = [0.001,0.005,0.01,0.05,0.1,0.5]
models = [("L1正则化:", LassoCV(max_iter=5000)), ("L2正则化", RidgeCV()),
("弹性网络", ElasticNetCV(l1_ratio=0.5))]
plt.figure(figsize=(15, 5))
for i, (name, model) in enumerate(models):
    plt.subplot(1, 3, i + 1)
    pipe = Pipeline([("poly", PolynomialFeatures(degree=15)), ("model", model)])
    # 将模型设置为10折交叉验证,其实在models中,各个模型的构造器中,可以指定cv=10,但是需要三个都指定。
    # 这里在循环中,只需要使用一行代码就可以了。
    pipe.set_params(model__cv=10)
    pipe.set_params(model__alphas=alphas)
    fit_and_plot(pipe)
    # 输出最佳的超参数alpha。
    print(name, model.alpha_)

输出

L1正则化: 0.001
L2正则化 0.05
弹性网络 0.001

在上面我们就可看到,经过交叉验证最佳参数的选择使得拟合效果更好了 

以上

学习在于总结和坚持,共勉

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值