# 八、处理真实数据 在本章中,我们将讨论处理现实数据的挑战,以及您可能会遇到的一些怪癖。 本章首先讨论偏差方差权衡,这是一种更原则性的方式,用于讨论您可能过拟合和欠拟合数据的不同方式,以及它们之间如何相互关联。 然后,我们讨论 K 折交叉验证技术,该技术是打击过拟合的重要工具,并介绍了如何使用 Python 实现该技术。 接下来,我们分析在实际应用任何算法之前清除数据并对其进行规范化的重要性。 我们看到一个示例,用于确定网站上最受欢迎的页面,这些页面将演示清洁数据的重要性。 本章还涵盖了记住对数值数据进行规范化的重要性。 最后,我们研究如何检测异常值并加以处理。 具体而言,本章涵盖以下主题: * 分析偏差/方差的权衡 * K 折交叉验证的概念及其实现 * 数据清理和规范化的重要性 * 确定网站受欢迎页面的示例 * 归一化数值数据 * 检测并处理异常值 # 偏差/方差权衡 在处理现实世界数据时,我们面临的基本挑战之一是拟合过高与拟合过低而不是拟合数据,模型或预测。 当我们谈论欠拟合和过拟合时,我们经常可以在偏差和方差以及偏差与方差之间进行权衡。 所以,让我们谈谈这意味着什么。 因此,从概念上讲,偏差和方差非常简单。 偏差与您与正确值的距离有多远,也就是说,您的总体预测在预测正确的总体值方面有多好。 如果您采用所有预测的均值,它们或多或少都在正确的位置上? 还是您的错误始终在一个方向或另一个方向上始终偏斜? 如果是这样,则您的预测会偏向某个方向。 方差只是衡量预测的分散程度和分散程度的指标。 因此,如果您的预测无处不在,那么差异就很大。 但是,如果他们非常关注正确的值,或者在高偏差的情况下甚至不正确的值,那么您的差异就很小。 让我们看一些例子。 让我们想象一下,以下飞镖代表了我们所做的一系列预测,而我们试图预测的实际值位于靶心的中心: ![](img/fae2cf23-43e6-4526-b005-39df58463d04.png) * 从左上角的飞镖板开始,您可以看到我们的点都分散在中心周围。 因此,总的来说,您知道平均误差非常接近实际。 我们的偏见实际上非常低,因为我们的预测都围绕着相同的正确点。 但是,我们的方差很高,因为这些点散布在整个地方。 因此,这是低偏差和高方差的示例。 * 如果我们继续移动到右上角的飞镖板上,就会看到我们的点始终一致地从其应有的位置偏向西北。 因此,这是我们的预测中存在高度偏见的一个示例,在这些偏见中始终存在一定偏差。 我们的差异很小,因为它们都紧密地聚集在错误的位置,但是至少它们彼此靠近,因此我们的预测是一致的。 那是低方差。 但是,偏见很高。 同样,这是高偏差,低方差。 * 在左下角的飞镖板上,您可以看到我们的预测分散在错误的均值周围。 因此,我们有很高的偏见。 一切都偏向了不该有的地方。 但是我们的差异也很大。 因此,这是两个世界中最糟糕的一个。 在此示例中,我们具有较高的偏见和较高的方差。 * 最后,在一个奇妙的完美世界中,您将看到一个示例,例如右下方向的飞镖盘,其中我们的偏见低,所有事物都集中在应该放置的地方,而方差很小,所有事物都紧密地聚集在应该放置的地方。 因此,在一个完美的世界中,这就是最终的结果。 实际上,您通常需要在偏差和方差之间进行选择。 归结为过拟合对数据的拟合不足。 让我们看下面的例子: ![](img/7a856ab7-9d88-404c-8cfb-97365495b525.png) ![](img/0386221c-18f7-4cc9-88aa-9139c77d4ba5.png) 这与偏见和差异的思考方式有些不同。 因此,在左图中,我们有一条直线,相对于这些观察,您可以认为这具有非常低的方差。 因此,该行没有太多变化,即,变化很小。 但是偏差(每个点的误差)实际上很高。 现在,将其与右图中的过拟合数据进行对比,我们已经竭尽全力来拟合观察值。 该线具有较高的方差,但具有较低的偏差,因为每个点都非常接近应有的位置。 因此,这是我们用方差权衡偏差的一个例子。 归根结底,您并不想减少偏差或减少方差,而是想减少误差。 真正重要的是,事实证明您可以将误差表示为偏差和方差的函数: ![](img/459a0f30-4b0c-4dfd-a4fb-91b39c005369.png) 以此来看,误差等于偏差平方加方差。 因此,这些因素都会造成整体误差,而偏差实际上会造成更大的误差。 但请记住,这是您真正要最小化的误差,而不是具体的偏差或方差,并且过于复杂的模型最终可能具有高方差和低偏差,而过于简单的模型将具有低方差和高偏差。 偏见。 但是,它们最终都可能在一天结束时具有相似的错误条件。 尝试拟合数据时,您只需要找到这两件事的正确的快乐媒介即可。 在接下来的部分中,我们将讨论一些实际上避免过拟合的更原则的方法。 但是,这只是我想克服的偏见和差异的概念,因为人们确实在谈论它,并且您将被期望知道这意味着什么。 现在,让我们将其与本书中较早的概念联系起来。 例如,在`k`个最近的邻居中,如果我们增加 K 的值,我们就会开始平均扩展到更大区域的邻居。 这具有减少方差的效果,因为我们可以在更大的空间上使事物平滑,但是它可能会增加我们的偏见,因为我们将要挑选到的人群可能与我们开始时的相关性越来越小 。 通过在大量邻居上平滑 KNN,我们可以减小方差,因为我们正在通过更多值平滑事物。 但是,我们可能会引入偏见,因为我们引入了越来越多的点,而这些点与开始时的点之间的关联性却不那么高。 决策树是另一个例子。 我们知道,单个决策树很容易过拟合,因此可能暗示它具有很大的方差。 但是,随机森林寻求权衡一些方差以减少偏差,并且它通过拥有多个随机变异的树并将其所有解决方案求平均来做到这一点。 就像当我们通过增加 KNN 中的 K 来对事物进行平均时:我们可以通过使用多个使用相似森林的随机森林决策树来平均决策树的结果。 这是偏差方差的权衡。 您知道必须在值的整体准确率,值的分散程度或紧密程度之间做出决定。 那就是偏差-方差的权衡,它们都会导致整体误差,这是您真正关心的要最小化的事情。 因此,请记住这些条款! # 用于避免过拟合的 K 折交叉验证 在本书的前面,我们讨论了训练和测试,将其作为防止过拟合并实际测量模型在从未见过的数据上的表现的好方法。 我们可以使用称为 K 折交叉验证的技术将其提升到一个新的水平。 因此,让我们谈谈您的武器库中用于对抗过拟合的强大工具; K 折交叉验证并了解其工作原理。 回想一下训练/测试,当时的想法是,我们将要构建机器学习模型的所有数据分为两个部分:训练数据集和测试数据集。 想法是,我们仅使用训练数据集中的数据来训练模型,然后使用为测试数据集保留的数据评估模型的表现。 这样可以防止我们过拟合现有的数据,因为我们正在针对该模型进行过从未见过的数据测试。 但是,训练/测试仍然有其局限性:您仍然可能最终过度适合您的特定训练/测试单元。 也许您的训练数据集并不能真正代表整个数据集,并且太多的东西最终出现在您的训练数据集中,使事情发生了偏差。 因此,这就是 K 折交叉验证的用武之地,它需要训练/测试并将其提升一个档次。 这个想法虽然听起来很复杂,但相当简单: 1. 我们没有将数据分为两个存储桶,一个用于训练,一个用于测试,而是将其分为 K 个存储桶。 2. 我们保留其中一个存储桶用于测试目的,以评估模型的结果。 3. 我们针对剩余的`K-1`桶对模型进行训练,然后获取测试数据集,并使用该数据集来评估模型在所有这些不同训练数据集中的表现。 4. 我们将这些结果误差度量(即那些 R 平方值)平均,以从 K 折交叉验证中获得最终误差度量。 仅此而已。 这是一种进行训练/测试的更强大的方法,而这也是一种方法。 现在,您可能会好好考虑一下,如果我对保留的那一个测试数据集过拟合该怎么办? 对于这些训练数据集中的每个数据集,我仍使用相同的测试数据集。 如果该测试数据集也不能真正代表事物怎么办? 有 K 折交叉验证的变体,也可以将其随机化。 因此,您可以每次都随机选择训练数据集是什么,而只是保持随机地将事物分配给不同的存储桶并测量结果。 但是通常,当人们谈论 K 折交叉验证时,他们谈论的是这种特定技术,即您保留一个存储桶用于测试,其余的存储桶用于训练,然后当您为每个桶建立一个模型时,相对于测试数据集评估全部训练数据集。 # 使用 Scikit-learn 的 K 折交叉验证的示例 幸运的是,Scikit-learn 使得此操作确实非常容易,并且比进行常规训练/测试更容易! 进行 K 折叠交叉验证非常简单,因此您也可以这样做。 现在,这一切在实践中的工作方式是您将要尝试调整一个模型,并且该模型将具有不同的变体,您可能需要对其进行调整的不同参数,对吗? 例如,多项式拟合的多项式次数。 因此,我们的想法是尝试使用模型的不同值,不同的变体,使用 K 折交叉验证对它们进行度量,然后找到一个对测试数据集最小化误差的变量。 那是您在那里的最佳去处。 在实践中,您想使用 K 折交叉验证来衡量模型相对于测试数据集的准确率,并且只是不断完善该模型,继续尝试其中的不同值,继续尝试该模型的不同变体,甚至尝试完全不同的建模方法,直到您找到使用 K 折交叉验证最大程度地减少误差的技术为止。 让我们深入一个例子,看看它是如何工作的。 我们将再次将其应用于鸢尾花数据集,重新访问 SVC,然后我们将进行 K 折交叉验证,看看它有多简单。 我们实际上使用一些真实的 Python 代码在此处进行 K 折交叉验证和训练/测试。 您会发现它实际上非常易于使用,这是一件好事,因为这是您应使用的一种技术,用于在监督学习中测量模型的准确率和有效性。 请继续打开`KFoldCrossValidation.ipynb`,然后继续操作。 我们将再次查看鸢尾花数据集; 还记得我们在谈到降维时介绍过的吗? 只是为了刷新您的记忆,鸢尾花数据集包含一组 150 个鸢尾花花的测量值,其中每朵花的花瓣的长度和宽度以及其萼片的长度和宽度。 我们也知道每朵花属于 3 种不同鸢尾花中的哪一种。 面临的挑战是创建一个模型,只要给定其花瓣和萼片的长度和宽度,就可以成功地预测鸢尾花的种类。 因此,让我们继续进行。 我们将使用 SVC 模型。 如果您还记得,那只是对数据进行分类的一种非常可靠的方法。 如果您需要去刷新内存,则有一个章节: ```py import numpy as np from sklearn import cross_validation from sklearn import datasets from sklearn import svm iris = datasets.load_iris() # Split the iris data into train/test data sets with #40% reserved for testing X_train, X_test, y_train, y_test = cross_validation.train_test_split(iris.data, iris.target, test_size=0.4, random_state=0) # Build an SVC model for predicting iris classifications #using training data clf = svm.SVC(kernel='linear', C=1).fit(X_train, y_train) # Now measure its performance with the test data clf.score(X_test, y_test) ``` 我们要做的是使用 Scikit-learn 的`cross_validation`库,我们首先进行常规的训练测试拆分,仅进行一次训练/测试拆分,然后看看如何工作。 为此,我们有一个`train_test_split()`函数,使它非常容易。 因此,此方法的工作方式是将一组特征数据输入到`train_test_split()`中。 `iris.data`仅包含每朵花的所有实际测量值。 `iris.target`基本上是我们要预测的事情。 在这种情况下,它包含每朵花的所有种类。 `test_size`说我们要训练与测试的百分比。 因此,0.4 表示我们将随机抽取 40% 的数据用于测试,并将 60% 的数据用于训练。 这带给我们的是 4 个数据集,基本上是特征数据和目标数据的训练数据集和测试数据集。 因此,`X_train`最终包含鸢尾花测量值的 60%,`X_test`包含 40% 用于测试模型结果的测量值。 `y_train`和`y_test`包含这些段中每个段的实际种类。 然后,在此之后,我们继续构建一个 SVC 模型,以根据鸢尾花物种的测量值来预测它们,然后仅使用训练数据来构建该模型。 我们使用线性核,仅使用训练特征数据和训练种类数据(即目标数据)来拟合此 SVC 模型。 我们称该模型为`clf`。 然后,我们在`clf`上调用`score()`函数以仅针对我们的测试数据集测量其表现。 因此,我们根据为鸢尾花测量保留的测试数据和鸢尾花种类对模型进行评分,并观察其效果如何: ![](img/7d9b6519-0e08-4385-af2a-4dc643935313.jpg) 事实证明,它确实做得很好! 在超过 96% 的时间中,我们的模型仅基于该鸢尾花的测量值就能够正确预测它从未见过的鸢尾花的种类。 太酷了! 但是,这是一个相当小的数据集,如果我没记错的话,大约有 150 朵花。 因此,我们仅使用 150 朵花中的 60% 进行训练,而仅使用 150 朵花中的 40% 进行测试。 这些仍然是很小的数字,因此我们仍然可能过度适合于我们进行的特定训练/测试分组。 因此,让我们使用 K 折交叉验证来防止这种情况。 事实证明,使用 K 折交叉验证(即使它是更可靠的技术),实际上实际上比训练/测试更容易使用。 所以,这很酷! 因此,让我们看看它是如何工作的: ```py # We give cross_val_score a model, the entire data set and its "real" values, and the number of folds: scores = cross_validation.cross_val_score(clf, iris.data, iris.target, cv=5) # Print the accuracy for each fold: print scores # And the mean accuracy of all 5 folds: print scores.mean() ``` 我们已经有了一个模型,我们为此预测定义了 SVC 模型,您需要做的就是在`cross_validation`包上调用`cross_val_score()`。 因此,您要在此函数中传递给定类型(`clf`)的模型,即所有测量的整个数据集,即我的所有特征数据(`iris.data`)和我的所有目标数据 (所有物种),`iris.target`。 我想要`cv=5`,这意味着它实际上将使用 5 个不同的训练数据集,同时保留`1`进行测试。 基本上,它将运行 5 次,这就是我们需要做的。 这将针对整个数据集自动评估我们的模型,分解出五种不同的方式,并给我们单独的结果。 如果我们将其输出打印出来,它将为我们提供这些迭代中的每一个迭代(即,这些折叠中的每一个)的实际误差度量的列表。 我们可以将它们平均在一起,以获得基于 K 折交叉验证的总体误差度量: ![](img/705c48f3-a3b4-4d63-ae15-c4159c0e5c76.png) 当我们这样做超过 5 倍时,我们可以看到我们的结果甚至比我们想象的还要好! 98% 的准确率。 太酷了! 实际上,在几次运行中我们都具有完美的准确率。 所以这真是太神奇了。 现在,让我们看看是否可以做得更好。 以前我们使用线性核,如果我们使用多项式核甚至更高级怎么办? 这会过拟合还是实际上会更好地拟合我们拥有的数据? 这种类型取决于这些花瓣测量值与实际物种之间是否存在线性关系或多项式关系。 因此,让我们尝试一下: ```py clf = svm.SVC(kernel='poly', C=1).fit(X_train, y_train) scores = cross_validation.cross_val_score(clf, iris.data, iris.target, cv=5) print scores print scores.mean() ``` 我们将使用相同的技术再次运行所有这些。 但是这次,我们使用的是多项式内核。 我们将其适合我们的训练数据集,在这种情况下您适合的位置并不重要,因为`cross_val_score()`会一直为您重新运行它: ![](img/705c48f3-a3b4-4d63-ae15-c4159c0e5c76.png) 事实证明,当我们使用多项式拟合时,最终得出的总体得分甚至低于我们的原始得分。 因此,这告诉我们多项式内核可能过拟合。 当我们使用 K 折交叉验证时,它显示出比线性核函数更低的分数。 这里重要的一点是,如果我们仅使用单个训练/测试单元,就不会意识到我们过拟合。 如果我们像在线性内核上那样只进行一次训练/测试拆分,我们实际上会得到相同的结果。 因此,我们可能会无意间在此处过拟合数据,甚至在不使用 K 折交叉验证的情况下甚至都不知道。 因此,这是一个很好的例子,它说明了救援人员使用 K 折的情况,并警告您过拟合,在这种情况下,单次训练/测试分站可能无法解决这一问题。 因此,请将其放在工具箱中。 如果您想更多地玩这个游戏,请尝试不同程度的尝试。 因此,您实际上可以指定其他数量的度数。 多项式内核的默认值为 3 度,但是您可以尝试其他一个,也可以尝试两个。 这样会更好吗? 如果降为 1,则基本上可以降级为线性核,对吗? 因此,也许仍然存在多项式关系,也许只是二阶多项式。 试试看,看看你能得到什么。 这就是 K 折交叉验证。 如您所见,感谢 Scikit-learn,它非常易于使用。 这是一种非常健壮的方式来衡量模型的良好状态的重要方法。 # 数据清理和规范化 现在,这是最简单的部分之一,但可能是整本书中最重要的部分。 我们将讨论清理输入数据,这将花费很多时间。 您清理输入数据的方式以及对原始输入数据的理解程度将对结果质量产生巨大影响-甚至可能比您选择的模型或模型的调优程度更大。 所以,要注意; 这是重要的东西! 作为数据科学家,清理原始输入数据通常是最重要且最耗时的工作! 让我们谈谈数据科学的一个不便之处,那就是您花费了大部分时间实际上只是在清理和准备数据,而实际上却很少用它来分析和尝试新的算法。 它并不像人们一直认为的那样迷人。 但是,这是一件非常重要的事情要注意。 您可能会在原始数据中找到很多不同的东西。 传入的数据(仅是原始数据)将非常脏,并且将以多种不同方式受到污染。 如果您不加以处理,它将使您的结果产生偏差,最终将最终导致您的公司做出错误的决定。 如果再次出现错误,那就是您提取了很多不良数据并没有考虑到这些错误,没有对这些数据进行清理,您告诉您的公司来根据结果来做某事,后来完全是错误的,您将会遇到很多麻烦! 所以,要注意! 您需要注意许多不同类型的问题和数据: * **离群值**:因此,也许您的人员在数据中表现得有些奇怪,而当您对它们进行挖掘时,它们却是您不应该首先关注的数据。 一个很好的例子是,如果您正在查看 Web 日志数据,并且看到一个会话 ID 不断地反复出现,并且它以人们永远无法完成的高速度执行某项操作。 您可能会看到一个机器人,该脚本正在某个地方运行以实际抓取您的网站。 甚至可能是某种恶意攻击。 但是无论如何,您都不希望这些行为数据通知您的模型,这些数据只能用来预测使用您的网站的真实人类的行为。 因此,监视异常值是识别在构建模型时可能希望从模型中删除的数据类型的一种方法。 * **缺失数据**:当数据不存在时您会怎么做? 回到网络日志的示例,您可能在该行中有一个引荐来源网址,也可能没有。 如果不存在该怎么办? 是否为丢失或未指定的内容创建新分类? 还是您将那条线全部淘汰? 您必须考虑正确的做法。 * **恶意数据**:可能有人在尝试玩您的系统,有人可能在试图欺骗系统,而您不希望那些人逃避它。 假设您正在建立一个推荐系统。 可能有人在外面捏造行为数据以推广他们的新产品,对吗? 因此,您需要警惕此类情况,并确保对输入数据识别先令攻击或其他类型的攻击,并从结果中过滤掉它们,不要让它们获胜。 。 * **错误数据**:如果某些系统中某处出现软件错误,而该错误仅在某些情况下会写出错误的值? 这有可能发生。 不幸的是,您没有很好的方法来了解这一点。 但是,如果您看到的数据看起来有些混乱,或者结果对您而言没有意义,那么深入研究有时可能会发现潜在的错误,这些错误首先导致写入错误的数据。 也许某些时候事情没有被正确地组合在一起。 可能不是整个会议期间都举行会议。 例如,人们在访问网站时可能会丢弃其会话 ID 并获取新的会话 ID。 * **不相关数据**:此处非常简单。 也许您只对纽约市人的数据感兴趣,或出于某种原因。 在这种情况下,来自世界各地的人们的所有数据都与您要查找的内容无关。 您要做的第一件事就是丢弃所有数据并限制数据,将其缩减为您真正关心的数据。 * **不一致数据**:这是一个很大的问题。 例如,在地址中,人们可以用许多不同的方式编写相同的地址:他们可能会缩写街道,或者可能不会缩写街道,他们可能根本不会将街道放在街道名称的末尾。 他们可能以不同的方式将行组合在一起,可能会拼写不同的东西,可能在美国使用邮政编码,或者在美国使用邮政编码加 4 码,他们可能有一个国家,他们可能没有一个国家。 您需要以某种方式找出您看到的变化以及如何将它们全部归一化。 * 也许我正在看有关电影的数据。 电影在不同国家/地区可能具有不同的名称,或者一本书在不同国家/地区可能具有不同的名称,但它们的含义相同。 因此,您需要在需要规范化数据,可以用许多不同方式表示相同数据的地方,并需要将它们组合在一起以获得正确的结果。 * **格式化**:这也可能是一个问题; 事物的格式可能不一致。 以日期为例:在美国,我们总是执行月日年(`MM/DD/YY`),但是在其他国家,他们可能会执行日月年(`DD/MM/YY`),谁知道呢。 您需要了解这些格式差异。 电话号码可能在区号周围带有括号,也许没有。 也许数字的每个部分之间都有破折号,也许没有。 也许社会保险号有破折号,也许没有。 这些都是您需要注意的事情,并且您需要确保在处理过程中不会将格式的变化视为不同的实体或不同的分类。 因此,有很多事情需要提防,而前面的列表只是要注意的主要问题。 记住:垃圾进,垃圾出。 您的模型仅与您提供给它的数据一样好,这是非常非常正确的! 您可以拥有一个非常简单的模型,如果您向其提供大量干净的数据,则其表现将非常好,并且实际上它可能会在较脏的数据集上胜过复杂的模型。 因此,确保您拥有足够的数据和高质量的数据通常是最主要的任务。 您会惊讶于现实世界中使用的一些最成功的算法是如此简单。 它们只有通过输入的数据质量和输入的数据量才能成功。 您并不总是需要精美的技术来获得良好的结果。 通常,数据的质量和数量与其他任何事物一样重要。 始终质疑您的结果! 您不想返回并且仅在得到不满意的结果时在输入数据中查找异常。 这会给您的结果带来无意的偏差,让您希望或期望的结果毫无疑问地通过,对吗? 您想一直质疑事物,以确保您一直在寻找这些事物,因为即使您找到自己喜欢的结果,即使结果是错误的,它仍然是错误的,它仍然会以错误方向通知您的公司。 以后可能会再次咬你。 例如,我有一个名为“无恨新闻”的网站。 这是非营利性组织,所以我不会试图通过告诉您来赚钱。 假设我只想在我拥有的该网站上找到最受欢迎的页面。 这听起来像是一个非常简单的问题,不是吗? 我应该只能够浏览一下我的 Web 日志,并计算出每个页面有多少次匹配,并对它们进行排序,对吗? 它能有多难?! 好吧,事实证明这真的很难! 因此,让我们深入研究该示例,看看为什么会很难,并查看一些必须进行的实际数据清除的示例。 # 清理 Web 日志数据 我们将展示清除数据的重要性。 我有一个自己的小网站上的一些网络日志数据。 我们将尝试查找该网站上浏览量最高的页面。 听起来很简单,但是正如您所看到的,这实际上是非常具有挑战性的! 因此,如果您想继续学习,`TopPages.ipynb`是我们在这里工作的笔记本电脑。 开始吧! 我实际上有一个从实际网站获取的访问日志。 这是来自 Apache 的真实 HTTP 访问日志,并包含在您的书中。 因此,如果您确实想在这里玩,请确保更新路径以将访问日志移动到保存书籍材料的位置: ```py logPath = "E:\\sundog-consult\\Packt\\DataScience\\access_log.txt" ``` # 在网络日志上应用正则表达式 因此,我去了互联网,获得了以下几段代码,这些代码段将 Apache 访问日志行解析为一堆字段: ```py format_pat= re.compile( r"(?P[\d\.]+)\s" r"(?P\S*)\s" r"(?P\S*)\s" r"\[(?P