零、前言
| | “有脑子的好处是可以学习,无知可以被知识取代,小知识可以逐渐堆积成大堆。” | |
| | - 道格拉斯·霍夫斯塔德 |
机器学习通常被称为人工智能中真正起作用的部分。它的目标是找到一个基于现有数据集(训练集)的函数,以便以最高可能的正确性预测以前看不到的数据集(测试集)的结果。这或者以标签和类的形式出现(分类问题),或者以连续值的形式出现(回归问题)。机器学习在现实应用中的具体例子从预测未来股票价格到从一组文档中对作者的性别进行分类。在这本书里,最重要的机器学习概念,以及适用于更大数据集的方法,将通过 Python 中的实际例子向读者阐明。我们将研究监督学习(分类&回归),以及已被发现适用于较大数据集的无监督学习(如主成分分析(PCA)、聚类和主题建模)。
谷歌、脸书和优步等大型信息技术公司声称他们成功地大规模应用了这种机器学习方法,从而引起了很大的轰动。随着大数据的出现和可用,对可扩展机器学习解决方案的需求呈指数级增长,许多其他公司和个人已经开始渴望成熟大数据集中隐藏相关性的成果。不幸的是,大多数学习算法不能很好地扩展,这使得台式计算机或更大的计算集群上的 CPU 和内存都很紧张。在这期间,即使大数据已经过了炒作的高峰期,可扩展的机器学习解决方案也并不充裕。
坦率地说,我们仍然需要解决许多瓶颈,即使是我们很难归类为大数据的数据集(想想高达 2GB 甚至更小的数据集)。这本书的使命是提供方法(有时是非传统的方法),在更大规模上应用最强大的开源机器学习方法,而不需要昂贵的企业解决方案或大型计算集群。在本书中,我们将使用 Python 和其他一些现成的解决方案,它们很好地集成在可扩展的机器学习管道中。阅读这本书是一次旅程,它将重新定义你对机器学习的了解,将你置于真正大数据分析的起点。
这本书涵盖了什么
第 1 章、可伸缩性的第一步,在正确的视角下设置了可伸缩机器学习的问题,并让你熟悉我们将在本书中使用的工具。
第 2 章、Scikit-learn 中的可扩展学习,讨论了随机梯度下降(SGD)的策略,在此策略中,我们减轻了内存消耗;它是基于核心外学习的主题。我们还将讨论可以处理各种数据的数据准备技术,例如哈希技巧。
第 3 章、快速学习支持向量机涵盖了能够以支持向量机的形式发现非线性的流算法。我们将介绍 Scikit-learn 的替代方案,例如 LIBLINEAR 和 Vowpal Wabbit,它们虽然作为外壳命令运行,但可以很容易地被 Python 脚本包装和指导。
第 4 章、神经网络和深度学习,提供了在 antio 框架内应用深度神经网络的有用策略,以及与 H2O 的大规模应用。尽管这是一个热门话题,但成功应用它可能是一个相当大的挑战,更不用说提供可扩展的解决方案了。我们还将借助带有自动编码器的无监督预训练。
第 5 章、使用 TensorFlow 进行深度学习,涵盖了有趣的深度学习技术以及神经网络的在线方法。尽管 TensorFlow 还处于起步阶段,但该框架提供了优雅的机器学习解决方案。我们还将在 TensorFlow 环境中利用 Keras 卷积神经网络的功能。
第 6 章、分级和回归树,解释了随机森林、梯度增强和 XGboost 的可扩展解决方案。CART 是分类和回归树的缩写,是一种机器学习方法,通常应用于集成方法的框架中。我们还将提供使用 H2O 的大规模应用的例子。
第 7 章、无监督学习在规模下,深入到无监督学习,因为我们将涵盖主成分分析、聚类分析和主题建模,使用正确的方法来扩大它们。
第 8 章、分布式环境–Hadoop 和 Spark ,教我们如何在虚拟机环境中设置 Spark,从单机转向计算网络范式。由于 Python 可以轻松地在一个机器集群上粘合和增强我们的工作,因此利用 Hadoop 集群的力量变得轻而易举。
第 9 章、带 Spark 的实用机器学习与 Spark 一起行动,教授立即开始操作数据和在大数据集上构建预测模型的所有要领。
附录、对图形处理器和图形处理器的介绍,将涵盖图形处理器和图形处理器计算的基础知识。如果您的系统允许,它将帮助您安装和准备您的环境,以便在图形处理器上使用安诺。
这本书你需要什么
执行本书中提供的代码示例需要在 macOS、Linux 或微软 Windows 上安装 Python 2.7 或更高版本。
整本书中的例子将频繁使用 Python 的基本库,如 SciPy、NumPy、Scikit-learn 和 StatsModels,以及少量的 matplotlib 和 pandas,用于科学和统计计算。我们还将利用一个名为 H2O 的核心外云计算应用。
这本书高度依赖于 Jupyter 及其由 Python 内核驱动的笔记本。我们将使用它的最新版本 4.1 来写这本书。
第一章将为您提供设置 Python 环境、这些核心库以及所有必要工具的所有分步说明和一些有用的提示。
这本书是给谁的
这本书适合有抱负和实际的数据科学从业者、开发人员,以及打算处理大型复杂数据集的所有人。我们努力使这本书尽可能为更多的读者所理解。然而,考虑到这本书的主题相当高级,建议读者熟悉基本的机器学习概念,如分类和回归、误差最小化函数和交叉验证,但不是严格强制的。
我们还假设有一些 Python、Jupyter Notebooks 和命令行执行的经验,以及合理的数学知识水平,以掌握我们提出的各种大型解决方案背后的概念。文本是以其他语言(R、Java 和 MATLAB)的程序员可以遵循的风格编写的。理想情况下,它非常适合(但不限于)熟悉机器学习并对利用 Python 感兴趣的数据科学家,相对于其他语言,如 R 或 MATLAB,因为它具有计算、内存和输入/输出能力。
惯例
在这本书里,你会发现许多区分不同种类信息的文本样式。以下是这些风格的一些例子和对它们的意义的解释。
文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、伪 URL、用户输入和 Twitter 句柄如下所示:“检查线性模型时,首先检查coef_属性。”
代码块设置如下:
from sklearn import datasets
iris = datasets.load_iris()
由于我们将在大多数示例中使用 Jupyter Notebooks,因此期望包含代码块的单元格中总是有一个输入(标记为In:)和一个输出(标记为Out:)。在您的电脑上,您只需在In:后输入代码,并检查结果是否与Out:内容一致:
In: clf.fit(X, y)
Out: SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0, degree=3, gamma=0.0, kernel='rbf', max_iter=-1, probability=False, random_state=None, shrinking=True, tol=0.001, verbose=False)
当一个命令应该在终端命令行中给出时,你会发现这个命令带有前缀$>,否则,如果是 Python REPL,它的前面会有>>>:
$>python
>>> import sys
>>> print sys.version_info
新名词和重要词语以粗体显示。你在屏幕上看到的单词,例如,在菜单或对话框中,出现在文本中,如下所示:“通常,你只需在单元格中的 In: 后键入代码并运行它。”
注
警告或重要提示会出现在这样的框中。
型式
提示和技巧是这样出现的。
读者反馈
我们随时欢迎读者的反馈。让我们知道你对这本书的看法——你喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它有助于我们开发出你真正能从中获益的标题。
要给我们发送一般反馈,只需发送电子邮件<[feedback@packtpub.com](mailto:feedback@packtpub.com)>,并在您的邮件主题中提及书名。
如果你对某个主题有专业知识,并且对写作或投稿感兴趣,请参见我们位于www.packtpub.com/authors的作者指南。
客户支持
现在,您已经自豪地拥有了一本书,我们有许多东西可以帮助您从购买中获得最大收益。
下载示例代码
你可以从你在http://www.packtpub.com的账户下载这本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问http://www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
- 使用您的电子邮件地址和密码登录或注册我们的网站。
- 将鼠标指针悬停在顶部的 SUPPORT 选项卡上。
- 点击代码下载&勘误表。
- 在搜索框中输入图书名称。
- 选择要下载代码文件的书籍。
- 从您购买这本书的下拉菜单中选择。
- 点击代码下载。
您也可以通过点击 Packt 出版网站图书网页上的代码文件按钮来下载代码文件。可以通过在搜索框中输入图书名称来访问该页面。请注意,您需要登录您的 Packt 帐户。
下载文件后,请确保使用最新版本的解压缩文件夹:
- 视窗系统的 WinRAR / 7-Zip
- zipeg/izp/un ARX for MAC
- 适用于 Linux 的 7-Zip / PeaZip
这本书的代码包也托管在 GitHub 上,网址为。我们还有来自丰富的图书和视频目录的其他代码包,可在https://github.com/PacktPublishing/获得。看看他们!
型式
在 Github 上,你还可以找到 Windows 的 Vowpal Wabbit 可执行文件。
下载本书的彩色图片
我们还为您提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。彩色图像将帮助您更好地理解输出中的变化。您可以从https://www . packtpub . com/sites/default/files/downloads/largescalechaningwith python _ color images . pdf下载此文件。
勘误表
尽管我们尽了最大努力来确保我们内容的准确性,但错误还是会发生。如果你在我们的某本书里发现了错误——可能是文本或代码中的错误——如果你能向我们报告,我们将不胜感激。通过这样做,你可以让其他读者免受挫折,并帮助我们改进这本书的后续版本。如果您发现任何勘误表,请访问http://www.packtpub.com/submit-errata,选择您的书籍,点击勘误表提交表链接,并输入您的勘误表的详细信息。一旦您的勘误表得到验证,您的提交将被接受,勘误表将上传到我们的网站或添加到该标题勘误表部分下的任何现有勘误表列表中。
要查看之前提交的勘误表,请前往https://www.packtpub.com/books/content/support并在搜索栏中输入图书名称。所需信息将出现在勘误表部分。
盗版
互联网上版权材料的盗版是所有媒体的一个持续问题。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上遇到任何形式的我们作品的非法拷贝,请立即向我们提供位置地址或网站名称,以便我们寻求补救。
请通过<[copyright@packtpub.com](mailto:copyright@packtpub.com)>联系我们,获取疑似盗版资料的链接。
我们感谢您在保护我们的作者方面的帮助,以及我们为您带来有价值内容的能力。
问题
如果您对本书的任何方面有问题,可以在<[questions@packtpub.com](mailto:questions@packtpub.com)>联系我们,我们将尽最大努力解决问题。
一、可扩展性的第一步
欢迎阅读这本书,关于 Python 的可扩展机器学习。
在本章中,我们将讨论如何使用 Python 从大数据中有效学习,以及如何使用您的单台机器或其他机器的集群来学习,例如,您可以从 【亚马逊网络服务】 ( AWS )或谷歌云平台获得这些机器。
在书中,我们将使用 Python 实现可扩展的机器学习算法。这意味着它们可以处理大量数据,并且不会因为内存限制而崩溃。它们还需要合理的时间,这对于数据科学原型和生产部署来说是可以管理的。章节围绕解决方案(如流数据)、算法(如神经网络或树的集合)和框架(如 Hadoop 或 Spark)进行组织。我们还将为您提供一些关于机器学习算法的基本提示,并解释如何使它们可扩展并适用于大规模数据集的问题。
给定这样的前提作为开始,你将需要学习基础知识(以便弄清楚写这本书的视角),并设置所有基本工具来立即开始阅读章节。
在本章中,我们将向您介绍以下主题:
- 可伸缩性实际上意味着什么
- 处理数据时应该注意哪些瓶颈
- 这本书会帮助你解决什么样的问题
- 如何使用 Python 有效地大规模分析数据集
- 如何快速设置机器来执行本书中的示例
让我们一起围绕使用 Python 的可扩展解决方案开始这一旅程!
详细说明可扩展性
即使现在炒作的是大数据,大数据集早在术语本身被创造出来之前就已经存在了。来自射电望远镜的大量文本、脱氧核糖核酸序列和大量数据一直是科学家和数据分析师面临的挑战。由于大多数机器学习算法的计算复杂度为 O(n 2 )甚至 O(n 3 ),其中 n 是训练实例的数量,数据科学家和分析师之前已经通过求助于可以更高效的数据算法来面对来自海量数据集的挑战。在大数据集的情况下,当机器学习算法在适当的设置后可以工作时,它被认为是可扩展的。数据集可能很大,因为有大量的案例或变量,或者两者都有,但是可伸缩的算法可以有效地处理它,因为它的运行时间几乎随着问题的大小而线性增加。因此,这只是一个用更多数据交换 1:1 更多时间(或更多计算能力)的问题。相反,机器学习算法在面对大量数据时无法扩展;它只是停止工作或运行,运行时间以非线性方式增加,例如指数级增加,从而使学习变得不可行。
廉价数据存储、大 RAM 和多处理器 CPU 的引入极大地改变了一切,提高了单台笔记本电脑分析大量数据的能力。在过去的几年里,另一个巨大的游戏改变者出现了,将注意力从单一的强大机器转移到了商品计算机集群(更便宜、更容易获得的机器)。这一重大变化是引入了 MapReduce 和开源框架 Apache Hadoop 及其 Hadoop 分布式文件系统 ( HDFS )以及通用计算机网络上的并行计算。
为了弄清楚这两种变化是如何深刻和积极地影响你解决大规模问题的能力的,我们应该首先从实际上阻止你(并且仍然阻止,这取决于你的问题有多大)分析大数据集的原因开始。
无论您的问题是什么,您最终都会发现,由于以下任何一个限制,您无法分析您的数据:
- 影响分析执行时间的计算
- I/O 会影响您在一个时间单位内可以从存储器中取出多少数据
- 内存影响您一次可以处理多少大数据
你的电脑有局限性,这将决定你是否能从你的数据中学习,以及在你碰壁之前需要多长时间。计算限制出现在许多密集的计算中,输入/输出问题会使您对数据的即时访问成为瓶颈,最后,内存限制会限制您只接受部分数据,从而限制您可能访问的矩阵计算类型或估计的精度甚至准确性。
就您正在分析的数据而言,这些硬件限制中的每一项也会对您产生不同的严重影响:
- 高数据,其特点是大量的案例
- 广泛的数据,其特征是大量的特征
- 又高又宽的数据,既有大量的案例又有大量的特征
- 稀疏数据,其特征在于大量的零条目或可被转换成零的条目(也就是说,数据矩阵可以是高的和/或宽的但有信息,但不是所有的矩阵条目都有信息值)
最后,它归结为您将使用的算法,以便从数据中学习。每个算法都有自己的特点,能够使用受偏差或方差影响不同的解决方案来映射数据。因此,关于你的问题,到目前为止,你已经通过机器学习解决了,你认为,基于经验或经验测试,某些算法可能比其他算法工作得更好。对于大规模问题,在决定算法时,您必须添加其他不同的考虑因素:
- 你的算法有多复杂;也就是说,如果数据中的行数和列数以线性或非线性方式影响计算量。大多数机器学习解决方案基于二次或三次复杂度的算法,因此强烈限制了它们对大数据的适用性。
- 你的模型有多少参数;在这里,这不仅仅是估计值的方差问题(过度拟合),而是计算它们可能需要的时间问题。
- 如果优化过程是可并行的;也就是说,您是否可以轻松地将计算分散到多个节点或 CPU 内核中,或者您是否必须依赖单个、顺序的优化过程?
- 算法应该一次性从所有数据中学习,还是可以用单个例子或小批量数据来代替?
如果你用数据特征和这些类型的算法交叉评估硬件限制,你会得到一大堆可能有问题的组合,这些组合会阻止你从大规模分析中得到结果。从实践的角度来看,所有有问题的组合都可以通过三种方法解决:
- 纵向扩展,即通过软件或硬件修改(更多内存、更快的 CPU、更快的存储磁盘和使用图形处理器)来提高单台机器的性能
- 横向扩展,即利用外部资源,即其他存储磁盘和其他中央处理器(或图形处理器),在多台机器上分配计算(和性能)
- 向上扩展和向外扩展,也就是说,将最好的向上扩展和向外扩展解决方案结合在一起
制作大规模的例子
一些激励性的例子可能会让事情对你来说更加清晰和难忘。让我们举两个简单的例子:
- 能够预测点击率 ( CTR )可以帮助你在互联网广告如此广泛、扩散、蚕食传统媒体传播大份额的这些天赚得相当多
- 当你的客户在搜索你的网站所提供的产品和服务时,能够向他们提供正确的信息,如果你能猜出在他们的结果顶部放什么,就能真正提高你的销售机会
在这两种情况下,我们都有相当大的数据集,因为它们是由用户在互联网上的交互产生的。
根据我们心目中的业务(我们可以想象这里的一些大公司),在我们的两个例子中,我们每天都在谈论数百万个数据点。在广告的情况下,数据当然很高,是一个连续的信息流,因为最近的数据,更能代表市场和消费者,取代了旧的数据。在搜索引擎的例子中,数据是广泛的,被你提供给你的客户的结果所提供的特征所丰富:例如,如果你在旅行业务中,你将有相当多的关于酒店、位置和所提供的服务的特征。
显然,可伸缩性是这两个问题的一个问题:
- 你必须从每天都在增长的数据中学习,你必须快速学习,因为当你在学习的时候,新的数据不断到来。然而,你必须处理显然无法存储的数据,因为矩阵太高或太大。
- 为了适应新的数据,你经常需要更新你的机器学习模型。你需要一种能够及时处理信息的算法。由于数据量的原因,O(n2)或 O(n3)的复杂性可能是您无法处理的;你需要一些能够以较低复杂度工作的算法(比如 O(n))或者通过划分数据使得 n 会小很多很多。
- 你必须能够快速预测,因为预测只能交付给新客户。同样,算法的复杂性也很重要。
可伸缩性问题可以通过一种或多种方式解决:
- 通过降低问题的维度来扩大规模;例如,在搜索引擎的情况下,通过有效地选择要使用的相关特征
- 使用正确的算法扩大规模;例如,在广告数据的情况下,有适当的算法来有效地从流中学习
- 通过利用多台机器扩展学习过程
- 在单个服务器上使用多处理和矢量化有效地扩展部署过程
在这本书里,我们将为你指出每一个提出的解决方案或算法能解决什么样的实际问题。您可以自动将特定的时间和执行约束(中央处理器、内存或输入/输出)与我们建议的最合适的解决方案联系起来。
介绍蟒蛇
由于我们的论文将依赖于 Python——我们为本书选择的开源语言——在阐明 Python 如何轻松帮助您解决大规模数据问题之前,我们必须停下来简单介绍一下这种语言。
Python 创建于 1991 年,是一种通用的、解释的、面向对象的语言,它已经缓慢而稳定地征服了科学界,并成长为一个成熟的数据处理和分析专用包生态系统。它可以让你进行无数次快速的实验,轻松的理论开发,以及快速的科学应用部署。
作为机器学习实践者,你会发现使用 Python 很有趣,原因有多种:
- 它为数据分析和机器学习提供了一个大型、成熟的软件包系统。它保证您将在数据分析过程中获得您可能需要的一切,有时甚至更多。
- 它非常通用。无论你的编程背景或风格是什么(面向对象还是过程),你都会喜欢用 Python 编程。
- 如果你还不知道,但是你很了解其他语言,比如 C/C++或者 Java,那么学习和使用起来非常简单。掌握基础知识后,没有比立即开始编码更好的学习方法了。
- 它是跨平台的;您的解决方案将在 Windows、Linux 和 macOS 系统上完美流畅地运行。你不必担心便携性。
- 虽然解释了,但与 R、MATLAB 等其他主流数据分析语言相比,无疑是快了(虽然比不上 C、Java 和新出现的 Julia 语言)。
- 由于其最小的内存占用和出色的内存管理,它可以处理内存中的大数据。当你使用数据角力的各种迭代和重复加载、转换、切割、切片、保存或丢弃数据时,内存垃圾收集器通常会保存天。
型式
如果你还不是专家(实际上我们需要一些 Python 的基础知识才能充分利用这本书),你可以阅读关于语言的所有内容,并直接从位于https://www.python.org/的 Python 基础上找到基本安装文件。
使用 Python 进行扩展
Python 是一种解释语言;它运行从内存中读取您的脚本,并在运行时执行它,从而访问必要的资源(文件、内存中的对象等)。除了解释之外,使用 Python 进行数据分析和机器学习时需要考虑的另一个重要方面是 Python 是单线程的。单线程意味着任何 Python 程序从脚本的开始到结束都是按顺序执行的,并且 Python 不能利用您的计算机中可能存在的多线程和处理器提供的额外处理能力(现在大多数计算机都是多核的)。
在这种情况下,使用 Python 进行扩展可以通过不同的策略来实现:
- 编译 Python 脚本,以实现更快的执行速度。虽然使用例如PyPy—一个及时的 ( JIT )编译器可以在http://pypy.org/找到,但是我们实际上在我们的书中没有采用这样的解决方案,因为它需要从头开始用 Python 编写算法。
- 使用 Python 作为包装语言;从而将 Python 执行的操作与外部库和程序的执行放在一起,其中一些能够进行多核处理。在我们的书中,当我们调用支持向量机的库 ( LIBSVM )或程序如Vowpal Wabbit(VW)、 XGBoost 或 H2O 来执行机器学习活动时,你会发现许多这样的例子。
- 有效使用矢量化技术,即矩阵计算专用库。这可以通过使用 NumPy 或熊猫来实现,这两个都使用来自图形处理器的计算。GPU 就像多核 CPU,每一个都有自己的内存和并行处理计算的能力(你可以算出它们有多个微小的内核)。尤其是在使用神经网络时,基于图形处理器的矢量化技术可以令人难以置信地加快计算速度。然而,图形处理器有其自身的局限性;首先,他们的可用内存在将你的数据传递到他们的内存并将结果传回你的 CPU 时有一定的 I/O,他们需要通过特殊的 API 进行并行编程,比如 CUDA 针对 NVIDIA 制造的 GPU(所以你必须安装合适的驱动和程序)。
- 将一个大问题分成几个块,并在内存中一次解决一个块(分治算法)。这将导致对内存或磁盘中的数据进行分区或二次采样,并管理机器学习问题的近似解决方案,这非常有效。需要注意的是,分区和二次采样都可以用于案例和特征(以及两者)。如果原始数据保存在磁盘存储器上,输入/输出限制将成为最终性能的决定性因素。
- 根据您将使用的学习算法,有效地利用多处理和多线程。一些算法自然能够将它们的操作分成并行的操作。在这种情况下,唯一的约束就是你的 CPU 和内存(因为你的数据必须为你将要使用的每个并行工作者复制)。其他一些算法将利用多线程,从而在相同的内存块上同时管理更多的操作。
使用 Python 横向扩展
横向扩展解决方案只需将多台机器连接成一个集群。当您连接机器(横向扩展)时,您还可以使用更强大的配置(从而增加中央处理器、内存和输入/输出),应用我们在上一段中提到的技术并提高它们的性能,来扩展每台机器。
通过连接多台机器,您可以以并行方式利用它们的计算能力。您的数据将分布在多个存储磁盘/内存中,通过让每台机器仅处理其可用数据(即,其自己的存储磁盘或内存)来限制输入/输出传输。
在我们看来,这意味着通过以下方式有效利用外部资源:
- H2O 框架
- Hadoop 框架及其组件,如 HDFS、MapReduce 和另一个资源协商者 ( 纱
- Hadoop 之上的 Spark 框架
这些框架中的每一个都将由 Python 控制(例如,Spark 通过其名为 pySpark 的 Python 接口)。
用于大规模机器学习的 Python
鉴于机器学习有许多有用的软件包,并且它是一种在数据科学家中非常流行的编程语言,Python 是我们在本书中介绍的所有代码的首选语言。
在本书中,当必要时,我们将提供进一步的说明,以便安装任何进一步必要的库或工具。在这里,我们将开始安装基础,即 Python 语言和最常用的计算和机器学习包。
在 Python 2 和 Python 3 之间选择
在开始之前,重要的是要知道 Python 有两个主要分支:版本 2 和版本 3。随着许多核心功能的发生变化,为一个版本构建的脚本有时与另一个版本不兼容(如果不出现错误和警告,它们将无法工作)。虽然第三个版本是最新的,但旧版本仍然是科学领域使用最多的版本,也是许多操作系统的默认版本(主要是为了升级时的兼容性)。当版本 3 发布时(2008 年),大多数科学包还没有准备好,所以科学界坚持使用以前的版本。幸运的是,从开始,几乎所有的包都被更新了,只留下了少数几个(参见http://py3readiness.org的兼容性概述)作为 Python 3 兼容性的孤儿。
尽管 Python 3(我们不应该忘记,它是 Python 的未来)最近越来越受欢迎,但 Python 2 仍然在数据科学家和数据分析师中广泛使用。此外,很长时间以来,Python 2 一直是默认的 Python 安装(例如,在 Ubuntu 上),所以这是大多数读者应该已经准备好的最有可能的版本。出于所有这些原因,我们将在本书中采用 Python 2。这不仅仅是对旧技术的热爱,这只是为了让Python 大规模机器学习能够被最大的受众所接受的一个实际选择:
- Python 2 代码将立即面向现有的数据专家受众。
- Python 3 用户会发现很容易转换我们的脚本,以便在他们喜欢的 Python 版本下工作,因为我们编写的代码很容易转换,我们将提供我们所有脚本和笔记本的 Python 3 版本,可从 Packt 网站免费下载。
型式
如果你需要深入了解 Python 2 和 Python 3 的区别,我们建议阅读这个关于编写 Python 2-3 兼容代码的网页:
http://python-future.org/compatible_idioms.html
从 Python-Future 开始,你可能还会发现关于如何将 Python 2 代码转换为 Python 3 的阅读很有用:
http://python-future.org/automatic_conversion.html
安装 Python
作为第一步,我们将为数据科学创建一个工作环境,您可以使用它来复制和测试书中的示例,并原型化您自己的大型解决方案。
无论您打算用什么语言开发应用,Python 都将为您提供一个轻松的时间来获取数据,根据数据构建模型,并提取在生产环境中进行预测所需的正确参数。
Python 是一种开源的、面向对象的、跨平台的编程语言,与它的直接竞争对手(例如,C/C++和 Java)相比,它产生的代码非常简洁易读。它允许您在很短的时间内构建一个工作的软件原型,并在未来对其进行测试、维护和扩展。它已经成为数据科学家工具箱中使用最多的语言,因为最终,它是一种通用语言,由于有大量可用的包,可以轻松快速地帮助您解决各种常见和特殊的问题,因此变得非常灵活。
分步安装
如果你从未使用过 Python(但这并不意味着你的机器上可能还没有安装它),你需要首先从项目的主网站https://www.python.org/downloads/下载安装程序(记住,我们使用的是版本 3),然后在你的本地机器上安装它。
本节为您提供了对机器上可安装内容的完全控制。当您打算使用 Python 作为原型和生产语言时,这非常有用。此外,它可以帮助您跟踪您正在使用的包的版本。无论如何,请注意,逐步安装确实需要时间和精力。相反,安装一个现成的科学发行版将减轻安装程序的负担,并且它可能非常适合首先开始和学习,因为它可以为您节省大量时间,尽管它将一次性在您的计算机上安装大量软件包(在大多数情况下,您可能永远不会使用)。因此,如果你想立即开始,不想为控制你的安装费太多心思,跳过这一部分,进入下一部分,科学发行版。
作为一种多平台编程语言,您可以找到运行在 Windows 或类似 Linux/Unix 操作系统上的计算机的安装程序。请记住,一些 Linux 发行版(如 Ubuntu)已经在存储库中打包了 Python 2,这使得安装过程更加容易。
-
打开一个 Python shell,在终端中键入
python,或者点击 Python 图标。 -
然后,要测试安装,请在 Python 交互式外壳或其由 Python 的标准 IDE 或其他解决方案(如 Spyder 或 PyCharm)提供的读取-评估-打印循环 ( REPL )界面中运行以下代码:
>>> import sys >>> print sys.version
如果出现语法错误,这意味着您运行的是 Python 2 而不是 Python 3。如果您没有遇到错误,并且您可以阅读到您的 Python 版本是 3.4.x 或 3.5.x(在撰写本文时,最新版本是 3.5.2),那么恭喜您运行了我们为这本书选择的 Python 版本。
为了澄清,当在终端命令行中给出一个命令时,我们在命令前面加上$。否则,如果是 Python REPL,前面会有>>>。
包装的安装
根据您的系统和过去的安装,Python 可能不会与您需要的所有东西捆绑在一起,除非您已经安装了一个发行版(另一方面,它通常填充了比您可能需要的更多的东西)。
要安装您需要的任何软件包,您可以使用pip或easy_install命令;但是,easy_install在未来会被放弃, pip 比它有重要的优势。
pip 是一个直接上网安装 Python 包并从 Python 包索引(https://pypi.python.org/pypi)中挑选的工具。 PyPI 是一个包含第三方开源包的存储库,这些包由它们的作者不断维护并存储在存储库中。
由于以下原因,最好使用pip安装所有设备:
- 它是 Python 的首选包管理器,从 Python 2.7.9 和 Python 3.4 开始,默认情况下它包含在 Python 二进制安装程序中
- 它提供卸载功能
- 无论出于什么原因,如果软件包安装失败,它都会回滚并清除您的系统
pip命令在命令行中运行,使得 Python 包的安装、升级和删除过程变得轻而易举。
正如我们提到的,如果您至少运行 Python 2.7.9 或 Python 3.4,则pip命令应该已经存在。要确保本地机器上安装了哪些工具,如果出现任何错误,请直接使用以下命令进行测试:
$ pip –V
在某些 Linux 和 Mac 安装中,安装的是 Python 3 而不是 Python 2,命令可能以pip3的形式出现,因此如果您在查找pip时收到错误,请尝试运行以下命令:
$ pip3 –V
如果是这样的话,记住pip3只适合在 Python 3 上安装软件包。由于我们在书中使用 Python 2(除非您决定使用最新的 Python 3.4),pip应该始终是您安装软件包的选择。
或者,您也可以测试旧的easy_install命令是否可用:
$ easy_install --version
型式
尽管有pip和它的优点,如果你在 Windows 上工作,使用easy_install还是有意义的,因为pip不会安装二进制包;因此,如果您在安装软件包时遇到意想不到的困难,easy_install可以拯救您的一天。
如果你的测试以错误结束,你真的需要从头开始安装 pip(这样做的同时也是easy_install)。
要安装 pip,只需按照在https://pip.pypa.io/en/stable/installing/给出的说明进行操作。最安全的方法是从https://bootstrap.pypa.io/get-pip.py下载get-pip.py脚本,然后使用以下命令运行:
$ python get-pip.py
顺便说一下,脚本还会从【https://pypi.python.org/pypi/setuptools】安装设置工具,其中包含easy_install。
作为替代,如果你运行的是一个类似 Debian/Ubuntu 的 Unix 系统,那么一个快速的快捷方式就是使用apt-get安装所有的东西:
$ sudo apt-get install python3-pip
检查完这个基本要求后,您现在可以安装运行本书中提供的示例所需的所有软件包了。要安装通用的<pk>包,您只需要运行以下命令:
$ pip install <pk>
或者,如果您更喜欢使用easy_install,您也可以运行以下命令:
$ easy_install <pk>
之后将下载并安装<pk>包及其所有依赖项。
如果您不确定是否安装了库,请尝试在其中导入模块。如果 Python 解释器引发一个 ImportError 错误,那么可以断定包还没有安装。
举个例子吧。安装了 NumPy 库后会出现这种情况:
>>> import numpy
如果没有安装,就会出现这种情况:
>>> import numpy
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named numpy
在后一种情况下,在导入之前,您需要通过pip或easy_install安装它。
注意不要将包与模块混淆。用pip,你安装一个包;在 Python 中,您可以导入一个模块。有时,包和模块有相同的名称,但在许多情况下,它们不匹配。例如, sklearn 模块包含在名为 Scikit-learn 的包中。
套餐升级
通常情况下,你会发现自己不得不升级一个包,因为新的版本要么是依赖项所需要的,要么是你想要使用的附加功能。为此,首先通过浏览__version__属性来检查您已经安装的库的版本,如下面使用 NumPy 包的示例所示:
>>> import numpy
>>> numpy.__version__ # 2 underscores before and after
'1.9.0'
现在,如果您想将其更新到较新的版本,确切地说是 1.9.2 版本,您可以从命令行运行以下命令:
$ pip install -U numpy==1.9.2
或者(但我们不建议这样做,除非有必要),您也可以使用以下命令:
$ easy_install --upgrade numpy==1.9.2
最后,如果您只是想将其升级到最新版本,只需运行以下命令:
$ pip install -U numpy
您也可以运行easy_install替代选项:
$ easy_install --upgrade numpy
科学分布
正如您到目前为止所读到的,对于数据科学家来说,创建工作环境是一项耗时的操作。你首先需要安装 Python,然后,一个接一个,你可以安装所有你需要的库。(有时,安装程序可能不会像您之前希望的那样顺利。)
如果您想节省时间和精力,并想确保您有一个可以使用的完全工作的 Python 环境,您可以下载、安装和使用科学的 Python 发行版。除了 Python,它们还包括各种各样的预装包,有时它们甚至有额外的工具和 IDE 设置供您使用。其中一些在数据科学家中非常有名,在接下来的部分中,您将发现其中两个包的一些关键特性,我们发现它们非常有用和实用。
要立即专注于本书的内容,我们建议您首先及时下载并安装一个科学的发行版,如 Anaconda (在我们看来,这是周围最完整的一个),并在练习本书中的示例后,决定完全卸载该发行版并单独设置 Python,它可以附带您的项目所需的软件包。
同样,如果可能的话,下载并安装包含 Python 3 的版本。
我们建议您尝试的第一个包是 Anaconda(https://www.continuum.io/downloads),这是由 Continuum Analytics 提供的 Python 发行版,包括近 200 个包,包括 NumPy、SciPy、pandas、IPython、matplotlib、Scikit-learn 和 StatsModels。这是一个跨平台的发行版,可以安装在其他现有 Python 发行版和版本的机器上,其基础版本是免费的。包含高级功能的附加组件单独收费。Anaconda 引入了二进制包管理器 conda ,作为管理您的包安装的命令行工具。正如其网站上所述,Anaconda 的目标是为大规模处理、预测分析和科学计算提供企业级 Python 分发。至于 Python 版本,我们推荐 Anaconda 发行版 4.0.0。(为了看一下安装了 Anaconda 的软件包,可以看看https://docs.continuum.io/anaconda/pkg-docs的列表。)
第二个建议是,如果你在 Windows 上工作,并且你想要一个可移植的发行版,那么 WinPython(http://winpython.sourceforge.net/)可能是一个非常有趣的替代版本(抱歉,没有 Linux 或 MacOS 版本)。WinPython 也是一个由社区维护的免费开源 Python 发行版。它也是为科学家设计的,它包括许多基本包,如 NumPy、SciPy、matplotlib 和 IPython(基本上与 Anaconda 的相同)。它还包括作为 IDE 的 Spyder ,如果您有使用 MATLAB 语言和界面的经验,这可能会有所帮助。它的关键优势是它是可移植的(你可以把它放在任何目录甚至 u 盘里),所以你可以在你的电脑上有不同的版本,把一个版本从一台 Windows 电脑移动到另一台,你可以通过替换它的目录很容易地用一个新版本替换一个旧版本。当您运行 WinPython 或其 shell 时,它会自动设置运行 Python 所必需的所有环境变量,就好像它是在您的系统上定期安装和注册的。
型式
在撰写时,Python 2.7 是 2015 年 10 月随着 2.7.10 版本准备的最新发行版;从那以后,WinPython 只发布了 Python 3 版本发行版的更新。在系统上安装发行版后,您可能需要更新本书中示例所需的一些关键包。
介绍 Jupyter/IPython
IPython 于 2001 年由 Fernando Perez 发起,作为一个免费项目,解决了 Python 栈缺乏科学研究的问题,该栈使用用户编程接口,可以将科学方法(主要是实验和交互发现)纳入软件开发过程。
科学方法意味着以可复制的方式对不同的假设进行快速实验(数据科学中的数据探索和分析任务也是如此),当使用 IPython 时,您将能够在代码编写过程中更自然地实现探索性、迭代性和试错研究策略。
最近,IPython 项目的很大一部分已经转移到了一个名为 Jupyter 的新项目。这个新项目将原始 IPython 接口的潜在可用性扩展到了广泛的编程语言中。(完整列表请访问https://github . com/IPython/IPython/wiki/IPython-其他语言内核。)
得益于内核的强大思想,运行用户代码的程序由前端接口进行通信,并将执行代码的结果反馈给接口本身;您可以使用相同的界面和交互式编程风格,无论您使用什么语言开发。
Jupyter (IPython 是零内核,最初的起始内核)可以简单地描述为可通过控制台或基于 web 的笔记本操作的交互式任务的工具,它提供了特殊的命令来帮助开发人员更好地理解和构建当前正在编写的代码。
与围绕编写脚本、随后运行并评估其结果的想法构建的 IDE 相反,Jupyter 允许您以名为单元格的块编写代码,依次运行每个单元格,并分别评估的结果,同时检查文本和图形输出。除了图形集成之外,由于可定制的命令、丰富的历史(JSON 格式)和计算并行性,它还为您提供了进一步的帮助,从而在处理繁重的数值计算时提高了性能。
这种方法对于涉及基于数据开发代码的任务也特别有效,因为它自动完成了经常被忽视的职责,即记录和说明数据分析是如何完成的,它的前提和假设,以及它的中间和最终结果。如果你工作的一部分也是展示你的作品,并说服项目的内部或外部利益相关者,Jupyter 只需付出很少的额外努力就能真正为你带来讲故事的魔力。在https://github . com/IPython/IPython/wiki/A-gallery-of-of-interior-IPython-Notebooks上有很多的例子,其中一些可能会像我们一样对你的工作有启发。
事实上,我们不得不承认,保持一个干净、最新的 Jupyter 笔记本为我们节省了无数次与经理/利益相关者的会议突然出现,要求我们匆忙呈现工作状态的时间。
简而言之,Jupyter 为您提供了以下功能:
- 查看每个分析步骤的中间(调试)结果
- 只运行代码的某些部分(或单元格)
- 以 JSON 格式存储中间结果,并能够对它们进行版本控制
- 展示您的作品(这将是文本、代码和图像的组合),通过 Jupyter Notebook Viewer 服务(http://nbviewer.jupyter.org/)共享,并轻松地将其导出为 HTML、PDF 或甚至幻灯片
Jupyter 是我们在这本书中最喜欢的选择,它被用来清晰有效地用脚本和数据来说明讲故事的操作及其结果。
尽管我们强烈建议使用 Jupyter,但如果您使用的是 REPL 或 IDE,则可以使用相同的说明并期望得到相同的结果(打印格式和返回结果的扩展除外)。
如果您的系统上没有安装 Jupyter,您可以使用以下命令立即进行设置:
$ pip install jupyter
型式
您可以在http://jupyter.readthedocs.io/en/latest/install.html找到关于 Jupyter 安装(涵盖不同操作系统)的完整说明。
如果你已经安装了 Jupyter,应该至少升级到 4.1 版本。
安装后,您可以立即开始使用 Jupyter,从命令行调用它:
$ jupyter notebook
一旦 Jupyter 实例在浏览器中打开,点击新建按钮,在笔记本部分选择 Python 2 (其他内核可能是出现在该部分,取决于您安装的内容):
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_01_01.jpg
此时,您的新空笔记本将看起来像下面的屏幕截图,您可以开始在单元格中输入命令:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_01_02.jpg
例如,您可以开始在单元格中键入以下内容:
In: print ("This is a test")
写入单元格后,只需按下播放按钮(在单元格选项卡下方)即可运行并获得输出。然后,将出现另一个单元格供您输入。当您在单元格中书写时,如果您按下上面菜单栏上的加号按钮,您将获得一个新的单元格,您可以使用菜单上的箭头从一个单元格移动到另一个单元格。
其他大多数功能都非常直观,我们邀请您尝试。为了更好地了解 jupyter 的工作原理,您可以使用快速入门指南,如http://Jupyter-notebook-初学者指南. readthedocs.io/en/latest/ 或您可以获得一本专门介绍 Jupyter 功能的书籍。
注
有关运行 IPython 内核时所有 Jupyter 功能的完整论述,请参考以下两本 Packt 出版书籍:
- IPython 交互式计算和可视化烹饪书作者:西里尔·罗桑特,帕克特出版社,2014 年 9 月 25 日
- 学习交互式计算和数据可视化的 IPython西里尔·罗桑特帕克特出版,2013 年 4 月 25 日
出于说明的目的,只要考虑到每一个 Jupyter 指令块都有一个编号的输入语句和一个输出语句,那么你会发现在这本书中呈现的代码被构造成两个块——至少当输出一点也不琐碎的时候——否则,只期望输入部分:
In: <the code you have to enter>
Out: <the output you should get>
通常,您只需在单元格中输入之后的代码并运行它。然后,您可以将您的输出与我们使用输出:提供的输出进行比较,然后是我们在测试代码时在计算机上实际获得的输出。
Python 包
我们将在本段中介绍的软件包将在本书中经常使用。如果您没有使用科学的发行版,我们将为您提供一个演练,介绍您应该决定哪些版本以及如何快速成功地安装它们。
NumPy
NumPy 是特拉维斯·奥列芬特的创造,是 Python 语言中每一个分析解决方案的核心。它为用户提供了多维数组以及一大组函数来对这些数组进行多种数学运算。数组是沿多维排列的数据块,用于实现数学向量和矩阵。数组不仅对存储数据有用,而且对快速矩阵运算(矢量化)也很有用,当您希望解决特定的数据科学问题时,这是不可或缺的。
-
撰写时版本:1 . 11 . 1
-
建议安装命令:
$ pip install numpy
型式
作为 Python 社区大量采用的约定,在导入 NumPy 时,建议将其别名为np:
import numpy as np
黑桃
SciPy 是特拉维斯·奥列芬特、佩鲁·彼得森和埃里克·琼斯的原创项目,它完善了 NumPy 的功能,为线性代数、稀疏矩阵、信号和图像处理、优化、快速傅立叶变换等提供了更多种类的科学算法。
-
编写时版本:0.17.1
-
建议安装命令:
$ pip install scipy
熊猫
熊猫处理一切 NumPy 和 SciPy 做不到的事情。特别是,由于其特定的对象数据结构、数据帧和序列,它允许处理不同类型(NumPy 的数组无法处理)和时间序列的复杂数据表。由于韦斯·麦金尼的创建,您将能够轻松流畅地从各种来源加载数据,然后对其进行切片、切割、处理缺失的元素、添加、重命名、聚合、重塑,最后根据您的意愿将其可视化。
-
编写时版本:0.18.0
-
建议安装命令:
$ pip install pandas
型式
按照惯例,熊猫作为pd进口:
import pandas as pd
科学学习
Scikit-learn 作为 Scikit(SciPy Toolkits)的一部分开始,是 Python 中数据科学操作的核心。它在数据预处理、监督和非监督学习、模型选择、验证和误差度量方面提供了您可能需要的一切。期待我们在整本书中详细讨论这个包。
Scikit-learn 始于 2007 年,是由大卫·库纳波发起的谷歌代码之夏项目。自 2013 年以来,它已被 Inria(法国计算机科学和自动化研究所)的研究人员接管。
Scikit-learn 提供了用于数据处理(sklearn.preprocessing和sklearn.feature_extraction)、模型选择和验证(sklearn.cross_validation、sklearn.grid_search和sklearn.metrics)的模块,以及一整套方法(sklearn.linear_model),在这些方法中,作为数字或概率的目标值预计是输入变量的线性组合。
-
编写时版本:0.17.1
-
建议安装命令:
$ pip install scikit-learn
型式
注意,导入的模块名为sklearn。
matplotlib 包
matplotlib 最初由约翰·亨特开发,是一个包含所有构建块的库,用于从数组创建高质量的图,并交互式地可视化它们。
你可以在 PyLab 模块中找到所有类似 MATLAB 的绘图框架。
-
编写时版本:1.5.1
-
建议安装命令:
$ pip install matplotlib
您可以简单地导入可视化所需的内容:
import matplotlib as mpl
from matplotlib import pyplot as plt
玄垣
由 radim řehůřek 编程的 Gensim 是一个开源包,适合通过使用并行可分发的在线算法来分析大型文本集合。在高级功能中,它实现了潜在语义分析 ( LSA )、通过潜在狄利克雷分配 ( LDA )进行主题建模以及谷歌的 word2vec ,这是一种将文本转换为向量特征的强大算法,用于有监督和无监督的机器学习。
-
编写时版本:0.13.1
-
建议安装命令:
$ pip install gensim
H2O
H2O 是由初创公司 H2O.ai (之前命名为 0xdata)创建的用于大数据分析的开放源代码框架。R、Python、Scala 和 Java 编程语言都可以使用。H2O 轻松允许使用独立机器(利用多处理)或 Hadoop 集群(例如,AWS 环境中的集群),从而帮助您纵向扩展和横向扩展。
- 网站: http://www.h2o.ai
- 本书写作时的版本:3.8.3.3
为了安装这个包,你首先必须在你的系统上下载并安装 Java,(你需要安装 Java 开发工具包 ( JDK ) 1.8,因为 H2O 是基于 Java 的。)然后您可以参考http://www.h2o.ai/download/h2o/python提供的在线说明。
我们可以在下面几行中一起概述所有的安装步骤。
您可以按照以下说明安装 H2O 及其 Python 应用编程接口,就像我们在书中一直使用的那样:
$ pip install -U requests
$ pip install -U tabulate
$ pip install -U future
$ pip install -U six
这些步骤将安装所需的软件包,然后我们可以安装框架,注意删除任何以前的安装:
$ pip uninstall h2o
$ pip install h2o
为了让安装与我们书中相同的版本,您可以使用以下命令更改最后一个pip install命令:
$ pip install http://h2o-release.s3.amazonaws.com/h2o/rel-turin/3/Python/h2o-3.8.3.3-py2.py3-none-any.whl
如果您遇到问题,请访问 H2O 谷歌小组页面,在那里您可以获得有关您的问题的帮助:
https://groups.google.com/forum/#!forum/h2ostream
XGBoost
XGBoost 是一个可扩展的、可移植的、分布式梯度增强库(一种树集成机器学习算法)。它可用于 Python、R、Java、Scala、Julia 和 C++并且可以在一台机器上工作(利用多线程),在 Hadoop 和 Spark 集群中都是如此。
- 网站:https://xgboost.readthedocs.io/en/latest/
- 编写时版本:0.4
在您的系统上安装 xboost 的详细说明可以在https://github.com/dmlc/xgboost/blob/master/doc/build.md找到。
在 Linux 和 Mac 操作系统上安装 XGBoost 非常简单,但是对于 Windows 用户来说就有点复杂了。因此,我们提供了特定的安装步骤,让 XGBoost 在 Windows 上运行:
-
首先下载安装Git Windows(https://git-for-windows.github.io/)。
-
然后你需要一个极简 GNU for Windows ( MinGW )编译器出现在你的系统上。可以根据自己系统的特点从http://www.mingw.org/下载。
-
在命令行中,执行以下命令:
$ git clone --recursive https://github.com/dmlc/xgboost $ cd xgboost $ git submodule init $ git submodule update -
Then, from the command line, copy the configuration for 64-bit systems to be the default one:
$ copy make\mingw64.mk config.mk或者,您可以复制纯 32 位版本:
$ copy make\mingw.mk config.mk -
复制配置文件后,可以运行编译器,设置为使用四个线程以加快编译过程:
$ make -j4 -
最后,如果编译器没有错误地完成了它的工作,你可以通过执行以下命令在你的 Python 中安装这个包:
$ cd python-package $ python setup.py install
Theano
安诺是一个 Python 库,允许您定义、优化和评估涉及多维数组的数学表达式。基本上,它为您提供了创建深度神经网络所需的所有构件。
- 网站:http://deeplearning.net/software/theano/
- 在写入时释放:0.8.2
antio 的安装应该很简单,因为它现在是 PyPI 上的一个包:
$ pip install Theano
如果您想要包的最新版本,您可以通过 GitHub 克隆获得它们:
$ git clone git://github.com/Theano/Theano.git
然后,您可以继续直接安装 Python:
$ cd Theano
$ python setup.py install
要测试您的安装,您可以从 shell/CMD 运行以下命令并验证报告:
$ pip install nose
$ pip install nose-parameterized
$ nosetests theano
如果您使用的是 Windows 操作系统,并且之前的说明不起作用,您可以尝试以下步骤:
-
安装 TDM-GCC x64(http://tdm-gcc.tdragon.net/)。
-
打开 Anaconda 命令提示符并执行以下命令:
$ conda update conda $ conda update –all $ conda install mingw libpython $ pip install git+git://github.com/Theano/Theano.git
型式
antao 需要 libpython,它还不兼容 3.5 版本,所以如果您的 Windows 安装不工作,这可能是原因。
此外,安奈诺的网站向 Windows 用户提供了一些信息,当其他一切都失败时,这些信息可能会支持你:
http://deep learning . net/software/茶诺/install_windows.html
在 GPU 上横向扩展的一个重要要求是安装 NVIDIA CUDA 驱动程序和 SDK 用于在 GPU 上生成和执行代码。如果您对 CUDA 工具包不太了解,您实际上可以从这个网页开始,以了解更多正在使用的技术:
https://developer.nvidia.com/cuda-toolkit
因此,如果您的电脑拥有 NVIDIA GPU,您可以从 NVIDIA 本身找到安装 CUDA 的所有必要说明,使用本教程页面:
http://docs . NVIDIA . com/cuda/cuda-快速入门-指南/index.html#axzz4A8augxYy
TensorFlow
就像 antano 一样, TensorFlow 是另一个用于数值计算的开源软件库,使用数据流图而不仅仅是数组。这样的图中的节点代表数学运算,而图的边代表在节点之间移动的多维数据阵列(所谓的张量)。最初,作为谷歌大脑团队的一部分,谷歌研究人员开发了 TensorFlow,最近他们将其开源给了 T4 公众。
- 网站:https://github . com/tensorlow/tensorlow
- 编写时发布:0.8.0
要在您的计算机上安装 TensorFlow,请按照以下链接中的说明进行操作:
https://github . com/tensorlow/tensorlow/blob/master/tensorlow/G3 doc/get _ started/OS _ setup . MD
Windows 支持目前不存在,但在当前的路线图中:
https://github . com/tensorlow/tensorlow/blob/master/tensorlow/G3 doc/resources/roadmap . MD
对于 Windows 用户来说,一个很好的折衷方案是在基于 Linux 的虚拟机或 Docker 机器上运行该包。(前面的操作系统设置页面提供了相关说明。)
sknn 图书馆
sknn 库(对于扩展, scikit-neuralnetwork )是派尔恩 2 的包装器,帮助你实现深度神经网络,而不需要你成为安诺的专家。另外,该库与 Scikit-learn API 兼容。
-
发布时间发布时间:0.7
-
要安装库,只需使用以下命令:
$ pip install scikit-neuralnetwork
或者,如果您想利用最先进的功能,如卷积、池化或升级,您必须按如下方式完成安装:
$ pip install -r https://raw.githubusercontent.com/aigamedev/scikit-neuralnetwork/master/requirements.txt
安装后,您还必须执行以下操作:
$ git clone https://github.com/aigamedev/scikit-neuralnetwork.git
$ cd scikit-neuralnetwork
$ python setup.py develop
正如在 XGBoost 中看到的,这将使sknn包在您的 Python 安装中可用。
Theanets
茶氨酸包是一个用 Python 编写的深度学习和神经网络工具包,使用茶氨酸来加速计算。就像 sknn 一样,为了创建深度学习模型,它试图使其更容易与中的模拟功能交互。
-
编写时版本:0.7.3
-
建议安装程序:
$ pip install theanets
也可以从 GitHub 下载当前版本,直接用 Python 安装包:
$ git clone https://github.com/lmjohns3/theanets
$ cd theanets
$ python setup.py develop
硬
Keras 是一个用 Python 编写的极简、高度模块化的神经网络库,能够在 TensorFlow 或 antao 之上运行。
-
编写时的版本:1.0.5
-
建议从 PyPI
$ pip install keras安装
您也可以使用以下命令安装最新的可用版本(建议使用持续开发的软件包):
$ pip install git+git://github.com/fchollet/keras.git
要安装在系统上的其他有用的软件包
在这本书的各页中,我们将看到许多包的漫长旅程,最后,我们用三个简单但非常有用的包来结束,它们几乎不需要演示,但需要安装在您的系统上:内存剖析器、气候和 神经实验室。
内存分析器是一个监控进程内存使用情况的包。它还有助于逐行剖析特定 Python 脚本的内存消耗。它可以按如下方式安装:
$ pip install -U memory_profiler
气候只是由 Python 的一些基本命令行实用程序组成。它可以按如下方式迅速安装:
$ pip install climate
最后,NeuroLab 是一个非常基础的神经网络包,松散地基于 MATLAB 中的神经网络工具箱 ( NNT )。是基于 NumPy 和 SciPy,而不是 antao;因此,不要期待惊人的表现,但要知道这是一个很好的学习工具箱。它可以很容易地安装如下:
$ pip install neurolab
总结
在这一介绍性章节中,我们已经说明了使用 Python(纵向扩展和横向扩展技术)使机器学习算法可扩展的不同方法。我们还提出了一些激励性的例子,并通过说明如何在您的机器上安装 Python 来为这本书做准备。特别是,我们向您介绍了 Jupyter,并涵盖了将在本书中使用的所有最重要的软件包。
在下一章中,我们将深入讨论随机梯度下降如何通过在单台机器上利用输入/输出来帮助您处理海量数据集。基本上,我们将涵盖从大文件或数据存储库中流式传输数据的不同方式,并将其输入到基本的学习算法中。你会惊讶于简单的解决方案是多么有效,你会发现即使是你的台式电脑也可以轻松处理大数据。
二、Scikit-learn 中的可扩展学习
将数据集加载到内存中、准备数据矩阵、训练机器学习算法以及使用样本外观察来测试其泛化能力通常不是什么大事,因为当今时代的计算机相当强大,但价格合理。然而,越来越多的情况是,要处理的数据规模如此之大,以至于不可能将其加载到计算机的核心内存中,即使是可管理的,其结果在数据管理和机器学习方面也是难以处理的。
除了核心内存处理之外,其他可行的策略也是可能的:将数据分成样本,使用并行性,最后以小批量或单个实例进行学习。本章将重点介绍 Scikit-learn 包提供的现成解决方案:来自数据存储的小批量实例流(我们的观察)以及基于它们的增量学习。这样的解决方案叫做核心外学习。
通过处理可管理的块和增量学习来处理数据是一个好主意。然而,当您试图实现它时,它也可能被证明是具有挑战性的,因为可用的学习算法和流中的流数据的限制将要求您在数据管理和特征提取方面有不同的想法。除了展示用于核心外学习的 Scikit-learn 功能之外,我们还将努力向您展示 Python 解决方案,解决您在被迫一次只观察一小部分数据时可能面临的明显令人生畏的问题。
在本章中,我们将涵盖以下主题:
- 核心外学习的方式在 Scikit-learn 中实现
- 使用哈希技巧有效管理数据流
- 随机学习的基本原理
- 通过在线学习实施数据科学
- 数据流的无监督转换
核心外学习
核心外学习指的是一组处理数据的算法,这些数据不能放入单台计算机的内存中,但可以很容易地放入一些数据存储中,如本地硬盘或网络存储库。您的可用内存,也就是单台机器上的核心内存,在大型服务器上可能从几千兆字节(有时是 2 GB,更常见的是 4 GB,但我们假设您最多有 2 GB)到 256 GB 不等。大型服务器就像您可以在云计算服务上获得的服务器,例如亚马逊弹性计算云 ( EC2 ),而您的存储容量仅使用一个外部驱动器就可以轻松超过万亿字节的容量(很可能约为 1 TB,但最高可达 4 TB)。
由于机器学习是基于全局降低成本函数,许多算法最初被认为是使用所有可用数据并在优化过程的每次迭代中访问这些数据。对于所有基于利用矩阵演算的统计学习的算法来说尤其如此,例如,反转矩阵,但是基于贪婪搜索的算法在采取下一步之前需要对尽可能多的数据进行评估。因此,最常见的开箱即用的类似回归的算法(特征的加权线性组合)更新它们的系数,试图最小化整个数据集的汇集误差。同样,由于对数据集中存在的噪声非常敏感,决策树必须根据所有可用数据决定最佳分割,以便找到最佳解决方案。
如果在这种情况下,数据无法容纳在计算机的核心内存中,您没有太多可能的解决方案。您可以增加可用内存(取决于主板的限制;之后,您将不得不求助于分布式系统,如 Hadoop 和 Spark(我们将在本书的最后几章中提到这种解决方案),或者简单地减少数据集,以便让它适合内存。
如果您的数据是稀疏的,也就是说,您的数据集中有许多零值,您可以将密集矩阵转换为稀疏矩阵。这对于具有许多列的文本数据来说是典型的,因为每一列都是一个单词,但是代表单词计数的值很少,因为单个文档通常显示有限的单词选择。有时,使用稀疏矩阵可以解决允许您加载和处理其他相当大的数据集的问题,但这不是灵丹妙药(抱歉,没有免费的午餐,也就是说,没有适合所有问题的解决方案),因为一些数据矩阵虽然稀疏,但可能有令人生畏的大小。
在这种情况下,您总是可以尝试通过减少实例数量或限制要素数量来减少数据集,从而减少数据集矩阵的维度及其在内存中所占的面积。通过只选取一部分观察值来减小数据集的大小,是一种称为二次采样(或简称采样)的解决方案。二次抽样本身没有错,但它有严重的缺点,在决定分析过程之前,有必要记住它们。
二次采样是一个可行的选择
当你进行二次抽样时,你实际上是在丢弃你的信息丰富性的一部分,你不能确定你只是在丢弃多余的,不是那么有用的观察。其实一些隐藏的宝石只有综合考虑所有数据才能发现。尽管在计算上很有吸引力——因为二次采样只需要一个随机生成器来告诉你是否应该选择一个实例——但通过选择二次采样数据集,你确实有可能限制算法以完整的方式学习数据中的规则和关联的能力。在偏差-方差权衡中,二次抽样导致预测的方差膨胀,因为由于随机噪声或数据中的异常观察,估计将更加不确定。
在大数据的世界里,拥有更多高质量数据的算法获胜,因为它可以比其他拥有更少(或更多噪音)数据的模型学会更多将预测与预测因子联系起来的方法。因此,二次抽样虽然作为一种解决方案是可以接受的,但会对机器学习活动的结果造成限制,因为预测不太精确,估计的方差更大。
通过在数据的多个子样本上学习多个模型,然后最终将所有解集合在一起或将所有模型的结果叠加在一起,从而创建一个简化的数据矩阵以供进一步训练,可以以某种方式克服子采样限制。这个过程被称为装袋。(您实际上是以这种方式压缩功能,从而减少了内存中的数据空间。)我们将在后面的章节中探讨集合和堆叠,并发现它们实际上如何减少因二次抽样而膨胀的估计方差。
作为替代方案,我们可以不切割实例,而是切割特征,但同样,我们会遇到这样的问题,即我们需要从数据中构建模型,以测试我们可以选择哪些特征,因此我们仍然需要构建一个包含无法放入内存的数据的模型。
一次优化一个实例
意识到二次采样虽然总是可行的,但并不是一个最优的解决方案,我们必须评估一种不同的方法,而核外实际上并不需要你放弃观察或特征。只不过训练一个模型需要更长一点的时间,需要更多的迭代和从你的存储到你的计算机内存的数据传输。我们立即提供了核心外学习过程如何工作的第一直觉。
让我们从学习开始,这是一个过程,我们试图将表达响应的未知函数(一个回归或分类问题的数字或结果)映射到可用数据。学习是可能的,通过拟合学习算法的内部系数,试图在可用数据上实现最佳拟合,即最小化成本函数,这种度量告诉我们我们的近似有多好。归根结底,我们谈论的是优化过程。
不同的优化算法,就像梯度下降一样,是能够处理任何数据量的过程。他们致力于导出优化梯度(优化过程中的一个方向),并让学习算法调整其参数,以便遵循梯度。
在梯度下降的具体情况下,经过一定次数的迭代,如果问题可以解决,并且没有其他问题,比如学习率太高,梯度应该变得很小,这样我们就可以停止优化过程。在这个过程的最后,我们可以确信已经找到了一个最优解(因为它是一个全局最优解,尽管有时它可能是一个局部最小值,如果要逼近的函数不是凸的)。
由于由梯度决定的方向性可以基于任意数量的示例,因此也可以在单个实例上进行。在单个实例上采用梯度需要小的学习速率,但是最终,该过程可以达到与在全部数据上采用梯度下降相同的优化。最后,我们的算法所需要的只是一个方向,即在拟合可用数据的基础上正确定位学习过程。因此,从从数据中随机抽取的单个案例中学习这样的方向是完全可行的:
- 我们可以获得相同的结果,就好像我们一次处理所有数据一样,尽管优化路径可能会变得有点粗糙;如果你的大部分观察都指向一个最佳方向,算法就会采用那个方向。唯一的问题是正确调整学习过程的正确参数,并多次传递数据,以确保优化完成,因为此学习过程比处理所有可用数据要慢得多。
- 我们在设法将单个实例保留在核心内存中,将大部分数据排除在外方面没有任何特殊问题。通过单个例子将数据从其存储库移动到我们的核心内存可能会产生其他问题。可伸缩性是有保证的,因为处理数据所需的时间是线性的;无论我们要处理的实例总数是多少,多使用一个实例的时间成本总是一样的。
将学习算法拟合到单个实例或一次拟合到内存的数据子集上的方法称为在线学习,基于这种单次观测的梯度下降称为随机梯度下降。如前所述,在线学习是一种核心外技术,被 Scikit-learn 中的许多学习算法所采用。
构建核心外学习体系
在接下来的几个段落中,我们将说明随机梯度下降的内部工作原理,提供更多的细节和推理。现在知道如何可能学习核外(由于随机梯度下降)允许我们更清楚地描述我们应该做什么来使它在我们的计算机上工作。
您可以将活动划分为不同的任务:
- 准备您的数据存储库访问,以便逐个实例地流式传输数据。此活动可能要求您在将数据提取到计算机之前随机化数据行的顺序,以便删除排序可能带来的任何信息。
- 首先进行一些数据调查,可能是对所有数据的一部分(例如,前一万行),试图弄清楚到达的实例在特征数量、数据类型、数据值的存在与否、每个变量的最小值和最大值以及平均值和中值方面是否一致。找出目标变量的范围或类别。
- 将每个接收到的数据行准备成学习算法可以接受的固定格式(密集或稀疏向量)。在这个阶段,您可以执行任何基本的转换,例如,将分类特征转换为数字特征,或者让数字特征通过特征本身的叉积进行交互。
- 将示例的顺序随机化后(如第一点所述),使用系统保持或在一定数量的观察后保持来建立验证程序。
- 通过重复流式传输数据或处理小样本数据来调整超参数。这也是进行一些特征工程(使用无监督学习和特殊变换函数,如核近似)并利用正则化和特征选择的合适时机。
- 使用您为培训保留的数据构建您的最终模型,并在全新的数据上理想地测试模型的有效性。
作为第一步,我们将讨论如何准备您的数据,然后利用 Python 包(如 pandas 和 Scikit-learn)中的有用功能,轻松创建适合在线学习的流。
从来源流式传输数据
当你有一个传输数据的生成过程时,一些数据真的在你的计算机中流动,你可以动态处理或丢弃这些数据,但除非你已经把它们存储在某个数据档案库中,否则以后不会再调用。这就像从流动的河流中拖水一样——河流一直在流动,但你可以过滤和处理所有流动的水。这与一次处理所有数据是完全不同的策略,这更像是把所有的水放在一个大坝里(类似于处理内存中的所有数据)。
作为流式传输的一个例子,我们可以引用传感器即时产生的数据流,或者更简单地说,推特上的推文流。一般来说,数据流的主要来源如下:
- 测量温度、压力和湿度的环境传感器
- GPS 跟踪传感器记录位置(纬度/经度)
- 记录图像数据的卫星
- 监控录像和录音
- 网络流量
但是,您不会经常处理真实的数据流,而是处理存储在存储库或文件中的静态记录。在这种情况下,可以根据某些标准重新创建流,例如,一次按顺序或随机提取单个记录。例如,如果我们的数据包含在一个 TXT 或 CSV 文件中,我们需要做的就是一次提取文件的一行,并将其传递给学习算法。
对于本章和下一章中的示例,我们将处理存储在本地硬盘上的文件,并准备将其提取为流所需的 Python 代码。我们不会使用玩具数据集,但我们不会在您的本地硬盘上塞满太多测试和演示数据。
数据集自己去尝试真实的东西
自 1987 年以来,在加州大学欧文分校(UCI)UCI 机器学习资源库已经被托管,这是一个用于机器学习社区对机器学习算法进行经验测试的大型数据集资源库。在撰写本文时,知识库包含大约 350 个数据集,这些数据集来自非常不同的领域和目的,从有监督的回归和分类到无监督的任务。您可以在https://archive.ics.uci.edu/ml/查看可用的数据集。
从我们的角度来看,我们选择了几个数据集,这些数据集将在整本书中变得有用,用一台不寻常但仍可管理的 2 GB 内存计算机和大量的行或列向您提出了具有挑战性的问题:
|数据集名称
|
数据集网址
|
问题类型
|
行和列
|
| — | — | — | — |
| 自行车共享数据集 | https://archive . ics . UCI . edu/ml/datasets/Bike+Sharing+Dataset | 回归 | 17389, 16 |
| BlogFeedback 数据集 | https://archive . ics . UCI . edu/ml/datasets/blogging feedback | 回归 | 60021, 281 |
| 社交媒体数据集中的嗡嗡声 | https://archive . ics . UCI . edu/ml/datasets/Buzz+in+社交+媒体+ | 回归和分类 | 140000, 77 |
| 普查-收入(KDD)数据集 | https://archive . ics . UCI . edu/ml/datasets/Census-Income+% 28KDD % 29 | 带有缺失数据的分类 | 299285, 40 |
| Covertype 数据集 | https://archive.ics.uci.edu/ml/datasets/Covertype | 分类 | 581012, 54 |
| 1999 年 KDD 杯数据集 | https://archive.ics.uci.edu/ml/datasets/KDD+Cup+1999+Data | 分类 | 4000000, 42 |
要从 UCI 存储库中下载并使用数据集,您必须转到该数据集的专用页面,并按照标题下的链接:下载:数据文件夹。我们已经为自动下载数据准备了一些脚本,这些脚本将被放在 Python 中您正在使用的目录中,从而使数据访问更加容易。
以下是我们准备好的一些函数,当我们需要从 UCI 下载任何数据集时,我们会在整个章节中回忆这些函数:
In: import urllib2 # import urllib.request as urllib2 in Python3
import requests, io, os, StringIO
import numpy as np
import tarfile, zipfile, gzip
def unzip_from_UCI(UCI_url, dest=''):
"""
Downloads and unpacks datasets from UCI in zip format
"""
response = requests.get(UCI_url)
compressed_file = io.BytesIO(response.content)
z = zipfile.ZipFile(compressed_file)
print ('Extracting in %s' % os.getcwd()+'\\'+dest)
for name in z.namelist():
if '.csv' in name:
print ('\tunzipping %s' %name)
z.extract(name, path=os.getcwd()+'\\'+dest)
def gzip_from_UCI(UCI_url, dest=''):
"""
Downloads and unpacks datasets from UCI in gzip format
"""
response = urllib2.urlopen(UCI_url)
compressed_file = io.BytesIO(response.read())
decompressed_file = gzip.GzipFile(fileobj=compressed_file)
filename = UCI_url.split('/')[-1][:-3]
with open(os.getcwd()+'\\'+filename, 'wb') as outfile:
outfile.write(decompressed_file.read())
print ('File %s decompressed' % filename)
def targzip_from_UCI(UCI_url, dest='.'):
"""
Downloads and unpacks datasets from UCI in tar.gz format
"""
response = urllib2.urlopen(UCI_url)
compressed_file = StringIO.StringIO(response.read())
tar = tarfile.open(mode="r:gz", fileobj = compressed_file)
tar.extractall(path=dest)
datasets = tar.getnames()
for dataset in datasets:
size = os.path.getsize(dest+'\\'+dataset)
print ('File %s is %i bytes' % (dataset,size))
tar.close()
def load_matrix(UCI_url):
"""
Downloads datasets from UCI in matrix form
"""
return np.loadtxt(urllib2.urlopen(UCI_url))
型式
下载示例代码
下载代码包的详细步骤在本书的前言中提到。请看看。
这本书的代码包也托管在 GitHub 上,网址为。我们还有来自丰富的图书和视频目录的其他代码包,可在https://github.com/PacktPublishing/获得。看看他们!
这些功能只是围绕处理压缩数据的各种包构建的方便的包装器,如tarfile、zipfile和gzip。文件是使用urllib2模块打开的,该模块生成远程系统的句柄,允许数据的顺序传输,并以字符串(StringIO)或二进制模式(BytesIO)从io模块存储在内存中,该模块专门用于流处理(【https://docs.python.org/2/library/io.html】)。存储在内存中后,它会被重新调用,就像文件是从专门用于从磁盘中解压缩压缩文件的函数中调用一样。
所提供的四个函数应该可以方便地帮助您快速下载数据集,无论它们是压缩的、涂了焦油的、gzip 的还是只是矩阵形式的纯文本,避免了手动下载和提取操作的麻烦。
第一个例子——流式传输自行车共享数据集
作为第一个例子,我们将使用自行车共享数据集。该数据集由两个 CSV 文件组成,包含 2011 年至 2012 年期间在美国华盛顿首都自行车共享系统内租赁的自行车的小时数和日数。这些数据显示了租赁当天相应的天气和季节信息。该数据集与由 Fanaee-T,Hadi 和 Gama,Joao 出版的结合集成检测器和背景知识的事件标记,《人工智能进展》(2013 年):第 1-15 页,斯普林格·柏林海德堡。
我们的第一个目标是使用前面几段中定义的方便的包装函数将数据集保存在本地硬盘上:
In: UCI_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00275/Bike-Sharing-Dataset.zip'
unzip_from_UCI(UCI_url, dest='bikesharing')
Out: Extracting in C:\scisoft\WinPython-64bit-2.7.9.4\notebooks\bikesharing
unzipping day.csv
unzipping hour.csv
如果运行成功,代码将指示 CSV 文件保存在哪个目录中,并打印两个解压缩文件的名称。
此时,将信息保存在物理设备上后,我们将编写一个脚本,构成核心外学习系统的核心,从文件中提供数据流。我们将首先使用csv库,为我们提供了双重选择:将数据恢复为列表或 Python 字典。我们将从一个列表开始:
In: import os, csv
local_path = os.getcwd()
source = 'bikesharing\\hour.csv'
SEP = ',' # We define this for being able to easily change it as required by the file
with open(local_path+'\\'+source, 'rb') as R:
iterator = csv.reader(R, delimiter=SEP)
for n, row in enumerate(iterator):
if n==0:
header = row
else:
# DATA PROCESSING placeholder
# MACHINE LEARNING placeholder
pass
print ('Total rows: %i' % (n+1))
print ('Header: %s' % ', '.join(header))
print ('Sample values: %s' % ', '.join(row))
Out: Total rows: 17380
Header: instant, dteday, season, yr, mnth, hr, holiday, weekday, workingday, weathersit, temp, atemp, hum, windspeed, casual, registered, cnt
Sample values: 17379, 2012-12-31, 1, 1, 12, 23, 0, 1, 1, 1, 0.26, 0.2727, 0.65, 0.1343, 12, 37, 49
输出将向我们报告已经读取了多少行,标题的内容 CSV 文件的第一行(存储在列表中)——以及一行的内容(为了方便起见,我们打印了最后一行)。csv.reader函数创建一个iterator,由于有一个for循环,它将逐个释放文件的每一行。请注意,我们在代码片段内部放置了两个注释,指出了在整个章节中,我们将在哪里放置其他代码来处理数据预处理和机器学习。
在这种情况下,必须使用定位方法来处理特征,即索引标签在标题中的位置。如果你必须广泛地操作你的特征,这可能是一个小麻烦。一个解决方案可能是使用csv.DictReader生成一个 Python 字典作为输出(这是无序的,但是特征很容易被它们的标签召回):
In: with open(local_path+'\\'+source, 'rb') as R:
iterator = csv.DictReader(R, delimiter=SEP)
for n, row in enumerate(iterator):
# DATA PROCESSING placeholder
# MACHINE LEARNING placeholder
pass
print ('Total rows: %i' % (n+1))
print ('Sample values: %s' % str(row))
Out: Total rows: 17379
Sample values: {'mnth': '12', 'cnt': '49', 'holiday': '0', 'instant': '17379', 'temp': '0.26', 'dteday': '2012-12-31', 'hr': '23', 'season': '1', 'registered': '37', 'windspeed': '0.1343', 'atemp': '0.2727', 'workingday': '1', 'weathersit': '1', 'weekday': '1', 'hum': '0.65', 'yr': '1', 'casual': '12'}
使用熊猫输入输出工具
作为 csv模块的替代,我们可以使用熊猫的read_csv功能。这样一个专门用于上传 CSV 文件的功能,是大量专门用于不同文件格式输入/输出的功能的一部分,如 http://pandas.pydata.org/pandas-docs/stable/io.html 熊猫文档所规定的。
使用熊猫输入输出功能的巨大优势如下:
- 如果您改变了源代码类型,您可以保持代码的一致性,也就是说,您只需要重新定义流迭代器
- 您可以访问大量不同的格式,例如 CSV、普通 TXT、HDF、JSON 和特定数据库的 SQL 查询
- 由于熊猫数据帧中典型的切片和切割方法
.loc、.iloc、.ix,数据以数据帧数据结构的形式被流式传输到所需大小的块中,以便您可以通过定位方式或通过调用其标签来访问特征
下面是一个使用与之前相同方法的例子,这次是围绕熊猫的read_csv功能构建的:
In: import pandas as pd
CHUNK_SIZE = 1000
with open(local_path+'\\'+source, 'rb') as R:
iterator = pd.read_csv(R, chunksize=CHUNK_SIZE)
for n, data_chunk in enumerate(iterator):
print ('Size of uploaded chunk: %i instances, %i features' % (data_chunk.shape))
# DATA PROCESSING placeholder
# MACHINE LEARNING placeholder
pass
print ('Sample values: \n%s' % str(data_chunk.iloc[0]))
Out:
Size of uploaded chunk: 2379 instances, 17 features
Size of uploaded chunk: 2379 instances, 17 features
Size of uploaded chunk: 2379 instances, 17 features
Size of uploaded chunk: 2379 instances, 17 features
Size of uploaded chunk: 2379 instances, 17 features
Size of uploaded chunk: 2379 instances, 17 features
Size of uploaded chunk: 2379 instances, 17 features
Sample values:
instant 15001
dteday 2012-09-22
season 3
yr 1
mnth 9
hr 5
holiday 0
weekday 6
workingday 0
weathersit 1
temp 0.56
atemp 0.5303
hum 0.83
windspeed 0.3284
casual 2
registered 15
cnt 17
Name: 0, dtype: object
这里,注意到迭代器是通过指定块大小来实例化的,也就是迭代器在每次迭代中必须返回的行数,这一点非常重要。chunksize参数可以取从 1 到任何值的值,尽管很明显,小批量(检索到的块)的大小与您的可用内存严格相连,以便在接下来的预处理阶段存储和操作它。
将更大的块带入内存仅在磁盘访问方面有优势。较小的块需要多次访问磁盘,并且根据物理存储的特性,需要更长的时间来传递数据。然而,从机器学习的角度来看,更小或更大的块对 Scikit 来说没有什么区别——学习核心外的函数,因为它们一次只考虑一个实例来学习,使它们在计算成本上真正线性。
使用数据库
作为熊猫输入/输出工具灵活性的一个例子,我们将提供一个使用 SQLite3 数据库的进一步例子,其中数据是从一个简单的查询逐块流式传输的。这个例子不仅仅是为了教学而提出的。从磁盘空间和处理时间的角度来看,在数据库中使用大型数据存储确实可以带来优势。
在 SQL 数据库中排列成表的数据可以标准化,从而消除冗余和重复,节省磁盘存储。数据库规范化是一种在数据库中排列列和表的方式,以减少它们的维度而不丢失任何信息。通常,这是通过拆分表并将重复的数据重新编码成键来实现的。此外,在内存、操作和多处理方面进行了优化的关系数据库可以加速和预测那些在 Python 脚本中处理的预处理活动。
使用 Python,SQLite(http://www.sqlite.org)是一个不错的默认选择,原因如下:
- 它是开源的
- 它可以处理大量数据(理论上每个数据库高达 140 TB,尽管不太可能看到任何 SQLite 应用处理如此大量的数据)
- 它在苹果操作系统、Linux 和视窗 32 和 64 位环境下运行
- 它不需要任何服务器基础架构或特定安装(零配置),因为所有数据都存储在磁盘上的单个文件中
- 使用 Python 代码可以很容易地将其扩展成存储过程
此外,Python 标准库包括一个sqlite3模块,提供从头开始创建数据库并使用它的所有功能。
在我们的示例中,我们将首先将包含自行车共享数据集的 CSV 文件每天和每小时上传到一个 SQLite 数据库,然后我们将像从 CSV 文件一样从该数据库进行流式传输。我们提供的数据库上传代码可以在整本书和您自己的应用中重复使用,而不局限于我们提供的特定示例(您只需更改输入和输出参数,仅此而已):
In : import os, sys
import sqlite3, csv,glob
SEP = ','
def define_field(s):
try:
int(s)
return 'integer'
except ValueError:
try:
float(s)
return 'real'
except:
return 'text'
def create_sqlite_db(db='database.sqlite', file_pattern=''):
conn = sqlite3.connect(db)
conn.text_factory = str # allows utf-8 data to be stored
c = conn.cursor()
# traverse the directory and process each .csv file useful for building the db
target_files = glob.glob(file_pattern)
print ('Creating %i table(s) into %s from file(s): %s' % (len(target_files), db, ', '.join(target_files)))
for k,csvfile in enumerate(target_files):
# remove the path and extension and use what's left as a table name
tablename = os.path.splitext(os.path.basename(csvfile))[0]
with open(csvfile, "rb") as f:
reader = csv.reader(f, delimiter=SEP)
f.seek(0)
for n,row in enumerate(reader):
if n==11:
types = map(define_field,row)
else:
if n>11:
break
f.seek(0)
for n,row in enumerate(reader):
if n==0:
sql = "DROP TABLE IF EXISTS %s" % tablename
c.execute(sql)
sql = "CREATE TABLE %s (%s)" % (tablename,\
", ".join([ "%s %s" % (col, ct) \
for col, ct in zip(row, types)]))
print ('%i) %s' % (k+1,sql))
c.execute(sql)
# Creating indexes for faster joins on long strings
for column in row:
if column.endswith("_ID_hash"):
index = "%s__%s" % \
( tablename, column )
sql = "CREATE INDEX %s on %s (%s)" % \
( index, tablename, column )
c.execute(sql)
insertsql = "INSERT INTO %s VALUES (%s)" % (tablename,
", ".join([ "?" for column in row ]))
rowlen = len(row)
else:
# raise an error if there are rows that don't have the right number of fields
if len(row) == rowlen:
c.execute(insertsql, row)
else:
print ('Error at line %i in file %s') % (n,csvfile)
raise ValueError('Houston, we\'ve had a problem at row %i' % n)
conn.commit()
print ('* Inserted %i rows' % n)
c.close()
conn.close()
该脚本提供了一个有效的数据库名称和模式来定位您想要导入的文件(接受通配符,如*),并从头创建一个您需要的新数据库和表,然后用所有可用的数据填充它们:
In: create_sqlite_db(db='bikesharing.sqlite', file_pattern='bikesharing\\*.csv')
Out: Creating 2 table(s) into bikesharing.sqlite from file(s): bikesharing\day.csv, bikesharing\hour.csv
1) CREATE TABLE day (instant integer, dteday text, season integer, yr integer, mnth integer, holiday integer, weekday integer, workingday integer, weathersit integer, temp real, atemp real, hum real, windspeed real, casual integer, registered integer, cnt integer)
* Inserted 731 rows
2) CREATE TABLE hour (instant integer, dteday text, season integer, yr integer, mnth integer, hr integer, holiday integer, weekday integer, workingday integer, weathersit integer, temp real, atemp real, hum real, windspeed real, casual integer, registered integer, cnt integer)
* Inserted 17379 rows
该脚本还报告了所创建字段的数据类型和行数,因此很容易验证在导入过程中一切是否顺利。现在很容易从数据库中流式传输。在我们的示例中,我们将在小时表和日表之间创建一个内部连接,以小时为基础提取数据,其中包含当天总租金的信息:
In: import os, sqlite3
import pandas as pd
DB_NAME = 'bikesharing.sqlite'
DIR_PATH = os.getcwd()
CHUNK_SIZE = 2500
conn = sqlite3.connect(DIR_PATH+'\\'+DB_NAME)
conn.text_factory = str # allows utf-8 data to be stored
sql = "SELECT H.*, D.cnt AS day_cnt FROM hour AS H INNER JOIN day as D ON (H.dteday = D.dteday)"
DB_stream = pd.io.sql.read_sql(sql, conn, chunksize=CHUNK_SIZE)
for j,data_chunk in enumerate(DB_stream):
print ('Chunk %i -' % (j+1)),
print ('Size of uploaded chunk: %i instances, %i features' % (data_chunk.shape))
# DATA PROCESSING placeholder
# MACHINE LEARNING placeholder
Out:
Chunk 1 - Size of uploaded chunk: 2500 instances, 18 features
Chunk 2 - Size of uploaded chunk: 2500 instances, 18 features
Chunk 3 - Size of uploaded chunk: 2500 instances, 18 features
Chunk 4 - Size of uploaded chunk: 2500 instances, 18 features
Chunk 5 - Size of uploaded chunk: 2500 instances, 18 features
Chunk 6 - Size of uploaded chunk: 2500 instances, 18 features
Chunk 7 - Size of uploaded chunk: 2379 instances, 18 features
如果您需要加速流,您只需要优化数据库,首先为您打算使用的关系查询构建正确的索引。
型式
conn.text_factory = str是剧本中非常重要的一部分;它允许存储 UTF-8 数据。如果忽略这样的命令,您可能会在输入数据时遇到奇怪的错误。
注意实例的排序
作为流数据主题的总结注释,我们必须警告您这样一个事实,即在流传输时,由于您的学习所基于的示例顺序,您实际上在学习过程中包含了隐藏的信息。
事实上,在线学习者基于他们评估的每个实例来优化他们的参数。每个实例都将引导学习者朝着优化过程中的某个方向前进。从全局来看,在给定足够多的评估实例的情况下,学习者应该采取正确的优化方向。然而,如果学习者改为通过有偏差的观察(例如,按时间排序的观察或以有意义的方式分组的观察)来训练,则算法也将学习偏差。为了不记起以前见过的例子,可以在训练中做一些事情,但无论如何都会引入一些偏见。如果你正在学习时间序列——对时间流动的反应通常是模型的一部分——这样的偏差是相当有用的,但在大多数其他情况下,它充当了某种过度拟合,并转化为最终模型中某种程度的泛化能力的缺乏。
如果您的数据具有某种排序,而您不希望机器学习算法学习到这种排序(例如 ID 顺序),作为一种谨慎措施,您可以在流式传输数据之前对其行进行洗牌,并获得更适合在线随机学习的随机顺序。
最快的方法,也是占用较少磁盘空间的方法,是在内存中流式传输数据集,并通过压缩来缩小数据集。在大多数情况下,但不是所有情况下,由于所应用的压缩算法以及您用于训练的数据的相对稀疏性和冗余性,这种方法都是可行的。在它不起作用的情况下,您必须直接在磁盘上洗牌,这意味着更多的磁盘空间消耗。
在这里,我们首先介绍一种快速的内存洗牌方法,这得益于能够将行快速压缩到内存中的zlib包,以及来自random模块的shuffle功能:
In: import zlib
from random import shuffle
def ram_shuffle(filename_in, filename_out, header=True):
with open(filename_in, 'rb') as f:
zlines = [zlib.compress(line, 9) for line in f]
if header:
first_row = zlines.pop(0)
shuffle(zlines)
with open(filename_out, 'wb') as f:
if header:
f.write(zlib.decompress(first_row))
for zline in zlines:
f.write(zlib.decompress(zline))
import os
local_path = os.getcwd()
source = 'bikesharing\\hour.csv'
ram_shuffle(filename_in=local_path+'\\'+source, \
filename_out=local_path+'\\bikesharing\\shuffled_hour.csv', header=True)
型式
对于 Unix 用户来说,sort命令可以很容易地用一次调用来使用(-R参数),它比任何 Python 实现都更容易、更高效地混合大量文本文件。它可以与使用管道的减压和压缩步骤相结合。
因此,类似下面的命令应该可以做到这一点:
zcat sorted.gz | sort -R | gzip - > shuffled.gz
如果内存不足以存储所有压缩数据,唯一可行的解决方案是像在磁盘上一样对文件进行操作。下面的代码片段定义了一个函数,该函数会重复地将您的文件分割成越来越小的文件,在内部对它们进行洗牌,并在一个更大的文件中再次随机排列它们。结果不是完美的随机重排,而是行分散到足以破坏任何可能影响在线学习的先前顺序:
In: from random import shuffle
import pandas as pd
import numpy as np
import os
def disk_shuffle(filename_in, filename_out, header=True, iterations = 3, CHUNK_SIZE = 2500, SEP=','):
for i in range(iterations):
with open(filename_in, 'rb') as R:
iterator = pd.read_csv(R, chunksize=CHUNK_SIZE)
for n, df in enumerate(iterator):
if n==0 and header:
header_cols =SEP.join(df.columns)+'\n'
df.iloc[np.random.permutation(len(df))].to_csv(str(n)+'_chunk.csv', index=False, header=False, sep=SEP)
ordering = list(range(0,n+1))
shuffle(ordering)
with open(filename_out, 'wb') as W:
if header:
W.write(header_cols)
for f in ordering:
with open(str(f)+'_chunk.csv', 'r') as R:
for line in R:
W.write(line)
os.remove(str(f)+'_chunk.csv')
filename_in = filename_out
CHUNK_SIZE = int(CHUNK_SIZE / 2)
import os
local_path = os.getcwd()
source = 'bikesharing\\hour.csv'
disk_shuffle(filename_in=local_path+'\\'+source, \
filename_out=local_path+'\\bikesharing\\shuffled_hour.csv', header=True)
随机学习
定义了流过程后,现在是时候看一看学习过程了,因为正是学习及其特定需求决定了在预处理阶段处理数据和转换数据的最佳方式。
与批处理学习相反,在线学习使用更大数量的迭代,一次从每个单个实例中获取方向,因此与批处理优化相比,在线学习允许更不稳定的学习过程,批处理优化会立即从整体数据中获取正确的方向。
分批梯度下降
机器学习的核心算法梯度下降因此被重新考虑,以适应在线学习。当处理批处理数据时,梯度下降可以使用比统计算法少得多的计算来最小化线性回归分析的成本函数。梯度下降的复杂度按照 O(np)* 的顺序排序,使得学习回归系数即使在出现大的 n (代表观测数)和大的 p (变量数)时也是可行的。当训练数据中存在高度相关甚至完全相同的特征时,它也能很好地工作。
一切都基于一个简单的优化方法:通过多次迭代来改变参数集,使其从一个随机的解开始逐渐收敛到最优解。梯度下降是一种理论上众所周知的优化方法,对于某些问题(如回归问题)具有已知的收敛保证。然而,让我们从下面的图像开始,该图像表示参数可以取的值(表示假设空间)之间的复杂映射(典型的神经网络),并导致成本函数的最小化:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_17.jpg
用一个比喻的例子,梯度下降就像蒙着眼睛在山上行走。如果你想在看不到路径的情况下下降到最低的山谷,你可以沿着你感觉正在下坡的方向前进;尝试一会儿,然后停下来,再次感受地形,然后朝着你感觉它正在下坡的地方前进,以此类推,一次又一次。如果你继续朝着地面下降的地方前进,你最终会到达一个点,因为地形平坦,你不能再下降了。希望在那个时候,你已经到达目的地了。
使用这种方法,您需要执行以下操作:
-
决定起点。这通常是通过对函数参数的初始随机猜测来实现的(多次重启将确保初始化不会因为不幸运的初始设置而导致算法达到局部最优)。
-
能够感受到地形,也就是能够分辨出它什么时候下沉。用数学术语来说,这意味着你应该能够得到实际参数化函数相对于目标变量的导数,也就是你正在优化的成本函数的偏导数。请注意,梯度下降适用于您的所有数据,试图同时优化所有实例的预测。
-
决定你应该沿着导数指示的方向走多长时间。用数学术语来说,这相当于一个权重(通常称为 alpha),用来决定在优化的每一步你应该改变多少参数。这个方面可以被认为是学习因素,因为它指出了你应该在每个优化步骤中从数据中学到多少。与任何其他超参数一样,alpha 的最佳值可以通过对验证集的性能评估来确定。
-
Determine when to stop, given a too marginal improvement of the cost function with respect to the previous step. In such a sense, you also should be able to notice when something goes wrong and you are not going in the right direction maybe because you are using too large an alpha for the learning. This is actually a matter of momentum, that is, the speed at which the algorithm converges toward the optimum. It is just like throwing a ball down a mountainside: it just rolls over small dents in the surface, but if its speed is too high, it won’t stop at the right point. Thus, if alpha is set correctly, the momentum will naturally slow down as the algorithm is approaching the optimum as shown in the following image in the right panel. However, if it is not set properly, it will just jump over the global optimum and report further errors to be minimized, as depicted in the following image on the right panel, when the optimization process causes parameters to jump across different values without achieving the required error minimization:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_18.jpg
为了更好地描述梯度下降的情况,让我们以线性回归为例,其参数通过这样的过程进行优化。
我们从成本函数 J 开始,给定权重向量 w :
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_13.jpg
训练数据矩阵 X 和系数向量 w 之间的矩阵向量乘法 Xw 表示线性模型的预测,其与响应 y 的偏差被平方,然后求和,最后除以两次 n ,即实例数。
最初,向量 w 可以是实例化的,使用的随机数取自平均值为零、标准差为单位的标准化正态分布。(实际上,初始化可以用很多不同的方法来完成,所有这些方法都同样适用于近似成本函数为碗形且具有唯一最小值的线性回归。)这允许我们的算法沿着优化路径的某个地方开始,并且可以有效地加速过程的收敛。当我们优化线性回归时,初始化不应该给算法带来太多麻烦(最坏的情况是,错误的开始只会让它变慢)。相反,当我们使用梯度下降来优化不同的机器学习算法(如神经网络)时,我们可能会因为错误的初始化而陷入困境。举例来说,如果初始的 w 仅仅充满了零值(风险是被困在一个完美对称的山顶上,在那里没有方向性可以立即带来比任何其他更好的优化),就会发生这种情况。这也可能发生在具有多个局部极小值的优化过程中。
给定起始随机系数向量 w ,我们可以立即计算成本函数 J(w) ,并通过从每个系数中减去成本函数偏导数的部分α(α,学习率)来确定每个系数的初始方向,如下式所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_14.jpg
这可以在求解偏导数后更好地表达出来,如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_15.jpg
值得注意的是,在给定特征向量 xj 的情况下,对每个奇异系数( wj )进行更新,但同时基于所有预测(因此是求和)。
在迭代 w 中的所有系数之后,系数的更新将完成,并且优化可以通过计算偏导数和更新 w 向量来重新开始。
该过程的一个有趣的特征是,随着 w 向量接近最优配置,更新将越来越少。因此,当相对于之前的操作,在 w 中引起的变化很小时,该过程可以停止。无论如何,当学习率 alpha 设置为正确的大小时,我们的更新确实会减少。事实上,如果它的值太大,它可能导致优化绕道而行并失败,在某些情况下,导致过程完全发散,并且不可能最终收敛到解决方案。事实上,优化往往会超出目标,实际上离目标更远。
另一端,过小的α值不仅会使优化过程向目标移动得太慢,还可能很容易陷入局部极小值。对于更复杂的算法尤其如此,就像神经网络一样。至于线性回归和它的分类对应物,逻辑回归,因为优化曲线是碗状的,就像凹曲线一样,它的特点是只有一个极小值,根本没有局部极小值。
在我们说明的实现中,α是一个固定的常数(一个固定的学习速率梯度下降)。由于α在收敛到最优解的过程中扮演着如此重要的角色,因此人们设计了不同的策略,使其随着优化的进行而开始变大和缩小。我们将在检查 Scikit-learn 实现时讨论这些不同的方法。
随机梯度下降
到目前为止看到的梯度下降的版本被称为整批梯度下降,通过优化整个数据集的误差来工作,因此需要将其存储在内存中。核心外版本为随机梯度下降 ( SGD )和小批量梯度下降。
在这里,公式保持完全相同,但为了更新;一次只更新一个实例,这样我们就可以将核心数据留在存储中,只在内存中进行一次观察:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_16.jpg
核心思想是,如果实例是随机挑选的,没有特定的偏差,优化将平均地朝着目标成本最小化的方向发展。这解释了为什么我们讨论了如何从一个流中删除任何排序,并使其尽可能随机。例如,在自行车共享的例子中,如果你有随机梯度下降,首先学习早期季节的模式,然后关注夏季,然后关注秋季,以此类推,取决于优化停止时的季节,模型将被调整为比其他季节更好地预测一个季节,因为最近的大多数例子来自那个季节。在随机梯度下降优化中,当数据为独立同分布 ( 内径)时,保证收敛到全局最小值。实际上,i.i.d .意味着您的示例不应该有顺序或分布,而应该像从可用示例中随机挑选一样向算法提出。
Scikit-learn SGD 实施
在 Scikit-learn 包中可以找到大量在线学习算法。并非所有的机器学习算法都有在线对应物,但这个列表很有趣,而且还在稳步增长。对于监督学习,我们可以将可用的学习者分为分类器和回归器,并对它们进行枚举。
作为量词,我们可以提到以下几点:
sklearn.naive_bayes.MultinomialNBsklearn.naive_bayes.BernoulliNBsklearn.linear_model.Perceptronsklearn.linear_model.PassiveAggressiveClassifiersklearn.linear_model.SGDClassifier
作为回归者,我们有两个选择:
sklearn.linear_model.PassiveAggressiveRegressorsklearn.linear_model.SGDRegressor
他们都可以增量学习,一个实例一个实例地更新自己;虽然只有SGDClassifier和SGDRegressor是基于我们之前描述的随机梯度下降优化,它们是本章的主要主题。SGD 学习器对于所有大规模问题来说都是最优的,因为它们的复杂性与 O(knp) 有关,其中 k 是数据的传递次数, n 是实例的数量, p 是特征的数量(如果我们使用稀疏矩阵,则自然是非零特征):一个完全线性的时间学习器,花费的时间与所显示的示例数量成正比。
其他在线算法将作为比较基准。此外,所有算法都使用相同的应用编程接口,基于在线学习和小批量的partial_fit方法(当您流式传输更大的块而不是单个实例时)。共享同一个应用编程接口使得所有这些学习技术在你的学习框架中可以互换。
与使用所有可用数据进行即时优化的 fit 方法相反,partial_fit基于传递的每个单个实例进行部分优化。即使将数据集传递给partial_fit方法,算法也不会处理整个批次,而是处理其单个元素,使得学习操作的复杂性确实是线性的。此外,partial_fit之后的学习者可以通过后续的partial_fit调用不断更新,这使得它非常适合从连续的数据流中进行在线学习。
分类时,唯一需要注意的是,在第一次初始化时,有必要知道我们将学习多少个类,以及它们是如何被标记的。这可以使用 classes 参数来完成,指出数值标签的列表。这需要事先探索,通过数据流记录问题的标签,并注意它们的分布,以防它们不平衡——一个类相对于其他类在数字上太大或太小(但是 Sciket-learn 实现提供了一种自动处理问题的方法)。如果目标变量是数字,知道它的分布仍然是有用的,但这不是成功运行学习者所必需的。
在 Scikit-learn 中,我们有两个实现——一个用于分类问题(SGDClassifier)一个用于回归问题(SGDRegressor)。分类实现可以使用一对全 ( OVA )策略处理多类问题。这个策略意味着,给定 k 个类,建立 k 个模型,每个类一个模型,与其他类的所有实例相对,因此创建 k 个二进制分类。这将产生 k 组系数和 k 个预测向量及其概率。最后,基于每个类别相对于其他类别的发射概率,将分类分配给具有最高概率的类别。如果我们需要给出多项式分布的实际概率,我们可以简单地通过除以它们的和来归一化结果。(这是神经网络中 softmax 层正在发生的事情,我们将在后面的章节中看到。)
Scikit-learn 中的分类和回归 SGD 实现都具有不同的损失函数(代价函数,随机梯度下降优化的核心)。
对于分类,用loss参数表示,我们可以依赖于以下内容:
loss='log':经典逻辑回归loss='hinge':软余量,即线性支持向量机loss='modified_huber':平滑的铰链损失
对于回归,我们有三个损失函数:
loss='squared_loss': 普通最小 平方 ( OLS )进行线性回归loss='huber':针对异常值的稳健回归的 Huber 损失loss='epsilon_insensitive':线性支持向量回归
我们将给出一些使用经典统计损失函数的例子,它们是逻辑损失和 OLS。铰链损失和支持向量机 ( 支持向量机)将在下一章讨论,详细介绍它们的功能是必要的。
提醒一下(这样你就不用去查阅其他任何补充的机器学习书籍了),如果我们把回归函数定义为 h,它的预测由 h(X) 给出,因为 X 是特征的矩阵,那么下面是合适的公式:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_01.jpg
因此,要最小化的 OLS 成本函数如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_02.jpg
在逻辑回归中,将二元结果 0 / 1 转化为优势比,πy 为正结果的概率,公式如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_03.jpg
因此,物流成本函数定义如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_05.jpg
定义 SGD 学习参数
为了在 Scikit-learn 中定义 SGD 参数,在分类和回归问题中(这样它们对SGDClassifier和SGDRegressor都有效),我们必须弄清楚当您不能一次评估所有数据时,如何处理正确学习所必需的一些重要参数。
第一个是n_iter,通过数据定义迭代次数。最初设置为 5 ,经验表明,在给定其他默认参数的情况下,应该对其进行调整,以便学习者查看10^6示例;因此,设置它的一个好的解决方案是n_iter = np.ceil(10**6 / n),其中 n 是实例的数量。值得注意的是,n_iter仅适用于内存中的数据集,因此它仅在通过 fit 方法操作时起作用,而不适用于partial_fit。实际上,partial_fit将重复相同的数据,只是如果你在你的过程中对它进行重新流,并且重新流的正确迭代次数是要沿着学习过程本身进行测试的,受数据类型的影响。在下一章中,我们将说明超参数优化,并讨论正确的遍数。
型式
在进行小批量学习时,每次完整地传递完所有数据后,重新整理数据可能是有意义的。
shuffle是需要的参数,如果你想打乱你的数据。它是指内存中的小批量,而不是指核心外的数据排序。它也适用于partial_fit,但在这种情况下,它的效果非常有限。请始终将其设置为 True,但是对于要以块为单位传递的数据,请将您的数据移出内核,如我们之前所述。
warm_start是与拟合方法一起工作的另一个参数,因为它会记住以前的拟合系数(但如果已经动态修改,则不会记住学习速率)。如果使用partial_fit方法,算法将记住先前学习的系数和学习速率表的状态。
average参数触发一个计算技巧,在特定情况下,开始对新系数和旧系数进行平均,从而加快收敛速度。它可以设置为True或一个整数值,指示从什么情况开始取平均值。
最后但同样重要的是,我们有learning_rate及其相关参数,eta0和power_t。learning_rate参数意味着每个观察到的实例如何影响优化过程。当从理论角度提出 SGD 时,我们提出了恒定速率学习,这可以通过设置learning_rate='constant'来复制。
然而,存在其他选择,让 eta https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_19.jpg渐降低。在分类中,提出的解决方案是learning_rate='optimal',由以下公式给出:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_06.jpg
这里, t 是时间步长,由实例数乘以迭代次数给出, t0 是一个启发式选择的值,因为莱昂·博图的研究,其版本的随机梯度 SVM 严重影响了 SGD Scikit-learn 实现(http://leon.bottou.org/projects/sgd)。这种学习策略的明显优势是,随着看到更多的例子,学习会减少,避免了由异常值给出的优化的突然扰动。显然,这个策略也是现成的,也就是说你和它没有太多关系。
在回归中,建议的学习衰退由该公式给出,对应于learning_rate= 'invscaling':
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_07.jpg
这里,eta0和power_t是要通过优化搜索进行优化的超参数(它们最初被设置为0和0.5)。值得注意的是,使用invscaling学习速率,SGD 将从较低的学习速率开始,低于最佳速率,并且它将降低得更慢,在学习期间适应性更强。
数据流特征管理
数据流带来的问题是,您无法像处理完整的内存数据集那样进行评估。对于一个正确和最优的方法来馈送你的 SGD 核外算法,你首先必须调查数据(例如,通过对文件的初始实例进行卡盘)并找出你手头的数据类型。
我们区分以下类型的数据:
- 定量值
- 用整数编码的分类值
- 以文本形式表达的非结构化分类值
当数据是定量的时,它可以仅仅被馈送给 SGD 学习器,但是事实上该算法对特征缩放相当敏感;也就是说,你必须将所有的量化特征纳入相同的价值范围,否则学习过程不会轻易正确地收敛。可能的缩放策略是转换[0,1],[-1,1]范围内的所有值,或者通过将其平均值居中到零并将其方差转换为单位来标准化变量。对于缩放策略的选择,我们没有特别的建议,但是如果您正在处理稀疏矩阵,并且您的大部分值为零,那么在[0,1]范围内转换会特别有效。
至于内存学习,在转换训练集上的变量时,你必须注意你使用的值(基本上,你需要得到每个特征的最小值、最大值、平均值和标准差。)并在测试集中重用它们,以便获得一致的结果。
鉴于您正在流式传输数据,并且不可能将所有数据都上传到内存中,您必须通过传递所有数据或至少一部分数据来计算它们(采样总是一种可行的解决方案)。使用一个短暂的流(一个你无法复制的流)会带来更具挑战性的问题;事实上,你必须不断追踪你不断接受的价值。
如果采样只需要您计算一大块 n 实例的统计数据(假设您的流没有特定的顺序),那么动态计算统计数据需要您记录正确的度量。
对于最小值和最大值,您需要为每个定量特征存储一个变量。从第一个值开始,您将该值存储为您的初始最小值和最大值,对于您将从流中接收的每个新值,您将不得不将其与之前记录的最小值和最大值进行比较。如果新实例超出了先前的值范围,则只需相应地更新变量。
此外,平均值不会带来任何特别的问题,因为您只需要保存看到的值的总和和实例的计数。至于方差,你需要回忆一下教科书的公式如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_08.jpg
值得注意的是,您需要知道平均值μ,这也是您从流中逐步学习的。然而,该公式可以解释如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_09.jpg
由于您只是记录实例的数量 n 和 x 值的总和,您只需要存储另一个变量,它是 x 平方值的总和,您将拥有配方的所有成分。
例如,使用自行车共享数据集,我们可以计算报告最终结果的运行平均值、标准偏差和范围,并绘制数据从磁盘流出时这些统计数据的变化情况:
In: import os, csv
local_path = os.getcwd()
source = 'bikesharing\\hour.csv'
SEP=','
running_mean = list()
running_std = list()
with open(local_path+'\\'+source, 'rb') as R:
iterator = csv.DictReader(R, delimiter=SEP)
x = 0.0
x_squared = 0.0
for n, row in enumerate(iterator):
temp = float(row['temp'])
if n == 0:
max_x, min_x = temp, temp
else:
max_x, min_x = max(temp, max_x),min(temp, min_x)
x += temp
x_squared += temp**2
running_mean.append(x / (n+1))
running_std.append(((x_squared - (x**2)/(n+1))/(n+1))**0.5)
# DATA PROCESSING placeholder
# MACHINE LEARNING placeholder
pass
print ('Total rows: %i' % (n+1))
print ('Feature \'temp\': mean=%0.3f, max=%0.3f, min=%0.3f,\ sd=%0.3f' % (running_mean[-1], max_x, min_x, running_std[-1]))
Out: Total rows: 17379
Feature 'temp': mean=0.497, max=1.000, min=0.020, sd=0.193
过一会儿,数据将从数据源流出,与temp特征相关的关键数字将被记录为平均值的运行估计,标准偏差将被计算并存储在两个单独的列表中。
通过绘制列表中的值,我们可以检查估计值相对于最终数字的波动程度,并了解在获得稳定的平均值和标准偏差估计值之前需要多少个实例:
In: import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(running_mean,'r-', label='mean')
plt.plot(running_std,'b-', label='standard deviation')
plt.ylim(0.0,0.6)
plt.xlabel('Number of training examples')
plt.ylabel('Value')
plt.legend(loc='lower right', numpoints= 1)
plt.show()
如果您之前处理了原始的自行车共享数据集,您将获得一个图表,其中数据明显有趋势(由于时间顺序,因为温度自然会随着季节而变化):
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_10.jpg
相反,如果我们使用数据集的混洗版本作为源,shuffled_hour.csv文件,我们可以获得几个更加稳定和快速收敛的估计。因此,我们将会了解到一个近似但更可靠的平均值和标准偏差的估计,从流中观察到更少的实例:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_11.jpg
两张图的不同提醒我们随机化观察顺序的重要性。即使学习简单的描述性统计也会受到数据趋势的严重影响;因此,在通过 SGD 学习复杂模型时,我们必须更加注意。
描述目标
此外,目标变量也需要在启动前进行探索。事实上,我们需要确定它假设了什么样的值,如果是绝对的,并弄清楚它在类中是否不平衡,或者当是一个数字时是否有偏斜分布。
如果我们正在学习一个数字响应,我们可以对特性采用前面显示的相同策略,而对于类,一个 Python 字典记录类的数量(键)和它们的频率(值)就足够了。
例如,我们将下载一个数据集进行分类,森林覆盖类型数据。
为了快速下载和准备数据,我们将使用数据集中定义的gzip_from_UCI函数亲自尝试本章的部分:
In: UCI_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/covtype/covtype.data.gz'
gzip_from_UCI(UCI_url)
如果在运行代码时出现问题,或者您更喜欢自己准备文件,只需前往 UCI 网站,下载数据集,并将其解包到 Python 当前工作的目录中:
https://archive . ics . UCI . edu/ml/机器学习-数据库/covtype/
一旦数据在磁盘上可用,我们可以扫描 581,012 个实例,将代表我们应该估计的类别的每行的最后一个值转换为其对应的森林覆盖类型:
In: import os, csv
local_path = os.getcwd()
source = 'covtype.data'
SEP=','
forest_type = {1:"Spruce/Fir", 2:"Lodgepole Pine", \
3:"Ponderosa Pine", 4:"Cottonwood/Willow",\
5:"Aspen", 6:"Douglas-fir", 7:"Krummholz"}
forest_type_count = {value:0 for value in forest_type.values()}
forest_type_count['Other'] = 0
lodgepole_pine = 0
spruce = 0
proportions = list()
with open(local_path+'\\'+source, 'rb') as R:
iterator = csv.reader(R, delimiter=SEP)
for n, row in enumerate(iterator):
response = int(row[-1]) # The response is the last value
try:
forest_type_count[forest_type[response]] +=1
if response == 1:
spruce += 1
elif response == 2:
lodgepole_pine +=1
if n % 10000 == 0:
proportions.append([spruce/float(n+1),\
lodgepole_pine/float(n+1)])
except:
forest_type_count['Other'] += 1
print ('Total rows: %i' % (n+1))
print ('Frequency of classes:')
for ftype, freq in sorted([(t,v) for t,v \
in forest_type_count.iteritems()], key = \
lambda x: x[1], reverse=True):
print ("%-18s: %6i %04.1f%%" % \
(ftype, freq, freq*100/float(n+1)))
Out: Total rows: 581012
Frequency of classes:
Lodgepole Pine : 283301 48.8%
Spruce/Fir : 211840 36.5%
Ponderosa Pine : 35754 06.2%
Krummholz : 20510 03.5%
Douglas-fir : 17367 03.0%
Aspen : 9493 01.6%
Cottonwood/Willow : 2747 00.5%
Other : 0 00.0%
输出显示两个类Lodgepole Pine和Spruce/Fir占据了大部分观察值。如果示例在流中被适当地打乱,SGD 将适当地学习正确的先验分布,并因此调整其概率发射(后验概率)。
如果与我们目前的情况相反,您的目标不是提高分类精度,而是增加接收机工作特性 ( ROC ) 曲线下面积 ( AUC )或 f1-score(可用于评估的误差函数;有关概述,您可以直接参考 Scikit-learn 文档,网址为http://Scikit-learn . org/stable/modules/model _ evaluation . html关于在不平衡数据上训练的分类模型,然后提供的信息可以帮助您在定义SGDClassifier时使用class_weight参数平衡权重,或者在部分拟合模型时使用sample_weight参数平衡权重。两者都通过增加或减少观察到的实例的重量来改变其影响。在这两种方式中,操作这两个参数将改变先验分布。加权类和实例将在下一章讨论。
在进行训练和跟班工作之前,我们可以检查一下类的比例是否总是一致的,以便向 SGD 传达正确的先验概率:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
proportions = np.array(proportions)
plt.plot(proportions[:,0],'r-', label='Spruce/Fir')
plt.plot(proportions[:,1],'b-', label='Lodgepole Pine')
plt.ylim(0.0,0.8)
plt.xlabel('Training examples (unit=10000)')
plt.ylabel('%')
plt.legend(loc='lower right', numpoints= 1)
plt.show()
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_02_12.jpg
在上图中,您可以注意到随着我们按现有顺序对数据进行流式传输,示例的百分比是如何变化的。在这种情况下,如果我们想要一个随机的在线算法从数据中正确学习,洗牌是非常必要的。
其实的比例是可变的;这个数据集有某种排序,可能是地理排序,应该通过重新排列数据来纠正,否则我们将冒着高估或低估某些类别相对于其他类别的风险。
散列技巧
如果在你的特性中,有类别(以值编码或者以文本形式留下),事情会变得有点棘手。通常,在批处理学习中,你会对类别进行一次热编码,并获得与类别一样多的新的二进制特征。不幸的是,在一个流中,你事先不知道你将处理多少个类别,甚至不能通过采样来确定它们的数量,因为稀有类别可能在流中出现得很晚,或者需要太大的样本才能被发现。您必须首先流式传输所有数据,并记录出现的每个类别。无论如何,流可能是短暂的,有时类的数量可能非常大,以至于它们不能存储在内存中。在线广告数据就是这样一个例子,因为它的量很大,很难存储起来,并且因为该流不能被传递超过一次。此外,广告数据多种多样,特征不断变化。
处理文本使这个问题变得更加明显,因为你无法预料你将要分析的文本中会有什么样的单词。在单词包模型中,对于每个文本,当前单词被计数,它们的频率值被粘贴在每个单词特有的特征向量的元素中,您应该能够预先将每个单词映射到一个索引。即使你能做到这一点,当一个未知的单词(因此以前从未映射过)在测试中或者预测器在生产中出现时,你也必须处理这种情况。此外,还应该补充的是,作为一种口语,由数十万甚至数百万个不同术语组成的词典并不罕见。
简单回顾一下,如果你能提前知道你的特性中的类,你可以使用 Scikit-learn(http://Scikit-learn . org/stable/modules/generated/sklearn . premization . onehotencoder . html)的一键编码器来处理它们。我们实际上不会在这里说明它,但基本上,这种方法与您在使用批处理学习时应用的方法没有任何不同。我们想向你说明的是,当你不能真正应用一热编码时。
有一种解决方案被称为哈希技巧,因为它基于哈希函数,可以处理整数或字符串形式的文本和分类变量。它也可以处理混合了数量特征数值的分类变量。one-hot 编码的核心问题是,它在将特征映射到特征向量的某个位置后,将该位置分配给该位置的值。哈希技巧可以将一个值唯一地映射到它的位置,而无需事先评估该特征,因为它利用了哈希函数的核心特性——确定性地将一个值或字符串转换为整数值。
因此,在应用它之前,唯一必要的准备工作是创建一个足够大的稀疏向量来表示数据的复杂性(可能包含从 2**19 到 2**30 的元素,具体取决于可用内存、计算机的总线架构以及您正在使用的哈希函数的类型)。如果您正在处理一些文本,您还需要一个标记器,即一个将您的文本拆分成单个单词并删除标点符号的功能。
一个简单的玩具例子就能说明这一点。我们将使用 Scikit-learn 包中的两个专用函数:HashingVectorizer,一个基于哈希技巧的用于文本数据的转换器,以及FeatureHasher,这是另一个转换器,专门用于将表示为 Python 字典的数据行转换为稀疏的特征向量。
作为第一个例子,我们将把一个短语变成一个向量:
In: from sklearn.feature_extraction.text import HashingVectorizer
h = HashingVectorizer(n_features=1000, binary=True, norm=None)
sparse_vector = h.transform(['A simple toy example will make clear how it works.'])
print(sparse_vector)
Out:
(0, 61) 1.0
(0, 271) 1.0
(0, 287) 1.0
(0, 452) 1.0
(0, 462) 1.0
(0, 539) 1.0
(0, 605) 1.0
(0, 726) 1.0
(0, 918) 1.0
生成的向量只有在特定索引处有单位值,指出短语(单词)中的标记和向量中特定位置之间的关联。不幸的是,除非我们在外部 Python 字典中映射每个令牌的哈希值,否则关联无法逆转。虽然这种映射是可能的,但它确实会消耗内存,因为根据语言和主题的不同,字典可能会很大,在数百万条目甚至更多的范围内。实际上,我们不需要保持这样的跟踪,因为散列函数保证总是从同一个令牌产生相同的索引。
哈希技巧的一个真正问题是冲突的可能性,当两个不同的令牌关联到同一个索引时就会发生冲突。在使用大型单词词典时,这是一种罕见但可能发生的情况。另一方面,在一个由数百万个系数组成的模型中,有影响力的很少。因此,如果发生冲突,可能会涉及两个不重要的令牌。当使用散列技巧时,概率是站在你这边的,因为有足够大的输出向量(例如,元素的数量在 2^24 之上),尽管冲突总是可能的,但是它们极不可能涉及模型的重要元素。
哈希技巧可以应用于正常特征向量,尤其是当有分类变量时。下面是一个带有FeatureHasher的例子:
In: from sklearn.feature_extraction import FeatureHasher
h = FeatureHasher(n_features=1000, non_negative=True)
example_row = {'numeric feature':3, 'another numeric feature':2, 'Categorical feature = 3':1, 'f1*f2*f3':1*2*3}
print (example_row)
Out: {'another numeric feature': 2, 'f1*f2*f3': 6, 'numeric feature': 3, 'Categorical feature = 3': 1}
如果您的 Python 字典包含数值的要素名称以及任何分类变量的要素名称和值的组合,字典的值将使用关键字的散列索引进行映射,从而创建一个一次性编码的要素向量,准备好由 SGD 算法学习:
In: print (h.transform([example_row]))
Out:
(0, 16) 2.0
(0, 373) 1.0
(0, 884) 6.0
(0, 945) 3.0
其他基本变换
正如我们从数据存储中绘制的示例一样,除了将分类特征转换为数字特征之外,还可以应用另一种转换,以便让学习算法增加其预测能力。变换可以通过函数(通过应用平方根、对数或其他变换函数)或通过对要素组的操作应用于要素。
在下一章,我们将提出关于多项式展开和随机厨房水槽方法的详细例子。在本章中,我们将预测如何通过嵌套迭代创建二次特征。二次特征通常是在创建多项式展开时创建的,其目的是截取预测特征如何在它们之间相互作用;这可能会以意想不到的方式影响目标变量中的响应。
作为直观阐明为什么二次特征在目标反应建模中很重要的一个例子,让我们解释两种药物对患者的影响。事实上,可能每种药物对我们正在对抗的疾病或多或少都有效。无论如何,这两种药物是由不同的成分组成的,当患者一起摄入时,往往会抵消彼此的效果。在这种情况下,虽然两种药物都有效,但由于它们的负面相互作用,它们一起根本不起作用。
从这个意义上说,特征之间的相互作用可以在各种各样的特征中找到,而不仅仅是在医学中,找到最重要的一个是至关重要的,这样我们的模型才能更好地预测它的目标。如果我们没有意识到某些特性与我们的问题相互作用,我们唯一的选择就是系统地测试它们,并让我们的模型保留那些工作得更好的特性。
在下面这个简单的例子中,一个名为 v 的向量,一个我们想象的刚刚在内存中流动以便被学习的例子,被转换成另一个向量 vv ,其中 v 的原始特征伴随着它们的乘法交互的结果(每个特征被所有其他特征相乘一次)。给定更多数量的特征,学习算法将使用 vv 向量代替原始的 v 向量,以实现数据的更好拟合:
In: import numpy as np
v = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
vv = np.hstack((v, [v[i]*v[j] for i in range(len(v)) for j in range(i+1, len(v))]))
print vv
Out:[ 1 2 3 4 5 6 7 8 9 10 2 3 4 5 6 7 8 9 10 6 8 10 12 14 16 18 20 12 15 18 21 24 27 30 20 24 28 32 36 40 30 35 40 45 50 42 48 54 60 56 63 70 72 80 90]
类似的转换,或者甚至更复杂的转换,可以在示例流向学习算法时动态生成,利用了数据批量小(有时减少到单个示例)的事实,并扩展了特征的数量,因此可以在内存中可行地实现几个示例。在下一章中,我们将探索更多这类转换的例子,以及它们成功集成到学习管道中的例子。
流中的测试和验证
在引入 SGD 后,我们没有展示完整的训练示例,因为我们需要介绍如何在流中测试和验证。使用批处理学习、测试和交叉验证是一个随机化观察顺序的问题,将数据集切割成折叠并取一个精确的折叠作为测试集,或者依次系统地取所有折叠来测试您的算法的学习能力。
流不能保存在内存中,因此在以下实例已经随机化的基础上,最好的解决方案是在流展开一段时间后进行验证实例,或者在数据流中系统地使用精确的、可复制的模式。
流的部分样本外方法实际上与测试样本相当,只有事先知道流的长度才能成功完成。对于连续流,这仍然是可能的,但是意味着一旦测试实例开始,就必须停止学习。这种方法被称为 n 策略后的保持。
交叉验证类型的方法可以使用系统的和可复制的验证实例抽样。定义了起始缓冲区后,每隔 n 次选择一个实例进行验证。这样的实例不是用于培训,而是用于测试目的。这种方法被称为每隔 n 次的周期性保持策略。
由于验证是在单个实例的基础上进行的,因此会计算一个全局性能度量,使用最新的一组 k 度量对数据的同一次传递中或以类似窗口的方式收集的所有误差度量进行平均,其中 k 是您认为具有有效代表性的一些测试。
事实上,在第一遍中,所有实例实际上都是学习算法看不到的。因此,当算法接收要学习的案例时,测试算法是有用的,在学习之前,根据观察结果验证其响应。这种方法被称为渐进验证。
在行动中尝试 SGD
作为本章的结论,我们将实现两个示例:一个用于基于森林覆盖类型数据的分类,一个用于基于自行车共享数据集的回归。我们将看到如何将之前对响应和特性分布的见解付诸实践,以及如何为每个问题使用最佳验证策略。
从分类问题开始,有两个值得注意的方面需要考虑。作为一个多类问题,首先我们注意到数据库中存在某种排序,并且类沿着实例流分布。作为第一步,我们将使用在章节中定义的ram_shuffle函数对数据进行洗牌,注意实例部分的顺序:
In: import os
local_path = os.getcwd()
source = 'covtype.data'
ram_shuffle(filename_in=local_path+'\\'+source, \
filename_out=local_path+'\\shuffled_covtype.data', \
header=False)
当我们在不占用太多磁盘的情况下压缩内存中的行并进行洗牌时,我们可以快速获得一个新的工作文件。下面的代码将使用对数丢失(相当于逻辑回归)来训练SGDClassifier,这样它就利用了我们以前对数据集中存在的类的了解。forest_type列表包含课程的所有代码,并且每次都传递给(尽管只有一个,第一个就足够了)SGD 学习者的partial_fit方法。
出于验证目的,我们在200.000观察到的情况下定义冷启动。每十次,就有一次被排除在培训之外,用于验证。这个模式允许重复性,即使我们要多次传递数据;每次通过时,相同的实例将作为样本外测试被忽略,从而允许创建验证曲线来测试多次通过相同数据的效果。
保持模式也伴随着渐进的验证。所以冷启动后的每一个病例在被送去训练前都要进行评估。虽然渐进式验证提供了有趣的反馈,但这种方法仅适用于第一遍;事实上,在最初的传递之后,所有的观察(但是保持模式中的观察)都将成为样本内的实例。在我们的示例中,我们将只进行一次传递。
提醒一下,数据集有581.012个实例,用 SGD 进行流式处理和建模可能会有点长(对于单台计算机来说,这是一个相当大的问题)。虽然我们放置了一个限制器来观察仅仅250.000的实例,但是仍然允许你的计算机运行大约 15-20 分钟,然后期待结果:
In: import csv, time
import numpy as np
from sklearn.linear_model import SGDClassifier
source = 'shuffled_covtype.data'
SEP=','
forest_type = [t+1 for t in range(7)]
SGD = SGDClassifier(loss='log', penalty=None, random_state=1, average=True)
accuracy = 0
holdout_count = 0
prog_accuracy = 0
prog_count = 0
cold_start = 200000
k_holdout = 10
with open(local_path+'\\'+source, 'rb') as R:
iterator = csv.reader(R, delimiter=SEP)
for n, row in enumerate(iterator):
if n > 250000: # Reducing the running time of the experiment
break
# DATA PROCESSING
response = np.array([int(row[-1])]) # The response is the last value
features = np.array(map(float,row[:-1])).reshape(1,-1)
# MACHINE LEARNING
if (n+1) >= cold_start and (n+1-cold_start) % k_holdout==0:
if int(SGD.predict(features))==response[0]:
accuracy += 1
holdout_count += 1
if (n+1-cold_start) % 25000 == 0 and (n+1) > cold_start:
print '%s holdout accuracy: %0.3f' % (time.strftime('%X'), accuracy / float(holdout_count))
else:
# PROGRESSIVE VALIDATION
if (n+1) >= cold_start:
if int(SGD.predict(features))==response[0]:
prog_accuracy += 1
prog_count += 1
if n % 25000 == 0 and n > cold_start:
print '%s progressive accuracy: %0.3f' % (time.strftime('%X'), prog_accuracy / float(prog_count))
# LEARNING PHASE
SGD.partial_fit(features, response, classes=forest_type)
print '%s FINAL holdout accuracy: %0.3f' % (time.strftime('%X'), accuracy / ((n+1-cold_start) / float(k_holdout)))
print '%s FINAL progressive accuracy: %0.3f' % (time.strftime('%X'), prog_accuracy / float(prog_count))
Out:
18:45:10 holdout accuracy: 0.627
18:45:10 progressive accuracy: 0.613
18:45:59 holdout accuracy: 0.621
18:45:59 progressive accuracy: 0.617
18:45:59 FINAL holdout accuracy: 0.621
18:45:59 FINAL progressive accuracy: 0.617
作为第二个例子,我们将尝试基于一系列天气和时间信息来预测华盛顿共享自行车的数量。给定数据集的历史顺序,我们不会将其打乱,并将问题视为时间序列问题。我们的验证策略是在看到一定数量的例子后测试结果,以便复制从那时起预测的不确定性。
有趣的是,注意到一些特征是分类的,所以我们应用了 Scikit-learn 的FeatureHasher类,以便将记录在字典中的类别表示为由变量名和类别代码组成的联合字符串。字典中为每个键分配的值是 1,以便类似于哈希技巧将创建的稀疏向量中的二进制变量:
In: import csv, time, os
import numpy as np
from sklearn.linear_model import SGDRegressor
from sklearn.feature_extraction import FeatureHasher
source = '\\bikesharing\\hour.csv'
local_path = os.getcwd()
SEP=','
def apply_log(x): return np.log(float(x)+1)
def apply_exp(x): return np.exp(float(x))-1
SGD = SGDRegressor(loss='squared_loss', penalty=None, random_state=1, average=True)
h = FeatureHasher(non_negative=True)
val_rmse = 0
val_rmsle = 0
predictions_start = 16000
with open(local_path+'\\'+source, 'rb') as R:
iterator = csv.DictReader(R, delimiter=SEP)
for n, row in enumerate(iterator):
# DATA PROCESSING
target = np.array([apply_log(row['cnt'])])
features = {k+'_'+v:1 for k,v in row.iteritems() \
if k in ['holiday','hr','mnth','season', \
'weathersit','weekday','workingday','yr']}
numeric_features = {k:float(v) for k,v in \
row.iteritems() if k in ['hum', 'temp', '\
atemp', 'windspeed']}
features.update(numeric_features)
hashed_features = h.transform([features])
# MACHINE LEARNING
if (n+1) >= predictions_start:
# HOLDOUT AFTER N PHASE
predicted = SGD.predict(hashed_features)
val_rmse += (apply_exp(predicted) \
- apply_exp(target))**2
val_rmsle += (predicted - target)**2
if (n-predictions_start+1) % 250 == 0 \
and (n+1) > predictions_start:
print '%s holdout RMSE: %0.3f' \
% (time.strftime('%X'), (val_rmse \
/ float(n-predictions_start+1))**0.5),
print 'holdout RMSLE: %0.3f' % ((val_rmsle \
/ float(n-predictions_start+1))**0.5)
else:
# LEARNING PHASE
SGD.partial_fit(hashed_features, target)
print '%s FINAL holdout RMSE: %0.3f' % \
(time.strftime('%X'), (val_rmse \
/ float(n-predictions_start+1))**0.5)
print '%s FINAL holdout RMSLE: %0.3f' % \
(time.strftime('%X'), (val_rmsle \
/ float(n-predictions_start+1))**0.5)
Out:
18:02:54 holdout RMSE: 281.065 holdout RMSLE: 1.899
18:02:54 holdout RMSE: 254.958 holdout RMSLE: 1.800
18:02:54 holdout RMSE: 255.456 holdout RMSLE: 1.798
18:52:54 holdout RMSE: 254.563 holdout RMSLE: 1.818
18:52:54 holdout RMSE: 239.740 holdout RMSLE: 1.737
18:52:54 FINAL holdout RMSE: 229.274
18:52:54 FINAL holdout RMSLE: 1.678
总结
在这一章中,我们已经看到了如何通过从硬盘上的文本文件或数据库中流式传输数据来实现核心外的学习,无论数据有多大。这些方法当然适用于比我们用来演示它们的例子大得多的数据集(这实际上可以使用非平均的、强大的硬件在内存中解决)。
我们还解释了使核外学习成为可能的核心算法——SGD——并检查了它的优缺点,强调了流必须是真正随机的(这意味着以随机的顺序)才能真正有效,除非顺序是学习目标的一部分。特别是,我们引入了 SGD 的 Scikit-learn 实现,将我们的重点限制在线性和逻辑回归损失函数上。
最后,我们讨论了数据准备,介绍了流的哈希技巧和验证策略,并总结了 SGD 拟合两种不同模型——分类和回归的知识。
在下一章中,我们将通过找出如何在我们的学习模式中启用非线性和支持向量机的铰链损失来继续丰富我们的核心外能力。我们还将展示 Scikit-learn 的替代产品,例如 Liblinear 、 Vowpal Wabbit 和 StreamSVM 。虽然作为外壳命令运行,但所有这些命令都可以很容易地被 Python 脚本包装和控制。
三、快速 SVM 实现
在前一章中已经试验过在线式学习,与批处理学习相比,您可能会对它的简单性、有效性和可扩展性感到惊讶。尽管一次只学习一个示例,但 SGD 可以很好地逼近结果,就好像所有数据都驻留在核心内存中,而您使用的是批处理算法一样。你所需要的是你的流确实是随机的(数据中没有趋势),并且学习者很好地适应了问题(学习率通常是固定的关键参数)。
无论如何,仔细检查这些成就,结果仍然只是可与批量线性模型相比,但不能与更复杂的学习者相比,这些学习者的特征是方差高于偏差,例如支持向量机、神经网络或决策树的打包和增强集成。
对于某些问题,如高而宽但稀疏的数据,根据观察,只有线性组合可能就足够了,因为具有更多数据的简单算法往往胜过在更少数据上训练的更复杂的算法。然而,即使使用线性模型,并通过将现有特征显式映射到更高维度的特征(使用不同顺序的交互、多项式展开和核近似),我们也可以加速和改进响应和特征之间复杂非线性关系的学习。
因此,在这一章中,我们将首先介绍线性支持向量机,作为线性模型的机器学习算法的替代,由不同的方法来解决从数据中学习的问题。然后,我们将演示如何从现有的特征中创建更丰富的特征,以便在面对大规模数据,尤其是高数据(即有许多案例可供学习的数据集)时,以更好的方式解决我们的机器学习任务。
总之,在本章中,我们将涵盖以下主题:
- 介绍支持向量机,并为您提供基本概念和数学公式,以了解它们的工作原理
- 提出具有铰链损失的 SGD 作为大规模任务的可行解决方案,该解决方案使用与批量 SVM 相同的优化方法
- 建议伴随 SGD 的非线性近似
- 提供除了 Scikit-learn 提供的 SGD 算法之外的其他大规模在线解决方案的概述
您自己要实验的数据集
与上一章一样,我们将使用来自 UCI 机器学习资源库的数据集,特别是自行车共享数据集(回归问题)和森林覆盖类型数据(多类分类问题)。
如果您以前没有这样做过,或者如果您需要再次下载这两个数据集,您将需要在数据集中定义的几个函数来亲自尝试真实的东西第 2 章、Scikit 中的可扩展学习-学习。需要的功能有unzip_from_UCI 和gzip_from_UCI。两者都有一个到 UCI 存储库的 Python 连接;下载一个压缩文件,并在 Python 工作目录下解压。如果您从 IPython 单元调用函数,您将在 IPython 查找它们的地方找到必要的新目录和文件。
万一功能对你不起作用,没关系;我们将为您提供直接下载的链接。之后,您所要做的就是将当前工作 Python 目录中的数据解包,您可以通过在您的 Python 接口(IPython 或任何 IDE)上运行以下命令来发现该目录:
In: import os
print "Current directory is: \"%s\"" % (os.getcwd())
Out: Current directory is: "C:\scisoft\WinPython-64bit-2.7.9.4\notebooks\Packt - Large Scale"
单车共享数据集
数据集由 CSV 格式的两个文件组成,包含 2011 年至 2012 年期间美国华盛顿首都自行车共享系统内每小时和每天租赁的自行车数量。提醒一下,这些数据包含租赁当天的相应天气和季节信息。
下面的代码片段将使用方便的unzip_from_UCI包装函数将数据集保存在本地硬盘上:
In: UCI_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/00275/Bike-Sharing-Dataset.zip'
unzip_from_UCI(UCI_url, dest='bikesharing')
Out: Extracting in C:\scisoft\WinPython-64bit-2.7.9.4\notebooks\bikesharing
unzipping day.csv
unzipping hour.csv
如果成功运行,代码将指示 CSV 文件保存在哪个目录中,并打印两个解压缩文件的名称。如果不成功,只需从https://archive . ics . UCI . edu/ml/machine-learning-databases/00275/Bike-Sharing-dataset . zip下载该文件,并将两个文件day.csv和hour.csv解压缩到您之前在 Python 工作目录中创建的名为bikesharing的目录中。
cover type 数据集
covertype 数据集由 Jock A. Blackard、Denis J. Dean 博士、Charles W. Anderson 博士和科罗拉多州立大学捐赠,包含 581,012 个示例和一系列 54 个制图变量,范围从海拔到土壤类型,预计能够预测森林覆盖类型,包括 7 种类型(因此这是一个多类问题)。为了确保与相同数据的学术研究的可比性,说明建议使用前 11,340 条记录进行培训,然后使用 3,780 条记录进行验证,最后使用剩余的 565,892 条记录作为测试示例:
In: UCI_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/covtype/covtype.data.gz'
gzip_from_UCI(UCI_url)
如果在运行代码时出现问题,或者您更喜欢自己准备文件,只需前往 UCI 网站,从https://archive . ics . UCI . edu/ml/机器学习-databases/covtype/covtype . data . gz下载数据集,并将其解包到 Python 当前正在处理的目录中。
支持向量机
支持向量机 ( 支持向量机)是一套用于分类和回归(也用于离群点检测)的监督学习技术,由于特殊函数——核函数的可用性,它可以适用于线性和非线性模型,因此非常通用。这种核函数的特点是能够使用有限的计算量将输入特征映射成新的、更复杂的特征向量。核函数非线性地重组原始特征,使得通过非常复杂的函数映射响应成为可能。从这个意义上说,支持向量机作为通用逼近器可以与神经网络相媲美,因此在许多问题中具有相似的预测能力。
与前一章中看到的线性模型相反,支持向量机最初是作为解决分类问题的一种方法,而不是回归问题。
支持向量机是由数学家弗拉基米尔·瓦普尼克和计算机科学家科琳娜·科尔特斯(但也有许多其他贡献者与瓦普尼克在算法上合作)于 90 年代在美国电话电报公司的实验室发明的。本质上,SVM 试图通过在特征空间中找到一个特定的分类超平面来解决分类问题。这种特定的超平面必须被表征为在类的边界之间具有最大的分隔边界的超平面(该边界旨在作为间隙,类本身之间的空间,没有任何示例)。
这种直觉意味着两个结果:
- 经验上,支持向量机试图通过在训练集中找到一个恰好在观察类中间的解来最小化测试误差,因此该解显然是计算性的(它是基于二次规划的优化—https://en.wikipedia.org/wiki/Quadratic_programming)。
- 由于解决方案仅基于相邻示例(称为支持向量)设置的类边界,因此可以忽略其他示例,这使得该技术对异常值不敏感,并且比基于矩阵求逆的方法(如线性模型)占用的内存更少。
给定这样一个算法的总体概述,我们将花费几页指出表征支持向量机的关键公式。尽管对该方法的完整而详细的解释超出了本书的范围,但概述它的工作原理确实有助于弄清楚该技术的幕后发生了什么,并为理解如何将其扩展到大数据提供了基础。
历史上,支持向量机被认为是硬边界分类器,就像感知器一样。事实上,最初设置支持向量机是为了寻找两个分离类的超平面,这两个超平面的倒易距离是最大可能的。这种方法对于线性可分离的合成数据非常有效。无论如何,在硬边界版本中,当 SVM 面对非线性可分离数据时,它只能使用特征的非线性变换来成功。然而,当错误分类错误是由于数据中的噪声而不是非线性时,它们总是被认为是失败的。
因此,通过考虑误差严重程度的成本函数引入了软裕度(因为硬裕度仅在发生误差时才进行跟踪),从而允许对误差不太大的错误分类情况有一定的容限,因为它们位于分离超平面旁边。
自从引入软边界以来,支持向量机也能够承受噪声引起的不可分性。通过围绕一个松弛变量构建成本函数来引入软边际,该变量近似于错误分类示例的数量。这样的松弛变量也被称为经验风险(从训练数据的角度来看,做出错误分类的风险)。
在数学公式中,给定 n 个示例的矩阵数据集 X 和 m 特征以及根据+1(归属)和-1(不归属)表示属于一个类的响应向量,二进制分类 SVM 努力最小化以下成本函数:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_07.jpg
在前面的函数中,w 是表示分离超平面和偏差 b 的系数向量,表示与原点的偏移。还有λ(λ``>=0),是正则化参数。
为了更好地理解成本函数是如何工作的,有必要将其分为两部分。第一部分是正则项:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_08.jpg
正则化项与向量 w 取高值时的最小化过程形成对比。第二项称为损失项或松弛变量,实际上是 SVM 最小化程序的核心:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_09.jpg
损失项输出错误分类误差的近似值。事实上,总和将倾向于为每个分类误差增加一个单位值,其总和除以 n,即示例数,将提供分类误差的大致比例。
通常,如在 Scikit-learn 实现中,λ从正则化项中移除,并由错误分类参数 C 乘以损失项来代替:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_10.jpg
前一个参数λ和新的 C 之间的关系如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_11.jpg
这只是一个惯例问题,因为优化公式中从参数λ到 C 的变化并不意味着不同的结果。
损失项的影响由超参数 C 来调节。C 的高值会对错误施加高惩罚,从而迫使 SVM 尝试对所有训练示例进行正确分类。因此,较大的 C 值往往会迫使余量更紧,并考虑较少的支持向量。裕度的这种减小转化为偏差的增加和方差的减小。
这导致我们具体说明某些观察相对于其他观察的作用;事实上,我们将那些分类错误或分类不可信的例子定义为支持向量,因为它们在边界内(噪声观测使分类分离不可能)。只考虑这样的例子,优化是可能的,这使得 SVM 确实是一种内存高效的技术:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_01.jpg
在前面的可视化中,可以注意到两组点(蓝色和白色)在两个特征维度上的投影。C 超参数设置为 1.0 的 SVM 解可以很容易地发现分隔线(在图中表示为连续线),尽管两边都有一些错误分类的情况。此外,由于离分隔线更远的相应类别的支持向量,边界可以被可视化(由两条虚线定义),是可识别的。在图表中,支持向量用一个外圆标记,你实际上可以注意到一些支持向量在边距之外;这是因为它们是错误分类的情况,SVM 必须出于优化目的跟踪它们,因为它们的误差在损失项中考虑:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_02.jpg
随着 C 值的增加,由于 SVM 在优化过程中考虑的支持向量越来越少,余量趋于受限。因此,分隔线的斜率也会改变。
相反,较小的 C 值往往会放宽余量,从而增加方差。极小的 C 值甚至会导致 SVM 考虑边距内的所有示例点。当有许多嘈杂的例子时,较小的 C 值是理想的。这样的设置迫使 SVM 忽略了裕度定义中的许多错误分类的例子(误差的权重较小,因此在搜索最大裕度时它们被容忍得更多。):
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_03.jpg
继续前面的视觉示例,如果我们减少超参数 C,由于支持向量的数量增加,边界实际上会扩大。因此,保证金是不同的,SVM 解决了一个不同的分界线。在对数据进行测试之前,没有可以被认为是正确的 C 值;正确的值总是必须通过交叉验证凭经验找到。到目前为止,C 被认为是 SVM 中最重要的超参数,是在决定使用什么样的核函数后设置的。
相反,核函数通过以非线性方式组合原始特征,将它们映射到更高维的空间中。以这种方式,原始特征空间中明显不可分离的组可以在更高维的表示中变成可分离的。这样的投影不需要太复杂的计算,尽管当投影到高维度时,将原始特征值显式转换成新特征值的过程会产生特征数量的潜在爆炸。内核函数可以简单地插入到决策函数中,从而取代特征和系数向量之间的原始点积,获得与显式映射相同的优化结果,而不是进行如此繁琐的计算。(这样的封堵被称为内核绝招,因为它真的是数学绝招。)
标准核函数是线性函数(意味着没有变换)、多项式函数、径向基函数 ( 径向基函数)和 sigmoid 函数。为了提供一个想法,径向基函数可以表示如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_12.jpg
基本上,径向基函数和其他内核只是将自己直接插入到之前看到的要最小化的函数的变体中。之前看到的优化函数被称为原始公式,而类似的表达式被称为对偶公式:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_13.jpg
尽管在没有数学证明的情况下,从原始公式到对偶公式的转换相当具有挑战性,但重要的是要理解,给定一个通过对例子进行比较的核函数,核技巧只是一个关于它可以展开的无限维特征空间的有限数量计算的问题。这种核心技巧使得该算法对于诸如图像识别或文本分类等相当复杂的问题特别有效(可与神经网络相比):
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_04.jpg
例如,由于 sigmoid 核,前面的 SVM 解是可能的,而下面的是由于径向基函数核:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_05.jpg
从视觉上看,径向基函数内核允许对边界进行非常复杂的定义,甚至将其分成多个部分(在前面的例子中,一个飞地是显而易见的)。
径向基函数核的公式如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_14.jpg
伽马是一个你可以先验定义的超参数。核变换在支持向量周围创建某种分类气泡,从而允许通过合并气泡本身来定义非常复杂的边界形状。
乙状结肠仁的配方如下:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_15.jpg
这里,除了γ,还应该选择 r 以获得最佳结果。
显然,基于 sigmoid、RBF 和多项式的解决方案,(是的,它隐式地进行了多项式展开,我们将在下面的段落中讨论。)内核呈现出比估计偏差更多的方差,因此在决定采用它们时需要严格的验证。尽管 SVM 对过度适应有抵抗力,但它肯定不能幸免。
支持向量回归与支持向量分类有关。它仅因符号(更类似于线性回归,使用β而不是系数向量 w)和损失函数而异:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_16.jpg
值得注意的是,唯一显著的区别是损失函数 L-ε,如果示例在距离回归超平面一定距离ε内,则损失函数 L-ε对误差不敏感(因此不计算误差)。这种成本函数的最小化优化了回归问题的结果,输出值而不是类。
铰链损失及其变体
作为对 SVM 内部螺母和螺栓的总结,请记住算法核心的成本函数是铰链损耗:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_17.jpg
如前所述, ŷ 表示为 X 和系数向量 w 的点积与偏差 b 之和:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_18.jpg
这让人想起感知器,这样的损失函数线性地惩罚误差,表示当例子被分类在边缘的错误侧时的误差,与它与边缘本身的距离成比例。虽然是凸的,但缺点是不能处处可微,它有时被总是可微的变量所代替,例如平方铰链损耗(也称为 L2 损耗,而 L1 损耗是铰链损耗):
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_19.jpg
另一个变型是休伯损失,当误差等于或低于某个阈值 h 时,它是二次函数,否则是线性函数。这种方法混合了基于误差的铰链损失的 L1 和 L2 变体,并且它是一种非常抗异常值的替代方案,因为较大的误差值不平方,因此需要学习 SVM 较少的调整。Huber 损失也是对数损失(线性模型)的一种替代方法,因为它计算速度更快,并且能够提供类别概率的估计(铰链损失不具有这种能力)。
从实践的角度来看,没有特别的报告表明胡贝尔损失或 L2 铰链损失可以始终比铰链损失表现得更好。最后,成本函数的选择可以归结为针对每个不同的学习问题测试可用的函数。(根据无免费午餐定理的原理,在机器学习中没有适合所有问题的解。)
了解 Scikit-学习 SVM 实施
Scikit-learn 提供了 SVM 的一个实现使用两个 C++库(带有一个 C API 来与其他语言接口),一个支持向量机库 ( LIBSVM )用于 SVM 分类和回归(http://www.csie.ntu.edu.tw/~cjlin/libsvm/)和 LIBLINEAR 用于在大型稀疏数据集上使用线性方法分类问题(http://www.csie.ntu.edu.tw/~cjlin/liblinear/)。这两个库都可以自由使用,计算速度非常快,并且已经在许多其他解决方案中进行了测试,sklearn.svm模块中的所有 Scikit-learn 实现都依赖于其中的一个,Perceptron和LogisticRegression类也顺便使用了它们。)让 Python 只是一个方便的包装器。
另一方面,SGDClassifier和SGDRegressor使用不同的实现,因为 LIBSVM 和 LIBLINEAR 都没有在线实现,都是批处理学习工具。事实上,在操作时,当通过cache_size参数为内核操作分配合适的内存时,LIBSVM 和 LIBLINEAR 的性能都是最好的。
分类的实现如下:
|班级
|
目的
|
超参数
|
| — | — | — |
| sklearn.svm.SVC | 二类和多类线性和核分类的 LIBSVM 实现 | c,核,度,γ |
| sklearn.svm.NuSVC | 同上 | 核,度,γ |
| sklearn.svm.OneClassSVM | 异常值的无监督检测 | 核,度,γ |
| sklearn.svm.LinearSVC | 它基于 LIBLINEAR,是一个二元多类线性分类器 | 罚金,损失,C |
关于回归,解决方案如下:
|班级
|
目的
|
超参数
|
| — | — | — |
| sklearn.svm.SVR | 回归的 LIBSVM 实现 | c,核,度,γ,ε |
| sklearn.svm.NuSVR | 同上 | 核,度,γ |
如您所见,每个版本都有相当多的超参数需要调整,使用 Scikit-learn 中grid_search模块的GridSearchCV,当使用默认参数时,SVMs 是很好的学习者,当通过交叉验证进行适当调整时,SVMs 是很好的学习者。
作为一条黄金法则,有些参数对结果的影响更大,因此应事先确定,其他参数则取决于它们的值。根据这样的经验法则,你必须正确设置以下参数(按重要程度排序):
C:这个就是我们之前讨论过的罚值。减小它会使余量变大,从而忽略更多的噪声,但也会增加计算量。最佳值通常可以在np.logspace(-3, 3, 7)范围内找到。kernel:这是非线性老黄牛,因为一个 SVM 可以设置为linear、poly、rbf、sigmoid,或者自定义内核(针对专家!).广泛使用的当然是rbf。degree:这个和kernel='poly'一起工作,表示多项式展开的维度。它被其他内核忽略。通常,2-5 的值效果最好。gamma:这是'rbf'、'poly'、'sigmoid'的系数;较高的值倾向于以更好的方式拟合数据。建议的网格搜索范围是np.logspace(-3, 3, 7)。nu:这是用nuSVR``nuSVC进行回归分类;此参数近似于未按置信度分类的训练点、错误分类的点以及边缘内部或边缘上的正确点。它应该是一个在[0,1]范围内的数字,因为它是相对于您的训练集的一个比例。最后,它作为高比例的 C 扩大了利润。epsilon:这个参数通过定义一个ε大范围来指定一个支持向量回归机将接受多少误差,在这个范围内,相对于点的真实值没有损失。建议搜索范围为np.insert(np.logspace(-4, 2, 7),0,[0])。penalty、loss和dual:对于线性 SVC,这些参数接受('l1','squared_hinge',False)、('l2','hinge',True)、('l2','squared_hinge',True)和('l2','squared_hinge',False)组合。('l2','hinge',True)组合相当于SVC(kernel='linear')学习者。
作为使用来自 Scikit-learn 的sklearn.svm模块的 SVC 和 SVR 进行基本分类和回归的示例,我们将使用 Iris 和 Boston 数据集,这是两个流行的玩具数据集(http://scikit-learn.org/stable/datasets/)。
首先,我们将加载 Iris 数据集:
In: from sklearn import datasets
iris = datasets.load_iris()
X_i, y_i = iris.data, iris.target
然后,我们将用径向基函数核拟合一个支持向量机(根据 Scikit-learn 中的其他已知示例选择了 C 和γ),并使用cross_val_score函数测试结果:
from sklearn.svm import SVC
from sklearn.cross_validation import cross_val_score
import numpy as np
h_class = SVC(kernel='rbf', C=1.0, gamma=0.7, random_state=101)
scores = cross_val_score(h_class, X_i, y_i, cv=20, scoring='accuracy')
print 'Accuracy: %0.3f' % np.mean(scores)
Output: Accuracy: 0.969
拟合模型可以为您提供一个索引,指出您的训练示例中有哪些支持向量:
In: h_class.fit(X_i,y_i)
print h_class.support_
Out: [ 13 14 15 22 24 41 44 50 52 56 60 62 63 66 68 70 72 76 77 83 84 85 98 100 106 110 114 117 118 119 121 123 126 127 129 131 133 134 138 141 146 149]
以下是 SVC 为 Iris 数据集选择的支持向量的图形表示,用颜色决策边界表示(我们测试了离散的值网格,以便能够为图表的每个区域投影模型将预测的类别):
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_06.jpg
型式
如果你对复制相同的图表感兴趣,你可以看看并调整这个来自的代码片段。
为了测试一个 SVM 回归器,我们决定用波士顿数据集尝试 SVR 。首先,我们将数据集上传到核心内存中,然后我们将示例的顺序随机化,值得注意的是,这样的数据集实际上是以一种微妙的方式排序的,从而使非顺序随机化交叉验证的结果无效:
In: import numpy as np
from sklearn.datasets import load_boston
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
boston = load_boston()
shuffled = np.random.permutation(boston.target.size)
X_b = scaler.fit_transform(boston.data[shuffled,:])
y_b = boston.target[shuffled]
型式
由于我们在 NumPy 包中使用了random模块中的permutation函数,因此您可能会从以下测试中获得不同的混洗数据集和稍微不同的交叉验证分数。此外,具有不同尺度的特征,将特征标准化是一种好的做法,这样它们将具有零中心均值和单位方差。尤其是在使用内核的 SVM 时,标准化确实至关重要。
最后,我们可以拟合 SVR 模型(我们决定了一些已知有效的 C、γ和ε参数),并使用交叉验证,通过均方根误差对其进行评估:
In: from sklearn.svm import SVR
from sklearn.cross_validation import cross_val_score
h_regr = SVR(kernel='rbf', C=20.0, gamma=0.001, epsilon=1.0)
scores = cross_val_score(h_regr, X_b, y_b, cv=20, scoring='mean_squared_error')
print 'Mean Squared Error: %0.3f' % abs(np.mean(scores))
Out: Mean Squared Error: 28.087
通过二次采样追求非线性支持向量机
与其他机器学习算法相比,支持向量机有很多优势:
- 它们可以处理大多数有监督的问题,如回归、分类和异常检测,尽管它们实际上最擅长二进制分类。
- 它们可以很好地处理有噪声的数据和异常值,并且由于它们只处理支持向量,因此它们倾向于不太过度。
- 它们适用于广泛的数据集(特征比示例多);不过,和其他机器学习算法一样,SVM 可以从降维和特征选择中获益。
作为缺点,我们必须提到以下几点:
- 它们只提供估计值,但不提供概率,除非您通过普拉特缩放进行一些耗时且计算密集型的概率校准
- 它们随着例子的数量呈超线性扩展
特别是,最后一个缺点极大地限制了支持向量机在大型数据集上的使用。该学习技术核心的优化算法——二次规划——在 Scikit-learn 实现中在 O(特征数量 samples^2 数量)*和 O(特征数量 samples^3 数量)*之间缩放,这种复杂性严重限制了该算法对于小于10^4例数的数据集的可操作性。
同样,正如在上一章中所看到的,当您给出一个批处理算法和太多数据时,只有几个选项:二次采样、并行化和通过流的核外学习。子采样和并行化很少被引用为最佳解决方案,流是实现具有大规模问题的支持向量机的首选方法。
然而,尽管使用较少,但利用储层采样很容易实现二次采样,这可以从来自数据集和无限在线流的流中快速产生随机样本。通过二次采样,您可以生成多个 SVM 模型,这些模型的结果可以进行平均以获得更好的结果。来自多个 SVM 模型的预测甚至可以叠加,从而创建一个新的数据集,并用于构建融合所有预测能力的新模型,这将在第 6 章、分级和回归树中描述。
储层取样是一种算法,用于在事先不知道流有多长的情况下,从流中随机选择样本。事实上,流中的每个观察都有相同的被选择概率。使用从流中的第一个观察值获取的样本进行初始化,样本中的每个元素都可以根据与到目前为止流过的元素数量成比例的概率随时被流中的示例替换。因此,例如,当流的第I元素到达时,它有可能被插入来代替样本中的随机元素。这样的插入概率相当于样本维数除以I;因此,它相对于流长度逐渐减小。如果流是无限的,则随时停止可确保样本代表到目前为止看到的元素。
在我们的例子中,我们从溪流中随机抽取两个互斥的样本——一个用于训练,一个用于测试。我们将使用原始的有序文件从 Covertype 数据库中提取这样的样本。(由于我们将在取样前对所有数据进行流式处理,随机取样不会受到排序的影响。)我们确定了一个 5000 个示例的训练样本,这个数字应该可以在大多数台式计算机上很好地扩展。至于测试集,我们将使用 20,000 个示例:
In: from random import seed, randint
SAMPLE_COUNT = 5000
TEST_COUNT = 20000
seed(0) # allows repeatable results
sample = list()
test_sample = list()
for index, line in enumerate(open('covtype.data','rb')):
if index < SAMPLE_COUNT:
sample.append(line)
else:
r = randint(0, index)
if r < SAMPLE_COUNT:
sample[r] = line
else:
k = randint(0, index)
if k < TEST_COUNT:
if len(test_sample) < TEST_COUNT:
test_sample.append(line)
else:
test_sample[k] = line
算法应该在数据矩阵的超500,000行上以相当快的速度流动。事实上,我们在流式传输过程中确实没有进行任何预处理,以尽可能保持最快的速度。因此,我们现在需要将数据转换为 NumPy 阵列,并对其功能进行标准化:
In: import numpy as np
from sklearn.preprocessing import StandardScaler
for n,line in enumerate(sample):
sample[n] = map(float,line.strip().split(','))
y = np.array(sample)[:,-1]
scaling = StandardScaler()
X = scaling.fit_transform(np.array(sample)[:,:-1])
一旦完成了训练数据 X 、 y ,我们就要以同样的方式处理测试数据;特别是,我们必须使用标准化参数(平均值和标准偏差)对特征进行标准化,如训练样本中所示:
In: for n,line in enumerate(test_sample):
test_sample[n] = map(float,line.strip().split(','))
yt = np.array(test_sample)[:,-1]
Xt = scaling.transform(np.array(test_sample)[:,:-1])
当训练集和测试集都准备好时,我们可以拟合 SVC 模型和预测结果:
In: from sklearn.svm import SVC
h = SVC(kernel='rbf', C=250.0, gamma=0.0025, random_state=101)
h.fit(X,y)
prediction = h.predict(Xt)
from sklearn.metrics import accuracy_score
print accuracy_score(yt, prediction)
Out: 0.75205
用 SGD 大规模实现 SVM
考虑到子采样的限制(首先,相对于在较大数据集上训练的模型的欠拟合),在对应用于大规模流的线性支持向量机使用 Scikit-learn 时唯一可用的选项仍然是SGDClassifier和SGDRegressor方法,这两种方法都在linear_model模块中可用。让我们看看如何最好地使用它们,并在示例数据集上改进我们的结果。
我们将利用本章中看到的前面的例子进行线性和逻辑回归,并将它们转化为有效的 SVM。至于分类,要求你使用loss超参数设置损失类型。参数的可能值为'hinge'、'squared_hinge'和'modified_huber'。所有这样的损失函数都在前面介绍过,并在本章讨论 SVM 公式时讨论过。
所有这些都意味着应用软裕度线性 SVM(无核),从而导致 SVM 抗误分类和噪声数据。但是,您也可以尝试使用损失'perceptron',这是一种导致没有余量的铰链损失的损失类型,当有必要求助于比其他可能的损失选择更偏向的模型时,这是一种合适的解决方案。
当使用这种范围的铰链损耗函数时,要获得最佳结果,必须考虑两个方面:
- 当使用任何损失函数时,随机梯度下降变得懒惰,仅当一个例子违反先前定义的边界时更新系数向量。这与对数或平方误差中的损失函数完全相反,实际上每个例子都被考虑用于系数向量的更新。如果学习中涉及许多特征,这种懒惰的方法会导致系数向量更稀疏,从而减少过拟合。(更密集的向量意味着更多的过拟合,因为一些系数可能比来自数据的信号捕获更多的噪声。)
- 只有
'modified_huber'损失允许概率估计,使其成为对数损失的可行替代(如在随机逻辑回归中发现的)。当处理多类1 对全部 ( OVA )预测时,改进的 Huber 也表现得更好,因为多个模型的概率输出优于铰链损失的标准决策函数特性(概率比决策函数的原始输出更好,因为它们在相同的尺度上,从 0 到 1 有界)。这个损失函数的工作原理是直接从决策函数中导出一个概率估计值:(clip(decision_function(X), -1, 1) + 1) / 2。
至于回归问题,SGDRegressor提供两个 SVM 损失选项:
'epsilon_insensitive'
'squared_epsilon_insensitive'
两者都激活线性支持向量回归,其中ε值内的误差(预测的残差)被忽略。超过ε值后,epsilon_insensitive损失按原样考虑误差。squared_epsilon_insensitive损失以类似的方式运行,尽管这里的误差越平方越不利,更大的误差对模型构建的影响越大。
在这两种情况下,设置正确的ε超参数至关重要。作为一个默认值,Scikit-learn 建议ε= 0.1,但是您的问题的最佳值必须通过交叉验证支持的网格搜索来找到,我们将在接下来的段落中看到。
请注意,在回归损失中,还有一个'huber'损失没有激活 SVM 类型的优化,只是修改了通常的'squared_loss',通过从平方损失切换到线性损失超过ε参数值的距离,对异常值不敏感。
至于我们的例子,我们将重复流过程一定次数,以演示如何设置不同的超参数和变换特征;为了减少重复代码行的数量,我们将使用一些方便的函数。此外,为了加快示例的执行速度,我们将限制算法引用的案例数或容差值。通过这种方式,培训和验证时间都保持在最低限度,没有任何示例会要求您等待超过一杯茶或咖啡的时间。
至于方便的包装函数,第一个函数的目的是最初将部分或全部数据流式传输一次(我们使用max_rows 参数设置一个限制)。完成流式传输后,该功能将能够计算出所有分类特征的级别,并记录数字特征的不同范围。提醒一下,录音范围是需要注意的一个重要方面。SGD 和 SVM 都是对不同范围尺度敏感的算法,当处理[-1,1]范围之外的数字时,它们的性能更差。
作为输出,我们的函数将返回两个经过训练的 Scikit-learn 对象:DictVectorizer(能够将字典中存在的特征范围转换为特征向量)和MinMaxScaler来重新缩放[0,1]范围内的数值变量(有助于保持数据集中的值稀疏,从而保持低内存使用,并在大多数值为零时实现快速计算)。作为一个独特的约束,您需要知道要用于预测模型的数值和分类变量的特征名称。未包含在列表的binary_features或numeric_features参数中的特征实际上将被忽略。当流没有要素名称时,您需要使用fieldnames参数命名它们:
In: import csv, time, os
import numpy as np
from sklearn.linear_model import SGDRegressor
from sklearn.feature_extraction import DictVectorizer
from sklearn.preprocessing import MinMaxScaler
from scipy.sparse import csr_matrix
def explore(target_file, separator=',', fieldnames= None, binary_features=list(), numeric_features=list(), max_rows=20000):
"""
Generate from an online style stream a DictVectorizer and a MinMaxScaler.
Parameters
----------
target file = the file to stream from
separator = the field separator character
fieldnames = the fields' labels (can be omitted and read from file)
binary_features = the list of qualitative features to consider
numeric_features = the list of numeric futures to consider
max_rows = the number of rows to be read from the stream (can be None)
"""
features = dict()
min_max = dict()
vectorizer = DictVectorizer(sparse=False)
scaler = MinMaxScaler()
with open(target_file, 'rb') as R:
iterator = csv.DictReader(R, fieldnames, delimiter=separator)
for n, row in enumerate(iterator):
# DATA EXPLORATION
for k,v in row.iteritems():
if k in binary_features:
if k+'_'+v not in features:
features[k+'_'+v]=0
elif k in numeric_features:
v = float(v)
if k not in features:
features[k]=0
min_max[k] = [v,v]
else:
if v < min_max[k][0]:
min_max[k][0]= v
elif v > min_max[k][1]:
min_max[k][1]= v
else:
pass # ignore the feature
if max_rows and n > max_rows:
break
vectorizer.fit([features])
A = vectorizer.transform([{f:0 if f not in min_max else min_max[f][0] for f in vectorizer.feature_names_},
{f:1 if f not in min_max else min_max[f][1] for f in vectorizer.feature_names_}])
scaler.fit(A)
return vectorizer, scaler
型式
这个代码片段可以很容易地在你自己的大规模数据机器学习应用中重用。如果您的流是在线流(连续流)或过长的流,您可以通过设置max_rows参数对观察到的示例数量应用不同的限制。
第二个函数将从数据流中提取数据并将其转换为特征向量,如果提供了合适的MinMaxScaler对象而不是None设置,则对数字特征进行归一化:
In: def pull_examples(target_file, vectorizer, binary_features, numeric_features, target, min_max=None, separator=',',
fieldnames=None, sparse=True):
"""
Reads a online style stream and returns a generator of normalized feature vectors
Parameters
----------
target file = the file to stream from
vectorizer = a DictVectorizer object
binary_features = the list of qualitative features to consider
numeric_features = the list of numeric features to consider
target = the label of the response variable
min_max = a MinMaxScaler object, can be omitted leaving None
separator = the field separator character
fieldnames = the fields' labels (can be omitted and read from file)
sparse = if a sparse vector is to be returned from the generator
"""
with open(target_file, 'rb') as R:
iterator = csv.DictReader(R, fieldnames, delimiter=separator)
for n, row in enumerate(iterator):
# DATA PROCESSING
stream_row = {}
response = np.array([float(row[target])])
for k,v in row.iteritems():
if k in binary_features:
stream_row[k+'_'+v]=1.0
else:
if k in numeric_features:
stream_row[k]=float(v)
if min_max:
features = min_max.transform(vectorizer.transform([stream_row]))
else:
features = vectorizer.transform([stream_row])
if sparse:
yield(csr_matrix(features), response, n)
else:
yield(features, response, n)
给定这两个函数,现在让我们再次尝试对前一章中看到的第一个回归问题(自行车共享数据集)建模,但这次使用的是铰链损失,而不是我们之前使用的均方误差。
作为的第一步,我们提供要流的文件的名称和一个定性和数字变量列表(从文件的头和文件的初始探索中导出)。包装函数的代码将返回一些关于热编码变量和值范围的信息。在这种情况下,大多数变量将是二进制的,这对于稀疏表示来说是一种完美的情况,因为我们数据集中的大多数值都是零:
In: source = '\\bikesharing\\hour.csv'
local_path = os.getcwd()
b_vars = ['holiday','hr','mnth', 'season','weathersit','weekday','workingday','yr']
n_vars = ['hum', 'temp', 'atemp', 'windspeed']
std_row, min_max = explore(target_file=local_path+'\\'+source, binary_features=b_vars, numeric_features=n_vars)
print 'Features: '
for f,mv,mx in zip(std_row.feature_names_, min_max.data_min_, min_max.data_max_):
print '%s:[%0.2f,%0.2f] ' % (f,mv,mx)
Out:
Features:
atemp:[0.00,1.00]
holiday_0:[0.00,1.00]
holiday_1:[0.00,1.00]
...
workingday_1:[0.00,1.00]
yr_0:[0.00,1.00]
yr_1:[0.00,1.00]
您可以从输出中注意到,定性变量已经使用它们的变量名进行了编码,并在下划线字符后添加了它们的值,并转换为二进制特征(当特征存在时,该特征的值为 1,否则设置为零)。请注意,我们总是在中使用带有average=True参数的 SGD 模型,以确保更快的收敛(这对应于使用 平均随机梯度下降 ( ASGD )模型,如前一章所述。):
In:from sklearn.linear_model import SGDRegressor
SGD = SGDRegressor(loss='epsilon_insensitive', epsilon=0.001, penalty=None, random_state=1, average=True)
val_rmse = 0
val_rmsle = 0
predictions_start = 16000
def apply_log(x): return np.log(x + 1.0)
def apply_exp(x): return np.exp(x) - 1.0
for x,y,n in pull_examples(target_file=local_path+'\\'+source,
vectorizer=std_row, min_max=min_max,
binary_features=b_vars, numeric_features=n_vars, target='cnt'):
y_log = apply_log(y)
# MACHINE LEARNING
if (n+1) >= predictions_start:
# HOLDOUT AFTER N PHASE
predicted = SGD.predict(x)
val_rmse += (apply_exp(predicted) - y)**2
val_rmsle += (predicted - y_log)**2
if (n-predictions_start+1) % 250 == 0 and (n+1) > predictions_start:
print n,
print '%s holdout RMSE: %0.3f' % (time.strftime('%X'), (val_rmse / float(n-predictions_start+1))**0.5),
print 'holdout RMSLE: %0.3f' % ((val_rmsle / float(n-predictions_start+1))**0.5)
else:
# LEARNING PHASE
SGD.partial_fit(x, y_log)
print '%s FINAL holdout RMSE: %0.3f' % (time.strftime('%X'), (val_rmse / float(n-predictions_start+1))**0.5)
print '%s FINAL holdout RMSLE: %0.3f' % (time.strftime('%X'), (val_rmsle / float(n-predictions_start+1))**0.5)
Out:
16249 07:49:09 holdout RMSE: 276.768 holdout RMSLE: 1.801
16499 07:49:09 holdout RMSE: 250.549 holdout RMSLE: 1.709
16749 07:49:09 holdout RMSE: 250.720 holdout RMSLE: 1.696
16999 07:49:09 holdout RMSE: 249.661 holdout RMSLE: 1.705
17249 07:49:09 holdout RMSE: 234.958 holdout RMSLE: 1.642
07:49:09 FINAL holdout RMSE: 224.513
07:49:09 FINAL holdout RMSLE: 1.596
我们现在来尝试一下森林覆盖类型的分类问题:
In: source = 'shuffled_covtype.data'
local_path = os.getcwd()
n_vars = ['var_'+'0'*int(j<10)+str(j) for j in range(54)]
std_row, min_max = explore(target_file=local_path+'\\'+source, binary_features=list(),
fieldnames= n_vars+['covertype'], numeric_features=n_vars, max_rows=50000)
print 'Features: '
for f,mv,mx in zip(std_row.feature_names_, min_max.data_min_, min_max.data_max_):
print '%s:[%0.2f,%0.2f] ' % (f,mv,mx)
Out:
Features:
var_00:[1871.00,3853.00]
var_01:[0.00,360.00]
var_02:[0.00,61.00]
var_03:[0.00,1397.00]
var_04:[-164.00,588.00]
var_05:[0.00,7116.00]
var_06:[58.00,254.00]
var_07:[0.00,254.00]
var_08:[0.00,254.00]
var_09:[0.00,7168.00]
...
在从流中采样并拟合我们的DictVectorizer和MinMaxScaler对象之后,我们这次可以使用渐进验证来开始我们的学习过程(在案例用于训练之前,通过在案例上测试模型来给出误差度量),给定大量可用的示例。代码中的sample变量设置的每一个特定数量的例子,脚本都以平均精度报告最近例子的情况:
In: from sklearn.linear_model import SGDClassifier
SGD = SGDClassifier(loss='hinge', penalty=None, random_state=1, average=True)
accuracy = 0
accuracy_record = list()
predictions_start = 50
sample = 5000
early_stop = 50000
for x,y,n in pull_examples(target_file=local_path+'\\'+source,
vectorizer=std_row,
min_max=min_max,
binary_features=list(), numeric_features=n_vars,
fieldnames= n_vars+['covertype'], target='covertype'):
# LEARNING PHASE
if n > predictions_start:
accuracy += int(int(SGD.predict(x))==y[0])
if n % sample == 0:
accuracy_record.append(accuracy / float(sample))
print '%s Progressive accuracy at example %i: %0.3f' % (time.strftime('%X'), n, np.mean(accuracy_record[-sample:]))
accuracy = 0
if early_stop and n >= early_stop:
break
SGD.partial_fit(x, y, classes=range(1,8))
Out: ...
19:23:49 Progressive accuracy at example 50000: 0.699
型式
不得不处理超过 575,000 个例子,我们在 50,000 个之后设置学习过程的提前停止。您可以根据您电脑的功率和时间可用性自由修改这些参数。请注意,代码可能需要一些时间。我们在 2.20 千兆赫的英特尔酷睿 i3 处理器上进行了大约 30 分钟的计算。
正则化特征选择
在批处理环境中,通常通过以下方式操作特征选择:
- 基于完整性(缺失值的发生率)、方差和变量间高度多重共线性的初步筛选,以获得相关和可操作特征的更清晰数据集。
- 基于特征和响应变量之间的单变量关联(卡方检验、F 值和简单线性回归)的另一个初始过滤,以便立即移除对预测任务没有用处的特征,因为它们与响应很少或没有关系。
- 在建模过程中,递归方法根据特征的能力插入和/或排除特征,以提高算法的预测能力,如在保持样本上测试的那样。使用仅相关特征的较小子集允许机器学习算法较少受过度拟合的影响,因为噪声变量和参数由于特征的高维性而过量。
在在线环境中应用这种方法当然仍然是可能的,但是就所需的时间而言是相当昂贵的,因为完成单个模型需要流传输大量的数据。基于大量迭代和测试的递归方法需要能够适应内存的灵活数据集。如前所述,在这种情况下,二次抽样将是一个很好的选择,以便找出以后应用于更大规模的特征和模型。
继续我们的核心外方法,正则化是理想的解决方案,作为一种选择变量的方式,同时流式传输和过滤掉有噪声或冗余的特征。正则化在在线算法中运行良好,因为在线机器学习算法正在运行并根据示例拟合其系数,而不需要为了选择而运行其他流。正则化实际上只是一个罚值,加入到学习过程的优化中。它依赖于特征系数和一个名为alpha的参数设置正则化的影响。当模型更新系数的权重时,正则化平衡介入。这时,如果更新的值不够大,正则化通过减少结果权重来起作用。排除或衰减冗余变量的技巧是通过正则化alpha参数实现的,该参数必须根据经验设置在正确的大小,以获得关于要学习的每个特定数据的最佳结果。
SGD 实现了与批处理算法中相同的正则化策略:
- L1 罚将多余和不那么重要的变量推到零
- L2 减少了不太重要的功能的重量
- L1 和 L2 正则化效应的弹性网混合
当存在异常和冗余变量时,L1 正则化是完美的策略,因为它会将这些特征的系数推到零,使它们在计算预测时变得无关紧要。
当变量之间存在许多相关性时,L2 是合适的,因为它的策略只是降低特征的权重,这些特征的变化对于损失函数最小化来说不太重要。对于 L2 来说,所有的变量都在继续对预测做出贡献,尽管有些变量不那么重要。
弹性网使用加权和将 L1 和 L2 混合在一起。这个解决方案很有趣,因为当处理高度相关的变量时,L1 正则化有时是不稳定的,根据所看到的例子选择其中一个。使用ElasticNet,许多不寻常的特征仍然会像在 L1 正则化中一样被推到零,但是相关的特征会像在 L2 一样被衰减。
SGDClassifier和SGDRegressor都可以使用penalty、alpha和l1_ratio参数实现 L1、L2 和弹性网正则化。
型式
阿尔法参数是决定什么样的惩罚或两者混合后最关键的参数。理想情况下,您可以使用10.0**-np.arange(1,7)生成的值列表测试从0.1到10^-7范围内的合适值。
如果penalty决定选择哪种正则化,alpha如上所述,将决定其强度。因为alpha是乘以惩罚项的常数;低α值对最终系数的影响很小,而高α值会显著影响最终系数。最后,l1_ratio代表当penalty='elasticnet'时,L1 处罚相对于 L2 的百分比是多少。
用 SGD 设置正则化非常容易。例如,您可以尝试更改前面的代码示例,在SGDClassifier中插入一个惩罚 L2:
SGD = SGDClassifier(loss='hinge', penalty='l2', alpha= 0.0001, random_state=1, average=True)
如果你更喜欢测试一个混合了两种正则化方法效果的弹性网,你所要做的就是通过设置l1_ratio来明确 L1 和 L2 之间的比率:
SGD = SGDClassifier(loss=''hinge'', penalty=''elasticnet'', \ alpha= 0.001, l1_ratio=0.5, random_state=1, average=True)
由于正则化的成功取决于插入正确的惩罚和最佳α,所以在我们的例子中,正则化将在处理超参数优化问题时发挥作用。
在 SGD 中包含非线性
将非线性插入线性 SGD 学习器(基本上是一个不用动脑的学习器)的最快方式是将从流中接收到的示例向量转换成新向量,该新向量包括幂变换和一定程度的特征组合。
组合可以表示特征之间的相互作用(说明当两个特征同时对响应有特殊影响时),因此有助于 SVM 线性模型包含一定量的非线性。例如,双向交互是通过两个特征的相乘来实现的。三向是通过将三个特征相乘等方式实现的,为更高程度的扩展创造了更复杂的交互。
在 Scikit-learn 中,预处理模块包含PolynomialFeatures类,可以通过所需次数的多项式展开自动变换特征向量:
In: from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import PolynomialFeatures
source = '\\bikesharing\\hour.csv'
local_path = os.getcwd()
b_vars = ['holiday','hr','mnth', 'season','weathersit','weekday','workingday','yr']
n_vars = ['hum', 'temp', 'atemp', 'windspeed']
std_row, min_max = explore(target_file=local_path+'\\'+source, binary_features=b_vars, numeric_features=n_vars)
poly = PolynomialFeatures(degree=2, interaction_only=False, include_bias=False)
SGD = SGDRegressor(loss='epsilon_insensitive', epsilon=0.001, penalty=None, random_state=1, average=True)
val_rmse = 0
val_rmsle = 0
predictions_start = 16000
def apply_log(x): return np.log(x + 1.0)
def apply_exp(x): return np.exp(x) - 1.0
for x,y,n in pull_examples(target_file=local_path+'\\'\
+source,vectorizer=std_row, min_max=min_max, \
sparse = False, binary_features=b_vars,\numeric_features=n_vars, target='cnt'):
y_log = apply_log(y)
# Extract only quantitative features and expand them
num_index = [j for j, i in enumerate(std_row.feature_names_) if i in n_vars]
x_poly = poly.fit_transform(x[:,num_index])[:,len(num_index):]
new_x = np.concatenate((x, x_poly), axis=1)
# MACHINE LEARNING
if (n+1) >= predictions_start:
# HOLDOUT AFTER N PHASE
predicted = SGD.predict(new_x)
val_rmse += (apply_exp(predicted) - y)**2
val_rmsle += (predicted - y_log)**2
if (n-predictions_start+1) % 250 == 0 and (n+1) > predictions_start:
print n,
print '%s holdout RMSE: %0.3f' % (time.strftime('%X'), (val_rmse / float(n-predictions_start+1))**0.5),
print 'holdout RMSLE: %0.3f' % ((val_rmsle / float(n-predictions_start+1))**0.5)
else:
# LEARNING PHASE
SGD.partial_fit(new_x, y_log)
print '%s FINAL holdout RMSE: %0.3f' % (time.strftime('%X'), (val_rmse / float(n-predictions_start+1))**0.5)
print '%s FINAL holdout RMSLE: %0.3f' % (time.strftime('%X'), (val_rmsle / float(n-predictions_start+1))**0.5)
Out: ...
21:49:24 FINAL holdout RMSE: 219.191
21:49:24 FINAL holdout RMSLE: 1.480
型式
PolynomialFeatures期望一个密集的矩阵,而不是一个稀疏的矩阵作为输入。我们的pull_examples功能允许设置稀疏参数,通常设置为True,可以改为设置为False,从而返回密集矩阵。
尝试显式高维映射
虽然多项式展开是一种非常强大的变换,但是当我们试图将展开到更高的程度,并快速对比由过度参数化(当您有太多冗余和无用的特征时)导致的过度拟合捕捉重要非线性的积极效果时,它们在计算上可能会很昂贵。正如在 SVC 和 SVR 中看到的,内核转换可以帮助我们。SVM 内核转换是隐式的,需要内存中的数据矩阵才能工作。Scikit-learn 中有一类基于随机近似的变换,在线性模型的上下文中,可以获得与核 SVM 非常相似的结果。
sklearn.kernel_approximation模块包含一些这样的算法:
RBFSampler:这近似于径向基函数核的特征图Nystroem:这使用训练数据的子集来近似内核映射AdditiveChi2Sampler:这近似于加法 chi2 内核的特征映射,一个用于计算机视觉的内核SkewedChi2Sampler:这近似于特征映射,类似于计算机视觉中使用的偏斜卡方核
除了 Nystroem 方法,前面的类都不需要从你的数据样本中学习,这使得它们非常适合在线学习。他们只需要知道一个示例向量是如何形成的(有多少特征),然后他们将产生许多随机的非线性,希望这些非线性能够很好地适合您的数据问题。
这些近似算法中没有复杂的优化算法可以解释;事实上,优化本身被随机化所取代,结果很大程度上取决于输出特征的数量,由n_components参数指出。输出特征越多,你就越有可能得到正确的非线性来完美地解决你的问题。
重要的是要注意,如果机会在创建正确的特征以提高预测方面真的有如此大的作用,那么结果的可再现性就变得至关重要,你应该努力获得它,否则你将无法以同样的方式持续地重新训练和调整你的算法。值得注意的是,每个类都有一个random_state参数,这样就可以控制随机特征的生成,并且以后可以在相同的计算机上重新生成。
科学文章 A .拉希米和本杰明·雷希特(http://www . eecs . Berkeley . edu/~ Brecht/papers/07 . rah . rec . nips . pdf)和随机厨房水槽的加权和:在学习中用随机化代替最小化中解释了这种特征创建技术的理论基础
就我们的目的而言,只要知道如何实施该技术并使其有助于改进我们的线性和基于 SVM 的 SGD 模型就足够了:
In: source = 'shuffled_covtype.data'
local_path = os.getcwd()
n_vars = ['var_'+str(j) for j in range(54)]
std_row, min_max = explore(target_file=local_path+'\\'+source, binary_features=list(),
fieldnames= n_vars+['covertype'], numeric_features=n_vars, max_rows=50000)
from sklearn.linear_model import SGDClassifier
from sklearn.kernel_approximation import RBFSampler
SGD = SGDClassifier(loss='hinge', penalty=None, random_state=1, average=True)
rbf_feature = RBFSampler(gamma=0.5, n_components=300, random_state=0)
accuracy = 0
accuracy_record = list()
predictions_start = 50
sample = 5000
early_stop = 50000
for x,y,n in pull_examples(target_file=local_path+'\\'+source,
vectorizer=std_row,
min_max=min_max,
binary_features=list(),
numeric_features=n_vars,
fieldnames= n_vars+['covertype'], target='covertype', sparse=False):
rbf_x = rbf_feature.fit_transform(x)
# LEARNING PHASE
if n > predictions_start:
accuracy += int(int(SGD.predict(rbf_x))==y[0])
if n % sample == 0:
accuracy_record.append(accuracy / float(sample))
print '%s Progressive accuracy at example %i: %0.3f' % (time.strftime('%X'), n, np.mean(accuracy_record[-sample:]))
accuracy = 0
if early_stop and n >= early_stop:
break
SGD.partial_fit(rbf_x, y, classes=range(1,8))
Out: ...
07:57:45 Progressive accuracy at example 50000: 0.707
超参数调谐
与批处理学习一样,在测试超参数的最佳组合时,核外算法没有捷径可走;您需要尝试一定数量的组合来找出可能的最佳解决方案,并使用样本外误差测量来评估它们的性能。
由于您实际上不知道您的预测问题是具有简单的平滑凸损失还是更复杂的凸损失,并且您不确切地知道您的超参数是如何相互作用的,因此如果没有尝试足够的组合,很容易陷入一些次优的局部最小值。不幸的是,目前 Scikit-learn 没有为核外算法提供专门的优化程序。考虑到在一个长流上训练一个 SGD 需要很长的时间,当使用这样的技术在你的数据上建立一个模型时,调整超参数真的会成为一个瓶颈。
在这里,我们提出一些经验法则,可以帮助您节省时间和精力,并取得最佳效果。
首先,您可以在一个窗口或一个适合内存的数据样本上调整参数。正如我们在内核支持向量机中看到的,即使你的数据流很大,使用一个储层样本也是相当快的。然后,您可以在内存中进行优化,并使用在流中找到的最佳参数。
正如微软研究院的莱昂·博图在他的技术论文中所说的那样:
“随机梯度下降的数学惊人地独立于训练集的大小。”
所有关键参数都是如此,尤其是学习率;对样本更有效的学习率对完整数据的效果最好。此外,通过尝试在一个小的采样数据集上收敛,可以猜测数据的理想传递次数。根据经验,我们报告了算法检查的10**6示例的指示性数量——正如 Scikit-learn 文档所指出的——我们经常发现这个数字是准确的,尽管理想的迭代次数可能会根据正则化参数而变化。
虽然在使用 SGD 时,大部分工作可以在相对较小的规模上完成,但我们必须定义如何解决固定多个参数的问题。传统上,手动搜索和网格搜索是最常用的方法,网格搜索通过系统地测试所有可能的参数组合的重要值来解决问题(例如,使用 10 或 2 的不同幂次的对数标度检查)。
最近,詹姆斯·伯格斯特拉(James Bergstra)和约舒亚·本吉奥(Yoshua Bengio)在他们的论文《超参数优化的随机搜索》中指出了一种基于超参数值随机采样的不同方法。尽管这种方法是基于随机选择的,但当超参数数量较低时,其结果通常与网格搜索相当(但需要较少的运行次数),当参数较多且并非所有参数都与算法性能相关时,其结果可能超过系统搜索的性能。
我们让读者通过参考前面提到的伯格斯特拉和本吉奥的论文来发现为什么这种简单而有吸引力的方法在理论上如此有效的更多原因。在实践中,在体验了它相对于其他方法的优越性之后,我们提出了一种基于 Scikit-learn 的ParameterSampler函数的方法,该方法在下面的示例代码片段中很好地适用于流。ParameterSampler能够随机采样不同的超参数集(来自分布函数或离散值列表),然后通过set_params方法应用于您的学习 SGD:
In: from sklearn.linear_model import SGDRegressor
from sklearn.grid_search import ParameterSampler
source = '\\bikesharing\\hour.csv'
local_path = os.getcwd()
b_vars = ['holiday','hr','mnth', 'season','weathersit','weekday','workingday','yr']
n_vars = ['hum', 'temp', 'atemp', 'windspeed']
std_row, min_max = explore(target_file=local_path+'\\'+source, binary_features=b_vars, numeric_features=n_vars)
val_rmse = 0
val_rmsle = 0
predictions_start = 16000
tmp_rsmle = 10**6
def apply_log(x): return np.log(x + 1.0)
def apply_exp(x): return np.exp(x) - 1.0
param_grid = {'penalty':['l1', 'l2'], 'alpha': 10.0**-np.arange(2,5)}
random_tests = 3
search_schedule = list(ParameterSampler(param_grid, n_iter=random_tests, random_state=5))
results = dict()
for search in search_schedule:
SGD = SGDRegressor(loss='epsilon_insensitive', epsilon=0.001, penalty=None, random_state=1, average=True)
params =SGD.get_params()
new_params = {p:params[p] if p not in search else search[p] for p in params}
SGD.set_params(**new_params)
print str(search)[1:-1]
for iterations in range(200):
for x,y,n in pull_examples(target_file=local_path+'\\'+source,
vectorizer=std_row, min_max=min_max, sparse = False,
binary_features=b_vars, numeric_features=n_vars, target='cnt'):
y_log = apply_log(y)
# MACHINE LEARNING
if (n+1) >= predictions_start:
# HOLDOUT AFTER N PHASE
predicted = SGD.predict(x)
val_rmse += (apply_exp(predicted) - y)**2
val_rmsle += (predicted - y_log)**2
else:
# LEARNING PHASE
SGD.partial_fit(x, y_log)
examples = float(n-predictions_start+1) * (iterations+1)
print_rmse = (val_rmse / examples)**0.5
print_rmsle = (val_rmsle / examples)**0.5
if iterations == 0:
print 'Iteration %i - RMSE: %0.3f - RMSE: %0.3f' % (iterations+1, print_rmse, print_rmsle)
if iterations > 0:
if tmp_rmsle / print_rmsle <= 1.01:
print 'Iteration %i - RMSE: %0.3f - RMSE: %0.3f\n' % (iterations+1, print_rmse, print_rmsle)
results[str(search)]= {'rmse':float(print_rmse), 'rmsle':float(print_rmsle)}
break
tmp_rmsle = print_rmsle
Out:
'penalty': 'l2', 'alpha': 0.001
Iteration 1 - RMSE: 216.170 - RMSE: 1.440
Iteration 20 - RMSE: 152.175 - RMSE: 0.857
'penalty': 'l2', 'alpha': 0.0001
Iteration 1 - RMSE: 714.071 - RMSE: 4.096
Iteration 31 - RMSE: 184.677 - RMSE: 1.053
'penalty': 'l1', 'alpha': 0.01
Iteration 1 - RMSE: 1050.809 - RMSE: 6.044
Iteration 36 - RMSE: 225.036 - RMSE: 1.298
代码利用了这样一个事实,即自行车共享数据集非常小,不需要任何采样。在其他情况下,通过水库取样或其他取样技术,限制已处理的行数或创建更小的样本是有意义的。如果您想更深入地探索优化,您可以更改random_tests变量,固定要测试的采样超参数组合的数量。然后,使用更接近于1.0的数字修改if tmp_rmsle / print_rmsle <= 1.01条件——如果不是1.0本身——从而让算法完全收敛,直到预测能力的某种可能增益可行。
型式
虽然建议使用分布函数,而不是从值列表中挑选,但是您仍然可以通过简单地增加可能从列表中挑选的值的数量来适当地使用我们之前建议的超参数范围。例如,对于 L1 和 L2 正则化中的 alpha,您可以使用 NumPy 的函数arrange,带有一个小步长,如10.0**-np.arange(1, 7, step=0.1),或者使用 NumPy logspace,带有一个高数值作为num参数:1.0/np.logspace(1,7,num=50)。
SVM 快速学习的其他选择
尽管 Scikit-learn 包提供了足够的工具和算法来学习内核外的东西,但是在自由软件中还有其他有趣的选择。有些是基于 Scikit-learn 本身使用的相同库,比如 Liblinear/SBM,还有一些是全新的,比如 sofia-ml、LASVM 和 Vowpal Wabbit。例如,基于选择性块最小化并作为原始库(https://www . csie . NTU . edu . tw/~ cjlin/libsvmtols/# large _ linear _ classification _ when _ data _ not _ fit _ in _ memory)的一个分叉liblinear-cdblock实现的 Liblinear/SBMis。Liblinear/SBM 通过使用新的数据样本训练学习者,并将其与已经用于最小化的先前样本混合(因此在算法名称中使用阻塞的术语),实现了在大量无法在内存中拟合的数据上拟合非线性支持向量机。
https://code.google.com/archive/p/sofia-ml/是 T2 的另一个选择。SofiaML 基于一种叫做 Pegasos SVM 的在线 SVM 优化算法。这个算法是一个在线的 SVM 近似,就像另一个软件由利昂·博图(http://leon.bottou.org/projects/lasvm)创建的叫做 LaSVM。所有这些解决方案都可以处理稀疏的数据,尤其是文本数据,并解决回归、分类和排序问题。到目前为止,我们测试的任何替代解决方案都没有像 Vowpal Wabbit 那样快速和通用,Vowpal Wabbit 是我们将在接下来的章节中介绍的软件,用于演示如何将外部程序与 Python 集成。
非线性,更快,带沃帕尔瓦比特
Vowpal Wabbit ( 大众)是一个为快速在线学习者开发的开源项目,最初于 2007 年由来自雅虎的约翰·兰福德、李立宏和亚历克斯·斯特雷尔发布!研究(http://hunch.net/?p=309)然后又相继被微软研究院赞助,因为约翰·兰福德成为了微软的首席研究员。这个项目已经发展了很多年,到今天已经到了 8.1.0 版本(在这一章写的时候),有将近一百个贡献者在做这个项目。(关于贡献随时间发展的可视化,在 https://www.youtube.com/watch?v=-aXelGLMMgk 有一个使用软件Gorce的有趣视频。).到目前为止,大众仍在不断开发,并在每次开发迭代中不断提高其学习能力。
大众汽车引人注目的特性是,与其他可用的解决方案(LIBLINEAR、Sofia-ml、svmsgd 和 Scikit-learn)相比,它非常快。它的秘密很简单,但非常有效:它可以同时加载数据和从中学习。异步线程对流入的示例进行解析,因为许多学习线程在一组不相交的特征上工作,因此即使在解析涉及高维特征创建(例如二次或三次多项式展开)时,也能确保高计算效率。在大多数情况下,学习过程的真正瓶颈是将数据传输到大众的磁盘或网络的传输带宽。
大众汽车可以解决分类(甚至多类和多标签)、回归(OLS 和分位数)和主动学习问题,提供大量附带的学习工具(称为约简),如矩阵分解、潜在狄利克雷分配 ( LDA )、神经网络、语言模型的 n-grams 和自举。
安装大众汽车
大众汽车可以从在线版本库 GitHub(https://github.com/JohnLangford/vowpal_wabbit)中检索到,在那里它可以被 Git 克隆或以打包 zip 的形式下载。它是在 Linux 系统上开发的,可以通过一系列简单的 make 和 make install 命令在任何 POSIX 环境下轻松编译。安装的详细说明可以直接在它的安装页面上找到,你可以直接从作者(https://github.com/JohnLangford/vowpal_wabbit/wiki/Download)那里下载 Linux 预编译二进制文件。
不幸的是,在视窗操作系统上运行的大众版本有点难获得。为了创建一个,首先参考大众的文档本身,其中详细解释了一个编译过程。
型式
在本书随附的网站上,我们将提供本书使用的大众 8.1.0 版本的 32 位和 64 位 Windows 二进制文件。
了解大众数据格式
大众可以使用特定的数据格式工作,并从外壳中调用。约翰·兰福德在他的在线教程中使用了这个样本数据集(https://github.com/JohnLangford/vowpal_wabbit/wiki/Tutorial,代表三座房屋,它们的屋顶可以被替换。我们觉得向您推荐并一起评论很有趣:
In:
with open('house_dataset','wb') as W:
W.write("0 | price:.23 sqft:.25 age:.05 2006\n")
W.write("1 2 'second_house | price:.18 sqft:.15 age:.35 1976\n")
W.write("0 1 0.5 'third_house | price:.53 sqft:.32 age:.87 1924\n")
with open('house_dataset','rb') as R:
for line in R:
print line.strip()
Out:
0 | price:.23 sqft:.25 age:.05 2006
1 2 'second_house | price:.18 sqft:.15 age:.35 1976
0 1 0.5 'third_house | price:.53 sqft:.32 age:.87 1924
文件格式的第一个值得注意的方面是它没有标题。这是因为大众使用哈希技巧将特征分配到一个稀疏向量中,因此事先知道根本不需要的特征。数据块由管道(字符|)划分为名称空间,作为不同的特征簇,每个特征簇包含一个或多个特征。
第一个命名空间总是包含响应变量的命名空间。响应可以是指出要回归的数值的实数(或整数)、二进制类或多个类中的一个类。响应总是在线上找到的第一个数字。一个二进制类可以使用1为正和-1为负进行编码(使用 0 作为响应只允许用于回归)。多个类应该从1开始编号,不建议使用间隙号,因为大众要求最后一个类,并考虑1和最后一个之间的所有整数。
响应值后紧接着的数字是权重(告诉您是否必须将一个示例视为多个示例或一个示例的一部分),然后是基数,它起着初始预测的作用(一种偏差)。最后,在撇号字符(')的前面,有一个标签,它可以是一个数字或文本,稍后会在大众输出中找到(在预测中,每个估计都有一个标识符)。重量、基数和标签不是强制性的:如果省略,重量将被估算为1,基数和标签无关紧要。
在第一个名称空间之后,您可以添加任意多个名称空间,用数字或字符串标记每个名称空间。为了被认为是命名空间的标签,它应该被粘在管道上,例如,|label。
在命名空间的标签之后,可以通过名称添加任何特征。要素名称可以是任何名称,但应该包含一个管道或冒号。您可以将整个文本放在名称空间中,每个单词都将被视为一个特性。每个特征都将被视为有价值的1。如果您想分配不同的数字,只需在要素名称的末尾加上一个冒号,然后将其值放在后面。
例如,Vowpal Wabbit 可读的有效行是:
0 1 0.5 'third_house | price:.53 sqft:.32 age:.87 1924
在第一个名称空间中,响应为0,示例权重为1,基数为0.5,标签为third_house。命名空间是无名的,由price(值为.53)、sqft(值为.32)、age(值为.87)、1924(值为1)四个特征构成。
如果您在一个示例中有一个特征,但在另一个示例中没有,则算法将在第二个示例中假设特征值为零。因此,像前面例子中的1924这样的特征可以作为二进制变量,因为当它存在时,当缺少0时,它被自动赋值1。这也告诉你大众如何处理缺失值——它会自动将它们视为0值。
型式
当一个值丢失时,您可以通过放置一个新的特性来轻松处理丢失的值。例如,如果特征是年龄,您可以添加一个新特征age_missing,它将是一个二进制变量,值为1。当估计系数时,该变量将充当缺失值估计器。
在作者的网站上,你还可以找到一个输入验证器,验证你的输入对大众来说是正确的,显示软件是如何解释的:
http://hunch.net/~vw/validate.html
Python 集成
有几个软件包将其与 Python(vowpal _ propowery、 Wabbit Wappa 或 pyvw )集成并安装它们在 Linux 系统中很容易,但在 Windows 上就难多了。无论您使用的是 Jupyter 还是 IDE,使用与 Python 脚本集成的大众汽车最简单的方法就是利用subprocess包中的Popen功能。这使得大众与 Python 并行运行。Python 只需通过捕捉其输出并打印在屏幕上,等待大众完成操作即可:
In: import subprocess
def execute_vw(parameters):
execution = subprocess.Popen('vw '+parameters, \
shell=True, stderr=subprocess.PIPE)
line = ""
history = ""
while True:
out = execution.stderr.read(1)
history += out
if out == '' and execution.poll() != None:
print '------------ COMPLETED ------------\n'
break
if out != '':
line += out
if '\n' in line[-2:]:
print line[:-2]
line = ''
return history.split('\r\n')
这些函数返回学习过程的输出列表,使其易于处理,提取相关的可重用信息(如错误度量)。作为其正确运行的先决条件,将大众可执行文件(即vw.exe文件)放在 Python 工作目录或系统路径中可以找到它的地方。
通过调用之前记录的房屋数据集上的函数,我们可以了解它是如何工作的,以及它产生了什么输出:
In:
params = "house_dataset"
results = execute_vw(params)
Out:
Num weight bits = 18
learning rate = 0.5
initial_t = 0
power_t = 0.5
using no cache
Reading datafile = house_dataset
num sources = 1
average since example example current current current
loss last counter weight label predict features
0.000000 0.000000 1 1.0 0.0000 0.0000 5
0.666667 1.000000 2 3.0 1.0000 0.0000 5
finished run
number of examples per pass = 3
passes used = 1
weighted example sum = 4.000000
weighted label sum = 2.000000
average loss = 0.750000
best constant = 0.500000
best constant's loss = 0.250000
total feature number = 15
------------ COMPLETED ------------
输出的初始行只是调用使用的参数,并确认正在使用哪个数据文件。最有趣的是按流式实例数量报告的递进(按 2 的幂报告,因此实例 1、2、4、8、16 等等)。关于损失函数,基于随后设置的暂停,报告平均损失度量,对于第一次迭代是递增的,其损失通过推迟字母h来表示(如果不包括暂停,则可能仅报告样本内度量)。在example weight栏中,会报告该示例的权重,然后该示例进一步描述为current label、current predict,并显示在该行中找到的特征数量(current features)。所有这些信息都应该有助于你持续关注学习流和学习过程。
完成学习后,汇报几项措施。平均损失是最重要的,尤其是在使用暂停时。由于比较的原因,使用这种损失是最有用的,因为它可以立即与best constant's loss(简单常数的基线预测能力)和使用不同参数配置的不同运行进行比较。
另一个非常有用的集成大众和 Python 的功能是我们准备的自动将 CSV 文件转换为大众数据文件的功能。您可以在下面的代码片段中找到它。这将有助于我们这次使用大众汽车复制以前的自行车共享和 covertype 问题,但它可以很容易地在您自己的项目中重用:
In: import csv
def vw_convert(origin_file, target_file, binary_features, numeric_features, target, transform_target=lambda(x):x,
separator=',', classification=True, multiclass=False, fieldnames= None, header=True, sparse=True):
"""
Reads a online style stream and returns a generator of normalized feature vectors
Parameters
----------
original_file = the CSV file you are taken the data from
target file = the file to stream from
binary_features = the list of qualitative features to consider
numeric_features = the list of numeric features to consider
target = the label of the response variable
transform_target = a function transforming the response
separator = the field separator character
classification = a Boolean indicating if it is classification
multiclass = a Boolean for multiclass classification
fieldnames = the fields' labels (can be omitted and read from file)
header = a boolean indicating if the original file has an header
sparse = if a sparse vector is to be returned from the generator
"""
with open(target_file, 'wb') as W:
with open(origin_file, 'rb') as R:
iterator = csv.DictReader(R, fieldnames, delimiter=separator)
for n, row in enumerate(iterator):
if not header or n>0:
# DATA PROCESSING
response = transform_target(float(row[target]))
if classification and not multiclass:
if response == 0:
stream_row = '-1 '
else:
stream_row = '1 '
else:
stream_row = str(response)+' '
quantitative = list()
qualitative = list()
for k,v in row.iteritems():
if k in binary_features:
qualitative.append(str(k)+\
'_'+str(v)+':1')
else:
if k in numeric_features and (float(v)!=0 or not sparse):
quantitative.append(str(k)+':'+str(v))
if quantitative:
stream_row += '|n '+\
' '.join(quantitative)
if qualitative:
stream_row += '|q '+\
' '.join(qualitative)
W.write(stream_row+'\n')
使用 SVM 约简和神经网络的几个例子
大众致力于最小化一般成本函数,如下所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/large-scale-ml-py/img/B05135_03_20.jpg
与之前看到的其他公式一样,w 是系数向量,并且根据所选的损失函数(OLS、逻辑或铰链)分别为每个*【Xi】*和 yi 获得优化。Lambda1 和 lambda2 是正则化参数,默认情况下为零,但可以使用大众命令行中的--l1和--l2 选项进行设置。
鉴于这样的基本结构,大众已经变得更加复杂和完整,随着时间的推移,使用减少范式。约简只是重用现有算法以解决新问题的一种方式,而不是从头开始编写新的求解算法。换句话说,如果你有一个复杂的机器学习问题 A,你只需要把它简化为 B。解决 B 暗示了 A 的解决方案。这也是合理的,因为人们对机器学习的兴趣越来越大,无法解决的问题数量激增,产生了大量新算法。这是一种有趣的方法,利用了基本算法提供的现有可能性,也是为什么大众在程序保持相当紧凑的情况下,随着时间的推移,其适用性不断增强的原因。如果你对这种方法感兴趣,可以看看约翰·兰福德的这两本教程:http://hunch.net/~reductions_tutorial/和http://hunch.net/~jl/projects/reductions/reductions.html。
出于其他说明的目的,我们将向您简要介绍几个减少,以实现一个 SVM 与一个RBFkernel和一个浅层神经网络使用大众在一个纯粹的核心外的方式。为此,我们将使用一些玩具数据集。
这是鸢尾数据集,变成了一个二元分类问题,从濑户和弗吉尼亚猜测鸢尾的颜色:
In: import numpy as np
from sklearn.datasets import load_iris, load_boston
from random import seed
iris = load_iris()
seed(2)
re_order = np.random.permutation(len(iris.target))
with open('iris_versicolor.vw','wb') as W1:
for k in re_order:
y = iris.target[k]
X = iris.values()[1][k,:]
features = ' |f '+' '.join([a+':'+str(b) for a,b in zip(map(lambda(a): a[:-5].replace(' ','_'), iris.feature_names),X)])
target = '1' if y==1 else '-1'
W1.write(target+features+'\n')
然后对于一个回归问题,我们将使用波士顿房价数据集:
In: boston = load_boston()
seed(2)
re_order = np.random.permutation(len(boston.target))
with open('boston.vw','wb') as W1:
for k in re_order:
y = boston.target[k]
X = boston.data[k,:]
features = ' |f '+' '.join([a+':'+str(b) for a,b in zip(map(lambda(a): a[:-5].replace(' ','_'), iris.feature_names),X)])
W1.write(str(y)+features+'\n')
首先,我们将尝试 SVM。kvsm 是基于 LaSVM 算法(具有在线和主动学习的快速核分类器—http://www.jmlr.org/papers/volume6/bordes05a/bordes05a.pdf)的约简,没有偏差项。大众版本通常只在一个过程中工作,对随机选择的支持向量进行 1-2 次再处理(尽管有些问题可能需要多次过程和再处理)。在我们的例子中,我们只是使用了一次传递和几次重新处理,以便在我们的二进制问题上使用径向基函数核(KSVM 只适用于分类问题)。实现的核是线性、径向基函数和多项式。为了让它工作,使用--ksvm选项,通过--reprocess设置一个重新处理的数字(默认为 1),选择带有--kernel的内核(选项有linear、poly和rbf)。然后,如果内核是多项式,则为--degree设置一个整数,如果使用的是径向基函数,则为--bandwidth设置一个浮点数(默认值为 1.0)。您还必须强制指定 l2 正则化;否则,减少不会正常工作。在我们的示例中,我们制作了带宽为 0.1:
In: params = '--ksvm --l2 0.000001 --reprocess 2 -b 18 --kernel rbf --bandwidth=0.1 -p iris_bin.test -d iris_versicolor.vw'
results = execute_vw(params)
accuracy = 0
with open('iris_bin.test', 'rb') as R:
with open('iris_versicolor.vw', 'rb') as TRAIN:
holdouts = 0.0
for n,(line, example) in enumerate(zip(R,TRAIN)):
if (n+1) % 10==0:
predicted = float(line.strip())
y = float(example.split('|')[0])
accuracy += np.sign(predicted)==np.sign(y)
holdouts += 1
print 'holdout accuracy: %0.3f' % ((accuracy / holdouts)**0.5)
Out: holdout accuracy: 0.966
神经网络是大众的另一个很酷的补充;感谢 Paul Mineiro 的工作(http://www . machinedlearnings . com/2012/11/unpimp-you-sigmoid . html),大众可以实现一个具有双曲正切( tanh )激活和(可选的)脱扣(使用--dropout选项)的单层神经网络。虽然只可能决定神经元的数量,但神经约简对回归和分类问题都很有效,并且可以平滑地接受大众作为输入的其他转换(例如二次变量和 n-gram),使其成为一个非常好的集成、通用(神经网络可以解决相当多的问题)和快速的解决方案。在我们的示例中,我们使用五个神经元和 drop 将其应用于波士顿数据集:
In: params = 'boston.vw -f boston.model --loss_function squared -k --cache_file cache_train.vw --passes=20 --nn 5 --dropout'
results = execute_vw(params)
params = '-t boston.vw -i boston.model -k --cache_file cache_test.vw -p boston.test'
results = execute_vw(params)
val_rmse = 0
with open('boston.test', 'rb') as R:
with open('boston.vw', 'rb') as TRAIN:
holdouts = 0.0
for n,(line, example) in enumerate(zip(R,TRAIN)):
if (n+1) % 10==0:
predicted = float(line.strip())
y = float(example.split('|')[0])
val_rmse += (predicted - y)**2
holdouts += 1
print 'holdout RMSE: %0.3f' % ((val_rmse / holdouts)**0.5)
Out: holdout RMSE: 7.010
更快的自行车共享
让我们在之前创建的自行车共享示例文件上试试大众,以解释输出组件。作为第一步,您必须将 CSV 文件转换为大众文件,之前的vw_convert功能将在这样做时派上用场。和前面一样,我们将使用vw_convert函数的transform_target参数传递的apply_log函数对数值响应进行对数变换:
In: import os
import numpy as np
def apply_log(x):
return np.log(x + 1.0)
def apply_exp(x):
return np.exp(x) - 1.0
local_path = os.getcwd()
b_vars = ['holiday','hr','mnth', 'season','weathersit','weekday','workingday','yr']
n_vars = ['hum', 'temp', 'atemp', 'windspeed']
source = '\\bikesharing\\hour.csv'
origin = target_file=local_path+'\\'+source
target = target_file=local_path+'\\'+'bike.vw'
vw_convert(origin, target, binary_features=b_vars, numeric_features=n_vars, target = 'cnt', transform_target=apply_log,
separator=',', classification=False, multiclass=False, fieldnames= None, header=True)
几秒钟后,新文件应该准备好了。我们可以立即运行我们的解决方案,这是一个简单的线性回归(大众的默认选项)。学习预计将进行 100 次,由大众自动实施的样本外验证控制(以可重复的方式系统绘制,每 10 次观察中有一次作为验证)。在这种情况下,我们决定在 16,000 个示例后设置保持样本(使用--holdout_after选项)。当验证上的验证错误增加(而不是减少)时,大众会在几次迭代后停止(默认为三次,但可以使用--early_terminate选项更改数量),避免过度拟合数据:
In: params = 'bike.vw -f regression.model -k --cache_file cache_train.vw --passes=100 --hash strings --holdout_after 16000'
results = execute_vw(params)
Out: …
finished run
number of examples per pass = 15999
passes used = 6
weighted example sum = 95994.000000
weighted label sum = 439183.191893
average loss = 0.427485 h
best constant = 4.575111
total feature number = 1235898
------------ COMPLETED ------------
最终的报告显示,六次通过(100 次可能通过)已经完成,样本外平均损失为 0.428。因为我们对RMSE和RMSLE感兴趣,所以我们必须自己计算。
然后,我们预测文件(pred.test)中的结果,以便能够读取它们,并使用与训练集中相同的保持策略计算我们的误差度量。结果确实比我们之前使用 Scikit-learn 的 SGD 获得的结果好得多(在一小部分时间内):
In: params = '-t bike.vw -i regression.model -k --cache_file cache_test.vw -p pred.test'
results = execute_vw(params)
val_rmse = 0
val_rmsle = 0
with open('pred.test', 'rb') as R:
with open('bike.vw', 'rb') as TRAIN:
holdouts = 0.0
for n,(line, example) in enumerate(zip(R,TRAIN)):
if n > 16000:
predicted = float(line.strip())
y_log = float(example.split('|')[0])
y = apply_exp(y_log)
val_rmse += (apply_exp(predicted) - y)**2
val_rmsle += (predicted - y_log)**2
holdouts += 1
print 'holdout RMSE: %0.3f' % ((val_rmse / holdouts)**0.5)
print 'holdout RMSLE: %0.3f' % ((val_rmsle / holdouts)**0.5)
Out:
holdout RMSE: 135.306
holdout RMSLE: 0.845
大众处理的覆盖型数据集
covertype 问题大众也能比我们之前管理的更好更容易解决。这一次,我们需要设置一些参数决定纠错锦标赛 ( ECT ,由大众汽车上的--ect参数调用),其中每个职业在一场淘汰锦标赛中竞争成为一个例子的标签。在很多例子中,ECT 可以胜过一对所有 ( OAA ),但这不是一般规律,ECT 是处理多类问题时需要测试的方法之一。(另一个可能的选择是--log_multi,使用在线决策树将样本分割成更小的集合,在这些集合中我们应用单一预测模型。)我们还将学习率设置为 1.0,并使用--cubic 参数创建三次多项式展开,指出哪些名称空间必须相互相乘(在这种情况下,三次的名称空间 f 由nnn字符串后跟--cubic表示。):
In: import os
local_path = os.getcwd()
n_vars = ['var_'+'0'*int(j<10)+str(j) for j in range(54)]
source = 'shuffled_covtype.data'
origin = target_file=local_path+'\\'+source
target = target_file=local_path+'\\'+'covtype.vw'
vw_convert(origin, target, binary_features=list(), fieldnames= n_vars+['covertype'], numeric_features=n_vars,
target = 'covertype', separator=',', classification=True, multiclass=True, header=False, sparse=False)
params = 'covtype.vw --ect 7 -f multiclass.model -k --cache_file cache_train.vw --passes=2 -l 1.0 --cubic nnn'
results = execute_vw(params)
Out:
finished run
number of examples per pass = 522911
passes used = 2
weighted example sum = 1045822.000000
weighted label sum = 0.000000
average loss = 0.235538 h
total feature number = 384838154
------------ COMPLETED ------------
型式
为了让这个例子更快,我们将传球次数限制在两次。如果你有时间,把数字提高到 100,看看如何进一步提高获得的精度。
这里,我们不需要进一步检查误差度量,因为报告的平均损失是精度度量 1.0 的补充;我们只是计算它的完整性,确认我们的保持精度正好是0.769:
In: params = '-t covtype.vw -i multiclass.model -k --cache_file cache_test.vw -p covertype.test'
results = execute_vw(params)
accuracy = 0
with open('covertype.test', 'rb') as R:
with open('covtype.vw', 'rb') as TRAIN:
holdouts = 0.0
for n,(line, example) in enumerate(zip(R,TRAIN)):
if (n+1) % 10==0:
predicted = float(line.strip())
y = float(example.split('|')[0])
accuracy += predicted ==y
holdouts += 1
print 'holdout accuracy: %0.3f' % (accuracy / holdouts)
Out: holdout accuracy: 0.769
总结
在本章中,我们通过向简单的基于回归的线性模型添加支持向量机,扩展了对核外算法的初步讨论。大部分时间,我们专注于 Scikit-learn 实现——大部分是 SGD——并以可以与 Python 脚本集成的外部工具的概述结束,例如约翰·兰福德的 Vowpal Wabbit。在此过程中,我们通过讨论储层采样、正则化、显式和隐式非线性变换以及超参数优化,完成了对在核外工作时模型改进和验证技术的概述。
在下一章中,我们将涉及更复杂和更强大的学习方法,同时介绍大规模问题中的深度学习和神经网络。如果你的项目围绕着图像和声音的分析,那么到目前为止我们所看到的可能还不是你想要的神奇解决方案。下一章将提供所有期望的解决方案。
2万+

被折叠的 条评论
为什么被折叠?



