Python机器学习——预测分析核心算法

978-7-115-43373-2
作者: 【美】Michael Bowles(鲍尔斯)
译者: 沙灜李鹏
编辑: 陈冀康

图书目录:

详情

本书通过结合Python语言特色以及核心的机器学习算法来向读者介绍一种简单且高效的数据分析和预测的方法,书中对于如何运用Python语言这一工具给出了详细而全面的描述。本书形式新颖,依托Python语言更好地将众多复杂的数学概念转化为简单易懂的示例。通过阅读本书,读者将深入理解每一种机器学习的算法,并且能够依据书中所介绍的方法来对自己身边的数据尝试进行分析,实现理论到使用技能的深层跨越。

图书摘要

版权信息

书名:Python机器学习——预测分析核心算法

ISBN:978-7-115-43373-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美]Michael Bowles

  译     赵 俐 曾少宁

  责任编辑  陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Michael Bowles

Machine Learning in Python: Essential Techniques for Predictive Analysis

Copyright © 2015 by John Wiley & Sons, Inc.

All right reserved. This translation published under license.

Authorized translation from the English language edition published by John Wiley & Sons, Inc.

本书中文简体字版由John Wiley & Sons公司授权人民邮电出版社出版,专有出版权属于人民邮电出版社。

版权所有,侵权必究。


在学习和研究机器学习的时候,面临令人眼花缭乱的算法,机器学习新手往往会不知所措。本书从算法和Python语言实现的角度,帮助读者认识机器学习。

本书专注于两类核心的“算法族”,即惩罚线性回归和集成方法,并通过代码实例来展示所讨论的算法的使用原则。全书共分为7章,详细讨论了预测模型的两类核心算法、预测模型的构建、惩罚线性回归和集成方法的具体应用和实现。

本书主要针对想提高机器学习技能的Python开发人员,帮助他们解决某一特定的项目或是提升相关的技能。


Michael Bowles拥有机械工程学士和硕士学位、仪器仪表博士学位以及MBA学位。他的履历涉及学术界、工业界以及商业界。他目前在一家初创公司工作,其中机器学习技术至关重要。他是多个管理团队的成员、咨询师以及顾问。他也曾经在加州山景城的黑客道场、创业公司孵化器和办公场所教授机器学习课程。

他出生于俄克拉荷马州并在那里获得学士和硕士学位。在东南亚待了一段时间后,他前往剑桥攻读博士学位,毕业后任职于MIT的Charles Stark Draper实验室[1]。之后他离开波士顿前往南加州的休斯飞机公司开发通信卫星。在UCLA获得MBA学位后,他前往旧金山的湾区工作。作为创始人以及CEO,他目前经营两家公司,这两家公司都已获风险投资。

他目前仍然积极参与技术以及创业相关的工作。近期项目包括使用机器学习技术进行自动交易,基于基因信息进行生物预测,使用自然语言处理技术进行网站优化,利用人口统计学及实验室数据预测医疗效果,在机器学习和大数据相关领域的公司里尽心尽责。可以通过www.mbowles.com联系到他。

[1]  Charles Stark Draper是一名美国科学家和工程师,被称为“惯性导航之父”。他是MIT仪器实验室的创始人,后来此实验室用其名来命名,此实验室设计了阿波罗登月计划中的阿波罗导航计算机。


Daniel Posner拥有经济学学士以及硕士学位,在波士顿大学完成生物统计学博士学位。他曾为医药生物领域的公司以及帕罗奥图退伍军人事务部医院的研究人员提供统计学方面的咨询。

Daniel和本书作者就书中相关主题有过广泛的合作。他们曾经一起撰写过开发Web级梯度提升算法的项目申请书。最近,他们合作利用随机森林和样条基扩展技术解决药物研制过程中的关键变量识别问题,其目标是提升预测效果以减少试验所需样本规模。


首先感谢Wiley出版社工作人员在本书创作期间提供的大量帮助。最早是组稿编辑Robert Elliot和我联系写作本书,他很容易相处。之后是Jennifer Lynn为本书的编辑。她对每个问题都积极响应,写作期间非常耐心地和我联系,保证我的写作计划如期完成。非常感谢你们的工作。

我也非常感谢如此敏锐、缜密的统计学家兼程序员Daniel Posner作为本书的技术编辑,从他那获得了巨大的安慰和帮助。感谢你的工作,也感谢你在机器学习、统计学以及算法上所做的有趣讨论。我还没见过其他谁的思维可以达到如此深入、如此迅捷。


 

从数据中提取有助于决策的信息正在改变着现代商业的组织,同时也对软件开发人员产生了直接的影响。一方面是对新的软件开发技能的需求,市场分析师预计到2018年对具有高级统计和机器学习技术的人才需求缺口将达140000~190000人。这对具有上述技能的人员来说意味着丰厚的薪水和可供选择的多种有趣的项目。另一方面对开发人员的影响就是逐步出现了统计和机器学习相关的核心工具,这减轻了开发人员的负担。当他们尝试新的算法时,不需要重复发明“轮子”。在所有通用计算机语言中,Python的开发人员已经站在了最前沿,他们已开发了当前最先进的机器学习工具包,但从拥有这些工具包到如何有效地使用它们仍然存在一定的距离。

开发人员可以通过在线课程、阅读质量上乘的书籍等方式来获得机器学习的相关知识。它们通常都对机器学习的算法、应用实例给出了精彩的阐述,但是因为当前算法如此之多,以至于很难在一门课程或一本书中覆盖全部算法的相关细节。

这给实践者带来了困难。当面临众多算法时,机器学习新手可能需要多次尝试才能做出决定,这往往需要开发人员来填补从问题提炼到最终问题解决之间的所有算法使用方便的细节。

本书尝试填补这一鸿沟,所采用的方法就是只集中于两类核心的“算法族”,这两类算法族已在广泛的应用领域中证明了其最佳的性能。此论断的证据如下:这两类算法在众多机器学习算法竞争者中已获得支配性地位,新开发的机器学习工具包都会率先支持此两类算法,以及研究工作给出的性能对比结论(见第1章)。重点关注这两类算法使我们可以更详细地介绍算法的使用原则,并通过一系列的示例细节来展示针对不同问题如何使用这些算法。

本书主要通过代码实例来展示所讨论的算法的使用原则。以我在加州山景城的黑客道场(Hacker Dojo)授课的经验来看,我发现开发人员更愿意通过直接看代码示例来了解算法原理,而不是通过数学公式推导。

本书使用Python语言,因为它能提供将功能和专业性良好结合的机器学习算法包。Python是一种经常使用的计算机语言,以产生精炼、可读性代码而著称。这导致目前已有相当数量的业界旗舰公司采用Python语言进行原型系统开发和部署。Python语言开发人员获得了广泛的支持,包括大量的同业开发人员组成的社区、各种开发工具、扩展库等等。Python广泛应用于企业级应用和科学研究领域。它有相当数量的工具包支持计算密集型应用,如机器学习。它也收集了当前机器学习领域的代表性算法(这样就可以省去重复性劳动)。Python相比专门的统计语言如R或SAS(Statistical Analysis System)是一门更通用的语言,它的机器学习算法包吸收了当前一流的算法,并且在一直扩充。

本书主要面向想提高机器学习技能的Python开发人员,不管是针对某一特定的项目,还是只想提升相关技能。开发人员很可能在工作中遇到新问题需要使用机器学习的方法来解决。当今机器学习的应用领域如此之广,使其已成为简历中一项十分有用的技能。

本书为Python开发人员提供如下内容:

为了能够顺利地理解这本书,读者所需的背景知识包括:了解编程、能够读写代码。因为本书的代码示例、库、包都是Python语言的,所以本书主要适用于Python开发人员。本书通过运行算法的核心代码来展示算法的使用原则,然后使用含有此算法的工具包来展示如何应用此算法来解决问题。开发人员通过源代码可以获得对算法的直观感受,就像其他人通过数学公式的推导来掌握算法。一旦掌握了算法的核心原理,应用示例就直接使用Python工具包,这些工具包都包含了能够有效使用这些算法必需的辅助模块(如错误检测、输入输出处理、模型所需数据结构的处理、基于训练模型的预测方法的处理,等等)。

除了编程背景,懂得相关数学、统计的知识将有助于掌握本书的内容。相关数学知识包括大学本科水平的微分学(知道如何求导,少量线性代数知识)、矩阵符号的意义、矩阵乘、求逆矩阵。这些知识主要是帮助理解一些算法中的求导部分,很多情况下就是一个简单函数的求导或基本的矩阵操作。能够理解概念层面上的数学计算将有助于对算法的理解。明白推导各步的由来有助于理解算法的强项和弱项,也帮助读者面对具体的问题时,决定哪个算法是最佳选择。

本书也用到了一些概率和统计知识。对这方面的要求包括熟悉大学本科水平的概率知识和概念,如实数序列的均值、方差和相关系数。当然即使这些知识对读者来说有些陌生,也不会影响读者对代码的理解。

本书涵盖了机器学习算法的两大类:惩罚线性回归(penalized linear regression),如岭回归算法(ridge)、Lasso算法;集成方法(ensemble methods),如随机森林算法(random forests)、梯度提升算法(gradient boosting)。上述两大类算法有一些变体,都可以解决回归和分类的问题(在本书开始部分将会介绍分类和回归的区别)。

如果读者已熟悉机器学习并只对其中的一类算法感兴趣,可以直接跳到相关的二章。每类算法由两章组成,一章介绍基本原理,另外一章介绍针对不同类型问题的用法。惩罚线性回归由下列两章组成:第4章“惩罚线性回归模型”和第5章“利用惩罚线性回归方法来构建预测模型”;集成方法由下列两章组成:第6章“集成方法”和第7章“用Python构建集成模型”。快速浏览第2章“通过理解数据来了解问题”将有助于理解算法应用章节中的问题。刚刚进入机器学习领域准备从头到尾通读的读者可以把第2章留到阅读那些算法应用章节前。

如上所述,本书涵盖两大类算法,这些算法近期都获得了发展,并将仍然获得持续性研究,它们都起源于早期的技术,但已使这些早期技术黯然失色。

惩罚线性回归代表了对最小二乘法回归方法(least squares regression)的相对较新的改善和提高。惩罚线性回归具有的几个特征使其成为预测分析的首选。惩罚线性回归引入了一个可调参数,使最终的模型在过拟合与欠拟合之间达到了平衡。它还提供不同的输入特征对预测结果的相对贡献的信息。上述这些特征对于构建预测模型都是十分重要的。而且,对于某些问题惩罚线性回归可以产生最佳的预测性能,特别是对于欠定的问题以及具有很多输入参数的问题,如基因领域、文本挖掘等。进一步,坐标下降法(coordinate descent methods)等新方法可以使惩罚线性回归模型训练过程运行得更快。

为了帮助读者更好地理解惩罚线性回归,本书也概要介绍了线性回归及其扩展,如逐步回归(stepwise regression),主要是希望能够培养读者对算法的直观感受。

集成方法是目前最有力的预测分析工具之一。它可以对特别复杂的行为进行建模,特别是过定的问题,通常这些都是与互联网有关的预测问题(如返回搜索结果和预测点击率)。由于集成方法的性能,许多经验丰富的数据科学家在做第一次尝试时都使用该方法。集成方法使用相对简单,而且可以依据对预测的贡献程度对输入特征排序。

目前集成方法与惩罚线性回归齐头并进。然而惩罚线性回归是从克服一般回归方法的局限性进化而来的,集成方法是从克服二元决策树的局限性进化而来的。因此本书介绍集成方法时,也会涉及二元决策树的背景知识,因为集成方法继承了二元决策树的一些属性。了解这些将有助于培养对集成方法的直觉。

本书遵循了着手解决一个预测问题的基本流程。开始阶段包括对数据的理解、如何形式化表示问题,然后开始尝试使用算法解决问题,评估其性能。在这个过程中,本书将概要描述每一步采用的方法及其原因。第1章给出本书涵盖的问题和所用方法的完整描述,本书使用来自UC Irvine数据仓库的数据集作为例子;第2章展示了一些数据分析的方法和工具,帮助读者对新数据集具有一定的洞察力。第3章“预测模型的构建:平衡性能、复杂性以及大数据”主要介绍由上述三者带给预测分析技术的困难以及所采用的技术,勾勒了问题复杂度、模型复杂度、数据规模和预测性能之间的关系,讨论了过拟合问题以及如何可靠地感知到过拟合,以及不同类型问题下的性能评价标准。第4章、第5章分别介绍惩罚线性回归的背景及其应用,即如何解决第2章所述的问题。第6章、第7章分别介绍集成方法的背景及其应用。

