书名:深度学习案例精粹
ISBN:978-7-115-50585-9
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 [爱尔兰] 艾哈迈德•曼肖伊(Ahmed Menshawy)
译 洪志伟 曹 檑 廖钊坡
责任编辑 张 爽
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright ©2018 Packt Publishing. First published in the English language under the title Deep Learning By Example.
All Rights Reserved.
本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
本书主要讲述了深度学习中的重要概念和技术,并展示了如何使用TensorFlow实现高级机器学习算法和神经网络。本书首先介绍了数据科学和机器学习中的基本概念,然后讲述如何使用TensorFlow训练深度学习模型,以及如何通过训练深度前馈神经网络对数字进行分类,如何通过深度学习架构解决计算机视觉、语言处理、语义分析等方面的实际问题,最后讨论了高级的深度学习模型,如生成对抗网络及其应用。
本书适合数据科学、机器学习以及深度学习方面的专业人士阅读。
Ahmed Menshawy是爱尔兰都柏林三一学院的研究工程师。他在机器学习和自然语言处理领域拥有超过5年的工作经验,并拥有计算机科学硕士学位。他曾在埃及开罗阿勒旺大学(Helwan University)计算机科学系做教学助理,负责机器学习和自然语言处理课程,如机器学习、图像处理等,并参与设计了阿拉伯文字到语音的系统。此外,他还是埃及的IST Networks工业研发实验室的机器学习专家。
我要感谢那些与我亲近并支持我的人,特别是我的妻子和我的父母。
Md. Rezaul Karim是德国弗劳恩霍夫应用信息技术研究所(Fraunhofer FIT)的科学家。他还是德国亚琛工业大学的博士研究生。在加入FIT之前,他曾担任过FIT爱尔兰调查中心(Insight Center)的数据分析研究员。此前,他曾在韩国三星电子公司担任首席工程师。
他在C ++、Java、R、Scala和Python等方面拥有9年的研发经验,并曾发表了有关生物信息学、大数据和深度学习的研究论文。他在Spark、Zeppelin、Hadoop、Keras、Scikit-learn、TensorFlow、DeepLearning4j、MXNet和H2O等领域具有实际工作经验。
Doug Ortiz是一位经验丰富的企业云、大数据、数据分析和解决方案架构师,负责架构的设计、开发、重新设计和集成企业解决方案。他精通亚马逊Web服务(AWS)、Azure、谷歌云、商务智能、Hadoop、Spark、NoSQL数据库和SharePoint等。
本书的3位译者利用业余时间,结合对深度学习领域的了解,经过3个月的努力,完成了对全书的翻译。本书的翻译在内容上忠于原文,在表述上又贴近汉语的表达习惯,同时风格也尽量与原著保持一致,以求尽可能地传达作者的原意。在对专业术语的翻译上,我们努力与业界的使用保持统一,使得初学者在阅读完本书后继续查阅其他学习材料时,不会出现术语或者名词矛盾的情况。
尽管已经对深度学习有了一段较长时间的研究,但是本书仍然让我们耳目一新。本书语言简练,和现在流行的机器学习类图书一样,本书没有侧重于揭示深度学习背后的数学理论基础,而是通过问题简述、方法概括以及代码,让读者能够尽快地理解并上手深度学习的算法。对于缺少理论知识的普通开发人员,本书提供了必需的基础知识,读者可以学到几乎所有的要点来探索和理解深度学习是什么。而对于了解深度学习理论知识却缺少实践开发经验的读者,本书提供了大量的案例,使读者能够快速地动手实现深度学习任务。
由于书中给出的代码非常详细,可以直接运行,对于急于将深度学习应用于实际项目的工程师来说,本书可以称为利器。同时,对于那些对深度学习有一定了解的读者,本书也不失为调整自身代码风格并且进一步加深对深度学习理解的宝贵资源。当然,对于有志于在深度学习领域做出一番成就的读者,在熟读本书后,建议进一步阅读更多的学习资料,去探索深度学习背后的数学支撑。
本书的内容涵盖了大多数的主流深度学习任务,包括图像领域的图像识别、目标检测任务,自然语言处理中的词嵌入、情感分析任务,以及无监督学习任务等。本书介绍了几个深度学习中的经典模型,包括卷积网络、循环神经网络和对抗生成网络,当下深度学习的大多数模型都是在这些基础模型上的改进与组合。本书的内容全面,不同领域的从业人员或多或少都能从中获得启发。
本书使用的编程语言是现在非常流行的Python,代码简单、优雅,易于上手,深度学习框架也是主流的TensorFlow框架,想要进一步学习的读者将会有极多的社区资源。当然,随着编程语言的进一步发展,我们也非常期待以后为读者呈现一本基于其他框架(如Pytorch、Caffe等)的图书。我们也期待以后能够将更多的、更新的学习任务和模型呈现给读者,如深度强化学习和注意力模型。
感谢翻译过程中所有帮助和指导过我们的人。由于译者水平有限,书中难免会有疏漏,还望读者不吝提出意见和建议。
本书将首先介绍机器学习的基础和如何可视化机器学习的过程,然后使用一些例子来展示传统的机器学习技术,最后讨论深度学习。在本书中,你会开始建立一些机器学习模型,这些模型最后会有助于你学习神经网络模型。通过这些,读者能够熟悉深度学习的一些基础知识,并了解各种以用户友好的方式支持深度学习的工具。本书旨在使普通开发人员能够获得一些深度学习的实践经验。读者能够学到几乎所有的要点来探索和理解深度学习是什么,并且直接动手实现一些深度学习任务。此外,本书将会使用目前最广泛应用的深度学习框架之一——TensorFlow。TensorFlow在社区的支持度很高,并且在日益增长,这使得它成为构建复杂深度学习应用的理想选择。
本书是一本深度学习方面的入门书籍,适合那些想要深入了解深度学习并且动手实现它的读者阅读。阅读本书,不要求读者具有机器学习、复杂统计学和线性代数等方面的背景知识。
第1章解释数据科学或者机器学习的定义,即在事先没有被告知或者编程的情况下,机器从数据集中学习知识的能力。例如,要编写一个可以识别数字的程序非常困难,该程序以手写数字作为输入图像,输出该图像所代表的是0~9的哪个数字。同样地,要判断收到的邮件是不是垃圾邮件这样的分类任务也是很困难的。为了解决这样的任务,数据科学家使用一些数据科学、机器学习领域的学习方法和工具,通过给机器一些能够区分每个数字的可解释特征来教会计算机如何自动识别数字。对于垃圾邮件分类问题也是如此,我们能够通过特定的算法教会计算机如何判断一封邮件是不是垃圾邮件,而不是使用传统的正则化表达式并编写数百条规则来对收到的邮件进行分类。
第2章讨论线性模型——数据科学领域的基本学习算法。理解线性模型如何工作对于学习数据科学的过程至关重要,因为它是大多数复杂学习算法(包括神经网络)的基本构建模块。
第3章介绍模型的复杂性和评估方法。这对构建成功的数据科学系统非常重要。有很多工具可以用来评估和选择模型,本章将讨论一些工具,通过添加更多描述性特征并从现有数据中提取有用信息,这些工具有助于你提高数据的价值。同样地,本章也会讨论与最优数量特征有关的其他工具,并解释为什么“有大量的特征却只有很少量的训练样本/观测值”会是一个问题。
第4章概述使用最广泛的深度学习框架之一——TensorFlow。它在社区的支持度很高,而且在日益增长,这对于构建复杂的深度学习应用来说是一个很好的选择。
第5章解释TensorFlow背后的主要计算概念——计算图模型,并演示如何通过实现线性回归和逻辑回归模型让读者慢慢走上正轨。
第6章解释前馈神经网络(Feed-forward Neural Network,FNN),FNN是一种特殊类型的神经网络,其中神经元之间的连接不构成环。因此,它与本书后续将要研究的神经网络中的其他结构(如循环类型神经网络)不同。FNN是一种广泛使用的架构,而且它是第一个类型最简单的神经网络。本章将会介绍一种典型的FNN架构,并且使用TensorFlow中的库来实现它。在讲完了这些概念之后,本章会给出一个数字分类的实际例子。例如,给定一组包含手写数字的图像,如何将这些图像分为10个(即0~9)不同的类别?
第7章讨论数据科学中的卷积神经网络(Convolutional Neural Network,CNN),CNN是一种特殊的深度学习架构,该架构使用卷积运算从输入图像中提取相关的可解释特征。把很多CNN层连接起来作为FNN,同时通过这种卷积运算来模拟人脑在识别物体时是怎样工作的。每个皮层神经元对受限区域空间(也称为感受野)中的刺激做出反应。特别地,生物医学成像问题有时会难以解决,但在本章中,你将可以看到如何使用CNN来识别图像中的这些模式。
第8章介绍CNN背后的基础知识和直觉/动机,并在可用于物体检测的最流行的数据集之一上进行演示。同样地,你将会看到CNN前面的一些层如何提取关于物体的一些基本特征,而最后的卷积层将会提取到更多的语义级特征,这些特征都是从前几层的基本特征中构建而来的。
第9章讨论迁移学习(Transfer Learning,TL),迁移学习是数据科学中研究的一个问题,主要涉及在解决特定任务时不断获取知识并使用这些已获得的知识来解决另一个不同但相似的任务。本章将展示数据科学领域中一种使用TL的现代实践和常见主题。这里的想法是在处理具有较小数据集的领域时,如何从具有非常大的数据集的领域中来获得帮助。最后,本章将重新探讨CIFAR-10目标检测示例,并尝试使用TL来缩短训练时间和减小性能误差。
第10章介绍循环神经网络(Recurrent Neural Network,RNN),RNN是一类广泛应用于自然语言处理(Natural Language Processing,NLP)的深度学习架构。这套架构使我们能够为当前预测提供上下文信息,并且还具有处理任何输入序列中长期依赖性的特定架构。本章将会演示如何构建一个序列到序列的模型,这对于NLP中的许多应用都是很有用的。这里将通过建立一个字符级语言模型来阐述这些概念,并且展示模型如何产生和原始输入序列相似的句子。
第11章解释了机器学习是一门主要基于统计学和线性代数的科学。由于反向传播算法,应用矩阵运算在大多数机器学习或深度学习架构中都非常普遍。这也是深度学习(机器学习)在总体上只以实数值作为输入的主要原因。事实上,这和很多的应用相矛盾,如机器翻译、情感分析等,它们以文本作为输入。因此,为了在这个应用中使用深度学习,我们需要以深度学习所接受的方式进行处理。本章将会介绍表征学习领域,这是一种从文本中学习实值表示方式的方法,同时保留实际文本的语义。例如,love的表示方式应该和adore的表示方式十分接近,因为它们都会在非常相似的语境中使用。
第12章讨论了自然语言处理中一个热门和流行的应用,即所谓的情感分析。现在大多数人通过社交媒体平台来表达他们对某事的看法,所以对于公司甚至政府来说,利用这些海量文本来分析用户对某事的满意度是非常重要的。在本章中,将使用RNN来构建一个情感分析解决方案。
第13章介绍自编码器网络,它是当下使用最广泛的深度学习架构之一。它主要用于高效解码任务的无监督学习,也可以用于通过学习特定数据集的编码或者表示方式来降维。本章将使用自编码器展示如何通过构建具有相同维度但噪声更小的另一个数据集,来对当前数据集进行去噪。为了在实践中使用这个概念,这里将会从MNIST数据集中提取重要的特征,并尝试以此来观察性能是如何显著提高的。
第14章讨论生成对抗网络(Generative Adversarial Network,GAN)。这是一种由两个彼此对抗的网络(因此有了名字中的对抗)组成的深度神经网络架构。2014年,Ian Goodfellow和包括Yoshua Benjio在内的其他研究人员在蒙特利尔大学的一篇论文(见arxiv官网)中介绍了GAN。提到GAN,Facebook的AI研究总裁Yann Lecun称对抗训练是机器学习过去10年中最有趣的想法。GAN潜力巨大,因为它可以学习模仿任何的数据分布。也就是说,在诸如图像、音乐、语言和文本等任何领域可以训练GAN以创造与我们类似的世界。从某种意义上说,它们是机器人艺术家,它们的输出(见nytimes官网)令人印象深刻同时也使人们受到鼓舞。
第15章指出我们能够使用GAN来实现非常多有趣的应用。本章将展示GAN的另一个有前景的应用,即基于CelebA数据库的人脸生成,并且演示如何将GAN用于建立那种对于数据集标记不准确或者缺少标记的半监督学习模型。
附录A包括鱼类识别示例的所有代码。
读者可以从异步社区(www.epubit.com)上下载本书的示例代码文件。
本书的代码包也托管在GitHub网站上面。同样地,其他丰富的图书和视频中的相关代码包也可以从GitHub网站获取。
我们还提供了一个PDF文件,其中包含了本书中所使用的截图/图的彩色图像,读者可以到异步社区中下载。
本书中使用了很多约定版式。
代码块设置如下。
html, body, #map {
height: 100%;
margin: 0;
padding: 0
}
当我们希望提醒读者注意代码块的特定部分时,相关行或条目以粗体显示。
[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)
``` 任何命令行输入或者输出的写法如下。
$ mkdir css
$ cd css
表示警告或重要注意事项。
表示提示信息和技巧。
本书由异步社区出品,社区(https://www.epubit.com/)为您提供相关资源和后续服务。
本书提供源代码和彩图文件,请在异步社区本书页面中点击,跳转到下载界面,按提示进行操作即可。注意,为保证购书读者的权益,该操作会给出相关提示,要求输入提取码进行验证。
作者和编辑尽最大努力来确保书中内容的准确性,但难免会存在疏漏。欢迎您将发现的问题反馈给我们,帮助我们提升图书的质量。
当您发现错误时,请登录异步社区,按书名搜索,进入本书页面,点击“提交勘误”,输入勘误信息,点击“提交”按钮即可。本书的作者和编辑会对您提交的勘误进行审核,确认并接受后,您将获赠异步社区的100积分。积分可用于在异步社区兑换优惠券、样书或奖品。
我们的联系邮箱是contact@epubit.com.cn。
如果您对本书有任何疑问或建议,请您发邮件给我们,并请在邮件标题中注明本书书名,以便我们更高效地做出反馈。
如果您有兴趣出版图书、录制教学视频,或者参与图书翻译、技术审校等工作,可以发邮件给我们;有意出版图书的作者也可以到异步社区在线提交投稿(直接访问www.epubit.com/selfpublish/submission即可)。
如果您是学校、培训机构或企业,想批量购买本书或异步社区出版的其他图书,也可以发邮件给我们。
如果您在网上发现有针对异步社区出品图书的各种形式的盗版行为,包括对图书全部或部分内容的非授权传播,请您将怀疑有侵权行为的链接发邮件给我们。您的这一举动是对作者权益的保护,也是我们持续为您提供有价值的内容的动力之源。
“异步社区”是人民邮电出版社旗下IT专业图书社区,致力于出版精品IT技术图书和相关学习产品,为作译者提供优质出版服务。异步社区创办于2015年8月,提供大量精品IT技术图书和电子书,以及高品质技术文章和视频课程。更多详情请访问异步社区官网https://www.epubit.com。
“异步图书”是由异步社区编辑团队策划出版的精品IT专业图书的品牌,依托于人民邮电出版社近30年的计算机图书出版积累和专业编辑团队,相关图书在封面上印有异步图书的LOGO。异步图书的出版领域包括软件开发、大数据、AI、测试、前端、网络技术等。
异步社区
微信服务号
数据科学或机器学习是一个使机器能够在不被告知数据或编程的情况下从数据集中学习知识的过程。例如,编写一个能够将手写数字作为输入图像并根据输入的图像输出值为0~9的程序非常困难。这同样适用于将收到的电子邮件分为垃圾邮件或非垃圾邮件的任务。为了解决这些问题,数据科学家使用数据科学或机器学习领域的学习方法和工具,通过向计算机提供一些可以区分一位数字和另一位数字的解释性特征,教会计算机如何自动识别数字。对于垃圾邮件/非垃圾邮件问题也是如此,我们可以通过特定的学习算法教会计算机如何区分垃圾邮件和非垃圾邮件,而不是使用正则表达式并编写数百条规则来对收到的电子邮件进行分类。
对于垃圾邮件过滤程序,你可以通过基于规则的方法对它进行编码,但它不会用于生产中,比如邮件服务器中的程序。所以建立一个学习系统是一个理想的解决方案。
用户可能每天都在使用数据科学应用程序,却不知道它就是数据科学应用程序。例如,某机构可能使用某些系统来检测大家发布的信件的邮政编码,以便自动将它们转发到正确的区域。如果用户使用亚马逊网站,它们通常会推荐用户购买一些东西,亚马逊就是通过了解用户经常搜索或购买哪些东西做到这一点的。
建立一个训练的机器学习算法需要一些基础的历史数据样本,从中学习如何区分不同的例子,并从这些数据中了解一些知识和趋势。之后,训练算法可用于对未知数据进行预测。学习算法将使用原始历史数据,并将尝试从该数据中了解一些知识和趋势。
本章将全面介绍数据科学,包括数据科学如何像一个黑盒子一样工作,以及数据科学家每天面临的挑战。本章具体讨论以下主题。
为了说明为特定数据构建学习算法的周期和挑战,我们考虑一个真实的例子。大自然保护协会正在与其他捕鱼公司合作,来共同监测捕捞活动并保护未来的渔业。大自然保护协会希望在未来使用摄像头来扩大这一监控范围。但是因为部署这些摄像头所产生的数据量非常巨大,而且手动处理的成本非常高,所以大自然保护协会想要开发一种学习算法来自动检测不同种类的鱼并分类,以加速视频审查过程。
图1.1所示的是保护协会部署的摄像头所拍摄的图像样本,这些图像将用于构建系统。
图1.1 大自然保护协会部署的摄像头拍摄到的画面
在这个例子中,我们的目标是分离不同的物种,包括渔船所捕获的金枪鱼、鲨鱼等。作为一个说明性的例子,把这个问题限制在金枪鱼和月鱼两个物种(见图1.2)中。
图1.2 金枪鱼类(左)和月鱼类(右)
在将问题限制在仅包含两种类型的鱼之后,我们可以从集合中抽取一些随机图像来发现这两种类型之间的一些物理差异。主要考虑以下物理差异。
我们可以将这些物理差异作为特征来帮助学习算法(分类器)来区分这两种鱼类。
对象的解释性特征是我们在日常生活中用来区分周围的物体的东西。即使是婴儿也可以使用这些解释性特征来了解周围环境,数据科学也是如此。为了建立一个可以区分不同对象(例如鱼类)的学习模型,我们需要给它一些解释性特征来学习(例如鱼的长度)。为了使模型更加确定并减少错误,我们可以在一定程度上增加对象的解释性特征。
因为这两种鱼类之间存在物理差异,所以这两种不同的鱼类种群有不同的模型或描述。因此,分类任务的最终目标是让分类器学习这些不同的模型,然后将这两种类型中的一种作为输入。分类器将通过选择最适合该图像的模型(金枪鱼模型或月鱼模型)来对它进行分类。
在这种情况下,摄像头收集到的金枪鱼和月鱼图像将作为分类器的知识库。最初,知识库(训练样本)将被标记。所以说,对于每幅图像,我们会预先知道它是金枪鱼还是月鱼。分类器将使用这些训练样本来对不同类型的鱼进行建模,然后我们可以使用训练阶段中已构建的分类器来自动标记在训练阶段中没有见到的未标记的鱼。这种未标记的数据通常称为未知数据。整个训练阶段的周期如图1.3所示。
图1.3 训练阶段周期
下面讨论分类器的训练阶段是如何工作的。
图1.4 两种鱼的长度直方图
我们会将这些数据输入分类器中,以对不同类别的鱼进行建模。
如前所述,用户可以通过之前提出的诸如长度、宽度和颜色这些物理差异(特征)来区分金枪鱼和月鱼。
用户也可以利用长度特征来区分两种鱼。因此,我们可以通过观察鱼的长度并判断它是否超过某一个值(length*)来区分不同的鱼类。
所以,基于训练样本,我们可以得出下面的规则。
If length(fish)> length* then label(fish) = Tuna
Otherwise label(fish) = Opah
为了求length*,我们可以设法测量训练样本的长度。假定用户测量了样本的长度并得到了图1.4所示的直方图。
在这个案例中,我们可以根据长度得出一条规则并用它来区分金枪鱼和月鱼。在这个特定的例子中,length*为7。所以先前的规则可以更新为:
If length(fish)> 7 then label(fish) = Tuna
Otherwise label(fish) = Opah
读者可能注意到了,这两个直方图存在重叠,因此这并不是一个很好的结果,这是因为长度特征并不能完全用于区分这两种类型。用户可以考虑纳入诸如宽度等特征,并把它们结合起来。如果我们设法测量训练样本的宽度,可能会得到图1.5所示的直方图。
图1.5 两种鱼的宽度直方图
可以看出,只根据一个特征是不能得出准确的结果的,输出模型将会做出很多错误的分类。我们可以设法结合这两个特征,并得到一些合理的模型。
如果结合这两个特征,我们就可能得到图1.6所示的散点图。
图1.6 两种鱼的长度和宽度子集之间的组合
结合长度和宽度特征的数值,可以得到图1.6所示的散点图。图中浅色点表示金枪鱼,深色点表示月鱼,我们可以认为中间这条黑线是用以区分两种鱼的规则或者决策边界。
举例来说,如果一条鱼的相关数值落在这个决策边界的上方,它就是一条金枪鱼;否则,我们就预测它为一条月鱼。
用户可以设法增加规则的复杂度以避免任何可能的错误,这样做会得到一个新的散点图,如图1.7所示。
图1.7 增加决策边界的复杂度来避免训练数据的错误分类
这个模型的好处是在训练样本上错误分类的数量接近于0,而实际上这并不是使用数据科学的目的。数据科学的目的是搭建一个能够在未知数据上泛化并表现良好的模型。为了判断该模型是否能够良好地泛化,我们将引入一个称为测试阶段的新阶段。在这个阶段里,用户给训练好的模型一张没有贴标签的图片,希望模型能给这张图片贴上一个正确的标签(金枪鱼或者月鱼)。
数据科学的最终目的是建立一个能够在生产中(而不是在训练集上)表现良好的模型。所以当我们看到模型在训练集中表现良好时,如图1.7所示,不要高兴得太早。大多数情况下,这种模型在识别图像中的鱼种类时表现得并不好。这种模型只在训练数据集上表现良好的状况称为过拟合,许多从业者都掉进了这个陷阱。
与其提出一个复杂的模型,不如让用户用一个相对不太复杂的并且能够在测试阶段泛化的模型。图1.8所示的是一个不太复杂的模型,该模型可以进一步减少错误分类的数量并且可以很好地泛化未知的数据。
图1.8 使用一个不太复杂的模型以便于可以在测试样本(未知数据)上泛化
不同的学习系统通常都遵循相同的设计流程。它们首先获取知识库并从数据中选择相关的可解释特征,然后遍历一系列候选的学习算法并监测每一个算法的性能,最后对它进行评估,以衡量训练过程是否成功。
本节将更详细地叙述所有的设计步骤,如图1.9所示。
图1.9 模拟学习流程的框架
学习周期里的数据预处理相当于算法的知识库。为了使学习算法能够对未知数据做出更精确的决策,用户必须以最佳形式提供这种知识库。因此,数据可能需要许多的清洗与预处理(转换)。
大多数数据集都需要经历这个步骤,在这个步骤里需要去除数据中的错误、噪声和冗余。数据必须是准确、完整、可靠和无偏的,这是因为使用劣质的知识库可能产生如下许多的问题。
在这个步骤里,数据通过一些转换而变得一致且具体。在数据预处理前有许多不同的转换方法可供参考。
样本的解释特征性(输入变量)可能是非常庞大的,比如用户拿到一个输入,将它作为训练样本(观察数据/示例),并且d非常大。这种情形的一个例子是文档分类任务3,在这个任务里用户拿到10 000个不同的单词,而输入变量则是这些不同单词的出现频次。
这种数量巨大的输入变量可能是有问题的,有时候甚至是灾难性的,因为开发人员有很多的输入变量,却只有很少的训练样本来帮助他们进行学习。为了避免拥有大量输入变量这个问题(维度灾难),数据科学家用降维技术来选取输入变量的一个子集。比如,在文本分类任务中,可以采用以下方法。
利用降维技术选择了一个合适的输入变量子集后,接下来就是模型选择了。选择合适的输入变量子集,将使得接下来的学习过程变得非常简单。
在这个步骤里,用户试图找到一个正确的模型来学习。
如果读者之前有过数据科学方面的经验,并曾经将学习方法应用到不同的领域和不同的数据当中,那么这部分内容会相对比较简单,因为这个步骤需要预先了解数据的样貌,以及知道什么样的假设是适合数据的本质的,并基于此来选择合适的学习方法。如果读者没有预备知识也没有关系,因为读者可以通过猜测和尝试不同参数的不同模型,并选择一个在测试集上表现最好的模型,来完成这个步骤。
此外,初始数据分析和数据可视化将会帮助你对数据的分布形式与性质做出更好的猜测。
学习指的是用户用来选择最佳模型参数的优化指标。有以下不同的优化指标可以选择:
有些优化问题可能本身很难解决,但选择正确的模型和误差函数会使得情况有所改善。
在这一步中,用户尝试测量模型在未知数据上的泛化误差。既然用户只有特定的数据而无法事先了解任何未知的数据,就可以通过在数据集里随机地选择一部分作为测试集,并且在训练过程中从不使用它,从而使得这部分数据充当未知的有效数据。有很多方法可用于评估所选模型的性能。
这一步的目的是比较在相同数据集上训练出来的不同模型的预测性能,并选择一个拥有最小的测试误差的模型,这个模型将会在未知数据上表现出较好的泛化误差。用户也可以用统计方法判断结果的显著性,以得到泛化误差更准确的信息。
搭建一个机器学习系统将面临一些挑战与问题,本节中我们将尝试解决这些问题。其中的许多问题是在特定领域内存在的,而另一些则不是。
下面列出了在尝试搭建学习系统时,开发人员通常会遇到的一些挑战和问题。
特征提取是搭建机器学习系统过程时重要的一步。如果开发人员通过选择适当/正确数量的特征来很好地解决这一挑战,那么学习过程的其余部分将会很简单。此外,特征提取是依赖于领域的,并且需要先验知识来了解哪些特征对于特定任务来说可能是重要的。例如,前文提到的鱼类识别系统的特征与垃圾邮件检测或识别指纹的特征不同。
特征提取将从开发人员拥有的原始数据开始,然后构建衍生的变量/数值(特征),这些特征将为学习任务提供有用的信息,并有助于后续的学习和评估(泛化)。
一些任务具有大量的特征而只有较少的训练样本(观察),用于促进随后的学习和泛化过程。在这种情况下,数据科学家使用降维技术来将大量的特征缩减为一个比较小的集合。
在鱼类识别任务中,读者可以看到长度、重量、鱼的颜色以及船的颜色可能会有所不同,同时可能还存在阴影、低分辨率的图像以及其他物体。所有这些问题都会对这些本应为鱼类分类任务提供信息的解释性特征产生影响。
一些解决方案在这种情况下会有帮助。例如,有人可能会考虑检测船只的ID并且去除船只中某些可能不包含系统所要检测鱼类的部分,这种解决方案将会减小搜索空间。
正如你在鱼类识别任务中所看到的,开发人员尝试通过增加模型复杂性并使得模型对训练样本中的每个实例都能完美分类,试图提高模型的性能。正如你在之后将看到的,这类模型并不能在未见过的数据上正常工作(比如开发人员用于测试模型性能的数据)。训练的模型在训练样本集上超常发挥但在测试样本集上表现不佳的情况称为过拟合。
如果读者浏览本章的后半部分,那么会看到我们将构建一个学习系统,其目标是将训练样本当作模型的知识库,以便从中学习并在未见过的数据上泛化。训练模型在训练数据集上出现的性能误差并不是用户所感兴趣的;相反,用户比较感兴趣的是训练模型在那些训练过程中未见过的测试样本上出现的误差。
有时候用户对于模型在某一特定任务上的表现不满意,而需要另一类模型。每种学习策略都有它自己关于数据的假设,而这种假设将成为学习的基础。作为一名数据研究员,读者应该去发掘哪种观点最适用于你的数据,这样读者就有能力去选择尝试哪一类模型而拒绝另一类模型。
正如在模型选择和特征提取的概念中所讨论一样,如果读者有以下先验知识,就可以解决这两个问题:
事先了解鱼类识别系统中的解释性特征使得用户能够区分不同类型的鱼类。通过努力想象分析所拥有的信息,用户可以做出更好的判断并且了解到不同鱼群分类的信息类型。在此先验知识的基础上,就可以选择合适的模型簇了。
缺失的特征主要是由于缺少数据或选择了“保密”选项而导致的。用户在学习过程中要怎么处理这类问题呢?例如,某类鱼的宽度特征因为某些原因而丢失了。有很多种方法可以处理这些丢失的特征。
为了介绍机器学习(特别是深度学习)的能力,本节将实现鱼类识别的例子。这并不需要读者了解代码的内部细节。本节重点讲述典型机器学习的流程。
该任务的知识库是一系列图像,其中每张图像都贴上了月鱼或者金枪鱼的标签。在这次实现中,读者将使用其中一种深度学习架构,该架构在图像和计算机视觉领域取得了突破。这种架构叫作卷积神经网络(CNN)。卷积神经网络是一类深度学习架构,它通过图像处理的卷积运算来从图像当中提取特征,以解释需要分类的对象。现在,我们可以把它当成一个魔术盒,它将读取图像,从中学习如何区分这两类对象(月鱼和金枪鱼),然后向这个盒子输入未贴标签的图像以测试它的学习过程,并观察它是否能够分辨图像中是哪一类鱼。
不同类型的学习将在后面的章节中讨论,因此我们将在后面了解为什么上述鱼类识别任务属于监督学习类别。
这个例子中将会使用Keras。现在我们可以把Keras当成一个使得搭建和使用深度学习更加简单的API。从Keras网站上可以看到:
Keras是一个高级神经网络API,用Python编写,能够在TensorFlow、CNTK或Theano之上运行。它旨在实现快速实验,能够以最短的时间把想法转换为结果是做好研究的关键。
正如前文所提到的,开发需要以历史数据库作为基础,这将用来使学习算法了解其后期应该完成的任务。但是我们还需要另一个数据集来测试学习算法在学习过程后执行任务的能力。总而言之,在学习过程中需要两类数据集。
第一类数据集是拥有输入数据及其对应标签的知识库,例如鱼的图像和它们的对应标签(月鱼和金枪鱼)。把这类数据输入学习算法中,学习算法从中学习并发现之后将用于分类未标记图像的模式/趋势。
第二类数据集主要用于测试模型将从知识库中学到的知识应用于未标记的图像或未查看的数据上的能力,并检查模型是否运行良好。
正如我们所看到的,开发人员只拥有用来当作学习算法知识库的数据。我们所拥有的所有数据都带有相应的正确的输出。现在需要以某种方法来准备这种不带正确输出的数据(模型将应用于这些数据上)。
在数据科学中,将进行以下操作。
验证/测试阶段通常分为如下两步。
(1)使用不同的学习算法/模型,并基于验证数据选择性能最好的学习算法/模型,此步骤为验证步骤。
(2)度量和报告所选模型在测试集上的准确率,此步骤是测试步骤。
现在我们将学习如何获取这些模型将会用到的数据,并了解它能训练得多好。
既然没有不带正确输出的训练样本,我们就可以从将要使用到的原始训练样本中生成一个。我们可以将数据样本分成3个不同的集合(如图1.10所示)。
图1.10 将数据分为训练集、验证集和测试集
这个例子只使用了一种学习算法,因此可以取消验证集,并重新把数据只分为训练集和测试集。通常,数据科学家采用75/25的百分比,或者70/30。
本节将对输入图像进行分析和预处理,将它变成这里的卷积神经网络学习算法所接受的格式。
接下来,从导入这个应用所需要的包开始。
import numpy as np
np.random.seed(2018)
import os
import glob
import cv2
import datetime
import pandas as pd
import time
import warnings
warings.filterwarnings("ignore")
from sklearn.cross_validation import KFold
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Flatten
from keras.layers.convolutional import Convolution2D, MaxPooling2D,
ZeroPadding2D
from keras.optimizers import SGD
from keras.callbacks import EarlyStopping
from keras.utils import np_utils
from sklearn.metrics import log_loss
from keras import __version__ as keras_version
为了使用数据集所提供的图片,我们需要通过以下代码把它们调整为同样大小。从OpenCV网站上可以得知,OpenCV是完成这项工作的一个好选择。
开源计算机视觉库(Open Source Computer Vision Library,OpenCV)是在BSD许可证下发行的,它在学术和商业应用当中都是免费的。它有C++、C、Python和Java接口,并且支持Windows、Linux、MacOS、iOS和Android系统。OpenCV是为高效计算而设计的,并着重于实时应用。OpenCV用优化的C/C++写成,这使得它可以利用多核处理器资源。启用OpenCL后,它可以利用底层异构计算平台的硬件加速。
可以通过使用Python软件包管理器和命令pip install opencv-python来安装OpenCV。
# Parameters
# ---------------
# img_path : path
# path of the image to be resized
def resize_image(img_path):
#reading image file
img = cv2.imread(img_path)
#Resize the image to be 32 by 32
img_resized = cv2.resize(img, (32,32), cv2.INTER_LINEAR)
return img_resized
现在需要加载所有数据集中的训练样本,并根据前面的函数改变每幅图像的大小。下面将实现一个从保存不同鱼类的不同文件夹中加载训练样本的函数。
# Loading the training samples and their corresponding labels
def load_training_samples():
#Variables to hold the training input and output variables
train_input_variables = []
train_input_variables_id = []
train_label = []
# Scanning all images in each folder of a fish type
print('Start Reading Train Images')
folders = ['ALB', 'BET', 'DOL', 'LAG', 'NoF', 'OTHER', 'SHARK', 'YFT']
for fld in folders:
folder_index = folders.index(fld)
print('Load folder {} (Index: {})'.format(fld, folder_index))
imgs_path = os.path.join('..', 'input', 'train', fld, '*.jpg')
files = glob.glob(imgs_path)
for file in files:
file_base = os.path.basename(file)
# Resize the image
resized_img = rezize_image(file)
# Appending the processed image to the input/output variables of the classifier
train_input_variables.append(resized_img)
train_input_variables_id.append(file_base)
train_label.append(folder_index)
return train_input_variables, train_input_variables_id, train_label
正如前面所讨论的,有一个测试集将作为未见过的数据来测试模型的泛化能力,所以我们必须对测试图片进行相同的操作:加载它们并改变它们的大小。
def load_testing_samples():
# Scanning images from the test folder
imgs_path = os.path.join('..', 'input', 'test_stg1', '*.jpg')
files = sorted(glob.glob(imgs_path))
# Variables to hold the testing samples
testing_samples = []
testing_samples_id = []
#Processing the images and appending them to the array that we have
for file in files:
file_base = os.path.basename(file)
# Image resizing
resized_img = rezize_image(file)
testing_samples.append(resized_img)
testing_samples_id.append(file_base)
return testing_samples, testing_samples_id
现在需要在另一个函数里调用前一个函数,这个函数将会用load_training_samples()函数来加载和改变训练样本的大小。同时,添加几行代码来将训练数据转换为Numpy格式,调整数据形状以适应分类器模型,最后把数据转换为浮点数类型。
def load_normalize_training_samples():
# Calling the load function in order to load and resize the training samples
training_samples, training_label, training_samples_id =load_training_samples()
# Converting the loaded and resized data into Numpy format
training_samples = np.array(training_samples, dtype=np.uint8)
training_label = np.array(training_label, dtype=np.uint8)
# Reshaping the training samples
training_samples = training_samples.transpose((0, 3, 1, 2))
# Converting the training samples and training labels into float format
training_samples = training_samples.astype('float32')
training_samples = training_samples / 255
training_label = np_utils.to_categorical(training_label, 8)
return training_samples, training_label, training_samples_id
对测试集也需要进行相同的操作。
def load_normalize_testing_samples():
# Calling the load function in order to load and resize the testing samples
testing_samples, testing_samples_id = load_testing_samples()
# Converting the loaded and resized data into Numpy format
testing_samples = np.array(testing_samples, dtype=np.uint8)
# Reshaping the testing samples
testing_samples = testing_samples.transpose((0, 3, 1, 2))
# Converting the testing samples into float format
testing_samples = testing_samples.astype('float32')
testing_samples = testing_samples / 255
return testing_samples, testing_samples_id
下面开始创建模型了。正如前文提到的,我们将一种名为CNN的深度学习架构(如图1.11所示)作为鱼类识别任务的学习算法。同样,因为本节只演示在Keras和TensorFlow深度学习平台的帮助下如何仅使用几行代码来解决复杂的数据科学任务,所以读者不需要了解本章中以前出现过的或即将出现的代码。
图1.11 CNN架构
CNN和其他深度学习架构的更多详细知识将在后面的章节中介绍。
现在继续构造一个函数以创建鱼类识别任务中使用的CNN架构。
def create_cnn_model_arch():
pool_size = 2 # we will use 2x2 pooling throughout
conv_depth_1 = 32 # we will initially have 32 kernels per conv.layer...
conv_depth_2 = 64 # ...switching to 64 after the first pooling layer
kernel_size = 3 # we will use 3x3 kernels throughout
drop_prob = 0.5 # dropout in the FC layer with probability 0.5
hidden_size = 32 # the FC layer will have 512 neurons
num_classes = 8 # there are 8 fish types
# Conv [32] ->Conv [32] -> Pool
cnn_model = Sequential()
cnn_model.add(ZeroPadding2D((1, 1), input_shape=(3, 32, 32),
dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_1, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(ZeroPadding2D((1, 1), dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_1, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(MaxPooling2D(pool_size=(pool_size, pool_size),
strides=(2, 2),
dim_ordering='th'))
# Conv [64] ->Conv [64] -> Pool
cnn_model.add(ZeroPadding2D((1, 1), dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_2, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(ZeroPadding2D((1, 1), dim_ordering='th'))
cnn_model.add(Convolution2D(conv_depth_2, kernel_size, kernel_size,
activation='relu',
dim_ordering='th'))
cnn_model.add(MaxPooling2D(pool_size=(pool_size, pool_size),
strides=(2, 2),
dim_ordering='th'))
# Now flatten to 1D, apply FC then ReLU (with dropout) and finally softmax(output layer)
cnn_model.add(Flatten())
cnn_model.add(Dense(hidden_size, activation='relu'))
cnn_model.add(Dropout(drop_prob))
cnn_model.add(Dense(hidden_size, activation='relu'))
cnn_model.add(Dropout(drop_prob))
cnn_model.add(Dense(num_classes, activation='softmax'))
# initiating the stochastic gradient descent optimiser
stochastic_gradient_descent = SGD(lr=1e-2, decay=1e-6, momentum=0.9,
nesterov=True) cnn_model.compile(optimizer=stochastic_gradient_descent,
# using the stochastic gradient descent optimiser
loss='categorical_crossentropy') # using the cross-entropy loss function
return cnn_model
在开始训练模型之前,我们需要使用模型评估和验证方法来帮助评估模型,并检测它的泛化能力。因此,接下来需要用到k折交叉验证法。读者不需要理解这种方法或它的工作原理,因为本书将在稍后详细解释这种方法。
下面开始并构造一个帮助评估和验证模型的函数。
def create_model_with_kfold_cross_validation(nfolds=10):
batch_size = 16 # in each iteration, we consider 32 training examples at once
num_epochs = 30 # we iterate 200 times over the entire training set
random_state = 51 # control the randomness for reproducibility of the results on the same platform
# Loading and normalizing the training samples prior to feeding it to the created CNN model
training_samples, training_samples_target, training_samples_id =
load_normalize_training_samples()
yfull_train = dict()
# Providing Training/Testing indices to split data in the training samples
# which is splitting data into 10 consecutive folds with shuffling
kf = KFold(len(train_id), n_folds=nfolds, shuffle=True,
random_state=random_state)
fold_number = 0 # Initial value for fold number
sum_score = 0 # overall score (will be incremented at each iteration)
trained_models = [] # storing the modeling of each iteration over the folds
# Getting the training/testing samples based on the generated training/testing indices by
Kfold
for train_index, test_index in kf:
cnn_model = create_cnn_model_arch()
training_samples_X = training_samples[train_index] # Getting the training input variables
training_samples_Y = training_samples_target[train_index] # Getting the training output/label variable
validation_samples_X = training_samples[test_index] # Getting the validation input variables
validation_samples_Y = training_samples_target[test_index] # Getting the validation output/label variable
fold_number += 1
print('Fold number {} from {}'.format(fold_number, nfolds))
callbacks = [
EarlyStopping(monitor='val_loss', patience=3, verbose=0),
]
# Fitting the CNN model giving the defined settings
cnn_model.fit(training_samples_X, training_samples_Y,
batch_size=batch_size,
nb_epoch=num_epochs,
shuffle=True, verbose=2,
validation_data=(validation_samples_X,
validation_samples_Y),
callbacks=callbacks)
# measuring the generalization ability of the trained model based on the validation set
predictions_of_validation_samples =
cnn_model.predict(validation_samples_X.astype('float32'),
batch_size=batch_size, verbose=2)
current_model_score = log_loss(Y_valid,
predictions_of_validation_samples)
print('Current model score log_loss: ', current_model_score)
sum_score += current_model_score*len(test_index)
# Store valid predictions
for i in range(len(test_index)):
yfull_train[test_index[i]] =
predictions_of_validation_samples[i]
# Store the trained model
trained_models.append(cnn_model)
# incrementing the sum_score value by the current model calculated score
overall_score = sum_score/len(training_samples)
print("Log_loss train independent avg: ", overall_score)
#Reporting the model loss at this stage
overall_settings_output_string = 'loss_' + str(overall_score) +
'_folds_' + str(nfolds) +
'_ep_' + str(num_epochs)
return overall_settings_output_string, trained_models
在构建好了模型并使用k折交叉验证法来评估和验证模型之后,我们需要在测试集上报告训练模型的结果。为了做到这一点,将使用k折交叉验证法,但是这次是在测试集中观察训练模型的效果。
接下来,需要以受过训练的CNN模型作为输入的函数,然后使用现有的测试集来测试它:
def test_generality_crossValidation_over_test_set(
overall_settings_output_string, cnn_models):
batch_size = 16 # in each iteration, we consider 32 training examples at once
fold_number = 0 # fold iterator
number_of_folds = len(cnn_models) # Creating number of folds based on the value used in the training step
yfull_test = [] # variable to hold overall predictions for the test set
#executing the actual cross validation test process over the test set
for j in range(number_of_folds):
model = cnn_models[j]
fold_number += 1
print('Fold number {} out of {}'.format(fold_number,
number_of_folds))
#Loading and normalizing testing samples
testing_samples, testing_samples_id =
load_normalize_testing_samples()
#Calling the current model over the current test fold
test_prediction = model.predict(testing_samples,
batch_size=batch_size, verbose=2)
yfull_test.append(test_prediction)
test_result = merge_several_folds_mean(yfull_test, number_of_folds)
overall_settings_output_string = 'loss_' +
overall_settings_output_string \ + '_folds_' +
str(number_of_folds)
format_results_for_types(test_result, testing_samples_id,overall_settings_output_string)
下面可以通过调用主函数create_model_with_kfold_cross_validation()来创建和训练CNN模型了,该CNN模型使用了10折交叉验证法。然后调用测试函数来度量模型在测试集上的泛化能力。
if __name__ == '__main__':
info_string, models = create_model_with_kfold_cross_validation()
test_generality_crossValidation_over_test_set(info_string, models)
在解释完鱼识别示例的主要构建模块之后,就可以将所有代码片段连接在一起,并查看如何通过几行代码来构建这样一个复杂的系统。完整的代码放在本书附录A中。
Arthur Samuel说:“数据科学使得计算机能够在没有明确编程的情况下具备学习能力。”所以任何通过读取训练样例以便在没有明确编程的情况下对未见过的数据进行决策的软件都可以视为学习。数据科学或学习有3种不同的形式。
图1.12所示为常见的数据科学/机器学习类型。
图1.12 数据科学/机器学习的不同类型
大部分数据科学家都使用监督学习。监督学习是指这样的一种情况:具有一些称为输入变量(X)的解释性特征;同时带有与训练样本相关联的标签,这些标签称为输出变量(Y),监督式学习算法的目标是学习从输入变量(X)到输出变量(Y)的映射函数。
因此,监督学习算法将尝试近似地学习从输入变量(X)到输出变量(Y)的映射,以便后续可以使用它来预测未知样本的Y值。
图1.13所示为监督数据科学系统的典型工作流程。
图1.13 一个典型的监督学习工作流程。上面的部分显示了训练过程,从把原始数据输入特征提取模块当中开始,用户将在这里选取有意义的解释性特征来代表数据。然后,将提取/选择的解释特征与训练集相结合,并将它反馈到学习算法以便从中学习。最后做一些模型评估来调整参数,并获得学习算法以从数据样本中获得最好的结果
这种学习称为监督学习,因为每个训练样本都有标签/输出。在这种情况下,我们称学习过程受一个监督者监督。算法在训练样本上做决策,然后监督者根据数据的正确标签进行纠正。当监督学习算法达到一个可接受的准确率水平后,学习过程就会终止。
监督学习有两种形式——分类任务和回归任务。
无监督学习被视为信息研究人员使用的第二种最常见的学习方式。在这种类型的学习中,数据只给出了解释性特征或输入变量(X),而没有任何相应的标签或输出变量。
无监督学习算法的目标是收集信息中隐藏的结构和样例。由于没有与训练样本相关的标记,因此这种学习称为无监督学习。无监督学习是一个没有修正的学习过程,它将尝试自行找到基本结构。
无监督学习可以进一步划分为两种形式——聚类任务和关联规则学习任务。
图1.14所示为一个简单的无监督学习示例,在这个例子中用户有一堆分散的文档,尝试把相似的文档划分在一块。
图1.14 无监督学习利用相似性度量(如欧氏距离)来将相似的文档进行分组并给出对应的决策边界
半监督学习是介于监督学习和无监督学习之间的一种学习类型,用户可以使用输入变量(X)进行训练,但只有其中的一部分转入变量通过输出变量(Y)进行标记/标记。
这类学习的一个很好的例子是Flickr,Flickr中有很多由网站用户上传的图片,但只有其中的一小部分图片贴了标签(例如日落、海洋和狗等),其余的都没有标签。
为了解决这类学习任务,开发人员可以从下面两种方法选择一种或者同时使用二者。
机器学习的最后一种学习类型是强化学习,在强化学习中只有奖励信号,而没有监督者。
强化学习算法会尝试做出一个决策,然后获得一个奖励信号,这个信号用于指出这个决策是否正确。此外,这种监督反馈或奖励信号可能不会立即出现,而是会有一定的延迟。例如,该算法现在会做出决定,但过了一段时间后,奖励信号才指出了决策是好还是坏。
数据是学习计算的信息库:任何出色或富有想象力的想法在缺少信息的情况下都将毫无意义。所以如果用户有一个可靠的信息科学应用以及正确的信息,那么就算做好准备工作了。
无论信息结构是什么样的,很明显,如今我们都有能力从信息中挖掘并获取一些激励。然而,因为海量信息已经司空见惯,所以大家都希望信息科学中的仪器和新技术有能力在合理的学习时间内处理这些海量信息。当今任何事情都在产生信息,而是否有能力接受它是一种考验。大型的组织(如谷歌、微软、IBM等)都在发展自己的海量信息科学规划,以处理由他们的客户每天产生的海量信息。
TensorFlow是一个开源的机器智能/数据科学平台,在2016年11月9日由谷歌发布。它是一个可扩展的分析平台,使数据科学家能够在可见的时间内构建具有大量数据的复杂系统,并且使他们能够使用贪婪的学习方法,这些学习方法为了获得好的性能需要大量的数据。
本章讲述了如何构建一个鱼类识别学习系统,并讲述了如何在TensorFlow和Keras的帮助下使用几行代码来构建复杂的应用程序,如识别鱼类的应用程序。这个代码示例并不要求读者理解,而是为了展示构建的复杂系统的可见性,以及在一般和特定的深度学习中数据科学如何成为一种易于使用的工具。
本章还展示了数据科学家在构建学习系统时可能遇到的一些挑战。
本章还研究了构建学习系统的典型设计周期,并解释了此周期中涉及的每个组件的总体思路。
最后,本章介绍了不同的学习类型,大企业或小公司每天都产生的大数据,大量的数据如何对构建可扩展的工具造成挑战,以及这些工具如何从数据中分析和挖掘到有价值的东西。
对于目前所提到的这些内容,读者可能会感到有点不知所措。不用担心,本章所讲到的大部分内容都会在其他章节中继续讨论,包括数据科学的挑战以及鱼类识别示例。本章的主要目的是让读者对数据科学及其开发周期有一个总体的了解,但不要求读者对其中的挑战和代码示例有深刻的理解。本章展示了一些代码示例以克服数据科学领域大多数新手的恐惧,并向他们展示如何用几行代码完成鱼识别等复杂系统。
接下来,本书将通过一个例子来讲解数据科学的基本概念,开启实战之旅。下一部分将主要通过著名的“泰坦尼克”示例来为后面的高阶内容做准备。本书还将讨论许多概念,包括回归和分类的不同学习方法,不同类型的性能错误,哪种性能错误更应该关注,以及一些与解决数据科学挑战和处理不同形式的数据样本相关的问题。
线性模型是数据科学领域的基本学习算法。理解线性模型如何工作对于学习数据科学至关重要,因为它是大多数复杂学习算法(包括神经网络)的基本构建模块。
本章将深入讲解数据科学领域的一个著名问题——“泰坦尼克号”示例。介绍这个例子的目的是引入线性模型以进行分类,并让读者看到一个完整的机器学习系统的运行过程,从数据的处理和探索到模型的评估。本章将介绍以下主题。
线性回归模型是最基本的回归模型,并且广泛用于可预测数据的分析。回归模型的总体思路是检查以下两件事情。
回归方程可表达输入变量(自变量)对输出变量(因变量)的影响。具有一个输入变量和一个输出变量的等式是回归方程最简单的形式,回归方程定义为y = c+bx。这里,y是被预测的因变量,c是常数,b是回归参数/系数,x是输入(自)变量。
线性回归模型是许多学习算法的基础模块,但这并不是它们受欢迎的唯一原因。以下是它受欢迎的关键因素。
为了更好地理解线性回归模型,本节将介绍一个有关广告的例子。我们将尝试通过研究不同公司在电视、广播和报纸上花费的广告金额来预测公司的销售情况。
为了用线性回归模型来建模本节中的广告数据样本,我们使用统计模型库来获得线性模型的良好特性。稍后,我们将使用scikit-learn,它对于一般的数据科学非常有用。
Python中有很多库可用于读取、转换或写入数据,pandas就是其中的一个库。pandas是一个开源的库,它具有强大的数据分析功能工具以及非常简单的数据结构。
读者可以通过许多不同的方式轻松获得pandas,其中最好的方法是通过conda安装它。
“conda是一个开源软件包管理系统和环境管理系统,用于安装多个版本的软件包及其依赖关系并在它们之间轻松切换。它适用于Linux系统、Mac OS X和Windows系统。虽然它是为Python程序创建的,但它可以打包并分发任何语言的软件。”
——conda官网
我们可以通过安装Anaconda轻松获得conda,Anaconda是一个开放的数据科学平台。
接下来,我们来看看如何使用pandas来读取广告数据样本。首先,读者需要导入pandas。
import pandas as pd
接下来,可以使用pandas.read_csv方法将数据加载到一个名为DataFrame的易于使用的pandas数据结构中。有关pandas.read_csv及其参数的更多信息,可以参考此方法的pandas文档。
# read advertising data samples into a DataFrame
advertising_data =
pd.read_csv('http://www-bcf.usc.edu/~gareth/ISL/Advertising.csv',
index_col=0)
传递给pandas.read_csv方法的第一个参数是表示文件路径的字符串值。该字符串可以是http、ftp、s3和文件的URL。传递的第二个参数是将用作数据行的标签/名称的列的索引。
现在,我们有DataFrame数据,其中包含URL中提供的广告数据,每行的标记是第一列的数值。如前所述,pandas提供易于使用的数据结构,我们可以将它当作数据容器。这些数据结构带有对应的一些方法,我们可以使用这些方法来转换或操作自己的数据。
现在,看一下广告数据的前5行,代码如下。
# DataFrame.head method shows the first n rows of the data where the
# default value of n is 5, DataFrame.head(n=5)
advertising_data.head()
输出如下。
TV |
Radio |
Newspaper |
Sales |
|
---|---|---|---|---|
1 |
230.1 |
37.8 |
69.2 |
22.1 |
2 |
44.5 |
39.3 |
45.1 |
10.4 |
3 |
17.2 |
45.9 |
69.3 |
9.3 |
4 |
151.5 |
41.3 |
58.5 |
18.5 |
5 |
180.8 |
10.8 |
58.4 |
12.9 |
这个问题属于监督学习类型的问题,其中有解释性特征(输入变量)和输出(输出变量)。
输入变量如下。
输出/结果/输出变量是销量,即在特定市场中单件产品的销量(单位是千件)。
可以使用DataFrame中的shape方法来了解数据中样本的形状。
# print the shape of the DataFrame
advertising_data.shape
Output:
(200, 4)
所以可以得知数据中共有200个样本。
为了理解数据的基本形式、特征与输出之间的关系以及其他信息,我们可以使用不同类型的可视化图。为了理解广告数据特征和输出之间的关系,本节将使用散点图。
为了对数据进行不同类型的可视化,我们可以使用Matplotlib,这是一个用于可视化的Python 2D库。要获得Matplotlib,可以按照Matplotlib官网中的安装说明进行操作。
下面导入可视化库Matplotlib。
import matplotlib.pyplot as plt
# The next line will allow us to make inline plots that could appear
directly in the notebook
# without poping up in a different window
%matplotlib inline
现在,使用散点图来可视化广告数据特征和输出变量之间的关系。
fig, axs = plt.subplots(1, 3, sharey=True)
# Adding the scatterplots to the grid
advertising_data.plot(kind='scatter', x='TV', y='sales', ax=axs[0],
figsize=(16, 8))
advertising_data.plot(kind='scatter', x='radio', y='sales', ax=axs[1])
advertising_data.plot(kind='scatter', x='newspaper', y='sales', ax=axs[2])
输出如图2.1所示。
图2.1 用于理解广告数据特征与响应变量之间关系的散点图
现在,我们需要了解广告是如何有助于增加销量的。首先,问自己几个问题。值得提出的问题有广告与销量之间的关系,哪种广告对销量的贡献更大,每种广告对销量的影响等。接下来,我们将尝试使用简单的线性模型来回答这些问题。
线性回归模型是一种学习算法,它使用一些解释性特征(输入或预测变量)来预测定量(也称为数值)输出。
只有一个特征的简单线性回归模型采用以下形式。
y = β0+ β1x
其中,y表示预测值(输出),即销量;x表示输入特征;β0表示截距;β1表示特征x的系数。
β0和β1都是模型系数。为了创建一个在广告例子中可以预测销量的模型,我们需要学习这些系数。β1是特征x对输出y的学习效果。例如,若β1 = 0.04,则意味着在电视广告上额外花费的100美元与增加4件销量相关联。接下来,我们看看如何学习这些系数。
为了评估模型的系数,这里需要用一条回归线来拟合数据,该回归线可以给出和实际销量类似的答案。为了得到最拟合数据的回归线,这里使用名为最小二乘的标准。我们需要找到一条线,以最小化预测值与观察值(实际值)之间的差异。换句话说,需要找到一条回归线,该回归线使残差平方和(sum of squared residuals)最小,如图2.2所示。
(预测值和观察值之间的差异)
图2.2 用回归线拟合数据点(电视广告的样本),使残差平方和最小
以下是图2.2中存在的一些量。
以下是系数与最小二乘线(回归线)的关系。
图2.3给出了图形化的解释。
图2.3 最小二乘线与模型系数之间的关系
现在,继续使用Statsmodels来学习这些系数。
# To use the formula notation below, we need to import the module like the following
import statsmodels.formula.api as smf
# create a fitted model in one line of code(which will represent the least squares line)
lm = smf.ols(formula='sales ~ TV', data=advertising_data).fit()
# show the trained model coefficients
lm.params
输出如下。
Intercept 7.032594
TV 0.047537
dtype: float64
正如之前所提到的,线性回归模型的一个优点是易于解释,因此接下来继续解释模型。
下面解释模型系数,如电视广告系数(β1)。输入特征(电视广告)支出增加1个单位与销量(输出)增加0.047 537个单位有关。换句话说,另外花费在电视广告上的100美元与销量中增加的4.7537件相关。
用电视广告数据构建学习模型的目标是预测未知数据对应的销量。因此,接下来将讲解基于已知的电视广告支出如何使用学习模型来预测未知的销量。
假设有一些之前未见过的电视广告支出数据,现在我们想知道它们对公司销量的影响。我们使用学习模型可以完成这个任务,假如我们想知道5万美元的电视广告能增加多少销量。
这里用学习的模型系数做出这样一个计算:
y = 7.032 594 + 0.047 537×50
执行以下代码。
# manually calculating the increase in the sales based on $50k
7.032594 + 0.047537*50000
输出结果如下。
9409.444
这里也可以使用Statsmodels来做预测。首先,需要在pandas DataFrame中输入电视广告费用,因为它是Statsmodels接口的输入参数。
# creating a Pandas DataFrame to match Statsmodels interface expectations
new_TVAdSpending = pd.DataFrame({'TV': [50000]})
new_TVAdSpending.head()
输出结果如下。
TV |
|
---|---|
0 |
50000 |
现在,可以继续使用预测函数来预测销量。
# use the model to make predictions on a new value
preds = lm.predict(new_TVAdSpending)
输出结果如下。
array([ 9.40942557])
下面绘制学习好的最小二乘线。绘制一条线需要两个点,每个点表示为(x,predict_value_of_x)。
因此,计算电视广告费用的最小值和最大值。
# create a DataFrame with the minimum and maximum values of TV
X_min_max = pd.DataFrame({'TV': [advertising_data.TV.min(),
advertising_data.TV.max()]})
X_min_max.head()
输出结果如下。
TV |
|
---|---|
0 |
0.7 |
1 |
296.4 |
分别计算这两个值的预测值。
# predictions for X min and max values
predictions = lm.predict(X_min_max)
predictions
输出结果如下。
array([7.0658692, 21.12245377])
现在,我们绘制实际数据,然后用最小二乘法线进行拟合。
# plotting the acutal observed data
advertising_data.plot(kind='scatter', x='TV', y='sales')
#plotting the least squares line
plt.plot(new_TVAdSpending, preds, c='red', linewidth=2)
输出结果如图2.4所示。
图2.4 实际数据和最小二乘线
这个例子的扩展和进一步的解释将在第3章中详细介绍。
本节将解释逻辑回归,它是广泛使用的分类算法之一。
什么是逻辑回归?逻辑回归的简单定义是它是一种涉及线性判别式的分类算法。
下面将用两点阐明这一定义。
图2.5 划分两个类别的线性分界面
如果数据样本不是线性可分的,那么可以通过添加更多的输入特征来将数据转换到更高维的空间来实现。
我们已经学习了如何将连续量(例如,电视广告费用对公司销量的影响)预测为输入值(例如,电视、广播和报纸广告方面的费用)的线性函数。但对于其他任务,输出并不会是连续量。例如,预测某人是否患病是一种分类问题,我们需要另一种学习算法来执行此操作。本节将深入研究逻辑回归的数学分析,这是一种用于分类任务的学习算法。
在线性回归中,我们尝试使用线性模型函数y = hθ (x) = θ Tx来预测该数据集中第i个样本x(i)的输出变量y(i)的值。但对于预测二元标签(y(i)∈{0,1})等分类任务来说,这并不是一个很好的解决方案。
逻辑回归是用于分类任务的众多学习算法之一,使用不同的假设类,我们可以预测特定样本属于第1个类的概率以及它属于第0个类的概率。因此,在逻辑回归中,我们将尝试学习以下函数。
函数通常称为sigmoid函数或logistic函数,它将x的值压缩到固定范围[0,1],如图2.6所示。因为把值压缩在[0,1],所以可以将解释为概率。
图2.6 sigmoid函数的形状
读者的目标是求的值,使得当输入样本x属于第1类时,概率比较大;当x属于第0类时,概率较大。
因此,假设有一组训练样本及其对应的二分类标签,读者需要做的是最小化下面的代价函数,它用于衡量给定的好坏。
注意,对于每个训练样本,方程的两个项中,只有一项非零(根据标签的值是否为0)。当时,最小化模型代价函数需要使尽量大;当时,最小化模型代价函数需要使尽量大。
现在,用一个代价函数来计算给定的假设拟合训练样本的程度。我们可以使用优化法来最小化,并求参数的最优解来学习如何对训练样本进行分类。完成此操作后,我们就可以使用这些参数判断这两个类标签(0和1)中哪一个最有可能,从而对新样本进行分类。如果,那么输出0;否则,输出1。同理,如果阈值定义为0.5,当时输出1;否则,输出0。
为了最小化代价函数,我们可以使用优化法来求出使代价函数最小化的的最佳值。因此,我们可以使用名为梯度的微积分工具,该工具试图求出成本函数的最大增长率。然后,可以从相反的方向来求出这个函数的最小值。例如,的梯度由表示,这表示取相对于模型参数的代价函数的梯度。因此,需要提供一个函数来计算任意的和。如果推导以上的代价函数相对于的梯度或导数,将得到以下结果。
将它写成向量形式,如下所示。
现在,我们从数学上了解了逻辑回归,接下来继续使用这种新的学习方法来解决分类任务。
“泰坦尼克号”沉船是历史上最悲惨的事件之一。这起事件导致2224名乘客和1502名船员死亡。在本节中,我们将使用数据科学来预测乘客是否能在这场悲剧中生存,然后根据这场悲剧的实际统计数据来测试这个模型的性能。
要开始学习“泰坦尼克号”示例,我们需要做以下工作。
1)从GitHub网站下载ML_Titanic ZIP格式的存储库或者在命令行窗口中执行命令Git clone命令复制GitHub网站中的ML_Titanic.git。
2)安装virtualenv。
3)导航到解压缩或复制存储库的目录,并使用virtualenv ml_titanic创建虚拟环境。
4)使用source ml_titanic/bin/activate激活环境。
5)安装依赖的包pip install -r requirements.txt。
6)在命令行窗口或者终端中执行ipython notebook。
7)按照本章中的示例代码进行操作。
8)当结束操作后,使用deactivate关闭虚拟环境。
在本节中,我们将要做一些数据预处理和分析。可以认为数据预处理和分析是机器学习中最重要的步骤之一,也可以认为它是最重要的步骤,没有之一。因为在这一步,我们将了解在训练过程一直与我们相伴的数据。此外,了解使用的数据后,我们可以缩小自己的候选算法集,从而选择最优的算法。
首先,导入需要软件包。
import matplotlib.pyplot as plt
%matplotlib inline
from statsmodels.nonparametric.kde import KDEUnivariate
from statsmodels.nonparametric import smoothers_lowess
from pandas import Series, DataFrame
from patsy import dmatrices
from sklearn import datasets, svms
import numpy as np
import pandas as pd
import statsmodels.api as sm
from scipy import stats
stats.chisqprob = lambda chisq, df: stats.chi2.sf(chisq, df)
用pandas读取“泰坦尼克号”中乘客和船员的数据。
titanic_data = pd.read_csv("data/titanic_train.csv")
接下来,检查数据集的维度,查看样本的数量和描述数据集的解释性特征的个数。
titanic_data.shape
Output:
(891, 12)
共有891个数据样本以及12个解释性特征来描述此记录。
list(titanic_data)
Output:
['PassengerId',
'Survived',
'Pclass',
'Name',
'Sex',
'Age',
'SibSp',
'Parch',
'Ticket',
'Fare',
'Cabin',
'Embarked']
接下来,查看样本的详细信息。
titanic_data[500:510]
输出结果如图2.7所示。
图2.7 “泰坦尼克号”中的样本
现在,我们有一个Pandas DataFrame,它包含需要分析的891名乘客的信息。DataFrame的列表示每个乘客/船员的解释性特征,例如姓名、性别或年龄。
其中一些解释性功能是完整的,没有任何缺失值,例如幸存特征,它有891个条目。其他解释性特征包含缺失值,例如年龄特征,它只有714个条目。DataFrame中的任何缺失值都表示为NaN。
如果我们观察所有的数据集特征,会发现Ticket和Cabin特征有许多缺失值(NaN),因此它们不会为我们的分析贡献很多力量。为了解决这个问题,我们将从DataFrame中删除它们。
使用以下代码从DataFrame中删除Ticket和Cabin特征。
titanic_data = titanic_data.drop(['Ticket','Cabin'], axis=1)
因为某些原因,数据集中会包含缺失值,但为了保持数据集的完整性,需要处理这些缺失值。在这个特定的问题中,这次我们选择删除它们。
使用以下代码来删除所有其他特征中的所有NaN值。
titanic_data = titanic_data.dropna()
现在,我们有了一个完善的数据集,可以用它来进行数据分析了。如果尝试在不删除Ticket和Cabin特征列表的情况下删除所有NaN,会发现大部分数据集都会被删除,因为.dropna()方法会从DataFrame中删除所有满足条件的,即使某一个特征中只有一个NaN。
下面可视化数据来查看一些特征的分布并理解解释性特征之间的关系。
# declaring graph parameters
fig = plt.figure(figsize=(18,6))
alpha=alpha_scatterplot = 0.3
alpha_bar_chart = 0.55
# Defining a grid of subplots to contain all the figures
ax1 = plt.subplot2grid((2,3),(0,0))
# Add the first bar plot which represents the count of people who survived vs not survived.
titanic_data.Survived.value_counts().plot(kind='bar', alpha=alpha_bar_chart)
# Adding margins to the plot
ax1.set_xlim(-1, 2)
# Adding bar plot title
plt.title("Distribution of Survival, (1 = Survived)")
plt.subplot2grid((2,3),(0,1))
plt.scatter(titanic_data.Survived, titanic_data.Age, alpha=alpha_scatterplot)
# Setting the value of the y label (age)
plt.ylabel("Age")
# formatting the grid
plt.grid(b=True, which='major', axis='y')
plt.title("Survival by Age, (1 = Survived)")
ax3 = plt.subplot2grid((2,3),(0,2))
titanic_data.Pclass.value_counts().plot(kind="barh", alpha=alpha_bar_chart)
ax3.set_ylim(-1, len(titanic_data.Pclass.value_counts()))
plt.title("Class Distribution")
plt.subplot2grid((2,3),(1,0), colspan=2)
# plotting kernel density estimate of the subse of the 1st class passenger’s age
titanic_data.Age[titanic_data.Pclass == 1].plot(kind='kde')
titanic_data.Age[titanic_data.Pclass == 2].plot(kind='kde')
titanic_data.Age[titanic_data.Pclass == 3].plot(kind='kde')
# Adding x label (age) to the plot
plt.xlabel("Age")
plt.title("Age Distribution within classes")
# Add legend to the plot.
plt.legend(('1st Class', '2nd Class','3rd Class'),loc='best')
ax5 = plt.subplot2grid((2,3),(1,2))
titanic_data.Embarked.value_counts().plot(kind='bar', alpha=alpha_bar_chart)
ax5.set_xlim(-1, len(titanic_data.Embarked.value_counts()))
plt.title("Passengers per boarding location")
样本可视化结果如图2.8所示。
图2.8 “泰坦尼克号”数据样本的初步可视化
正如前面所提到的,这种分析的目的是根据可用的特征(如pclass、sex、age和fare price)来预测特定乘客是否能够在这次事故中生存。因此,弄清楚我们是否能够更好地了解那些幸存和死亡的乘客。
首先,绘制一个条形图,以查看每个类别(幸存/死亡)中的观察数量。
plt.figure(figsize=(6,4))
fig, ax = plt.subplots()
titanic_data.Survived.value_counts().plot(kind='barh', color="blue", alpha=.65)
ax.set_ylim(-1, len(titanic_data.Survived.value_counts()))
plt.title("Breakdown of survivals(0 = Died, 1 = Survived)")
输出结果如图2.9所示。
图2.9 生存状况
通过按性别细分图2.9,我们可以更好地理解数据。
fig = plt.figure(figsize=(18,6))
#Plotting gender based analysis for the survivals.
male = titanic_data.Survived[titanic_data.Sex == 'male'].value_counts().sort_index()
female = titanic_data.Survived[titanic_data.Sex == 'female'].value_counts().sort_index()
ax1 = fig.add_subplot(121)
male.plot(kind='barh',label='Male', alpha=0.55)
female.plot(kind='barh', color='#FA2379',label='Female', alpha=0.55)
plt.title("Gender analysis of survivals (raw value counts) ");
plt.legend(loc='best')
ax1.set_ylim(-1, 2)
ax2 = fig.add_subplot(122)
(male/float(male.sum())).plot(kind='barh',label='Male', alpha=0.55)
(female/float(female.sum())).plot(kind='barh',
color='#FA2379',label='Female', alpha=0.55)
plt.title("Gender analysis of survivals")plt.legend(loc='best')
ax2.set_ylim(-1, 2)
按类别细分后的数据如图2.10所示。
图2.10 按性别特征进一步细分“泰坦尼克号”数据
现在,我们有了关于两个可能类(生存和死亡)的更多信息。数据分析和可视化步骤是必要的,因为它可以让我们更深入地了解数据结构,并帮助我们为问题选择合适的学习算法。如你所见,我们从非常基本的图开始,然后增加图的复杂性以发现关于正在使用的数据的更多信息。
本节旨在预测幸存者,因此,结果将是生存或死亡,这是一个二元分类问题:其中只有两个可能的类。
有很多学习算法可以用于二元分类问题,逻辑回归就是其中之一。正如维基百科所解释的:
在统计学中,逻辑回归是基于一个或多个预测变量预测可分类因变量的结果的一种回归分析方法(因变量可以取有限数目的值,其大小是没有意义的,但大小的排序可能是有意义的,也可能是没有意义的)。也就是说,逻辑回归用于估计定性输出模型中参数的实际值。描述单个试验的可能结果的概率可以建模为一个关于解释性(预测)变量的逻辑函数。“逻辑回归”常用于专门指代因变量是二元的问题,也就是说,可用类别的数量是两个,而两个以上类别的问题称为多项逻辑回归;如果类别是有序的,则称为有序逻辑回归。逻辑回归的目的是预测分类因变量与一个或多个自变量之间的关系,这些变量通常(但不一定)是连续的,但是可以使用概率值作为因变量的预测值。因此,解决这一类问题的时候可以使用和概率回归计算相似的方法。
为了使用逻辑回归,我们需要创建一个公式,以告诉模型特征/输入的类型。
# model formula
# here the ~ sign is an = sign, and the features of our dataset
# are written as a formula to predict survived. The C() lets our
# regression know that those variables are categorical.
formula = 'Survived ~ C(Pclass) + C(Sex) + Age + SibSp + C(Embarked)'
# create a results dictionary to hold our regression results for easy
analysis later
results = {}
# create a regression friendly dataframe using patsy's dmatrices function
y,x = dmatrices(formula, data=titanic_data, return_type='dataframe')
# instantiate our model
model = sm.Logit(y,x)
# fit our model to the training data
res = model.fit()
# save the result for outputing predictions later
results['Logit'] = [res, formula]
res.summary()
Output:
Optimization terminated successfully.
Current function value: 0.444388
Iterations 6
输出结果如图2.11所示。
图2.11 逻辑回归的结果
下面绘制模型与实际模型的预测值与残差(即目标变量的实际值与预测值之间的差值)。
# Plot Predictions Vs Actual
plt.figure(figsize=(18,4))
plt.subplot(121, axisbg="#DBDBDB")
# generate predictions from our fitted model
ypred = res.predict(x)
plt.plot(x.index, ypred, 'bo', x.index, y, 'mo', alpha=.25)
plt.grid(color='white', linestyle='dashed')
plt.title('Logit predictions, Blue: \nFitted/predicted values: Red')
# Residuals
ax2 = plt.subplot(122, axisbg="#DBDBDB")
plt.plot(res.resid_dev, 'r-')
plt.grid(color='white', linestyle='dashed')
ax2.set_xlim(-1, len(res.resid_dev))
plt.title('Logit Residuals')
绘制结果如图2.12所示。
图2.12 更深入地了解逻辑回归模型
现在,我们已经构建了逻辑回归模型,在此之前,我们已经对数据集进行了一些分析和探讨。前面的例子显示了构建机器学习解决方案的一般流程。
很多时候,从业者会陷入一些技术陷阱,因为他们缺乏对机器学习概念的理解。例如,有人可能在测试集中获得99%的准确性,然后在没有对数据中类的分布进行调查(例如有多少样本是负数以及有多少样本是正数)的情况下就部署了模型。
为了突出其中的一些概念,并区分我们需要注意的以及那些应该真正关心的误差,下面将介绍误差分析。
在机器学习中,有两种类型的误差,作为数据科学的新手,我们需要了解它们之间的关键差异。如果最终将误差类型的损失函数最小化,那么整个学习系统将是无用的,我们将无法在实践中在未见过的数据上使用它。为了尽量减少从业者对这两类误差的这种误解,以下两节将对它们进行解释。
表现误差是我们面临的第一种类型的误差。通过训练得到此类误差的较小值并不意味着读者的模型将在未知数据上会更好地分类(泛化)。为了更好地理解这种类型的误差,这里将给出一个有关分类问题的简单例子。在课堂上解决问题的目的不是要在考试中再次解决同样的问题,而是为了能够解决其他问题,这些问题不一定与我们在课堂上解决的问题类似。考试题目可能来自同一系列的课堂练习,但不一定完全相同。
表现误差是训练模型在真实结果/输出已知的训练集上表现的能力。如果设法在训练集上实现零错误,那么对读者来说,这表明模型(大部分情况下)对于之前没见过的数据将不能很好地分类(不会泛化),另一方面,在数据科学中将训练集作为学习算法的基础知识,目的是很好地处理未来看不见的数据。
在图2.13中,上方曲线表示表现误差。每次增加模型记忆事物的能力(例如,通过增加解释性特征的数量来增加模型复杂性)时,我们都会发现这个表现误差将接近于零。可以证明,如果你有与样本一样多的特征,那么表现误差将为零。
图2.13 表现误差(粗黑曲线)和泛化/真实误差(浅灰色曲线)
泛化/真实误差是数据科学中的第二种误差类型,也是更重要的误差类型。构建学习系统的整体目的是能够在测试集上获得较小的泛化误差。换句话说,使模型在一组尚未在训练阶段使用的样本中很好地进行判断。如果我们仍然考虑2.5节中的课程场景,那么可以将泛化误差视为解决考试问题的能力,这些问题不一定与我们在校学习的学科中解决的问题类似。因此,泛化性能是为了正确预测未知数据的结果/输出模型使用在训练阶段学到的技能(参数)的能力。
在图2.13中,浅灰色曲线表示泛化误差。可以看到,随着模型复杂性的增加,泛化误差将会减小,直到模型失去降低泛化误差的能力,并且泛化误差不再减小。导致泛化误差的泛化能力不再增加的这一部分曲线,称为过拟合曲线。
本节的主旨是尽可能地减小泛化误差。
线性模型是一个非常强大的工具,如果我们的数据与其假设相匹配,可以将它用作初始学习算法。理解线性模型将帮助读者理解将线性模型作为基础构件的更为复杂的模型。
接下来,我们将继续使用“泰坦尼克号”示例更详细地解析模型的复杂性以及如何评估模型。模型复杂性是一个非常强大的工具,读者需要谨慎地操作它以增强模型的泛化能力,误用它会导致过度拟合问题。