机器学习实践:测试驱动的开发方法(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-20 23:18:48

点击下载

作者:柯克(Matthew Kirk)

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

机器学习实践:测试驱动的开发方法

机器学习实践:测试驱动的开发方法试读:

前言

这是一本介绍如何解决棘手问题的书。机器学习是计算技术的一项令人叹为观止的应用,因为它要解决的很多问题都源自科幻小说。机器学习算法可用于解决语音识别、映射、推荐以及疾病检测等复杂问题。机器学习的应用领域浩瀚无垠,也正因为如此它才这样引人入胜。

然而,这种灵活性也使得机器学习技术令人望而却步。它的确可以解决许多问题,但如何知晓我们求解的是否是正确的问题,或者是否应最先求解某个问题呢?此外,令人沮丧的是,大部分学术编码标准都不够严密。

即便是在今天,人们仍未对如何编写高质量的机器学习代码给予足够的关注,这是无比遗憾的。将一种观念在整个行业广泛传播的能力取决于有效沟通的能力。如果我们编写的代码本身就质量低劣,那么恐怕不会有很多人愿意聆听我们的讨论。

本书便是我对于该问题的回答。我试图按照容易理解的方式为大家讲授机器学习。这门学科本身难度就不小,况且我们还需要阅读代码,尤其是那些极难理解的古老 C 实现,无疑更是雪上加霜。

很多读者会对本书采用 Ruby 而非 Python 感到非常困惑。我的理由是用 Ruby 编写测试程序是一种诠释你所写代码的美妙方式。本质上,这本通篇采用测试驱动方法的书讲述的是如何沟通,具体说来是如何与机器学习这个奇妙的世界沟通。从本书可学到的知识

应该说,本书对机器学习的介绍并不全面。因此,我向你强烈推荐 Peter Flach 编著的 Machine Learning: The Art and Science of Algorithms that Make Sense of Data(Cambridge University Press)1。如果你希望读一些数学味道较浓的书,可参阅 Tom Mitchell 的 Machine Learning 系列。此外,你还可参考 Stuart Russel 和 Peter Norvig 编写的有关人工智能的名著 Artificial Intelligence: A Modern Approach, 3rd Edition(Prentice Hall)。1中文版即将由人民邮电出版社出版。——编者注

阅读完本书之后,你并不会获得机器学习的博士学位,但我希望本书能向你传授足够的知识,帮助你开始研究如何利用机器学习技术解决涉及数据的现实问题。对于求解问题的方法以及如何在基础层面上使用它们,本书会提供大量相关示例。

你还将掌握如何解决那些比普通的单元测试更为模糊的复杂问题。本书的阅读方法

阅读本书的最佳方法是找到一些能够让你感到兴奋的例子。我力图每一章都介绍一些这样的例子,虽然有时无法尽如人意。我不希望本书过于偏重理论,而是希望通过示例向你介绍机器学习能够解决的一些具体问题,以及我处理数据的方法。

在本书的大多数章中,我都试图在一开始便引入一个商业案例,之后开始研究一个可求解的问题,直到结尾。本书是一部短篇读物,因为我希望你能够专注于理解书中的代码,并深入思考这些问题,而非沉浸在理论当中。本书读者

本书面向的读者包括三个群体:开发人员、CTO 以及商业分析师。

开发人员已经熟知如何编写代码,且想更多地了解机器学习这个激动人心的领域。他们拥有在计算环境中求解问题的背景,可能使用过 Ruby 编写代码,也可能从未接触过这种语言。他们是本书的主要读者群,但本书在写作时也兼顾了 CTO 和商业分析师。

CTO 是那些真正希望了解如何利用机器学习技术提升公司业务的人。他们可能听说过 K 均值算法、K 近邻算法,但不知道这些算法的适用性。商业分析师与之相似,只是不像 CTO 那样关注技术细节。为了这两个读者群,我在每章的开头都准备了一个商业案例。作者联系方式

如果你喜欢我之前所做的演讲,或想为某个问题寻求帮助,欢迎通过电子邮件与我联系。我的邮箱是 matt@matthewkirk.com。为了增进我们之间的联系,如果你来到西雅图地区,且我们的日程允许的话,我非常乐意请你喝一杯咖啡。

如果希望查看本书的所有代码,可从 GitHub 站点 http://github.com/thoughtfulml 免费下载。排版约定

本书使用了下列排版约定。● 楷体表示新术语或突出强调的内容。● 等宽字体(constant width)表示程序片段,以及正文中出现的变量、函数名、数据库、

数据类型、环境变量、语句和关键字等。● 加粗等宽字体(constant width bold)表示应该由用户输入的命令或其他文本。 该图标表示提示或建议。 该图标表示一般注记。 该图标表示警告或警示。 该图标表示非常重要的警告,请仔细阅读。使用代码示例

补充材料(代码示例、练习等)可以从 http://github.com/thoughtfulml 下载。

本书是要帮你完成工作的。一般来说,如果本书提供了示例代码,你可以把它用在你的程序或文档中。除非你使用了很大一部分代码,否则无需联系我们获得许可。比如,用本书的几个代码片段写一个程序就无需获得许可,销售或分发 O'Reilly 图书的示例光盘则需要获得许可;引用本书中的示例代码回答问题无需获得许可,将书中大量的代码放到你的产品文档中则需要获得许可。

我们很希望但并不强制要求你在引用本书内容时加上引用说明。引用说明一般包括书名、作者、出版社和 ISBN。比如:“Thoughtful Machine Learning by Matthew Kirk (O'Reilly). Copyright 2015 Matthew Kirk, 978-1-449-37406-8”。

如果你觉得自己对示例代码的用法超出了上述许可的范围,欢迎你通过 permissions@oreilly.com 与我们联系。®Safari Books Online

Safari Books Online(http://www.safaribooksonline.com)是应运而生的数字图书馆。它同时以图书和视频的形式出版世界顶级技术和商务作家的专业作品。技术专家、软件开发人员、Web 设计师、商务人士和创意专家等,在开展调研、解决问题、学习和认证培训时,都将 Safari Books Online 视作获取资料的首选渠道。

对于组织团体、政府机构和个人,Safari Books Online 提供各种产品组合和灵活的定价策略。用户可通过一个功能完备的数据库检索系统访问 O'Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology 以及其他几十家出版社的上千种图书、培训视频和正式出版之前的书稿。要了解 Safari Books Online 的更多信息,我们网上见。联系我们

请把对本书的评价和问题发给出版社。

美国:

  O'Reilly Media, Inc.

  1005 Gravenstein Highway North

  Sebastopol, CA 95472

中国:

  北京市西城区西直门南大街2号成铭大厦 C 座807室(100035)

  奥莱利技术咨询(北京)有限公司

O'Reilly 的每一本书都有专属网页,你可以在那儿找到本书的相关信息,包括勘误表、示例代码以及其他信息。本书的网站地址是:

  http://shop.oreilly.com/product/0636920032298.do

对于本书的评论和技术性问题,请发送电子邮件到:bookquestions@oreilly.com

要了解更多 O'Reilly 图书、培训课程、会议和新闻的信息,请访问以下网站:

  http://www.oreilly.com

我们在 Facebook 的地址如下:http://facebook.com/oreilly

请关注我们的 Twitter 动态:http://twitter.com/oreillymedia

我们的 YouTube 视频地址如下:http://www.youtube.com/oreillymedia致谢● Mike Loukides,对于我的利用测试驱动开发来编写机器学

习代码的想法,他表现出了浓厚的兴趣。● 我的编辑 Ann Spencer,在我撰写本书的数月间,她教给了

我大量编辑知识和技巧,并针对本书的设计提出了宝贵意见。

我要感谢 O'Reilly 团队的所有成员,是他们成就了本书。我尤其要感谢下面这些人。

我的审稿人:● Brad Ediger,当我提出要编写一本关于基于测试驱动的机器

学习代码的书籍时,他对于这一古怪想法兴奋不已,并对本书的

初稿提出了大量极有价值的意见。● Starr Horne,他在审阅过程中向我提供了大量真知灼见。感

谢他在电话会议中与我讨论了机器学习、错误报告等。● Aaron Sumner,他针对本书的代码结构提出了极有价值的

意见。

我极为出色的合作者,以及在本书编写过程中提供帮助的朋友们:Edward Carrel、Jon-Michael Deldin、Christopher Hobbs、Chris Kuttruff、Stefan Novak、Mike Perham、Max Spransy、Moxley Stratton 以及 Wafa Zouyed。

如果没有家人的支持和鼓励,我不可能顺利完成本书。● 献给我的妻子 Sophia,是她帮助我坚持梦想,并帮助我将

对本书的设想变成现实。● 献给我的祖母 Gail,在我幼年时,是她引导我对学习产生了

浓厚的兴趣。我还记得在一次公路旅行中,她心无旁骛地向我询

问一些关于我正在看的那本“咖啡书”(实际上是讲 Java 的

书)的问题。● 献给我的父亲 Jay 和母亲 Carol,是他们教会了我如何分析

系统,以及如何为其注入人类的情感。● 献给我的弟弟 Jacob 以及侄女 Zoe 和 Darby,感谢他们教会

了我如何通过孩子的眼光重新认识世界。

最后,我将本书献给科学和对知识不断求索的人。▶▶第1章测试驱动的机器学习

伟大的科学家既是梦想家,也是怀疑论者。在现代历史中,科学家们取得了一系列重大突破,如发现地球引力、登上月球、发现相对论等。所有这些科学家都有一个共同点,那就是他们都有着远大的梦想。然而,在完成那些壮举之前,他们的工作无不经过了周密的检验和验证。

如今,爱因斯坦和牛顿已离我们而去,但所幸我们处在一个大数据时代。随着信息时代的到来,人们迫切需要找到将数据转化为有价值的信息的方法。这种需求的重要性已日益凸显,而这正是数据科学和机器学习的使命。

机器学习是一门充满魅力的学科,因为它能够利用信息来解决像人脸识别或笔迹检测这样的复杂问题。很多时候,为完成这样的任务,机器学习算法会采用大量的测试。典型的测试包括提出统计假设、确定阈值、随着时间的推移将均方误差最小化等。理论上,机器学习算法具备坚实的理论基础,可从过去的错误中学习,并随时间的推移将误差最小化。

然而,我们人类却无法做到这一点。机器学习算法虽能将误差最小化,但有时我们可能“指挥失误”,没能令其将“真正的误差”最小化,我们甚至可能在自己的代码中犯一些不易察觉的错误。因此,我们也需要通过一些测试来发现自己所犯的错误,并以某种方式来记录我们的进展。用于编写这类测试的最为流行的方法当属测试驱动开发(Test-Driven Development,TDD)。这种“测试先行”的方法已成为编程人员的一种最佳实践。然而,这种最佳实践有时在开发环境中却并未得到运用。

采用驱动测试开发(为简便起见,下文中统称 TDD)有两个充分的理由。首先,虽然在主动开发模式中,TDD 需要花费至少15%~35% 的时间,却能够排除多达90% 的程序缺陷(详情请参阅 http://research.microsoft.com/en-us/news/features/nagappan-100609.aspx)。其次,采用 TDD 有利于将代码准备实现的功能记录下来。当代码的复杂性增加时,人们对规格说明的需求也愈发强烈,尤其是那些需要依据分析结果制定重大决策的人。

哈佛大学的两位学者 Carmen Reinhart 和 Kenneth Rogoff 曾撰写过一篇经济学论文,大意是 说那些所承担的债务数额超过其国内生产总值90% 以上的国家的经济增长遭遇了严重滑 坡。后来,Paul Ryan 在总统竞选中还多次引用了这个结论。2013年,麻省大学的三位研 究者发现该论文的计算有误,因为在其分析中有相当数量的国家未被考虑。

这样的例子还有很多,只是可能情况不像这个案例这样严重。这个案例说明,统计分析中的一处错误可能会对一位学者的学术声誉造成打击。一步出错可能会导致多处错误。上面两位哈佛学者本身都具有多年的研究经历,而且这篇论文的发表也经过了严格的同行评审,但仍然出现了这样令人遗憾的错误。这样的事情在任何人身上都有可能发生。使用 TDD 将有助于降低犯类似错误的风险,而且可以帮助这些研究者避免陷入万分尴尬的境地。1.1 TDD的历史

1999年,Kent Beck 通过其极限编程(extreme programming)方面的工作推广了 TDD。TDD 的强大源自其先定义目标再实现这些目标的能力。TDD 的实践步骤如下:首先编写一项无法通过的测试(由于此时尚无功能代码,因此测试会失败),再编写可使其通过的功能代码,最后重构初始代码。一些人依据众多测试库的颜色将其称为“红-绿-重构”(red-green-refactor)。红色表示编写一项最初无法通过的测试,而你需要记录自己的目标;绿色表示通过编写功能代码使测试通过。最后,对初始代码进行重构,直到自己对其设计感到满意。

在传统开发实践中,测试始终是中流砥柱,但 TDD 强调的是“测试先行”,而非在开发周期即将结束时才考虑测试。瀑布模型采用的是验收测试(acceptance test),涉及许多人员,通常是大量最终用户(而非开发人员),且该测试发生在代码实际编写完毕之后。如果不将功能覆盖范围作为考虑因素,这种方法看起来的确很好。很多时候,质量保证专业人员仅对他们感兴趣的方面进行测试,而非进行全面测试。1.2 TDD与科学方法

TDD 之所以如此引人瞩目,部分原因在于它能够与人们及其工作方式保持良好的同步。它所遵循的“假设-测试-理论探讨”流程使之与科学方法有诸多相似之处。

科学需要反复试验。科学家在工作中也都是首先提出某个假设,接着检验该假设,最后将这些假设升华到理论高度。 我们也可将“假设-测试-理论探讨”这个流程称为“红-绿-重构”。

与科学方法一样,对于机器学习代码,测试(检验)先行同样适用。大多数机器学习践行者都会运用某种形式的科学方法,而 TDD 会强制你去编写更加清晰和稳健的代码。实际上,TDD 与科学方法的关系绝不止于相似。本质上,TDD 是科学方法的一个子集,理由有三:一是它需要构建有效的逻辑命题;二是它通过文档共享结果;三是它采用闭环反馈的工作机制。

TDD 之美在于你也可利用它进行试验。很多时候,首先编写测试代码时,我们都抱有一个信念,即最初测试中遇到的那些错误最终一定可被修正,但实际上我们并非一定要遵循这种方式。我们可以利用测试来对那些可能永远不会被实现的功能进行试验。对于许多不易解决的问题,按照这种方式进行测试十分有用。1.2.1 TDD可构建有效的逻辑命题

科学家们在使用科学方法时,首先尝试着去求解一个问题,然后证明方法的有效性。问题求解需要创造性猜想,但如果没有严格的证明,它只能算是一种“信念”。

柏拉图认为,知识是一种被证明为正确的信念。我们不但需要正确的信念,而且也需要能够证明其正确的确凿证据。为证明我们的信念的正确性,我们需要构建一个稳定的逻辑命题。在逻辑学中,用于证明某个观点是否正确的条件有两种——必要条件和充分条件。

必要条件是指那些如果缺少了它们假设便无法成立的条件。例如,全票通过或飞行前的检查都属于必要条件。这里要强调的是,为确保我们所做的测试是正确的,所有的条件都必须满足。

与必要条件不同,充分条件意味着某个论点拥有充足的证据。例如,打雷是闪电的充分条件,因为二者总是相伴出现的,但打雷并不是闪电的必要条件。很多情形下,充分条件是以统计假设的形式出现的。它可能不够完善,但要证明我们所做测试的合理性已然足够充分了。

为论证所提出的解的有效性,科学家们需要使用必要条件和充分条件。科学方法和 TDD 都需要严格地使用这两种条件,以使所提出的一系列论点成为一个有机整体。然而,二者的不同之处在于,科学方法使用的是假设检验和公理,而 TDD 使用的则是集成和单元测试(参见表1-1)。

表1-1:TDD与科学方法的比较 科学方法TDD必要条件公理纯粹的功能测试充分条件统计假设检验单元和集成测试

示例1:借助公理和功能测试完成证明1

法国数学家费马于1637年提出著名的猜想 :对于大于2的任意nnn整数 n,关于 a、b、c 的方 程 a + b + c 不存在正整数解。表面上看,这好像是一个比较简单的问题,而且据说费马声称他已经完成了证明。他在读书笔记中写道:“我确信已发现了一种美妙的证法,可惜这里空白的地方太小,写不下。”1即我们熟知的费马大定理。——译者注

此后的358年间,该猜想一直未得到彻底证实。1995年,英国数学家安德鲁·怀尔斯(Andrew Wiles)借助伽罗瓦(Galois)变换和椭圆曲线完成了费马大定理的最终证明。他长达100页的证明虽然称不上优雅,但每一步都经得起严格推敲。每一小节的论证都承前启后。

这100页证明中的每一步都建立在之前已被人们证明的公理和假设的基础之上,这与功能测试套件何其相似!用程序设计术语来说,怀尔斯在其证明中使用的所有公理和断言都可作为功能测试。这些功能测试只不过是以代码形式展现的公理和断言,每一步证明都是下一小节的输入。

大多数情况下,软件生产过程中并不缺少测试。很多时候,我们所编写的测试都是关于代码的随意的断言。许多情形下,为了使用以前的样例,我们只对打雷而不对闪电进行测试。即,我们的测试只关注了充分条件,而忽略了必要条件。

示例2:借助充分条件、单元测试和集成测试完成证明

与纯数学不同,充分条件只关心是否有足够的证据来支持某个因果关系。下面以通货膨胀为例来说明。自19世纪开始,人们已经在研究这种经济学中的神秘力量。要证明通货膨胀的存在,我们所面临的问题是并无任何公理可用。

不过,我们可以依据来自观察的充分条件来证明通货膨胀的确存在。我们观察过经济数据并从中分离出已知正确的因素,根据此经验,我们发现随着时间的推移,尽管有时也会下降,但长期来看经济是趋于增长的。通货膨胀的存在可以只通过我们之前所做的具有一致性的观察来证明。

在软件开发领域,经济学中的这类充分条件对应集成测试。集成测试旨在测试一段代码的首要行为。集成测试并不关心代码中微小的改动,而是观察整个程序,看所期望的行为是否能够如期发生。同样,如果将经济视为一个程序,则我们可断言通货膨胀或通货紧缩是存在的。1.2.2 TDD要求你将假设以文字或代码的形式记录下来

学术机构通常要求教授发表其研究成果。虽然有很多人抱怨各大学过于重视发表文章,但其实这样做是合理的:发表是一种使研究成果成为永恒的方法。如果教授们决定独自研究并取得重大突破,却不将其成果发表,那么这种研究将无任何价值。

TDD 同样如此:测试在同行评审中能够发挥重要的作用,也可作为一个版本的文档。实际上很多时候,在使用 TDD 时,文档并不是必需的。由于软件具有抽象性,且总处在变化之中,因此如果某人没有将其代码文档化或对代码进行测试,将来它便极有可能被修改。如果缺乏能够保证这些代码按特定方式运行的测试,则当新的编程人员参与到该软件的开发和维护工作中时,将无法保证他不会改动代码。1.2.3 TDD和科学方法的闭环反馈机制

科学方法和 TDD 均采用闭环反馈的机制。当某人提出一项假设,并对其进行检验(测试)时,他会找到关于自己所探索问题的更多信息。对于 TDD,也同样如此;某个人对其所想进行测试,之后当他开始编写代码时,对于如何进行便可以做到心中有数。

总之,TDD 是一种科学方法。我们提出一些假设,对其进行检验(测试),之后再重新检视。TDD 践行者们遵循的也是相同的步骤,即首先编写无法通过的测试,接着找到解决方案,然后再对这个解决方案进行重构。

示例:同行评审

许多领域,无论是学术期刊、图书出版,还是程序设计领域,都有自己的同行评审,且形式各异。原因编辑(reason editor)之所以极有价值,是因为他们对于一部作品或一篇文章而言是第三方,能够给出客观的反馈意见。在科学界,与之对应的则是对期刊文章的同行评审。

TDD 则不同,因为第三方是一个程序。当某人编写测试时,程序以代码的形式表示假设和需求,而且是完全客观的。在其他人查看代码之前,这种反馈对于程序开发人员检验其假设是很有价值的。此外,它还有助于减少程序缺陷和功能缺失。

然而,这并不能缓解机器学习或数学模型与生俱来的问题,它只是定义了处理问题和寻求足够好的解的基本过程。1.3 机器学习中的风险

对于开发过程而言,虽然使用科学方法和 TDD 可提供良好的开端,但我们仍然可能遇到一些棘手的问题。一些人虽然遵循了科学方法,但仍然得到了错误的结果;TDD 可帮助我们创建更高质量的代码,且更加客观。接下来的几个小节将介绍机器学习中经常会遇到的几个重要问题:● 数据的不稳定性● 欠拟合● 过拟合● 未来的不可预测性1.3.1 数据的不稳定性

机器学习算法通过将离群点最少化来尽量减少数据中的不稳定因素。但如果错误的来源是人为失误,该如何应对?如果错误地表示了原本正确的数据,最终将使结果偏离真实情况,从而产生偏倚。

对我们所拥有的不正确信息的数量予以考虑,这是一个重要的现实问题。例如,如果我们使用的某个应用程序编程接口(API)将原本表示二元信息的0和1修改为 -1和 +1,则这种变化对于模型的输出将是有害的。对于时间序列,其中也可能存在一些缺失数据。这种数据中的不稳定性要求我们找到一种测试数据问题的途径,以减少人为失误的影响。1.3.2 欠拟合

如果模型未考虑足够的信息,从而无法对现实世界精确建模,将产生欠拟合(underfitting)现象。例如,如果仅观察指数曲线上的两点,我们可能会断言这里存在一个线性关系(如图1-1所示)。但也有可能并不存在任何模式,因为只有两个点可供参考。图1-1:在 [-1,+1] 区间内,直线可对指数曲线取得良好的逼近效果

不幸的是,如果对该区间([-1,+1])进行扩展,将无法看到同样的逼近效果,取而代之的是显著增长的逼近误差(如图1-2所示)。图1-2:在 [-20,20] 区间内,直线将无法拟合指数曲线

统计学中有一个称为 power 的测度,它表示无法找到一个假负例(false negative)的概率。当 power 的值增大时,假负例的数量将减少。然而,真正影响该测度的是样本规模。如果样本规模过小,将无法获取足够的信息,从而无法得到一个良好的解。1.3.3 过拟合

样本数太少是很不理想的一种情形,此时还存在对数据产生过拟合(overfitting)的风险。仍以相同的指数曲线为例,比如共有来自这条指数曲线的30 000个采样点。如果我们试图构建一个拥有300 000个算子的函数,便是对指数曲线过拟合,其实际上是记忆了全部30 000个数据点。这是有可能出现的,但如果有一个新数据点偏离了那些抽样,则这个过拟合模型对该点将产生较大的误差。

表面看来缓解模型欠拟合的最佳途径是为其提供更多的信息,但实际上这本身可能就是一个难以解决的问题。数据越多,通常意味着噪声越多,问题也越多。使用过多的数据和过于复杂的模型将使学习到的模型只能在该数据集上得到合理的结果,而对其他数据集将几乎完全不可用。1.3.4 未来的不可预测性

机器学习非常适合不可预测的未来,因为大多数算法都需要从新息(即新的信息)中学习。但当新息到来时,其形式可能是不稳定的,而且会出现一些之前未预料到的新问题。我们并不清楚什么是未知的。在处理新息时,有时很难预测我们的模型是否仍能正常工作。1.4 为降低风险应采用的测试

既然我们面临着若干问题,如不稳定的数据、欠拟合的模型、过拟合的模型以及未来数据的不确定性,到底应如何应对?好在有一些通用指导方针和技术(被称为启发式策略)可循,若将其写入测试程序,则可降低这些问题发生的风险。1.4.1 利用接缝测试减少数据中的不稳定因素

在其著作 Working Effectively with Legacy Code(Prentice Hall 出版)中,Michale Feathers 在处理遗留代码时引入了接缝测试(testing seams)这个概念。接缝是指一个代码库的不同部分在集成时的连接点。在遗留代码中,很多时候都会遇到这样的代码:其内部机制不明,但当给定某些输入时,其行为可预测。机器学习算法虽不等同于遗留代码,但二者有相似之处。对待机器学习算法,也应像对待遗留代码那样,将其视为一个黑箱。

数据将流入机器学习算法,然后再从中流出。可通过对数据输入和输出进行单元测试来检验这两处“接缝”,以确保它们在给定误差容限内的有效性。

示例:对神经网络进行接缝测试

假设你准备对一个神经网络模型进行测试。你知道神经网络的输入数据取值需要介于0和1之间,且你希望所有数据的总和为1。当数据之和为1时,意味着它相当于一个百分比。例如,如果你有两个小玩具和三个陀螺,则数据构成的数组将为 (2/5,3/5)。由于我们希望确保输入的信息为正,且和为1,因此在测试套件中编写了下列测试代码:it 'needs to be between 0 and 1' do @weights = NeuralNetwork.weights @weights.each do |point| (0..1).must_include(point) endendit 'has data that sums up to 1' do @weights = NeuralNetwork.weights @weights.reduce(&:+).must_equal 1end

接缝测试是一种定义代码片段之间接口的好方法。虽然这个例子非常简单,但请注意,当数据的复杂性增加时,这些接缝测试将变得更加重要。新加入的编程人员接触到这段代码时,可能不会意识到你所做的这些周密考虑。1.4.2 通过交叉验证检验拟合效果

交叉验证是一种将数据划分为两部分(训练集和验证集)的方法,如图1-3所示。训练数据用于构建机器学习模型,而验证数据则用于验证模型能否取得期望的结果。这种策略提升了我们找到并确定模型中潜在错误的能力。图1-3:我们真正的目标是将交叉验证错误率或真实错误率最小化 训练专属于机器学习世界。由于机器学习算法的目标是将之前的观测映射为结果,因此训练非常重要。这些算法会依据人们所收集到的数据进行学习,因此如果缺少用于训练的初始数据集,该算法将百无一用。

交换训练集和验证集,有助于增加验证次数。你需要将数据集一分为二。第一次验证中,将集合1作为训练集,而将集合2作为验证集,然后将二者交换,再进行第二次验证。根据拥有的数据量,你可将数据划分为若干更小的集合,然后再按照前述方式进行交叉验证;如果你拥有的数据足够多,则可在任意数量的集合上进行交叉验证。

大多数情况下,人们会选择将验证数据和训练数据分为两部分,一部分用于训练模型,而另一部分用于验证训练结果在真实数据上的表现。例如,假设你正在训练一个语言模型,利用隐马尔可夫模型(Hidden Markov Model,HMM)对语言的不同部分进行标注,则你会希望将该模型的误差最小化。

示例:对模型进行交叉验证

依据我们训练好的模型,训练错误率大致为5%,但是当我们引入训练集之外的数据时,错误率可能会飙升到15%。这恰恰说明了使用经划分的数据集的重要性;好比复式记账之于会计,对于机器学习而言这一点是极为必要的。例如:def compare(network, text_file) misses = 0 hits = 0 sentences.each do |sentence| if model.run(sentence).classification == sentence.classification hits += 1 else misses += 1 end end assert misses < (0.05 * (misses + hits))enddef test_first_half compare(first_data_set, second_data_set)enddef test_second_half compare(second_data_set, first_data_set)end

首先将数据划分为两个子集,这个方法消除了可能由机器学习模型中不恰当的参数引起的一些常见问题。这是在问题成为任何代码库的一部分之前,找到它们的绝佳途径。1.4.3 通过测试训练速度降低过拟合风险

奥卡姆剃刀准则(Occam's Razor)强调对数据建模的简单性,并且认为越简单的解越好。这直接意味着“避免对数据产生过拟合”。越简单的解越好这种观点与过拟合模型通常只是记忆了它们的输入数据存在一些联系。如果能够找到更简单的解,它将发现数据中的一些模式,而非只是解析之前记忆的数据。

一种可间接度量机器学习模型复杂度的指标是它所需的训练时长。例如,假设你为解决某个问题,对两种不同的方法进行了测试,其中一种方法需要3个小时才能完成训练,而另一种方法只需30分钟。通常认为花费训练时间越少的那个模型可能越好。最佳方法可能是将基准测试包裹在代码周围,以考察它随着时间的推移变得更快还是更慢。

许多机器学习算法都需要设置最大迭代次数。对于神经网络,你可能会将最大 epoch 数设为1000,表明你认为如果模型在训练中不经历1000次迭代,便无法获得良好的质量。epoch 这种测度所度量的是所有输入数据的完整遍历次数。

示例:基准测试

更进一步,你也可使用像 MiniTest 这样的单元测试框架。这类框架会向你的测试套件增加一定的计算复杂性和一个 IPS(iteration per second,每秒迭代次数)基准测试,以确保程序性能不会随时间而下降。例如:it 'should not run too much slower than last time' do bm = Benchmark.measure do model.run('sentence') end bm.real.must_be < (time_to_run_last_time * 1.2)end

这里,我们希望测试的运行时间不超过上次执行时间的20%。1.4.4 检测未来的精度和查全率漂移情况

精度(precision)和查全率(recall)是度量机器学习实现性能2的两种方式。精度是对真正例的比例(即真正率)的度量 。例如,若精度为4/7,则意味着所预测的7个正例中共有4个样本是真正例。查全率是指真正例的数目与真正例和假负例数目之和的比率。例如,若有4个真正例和5个假负例,则相应的查全率为4/9。2精度(precision)并不等同于真正率(true positive rate)。真正率是指实际正例中被预测正确的样本比例,而精度则是在所预测的正例中实际正例所占的比例。此外,精度也不同于准确率(accuracy),后者是指被正确分类的样本在整个测试集中所占的比例。——译者注

为计算精度和查全率,用户需要为模型提供输入。这使得学习流程成为一个闭环,并且由于数据被误分类后所提供的反馈信息,随着时间的推移,系统在数据上的表现也会得到提升。例如,网飞3(Netflix)公司 会依据你的电影观看历史来预测你对某部影片的星级评价。如果你对该系统预测的评分不满意,并按照自己的意志重新评分,或者表明你对该部影片不感兴趣,则网飞再将你的反馈信息输入模型,以服务于将来的预测。3网飞公司是一家在线影片租凭提供商,拥有极为优秀的影片自动推荐引擎。——译者注1.5 小结

机器学习是一门科学,并且需要借助客观的方法来解决问题。像科学方法一样,TDD 也有助于问题的解决。TDD 和科学方法之所以相似,是因为二者具有下列三个共同点。● 二者均认为解应当符合逻辑,且具有有效性。● 二者均通过文档共享结果,且可持续不断地工作。● 二者都有闭环反馈的工作机制。

虽然科学方法和 TDD 有许多相似之处,但机器学习仍有其特有的问题:● 数据的不稳定性● 欠拟合● 过拟合● 未来的不可预测性

好在,借助表1-2所示的启发式策略,可在一定程度上缓解这些挑战。

表1-2:降低机器学习风险的启发式策略问题/风险启发式策略数据的不稳定性接缝测试欠拟合交叉验证过拟合基准测试(奥卡姆剃刀准则)未来的不可预测性随着时间的推移追踪精度和查全率

美妙的是,在真正开始编写代码之前,你可以编写或思考所有这些启发式策略。像科学方法一样,测试驱动开发也是求解机器学习问题的一种极有价值的方法。▶▶第2章机器学习概述

既然你选择了本书,说明你对机器学习感兴趣。对于何为机器学习你可能已有一定的了解,这是一门常常被模糊界定的学科。本章将介绍到底什么是机器学习,以及思考机器学习算法的一般框架。2.1 什么是机器学习

机器学习是具有坚实理论基础的计算机科学和含噪声的真实数据的交集。本质上,机器学习是一门关于如何使机器像人类那样去理解数据的科学。

机器学习是一种人工智能,该领域中的算法或方法可从数据中提取出一些模式。一般说来,机器学习所要处理的问题不多,如表2-1所示。这些问题将在下面陆续进行介绍。

表2-1:机器学习问题问题机器学习类型将数据拟合为某个函数或函数逼近有监督学习在无反馈条件下对数据进行推断无监督学习进行有奖励和回报的比赛或游戏强化学习2.1.1 有监督学习

有监督学习(或函数逼近)就是依据给定数据拟合出某种类型的函数。例如,给定图2-1所示的含噪声数据,可从中拟合出一条直线来逼近这些数据点。图2-1:从一些随机数据点中拟合出的直线2.1.2 无监督学习

无监督学习的目标是探索使得数据表现出特殊性的原因。例如,当给定许多数据点时,我们可依据相似性进行分组(如图2-2所示),或确定哪些变量更优。图2-2:聚类是无监督学习的一个典型示例2.1.3 强化学习

强化学习的目标是探索如何进行有奖励和回报的多阶段比赛或游戏。可将其视为一个对某物的生命周期进行优化的算法。强化学习算法的一个常见的例子是一只老鼠试图在迷宫中找到奶酪。在多数情况下,老鼠不会获得任何奖赏,除非它最终找到那块奶酪。

本书将只介绍有监督学习和无监督学习,而不涉及强化学习。如果你希望进一步了解强化学习,可参考最后一章中罗列的相关资源。2.2 机器学习可完成的任务

真正使得机器学习独一无二的是其寻求最优解的能力。但每种机器学习算法都有其特殊性和缺点。针对特定的任务,总有一些算法的性能会优于其他算法。本书只介绍几种典型算法。表2-2展示了一个算法“矩阵”,以帮助你大致了解这些算法,并确定每种算法对你是否有用。

表2-2:机器学习算法矩阵算法类型类限制条件适用场合K 有监基于一般说来,KNN 适合解决基于距离的问题近邻督学实例擅长度量基于距离的(KNN习的逼近,但易受维数灾)难的影响朴有监概率要求所给问题适用于问题域中各类的概率素贝叶督学的中,各输入相互独立均大于0的情形斯分类习(NB)隐有监马尔要求系统信息满适用于时间序列数据和无记马尔可督 / 无可夫足马尔可夫假设忆性的信息夫模型监督的(HMM学习)支有监决策要求待分类的两适用于求解两类分类问题持向量督学面个类别有明确的差别机习(SVM)神有监非线几乎没有限制条适用于二值输入经网络督学性函件(NN)习数逼近聚无监聚类无限制适用于当给定某种形式的距类督学离度量(如欧氏距离、曼哈顿距习离等)时,能表现出明确分组特性的数据(有监回归限制很少适用于变量连续的情形核)岭督学回归习滤无监特征无限制适用于有大量变量需要过滤波督学变换的数据习

在阅读本书时,请不时地回顾该表,以帮助你理解不同算法之间的联系。

只有将机器学习用于解决实际问题才能体现出其真正的价值,所以让我们开始实现一些机器学习算法吧! 在开始学习之前,你需要下载和安装 Ruby 编译器 https://www.ruby-lang.org/en/。我在撰写本书时使用的版本是2.1.2,但考虑到 Ruby 社区极其活跃,版本更新十分迅速,因此我将所有的改动都将体现在本书配套的代码资源中,读者可从 GitHub 站点 https://github.com/thoughtfulml/examples 获取最新版本 的代码。2.3 本书采用的数学符号

本书借助数学知识来求解问题,但所有示例的设计都是面向程序开发人员的。本书中采用的数学符号如表2-3所示。

表2-3:本书示例中所采用的数学符号符号读法功能从 x 到 x 的所等价于 x + x + … + x02012有 x 之和|x |x 的绝对值无论 x 是否为正实数,|x | 都为正实数。例如,当 x =-1时,|x |=1;当 x =1时,|x | 仍为124的平方根是2 的逆运算z = 向量 z 的两个表示 XY 平面上的一点,可用向量表示,向量kk是一组数值(0.5,0.分量分别等于0.5和5)0.5ilog2log以2为底2的对它是方程2=2的解2数P (A )事件 A 发生的概很多场合下,该值等于事件 A 发生的次数与所率有事件发生次数总和的比值P (A |已知事件 B 发B )生,事件 A 发生的概等于率{1,2,3}集合的交运算结果为 {1}∩{1}{1,2,3}集合的并运算结果为 {1,2,3,4}∪{4,1}det C矩阵 C 的行列式用于帮助确定某个矩阵是否可逆a 与 b 成正比意味着 m · a = bmin 最小化 f(x)f(x) 为将要最小化的目标函数f(x)TX矩阵 X 的转交换矩阵行和列上的所有元素2.4 小结

应当说,本章对机器学习的介绍并不全面,但并无大碍。对于像机器学习这样复杂的学科,我们总是需要不断地学习。本章已经为你学习后续章节奠定了良好的基础。▶▶第3章K 近邻分类

你很可能认识某个钟情于某个品牌(如某个技术公司或服装制造商)的人。通常,你可通过观察该人的衣着和言谈举止作出判断。但确定品牌亲和力是否还有其他方式呢?

对于一个电子商务网站,我们可通过查看相似用户的历史订单,了解他们曾购买过的商品,以判断品牌忠诚度。例如,我们不妨假设某个用户拥有一组历史订单,每个订单中包含两款商品,如图3-1所示。图3-1:涉及多个品牌的某用户历史订单

依据该用户的历史订单,可以看出他购买了大量 Milan Clothing Supplies(虚构的品牌,但不影响你理解该问题)的服装。在最近的五个订单中,他购买了五件 Milan Clothing Supplies 的衬衫。因此,可以说该用户对这家公司有某种程度的好感。对此有所了解后,如果我们提出一个问题:该用户对哪个品牌特别感兴趣? Milan Clothing Supplies 无疑是首选。

这种一般性的思想被称为 K 近邻(KNN)分类算法。在本例中,K=5,而每个订单代表对某个品牌的投票。得到票数最多的品牌便是分类结果。本章将介绍 K 近邻分类,给出其定义,并通过一个示例来说明如何将其运用于检测人脸图像中是否有眼镜或胡须。 K 近邻分类是一种基于实例的有监督学习方法,尤其适用于对距离敏感的数据。它容易受到“维数灾难”的影响,而且与基于距离的算法一样,也面临许多其他问题,稍后我们将逐一进行介绍。图3-1:涉及多个品牌的某用户历史订单

依据该用户的历史订单,可以看出他购买了大量 Milan Clothing Supplies(虚构的品牌,但不影响你理解该问题)的服装。在最近的五个订单中,他购买了五件 Milan Clothing Supplies 的衬衫。因此,可以说该用户对这家公司有某种程度的好感。对此有所了解后,如果我们提出一个问题:该用户对哪个品牌特别感兴趣? Milan Clothing Supplies 无疑是首选。

这种一般性的思想被称为 K 近邻(KNN)分类算法。在本例中,K=5,而每个订单代表对某个品牌的投票。得到票数最多的品牌便是分类结果。本章将介绍 K 近邻分类,给出其定义,并通过一个示例来说明如何将其运用于检测人脸图像中是否有眼镜或胡须。 K 近邻分类是一种基于实例的有监督学习方法,尤其适用于对距离敏感的数据。它容易受到“维数灾难”的影响,而且与基于距离的算法一样,也面临许多其他问题,稍后我们将逐一进行介绍。3.1 K 近邻分类的历史

KNN 算法最早由 Drs. Evelyn Fix 和 J.L. Hodges Jr 博士在为美国航天医学空军学校(U.S. Air Force School of Aviation Medicine)撰写的一份未公开发表的技术报告中提出。Fix 和 Hodges 最初的研究重点是将分类问题划分为若干子问题。● F 分布和 G 分布完全已知。● 除少数参数外,F 分布和 G 分布完全已知。● 除了概率密度函数可能存在外,F 和 G 均未知。

Fix 和 Hodges 指出,如果已知两类的分布,或除一些参数外分布已知,则可轻松得到一些有意义的解。因此,他们将工作的重点放在两类分布未知时如何分类这个更困难的问题上。他们的杰出工作为 KNN 算法奠定了坚实的基础。

该算法已被证明当数据规模趋于无穷时,其渐近错误率的上界为贝叶斯错误率的两倍。这意味着当更多的实体被添加到数据集中后,该算法的错误率将不会大于贝叶斯错误率。此外,KNN 的原理非常简单,很容易用于初步尝试某个分类问题的求解。对于许多情形,该算法都能够提供令人满意的结果。

但使用该算法也面临着一系列挑战。如,怎样选择 K 的值?如何确定哪些实例是近邻,而哪些不是?这些是我们将在接下来的几个小节中所要回答的问题。3.2 基于邻居的居住幸福度

设想你准备购置一处房产,而且有了两个选择。你希望了解周围的邻居生活得是否幸福(你当然不希望搬到一个不幸福的居住区)。你去询问周围的人在那里生活得是否幸福,并将收集到的信息整理为表3-1。

表3-1:房屋的幸福度经度纬度是否幸福562是320否181是2014否3030是3535是 之所以用经纬度来表示房屋的位置,是因为我们希望确定一个足够小的邻域。

假设我们感兴趣的两座房屋的位置分别是 (10,10) 和 (40,40)。那么选择哪一座房屋会有助于提升幸福感,选择哪一个会降低幸福感?确定这一点的一种方法是找到最近的邻居,然后看看他们是否幸福。这里的“最近”是指绝对距离(也称欧氏距离)意义上的最近。

如表3-1所示的二维点之间的欧氏距离的计算公式为。

整个分析流程可用下列 Ruby 代码表示:require 'matrix'# 向量v1和v2之间的欧氏距离# 请注意Vector#magnitude表示从原点(0,0,....)到该向量首端的欧氏距离distance = ->(v1, v2) { (v1 - v2).magnitude}house_happiness = { Vector[56, 2] => 'Happy', Vector[3, 20] => 'Not Happy', Vector[18, 1] => 'Happy', Vector[20, 14] => 'Not Happy', Vector[30, 30] => 'Happy', Vector[35, 35] => 'Happy'}house_1 = Vector[10, 10]house_2 = Vector[40, 40]find_nearest = ->(house) { house_happiness.sort_by {|point, v| distance.(point, house) }.first}find_nearest.(house_1) #=> [Vector[20, 14], "Not Happy"]find_nearest.(house_2) #=> [Vector[35, 35], "Happy"]

以此为基础进行推断,可看到距离第一座房屋最近的邻居不幸福,而距离第二座房屋最近的邻居生活得很幸福。但如果增加考察的邻居数目,结果是否会有变化?# 使用与上面相同的代码find_nearest_with_k = ->(house, k) { house_happiness.sort_by {|point, v| distance.(point, house) }.first(k)}find_nearest_with_k.(house_1, 3)#=> [[Vector[20, 14], "Not Happy"], [Vector[18, 1], "Happy"], [Vector[3, 20], "Not Happy"]]find_nearest_with_k.(house_2, 3)#=> [[Vector[35, 35], "Happy"], [Vector[30, 30], "Happy"], [Vector[20, 14], "Not Happy"]]

增加参考的邻居数目并未改变分类结果!这是一件值得庆贺的好事,它提升了我们对分类结果的信心。该方法演示了 K 近邻分类过程。我们或多或少会参考最近的邻居,并依据他们的属性给出一个评分。在本例中,我们希望了解选择哪座房屋会更幸福,但数据的重要性绝对毋庸置疑。

由于其简单性(你刚才已经有所了解),KNN 是一种非常出色的算法,而且功能强大,可用于对数据进行分类或回归(详情请参阅下面的附注栏)。分类与回归请注意,在上面的场景中,我们只关心选择房屋是否会影响幸福感,即我们并不打算定量评估幸福感,而只是检查它是否符合我们的标准。这便是一个分类问题,而且可以多种形式出现。很多时候,分类问题只涉及两个类别,这意味着只有两种可能的答案,如好或坏、真或假、正确或错误。许多问题都可转化为这种两类分类问题。另一方面,我们也可为房屋寻找一个衡量幸福感的数值,这将是一个回归问题。本章不会介绍回归问题,不过在第9章讨论核岭回归时会介绍。

本章涉及众多知识点,并围绕 KNN 算法的使用分成数个主题。我们首先讨论如何选择 K 这个用于确定邻域的常量。之后再深入探讨何为“最近”,并给出一个利用 OpenCV 实现人脸分类的示例。3.3 如何选择 K

在评估房屋居住幸福度这个示例中,我们隐式地将 K 取为5。对于依据某人最近的购物史作出快速判断来说,这个 K 值已经足够了。但对于更复杂的问题,我们可能无力猜测 K 具体取多少合适。

K 近邻算法中的 K 是介于1和数据点总数之间的一个任意整数。在这样宽泛的区间内,你可能会认为选择最优的 K 值非常困难,但实际上这个问题并不像你想象的那么复杂。要确 定 K,主要有三种方案可供选择:● 猜测● 使用启发式策略● 通过算法优化3.3.1 猜测 K 的值

猜测是最简单的解决方案。在对商标分组的例子中,我们将 K=11作为一个良好的猜测。我们知道,对于预测个体的购物行为,

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载