Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(一)(3)

简介: Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(一)

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(一)(2)https://developer.aliyun.com/article/1482406


测试和验证

了解一个模型将如何推广到新案例的唯一方法是实际在新案例上尝试。一种方法是将你的模型投入生产并监控其表现。这种方法效果很好,但如果你的模型非常糟糕,用户会抱怨——这不是最好的主意。

更好的选择是将数据分为两组:训练集测试集。正如这些名称所暗示的,你使用训练集训练你的模型,并使用测试集测试它。新案例的错误率称为泛化误差(或样本外误差),通过在测试集上评估你的模型,你可以得到这个误差的估计。这个值告诉你你的模型在它从未见过的实例上的表现如何。

如果训练误差很低(即,你的模型在训练集上犯了很少的错误),但泛化误差很高,这意味着你的模型正在过拟合训练数据。

提示

通常使用 80%的数据进行训练,保留20%用于测试。但是,这取决于数据集的大小:如果包含 1000 万个实例,那么保留 1%意味着你的测试集将包含 100,000 个实例,可能足够得到泛化误差的良好估计。

超参数调整和模型选择

评估一个模型很简单:只需使用一个测试集。但是假设你在两种类型的模型之间犹豫不决(比如,线性模型和多项式模型):你如何决定呢?一种选择是训练两种模型,并比较它们在测试集上的泛化效果。

现在假设线性模型泛化效果更好,但你想应用一些正则化来避免过拟合。问题是,你如何选择正则化超参数的值?一种选择是使用 100 个不同的值训练 100 个不同的模型。假设你找到了一个最佳的超参数值,可以产生泛化误差最低的模型——比如,只有 5%的误差。你将这个模型投入生产,但不幸的是它的表现并不如预期,产生了 15%的错误。发生了什么?

问题在于你在测试集上多次测量了泛化误差,并且调整了模型和超参数以产生最佳模型针对那个特定集合。这意味着该模型不太可能在新数据上表现得很好。

解决这个问题的常见方法称为留出验证(图 1-25):您只需留出部分训练集来评估几个候选模型并选择最佳模型。新的留出集称为验证集(或开发集,或开发集)。更具体地说,您在减少的训练集上(即完整训练集减去验证集)上训练多个具有不同超参数的模型,并选择在验证集上表现最佳的模型。在进行留出验证过程之后,您在完整训练集上(包括验证集)训练最佳模型,这将为您提供最终模型。最后,您评估这个最终模型在测试集上,以获得泛化误差的估计。

图 1-25. 使用留出验证进行模型选择

这种解决方案通常效果很好。然而,如果验证集太小,则模型评估将不够精确:您可能会错误地选择次优模型。相反,如果验证集太大,则剩余的训练集将比完整训练集小得多。为什么这样不好呢?因为最终模型将在完整训练集上训练,所以将候选模型训练在一个小得多的训练集上进行比较并不理想。这就好比选择最快的短跑选手参加马拉松比赛。解决这个问题的一种方法是执行重复的交叉验证,使用许多小的验证集。每个模型在其余数据上训练后,每个验证集对其进行一次评估。通过对模型的所有评估进行平均,您将获得更准确的性能度量。然而,这种方法的一个缺点是:训练时间将乘以验证集的数量。

数据不匹配

在某些情况下,很容易获得大量用于训练的数据,但这些数据可能不会完全代表将在生产中使用的数据。例如,假设您想创建一个移动应用程序来拍摄花朵并自动确定它们的种类。您可以轻松地在网上下载数百万张花朵图片,但它们不会完全代表实际使用移动设备上应用程序拍摄的图片。也许您只有 1,000 张代表性图片(即实际使用应用程序拍摄的图片)。

在这种情况下,最重要的规则是记住验证集和测试集必须尽可能代表您预期在生产中使用的数据,因此它们应该完全由代表性图片组成:您可以对它们进行洗牌,并将一半放入验证集,一半放入测试集(确保没有重复或近似重复的图片同时出现在两个集合中)。在对网络图片训练模型后,如果您观察到模型在验证集上的表现令人失望,您将不知道这是因为您的模型已经过度拟合训练集,还是仅仅是由于网络图片和移动应用程序图片之间的不匹配。

一种解决方案是在另一个由 Andrew Ng 命名为训练-开发集的集合中保留一些训练图片(来自网络)(图 1-26)。在模型训练完成后(在训练集上,不是在训练-开发集上),您可以在训练-开发集上评估它。如果模型表现不佳,则必须过度拟合训练集,因此应尝试简化或正则化模型,获取更多训练数据,并清理训练数据。但如果模型在训练-开发集上表现良好,则可以在开发集上评估模型。如果模型在开发集上表现不佳,则问题可能来自数据不匹配。您可以尝试通过预处理网络图像使其看起来更像移动应用程序将拍摄的图片,然后重新训练模型来解决此问题。一旦您有一个在训练-开发集和开发集上表现良好的模型,您可以最后一次在测试集上评估它,以了解它在生产中的表现如何。

图 1-26。当真实数据稀缺时(右侧),您可以使用类似丰富的数据(左侧)进行训练,并在训练-开发集中保留一些数据以评估过拟合;然后使用真实数据来评估数据不匹配(开发集)并评估最终模型的性能(测试集)

练习

在本章中,我们已经介绍了机器学习中一些最重要的概念。在接下来的章节中,我们将深入探讨并编写更多代码,但在此之前,请确保您能回答以下问题:

  1. 您如何定义机器学习?
  2. 您能否列出四种应用程序的类型,它们在哪些方面表现出色?
  3. 什么是标记训练集?
  4. 最常见的两种监督任务是什么?
  5. 您能否列出四种常见的无监督任务?
  6. 您会使用什么类型的算法来允许机器人在各种未知地形中行走?
  7. 您会使用什么类型的算法将客户分成多个群组?
  8. 您会将垃圾邮件检测问题框定为监督学习问题还是无监督学习问题?
  9. 什么是在线学习系统?
  10. 什么是离线学习?
  11. 依赖相似度度量进行预测的算法类型是什么?
  12. 模型参数和模型超参数之间有什么区别?
  13. 基于模型的算法搜索什么?它们成功的最常见策略是什么?它们如何进行预测?
  14. 您能否列出机器学习中的四个主要挑战?
  15. 如果您的模型在训练数据上表现良好,但对新实例的泛化能力差,那么发生了什么?您能否列出三种可能的解决方案?
  16. 什么是测试集,为什么要使用它?
  17. 验证集的目的是什么?
  18. 什么是训练-开发集,何时需要它,以及如何使用它?
  19. 如果使用测试集调整超参数会出现什么问题?

这些练习的解决方案可以在本章笔记本的末尾找到,网址为https://homl.info/colab3

