过拟合与欠拟合

概念

之前,我们介绍过拟合的概念。拟合指的是构建的模型能够符合样本数据的特征。与拟合相关的两个概念是欠拟合与过拟合。

  • 欠拟合:模型过于简单,未能充分捕获样本数据的特征。表现为模型在训练集上的效果不好。
  • 过拟合:模型过于复杂,过分捕获样本数据的特征,从而将样本数据中一些特殊特征当成了共性特征。表现为模型在训练集上的效果非常好,但是在未知数据上的表现效果不好。

解决方案

如果产生欠拟合,可以采用如下方式,来达到更好的拟合效果。

  • 增加迭代次数
  • 增加模型复杂度(例如,引入新的特征)
  • 通过多项式扩展
  • 使用更复杂的模型(例如非线性模型)

如果产生过拟合,可以采用如下方式,来降低过拟合的程度。

  • 收集更多的数据
  • 正则化
  • 降低模型的复杂度
  • 减少迭代次数
  • 选择简单的模型

现在,我们来介绍下多项式扩展与正则化。

 

多项式扩展

我们可以使用线性回归模型来拟合数据,然而,在现实中,数据未必总是线性(或接近线性)的。当数据并非线性时,直接使用LinearRegression的效果可能会较差,产生欠拟合。

# x = np.linspace(0, 10, 50)
# print(x.shape)
# print(x[:, np.newaxis].shape)

(50,)
(50, 1)


import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False

x = np.linspace(0, 10, 50)
# 真实的数据分布。该分布是非线性的。
y = x * np.sin(x)
# np.newaxis 表示进行维度的扩展,可以认为是增加一个维度,该维度为1。
# 此种方式也可以通过reshape方法来实现。
X = x[:, np.newaxis]
lr = LinearRegression()
lr.fit(X, y)
# 输出在训练集上的分值。查看线性回归LinearRegression在非线性数据集上的拟合效果。
print(lr.score(X, y))
# 将样本数据以散点图进行绘制。
plt.scatter(x, y, c="g", label="样本数据")
# 绘制预测值(模型的回归线)
plt.plot(X, lr.predict(X), "r-", label="拟合线")
plt.legend()
plt.show()
# 结果:R ^ 2值为0.05908132146396671,模型在训练集上表现非常不好,产生欠拟合。
0.05908132146396671

 

 

# 多项式扩展的规则:
# 每个输入特征分别带有一个指数(指数值为非负整数),然后让指数之间进行任意可能的组合,
# 但要保证所有的指数之和不能大于扩展的阶数。

# 类似PolynomialFeatures这样功能的类(数据预处理),所有的转换方法都叫做transform。
# 拟合与转换可以同时进行,方法名称都叫做fit_transform。

import numpy as np
# sklearn.preprocessing 该模块提供数据预处理的相关功能。
# PolynomialFeatures多项式扩展类,可以对模型进行n阶扩展。从而可以解决欠拟合问题。
from sklearn.preprocessing import PolynomialFeatures

X = np.array([[1, 2], [3, 4]])
# X = np.array([[1, 2, 3], [3, 4, 5]])
# 定义多项式扩展类,参数指定要扩展的阶数。
poly = PolynomialFeatures(2)
# 拟合模型,计算power_的值。
# poly.fit(X)
# 对数据集X进行多项式扩展,即进行多项式转换。
# r = poly.transform(X)
# 我们可以令fit与transofrm两步一起完成。
r = poly.fit_transform(X)

print("转换之后的结果:")
print(r)
print("指数矩阵:")
# 指数矩阵,形状为(输出特征数,输入特征数)。
print(poly.powers_)
print(f"输入的特征数量:{poly.n_input_features_}")
print(f"输出的特征数量:{poly.n_output_features_}")

# 根据power_矩阵,自行计算转换结果。
# 循环获取X中的每一个样本。
for x1, x2 in X:
    for e1, e2 in poly.powers_:
        print(x1 ** e1 * x2 ** e2, end="\t")
    print()



转换之后的结果:
[[ 1.  1.  2.  1.  2.  4.]
 [ 1.  3.  4.  9. 12. 16.]]