为了运行书中的代码示例,需要有Python2.x、SciPy、NumPy、Pandas和scikit-learn。由于交叉依赖和版本的问题,这些软件的安装可以会有些困难。为了简化上述软件安装过程,可以使用来自Continuum Analytics(http://continuum.io/)的这些包的免费分发版。Continuum Analytics提供的Anaconda 软件包可自由下载并且包含Python2.x在内的运行本书代码所需的全部软件包。我在Ubuntu 14.04 Linux发行版上测试了本书的代码,但是没有在其他的操作系统上测试过。

为了便于对本书的理解,本书通篇采用如下的约定。

警告

 

这些方框表示是与其周围文本直接相关的不能忘记的重要信息。

注释

 

表示与当前讨论相关的注释、说明、提示和技巧。

研究本书示例代码时,可以选择手工敲入这些代码,也可以直接使用随书带的源代码文件。本书用到的所有源代码都可以从http://www.wiley.com/go/pythonmachinelearning中下载获得。在本书代码片段旁会有一个下载的小图标,并注明文件名,这样就可以知道此文件在下载的源代码中,并且可以很轻松地从下载源代码中找到此文件。读者可访问上述网站,定位到本书书名(可以使用搜索框或书名列表),在本书详细介绍页面点击“Download Code”链接就可以获得本书的全部源代码。

注释

 

因为很多书的书名都非常相似,最简单的方法就是通过ISBN来查找,本书的ISBN是978-1-118-96174-2。

下载源代码后,只需用你惯用的解压缩工具解压缩即可。

我们已尽最大可能避免在文本和代码中出现错误。但是没有人是完美的,同样错误也难免会发生。如果读者在书中发现了错误,诸如拼写错误、代码错误等等,能及时反馈,我们将非常感谢。提交勘误表,能减少其他读者的困惑,同时也帮助我们提供更高质量的内容。

获得本书勘误表的方法:访问http://www.wiley.com,通过搜索框或书名列表定位到本书;然后进入本书的详细介绍页面,点击“Book Errata”链接,可以看到关于本书所有已提交的并由Wiley出版社编辑上传的勘误表。


本书集中于机器学习领域,只关注那些最有效和获得广泛使用的算法。不会提供关于机器学习技术领域的全面综述。这种全面性的综述往往会提供太多的算法,但是这些算法并没有在从业者中获得积极的应用。

本书涉及的机器学习问题通常是指“函数逼近(function approximation)”问题。函数逼近问题是有监督学习(supervised learning)问题的一个子集。线性回归和逻辑回归是解决此类函数逼近问题最常见的算法。函数逼近问题包含了各种领域中的分类问题和回归问题,如文本分类、搜索响应、广告放置、垃圾邮件过滤、用户行为预测、诊断等。这个列表几乎可以一直列下去。

从广义上说,本书涵盖了解决函数逼近问题的两类算法:惩罚线性回归和集成方法。本章将介绍这些算法,概述它们的特性,回顾算法性能对比研究的结果,以证明这些算法始终如一的高性能。

然后本章讨论了构建预测模型的过程,描述了这里介绍的算法可以解决的问题类型,以及如何灵活地构建问题模型,选择用于做预测的特征。本章也描述了应用算法的具体步骤,包括预测模型的构建、面向部署的性能评估等。

有几个因素造就了惩罚线性回归和集成方法成为有用的算法集。简单地说,面对实践中遇到的绝大多数预测分析(函数逼近)问题,这两类算法都具有最优或接近最优的性能。这些问题包含:大数据集、小数据集、宽数据集(wide data sets)[1]、高瘦数据集(tall skinny data sets)[2]、复杂问题、简单问题,等等。Rich Caruana及其同事的两篇论文为上述论断提供了证据。

1.“An Empirical Comparison of Supervised Learning Algorithms,” Rich Caruana,Alexandru Niculescu-Mizi。

2.“An Empirical Evaluation of Supervised Learning in High Dimensions,” Rich Caruana, Nikos Karampatziakis和Ainur Yessenalina。

在这两篇论文中,作者选择了各种分类问题,用各种不同的算法来构建预测模型。然后测试这些预测模型在测试数据中的效果,这些测试数据当然不能应用于模型的训练阶段,对这些算法根据性能进行打分。第一篇论文针对11个不同的机器学习问题(二元分类问题)对比了9个基本算法。所选问题来源广泛,包括人口统计学、文本处理、模式识别、物理学和生物学。表1-1列出了此篇论文所用的数据集,所用名字与论文中的一致。此表还展示了针对每个数据集做预测时使用了多少属性(特征)以及正例所占的百分比。

术语“正例”(positive example)在分类问题中是指一个实验(输入数据集中的一行数据)其输出结果是正向的(positive)。例如,如果设计的分类器是判断雷达返回信号是否表明出现了一架飞机,那么正例则是指在雷达视野内确实有一架飞机的那些结果。正例这个词来源于这样的例子:两个输出结果分别代表出现或不出现。其他例子还包括在医学检测中某种疾病出现或不出现,退税中是否存在欺骗。

不是所有的分类问题都处理出现或不出现的问题。例如通过计算机分析一个作家发表的作品或者其手写体的样本来判断此人的性别:男性或女性,在这里性别的出现或不出现是没有什么意义的。在这些情况下,指定哪些为正例、哪些为负例则有些随意,但是一旦选定,在使用中要保持一致。

在第1篇论文的某些数据集中,某一类的数据(例子)要远多于其他类的数据(例子),这叫作非平衡(unbalanced)。例如,2个数据集Letter.p1 和Letter.p2.都是用于解决相似的问题:在多种字体下正确地分出大写字母。Letter.p1的任务是在标准字母的混合集中正确区分出大写字母O,Letter.p2的任务是将字母正确划分成A-M和N-2的两类。表1-1中的“正例百分比”一栏反映了这种数据非平衡的差异性。

表1-1 机器学习算法比较研究中问题的梗概

数据集名称

属性数

正例百分比

Adult

14

25

Bact

11

69

Cod

15

50

Calhous

9

52

Cov_Type

54

36

HS

200

24

Letter.p1

16

3

Letter.p2

16

53

Medis

63

11

Mg

124

17

Slac

59

50

表1-1还显示了每个数据集所使用的属性(特征)的数量。特征就是基于此进行预测的变量。例如,预测一架飞机能否按时到达目的地,可能导入下列属性(特征):航空公司的名字、飞机的制造商和制造年份、目的地机场的降水量和航线上的风速与风向等等。基于很多特征做预测很可能是一件很难说清楚是福还是祸的事情。如果导入的特征与预测结果直接相关,那么当然是一件值得祝福的事情。但是如果导入的特征与预测结果无关,就是一件该诅咒的事情了。那么如何区分这两种属性(该祝福的属性、该诅咒的属性)则需要数据来说话。第3章将进行更深入的讨论。

本书涵盖的算法与上述论文中提到的其他算法的比较结果如表1-2所示。针对表1-1列出的问题,表1-2列出了性能打分排前5名的算法。本书涵盖的算法脱颖而出(提升决策树(Boosted Decision Trees)、随机森林(Random Forests)、投票决策树(Bagged Decision Trees)和逻辑回归(Logistic Regression)。前3个属于集成方法。在那篇论文撰写期间惩罚回归还没有获得很好的发展,因此在论文中没有对此进行评价。逻辑回归是属于与之相对比较接近的算法,可以用来评测回归算法的性能。对于论文中的9个算法各有3种数据规约方法,所以一共是27种组合。前5名大概占据性能评分排名的前20%。第1行针对Covt数据集的算法排名可以看到:提升决策树算法占第1名、第2名;随机森林算法占第4名、第5名;投票决策树算法占第3名。出现在前5名但是本书没有涵盖的算法在最后一列列出(“其他”列)。表中列出的算法包括:K最近邻(K Nearest Neighbors, KNNs)、人工神经网络(artificial neural nets, ANNs)、支持向量机(support vector machine, SVMs)。

表1-2 本书涵盖的机器学习算法针对不同问题的性能表现

算法

提升决策树

随机森林

投票决策树

逻辑回归

其他

Covt

1, 2

4, 5

3

 

 

Adult

1, 4

2

3, 5

 

 

LTR.P1

1

 

 

 

支持向量机
K最近邻

LTR.P2

1, 2

4, 5

 

 

支持向量机

MEDIS

 

1, 3

 

5

人工神经网络

SLAC

 

1, 2, 3

4, 5

 

 

HS

1, 3

 

 

 

人工神经网络

MG

 

2, 4, 5

1, 3

 

 

CALHOUS

1, 2

5

3, 4

 

 

COD

1, 2

 

3, 4, 5

 

 

BACT

2, 5

 

1, 3, 4

 

 

在表1-2中,逻辑回归只在一个数据集下进入前5。原因是针对表1-2中的数据集规模及其所选的特征,不能体现逻辑回归的优势。相对于数据集的规模(每个数据集有5 000个示例)采用的特征太少了(最多也就200个)。选取的特征有限,因此现有的数据规模不足以选出一个合适的预测模型,而且训练数据集规模又足够小,使得训练时间不至太长,因此其他算法也可以获得很好的结果。

注意


正如将在第3章、第5章和第7章中所看到的那样,当数据含有大量的特征,但是没有足够多的数据或时间来训练更复杂的集成方法模型时,惩罚回归方法将优于其他算法。

Caruana等人的最新研究(2008年)关注在特征数量增加的情况下,上述算法的性能。也就是说这些算法面对大数据表现如何。有很多领域的数据拥有的特征已远远超过了第一篇论文中的数据集的规模。例如,基因组问题通常有数以万计的特征(一个基因对应一个特征),文本挖掘问题通常有几百万个特征(每个唯一的词或词对对应一个特征)。线性回归和集成方法随着特征增加的表现如表1-3所示。表1-3列出了第2篇论文中涉及的算法的评分情况,包括算法针对每个问题的性能得分,最右列是此算法针对所有问题的平均得分。算法分成2组,上半部分是本书涵盖的算法,下半部分是其他算法。表1-3中的算法依次为:BSTDT(Boosted Decision Tress)-提升决策树;RF(Random Forests)-随机森林;BAGDT(Bagged Decision Trees)-投票决策树;BSTST(Boosted Stumps)-提升二叉树:LR(Logistic Regression)-逻辑回归;SVM(Support Vector Machines)-支持向量机;ANN(Artificial Neural Nets)-人工神经网络;KNN(Distance Weighted kNN)-距离加权K最近邻;PRC(Voted Perceptrons)-表决感知器;NB(Naive Bayes)-朴素贝叶斯。

表1-3中的问题是依其特征规模依次排列的,从761个特征到最终的685569个特征。线性(逻辑)回归在11个测试中的5个进入前3。而且这些优异的分数主要集中在更大规模的数据集部分。注意提升决策树(表1-3标为BSTDT)和随机森林(表1-3标为RF)其表现仍然接近最佳。它们针对所有问题的平均得分排名第1、第2。

表1-3 本书涵盖的机器学习算法面对大数据问题的性能

本书涵盖的算法除了性能外,在其他方面也有优势。惩罚线性回归模型一个重要优势就是它训练所需时间。当面对大规模的数据时,训练所需时间就成为一个需要考量的因素。某些问题的模型训练可能需要几天到几周,这往往是不能忍受的,特别是在开发早期,需要尽早在多次迭代之后找到最佳的方法。惩罚线性回归方法除了训练时间特别快,部署已训练好的模型后进行预测的时间也特别快,可用于高速交易、互联网广告的植入等。研究表明惩罚线性回归在许多情况下可以提供最佳的答案,在即使不是最佳答案的情况下,也可以提供接近最佳的答案。

而且这些算法使用十分简单,可调参数不多,都有定义良好、结构良好的输入数据类型。它们可以解决回归和分类的问题。当面临一个新问题的时候,在1~2小时内完成输入数据的处理、训练模型、输出预测结果是司空见惯的。

这些算法的一个最重要特性就是可以明确地指出哪个输入变量(特征)对预测结果最重要。这已经成为机器学习算法一个无比重要的特性。在预测模型构建过程中,最消耗时间的一步就是特征提取(feature selection)或者叫作特征工程(feature engineering)。就是数据科学家选择哪些变量用于预测结果的过程。根据对预测结果的贡献程度对特征打分,本书涵盖的算法在特征提取过程中可以起到一定的辅助作用,这样可以抛掉一些主观臆测的东西,让预测过程更有一定的确定性。

惩罚线性回归方法是由普通最小二乘法(ordinary least squares,OLS)衍生出来的。而普通最小二乘法是在大约200年前由高斯(Gauss)和法国数学家阿德里安-马里•勒让德(Legendre)提出的。惩罚线性回归设计之初的想法就是克服最小二乘法的根本缺陷。最小二乘法的一个根本问题就是有时它会过拟合。如图1-1所示,考虑用最小二乘法通过一组点来拟合一条直线。这是一个简单的预测问题:给定一个特征x,预测目标值y。例如,可以是根据男人的身高来预测其收入。根据身高是可以稍微预测男人的收入的(但是女人不行)。

图1-1 普通最小二乘法拟合

图1-1中的点代表(男人的身高、男人的收入),直线代表使用最小二乘法的预测结果。在某种意义上说,这条直线就代表了在已知男人身高的情况下,对男人收入的最佳预测模型。现在这个数据集有6个点。假设这个数据集只有2个点。想象有一组点,就像图1-1中的点,但是你不能获得全部的点。可能的原因是要获得所有这些点的代价太昂贵了,就像前面提到的基因组数据。只要有足够多的人手,就可以分离出犯罪分子的基因组,但主要问题是代价的原因,你不可能获得他们的全部基因序列。

以简单的例子来模拟这个问题,想象只给你提供当初6个点中的任意2个点。那么拟合出来的直线会发生哪些变化?这将依赖于你得到的是哪2个点。实际看看这些拟合的效果,可以从图1-1中任意选出2个点,然后想象穿过这2个点的直线。图1-2展示了穿过图1-1中2个点的可能的直线。可以注意到拟合出来的直线依赖于这2个点是如何选择的。

图1-2 只有2个点的情况下拟合的直线

使用2个点来拟合一条直线的主要问题是针对直线的自由度(degrees of freedom)[3]没有提供足够的数据。一条直线有2个自由度。有2个自由度意味着需要2个独立的参数才能唯一确定一条直线。可以想象在一个平面抓住一条直线,然后在这个平面上下滑动这条直线,或者旋转它以改变其斜率。与x轴的交点和斜率是相互独立的,它们可以各自改变,两者结合在一起确定了一条直线。一条直线的自由度可以表示成几种等价的方式(可以表示成与y轴的交点和斜率、直线上的2个点,等等)。所有这些确定一条直线的表示方法都需要2个参数。

当自由度与点数相同时,预测效果并不是很好。连接这些点构成了直线,但是在不同点对之间可以形成大量不同的直线。对在自由度与点数相同的情况下所做的预测并不能报太大的信心。图1-1是6个点拟合一条直线(2个自由度)。也就是说6个点对应2个自由度。从大量的人类基因中找到可导致遗传基因的问题可以阐明相似的道理。例如要从大约20000个人类基因中找到可导致遗传的基因,可选择的基因越多,需要的数据也越多。20000个不同基因就代表20000个自由度,甚至从20000个人获取的数据都不足以得到可靠的结果,在很多情况下,一个相对预算合理的研究项目只能负担得起大约500个人的样本数据。在这种情况下,惩罚线性回归就是最佳的选择了。

惩罚线性回归可以减少自由度使之与数据规模、问题的复杂度相匹配。对于具有大量自由度的问题,惩罚线性回归方法获得了广泛的应用。在下列问题中更是得到了偏爱:基因问题,通常其自由度(也就是基因的数目)是数以万计的;文本分类问题,其自由度可以超过百万。第4章将提供更多的细节:这些方法如何工作、通过示例代码说明算法的机制、用Python工具包实现一个机器学习系统的过程示例。

本书涵盖的另一类算法就是集成方法(ensemble methods)。集成方法的基本思想是构建多个不同的预测模型,然后将其输出做某种组合作为最终的输出,如取平均值或采用多数人的意见(投票)。单个预测模型叫作基学习器(base learners)。计算学习理论(computation learning theory)的研究结果证明只要基学习器比随机猜测稍微好些(如果独立预测模型的数目足够多),那么集成方法就可以达到相当好的效果。

研究人员注意到某些机器学习算法输出结果不稳定,这一问题导致了集成方法的提出。例如,在现有数据集基础上增加新的数据会导致最终的预测模型或性能突变。二元决策树和传统的神经网络就有这种不稳定性。这种不稳定性会导致预测模型性能的高方差,取多个模型的平均值可以看作是一种减少方差的方法。技巧在于如何产生大量的独立预测模型,特别是当它们都采用同样的基学习器时。第6章将深入讨论这是如何做到的。方法很巧妙,理解其运作的基本原理也相对容易。下面是其概述。

集成方法为了实现最广泛的应用通常将二元决策树作为它的基学习器。二元决策树通常如图1-3所示。图1-3中的二元决策树以一个实数x作为最初的输入,然后通过一系列二元(二值)决策来决定针对x的最终输出应该是何值。第1次决策是判断x是否小于5。如果回答“no”,则二元决策树输出值4,在由决策的方框下面标为“No”的分支引出的圆圈内。每个可能的x值通过决策树都会产生输出y。图1-4将输出y画为针对决策树的输入x的函数。

图1-3 二元决策树示例

图1-4 二元决策树示例输入-输出图

由此产生的问题是:这些比较值是如何产生的(如例子中的x<5?),输出的值是如何确定的(图1-3决策树底部圆圈中的值)。这些值都来自于基于输入数据的二元决策树的训练。训练算法不难理解,在第6章会详细叙述。需要注意的很重要的一点是给定输入数据,训练所得的二元决策树的这些值都是确定的。一种获得不同模型的方法是先对训练数据随机取样,然后基于这些随机数据子集进行训练。这种技术叫作投票[bagging,来自于自举集成算法(bootstrap aggregating)的简化说法]。此方法可以产生大量的具有稍许差异的二元决策树。这些决策树的输出经过平均或投票产生最终的结果。第6章将描述此项技术的细节和其他更有力的工具。

这2类算法的概要比较如表1-4所示。惩罚线性回归的优势在于训练速度非常快。大规模数据集的训练时间可以是小时、天,甚至是几周。要获得一个可以部署的解决方案往往需要进行多次训练。过长的训练时间会影响大数据问题的解决进度及其部署。训练所需时间当然越短越好,因此惩罚线性回归因其训练所需时间短而获得广泛使用就是显而易见的了。依赖于问题,此类算法相比集成方法可能会有一些性能上的劣势。第3章将更深入地分析哪类问题适用于惩罚回归,哪类问题适用于集成方法。即使在某些情况下,惩罚线性回归的性能不如集成方法,它也可以是建立一个机器学习系统的有意义的第一步尝试。

表1-4 惩罚线性回归与集成方法权衡比较

 

训练速度

预测速度

问题复杂度

处理大量特征

惩罚线性回归

+

+

+

集成方法

+

在系统开发的早期阶段,为了特征的选择、进一步明确问题的形式化描述,训练的过程往往需要多次迭代。决定哪些特征作为预测模型的输入是需要考虑的。有时这个过程是显而易见的,但是通常需要多次迭代之后才逐渐显现出来。把能找到的所有特征都输入进去通常不是一个好的解决方案。

试错法是确定模型最佳输入的典型方法。例如,如果想预测网站的用户是否会点击某个广告链接,首先用到用户的人口统计学信息。但是结果可能并不能达到想要的精度,因此尝试导入用户在此网站过去行为的信息:在过去的网站访问过程中,此用户点击过哪些广告或购买过哪些产品。增加用户访问此网站之前的其他网站的相关信息也会有些帮助。这些尝试都导致了一系列的实验:导入新的数据,然后看看新的数据对结果是否有帮助。这种迭代过程在2个方面都是很耗时的:数据的处理、预测模型的训练。惩罚线性回归通常要比集成方法快,而这种时间上的差异性是机器学习系统开发阶段需要考虑的一个重要因素。

例如,如果训练集合在GB级别,惩罚线性回归算法的训练时间在30分钟这个级别,集成方法可能需要5~6小时。如果特征工程阶段需要10次迭代来选择最佳特征集合,则单单这个阶段就会产生1天对应1周的时间差异。一个有用的技巧就是在开发的早期阶段,如特征工程阶段,利用惩罚线性模型进行训练。这给数据科学家提供一个基本的判断:哪些变量(特征)是有用的、重要的,同时提供了一个后续与其他算法性能比较上的基线。

除了可以获得训练时间上的收益,惩罚线性方法产生预测结果也比集成方法快得多。产生预测结果需要使用一个训练好的模型。对于惩罚线性回归,训练好的模型就是一系列实数:每个实数对应一个用于做预测的特征。所涉及的浮点操作的次数就是用来做预测的变量数。对于对时间高度敏感的预测,如高速交易、互联网广告植入,计算时间上的差异往往意味着盈利还是亏损。

对于一些问题,线性方法相比集成方法可以获得同等或更好的性能。一些问题不需要复杂的模型。第3章将详细讨论问题的复杂度,数据科学家的任务就是如何平衡问题的复杂度、预测模型的复杂度和数据集规模,以获得一个最佳的可部署模型。基本思想是如果问题不是很复杂,而且不能获得足够多的数据,则线性方法比更加复杂的集成方法可能会获得全面更优的性能。基因组数据就是此类问题的典型代表。

一般的直观感受是基因数据规模巨大。当然以比特为单位,基因数据集确实是非常庞大的,但是如果为了产生准确的预测,则其规模还需要进一步增加。为了理解两者之间的差别,考虑下面一个假想的实验。假设有2个人,一个人有可遗传条件基因,另外一个人没有。如果有这2个人的基因序列,那么能确定哪个基因是可遗传条件基因?显然,这是不可能的,因为这2个人之间有很多基因是不同的。那么需要多少人才能完成这个任务呢?至少人数要与基因数相等,如果考虑到噪声,就需要更多的人了。人类大约有20000个基因,因计算方法不同而略有差异。获得每条数据大约需要1000美元,要获得足够多的数据以完美地解决此问题至少需要2000万美元。

就像本章前面讨论的那样,这种情况与用2个点来拟合一条直线非常相似。模型的自由度要比数据点少。数据集规模通常需要是自由度的倍数关系。因为数据集的规模是固定的,所以需要调整模型的自由度。惩罚线性回归的相关章节将介绍惩罚线性回归如何支持这种调整以及依此如何达到最优的性能。

注意


本书涵盖的两大类算法的分类与作者和Jeremy Howard在2012年O’Reilly Strata 国际会议中提出的完全吻合。Jeremy负责介绍集成方法,作者负责介绍接受惩罚线性回归,并就两者的优缺点进行了有趣的讨论。事实上,这两类算法占当前构建的预测模型的80%,这不是没有原因的。

第3章将更详细地讨论为什么一个算法或者另一个算法是一个问题的更好选择。这与问题的复杂度、算法内在固有的自由度有关。线性模型倾向于训练速度快,并且经常能够提供与非线性集成方法相当的性能,特别是当能获取的数据受限时。因为它们训练时间短,在早期特征选取阶段训练线性模型是很方便的,然后可以据此大致估计针对特定问题可以达到的性能。线性模型可以提供关于特征对预测的相关信息,可以辅助特征选取阶段的工作。在有充足数据的情况下,集成方法通常能提供更好的性能,也可以提供相对间接的关于结果的贡献的评估。

使用机器学习需要几项不同的技能。一项就是编程技能,本书不会把重点放在这。其他的技能用于获得合适的模型进行训练和部署。这些其他技能将是本书重点关注的。那么这些其他技能包括哪些内容?

最初,问题是用多少有些模糊的日常语言来描述的,如“给网站访问者展示他们很可能点击的链接”。将其转换为一个实用的系统需要用具体的数学语言对问题进行重述,找到预测所需的数据集,然后训练预测模型,预测网站访问者对出现的链接点击的可能性。对问题用数学语言进行重叙,其中就包含了对可获得的数据资源中抽取何种特征以及对这些特征如何构建的假设。

当遇到一个新问题时,应该如何着手?首先,需要浏览可获得的数据,确定哪类数据可能用于预测。“浏览”的意思是对数据进行各种统计意义上的检测分析,以获得直观感受这些数据透露了什么信息,这些信息又与要预测的有怎样的关系。在某种程度上,直觉可以指导你做些工作,也可以量化结果,测试潜在的这些预测特征与结果的相关性。第2章将详细介绍对数据集测试分析的过程,本书余下部分所述的算法及其比较会用到这些数据集。

假设通过某种方法,选择了一组特征,开始训练机器学习算法。这将产生一个训练好的模型,然后是估计它的性能。下一步,可能会考虑对特征集进行调整,包括增加新的特征,删除已证明没什么帮助的特征,或者选择另外一种类型的训练目标(也叫作目标函数),通过上述调整看看能否提高性能。可以反复调整设计决策来提高性能。可能会把导致性能比较差的数据单独提出来,然后尝试是否可以从中发现背后的规律。这可以导致添加新的特征到预测模型中,也可以把数据集分成不同的部分分别考虑,分别建立不同的预测模型。

本书的目的是让你熟悉上述处理过程,以后遇到新问题就可以独立完成上述步骤。当重述问题、提取特征、训练算法、评估算法时,需要熟悉不同算法所要求的输入数据结构。此过程通常包括如下步骤。

(1)提取或组合预测所需的特征。

(2)设定训练目标。

(3)训练模型。

(4)评估模型在测试数据上的性能表现。

注意


在完成第一遍过程后,可以通过选择不同的特征集、不同的目标等手段来提高预测的性能。

机器学习要求不仅仅是熟悉一些工具包。它是开发一个可以实际部署的模型的全部过程,包括对机器学习算法的理解和实际的操作。本书的目标就是在这方面提供帮助。本书假设读者具有大学本科的基础数学知识、理解基本的概率和统计知识,但是本书不预设读者具有机器学习的背景知识。同时本书倾向于给读者直接提供针对广泛问题具有最佳性能的算法,而不需要通览所有机器学习相关的算法或方法。有相当数量的算法很有趣,但是因为各种原因并没有获得广泛使用。例如,这些算法可能扩展性不好,不能对内部的运行机理提供直观的解释,或者很难使用,等等。例如,众所周知随机森林算法(本书将会介绍)在在线机器学习算法竞争中遥遥领先。通常有非常切实的原因导致某些算法被经常使用,本书的目标就是在你通读完本书后对这方面具有充分了解。

参加机器学习算法竞赛可以看作是解决真实机器学习问题的一个仿真。首先机器学习算法竞赛会提供一个简短的描述(例如,宣称一个保险公司想基于现有机动车保险政策更好地预测保费损失率)。作为参赛选手,你要做的第一步就是仔细审视数据集中的数据,确定需要做哪种形式的预测。通过对数据的审视,可以获得直观的感受:这些数据代表什么,它们是如何与预测任务关联起来的。数据通常可以揭示可行的方法。图1-5描述了从通用语言对预测目标的描述,到对数据的整理准备,以作为机器学习算法输入的基本步骤。

首先,通俗的说法“获得更好的结果”需要先转换成可测量可优化的具体目标。作为网站的拥有者,更好的结果可以是提高点击率或更高的销售额(或更高的利润)。下一步就是收集数据,只要其有助于预测:特定用户有多大可能性会点击各种不同类型的链接,或购买在线提供的各种商品。将这些数据表示为特征的矩阵,如图1-5所示。以网站为例,这些特征可能包括:网站访问者之前浏览的其他网页、访问者之前购买的商品。除了用于预测的这些特征,针对此类问题的机器学习算法还需要已知正确的答案用于训练。在图1-5中表示为“目标”。本书涵盖的算法通过用户过去的行为来发现用户的购买模式,当然算法不是单纯地记忆用户过去的行为,毕竟一个用户不可能重复购买他昨天刚刚购买的商品。第3章将详细讨论无记忆行为的预测模型的训练过程。

图1-5 构造一个机器学习问题

通常构造一个机器学习问题可以采用不同的方法。这就导致了问题的构造、模型的选择、模型的训练、模型性能评估这一过程会发生多次迭代,如图1-6所示。

图1-6 从问题形式化到性能评估的迭代过程

与问题随之而来的是定量的训练目标,或者部分任务是数据提取(这些数据叫作目标或标签)。例如,考虑建立一个自动化预测证劵交易的系统。为了实现交易的自动化,第一步可能是预测证劵的价格变化。这些价格是很容易获得的,因此利用历史数据构建一个训练模型来预测未来价格的变化应该是容易的。但是即使这一过程包含了多种算法的选择和实验,未来价格的变化仍然可以用多种方法来计算。这种价格的变化可以是当前价格与10分钟之后的价格的差异、当前价格与10天之后的价格差异,也可以是当前价格与接下来的10分钟内价格的最高值、最低值之间的差异。价格的变化可以用一个2值的变量来表示:“高”或“低”,这依赖于10分钟之后价格是升高还是降低。所有这些选择将会导致不同的预测模型,这个预测模型将用于决定是买入还是卖出证劵,需要实验来确定最佳的选择。

确定哪些特征可用于预测也需要实验尝试。这个过程就是特征提取和特征工程。特征提取就是一个把自由形式的各种数据(如一个文档中的字词、一个网页中的字词)转换成行、列形式的数字的过程。例如,垃圾邮件过滤的问题,输入就是邮件的文本,需要提取的东西包括:文本中大写字母的数量、所有大写的词的数量、在文档中出现词“买”的次数,等等,诸如此类的数值型特征。然后基于这些特征把垃圾邮件从非垃圾邮件中区分出来。

特征工程就是对特征进行整理组合,以达到更富有信息量的过程。建立一个证劵交易系统包括特征提取和特征工程。特征提取将决定哪些特征可以用来预测价格。过往的价格、相关证劵的价格、利率、从最近发布的新闻提取的特征都是现有公开讨论的各种交易系统的输入数据。而且证劵的价格还有一系列的工程化特征,包括:指数平滑异同移动平均线(moving average convergence and divergence,MACD)、相对强弱指数(relative strength index,RSI)等。这些特征都是过往价格的函数,它们的发明者都认为这些特征对于证劵交易是非常有用的。

选好一系列合理的特征后,就像本书描述的那样,需要训练一个预测模型,评价它的性能,然后决定是否部署此模型。为了确保模型的性能足够满足要求,通常需要调整采用的特征。一个确定使用哪些特征的方法就是尝试所有的组合,但是这样时间代价太大。不可避免地,你面临着提高性能的压力,但是又需要迅速获得一个训练好的模型投入使用。本书讨论的算法有一个很好的特征,它们提供对每个特征对最终预测结果的贡献的度量。经过一轮训练,将会对特征打分以标识其重要性。这些信息可以帮助加速特征工程的过程。

注意


数据准备和特征工程估计会占开发一个机器学习模型80%~90%的时间。

模型的训练也是一个过程,每次开始都是先选择作为基线的特征集合。作为一个现代机器学习算法(如本书描述的算法),通常训练100~5000个不同的模型,然后从中精选出一个模型进行部署。产生如此之多的模型的原因是提供不同复杂度的模型,这样可以挑选出一个与问题、数据集最匹配的模型。如果不想模型太简单又不想放弃性能,不想模型太复杂又不想出现过拟合问题,那么需要从不同复杂度的模型中选择一个最合适的。

一个模型合适与否是由此模型在测试数据集上的表现来决定的。这个虽然概念上很简单,却是非常重要的一步。需要留出一部分数据,不用于训练,用于模型的测试。在训练完成之后,用这部分数据集测试算法的性能。本书讨论了留出这部分测试数据的方法。不同的方法各有其优势,主要依赖于训练数据的规模。就像字面上理解那么简单,人们持续地提出各种复杂的方法让测试数据“渗入”训练过程。在处理过程的最后阶段,你将获得一个算法,此算法读取数据,产生准确的预测。在这个过程中,你可能需要检测环境条件的变化,这种变化往往会导致潜在的一些统计特性的变化。

依赖于读者的背景和是否有时间来了解基本原理,读者可以采用不同的方式来阅读本书。图1-7为本书各章之间的依赖关系。

图1-7 各章依赖关系

第2章仔细审视各种数据集。这些数据集用于本书中的问题实例,用以说明算法的使用,以及各种算法之间基于性能和其他特征的比较。面对一个新的机器学习问题的起点就是深入专研数据集,深入理解数据集,了解它们的问题和特质。第2章的部分内容就是展示Python中可以用于数据集探索的一些工具集。可以浏览第2章中的部分例子,不需要阅读全部例子就可以了解整个流程,当在后续章节遇到具体的应用实例时,可以返回到第2章阅读相关的部分。

第3章主要介绍机器学习问题中的基本权衡、贯穿本书的关键概念。一个关键概念就是预测问题的数学描述,也会涉及分类和回归问题的差别。第3章也介绍了如何使用样本外(out-of-sample)数据(测试数据)来评估预测模型的性能。样本外数据是指在模型训练过程中不包括的数据。一个好的机器学习实践者要求对一个实际部署的预测模型的性能表现有相对稳定的预估。这就要求使用训练数据集以外的数据来模拟新的数据。第3章将介绍这么做的原因、实现的方法以及这些方法之间如何取舍。另外一个重要的概念就是系统性能的测量方法,第3章将描述这些方法以及它们之间的取舍。对机器学习比较熟悉的读者可以浏览本章,快速略过代码实例,而不需要仔细阅读代码然后运行代码。

第4章介绍训练惩罚回归模型的核心思想,以及基本概念及算法的来源。第3章引入的一些实例导致了惩罚线性回归方法的产生。第4章展示了解决惩罚线性回归训练问题的核心算法代码,以及线性回归方法的几种扩展。一种扩展是将因素变量(factor variable)编码为实数,这样就可以使用线性回归方法。线性回归方法只能用在预测值是数值的情况下,也就是说需要对预测值进行量化。许多实际的重要问题通常的变量是这样的形式:“单身、已婚或离异”等,这种变量对做预测是很有帮助的。如果要引入此种类型的变量(类别变量,categorical variables)到一个线性回归模型,意味着需要设计一种转换方法将类别变量转换为实数变量,第4章将会介绍这些方法。第4章还介绍叫作基扩展(basis expansion)的方法,此方法从非线性回归中获得非线性函数,有时基扩展用于进一步从线性回归中“挤榨”出一些性能的提升。

第5章将第4章介绍的惩罚回归算法应用于第2章提到的问题中。本章概述实现了惩罚回归算法的Python工具包,并用这些工具包来解决问题。本章的目的是尽可能覆盖广泛的各类问题的变体,这样当读者遇到一个问题时,可以找到一个最接近的问题作为借鉴。除了量化并比较预测的性能,第5章也考查这些算法的其他特征。理解特征的选择、特征的重要性(对最终预测结果的贡献)是很重要的,这种理解能力可以加快面临新问题时的开发进程。

第6章关注集成方法。因为集成方法绝大多数情况下基于二元决策树,第一步就是理解训练和使用二元决策树的原则。集成方法的很多特性都是直接继承于二元决策树。基于上述理解,本章介绍3个主要的集成方法: Bagging、提升(boosting)和随机森林。上述每个算法都介绍了使用的基本原则、核心算法的代码,这样读者就会了解如何使用这些算法。

第7章应用集成方法来解决第2章中的问题,然后对各种算法进行对比分析。对比分析的内容包括:预测的性能、训练所需的时间和性能等。所有的算法会给出特征的重要性打分。对于特定的问题会对比分析特征在不同的算法中对预测结果的重要性。

以笔者的经验,向程序员和计算机科学家教授机器学习,代码实例要优于数学公式。这就是本书所采用的方法:提供一些基础的数学知识、算法框架和代码实例来说明算法的关键点。本书讨论的几乎所有的算法都可以在本书或网站上找到代码,这么做的初衷就是让读者能够尽快运行代码并解决面临的实际问题。

本章介绍了本书要解决的问题以及构建预测模型的处理流程。本书关注两类算法族。限定介绍的算法的数量,可以让我们更透彻地解释这些算法的背景知识以及这些算法的运行机理。本章通过性能对比说明了为什么选择这两类算法。讨论了这两类算法族的特性和各自的优势,并且详细描述了各自适合解决的问题。

本章还介绍了构建一个预测模型的步骤,每个步骤的各种选择的权衡,对输出结果的考虑。非模型训练时使用的数据可以用来评估预测模型。

本书的目的是使机器学习知之甚少的程序员通过本书的学习,能够胜任将机器学习技术引入项目的工作。本书并不关注大量的算法。相反,只关注当前一流的算法,这些算法可以满足对性能、灵活性和清晰的要求。一旦了解它们是怎么工作的,并且拥有了使用它们的一些经验,就会发现它们很容易上手。这些算法可以解决广泛的问题,而不需要先做大量的训练,这也帮助读者理解这些算法高性能的原因。

1.Caruana, Rich, and Alexandru Niculescu‐Mizil. “An Empirical Comparison of Supervised Learning Algorithms.” Proceedings of the 23rd International Conference on Machine Learning. ACM, 2006.

2.Caruana, Rich, Nikos Karampatziakis, and Ainur Yessenalina. “An Empirical Evaluation of Supervised Learning in High Dimensions.” Proceedings of the 25th International Conference on Machine Learning. ACM, 2008.

[1] 宽数据集(wide data set)指每次观测时有大量的测量项,但是观测次数有限的数据。若把数据看成表格形式,则此类数据集列数很多,而行数有限。典型的此类数据集包括神经影像、基因组以及其他生物医学方面的。——译者注。

[2] 高瘦数据集(tall skinny data set)指每次观测时测量项有限,但是进行了大量的观测。若把数据看成表格的形式,则此类数据集列数有限,行数很多。典型的此类数据集包括临床试验数据、社交网络数据等。——译者注。

[3] 自由度:统计学上的自由度(degree of freedom, df)是指当以样本的统计量来估计总体的参数时,样本中独立或能自由变化的自变量的个数称为该统计量的自由度。


新数据集(问题)就像一个包装好的礼物,它充满了承诺和希望。一旦你能解决它,你就收获了喜悦。但是直到你打开它,它都一直保持着神秘。本章就是告诉你怎么“打开”新的数据集,看清楚里面都有什么,知道如何处置这些数据,并且开始思考如何利用这些数据构建相应的模型。

本章有两个目的:一是熟悉这些数据集,这些数据集被用来作为解决各种类型问题的例子,主要是利用第4章和第6章介绍的算法;另一个目的就是展示Python中分析数据的工具包。

本章用一个简单的例子来回顾基础问题的架构、术语、机器学习数据集的特性。此节介绍的术语将在本书后续章节中用到。在了解了通用的术语后,本章将会依次介绍几类不同的函数逼近问题。这些问题阐明了机器学习问题的通常变体,这样就知道如何识别这些变化,并且知道如何处理它们(本节提供代码实例)。

本书介绍的算法通常是从一个充满了数字,可能是特征(变量)的矩阵(或表格)开始的。表2-1展示了一些术语,代表了一个小规模的二维机器学习数据集。此表提供了一个数据集的基本印象,这样对“列代表属性特征,行代表实例”等约定就比较熟悉。这个例子中的问题是预测下一年在线购买书籍所需花费的金额。

表2-1 一个机器学习问题的数据

用户id

属性1

属性2

属性3

标签

001

6.5

Male

12

120美元

004

4.2

Female

17

270美元

007

5.7

Male

3

75美元

008

5.8

Female

8

600美元

数据是按照行和列组织的。每行代表一个实例(或者叫一个例子、观察)。在表2-1中每列指定相应的列名,用来指明在一个机器学习问题中所起的作用。标明为“属性”的列用来预测在买书上所花的费用。在标明为“标签”的列,可以看到去年每个顾客在购书上的花费。

注意


机器学习数据集通常列对应一个属性,行对应一个观察,但也有例外。例如,有些文本挖掘问题的数据矩阵就是另外的形式:列对应一个观察,行对应一个属性。

在表2-1中,一行代表一个顾客,此行的数据都与此顾客相关。第一列叫作UserID(用户ID),是每行惟一的识别符。实际问题中可能有惟一识别符也可能没有。例如,网站通常为网站的访客建立一个相应的用户ID,并且在此用户访问网站期间,用户的所有行为都与此用户ID绑定。如果用户在此网站上没有注册,则用户的每次访问都将获得一个不同的用户ID。通常每个观察会被分配一个ID,这个就是预测的目标对象。第2~第4列称为属性,以代替更具体的名字,如身高、性别等。这主要是为了突出他们在预测过程中起到的作用。属性就是在具体实例中用来预测的数据。

标签就是需要预测的数据。在这个例子中,用户ID就是一个简单的数字,属性1是身高,属性2是性别,属性3是此人去年阅读的书籍的数量。标签列上的数字代表每人去年在线购书的花费。那么不同类型的数据分别代表什么样的角色呢?一个机器学习算法是如何利用用户ID、属性和标签列的呢?最简短的回答就是:忽略用户ID。使用属性来预测标签。

惟一的用户ID只是起到记账的目的,在某些情况下可以根据用户ID检索到用户的其他数据。通常机器学习算法并不直接使用用户ID。属性是挑选出来用于预测的。标签是观察得到的结果,机器学习基于此来构建预测模型。

预测通常不用用户ID信息,因为它太特殊了。它一般只属于一个实例。一个机器学习的技巧就是构建的模型要有泛化能力(即可以解决新的实例,而不仅仅是把过去的例子都记下来)。为了达到这个目的,算法必须能够关注到不止一行的数据。一个可能的例外是,如果用户ID是数字的,并且是按照用户登录的时间依次进行分配的。这样就指示了用户的登录日期,那么如果用户ID比较接近,就证明了用户是在比较接近的时间上登录的,依此为条件可以把用户划分为不同的组。

构建预测模型的过程叫作训练。具体的方法依赖于算法,后续章节会详述,但基本上采用迭代的方式。算法假定属性和标签之间存在可预测的关系,观察出错的情况,做出修正,然后重复此过程直到获得一个相对满意的模型。技术细节后续会介绍,这里只是介绍基本思想。

名字的含义:

属性和标签有不同的名字。机器学习的初学者往往被这些名词迷惑,不同的作者可能会采用不同的名字,甚至一篇文章的段落与段落之间都会采用不同的名字。

属性(用来进行预测的变量)也被称为:

  • 预测因子
  • 特征
  • 独立变量
  • 输入

标签通常也被称为:

  • 结果
  • 目标
  • 依赖变量
  • 响应

表2-1中的属性可以分成2类:数值变量、类别(或因素、因子)变量。属性1(身高)是一个数值变量,也是最常见的属性类型。属性2是性别,可以是男性或女性。这种类型的属性叫作类别变量或因素变量。类别变量的一个特点就是不同值之间没有顺序关系。男性<女性是没有意义的(噢,忘掉几个世纪的争吵吧)。类别变量可以是2值的,如男性和女性,也可以是多值的,如美国的州(AL,AK,AR,…WY)。关于属性还有其他差别(如整数与浮点数),但这些差别对机器学习算法的影响并不像数值变量和类别变量那么大。主要原因是很多机器学习算法只能处理数值变量,不能处理类别变量或因素变量(factor variable)。例如,惩罚回归算法只能处理数值变量,SVM、核方法、K最近邻也是同样。第4章将介绍将类别变量转换为数值变量的方法。这些变量的特性将会影响算法的选择以及开发一个预测模型的努力的方向,因此这也是当面临一个新问题时,需要考虑的因素之一。

这种二分法同样适用于标签。表2-1所示的标签是数值的:去年在线购书所花费的金额。然而在其他问题中,标签就可能是类别的。例如,如果表2-1的任务是预测哪些人下一年的花费超过200美元,那么问题就变了,解决问题的方法也随之变了。预测哪些顾客的花费会超过200美元的新问题会产生新的标签。这些标签会在两个值中选一个。表2-1中的标签与新的逻辑命题“花费>200美元”下的新标签之间的关系如表2-2所示。表2-2所示的新标签取值为:真或假。

表2-2 数值标签与类别标签

表2-1标签

>200美元?

120美元

False

270美元

True

75美元

False

600美元

True

当标签是数值的,就叫作回归问题。当标签是类别的,就叫作分类问题。如果分类结果只取2个值,就叫作二元分类问题。如果取多个值,就是多类别分类问题。

在很多情况下,问题的类型是由设计者选择的。刚刚的例子就是如何把一个回归问题转换为二元分类问题,只需要对标签做简单的变换。这实际上是面临一个问题时所做的一种权衡。例如,分类目标可以更好地支持2种行为选择的决策问题。

分类问题也可能比回归问题简单。例如考虑2个地形图的复杂度差异,一个地形图只有一个等高线(如30.5米的等高线),而另一个地形图每隔3.05米就有一个等高线。只有一个等高线的地形图将地图分成高于30.5米的区域和低于30.5米的区域,因此相比另一个地形图含有更少的信息。一个分类器就相当于只算出一个等高线,而不再考虑与这条分界线的远近距离之类的问题,而回归的方法就相当于要绘制一个完整的地形图。

初始审视数据集时,还需要考查数据集的其他特性。下面是一个检查清单,是为了熟悉数据集需要考察的一系列事情,这也有利于明确后续预测模型的开发流程。这些都是很简单的事情,但是直接影响后续步骤,通过这个过程可以了解此数据集的特性。

需要检查的事项:

第一个要确认的就是数据的规模。将数据读入二维数组,则外围数组的维度就是行数,内部数组的维度就是列数。下节将会展示针对某一数据集应用此方法获取数据的规模。

下一步就要确定每行有多少缺失的值。这么一行行处理数据的原因是处理缺失数据最简单的方法就是直接抛弃有缺失数据的行(如至少少了一个值的行)。在很多情况下,这样做会导致结果偏差,但在抛弃的行不多的情况下,并不会产生实质性的差异。通过计算具有缺失数据的行数(加上具体缺失的项数),就可以知道如果采用最简单的方法,则实际上抛弃了多少数据。

如果你有大量的数据,例如正在收集互联网上的数据,那么丢失的数据相对于你获得的数据总量应该是微不足道的。但如果处理的是生物数据,这些数据都比较昂贵,而且有多种属性,这时抛弃这些数据的代价就太大了。在这种情况下,需要找到方法把丢失的值填上,或者使用能够处理丢失数据的算法。把丢失的数据填上的方法一般叫作遗失值插补(imputation)。遗失值插补的最简单方法就是用每行所有此项的值的平均值来代替遗失的值。更复杂的方法要用到第4章和第6章介绍的预测模型。用预测模型时,将含有遗失值的那列属性当作标签,当然在进行这步之前要确保将初始问题的标签移除。

接下来的小节将从头到尾介绍分析数据集的完整过程,并引入刻画数据集的一些方法,这些都将帮助你确定如何解决建模的问题。

此小节将介绍在分类问题上首先需要做的工作。首先是简单的测量:数据的规模、数据类型、缺失的数据等。接着是数据的统计特性、属性之间的关系、属性与标签之间的关系。本节的数据集来自UC Irvine 数据仓库(见参考文献1)。数据来源于实验:测试声纳是否可以用于检测在港口军事行动后遗留下来的未爆炸的水雷。声纳信号又叫作啁啾信号(chirped signal),即信号在一个脉冲期间频率会增加或降低。此数据集的测量值代表声纳接收器在不同地点接收到的返回信号,其中在大约一半的例子中,返回的声纳信号反映的是岩石的形状,而另一半是金属圆筒的形状(水雷)。下文就用“岩石vs.水雷”来代表这个数据集。

对新数据集所做的第一件事就是确定数据集的规模。代码清单2-1为获取“岩石vs. 水雷”数据集规模的代码。在本章的后续内容,将多次遇到此数据集,主要用来作为介绍算法的例子,此数据集来源于UC Irvine数据仓库。在此例中,确定数据集的行数、列数的代码十分简单。数据集文件是由逗号分割的,一次实验数据占据文本的一行。文件处理十分简单:读入一行,对数据按逗号进行分割,将结果列表存入输出列表即可。

代码清单2-1 确定新数据集规模-rockVmineSummaries.py(输出:outputRocksVMinesSummaries.txt)

__author__ = 'mike_bowles'
import urllib2
import sys

#read data from uci data repository
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

data = urllib2.urlopen(target_url)

#arrange data into list for labels and list of lists for attributes
xList = []
labels = []
for line in data:
    #split on comma
    row = line.strip().split(",")
    xList.append(row)

sys.stdout.write("Number of Rows of Data = " + str(len(xList)) + '\n')
sys.stdout.write("Number of Columns of Data = " + str(len(xList[1])))


Output:
Number of Rows of Data = 208
Number of Columns of Data = 61

如代码输出所示,此数据集为208行,61列(每行61个字段)。这有什么影响吗?数据集的规模(行数、列数)至少在以下几个方面会影响你对数据的处理。首先,根据数据的规模可以大致判断训练所需的时间。对于像“岩石vs.水雷”这种小数据集,训练时间会少于1分钟,这有利于在训练过程中不断调整和迭代。如果数据集规模增加到1 000×1 000,惩罚线性回归训练时间将不到一分钟,而集成方法训练时间需要几分钟。如果数据集的行、列增加到万级规模,则惩罚线性回归的训练时间将达到3~4小时,而集成方法则长达12~24小时。更长的训练时间将会影响你的开发进度,因为通常需要迭代几次来对算法进行调整或优化。

另外一个重要的观察是如果数据集的列数远远大于行数,那么采用惩罚线性回归的方法则有很大的可能获得最佳的预测,反之亦然。在第3章有实际的例子,这会加深对这个结论的理解。

根据应做事项清单,下一步要做的就是确定哪些列是数值型的,哪些列是类别型的。代码清单2-2为针对“岩石vs.水雷”数据集完成上述分析的代码。代码依次检查每一列,确定数值型(整型或浮点型)的条目数量、非空字符串的条目数量、内容为空的条目数量。分析的结果是:前60列都是数值型,最后一列都是字符串。这些字符串值是标签。通常类别型变量用字符串表示,如此例所示。在某些情况下,二值类别变量可以表示成0和1。

代码清单2-2 确定每个属性的特征-rockVmineContents.py(输出:outputRocksVMinesContents.txt)

__author__ = 'mike_bowles'
import urllib2
import sys

#read data from uci data repository
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

data = urllib2.urlopen(target_url)


#arrange data into list for labels and list of lists for attributes
xList = []
labels = []
for line in data:
    #split on comma
    row = line.strip().split(",")
    xList.append(row)
nrow = len(xList)
ncol = len(xList[1])

type = [0]*3
colCounts = []

for col in range(ncol):
    for row in xList:
        try:
            a = float(row[col])
            if isinstance(a, float):
                type[0] += 1
        except ValueError:
            if len(row[col]) > 0:
                type[1] += 1
            else:
                type[2] += 1

    colCounts.append(type)
    type = [0]*3

sys.stdout.write("Col#" + '\t' + "Number" + '\t' +
                 "Strings" + '\t ' + "Other\n")
iCol = 0
for types in colCounts:
    sys.stdout.write(str(iCol) + '\t\t' + str(types[0]) + '\t\t' +
                     str(types[1]) + '\t\t' + str(types[2]) + "\n")
    iCol += 1

Output:
Col# Number Strings Other
  0    208     0      0
  1    208     0      0
  2    208     0      0
  3    208     0      0
  4    208     0      0
  5    208     0      0
  6    208     0      0
  7    208     0      0
  8    208     0      0
  9    208     0      0
 10    208     0      0
 11    208     0      0
  .      .     .      .
  .      .     .      .
  .      .     .      .
 54    208     0      0
 55    208     0      0
 56    208     0      0
 57    208     0      0
 58    208     0      0
 59    208     0      0
 60    0     208      0

确定哪些属性是类别型,哪些是数值型之后,下一步就是获得数值型属性的描述性统计信息和类别型属性具体类别的数量分布。代码清单2-3为这两个处理过程的实例代码。

代码清单2-3 数值型和类别型属性的统计信息-rVMSummaryStats.py(输出:outputSummaryStats.txt)

__author__ = 'mike_bowles'
import urllib2
import sys
import numpy as np

#read data from uci data repository
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")
data = urllib2.urlopen(target_url)


#arrange data into list for labels and list of lists for attributes
xList = []
labels = []

for line in data:
    #split on comma
    row = line.strip().split(",")
    xList.append(row)
nrow = len(xList)
ncol = len(xList[1])

type = [0]*3
colCounts = []

#generate summary statistics for column 3 (e.g.)
col = 3
colData = []
for row in xList:
    colData.append(float(row[col]))

colArray = np.array(colData)
colMean = np.mean(colArray)
colsd = np.std(colArray)
sys.stdout.write("Mean = " + '\t' + str(colMean) + '\t\t' +
            "Standard Deviation = " + '\t ' + str(colsd) + "\n")

#calculate quantile boundaries
ntiles = 4

percentBdry = []

for i in range(ntiles+1):
    percentBdry.append(np.percentile(colArray, i*(100)/ntiles))

sys.stdout.write("\nBoundaries for 4 Equal Percentiles \n")
print(percentBdry)
sys.stdout.write(" \n")

#run again with 10 equal intervals
ntiles = 10

percentBdry = []

for i in range(ntiles+1):
    percentBdry.append(np.percentile(colArray, i*(100)/ntiles))

sys.stdout.write("Boundaries for 10 Equal Percentiles \n")
print(percentBdry)
sys.stdout.write(" \n")


#The last column contains categorical variables

col = 60
colData = []
for row in xList:
    colData.append(row[col])

unique = set(colData)
sys.stdout.write("Unique Label Values \n")
print(unique)

#count up the number of elements having each value

catDict = dict(zip(list(unique),range(len(unique))))

catCount = [0]*2

for elt in colData:
    catCount[catDict[elt]] += 1
sys.stdout.write("\nCounts for Each Value of Categorical Label \n")
print(list(unique))
print(catCount)

Output:
Mean =   0.053892307       Standard Deviation =       0.046415983

Boundaries for 4 Equal Percentiles
 [0.0057999999999999996, 0.024375000000000001, 0.044049999999999999,
 0.064500000000000002, 0.4264]

Boundaries for 10 Equal Percentiles
[0.00579999999999, 0.0141, 0.022740000000, 0.0278699999999,
0.0362200000000, 0.0440499999999, 0.050719999999, 0.0599599999999,
0.0779400000000, 0.10836, 0.4264]
Unique Label Values
set(['R', 'M'])

Counts for Each Value of Categorical Label
['R', 'M']
[97, 111]

代码第一部分读取数值型数据的某一列,然后产生它的统计信息。第一步计算此属性的均值和方差。了解这些统计信息可以加强在建立预测模型时的直观感受。

第二部分代码主要是为了找到异常值。基本过程如下:假设在下面数值列表[0.1,0.15,0.2,0.25,0.3,0.35,0.4,4]中确定是否有异常值,显然最后一个数“4”是异常值。

发现这种异常值的一种方法是:将一组数字按照百分位数进行划分。例如,第25百分位数是含有最小的25%的数,第50百分位数是含有最小的50%的数。把这种分组可视化最简单的方法是假想把这些数据按顺序排列。上述的例子已经按顺序排好,这样就可以很容易地看到百分位数的边界。一些经常用到的百分位数通常被赋予特殊的名字。将数组按照1/4、1/5、1/10划分的百分位数通常分别叫作四分位数(quartiles,按顺序排列的一组数据被划分为4个相等部分的分割点的数值)、五分位数(quintiles)和十分位数(deciles)。

上述的数组很容易定义出四分位数,因为此数组已按顺序排好,共有8个元素。第一个四分位数含有0.1和0.15,以下的以此类推。可以注意到这些四分位数的跨度。第一个是0.05(0.15~0.1)。第二个四分位数的跨度也大致相同。然而最后一个四分位数的跨度却是3.6,这个是其他四分位数跨度的几十倍。

代码清单2-3中四分位数边界的计算过程与之类似。程序计算四分位数,然后显示最后一个四分位数的跨度要比其他的宽很多。为了更加准确,又计算了十分位数,同样证明了最后一个十分位数的跨度要远远大于其他的十分位数。有些情况下的最后一个分位数变宽是正常的,因为通常数据的分布在尾部会变稀疏。

更具体地研究异常点(异常值)的一个方法就是画出数据的分布图,然后与可能的分布进行比较,判断相关的数据是否匹配。代码清单2-4展示如何使用Python的probplot函数来帮助确认数据中是否含有异常点。分布图展示了数据的百分位边界与高斯分布的同样百分位的边界对比。如果此数据服从高斯分布,则画出来的点应该是一条直线。来自“岩石vs.水雷”数据集的第4列(第4属性)的一些点远离这条直线,如图2-1所示。这说明此数据集尾部的数据要多于高斯分布尾部的数据。

代码清单2-4 “岩石vs. 水雷”数据集的第4列的分位数图-qqplotAttribute.py

__author__ = 'mike bowles'
import numpy as np
import pylab
import scipy.stats as stats
import urllib2
import sys

target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

data = urllib2.urlopen(target_url)


#arrange data into list for labels and list of lists for attributes
xList = []
labels = []

for line in data:
    #split on comma
    row = line.strip().split(",")
    xList.append(row)
nrow = len(xList)
ncol = len(xList[1])
type = [0]*3
colCounts = []

#generate summary statistics for column 3 (e.g.)
col = 3
colData = []
for row in xList:
    colData.append(float(row[col]))


stats.probplot(colData, dist="norm", plot=pylab)
pylab.show()

图2-1 “岩石vs.水雷”数据集第4属性的分位数图

那么如何利用这些信息?异常点在建模或预测期间都会带来麻烦。基于此数据集训练完一个模型后,可以查看此模型预测错误的情况,然后确认此错误是否与这些异常点有关。如果确实是这样的话,可以采取步骤进行校正。例如,可以复制这些预测模型表现不好的例子,以加强这些例子在数据集中的比重。也可以把这些不好的例子分离出来,然后单独训练。如果认为预测模型在真正部署时不会遇到此类异常数据,则也可以把这些例子排除出数据集。一个可行办法是在对数据集进行探究阶段,先产生四分位数边界,然后看看潜在的异常点的规模对后续建模及预测可能的影响。这样在分析错误时,可以通过分位数图(quantile-quantile,Q-Q)确定哪些数据可以称为异常点。

上述的分析过程只适用于数值属性。那么类别属性呢?你可能想知道一共可以分为几类、每类数据的数目。想获得这些信息主要是基于以下原因:性别属性有两个值(男、女),但是如果属性是美国的州,则有50个可能的值。随着属性数目的增加,处理的复杂度也在增加。绝大多数二元决策树算法(集成方法的基础)对于其可以处理的类别数是有限制的。由Breiman和Cutler(此算法的发明人)写的流行的随机森林算法包支持32个类别。如果一个属性超过32个类别,则需要合并。

有时在训练过程中会随机抽取数据集的一个子集,然后在此子集上训练一系列的模型。例如,如果类别属性就是美国的州,其中爱达荷州只出现了两次。一个随机抽取的训练用数据子集中很可能不含有爱达荷州的样本。你需要在这些问题发生前就预见到可能会出现这样的情况,然后再着手进行处理。以两个爱达荷州的样本为例,可以把它与蒙大纳州或怀俄明州合并,也复制这两个样本(增加其所占的比例),或者控制随机取样保证抽取到含有爱达荷州的样本,这个过程叫作分层抽样(stratified sampling)。

Python Pandas工具包可以帮助自动化数据统计分析的过程,已经被证实在数据预处理阶段特别有用。Pandas工具包可以将数据读入一种特定的数据结构,叫作数据框(data frame)。数据框是依据CRAN-R数据结构建模的。

注意


Pandas工具包的安装可能会有困难,主要原因是它有一系列的依赖,每个依赖必须安装正确的版本,而且相互之间要匹配,或者诸如此类的问题。绕过此类障碍的一个简单的方法就是直接安装Anaconda Python Distribution分发包,此分发包可以直接从Continuum Analytics(http://continuum.io)处下载。安装过程十分简单,只要按指令依次进行就可以安装好数据分析、机器学习所需的大量软件包。

你可以把数据框当成一个表格或者类似矩阵的数据结构,如表2-1所示。数据框定义行代表一个实例(一次实验、一个例子、一次测量等),列代表一个特定的属性。此结构像矩阵,但又不是矩阵,因为每列的元素很可能是不同类型的。形式上矩阵里的所有元素都是来自一个域的(如实数、二进制数、复数等)。但对于统计学来说,矩阵的限制太严格了,因为统计方面的一个样本往往是多个不同类型的值的混合。

表2-1样例中的第1个属性列是实数,第两个属性列是类别变量(属性),第3个属性列是整数。在一个列内,所有元素的取值都是同一类型,但是列与列之间是不同的。通过数据框,可以通过索引(index)的方式访问具体某个元素,类似Python中访问一个Numpy数组或二维数组中的元素(element)。类似地,采用索引切片(index slicing)可以访问整行或整列,而且在Pandas数据框中,可以通过名字来访问行或列。这对于小规模或中等规律的数据是十分方便的(搜索“Pandas introduction”会找到关于使用Pandas的入门指导的链接)。

如何从UC Irvine 数据仓库网站读取“岩石vs.水雷”数据的CSV文件如代码清单2-5所示。这里的输出只是完整输出中的一部分。自行运行代码就可以获得完整输出。

代码清单2-5 用Python Pandas 读入数据、分析数据- pandasReadSummarizer.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plot
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

#read rocks versus mines data into pandas data frame
rocksVMines = pd.read_csv(target_url,header=None, prefix="V")

#print head and tail of data frame
print(rocksVMines.head())
print(rocksVMines.tail())

#print summary of data frame
summary = rocksVMines.describe()
print(summary)

Output (truncated):

       V0    V1     V2   ...    V57    V58    V59 V60
0 0.0200 0.0371 0.0428   ... 0.0084 0.0090 0.0032  R
1 0.0453 0.0523 0.0843   ... 0.0049 0.0052 0.0044  R
2 0.0262 0.0582 0.1099   ... 0.0164 0.0095 0.0078  R
3 0.0100 0.0171 0.0623   ... 0.0044 0.0040 0.0117  R
4 0.0762 0.0666 0.0481   ... 0.0048 0.0107 0.0094  R

[5 rows x 61 columns]

        V0     V1     V2   ...    V57    V58    V59 V60
203 0.0187 0.0346 0.0168   ... 0.0115 0.0193 0.0157  M
204 0.0323 0.0101 0.0298   ... 0.0032 0.0062 0.0067  M
205 0.0522 0.0437 0.0180   ... 0.0138 0.0077 0.0031  M
206 0.0303 0.0353 0.0490   ... 0.0079 0.0036 0.0048  M
207 0.0260 0.0363 0.0136   ... 0.0036 0.0061 0.0115  M

[5 rows x 61 columns]


              V0         V1   ...        V58        V59
count 208.000000 208.000000   ... 208.000000 208.000000
mean    0.029164   0.038437   ...   0.007941   0.006507
std     0.022991   0.032960   ...   0.006181   0.005031
min     0.001500   0.000600   ...   0.000100   0.000600
25%     0.013350   0.016450   ...   0.003675   0.003100
50%     0.022800   0.030800   ...   0.006400   0.005300
75%     0.035550   0.047950   ...   0.010325   0.008525
max     0.137100   0.233900   ...   0.036400   0.043900

读入数据后,程序第一部分首先打印头数据和尾数据。注意到所有的头数据都有R标签,所有的尾数据都有M标签。对于这个数据集,第一部分是R标签的(岩石),第二部分是M标签的(水雷)。在分析数据时首先要注意到此类信息。在后续章节中会看到,确定模型的优劣有时需要对数据进行取样。那么取样就需要考虑到数据的存储结构。最后的代码打印输出实数属性列的统计信息。

Pandas可以自动计算出均值、方差、分位数。由于describe函数输出的总结(统计信息)本身就是一个数据框,因此可以自动化属性值的筛选过程以发现异常点。可以比较不同分位数之间的差异。对于同一属性列,如果存在某一个差异严重异于其他差异,则说明存在异常点。这就值得进一步探究这些异常点牵扯到多少行数据,这些异常点涉及的数据很可能是少量的,这些都需要仔细分析。

可视化可以提供对数据的直观感受,这个有时是很难通过表格的形式把握到的。此节将介绍很有用的可视化方法。分类问题和回归问题的可视化会有所不同。在有鲍鱼和红酒数据集的章节中看到回归问题的可视化方法。

对于具有多个属性问题的一种可视化方法叫作平行坐标图(parallel coordinates plot)。图2-2为平行坐标图的基本样式。图右边的向量([1 3 2 4])代表数据集中某一行属性的值。这个向量的平行坐标图如图2-2中的折线所示。这条折线是根据属性的索引值和属性值画出来的。整个数据集的平行坐标图对于数据集中的每一行属性都有对应的一条折线。基于标签对折线标示不同的颜色,更有利于观测到属性值与标签之间的关系。(在Wikipedia输入“parallel coordinates”会检索出更多的例子。)

图2-2 平行坐标图

代码清单2-6展示了如何获得“岩石vs.水雷”数据集的平行坐标图。图2-3为结果。折线根据对应的标签赋予不同的颜色:R(岩石)是蓝色,M(水雷)是红色。有时候图画出来后标签(类别)之间可以很明显地区分出来。著名的“鸢尾花数据集”类别之间就可以很明显地区分出来,这就是机器学习算法进行分类应该达到的效果。对应“岩石vs.水雷”数据集则看不到明显的区分。但是有些区域蓝色和红色的折线是分开的。沿着图的底部,蓝色的线要突出一点儿,在属性索引30~40之间,蓝色的线多少要比红色的线高一些。[1]这些观察将有助于解释和确认某些预测的结果。

代码清单2-6 实数值属性的可视化:平行坐标图-linePlots.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plot
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

#read rocks versus mines data into pandas data frame
rocksVMines = pd.read_csv(target_url,header=None, prefix="V")

for i in range(208):
    #assign color based on "M" or "R" labels
    if rocksVMines.iat[i,60] == "M":
        pcolor = "red"
    else:
        pcolor = "blue"

    #plot rows of data as if they were series data
    dataRow = rocksVMines.iloc[i,0:60]
    dataRow.plot(color=pcolor)

plot.xlabel("Attribute Index")
plot.ylabel(("Attribute Values"))
plot.show()

图2-3 “岩石vs.水雷”数据集属性的平行坐标图

另外一个需要了解的问题就是属性之间的关系。获得这种成对关系的快速方法就是绘制属性与标签的交会图(cross-plots)。代码清单2-7展示了如何产生代表性属性对的交会图。这些交会图(又叫作散点图,scatter plots)展示了这些属性对之间关系的密切程度。

代码清单2-7 属性对的交会图-corrPlot.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plot
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

#read rocks versus mines data into pandas data frame
rocksVMines = pd.read_csv(target_url,header=None, prefix="V")

#calculate correlations between real-valued attributes
dataRow2 = rocksVMines.iloc[0:208,1]
dataRow3 = rocksVMines.iloc[0:208,2]

plot.scatter(dataRow2, dataRow3)


plot.xlabel("2nd Attribute")
plot.ylabel(("3rd Attribute"))
plot.show()

dataRow21 = rocksVMines.iloc[20,0:60]

plot.scatter(dataRow2, dataRow21)


plot.xlabel("2nd Attribute")
plot.ylabel(("21st Attribute"))
plot.show()

图2-4和图2-5为来自“岩石vs.水雷”数据集的两对属性的散点图。“岩石vs.水雷”数据集的属性是声纳返回的取样值。声纳返回的信号又叫啁啾信号 ,因为它是一个脉冲信号,开始在低频,然后上升到高频。这个数据集的属性就是声波由岩石或水雷反射回来的时间上的取样。这些返回的声学信号携带的时间与频率的关系与发出的信号是一样的。数据集的60个属性是返回的信号在60个不同时间点的取样(因此是60个不同的频率)。你可能会估计相邻的属性会比隔一个的属性更相关,因为在相邻时间上的取样在频率上的差别应该不大。

图2-4 “岩石vs.水雷”数据集第2个属性与第3个属性的交会图

图2-5 “岩石vs.水雷”数据集第2个属性与第21个属性的交会图

这种直观感受在图2-4和图2-5中得到了证实。图2-4中的点要比图2-5中的点更集中于一条直线。如果想进一步了解数值相关和散点图的形状两者之间的关系,请在wikipedia搜索“correlation(相关)”相关页面。基本上,如果散点图上的点沿着一条“瘦”直线排列,则说明这两个变量强相关;如果这些点形成一个球型,则说明这些点不相关。

应用同样原则,可以画出任何一个属性与最终目标(标签)的散点图,研究两者之间的相关性。若对应目标是实数(回归问题),则画出的散点图会与图2-4和图2-5十分相似。“岩石vs.水雷”数据集是一个分类问题,目标是二值的,但是遵循同样的步骤。

代码清单2-8展示如何画出标签和第35个属性的散点图。为什么选用第35个属性作为展示属性与标签关系的例子,灵感来自于平行坐标图2-3。这个平行坐标图显示岩石数据与水雷数据在属性索引值35左右有所分离。则标签与索引值35附近的属性的关系也应该显示这种分离,正如图2-6和图2-7所示。

代码清单2-8 分类问题标签和实数值属性之间的相关性-targetCorr.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plot
from random import uniform
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

#read rocks versus mines data into pandas data frame
rocksVMines = pd.read_csv(target_url,header=None, prefix="V")

#change the targets to numeric values
target = []
for i in range(208):
    #assign 0 or 1 target value based on "M" or "R" labels
    if rocksVMines.iat[i,60] == "M":
        target.append(1.0)
    else:
        target.append(0.0)

#plot 35th attribute
dataRow = rocksVMines.iloc[0:208,35]
plot.scatter(dataRow, target)

plot.xlabel("Attribute Value")
plot.ylabel("Target Value")
plot.show()

#
#To improve the visualization, this version dithers the points a little
# and makes them somewhat transparent
target = []
for i in range(208):

#assign 0 or 1 target value based on "M" or "R" labels
    # and add some dither

    if rocksVMines.iat[i,60] == "M":
        target.append(1.0 + uniform(-0.1, 0.1))
    else:
        target.append(0.0 + uniform(-0.1, 0.1))

    #plot 35th attribute with semi-opaque points
dataRow = rocksVMines.iloc[0:208,35]
plot.scatter(dataRow, target, alpha=0.5, s=120)

plot.xlabel("Attribute Value")
plot.ylabel("Target Value")
plot.show()

如果把M用1代表,R用0代表,就会得到如图2-6所示的散点图。在图2-6中可以看到一个交会图常见的问题。当其中一个变量只取有限的几个值时,很多点会重叠在一起。如果这种点很多,则只能看到很粗的一条线,分辨不出这些点是如何沿线分布的。

图2-6 标签-属性交会图

代码清单2-8产生了第二图,通过2个小技巧克服了上述的问题。每个点都加上一个小的随机数,产生了少量的离散值(这里是对标签值进行了处理)。标签值最初是0或1。在代码中可以看到,标签值加上了一个在−0.1和0.1之间均匀分布的随机数,这样就把这些点分散开,但是又不至于把这2条线混淆。此外,这些点绘制时取alpha=0.5,这样这些点就是半透明的。那么在散点图中若多个点落在一个位置就会形成一个更黑的区域,这时需要对数据做一些微调使你能看到你想看到的。

这两种方法的效果如图2-7所示。可以注意到第35个属性在左上方的点更加集中一些,然而在下面的数据从右到左分布得更加均匀些。上方的数据对应水雷的数据。下面的数据对应岩石的数据。由图观察可知,可以因此建立一个分类器,判断第35个属性是否大于或小于0.5。如果大于0.5,就判断为岩石,如果小于0.5,就判断为水雷。在第35个属性值小于0.5的实例中,水雷的分布要更密集,而且在属性值小于0.5的实例中,岩石的分布要稀疏得多。这样就可以获得一个比随机猜测好些的结果。

图2-7 经过扰动和半透明处理的标签-属性图

注意


在第5章和第7章将会看到更系统的构建分类器的方法。它们会用到所有的属性,而不仅仅是一、二个属性。当看到它们是如何做决策时,可以回头看看本章的例子就会理解为什么它们的选择是明智的。

两个属性(或一个属性、一个标签)的相关程度可以由皮尔逊相关系数(Pearson’s correlation coefficient)来量化。给定2个等长的向量uv(如公式2-1和公式2-2所示)。首先u的所有元素都减去u的均值(见公式2-3)。对v也做同样的事情。

公式2-1 向量u的元素

公式2-2 向量u的均值

公式2-3 向量u中每个元素都减去均值

以向量Δu相同的定义方式,对应第二个向量v,定义向量Δv。

uv之间的皮尔森相关系数如公式2-4所示。

公式2-4 皮尔森相关系数定义

代码清单2-9展示了用该函数计算图2-3和图2-5中属性对的相关系数。相关系数和图中展示的结果一致,索引值距离比较近的属性间相关系数也比较高。

代码清单2-9 对属性2和属性3、属性2和属性21 分别计算各自的皮尔森相关系数-corrCalc.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
from math import sqrt
import sys
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

#read rocks versus mines data into pandas data frame
rocksVMines = pd.read_csv(target_url,header=None, prefix="V")

#calculate correlations between real-valued attributes
dataRow2 = rocksVMines.iloc[0:208,1]
dataRow3 = rocksVMines.iloc[0:208,2]
dataRow21 = rocksVMines.iloc[0:208,20]

mean2 = 0.0; mean3 = 0.0; mean21 = 0.0
numElt = len(dataRow2)
for i in range(numElt):
    mean2 += dataRow2[i]/numElt
    mean3 += dataRow3[i]/numElt
    mean21 += dataRow21[i]/numElt

var2 = 0.0; var3 = 0.0; var21 = 0.0
for i in range(numElt):
    var2 += (dataRow2[i] - mean2) * (dataRow2[i] - mean2)/numElt
    var3 += (dataRow3[i] - mean3) * (dataRow3[i] - mean3)/numElt
    var21 += (dataRow21[i] - mean21) * (dataRow21[i] - mean21)/numElt

corr23 = 0.0; corr221 = 0.0
for i in range(numElt):

    corr23 += (dataRow2[i] - mean2) * \
              (dataRow3[i] - mean3) / (sqrt(var2*var3) * numElt)
    corr221 += (dataRow2[i] - mean2) * \
               (dataRow21[i] - mean21) / (sqrt(var2*var21) * numElt)

sys.stdout.write("Correlation between attribute 2 and 3 \n")
print(corr23)
sys.stdout.write(" \n")
sys.stdout.write("Correlation between attribute 2 and 21 \n")
print(corr221)
sys.stdout.write(" \n")


Output:
Correlation between attribute 2 and 3
0.770938121191

Correlation between attribute 2 and 21
0.466548080789

对于计算少量的相关性,将相关性结果打印输出或者画成散点图都是可以的。但是对于大量的数据,就很难用这种方法整体把握相关性。如果问题有100以上的属性,则很难把散点图压缩到一页。

获得大量属性之间相关性的一种方法就是计算出每对属性的皮尔森相关系数后,将相关系数构成一个矩阵,矩阵的第ij-th个元素对应第i个属性与第j个属性的相关系数,然后把这些矩阵元素画到热图上。代码清单2-10为热图的代码实现。图2-8就是这种热图。沿着斜对角线的浅色区域证明索引值相近的属性相关性较高。正如上文提到的,这与数据产生的方式有关。索引相近说明是在很短的时间间隔内取样的,因此声纳信号的频率也接近,频率相近说明目标(标签)也类似。

代码清单2-10 属性相关系数可视化-sampleCorrHeatMap.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plot
target_url = ("https://archive.ics.uci.edu/ml/machine-learning-"
"databases/undocumented/connectionist-bench/sonar/sonar.all-data")

#read rocks versus mines data into pandas data frame
rocksVMines = pd.read_csv(target_url,header=None, prefix="V")

#calculate correlations between real-valued attributes
corMat = DataFrame(rocksVMines.corr())

#visualize correlations using heatmap
plot.pcolor(corMat)
plot.show()

图2-8 展示属性对相关性的热图

属性之间如果完全相关(相关系数=1)意味着数据可能有错误,如同样的数据录入两次。多个属性间的相关性很高(相关系数>0.7),即多重共线性(multicollinearity),往往会导致预测结果不稳定。属性与标签的相关性则不同,如果属性和标签相关,则通常意味着两者之间具有可预测的关系。

在探究“岩石vs.水雷”数据集的过程中,本节介绍了一系列的工具,以加深对数据集的理解和直观感受。本节已深入细节,如这些工具的来源、用法等。下节将用同样的工具来分析后续机器学习算法用到的其他数据集。因为这些工具都已介绍过,下一节将只介绍因为问题的不同,而对工具所做的改变。

探测未爆炸的水雷数据集的工具同样可以用于回归问题。在给定物理测量值的情况下,预测鲍鱼的年龄就是此类问题的一个实例。鲍鱼的属性中包括因素属性,下面将说明属性中含有因素属性后与上例有什么不同。

鲍鱼数据集的问题是根据某些测量值预测鲍鱼年龄。当然可以对鲍鱼进行切片,然后数年轮获得鲍鱼年龄的精确值,就像通过数树的年轮得到树的年龄一样。但是问题是这种方法代价比较大,耗时(需要在显微镜下数年轮)。因此更方便经济的方法是做些简单的测量,如鲍鱼的长度、宽度、重量等指标,然后通过一个预测模型对其年龄做相对准确的预测。预测分析有大量的科学应用,学习机器学习的一个好处就是可以将其应用到一系列很有趣的问题上。

鲍鱼数据集可以从UC Irvine数据仓库中获得,其URL是http://archive.ics.uci.edu/ml/machine-learning-database/abalone/abalone.data。此数据集数据以逗号分隔,没有列头。每个列的名字存在另外一个文件中。代码清单2-11将鲍鱼数据集读入Pandas数据框,然后进行分析,这些分析与“分类问题:用声纳探测未爆炸的水雷”节中的一样。由数据的性质决定的,“岩石vs.水雷”数据集的列名(属性名)更加通用。为了能够从直觉上判断提出的预测模型是否可接受,理解鲍鱼数据集各个列名(属性名)的意义是十分重要的。因此,在代码中将列名(属性名)直接拷贝到代码中,与相关的数据绑定在一起,帮助直接感受下一步机器学习算法应该怎么预测。建立预测模型所需的数据包括性别、长度、直径、高度、整体重量、去壳后重量、脏器重量、壳的重量、环数。最后一列“环数”是十分耗时采获得的,需要锯开壳,然后在显微镜下观察得到。这是一个有监督机器学习方法通常需要的准备工作。基于一个已知答案的数据集构建预测模型,然后用这个预测模型预测不知道答案的数据。

代码清单2-11不仅展示了产生统计信息的代码,而且展示了打印输出的统计信息。第一部分打印数据集的头和尾。为了节省空间只显示了头。当你自己运行代码时,就可以看到全部的输出。绝大多数数据框中的数据是浮点数。第一列是性别,标记为M(雄性)、F(雌性)和I(不确定的)。鲍鱼的性别在出生时是不确定的,成熟一些之后才能确定。因此对于小的鲍鱼其性别是不确定的。鲍鱼的性别是一个三值的类别变量。类别属性需要特别注意。一些算法只能处理实数值的属性(如支持向量机(support vector machines)、K最近邻、惩罚线性回归,这些将在第4章介绍)。第4章会讨论把类别属性转换成实数值属性的技巧。代码清单2-11还展示了实数值属性按列的统计信息。

代码清单2-11 鲍鱼数据集的读取与分析-abaloneSummary.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
from pylab import *
import matplotlib.pyplot as plot

target_url = ("http://archive.ics.uci.edu/ml/machine-"
              "learning-databases/abalone/abalone.data")
#read abalone data
abalone = pd.read_csv(target_url,header=None, prefix="V")
abalone.columns = ['Sex', 'Length', 'Diameter', 'Height',
                   'Whole weight','Shucked weight', 'Viscera weight',
                   'Shell weight', 'Rings']


print(abalone.head())
print(abalone.tail())

#print summary of data frame
summary = abalone.describe()
print(summary)

#box plot the real-valued attributes
#convert to array for plot routine
array = abalone.iloc[:,1:9].values
boxplot(array)
plot.xlabel("Attribute Index")
plot.ylabel(("Quartile Ranges"))
show()

#the last column (rings) is out of scale with the rest
# - remove and replot
array2 = abalone.iloc[:,1:8].values
boxplot(array2)
plot.xlabel("Attribute Index")
plot.ylabel(("Quartile Ranges"))
show()

#removing is okay but renormalizing the variables generalizes better.
#renormalize columns to zero mean and unit standard deviation
#this is a common normalization and desirable for other operations
# (like k-means clustering or k-nearest neighbors
abaloneNormalized = abalone.iloc[:,1:9]

for i in range(8):
    mean = summary.iloc[1, i]
    sd = summary.iloc[2, i]

    abaloneNormalized.iloc[:,i:(i + 1)] = (
                    abaloneNormalized.iloc[:,i:(i + 1)] - mean) / sd


array3 = abaloneNormalized.values
boxplot(array3)
plot.xlabel("Attribute Index")
plot.ylabel(("Quartile Ranges - Normalized "))
show()

Printed Output: (partial)
  Sex Length Diameter Height Whole wt Shucked wt Viscera wt
0   M  0.455    0.365  0.095   0.5140     0.2245     0.1010
1   M  0.350    0.265  0.090   0.2255     0.0995     0.0485
2   F  0.530    0.420  0.135   0.6770     0.2565     0.1415
3   M  0.440    0.365  0.125   0.5160     0.2155     0.1140
4   I  0.330    0.255  0.080   0.2050     0.0895     0.0395

   Shell weight Rings
0         0.150    15
1         0.070     7
2         0.210     9
3         0.155    10
4         0.055     7
     Sex Length Diameter Height Whole weight Shucked weight
4172   F  0.565    0.450  0.165       0.8870         0.3700
4173   M  0.590    0.440  0.135       0.9660         0.4390
4174   M  0.600    0.475  0.205       1.1760         0.5255
4175   F  0.625    0.485  0.150       1.0945         0.5310
4176   M  0.710    0.555  0.195       1.9485         0.9455

     Viscera weight Shell weight Rings
4172         0.2390       0.2490    11
4173         0.2145       0.2605    10
4174         0.2875       0.3080     9
4175         0.2610       0.2960    10
4176         0.3765       0.4950    12
           Length    Diameter      Height    Whole wt  Shucked wt
count 4177.000000 4177.000000 4177.000000 4177.000000 4177.000000
mean     0.523992    0.407881    0.139516    0.828742    0.359367
std      0.120093    0.099240    0.041827    0.490389    0.221963
min      0.075000    0.055000    0.000000    0.002000    0.001000
25%      0.450000    0.350000    0.115000    0.441500    0.186000
50%      0.545000    0.425000    0.140000    0.799500    0.336000
75%      0.615000    0.480000    0.165000    1.153000    0.502000
max      0.815000    0.650000    1.130000    2.825500    1.488000

       Viscera weight Shell weight       Rings
count     4177.000000  4177.000000  4177.000000
mean         0.180594     0.238831     9.933684
std          0.109614     0.139203     3.224169
min          0.000500     0.001500     1.000000
25%          0.093500     0.130000     8.000000
50%          0.171000     0.234000     9.000000
75%          0.253000     0.329000    11.000000
max          0.760000     1.005000    29.000000

不仅可以列出统计信息,还可以像代码清单2-11那样产生每个实数值属性(列)的箱线图(box plots)。第一个箱线图如图2-9所示。箱线图又叫作盒须图(box and whisker plots)、盒式图、盒状图。这些图显示了一个小长方形,有一个红线穿过它。红线代表此列数据的中位数(第50百分位数),长方形的顶和底分别表示第25百分位数和第75百分位数(或者第一四分位数、第三四分位数)。可以比较打印出来的统计信息和箱线图中的线段来证实这一点。在盒子的上方和下方有小的水平线,叫作盒须(whisker)。它们分别据盒子的上边和下边是四分位间距的1.4倍,四分位间距就是第75百分位数和第25百分位数之间的距离,也就是从盒子的顶边到盒子底边的距离。也就是说盒子上面的盒须到盒子顶边的距离是盒子高度的1.4倍。这个盒须的1.4倍距离是可以调整的,详见箱线图的相关文档。在有些情况下,盒须要比1.4倍距离近,这说明数据的值并没有扩散到原定计算出来的盒须的位置。在这种情况下,盒须被放在最极端的点上。在另外一些情况下,数据扩散到远远超出计算出的盒须的位置(1.4倍盒子高度的距离),这些点被认为是异常点。

图2-9 鲍鱼数据集的实数值属性箱线图

图2-9所示的箱线图是一种比打印出数据更快、更直接的发现异常点的方法,但是最后一个环数属性(最右边的盒子)的取值范围导致其他属性都被“压缩”了(导致很难看清楚)。一种简单的解决方法就是把取值范围最大的那个属性删除。结果如图2-10所示。这个方法并不令人满意,因为没有实现根据取值范围自动缩放(自适应)。

图2-10 鲍鱼数据集实数值属性箱线图

代码清单2-11的最后一部分代码在画箱线图之前将属性值归一化(normalization)。此处的归一化指确定每列数据的中心,然后对数值进行缩放,使属性1的一个单位值与属性2的一个单位值相同。在数据科学中有相当数量的算法需要这种归一化。例如,K-means 聚类方法是根据行数据之间的向量距离来进行聚类的。距离是对应坐标上的点相减然后取平方和。单位不同,算出来的距离也会不同。到一个杂货店的距离以英里为单位是1英里,以英尺为单位就是5 280英尺。代码清单2-11中的归一化是把属性数值都转换为均值为0、标准差为1的分布。这是最通用的归一化。归一化计算用到了函数summary()的结果。归一化后的效果如图2-11所示。

图2-11 归一化鲍鱼数据集属性的箱线图

注意归一化到标准差1.0并不意味着所有的数据都在−1.0和+1.0之间。盒子的顶边和底边多少都会在−1.0和+1.0附近,但是还有很多数据在这个边界外。

下一步是看属性之间、属性与标签之间的关系。对于“岩石vs.水雷”数据集,加颜色的平行坐标图以图形化方式展示了这两种关系。针对鲍鱼问题,上述方法需要做些修正。岩石vs.水雷是分类问题。平行坐标图对于此类问题,折线代表了一行数据,折线的颜色表明了其所属的类别。这有利于可视化属性和所属类别之间的关系。鲍鱼问题是一个回归问题,应该用不同的颜色来对应标签值的高低。也就是实现由标签的实数值到颜色值的映射,需要将标签的实数值压缩到[0.0,1.0]区间。代码清单2-12由函数summary()获得最大、最小值实现这种转换。结果如图2-12所示。

代码清单2-12 鲍鱼数据的平行坐标图-abalonParallelPlot.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plot
from math import exp

target_url = ("http://archive.ics.uci.edu/ml/machine-"
              "learning-databases/abalone/abalone.data")
#read abalone data
abalone = pd.read_csv(target_url,header=None, prefix="V")
abalone.columns = ['Sex', 'Length', 'Diameter', 'Height',
                   'Whole Wt', 'Shucked Wt',
                   'Viscera Wt', 'Shell Wt', 'Rings']
#get summary to use for scaling
summary = abalone.describe()
minRings = summary.iloc[3,7]
maxRings = summary.iloc[7,7]
nrows = len(abalone.index)

for i in range(nrows):
    #plot rows of data as if they were series data
    dataRow = abalone.iloc[i,1:8]
    labelColor = (abalone.iloc[i,8] - minRings) / (maxRings - minRings)
    dataRow.plot(color=plot.cm.RdYlBu(labelColor), alpha=0.5)

plot.xlabel("Attribute Index")
plot.ylabel(("Attribute Values"))
plot.show()

#renormalize using mean and standard variation, then compress
# with logit function
meanRings = summary.iloc[1,7]
sdRings = summary.iloc[2,7]

for i in range(nrows):
    #plot rows of data as if they were series data
    dataRow = abalone.iloc[i,1:8]
    normTarget = (abalone.iloc[i,8] - meanRings)/sdRings
    labelColor = 1.0/(1.0 + exp(-normTarget))
    dataRow.plot(color=plot.cm.RdYlBu(labelColor), alpha=0.5)

plot.xlabel("Attribute Index")
plot.ylabel(("Attribute Values"))
plot.show()

图2-12的平行坐标图为鲍鱼年龄(壳的环数)和用于预测年龄的属性之间的关系。折线使用的颜色标尺从深红棕色、黄色、浅蓝色一直到深蓝色。图2-11的箱线图显示整个数据集的最大值和最小值分布十分广泛。图2-12有压缩的效果,导致绝大多数的数据都分布在颜色标尺的中间部分。尽管如此,图2-12还是能够显示每个属性和目标环数的相关性。在属性值相近的地方,折线的颜色也比较接近,则会集中在一起。这些相关性都暗示可以构建相当准确的预测模型。相对于那些体现了良好相关性的属性和目标环数,有些微弱的蓝色折线与深橘色的区域混合在一起,说明有些实例可能很难正确预测。

图2-12 鲍鱼数据集的彩色平行坐标图

改变颜色映射关系可以从不同的层面来可视化属性与目标之间的关系。代码清单2-11的最后一部分用到了箱线图中用过的归一化。此归一化不是让所有的值都落到0和1之间。首先让取负值的数据与取正值的数据基本上一样多。代码清单2-11中使用分对数变换(logit transform)实现数值到(0,1)的映射。分对数变换如公式2-5所示,分对数函数如图2-13所示。

公式2-5 分对数转换公式

如图2-13所示,分对数函数将很大的负数映射成0(接近),很大的正数映射成1(接近),0映射成0.5。在第4章还会看到分对数函数,在将线性函数与概率联系起来它起到了关键的作用。

图2-13 分对数函数

图2-14为归一化之后的结果。转换后可以更充分地利用颜色标尺中的各种颜色。注意到针对整体重量和去壳后的重量这两个属性,有些深蓝的线(对应具有大环数的品种)混入了浅蓝线的区域,甚至是黄色、亮红的区域。这意味着,当鲍鱼的年龄较大时,仅仅这些属性不足以准确地预测出鲍鱼的年龄(环数)。好在其他属性(如直径、壳的重量)可以很好地把深蓝线区分出来。这些观察都有助于分析预测错误的原因。

图2-14 鲍鱼数据的平行坐标图

最后一步是看不同属性之间的相关性和属性与目标之间的相关性。代码清单2-13为针对鲍鱼数据产生关联热图和关系矩阵的代码。遵循的方法与“岩石vs.水雷”数据集相应章节里的方法一样,只有一个重要差异:因为鲍鱼问题是进行实数值预测,所以在计算关系矩阵时可以包括目标值。

代码清单2-13 鲍鱼数据的相关性计算-abaloneCorrHeat.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
import matplotlib.pyplot as plot

target_url = ("http://archive.ics.uci.edu/ml/machine-"
              "learning-databases/abalone/abalone.data")
#read abalone data
abalone = pd.read_csv(target_url,header=None, prefix="V")
abalone.columns = ['Sex', 'Length', 'Diameter', 'Height',
                   'Whole weight', 'Shucked weight',
                   'Viscera weight', 'Shell weight', 'Rings']

#calculate correlation matrix
corMat = DataFrame(abalone.iloc[:,1:9].corr())
#print correlation matrix
print(corMat)

#visualize correlations using heatmap
plot.pcolor(corMat)
plot.show()

                 Length Diameter   Height Whole Wt  Shucked Wt
Length         1.000000 0.986812 0.827554  0.925261   0.897914
Diameter       0.986812 1.000000 0.833684  0.925452   0.893162
Height         0.827554 0.833684 1.000000  0.819221   0.774972
Whole weight   0.925261 0.925452 0.819221  1.000000   0.969405
Shucked weight 0.897914 0.893162 0.774972  0.969405   1.000000
Viscera weight 0.903018 0.899724 0.798319  0.966375   0.931961
Shell weight   0.897706 0.905330 0.817338  0.955355   0.882617
Rings          0.556720 0.574660 0.557467  0.540390   0.420884

               Viscera weight Shell weight    Rings
Length               0.903018     0.897706 0.556720
Diameter             0.899724     0.905330 0.574660
Height               0.798319     0.817338 0.557467
Whole weight         0.966375     0.955355 0.540390
Shucked weight       0.931961     0.882617 0.420884
Viscera weight       1.000000     0.907656 0.503819
Shell weight         0.907656     1.000000 0.627574
Rings                0.503819     0.627574 1.000000

图2-15为关联热图。在图2-15中,红色代表强相关,蓝色代表弱相关。目标(壳上环数)是最后一项,即关联热图的第一行和最右列。蓝色说明这些属性与目标弱相关。浅蓝对应目标与壳的重量的相关性。这个结果与在平行坐标图看到的一致。如图2-15所示,在偏离对角线的单元内,红棕色的值代表这些属性相互高度相关。这多少与平行坐标图的结论有些矛盾,因为在平行坐标图中,目标与属性的一致性是相当强的。代码清单2-13展示了具体的关联值。

图2-15 鲍鱼数据的关联热图

在此小节看到如何将用于分类问题(岩石vs.水雷)的工具修改后用于回归问题。修改主要来源于这两类问题的本质差别:回归问题标签是实数值,而二元分类问题的标签是二值变量。下节将采用同样的手段来分析全部是数值属性的回归问题。因为是回归问题,可以采用鲍鱼数据分析同样的工具。因为所有属性都是数值型,分析时可以包含所有的属性,如计算相关性时。

红酒口感数据集包括将近1 500种红酒的数据。每一种红酒都有一系列化学成分的测量指标,包括酒精含量、挥发性酸、亚硝酸盐。每种红酒都有一个口感评分值,是三个专业评酒员的评分的平均值。问题是构建一个预测模型,输入化学成分的测量值,预测口感评分值,使之与评酒员的评分一致。

代码清单2-14为获得红酒数据集统计信息的代码。代码打印输出数据集的数值型统计信息,在代码清单的最后部分可以看到。代码还产生了归一化属性的箱线图,可以直观发现数据集中的异常点。图2-16为箱线图。数值型统计信息和箱线图都显示含有大量的边缘点。在对此数据集进行训练时要记住这一点。当分析预测模型的性能时,这些边缘点很可能就是分析模型预测错误的一个重要来源。

代码清单2-14 红酒数据统计信息-wineSummary.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
from pylab import *
import matplotlib.pyplot as plot

target_url = ("http://archive.ics.uci.edu/ml/machine-"
"learning-databases/wine-quality/winequality-red.csv")
wine = pd.read_csv(target_url,header=0, sep=";")

print(wine.head())

#generate statistical summaries
summary = wine.describe()
print(summary)

wineNormalized = wine
ncols = len(wineNormalized.columns)

for i in range(ncols):
    mean = summary.iloc[1, i]
    sd = summary.iloc[2, i]

    wineNormalized.iloc[:,i:(i + 1)] = \
        (wineNormalized.iloc[:,i:(i + 1)] - mean) / sd
array = wineNormalized.values
boxplot(array)
plot.xlabel("Attribute Index")
plot.ylabel(("Quartile Ranges - Normalized "))
show()

Output - [filename - wineSummary.txt]
    fixed acidity volatil acid citric acid resid sugar chlorides
0             7.4          0.70       0.00         1.9     0.076
1             7.8          0.88       0.00         2.6     0.098
2             7.8          0.76       0.04         2.3     0.092
3            11.2          0.28       0.56         1.9     0.075
4             7.4          0.70       0.00         1.9     0.076

    free sulfur dioxide tot sulfur dioxide density   pH sulphates
0                    11                 34  0.9978 3.51      0.56
1                    25                 67  0.9968 3.20      0.68
2                    15                 54  0.9970 3.26      0.65
3                    17                 60  0.9980 3.16      0.58
4                    11                 34  0.9978 3.51      0.56

    alcohol quality
0       9.4       5
1       9.8       5
2       9.8       5
3       9.8       6
4       9.4       5
        fixed acidity volatile acidity citric acid residual sugar
count     1599.000000      1599.000000 1599.000000    1599.000000
mean         8.319637         0.527821    0.270976       2.538806
std          1.741096         0.179060    0.194801       1.409928
min          4.600000         0.120000    0.000000       0.900000
25%          7.100000         0.390000    0.090000       1.900000
50%          7.900000         0.520000    0.260000       2.200000
75%          9.200000         0.640000    0.420000       2.600000
max         15.900000         1.580000    1.000000      15.500000

         chlorides free sulfur dioxide tot sulfur dioxide     density
count  1599.000000         1599.000000        1599.000000 1599.000000
mean      0.087467           15.874922          46.467792    0.996747
std       0.047065           10.460157          32.895324    0.001887
min       0.012000            1.000000           6.000000    0.990070
25%       0.070000            7.000000          22.000000    0.995600
50%       0.079000           14.000000          38.000000    0.996750
75%       0.090000           21.000000          62.000000    0.997835
max       0.611000           72.000000         289.000000    1.003690

                pH   sulphates     alcohol     quality
count  1599.000000 1599.000000 1599.000000 1599.000000
mean      3.311113    0.658149   10.422983    5.636023
std       0.154386    0.169507    1.065668    0.807569
min       2.740000    0.330000    8.400000    3.000000
25%       3.210000    0.550000    9.500000    5.000000
50%       3.310000    0.620000   10.200000    6.000000
75%       3.400000    0.730000   11.100000    6.000000
max       4.010000    2.000000   14.900000    8.000000

图2-16 归一化红酒数据的属性与目标箱线图

加入颜色标记的平行坐标图更易于观察属性与目标的相关程度。代码清单2-15为生成平行坐标图的代码。图2-17为平行坐标图。图2-17的主要不足在于对取值范围较小的变量进行了压缩。

图2-17 红酒数据的平行坐标图

为了克服这个问题,代码清单2-15对红酒数据进行了归一化,然后重画了平行坐标图。图2-18为归一化之后的平行坐标图。

代码清单2-15 红酒数据的平行坐标图-wineParalleIPLot.Py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
from pylab import *
import matplotlib.pyplot as plot
from math import exp

target_url = "http://archive.ics.uci.edu/ml/machine-learning-databases/
wine-quality/winequality-red.csv"
wine = pd.read_csv(target_url,header=0, sep=";")

#generate statistical summaries
summary = wine.describe()
nrows = len(wine.index)
tasteCol = len(summary.columns)
meanTaste = summary.iloc[1,tasteCol - 1]
sdTaste = summary.iloc[2,tasteCol - 1]
nDataCol = len(wine.columns) -1

for i in range(nrows):
    #plot rows of data as if they were series data
    dataRow = wine.iloc[i,1:nDataCol]
    normTarget = (wine.iloc[i,nDataCol] - meanTaste)/sdTaste
    labelColor = 1.0/(1.0 + exp(-normTarget))
    dataRow.plot(color=plot.cm.RdYlBu(labelColor), alpha=0.5)

plot.xlabel("Attribute Index")
plot.ylabel(("Attribute Values"))
plot.show()

wineNormalized = wine
ncols = len(wineNormalized.columns)

for i in range(ncols):
    mean = summary.iloc[1, i]
    sd = summary.iloc[2, i]
    wineNormalized.iloc[:,i:(i + 1)] =
    (wineNormalized.iloc[:,i:(i + 1)] - mean) / sd

#Try again with normalized values
for i in range(nrows):
    #plot rows of data as if they were series data
    dataRow = wineNormalized.iloc[i,1:nDataCol]
    normTarget = wineNormalized.iloc[i,nDataCol]
    labelColor = 1.0/(1.0 + exp(-normTarget))
    dataRow.plot(color=plot.cm.RdYlBu(labelColor), alpha=0.5)

plot.xlabel("Attribute Index")
plot.ylabel(("Attribute Values"))
plot.show()

归一化红酒数据的平行坐标图可以更方便地观察出目标与哪些属性相关。图2-18展示了属性间清晰的相关性。在图的最右边,深蓝线(高口感评分值)聚集在酒精含量属性的高值区域;但是图的最左边,深红线(低口感评分值)聚集在挥发性酸属性的高值区域。这些都是最明显的相关属性。在第5章和第7章的预测模型中将会对属性基于对预测所做的贡献进行评分,我们会看到预测模型是如何支撑上述这些观察结果的。

图2-18 归一化红酒数据的平行坐标图

图2-19为属性之间、属性与目标之间的关联热图。在这个热图中,暖色对应强相关(颜色标尺的选择与平行坐标图中的正好相反)。红酒数据的关联热图显示口感评分值(最后一列)与酒精含量(倒数第二列)高度正相关,但是与其他几个属性(包括挥发性酸等)高度负相关。

图2-19 红酒数据的关联热图

分析红酒数据所用的工具在前面都已经介绍和使用过。红酒数据集展示了这些工具可以揭示的信息。平行坐标图和关联热图都说明酒精含量高则口感评分值高,然而挥发性酸高则口感评分值低。在第5、第7章可以看到,预测模型中的一部分工作就是研究各种属性对预测的重要性。红酒数据集就是一个很好的例子,展示了如何通过探究数据来知晓向从哪个方向努力来构建预测模型以及如何评价预测模型。下节将探究多类别分类问题的数据集。

多类别分类问题与二元分类问题类似,不同之处在于它有几个离散的输出,而不是只有两个。回顾探测未爆炸的水雷的问题,它的输出只有两种可能性:声纳探测的物体是岩石或者水雷。而红酒口感评分问题根据其化学成分会产生几个可能的输出(其口感评分值是从3分到8分)。但是对于红酒口感评分问题,口感评分值存在有序的关系。打5分的红酒要好于打3分的,但是要劣于打8分的。对于多类别分类问题,输出结果是不存在这种有序关系的。

此节将根据玻璃的化学成分来判断玻璃的类型,目标是确定玻璃的用途。玻璃的用途包括建筑房间用玻璃、车辆上的玻璃、玻璃容器等。确定玻璃的用途类型是为了鉴证。例如在一个车祸或犯罪现场,会有玻璃的碎片,确定这些玻璃碎片的用途、来源,有助于确定谁是过错方或者谁是罪犯。代码清单2-16为生成玻璃数据集的统计信息的代码。图2-20为归一化玻璃数据的箱线图,箱线图显示有相当数量的异常点。

代码清单2-16 玻璃数据集的统计信息-glassSummary.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
from pylab import *
import matplotlib.pyplot as plot

target_url = ("https://archive.ics.uci.edu/ml/machine-"
              "learning-databases/glass/glass.data")

glass = pd.read_csv(target_url,header=None, prefix="V")
glass.columns = ['Id', 'RI', 'Na', 'Mg', 'Al', 'Si',
                 'K', 'Ca', 'Ba', 'Fe', 'Type']

print(glass.head())

#generate statistical summaries
summary = glass.describe()
print(summary)
ncol1 = len(glass.columns)

glassNormalized = glass.iloc[:, 1:ncol1]
ncol2 = len(glassNormalized.columns)
summary2 = glassNormalized.describe()

for i in range(ncol2):
    mean = summary2.iloc[1, i]
    sd = summary2.iloc[2, i]

glassNormalized.iloc[:,i:(i + 1)] = \
       (glassNormalized.iloc[:,i:(i + 1)] - mean) / sd

array = glassNormalized.values
boxplot(array)
plot.xlabel("Attribute Index")
plot.ylabel(("Quartile Ranges - Normalized "))
show()

Output: [filename - ]
print(glass.head())

 Id      RI    Na   Mg   Al    Si    K   Ca Ba Fe Type
0 1 1.52101 13.64 4.49 1.10 71.78 0.06 8.75  0  0    1
1 2 1.51761 13.89 3.60 1.36 72.73 0.48 7.83  0  0    1
2 3 1.51618 13.53 3.55 1.54 72.99 0.39 7.78  0  0    1
3 4 1.51766 13.21 3.69 1.29 72.61 0.57 8.22  0  0    1
4 5 1.51742 13.27 3.62 1.24 73.08 0.55 8.07  0  0    1


print(summary) - Abridged
              Id         RI         Na         Mg         Al
count 214.000000 214.000000 214.000000 214.000000 214.000000
mean  107.500000   1.518365  13.407850   2.684533   1.444907
std    61.920648   0.003037   0.816604   1.442408   0.499270
min     1.000000   1.511150  10.730000   0.000000   0.290000
25%    54.250000   1.516523  12.907500   2.115000   1.190000
50%   107.500000   1.517680  13.300000   3.480000   1.360000
75%   160.750000   1.519157  13.825000   3.600000   1.630000
max   214.000000   1.533930  17.380000   4.490000   3.500000
               K         Ca         Ba         Fe       Type
count 214.000000 214.000000 214.000000 214.000000 214.000000
mean    0.497056   8.956963   0.175047   0.057009   2.780374
std     0.652192   1.423153   0.497219   0.097439   2.103739
min     0.000000   5.430000   0.000000   0.000000   1.000000
25%     0.122500   8.240000   0.000000   0.000000   1.000000
50%     0.555000   8.600000   0.000000   0.000000   2.000000
75%     0.610000   9.172500   0.000000   0.100000   3.000000
max     6.210000  16.190000   3.15000  0 0.510000   7.000000

图2-20 玻璃数据的箱线图

玻璃数据的箱线图显示有相当数量的异常点,至少与前面的例子相比,异常点数量上是比较多的。玻璃数据集有几个因素可能会导致出现异常点。首先这是一个分类问题,在属性值和类别之间不需要存在任何连续性,也就是说不应期望在各种类别之间,属性值是相互接近的、近似的。另外一个玻璃数据比较独特的地方是它的数据是非平衡的。成员最多的类有76个样本,而成员最小的类只有9个样本。统计时,平均值可能是由成员最多的那个类的属性值决定,因此不能期望其他的类别也有相似的属性值。采取激进的方法来区分类别可能会达到较好的结果,但这也意味着预测模型需要跟踪不同类别之间复杂的边界。在第3章可以了解到,如果给定足够多的数据,集成方法可以比惩罚线性回归方法产生更复杂的决策边界。而在第5、第7章可以看到哪种方法可以获得更好的效果。

平行坐标图可能对此数据集揭示的信息更多。图2-21为其平行坐标图。数据根据输出类别用不同的颜色标记。有些类别区分度很好。例如,深蓝色的线聚集度很好,在某些属性上与其他类别的区分度也很好。深蓝的线在某些属性上经常处于数据的边缘,也就是说,是这些属性上的异常点。浅蓝的线在某些属性上也与深蓝的线一样,处于边缘地带,但是数量上要比深蓝的少,而且两者都在边缘地带时的所属的属性也不尽相同。棕色的线聚集性也很好,但其取值基本上在中心附近。

图2-21 玻璃数据的平行坐标图

代码清单2-17为产生玻璃数据的平行坐标图的代码。针对岩石vs.水雷问题,平行坐标图的线用2种颜色代表了2种目标类别。在回归问题(红酒口感评分、鲍鱼预测年龄),标签(目标类别)取实数值,平行坐标图的线取一系列不同的颜色。在多类别分类问题中,每种颜色代表一种类别,共有6种类别,6种颜色。标签是1~7,没有4。颜色的选择与回归问题中的方式类似:将目标类别(标签)除以其最大值,然后再基于此数值选择颜色。图2-22为玻璃数据的关联热图。关联热图显示了属性之间绝大多数是弱相关的,说明属性之间绝大多数是相互独立的,这是件好事情。标签(目标类别)没有出现在热图中,因为目标(类别)只取几个离散值中的一个。不包括目标类别无疑减少了关联热图所能揭示的信息。

代码清单2-17 玻璃数据的平行坐标图-glassParallelPlot.py

__author__ = 'mike_bowles'
import pandas as pd
from pandas import DataFrame
from pylab import *
import matplotlib.pyplot as plot

target_url = ("https://archive.ics.uci.edu/ml/machine-"
              "learning-databases/glass/glass.data")

glass = pd.read_csv(target_url,header=None, prefix="V")
glass.columns = ['Id', 'RI', 'Na', 'Mg', 'Al', 'Si',
                 'K', 'Ca', 'Ba', 'Fe', 'Type']

glassNormalized = glass
ncols = len(glassNormalized.columns)
nrows = len(glassNormalized.index)
summary = glassNormalized.describe()
nDataCol = ncols - 1

#normalize except for labels
for i in range(ncols - 1):
    mean = summary.iloc[1, i]
    sd = summary.iloc[2, i]

glassNormalized.iloc[:,i:(i + 1)] = \
        (glassNormalized.iloc[:,i:(i + 1)] - mean) / sd

#Plot Parallel Coordinate Graph with normalized values
for i in range(nrows):

    #plot rows of data as if they were series data
    dataRow = glassNormalized.iloc[i,1:nDataCol]
    labelColor = glassNormalized.iloc[i,nDataCol]/7.0
    dataRow.plot(color=plot.cm.RdYlBu(labelColor), alpha=0.5)

plot.xlabel("Attribute Index")
plot.ylabel(("Attribute Values"))
plot.show()

图2-22 玻璃数据的关联热图

对玻璃数据的研究揭示了一个有趣的问题。具体地说,箱线图以及平行坐标图暗示了如果给定足够多的数据,采用集成方法是一个很好的选择。一系列的属性用来区分一个类别,明显类别之间会有复杂的边界。哪种算法会产生最佳的预测性能还有待进一步观察。本章学习的分析数据的方法已圆满完成了任务。它们可以帮助加深对问题的理解,通过各种权衡后可以更好地预判哪种算法可以获得较好的性能。

本章介绍了用于探究新数据集的一些工具,接下来就是如何建立预测模型。这些工具从简单地获取数据集的规模开始,包括确定数据集属性和目标的类型等。这些关于数据集的基本情况会对数据集的预处理、预测模型的训练提供帮助。本章还包括一些统计概念,帮助加深对数据的理解。这些概念包括:简单的统计信息(均值、标准差、分位数)、二阶统计信息,如属性间的相关性、属性与目标间的相关性。当目标是二值时,计算属性与目标相关性的方法与目标是实数(回归问题)时有所不同。本章也介绍了可视化技巧:利用分位数图来显示异常点;利用平行坐标图来显示属性和目标之间的相关性。上述方法和技巧都可以应用到本书后续的内容,用来验证算法以及算法之间的对比。

Gorman, R. P., and Sejnowski, T. J. (1988). UCI Machine Learning Repository.https://archive.ics.uci.edu/ml/datasets/Connectionist+Bench+%28Sonar,+Mines+vs.+Rocks%29. Irvine, CA: University of California, School of Information and Computer Science.

[1] 建议读者自行运行代码,观察输出的彩色平行坐标图,就可以看到文中所述的效果。下面的图例也有同样的问题。——译者注。


相关图书

ChatGPT原理与应用开发
ChatGPT原理与应用开发
动手学机器学习
动手学机器学习
机器学习与数据挖掘
机器学习与数据挖掘
机器学习公式详解 第2版
机器学习公式详解 第2版
自然语言处理迁移学习实战
自然语言处理迁移学习实战
AI医学图像处理(基于Python语言的Dragonfly)
AI医学图像处理(基于Python语言的Dragonfly)

相关文章

相关课程