¹ 有趣的事实:这个听起来奇怪的名字是由弗朗西斯·高尔顿引入的统计术语,当时他正在研究高个子父母的孩子往往比父母矮的事实。由于孩子们较矮,他将此称为回归到平均值。然后,这个名字被应用于他用来分析变量之间相关性的方法。

² 请注意,动物与车辆相当分离,马与鹿接近但与鸟类相距甚远。图由 Richard Socher 等人允许复制,来源于“通过跨模态转移实现零样本学习”,第 26 届国际神经信息处理系统会议论文集 1(2013):935-943。

³ 这就是系统完美运行的时候。在实践中,它经常为每个人创建几个簇,并有时会混淆看起来相似的两个人,因此您可能需要为每个人提供一些标签,并手动清理一些簇。

⁴ 按照惯例,希腊字母θ(theta)经常用于表示模型参数。

⁵ 如果您还不理解所有的代码,没关系;我将在接下来的章节中介绍 Scikit-Learn。

⁶ 例如,根据上下文知道是写“to”、“two”还是“too”。

⁷ Peter Norvig 等,“数据的不合理有效性”,《IEEE 智能系统》24 卷 2 期(2009 年):8–12。

⁸ 图片经 Michele Banko 和 Eric Brill 许可重印,“用于自然语言消歧的非常大的语料库的扩展”,《计算语言学协会第 39 届年会论文集》(2001 年):26–33。

⁹ David Wolpert,“学习算法之间缺乏先验区别”,《神经计算》8 卷 7 期(1996 年):1341–1390。

第二章:端到端的机器学习项目

在本章中,您将通过一个示例项目端到端地工作,假装自己是一家房地产公司最近雇用的数据科学家。这个例子是虚构的;目标是说明机器学习项目的主要步骤,而不是了解房地产业务。以下是我们将要走过的主要步骤:

  1. 看大局。
  2. 获取数据。
  3. 探索和可视化数据以获得洞见。
  4. 为机器学习算法准备数据。
  5. 选择模型并训练它。
  6. 微调您的模型。
  7. 呈现解决方案。
  8. 启动、监控和维护您的系统。

使用真实数据

当您学习机器学习时,最好尝试使用真实世界的数据,而不是人工数据集。幸运的是,有成千上万的开放数据集可供选择,涵盖各种领域。以下是您可以查找数据的一些地方:

  • 流行的开放数据存储库:
  • 元门户网站(它们列出开放数据存储库):
  • 其他页面列出了许多流行的开放数据存储库:

在本章中,我们将使用 StatLib 存储库中的加利福尼亚房价数据集(见图 2-1)。该数据集基于 1990 年加利福尼亚人口普查数据。虽然不是最新的(在那个时候,旧金山湾区的漂亮房子仍然是负担得起的),但它具有许多学习的优点,因此我们将假装它是最新的数据。出于教学目的,我添加了一个分类属性并删除了一些特征。

图 2-1。加利福尼亚房屋价格

看大局

欢迎来到机器学习房地产公司!您的第一个任务是使用加利福尼亚人口普查数据来建立该州房价模型。这些数据包括加利福尼亚每个街区组的人口、中位收入和中位房价等指标。街区组是美国人口普查局发布样本数据的最小地理单位(一个街区组通常有 600 到 3000 人口)。我会简称它们为“区”。

您的模型应该从这些数据中学习,并能够预测任何地区的房屋中位数价格,考虑到所有其他指标。

提示

由于您是一个组织良好的数据科学家,您应该做的第一件事是拿出您的机器学习项目清单。您可以从附录 A 开始;对于大多数机器学习项目来说,这应该运行得相当顺利,但请确保根据您的需求进行调整。在本章中,我们将逐个检查许多项目,但也会跳过一些,要么是因为它们是不言自明的,要么是因为它们将在后续章节中讨论。

构建问题框架

向老板提出问题的第一个问题是业务目标究竟是什么。构建模型可能不是最终目标。公司希望如何使用和从这个模型中受益?知道目标很重要,因为它将决定您如何构建问题,选择哪些算法,使用哪种性能度量来评估您的模型,以及您将花费多少精力来调整它。

你的老板回答说,你模型的输出(一个区域的中位房价的预测)将被馈送到另一个机器学习系统(见图 2-2),以及许多其他信号。这个下游系统将确定是否值得在一个给定区域进行投资。做对这一点至关重要,因为它直接影响收入。

下一个问题要问你的老板是,当前的解决方案是什么样子的(如果有的话)。当前情况通常会给你一个性能的参考,以及解决问题的见解。你的老板回答说,目前专家们手动估计区域房价:一个团队收集关于一个区域的最新信息,当他们无法获得中位房价时,他们使用复杂的规则来估计。

图 2-2。房地产投资的机器学习流程

这是昂贵且耗时的,他们的估计并不好;在他们设法找出实际的中位房价的情况下,他们经常意识到他们的估计偏差超过 30%。这就是为什么公司认为训练一个模型来预测一个区域的中位房价,给出该区域的其他数据,将是有用的。人口普查数据看起来是一个很好的数据集,可以用来实现这个目的,因为它包括数千个区域的中位房价,以及其他数据。

有了所有这些信息,你现在可以开始设计你的系统了。首先,确定模型将需要什么样的训练监督:是监督、无监督、半监督、自监督还是强化学习任务?这是一个分类任务、回归任务还是其他任务?应该使用批量学习还是在线学习技术?在继续阅读之前,暂停一下,尝试自己回答这些问题。

你找到答案了吗?让我们看看。这显然是一个典型的监督学习任务,因为模型可以通过标记的示例进行训练(每个实例都带有预期的输出,即区域的中位房价)。这是一个典型的回归任务,因为模型将被要求预测一个值。更具体地说,这是一个多元回归问题,因为系统将使用多个特征来进行预测(区域的人口,中位收入等)。这也是一个单变量回归问题,因为我们只是尝试预测每个区域的一个单一值。如果我们试图预测每个区域的多个值,那么它将是一个多元回归问题。最后,系统中没有连续的数据流进入,没有特别需要快速调整到变化的数据,数据足够小以适应内存,因此普通的批量学习应该做得很好。

提示

如果数据很大,你可以将批量学习工作分配到多台服务器上(使用 MapReduce 技术),或者使用在线学习技术。

选择一个性能度量

你的下一步是选择一个性能度量。回归问题的一个典型性能度量是均方根误差(RMSE)。它给出了系统在预测中通常产生多少误差的概念,对大误差给予更高的权重。方程 2-1 显示了计算 RMSE 的数学公式。

方程 2-1。均方根误差(RMSE)

