机器学习:过拟合与欠拟合
一、基础理解
1)训练模型的目的
- 训练模型不是为了最大程度的拟合样本点,而是为了获得一个可以预测的模型,当有了新的样本时,该模型可以给出很好的解答,因此衡量模型对于训练数据集的拟合程度时是没有意义的,我们真正需要的是该模型的泛化能力;
- 均方误差:描述两组数之间的相同程度;
- 机器学习领域,用模型在 X_test 上的预测结果 y_predict 与 y 之间的均方误差反应模型拟合数据的程度;
- 均方误差越小,拟合程度越高,模型越可能过拟合;越大,拟合程度越低,模型越可能欠拟合;
- 均方误差 = 0:模型拟合住全部数据;
2)模型复杂度
-
degree 越大,多项式回归的模型越复杂
- 该模型,对于不同的算法表示不同的意思;
- 每一个模型都可以通过参数的调整,使得其从简单变复杂;
- 对于 kNN 算法,k 越小模型越复杂,k 越大模型越简单,当 k 取最大值,也就是 k 的值和样本种数一样时,模型最简单,此时也就是说,看数据集中哪种样本数量最多就认为预测结果为该最多的样本类型;当 k 为 1 时,为 kNN 算法中最复杂的模型,因为对于每一个预测点,都要找到离其最近的一个点;
3)一般来说有两个规律:
- 对于训练数据集:随着模型越来越复杂,模型的准确率对于 训练数据集 来说越来越高;
- 原因:模型越复杂,对于训练数据集的拟合程度越高,相应的对应训练数据集的准确率越来越高;
- 对于测试数据集:随着模型复杂度的增高,模型对新的样本数据的预测准确度会逐渐升高,在逐渐降低;
- 模型复杂度对于新的样本的拟合准确率变化的过程,其实就是模型从 欠拟合到过拟合的过程;
4)定义
- 欠拟合:算法所训练的模型不能完整表述数据关系;
- 数据关系:样本的分布规律,或者特征与对应样本之间的关系;
- 过拟合:算法所训练的模型过多地表达了数据间的关系;
- 过拟合所过多的表达的数据间的关系,很有可能是数据间的噪音关系;
- 也就是说,通常采集到的数据是有噪音的,拟合数据时很有可能把噪音当成了数据进行了训练所以就产生了过拟合的结果;当预测一个新的样本时,拟合的模型曲线实际上加入了很多其学习的噪音;
5)例:识别图片中的猫和狗
- 如果机器学习算法所学习的模型认为,所有的有眼睛的动物都称为 猫 / 狗,则该模型就是一个欠拟合的模型;因为该模型寻找的特征太普遍,太一般了,不能完整的表达要识别的内容所代表的特;
- 如果机器学习算法学习的模型认为,毛发的颜色为黄色的动物就是狗,则该模型就是一个过拟合的模型;因为并不是所有黄色毛发的动物都是狗,可能该模型只针对训练数据集进行了学习,而且学习到的是噪音,毛发为黄色并不是识别狗的一般特征,而是一个太细节的特征;
6)其它
- 对应欠拟合和过拟合,尤其是过拟合,在具体的机器学习的工作、算法开发的过程中,是一直要解决的重点问题;
- 最好的模型:泛化能力最好,也就是对应测试数据集准确率最高的模型;
- 机器学习主要解决的问题是过拟合;
- 在网格搜索中,将原始数据集分为训练数据集和测试数据集,训练数据集用于训练模型,对于不同参数训练出的模型,寻找使得测试数据集预测结果最好的对应的一组参数,作为最终获得的模型的参数;
- 使用此种方式虽然已经可以很好的评测模型的泛化能力,但此方法还不是最好的方法,
二、实例
1)模拟数据并使用线性回归拟合
-
获取并绘制数据集
import numpy as np import matplotlib.pyplot as plt np.random.seed(666) x = np.random.uniform(-3.0, 3.0, size=100) X = x.reshape(-1, 1) y = 0.5 * x**2 + x + 2 + np.random.normal(0, 1, size=100) plt.scatter(x, y) plt.show()
-
使用线性回归拟合数据
-
from sklearn.linear_model import LinearRegression lin_reg = LinearRegression() lin_reg.fit(X, y) lin_reg.score(X, y) # 输出:0.4953707811865009
# 准确率为 0.495,比较低,直线拟合数据的程度较低。
- 使用均方误差判断拟合程度
from sklearn.metrics import mean_squared_error y_predict = lin_reg.predict(X) mean_squared_error(y, y_predict) # 输出:3.0750025765636577
# mean_squared_error:求均方误差的方法。
- 绘制拟合结果
y_predict = lin_reg.predict(X) plt.scatter(x, y) plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r') plt.show()
-
分析
- 直接使用线性回归,和将样本添加了多项式特征后再使用线性回归,这两个线性回归对应的方程是不一样的,相应的求取的线性回归的系数的个数是不同的,虽然 R^2 都可以表示线性的拟合程度,但是两者是在不同的维度上拟合数据;
- 多项式回归中可以使用score求得的 R^2 来衡量数据拟合程度;
- 多项式回归也可以使用 “均方误差” 类衡量拟合数据的程度。
2)使用多项式回归拟合
-
封装 Pipeline 管道
from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.preprocessing import StandardScaler def PolynomialRegression(degree): return Pipeline([ ('poly', PolynomialFeatures(degree=degree)), ('std_scaler', StandardScaler()), ('lin_reg', LinearRegression()) ])
-
使用 Pipeline 拟合数据:degree = 2
poly2_reg = PolynomialRegression(degree=2) poly2_reg.fit(X, y) y2_predict = poly2_reg.predict(X) # 比较真值和预测值的均方误差 mean_squared_error(y, y2_predict) # 输出:1.0987392142417856
-
绘制拟合结果
plt.scatter(x, y) plt.plot(np.sort(x), y2_predict[np.argsort(x)], color='r') plt.show()
-
调整 degree = 10
poly10_reg = PolynomialRegression(degree=10) poly10_reg.fit(X, y) y10_predict = poly10_reg.predict(X) mean_squared_error(y, y10_predict) # 输出:1.0508466763764164 plt.scatter(x, y) plt.plot(np.sort(x), y10_predict[np.argsort(x)], color='r') plt.show()
-
调整 degree = 100
poly100_reg = PolynomialRegression(degree=100) poly100_reg.fit(X, y) y100_predict = poly100_reg.predict(X) mean_squared_error(y, y100_predict) # 输出:0.6874357783433694 plt.scatter(x, y) plt.plot(np.sort(x), y100_predict[np.argsort(x)], color='r') plt.show()
-
分析
- degree=2:均方误差为 1.0987392142417856;
- degree=10:均方误差为 1.0508466763764164;
- degree=100:均方误差为 0.6874357783433694;
- degree 越大拟合的效果越好,因为样本点是一定的,我们总能找到一条曲线将所有的样本点拟合,也就是说将所有的样本点都完全落在这根曲线上,使得整体的均方误差为 0;
- 红色曲线并不是所计算出的拟合曲线,而此红色曲线只是原有的数据点对应的 y 的预测值连接出来的结果,而且有的地方没有数据点,因此连接的结果和原来的曲线不一样;
-
还原原来的曲线
# np.linspace(-3, 3, 100):在 [-3, 3] 之间均匀取 100 个值,包含 -3 和 3 X_plot = np.linspace(-3, 3, 100).reshape(100, 1) # X_plot 的预测值 y_plot = poly100_reg.predict(X_plot) # 绘制 X_plot 和 y_plot :绘制结果比之前的结果更准确,因为 X_plot 的取值是在[-3, 3]之间均匀取值,所以不会出现两点之间间隔太大 plt.scatter(x, y) plt.plot(X_plot[:, 0], y_plot, color='r') # 设置坐标轴的范围 plt.axis([-3, 3, -1, 10]) plt.show()
- 曲线图
- 绘制结果比之前的结果更准确,因为 X_plot 的取值是在 [-3, 3] 之间均匀取值,所以不会出现两点之间间隔太大;
2)分析
- 疑问1:多项式回归的曲线是有规律的,样本点的分布是没有固定规律的,一定有一条曲线可以拟合住所有样本点吗?
- 答疑1:样本点的分布虽然没有规律,但是样本个数是有限的,而多项式曲线是无限的,推到理想化,多项式的幂数无限增大,总能达到可以完全拟合住所有样本点的时候;
- 疑问2:随着 degree 的增大,虽然均方误差会逐渐减小,但均方误差更小时得到的拟合曲线真的是反应样本数值走势相应的曲线吗?
- 疑问3:怎么界定拟合程度呢?也就是过拟合或者欠拟合。
三、train_test_split 的意义
1)问题
- 虽然学习到的曲线,使得整个样本使用这条曲线拟合的话,误差变小了,但是如果有一个新的样本的话,可能会出现,预测的结果不在样本分布的趋势上,则预测结果很有可能是错误的;
- 换句话说,在过拟合的场景下,虽然拟合曲线将原先的样本点拟合的程度非常高,总体的误差非常小,但是一旦有新的样本点,该模型曲线不能对新的样本点做出很好的预测,此种情况下此模型的泛化能力比较弱;
- 模型泛化的能力也就是由此及彼的能力,也就是根据已知的训练数据得到的模型曲线,在面对新的样本时,其预测能力非常弱,也就是其泛化能力非常差;
2)方案(train_test_split 的意义)
- 使用训练、测试数据集的分离,也就是将原本数据集分离成测试数据集和训练数据集,而获取模型只使用训练数据集,则测试数据集对于模型来说就是全新的数据,如果使用训练数据集得到的模型,对于测试数据集也能得到很好的结果,则可认为该模型的泛化能力很强,因为该模型能通过训练数据得到的结果很好的给出测试数据(从来没有见过的数据)相应的结果;
- 如果模型面对测试数据时给出的结果很差,该模型的泛化能力很弱,则很可能是在训练该模型时遭到了过拟合;
3)实例
-
模拟数据集
import numpy as np import matplotlib.pyplot as plt np.random.seed(666) x = np.random.uniform(-3.0, 3.0, size=100) X = x.reshape(-1, 1) y = 0.5 * x**2 + x + 2 + np.random.normal(0, 1, size=100)
-
分割数据集
from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
-
使用线性规划训练模型
from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error lin_reg = LinearRegression() lin_reg.fit(X_train, y_train) y_predict = lin_reg.predict(X_test) mean_squared_error(y_test, y_predict) # 输出:2.2199965269396573
# 均方误差:2.2199965269396573
-
使用多项式回归(使用管道 Pipeline)
from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error def PolynomialRegression(degree): return Pipeline([ ('poly', PolynomialFeatures(degree=degree)), ('std_scaler', StandardScaler()), ('lin_reg', LinearRegression()) ])
- degree = 2
poly2_reg = PolynomialRegression(degree=2) poly2_reg.fit(X_train, y_train) y2_predict = poly2_reg.predict(X_test) mean_squared_error(y_test, y2_predict) # 输出:0.80356410562979
# 均方误差:0.80356410562979
- degree = 10
poly10_reg = PolynomialRegression(degree=10) poly10_reg.fit(X_train, y_train) y10_predict = poly10_reg.predict(X_test) mean_squared_error(y_test, y10_predict) # 输出:0.9212930722150794
# 均方误差:0.9212930722150794
- degree = 100
poly100_reg = PolynomialRegression(degree=100) poly100_reg.fit(X_train, y_train) y100_predict = poly100_reg.predict(X_test) mean_squared_error(y_test, y100_predict) # 输出:14075796419.234262
# 均方误差:14075796419.234262
4)分析
- 如果模型面对测试数据时给出的结果很差,该模型的泛化能力很弱,则很可能是在训练该模型时遭到了过拟合;
- 训练模型不是为了最大程度的拟合样本点,而是为了获得一个可以预测的模型,当有了新的样本时,该模型可以给出很好的解答,因此衡量模型对于训练数据集的拟合程度时是没有意义的,我们真正需要的是该模型的泛化能力;