书名:Python机器学习经典实例(第2版)
ISBN:978-7-115-55692-9
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 [意] 朱塞佩•查博罗(Giuseppe Ciaburro)
[美] 普拉蒂克•乔希(Prateek Joshi)
译 王海玲 李 昉
责任编辑 武晓燕
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
Copyright ©2019 Packt Publishing. First published in the English language under the title Python Machine Learning Cookbook,Second Edition.
All rights reserved.
本书由英国Packt Publishing公司授权人民邮电出版社有限公司出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
本书介绍了如何使用scikit-learn、TensorFlow等关键库来有效解决现实世界的机器学习问题。本书着重于实用的解决方案,提供多个案例,详细地讲解了如何使用Python生态系统中的现代库来构建功能强大的机器学习应用程序;还介绍了分类、聚类和推荐引擎等多种机器学习算法,以及如何将监督学习和无监督学习技术应用于实际问题;最后,介绍了强化学习、深度神经网络和自动机器学习等应用示例。
本书适合数据科学家、机器学习开发人员、深度学习爱好者以及希望使用机器学习技术和算法解决实际问题的Python程序员阅读。
朱塞佩•查博罗(Giuseppe Ciaburro)拥有环境技术物理学博士学位和两个学科的硕士学位,他的重点研究方向是机器学习在城市声环境研究中的应用,现在在意大利那不勒斯第二大学的建筑环境控制实验室工作。朱塞佩• 查博罗有超过15年的编程专业经验(Python、R、MATLAB),最初从事燃烧学领域的研究,后又致力于声学和噪音控制方向,并出版过几本著作,销量均不错。
普拉蒂克• 乔希(Prateek Joshi)是一位人工智能专家,写过几本书,也是一位TEDx演讲者,曾位列福布斯30岁以下的30位精英榜单,并在美国消费者新闻与商业频道(CNBC)、TechCrunch、硅谷商业期刊(Silicon Valley Business Journal)及更多的刊物上发表过文章。普拉蒂克•乔希是Pluto AI公司(一家由风投资助的致力于搭建水资源智能管理平台的硅谷初创企业)的创始人。普拉蒂克•乔希毕业于南加州大学,拥有人工智能硕士学位,曾在NVIDIA和微软研究院工作过。
格雷格• 沃尔特斯(Greg Walters)从1972年起就从事计算机编程工作,他精通 Visual Basic、Visual Basic.NET、Python和SQL语言,使用过的技术有MySQL、SQLite、Microsoft SQL Server、Oracle、C++、Delphi、Modula-2、Pascal、C、80x86汇编、COBOL和Fortran等。
格雷格•沃尔特斯现在是一位培训讲师,培训出很多计算机软件方面的人才,他培训过的课程有MySQL、Open Database Connectivity、Quattro Pro、Corel Draw、Paradox、Microsoft Word、Excel、DOS、Windows 3.11、Windows工作组、Windows 95、Windows NT、Windows 2000、Windows XP以及Linux等。
格雷格•沃尔特斯现在已经退休了。他酷爱音乐,热爱烹饪,并乐于以自由职业者的身份参加各种项目。
王海玲,毕业于吉林大学计算机系,从小喜爱数学,曾获得华罗庚数学竞赛全国二等奖;拥有世界500强企业多年研发经验;作为项目骨干成员,参与过美国惠普实验室机器学习项目。
李昉,毕业于东北大学自动化系,大学期间曾获得“挑战杯”全国一等奖;拥有惠普、文思海辉等世界500强企业多年研发经验,随后加入互联网创业公司;2013年开始带领研发团队将大数据分析运用于“预订电商”价格分析预测(《IT经理世界》2013年第6期);现在中体彩彩票运营管理有限公司负责大数据和机器学习方面的研发;同时是集智俱乐部成员,参与翻译人工智能图书Deep Thinking。
本书是读者热切期盼的《Python机器学习经典实例》一书的第2版。这本书让我们可以采用最新的方法来处理现实世界的机器学习和深度学习任务。
读者将在多个实例的帮助下,学习使用Python生态圈内的各种工具库来构建强大的深度学习应用。本书还将基于实例演示方法,指导读者如何实现分类、聚类和推荐引擎等多种机器学习算法。本书注重实际解决方案,书中的各章将指导读者把监督学习和无监督学习技术应用到真实问题中。本书最后几章,还会讲解几个用于演示最新技术的实例,最新技术包括强化学习、深度神经网络和自动机器学习等。
阅读完本书,读者已通过真实的例子了解了应用机器学习技术所需的各种技巧,并可以利用Python生态圈内各工具的强大功能来解决实际问题。
本书是为数据科学家、机器学习开发人员、深度学习爱好者以及希望使用机器学习技术和算法解决实际问题的Python程序员准备的。如果你正面对工作上的一些挑战,并希望采用已经写好的代码方案来实现机器学习和深度学习领域的关键任务,那么本书非常适合你。
第1章“监督学习”,介绍了各种机器学习范式,这将有助于你理解机器学习领域内各子域的划分。这一章简要介绍了监督学习和无监督学习的不同,以及回归、分类、聚类等相关概念,同时也介绍了预处理机器学习时所需的数据,并详细探讨了回归分析技术,讲解了如何将其应用于几个现实问题,包括房屋价格估算和共享单车需求分布评估等。
第2章“构建分类器”,展示了如何使用不同的模型对数据进行分类。在这一章,读者将学习包括逻辑回归和朴素贝叶斯模型在内的多项技术,并了解如何评估分类算法的准确度。本章还将介绍交叉验证的概念,如何使用交叉验证方法来验证机器学习模型,以及什么是验证曲线和如何进行绘制,最后介绍如何把这些监督学习技术应用到真实问题上,如收入阶层评估和热门话题分类等。
第3章“预测建模”,讲述了预测建模的前提和这么做的必要性。这一章将会介绍支持向量机及其工作原理,以及如何使用支持向量机对数据分类,也会介绍超参数的概念以及它们对支持向量机性能的影响,并讲解如何使用网格搜索来找出最优的超参数设置。本章还会讨论如何估计结果的置信度。
第4章“无监督学习——聚类”,涵盖了无监督学习的概念和应用。这一章将介绍如何执行数据聚类,如何将k-means算法应用于数据聚类,还将讲解样本数据可视化聚类的过程,并讨论高斯模型。最后介绍如何应用这些技术,以利用客户信息做市场细分。
第5章“可视化数据”,讲解如何可视化数据,以及可视化数据对机器学习的重要性。这一章将学习如何使用Matplotlib和数据进行交互,并使用不同的技术可视化数据。本章还将探讨直方图及其用处,并探索不同的可视化数据的方法,包括线形图、散点图和气泡图,以及热力图和3D图等。
第6章“构建推荐引擎”,介绍了推荐引擎,并展示了如何用推荐引擎进行电影推荐。本章将构建一个k近邻分类器来找出数据集中的相似用户,然后用TensorFlow的过滤器模型生成电影推荐。
第7章“文本数据分析”,学习如何分析文本数据。这一章将介绍很多概念,如词袋模型、标记(tokenization)解析和词干提取等,以及可以从文本数据中提取的特征。本章还将探讨如何构建文本分类器,然后使用这些技术来推断句子的情感。
第8章“语音识别”,演示了如何分析音频数据。这一章将介绍如何从音频数据中提取特征,什么是隐马尔可夫模型,以及如何用它们自动识别出语音中的单词。
第9章“时序列化和时序数据分析”,介绍了时序数据的不同特征,什么是条件随机场,以及如何将其用于预测,以及如何将这种技术用于分析股市数据。
第10章“图像内容分析”,将讲解如何分析图像,如何从图像中检测关键点和提取特征。本章还将介绍如何构建视觉码本并从图像分类中提取特征变量,以及如何使用极端随机森林进行对象识别。
第11章“生物特征人脸识别”,演示了如何进行人脸识别。这一章将学习面部检测和面部识别的不同,以及如何利用降维和主成分分析(PCA)技术达成目标,还介绍如何对网络摄像头拍摄的图像进行人脸检测,然后应用这些技术来识别镜头中的人。
第12章“强化学习”,介绍了强化学习技术及相关应用,包括强化学习设置的要素、强化学习方法、存在的挑战,以及相关的主题,如马尔可夫决策过程、Q学习等。
第13章“深度神经网络”,介绍感知机及其在构建神经网络方面的应用,并讨论深度神经网络各层之间的连接,以及神经网络如何通过训练数据进行学习并构建模型,还会介绍损失函数和反向传播的概念,之后会使用这些技术进行光学字符识别。
第14章“无监督表示学习”,探讨以无监督学习的方式对图像、视频、自然语言语料等数据进行表示学习的问题。这一章会介绍自动编码器及其相关应用,以及词嵌入和t-SNE算法等,也会介绍如何使用降噪编码器检测使用词嵌入的欺诈交易,最后,将利用所学过的知识实现LDA。
第15章“自动机器学习与迁移学习”,讨论了基于自动机器学习和迁移学习的技术。这一章将讲解如何使用Auto-WEKA和AutoML生成机器学习管道,并介绍Auto-Keras,以及如何使用MLBox进行泄漏检测。另外,本章还会介绍如何利用所学的知识实现迁移学习。
第16章“生产中的应用”,讨论了生产环境相关的问题。这一章会为读者介绍如何处理非结构化数据,以及如何在机器学习模型中跟踪变化,还会介绍如何优化模型,以及如何部署机器学习模型。
熟悉Python编程和机器学习概念,将有助于阅读本书。
可以用你的账号从Packt官网上下载本书的示例代码。如果你是从其他途径购买的本书,可以访问Packt官网并注册,我们将通过电子邮件把代码发送给你。
通过以下步骤下载示例代码文件:
1.在Packt官网上登录或注册账号;
2.选择“SUPPORT”标签;
3.单击“Code Downloads & Errata”;
4.在“Search”文本框中输入书的名字,然后按照屏幕指示下载代码。
文件下载完成后,请确保使用下列软件的最新版本来解压文件:
本书还提供了一个包含书中图片和表格等彩色图片的PDF文件,可以在异步社区上进行下载。
本书使用了几种不同的文本样式。
CodeInText
:表示文中的代码、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟URL、用户输入和推特句柄等,例如“我们将使用本书给出的simple_classifier.py
文件。”
代码块使用以下格式:
import numpy as np
import matplotlib.pyplot as plt
X = np.array([[3,1], [2,5], [1,8], [6,4], [5,2], [3,5], [4,7],
[4,-1]])
当希望读者特别注意代码块中的某一部分时,会把相关代码行粗体显示:
[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
**exten => s,102,Voicemail(b100)**
exten => i,1,Voicemail(s0)
命令行的输入或输出格式如下:
data = np.array([[3, -1.5, 2, -5.4], [0, 4, -0.3, 2.1], [1, 3.3, -1.9,
-4.3]])
表示警告或重要笔记。
表示提示或诀窍。
在本书中,读者将会频繁地看到这些标题:准备工作、详细步骤、工作原理和更多内容。
为了指导读者更好地完成各章节的学习,请先阅读下面的说明。
这部分介绍了当前小节涵盖的内容,如何设置软件、必需的准备工作。
这部分介绍具体的操作步骤。
这部分通常包含了对前一部分的深入解释。
这部分会补充一些信息,帮助读者更好地理解前面所学的内容。
本书由异步社区出品,社区(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、测试、前端、网络技术等。
异步社区
微信服务号
本章将涵盖以下内容:
在本书的学习过程中,你将会用到许多Python程序包,如NumPy、SciPy、scikit-learn和Matplotlib等,来处理多种任务。如果你使用的是Windows系统,推荐安装兼容SciPy及其关联程序包的Python发行版本。这些发行版已经集成了所有必需的程序包。如果你使用的是macOS或Ubuntu系统,安装这些程序包就相当简单了,详情参见相应官网。
在继续学习之前,请确保你已经在机器上安装好上述程序包。为了简单快捷,我们将分别在各小节对用到的函数进行详细解释。
机器学习这一研究领域交叉协同了包括计算机科学、统计学、神经生物学和控制理论等在内的多个学科,它在很多领域发挥了重要作用,并从根本上改变了软件编程的思路。对于人类,或更普遍的,对于每种生物,学习是通过经验对某种环境系统进行适应的过程,而这种适应过程必须使得在没有人类介入的情况下系统可以获得改善。为了达成目标,系统必须具备学习能力,即,对于给定的问题,可以通过一系列与之关联的样例抽取出有用信息。
如果你熟悉机器学习的基础知识,那么肯定知道什么是监督学习。不妨快速地复习下,监督学习(supervised learning)就是指基于有标签的样本数据来构建机器学习模型。监督学习算法将生成一个函数,该函数通过一组有标签的样本将输入值连接到期望的输出,其中每个输入数据都有其相对应的输出数据。监督学习算法用于构建预测模型,例如,如果要构建一个可以基于多个参数如面积、位置等来估算房屋价格的系统,首先要创建一个数据库并进行标记,算法需要了解不同参数和价格的对应关系,而基于已标记的数据,算法就可以学习利用输入参数计算出房屋价格。
无监督学习(unsupervised learning)则完全相反,它使用的是没有标签的数据。无监督学习算法在没有用于构建描述性模型的预分类样本数据集的帮助下,尝试通过普通输入获取知识。假设有一组数据点,我们只想将它们划分到多个组中,而并不了解划分的确切规则,这时,无监督学习算法就会尝试以最可能的方式将给定数据集划分到固定数量的组中。我们将在后续章节探讨无监督学习。
接下来的各节将介绍多种数据处理技术。
数组是很多编程语言的基本元素。作为有序的数据对象,它和列表非常相似,只是所包含元素的类型受到了约束。创建数组对象时,会使用名为类型代码的字符声明元素类型。
本节将介绍数组的创建过程。首先用NumPy包创建一个数组,然后再来看下它的结构。
在Python中创建数组的方法如下:
1.开始前,先导入NumPy包:
>> import numpy as np
这里刚刚导入的是一个必需的包,它是使用Python进行科学计算的基础程序库,包含了很多模块,其中几项如下:
除了这些常见功能,NumPy也作为通用数据的多维容器使用,其中可以包含任意类型的数据。这使得NumPy可以与不同类型的数据库集成。
需要注意的是,导入Python初始发布中不包含的库时,需要使用pip install命令,后面跟上软件包的名字。这个命令只使用一次即可,不需要每次运行代码时都执行它。
2.现在创建一些样本数据,把以下代码输入到Python终端:
>> data = np.array([[3, -1.5, 2, -5.4], [0, 4, -0.3, 2.1], [1, 3.3,
-1.9, -4.3]])
上面的np.array()
函数创建了一个NumPy数组。NumPy数组是类型相同的数据网格,由非负整数元组索引。秩(rank)和形状(shape)是NumPy数组的基本特征。rank
变量表示数组的维度,称为数组的秩;shape
变量是一个整型元组,它返回的是数组每个维度上的大小。
3.下面把新创建的数组显示出来:
>> print(data)
返回的结果如下:
[[ 3. -1.5 2. -5.4]
[ 0. 4. -0.3 2.1]
[ 1. 3.3 -1.9 -4.3]]
现在可以对数据进行操作了。
NumPy是Python环境中的一个扩展包,是科学计算的基础。这是因为它为已经可用的工具增加了N维数组的典型特征,逐元运算,大量的线性代数数学运算,以及集成和调用C、C++和FORTRAN源代码的能力。本节介绍如何使用NumPy库创建数组。
NumPy提供了创建数组的多个工具,例如,可以使用arange()
函数创建一个0~10的一维等差数组,代码如下:
>> NpArray1 = np.arange(10)
>> print(NpArray1)
返回的结果如下:
[0 1 2 3 4 5 6 7 8 9]
如果要创建一个10到100,步长为5(相邻值以预设步长递进)的数值数组,可以使用下面的代码:
>> NpArray2 = np.arange(10, 100, 5)
>> print(NpArray2)
数组打印的结果如下:
[10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95]
同样,若要创建一个在限定的数值范围内,包含50个元素的等差数组,可以使用linspace()
函数:
>> NpArray3 = np.linspace(0, 10, 50)
>> print(NpArray3)
打印出的结果如下:
[ 0. 0.20408163 0.40816327 0.6122449 0.81632653 1.02040816
1.2244898 1.42857143 1.63265306 1.83673469 2.04081633 2.24489796
2.44897959 2.65306122 2.85714286 3.06122449 3.26530612 3.46938776
3.67346939 3.87755102 4.08163265 4.28571429 4.48979592 4.69387755
4.89795918 5.10204082 5.30612245 5.51020408 5.71428571 5.91836735
6.12244898 6.32653061 6.53061224 6.73469388 6.93877551 7.14285714
7.34693878 7.55102041 7.75510204 7.95918367 8.16326531 8.36734694
8.57142857 8.7755102 8.97959184 9.18367347 9.3877551 9.59183673
9.79591837 10. ]
这只是使用NumPy的一些简单例子,接下来的章节将会更深入地探讨这个主题。
在现实世界中,我们要处理很多原始数据,但机器学习算法并不能很好地对这些原始数据进行处理,因而在为不同算法提供用于机器学习的数据之前,需要先进行预处理。这是一个非常耗时的过程,在某些场景中,甚至会耗费掉整个数据分析流程80%的时间。然而,由于它对后面的数据分析流程至关重要,所以了解数据预处理技术的最佳实践就很有必要。在把数据发送给任何机器学习算法前,需要交叉验证数据质量和精确度。如果不能正确地访问Python中存储的数据,或者不能将原始数据转换成可以分析的数据,我们就无法继续。数据预处理方式有很多种,比如标准化、数据缩放、归一化、二值化和one-hot编码等,接下来将通过简单的实例对这些数据预处理技术进行讲解。
标准化(standardization)或均值移除(mean removal)是一种中心化数据的技术,它先简单减去特征的平均值,再除以一个非常数(non-constant)的标准差以进行数据缩放。通常,从特征中减去均值可以使得数据集以0为中心,从而消除特征间的偏差。使用的公式如下:
经过标准化处理的数据对特征值进行了数据缩放,标准化后的数据服从正态分布:
公式中的mean表示均值,sd表示通过均值计算出的标准差。
Python中预处理数据的方法如下。
1.首先导入库:
>> from sklearn import preprocessing
sklearn是Python编程语言中免费的机器学习库,它支持很多分类、回归和聚类算法,包括支持向量机、随机森林、梯度提升算法、k均值以及DBSCAN等,并支持Python的数值处理工具包NumPy和科学处理工具包SciPy之间的互操作。
2.为了理解均值移除后的数据,先来看看之前创建的向量的均值和标准差:
>> print("Mean: ",data.mean(axis=0))
>> print("Standard Deviation: ",data.std(axis=0))
mean()
函数返回样本数据的算术平均数,返回值类型可能是序列或迭代器。std()
函数返回数据的标准差,标准差用于统计数组元素的分布情况。axis
参数声明了这些函数计算所作用的数据轴(0
表示列,1
表示行)。返回的结果如下:
Mean: [ 1.33333333 1.93333333 -0.06666667 -2.53333333]
Standard Deviation: [1.24721913 2.44449495 1.60069429 3.30689515]
3.下面继续标准化操作:
>> data_standardized = preprocessing.scale(data)
preprocessing.scale()
函数对数据集上的所有数据轴进行标准化操作,标准化后的数据集以均值为中心点,并调整大小得到单位方差。
4.现在重新计算标准化处理后的数据均值和标准差:
>> print("Mean standardized data: ",data_standardized.mean(axis=0))
>> print("Standard Deviation standardized data:
",data_standardized.std(axis=0))
返回的结果如下:
Mean standardized data: [ 5.55111512e-17 -1.11022302e-16
-7.40148683e-17 -7.40148683e-17]
Standard Deviation standardized data: [1. 1. 1. 1.]
可以看到均值几乎等于0,标准差为1。
sklearn的preprocessing包包含了几个常见的工具函数,以及可以修改我们需要的表示中的可用特征以使其适用于需求的转换类。本节用到的是scale()
函数(z-score标准化)。简要来说,z-score(z分数,也叫标准分数)表示观测点的值或数据高于被观测值或测量值的平均值的标准偏差数。高于均值的值其z分数为正,低于均值的值其z分数为负。z分数是没有量纲的数值,它通过将各分值减去样本均值得到离差,再将离差除以样本的标准差计算得出。
有时我们并不了解数据分布中的最小值和最大值,这种情况无法使用其他类型的数据转换,这时标准化方法就非常有用了。数据转换后,归一化的数据中没有最小值和固定的最大值,并且,标准化技术不会被极值影响,或至少不像其他方法那样被影响。
数据集中每个特征的数据值变化范围可能很大,因此,有时将特征的数据范围缩放到合理的大小是非常重要的。经过统计处理后,就可以对来自不同分布和不同变量的数据进行比较。
训练机器学习算法前对数据范围进行缩放是一个最佳实践。数据缩放消除了单位差异,就可以对不同来源的数据进行对比了。
这里将使用min-max方法[通常称为特征缩放(feature scaling)]来获取[0,1]区间范围内的缩放数据,使用的公式如下:
在给定的最大最小值范围内对特征进行缩放(本例是0和1之间)时,每个特征的最大值都被缩放成单位值,这里用到的函数是preprocessing.MinMaxScaler()
。
在Python中缩放数据的方法如下。
1.首先定义变量data_scaler
:
>> data_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
2.接下来调用方法fit_transform()
,拟合数据并进行转换(此处使用和1.4节相同的数据):
>> data_scaled = data_scaler.fit_transform(data)
这里将返回一个特定形状的NumPy数组。为了了解函数是如何转换数据的,我们显示出数组中每列的最大值和最小值。
3.先打印出原始数据,然后再打印处理后的数据:
>> print("Min: ",data.min(axis=0))
>> print("Max: ",data.max(axis=0))
返回的结果如下:
Min: [ 0. -1.5 -1.9 -5.4]
Max: [3. 4. 2. 2.1]
4.现在用下面的代码对缩放后的数据进行相同的操作:
>> print("Min: ",data_scaled.min(axis=0))
>> print("Max: ",data_scaled.max(axis=0))
返回的结果为:
Min: [0. 0. 0. 0.]
Max: [1. 1. 1. 1.]
进行缩放后,所有的特征值都位于指定范围内。
5.下面的代码显示出缩放后的数组:
>> print(data_scaled)
输出如下:
[[ 1. 0. 1. 0. ]
[ 0. 1. 0.41025641 1. ]
[ 0.33333333 0.87272727 0. 0.14666667]]
现在,所有数据都处于相同的区间。
数据具有不同的值域时,相对于较小的数值区间,较大的数值区间施加给因变量的影响可能更大,从而影响模型预测的准确率。我们的目标是提高预测的准确率并避免上述情况的发生,因而,可能需要对不同特征的数据值进行缩放,以使其具有相近的区间范围。通过这一统计过程,可以对来自不同分布、不同变量或单位不同的变量的数据进行比较。
特征缩放把数据值集合限定在了指定区间,这确保了所有的特征都具有相同尺度,但是它并不能很好地处理异常值,因为异常值缩放后,变成了具有新的变化范围的异常值。在这种情况下,保留了异常值的实际值就受到了压缩。
当需要调整特征向量的值,以使其可以在一个更常用的尺度上测量时,就可以使用数据归一化技术。机器学习中最常用的归一化形式是通过调整特征向量的值,让其数值之和为1。
可调用函数preprocessing.normalize()
进行归一化操作,该函数将各输入变量缩放成单位范数(向量长度)。它支持3种类型的范数:L1范数、L2范数和max范数,这点我们稍后解释。如果向量x是长度为n的协变量,那么归一化后的向量是y=x/z,其中z的定义如下:
范数是一个函数,它为属于一个向量空间的每个非0向量分配一个正长度,0除外。
Python中归一化数据的方法如下。
1.如前所述,归一化操作可调用函数preprocessing.normalize()
(此处使用和前一节相同的数据):
>> data_normalized = preprocessing.normalize(data, norm='l1',
axis=0)
2.下面的代码用于显示归一化后的数组:
>> print(data_normalized)
返回的结果如下:
[[ 0.75 -0.17045455 0.47619048 -0.45762712]
[ 0. 0.45454545 -0.07142857 0.1779661 ]
[ 0.25 0.375 -0.45238095 -0.36440678]]
这个方法经常用于确保数据集不会因为特征的基本性质而被人为地增强。
3.如前所述,归一化的数组各列(特征)总和必须为1,我们对每列做个校验:
>> data_norm_abs = np.abs(data_normalized)
>> print(data_norm_abs.sum(axis=0))
第一行代码使用函数np.abs()
计算数组各元素的绝对值,第二行代码使用sum()
函数对每列(axis=0
)求和,返回的结果如下:
[1. 1. 1. 1.]
可以看出,各列元素的绝对值之和为1,数据成功归一化。
在本节中,我们把数据归一化为单位范数。每个带有至少一个非零元素的样本,都独立于其他样本被重新缩放,因而其范数为1。
在文本分类和聚类问题中,把输入数据缩放成单位范数是一个非常常见的任务。
二值化用于将数值型特征向量转换为布尔型向量。在数字图像处理领域,图像二值化是把彩色或灰度图像转换成二值图像的处理过程,即,转换成只有两种颜色的图像,比如最典型的,黑色和白色。
二值化技术用于识别对象、形状,特别是字符,它可以把感兴趣的对象从其背景中区分出来。而骨架化是对对象的基本要素和轮廓的表示,这通常是进行后续识别过程的开始。
Python中二值化数据的方法如下。
1.调用函数preprocessing.Binarizer()
,对数据进行二值化处理(这里使用和前一节相同的数据):
>> data_binarized =
preprocessing.Binarizer(threshold=1.4).transform(data)
函数preprocessing.Binarizer()
根据阈值参数threshold
对数据进行二值化处理,大于这个参数的值映射为1,小于等于这个参数的值映射为0。当threshold
采用默认值0时,只有正值映射为1。本例中,threshold
参数的值为1.4
,因而大于1.4
的值映射为1,小于等于1.4
的值则映射为0。
2.使用下面的代码显示出二值化处理后的数组:
>> print(data_binarized)
返回的结果如下:
[[ 1. 0. 1. 0.]
[ 0. 1. 0. 1.]
[ 0. 1. 0. 0.]]
这是一种非常有用的技术,通常在我们对数据有一定的先验知识时使用。
本节对数据进行了二值化处理,这一技术的基本思想是设定一条固定的分界线,因而问题就变成了通过选取合适的阈值来判定图像中光强低于该值的像素点都属于背景,高于该值的像素点都属于特定对象。
二值化方法在计数数据上广泛使用,分析员可以决定是否只考虑字符的出现与否,而不必关心字符出现的频次。另外,二值化方法可以在只考虑随机布尔变量的估计器中用于数据的预处理。
我们经常处理遍布整个向量空间的稀疏型数值数据,但并不需要真正存储这些数据的值,这时one-hot编码就有用武之地了。我们可以把one-hot编码想象成收紧特征向量的工具,在遍历每个特征的不同值并记录其不同值的个数后,使用one-of-k的方案来编码,特征向量中的每个值都基于这一方案进行编码,这使得向量空间的处理更加高效。
假设我们正在处理一个具有4个特征维度的向量,为了给向量中的某个特征nth进行编码,我们先遍历并记录nth特征的不同取值的个数。如果共有k个不同取值,那么特征会被转换成一个k维向量,其中只有一个元素的值为1,而其他值全部为0。下面看个简单的例子来帮助理解。
Python对数据进行one-hot编码的方法如下。
1.来看一个4行(向量)3列(特征)的数组:
>> data = np.array([[1, 1, 2], [0, 2, 3], [1, 0, 1], [0, 1, 0]])
>> print(data)
数组打印出来的结果如下:
[[1 1 2]
[0 2 3]
[1 0 1]
[0 1 0]]
下面来分析每列(特征)的值,具体如下。
因而每个特征的不同取值加总后的总计可能取值为:2+3+4=9种,即,想唯一表示每个向量,总共需要9个索引位置,这3个特征可以如下表示。
2.为了把表示类别的整型值特征表示成one-hot数值数组,可以调用函数preprocessing.OneHotEncoder()
:
>> encoder = preprocessing.OneHotEncoder()
>> encoder.fit(data)
第一行代码设置要使用的编码器,然后调用fit()
函数对OneHotEncoder
对象和数据数组进行拟合。
3.接下来可以使用one-hot编码来转换数组数据了,这里使用transform()
函数:
>> encoded_vector = encoder.transform([[1, 2, 3]]).toarray()
如果打印出encoded_vector
,预期的输出如下:
[[0. 1. 0. 0. 1. 0. 0. 0. 1.]]
结果很清楚,第一个特征的值为1,one-hot编码为01,输出数组中的索引为1;第二个特征的值为2,one-hot编码为001,输出数组中的索引为4;第三个特征的值为3,ont-hot编码为0001, 输出数组中的索引为8。可以验证,只有这些索引位置的值为1,而其他索引位置的值均为0。需要注意的是,Python中的索引是从0开始的,所以9个索引位置的索引为0到8。
preprocessing.OneHotEncoder()
函数把表示类别的整型特征编码成one-hot数值数组,它从表示了分类或离散特征假定的值的数字或字符串数组开始,使用one-hot编码方案对特征编码,并返回哑变量(dummy variable,又称虚拟变量)。编码后,函数为每个类别创建了一个二值列,并返回一个稀疏或稠密数组。
有时我们必须对类别数据进行转换,因为实际上很多机器学习算法并不能直接对类别数据进行处理。在算法使用这些类别数据前必须首先将其转换成数值型数据,输入和输出变量皆是如此。
在监督学习中,我们经常会处理各种各样的标签,这些标签可能是数字,也可能是文字。如果是数字,算法可以直接使用这些数据。但标签往往需要以人类可理解的形式存在,因而通常我们会用文字给训练数据打标签。
标签编码指的是将文字标签转换成数值形式,这样算法才能理解标签并进行操作。下面来看一下详细的步骤。
Python中对数据进行标编码的方法如下。
1.创建一个新的Python文件,并导入preprocessing包:
>> from sklearn import preprocessing
2.这个程序库包含了数据预处理所需的多个函数。对一个具有0到n_classes-1个取值的标签进行编码,可以调用函数preprocessing.LabelEncoder()
。下面的代码定义了标签编码器:
>> label_encoder = preprocessing.LabelEncoder()
3.label_encoder
对象知道如何理解文字标签。先来创建一些标签:
>> input_classes = ['audi', 'ford', 'audi', 'toyota', 'ford',
'bmw']
4.现在对这些标签进行编码。首先,调用fit()
函数来拟合标签编码器,然后打印出拟合结果:
>> label_encoder.fit(input_classes)
>> print("Class mapping: ")
>> for i, item in enumerate(label_encoder.classes_):
... print(item, "-->", i)
5.运行上面的代码,终端显示如下:
Class mapping:
audi --> 0
bmw --> 1
ford --> 2
toyota --> 3
6.由上面的输出可以看出,文字标签被转换成了从0开始的索引。现在,可以对标签数据集进行转换了,请看下面的示例代码:
>> labels = ['toyota', 'ford', 'audi']
>> encoded_labels = label_encoder.transform(labels)
>> print("Labels =", labels)
>> print("Encoded labels =", list(encoded_labels))
终端显示的输出如下:
Labels = ['toyota', 'ford', 'audi']
Encoded labels = [3, 2, 0]
7.这种方式比手动维护文字和数字间的映射关系更加简单方便。下面可以通过把数字转换回文字标签来验证转换的正确性:
>> encoded_labels = [2, 1, 0, 3, 1]
>> decoded_labels = label_encoder.inverse_transform(encoded_labels)
>> print("Encoded labels =", encoded_labels)
>> print("Decoded labels =", list(decoded_labels))
这里调用了函数inverse_transform()
来把数字标签转换回原始编码,输出结果如下:
Encoded labels = [2, 1, 0, 3, 1]
Decoded labels = ['ford', 'bmw', 'audi', 'toyota', 'bmw']
可以看出,映射关系完全正确。
在本节中,我们使用了preprocessing.LabelEncoder()
函数把文字标签转换成了其数值形式,为此,我们首先尽量多地设置了一系列表示汽车品牌的标签,之后,把这些标签转换成数值序列,最后,通过打印出文字标签及其转换后的数值标签,验证了转换过程的正确性。
在这两节关于one-hot编码和标签编码的内容中,我们了解了如何转换数据。这两种方法都可以用于处理分类数据,那么它们的优缺点有哪些不同呢?
线性回归指的是利用输入变量间的线性组合来发现潜在的函数关系。前面的例子中包含一个输入变量和一个输出变量,这种简单的线性回归很容易理解,但却能表示出回归技术的本质。了解了这些概念后,解决其他类型的回归问题就变得容易些了。
示例参见图1-1。
图1-1
线性回归方法可以精确识别出二维平面中一条表示点分布的直线。就是说,如果与观测值对应的点靠近直线,那么所选择的模型就可以有效地描述出变量间的关联关系。
理论上讲,靠近观测值的直线可以有无数条,但实际上,最优化表示数据的数学模型只有一个。在一个线性的数学关系中,变量y的观测值可以经由线性函数中变量x的观测值计算得出。对于每次观测,可以使用下面的公式:
上面的公式中,x是解释型变量(又称自变量),y是被解释变量(又称因变量),α和β是参数,分别表示斜率和y轴的截距,这两个参数必须基于模型中收集到的变量的观测值进行估计。
斜率α非常重要,它表示了自变量每增加一个单位量因变量随之发生的平均变化程度。斜率是如何作用的呢?如果斜率为正,回归线由左向右递增;如果斜率为负,则回归线由左向右递减。当斜率为0时,自变量的变化不会引起因变量的变化。但并非只有参数α确定了变量间的权重关系,更准确地说,是参数α的值较为重要。斜率为正时,自变量的值越高,则因变量的值也越高;而斜率为负时,自变量的值越高,因变量的值就越低。
线性回归的主要目标是提取出输入变量和输出变量间潜在的线性关系模型,这就要求实际输出与线性方程预测输出的误差的平方和最小化,这种方法称为普通最小二乘法(Ordinary Least Square,OLS)。在普通最小二乘法中,系数通过最小化被解释变量的观测值和拟合值的误差平方和来估计,使用的方程式如下:
RSS表示了实验数据(xi, yi)和直线上对应点(即预测值)之间距离的平方和。
也许你会说,或许用曲线可以更好地拟合这些点,但线性模型是不能使用曲线进行拟合的。线性模型的主要优点是方程简单。如果用非线性回归,模型的准确度可能更高,但拟合速度却会慢很多。就像在图1-1中看到的,模型尝试使用直线来近似拟合输入的数据点。下面看看在Python中如何构建线性回归模型。
回归用于找出输入数据和连续值输出数据的关系。连续值数据通常表示成实数,回归的目标是估计可以计算出输入到输出的映射的核心函数。让我们从一个非常简单的例子开始,考虑下面输入和输出的映射关系:
1 --> 2
3 --> 6
4.3 --> 8.6
7.1 --> 14.2
如果问你输入和输出的关系,你可以很容易地分析出内在模式,可以看出每组数据中输出都是输入数据的两倍,所以转换方程式可以表示成:
这是个很简单的函数,把输入值和输出值关联起来。然而现实世界并非如此,实际问题的函数并不会这么显而易见。
假设你有一个各行用逗号分隔的数据文件VehiclesItaly.txt
,其中第一个元素是输入值,第二个元素是对应这个输入的输出值,我们的目标是找出各个地区的车辆注册数和人口数量之间的线性回归关系。你可以将这个文件作为输入参数。如你所料,变量Registrations
包含了在意大利各地区注册的车辆数,变量Population
则包含了不同地区的人口数。
Python中构建线性回归器的方法如下。
1.创建文件regressor.py
,并加入下面这几行代码:
filename = "VehiclesItaly.txt"
X = []
y = []
with open(filename, 'r') as f:
for line in f.readlines():
xt, yt = [float(i) for i in line.split(',')]
x.append(xt)
y.append(yt)
上面的代码把输入数据加载到变量X
和y
,其中X
是独立变量(解释型变量),y
是依赖变量(被解释变量)。循环代码部分对数据进行逐行解析,并根据逗号分隔符提取数据,然后将其转换成浮点值存入变量X
和y
。
2.构建机器学习模型时,需要一种方法来检查模型是否达到一定的满意度。为此,需要把数据划分成两组:训练数据集和测试数据集。训练数据集用于构建模型,测试数据集用于检验模型在未知数据上的表现。现在,先把数据集划分成训练数据集和测试数据集:
num_training = int(0.8 * len(X))
num_test = len(X) - num_training
import numpy as np
# 训练数据
X_train = np.array(X[:num_training]).reshape((num_training,1))
y_train = np.array(y[:num_training])
# 测试数据
X_test = np.array(X[num_training:]).reshape((num_test,1))
y_test = np.array(y[num_training:])
留出80%的数据作为训练数据集,剩余的20%用作测试数据集,然后,创建4个数组:X_train、X_test
、y_train
和y_test
。
3.可以训练模型了。下面创建一个回归器对象:
from sklearn import linear_model
# 创建线性回归对象
linear_regressor = linear_model.LinearRegression()
# 使用训练集训练模型
linear_regressor.fit(X_train, y_train)
首先从sklearn库中导入用于回归的方法linear_model
,其中预期的目标值是输入变量的线性组合;然后调用函数LinearRegression()
,执行普通最小二乘法线性回归;最后,调用fit()
函数拟合线性模型,这里传入了两个参数——训练数据X_train
和目标值y_train
。
4.现在已经基于训练数据训练了线性回归器,其中fit()
函数获取输入数据并对模型进行了训练。想了解数据如何拟合的,需要使用拟合出的模型在训练数据上进行预测:
y_train_pred = linear_regressor.predict(X_train)
5.现在使用库matplotlib绘制输出结果:
import matplotlib.pyplot as plt
plt.figure()
plt.scatter(X_train, y_train, color='green')
plt.plot(X_train, y_train_pred, color='black', linewidth=4)
plt.title('Training data')
plt.show()
在终端运行代码,结果如图1-2所示。
图1-2
6.前面的代码使用训练好的模型对训练数据进行了输出预测,但这不能说明模型在未知数据上的表现。由于我们是在训练数据上运行模型的,所以只能了解模型对训练数据的拟合效果。从图1-2来看,效果还不错。
7.现在在测试数据上运行模型并绘制出预测结果图,代码如下:
y_test_pred = linear_regressor.predict(X_test)
plt.figure()
plt.scatter(X_test, y_test, color='green')
plt.plot(X_test, y_test_pred, color='black', linewidth=4)
plt.title('Test data')
plt.show()
8.在终端运行上面的代码,返回的输出如图1-3所示。
图1-3
如你所料,各地区的人口数和车辆注册数之间成正关联关系。
本节调用了linear_model
中的LinearRegression()
函数,揭示了每个州的车辆注册数和这个州的人口数之间的线性回归关系。构建模型后,首先使用训练数据验证了模型对数据的拟合效果,然后使用测试数据验证了预测结果。
检验模拟效果的最佳方式就是使用特别的图形来展示输出结果。实际上在本节中已经使用了这项技术,前面曾绘制出带有回归线的散点分布图。第5章将介绍用于检验模型表现的其他种类的图形。
了解了如何构建一个回归器之后,下面最重要的就是如何评估回归器的泛化能力。在模型评价标准中,我们用误差(error)表示真实值和回归器预测值之间的差异。
先快速了解几个可用于衡量回归器泛化能力的指标。回归器可以用很多不同的指标来评估,而scikit-learn库支持指标的计算。sklearn.metrics
模块包含的指标有得分函数、性能指标、成对(pairwise)指标以及距离计算等。
Python中计算回归准确率的方法如下。
使用已有的函数来评估1.10节中创建的线性回归模型的性能:
import sklearn.metrics as sm
print("Mean absolute error =", round(sm.mean_absolute_error(y_test,
y_test_pred), 2))
print("Mean squared error =", round(sm.mean_squared_error(y_test,
y_test_pred), 2))
print("Median absolute error =",
round(sm.median_absolute_error(y_test, y_test_pred), 2))
print("Explain variance score =",
round(sm.explained_variance_score(y_test, y_test_pred), 2))
print("R2 score =", round(sm.r2_score(y_test, y_test_pred), 2))
返回的结果如下:
Mean absolute error = 241907.27
Mean squared error = 81974851872.13
Median absolute error = 240861.94
Explain variance score = 0.98
R2 score = 0.98
R2
得分几乎为1,说明模型对数据的预测效果非常好。逐一查看各个指标难免冗赘,这里仅挑选一两个指标来评估模型。最佳实践是使均方误差尽可能低,解释方差尽可能高。
回归器可以使用很多不同的指标进行评估,示例如下。
sklearn.metrics
模块包含了衡量预测误差的一系列函数:
_score
结尾的函数返回的是需要最大化的值,值越大表示模型的泛化能力越强。_error
或_loss
结尾的函数返回的是需要最小化的值,值越小表示模型的泛化能力越强。模型训练结束后,可以将其保存成文件,这样以后使用时只需简单加载进来就可以了。
下面看看如何保存模型。这里要用到用于保存Python对象的pickle
模块,该模块是Python安装的标准库的一部分。
Python中模型持久化的方法如下。
1.在regressor.py
文件中加入下面的代码:
import pickle
output_model_file = "3_model_linear_regr.pkl"
with open(output_model_file, 'wb') as f:
pickle.dump(linear_regressor, f)
2.回归器对象将被保存在文件saved_model.pkl
中,加载、使用此对象的方法如下:
with open(output_model_file, 'rb') as f:
model_linregr = pickle.load(f)
y_test_pred_new = model_linregr.predict(X_test)
print("New mean absolute error =",
round(sm.mean_absolute_error(y_test, y_test_pred_new), 2))
返回的结果如下:
New mean absolute error = 241907.27
这里,我们把回归器对象从文件加载到model_linregr
变量中。对比之前的结果,可以确认它们是完全相同的。
pickle
模块可将任意的Python对象转换成字节序列,这一过程又称为对象的序列化。可以发送或者存储表示对象的字节流,然后创建出具有相同特征的新对象,这一反向操作称为解包。
Python还可以使用marshal
模块进行序列化操作,通常,我们更推荐使用pickle
模块序列化Python对象。marshal
模块可用于支持以.pyc
结尾的Python文件。
线性回归的主要问题是它对异常值非常敏感,在实际收集数据的过程中,经常会碰到错误的度量结果,而线性回归使用了普通最小二乘法来确保平方误差最小化。异常值之所以更容易引发问题,是因为它们在全部误差中的占比很高,从而会破坏整个模型。
所谓异常值,是指相比其他数据值的极端值,或者说距离其他观察值较为偏远的值。异常值会影响数据分析的结果,更具体地说,会影响描述统计分析和相关性分析。我们需要在数据清洗阶段找出这些异常数据,但也可以在接下来的数据分析中找出并处理这些异常数据。异常值可以是只针对某个单一变量的单元异常值,也可以是由多个变量的值组合而成的多元异常值。请看图1-4。
图1-4
图1-4中右下角的两个点很明显是异常值,但模型却试图拟合所有的数据点,这就会导致整个模型的错误。异常值的特征是,和分布中的其他数据相比极其大或者极其小,这意味着相对其他数据分布的孤立情况。即使仅通过目测,也可以看出图1-5所示模型的拟合效果明显更好。
图1-5
普通最小二乘法在构建模型时会考虑每一个数据点,因而实际得到的模型就如图1-5中虚线所示。显而易见该模型不是最佳的。
正则化方法需要修改性能函数,性能函数通常选择训练集上回归误差的平方和。当变量较多时,相比较少的变量,线性模型的最小二乘估计经常有一个较小的偏差和一个较大的方差,这种情况下,就会出现过拟合的问题。为使用较大的偏差和较小的方差来改进预测精度,可以采用对变量进行筛选和降维的处理方法,但这些方法不仅计算量很大,而且还会增加解释难度。
另一个解决过拟合问题的途径是修改估计方法,忽略使用无偏参数估计的需要,转而考虑使用有偏估计器的可能,有偏估计方法可能会有更小的方差。现有的几种有偏估计器中,大多是基于正则化方法的,其中岭回归、Lasso回归和ElasticNet回归是最常使用的方法。
岭回归是对系数规模施加惩罚项的方法,在1.10节中已经讲过,在普通最小二乘法中,系数通过最小化观察到的因变量和拟合值之间的残差平方和来确定,依据的方程式如下:
为了估计β系数,岭回归在最基本的残差平方和公式基础上加入了惩罚项,我们把λ(≥0)定义为调解参数(又称正则化参数、岭参数),它和系数β的平方和(不包括截距)相乘就构成了惩罚项,方程式如下:
很明显,λ = 0意味着对模型不施加任何惩罚,得到的仍然是最小二乘解,而当岭参数λ趋向于更大或无穷大时,意味着对模型施加较大的惩罚,这会使很多系数的值更接近0值,但并不表示它们会被排除在模型外。下面看看在Python中如何构建岭回归器。
在Python中创建岭回归器的方法如下。
1.可以使用前面例子(构建线性回归器)中的数据(来自文件VehiclesItaly.txt
)。这个文件中的每行包含两个值,第一个值是解释变量,第二个值是被解释变量。
2.把下面几行代码加入文件regressor.py
中,现在用下面的参数初始化岭回归器:
from sklearn import linear_model
ridge_regressor = linear_model.Ridge(alpha=0.01,
fit_intercept=True, max_iter=10000)
3.参数alpha
用于控制复杂度,alpha
的值越接近0,岭回归器的表现越近似于使用普通最小二乘法的线性回归器,因此,若想让其对异常值具有良好的健壮性,就要为alpha
分配一个较大的值。这里我们用一个中等大小的值0.01。
4.下面来训练回归器:
ridge_regressor.fit(X_train, y_train)
y_test_pred_ridge = ridge_regressor.predict(X_test)
print( "Mean absolute error =",
round(sm.mean_absolute_error(y_test, y_test_pred_ridge), 2))
print( "Mean squared error =", round(sm.mean_squared_error(y_test,
y_test_pred_ridge), 2))
print( "Median absolute error =",
round(sm.median_absolute_error(y_test, y_test_pred_ridge), 2))
print( "Explain variance score =",
round(sm.explained_variance_score(y_test, y_test_pred_ridge), 2))
print( "R2 score =", round(sm.r2_score(y_test, y_test_pred_ridge), 2))
运行代码可以看到误差指标。你可以自己创建一个线性回归器,然后在相同的数据集上进行操作,并与引入了正则化方法的模型效果进行参照对比。
岭回归是对系数规模施加了惩罚的正则化方法,除了岭系数在计算时会减去一个数值外,它和最小二乘法是相同的。在岭回归中,缩放转换会有重大影响,因此,为了避免因预测使用的测量尺度不同而导致不同的结果,建议估计模型前对所有因子进行标准化处理。标准化变量的方法是先减去均值再除以它们的标准差。
线性回归模型有一个主要的局限,就是它只能使用线性函数对输入数据进行拟合,而多项式回归模型通过拟合多项式方程来克服这类问题,从而提高模型的准确性。
当因变量和多个自变量间的关系呈曲线时,就应使用多项式模型。有时,多项式模型也可用于对具有非线性关系的小范围内的解释变量进行建模。在一个线性回归模型中加入一个二次项或三次项,就可以转换成多项式曲线。然而,由于多项式是对解释变量做的平方或立方,而非β系数,仍然可以作为线性模型处理,因而曲线建模变得非常简单,并不需要创建庞大的非线性模型。请看图1-6。
图1-6
从数据点的分布模式可以看出,存在一条自然的曲线,而线性模型并不能捕捉到这种曲线关系。再来看看多项式模型的效果,如图1-7所示。
图1-7
图中虚线表示线性回归模型,实线表示多项式回归模型,多项式模型的曲率由多项式次数决定,随着模型曲率的增加,模型也愈加准确。然而,曲率也会增加模型的复杂度并使其拟合速度变得更慢。这就需要做出权衡了,在给定的计算能力限制下,你必须对期望的模型准确度做出决定。
在Python中构建多项式模型的方法如下。
1.在这个例子中,我们将仅处理二次抛物线回归。我们已测量出一天中几小时的温度,现在要知道的是一天中不同时间的温度趋势,即使是我们没有测量过温度的时间。当然,这些时间都是处于已测量过的时间范围之内的:
import numpy as np
Time = np.array([6, 8, 11, 14, 16, 18, 19])
Temp = np.array([4, 7, 10, 12, 11.5, 9, 7])
2.现在要显示出一天中几个点的温度值:
import matplotlib.pyplot as plt
plt.figure()
plt.plot(Time, Temp, 'bo')
plt.xlabel("Time")
plt.ylabel("Temp")
plt.title('Temperature versus time')
plt.show()
生成的图像如图1-8所示。
图1-8
对图1-8进行分析可知,通过类似下面这样的方程式表示的二次多项式,是可以对曲线形模式的数据进行建模的:
未知系数β0、β1和β2,可以通过减小平方和的值来进行估计,即最小化模型数据的离差至其最低的值(最小二乘拟合)。
3.下面计算多项式系数:
beta = np.polyfit(Time, Temp, 2)
函数numpy.polyfit()
返回最佳拟合数据的n(给定的)次多项式的系数,函数返回的系数按降次排序(最高次项的系数在前面),如果多项式是n次的,那么返回长度为n+1。
4.创建模型后,要对模型拟合数据的效果进行验证,要做到这一点,需要使用模型在均匀间隔的时间内估计多项式。如果要估计特定数据点的模型,可以使用poly1d()
函数,这个函数返回由我们提供的数据点估计的n次多项式的值。输入参数是长度为n+1的向量,它的元素就是要估计的以降次排序的n次多项式的系数:
p = np.poly1d(beta)
从后面的图可以看出,这和输出值很接近。如果想更加逼近,可以增加多项式的次数。
5.现在可以同时绘制出原始数据以及模型曲线:
xp = np.linspace(6, 19, 100)
plt.figure()
plt.plot(Time, Temp, 'bo', xp, p(xp), '-')
plt.show()
打印结果如图1-9所示。
图1-9
分析图1-9可以看出,曲线充分拟合了数据。相比简单的线性回归模型,多项式模型做出了更大程度的拟合。在回归分析中,让模型的阶数尽可能地低是非常重要的,在最先的分析中,模型是个一阶多项式,如果结果不理想,可以尝试使用二阶多项式。使用更高阶的多项式可能导致错误的估计。
当线性回归的拟合效果不理想时就应使用多项式回归。多项式回归模型使用曲线拟合数据,其中出现的因子的次数可能等于或大于2。当变量间的关系看起来是曲线时,通常会使用多项式模型。
到底要使用几次多项式?这取决于我们对精度的预期。多项式次数越高,模型的精度越高,但计算难度也越高。另外,有必要对发现的系数的重要性进行验证,让我们马上开始吧。
是时候学以致用了,现在就来把所有学到的理论应用到房屋价格估算的问题上吧,这是用于理解回归的最经典的案例之一。作为一个很好的切入点,这个案例不仅简单易懂,还与人们的生活紧密相关,因此在用机器学习处理更复杂的问题之前,可以通过房屋价格估算的例子更好地理解相关概念。这里将用使用了AdaBoost算法的决策树回归器来解决这个问题。
决策树是一个树状模型,其中的每个节点都表示一个简单决策,从而影响最终结果。叶子节点表示输出值,分支表示根据输入特征做出的中间决策。AdaBoost是指自适应增强(adaptive boosting)算法,这是一种利用其他系统提升模型准确度的技术,我们把这种技术称为弱学习器,它将不同版本的算法得到的结果组合起来,加权汇总成最终的结果。AdaBoost算法会把每个阶段收集到的信息反馈给系统,这样学习器就可以在后一阶段重点训练难以分类的样本。这种学习方式可以增强系统的准确性。
首先使用AdaBoost算法对数据集进行回归拟合,再计算误差,然后根据误差评估结果,再用同样的数据集重新拟合。可以把这个过程看作回归器的调优过程,直到达到期望的准确度。假设你有一个包含影响房价的各种参数的数据集,我们的目标就是估计这些参数和房价的关系,这样就可以根据未知参数估计房价了。
在Python中估算房屋价格的方法如下。
1.创建一个新文件housing.py
,并加入下面的代码:
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor
from sklearn import datasets
from sklearn.metrics import mean_squared_error,
explained_variance_score
from sklearn.utils import shuffle
import matplotlib.pyplot as plt
2.网上有一个标准房屋价格数据库,人们经常用它来研究机器学习。可以通过异步社区下载数据。这里将使用一个经过轻度修改的数据集版本,以及随之提供的相关代码文件。最棒的是scikit-learn
提供了一个可以直接加载这个数据集的函数:
housing_data = datasets.load_boston()
每个数据点包含了影响房价的12个输入参数,可以用housing_data.data
获取输入数据,用housing_data.target
获取对应的房屋价格。相应的属性列表如下所示。
crim
:城镇人均犯罪率。zn
:住宅用地超过2322.576m2(25 000平方英尺)的比例。indus
:城镇非零售商用土地的比例。chas
:查理斯河空变量(如果边界是河流,则为1;否则为0)。nox
:一氧化氮浓度(每千万分含量)。rm
:住宅平均房间数。age
:1940年之前建成的自用房屋比例。dis
:到波士顿5个中心区域的加权距离。rad
:枢纽公路的接近指数。tax
:每 10 000美元的全值财产税率。ptratio
:城镇师生比例。lstat
:人口中地位低下者的比例。medv
:自住房的平均房价,以千美元计。在这些参数中,target
(房价)是因变量,其他的12个参数是可能的影响因子。分析的目标是拟合出可以最佳解释target
变化的回归模型。
3.下面把数据分成输入和输出部分。为使结果与数据顺序无关,先将数据顺序打乱:
X, y = shuffle(housing_data.data, housing_data.target, random_state=7)
sklearn.utils.shuffle()
函数通过对集合进行随机排序这种简单的方式来打乱数组或稀疏矩阵的元素顺序。打乱数据的顺序可以降低方差,并确保模型不会过拟合,并具有更好的泛化能力。random_state
参数用于控制如何打乱数据,以便我们可以重新生成相同的结果。
4.下面把数据集划分成训练集和测试集,我们分配80%的数据用于训练,20%的数据用于测试:
num_training = int(0.8 * len(X))
X_train, y_train = X[:num_training], y[:num_training]
X_test, y_test = X[num_training:], y[num_training:]
记住,机器学习算法是使用一个有限的数据集合来训练模型的。在训练阶段,模型基于训练集的预测结果进行评估,但算法的目标是生成可以预测未知数据的模型,换言之,一个从已知数据开始进而解决未知数据泛化问题的模型。因而我们把数据划分成两部分:训练数据和测试数据。训练数据用于训练模型,测试数据用于验证系统的泛化能力。
5.现在已经可以拟合决策树回归模型了。选一个最大深度为4的树,这能控制决策树不变成任意深度:
dt_regressor = DecisionTreeRegressor(max_depth=4)
dt_regressor.fit(X_train, y_train)
DecisionTreeRegressor()
函数用于构建决策树回归器。
6.下面使用AdaBoost算法来拟合决策树回归模型:
ab_regressor = AdaBoostRegressor(DecisionTreeRegressor(max_depth=4),
n_estimators=400, random_state=7)
ab_regressor.fit(X_train, y_train)
AdaBoostRegressor()
函数用于比较结果,看看AdaBoost算法对决策树回归器性能的改善程度。
7.接下来对决策树回归器的性能进行评估:
y_pred_dt = dt_regressor.predict(X_test)
mse = mean_squared_error(y_test, y_pred_dt)
evs = explained_variance_score(y_test, y_pred_dt)
print("#### Decision Tree performance ####")
print("Mean squared error =", round(mse, 2))
print("Explained variance score =", round(evs, 2))
首先,使用predict()
函数基于测试数据来预测被解释变量。然后,计算出均方误差和可解释方差。均方误差是输入的所有数据点的真实值和预测值的离差平方的平均值。可解释方差表明,数据中有多少比例的变异可以被模型解释。
8.下面对AdaBoost的性能进行评估:
y_pred_ab = ab_regressor.predict(X_test)
mse = mean_squared_error(y_test, y_pred_ab)
evs = explained_variance_score(y_test, y_pred_ab)
print("#### AdaBoost performance ####")
print("Mean squared error =", round(mse, 2))
print("Explained variance score =", round(evs, 2))
终端显示的输出结果如下:
#### Decision Tree performance ####
Mean squared error = 14.79
Explained variance score = 0.82
#### AdaBoost performance ####
Mean squared error = 7.54
Explained variance score = 0.91
从上面的结果可以看出,使用AdaBoost算法后,得到的误差较低,方差分接近于1。
DecisionTreeRegressor
方法构建了一个决策树回归器。决策树用于为多个输入变量x1,x2,…,xn预测因变量或类别y。如果y是连续的,则此决策树称为回归树,如果y表示类别,则称为分类树。算法的决策判定过程如下:根据每一节点的输入值xi和答案,判定进入左分支还是右分支,到达叶节点后,即是找到了预测结果。在回归树中,我们将数据空间划分成微小的部分,并对每个划分使用不同的简单模型。树中的非叶子节点表示了模型选用的判定途径。
回归树由一系列节点组成,这些节点将根分支拆分为两个子分支。这样的细分还在继续,一直到叶子节点,从而形成了一个树状分层结构。每个新的分支都会连接到另一个子节点,或成为表示预测值的叶子节点。
AdaBoost回归器是一个元估计器,开始时先把真实数据集输入到回归器,然后下一次迭代中在相同的数据集上加入同样的回归器,但各回归器实例的权重会根据当前预测的误差进行调整。如此连续迭代后,多个弱回归器组合成的强回归器就可以进行更难的预测。这有助于对结果进行对比,并明白AdaBoost算法是如何提高决策树回归器模型的性能的。
所有特征都同等重要吗?在房屋价格估算的案例中,共有13个输入特征,它们对模型都有影响。然而,一个重要的问题是,如何判断哪个特征更加重要?显然,并非所有特征对结果都有同等程度的影响。如果要舍弃一些特征,就要了解哪些特征不太重要,而scikit-learn包含了这一功能。
下面看看如何计算特征的重要性。特征重要性给出了模型构成中每个特征的值的重要性度量。构建模型时某个属性用到的越多,其相对重要性就越高。数据集中每个属性的重要性都被显示计算,以对各属性进行归类和对比。特征重要性是包含在模型内的一个属性feature_importances_
。
计算特征相对重要性的方法如下。
1.先提取出模型的重要性属性,在文件housing.py
中加入下面的代码:
DTFImp= dt_regressor.feature_importances_
DTFImp= 100.0 * (DTFImp / max(DTFImp))
index_sorted = np.flipud(np.argsort(DTFImp))
pos = np.arange(index_sorted.shape[0]) + 0.5
回归器对象有一个feature_importances_
方法,它会给出每个特征的相对重要性。为对比结果,对重要性的值作归一化处理,然后按索引进行排序后再反序,这样就得到了按重要性的值降序排列的结果。最后为了显示方便,把x轴上的标签位置中心化处理。
2.接着可视化结果,画出柱状图:
plt.figure()
plt.bar(pos, DTFImp[index_sorted], align='center')
plt.xticks(pos, housing_data.feature_names[index_sorted])
plt.ylabel('Relative Importance')
plt.title("Decision Tree regressor")
plt.show()
3.我们从feature_importances_
方法中得到重要性的值,并将其缩放到0~100。得到的基于决策树回归器的结果如图1-10所示。
决策树回归器表明,最重要的特征是RM。
4.现在对AdaBoost模型执行同样的过程:
ABFImp= ab_regressor.feature_importances_
ABFImp= 100.0 * (ABFImp / max(ABFImp))
index_sorted = np.flipud(np.argsort(ABFImp))
pos = np.arange(index_sorted.shape[0]) + 0.5
图1-10
5.对结果进行可视化,画出柱状图:
plt.figure()
plt.bar(pos, ABFImp[index_sorted], align='center')
plt.xticks(pos, housing_data.feature_names[index_sorted])
plt.ylabel('Relative Importance')
plt.title("AdaBoost regressor")
plt.show()
AdaBoost模型给出的结果如图1-11所示。
图1-11
AdaBoost模型得出的最重要的特征是LSTAT。事实上,如果你在数据上构建多种回归器,可以看到大多数模型给出的最重要特征是LSTAT。这就展示出了使用AdaBoost算法的决策树回归模型的优势。
特征重要性给出了模型构成中每个特征的重要性的度量。一个属性在模型构建过程中使用的越多,它的相对重要性就越高。本节使用feature_importances_
方法从模型中提取出了特征的相对重要性。
相对重要性返回了决策树构成中每个特征的使用度量,在决策树中一个属性用于决策的使用次数越多,它的相对重要性就越高。数据集中每个属性的相对重要性都得到了显示计算,我们可以据此对各个属性进行分类和对比。
本节将用一种新的回归方法来解决共享单车分布的问题,我们使用随机森林回归器来估计输出结果。随机森林是决策树的集合,它基本上是由数据集的若干子集构建的一组决策树,使用平均值来改善整体性能。
本例将使用文件bike_day.csv
中的数据集,可以在UCI网站的Bike Sharing Dataset Data Set网页中下载。数据集共包含16个列,前两列是序列号和日期,分析中不会用到,最后3列对应3种不同类型的输出,而最后一列是第14列和第15列之和,因此构建模型时可以不用考虑第14列和第15列。下面看看如何用Python构建一个随机森林回归器模型,我们将逐行解析每个步骤中的代码。
下面是评估共享单车需求分布的方法。
1.首先需要导入两个库:
import csv
import numpy as np
2.因为处理的是CSV文件,所以导入了专门处理这类文件的CSV程序包。现在把数据导入到Python环境中:
filename="bike_day.csv"
file_reader = csv.reader(open(filename, 'r'), delimiter=',')
X, y = [], []
for row in file_reader:
X.append(row[2:13])
y.append(row[-1])
这段代码读取了CSV文件中的所有数据,csv.reader()
函数返回了一个reader
对象,这个对象将在给定的CSV文件中逐行迭代。CSV文件中的每一行读取后都返回一个字符串列表,本例中共返回了两个列表:X
和y
。我们从输出值中分离出数据并返回。下面提取特征名称:
feature_names = np.array(X[0])
特征名称在图像显示时很有用。因为第一行是特征名称,所以要把它们从X
和y
中去掉:
X=np.array(X[1:]).astype(np.float32)
y=np.array(y[1:]).astype(np.float32)
同时把两个列表转换成了两个数组。
3.下面打乱两个数组中的数据,以确保它们和文件中的数据排列顺序无关:
from sklearn.utils import shuffle
X, y = shuffle(X, y, random_state=7)
4.和前面用过的处理一样,需要把数据划分成训练集和测试集。这次,我们将90%的数据用于训练,剩余的10%用于测试:
num_training = int(0.9 * len(X))
X_train, y_train = X[:num_training], y[:num_training]
X_test, y_test = X[num_training:], y[num_training:]
5.接下来训练回归器:
from sklearn.ensemble import RandomForestRegressor
rf_regressor = RandomForestRegressor(n_estimators=1000,
max_depth=10, min_samples_split=2)
rf_regressor.fit(X_train, y_train)
RandomForestRegressor()
函数构建了一个随机森林回归器。这里,n_estimators
指的是评估器数量,即我们要在随机森林中使用的决策树个数。max_depth
参数指定每棵树的最大深度,min_samples_split
参数指定树中拆分一个节点需要的数据样本个数。
6.下面对随机森林回归器的性能进行评估:
y_pred = rf_regressor.predict(X_test)
from sklearn.metrics import mean_squared_error,
explained_variance_score
mse = mean_squared_error(y_test, y_pred)
evs = explained_variance_score(y_test, y_pred)
print( "#### Random Forest regressor performance ####")
print("Mean squared error =", round(mse, 2))
print("Explained variance score =", round(evs, 2))
返回的结果如下:
#### Random Forest regressor performance ####
Mean squared error = 357864.36
Explained variance score = 0.89
7.现在提取出特征的相对重要性:
RFFImp= rf_regressor.feature_importances_
RFFImp= 100.0 * (RFFImp / max(RFFImp))
index_sorted = np.flipud(np.argsort(RFFImp))
pos = np.arange(index_sorted.shape[0]) + 0.5
对结果可视化,绘制出柱状图:
import matplotlib.pyplot as plt
plt.figure()
plt.bar(pos, RFFImp[index_sorted], align='center')
plt.xticks(pos, feature_names[index_sorted])
plt.ylabel('Relative Importance')
plt.title("Random Forest regressor")
plt.show()
绘制出的图如图1-12所示。
图1-12
看来温度是影响自行车租赁的最重要因素。
随机森林是一个特殊类型的回归器,它由一组简单回归器(决策树)组成,可以表示成独立同分布的随机向量,其中每个向量都选择各个决策树的预测平均值。这种类型的结构属于集成学习,它大大地改善了回归的准确率。随机森林中的每棵树都由训练数据集中的一个随机的子集构成和训练,因而随机森林中的树并未用到全部数据,我们不再为每个节点选择最佳属性,而是从一组随机选择的属性中挑选一个最佳的。
随机性因素构成了回归器的组成部分,其目的是增加多样性并减少相关性。在回归问题中,随机森林返回的最终结果是不同决策树输出结果的平均值。当随机森林算法用于分类问题时,返回的是不同树输出类别中的众数。
现在把第14列和第15列加入数据集,看看结果有什么区别。在新的特征重要性柱状图中,除去这两个特征外,其他特征都变成了0。这是因为输出结果可以通过简单地对第14列和第15列相加求和得到,算法并不需要其他特征来计算输出结果。在for
循环中用下面这行代码替代原代码,其余代码保持不变:
X.append(row[2:15])
如果画出特征重要性的柱状图,如图1-13所示。
图1-13
和预想的一样,从图中可以看出,只有这两个特征重要,这也符合常理,因为最终结果仅仅是这两个特征简单相加得到的,因而这两个变量也就与输出结果有最直接的关系,回归器也相应认为它不需要使用其他的特征来预测结果。在消除数据集冗余变量方面,随机森林是个非常有用的工具。但这点并非与前面模型的唯一不同,如果对模型性能进行分析,可以看出有大幅改善:
#### Random Forest regressor performance ####
Mean squared error = 22552.26
Explained variance score = 0.99
这里得到了99%的可解释方差,非常棒的结果。
还有另外一个文件bike_hour.csv
,它包含了按小时统计的数据。我们需要用到第3~14列,现在变动下列代码,其余部分保持不变:
filename="bike_hour.csv"
file_reader = csv.reader(open(filename, 'r'), delimiter=',')
X, y = [], []
for row in file_reader:
X.append(row[2:14])
y.append(row[-1])
运行新代码,回归器给出的性能如下:
#### Random Forest regressor performance ####
Mean squared error = 2613.86
Explained variance score = 0.92
特征重要性的柱状图如图1-14所示。
图1-14
由图1-14看出,最重要的特征是一天中的不同时点,这也合乎我们的理解。次重要的特征是温度,这与我们之前的分析结果一致。
本章将涵盖以下内容:
本章用到了下列文件(可通过GitHub下载):
simple_classifier.py
;logistic_regression.py
;naive_bayes.py
;data_multivar.txt
;splitting_dataset.py
;confusion_matrix.py
;performance_report.py
;car.py
;car.data.txt
;income.py
;adult.data.txt
;wine.quality.py
;wine.txt
;post.classification
。在机器学习领域中,分类(classification)指的是根据数据特性将其归入若干类别的过程。这和第1章中讨论的回归不同,在回归问题中输出结果是一个实数值。监督学习分类器使用有标签的训练数据构建模型,然后用此模型对未知数据进行分类。
分类器可以是实现了分类功能的任何算法。最简单的情况,分类器可以是一个数学函数。在更多的真实问题中,分类器有很多复杂的形式。在本章的学习过程中,你将会看到把数据分成两类的二元分类器和把数据分成多于两个类别的多元分类器。解决分类问题的数学技术都倾向于二元分类问题的处理,通过不同的方法对其进行扩展,进而也能处理多元分类问题。
在机器学习中,对分类器准确性的评估至关重要,我们需要学会如何使用现有的数据,来构建出可以泛化到真实世界的模型。本章就将介绍这些内容。
分类器(classifier)是带有某些特征的系统,可以对被检查样本进行识别归类。在不同的分类方法中,组又被称为类(class)。分类器的目标是确认一个可使性能达到最优的分类规范。分类器性能通过泛化能力进行评估,泛化(generalization)是指对每个新实验数据进行正确地归类。对分类识别方式的不同,形成了构建分类器模型的不同方法。
分类器基于从数据集的一系列样例学习的知识,来识别新对象的类型。分类器从数据集开始,学习并提取模型,然后将之用于新实例的分类。
使用训练数据构建一个简单分类器的方法如下。
1.这里使用的文件是simple_classifier.py
,已经作为参考资料给出。和第1章中一样,首先导入numpy包和matplotlib.pyplot包,然后创建一些样例数据:
import numpy as np
import matplotlib.pyplot as plt
X = np.array([[3,1], [2,5], [1,8], [6,4], [5,2], [3,5], [4,7], [4,-1]])
2.为这些数据点分配标签:
y = [0, 1, 1, 0, 0, 1, 1, 0]
3.因为只有两个类,所以y
列表中有0和1两个值。一般情况下,如果有N个类,那么y
的取值范围就是0到N−1。下面根据标签把数据归类:
class_0 = np.array([X[i] for i in range(len(X)) if y[i]==0])
class_1 = np.array([X[i] for i in range(len(X)) if y[i]==1])
4.为了对数据有个直观的认识,我们对数据进行可视化:
plt.figure()
plt.scatter(class_0[:,0], class_0[:,1], color='black', **<strong>marker='s'</strong>**)
plt.scatter(class_1[:,0], class_1[:,1], color='black', **<strong>marker='x'</strong>**)
plt.show()
结果如图2-1所示,是个散点图(scatterplot),图2-1使用方块和叉这两种标记表示两类数据。参数marker
指明了表示数据点的标记的样式,本例中用方块表示类别为class_0
的数据,用叉表示类别为class_1
的数据。
5.前面的两行代码中,仅使用了X
和y
之间的映射创建了两个列表。如果要直观地展示出不同类型的数据,并划出一条分割线,要怎么实现呢?很简单,只要用直线方程在两类数据之间画一条直线就行了。下面是实现方法:
line_x = range(10)
line_y = line_x
图2-1
6.上面的代码用数学方程y=x创建了一条直线,下面画出这条直线:
plt.figure()
plt.scatter(class_0[:,0], class_0[:,1], color='black', marker='s')
plt.scatter(class_1[:,0], class_1[:,1], color='black', marker='x')
plt.plot(line_x, line_y, color='black', linewidth=3)
plt.show()
7.运行代码,返回的图形如图2-2所示。
图2-2
由图2-2可以看出,两个类别之间的分割线构成非常简单。在这个小例子中,运算都很简单。但更多的情况下,创建一条两个类别之间的分割线是非常困难的。
本小节演示了如何构造一个简单的分类器,从识别平面上一系列尽可能多的数据点开始,然后为这些点分配类别0或1,从而把它们划分成两组。为理解数据点的空间分配,我们为每个类别使用了不同的标记样式进行可视化,最后,用方程式y=x表示的直线将这两组数据分开。
本节基于以下规则构建了一个简单的分类器:有输入数据点(a,b),若a大于或等于b,则该点归类成class_0
,否则,归类成class_1
。如果逐个检查数据点,会发现每个数据点都是这样分类的,我们已经构建出了一个可以对未知数据进行分类的线性分类器。之所以称其为线性分类器,是因为分割线是一条直线,如果分割线是曲线的话,那么这个分类器就是一个非线性分类器。
这样简单的分类器之所以可行,是因为数据点很少,可以很直观地判断出分割线。如果数据点多达几千个呢?如果对这一过程进行泛化处理?请看下一节。
尽管本节也出现了回归这个词,但逻辑回归其实是一种分类方法。给定一组数据点,我们的目标是构建出可以绘制类别之间线性边界的模型,它通过求解由训练数据导出的一组方程来提取边界。本节我们就将构建一个这样的逻辑回归分类器。
逻辑回归是依赖变量具有二分性时使用的非线性回归模型,目的是确定出一次观测中依赖变量生成两个值的概率。非线性回归模型也可用于对观测结果进行分类,按照各自的特征将它们归入两个类别中。
构建逻辑回归分类器的方法如下。
1.用Python构建一个逻辑回归分类器,这里用到的是作为参考资料给出的文件logistic_regression.py
。假设已经导入了必需的程序包,接下来创建一些带有训练标签的样例数据:
import numpy as np
from sklearn import linear_model
import matplotlib.pyplot as plt
X = np.array([[4, 7], [3.5, 8], [3.1, 6.2], [0.5, 1], [1, 2], [1.2,
1.9], [6, 2], [5.7, 1.5], [5.4, 2.2]])
y = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2])
这里假定总共有3个类别(0、1和2)。
2.接下来初始化逻辑回归分类器:
classifier = linear_model.LogisticRegression(solver='lbfgs', C=100)
上面的函数中有一些需要声明的参数,其中比较重要的两个是solver
和C
。参数solver
用于设置求解方程式的算法类型,参数C
表示正则化强度,数值越小,正则化强度越高。
3.下面训练分类器:
classifier.fit(X, y)
4.画出数据点和边界,首先需要定义图形的数据范围:
x_min, x_max = min(X[:, 0]) - 1.0, max(X[:, 0]) + 1.0
y_min, y_max = min(X[:, 1]) - 1.0, max(X[:, 1]) + 1.0
上面设定的值表示我们希望在图中使用的数据范围,值域通常是从数据中的最小值到最大值。清楚起见,在代码中增加了一个余量1.0。
5.为了画出边界,还需要利用一组网格数据求出方程并画出边界。下面继续定义网格:
# 设置网格数据的步长
step_size = 0.01
# 定义网格
x_values, y_values = np.meshgrid(np.arange(x_min, x_max, step_size),
np.arange(y_min, y_max, step_size))
变量x_values
和变量y_values
包含求解方程的网格点。
6.下面计算分类器对所有数据点的分类结果:
# 计算分类结果
mesh_output = classifier.predict(np.c_[x_values.ravel(),
y_values.ravel()])
# 数组变形
mesh_output = mesh_output.reshape(x_values.shape)
7.用彩色区域画出边界:
# 用彩图画出分类结果
plt.figure()
# 选定一个配色方案
plt.pcolormesh(x_values, y_values, mesh_output, cmap=plt.cm.gray)
这是一个三维画图器,可以使用选定的配色方案画出二维数据点和关联值的各个分区。
8.接下来把训练数据点画在图上:
# 在图中画出训练数据点
plt.scatter(X[:, 0], X[:, 1], c=y, s=80, edgecolors='black',
linewidth=1, cmap=plt.cm.Paired)
# 设定图形边界
plt.xlim(x_values.min(), x_values.max())
plt.ylim(y_values.min(), y_values.max())
# 设置X轴和Y轴的刻度
plt.xticks((np.arange(int(min(X[:, 0])-1), int(max(X[:, 0])+1),1.0)))
plt.yticks((np.arange(int(min(X[:, 1])-1), int(max(X[:, 1])+1),1.0)))
plt.show()
plt.scatter
方法用于把数据点画在二维图形上。X[:, 0]
表示应使用0轴(本例中为x轴)上的所有数据值,X[:, 1]
表示应使用1轴(本例中为y轴)上的所有数据值。参数C=y
表示使用的颜色序列。我们用目标标签映射到cmap
的颜色清单。通常希望为不同的标签使用不同的颜色,因此使用y作为映射。坐标轴的取值范围由plt.xlim
和plt.ylim
设定。为了标记坐标轴的数值,需要用到plt.xticks
和plt.yticks
,有了刻度值,就可以清楚地看出数据点的位置。在前面的代码中,我们希望刻度值范围在数据的最小值和最大值之间,并加入余量1。同样,我们希望刻度值是整数,所以使用int()
函数进行取整操作。
9.运行代码,得到的输出如图2-3所示。
图2-3
10.下面来看看参数C
对模型的影响。参数C
表示对分类错误的惩罚值,如果把C
设置为1.0,得到的输出结果将如图2-4所示。
图2-4
11.如果将C
设成10 000,结果如图2-5所示。
图2-5
随着参数C
的不断增大,分类错误的惩罚值越来越高,因而,各类型的边界也更优。
逻辑回归(logistic regression)是监督学习算法中的分类方法。借助统计方法,逻辑回归可以生成一个概率结果,这个结果表示了某个给定的输入值属于某个给定类别的概率。在二分类逻辑回归问题中,如果属于其中一类的输出概率为P,那么属于另一类的输出概率则为1−P,其中P表示概率,取值为0~1。
逻辑回归使用逻辑函数来对输入值进行归类。逻辑函数也称为sigmoid函数,它是一条S形的曲线,可以把任意的实数值映射到0~1,极值除外。sigmoid函数可用下面的方程式描述:
这个函数将实数值转换成0~1的数值。
为了得到用概率表示的逻辑回归方程,需要在逻辑回归方程式中包括概率:
由于以e为底的指数函数是自然对数(ln)的逆运算,因而也可以写成:
这个函数称为logit函数。另外,这个函数让我们可以把概率(取值在0~1)关联到整个实数集。它是一个链接函数,表示了逻辑函数的相反面。
分类器解决的是从一个较大的集合中识别出具有某些特征的个体子集问题,并可能使用一个称为先验(priori)的个体子集(训练集)。朴素贝叶斯分类器是利用贝叶斯定理来构建模型的有监督学习分类器。本节将构建一个朴素贝叶斯分类器。
贝叶斯分类器的基本原理是,某些个体以基于观测值的给定概率归属于某个特定类别。概率基于的假设是,观测到的特征可以相互依赖,也可以相互独立;如果特征是相互独立的,那么这样的贝叶斯分类器就被称为朴素贝叶斯分类器。朴素贝叶斯分类器假定某个特定特征是否存在与其他特征是否存在无关,从而大大简化了计算。下面将构建一个朴素贝叶斯分类器。
在Python中构建朴素贝叶斯分类器的方法如下。
1.这里将使用作为参考资料给出的naive_bayes.py
文件,先导入几个库:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
2.给出的文件data_multivar.txt
包含了要使用的数据,每行中的数据以逗号分隔。现在从该文件加载数据:
input_file = 'data_multivar.txt'
X = []
y = []
with open(input_file, 'r') as f:
for line in f.readlines():
data = [float(x) for x in line.split(',')]
X.append(data[:-1])
y.append(data[-1])
X = np.array(X)
y = np.array(y)
现在已经分别把输入数据和标签加载到了变量X
和y
中。标签总共有4种:0、1、2和3。
3.构建朴素贝叶斯分类器:
classifier_gaussiannb = GaussianNB()
classifier_gaussiannb.fit(X, y)
y_pred = classifier_gaussiannb.predict(X)
函数gaussiannb()
表明使用的是高斯朴素贝叶斯模型。
4.接下来计算分类器的准确率:
accuracy = 100.0 * (y == y_pred).sum() / X.shape[0]
print("Accuracy of the classifier =", round(accuracy, 2), "%")
得到的准确率结果如下:
Accuracy of the classifier = 99.5 %
5.下面画出数据点和边界。这里将使用和2.4节中相同的步骤:
x_min, x_max = min(X[:, 0]) - 1.0, max(X[:, 0]) + 1.0
y_min, y_max = min(X[:, 1]) - 1.0, max(X[:, 1]) + 1.0
# 设置网格数据的步长
step_size = 0.01
# 定义网格
x_values, y_values = np.meshgrid(np.arange(x_min, x_max, step_size),
np.arange(y_min, y_max, step_size))
# 计算分类器结果
mesh_output = classifier_gaussiannb.predict(np.c_[x_values.ravel(),
y_values.ravel()])
# 数组变形
mesh_output = mesh_output.reshape(x_values.shape)
# 将分类结果可视化
plt.figure()
# 选择配色方案
plt.pcolormesh(x_values, y_values, mesh_output, cmap=plt.cm.gray)
# 在图中画出训练数据点
plt.scatter(X[:, 0], X[:, 1], c=y, s=80, edgecolors='black', linewidth=1,
cmap=plt.cm.Paired)
# 设定图形边界
plt.xlim(x_values.min(), x_values.max())
plt.ylim(y_values.min(), y_values.max())
# 设置x轴和y轴的刻度
plt.xticks((np.arange(int(min(X[:, 0])-1), int(max(X[:, 0])+1), 1.0)))
plt.yticks((np.arange(int(min(X[:, 1])-1), int(max(X[:, 1])+1), 1.0)))
plt.show()
输出结果如图2-6所示。
图2-6
不同类别的边界并非一定是直线。在2.4节中,我们使用了所有的训练数据,而机器学习的最佳实践是,不要让训练数据和测试数据出现重叠。在理想情况下,需要用一些没有参与训练的数据进行测试,这样可以更好地评估模型对未知数据的泛化能力。对此scikit-learn
给出了一个非常有效的处理方法,这一点在下一节讲述。
贝叶斯分类器(bayesian classifier)应用基于贝叶斯定理。分类器需要用到先验知识和问题相关的条件概率。数值通常是未知的,但可以对其进行估计。如果定理中涉及的概率能够得到可靠的估计,那么贝叶斯分类器就通常是可信的并且非常简洁。
假设样本空间中每个基本事件发生的概率相同,则给定事件E发生的概率,等于E包含的基本事件数与样本空间中包含的基本事件总数的比值。概率公式如下:
给定两个事件A和B,如果事件A和事件B相互独立,即其中一个事件的发生不会影响另一个事件的发生,那么A和B同时发生的联合概率,等于事件A发生的概率和事件B发生的概率之积:
如果两个事件相互独立,即其中一个事件的发生不会影响另一个事件发生的概率,则可以应用同样的规则,假定P(B | A)是在事件A发生的条件下事件B发生的概率(这里引入了条件概率的概念,我们马上会详细介绍),则A和B同时发生的联合概率为:
条件概率(conditional probability)是指当事件A发生的条件下,事件B发生的概率,记为P(B | A),计算公式如下:
若A和B是两个相互独立的事件,根据前面的陈述,事件A和事件B的联合概率可以使用下面的公式计算:
或者类似地,可以使用下面的公式:
对比上面两个公式,可以看出等式左边的项是相同的,这就说明等式右边的项也是相同的,因而有下面的等式:
通过求解这个条件概率的方程式,可以得出:
上面得出的公式就是贝叶斯定理的数学表示,使用哪一个公式则取决于我们的需求。
1763年,托马斯•贝叶斯牧师所写的一篇文章在英格兰发表,这篇文章因其蕴含的意义而知名。根据这篇文章所讲,对现象的预测不仅取决于科学家从他的实验中获得的观察结果,而且还取决于他自己对所研究现象的看法与理解(甚至取决于在进行实验之前的理解)。20世纪,布鲁诺•德费奈蒂(Bruno de Finetti,La prévision: ses lois logiques,ses sources subjectives,1937)、L J萨维奇(L J Savage,The Fondations of Statistics Reconsidered,1959)和其他一些著名学者将这些前提发展起来。
本节介绍如何把数据集合理地划分成训练集和测试集。如1.10节中所述,当我们构建机器学习模型时,需要采用一定的方式来验证模型,以评估模型是否达到了满意的拟合效果。故而,需要把数据分成两组:训练数据集和测试数据集。训练数据集用于构建模型,测试数据集用于对学习好的模型在未知数据上的表现进行评估。
本节将介绍如何为训练阶段和测试阶段划分数据集。
机器学习模型的根本目标是进行精确预测。在使用模型进行预测前,有必要对模型的预测性能进行评估。为了对模型的预测效果进行评估,需要使用全新的数据。基于相同的数据训练和测试模型,从方法论上讲就是错误的,对训练过程使用过的数据样本标记预测而获得的高准确率评分,并不能表示模型对新数据所属类别的预测效果很好,而基于此得到的模型泛化能力也没有保证。
划分数据集的方法如下。
1.本节前面的部分和2.5.2节中的类似(加载Splitting_dataset.py
文件):
import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
input_file = 'data_multivar.txt'
X = []
y = []
with open(input_file, 'r') as f:
for line in f.readlines():
data = [float(x) for x in line.split(',')]
X.append(data[:-1])
y.append(data[-1])
X = np.array(X)
y = np.array(y)
# 把数据集划分成训练集和测试集
from sklearn import model_selection
X_train, X_test, y_train, y_test =
model_selection.train_test_split(X, y, test_size=0.25,
random_state=5)
# 构建分类器
classifier_gaussiannb_new = GaussianNB()
classifier_gaussiannb_new.fit(X_train, y_train)
这里,我们把参数test_size
设成了0.25,表示分配25%的数据给测试数据集,其余75%的数据则用于训练。
2.在测试数据上评估分类器:
y_test_pred = classifier_gaussiannb_new.predict(X_test)
3.接下来计算分类器的准确率:
accuracy = 100.0 * (y_test == y_test_pred).sum() / X_test.shape[0]
print("Accuracy of the classifier =", round(accuracy, 2), "%")
返回的结果如下:
**<strong>Accuracy of the classifier = 98.0 %</strong>**
4.画出测试数据的数据点和边界:
# 画出分类器图形结果
# 定义数据
X= X_test
y= y_test
# 定义图形数据范围
x_min, x_max = min(X[:, 0]) - 1.0, max(X[:, 0]) + 1.0
y_min, y_max = min(X[:, 1]) - 1.0, max(X[:, 1]) + 1.0
# 设置网格步长
step_size = 0.01
# 定义网格
x_values, y_values = np.meshgrid(np.arange(x_min, x_max, step_size),
np.arange(y_min, y_max, step_size))
# 计算分类器结果
mesh_output = classifier_gaussiannb_new.predict(np.c_[x_values.ravel(),
y_values.ravel()])
# 数组变形
mesh_output = mesh_output.reshape(x_values.shape)
# 画出结果
plt.figure()
# 选择配色方案
plt.pcolormesh(x_values, y_values, mesh_output, cmap=plt.cm.gray)
# 在图中画出训练数据点
plt.scatter(X[:, 0], X[:, 1], c=y, s=80, edgecolors='black',
linewidth=1, cmap=plt.cm.Paired)
# 设置图形边界
plt.xlim(x_values.min(), x_values.max())
plt.ylim(y_values.min(), y_values.max())
# 设置x轴和y轴的刻度
plt.xticks((np.arange(int(min(X[:, 0])-1), int(max(X[:, 0])+1), 1.0)))
plt.yticks((np.arange(int(min(X[:, 1])-1), int(max(X[:, 1])+1), 1.0)))
plt.show()
5.运行代码,结果如图2-7所示。
图2-7
本节使用scikit-learn库的train_test_split()
函数对数据进行了划分。这个函数把数组或矩阵中的数据随机划分成训练集和测试集。对输入数据的随机性划分保证了训练数据和测试数据具有相似的分布。当不需要保留输入数据的顺序时可以使用这个方法。
性能评估依赖于使用的数据,因而,对测试集和训练集的随机划分并不能确保获得满意的统计学结果。在不同的随机划分情况中进行重复评估,并计算出性能的平均值和标准差,会得到一个更为可靠的评估结果。
然而,即使在不同的随机划分情况进行重复评估,也无法避免在测试(或训练)阶段对最复杂的数据进行分类。
交叉验证(cross-validation)是机器学习的重要概念。在上一节中,我们把数据集划分成了训练集和测试集。不过,为了能让模型具有更好的鲁棒性,还需要使用数据集的不同子集重复这个过程。如果只对某个特定的子集微调,很可能导致过拟合。过拟合(overfitting)是指微调模型致其过度拟合训练数据,但在未知数据上的表现却很糟糕的情况。我们希望机器学习模型可以在未知数据上具有良好的表现。本节将学习如何使用交叉验证来评估模型准确度。
构建机器学习模型时,我们通常关注的是查准率(precision)、查全率(recall)和F1分数,可以使用参数评分标准获得各项性能指标的得分。查准率指的是正确分类的样本数占归入该类别的样本总数的百分比,查全率指的是正确分类的样本数占该类别应有样本总数的百分比。
使用交叉验证来评估模型准确度的方法如下。
1.这里将使用2.5节用到的分类器,首先计算模型准确率:
from sklearn import model_selection
num_validations = 5
accuracy = model_selection.cross_val_score(classifier_gaussiannb,
X, y, scoring='accuracy', cv=num_validations)
print "Accuracy: " + str(round(100*accuracy.mean(), 2)) + "%"
2.用前面的函数计算查准率、查全率和F1分数:
f1 = model_selection.cross_val_score(classifier_gaussiannb,
X, y, scoring='f1_weighted', cv=num_validations)
print "F1: " + str(round(100*f1.mean(), 2)) + "%"
precision = model_selection.cross_val_score(classifier_gaussiannb,
X, y, scoring='precision_weighted', cv=num_validations)
print "Precision: " + str(round(100*precision.mean(), 2)) + "%"
recall = model_selection.cross_val_score(classifier_gaussiannb,
X, y, scoring='recall_weighted', cv=num_validations)
print "Recall: " + str(round(100*recall.mean(), 2)) + "%"
假设有一个包含100个样本的测试数据集,其中82个是我们感兴趣的,现在要用分类器来选出这82个样本。最终,分类器选出了73个它认为是我们感兴趣的样本。而这73个选出的样本中,只有65个是我们真正感兴趣的,剩余的8个是被错误分类的。可以用下面的方法计算查准率:
计算查全率的方法为:
一个好的机器学习模型需要同时具有较高的查准率和较高的查全率,查准率和查全率是一对矛盾的度量指标,很容易使其中之一达到100%,但另一个指标就会变得很低,而我们希望的是这两个指标的值同时都较高。我们使用F1分数对此进行量化,F1分数实际上是查准率和查准率的调和平均,是二者的合成指标。
在前面的例子中,F1分数的计算过程如下:
交叉验证使用了所有可用的数据。把数据按固定大小分组后,将其交替用作测试集和训练集。因此,每种情况的数据要么进行了分类(至少一次),要么用于训练,但获得的性能取决于特定的数据划分,故而可以多次重复交叉验证,以使得性能评估和特定的数据划分无关。
混淆矩阵(confusion matrix)是用于了解分类模型性能的数据表,可以帮助我们理解如何把测试数据分成不同的类别。当我们想对算法进行微调时,在修改算法前需要先了解数据的错误分类情况。有些类别的分类效果比其他类别的更差些,这点可以用混淆矩阵帮助理解。先看图2-8。
图2-8
通过图2-8,可以看出是如何把数据分入不同类别的。在理想情况下,矩阵所有非对角元素都应为0,这是最完美的分类结果。先来看看类别0,属于它的样本总数是52个,这个数字通过对第一行的数字求和得出,其中45个样本被正确地预测,4个样本被错误预测成类别1,还有3个样本被错误预测成类别2。可以对后面两行进行同样的分析,需要注意的是来自类别1的11个样本被错误预测成了类别0,占了类别1样本总数的16%。这就是模型需要优化的切入点。
混淆矩阵通过把分类结果和真实数据对比,识别出了分类错误的类型。矩阵中的对角元素表示正确分类的样本数,其余的元素则表示错误分类的情况。
可视化混淆矩阵的方法如下。
1.这里将使用作为参考资料给出的confusion_matrix.py
文件,下面是从数据中提取混淆矩阵的方法:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
这里用到了一些样本数据。样本数据有0~3共4个类别,我们也对标签进行了预测,并使用confusion_matrix
方法提取出混淆矩阵并画出它。
2.继续进行函数的定义:
# 显示混淆矩阵
def plot_confusion_matrix(confusion_mat):
plt.imshow(confusion_mat, interpolation='nearest',
cmap=plt.cm.Paired)
plt.title('Confusion matrix')
plt.colorbar()
tick_marks = np.arange(4)
plt.xticks(tick_marks, tick_marks)
plt.yticks(tick_marks, tick_marks)
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.show()
这里使用imshow()
函数画出混淆矩阵。其他函数都非常简单,只是使用相关函数设置了标题、颜色栏、刻度和标签。因为数据集中有4种标签,所以tick_marks
参数的取值范围是0到3。np.arange()
函数将生成一个numpy数组。
3.现在来定义真实数据和预测数据,然后调用confusion_matrix()
函数:
y_true = [1, 0, 0, 2, 1, 0, 3, 3, 3]
y_pred = [1, 1, 0, 2, 1, 0, 1, 3, 3]
confusion_mat = confusion_matrix(y_true, y_pred)
plot_confusion_matrix(confusion_mat)
4.运行代码,将会得到图2-9所示的输出。
图2-9
从图2-9中可以看出,对角线的颜色很亮,并且我们希望它越亮越好。黑色区域表示0,在非对角区域有一些灰色的方格,表示分类错误的样本数。例如第一行的灰格,就表示了真实标签为0,而预测标签为1的错误分类情况。
混淆矩阵展示了真实分类和模型预测分类的信息,这样系统的性能就可以基于矩阵中的数据进行评估了。下面所示的是一个二分类模型的混淆矩阵。
预测为正例 |
预测为反例 |
|
---|---|---|
真正例 |
TP |
FN |
真反例 |
FP |
TN |
混淆矩阵可以表示出如下的含义:
混淆矩阵展示了算法的性能,矩阵中的每行代表的是真实类别的实例,每列代表的是预测类别中的实例。混淆矩阵这个术语的意思就是,可以很容易地看出系统是否混淆了两个类别。
在2.7节中,我们介绍了评估模型准确率的几个性能指标。请牢记这几个指标的含义。准确率表示的是正确分类的百分比,查准率表示的是所有预测为正例中的样本中被正确预测的百分比,查全率表示测试集中所有正例样本中被预测为正例的百分比。最后,F1分数是使用查准率和查全率共同计算出的结果。本节将介绍如何提取性能报告。
scikit-learn库中有一个可以直接输出查准率、查全率和F1分数的函数,下面看看如何实现。
提取性能报告的方法如下。
1.在一个新的Python文件中加入下面的代码(加载performance_report.py
文件):
from sklearn.metrics import classification_report
y_true = [1, 0, 0, 2, 1, 0, 3, 3, 3]
y_pred = [1, 1, 0, 2, 1, 0, 1, 3, 3]
target_names = ['Class-0', 'Class-1', 'Class-2', 'Class-3']
print(classification_report(y_true, y_pred, target_names=target_names))
2.运行代码后,终端上会显示图2-10所示的结果。
不需要分别计算这些指标,可以直接用这个函数从模型中提取出所有的统计值。
图2-10
本节使用scikit-learn库中的classification_report()
函数来提取性能报告,这个函数创建了一个包含主要性能指标的文本报告,并返回每个类别的查准率、查全率和F1分数。参考2.8节中的术语介绍,我们可以这样计算这些指标。
报告中的平均值包括微平均(micro average)(总的真正例、假反例和假正例的全局指标平均值)、宏平均(macro average)(各标签的非加权平均)、加权平均(weighted average)(各标签加入support权重的平均值),以及样本平均(sample average)(仅用于多标签分类)。
接下来看看如何使用分类技术解决现实问题。这里将使用一个包含了汽车多种细节的数据集,如车门数量、后备厢大小、维修成本等。分类的目标是把汽车质量分成4类:不达标、达标、良好和优秀。
可以从UCI网站的Car Evaluation Data Set页面中下载数据集。
这里需要把数据集中的值看作字符串。我们仅考虑数据集中的6个属性,下面列出了这6个属性以及它们可能的取值。
buying
:可能取值为vhigh
、high
、med
、low
。maint
:可能取值为vhigh
、high
、med
、low
。doors
:可能取值为2
、3
、4
、5
或更多。persons
:可能取值为2
、4
或更多。lug_boot
:可能取值为small
、med
、big
。safety
:可能取值为low
、med
、high
。考虑到每一行都包含字符串属性,因此需要假设所有特征都是字符串类型,并据此设计分类器。上一章我们使用随机森林构建了回归器,本节我们使用随机森林来构建分类器。
基于特征评估汽车质量的方法如下。
1.这里使用已作为参考资料给出的car.py
文件,先来导入两个包:
from sklearn import preprocessing
from sklearn.ensemble import RandomForestClassifier
2.然后加载数据集:
input_file = 'car.data.txt'
# 读取数据
X = []
count = 0
with open(input_file, 'r') as f:
for line in f.readlines():
data = line[:-1].split(',')
X.append(data)
X = np.array(X)
每一行都包含由逗号分隔的单词列表。解析输入文件,对每一行数据进行分割,然后将列表附加到主数据。我们将忽略每行的最后一个字符,因为那是一个换行符。由于Python程序包仅能处理数值型数据,所以需要把这些属性转换成程序包可以理解的形式。
3.上一章我们介绍过标签编码,下面就用标签编码技术把字符串转换成数字:
# 将字符串转换成数值
label_encoder = []
X_encoded = np.empty(X.shape)
for i,item in enumerate(X[0]):
label_encoder.append(preprocessing.LabelEncoder())
X_encoded[:, i] = label_encoder[-1].fit_transform(X[:, i])
X = X_encoded[:, :-1].astype(int)
y = X_encoded[:, -1].astype(int)
由于每个属性都只有有限个可能取值,所以可以使用标签编码将其转换成数字。需要为不同的属性使用不同的标签编码器,比如,lug_boot
属性有3个可能的取值,需要建立一个可对这3个属性编码的标签编码器。每行的最后一个值是类别,我们把它赋值给变量y
。
4.下面来训练分类器:
# 构建随机森林分类器
params = {'n_estimators': 200, 'max_depth': 8, 'random_state': 7}
classifier = RandomForestClassifier(**params)
classifier.fit(X, y)
你可以改变参数n_estimators
和max_depth
的值,看看它们对分类准确度有什么影响。稍后我们会以标准化的方式来处理参数选择问题。
5.现在进行交叉验证:
# 交叉验证
from sklearn import model_selection
accuracy = model_selection.cross_val_score(classifier,
X, y, scoring='accuracy', cv=3)
print("Accuracy of the classifier: " +
str(round(100*accuracy.mean(), 2)) + "%")
一旦训练好分类器,就需要评估它的性能,我们使用三折交叉验证来计算准确率,返回的结果如下:
Accuracy of the classifier: 78.19%
6.构建分类器的主要目的就是用它对孤立的和未知的数据进行分类,下面用分类器对一个单一数据点进行分类:
# 对单一数据点进行编码测试
input_data = ['high', 'low', '2', 'more', 'med', 'high']
input_data_encoded = [-1] * len(input_data)
for i,item in enumerate(input_data):
input_data_encoded[i] =
int(label_encoder[i].transform([input_data[i]]))
input_data_encoded = np.array(input_data_encoded)
第一步是把数据转换成数值类型,需要使用之前训练分类器时使用的标签编码器,因为我们需要保持数据编码规则的前后一致。如果输入数据点里出现了未知数据,标签编码器就会出现异常,因为它不知道如何对这些数据进行编码。比如,如果你把列表中的一个值vhigh改成abcd,那么标签编码器就不知道如何编码了,因为它不知道如何处理这个字符串。这就像是错误检查,看看输入数据点是否有效。
7.现在可以预测数据点的输出类型了:
# 预测并打印特定数据点的输出
output_class = classifier.predict([input_data_encoded])
print("Output class:",
label_encoder[-1].inverse_transform(output_class)[0])
我们使用predict()
方法估计输出类型。如果直接输出编码后的数字标签,对于我们是没有任何意义的。因此,需要使用inverse_transform
方法把标签转换回原来的形式,然后打印出类别预测结果。返回的结果如下:
Output class: acc
随机森林(random forest)由利奥•布雷曼(Leo Breiman,美国加利福尼亚大学伯克利分校)基于分类树的应用研究开发,他将分类树技术加以扩展,集成到蒙特卡洛模拟程序中,从而形成随机森林。随机森林基于创建的一大组树分类器,其中每个分类器都对单个实例进行分类,并在这个过程中对特征进行估计。对比森林中每棵树的分类提议即可得到最后的分类结果,也就是所得票数最多的那一个。
随机森林有3个可调整参数:树的数量、末节点的最小幅度,以及每个节点上可采样的变量数。如果没有过拟合问题,前两个参数只会影响计算量。
2.10节中用随机森林构建了分类器,但我们并不真正了解应该怎么定义参数。我们处理了两个参数:n_estimators
和max_depth
,它们称为超参数(hyperparameter)。分类器的性能就依赖于超参数的设置。当改变超参数时,可以看到分类器性能的变化情况,这时候就可以利用验证曲线进行观察。
验证曲线可以帮我们了解各个超参数对训练得分的影响。通常,我们只对感兴趣的超参数进行调整,其他参数则保持不变。然后就可以通过可视化方法来了解超参数对性能评分的影响了。
生成验证曲线的方法如下。
1.把下面的代码加入到2.10节中用到的Python文件中:
# 验证曲线
import matplotlib.pyplot as plt
from sklearn.model_selection import validation_curve
classifier = RandomForestClassifier(max_depth=4, random_state=7)
parameter_grid = np.linspace(25, 200, 8).astype(int)
train_scores, validation_scores = validation_curve(classifier, X,
y, "n_estimators", parameter_grid, cv=5)
print("##### VALIDATION CURVES #####")
print("\nParam: n_estimators\nTraining scores:\n", train_scores)
print("\nParam: n_estimators\nValidation scores:\n", validation_scores)
在这个例子中,定义分类器时固定了参数max_depth
的值。现在想了解要使用的评估器的最佳数量,于是用parameter_grid
定义搜索空间。评估器数量将在25到200之间以8为步长进行迭代。
2.运行代码,输出如图2-11所示。
图2-11
3.接下来画出图形:
# 画出曲线
plt.figure()
plt.plot(parameter_grid, 100*np.average(train_scores, axis=1),
color='black')
plt.title('Training curve')
plt.xlabel('Number of estimators')
plt.ylabel('Accuracy')
plt.show()
4.得到的图形结果如图2-12所示。
图2-12
5.用同样的方法对max_depth
参数进行验证:
classifier = RandomForestClassifier(n_estimators=20, random_state=7)
parameter_grid = np.linspace(2, 10, 5).astype(int)
train_scores, valid_scores = validation_curve(classifier, X, y,
"max_depth", parameter_grid, cv=5)
print("\nParam: max_depth\nTraining scores:\n", train_scores)
print("\nParam: max_depth\nValidation scores:\n", validation_scores)
我们把n_estimators
参数设成固定值20,然后观察参数max_depth
变化对性能的影响。终端上显示的输出如图2-13所示。
图2-13
6.画出图形:
# 画出曲线
plt.figure()
plt.plot(parameter_grid, 100*np.average(train_scores, axis=1),
color='black')
plt.title('Validation curve')
plt.xlabel('Maximum depth of the tree')
plt.ylabel('Accuracy')
plt.show()
7.运行代码,输出如图2-14所示。
图2-14
本节我们使用scikit-learn库的validation_curve()
函数来画出验证曲线。这个函数会给出不同参数下的训练得分和测试得分,并为评估器的某个特定参数的不同取值计算性能得分。
为评估器选择合适的超参数是设置模型的基本过程。在常见的实现中,网格搜索是最常用的方法之一,这个方法可以选出在一个或多个验证集上具有最高得分的超参数。
学习曲线可以帮我们理解训练数据集的大小对机器学习模型的影响。当计算能力受到限制时,这一点非常有用。下面改变训练数据集的大小,把学习曲线画出来。
学习曲线可以展示出评估器在不同数量的训练样本下的测试得分和训练得分。
生成学习曲线的方法如下。
1.打开2.11节的Python文件,加入下面的代码:
from sklearn.model_selection import validation_curve
classifier = RandomForestClassifier(random_state=7)
parameter_grid = np.array([200, 500, 800, 1100])
train_scores, validation_scores = validation_curve(classifier, X,
y, "n_estimators", parameter_grid, cv=5)
print("\n##### LEARNING CURVES #####")
print("\nTraining scores:\n", train_scores)
print("\nValidation scores:\n", validation_scores)
我们用训练集大小分别为200、500、800和1100的样本来评估性能指标,其中validation_curve
方法中的参数cv
设置成5,表示我们要使用的是五折交叉验证。
2.运行代码,输出结果如图2-15所示。
图2-15
3.下面画出学习曲线图:
# 画出曲线
plt.figure()
plt.plot(parameter_grid, 100*np.average(train_scores, axis=1),
color='black')
plt.title('Learning curve')
plt.xlabel('Number of training samples')
plt.ylabel('Accuracy')
plt.show()
4.得到的输出如图2-16所示。
图2-16
尽管小一点的训练数据集看起来得到的准确率更高,但也容易引发过拟合的问题。如果选择大一点的训练数据集,就需要消耗更多的资源。因此,训练集大小的选择是一个需要结合计算能力进行综合考虑的问题。
本节使用scikit-learn库中的validation_curve
方法画出了学习曲线,这个方法给出了不同训练集大小下交叉验证的训练得分和测试得分。
学习曲线让我们可以通过增加训练数据来检验模型性能的变化,并估计方差误差和偏差误差。如果随着训练集的增大,测试得分和训练得分不再变化,那继续增加训练数据就没有意义了。
本节将根据14个属性来构建一个分类器,用于评估一个人的收入等级。可能的输出类型是“高于50K”和“低于或等于50K”。这个数据集稍微有点复杂,里面的每个数据点都既包含数字又包含字符串。数值型数据是有价值的,在这种情况下,不能用标签编码器进行编码,而需要设计一个既可以处理数值型数据,又可以处理非数值型数据的系统。
本例将使用美国人口普查收入数据集中的数据,可从UCI官网的Census Income Data Set页面下载数据集。
该数据集:
属性列表如下所示。
估算收入阶层的方法如下。
1.这里将使用已作为参考资料提供的income.py
文件,用朴素贝叶斯分类器模型来进行估计。首先导入两个包:
import numpy as np
from sklearn import preprocessing
from sklearn.naive_bayes import GaussianNB
2.然后加载数据集:
input_file = 'adult.data.txt'
# 读取数据
X = []
y = []
count_lessthan50k = 0
count_morethan50k = 0
num_images_threshold = 10000
3.我们将使用数据集中的20 000个数据点,每个类别都是10 000个数据,从而避免出现类别不平衡问题。在训练时,如果使用的大部分数据点属于某个类别,那么分类器就会倾向于此类别,从而引起偏差。因此,最好使用有相同数量数据点的类别。
with open(input_file, 'r') as f:
for line in f.readlines():
if '?' in line:
continue
data = line[:-1].split(', ')
if data[-1] == '<=50K' and count_lessthan50k <
num_images_threshold:
X.append(data)
count_lessthan50k = count_lessthan50k + 1
elif data[-1] == '>50K' and count_morethan50k <
num_images_threshold:
X.append(data)
count_morethan50k = count_morethan50k + 1
if count_lessthan50k >= num_images_threshold and
count_morethan50k >= num_images_threshold:
break
X = np.array(X)
同样,这也是一个带逗号分隔符的文件。我们还是像之前那样处理,把数据加载到变量X
中。
4.我们需要把字符串类型的数据转换成数值型数据,同时保持数值型数据不变。
# 将字符串数据转换成数值型数据
label_encoder = []
X_encoded = np.empty(X.shape)
for i,item in enumerate(X[0])
if item.isdigit():
X_encoded[:, i] = X[:, i]
else:
label_encoder.append(preprocessing.LabelEncoder())
X_encoded[:, i] = label_encoder[-1].fit_transform(X[:, i])
X = X_encoded[:, :-1].astype(int)
y = X_encoded[:, -1].astype(int)
isdigit()
函数用于识别数值型数据。我们已经把所有的字符串数据转换成了数值型数据,并把所有的标签编码保存在一个列表中,便于在后面对未知数据进行分类时使用。
5.下面来训练分类器:
# 构建分类器
classifier_gaussiannb = GaussianNB()
classifier_gaussiannb.fit(X, y)
6.然后把数据划分成训练集和测试集,以便提取性能指标:
# 交叉验证
from sklearn import model_selection
X_train, X_test, y_train, y_test =
model_selection.train_test_split(X, y, test_size=0.25, random_state=5)
classifier_gaussiannb = GaussianNB()
classifier_gaussiannb.fit(X_train, y_train)
y_test_pred = classifier_gaussiannb.predict(X_test)
7.提取性能指标:
# 计算分类器的F1分数
f1 = model_selection.cross_val_score(classifier_gaussiannb,
X, y, scoring='f1_weighted', cv=5)
print("F1 score: " + str(round(100*f1.mean(), 2)) + "%")
返回的结果如下:
F1 score: 75.9%
8.接下来看看如何对单个数据点进行分类,需要先把数据点转换成分类器可以理解的形式:
# 在单个数据实例上进行编码测试
input_data = ['39', 'State-gov', '77516', 'Bachelors', '13',
'Never-married', 'Adm-clerical', 'Not-in-family', 'White', 'Male',
'2174', '0', '40', 'United-States']
count = 0
input_data_encoded = [-1] * len(input_data)
for i,item in enumerate(input_data):
if item.isdigit():
input_data_encoded[i] = int([input_data[i]])
else:
input_data_encoded[i] =
int(label_encoder[count].transform([input_data[i]]))
count = count + 1
input_data_encoded = np.array(input_data_encoded)
9.现在可以进行分类了:
# 预测并打印特定数据点的输出结果
output_class = classifier_gaussiannb.predict([input_data_encoded])
print(label_encoder[-1].inverse_transform(output_class)[0])
和之前一样,用predict
方法获取输出类型,然后用inverse_transform
方法把标签转换成初始形式后打印出来,返回的结果如下:
<=50K
贝叶斯分类器的基本原理是,基于一定的观测,个体以给定的概率属于某个特定类别。这种概率基于的假设是,观测到的特征之间可能是相互依赖的,也可能是相互独立的。如果相互独立,那么贝叶斯分类器就称为朴素贝叶斯分类器。朴素贝叶斯分类器假定对于某个给定的类别,某个特定的特征是否出现和其他特征的出现与否没有关联,这极大地简化了计算。后面将会构建一个朴素贝叶斯分类器。
贝叶斯定理在分类问题上的应用是非常直观的,如果我们观察某个可度量的特征,就可以在观察后估计出这个特征表示某个特定类别的概率。
本节将根据所酿制葡萄酒的化学性质来预测葡萄酒的质量。代码中使用的葡萄酒数据集,包含了177行13列的数据,其中第一列是类别标签。数据来自对3个不同品种的葡萄所酿制的葡萄酒的化学性质分析。这3种葡萄都产自意大利皮埃蒙特区,它们分别是内比奥罗(Nebbiolo)、巴贝拉(Barberas)和格里尼奥利诺(Grignolino),而用内比奥罗酿制的葡萄酒就是名为巴罗洛(Barolo)的红葡萄酒。
数据包含了这3种葡萄酒的某些组成成分的含量,以及一些光谱分析数据。属性列表如下:
第一列数据是葡萄酒类别的标签(0、1或2)。
对葡萄酒的质量进行预测的方法如下。
1.这里将使用已作为参考资料提供的数据文件wine.quality.py
。首先,导入NumPy库并从wine.txt文件加载数据:
import numpy as np
input_file = 'wine.txt'
X = []
y = []
with open(input_file, 'r') as f:
for line in f.readlines():
data = [float(x) for x in line.split(',')]
X.append(data[1:])
y.append(data[0])
X = np.array(X)
y = np.array(y)
上列代码返回了两个数组,输入数据X
和目标y
。
2.接下来把数据划分成两组:训练数据集和测试数据集。训练数据集用于构建模型,测试数据集用于评估训练好的模型对未知数据的拟合效果:
from sklearn import model_selection
X_train, X_test, y_train, y_test =
model_selection.train_test_split(X, y, test_size=0.25, random_state=5)
这里返回了4个数组:X_train
、X_test
、y_train
和y_test
,这些数据将用于训练和验证模型。
3.下面训练分类器:
from sklearn.tree import DecisionTreeClassifier
classifier_DecisionTree = DecisionTreeClassifier()
classifier_DecisionTree.fit(X_train, y_train)
这里使用了决策树算法来训练模型。决策树算法基于用于分类和回归的无参数监督学习方法,目标是使用根据数据特征推断出的决策规则,构建出用于预测目标变量值的模型。
4.接下来计算分类器的准确率:
y_test_pred = classifier_DecisionTree.predict(X_test)
accuracy = 100.0 * (y_test == y_test_pred).sum() / X_test.shape[0]
print("Accuracy of the classifier =", round(accuracy, 2), "%")
返回的结果如下:
Accuracy of the classifier = 91.11 %
5.最后,用混淆矩阵计算出模型性能:
from sklearn.metrics import confusion_matrix
confusion_mat = confusion_matrix(y_test, y_test_pred)
print(confusion_mat)
返回的结果如下:
[[17 2 0]
[ 1 12 1]
[ 0 0 12]]
其中的非对角元素表示错误的分类,可以看出,只有4个分错了。
本节介绍了如何使用决策树算法,基于所酿制葡萄酒的化学性质来预测葡萄酒的质量。决策树用图形化的方式给出了建议或选择,通常两个方案的优劣并不那么容易区分,也就意味着不能立刻做出选择。决策需要经过一系列的层次化条件做出,用表格和数字表示这个逻辑非常困难。事实上,即便可以用表格来表示,但由于判断过程和最终决策并不那么一目了然,读者可能依然会感到困惑。
树状结构通过突出显示我们插入的用于决策或估计的分支,让我们可以非常清晰地获取到需要的信息。决策树技术有助于通过创建具有可能结果的模型来识别策略或追求目标。通过决策树可以直观地看出决策过程和结果,这比数字表格更加有说服力。人类的大脑更乐于先看到解决方案,再返回去理解决策过程,而不是面对一堆的算术分析、百分数和描述结果的数据。
新闻组是关于多种主题的讨论组,它通过遍布世界各地的新闻服务器来收集客户端的信息并进行转发,一方面将信息转发给所有订阅用户,另一方面转发给网络上的其他新闻服务器。这项技术的成功源于用户间的相互讨论,每个人都应遵守新闻组的规则。
本节将构建一个分类器模型,用于将某一主题的成员归类到特定的讨论组。这有助于验证主题是否和讨论组相关。我们将使用包含在20个新闻组数据集中的数据,可自行下载20 Newsgroups数据集。
该数据集收集了20 000份新闻组文档,这些文档划归20个不同的新闻组。这些文档最初由肯兰格(Ken Lang)收集,并在其论文“Newsweeder paper: Learning to filter netnews”中发布。这个数据集在处理文本分类问题时特别有用。
本节将介绍如何对新闻组热门话题进行分类。
1.这里使用已作为参考资料给出的post.classification.py
文件。先导入数据集:
from sklearn.datasets import fetch_20newsgroups
数据集包含在sklearn.datasets库中,这样恢复数据就会非常方便。预料中的,数据集包含了和20个新闻组相关的发布,我们将把分析限制在下面两个新闻组:
NewsClass = ['rec.sport.baseball', 'rec.sport.hockey']
2.下载数据:
DataTrain = fetch_20newsgroups(subset='train',categories=NewsClass,
shuffle=True, random_state=42)
3.数据有两个属性:data
和target
。很明显,data
表示输入数据,target
表示输出结果。确认下选择的新闻组:
print(DataTrain.target_names)
返回的结果如下:
['rec.sport.baseball', 'rec.sport.hockey']
4.接下来确认数组形状:
print(len(DataTrain.data))
print(len(DataTrain.target))
返回的结果如下:
1197
1197
5.下面使用CountVectorizer()
函数从文本中提取特征:
from sklearn.feature_extraction.text import CountVectorizer
CountVect = CountVectorizer()
XTrainCounts = CountVect.fit_transform(DataTrain.data)
print(XTrainCounts.shape)
返回的结果如下:
(1197, 18571)
这样,我们就统计出了单词出现的频数。
6.现在把文档中每个单词出现的频数除以文档所有单词的总频数:
from sklearn.feature_extraction.text import TfidfTransformer
TfTransformer = TfidfTransformer(use_idf=False).fit(XTrainCounts)
XTrainNew = TfTransformer.transform(XTrainCounts)
TfidfTransformer = TfidfTransformer()
XTrainNewidf = TfidfTransformer.fit_transform(XTrainCounts)
7.可以构建分类器了:
from sklearn.naive_bayes import MultinomialNB
NBMultiClassifier = MultinomialNB().fit(XTrainNewidf, DataTrain.target)
8.最后,计算出分类器的准确率:
NewsClassPred = NBMultiClassifier.predict(XTrainNewidf)
accuracy = 100.0 * (DataTrain.target == NewsClassPred).sum() /
XTrainNewidf.shape[0]
print("Accuracy of the classifier =", round(accuracy, 2), "%")
得到的输出结果如下:
Accuracy of the classifier = 99.67 %
本节构建了一个用于把某个新闻组主题的成员划归到特定讨论组的分类器。我们使用了标记化(tokenization)方法来提取文本特征。在标记化阶段,识别出了组成句子的原子级元素也就是标记(token)。基于识别出的标记,就可以对句子本身进行分析和估计了。提取出文本特征后,基于多项式朴素贝叶斯算法的分类器就构造好了。
当特征表示的是文档中单词(文本或图像)的频数时,就可以使用多项式朴素贝叶斯算法对文本或图像进行分析。