RMSE ( X , h ) = 1 m ∑ i=1 m h(x (i) )-y (i) 2

尽管均方根误差通常是回归任务的首选性能度量,但在某些情况下,您可能更喜欢使用另一个函数。例如,如果有许多异常值区域。在这种情况下,您可能考虑使用平均绝对误差(MAE,也称为平均绝对偏差),如方程 2-2 所示:

方程 2-2. 平均绝对误差(MAE)

MAE ( X , h ) = 1 m ∑ i=1 m h ( x (i) ) - y (i)

均方根误差(RMSE)和平均绝对误差(MAE)都是衡量两个向量之间距离的方法:预测向量和目标值向量。各种距离度量,或范数,都是可能的:

  • 计算平方和的根(RMSE)对应于欧几里德范数:这是我们都熟悉的距离概念。它也被称为ℓ[2] 范数,表示为∥ · ∥[2](或只是∥ · ∥)。
  • 计算绝对值之和(MAE)对应于ℓ[1] 范数,表示为∥ · ∥[1]。这有时被称为曼哈顿范数,因为它测量了在城市中两点之间的距离,如果您只能沿着正交的城市街区行驶。
  • 更一般地,包含n个元素的向量v的ℓ[k] 范数被定义为∥v∥[k] = (|v[1]|^(k) + |v[2]|^(k) + … + |v[n]|(*k*))(1/k)。ℓ[0]给出向量中非零元素的数量,ℓ[∞]给出向量中的最大绝对值。

范数指数越高,就越关注大值并忽略小值。这就是为什么均方根误差(RMSE)比平均绝对误差(MAE)更容易受到异常值的影响。但是当异常值呈指数稀有性(如钟形曲线)时,均方根误差表现非常好,通常更受青睐。

检查假设

最后,列出并验证迄今为止已经做出的假设(由您或他人)是一个很好的做法;这可以帮助您及早发现严重问题。例如,您的系统输出的区域价格将被馈送到下游机器学习系统中,您假设这些价格将被如此使用。但是,如果下游系统将价格转换为类别(例如,“便宜”,“中等”或“昂贵”),然后使用这些类别而不是价格本身呢?在这种情况下,完全准确地获取价格并不重要;您的系统只需要获取正确的类别。如果是这样,那么问题应该被定义为一个分类任务,而不是一个回归任务。您不希望在为回归系统工作数月后才发现这一点。

幸运的是,在与负责下游系统的团队交谈后,您确信他们确实需要实际价格,而不仅仅是类别。太好了!您已经准备就绪,灯光是绿色的,现在可以开始编码了!

获取数据

现在是动手的时候了。毫不犹豫地拿起你的笔记本电脑,浏览代码示例。正如我在前言中提到的,本书中的所有代码示例都是开源的,可在在线作为 Jupyter 笔记本使用,这些笔记本是交互式文档,包含文本、图像和可执行代码片段(在我们的情况下是 Python)。在本书中,我假设您正在 Google Colab 上运行这些笔记本,这是一个免费服务,让您可以直接在线运行任何 Jupyter 笔记本,而无需在您的计算机上安装任何东西。如果您想使用另一个在线平台(例如 Kaggle),或者如果您想在自己的计算机上本地安装所有内容,请参阅本书 GitHub 页面上的说明。

使用 Google Colab 运行代码示例

首先,打开一个网络浏览器,访问https://homl.info/colab3:这将带您到 Google Colab,并显示本书的 Jupyter 笔记本列表(参见图 2-3)。您将在每章找到一个笔记本,以及一些额外的笔记本和 NumPy、Matplotlib、Pandas、线性代数和微积分的教程。例如,如果您点击02_end_to_end_machine_learning_project.ipynb,将会在 Google Colab 中打开第二章的笔记本(参见图 2-4)。

