数据科学与工程技术丛书
预测分析:R语言实现
Mastering Predictive Analytics with R
[希] 鲁伊·米格尔·福特(Rui Miguel Forte) 著
吴今朝 译
图书在版编目(CIP)数据
预测分析:R语言实现/(希)鲁伊·米格尔·福特(Rui Miguel Forte)著;吴今朝译. —北京:机械工业出版社,2016.10
(数据科学与工程技术丛书)
书名原文:Mastering Predictive Analytics with R
ISBN 978-7-111-55354-0
I. 预… II.① 鲁…② 吴… III. 程序语言―程序设计 IV. TP312
中国版本图书馆CIP数据核字(2016)第274893号
本书版权登记号:图字:01-2015-5215
Rui Miguel Forte:Mastering Predictive Analytics with R (ISBN: 978-1-78398-280-6).
Copyright ?2015 Packt Publishing. First published in the English language under the title “Mastering Predictive Analytics with R”.
All rights reserved.
Chinese simplified language edition published by China Machine Press.
Copyright ?2017 by China Machine Press.
本书中文简体字版由Packt Publishing授权机械工业出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。
预测分析:R语言实现
出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037)
责任编辑:何欣阳
印 刷:藁城市京瑞印刷有限公司 版 次:2017年1月第1版第1次印刷
开 本:185mm×260mm 1/16 印 张:16.25
书 号:ISBN 978-7-111-55354-0 定 价:59.00元
凡购本书,如有缺页、倒页、脱页,由本社发行部调换
客服热线:(010)88379426 88361066 投稿热线:(010)88379604
购书热线:(010)68326294 88379649 68995259 读者信箱:hzit@hzbook.com
版权所有·侵权必究
封底无防伪标均为盗版
本书法律顾问:北京大成律师事务所 韩光/邹晓东
译 者 序
本书是一本比较全面的预测建模教材,覆盖了最常见的一些技术,例如逻辑回归、神经网络、支持向量机、隐马尔可夫模型、时间序列分析、推荐系统等。本书属于Packt出版社系列图书中的Mastering级别,是有一定难度和深度的高级教程。
作为一位兼具科研和产业经验的专家,作者很巧妙地把握了理论和实践之间的平衡。他的做法是,先以比较通俗的方式讲解理论背景,再通过一些实际案例的直观示范来帮助读者理解相关的理论和方法。这样就让读者既能对各种预测分析方法的理论基础有更深入的认识,又能掌握在实际工作中运用这些技术的方法。此外,作者还提供了大量的参考资料和在线资源,供学有余力的读者进一步提高。
在翻译完成《基于R语言的自动数据收集:网络抓取和文本挖掘实用指南》一书后,我就在机械工业出版社编辑推荐的后续书单中挑出了这本书。我之所以会对这本书感兴趣,是因为之前那本书的核心内容是如何在线抓取数据,而现在这本书的核心内容则是如何对数据进行预测建模,两本书结合起来,就构成了一个完整的技术体系。
在多年的应用开发经历中,我一直更喜欢这种个人能掌控完整技术链条的工作风格,相信很多科研工作者和小团队的技术带头人也会有同感。其实这种风格和团队合作并不矛盾。实际上,只有具备了掌控全局的能力,才能提高团队合作的效率,降低沟通成本。
在之前那本书的读者评论里,有一条我想分享出来:“这本书值得一读,作者很诚实,确实一本书不能解决你所有的问题,但是可以给你一些思路,顺着这个思路不断扩展自己的知识,最后娴熟运用。阅读纸质书最大的好处就是系统性,书中很多知识都通过网络资源零散地学过,但始终不成体系,本书能给你一个很好的网络数据获取的系统框架。”
确实,学习技术的过程是无止境的,但一套科学的体系能让读者把握全局,少走弯路。本书就比较系统地讲解了有监督学习的预测建模技术。
作为译者,我想给读者的一条建议就是多动手。在技术领域,看懂了书不等于掌握了技术。读者如果没有经过实际的应用,对书中内容的理解不但达不到足够的深度,而且很容易遗忘。针对自己感兴趣的某个问题,先把模型设计出来,把代码调通,再对模型进行优化,最后得到理想的结果,这个过程是非常关键的。
比如在之前那本书的GitHub讨论区里就有一个很有意思的话题,有一位读者在尝试抓取某个新闻下的所有评论时遇到了问题,后来才发现是iFrame元素的原因,进而引出了一些原书中没有讲到的技术。在和我一起分析讨论并调通了代码后,这位读者觉得收获很大。
照例,我也给本书开通了一个GitHub讨论区,链接是:https://github.com/coderLMN/ MasteringPredictiveAnalyticsWithR/issues,欢迎读者去提出问题、解答和建议,参与讨论。
另外需要说明的一点是,我在译稿中加入了不少译者注,目的是帮助读者理解某些比较晦涩的概念、公式和代码。但是因为个人水平有限,译者注里的解释和说明难免会有不严谨甚至错误的地方,请读者多多指正。
最后,我要感谢我的家人在本书翻译过程中的付出和耐心。因为有他们的支持,我才能以一种精耕细作的方式完成本书的翻译工作。
吴今朝
前 言
预测分析以及更一般意义上的数据科学当前正处于被追捧的热潮中,因为像垃圾邮件过滤、单词补全和推荐引擎这样的预测性技术已经被广泛运用于日常生活。这些技术现在不仅越来越被我们所熟悉,还赢得了我们的信任。在计算机处理能力和软件方面(例如R语言及其大量专用的扩展包)的发展产生了这样的局面:用户经过培训就可以使用这些工具,而无需具备统计学的高级学位,也不需要使用公司或大学实验室专用的硬件。技术的成熟度和基础软硬件的可用性结合起来,让很多该领域的从业者倍感兴奋,他们感到可以为自己的领域和业务设计一些能产生重要影响的工具,事实也确实如此。
与此同时,很多新进入该领域的人士很快发现其中有很多陷阱需要克服。实际上,没有哪个学位足以把一位学生或从业者训练为成功的预测建模者。该领域依赖于很多学科,例如计算机科学、数学和统计学。当前,进入该领域的人们不仅只在其中的一门学科有比较强的背景,还往往会比较专精于其他学科。在给研究生和从业者们讲授了有关本书材料的几次课程之后,我发现学员们反复表达的两个最大担忧是对编程和数学的恐惧。有意思的是,对这两者的表达几乎总是互斥的。预测分析学实际上是一种实践性的学科,但同时也是一种具备较强理论基础的学科,这些理论基础的知识对于从业者是很关键的。因此,掌握预测分析需要一系列不同的技能,从编写良好的软件到实现一种新技术或对数据进行预处理,再到理解某个模型的假设条件,如何有效地训练该模型,如何对该模型出现的问题进行诊断,以及如何调整模型的参数以获得更好的结果。
讨论到这里,很自然地会反向思考预测分析学作为一个领域实际会覆盖的内容。事实上,该领域和机器学习、数据挖掘、商业分析学、数据科学等其他相关领域的边界是比较模糊的。本书中会用到的定义非常宽泛。对于本书的主题而言,预测分析学是一个领域,它利用数据建立模型来预测未来我们感兴趣问题的结果。当然,它和机器学习领域会有很大的重叠,机器学习更多地研究从数据中学习的程序和算法。这种重叠的情况对于数据挖掘(以从数据中提取知识和模式为目标)也同样成立。数据科学正在迅速成为覆盖所有这些领域的综合术语,它还包括了其他主题,例如呈现数据分析结果的信息可视化,围绕在实际环境中部署模型的业务概念,以及数据管理。本书会着重于机器学习,但我们不会覆盖学习可行性的理论探索,也不会讲解着眼于从无特定预测目标的数据中寻找模式和聚类的无监督学习方法。取而代之,我们会探索像时间序列这样的一些主题,通常在机器学习的教材里不会讲解它们。
无论对于学习预测分析学还是解决实际环境中的问题,R语言都是一个优秀的平台。它是一个开源项目,有一个持续快速增长的用户社区。在编写本书时,它和Python是全世界数据科学家最常用的两种语言。它有很多适用于不同建模技术和应用领域的扩展包,其中很多可以通过连接到Comprehensive R Archive Network (CRAN)从R语言平台本身直接获取。该语言还有很多在线资源,从教程到在线课程都包含在内。我们尤其要提到优秀的交叉验证式论坛(http://stats.stackexchange.com/)以及R-bloggers 网站(http://www.r-bloggers.com/),该网站包含了大量来自不同博客的关于R语言应用的文章。对于那些对R语言有点生疏的读者,我们提供了一个免费在线教程章节,它是从我们在AUEB学生的课程讲义演化而来的。
本书的主要任务是在(强调直觉及实践而不是理论的)低端入门教程和(专注于数学、细节和严谨性的)高端学术教材之间的鸿沟上架起桥梁。另一个同等重要的目标是给读者灌输一些良好的实践经验,比如学习如何适当地测试和评估一个模型。我们还要强调一些重要的概念,例如偏误-方差权衡和过拟合,这些概念在预测建模中是普遍存在的,并会在不同模型中以多种形式反复出现。
从编程的角度来说,虽然我们假定你已经熟悉R语言,不过还是会详细解释并讨论每个代码示例,以便读者提高他们的自信心,循序渐进。尽管如此,在学习的过程中,或者至少在转到下一章之前,实际运行代码的重要性是如何强调都不为过的。为了尽可能让这个过程顺利进行,我们已经为教材中的所有章节提供了代码文件,其中包含了教材中所有的代码示例。此外,我们还在很多地方编写了自己对于特定技术的简单实现方法。典型的两个示例是第4章里的口袋感知器算法和第7章的AdaBoost自适应增强方法。在某种程度上,这么做是为了鼓励用户学习如何编写他们自己的函数,而不是完全依赖于已有的实现方法,因为并不是所有方法都有现成的函数可用。
重现能力是数据分析的一项关键技能,而且它并不限于教育领域。因此,我们大量使用了可自由获取的数据集并尽力在需要随机数生成器的地方运用特定的种子值。最后,我们尽可能尝试利用相对小规模的数据集,以确保读者在阅读本书时运行代码不需要等待太长的时间或被迫寻求更好的硬件。我们要提醒你,在真实世界里,耐心是一种非常有益的美德,因为你感兴趣的大部分数据集会比我们学习本书时用到的更大。
每章的结尾是两个或多个实际的建模案例,每章的开始则是一些理解新模型或技术所必需的理论和背景知识。虽然不避讳用数学解释重要的细节,但是我们在这方面很慎重,相关的介绍适可而止,以确保读者能理解相关的基本概念就可以了。这样做符合本书的理念,即弥补入门教程和涉及更多细节的学术教材之间的差距。具备高中数学背景知识的读者可以确信,他们能够借助基本的数学知识完整地学习本书的所有内容。学习所需的关键技能是简单微积分(例如简单微分)、概率论的关键概念(例如均值、方差、相关系数),以及重要的概率分布(例如二项分布和正态分布)。虽然我们不提供这方面的教程,但在前面几章我们的确是循序渐进的。为了照顾那些数学爱好者的需求,我们经常会以提示的形式提供额外的技术细节,并给出一些参考资料作为所讨论内容的自然延伸。
有时候,我们需要给出某个概念的直观解释,以节省篇幅,避免另辟一章专门讨论不必要的纯理论。在这么做的时候(例如对第4章里的反向传播算法),我们会确保前后的衔接性,让读者能具备坚实的基础知识来进一步掌握更详细的内容。同时,还会给出精心挑选的参考文献,其中很多都是可读性好而且可以免费获取的文章、论文或在线教材。当然,我们会在任何必要的地方引用重要的教材。
本书没有练习题,但是鼓励你把好奇心发挥到极致。好奇心对于预测建模者来说是一种巨大的天赋。我们从中获取了分析数据的很多网站上都有我们没有研究到的其他大量数据集。我们偶尔还会讲解如何创建人工数据来演示某个特定技术背后的概念验证过程。很多用来创建和训练模型的R语言函数都有一些其他调优参数是本书中没有时间讲解的。用到的扩展包也往往会包含和我们讲解的函数相关的其他函数,正如用到的扩展包本身也往往会有其他替代包可用。所有这些都是进一步研究和实验的途径。要掌握预测分析学,认真学习和个人的探索及练习都是同等重要的。
学生在该领域的一个普遍诉求,是用额外的实例来模拟有经验的建模者针对数据集所遵循的实际过程。在现实中,可信的模拟过程从分析开始后可能会持续很多小时。这是因为花在预测建模上的大部分时间都用来研究数据、尝试新特征和预处理步骤,以及对结果试验不同的模型。简而言之,正如我们在第1章中将要看到的,探索、试验和误差是有效分析的关键组成部分。编写一本讲解关于每个数据集的错误或不成功方案的书是完全不现实的。相反,强烈推荐读者将本书中的所有数据分析过程视为改进的起点,并自己延续这个过程。好的思路是尝试把其他章节讲解的技术运用于特定数据集,以便观察其他方法是否有效。从简单地给某个输入特征运用不同的变换方式到采用另一章里讲解的完全不同的模型,任何尝试都是可以的。
作为最后一个提示,我们要指出,创建美观规范的图来呈现数据分析结果是一项重要的技能,尤其是在职场中。虽然R语言的基础绘图能力覆盖了基本的需求,但它往往缺乏美观性。因此,除了用分析代码中的某些函数产生的特定图形之外,我们会用ggplot2包绘图。虽然我们不提供这方面的教程,但是本书中包括的所有产生绘图的代码都在配套的代码文件里,希望用户可以从中受益。ggplot2包的一个很有用的在线参考资料是“the Cookbook for R”(http://www.cookbook-r.com/Graphs)网站上有关图形的章节。
本书内容
第1章会讲解统计模型的通用语言和在对这些模型进行分类时所依据的一些重要差别,由此开启我们的学习之旅。本章的亮点是对预测建模过程的探索,我们会通过它展示第一个模型,即k近邻(k Nearest Neighbor,kNN)模型。
第2章会介绍预测数量值最简单且最著名的方法。本章的重点是理解线性回归的假设,以及一些可以用来评估训练模型质量的诊断工具。此外,本章还会涉及正则化的重要概念,它可以用于避免预测模型常见的一种瑕疵—过拟合(over fitting)。
第3章会对前一章里线性模型的思想进行扩展,方法是引入广义线性模型的概念。虽然这类模型有很多示例,但本章的重点是逻辑回归这样一个针对分类问题的流行方法。我们还会探讨该模型扩展到针对多类别的情况,发现该方法对于二元分类(binary classification)的效果最好。
第4章会讲解能够处理回归及分类两种任务的一种仿生模型。神经网络有很多种,而本章会重点关注多层感知网络(multilayer perceptron network)。神经网络是复杂的模型,本章的主要关注点是理解在训练过程中起作用的一组不同的配置和优化参数。
第5章会通过学习支持向量机来掌握非线性模型的问题。在这部分,我们会通过利用最大边缘分离(maximum margin separation)来尝试以几何方式拟合我们的训练数据,以探索对分类问题进行思考的另一种方法。本章还会介绍交叉验证(cross-validation)这一评估和优化模型的基本技术。
第6章讲解决策树(decision tree)这样另一类已经成功运用于回归和分类等问题的模型。决策树的类型有很多种,本章会介绍一批不同的训练算法,例如CART和C5.0。我们还可以看到,树形方法具有独特的优点,例如内建的特征选择、支持缺失数据和类别变量,以及非常易于解释的输出。
第7章不同于以往章节,本章会采用一种迂回方式,不会讲解新类型的模型,而是尝试解答如何把不同模型有效地结合起来的问题。我们会讲解装袋(bagging)和增强(boosting)这两种著名的技术,并会把随机森林(random forest)作为树的装袋的一种特例进行介绍。
第8章讲解的是机器学习研究领域的一个活跃领域,即概率图模型。这些模型通过一个图结构把变量之间的条件性独立关系(conditional independence relation)进行编码,已经成功运用于从计算机视觉到医疗诊断等很多领域的问题中。本章会学习它的两种主要表现形式,即朴素贝叶斯(Na阅读准备
运行本书代码的唯一硬性要求就是安装R。它可以从http://www.r-project.org/自由获取,并可以在所有主流操作系统上运行。本书中的代码已经在R的3.1.3版本上测试过。
所有的章节都至少会引入一个R基础安装包里没有预装的新扩展包。我们不会在教材中专门讲解安装R包的过程,但是如果某个包当前没有安装在你的系统里,或它需要更新,你就可以利用install.packages()函数来安装它。例如,下面的命令会安装tm包:
我们使用的所有包都可以在CRAN上获取。下载和安装它们以及获取我们在实例中使用的开源数据集都需要Internet连接。最后,虽然不是绝对必需,但我们要推荐你养成利用集成开发环境(Integrated Development Environment,IDE)进行R语言编程的习惯。有个很棒的IDE是RStudio (http://www.rstudio.com/),它是开源的。
读者人群
本书是为预测建模相关领域的从业者中的新秀和老手编写的。本书大部分的内容已经在研究生、专业人士和R语言培训的授课中使用过,因此它在策划的时候就已经把这些学员的情况都考虑到了。读者必须熟悉R语言,不过即便是那些从来没有接触过这种语言的人,也能够通过阅读在线教程的章节掌握必要的背景知识。不熟悉R语言的读者至少要接触过某些编程语言,例如Python。那些具备MATLAB背景的人会发现切换到R语言相当容易。正如之前提到的,本书对数学的要求是非常适度的,只需要中学数学的某些元素,例如均值和方差的概念及基础的微分。
本书约定
在本书中,你会看到用来区分不同类型信息的多种文本样式。这里是一些关于这些样式的示例及其含义的解释。
代码段的样式设置如下:
新术语和重要的关键字会用粗体显示。
警告或重要的注解会出现在像这样的一个方框内部。
提示和小技巧的样式是这样的。
下载样例代码
你可以从http://www.packtpub.com通过个人账号下载你所购买的所有Packt书籍的样例代码文件。如果你从其他地方购买了本书,你可以访问http://www.packtpub.com/support并完成账号注册,以便直接通过电子邮件获得相关文件。
你也可以访问华章图书官网http://www.hzbook.com/,通过注册并登录个人账号,下载本书中的源代码。
致谢
每段伟大的探险背后都有一个精彩的故事,本书的写作也不例外。正是因为有了很多人的贡献本书才得以面世。我要感谢我在AUEB教过的很多学生,他们的投入和支持简直是铺天盖地的。可以确信的一点是,我从他们那里学到的东西和他们从我这里学到的一样多,甚至更多。我还要感谢Damianos Chatziantoniou策划在希腊开设领先的研究生数据科学课程。Workable公司是一个大熔炉,在这里我能和才华横溢且激情澎湃的工程师们并肩工作,从事有益于全球商业的激动人心的数据科学项目。为此,我要感谢我的同事们,特别是两位点石成金的公司创始人Nick和Spyros。
我要感谢Subho、Govindan、Edwin和Packt出版社所有同仁的专业和耐心。我要对很多给予我鼓励和激励的朋友表达我永恒的感谢。我的家人和亲友们在本项目中给予了我不可思议的支持。特别地,我要感谢我的父亲Libanio,他鼓励我从事科学事业,还有我的母亲Marianthi,她对我的信心一直远远超过其他任何人。感谢我的妻子Despoina耐心而坚定地站在我一边,即使这本书让我在她首次怀孕的时候难以陪伴在旁。最后也同样重要的是,在本书写作的收尾阶段,我的小女儿在我身边安睡并天真无邪地守望着我。她帮助我的方式是无法用语言描述的。
目 录
译者序
前 言
第1章 准备预测建模1
1.1 模型1
1.1.1 从数据中学习2
1.1.2 模型的核心组成部分5
1.1.3 我们的第一个模型:k近邻5
1.2 模型的类型7
1.2.1 有监督、无监督、半监督和强化学习模型7
1.2.2 参数化和非参数化模型8
1.2.3 回归和分类模型8
1.2.4 实时和批处理机器学习模型9
1.3 预测建模的过程9
1.3.1 定义模型的目标9
1.3.2 收集数据10
1.3.3 选取模型11
1.3.4 数据的预处理12
1.3.5 特征工程和降维19
1.3.6 训练和评估模型22
1.3.7 重复尝试不同模型及模型的最终选择25
1.3.8 部署模型25
1.4 性能衡量指标25
1.4.1 评估回归模型26
1.4.2 评估分类模型26
1.5 小结30
第2章 线性回归31
2.1 线性回归入门31
2.2 简单线性回归33
2.3 多元线性回归36
2.3.1 预测CPU性能37
2.3.2 预测二手汽车的价格38
2.4 评估线性回归模型40
2.4.1 残差分析42
2.4.2 线性回归的显著性检验45
2.4.3 线性回归的性能衡量指标47
2.4.4 比较不同的回归模型49
2.4.5 在测试集上的性能50
2.5 线性回归的问题51
2.5.1 多重共线性51
2.5.2 离群值52
2.6 特征选择53
2.7 正则化55
2.7.1 岭回归55
2.7.2 最小绝对值收缩和选择算子56
2.7.3 在R语言里实现正则化57
2.8 小结59
第3章 逻辑回归61
3.1 利用线性回归进行分类61
3.2 逻辑回归入门63
3.2.1 广义线性模型63
3.2.2 解释逻辑回归中的系数64
3.2.3 逻辑回归的假设65
3.2.4 最大似然估计65
3.3 预测心脏病66
3.4 评估逻辑回归模型69
3.4.1 模型的偏差70
3.4.2 测试集的性能73
3.5 利用lasso进行正则化73
3.6 分类指标74
3.7 二元逻辑分类器的扩展76
3.7.1 多元逻辑回归76
3.7.2 有序逻辑回归80
3.8 小结83
第4章 神经网络84
4.1 生物神经元84
4.2 人工神经元85
4.3 随机梯度下降86
4.3.1 梯度下降和局部极小值88
4.3.2 感知器算法88
4.3.3 线性分离91
4.3.4 逻辑神经元92
4.4 多层感知器网络92
4.5 预测建筑物的能源效率95
4.6 重新进行玻璃类型预测99
4.7 预测手写数字102
4.8 小结106
第5章 支持向量机108
5.1 最大边缘分类108
5.2 支持向量分类111
5.3 核和支持向量机113
5.4 预测化学品的生物降解115
5.5 交叉验证118
5.6 预测信用评分120
5.7 用支持向量机进行多类别分类123
5.8 小结123
第6章 树形方法124
6.1 树形模型的直观印象124
6.2 训练决策树的算法126
6.2.1 分类和回归树126
6.2.2 回归模型树131
6.2.3 CART分类树131
6.2.4 C5.0133
6.3 在合成的二维数据上预测类别归属关系134
6.4 预测纸币的真实性136
6.5 预测复杂的技能学习138
6.5.1 在CART树里对模型参数进行调优140
6.5.2 树模型中的变量重要性141
6.5.3 回归模型树实用示例142
6.6 小结143
第7章 集成方法144
7.1 装袋144
7.1.1 边缘和袋外观测数据145
7.1.2 用装袋预测复杂技能学习146
7.1.3 用装袋预测心脏病146
7.1.4 装袋的局限性150
7.2 增强151
7.3 预测大气中伽马射线的辐射152
7.4 利用增强算法预测复杂技能学习156
7.5 随机森林157
7.6 小结159
第8章 概率图模型161
8.1 图论入门161
8.2 贝叶斯定理163
8.3 条件性独立163
8.4 贝叶斯网络164
8.5 朴素贝叶斯分类器165
8.6 隐马尔可夫模型172
8.7 预测启动子基因序列174
8.8 预测英语单词里的字母特征179
8.9 小结182
第9章 时间序列分析184
9.1 时间序列的基本概念184
9.2 一些基本的时间序列185
9.2.1 白噪声185
9.2.2 随机漫步187
9.3 平稳性188
9.4 平稳时间序列模型189
9.4.1 移动平均模型189
9.4.2 自回归模型192
9.4.3 自回归移动平均模型193
9.5 非平稳时间序列模型194
9.5.1 整合自回归移动平均模型194
9.5.2 自回归条件异方差模型195
9.5.3 广义自回归条件异方差模型195
9.6 预测强烈地震196
9.7 预测猞猁的诱捕199
9.8 预测外汇汇率200
9.9 其他时间序列模型202
9.10 小结203
第10章 主题建模204
10.1 主题建模概况204
10.2 隐含狄式分布205
10.2.1 狄式分布205
10.2.2 生成过程208
10.2.3 拟合LDA模型209
10.3 对在线新闻报道的主题进行建模210
10.3.1 模型稳定性215
10.3.2 找出主题数量216
10.3.3 主题分布217
10.3.4 单词分布219
10.3.5 LDA扩展模型220
10.4 小结220
第11章 推荐系统222
11.1 评分矩阵222
11.2 协同过滤225
11.2.1 基于用户的协同过滤225
11.2.2 基于商品的协同过滤228
11.3 奇异值分解228
11.4 R语言和大数据231
11.5 预测电影和笑话的推荐232
11.6 加载和预处理数据233
11.7 对数据进行探索234
11.7.1 评估二元的top-N推荐236
11.7.2 评估非二元的top -N推荐239
11.7.3 评估每种预测方法241
11.8 推荐系统的其他方法242
11.9 小结243
第1章
准备预测建模
在第1章,我们首先要学习的内容是掌握模型的通用语言,并深入了解预测建模的过程。预测建模的很多方面会涉及统计学和机器学习的关键概念,本章会对这些领域的核心特征进行简短的介绍,它是预测建模者所需的基础知识。我们会重点强调的一个重要知识点是如何对适用于我们要解决的问题类型的模型进行评估。最后,我们会讲解第一种模型,即k近邻(k-nearest neighbor)模型,以及对预测建模者非常有用的一个R语言包caret。
1.1 模型
模型是预测分析学的核心,因此,本书一开始会讨论各种模型及其形式。简而言之,模型是我们要理解和分析的状态、流程或系统的一种表现形式。我们创建模型的目的是根据它得出推论以及(在本书中对我们更为重要的一点)对世界进行预测。模型的格式和风格有很多种,我们在本书中会探讨这种多样性中的一部分。模型可以是和我们能够观察或测量的数量值相关的一些方程,也可以是一套规则。我们大部分人在学校都熟悉的一个简单模型是牛顿第二运动定律。该定律表明,一个物体受到的合力会使之在合力作用的方向加速,加速度和合力大小成正比,和物体的质量成反比。
我们通常会用一个以字母F、m和a代表模型包含的量的方程总结这个规则。我们还利用大写希腊字母sigma (Σ)来表示要对受力求和,用字母上的箭头表示它们是向量(就是既有大小也有方向的量):
这个简单却强大的模型让我们能够对世界进行某些预测。例如,如果对已知质量的物体施加已知的作用力,我们就可以利用这个模型来预测它的加速度。如同大部分模型一样,该模型也会作出某些假设和概括。例如,它会假设物体的颜色、它所处环境的温度,以及它所处空间的准确坐标都和模型中列出的三个数值的相互影响无关。这样,模型就可以把待分析的流程或系统中的特定实例(在这个示例中,就是我们对其运动状况感兴趣的物体)的各种细节进行抽象化,从而把我们的关注点限制在重要的性质上。
牛顿第二运动定律并不是描述物体运动的唯一模型。物理学专业的学生很快会发现其他更复杂的模型,例如那些把相对论质量考虑进去的模型。总体而言,如果模型需要考虑更多数量的数值或具有更复杂的结构,它们往往就会更复杂。例如,非线性模型一般会比线性模型更复杂。在实际工作中,要决定采用哪个模型,并不仅仅是选择更复杂的模型而不是相对简单的模型那么简单。实际上,我们在本书中逐步讲解很多不同模型的过程中,会把这个问题作为核心主题反复进行讨论。为了直观地理解其中的道理,可以考虑一下这个案例:我们用来测量物体质量和施加作用力的设备是非常粗糙的。在这样的情况下,寄希望于采用更复杂的模型也许就没有什么意义,因为我们知道,由于输入中的这种噪声,这样的预测所额外增加的精确度对结果产生不了什么差别。我们需要采用更简单模型的另一种情况是,在应用中我们根本不需要更高的精确度。第三种情况是更复杂的模型涉及我们无法测量的数值。最后,如果因为复杂度的关系,模型会需要太长时间进行训练或预测,我们也不会使用更复杂的模型。
1.1.1 从数据中学习
在本书中,我们要学习的模型具有两种重要和本质的特征。第一个特征是我们不会用数学推理或逻辑归纳的手段从已知事实产生模型,也不会根据技术规范或商业规则来构建模型;相反,预测分析学领域是根据数据来构建模型的。更具体地说,我们会假设,对于要完成的任何具体预测任务,我们会把该任务以某种方式关联或以衍生出来的某些数据作为起点。例如,如果要创建一个预测某国各地全年降雨量的模型,我们也许已经收集了(或具备了收集的手段)关于不同地点降雨量的数据,同时会测量一些我们会关心的数值(例如海拔高度、经度和纬度)。我们创建的这个模型之所以有能力执行预测任务,是因为我们可以利用一组有限地点的降雨量测量数据的样例来预测我们没有收集过任何数据的地点的降雨量。
我们建立模型时所针对的问题的第二个重要特征是,在根据某些数据创建模型来描述特定现象的过程中,我们必然会遇到某些随机性的来源。我们称之为模型的随机成分(stochastic)或不确定成分(nondeterministic component)。有时候,我们要尝试建模的系统本身并不具有任何内在的随机性,而是数据包含了随机成分。数据中的随机来源的一个范例是在测量时对温度之类的数量进行读取时所产生的误差。不包含内在随机成分的模型称为确定型模型(deterministic model),比如牛顿第二定律就是这类模型的一个范例。随机模型则是假设在建模的过程中含有内在的随机源的模型。有时候,这种随机性的来源是来自这样一个事实:对最有可能影响系统的所有变量都进行测量是不可能的,因而我们只能利用概率来对其进行建模。纯粹随机模型的一个著名示例是扔一个六面无偏误的骰子。回顾一下,在概率中,我们使用随机变量(random variable)这个术语来描述某个实验或随机过程的特定结果值。在扔骰子示例中,我们可以定义随机变量Y作为每次扔骰子之后朝上的那一面的点数,由此产生了下面的模型:
该模型告诉我们,扔到一个特定数字(比如3)的概率是六分之一。注意,我们没有对具体扔骰子的结果进行明确的预测,相反,我们表明的是,每种结果出现的可能性是均等的。
概率是在日常交流中经常会用到,但有时也导致其准确含义产生混淆的一个术语。其实,对概率有很多不同的理解方式。两种常常被援引的解释方式是频率论概率(Frequentist probability)和贝叶斯概率(Bayesian probability)。频率论概率是和重复试验相关的,例如扔单面骰子。在这种情况下,如果该实验重复了无穷次,那么看到数字3的概率就是数字3出现的相对比例。贝叶斯概率则是和在看到特定结果时主观上置信或意外的程度相关的,因此可以用于对一次性事件赋予含义,例如某位总统候选人赢得大选的概率。在我们的扔骰子试验里,我们看到数字3时的意外程度和看到其他数字时是一样的。注意,在这两种情况下,我们讨论的仍然是同一个数字概率(1/6),而只是解释不同而已。
在扔骰子模型的情况下,我们没有任何变量需要测量。不过,在大部分情况下,我们会看到包含了一批需要测量的自变量的模型,而这些模型会用来对某个因变量进行预测。预测性建模依赖于很多不同的领域,因此,根据你所学习的具体学科,你会发现它关联了不同的一些领域。在展开讲解这个问题之前,让我们把一个数据集加载到R语言里。R语言预装了很多常被引用的数据集,我们要挑出其中可能最著名的一个,即鸢尾花数据集(iris data set)。
要查看R还绑定了哪些其他的数据集,我们可以利用data()命令获取一个数据集清单及每个数据集的简短描述。如果要修改一个数据集里的数据,我们可以把待分析的数据集名作为输入参数提供给data()命令,例如,data(iris)会重载iris数据集。
iris数据集由鸢尾花的3个不同品种的共150个样本的测量数据组成。在前面的代码中,我们可以看到对每个样本产生了4种测量值,分别是花瓣(petal)和萼片(sepal)的长度和宽度。iris数据集常被用来作为一个典型的标杆,用于评估根据前文中4种测量数据来预测鸢尾花样本品种的不同模型。花瓣长、花瓣宽、萼片长、萼片宽合在一起,在文献中会被称为特征(feature)、属性(attribute)、预测因子(predictor)、维度(dimension)或自变量(independent variable)。在本书中,我们会优先使用特征这个词,但其他术语也同样是可以用的。类似地,该数据框中的品种(species)列是我们要利用模型来进行预测的,因此它被称为因变量(dependent variable)、输出(output)或目标(target)。同理,为了保持一致性,在本书中我们会优先使用其中之一,即输出。该数据框的每一行对应单个数据点,被称为一条观测数据(observation),不过它通常会包括对一组特征的观测值。
由于我们将要用到一些数据集(比如前面讲解的iris数据集)来构建预测模型,因此设定一些符号惯例会大有帮助。这里要讲解的惯例在大部分文献里很常见。我们会用大写字母Y来代表输出变量,带下标的大写字母Xi来表示第i个特征。例如,在我们的iris数据集里,我们有4个特征,分别表示为X1到X4。我们会用小写字母表示单条观测数据,因此x1就对应第一条观测数据。注意,x1本身是由特征分量xij组成的一个向量,因此x12就代表第一条观测数据中的第二个特征值。为简单起见,我们会尽量谨慎使用双后缀,也不会使用箭头或其他向量标记的形式。通常情况下,我们会讨论观测数据或特征两者之一,因此变量的大小写就能让读者明确到底它指的是这两种情况中的哪一个。
在考虑对应某个数据集的预测模型时,我们通常会假设,对于具有n个特征的模型,有一个真实或理想的函数f可以把特征映射到输出:
我们会把这个函数称为目标函数(target function)。在实践中,我们只能用可以获得的数据来训练我们的模型,这就需要创建我们自己的函数,寄希望于它对于目标函数是个良好的评估。我们可以通过在符号f上插入符号“^”来表示我们的预测函数,对于输出Y也同样是如此表示的,因为我们的预测函数的输出就是预测的输出。不幸的是,对于所有(我们数据中的或一般情况下的)观测数据而言,我们的预测输出并不总是和实际输出相符:
有了这些背景,我们基本上就可以把预测建模的过程总结为:创建一个函数来预测数量值,并使它产生的相对目标函数的误差最小化。到这里我们能问的一个好问题就是,误差是从何而来?换句话说,为什么我们通常无法通过分析一个数据集,准确地重现它背后的目标函数?
这个问题的答案是:在现实情况下,有几种潜在的误差源是我们必须面对的。记住,数据集里的每条观测数据包含了n个特征的值,因此我们可以把观测数据看作几何学中n维特征空间里的点。在这个空间里,根据目标函数的确切定义,我们的目标函数必须经过这些点。如果现在思考用一个函数来拟合有限个点的一般性问题,我们会很快发现,实际上有无限个函数可以通过这些点的集合。预测建模的过程会涉及我们用于数据的模型类型的选择,因而会限定我们用于拟合数据的目标函数的范围。同时,无论我们选择什么模型,数据内在的随机性都无法消除。这些思想就引出了建模过程中会遇到的误差类型的重要差别,具体而言,它们可以分为可化简误差(reducible error)和不可化简误差(irreducible error)。
可化简误差本质上是预测建模者能够通过选择模型结构使之最小化的误差,选择的模型应该能对要建模的过程作出有效的假设,且其预测函数和基础目标函数的形式相同。例如,我们在下一章会看到,线性模型会给特征值之间加入一个线性关系来产生输出。这个约束性的假设意味着无论我们采用什么训练方法,拥有多少数据,投入多少计算能力,如果在现实世界里的特征并非线性相关,那么我们的模型就必然对某些观测数据产生误差。反过来,一个不可化简误差的示例是当我们尝试根据不充分的特征集来创建模型时产生的。这种误差通常是普遍的而不是异常情况下的。通常,发现要采用哪些特征是创建精确模型的工作中最耗时的活动。
有时候,我们知道的重要特征不一定能直接测量。其他时候,要收集太多特征的数据也许就是不现实的或者成本太高了。此外,这类问题的解决办法也不仅仅是尽可能增加特征的问题。给模型增加更多特征会让它变得更复杂,而且还存在因为引入和输出无关的特征而给模型里加入噪声的风险。这样还意味着我们的模型函数会有更多的输入,因而变成在更高维空间里的函数。给一个模型加入更多特征的一些潜在的实际后果包括:增加训练模型所需的时间,增大收敛到最终解决方案的难度,以及在特定情况下(例如特征值是高度相关的)实际上会降低模型的精确度。最后,另一个我们必须接受的不可化简误差来源是测量特征产生的误差,即数据本身可能就是有噪声的。
为了使可化简误差最小化,不仅要选择合适的模型,也要确保模型的训练是正确的。因此,可化简误差也可能源于没有找到适用于模型假设的特定函数。例如,即使我们准确地选择了对一个线性模型进行训练,我们可以使用的特征也会有无限多的线性组合。正确地选择模型的参数(在该示例中就是线性模型的系数)也是使可化简误差最小化的一个方面。当然,准确训练模型的一个重要部分是利用良好的优化过程来对模型进行拟合。在本书中,我们对要学习的每个模型的训练方式至少会给出一个总体性的直观讲解。总体上,我们会避免深入钻研优化过程的数学原理,但是会为感兴趣的读者提供相关文献作为参考资料。
1.1.2 模型的核心组成部分
到目前为止,我们已经明确了模型背后的一些核心概念,以及讨论数据的通用语言。在本节,我们要了解统计学模型的核心组成部分有哪些。这些主要组成部分通常是:
带有需要调优参数的一组方程
代表我们建模所针对系统或过程的一些数据
描述该模型拟合优度的一个概念
更新参数以改善该模型拟合优度的一种方法
正如我们会在本书中看到的,像神经网络、线性回归和支持向量机这样的大部分模型都有描述它们的特定参数化方程。让我们来观察一个要根据3个我们称为X1、X2和X3的输入特征来预测输出Y的线性模型:
该模型正好是用一个方程描述的,并且该方程提供了模型的线性结构。该方程由4个参数(在本例中也称为系数)进行了参数化。在下一章,我们会看到这些参数扮演的确切角色,但是对于目前的讨论,重要的是注意到线性模型是参数化模型的一种样例。参数集合通常会比可用的数据量小得多。
有了一组方程和一些数据,我们就可以来讨论模型的训练了。它涉及为模型的参数赋值,从而让模型更精确地描述这些数据。我们通常会运用特定的标准衡量指标来表示模型对数据的拟合优度,也就是模型描述训练数据的精确程度。训练过程通常是一个涉及对数据进行计算的迭代程序,通过它可以为参数计算新值,从而增加该模型的拟合优度。例如,一个模型可能有一个目标或误差函数。通过对它求微分并设置微分为零,我们就可以找到产生最小误差的参数组合。一旦我们完成这个过程,就称该模型为完成训练的模型,并称该模型根据数据进行了学习。这些术语是从机器学习学科派生出来的,虽然通常在统计学学科也有类似的说法,而统计学对于该过程也有自己的术语。在本书里主要会采用来自机器学习领域的术语。
1.1.3 我们的第一个模型:k近邻
为了正确地理解本章的某些思想,我们会讲解本书的第一个模型,即k近邻模型,它通常简称为kNN。简而言之,这种简单的方法实际上会避免构建一个明确的模型来表示数据中的特征如何组合从而产生一个目标函数。相反,它依赖于一个概念,也就是如果我们要对之前没有见过的数据点进行预测,就要在原始训练数据中查找k个和新数据点最相似的观测数据。然后就可以针对已知的这k个邻居的目标函数值,利用某种求均值的技术来计算出一个预测值。让我们利用iris数据集,通过示例的方式来理解这个思路。假设我们收集了一份未识别的新鸢尾花样本,它具有如下测量值:
我们打算采用kNN算法来预测应该用鸢尾花的哪个品种来标识新样本。运用kNN算法的第一步,是确定新样本的k个最邻近样本。为此,我们要给出关于两条观测数据之间相似性的更准确定义。常用的方法是计算特征空间里两条观测数据之间的数量化距离。直觉上,两个相似的观测数据会在特征空间里相互接近,因此它们之间的距离会比较小。要在特征空间里计算两条观测数据之间的距离,我们经常利用欧几里得距离(Euclidean distance),它是两点之间直线的长度。两条观测数据x1和x2之间的欧几里得距离的计算方法如下:
回想一下,在前面的公式里的第二个下标j对应的是第j个特征。因此,这个公式实质上是告诉我们,对于每个特征,取两条观测数据之差的平方,并把所有这些平方差叠加,然后对结果取平方根。距离还会有其他的很多种定义,但这一种是在kNN领域最常见到的。我们会在第11章里看到更多的距离衡量指标。
为了给鸢尾花新样本找到最邻近样本,我们就必须计算它到iris数据集里每个点的距离,然后对结果进行排序。首先,对鸢尾花数据构造子集,让它只包含我们需要的特征,这样就排除了品种(species)列,而这一列正是我们想要预测的。然后,要自定义一个函数来计算欧几里得距离。下一步,调用apply()函数,运用自定义函数来对数据框里的每个鸢尾花观测数据进行计算。最后,调用R语言的sort()函数,并设定其index.return参数为TRUE,这样就可以把每个计算的距离和它在鸢尾花数据框里的行号索引对应起来。
$x属性包含了鸢尾花新样本和鸢尾花数据框里的观测数据之间计算距离的实际值。$ix属性则包含了对应观测数据的行号。如果要找出距离最近的5个邻居,我们就可以把$ix属性中的前5个数据项作为行号,对原始的鸢尾花数据框划分子集:
正如我们所见,5个最邻近的新样本中有4个属于versicolor品种,而另一种是virginica品种。对于这类要挑出一个类别标签的问题,可以把多数票作为计算均值的技术,从而得出我们的最终预测。因此,我们会把新样本标记为属于versicolor品种。注意,设定k的值为奇数是一个好的思路,因为这样需要应付票数平局问题的可能性更小(在输出的标签数为2时,则完全消除了平局)。对于票数平局的情况,惯例上的解决办法是在票数平局的标签中进行随机抽取。注意,在该过程中,我们根本没有尝试去说明这4个特征是如何与输出相关联的。因此,我们经常把kNN模型称为一种惰性学习方法(lazy learner),因为实际上它只是记住了训练数据,并在预测过程中直接使用它。对于kNN模型,还有更多内容可以讨论,但首先我们要回到关于模型的一般性讨论,并讲解对模型进行分类的不同方式。
1.2 模型的类型
掌握了模型基本组成部分的总体思路之后,就可以进一步探讨建模者用来给不同模型分类的一些常用区分方式。
1.2.1 有监督、无监督、半监督和强化学习模型
我们已经观察了iris数据集,它由4个特征和1个输出变量(即品种species变量)组成。在训练数据中包含了所有观测数据的输出变量值,这就是有监督学习(supervised learning)环境的关键特性,它代表了我们会遇到的大部分常见场景。简而言之,在有监督学习的环境下训练模型的优势在于,对于训练数据中的数据点,已经有了要预测变量的正确答案。正如在上一节所见,kNN就是一个使用有监督学习方法的模型,因为该模型对一个输入点的预测是通过组合该点的一小批邻近点的输出变量值来进行的。在本书中,主要会重点讲解有监督学习。
按照上面这种根据能否获得输出变量值来区分不同模型的方式,我们还会看到第二种场景,即输出变量不明确的情况。这种场景被称为无监督学习(unsupervised learning)的环境。iris数据集的一个无监督版本只包含4个特征。如果我们得不到品种(species)这个输出变量,那么显然就无从知道每条观测数据对应的是哪个品种。实际上,我们都不会知道鸢尾花在该数据集里有多少个品种,或每个品种里各有多少观测数据。初看上去,似乎在缺少这个信息的条件下开展不了什么有用的预测性工作。实际上,我们能做的是利用我们能获得的4个特征,查看数据并根据它们之间的相似程度给观测数据分组。这个过程被称为聚类(clustering)。聚类的一个好处是我们可以在数据中发现数据点的自然分组,例如,我们也许能发现iris数据集的一个无监督版本里的鸢尾花样本形成了3个互不相同的分组,而它们对应着3个不同的品种。
在根据能否获得输出变量划分出来的无监督和有监督方法两个极端之间,还有半监督(semi-supervised)和强化学习(reinforcement learning)两种环境。半监督模型是利用一部分(通常很少)包含输出变量值而其他部分完全无标签的数据来创建的。很多这类模型会首先利用数据集的有标签部分来粗略地训练模型,然后引入无标签数据,用训练的模型对其产生预测的标签。
在强化学习环境里无法获得输出变量,但提供了其他和输出变量直接关联的信息。这样的一个示例是,根据已完成的棋谱数据,对赢得国际象棋比赛的下一步最佳走法进行预测。在训练数据中并没有单步走法的输出值,但对于每一局比赛,每个玩家下棋的整体序列产生赢或输的结果。由于篇幅所限,在本书中不会讲解半监督和强化学习的内容。
1.2.2 参数化和非参数化模型
在前面已经讲到了,我们遇到的大部分模型都是参数化模型(parametric model),我们还看到了一个简单线性模型的示例。参数化模型的一个特征是它们往往会定义一个函数形式(functional form)。这意味着它们把在所有可能的函数中选择目标函数的问题化简为一个具体的函数族,这个函数族就构成了一个参数集合。选择能定义模型的特定函数,实质上就是为这些参数选取确切的值。因此,回到那个有3个特征的线性模型示例,我们可以看到,有下面两种可能的参数选择(当然,选择是无穷的,这里我们只是演示了两个具体的选择):
Y1=0.68+2.54X1+9.12X2-0.13X3
Y2=4.56+3.08X1-2.29X2+1.11X3
这里,我们给输出Y使用了一个下标来注明两种不同的参考模型。这其中的哪一个会是更优的选择?答案是,这取决于数据。如果我们把每个模型运用于数据集里的观测数据,就会对每条观测数据产生预测输出。对于有监督学习而言,训练数据中的每条观测数据都标记了输出变量的正确值。为了评估模型的拟合优度,可以定义一个误差函数,用它来衡量我们的预测输出和正确输出的差异程度。然后,这个误差函数就可以用来对案例里的两个候选模型进行挑选,但是它更一般的作用在于通过渐进地逐步优化候选模型,对模型进行迭代式的改进。
某些参数化模型比线性模型更灵活,这意味着它们可以用来表达更多种可能的参考函数。线性模型会要求输出是输入特征的线性加权组合,这是比较严格的。可以直观地看出,更灵活的模型往往能让我们更精确地对输入数据进行近似,不过,当观察到过拟合问题时,我们就会发现这样并不总是好事。更灵活的模型往往也更复杂,因而对它们的训练往往会比训练不那么灵活的模型更难。
模型并不一定都是参数化的,实际上,没有参数的那一类模型被(名正言顺地)称为非参数化模型(nonparametric model)。非参数化模型一般对输出函数的具体形式不作任何假设。构建一个没有参数的目标函数有不同的方式。样条(spline)是非参数化模型的常见示例。样条背后的核心思想是,对于形式未知的输出函数,我们设想它是准确定义于训练数据的所有观测数据对应的点上。在这些点之间,可以利用平滑多项式函数对输出函数进行局部插值。实质上,输出函数是以分片方式在训练数据的点之间的空间中构建的。不像大部分场景那样,样条可以对训练数据确保百分之百的精确度,然而,在训练数据中存在某些误差是完全正常的。另一个非参数化模型的范例是我们已经看到的k近邻算法。
1.2.3 回归和分类模型
回归(regression)和分类(classification)模型的差别和我们要预测的输出类型有关,并且通常会关联到有监督学习。回归模型会预测一个数量或量化的值,例如证券市场指数、降雨量或某项目的成本。分类模型则会从一个有限的(然而可能还是会很大)类别或分类的集合中预测其中一个值。分类的示例包括预测某个网站的主题、某用户会输入的下一个单词、某人的性别,或根据一系列症状判断某病人是否患有某种疾病。我们要在本书中学习的大部分模型都刚好属于这两种类型之一,虽然有一些模型可以用于解决这两类问题,比如神经网络。这里要重点强调的是,这种区分只是针对输出的,而不是针对用来预测输出的特征值本身是定量还是定性。总体而言,在回归和分类等模型中使用的特征,既可以编码为定性的,也能编码为定量的特征形式。之前,当我们构建kNN模型来根据鸢尾花样本测量值预测鸢尾花品种的时候,就是在解决一个分类的问题,因为我们的品种输出变量只能从3个不同的标签中选取1个。kNN方法也可以被用到回归环境里;在这种情况下,模型可以通过取均值或中位数,把被选中的最近邻的输出变量的数量值进行合并,从而产生其最终预测结果。因此,kNN也是一种可以用于回归和分类两种环境的模型。
1.2.4 实时和批处理机器学习模型
预测模型可以利用实时机器学习(real-time machine learning),也可能涉及批处理学习(batch learning)。实时机器学习这个术语可以参考两种不同的场景,虽然它的思路明显不是指实时机器学习必须实时(也就是说,在一个预先设定的、通常比较小的时间范围内)作出预测。例如,一个神经网络模型在完成训练后,只需要少量的计算(取决于输入和网络层的数量)就能产生输出的预测。不过,这并不是我们所讨论的实时机器学习的含义。
运用实时机器学习的模型的一个范例是天气预报,它会利用从各种气象设备获取其读数的数据流。在这里,模型的实时性是指这样的事实:我们为了进行天气预报,只选取在最近的时间窗口里获取的一批读数。时间回溯得越远,读数的相关度就越低,因此我们可以选取最近的信息来进行预报。当然,要在实时环境下使用的模型也必须能快速计算出它们的预测结果—如果系统需要几个小时根据早晨的测量值计算晚上的预测结果那这就没多大用处,因为等到计算完成的时候,预测结果已经没什么价值了。
说到利用最近一段时间范围内获取的信息来进行预测的模型,通常指那些已经用一些能代表所有将来要预测的数据的典型数据进行了训练的模型。如果描述的是能探测目标过程的性质以某种方式变化的模型,会产生对实时机器学习的第二种诠释。在本书中讨论到时间序列模型时,我们会把重点放在第一种情况的示例上。
1.3 预测建模的过程
通过观察模型的某些不同特征,我们已经对预测建模过程的各种步骤有所了解。在本节,我们要顺序讲解这些步骤,并理解每个步骤是如何对该任务的整体成功起作用的。
1.3.1 定义模型的目标
简而言之,每个项目的第一步是准确找出期望的结果是什么,因为这样有助于引导我们在项目的进展过程中做出正确的决定。在一个预测分析学项目里,这个问题包括深入研究我们要进行的预测的类型,以及从细节上去理解任务。例如,假定我们要尝试创建一个模型来预测某公司的雇员流失。我们首先需要准确定义这个任务,同时尽量避免把问题发散得太广或收敛得过细。我们可以把雇员流失的情况用全职的新雇员在入职后的最初六个月之内跳槽的比例来衡量。注意,一旦合理地定义了问题,对于采用什么数据开展工作的思考而言,我们就已经取得了一些进展。例如,我们不需要对兼职的合同工或实习生收集数据。这项任务也意味着只需要从所在公司收集数据,但同时我们也认识到,该模型不一定适用于为其他公司的职员进行预测。如果只对流失情况感兴趣,也就意味着不需要对员工的工作效率或病假情况作出预测(不过,问一下负责我们要创建的模型的主管领导也没有坏处,免得将来出问题)。
一旦我们对要创建的模型有了足够明确的思路,下一个要问的逻辑性问题就是我们想要达到的性能是怎样的,以及该如何衡量它。那就是说,需要为模型定义一个性能指标,以及满足要求的性能的最低阈值。在本书中,我们会针对评估模型性能的方法进行相当详细的讲解。就目前而言,我们要强调的是,虽然在用某些数据训练模型之后再讨论其性能评估问题的情况并不少见,但是在实践中要记住的很重要的一点是,定义模型的期望值和性能目标是预测建模者在一开始就必须和项目利益相关方讨论的问题。模型从来都不是完美无瑕的,建模者很容易陷入尝试改善性能的无限循环中。清楚的性能目标不仅对于我们决定采用哪些方法有指导意义,也有助于使我们了解模型是否满足要求。
最后,还需要考虑到收集数据的时候能够获得的数据,以及使用该模型时所处的场景。例如,假定我们知道,雇员流失模型要被用作决定是否聘用某个应聘者的因素之一。在这个背景下,就只能从现有雇员的数据中收集他们被聘用之前的那部分数据。我们不能使用他们的第一次绩效评价的结果,因为对于应聘者来说,这些数据还不存在。
1.3.2 收集数据
训练一个模型用于预测往往是一种数据密集型的探险,如果这里面有一种东西是多多益善的话,那就是数据。数据收集经常是整个过程中最耗费时间和资源的部分,这也就是要在第一步把定义任务和正确识别要收集的数据两项工作做好的重要性所在。当我们学习诸如逻辑回归之类模型的工作原理时,通常会通过一个示例数据集来进行,这也是我们在本书中遵循的主要方法。不幸的是,没有模拟收集数据这个过程的方法,这样看起来我们的大部分精力是花在训练和改进模型上的。在利用已有数据集来学习模型的时候,必须牢记,通常有很多精力是花在收集、甄选和预处理数据上的。我们会在后续章节更仔细地观察数据预处理。
在收集数据的时候,要一直关注我们收集的数据种类是否正确。很多在预处理阶段进行的合理性检查在收集过程中也适用,这是为了让我们能发现是否在该过程的早期犯了错误。例如,总是要检查对特征的测量是否正确,是否采用了正确的度量单位。还必须确保数据是从足够近期、可靠且与手头任务相关的数据源收集的。在上一节讲解的雇员流失模型里,当收集以往雇员的信息时,必须确保对特征的度量是一致的。例如,在度量某个人在公司里工作了多少天的时候,必须一致地使用日历天或工作日。在收集日期数据的时候还必须进行格式检查,例如当某人加入或离开公司的时候,要统一使用美国日期格式(月份后面是日期)或欧洲格式(日期后面是月份),这两种格式不能混用,否则,一个类似03/05/2014的日期就会带有歧义。我们还要尽可能从较大范围的样本获取信息,以免在数据收集过程中引入隐藏的偏误。例如,如果我们需要雇员流失的通用模型,就不能只从女性雇员或者单个部门的雇员收集数据。
我们如何知道已经收集了足够多的数据了?一开始,在收集数据的过程中,没有创建和测试任何模型的时候,还无从得知最后需要多少数据,而且这时候也没有任何简单的经验规则可以照搬。不过,我们可以预见问题的某些特征会需要更多的数据。例如,在创建一个可以从3个类别中学习预测的分类器的时候,可能就需要检查是否有足够的观测数据来代表每个类别。
输出类别的数量越大,需要收集的数据就越多。类似地,对于回归模型,对照预测结果的范围来检查训练数据里的输出变量范围也是有用的。如果要创建一个覆盖很大输出范围的回归模型,与同等精确度需求下覆盖较小输出范围的回归模型相比,也需要收集更多的数据。
另一个有助于估算需要多少数据的重要因素就是模型的性能要求。直观地说,模型所需要的精确度越高,必须收集的数据就越多。还必须注意,改善模型的性能并非一个线性的过程。把精确度从90%提高到95%往往会比从70%到90%的飞跃还需要更多的努力以及多得多的数据。有更少参数或设计更简单的模型,例如线性回归模型,需要的数据往往比类似神经网络等更复杂的模型更少。最后,要放到模型里的特征数目越多,必须收集的数据量就越大。
此外,必须注意到一个事实:对额外数据的需求也不会是线性的。也就是说,创建一个带有2倍数量的特征的模型往往需要的数据量远远多于原有数据量的2倍。考虑到模型所需要处理的输入的不同组合的数量,这个道理应该是显而易见的。增加2倍的维度数量会导致远大于2倍的可能的输入组合数量。为了理解这个道理,假定有个模型带有3个输入特征,每个特征可以取10个可能的值,我们就有了103=1000种可能的输入组合。如果加入1个额外的特征,它也可以取10个可能的值,就把可能的组合增加到了10 000个,这就比原先输入组合数量的2倍大多了。
有一些研究工作在尝试从更定量的视角讨论某个数据集是否有足够数据的问题,但我们在本书中没有时间讲解它们。更深入学习预测建模的这个领域的一个好的起步点是研究学习曲线(learning curve)。简而言之,借助这种方法,通过先从小部分数据起步并逐渐增加更多数据,可以对同一组数据创建连续的模型。这里的思路是,通过这个过程,如果预测的精确度对于测试数据总是在改善而不是递减,我们就很可能可以通过获取更多数据受益。作为数据收集阶段的最后一个提示是,即使我们认为已经有了足够的数据,在选择终止收集并开始建模之前,也总是应该考虑为了获得更多数据会给我们带来多少成本(从时间和资源方面而言)。
1.3.3 选取模型
一旦明确了预测任务,并得到了合适的数据,下一步就是选取我们的第一个模型。首先,根本就没有什么最好的模型,甚至运用某些经验法则的最优模型也是不存在的。在大部分情况下,明智的做法是在起步阶段先用一个简单模型,例如在分类任务的情况下采用朴素贝叶斯模型(Na飗e Bayes model)或逻辑回归(logistic regression),或在回归的情况下采用线性模型(linear model)。简单的模型会让我们得到一个初始的基线性能,然后我们就可以努力地改进它。起步的简单模型也有利于回答一些有用的问题,例如有多少特征对结果有贡献,也就是说,每个特征的重要性有多大,它和输出是正相关还是负相关。有时候,这种分析本身就是产生第一个简单模型并延续到用于最终预测的更复杂模型的依据。
有时候,简单模型可能就对我们手头的任务给出了足够的精确度,因此我们就无需投入更多精力来获得那么一丁点额外的改进。另一方面,简单模型通常对于任务来说是不够的,这就要求我们挑出更复杂的模型。选择更复杂模型而非简单模型并不总是简单直接的决定,即使我们可以看到复杂模型的精确度会高很多。具体的一些限制条件(例如现有的特征数量或能够获得的数据)都可能阻碍我们转而使用更复杂的模型。要了解如何选择一个模型,就涉及理解工具箱里的那些模型的各种优势和限制。对于在本书遇到的每种模型,我们都要特别注意学习这些要点。在实际项目中,为了帮助指导我们的决定,需要经常回顾任务需求,并提出一些问题,例如:
承担的任务是什么类型的?某些模型只适合特定任务,例如回归、分类或聚类。
模型是否需要解释它的预测结果?某些模型,例如决策树,更善于给出易于解读的要点,用于解释它们作出具体预测的原因。
是否有任何关于预测时间的限制?
是否需要频繁更新模型?从而导致的训练时间是否很关键?
如果特征高度相关,该模型是否能正常工作?
该模型是否能根据特征的数量和得到的数据量有效地进行伸缩?例如,如果我们有海量的数据,我们可能需要一个训练过程能够并行化的模型,以便利用并行计算机架构。
在实践中,即使初步分析指向了特定模型,在作出最终决定之前,也很有可能需要尝试很多其他选项。
1.3.4 数据的预处理
在使用数据训练模型之前,通常需要对它进行预处理。在本节,我们会讨论一批一些常用到的预处理步骤。其中一些是为了检测并解决数据中的问题所必需的,而其他的则是用于变换数据让它们适用于选择的模型。
探索性的数据分析
一旦有了一些数据,并决定针对某个具体模型开始工作,首先需要做的事情就是查看数据本身。这不一定是流程中非常结构化的一部分,它主要包括理解每个特征测量的是什么,以及对我们已经收集的数据进行初步了解。理解每个特征代表了什么,以及它的度量单位是什么,这些步骤确实是非常重要的。对其度量单位的一致性进行检查也是一个很好的思路。我们有时把这项对数据进行探索并可视化的分析过程称为探索性数据分析(exploratory data analysis)。
一个很好的实践是对我们的数据框调用R语言的summary()函数,获得每个特征的一些基础衡量指标,例如均值和方差,还有极大极小值等。有时候很容易通过数据中的不一致发现在数据收集过程中有一些错误。例如,对于一个回归问题,多条观测数据具有相同的特征值,输出却大相径庭,这就有可能(取决于应用的情况)是一个信号,表明存在错误的测量值。类似地,了解特征的测量过程中是否存在显著的噪声也是一个好的思路。这样有时候可能会产生不同的模型选择,或意味着该特征必须被忽略。
另一个对数据框里的特征进行概括的有用函数是psych包里的describe()函数。它返回有关每个特征的偏斜程度,以及针对位置(例如均值和中位数)和分布情况(例如标准差)的常规衡量指标。
探索性的数据分析的一个重要部分是利用绘图把数据可视化。根据情景的不同,有多样化的绘图可以供我们使用。例如,为数值特征创建箱线图(box plot)来对范围和四分位数(quartiles)进行可视化。条形图(bar plot)和马赛克图(mosaic plot)可以用于数据的分类输入特征在不同组合下的比例情况进行可视化。本书不会在信息可视化方面探讨更多的细节,因为它是一个自成体系的领域。
R是一个创建可视化的完美平台。基础R软件包提供了很多不同的函数来对数据进行绘图。有两个能创建更多高级绘图的杰出扩展包,它们是lattice和ggplot2。有两本关于这两个扩展包的参考书,其中还讲解了有效进行可视化的原理,它们是《Lattice: Multivariate Data Visualization with R》和《ggplot2: Elegant Graphics for Data Analysis》,它们都是Springer出版社出版的,属于Use R!系列教材之一。
特征变换
通常,我们会发现数值特征是用完全互不相同的进位制来衡量的。例如,用摄氏度来衡量某人的体温,因此它的数值通常会在36~38的范围之内。同时,我们也会测量某人每微升血液中的白细胞计数。这个特征一般是以千来取值的。如果要把这些特征作为某个算法(例如kNN)的输入,会发现白细胞计数的较大数值会在欧几里得距离的计算中处于支配地位。在输入里可能还有一些对于分类重要且有用的其他特征,但如果衡量它们的进位制产生的是比1000小很多的值,我们实际上就主要是根据单个特征(也就是白细胞计数)来选取我们的最邻近的样本。这种问题通常会出现于并影响到很多模型,而不仅仅是kNN。我们处理这种问题的方法是,在将这些输入特征用于我们的模型之前,对这些特征进行变换(也称为比例缩放)。
我们会讨论特征比例缩放的三种流行方法。当我们知道我们的输入特征接近正态分布的时候,一种可能用到的变换是Z评分归一化(Z-score normalization),它的原理是对特征减去其均值并除以其标准差:
E (x)是x的期望或均值,标准差是x的方差的平方根,该方差表示为Var (x)。注意,这种变换的结果是,新的特征将以均值0为中心,方差为1。另一种可能的变换更适合输入符合均匀分布的情况,就是按比例缩放所有的特征和输出,让它们处于单个区间内,典型的是单位区间[0,1]:
第三种方法被称为博克斯-考克斯变换(Box-Cox transformation)。这种变换通常运用在输入特征呈现高度偏斜(不对称)而我们的模型要求输入特征符合正态分布或至少是对称分布的情况:
因为是在分母里,所以它必须取非0的值。该变换实际上对值为0的是有定义的:在本例中,它就是输入特征的自然对数,即ln(x)。
注意,这是一种参数化的变换,因而需要给指定一个具体的值。有多种方式可以根据数据本身给估算一个适当的值。我们会为此讲解一种进行这种估算的技术,称为交叉验证(cross-validation),我们会在本书第5章里遇到它。
博克斯-考克斯变换的原始参考资料是在1964年发表在皇家统计学会杂志(the Journal of the Royal Statistical Society)上的一篇论文,名为《An analysis of Transfor-mations》,作者是G. E. P. Box和D. R. Cox两人。
为了直观感受这些变换在实践中的应用情况,我们要在iris数据集的Sepal.Length特征上进行尝试。不过,在开始之前,要介绍需要运用的第一个R扩展包,即caret。
caret包是一个具备多种用途的非常有用的组件。它提供了预测建模过程中常用的一组辅助函数,覆盖了从数据预处理和可视化到特征选择和再抽样技术。它的特性还包括:具有针对很多预测模型函数的统一接口,并提供了并行处理的功能。
利用caret组件进行预测建模的权威参考资料是一本名为《Applied Predictive Modeling》的书,该书由Max Kuhn和Kjell Johnson编写,Springer出版社出版。Max Kuhn是caret包本身的主要作者。该书还有一个配套的网站:http:// appliedpredic-tivemodeling.com。
当对用来训练模型的数据进行输入特征的变换时,必须记住,对于在预测时要用到的后续输入,也需要对其中的特征运用同样的变换。因此,利用caret包对数据的变换是分两步完成的。在第一步,调用preProcess()函数保存要运用到数据上的变换参数,然后在第二步,调用predict()函数对变换进行实际的计算。我们往往会只调用一次preProcess(),然后在每次需要对某些数据进行同样的变换时就调用predict()函数。preProcess()函数会把一个带有某些数量值的数据框作为它的第一个输入,我们也会给method参数指定一个包含要运用的变换名的向量。然后predict() 函数会一并读入前面函数的输出和我们要变换的数据—对于训练数据本身,它很可能是同一个数据框。让我们看看这整个过程的实际运用:
下载样例代码
你可以从你在http://www.packtpub.com的账号下载你已经购买的所有Packt出版书籍的样例代码文件。如果你是在其他地方购买本书,你可以访问http://www.packtpub.com/support并注册,文件会直接通过电子邮件发送给你。
我们已经创建了鸢尾花数据的数值特征的3个版本,它们的区别在于每个版本用到了不同的变换。为了把变换的效果可视化,我们可以通过调用density()函数,对每个比例缩放后的数据框绘制萼片长度(Sepal.Length)特征的密度图,并绘制结果,如下所示:
注意,Z评分(Z-score)和单位区间(unit interval)变换在对数值进行了平移和缩放的同时保留了密度图的总体形态,而博克斯-考克斯变换还改变了整体形态,产生了比原始图偏斜更少的密度图。
类别特征的编码
从线性回归到神经网络,很多模型都要求所有输入是数值型的,因此我们经常需要一种把分类字段在某个数值标尺中进行编码的方法。例如,如果我们有一个尺寸特征,它从一个集合{small, medium, large}中取值,我们就会需要用数值1、2、3来分别代表它。在有序分类的情况下(例如刚才描述的尺寸特征),这种映射应该是合理的。
数字3是这个数值区间里最大的,它对应的是large分类,相比用取值2代表的medium分类,它离用数字1代表的small分类更远。这个数值区间的用法只是一种可能的映射,尤其是它强制让medium分类与large和small分类的间距相等,这样做是否合适,取决于我们对于具体特征的认知。在无序分类的情况下,例如品牌或颜色,总体上我们会避免把它们映射到单个数值区间里。例如,如果把集合{blue, green, white, red, orange}相应地映射为数字1到5,那么这个区间就是随意的,没有什么理由让red离white更近而离blue比较远。为了克服这个问题,我们创建了一系列指示特征Ii,它的形式如下:
我们需要和分类一样多的指示特征,因此对于颜色示例,可以创建5个指示特征。在这个情况下,I1就可以是:
通过这种方式,原始的颜色特征会被映射为5个指示特征,对于每条观测数据,这些指示特征中只有一个会取值为1,其他的都为0,这是因为在原始特征中,每条观测数据只包含一种颜色值。指示特征是二进制的特征,因为它们只会取两个值:0和1。
我们经常还会遇到另一种方法,即利用n-1个二进制特征对一个因素的n个级别进行编码。这是通过选择一个级别作为参考级别,它用所有n-1个二进制特征都取值为0的情况来表示。这样对于特征数量而言更为经济,并且可以避免在它们之间引入一种线性依赖关系,但是它违反了所有特征相互之间等距的性质。
缺失数据
有时候,数据会包含缺失的值,这是因为对于某些观测数据,某些特征是无法获取或无法合适地测量的。例如,假定在iris数据集里,缺少了某条观测数据里的花瓣长度(petal length)的测量值。这样,在这个鸢尾花样本的Petal.Length特征里就会有一个缺失值。大部分模型并不先天具备处理缺失数据的能力。通常情况下,数据里的缺失值会用空条目或NA符号表示。我们必须检查是否在数据中实际存在着缺失值,但被错误地分配了一个像0这样的(通常是非常合法的)特征值。
在决定如何处理缺失数据之前,尤其是当我们的方法是直接丢弃带有缺失值的观测数据时,必须认识到具体缺失的值可能会符合某个模式。具体而言,我们经常要区分不同的所谓缺失值机制。在理想的随机完全缺失(Missing Completely At Random,MCAR)情况下,缺失值的出现是独立于它们所属的特征以及其他特征的真实值的。在这种情况下,如果某个鸢尾花花瓣长度缺失了一个值,那么这个缺失的情况是独立于该花瓣实际有多长以及其他任何特征(例如该观测数据是否来自versicolor或setosa品种)的。随机缺失(Missing At Random,MAR)的情况就更不那么理想。在这种情况下,某个缺失值是独立于其所属特征的真实值的,但可能会和其他特征相关。这种情况的一个示例是,缺失的花瓣长度值主要在iris数据集的setosa品种样本里出现,但它们的出现仍然独立于实际花瓣长度的值。在问题最多的非随机缺失(Missing Not At Random,MNAR)情况里,存在某种模式能够解释缺失值会根据该特征本身的真实值出现。例如,如果在测量非常小的花瓣长度时遇到困难,并且最终产生了一些缺失值,那么,在这种情况下简单地丢弃不完整样本,就可能只得到花瓣长度较大的观测数据样本,因而使数据产生偏误。
处理缺失值有很多种方法,但在本书中我们不会深入钻研这个问题。在有缺失值的少数情况下,我们会从数据集里排除它们,但是要当心,在一个实际项目里,要调研缺失值的来源,以便确保能安全地进行排除操作。另一种方法是尝试猜测或估算缺失值。kNN算法本身就是一种这样的方法,它会给某个特征有缺失值的样本找到最邻近的样本。具体做法是先排除缺失值所在的维度(即特征),再进行距离的计算。随后,这个缺失值就可以被计算为最邻近的样本在该维度取值的均值。
对如何处理缺失值感兴趣的读者可以在Wiley出版社出版的《Statistical Analysis with Missing Data》(第二版,由Roderick J. A. Little和Donald B. Rubin编写)一书中找到详细的论述。
离群值
离群值(Outlier)也是经常需要处理的一种问题。离群值是其中一个或多个特征严重偏离了其他数据的某条观测数据。在某些情况下,这也可能是某个真实的罕见状况,代表了在我们要建模的系统里的一种合乎情理的表现。在其他情况下,这可能说明在测量数据中存在一个错误。例如,在记录人口年龄的时候,一个110的值可能会是离群值,有可能是因为对实际值11的记录错误而产生的。但它也有可能是一个正确而极为罕见的测量值。通常,要处理的问题领域会对于离群值是否属于测量错误给出很好的指示,如果是这样的话,作为数据预处理的一部分,常常会需要从数据中完全排除离群值。在第2章中我们会看到排除离群值的更多细节。
丢弃有问题的特征
预处理数据集也包括了丢弃某些特征的决定,如果知道它们会给我们的模型带来问题的话。一个常见的示例是两个或多个特征相互之间高度相关的情况。在R里,可以利用cor()函数轻松地对某个数据框计算其中每一对特征的相关系数:
在这里,可以看到Petal.Length特征和Petal.Width特征是高度相关的,它们的相关系数大于0.96。caret组件提供了findCorrelation()函数,它把一个相关系数矩阵作为输入,还可以选用为特征配对的相关系数的绝对值指定一个阈值的cutoff参数。它会返回一个(有可能长度为0的)向量,该向量会显示由于相关性而需要从该数据框里删除的列。cutoff的默认设置是0.9:
消除相关性的另一种方法是对整个特征空间进行完全的变换,例如在很多降维(dimen-sionality reduction)方法里采用的技术,比如主成分分析(Principal Component Analysis,PCA)和奇异值分解(Singular Value Decomposition,SVD)。我们稍后会介绍前者,后者则会在第11章里介绍。
类似的,还会需要删除相互为线性组合(linear combinations)的特征。对于特征的线性组合,指的是把每个特征乘以一个标量常数所得到的特征之和。为了查看caret包是如何处理这些特征,我们要创建一个新的鸢尾花数据框,在它里面增加两个列,分别名为Cmb和Cmb.N,如下所示:
正如我们所见,Cmb是Sepal.Length 和Petal. Width特征的完全线性组合。Cmb.N则是和Cmb相同但加上了某些均值为0且标准差很小(0.1)的高斯噪声的特征,因而它的值很接近Cmb的值。利用findLinearCombos()函数,caret包能够检测出严格的特征线性组合,不过当这些特征有噪声时就检测不到了:
正如我们所见,该函数只建议我们应该从该数据框中删除第5个特征(Cmb),因为它是第1和第4个特征的一个严格的线性组合。严格的线性组合很少见,不过当有很大数量的特征并且它们之间存在冗余时会出现。具有相关性的特征以及线性组合都是线性回归模型里的问题,我们很快会在第2章看到。在本章,我们也会看到一种检测相互之间非常接近线性组合的特征的方法。
对于有问题的特征要审视的最后一个问题,就是在数据集里存在根本不变化或者说方差近似为0的特征。对于某些模型,具有这种类型的特征不会给我们带来问题。对于其他模型,它可能就会产生问题,我们会演示为何会是这样的。正如前一个示例,我们会创建一个新的鸢尾花数据框,如下所示:
这里,ZV列对于所有观测对象都是一个常数6.5。Yellow列则是一个虚构的列,用来记录观测对象在花瓣上是否带有黄色。除了第一个以外,其他所有的观测对象都设定这个特征为FALSE,因而让它成为方差近似为0的列。caret包采用的是方差近似为0的定义,这个定义会检查某个特征取的唯一值数量和总观测对象数量相比是否非常小,或者最常见值和第二常见值出现数量的比例(被称为频数比,frequency ratio)是否非常高。把nearZeroVar()函数运用到一个数据框,会返回包含了方差为0或近似为0的特征构成的一个向量。通过设定saveMetrics参数为TRUE,我们可以看到更多关于我们数据框里的特征的信息:
在这里,我们可以看到,ZV列被识别为方差为0(zeroVar=TRUE)的列(由定义可知,它也是方差近似为0的列)。Yellow列确实有非0方差,但它较高的频数比和较低的独特值比例使它被判定为方差近似为0的列(nzv=TRUE)。在实践中,往往会删除方差为0的列,这是因为他们不会为我们的模型提供任何信息。不过,要删除方差近似为0的列就比较微妙,必须小心从事。要理解这一点,可以考虑这样一个事实:利用上面那个newer_iris数据集进行品种预测的某个模型会学习到,如果某个样本在花瓣里有黄色,那么不管所有其他预测因素如何,我们都会预测出它是setosa品种,因为它对应的是整个数据集里唯一的一个在花瓣里有黄颜色的观测数据。这在现实中也许确实成立,如果是这样的话,这个黄色的特征就是有信息量的,我们也应该保留它。另一方面,在鸢尾花瓣中出现黄颜色也可能是完全随机的,不仅不能标识品种,而且是极罕见的情况。这就解释了为什么在数据集中只有一条观测数据在花瓣里带有黄色。在这种情况下,由于前面所说的结论,保留该特征就是危险的。保留该特征的另一个潜在问题会在我们把数据划分为训练和测试数据集或以其他方式划分数据(比如第5章里讲解的交叉验证)的时候浮现出来。这里的问题是,对我们数据的划分会导致方差近似为0的列里出现唯一值,例如某个数据集的Yellow列里只有FALSE值。
1.3.5 特征工程和降维
在模型里采用的特征数量和类型是在预测建模过程中要作出的最重要决定。为模型配备正确的特征,就是确保我们为预测准备了充分的依据。在另一面,要处理的特征数量就正好是该模型具有的维度数量。大量的维度会成为复杂度加剧的根源。高维的问题通常来自于数据的稀疏性(data sparsity),它意味着由于存在的维度数量的原因,会导致覆盖所有特征取值的可能组合范围的极大增长,以至于难以收集足够的数据为训练过程提供足够的有代表性的样本。我们还通常谈到维度诅咒(curse of dimensionality)。它描述了一个事实:由于可能的输入产生的空间之大是压倒性的,导致我们所收集的数据点在特征空间里很可能会相互远离。其结果是局部方法(例如利用训练数据中靠近待预测点的观测数据来进行预测的kNN算法)在高维空间里的效果不佳。太大的特征集合还会产生显著增加训练模型(以及在某些情况下作出预测)所需时间等方面的问题。
因此,特征工程包括两类流程。第一类会增大特征空间,它会根据我们数据里的特征来设计新的特征。有时候,由两个原始特征的乘积或比率构成一个新特征会有更好的效果。有多种方法可以把已有特征组合为新特征,而这通常需要相关问题具体应用领域的专业知识的指导。从总体思路而言,这个过程需要经验和大量的反复试验。注意,无法保证加入新特征一定不会降低性能。有时候,增加一个噪声很大或者与已有特征高度相关的特征确实会导致我们损失精确度。
特征工程的第二类过程是特征缩减或收缩,它会减小特征空间的尺寸。在前面关于数据预处理的小节可以看到如何检测会给模型带来某些问题的个别特征。特征选择(feature selection)指从原始的一组特征中选取对于目标输出最具信息量的特征子集的过程。某些方法(例如树形模型)具备内建的特征选择方法,这是我们将会在第6章里看到的。在第2章我们也会探讨对线性模型进行特征选择的一些方法。另一种减少总特征数的方法,即降维(dimensionality reduction)的概念,是把整个特征集变换为全新的一个数量更少的特征集。这方面的一个经典范例是主成分分析(Principal Component Analysis,PCA)。
简而言之,PCA 会创建一个输入特征的新集合,它被称为主成分(principal components),这个集合中全部都是原始输入特征的线性组合。对于第一个主成分,要选取线性组合的权值,以便能获取数据中最大量的变异(variation)。如果我们能够把第一个主成分可视化为原始特征空间里的一条直线,它应该是上面的数据变异最大的一条直线。它还恰好应该是最接近原始特征空间上所有数据点的一条线。后续的每个主成分要尽可能获取最大变异的一条直线,但是要满足的条件是,新的主成分和之前计算得到的主成分不相关。因此,第二个主成分要选取在数据中具有最高程度变异的原始输入特征的线性组合,同时和第一个主成分不相关。
主成分是根据它们获取的变异量以降序进行自然排序的。这让我们能够通过保留前N个组成部分来以简单的方式进行降维,我们在这个过程中选取N的依据是,这样选取的主成分元素要包含来自原始数据集最小量的方差。我们不会深入介绍计算主成分所需的相关线性代数方面的细节。
相反,我们会把关注点转移到一个事实:这个过程是对原始特征的方差和度量单位敏感的。为此,我们经常会在进行这个过程之前对特征进行比例缩放。为了可视化地讲解PCA多么有用,我们会再一次转到我们忠实的鸢尾花(iris)数据集。可以利用caret包执行PCA算法。为此,指定在preProcess()函数的method参数为pca。也可以利用thresh参数,它指定了必须保持的最小方差。我们会显式使用0.95这个值,这样就保持了原始数据95%的方差,但是注意,这也是该参数的默认值:
这个变换的结果是,我们现在只剩下了2个特征,所以我们可以得出结论:鸢尾花的数值特征的前2个主成分就包含了该数据集里超过95%的变异。
如果我们对了解用来计算主成分的权值感兴趣,可以查看pp_pca对象的rotation属性:
这意味着第一个主成分PC1的计算方法如下:
0.52·Sepal.Length-0.27·Sepal.Width+0.58·Petal.Length+0.57·Petal.Width
有时候,相比直接指定一个主成分获取的总方差的阈值,我们可能会想要查看每个主成分及其方差的绘图。这种图称为陡坡图(scree plot),要绘制这样的图,可以先进行PCA并声明我们要保留所有的主成分元素。为此,不要指定一个方差的阈值,而是设定要保留的主成分数量的pcaComp参数。主成分的总数和开始时的原始特征或维度数是相等的,所以就设置它为4,也就是包括了所有主成分。然后计算这些组成部分的方差和累积方差,并把它保存在一个数据框里。最后,我们要在后续的图表中绘制它,并注意图中括号里的数字是获取方差的累积百分比:
正如我们所见,第一个主成分占到了iris数据集总方差的73.45%,再加上第二个主成分,获得的总方差是96.27%。PCA 是一种无监督的降维方法,它不需要利用输出变量,即使输出结果能够获得。相反,它是从几何的角度去看待特征空间里的数据的。这意味着我们不能确保PCA给出的新特征空间一定能在预测问题里有良好表现,它只能带来更少特征的计算优势。即使PCA减少了模型的精确度,但是只要这种精确度的减少比较小而且对于特定任务是可以接受的,计算上的这些优势就会让它成为可行的选择。作为最后一条提示,必须指出,通常被称为荷载(loading)的主成分权值在归一化的情况下是唯一的,只有正负号的变化。在特征之间具有完全相关或完全线性组合的情况下,我们会得到一些恰好为0的主成分。
1.3.6 训练和评估模型
在之前对参数化模型的讨论中,我们看到这些模型会有一个利用训练数据集对模型进行训练的过程。而对于非参数化模型来说,通常它们要么进行惰性学习—也就是除了记住训练数据之外并没有真正的训练过程;要么像样条的情况那样,会对训练数据进行局部的计算。
无论哪种方式,如果要评估模型的性能,就需要把数据分拆为一个训练集(training set)和一个测试集(test set)。这里的关键思路是,要根据模型处理后续未知数据的预期表现来评估我们的模型。我们的做法是利用测试集,也就是把收集到的数据的一部分(通常是15%~30%)留出来,在训练过程中不要用到它们。例如,一种可能的分拆方式是把原始数据里80%的观测数据作为训练集,把剩下的20%作为测试集。需要一个测试集的原因是,我们无法用训练集公平地评估模型的性能,因为训练数据是用来拟合模型的,而它并不能代表我们之前没有看到过的数据。从预测的角度来说,如果我们的目标只是针对训练数据产生最佳性能,那么最好的手段就是直接记住输入数据和期望的输出值,这样我们的模型就成了一个简单的查找表了!
有一个好问题是,如何决定把多少数据分别用于训练和测试。这里就涉及一些权衡,让这个问题的答案意义重大。一方面,我们会想把尽可能多的数据放在训练集里,这样模型就可以根据更多的样本进行学习。另一方面,我们也想有一个大的测试集,这样就能使用很多样本对训练好的模型进行测试,以便让我们对该模型预测性能的估算具有最小的方差。如果我们的测试集里只有少量观测数据,那么我们就无法真正总结出模型用于未知数据的性能的一般规律。
另一个起作用的因素是,初始收集的数据量有多大。如果只有很少的数据,我们可能就必须用其中的大部分去训练模型,比如划分为85%/15%。如果我们有充足的数据,那么我们也许会考虑70%/30%的划分,这样就能对于测试集产生更精确的预测。
要利用caret包划分数据集,可以用createDataPartition()函数创建一个抽样向量,里面包含了要在测试集里用到的那些行的索引。对这些行的选取过程是通过利用p参数对行进行随机抽样,直到抽样的行数达到预先指定的比例:
在报告涉及生成随机数的统计学分析结果时,有一种可取的做法是,用随机选取但固定的一个数值调用set.seed()函数。该函数会确保从后续关于随机数生成的函数调用所生成的随机数在代码每次运行的时候都是相同的。这样做能让其他阅读这个分析过程的人能准确地重现分析结果。注意,如果代码里有几个函数进行了随机数的生成,或同一个随机数生成函数被调用了多次,原则上我们必须在每一次调用之前先调用set.seed()。
利用为iris数据集创建的抽样向量,我们就能构建自己的训练集和测试集了。我们会对之前在试验不同的特征变换方法时构建的一些iris数据集版本进行这个操作。
现在我们就可以为iris数据集创建并测试3种不同的模型。它们分别是未归一化(unnormalized)模型、输入特征已通过Z评分(Z-score)变换进行中心化和比例缩放的模型,以及带有两个主成分的PCA模型。在创建好这些模型之后,可以使用测试集来衡量它们中每一个的预测性能。不过,这样就意味着在我们对其未知精确度的最终估算中,已经在模型选取过程中用到了测试集,因而产生了有偏误的估算。因为这个原因,我们经常会保持一套单独划分的数据,通常和测试集一样大,被称为验证集(validation set)。这个数据集是用来在运用测试集预测未知性能之前对模型参数(例如kNN里的k值)以及输入特征的不同编码和变换进行调优的。在第5章中,我们会讨论这种方法的一种替代方法,称为交叉验证(cross-validation)。
一旦划分了数据,按照所要求的相关训练过程训练了模型,并对模型参数进行了调优,我们就必须在测试集上评估它的性能。通常我们不会在测试集里得到和训练集里相同的性能。有时候我们甚至可能会发现,当部署模型的时候,看到的性能和根据训练集或测试集的性能而得到的预期并不相符。这种性能上的不一致有很多可能的原因。其中第一个原因就是,之前收集的数据要么对建模的过程不具有代表性,要么就是某些特征输入的组合没有纳入训练数据中。这样就可能产生和预期不一致的结果。这种情况既可能发生在真实世界中,也可能发生在测试集里,比如它包含了离群值(outlier)的情况。另一种普遍存在的情况是模型过拟合(over fitting)问题。
过拟合问题的表现是这样的:某些模型,尤其是更灵活的模型,在它们的训练数据集里表现良好,但对于未知的测试集就明显表现更差。在这种问题里出现的情况是,某个模型对训练数据中的观测数据拟合得过于严密,却无法对未知数据广义化。换言之,该模型在训练数据集中提取了不真实的细节和变异,而这些细节和变异对于整体的基础观测数据并不具有代表性。过拟合是我们不能根据训练数据的性能来选取模型的关键原因之一。其他训练和测试数据性能不一致的来源还包括模型偏误和方差。这些因素合在一起就构成了统计性建模领域中的一种著名的权衡,即偏误-方差权衡(bias-variance tradeoff)。
统计性模型的方差是指,如果使用另一个选取的训练集(但还是从要预测的同一个原始过程或系统生成的)来训练模型,模型预测出的函数会有多大的变化。我们想要的是一个较低的方差,因为我们当然不希望从同一个过程生成的不同训练集预测出很不一样的函数。模型偏误则是指在预测函数中内生出来的错误,它是关于具体的一个模型能够学习哪些函数形式的局限性的结果。例如,线性模型会在试图拟合非线性函数的时候引入偏误,这是因为它们只能学习线性函数。一个良好的预测模型的理想状况是它兼具了低方差和低偏误。对于一个预测建模者,很重要的一点是注意这样的事实:偏误-方差权衡(bias-variance trade-off)来源于对模型的选择。对目标函数作出更少假设而产生的复杂模型,相比于更简单却更严格的模型(例如线性模型),往往会有更少的偏误和更大的方差。究其原因,是因为更复杂模型具有的灵活性让它们能够更近似地估算训练数据,但这样的结果是它们对训练数据里的变化也会更敏感。当然,这种情况和复杂模型经常会表现出来的过拟合问题也是相关的。
我们可以通过先在iris数据集上训练某些kNN模型来实际观察过拟合的效果。有很多扩展包都提供了kNN算法的实现,但是我们会使用我们熟悉的caret包提供的knn3()函数。要用这个函数训练模型,我们所要做的就是给它提供一个包含数值输入特征的数据框、一个输出标签的向量,以及我们要用于该预测的最近邻数量k:
要观察k的不同取值的效果,我们可以利用现成的2个维度的鸢尾花PCA模型,用来进行可视化和反复训练:
在前面的图中,我们使用了不同的符号来注明对应不同品种的数据点。图中显示的线对应了不同鸢尾花品种之间的判定边界(decision boundary),这些品种也就是我们输出变量的类别标签。注意,使用一个较低的k值(例如1),会非常严密地获得在数据中的局部变异,结果是判定边界非常不规整。更大的k值要利用很多邻近点来创建一个预测,产生的是平滑的效果和更平滑的决策边界。在kNN里对k值进行调优就是一个对模型参数进行调优以平衡过拟合效果的示例。
在本节,我们还没有提到过任何具体的性能衡量指标。回归和分类对于模型的质量有不同的衡量指标,我们会在对关于预测建模过程的讨论进行总结之后再讲解它们。
1.3.7 重复尝试不同模型及模型的最终选择
在这个过程(而且肯定是一个迭代过程)的第一次迭代中,我们通常会在训练并评估完一个简单模型后到达这个阶段。简单模型通常让我们能以最少的精力获得一个快速而粗略的解决方案,从而让我们大致感受到离一个会产生合理精确度预测的模型还有多远。简单模型还可以给我们产生一个性能的基线水平,我们可以用它作为未来模型性能的标杆。作为建模者,我们在不同方法之间往往会厚此薄彼,但重要的是记住,要对解决问题的不同方法进行尝试,并利用数据帮助我们决定最终采用哪一个,这是值得花费精力的事情。
在选取最终的模型之前,有必要考虑的事,采用多个模型来解决我们的问题是否是个好的思路。在第7章里,我们会用一整章来学习涉及多个模型共同作用来提高整体系统的预测精确度的技术。
1.3.8 部署模型
一旦选取了采用的最终模型,我们就需要落实它的实现,这样最终用户就能可靠地使用它。程序员们称该过程为部署到生产系统(deploying to production)。这是扎实的软件工程原理会起到极其重要作用的领域。下面的指导原则给出了一些有用的建议:
模型必须进行优化,以提高它计算预测结果的速度。例如,这意味着确保任何要在运行时计算的特征能高效地完成处理。
模型必须有完备的文档。最终的输入特征必须清晰定义,用于训练的方法和数据必须保存,以便在需要更改时易于重新进行训练。训练集和测试集的原始性能也必须保存下来,作为后续改进的参考。
模型性能必须持续进行监控。它的重要性不仅在于作为验证模型是否达到预期效果的手段,而且在于捕捉任何潜在的数据变化。如果要建模的过程随时间改变了,我们模型的性能就有可能下降,这就表明了训练一个新模型的必要性。
用来实现该模型的软件必须利用标准的单元和集成测试方法进行适当的测试。通常,我们会利用大量已有的R语言包,它们的函数已经通过了测试,但模型最终的部署会需要我们自己编写某些额外的代码,比如用来进行特征计算的代码。
部署的模型必须能处理输入中的错误。例如,如果某些输入特征是缺失的,它应该通过适当的错误信息让用户明确了解模型无法作出预测的原因。错误和警告也必须写进日志,尤其是对于模型被部署用于实时环境下的持续性预测的情况。
1.4 性能衡量指标
在上一节讨论预测建模过程的时候,我们讨论了利用训练集和测试集对被训练模型的性能进行评估的重要性。在本节,我们要审视在描述不同模型的预测精确度时常遇见的某些性能衡量指标。其实,根据问题的不同类型,会需要使用略有差异的性能评估方式。由于本书讨论的重点是有监督的模型,所以我们会审视如何评估回归模型和分类模型。对于分类模型,我们还会讨论二元分类任务这样一个非常重要且经常遇到各类问题的模型所采用的某些额外的衡量指标。
1.4.1 评估回归模型
在一个回归的场景里,让我们回顾一下,通过模型,我们要构建一个函数,它是理论上的基础目标函数f的近似。该模型的输入是我们选择的输入特征的值。如果我们把这个函数运用到训练数据里的每个用函数真实值yi标记的观测数据xi,我们就会得到一组(yi, i)的配对数据。为了确保我们清楚最后一点,这里的第一个数据项是在我们训练数据的第i条观测数据的输出变量实际值,而第二个数据项则是把我们的模型运用到该观测数据的特征值上产生的预测值。
如果模型和数据拟合得很好,在训练集里这两个值就会相互非常接近。如果在测试集里也是如此,那么就认为这个模型应该能对将来的未知观测数据有良好的表现。为了对预测值和正确值在一个数据集的所有观测数据中相互接近的概念进行量化,我们要定义一个衡量指标,它称为均方差(Mean Square Error,MSE),如下所示:
这里的n是数据集里观测数据的总数。因此,这个方程告诉我们,首先要对测试集里每条观测数据i计算其输出值及预测值之差的平方,然后把它们累加起来再除以观测数据的数量,得到所有这些值的平均数。这样,这个衡量指标被称为均方差的原因就很清楚了。这个数值越低,观测数据中输出变量的实际值和我们预测结果之间的平均误差越小,我们的模型就越准确。我们有时候也会参考均方根误差(Root Mean Square Error,RMSE),它是均方差和误差平方和(Sum of Squared Error,SSE)的平方根,其中误差平方和SSE和均方差MSE 是相似的,只是它没有除以训练样本个数n而产生归一化结果。对训练数据集计算得到的这些数量值是很有价值的,它的较小数值就表明我们充分训练了模型。总体而言我们并不期望它达到0,而且由于过拟合的问题,我们也不能根据这些数量值在模型中进行取舍。而计算这些衡量指标的关键领域是测试数据。在大部分情况下,某个模型训练数据的均方差(或者等同的指标:均方根误差或误差平方和)会比对测试数据计算出的相应衡量指标更小。存在过拟合数据情况的模型m1和另一个模型m2对比,往往可以通过m1模型产生了更低的训练均方差却有更高的测试均方差来进行识别。
1.4.2 评估分类模型
在回归模型里,我们的预测函数对于特定观测数据xi产生近似输出yi的不正确程度是在均方差里体现的。具体而言,对较大的误差取平方会导致在某个数据点上的很大误差比多个数据点上的一些小误差影响更大。这恰恰是因为在回归中,我们处理的是数值型输出,这样不仅可以衡量我们对于哪些观测数据的预测不够好,还可以衡量我们偏离了多远。
对于进行分类的模型,我们也可以定义一个误差率,但这里我们只能讨论模型作出错误分类的数量。具体地说,错误率是由如下公式给出的:
这个衡量指标利用了指示函数(indicator function),它在预测类别和标签类别不同的情况下返回的值为1。因此,误差率就可以这样进行计算:对输出变量的类型预测错误个数进行计数,然后把这个计数除以数据集里的观测数据数量。在这种方式下,可以看到,误差率实际上是模型作出错误分类的观测数据所占的百分比。必须要指出的是,这种衡量指标把所有类型的分类错误视为等同的。如果某些错误分类的成本要高于其他错误分类,那么这种衡量指标可以通过加入权值来进行调整,这些权值的作用是给每种错误的分类乘以一个与其成本成比例的数量。
如果要诊断某个回归问题中误差的最大来源,我们往往会先查看预测值和实际值之间误差最大的那个点。而在进行分类时,计算所谓的混淆矩阵(confusion matrix)往往是很有用的。它能显示在数据上产生的所有配对的错误分类。现在,我们要回到鸢尾花品种分类问题了。在前面的小节,我们训练了3个kNN模型。我们现在会看到如何能够评估它们的性能。和很多分类模型一样,kNN返回的预测既可以是最终的类别标签,也可以是与每种可能的输出类型相关的一组评分。有时候就像这里的示例一样,这些评分实际上是该模型给出的每种可能输出的概率。不管该评分是不是概率,我们都可以根据这些评分来决定选取哪个输出标签,通常就是直接选取评分最高的那个标签。在R语言里,用来作出模型预测的最常见函数是predict()函数,我们在自己的kNN模型里会用到它:
在该kNN模型里,我们可以通过计算属于每个输出标签的最邻近样本的比例,从而把输出的评分指定为直接概率。在上面显示的三个测试示例中,virginica品种在其中两个示例中的概率为1,而在另一个示例中只有60%的概率。另外的40%属于versicolor品种,因此看起来在后面那个示例中,每5个最邻近样本中有3个属于virginica品种,而另外2个属于versicolor品种。很显然,相比这个分类结果,我们应该会对前面两个分类结果更有信心。我们现在要在测试数据上对这三个模型进行类别预测的计算:
我们可以利用caret包的postResample()函数来显示模型在测试集上的精确度指标:
在这里,精确度等于1减去误差率,也就是正确分类的观测数据比例。可以看到,所有的模型在精确度方面都非常接近,其中使用了Z评分(Z-score)变换进行归一化的那个模型胜出了。由于测试集较小,这个差别并不显著。卡巴统计量(Kappa statistic)的定义如下所示:
卡巴统计量是设计用来抵消随机因素的,它的取值空间为[-1, 1],其中1代表完全精确,-1代表完全不精确,而0是在精确度恰好等于随机猜测法的结果时出现的。注意,对于一个分类模型来说,随机猜测法猜测的是最常见的那个类别。在鸢尾花分类模型的示例中,3个品种在数据中出现的机会是均等的,因此期望精确度等于三分之一。我们鼓励读者通过给期望精确度运用这个值来进行检验,我们可以根据各个精确度的值来获得卡巴统计量的观测值。
我们还可以利用一个混淆矩阵来检查模型作出的特定错误分类。它可以通过用正确的输出标签对预测结果进行交叉分析(cross-tabulating)直接构建出来:
caret包还有一个非常有用的confusionMatrix()函数,它能自动计算这张表以及几种其他的性能衡量指标,该函数功能的说明可以查阅http://topepo.github.io/caret/other.html。
在前面的混淆矩阵里,我们可以看到正确分类的观测数据的数量是28个,它是主对角线上的数值10、9、9之和。该表格给我们展示的是setosa品种看起来用我们的模型更易于预测,因为它从来没有和其他品种混淆。不过,versicolor和virginica品种会互相混淆,该模型对这两个品种都各有1次错误的分类。因此,我们可以推测,计算混淆矩阵可以作为一个有用的练习。找出经常混淆的类别配对可以指导我们对模型作出改进,例如去寻找有助于区分这些类别的特征。
评估二元分类模型
有一种特殊的分类任务被称为二元分类(binary classification),它是在我们只有两个类别的情况下出现的。一些典型的二元分类的场景如下:
根据收到的电子邮件的内容和标题,把它们分类为垃圾邮件或非垃圾邮件
根据病人的症状和病史,把他们分类为是否患有某疾病
根据查询关键字和文档中的单词,把一个文档的大数据库中的某个文档分类为是否和该查询相关
把生产线上的某个产品分类为次品或不是次品
根据客户的信用评分和财务状况,预测在某银行申请贷款的某位客户还款时是否会发生违约
在二元分类任务里,通常把两个类别称为阳性类(positive class)和阴性类(negative class)。按照惯例,阳性类对应的是模型尝试预测的一种特殊情况,并且通常比阴性类更为少见。从之前的示例,我们可以给垃圾邮件、生产线的次品、违约客户等等使用阳性类标签。现在考虑医疗诊断领域的一个示例,在这个领域,我们要尝试训练一个模型来诊断某个在10 000人中只出现1例的疾病。我们会把阳性类分配给患有这个疾病的病人。注意,在这样的一个场景中,误差率本身不是模型的充分衡量指标。例如,可以通过把每个病人都预测为健康的来设计出最简单的分类器,它只有0.01%的误差率,但这样的一个分类器是没有用的。通过分析混淆矩阵,可以得出更有用的衡量指标。假定已经构建了一个诊断罕见疾病的模型,对于100 000病人的测试样本,我们获得了如下的混淆矩阵:
二元分类问题是如此常见,以至于它的二元混淆矩阵的单元都有它们自己的命名。在包含了正确分类条目的主对角线上,我们称其元素为真阴性(true negative)和真阳性(true positive)。在示例中,我们有99 900例真阴性和13例真阳性。当把实际上属于阴性类的观测数据错误分类为阳性类时,就得到了一个假阳性(false positive),也称为第一类错误(Type I error)。假阴性(false negative)或第二类错误(Type II error)则出现在把阳性类的观测数据错误分类为阴性类的情况下。在这个示例中,模型得到了78例假阳性和9例假阴性。
现在我们要引入两个在二元分类环境中非常重要的衡量指标,它们是查准率(precision)和查全率(recall)。查准率的定义是准确预测的阳性类例数占预测的阳性类总例数的比例。利用前面的二元混淆矩阵的标签,查准率可由如下公式得出:
因此,查准率实际上衡量的是我们对阳性类进行预测的精确度。根据定义,只要从不预测阳性类就能达到100%的查准率,因为以这种方式可以保证不会犯任何错误。反过来,查全率的定义是阳性类的正确预测数在数据集里所有阳性类成员中的占比。同理,利用二元混淆矩阵中的标签,可以把查全率的定义表示为:
查全率衡量的是从数据集中识别所有阳性类成员的能力。可以通过把所有数据点都预测为阳性类来轻松达到最大查全率。这样我们会犯很多错误,但永远不会有假阴性。注意,查准率和查全率构成了模型性能的一个权衡。一方面,如果对任何数据点都不预测为阳性类,我们会得到0查全率,但查准率是最大的。而另一方面,如果把所有的数据点都预测为属于阳性类(记住,阳性类是罕见的类),我们会得到最大查全率,但查准率是非常低的。换言之,尝试减小第一类错误会导致第二类错误增加,反之亦然。这种相反的关系经常可以针对具体的问题在查全-查准曲线(precision-recall curve)上绘制出来。通过采用一个适当的阈值参数,我们就可以对模型的性能进行调优,从而达到这个查全-查准曲线上适合我们情况的某个点。例如,在某些问题的领域,由于把阳性类的观测数据错误分类为阴性类的成本很高,因此,我们会偏向于得到更高的查全率而不是查准率。因为经常要用单个数值描述某个模型的性能,我们定义了一个叫做F1分数(F1 score)的衡量值,它合并了查全率和查准率。具体来说,F1分数的定义是查准率和查全率之间的调和平均数:
读者应该在示例的混淆矩阵里检验一下,查准率是14.3%,查全率是59.1%,而F1分数是0.23。
1.5 小结
在本章,我们探讨了关于预测模型的一些基本思想。我们看到有很多种方式可以对模型进行分类,并在这个过程中学习了一些重要的区分方式,例如有监督和无监督学习,回归和分类等。然后,我们列出了构建预测模型所需要的步骤,从数据收集的过程开始,一直到模型的评价和部署。关键在于,这个过程是迭代式的,往往要尝试并训练很多不同的模型才能得到最终的模型。为了比较我们创建的不同模型的性能,我们讲解了一些模型性能的基本概念,例如回归的均方差和分类的分类误差率。
我们还介绍了我们的第一个模型,即k最近邻模型,它对于分类和回归都是有用的。kNN是一个很灵活的模型,它不对基础数据作出任何明确的假设。因此,它能拟合非常复杂的判定边界(decision boundary)。它是一种惰性学习方法,因为它不会构建一个模型来描述输入特征和输出变量之间的关系,因此,它不需要一个很长的训练过程。而另一方面,对于具有很多维度的数据,它会需要很长时间才能产生预测结果,而且由于这种模型需要记住所有的训练数据才能找到目标点的最邻近样本,它往往需要很多存储空间。kNN对不同特征的重要性是不加区分的,而且它在预测中采用了距离度量,这些事实意味着,在一方面,它不具备内建的处理缺失数据的方法,而另一方面,它经常需要把特征变换到相似的比例尺度。最后,该模型可以通过选择合适的k值(即最邻近样本的数量)来进行调优,以便平衡过拟合的程度。在对预测建模过程的基本知识具备了坚实的基础之后,我们在下一章要学习线性回归。
第2章
线 性 回 归
我们从第1章了解到回归问题会预测一个数值型的输出。最简单和最常见的回归类型就是线性回归。本章要探讨为什么线性回归如此常用,以及它的局限性和扩展问题。
2.1 线性回归入门
在线性回归(linear regression)中,输出变量是通过输入特征的一个线性加权组合来预测的。下面是简单线性模型的一个示例:
=1x+0
上述模型实质上表达的是,我们要估算一个用表示的输出,它是由字母x标记的一个预测变量(也就是特征)的一个线性函数。由希腊字母表示的项是模型的参数,被称为回归系数(regression coefficient)。一旦我们训练好模型并给这些参数选定了值,我们就可以通过在方程里直接代入x的任何值对输出变量进行预测。另一个线性模型的示例由下面的方程给出,这次的模型带有三个特征,回归系数也已经赋值了:
=1.91x1+2.56x2-7.56x3+0.49
和前面那个方程一样,在该方程中我们也可以看到系数比特征数多1个。这个附加的系数0被称为截距(intercept),它是当所有输入特征值都为0时模型的期望值。其他的参数可以解释为某特征每增加1个单位时输出值的预期变化。例如,在前面的方程中,如果特征x1的值增加1个单位,输出的期望值就会增加1.91个单位。类似地,特征x3的值增加1个单位会让输出减少7.56个单位。在简单的一维回归问题中,我们可以把输出绘制在图的y轴,把输入特征绘制在x轴。在这个示例中,模型预测了两个轴之间的一种直线关系,其中0代表该直线和y轴交叉或相交的交点,而1则代表该直线的斜率。我们通常把这种一个特征(因而有两个回归系数)的情况称为简单线性回归(simple linear regression),把两个或多个特征的情况称为多元线性回归(multiple linear regression)。
线性回归的假设
在深入钻研线性回归模型的训练方法及其性能的细节之前,我们要来看一下模型的假设。模型假设主要描述了模型对于我们尝试预测的输出变量y的判断。具体来说,线性回归模型会假设输出变量是一组特征变量的加权线性函数。此外,该模型还假设对于特征变量的固定值,输出是具有常数方差的正态分布。这就等于说,以两个输入特征为例,该模型假设的是,真实的输出变量y可以用如下的一个方程表示:
y=2x2+1x1+0+
这里,代表一个误差项,它是一个均值为0且方差为常数2的正态分布:
~N (0, 2)
我们可能听说过更正式地描述常数方差概念的术语—同方差性(homoscedasticity)。说到同方差性或者常数方差(constant variance),我们指的是这样的事实:误差部分的方差不会随着输入特征的值或水平(level)而变化。在下面的绘图中,我们可视化了一个具有异方差(heteroskedastic)误差(也就是不具有常数方差的误差)的线性关系虚构示例。其中,输入特征值较小的数据点离直线较近,因为在绘图的这个区域方差比较低,而较大的输入特征值就离直线更远,因为它的方差更大。
项是实际函数y的一个不能化简的误差分量,可以用来表示随机误差,例如在特征值里的测量误差。在训练一个线性回归模型时,即使得到了所有合适的特征和充足的数据,并且要建模的系统也确实是线性的,但我们还是会预期在对输出的估算中观测到一定数量的误差。换言之,即使是线性的实际函数,我们还是会预期,一旦找到了对训练样本拟合最佳的一条直线,但由于该误差分量体现的这个固有方差,这条直线也不会正好穿过所有(或者甚至不会穿过任何一个)数据点。不过,需要记住的关键一点是,在这个理想化的场景里,因为误差分量的均值为0且方差为常数,所以在具有足够大的样本的前提下,训练准则让我们能接近回归系数的真实值,因为这些误差会互相抵消。
另一个重要的假设和误差项的独立性有关。这意味着我们不希望和某条具体的观测数据相关的残差(residual)或误差项以某种方式也和另一条观测数据具有相关性。作为测量过程有误的典型结果,当观测数据相互之间具有函数关系时,这个假设就不成立了。如果取出训练数据的一部分,把所有的特征值和输出值乘以2,并把这些新数据点加入训练数据,我们就会产生一种拥有更大数据集的错觉,不过,这样做的结果是导致存在误差项互相有依赖关系的配对观测数据,从而使我们的模型假设不能成立。附带再说一句,对于任何模型,这种人工扩大数据集的方式都永远是不可取的。类似地,如果观测数据以某种方式通过某个未测量的变量相互关联,也会产生互相关联的误差项。例如,如果我们在测量某个生产线零件的故障率,那么来自同一个工厂的零件可能会在误差里具有相关性,比如,来源于在组装过程的不同标准和规程。因此,如果不把工厂作为一个特征加进来,我们就会在样本里看到,来自同一个工厂的零件的观测数据之间会存在相互关联的误差。实验设计(experimental design)的研究内容就是关于识别并消除误差项里的相关性的,但这个主题超出了本书的范围。
最后还有一个重要的假设,它表达了这样一个概念:特征本身在统计学上是相互独立的。这里有必要阐明的是,在线性模型中,虽然输入特征必须是线性加权的,但是它们本身可以是另一个函数的输出。举例说明,如下方程式是一个具有三个特征sin(z1), ln(z2)和exp(z3)的线性模型,这看起来也许有点令人吃惊:
y=0.8 sin(z1)-0.6 ln(z2)+2.3ez3
通过对输入特征进行一些变换并替换到模型中,我们就可以看出它其实是一个线性模型:
现在,我们有了一个更易于识别为线性回归模型的方程式。如果前面的示例让我们相信几乎任何方程都可以变换为一个线性模型,那么下面两个示例会有力地说服我们,实际上并非如此:
由于第一个回归系数(1)的存在,上面两个模型都不是线性模型。第一个模型之所以不是线性模型,是因为1成为第一个输入特征的指数。在第二个模型里,1在一个正弦函数的内部。从这些示例要吸取的重要教训是,有些情况下,为了让数据符合线性模型,我们可以对输入特征进行变换,但是,需要小心从事,让回归系数始终是变换后新特征的线性权值。
2.2 简单线性回归
在着眼于某些真实环境的数据集之前,尝试在人造数据上训练模型是非常有帮助的。在这样的人造场景里,我们事先就知道了实际输出函数是什么,而这对于真实环境的数据来说通常是不成立的。进行这种练习的好处是,它会让我们对自己的模型在所有假设都完全成立的理想场景下的工作情况有清楚的了解,而且它有助于对具备理想的线性拟合时发生的情况进行可视化。我们先模拟一个简单线性回归模型。后面的R语言代码片段会为下面这个只有1个输入特征的线性模型创建一个带有100条模拟观测数据的数据框:
y=1.67x1-2.93+N (0, 22)
下面是用于这个简单线性回归模型的代码:
对于输入特征,我们要从一个均匀分布中随机抽样一些点。我们用均匀分布让数据点理想地分散开。注意,最后那个df数据框的用途是模拟我们在实践中会获得的一个数据框,因此,其中并没有包含误差项e,因为它在实际环境里是无法得到的。
当利用类似上述数据框里的某些数据训练一个线性模型的时候,实际上希望的是能产生一个和数据背后的模型具有相同系数的线性模型。换言之,原始的系数就定义了一个总体回归线(population regression line)。在这种情况下,总体回归线代表了数据实际对应的模型。一般来说,我们会发现自己在试图对一个未必线性的函数进行建模。在这种情况下,仍然可以把总体回归线定义为最有可能的线性回归线,但一个线性回归模型显然不会有实际模型那样良好的表现。
估算回归系数
对于简单回归模型来说,训练模型的过程相当于根据数据集对两个回归系数进行估算。正如从之前构建的数据框里所看到的,我们的数据实际上是一系列的观测数据,其中每一个数据是一对值(xi, yi),其中第一个元素是输入特征值,第二个元素是它对应的输出标签。其实,对于简单线性回归,有可能写出两个方程,用来计算这两个回归系数。我们不会直接给出这些方程,而是先要用简短篇幅来回顾一些读者在之前很可能遇到过的非常基础的统计量,因为它们很快就会起重要作用。
一组值的均值(mean)就是这些值的平均值,它经常被描述为位置的衡量指标,用来大致表示这些值在衡量它们的标尺上居中的位置。在统计学专业里,一个随机变量的平均值经常被称为期望值(expectation),因此我们经常可以看到一个随机变量X的均值被标记为E(X)。另一个普遍使用的记号是上横杠标记(bar notation),即可以通过给一个变量顶部加上一个横杠表示对一个变量取平均值的概念。要对此进行图示,下面的两个方程就表示了输出变量y和输入特征x的均值:
第二个很常见的统计量应该也是大家所熟悉的,它就是变量的方差(variance)。方差衡量的是每个变量值和均值之间距离的平方的平均值。从这个意义上说,它是一个离散度的衡量指标,因此低方差意味着大部分值是靠近均值进行聚集的,而更大的方差则会导致变量值是扩散开的。注意,方差的定义中包含了均值的定义,因此,我们会看到在下列显示输入特征x的方差的方程里用到了上部带一个横杠的x变量:
最后,用下列方程来定义两个随机变量x和y之间的协方差(covariance):
从前面这个方程可以清楚地看到,前面定义的方差实际上就是协方差在两个变量相同时的一种特殊情况。协方差衡量的是两个变量相互之间关联的紧密程度,可以是正值或负值。正的协方差表明了正相关性,也就是说,当一个变量增大时,另一个也会增大。负的协方差则代表相反的含义,当一个变量增大时,另一个往往会减小。当两个变量从统计学角度是互相独立因而不相关的,它们的协方差就是0(不过需要说明的是:反过来,协方差为0并不一定能得出统计学角度的相互独立关系)。
掌握了这些基本概念之后,现在我们就可以给出简单线性回归里两个回归系数的近似计算公式了:
第一个回归系数等于输出与输入特征的协方差除以输入特征的方差所得到的比例。注意,如果输出变量和输入特征相互独立,则协方差为0,因而线性模型就是一条没有斜率的水平直线。在实践中要注意的是,即使两个变量从统计学角度是互相独立的,通常还是会得到少量的协方差,这是由于误差的随机性所致,因此如果要训练一个线性回归模型来表示它们之间的关系,第一个回归系数一般会是一个非零值。后面我们会看到,如何运用显著性检验(significance test)来检测出我们不应该纳入模型中的特征。
要在R语言中实现线性回归,进行这些计算并不是必需的,因为R语言提供了lm()函数,它会为我们构建一个线性回归模型。下面的代码样例会用到之前创建的df数据框,并计算出回归系数:
在第一行我们可以看到lm()函数的用法:首先指定一个公式,然后接着给出data参数,它在示例中就是df数据框。对于简单线性回归,给lm()函数指定公式的语法是:输出变量的名字后面跟一个波浪符(~),然后再跟一个输入特征的名字。在本章后续内容里,当我们学习多元线性回归时,会看到如何指定更复杂的公式。最后,输出内容给出的是两个回归系数的值。注意,0系数被标记为截距(Intercept),而1系数是用线性模型方程里对应特征的名字(在这个示例中是x1)来标记的。
下图在同一张图上显示了总体回归线和近似线:
正如我们所见,这两条线互相非常贴近,以至于几乎难以区分,这表明该模型对于实际总体回归线的近似是非常接近的。根据第1章的内容,我们知道可以用均方差作为规范来表示该模型拟合当前数据集的紧密程度,以及该模型将来会拟合类似测试集的紧密程度。我们在本章要了解它和其他几种模型性能及质量的衡量指标,但我们首先要把这个回归模型广义化,用来处理超过1个的输入特征。
2.3 多元线性回归
只要有多于一个输入特征,并且要构建一个线性回归模型,我们就处于多元线性回归的领域了。具有k个输入特征的多元线性回归模型的一般方程如下所示:
y=kxk+k-1xk-1+…+1x1+0+
关于模型和误差分量的假设还是和简单线性回归的一样,记住,因为现在有了超过1个的输入特征,我们假设它们是相互独立的。我们在讲解多元线性回归时不会再使用模拟数据,而是要分析两套实际环境下的数据集。
2.3.1 预测CPU性能
我们的第一个实际环境下的数据集由研究者Dennis F. Kibler,David W. Aha,和Marc K. Albert于1989年在一篇论文中所提出,该论文名为《基于实例的实值属性预测》(Instance-based prediction of real-valued attributes),发表于《计算智能杂志》(Journal of Computational Intelligence)。该数据包含了不同CPU型号的特征,例如周期时间和缓存数量。在选择不同的处理器时,要考虑到所有这些因素,但理想情况下,我们希望在单一的数值标尺上对处理器进行比较。因此,我们经常会开发程序,用标杆来比较某个CPU的相对性能。数据集还带有已发布的CPU相对性能数据,我们的目标就是把这些可以获得的CPU特性作为预测所采用的特征。该数据集可以从UCI机器学习资源库(UCIMachine Learning Repository)在线获取,它的链接是:http://archive.ics.uci.edu/ml/datasets/Computer+Hardware。
UCI机器学习资源库是一个非常好的在线资源,它存放了大量数据集,其中很多都经常被各种著作和教材的作者引用。花费精力去熟悉这个网站及其数据集是非常值得的。有一种学习预测分析技术的好方法,就是把你在本书中学习的技术实际运用到不同的数据集上,而UCI资源库恰恰为此提供了很多这样的数据集。
machine.data文件包含了我们所有的数据,它使用逗号分开的格式(类似于.CSV文件),每一行对应一个CPU型号。我们要把它导入R并标记所有的列。注意,里面一共有10列,但我们的分析不需要前两列,因为它们不过是CPU的品牌和型号名。类似地,最后一列是研究者们自己得出的相对性能预测估算,实际输出变量PRP在第9列。我们会把需要的数据保存在一个名为machine的数据框里:
该数据集还带有各列数据的定义:
列 名 定 义
MYCT 机器周期时间,以纳秒为单位
MMIN 最小主内存,以KB为单位
MMAX 最大主内存,以KB为单位
CACH 缓存,以KB为单位
CHMIN 最小通道数,以个数为单位
CHMAX 最大通道数,以个数为单位
PRP 发布的相对性能(即输出变量)
该数据集不包含缺失值,所以就不需要删除或修改任何观测数据。我们会注意到的一个问题是我们只有大约200个数据点,一般来说这算是非常小的样本了。尽管如此,我们还是会继续把数据划分为训练集和测试集,划分比例是85% / 15%,如下所示:
既然已经把数据设置好了,我们通常就要继续分析和检查针对线性回归的假设是否成立。例如,我们会希望了解是否存在任何高度相关的特征。为此,可以用caret包的cor()函数构建一个相关度矩阵,并利用findCorrelation()函数获得关于哪些特征应该去掉的建议:
在采用代表高度相关性的默认阈值0.9的情况下,我们发现没有哪个特征应该去掉。当把这个阈值减小到0.75的时候,我们会看到caret包推荐去掉第三个特征(MMAX)。正如前面代码的最后一行所显示的,该特征和MMIN之间的相关度为0.768。虽然这个值并不是特别大,但它还是大到让我们会在某种程度上担心它会影响我们的模型。当然,直观地说,如果我们看一下输入特征的定义,当然我们往往会预期某个具有相对较高的最小主内存的型号也会具有相对较高的最大主内存。线性回归有时候对于具有相关性的变量也能给出一个较好的模型,但如果变量是不相关的,我们会期望得到更好的结果。就目前而言,我们决定还是保留数据集里的所有特征。
2.3.2 预测二手汽车的价格
我们的第二个数据集是在caret包里包含的汽车数据框,它是由Shonda Kuiper于2008年从Kelly Blue Book网站www.kbb.com收集的。这是一个获取二手车可靠价格的在线资源。该数据集包括了804种通用汽车(GM)品牌的汽车,所有汽车都是2005年出厂的。它包含了一组汽车属性,例如里程数、发动机排量,以及建议的销售价格。其中很多特征是二元的指示变量,例如Buick(别克商标)特征,它代表具体的某辆车的商标是否是Buick。所有的二手车在标价时都处于完好状态,使用时间少于一年,因此车况没有作为一个特征被纳入。我们对这个数据集的目标是创建一个能根据这些属性预测二手车售价的模型。特征的定义如下所示:
列 名 定 义
Price 以美元为单位的建议零售价(输出变量)
Mileage 车辆已经行驶的英里数
Cylinder 车辆发动机里的气缸数量
Doors 车门的数量
Cruise 代表车辆是否具有定速巡航控制的指示变量
Sound 代表车辆是否具有升级的音响设备的指示变量
Leather 代表车辆是否具有真皮座椅的指示变量
Buick 代表车辆的商标是否是别克(Buick)的指示变量
Cadillac 代表车辆的商标是否是凯迪拉克(Cadillac)的指示变量
Chevy 代表车辆的商标是否是雪佛兰(Chevy)的指示变量
Pontiac 代表车辆的商标是否是庞蒂亚克(Pontiac)的指示变量
Saab 代表车辆的商标是否是萨博(Saab)的指示变量
Saturn 代表车辆的商标是否是土星(Saturn)的指示变量
convertible 代表车辆的类型是否是敞篷车的指示变量
coupe 代表车辆的类型是否是双门轿车的指示变量
hatchback 代表车辆的类型是否是斜背式汽车的指示变量
sedan 代表车辆的类型是否是四门轿车的指示变量
wagon 代表车辆的类型是否是旅行轿车的指示变量
和前面关于CPU数据的machine数据集的处理方式一样,我们要分析输入特征的相关系数:
类似对前面CPU数据的machine数据集的处理,我们把caret包中findCorrelation()函数的cutoff参数设定为0.75,就得到了特征之间的相关情况。通过直接查看相关矩阵,我们发现在Doors特征和coupe特征之间存在相对较高程度的相关性。通过对这两个特征进行交叉分析(cross-tabulating),可以看到为什么会存在这种情况。如果知道某个汽车的类型是双门轿车,那么车门的数量就总是等于2。如果该汽车不是双门轿车,那么它最有可能的车门数是4。
这些二手车数据的另一个有问题的表现是,某些特征是其他一些特征的完全线性组合。这个问题是通过使用caret包里的findLinearCombos()函数发现的:
在这里,我们获得的指示是去掉coupe列和wagon列,它们分别是第15和第18个特征,因为它们恰好是其他特征的组合。我们会从我们的数据框里去掉它们,这样还能消除之前看到的相关性问题。
下一步,我们要把数据划分为训练集和测试集:
既然已经准备好了数据,我们就要创建一些模型了。
2.4 评估线性回归模型
再次利用lm()函数,用线性回归模型来拟合数据。我们的两套数据集会用到上述数据框里剩下的所有输入特征。R提供了一种编写公式的简写方式,它可以把某个数据框里的所有列作为特征,除了被选为输出的列之外。这是利用一个句号符来完成的,如下列代码片段所示:
一旦我们准备好了所有的数据,训练一个线性回归模型就是一行代码的事情,但是如果要研究这个模型以便判定其效果,后面就会有重要的工作。幸运的是,我们可以利用summary()函数立刻获得某些关于这个模型的重要信息。该函数对于CPU数据集的输出如下所示:
在重复了一次之前对lm()函数本身的调用之后,summary()函数给出的信息形成了三个独立的部分。第一部分是模型残差(residual)的摘要,它是模型对训练它的数据中的观测数据产生的误差。第二部分是一个表格,包含了模型各系数的预测值及其显著性检验的结果。最后几行则显示了该模型的一些总体性能衡量指标。如果在二手车数据集上重复同样的过程,我们会在该模型的摘要里注意到如下的一行:
产生这个结果的原因是由于潜在的依赖关系,导致我们还有1个特征对输出的作用无法和其他特征分清楚。这种现象被称为混叠(aliasing)。alias()命令会显示我们需要从模型中去除的特征:
正如我们所见,有问题的特征就是Saturn特征,所以我们会去除这个特征并重新训练模型。要从一个线性回归模型去除某个特征,可以把它加到公式中的句点之后,并在它之前加上一个减号:
2.4.1 残差分析
残差就是模型对特定观测数据产生的误差。换言之,它是输出的实际值和预测值之间的差异:
ei=yi=
在构建一个良好的回归模型时,对残差的分析是非常重要的,因为它能体现这个模型的很多方面,从不能成立的假设和拟合的质量到其他问题(比如离群值)。要理解残差摘要里的衡量指标,可以想象把残差从小到大排序。除了在这个序列里两个极端位置上的最小值和最大值之外,这个摘要还会显示第一个和第三个四分位数(quartile),它们分别是这个序列中处于四分之一和四分之三位置的值。中位数(median)则是序列正中位置的值。四分位间距(interquartile range)是序列处于第一和第三个四分位数之间的部分,根据定义可知,它包含了一半数据。首先来看一下CPU模型里的残差摘要,其中有趣的一点是,第一个和第三个四分位数的值与最小和最大值相比是很小的。这是存在一些具有很大残差误差的点的首要迹象。在理想情况下,残差应该具有0中位数和较小的四分位数值。可以通过关注lm()函数所产生模型具有的residuals属性,来重现summary()函数产生的残差摘要:
注意,在前面关于二手车模型的示例中,我们需要把残差的值和输出变量的平均值进行比较,以获得残差是否过大的印象。这样,前面的结果显示在训练数据中二手车的平均售价为$21k,预测结果的 50% 基本是在正确值的±$1.6k范围内,这个情况看起来是相当合理的。显然,CPU模型的残差的绝对值都小得多,这是因为该模型的输出变量(即发布的CPU相对性能)比二手车模型里的Price变量值要小得多。
在线性回归里,我们假设模型里不能化简的误差是以正态分布的形式随机分布的。一种称为分位图(Quantile-Quantile plot,Q-Q plot)的诊断图有助于从视觉上评判该假设的符合程度。这种图背后的关键思想是,我们可以通过比较它们分位数(quantile)的值来比较两种分布。一个分布的分位数基本上是一个随机变量的距离均等的区间,这样的每个区间具有相同的概率。例如,四分位数把一个分布划分为4个概率相等的部分。如果两个分布相同,那么分位图就应该是直线y=x的图形。要检查残差是否符合正态分布,可以用它们的分布和一个正态分布进行比较,查看得到的图形和y=x的接近程度。
有很多其他的方法可以检查模型残差是否是正态分布。有一个很好的工具是R语言的nortest包,它实现了很多知名的正态性检验方法,包括Anderson-Darling检验和Lilliefors检验。此外,stats包包含了shapiro.test()函数,可以用于进行Shapiro-Wilk正态性检验。
下列代码会对两个数据集创建分位图:
两个数据集的分位图如下所示:
两个模型的残差看起来和理论上的正态分布分位数相当贴近,虽然拟合程度并非完美,但是这对于大部分实际环境数据来说是典型的情况。第二种对线性回归非常有用的诊断图被称为残差图(residual plot)。它是反映训练数据中观测数据的残差和对应拟合值之间关系的一种图表。换言之,它是配对(, ei)的一个图。残差图有两个重要属性是我们特别感兴趣的。首先,我们希望通过检查残差是否在不同的拟合值范围中保持大小一致,来确认有关常数方差的假设是成立的。其次,我们必须检验在残差中不存在某种模式。但是,如果我们观察到了某种模式,它就有可能是一个迹象,表明对应的模型对于涉及的特征是非线性的,或者在模型里有缺失的特征没有被纳入。实际上,一种发现新特征的有用方法就是寻找和模型残差相关的新特征。
这两张图在图的左侧部分都显示了细微的残差递减模式。稍微更令人担心的是,对于两个输出变量的较大值,残差的方差(the variance of the residual)看起来有点大,这有可能表明误差并不是同方差的(homoscedastic)。这个问题在第二个有关二手车信息的图中更为显著。在前面的两个残差图中,我们也标记了某些较大的残差(绝对值大小)。我们很快还会看到可能有一些潜在的离群值。另一种获得残差图的方法是对lm()函数本身产生的模型调用plot()函数。这样会创建4个诊断图,包括残差图和分位图。
2.4.2 线性回归的显著性检验
在检查了残差的摘要之后,我们应该关注的下一个东西是模型所产生的系数表。在这里,每个估算出来的系数都配套了额外的一组数值,在最后还有一组星号或点。首先,这样一连串的数值看起来可能有点混乱,但是把所有这些信息包括进来是有充分理由的。当对某些数据收集测量值并指定一组特征来构建一个线性回归模型时经常会遇到的情况是,里面有一个或多个特征和我们要预测的输出实际上并不相关。当然,这是我们在之前收集数据的时候并没有在意的问题。理想情况下,我们会希望这个模型不但找到和输出变量确实存在依赖关系的那些特征所对应的最佳系数值,而且能告诉我们哪些特征是不需要的。
判定模型是否需要某个特征的一种可参考的方法是训练两个模型而不是一个。第二个模型会包含第一个模型的所有特征,再去掉需要确认其显著性的那个特征。然后就可以通过它们残差的分布情况来检验两个模型是否有差别。实际上这就是R语言会对我们在每个模型里指定的所有特征进行的处理。对于每个系数,都会针对它对应的特征和输出变量无关的无效假设(null hypothesis)构建一个置信区间(confidence interval)。具体而言,对于每个系数,我们都会考虑一个包括除了该系数对应的特征之外的所有其他特征的线性模型。然后,我们会检验把这个特征加入模型是否能显著改变残差误差的分布,这种改变会是该特征和输出存在线性关系的证据,如果成立,则该系数就应该是一个非0值。R语言的lm()函数会自动为我们进行这些检验。
在统计学里,置信区间会把一个点估计(point estimate)和该估计的精度合并在一起。具体做法是指定一个区间,在某种程度的可信度下,可以期望要估计的参数的实际值会处于该区间内。某个参数的95%置信区间实际上告诉我们的是,如果要从同一个实验收集 100 个数据样本,并在每个样本里为这个估计的参数构建一个95%的置信区间,那么这些数据样本里有 95 个会满足目标参数的实际值,处于它所对应的置信区间内。对于同样程度的可信度,为方差较大的点估计(例如对只有很少的数据点作出估计的情况)构建的置信区间,和对较小方差作出的估计相比,往往会定义出范围更大的区间。
我们来看看CPU模型的摘要输出的一个快照,它显示了CPU模型中截距和 MYCT 特征的系数:
我们目前关注的是MYCT特征,它所在行里的第一个数值是其系数的估算值,这个值大约是0.05 (5.210×10-2)。标准误(standard error)是这个估算值的标准差(standard deviation),它等于下一个数值给出的0.01885。我们可以通过对在0和该系数估算值之间的标准误个数进行计数,判定该系数是否实际为0(代表该特征与输出线性无关)的置信度。为此,我们可以用系数估算值除以标准误,这就是t值(t-value)的定义,也就是该行里的第三个值:
这样,我们的MYCT系数值和0值之间几乎隔开了3个标准误的数值,这个指标很好地表明该系数不太可能为0。t值越大,就越能说明我们应该给模型中的这个特征赋予非0的系数值。我们可以把这个绝对数值转变为一个概率,它可以告诉我们该系数的真实值为0的可能性。这个概率是从学生t分布(Student抯 t-distribution)得到的,被称为p值(p-value)。对于MYCT特征,这个概率是0.006335,是很小的。可以利用pt()函数得到这个值:
pt()函数是t分布的分布函数,而t分布是对称的。为了理解p值以上面这种方式进行计算的原因,要注意我们感兴趣的是t值的绝对值比我们计算的值更大的概率。要得到这个结果,我们首先要获得该t分布上尾或右尾(the upper / right tail)部分的概率,然后将其乘以2,这样就把下尾(the lower tail)部分也包含进来了。运用基本的分布函数在R语言里是一项非常重要的技能,如果前面这个示例看起来难度太大,我们在关于R语言的在线教程里还有一些示例可供参考。t分布是以自由度作为参数的。
自由度的数量实际上是在计算某个统计量(例如某个系数的估计值)时能够自由变化的变量数。在线性回归主题下,它就相当于训练数据中观测数据的个数减去模型中参数的个数(即回归系数的个数)。对于我们的CPU模型,这个数字就是179-7=172。对于具有更多数据点的二手车模型,这个数字是664。自由度这个名字来源于它和独立维度(或者说,作为某个系统的输入的信息片段)的数量之间的关系,从而反映系统能够(在不违反任何对于输入的约束条件的条件下)自由配置的程度。
作为一般的经验规则,我们会希望p值小于0.05,这相当于我们希望系数不为0值的估计具有95%的置信区间。每个系数旁边的星号数量给我们提供了一种关于置信度水平的快速视觉辅助,其中一颗星对应95%的经验规则,而两颗星代表了99%的置信区间。因此,在模型摘要里,每个不带任何星号的系数对应的就是一个按照经验规则我们没有把握将其纳入模型的特征。在CPU模型里,CHMIN特征是唯一可疑的特征,而其他特征的p值都非常小。二手车模型的情况就不同了。这里,有4个特征都是可疑的,截距也是如此。
在线性回归模型主题下正确理解p值的含义是很重要的。首先,我们不能也不应该对p值进行互相比较来评判哪个特征是最重要的。其次,较高的p值并不一定代表某个特征和输出之间不存在线性关系;它只表明在所有其他模型特征存在时,这个特征对于输出变量不能提供任何新的信息。最后,我们必须永远记住,95%的经验规则并非绝对真理,它只是在特征(以及对应的系数)的数量不是特别大的时候确实有用。在95%的置信度下,如果在模型里有1000个特征,可以预期得到错误系数的结果是50个。因此,线性回归系数的显著性检验对于高维问题并不是那么有用。
最后一个显著性检验值实际上出现在lm()输出摘要的最下部,并且是在最后一行。这一行给我们提供了F统计量(F statistic),其命名来源于检查两个(理想状况下是正态的)分布的方差之间是否存在统计显著性(statistical significance)的F检验(F test)。这个示例中的F统计量尝试评价某个所有系数都为0的模型中的残差方差(the variance of the residuals)是否和我们训练的模型的残差方差有显著性的差异。
换言之,F检验会告诉我们训练的模型是否能解释输出中的某些方差,如果能,我们就知道至少有一个系数必须为非0值。虽然这个检验对于我们有很多系数的情况不是那么有用,但它还是能检验系数的整体显著性,而不会遇到t检验对于单个系数的那种问题。摘要部分对此显示了一个非常小的p值,所以我们知道我们的系数里至少有一个是非0值。我们可以调用anova()函数重现运行过的F检验,该函数代表方差分析(analysis of variance)。该检验会用只有截距而没有特征的方式建立的空模型(null model)和训练模型进行比较。我们要用CPU数据集对这个函数进行如下演示:
注意,空模型的公式是PRP~1,其中的1代表截距。
2.4.3 线性回归的性能衡量指标
在摘要里,最后的细节是和该模型作为一个整体的性能以及该线性模型和数据的拟合程度相关的。要理解我们如何评估线性回归的拟合,必须首先指出的是,线性回归模型的训练准则是使均方差(MSE)在训练数据上最小化。换言之,把一个线性模型拟合到一组数据点相当于找到一条直线,它的斜率和位置要让它与这些点的距离的平方的总和(或平均值)最小。因为我们把某个数据点和它在该直线上的预测值之间的误差称为残差,我们可以定义残差平方和(Residual Sum of Square,RSS)为所有残差的平方之和:
换言之,RSS就是误差平方和(Sum of Squared Error,SSE),因此我们可以通过下面的简单公式把它和我们已经熟悉的均方差(MSE)联系起来:
除了某些历史原因之外,RSS之所以成为值得注意的重要衡量指标,是因为它和另一个被称为RSE的重要衡量指标相关,我们后续会讨论到RSE。为此,我们需要先对训练线性回归模型的过程有个直观的了解。如果用人工数据多次进行简单线性回归实验,每次改变随机种子(random seed)以获得不同的随机样本,就会得到一组很可能非常接近实际总体回归线的回归线,正如进行单次实验所看到的那样。这表明了一个事实,即线性模型总体而言是具有低方差的特点的。当然,尝试近似的未知函数也有可能是非线性的,如果是这样,那么即使总体回归线也不太可能对于非线性函数的数据产生良好的拟合。这是因为线性假设是非常严格的,因此,线性回归是一种带有较高偏误的方法。
我们要定义一个叫作残差标准差(Residual Standard Error,RSE)的衡量指标,它会估计我们的模型和目标函数之间的标准差。这就是说,它会大致地衡量模型和总体回归线之间的平均距离。它是以输出变量的单位衡量的,并且是一个绝对值。因此,它需要和y的值进行比较,以便判定它对于特定样本是高还是低。带有k个输入特征的模型的一般RSE可以按如下公式计算:
对于简单线性回归,其特征数k=1:
可以利用前面的公式对两个模型计算RSE值,结果如下所示:
要解释这两个模型的RSE值,就需要把它们和输出变量的均值进行比较:
注意,在CPU模型中,其取值为61.3的RSE值和二手车模型大约为2947的RSE值相比是相当小的。但是,就它们和对应输出变量的均值的近似程度而言,我们可以看到反而是二手车模型的RSE表现了更优的拟合。
现在,虽然RSE作为一个绝对数值在和输出变量的均值进行比较方面是有用的,但是我们经常需要一个相对数值用来在不同的训练场景下进行比较。为此,在评估线性回归模型的拟合情况时,我们也经常观察R2统计量(R2 statistic)。在摘要信息里,它是用“multiple R-squared”来指示的。在提供它的计算公式之前,我们首先要讲解总平方和(Total Sum of Square,TSS)的概念。总平方和是和输出变量里的总方差成比例的,它的设计思路是,在进行回归之前,用它来衡量输出变量内在的变异性大小。TSS的计算公式是:
R2统计量背后的思想是,如果一个线性回归模型是对实际总体模型比较接近的拟合,它就应该能完整地获得输出中的所有方差。实际上,我们经常把R2统计量当做一个相对数值,用它来指明某个回归模型解释了输出变量的方差中多大的比例。当运用回归模型得到了输出变量的一个估计值时,可以看到,观测数据中的误差被称为残差,而RSS实际上是与预测值和输出函数真实值之间留下的差异成比例的。因此,可以把R2统计量(即线性回归模型所解释的输出y中方差的数量)定义为起始方差(TSS)和终结方差(RSS)之间的差值相对于这个起始方差(TSS)的比例。用公式表示,这个计算就是:
从这个公式我们可以看到,R2的范围在0和1之间。接近1的值表明良好的拟合,因为它意味着大部分输出变量里的方差已经被回归模型所解释。另一方面,一个较小的值表明模型里的误差中还有显著的方差,说明模型拟合得不够好。让我们来看看,对于这两个模型,R2统计量是如何手工计算出来的:
我们用到了lm()函数训练的模型的fitted.values属性,它是模型对于训练数据作出的预测。计算出的两个值都相当大,其中二手车模型又一次显示出了略微更好一些的拟合。现在我们看到了评价线性回归模型的两个重要衡量指标,即RSE和R2统计量。现在,我们要考虑两个变量之间的线性关系是否还有一种更总体性的衡量指标,也可以运用到示例中。从统计学角度,我们可以想起相关系数的概念,它正好是描述这个问题的。
两个随机变量X和Y之间的相关系数(correlation)可以由下面的公式得出:
其实,在简单回归的情况下,输出变量和输入特征之间相关系数的平方就是R2统计量,这个结果进一步增强了后者作为一种有用的衡量指标的重要性。
2.4.4 比较不同的回归模型
当需要对用同一组输入特征训练的两个不同回归模型之间进行比较时,R2统计量就会非常有用。不过,我们还经常需要对输入特征数量并不相等的两个模型进行比较。例如,在特征选择的过程中,我们会需要知道把某个特征纳入模型是否是个好的思路。R2统计量的一个局限性是它对具有更多输入参数的模型往往会产生更大的值。
调整后的R2(adjusted R2)试图纠正一个问题,即R2统计量对具有更多输入特征的模型总是会产生更大的值,因而容易受到过拟合的影响。调整后的R2总体上比R2本身更小,这是我们可以通过检查模型摘要里的值来检验的。调整后的R2的计算公式如下所示:
其中n和k的定义和在R2统计量里是一样的。现在,让我们在R语言里实现这个函数,并对我们的两个模型计算调整后的R2:
另外还有几个常用的性能衡量指标也可以用来比较带有不同数量特征的模型。赤池信息准则(Akaike Information Criterion,AIC)使用了一种信息论方法,通过平衡模型的复杂性和精确度来评估模型的相对质量。在我们按方差最小化方法训练的线性回归模型上,它和另一个著名统计量—马洛斯Cp(Mallow抯 Cp)是成比例的,所以这两个性能衡量指标是可以互换使用的。第三个衡量指标是贝叶斯信息准则(Bayesian Information Criterion,BIC)。它和前两个衡量指标相比,对带有更多变量的模型往往会产生更不利的结果。
2.4.5 在测试集上的性能
到目前为止,我们在训练数据上观察了模型的性能。这对于判定一个线性模型能否很好地拟合数据有重要意义,但不会让我们对它处理未知数据的预测精确度有清晰的认识。为此,我们要转到测试数据集。要利用模型进行预测,可以调用predict()函数。这是R语言里的一个通用函数,有很多包扩展了它。对于用lm()训练过的模型,我们只需要提供模型和带有我们需要进行预测的观测数据的数据框即可:
下一步,要定义我们自己的函数,用来计算均方差(MSE):
对于每个模型,我们已经调用了compute_mse()函数来返回训练和测试的均方差。在这个示例中,碰巧两个测试均方差的值都比训练均方差的值更小。测试均方差是略微大于还是略微小于训练均方差并不是特别重要的问题。重要的问题是测试均方差并没有显著大于训练均方差,因为测试均方差显著大于训练均方差就表明我们的模型对数据是存在过拟合的。注意,尤其是对于CPU模型,在原始数据集里的观测数据数量很少,这就导致测试集也是很小的。因此,就上述模型对于未知数据的预测性能而言,我们会在估计的精确度方面持保留态度,因为利用一个小的测试集作出的预测会具有更大的方差。
2.5 线性回归的问题
在本章,我们已经看到了在尝试构建线性回归模型的时候遇到某些问题的一些示例。我们讨论过的一大类问题是和模型在线性、特征独立性和同方差性及误差的正态性等方面的假设相关的。我们还具体看到了诊断这类问题的方法,要么借助像残差图这样的图,或者利用能识别非独立成分的函数。本节要探讨线性回归中可能出现的一些其他问题。
2.5.1 多重共线性
作为预处理步骤的一部分,我们会尽力去除相互线性相关的特征。在此过程中,我们寻找的是一种完全的线性关系,这就是完全共线性(perfect collinearity)的示例。共线性(collinearity)是描述两个特征近似具有一种线性关系的性质。这对于线性回归会产生一个问题,因为我们会尝试给相互之间接近是线性函数的变量分配独立的系数。这样会导致的情况是:两个高度共线性的特征具有较大的p值表明它们和输出变量不相关,但如果去除其中一个特征并重新训练该模型,剩下的那个特征就会具有较小的p值。共线性的另一种典型迹象是某个系数出现不正常的符号,例如在一个预测收入水平的线性模型里,教育背景的系数为负。两个特征之间的共线性可以通过配对相关系数进行检测。处理共线性的一种方式是把两个特征合并为一个(例如通过取平均值);另一种方式是直接去除其中一个特征。
多重共线性(multicollinearity)出现在线性关系涉及多于两个特征的情况。检测它的一种标准方法是对线性模型中的每个输入特征计算其方差膨胀因子(variance inflation factor,VIF)。简而言之,VIF可以用来估计由于该特征和其他特征共线性而直接导致在具体系数的估计过程中能观察到的方差的增量。这通常是通过拟合一个线性回归模型来进行的,我们把其中的一个特征作为输出特征,把其他特征仍保留为常规的输入特征。然后我们对这个线性模型计算R2统计量,并据此利用公式1 / (1-R2)计算我们选取的那个特征的VIF。在R语言中,car包包含了vif()函数,它能方便地对一个线性回归模型中的所有特征计算VIF值。这里有一个经验规则是,VIF分数为4或更大的特征就是可疑的,而分数大于10就表明了多重共线性的极大可能性。既然看到了二手车数据中存在必须去除的线性依赖特征,就让我们来调查在余下的特征中是否存在多重共线性:
这里有3个值略大于4,但没有更大的了。作为一个示例,如下的代码演示了sedan特征的VIF值是如何计算的:
2.5.2 离群值
当观察我们的两个模型的残差时,我们会看到有某些观测数据比其他数据具有明显更大的残差。例如,根据CPU模型的残差图,我们可以看到观测数据200有非常大的残差。这就是一个离群值(outlier)的示例,它是一种预测值距离其实际值非常远的观测数据。由于对残差取平方的原因,离群值对RSS往往会产生显著的影响,给我们带来模型拟合效果不佳的印象。离群值可能是因为测量误差产生的,对它们的检测很重要,因为它们可能预示着不准确或非法的数据。另一方面,离群值也可能只是没有选对特征或创建了错误种类的模型的结果。
因为我们通常并不知道某个离群值是一个数据收集过程中的错误数据还是真实的观测数据,处理离群值会非常棘手。有时候,尤其是当我们有很少的离群值时,一种常见的手段是去除它们,因为包括它们往往会产生显著改变预测模型系数的效果。我们要说,离群值经常是具有较高影响(influence)的点。
离群值并不是具有高影响的唯一观测数据。高杠杆率点(high leverage point)也是这样的观测数据,在它们的特征中至少有一个具有极端值,因而远离大部分其他观测数据。cook距离(cook's distance)是一个结合了离群值和高杠杆率的概念来识别对数据具有高影响的那些点的典型衡量指标。要更深入地探索线性回归的诊断方法,有一个很好的参考书是John Fox编写的《An R Companion to Applied Regression》,由Sage Publications出版。
为了展示去除一个离群值的效果,我们要通过利用去掉观测数据 200 的训练数据创建一个新的CPU模型。然后,观察新模型是否在训练数据上有所改善。这里,我们会显示所采取的步骤和只有最后三行的模型摘要片段:
正如从减小的RSE和调整后的R2统计量看到的,我们在训练数据上得到了更好的拟合。当然,模型精确度的实际衡量还要看它在测试数据上的表现,我们也无法保证把观测数据200标记为虚假离群值的决定一定是正确的。
我们得到了一个比以前更小的测试MSE,这往往是表明我们作出了正确选择的一个良好迹象。这里要再说一遍,因为测试集比较小,所以我们也无法肯定这个事实,尽管从MSE迹象来看是正面的。
2.6 特征选择
我们的CPU模型只有6个特征。通常,我们遇到实际环境的数据集会具有来自多种不同观测数据的非常大量的特征。另外,我们会在不太确定哪些特征在影响输出变量方面比较重要的情况下,不得不采用大量的特征。除此之外,我们还有会遇到可能要分很多水平的分类变量,对它们我们只能创建大量的新指示变量,正如在第1章里所看到的那样。当面对的场景涉及大量特征时,我们经常会发现输出只依赖于它们的一个子集。给定k个输入特征,可以形成2k个不同的子集,因此即使对于中等数量的特征,子集的空间也会大到无法通过逐个子集的拟合来进行完整的探索。
要理解为什么可能的特征子集会有2k个,一种简单的方式是这样的:我们可以给每个子集分配一个独立的识别码,这个识别码是k位的二进制数字构成的字符串,如果我们选择在该子集中包含第i个特征(特征可以任意排序),那么第i个位上的数字就是1。例如,如果我们有三个特征,字符串101就对应了只包括第一个和第三个特征的子集。在这种方式下,我们构建了所有可能的二进制字符串,它从由k个0构成的字符串一直排到由k个1构成的字符串,因此我们就得到了从0到2k-1的所有整数,也就是总计2k个子集。
特征选择(feature selection)指在模型中选择一个特征子集以构成一个带有更少特征的新模型的过程。它会去除我们认为和输出变量无关的特征,因而产生一个更易于训练和解释的更简单模型。有很多方法被设计用来进行这项任务,它们一般不会用穷举的方式去搜遍可能的子集空间,而是在该空间进行引导式搜索(guided search)。
这类方法之一是前向选择(forward selection),它是通过一系列步骤进行特征选择的逐步回归(stepwise regression)的一个范例。对于前向选择,它的思路是从一个没有选择特征的空模型起步,接着进行k次(针对每个特征进行一次)简单线性回归并从中选取最好的一个。这里比较的是有相同特征数量的模型,因此可以使用R2统计量来指导我们的选择,虽然我们也可以使用AIC之类的衡量指标。一旦选择了要加入的第一个特征,我们就可以从余下的k-1个特征中选取要加入的另一个特征。因此,现在我们可以针对每个可能的特征配对进行k-1次多元回归,配对中的其中一个特征是在第一步选择的那个特征。我们可以继续按这种方式增加特征,直到我们评价了包含所有特征的模型并结束这个过程。注意,在每一个步骤里,我们都为所有后续的步骤作出了把哪个特征包含进来的艰难选择。例如,在多于一个特征的模型当中,没有包含我们在第一步选择的那个特征的那些模型就永远不需要考虑了。因此,我们并不是穷举式地搜索我们的空间。实际上,如果考虑到我们还评估了空模型,我们就可以计算进行了线性回归的模型总数为:
这个计算结果的数量级是在k2的量级上,即使对于比较小的k值而言,也已经明显比2k小多了。在前向选择过程的最后阶段,我们必须从对应每个步骤结束时得到的子集所构成的k+1个模型中进行选择。由于过程的这个最终部分要比较带有不同数量特征的模型,我们通常会使用诸如AIC的准则或调整后的R2来作出模型的最终选择。对于CPU数据集,可以通过运行下列命令来演示这个过程:
step()函数实现了前向选择的过程。我们首先给它提供了通过在训练数据上拟合无特征的线性模型而得到的空模型。对于scope参数,我们指定的是要求算法从空模型开始一直逐步处理到包含所有6个特征的完整模型。在R语言里发出这条命令会产生一个输出,它会显示迭代每一步指定了哪个特征子集。为了节省空间,我们在下表呈现这个结果以及每个模型的AIC值。注意,AIC值越小,模型就越好。
步骤 子集里含有的特征 AIC值
0 { } 1839.13
1 {MMAX} 1583.38
2 {MMAX, CACH} 1547.21
3 {MMAX, CACH, MMIN} 1522.06
4 {MMAX, CACH, MMIN, CHMAX} 1484.14
5 {MMAX, CACH, MMIN, CHMAX, MYCT} 1478.36
step()函数对前向选择采用了一种替代性的规格,它会在任何一个剩余的特征加入当前特征子集都无法改善总体评分的情况下终止。对于数据集而言,只有一个特征会从最终模型中排除,因为把它加入进来并没有提高总体评分。既有趣又令人放心的是,这个被排除的特征就是CHMIN,它也是唯一具有相对较高p值的变量,说明在有其他特征存在的情况下我们无法确信输出变量和这个特征是相关的。
有人可能会想知道是否可以从相反方向进行变量选择,也就是从一个完整的模型开始,根据去除哪个特征会给模型评分带来最大的改善来逐个去除特征。这样实际上是可以的,这样的过程被称为后向选择(backward selection)或后向淘汰(backward elimination)。在R语言里的做法是利用step()函数,指定direction参数为backward并从完整模型开始。可以用二手车数据集来演示这个过程,并把结果保存到一个新的二手车模型里:
在二手车数据集上得到的最终线性回归模型的公式如下:
正如我们所见,最终的模型丢弃了Cruise、Sound和Chevy特征。观察我们之前的模型摘要,我们可以看到,这三个特征都具有较高的p值。前面的两种方法是一种贪心算法(greedy algorithm)的示例。这也就是说,一旦作出了是否包含某个变量的选择,它就是最终决定,之后也不能撤销了。为了对它进行弥补,就有了第三种变量选择的方法,它称为混合选择(mixed selection)或双向淘汰(bidirectional elimination),它一开始会表现为前向选择方法,采用的是增加变量的前向步骤,但是在能够改善AIC的情况下也可以包含后向的步骤。不出所料,step()函数在direction参数被指定为both时就是这样做的。
既然有了两个新模型,我们就可以观察它们在测试集上的表现:
对于CPU模型,我们在测试数据集上的表现比初始模型略有改善。合适的下一步的工作可以是探索这个缩减后的特征集是否和去除离群值配合起来效果更好,这个问题留给读者作为练习。相比之下,对于二手车模型,作为去除所有这些特征的结果,我们看到测试MSE略有增加。
2.7 正则化
变量选择是一个重要的过程,因为它试图通过去除与输出无关的变量,让模型解释更简单、训练更容易,并且没有虚假的关联。这是处理过拟合问题的一种可能的方法。总体而言,我们并不期望一个模型能完全拟合训练数据。实际上,过拟合问题通常意味着,如果过分拟合训练数据,对我们在未知数据上的预测模型精确度反而是有害的。在关于正则化(regularization)的这一节,我们要学习一种减少变量数以处理过拟合的替代方法。正则化实质上是在训练程序中引入刻意的偏误或约束条件,以此防止系数取值过大的一个过程。因为这是一个尝试缩小系数的过程,所以我们要观察的这种方法也称为收缩方法(shrinkage method)。
2.7.1 岭回归
当参数的数量非常大的时候,尤其是和能获得的观测数据的数量相比很大时,线性回归往往会表现出非常高的方差。这也就是说,在一些观测数据中的微小变化会导致系数的显著变化。岭回归(ridge regression)是一种通过其约束条件引入偏误但能有效地减小模型的方差的方法。岭回归试图把残差平方和(RSS)加上系数的平方和乘以一个用希腊字母表示的常数构成的项(∑2)的累加和最小化。对于一个带有k个参数的模型(不包括常数项0)以及带有n条观测数据的数据集,岭回归会使下列数量最小化:
在这里,我们还是想把RSS最小化,但第二个项是惩罚项,当任何系数很大时它也会很大。因此,在最小化的时候,我们就会有效地把系数压缩到更小的值。参数被称为元参数(meta parameter),它是我们需要选择或调优的。一个非常大的值会掩盖RSS项并把系数压缩到0。过小的值对于过拟合就没什么效果,而一个等于0的参数则只是进行普通的线性回归。
当进行岭回归时,经常需要通过把所有特征值除以它们的方差来进行比例缩放。这对于普通线性回归不适用,因为如果某个特征用一个等于10的因子进行缩放,那么系数也会被一个等于1/10的因子进行缩放来补偿。在岭回归里,特征的缩放则是通过惩罚项来影响所有其他特征的计算的。
2.7.2 最小绝对值收缩和选择算子
最小绝对值收缩和选择算子(lasso)是岭回归的一种替代正则化方法。它们之间的差别只是体现在惩罚项里,它最小化的是系数的绝对值之和。
其实,惩罚项里的这个差别是非常显著的,由于lasso会把某些系数完全收缩到0,所以它兼具了收缩和选择的功能,而这对于岭回归是不具备的。即便如此,在这两种模型之间并没有明确的赢家。依赖于输入特征的某个子集的模型往往用lasso表现更好;很多不同变量的系数具有较大分散度的模型则往往在岭回归下有更好的表现。两者通常都是值得尝试的。
岭回归里的惩罚项经常被称为l2惩罚项,而lasso里的惩罚项被称为l1惩罚项。这是来自向量的范数(norm)的数学概念。向量的范数是一个函数,它会给该向量分配一个代表其长度或大小的正数。l1和l2范数都是一类被称为p范数(p-norm)的范数
中的范例,对于具有n个分量的向量v,p范数具有下面的一般形式:
2.7.3 在R语言里实现正则化
有很多不同的函数和扩展包都实现了岭回归,例如MASS包里的lm.ridge()函数和genridge包里的ridge()函数。对于lasso来说,也有lars包可用。在本章,我们会采用来自glmnet包的glmnet()函数,这是因为它具有一致且友好的接口。正则化处理的关键是确定采用一个适当的值。glmnet()函数采用的方法是利用一组不同的值,并针对每个值训练一个回归模型。然后,你既可以手工挑出一个值,也可以利用某个技术来估算最佳的值。我们可以通过lambda参数指定要尝试的值序列;否则,函数会采用默认的100个值的序列。给glmnet()函数的第一个参数必须是一个特征矩阵,它可以利用model.matrix()函数来构建。
第二个参数是一个带有输出变量的向量。最后,alpha参数是岭回归(参数取值为0)和lasso(参数取值为1)之间的切换开关。我们现在准备用二手车数据集来训练一些模型:
因为提供了250个值的序列,所以我们实际上训练了250个岭回归模型和另外250个lasso模型。可以对第100个模型来查看glmnet()函数产生的对象的lambda属性里的值,并对该对象调用coef()函数来获得对应的系数,如下所示:
也可以调用plot()函数来获得一个绘图,显示系数的值是如何随着值的对数变化而变化的。把岭回归和 lasso 的对应绘图并排显示是非常有帮助的:
这两幅图的关键差异在于,lasso会强制把很多系数减小到正好为0,而在岭回归里,系数往往是平滑地减小,只有当取极端值时才会全部一起变为0。这个规律可以进一步从两幅图的上部水平轴上的数值看出来,它显示的是当取不同值时非零系数的个数。从这个意义上说,lasso在两个方面具有明显优势:它经常可以用来进行特征选择(因为系数为0的特征实际上是不包含在模型中的),以及为了把过拟合问题最小化而进行正则化。我们可以通过改变赋予xvar参数的值来获得其他有用的绘图。它取值为norm时会在x轴绘制系数的l1范数,取值为dev时则会绘制该模型所解释的偏差(deviance)的百分比。我们会在下一章学习有关偏差的内容。
为了处理给找到一个合适值的问题,glmnet包提供了cv.glmnet()函数。它对训练数据运用了一种称为交叉验证(cross-validation)的技术(我们会在第5章中学习到它),从而找到能够最小化均方差(MSE)的合适的值。
如果我们绘制cv.glmnet()函数产生的结果,可以看到MSE是如何随着的不同取值而变化的:
每个点上下的竖条是为每个的绘制值而显示的MSE估计值上下的标准差的误差条。绘图还显示了两条垂直的虚线。第一条垂直线对应的是lambda.min的值,它是交叉验证提出的优化值。右侧的第二条垂直线是lambda.1se属性的值。它对应了距离lambda. min一个标准误(standard error)的值,并产生了一个更为正则化的模型。
在glmnet包里也有一个predict()函数,它现在可以在多种不同的背景下执行。例如,可以针对某个不在原始清单里的值求得模型的系数。例如,进行如下操作:
注意,貌似lasso在这个示例中并没有把任何系数强制为0,这说明根据MSE的情况,不建议去除二手车数据集里的任何系数。最后,我们可以再次调用predict()函数,通过newx参数为要进行预测的观测数据提供一个特征矩阵,从而用一个正则化的模型来作出预测:
在测试数据上,lasso模型的效果最好,且略好于普通模型,而不像岭回归在这个示例中的表现那么不理想。
2.8 小结
在本章,我们学习了线性回归,这是一种让我们能在有监督学习环境下拟合线性模型的方法,在这种环境下,我们有一些输入特征和一个数值型的输出。简单线性回归是对只有一个输入特征的情况的命名,而多元线性回归则描述了具有多个输入特征的情况。线性回归是解决回归问题很常用的第一步骤。它假定输出是输入特征的线性加权组合,再加上一个无法化简、符合正态分布、具有0均值和常数方差的误差项。这种模型也假设特征是相互独立的。线性回归的性能可以通过一组不同的衡量指标来进行评价,从更标准的MSE到诸如R2 统计量等其他指标。我们探讨了几种模型诊断和显著性检验方法,它们用于检测从不成立的假设到离群值等问题。最后,我们还讨论了如何用逐步回归进行特征选择,以及利用岭回归和lasso进行正则化。
线性回归模型具有多种优势,包括快速和开销小的参数计算过程,以及易于解释和推断的模型,这是因为它具有形式简单的优点。有很多检验方法可以用来诊断关于模型拟合的问题,并对系数的显著性进行假设检验。总体来说,可以认为它是低方差的一种方法,因为它对于数据中的小误差比较健壮(robust)。就其不足之处而言,因为它作出了非常严格的假设,尤其是输出函数在模型参数里必须是线性的,所以它就会引入很高程度的偏误,对于比较复杂或高度非线性的一般函数,这种方法往往就表现不佳。此外,我们也看到了,当输入特征数量变得很多时,我们就不能依赖于系数的显著性检验。当我们在一个高维特征空间里工作时,这个事实再加上特征之间的独立性假设,就会使线性回归成为相对较差的一种选择。
在下一章,我们会学习逻辑回归,它是一种用于分类问题的重要方法。