指数矩阵:
[[0 0]
 [1 0]
 [0 1]
 [2 0]
 [1 1]
 [0 2]]
输入的特征数量:2
输出的特征数量:6
1	1	2	1	2	4	
1	3	4	9	12	16

多项式扩展解决欠拟合

现在,就让我们对之前的程序来进行多项式扩展,尝试解决欠拟合问题。

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures

mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False

x = np.linspace(0, 10, 50)
y = x * np.sin(x)
X = x[:, np.newaxis]
figure, ax = plt.subplots(2, 3)
figure.set_size_inches(18, 10)
ax = ax.ravel()

# n为要进行多项式扩展的阶数。
for n in range(1, 7):
    # 注意:多项式1阶扩展,相当于没有扩展(没有变化)。
    poly = PolynomialFeatures(degree=n)
    X_transform = poly.fit_transform(X)
    # 注意:多项式扩展之后,我们依然可以将数据视为线性的,因此,我们还是可以通过之前的
    # LinearRegression来求解问题。
    lr = LinearRegression()
    # 使用多项式扩展之后的数据集来训练模型。
    lr.fit(X_transform, y)
    ax[n - 1].set_title(f"{n}阶,拟合度:{lr.score(X_transform, y):.3f}")
    ax[n - 1].scatter(x, y, c="g", label="样本数据")
    ax[n - 1].plot(x, lr.predict(X_transform), "r-", label="拟合线")
    ax[n - 1].legend()

 

 

流水线

在上例中,我们使用多项式对训练数据进行了转换(扩展),然后使用线性回归类(LinearRegression)在转换后的数据上进行拟合。可以说,这是两个步骤。我们虽然可以分别去执行这两个步骤,然而,当数据预处理的工作较多时,可能会涉及更多的步骤(例如标准化,编码等),此时再去一一执行会显得过于繁琐。
流水线(Pipeline类)可以将每个评估器视为一个步骤,然后将多个步骤作为一个整体而依次执行,这样,我们就无需分别执行每个步骤。流水线中的所有评估器(除了最后一个评估器外)都必须具有转换功能(具有transform方法)。
流水线具有最后一个评估器的所有方法。当调用某个方法f时,会首先对前n - 1个(假设流水线具有n个评估器)评估器执行transform方法(如果调用的f是fit方法,则n-1个评估器会执行fit_transform方法),对数据进行转换,然后在最后一个评估器上调用f方法。
例如,当在流水线上调用fit方法时,将会依次在每个评估器上调用fit方法,然后再调用transform方法,接下来将转换之后的结果传递给下一个评估器,直到最后一个评估器调用fit方法为止(最后一个评估器不会调用transform方法)。而当在流水线上调用predict方法时,则会依次在每个评估器上调用transform方法,最后在最后一个评估器上调用predict方法。

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
# sklearn库提供的流水线类,作用就是将多个评估器打包成一个整体,这样,当我们对流水线进行某些操作时,
# 流水线内的所有评估器都会执行相关的操作。这样,就可以作为一个整体而执行,无需我们分别对每个评估器
# 单独进行执行。
from sklearn.pipeline import Pipeline

mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False

x = np.linspace(0, 10, 50)
y = x * np.sin(x)
X = x[:, np.newaxis]
# 定义流水线中的每一个评估器。
# 格式为一个含有元组的列表。每个元组定义流水线中的一个步骤。
# 元组中含有两个元素。前面的元素为流水线步骤的名称,后面的
# 元素为该流水线步骤处理的对象。
estimators = [("poly", PolynomialFeatures()), ("lr", LinearRegression())]
# 创建流水线对象,将评估器数组传递给流水线类。
pipeline = Pipeline(estimators)
# 流水线的steps属性,可以返回流水线所有的步骤。包括步骤名与该步骤的处理对象。
# pipeline.steps
# 流水线的named_steps属性,与steps属性相似,只是类型不同(字典类型)。
# pipeline.named_steps
# 设置流水线对象的参数信息。
# 如果需要为流水线的某个步骤处理对象设置相关的参数,则参数名为:步骤名__处理对象参数。
pipeline.set_params(poly__degree=8)
# 获取流水线支持设置的所有参数。
# print(pipeline.get_params())