Jupyter 笔记本由一系列单元格组成。每个单元格包含可执行代码或文本。尝试双击第一个文本单元格(其中包含句子“欢迎来到机器学习房地产公司!”)。这将打开单元格进行编辑。请注意,Jupyter 笔记本使用 Markdown 语法进行格式化(例如,**粗体***斜体*# 标题url等)。尝试修改这段文本,然后按 Shift-Enter 查看结果。

图 2-3. Google Colab 中的笔记本列表

图 2-4. 在 Google Colab 中的笔记本

接下来,通过选择插入→“代码单元格”菜单来创建一个新的代码单元格。或者,您可以在工具栏中点击+代码按钮,或者将鼠标悬停在单元格底部直到看到+代码和+文本出现,然后点击+代码。在新的代码单元格中,输入一些 Python 代码,例如print("Hello World"),然后按 Shift-Enter 运行此代码(或者点击单元格左侧的▷按钮)。

如果您尚未登录 Google 账户,现在将被要求登录(如果您尚未拥有 Google 账户,您需要创建一个)。一旦您登录,当您尝试运行代码时,您将看到一个安全警告,告诉您这个笔记本不是由 Google 编写的。一个恶意的人可能会创建一个试图欺骗您输入 Google 凭据的笔记本,以便访问您的个人数据,因此在运行笔记本之前,请务必确保信任其作者(或在运行之前仔细检查每个代码单元格将执行的操作)。假设您信任我(或者您计划检查每个代码单元格),现在可以点击“仍然运行”。

Colab 将为您分配一个新的运行时:这是位于 Google 服务器上的免费虚拟机,包含一堆工具和 Python 库,包括大多数章节所需的一切(在某些章节中,您需要运行一个命令来安装额外的库)。这将需要几秒钟。接下来,Colab 将自动连接到此运行时,并使用它来执行您的新代码单元格。重要的是,代码在运行时上运行,而不是在您的计算机上。代码的输出将显示在单元格下方。恭喜,您已在 Colab 上运行了一些 Python 代码!

提示

要插入新的代码单元格,您也可以键入 Ctrl-M(或 macOS 上的 Cmd-M),然后按 A(在活动单元格上方插入)或 B(在下方插入)。还有许多其他可用的键盘快捷键:您可以通过键入 Ctrl-M(或 Cmd-M)然后 H 来查看和编辑它们。如果您选择在 Kaggle 上或使用 JupyterLab 或带有 Jupyter 扩展的 IDE(如 Visual Studio Code)在自己的机器上运行笔记本,您将看到一些细微差异——运行时称为内核,用户界面和键盘快捷键略有不同等等——但从一个 Jupyter 环境切换到另一个并不太困难。

保存您的代码更改和数据

您可以对 Colab 笔记本进行更改,并且只要保持浏览器标签打开,这些更改将持续存在。但一旦关闭它,更改将丢失。为了避免这种情况,请确保通过选择文件→“在驱动器中保存副本”将笔记本保存到您的谷歌驱动器。或者,您可以通过选择文件→下载→“下载.ipynb”将笔记本下载到计算机。然后,您可以稍后访问https://colab.research.google.com并再次打开笔记本(从谷歌驱动器或通过从计算机上传)。

警告

Google Colab 仅用于交互使用:您可以在笔记本中玩耍并调整代码,但不能让笔记本在长时间内无人看管运行,否则运行时将关闭并丢失所有数据。

如果笔记本生成了您关心的数据,请确保在运行时关闭之前下载这些数据。要做到这一点,请点击文件图标(参见图 2-5 中的步骤 1),找到要下载的文件,点击其旁边的垂直点(步骤 2),然后点击下载(步骤 3)。或者,您可以在运行时挂载您的谷歌驱动器,使笔记本能够直接读写文件到谷歌驱动器,就像它是一个本地目录一样。为此,请点击文件图标(步骤 1),然后点击谷歌驱动器图标(在图 2-5 中用圈圈圈出)并按照屏幕上的说明操作。

图 2-5。从 Google Colab 运行时下载文件(步骤 1 至 3),或挂载您的谷歌驱动器(圈圈图标)

默认情况下,您的谷歌驱动器将挂载在*/content/drive/MyDrive*。如果要备份数据文件,只需通过运行!cp /content/my_great_model /content/drive/MyDrive将其复制到此目录。任何以感叹号(!)开头的命令都被视为 shell 命令,而不是 Python 代码:cp是 Linux shell 命令,用于将文件从一个路径复制到另一个路径。请注意,Colab 运行时在 Linux 上运行(具体来说是 Ubuntu)。

交互性的力量和危险

Jupyter 笔记本是交互式的,这是一件好事:您可以逐个运行每个单元格,在任何时候停止,插入单元格,玩弄代码,返回并再次运行相同的单元格等等,我强烈鼓励您这样做。如果您只是逐个运行单元格而从不玩弄它们,您学习速度不会那么快。然而,这种灵活性是有代价的:很容易按错误的顺序运行单元格,或者忘记运行一个单元格。如果发生这种情况,后续的代码单元格很可能会失败。例如,每个笔记本中的第一个代码单元格包含设置代码(如导入),因此请确保首先运行它,否则什么都不会起作用。

提示

如果您遇到奇怪的错误,请尝试重新启动运行时(通过选择运行时→“重新启动运行时”菜单)然后再次从笔记本开头运行所有单元格。这通常可以解决问题。如果不行,很可能是您所做的更改之一破坏了笔记本:只需恢复到原始笔记本并重试。如果仍然失败,请在 GitHub 上提交问题。

书中代码与笔记本代码

您有时可能会注意到本书中的代码与笔记本中的代码之间存在一些小差异。这可能是由于以下几个原因:

  • 图书馆可能在您阅读这些文字时略有变化,或者尽管我尽力了,但书中可能存在错误。遗憾的是,我不能在您的这本书中神奇地修复代码(除非您正在阅读电子副本并且可以下载最新版本),但我可以修复笔记本。因此,如果您在从本书中复制代码后遇到错误,请查找笔记本中的修复代码:我将努力保持它们没有错误,并与最新的库版本保持同步。
  • 笔记本包含一些额外的代码来美化图形(添加标签,设置字体大小等)并将它们保存为高分辨率以供本书使用。如果您愿意,可以安全地忽略这些额外的代码。

我优化了代码以提高可读性和简单性:我尽可能将其线性化和扁平化,定义了很少的函数或类。目标是确保您运行的代码通常就在您眼前,而不是嵌套在几层抽象中,需要搜索。这也使您更容易玩弄代码。为简单起见,我没有进行太多的错误处理,并且将一些不常见的导入放在需要它们的地方(而不是像 PEP 8 Python 风格指南建议的那样将它们放在文件顶部)。也就是说,您的生产代码不会有太大的不同:只是更模块化,还有额外的测试和错误处理。

好了!一旦您熟悉了 Colab,您就可以下载数据了。

下载数据

在典型环境中,您的数据可能存储在关系数据库或其他常见数据存储中,并分布在多个表/文档/文件中。要访问它,您首先需要获取您的凭据和访问授权,并熟悉数据模式。然而,在这个项目中,情况要简单得多:您只需下载一个压缩文件housing.tgz,其中包含一个名为housing.csv的逗号分隔值(CSV)文件,其中包含所有数据。

与手动下载和解压数据相比,通常最好编写一个函数来执行此操作。特别是如果数据经常更改,这将非常有用:您可以编写一个小脚本,使用该函数获取最新数据(或者您可以设置定期自动执行此操作的计划任务)。自动获取数据的过程也很有用,如果您需要在多台机器上安装数据集。

这是用于获取和加载数据的函数:

from pathlib import Path
import pandas as pd
import tarfile
import urllib.request
def load_housing_data():
    tarball_path = Path("datasets/housing.tgz")
    if not tarball_path.is_file():
        Path("datasets").mkdir(parents=True, exist_ok=True)
        url = "https://github.com/ageron/data/raw/main/housing.tgz"
        urllib.request.urlretrieve(url, tarball_path)
        with tarfile.open(tarball_path) as housing_tarball:
            housing_tarball.extractall(path="datasets")
    return pd.read_csv(Path("datasets/housing/housing.csv"))
housing = load_housing_data()

当调用load_housing_data()时,它会查找datasets/housing.tgz文件。如果找不到该文件,它会在当前目录内创建datasets目录(默认为*/content*,在 Colab 中),从ageron/data GitHub 存储库下载housing.tgz文件,并将其内容提取到datasets目录中;这将创建datasets/housing目录,并在其中包含housing.csv文件。最后,该函数将此 CSV 文件加载到一个包含所有数据的 Pandas DataFrame 对象中,并返回它。

快速查看数据结构

您可以通过使用 DataFrame 的head()方法来查看数据的前五行(请参见图 2-6)。

图 2-6。数据集中的前五行

每一行代表一个地区。有 10 个属性(并非所有属性都显示在屏幕截图中):longitudelatitudehousing_median_agetotal_roomstotal_bedroomspopulationhouseholdsmedian_incomemedian_house_valueocean_proximity

info()方法对于快速获取数据的简要描述非常有用,特别是总行数、每个属性的类型和非空值的数量:

>>> housing.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 10 columns):
 #   Column              Non-Null Count  Dtype
---  ------              --------------  -----
 0   longitude           20640 non-null  float64
 1   latitude            20640 non-null  float64
 2   housing_median_age  20640 non-null  float64
 3   total_rooms         20640 non-null  float64
 4   total_bedrooms      20433 non-null  float64
 5   population          20640 non-null  float64
 6   households          20640 non-null  float64
 7   median_income       20640 non-null  float64
 8   median_house_value  20640 non-null  float64
 9   ocean_proximity     20640 non-null  object
