Auto Byte

专注未来出行及智能汽车科技

微信扫一扫获取更多资讯

Science AI

关注人工智能与其他前沿技术、基础学科的交叉研究与融合发展

微信扫一扫获取更多资讯

利用学习曲线诊断模型的偏差和方差

学习曲线是监督学习算法中诊断模型 bias 和 variance 的很好工具。本文将介绍如何使用 scikit-learn 和 matplotlib 来生成学习曲线,以及如何使用学习曲线来诊断模型的 bias 和 variance,引导进一步的优化策略。



在构建机器学习模型的时候,我们希望尽可能地保持最低的误差。误差的两个主要来源是 bias(偏差)和 variance(方差)。如果成功地将这两者减小了,我们就能构建更加准确的模型。


但是如何诊断 bias 和 variance 呢?如果检测到了这两者中存在的异常,又该如何处理呢?在这篇文章中,我们将使用学习曲线来回答以上两个问题。我们会使用实际的数据集来预测电厂的电能输出。


在预测电厂的电能输出的时候,会生成学习曲线。(图源:Pexels:https://www.pexels.com/photo/black-metal-current-posts-157827/)


假设读者熟悉 scikit-learn 和相关的机器学习理论。如果对交叉验证和监督学习不陌生,那么阅读此文是比较合适的。如果你是机器学习的新手,而且从来没有尝试过 scikit,你可以在这里学习一下(https://www.dataquest.io/blog/machine-learning-tutorial/)。


首先我们简单了解一下 bias 和 variance。


bias-variance 权衡


在监督学习中,我们假设特征和目标之间是有实际联系的,并且要用一个模型来预测这种未知关系。当假设是正确的时候,确实会存在这样一个模型,它能够完美地描述特征和目标之间的关系 f。


事实上,f 总是完全未知的,我们使用一个模型 f^来估计它(请注意 f 和 f^表达上的略微不同之处)。我们使用一个确定的训练集来得到一个确定的模型 f^。如果我们使用了一个不同的训练集,我们很可能会得到一个不同的 f^。f^随着我们对训练集的改变而变化的程度就叫做 variance。


为了估计真正的 f, 我们会使用线性回归或者随机森林等不同的方法。以线性回归为例,其假设特征和目标之间是线性的。然而,在大多数现实场景中,特征和目标之间的关系是复杂的,远非简单的线性关系。简化的假设为模型引入了 bias(偏差)。与实际关系对应的假设越错误,bias 就会越高,反之亦然。


通常,模型 f^在特定测试集上测试的时候会有一些误差。bias 和 variance 给模型带来的额外误差可以用数学的形式表示出来。为了得到较低的误差,我们需要尽可能将两者保持在各自的最小值。然而,这几乎是不可能的。在 bias 和 variance 之间存在 trade-off(权衡)。


低 bias 的模型会很好地适应训练数据。如果我们改变训练集,会得到特别不一样的模型 f^。


可以从图中看到,低 bias 的方法能够捕捉到不同训练数据集中的大部分差异(甚至是在较小的数据集中)。如果我们改变数据集的时候 f^会改变很多,意味着该模型是高 variance。


模型的 bias 越低,它适应数据的能力就越强,同时 variance 也越高。所以,bias 越低,variance 越高。


反过来也说得通:bias 越高,variance 越低。一个高 variance 的模型构建的简单模型通常是不能很好适应数据集的。当我们改变数据集的时候,从高 bias 的算法得到的模型 f^ 通常不会有很大不同。


如果我们改变训练集的时候 f^ 不会改变太多,那么 variance 就比较低,这恰好证明了我们的观点:bias 越高,variance 越低。


从数学上分析,我们想要得到低 bias 和低 variance 的原因是很明显的。如上所述,bias 和 variance 只能增加模型的误差。尽管从一个更直觉的角度而言,我们希望低 bias 来避免构建太简单的模型。在大多数情况中,简单的模型在训练集上的表现是很糟糕的,并且它极有可能在测试数据上也是同样糟糕的表现。


类似地,我们希望较低的 variance 来避免构建一个过于复杂的模型。这样的模型几乎能够完美的适应训练集中的所有数据点。然而,训练数据通常都包含噪声,而且它仅仅是更大的数据中的一个小样本。过于复杂的模型能够捕获这种噪声。当在样本外的数据上测试的时候,性能通常会很差。这是因为模型在样本训练数据上学习得太极致了。它对一些东西特别了解,但是对于其它一无所知。


在实际中,我们需要接受一个 trade-off。我们不可能同时得到低 bias 和低 variance,所以我们期望得到某种中间结果。


图源:http://scott.fortmann-roe.com/docs/BiasVariance.html


下面我们要生成并解释学习曲线,同时会对 tradeoff 有一些直观了解。


学习曲线-基本思想


假设我们拥有一些数据,并且将它们分割成训练集和验证集。我们从训练集中拿一个例子(对,仅用一个样本)来训练模型,并用它来估计一个模型。然后我们在验证集上衡量这个基于一个训练样本的误差。在训练集上的误差是 0,因为它能够很容易地适应一个数据点。然而,在验证集上的误差会特别大。这是因为,这个模型是在一个样本上建立的,它几乎不能够准确地泛化到之前没有见过的数据上。


现在我们考虑一下不是 1 个训练样本的情况,我们取 10 个训练样本来重复上述实验。然后我们取 50 个、100 个、500 个直至使用整个训练集。随着训练集的改变,误差得分会或多或少的改变。


因此我们会监控两个误差得分:一个针对训练集,另一个针对验证集。如果我们把两个误差得分随着训练集的改变画出来,最终我们会得到两个曲线。它们被称为学习曲线。


简而言之,学习曲线会展示误差是如何随着训练集的大小的改变而发生变化的。下图应该能够帮助你可视化我们到目前为止描述过的所有过程。在训练集这一列你可以看到,我们持续增加训练集的大小,这让我们的模型 f^发生了轻微的改变。


在第一行中,当 n=1(n 是训练集中样本的数量)的时候,模型能够完美地适应单个训练数据点。然而,同样的模型在具有 20 个数据点的验证集中性能很差。所以,模型在训练集中的误差是 0,但是在验证集中的误差特别高。


随着我们增加训练集的大小,模型不再完美地适应训练集了。所以训练误差变得更大了。但是因为模型在更多的数据上进行了训练,所以它能够更好地适应验证集。因此,验证误差降低了。要提醒您的是:下面三个实验中验证集是一样的。


如果把每个不同大小的训练集上的误差分数画出来,我们就能够得到两组看起来比较相似的学习曲线:


学习曲线可以诊断监督学习中的 bias 和 variance。下面我们探究一下如何进行:


数据介绍


上述的学习曲线是用于教学目的的理想情况。在实际中,它们通常看起来很不一样。所以,让我们使用现实的数据来讨论。


我们将要构建预测电厂每小时电能输出的回归模型。我们所用的数据来自于土耳其的研究者 Pınar Tüfekci 和 Heysem Kaya,数据可以在这里下载到(https://archive.ics.uci.edu/ml/datasets/Combined+Cycle+Power+Plant)。因为数据是以.xlsx 的形式存储的,所以我们使用 pandas 的 read_excel() 函数来读取它:

  1. import pandas as pd

  2. electricity = pd.read_excel('Folds5x2_pp.xlsx')

  3. print(electricity.info())

  4. electricity.head(3)

  1. <class 'pandas.core.frame.DataFrame'>

  2. RangeIndex: 9568 entries, 0 to 9567

  3. Data columns (total 5 columns):

  4. AT    9568 non-null float64

  5. V     9568 non-null float64

  6. AP    9568 non-null float64

  7. RH    9568 non-null float64

  8. PE    9568 non-null float64

  9. dtypes: float64(5)

  10. memory usage: 373.8 KB

  11. None


我们快速解释一下每一列的名字:


PE 这一列是目标变量,它描述的是每小时的电能输出净值。其他所有变量都是潜在特征,每个变量实际上都是每小时的平均值(而不像 PE 一样是净值)。


确定训练集的大小


我们首先确定用来生成学习曲线的训练集的大小。


最小值是 1,最大值是训练集的样本总数。我们的训练集共有 9568 个样本,所以最大值是 9568。


然而,我们还没有设置好验证集。我们将会使用 80:20 的比例来设置训练集和验证集,最终我们的训练集有 7654 个样本(80%),验证集有 1914 个样本(20%),用来生成学习曲线的训练集的最大样本数就是 7654。


在这种情况下,我们用以下 6 种大小的训练集:

  1. train_sizes = [1, 100, 500, 2000, 5000, 7654]


应该注意,每个特定大小的训练集都会训练一个新的模型。如果你使用了交叉验证,也就是我们在本文中使用的方法,那么每个训练集大小会训练出 k 个不同的模型(k 是交叉验证的次数)。为了节省代码的运行时间,将交叉验证设置到 5-10 是比较现实的。


scikit-learn 中的 learning_curve() 函数


我们将使用 scikit-learn 中的 learning_curve() 函数来生成一个回归模型的学习曲线。不需要我们自己设置验证集,learning_curve() 函数会自己完成这个任务。


在下面的代码中,我们执行了以下几点:


  • 从 sklearn 中完成需要的 import;

  • 声明特征和目标;

  • 使用 learning_curve() 函数生成需要的数据来绘制学习曲线。函数会返回一个包含三个元素的元组:训练集大小、训练集和验证集上的误差得分。在这个函数内部,我们使用了以下参数:

  •  estimator-代表我们估计实际模型时所用的学习算法;

  •  X-包含特征的数据;

  •  y-包含目标的数据;

  •  train_sizes—所用的特定的训练集大小;

  •  cv-确定交叉验证分割策略(我们马上会讨论这个内容);

  •  scoring-代表所用的误差指标;我们使用 nearest proxy 和负 MSE,我们随后必须颠倒一下符号。

  1. from sklearn.linear_model import LinearRegression

  2. from sklearn.model_selection import learning_curve

  3. features = ['AT', 'V', 'AP', 'RH']

  4. target = 'PE'

  5. train_sizes, train_scores, validation_scores = learning_curve(

  6.                                                   estimator = LinearRegression(), X = electricity[features],

  7.                                                   y = electricity[target], train_sizes = train_sizes, cv = 5,

  8.                                                   scoring = 'neg_mean_squared_error')

我们已经知道了什么是 train_sizes。现在让我们来查看一下 learning_curve() 函数返回的另外两个变量:

  1. print('Training scores:\n\n', train_scores)

  2. print('\n', '-' * 70) # separator to make the output easy to read

  3. print('\nValidation scores:\n\n', validation_scores)

  1. Training scores:

  2. [[ -0.          -0.          -0.          -0.          -0.        ]

  3. [-19.71230701 -18.31492642 -18.31492642 -18.31492642 -18.31492642]

  4. [-18.14420459 -19.63885072 -19.63885072 -19.63885072 -19.63885072]

  5. [-21.53603444 -20.18568787 -19.98317419 -19.98317419 -19.98317419]

  6. [-20.47708899 -19.93364211 -20.56091569 -20.4150839  -20.4150839 ]

  7. [-20.98565335 -20.63006094 -21.04384703 -20.63526811 -20.52955609]]

  8. ----------------------------------------------------------------------

  9. Validation scores:

  10. [[-619.30514723 -379.81090366 -374.4107861  -370.03037109 -373.30597982]

  11. [ -21.80224219  -23.01103419  -20.81350389  -22.88459236  -23.44955492]

  12. [ -19.96005238  -21.2771561   -19.75136596  -21.4325615   -21.89067652]

  13. [ -19.92863783  -21.35440062  -19.62974239  -21.38631648  -21.811031  ]

  14. [ -19.88806264  -21.3183303   -19.68228562  -21.35019525  -21.75949097]

  15. [ -19.9046791   -21.33448781  -19.67831137  -21.31935146  -21.73778949]]


因为我们指定了 6 个不同的训练集大小,你或许期望看到每个误差得分有 6 个结果。然而,我们得到结果是每个误差得分对应着 6 行数字,每行有 5 个误差得分。


出现这个结果的原因是 learning_curve() 函数运行了 k-fold 交叉验证, 其中 k 的值是通过我们所赋的 cv 参数指定的。


在我们的实验中,cv = 5, 所以会有 5 次分割。每次分割都会在特定大小的训练集上训练出一个模型。上面数组中的每一列都代表一次分割,每一行代表一个训练集大小。下表给出的训练误差有助于您更好地理解这个过程。


为了画出学习曲线,对于每个训练集大小,我们只需要一个误差得分。基于这个原因,在下面的代码中我们会对每一行中的值求平均值,并且颠倒误差得分的符号(正如前面讨论过的一样):

  1. train_scores_mean = -train_scores.mean(axis = 1)

  2. validation_scores_mean = -validation_scores.mean(axis = 1)print('Mean training scores\n\n', pd.Series(train_scores_mean, index = train_sizes))print('\n', '-' * 20) # separatorprint('\nMean validation scores\n\n',pd.Series(validation_scores_mean, index = train_sizes))

  3. Mean training scores

  4. 1       -0.000000

  5. 100     18.594403

  6. 500     19.339921

  7. 2000    20.334249

  8. 5000    20.360363

  9. 7654    20.764877

  10. dtype: float64

  11. --------------------

  12. Mean validation scores

  13. 1       423.372638

  14. 100      22.392186

  15. 500      20.862362

  16. 2000     20.822026

  17. 5000     20.799673

  18. 7654     20.794924

  19. dtype: float64


现在我们已经有了所有的数据了,只需要画出学习曲线。


然而,在绘制学习曲线之前,我们需要停下来做一个重要的观察。也许你已经注意到了,在有些不同大小的训练集上,误差得分是相同的。对于训练集样本数为 1 的那一行,出现这种情况并不意外,(因为都是 0),但是对于其他行呢?除了最后一行,我们有很多相同的值。例如,第二行中有很多值是和第二列相同的,为什么会这样呢?


这是由于没有对每一份训练集做随机化处理形成的。让我们在下表的帮助下看看下一个例子。当我们的训练集大小是 500 的时候,前 500 个样本被选择。对于第一次分割,这 500 个样本会从第二块中选择。从第二次分割开始,这 500 个样本都将从第一块中选择。因为我们没有随机化训练集,从第二次分割时,这 500 个样本都是一样的。这能够合理解释前面提到的在 500 个训练样本的训练集中,从第二次分割开始所有的误差得分都是一样的结果。


同样的推理能够适用于 100 个样本的情况,类似的推理也适用于其他情况。


为了消除这种现象,我们需要在 learning_curve() 函数中将 shuffle 参数设置为 true。这就能够将训练集中的每一次分割的数据索引随机化。我们之前没有做随机处理的原因是:


  • 数据集文档中已有说明,数据进行了 5 次随机化处理,所以这里没有必要再做随机化处理。

  • 我想让你了解这种看起来很奇怪的现象,以免在实践的过程中困在这个问题上。


最后,我们绘制学习曲线。


学习曲线-高 bias 和低 variance


我们使用常规的 matplotlib 流程来绘制学习曲线:

  1. import matplotlib.pyplot as plt%matplotlib inline

  2. plt.style.use('seaborn')

  3. plt.plot(train_sizes, train_scores_mean, label = 'Training error')

  4. plt.plot(train_sizes, validation_scores_mean, label = 'Validation error')

  5. plt.ylabel('MSE', fontsize = 14)

  6. plt.xlabel('Training set size', fontsize = 14)

  7. plt.title('Learning curves for a linear regression model', fontsize = 18, y = 1.03)

  8. plt.legend()

  9. plt.ylim(0,40)

  10. (0, 40)


我们可以从这幅图中提炼出很多信息。下面我们详细探讨:


当训练集的大小是 1 的时候,我们可以看到训练集中的 MSE 是 0。这是很正常的情况,因为模型能够完美地适应一个数据点,在训练集中的预测结果是完美的。


但是在验证集上(验证集有 1914 个样本)测试模型的时候,MSE 会剧烈增长到 423.4。由于这个值特别大,所以我们将 Y 轴的区间限制在了 0 到 40。这让我们能够准确地读到大多数 MSE。因为一个仅在单个样本上训练得到的模型是极其不可能泛化到 1914 个从未见过的新样本上的,所以这个结果也在预料之中。


当训练集样本数为 100 的时候,训练过程的 MSE 会急剧增大,而验证过程的 MSE 会减小。这个线性回归模型不能完美地预测所有的 100 个数据点,所以 MSE 会大于 0。然而,此时训练集的性能仍然优于验证集,这是由于在验证集上估计了更多的数据。


从 500 个数据点开始,验证集的 MSE 能够保持大致不变。这给我们一个重要信息:增加更多的训练数据点也不会带来更好的模型。所以与其浪费时间(金钱)来收集数据,我们更需要的是做点其他事情,例如尝试一下能够构建更加复杂模型的算法。


为了避免误解概念,需要注意的很重要的一点是:增加更多的训练数据样本确实是无济于事的。然而,增加更多的特征就是另外一回事了,因为增加特征能够增加模型的复杂度。


现在我们来讨论一下 bias 和 variance 的诊断。bias 问题的主要标志是较高的验证误差。在我们的例子中,验证 MSE 保持在接近 20 的值。但是这个值有多好呢?


从技术角度而言,大小为 20 的 MSE 的单位是兆瓦特^2(MW^2)(因为计算 MSE 的时候取了平方)。但是我们目标列中的数值是以 MW 为单位的。给 20MW^2 取平方根,得到的近似值是 4.5MW。每个目标值代表的是一小时的最终电能输出。所以每小时我们的模型都会接近于 4.5MW 的平均值。Quora 有这么一个答案,4.5MW 的能量相当于 4500 个手持吹风机产生的热能。如果我们要预测更长时间(比如一天或者更长时间)的电能输出的时候,这种误差会更大。


由此可以确定,20MW^2 的 MSE 是相当大的。所以我们的模型存在 bias 问题。但是它是一个低 bias 问题呢还是高 bias 问题呢?


为了找到这个答案,我们需要注意一下训练误差。如果训练误差特别小,这就说明估计模型能够很好地拟合训练数据,这就是说模型在对应的数据集上有较小的 bias。


如果训练误差比较高,就说明估计模型不能很好地拟合训练数据,也就意味着在对应的数据集上有较高的 bias。


在我们的例子中,训练过程的 MSE 稳定在 20MW^2 左右。我们在前面分析过,这是一个相当高的误差得分。因为验证过程的 MSE 比较高,所以训练 MSE 也是比较高的,我们的模型就有一个较高的 bias 问题。


现在让我们诊断一下最终的 variance 问题。对 variance 的估计可以通过以下两种方式完成:


  • 通过检查验证学习曲线和训练学习曲线之间的差距;

  • 通过检查训练误差:检查误差的值随着训练样本数增加的变化。


较小的差距代表较小的 variance。通常,差距越小,variance 越小。反之亦然:差距越大,variance 越大。


正如我们之前观察到的一样,如果 variance 比较大,那么说明模型过于拟合训练数据了。当模型过拟合的时候,它在泛化到从未见过的数据上时会存在问题。当这样一个模型分别在训练集和验证集上测试的时候,训练误差会比较低,验证误差通常会比较高。当我们改变训练集大小的时候,这种模式会继续存在,训练集和验证集之间的差距会决定这两个学习曲线之间的距离。


训练误差和验证误差之间的关系,以及训练学习曲线和验证学习曲线之间的差距可以总结如下:


gap=validation error−training error


两个误差之间的差距越大,曲线之间的距离越大,variance 越大。


在我们的情况中,曲线之间的差距是比较小的,所以我们可以稳妥地说,模型的 variance 是比较低的。


高的训练 MSE 得分也是一个快速检测低 variance 的方式。如果学习算法的 variance 比较低,那么当我们改变训练集的时候算法会生成比较简单并且比较相似的模型。因为模型过于简单,它们甚至不能很好的拟合训练数据集(欠拟合)。所以这种情况应该是较高的训练 MSE。所以,高训练 MSE 可以作为低 variance 的标志。


在我们的例子中,训练 MSE 大约稳定在 20,我们已经证明过,这是一个很高的值。所以,除了较小的学习曲线差距之外,我们可以使用较大的训练误差来确认模型具有较低 variance 问题。


目前,我们可以总结如下:


  • 我们的学习算法会遇到这几个问题:高 bias,低 variance,以及对训练数据的欠拟合。

  • 在目前的学习算法下,增加更多的训练样本极不可能得到更好的模型。


在这种情形下我们的解决方案是转向一个更加复杂的学习算法。这应该能够降低 bias,并增加 variance。尝试增加训练样本的数量是一个误区。


通常,以下两种修正方式在处理高 bias 和低 variance 的问题时会比较奏效:


  • 用更多的特征训练当前的学习算法,即通过增加模型的复杂度来降低 bias。

  • 减少对当前算法的正则化。简言之,正则化能够避免算法在训练数据上过拟合。如果我们减少了正则化,模型会更好地拟合训练数据,所以,就会增加 variance,降低 bias。


学习曲线-低 bias 和高 variance


让我们看一下未正则化的随机森林回归器是如何运行的。我们使用和前面相同的流程生成学习曲线。这一次我们会将所有的内容封装在一个函数中,以便以后使用。作为对照,我们也会展示出线性回归模型的曲线。

  1. ### Bundling our previous work into a function ###def learning_curves(estimator, data, features, target, train_sizes, cv):

  2.    train_sizes, train_scores, validation_scores = learning_curve(

  3.                                                 estimator, data[features], data[target], train_sizes = train_sizes,

  4.                                                 cv = cv, scoring = 'neg_mean_squared_error')

  5.    train_scores_mean = -train_scores.mean(axis = 1)

  6.    validation_scores_mean = -validation_scores.mean(axis = 1)

  7.    plt.plot(train_sizes, train_scores_mean, label = 'Training error')

  8.    plt.plot(train_sizes, validation_scores_mean, label = 'Validation error')

  9.    plt.ylabel('MSE', fontsize = 14)

  10.    plt.xlabel('Training set size', fontsize = 14)

  11.    title = 'Learning curves for a ' + str(estimator).split('(')[0] + ' model'

  12.    plt.title(title, fontsize = 18, y = 1.03)

  13.    plt.legend()

  14.    plt.ylim(0,40)### Plotting the two learning curves ###from sklearn.ensemble import RandomForestRegressor

  15. plt.figure(figsize = (16,5))for model, i in [(RandomForestRegressor(), 1), (LinearRegression(),2)]:

  16.    plt.subplot(1,2,i)

  17.    learning_curves(model, electricity, features, target, train_sizes, 5)


观察学习曲线,我们可以发现已经成功地降低了 bias。虽然还存在很明显的 bias,但是已经不像之前那么大了。观察训练曲线我们可以判断,这次的模型具有较低的 bias 问题。


两条曲线之间的差距表明模型的 variance 有着大幅度的增大。较小的训练 MSE 证实了对高 variance 的判断。


较大的曲线差距和较低的训练误差同样也标志着过拟合问题的存在。当模型在训练集上性能较好,而在测试集上性能很差的时候,就是过拟合问题。


我们在这里还能观察到的另一个重要现象就是:增加新的训练样本很可能能够得到更好的模型。验证学习曲线并没有稳定在使用最大训练样本量的地方。它还有继续降低,朝着训练学习曲线收敛的潜力,这和我们在线性回归模型的情况中看到的收敛是类似的。


目前,我们可以得到如下结论:


  • 随机森林出现了较高的 variance 和相当低的 bias,以及在训练集上的过拟合问题。

  • 在目前的学习算法下,增加更多的学习样本非常有可能得到更好性能的模型。


至此,我们可以做以下的事来改善我们的模型:


  • 增加更多的训练样本

  • 为目前所用的算法增加正则化。这会增加模型的 bias,降低模型的 variance。

  • 减少我们目前在训练及数据中所用的特征数。算法仍旧会很好地适应训练集,但是由于特征数目减少了,算法会构建相对简单的模型。这应该能够增加模型的 bias,降低模型的 variance。


我们还是要对随机森林算法尝试一下正则化。方式之一就是调整每个决策树叶子节点的最大值。这可以通过使调整 RandomForestRegressor() 函数的 max_leaf_nodes 参数来实现。你没有必要理解这个正则化技术。我们的目标是使你能够注意正则化对学习曲线带来的影响。

  1. learning_curves(RandomForestRegressor(max_leaf_nodes = 350), electricity, features, target, train_sizes, 5)


还不错,训练学习曲线和测试学习曲线之间的差距缩小了。bias 好像增大了一些,这正是我们想要的结果。


但是我们的工作还未结束。验证过程的 MSE 还有继续降低的潜力。为了达到这个目标,还有一些可以做的工作:


  • 增加更多的训练样本

  • 增加特征

  • 特征选择

  • 超参数优化


理想化的学习曲线和不可约化的误差


这两种学习曲线构成了一个可以对机器学习过程中的模型进行快速检查的很好的工具,那么我们怎么知道何时停止呢?怎么识别完美的学习曲线呢?


对于我们之前的回归例子,你也许会认为最好的情形应该是两条学习曲线都收敛至 MSE 为 0 的时候。那是完美的情况,可是事实上,很不幸这是不可能的。无论是从实践角度还是理论角度。这是由于不可约误差(irreducible error)的存在。


当我们构建一个能够映射特征 X 和目标 Y 的关系的模型时,我们首先会假设存在这么一个关系。在假设正确的条件下,会存在一个能够完美描述 X 和 Y 之间关系的模型,就像这样:


Y=f(X)+irreducible error  (1)


可是这里为啥会有一个 error 项呢?我们不是说 f 能够完美地描述 X 和 Y 之间的关系吗?


存在误差的原因是 Y 并不是我们所拥有的有限特征 X 的函数。还有更多的特征能够影响 Y。而我们没有这些特征。还有可能是这种情况:X 包含测量误差,所以 Y 也是一个不可约误差的函数。


现在我们解释一下这个误差不可约的原因。当我们用 f^(X) 估计 f(X) 时,我们引入另一个误差—可约误差(reducible error)。


f(X)=^f(X)+reducible error  (2)


将公式(1)中的 f(X) 替换掉,我们得到下面的式子:


Y=^f(X)+reducible error+irreducible error  (3)


可约误差可以通过构建更好的模型来减小。从方程(2)中我们可以发现:如果可约误差变成 0,我们的估计模型 f^(X) 等于真实模型了。然而,从方程(3)中我们可以看到,即使可约误差变成了 0,不可约误差仍旧存在。这就是这个误差被称作不可约误差的原因。


这告诉我们,在实际中性能最好的模型会收敛于某个不可约误差,而不是理想的误差值(对于 MSE,理想的误差值是 0;我们将会看到,其他的误差值会有和 MSE 不同的理想值)。


在实际中,不可约误差的准确值几乎总是未知的。我们也假设不可约误差和 X 是独立的。这意味着我们不能使用 X 来寻找真实的不可约误差。用更加严密的数学语言描述,就是:不存在从 X 到不可约误差之间的映射函数 g。


irreducible error≠g(X)


所以,没有办法基于我们所拥有的数据来知道不可约误差的真实值。实际上,最佳应对方法就是尝试得到尽可能小的误差得分,同时要记得:误差得分的极限是某个给定的不可约误差。


对于分类问题,又是怎么样的呢?


目前,我们已经了解了回归问题中的学习曲线。对于分类任务,这个过程几乎是一样的。主要的区别就是:我们必须选择另一个误差度量--一个能够用来衡量分类器性能的度量。让我们看一个例子:



图源:scikit-learn 文档(http://scikit-learn.org/stable/auto_examples/model_selection/plot_learning_curve.html)


与我们之前看到的不一样的是,你要注意到训练学习曲线位于验证学习曲线上方。这是因为我们使用的误差得分是准确率,用准确率来描述模型的性能。准确率越高,模型性能越好。而 MSE 是在描述模型有多差。MSE 越小,模型性能越好。


这幅图中也存在不可约误差的含义。对于描述模型有多差的度量指标而言,不可约误差是以下限的形式存在:实际模型不可能比它还低。对于描述模型有多好的度量指标而言,不可约误差是以上限的形式存在:实际模型不可能比它高。


要注意的一点是,在更多数技术性写作中,贝叶斯误差通常指的是分类器的可能最佳错误得分。这个概念和不可约误差是类似的。


后续内容


在任何监督学习算法中,学习曲线构成了诊断模型 bias 和 variance 的很好工具。我们已经学到了如何使用 scikit-learn 和 matplotlib 来生成学习曲线,以及如何使用学习曲线来诊断模型的 bias 和 variance。


为了强化你所学到的内容,可以考虑以下的内容:


  • 使用另一个数据集为回归模型生成学习曲线;

  • 为分类任务生成学习曲线;

  • 不使用 learning_curve() 函数,手写代码,从 0 开始得到一个监督学习任务的学习曲线。可选择交叉验证;

  • 对比一下使用交叉验证和未使用交叉验证的学习曲线。这两种曲线应该对应同一个学习算法(数据也应该是一样的)。


原文链接:https://www.dataquest.io/blog/learning-curves-machine-learning/

工程工程机器学习
10
暂无评论
暂无评论~