# 在流水线对象上调用fit方法,相当于对除了最后一个评估器之外的所有评估器调用fit_transform方法,
# 然后最后一个评估器调用fit方法。
pipeline.fit(X, y)
# 流水线对象具有最后一个评估器的所有方法。
# 当通过流水线对象,调用最后一个评估器的方法时,会首先调用之前所有评估器的transform方法。
score = pipeline.score(X, y)
plt.title(f"8阶,拟合度:{score:.3f}")
plt.scatter(X, y, c="g", label="样本数据")
# 当调用流水线对象的predict方法时,除最后一个评估器外,其余评估器会调用transform方法,然后,最后
# 一个评估器调用predict方法。
plt.plot(X, pipeline.predict(X), "r-", label="拟合线")


[<matplotlib.lines.Line2D at 0x95f3f98>]

 

 

多项式产生过拟合

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

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression

mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False

def true_fun(X):
    return np.cos(1.5 * np.pi * X)

np.random.seed(0)
n_samples = 30
degrees = [1, 4, 10, 15]

x_train = np.sort(np.random.rand(n_samples))
y_train = true_fun(x_train) + np.random.randn(n_samples) * 0.1
X_train = x_train[:, np.newaxis]

plt.figure(figsize=(18, 10))
for i, n in enumerate(degrees):
    plt.subplot(2, 2, i + 1)
    pipeline = Pipeline([("poly", PolynomialFeatures(degree=n)), 
            ("lr", LinearRegression())])
    pipeline.fit(X_train, y_train)
    train_score = pipeline.score(X_train, y_train)

    x_test = np.linspace(0, 1, 100)
    y_test = true_fun(x_test)
    X_test = x_test[:, np.newaxis]
    test_score = pipeline.score(X_test, y_test)
    plt.plot(X_test, pipeline.predict(X_test), label="预测线")
    plt.plot(X_test, true_fun(X_test), label="真实线")
    plt.scatter(X_train, y_train, c='b', s=20, label="样本数据")
    plt.xlabel("x")
    plt.ylabel("y")
    plt.xlim((0, 1))
    plt.ylim((-2, 2))
    plt.legend(loc="best")
    plt.title(f"训练集:{train_score:.3f} 测试集:{test_score:.3f}")
    # print(pipeline.named_steps["lr"].coef_)
plt.show()

说明:
以上假设样本数量为m,特征数量为n。
α>0并且0<=p<=1

 

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
mpl.rcParams["font.family"] = "serif"
mpl.rcParams["axes.unicode_minus"] = False

X, y, w = make_regression(n_samples=10, n_features=10, coef=True,
        random_state=1, bias=3.5)

alphas = np.logspace(-4, 4, 200)
coefs = []
ridge = Ridge()
for a in alphas:
    ridge.set_params(alpha=a)
    ridge.fit(X, y)
    coefs.append(ridge.coef_)

ax = plt.gca()
ax.plot(alphas, coefs)
ax.set_xscale('log')
ax.set_xlabel('alpha')
ax.set_ylabel('weights')