dtypes: float64(9), object(1)
memory usage: 1.6+ MB
注意

在本书中,当代码示例包含代码和输出混合时,就像这里一样,它的格式类似于 Python 解释器,以便更好地阅读:代码行以>>>(或缩进块的情况下为...)为前缀,输出没有前缀。

数据集中有 20,640 个实例,这意味着按照机器学习的标准来说,它相当小,但是非常适合入门。您会注意到total_bedrooms属性只有 20,433 个非空值,这意味着有 207 个地区缺少这个特征。您需要稍后处理这个问题。

所有属性都是数字的,除了ocean_proximity。它的类型是object,因此它可以保存任何类型的 Python 对象。但是由于您从 CSV 文件中加载了这些数据,您知道它必须是一个文本属性。当您查看前五行时,您可能会注意到ocean_proximity列中的值是重复的,这意味着它可能是一个分类属性。您可以使用value_counts()方法找出存在哪些类别以及每个类别包含多少个地区:

>>> housing["ocean_proximity"].value_counts()
<1H OCEAN     9136
INLAND        6551
NEAR OCEAN    2658
NEAR BAY      2290
ISLAND           5
Name: ocean_proximity, dtype: int64

让我们看看其他字段。describe()方法显示了数字属性的摘要(图 2-7)。

图 2-7。每个数字属性的总结

countmeanminmax行是不言自明的。请注意,空值被忽略了(因此,例如,total_bedroomscount是 20,433,而不是 20,640)。std行显示了标准差,它衡量了值的分散程度。⁠⁵ 25%50%75%行显示了相应的百分位数:百分位数表示给定百分比的观察值中有多少落在一组观察值之下。例如,25%的地区的housing_median_age低于 18,而 50%低于 29,75%低于 37。这些通常被称为第 25 百分位数(或第一个四分位数)、中位数和第 75 百分位数(或第三四分位数)。

另一种快速了解您正在处理的数据类型的方法是为每个数字属性绘制直方图。直方图显示了具有给定值范围的实例数量(在水平轴上)(在垂直轴上)。您可以一次绘制一个属性,也可以在整个数据集上调用hist()方法(如下面的代码示例所示),它将为每个数字属性绘制一个直方图(参见图 2-8):

import matplotlib.pyplot as plt
housing.hist(bins=50, figsize=(12, 8))
plt.show()

图 2-8。每个数字属性的直方图

查看这些直方图,您会注意到一些事情:

  • 首先,中位收入属性看起来不像是以美元(USD)表示的。在与收集数据的团队核实后,他们告诉您数据已经被缩放,并且对于更高的中位收入,已经被限制在 15(实际上是 15.0001),对于更低的中位收入,已经被限制在 0.5(实际上是 0.4999)。这些数字大致表示数万美元(例如,3 实际上表示约 30,000 美元)。在机器学习中使用预处理的属性是很常见的,这并不一定是问题,但您应该尝试了解数据是如何计算的。
  • 房屋中位年龄和房屋中位价值也被限制了。后者可能是一个严重的问题,因为它是您的目标属性(您的标签)。您的机器学习算法可能会学习到价格永远不会超过那个限制。您需要与客户团队(将使用您系统输出的团队)核实,看看这是否是一个问题。如果他们告诉您他们需要精确的预测,甚至超过 50 万美元,那么您有两个选择:
  • 收集那些标签被限制的地区的正确标签。
  • 从训练集中删除这些地区(也从测试集中删除,因为如果您的系统预测超过 50 万美元的值,它不应该被评估不良)。
  • 这些属性具有非常不同的比例。当我们探索特征缩放时,我们将在本章后面讨论这一点。
  • 最后,许多直方图是右偏的:它们在中位数的右侧延伸得更远,而不是左侧。这可能会使一些机器学习算法更难检测到模式。稍后,您将尝试转换这些属性,使其具有更对称和钟形分布。

现在您应该对您正在处理的数据有更好的了解。

警告

等等!在进一步查看数据之前,您需要创建一个测试集,将其放在一边,永远不要查看它。

创建一个测试集

在这个阶段自愿设置一部分数据似乎有点奇怪。毕竟,您只是快速浏览了数据,而在决定使用什么算法之前,您肯定应该更多地了解它,对吧?这是真的,但您的大脑是一个惊人的模式检测系统,这也意味着它很容易过拟合:如果您查看测试集,您可能会在测试数据中发现一些看似有趣的模式,导致您选择特定类型的机器学习模型。当您使用测试集估计泛化误差时,您的估计将过于乐观,您将启动一个性能不如预期的系统。这被称为数据窥探偏差。

创建测试集在理论上很简单;随机选择一些实例,通常是数据集的 20%(如果您的数据集非常大,则可以更少),并将它们放在一边:

import numpy as np
def shuffle_and_split_data(data, test_ratio):
    shuffled_indices = np.random.permutation(len(data))
    test_set_size = int(len(data) * test_ratio)
    test_indices = shuffled_indices[:test_set_size]
    train_indices = shuffled_indices[test_set_size:]
    return data.iloc[train_indices], data.iloc[test_indices]

然后,您可以像这样使用这个函数:

>>> train_set, test_set = shuffle_and_split_data(housing, 0.2)
>>> len(train_set)
16512
>>> len(test_set)
4128

好吧,这样做是有效的,但并不完美:如果再次运行程序,它将生成不同的测试集!随着时间的推移,您(或您的机器学习算法)将看到整个数据集,这是您要避免的。