正则化之间的比较

  • L2正则化不会产生稀疏解,L1正则化会产生稀疏解,这也使得LASSO成为一种监督特征选择技术。
  • 如果数据的维度中存在噪音和冗余,稀疏的解可以找到有用的维度并且减少冗余,提高回归预测的准确性和鲁棒性。
  • Ridge模型具有较高的准确性、鲁棒性以及稳定性,而LASSO模型具有较高的求解速度。
  • 如果既要考虑稳定性也考虑求解的速度,可考虑使用Elasitc Net。
  • import numpy as np
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import PolynomialFeatures
    from sklearn.linear_model import LinearRegression, Lasso, Ridge, ElasticNet
    
    mpl.rcParams["font.family"] = "SimHei"
    mpl.rcParams["axes.unicode_minus"] = False
    
    def true_fun(X):
        return np.cos(1.5 * np.pi * X)
    
    np.random.seed(0)
    n_samples = 30
    
    x_train = np.sort(np.random.rand(n_samples))
    y_train = true_fun(x_train) + np.random.randn(n_samples) * 0.1
    X_train = x_train[:, np.newaxis]
    models = [("线性回归(无正则化)", LinearRegression()), ("L1正则化:", Lasso(alpha=0.01)), 
             ("L2正则化", Ridge(alpha=0.01)), ("弹性网络", ElasticNet(alpha=0.01, l1_ratio=0.5))]
    plt.figure(figsize=(18, 10))
    for i, (name, model) in enumerate(models):
        plt.subplot(2, 2, i + 1)
        pipeline = Pipeline([("poly", PolynomialFeatures(degree=15)), ("model", model)])
        pipeline.fit(X_train, y_train)
        train_score = pipeline.score(X_train, y_train)
    
        x_test = np.linspace(0, 1, 100)
        y_test = true_fun(x_test)
        X_test = x_test[:, np.newaxis]
        test_score = pipeline.score(X_test, y_test)
        plt.plot(X_test, pipeline.predict(X_test), label="预测线")
        plt.plot(X_test, true_fun(X_test), label="真实线")
        plt.scatter(X_train, y_train, c='b', s=20, label="样本数据")
        plt.xlabel("x")
        plt.ylabel("y")
        plt.xlim((0, 1))
        plt.ylim((-2, 2))
        plt.legend(loc="best")
        plt.title(f"{name} 训练集:{train_score:.3f} 测试集:{test_score:.3f}")
        print(pipeline.named_steps["model"].coef_)
    plt.show()

    训练集,验证集与测试集

    当模型建立后,我们需要评估下模型的效果,例如,是否存在欠拟合,过拟合等。但是,在我们建立模型时,我们不能使用全部数据用于训练(考试的示例)。因此,我们可以将数据集分为训练集与测试集。然而,模型并不是绝对单一化的,其可能含有很多种不同的配置方案(参数),这种参数不同于我们之前接触过的权重(w)与偏置(b),这是因为,权重与偏置是通过数据学习来的,而这种参数我们需要在训练前事先指定(例如,正则化的参数alpha等),而这些参数取值不同,也可能会导致训练出来模型的结果也不同。我们将这种参数称为超参数。可以说,超参数不是通过训练得出(事先指定),但是超参数的取值可能会对模型性能造成较大的影响。
    因此,我们需要不断去调整超参数的值,进而选择一个合适的超参数,使得模型的表现最优(或接近最优)。我们可以使用测试集去验证这一点。然而,这会导致选择的合适超参数后,无法去检验模型最终的效果。此时,我们可将数据进一步划分,即将原来的训练集再次切割,分为两部分:训练集与验证集。训练集用来建立模型(与之前相同),验证集用来选择合适的超参数,而测试集用于最终模型效果的评估(测试集应该总是作为最终结果的评估,而不是作为中间结果的评估)。

    交叉验证

    将数据分为训练集,验证集与测试集后,可以解决上述的问题,不过,这样划分依然具有缺陷:

  • 划分验证集后,会将模型的训练数据进一步减少,不利于模型的训练。
  • 不同的划分方式,模型的结果也可能不尽相同。
  • 我们可以使用交叉验证来解决以上问题。在训练集中,我们分成k个部分,然后使用其中的k - 1个部分作为训练集,剩下的一部分作为验证集来对模型进行评估。如此重复的进行k次,最终取k次评估的平均值。这种交叉验证方式我们称为“k折交叉验证”。
    k折交叉验证的优缺点如下:

  • 优点:不需要额外的验证集数据,因此不会令训练集数据较少。
  • 缺点:需要重复计算k次,大大增加的程序的运行时间。
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LassoCV, RidgeCV, ElasticNetCV

mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False

def true_fun(X):
    return np.cos(1.5 * np.pi * X)