一种解决方案是在第一次运行时保存测试集,然后在后续运行中加载它。另一个选项是在调用np.random.permutation()之前设置随机数生成器的种子(例如,使用np.random.seed(42))⁠^([6](ch02.html#idm45720239285936)),以便它始终生成相同的洗牌索引。

然而,这两种解决方案在获取更新的数据集后会失效。为了在更新数据集后仍然保持稳定的训练/测试分割,一个常见的解决方案是使用每个实例的标识符来决定是否应该放入测试集中(假设实例具有唯一且不可变的标识符)。例如,您可以计算每个实例标识符的哈希值,并且如果哈希值低于或等于最大哈希值的 20%,则将该实例放入测试集中。这确保了测试集将在多次运行中保持一致,即使您刷新数据集。新的测试集将包含新实例的 20%,但不会包含以前在训练集中的任何实例。

这是一个可能的实现:

from zlib import crc32
def is_id_in_test_set(identifier, test_ratio):
    return crc32(np.int64(identifier)) < test_ratio * 2**32
def split_data_with_id_hash(data, test_ratio, id_column):
    ids = data[id_column]
    in_test_set = ids.apply(lambda id_: is_id_in_test_set(id_, test_ratio))
    return data.loc[~in_test_set], data.loc[in_test_set]

不幸的是,住房数据集没有标识符列。最简单的解决方案是使用行索引作为 ID:

housing_with_id = housing.reset_index()  # adds an `index` column
train_set, test_set = split_data_with_id_hash(housing_with_id, 0.2, "index")

如果使用行索引作为唯一标识符,您需要确保新数据附加到数据集的末尾,并且永远不会删除任何行。如果这不可能,那么您可以尝试使用最稳定的特征来构建唯一标识符。例如,一个地区的纬度和经度保证在几百万年内保持稳定,因此您可以将它们组合成一个 ID,如下所示:⁠^([7](ch02.html#idm45720242437568))

housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_data_with_id_hash(housing_with_id, 0.2, "id")

Scikit-Learn 提供了一些函数,以各种方式将数据集拆分为多个子集。最简单的函数是train_test_split(),它基本上与我们之前定义的shuffle_and_split_data()函数做的事情差不多,但有一些额外的功能。首先,有一个random_state参数,允许您设置随机生成器种子。其次,您可以传递多个具有相同行数的数据集,并且它将在相同的索引上拆分它们(例如,如果您有一个单独的标签 DataFrame,这是非常有用的):

from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

到目前为止,我们已经考虑了纯随机抽样方法。如果您的数据集足够大(尤其是相对于属性数量),这通常是可以接受的,但如果不是,您就有可能引入显著的抽样偏差。当调查公司的员工决定打电话给 1,000 人询问一些问题时,他们不会仅仅在电话簿中随机挑选 1,000 人。他们会努力确保这 1,000 人在问题上代表整个人口。例如,美国人口中 51.1%是女性,48.9%是男性,因此在美国进行良好的调查将尝试保持这个比例在样本中:511 名女性和 489 名男性(至少如果可能的话,答案可能会因性别而有所不同)。这被称为分层抽样:人口被划分为称为的同质子群,从每个层中抽取正确数量的实例以确保测试集代表整体人口。如果进行调查的人员使用纯随机抽样,那么抽取一个偏斜的测试集,女性参与者少于 48.5%或多于 53.5%的概率约为 10.7%。无论哪种方式,调查结果可能会相当偏倚。

假设您与一些专家交谈过,他们告诉您,中位收入是预测中位房价的一个非常重要的属性。您可能希望确保测试集代表整个数据集中各种收入类别。由于中位收入是一个连续的数值属性,您首先需要创建一个收入类别属性。让我们更仔细地看一下中位收入直方图(回到图 2-8):大多数中位收入值聚集在 1.5 到 6 之间(即 15,000 美元至 60,000 美元),但有些中位收入远远超过 6。对于每个层,您的数据集中应该有足够数量的实例,否则对层重要性的估计可能会有偏差。这意味着您不应该有太多层,并且每个层应该足够大。以下代码使用pd.cut()函数创建一个具有五个类别(从 1 到 5 标记)的收入类别属性;类别 1 范围从 0 到 1.5(即 15,000 美元以下),类别 2 从 1.5 到 3,依此类推:

housing["income_cat"] = pd.cut(housing["median_income"],
                               bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
                               labels=[1, 2, 3, 4, 5])

这些收入类别在图 2-9 中表示:

housing["income_cat"].value_counts().sort_index().plot.bar(rot=0, grid=True)
plt.xlabel("Income category")
plt.ylabel("Number of districts")
plt.show()

现在您可以根据收入类别进行分层抽样。Scikit-Learn 在sklearn.model_selection包中提供了许多拆分器类,实现了各种策略来将数据集拆分为训练集和测试集。每个拆分器都有一个split()方法,返回相同数据的不同训练/测试拆分的迭代器。

图 2-9。收入类别直方图

要准确,split()方法产生训练和测试索引,而不是数据本身。如果您想更好地估计模型的性能,拥有多个拆分可能是有用的,这将在本章后面讨论交叉验证时看到。例如,以下代码生成了同一数据集的 10 个不同的分层拆分:

from sklearn.model_selection import StratifiedShuffleSplit
splitter = StratifiedShuffleSplit(n_splits=10, test_size=0.2, random_state=42)
strat_splits = []
for train_index, test_index in splitter.split(housing, housing["income_cat"]):
    strat_train_set_n = housing.iloc[train_index]
    strat_test_set_n = housing.iloc[test_index]
    strat_splits.append([strat_train_set_n, strat_test_set_n])

现在,您可以使用第一个拆分:

strat_train_set, strat_test_set = strat_splits[0]

或者,由于分层抽样相当常见,可以使用train_test_split()函数的stratify参数来更快地获得单个拆分:

strat_train_set, strat_test_set = train_test_split(
    housing, test_size=0.2, stratify=housing["income_cat"], random_state=42)

让我们看看这是否按预期工作。您可以从测试集中查看收入类别的比例:

>>> strat_test_set["income_cat"].value_counts() / len(strat_test_set)
3    0.350533
2    0.318798
4    0.176357
5    0.114341
1    0.039971
Name: income_cat, dtype: float64

使用类似的代码,您可以测量完整数据集中的收入类别比例。图 2-10 比较了整体数据集中的收入类别比例,使用分层抽样生成的测试集以及使用纯随机抽样生成的测试集。如您所见,使用分层抽样生成的测试集的收入类别比例几乎与完整数据集中的相同,而使用纯随机抽样生成的测试集则有所偏差。

图 2-10。分层与纯随机抽样的抽样偏差比较

您不会再使用income_cat列,因此可以将其删除,将数据恢复到原始状态:

for set_ in (strat_train_set, strat_test_set):
    set_.drop("income_cat", axis=1, inplace=True)

我们花了相当多的时间在测试集生成上,这是一个经常被忽视但至关重要的机器学习项目的关键部分。此外,当我们讨论交叉验证时,许多这些想法将在以后派上用场。现在是时候进入下一个阶段了:探索数据。

探索和可视化数据以获得洞见

到目前为止,您只是快速浏览了数据,以对您正在处理的数据类型有一个大致了解。现在的目标是深入一点。

首先,请确保您已经将测试集放在一边,只探索训练集。此外,如果训练集非常庞大,您可能希望对探索集进行抽样,以便在探索阶段进行操作更加轻松和快速。在这种情况下,训练集相当小,因此您可以直接在完整集上工作。由于您将尝试对完整训练集进行各种转换,因此应该先复制原始数据,以便之后可以恢复到原始状态:

housing = strat_train_set.copy()

可视化地理数据

由于数据集包含地理信息(纬度和经度),因此创建一个散点图来可视化所有地区是一个好主意(图 2-11):

housing.plot(kind="scatter", x="longitude", y="latitude", grid=True)
plt.show()

图 2-11。数据的地理散点图

这看起来确实像加利福尼亚,但除此之外很难看出任何特定的模式。将alpha选项设置为0.2可以更容易地可视化数据点密度较高的地方(图 2-12):

housing.plot(kind="scatter", x="longitude", y="latitude", grid=True, alpha=0.2)
plt.show()

现在好多了:您可以清楚地看到高密度区域,即旧金山湾区、洛杉矶周围和圣迭戈周围,以及中央谷地(特别是萨克拉门托和弗雷斯诺周围)的一长串相当高密度区域。

我们的大脑非常擅长发现图片中的模式,但您可能需要调整可视化参数来突出这些模式。

图 2-12。一个更好的可视化,突出显示高密度区域

接下来,您将查看房屋价格(图 2-13)。每个圆的半径代表地区的人口(选项s),颜色代表价格(选项c)。在这里,您使用了一个预定义的颜色映射(选项cmap)称为jet,从蓝色(低值)到红色(高价格):⁠⁸

housing.plot(kind="scatter", x="longitude", y="latitude", grid=True,
             s=housing["population"] / 100, label="population",
             c="median_house_value", cmap="jet", colorbar=True,
             legend=True, sharex=False, figsize=(10, 7))
plt.show()

这幅图告诉你,房价与位置(例如靠近海洋)和人口密度密切相关,这可能是你已经知道的。聚类算法应该对检测主要集群和添加衡量到集群中心距离的新特征很有用。海洋接近度属性也可能有用,尽管在加利福尼亚北部,沿海地区的房价并不太高,所以这并不是一个简单的规则。

图 2-13. 加利福尼亚房价:红色是昂贵的,蓝色是便宜的,较大的圆圈表示人口较多的地区

寻找相关性

由于数据集不太大,你可以使用corr()方法轻松计算每对属性之间的标准相关系数(也称为皮尔逊相关系数):

corr_matrix = housing.corr()

现在你可以看看每个属性与房屋中位价值的相关程度:

>>> corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value    1.000000
median_income         0.688380
total_rooms           0.137455
housing_median_age    0.102175
households            0.071426
total_bedrooms        0.054635
population           -0.020153
longitude            -0.050859
latitude             -0.139584
Name: median_house_value, dtype: float64

相关系数的范围是-1 到 1。当它接近 1 时,意味着有很强的正相关性;例如,当中位收入上升时,中位房价往往会上涨。当系数接近-1 时,意味着有很强的负相关性;你可以看到纬度和中位房价之间存在微弱的负相关性(即,当你向北走时,价格略有下降的趋势)。最后,接近 0 的系数意味着没有线性相关性。

检查属性之间的相关性的另一种方法是使用 Pandas 的scatter_matrix()函数,它将每个数值属性与其他每个数值属性绘制在一起。由于现在有 11 个数值属性,你将得到 11² = 121 个图,这些图无法放在一页上,所以你决定专注于一些看起来与房屋中位价值最相关的有希望的属性(图 2-14):

from pandas.plotting import scatter_matrix
attributes = ["median_house_value", "median_income", "total_rooms",
              "housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12, 8))
plt.show()

图 2-14. 这个散点矩阵将每个数值属性与其他每个数值属性绘制在一起,主对角线上是每个数值属性值的直方图(从左上到右下)

如果 Pandas 将每个变量与自身绘制在一起,主对角线将充满直线,这将没有太大用处。因此,Pandas 显示了每个属性的直方图(还有其他选项可用;请参阅 Pandas 文档以获取更多详细信息)。

看着相关性散点图,似乎最有希望预测房屋中位价值的属性是中位收入,所以你放大了它们的散点图(图 2-15):

housing.plot(kind="scatter", x="median_income", y="median_house_value",
             alpha=0.1, grid=True)
plt.show()

图 2-15. 中位收入与中位房价

这个图揭示了一些事情。首先,相关性确实非常强;你可以清楚地看到上升趋势,点的分散程度也不大。其次,你之前注意到的价格上限在$500,000 处清晰可见。但这个图还显示了其他不太明显的直线:一个在$450,000 左右的水平线,另一个在$350,000 左右,也许还有一个在$280,000 左右,以及一些更低的线。你可能想尝试删除相应的地区,以防止你的算法学习复制这些数据怪癖。

警告

相关系数只能测量线性相关性(“随着x的增加,y通常上升/下降”)。它可能完全忽略非线性关系(例如,“随着x接近 0,y通常上升”)。图 2-16 展示了各种数据集以及它们的相关系数。请注意,尽管底部行的所有图都具有相关系数为 0,但它们的坐标轴显然是独立的:这些是非线性关系的示例。此外,第二行显示了相关系数等于 1 或-1 的示例;请注意,这与斜率无关。例如,您的身高以英寸为单位与以英尺或纳米为单位的身高的相关系数为 1。

图 2-16. 各种数据集的标准相关系数(来源:维基百科;公共领域图像)

尝试属性组合

希望前面的部分给您提供了一些探索数据和获得见解的方法。您发现了一些可能需要在将数据馈送到机器学习算法之前清理的数据怪癖,并且找到了属性之间的有趣相关性,特别是与目标属性相关的。您还注意到一些属性具有右偏分布,因此您可能希望对其进行转换(例如,通过计算其对数或平方根)。当然,每个项目的情况会有很大不同,但总体思路是相似的。

在准备数据用于机器学习算法之前,您可能希望尝试各种属性组合。例如,一个地区的总房间数如果不知道有多少户,就不是很有用。您真正想要的是每户的房间数。同样,单独的卧室总数并不是很有用:您可能想要将其与房间数进行比较。每户人口也似乎是一个有趣的属性组合。您可以按照以下方式创建这些新属性:

housing["rooms_per_house"] = housing["total_rooms"] / housing["households"]
housing["bedrooms_ratio"] = housing["total_bedrooms"] / housing["total_rooms"]
housing["people_per_house"] = housing["population"] / housing["households"]

然后再次查看相关矩阵:

>>> corr_matrix = housing.corr()
>>> corr_matrix["median_house_value"].sort_values(ascending=False)
median_house_value    1.000000
median_income         0.688380
rooms_per_house       0.143663
total_rooms           0.137455
housing_median_age    0.102175
households            0.071426
total_bedrooms        0.054635
population           -0.020153
people_per_house     -0.038224
longitude            -0.050859
latitude             -0.139584
bedrooms_ratio       -0.256397
Name: median_house_value, dtype: float64

嘿,不错!新的bedrooms_ratio属性与房屋中位数的相关性要比总房间数或卧室总数高得多。显然,卧室/房间比例较低的房屋往往更昂贵。每户房间数也比一个地区的总房间数更具信息量——显然,房屋越大,价格越高。

这一轮探索不必绝对彻底;重点是要以正确的方式开始,并迅速获得有助于获得第一个相当不错原型的见解。但这是一个迭代过程:一旦您建立起一个原型并使其运行起来,您可以分析其输出以获得更多见解,并回到这一探索步骤。

为机器学习算法准备数据

现在是为您的机器学习算法准备数据的时候了。您应该为此目的编写函数,而不是手动操作,有几个很好的理由:

  • 这将使您能够轻松在任何数据集上重现这些转换(例如,下次获取新数据集时)。
  • 您将逐渐构建一个转换函数库,可以在将来的项目中重复使用。
  • 您可以在实时系统中使用这些函数,在将新数据馈送到算法之前对其进行转换。
  • 这将使您能够轻松尝试各种转换,并查看哪种转换组合效果最好。

但首先,恢复到一个干净的训练集(再次复制strat_train_set)。您还应该分开预测变量和标签,因为您不一定希望对预测变量和目标值应用相同的转换(请注意,drop()会创建数据的副本,不会影响strat_train_set):

housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()

清理数据

大多数机器学习算法无法处理缺失特征,因此您需要处理这些问题。例如,您之前注意到total_bedrooms属性有一些缺失值。您有三种选项来解决这个问题:

  1. 摆脱相应的地区。
  2. 摆脱整个属性。
  3. 将缺失值设置为某个值(零、均值、中位数等)。这称为填充

使用 Pandas DataFrame 的dropna()drop()fillna()方法可以轻松实现这些功能:

housing.dropna(subset=["total_bedrooms"], inplace=True)  # option 1
housing.drop("total_bedrooms", axis=1)  # option 2
median = housing["total_bedrooms"].median()  # option 3
housing["total_bedrooms"].fillna(median, inplace=True)

您决定选择第 3 个选项,因为它是最不破坏性的,但是不使用前面的代码,而是使用一个方便的 Scikit-Learn 类:SimpleImputer。好处是它将存储每个特征的中位数值:这将使得不仅可以在训练集上填补缺失值,还可以在验证集、测试集和任何输入到模型的新数据上填补缺失值。要使用它,首先需要创建一个SimpleImputer实例,指定您要用该属性的中位数替换每个属性的缺失值:

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(strategy="median")

由于中位数只能计算在数值属性上,因此您需要创建一个仅包含数值属性的数据副本(这将排除文本属性ocean_proximity):

housing_num = housing.select_dtypes(include=[np.number])

现在,您可以使用fit()方法将imputer实例拟合到训练数据中:

imputer.fit(housing_num)

imputer只是计算了每个属性的中位数,并将结果存储在其statistics_实例变量中。只有total_bedrooms属性有缺失值,但是您不能确定系统上线后新数据中是否会有缺失值,因此最好将imputer应用于所有数值属性:

>>> imputer.statistics_
array([-118.51 , 34.26 , 29\. , 2125\. , 434\. , 1167\. , 408\. , 3.5385])
>>> housing_num.median().values
array([-118.51 , 34.26 , 29\. , 2125\. , 434\. , 1167\. , 408\. , 3.5385])

现在,您可以使用这个“训练好的”imputer来转换训练集,用学习到的中位数替换缺失值:

X = imputer.transform(housing_num)

缺失值也可以用均值(strategy="mean")、最频繁值(strategy="most_frequent")或常数值(strategy="constant", fill_value=…)替换。最后两种策略支持非数值数据。

提示

sklearn.impute包中还有更强大的填充器(仅适用于数值特征):

  • KNNImputer用该特征的k个最近邻值的均值替换每个缺失值。距离基于所有可用的特征。
  • IterativeImputer为每个特征训练一个回归模型,以预测基于所有其他可用特征的缺失值。然后,它再次在更新后的数据上训练模型,并在每次迭代中重复这个过程,改进模型和替换值。

Scikit-Learn 转换器输出 NumPy 数组(有时候也输出 SciPy 稀疏矩阵),即使它们以 Pandas DataFrame 作为输入。因此,imputer.transform(housing_num)的输出是一个 NumPy 数组:X既没有列名也没有索引。幸运的是,将X包装在 DataFrame 中并从housing_num中恢复列名和索引并不太困难:

housing_tr = pd.DataFrame(X, columns=housing_num.columns,
                          index=housing_num.index)


Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(一)(4)https://developer.aliyun.com/article/1482408#slide-31

相关文章
|
2天前
|
机器学习/深度学习 算法 TensorFlow
TensorFlow 2keras开发深度学习模型实例:多层感知器(MLP),卷积神经网络(CNN)和递归神经网络(RNN)
TensorFlow 2keras开发深度学习模型实例:多层感知器(MLP),卷积神经网络(CNN)和递归神经网络(RNN)
|
4天前
|
机器学习/深度学习 PyTorch TensorFlow
TensorFlow、Keras 和 Python 构建神经网络分析鸢尾花iris数据集|代码数据分享
TensorFlow、Keras 和 Python 构建神经网络分析鸢尾花iris数据集|代码数据分享
15 0
|
13天前
|
机器学习/深度学习 运维 监控
TensorFlow分布式训练:加速深度学习模型训练
【4月更文挑战第17天】TensorFlow分布式训练加速深度学习模型训练,通过数据并行和模型并行利用多机器资源,减少训练时间。优化策略包括配置计算资源、优化数据划分和减少通信开销。实际应用需关注调试监控、系统稳定性和容错性,以应对分布式训练挑战。
|
15天前
|
机器学习/深度学习 人工智能 算法框架/工具
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(八)(4)
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(八)
30 0
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(八)(4)
|
15天前
|
机器学习/深度学习 算法框架/工具 TensorFlow
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(七)(4)
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(七)
46 0
Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(七)(4)
|
4月前
|
机器学习/深度学习 人工智能 API
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:1~5
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:1~5
73 0
|
4月前
|
机器学习/深度学习 存储 人工智能
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:6~11(3)
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:6~11(3)
81 0
|
4月前
|
机器学习/深度学习 Dart TensorFlow
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:6~11(5)
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:6~11(5)
73 0
|
3月前
|
机器学习/深度学习 PyTorch TensorFlow
Python中的深度学习:TensorFlow与PyTorch的选择与使用
Python中的深度学习:TensorFlow与PyTorch的选择与使用
|
3月前
|
机器学习/深度学习 数据可视化 TensorFlow
基于tensorflow深度学习的猫狗分类识别
基于tensorflow深度学习的猫狗分类识别
65 1