np.random.seed(0)
n_samples = 30
x_train = np.sort(np.random.rand(n_samples))
y_train = true_fun(x_train) + np.random.randn(n_samples) * 0.1
X_train = x_train[:, np.newaxis]
alphas = [0.001, 0.005, 0.01, 0.05, 0.1, 0.5]
models = [("L1正则化:", LassoCV(alphas=alphas, max_iter=5000)), ("L2正则化", RidgeCV(alphas=alphas)), 
        ("弹性网络", ElasticNetCV(l1_ratio=0.5, alphas=alphas))]
plt.figure(figsize=(18, 5))
for i, (name, model) in enumerate(models):
    plt.subplot(1, 3, i + 1)
    pipeline = Pipeline([("poly", PolynomialFeatures(degree=15)), ("model", model)])
    pipeline.set_params(model__cv=10)
    pipeline.fit(X_train, y_train)
    train_score = pipeline.score(X_train, y_train)

    x_test = np.linspace(0, 1, 100)
    y_test = true_fun(x_test)
    X_test = x_test[:, np.newaxis]
    test_score = pipeline.score(X_test, y_test)
    plt.plot(X_test, pipeline.predict(X_test), label="预测线")
    plt.plot(X_test, true_fun(X_test), label="真实线")
    plt.scatter(X_train, y_train, c='b', s=20, label="样本数据")
    plt.xlabel("x")
    plt.ylabel("y")
    plt.xlim((0, 1))
    plt.ylim((-2, 2))
    plt.legend(loc="best")
    plt.title(f"{name} 训练集:{train_score:.3f} 测试集:{test_score:.3f}")
    print(pipeline.named_steps["model"].alpha_)
plt.show()

模型持久化

当我们训练好模型后,就可以使用模型进行预测。然而,这毕竟不像打印一个Hello World那样简单,当我们需要的时候,重新运行一次就可以了。在实际生产环境中,数据集可能非常庞大,如果在我们每次需要使用该模型时,都去重新运行程序,势必会耗费大量的时间。
为了方便以后能够复用,我们可以将模型保存,在需要的时候,直接加载之前保存的模型,就可以直接进行预测。其实,保存模型,就是保存模型的参数(结构),在载入模型的时候,将参数(结构)恢复成模型保存时的参数(结构)而已。

保存模型

注意:保存模型时,保存位置的目录必须事先存在,否则会出现错误。

 

from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.externals import joblib

X, y = load_diabetes(return_X_y=True)
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.25, random_state=0)
lr = LinearRegression()
lr.fit(train_X, train_y)
joblib.dump(lr, "lr.model")

载入模型

我们可以载入之前保存的模型,进行预测。

model = joblib.load("lr.model")
print(model.predict(test_X))

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深度学习中的过拟合欠拟合是两个常见的问题。 过拟合指的是模型在训练集上表现很好,但在测试集上表现不佳的情况。它表示模型在训练时过度适应了训练数据的噪声和细节,导致在新数据上泛化能力较差。过拟合表现通常是训练集上的损失函数较低,但测试集上的损失函数较高。 欠拟合指的是模型在训练集和测试集上都表现较差的情况,它表示模型无法很好地拟合训练数据。欠拟合的主要原因是模型的复杂度不足或者训练数据量不足。欠拟合表现通常是训练集和测试集上的损失函数都较高。 解决过拟合的常用方法包括: 1. 增加更多的训练数据,以减少模型对于噪声和细节的依赖。 2. 使用正则化技术,如L1正则化、L2正则化等,通过限制模型参数的大小来减少过拟合。 3. 使用早停法(early stopping),即在验证集上监测模型性能,当性能不再提升时停止训练,避免过拟合。 4. 使用Dropout技术,在训练过程中随机丢弃部分节点,减少模型的过度适应。 5. 进行数据增强,如图片旋转、翻转等,生成更多的训练样本,增加模型的泛化能力。 解决欠拟合的常用方法包括: 1. 增加模型的复杂度,如增加网络层数、增加每层的神经元数量等,提升模型的表达能力。 2. 增加特征工程,通过引入更多的特征或变换已有特征来提高模型的性能。 3. 减少正则化力度或者移除正则化操作,以允许模型更好地拟合训练数据。 4. 增加训练轮数和学习率,使模型更充分地学习训练数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值