Java-机器学习-全-

Java 机器学习(全)

零、前言

Java 中的机器学习,第二版,将为您提供从复杂数据中快速获得洞察力所需的技术和工具。您将从学习如何将机器学习方法应用于各种常见任务开始,包括分类、预测、预报、购物篮分析和聚类。

这是一个实用的教程,使用实际操作的例子来逐步完成机器学习的一些现实应用。在不回避技术细节的情况下,您将使用清晰实用的例子来探索 Java 库的机器学习。您将探索如何为分析准备数据,选择机器学习方法,并衡量该过程的成功。

这本书是给谁的

如果你想学习如何使用 Java 的机器学习库从你的数据中获得洞察力,这本书是给你的。它将帮助您快速启动和运行,并为您提供轻松成功地创建、定制和部署机器学习应用程序所需的技能。为了充分利用这本书,你应该熟悉 Java 编程和一些基本的数据挖掘概念,但是不需要有机器学习的经验。

从这本书中获得最大收益

本书假设用户具备 Java 语言的工作知识和机器学习的基本概念。这本书大量使用了 JAR 格式的外部库。假设用户知道在终端或命令提示符下使用 JAR 文件,尽管这本书也解释了如何做。用户可以很容易地在任何通用的 Windows 或 Linux 系统上使用本书。

下载示例代码文件

你可以从你在www.packt.com的账户下载本书的示例代码文件。如果你在其他地方购买了这本书,你可以访问 www.packt.com/support 的并注册,让文件直接通过电子邮件发送给你。

您可以按照以下步骤下载代码文件:

  1. www.packt.com登录或注册。
  2. 选择支持选项卡。
  3. 点击代码下载和勘误表。
  4. 在搜索框中输入图书名称,然后按照屏幕指示进行操作。

下载文件后,请确保使用最新版本的解压缩或解压文件夹:

  • WinRAR/7-Zip for Windows
  • 适用于 Mac 的 Zipeg/iZip/UnRarX
  • 用于 Linux 的 7-Zip/PeaZip

该书的代码包也托管在 GitHub 的 https://GitHub . com/packt publishing/Machine-Learning-in-Java-Second-Edition 上。如果代码有更新,它将在现有的 GitHub 库中更新。

我们在也有丰富的书籍和视频目录中的其他代码包。看看他们!

下载彩色图像

我们还提供了一个 PDF 文件,其中有本书中使用的截图/图表的彩色图像。可以在这里下载:www . packtpub . com/sites/default/files/downloads/9781788474399 _ color images . pdf

使用的惯例

本书通篇使用了许多文本约定。

CodeInText:表示文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、伪 URL、用户输入和 Twitter 句柄。下面是一个例子:“解压存档文件并在解压的存档文件中找到weka.jar

代码块设置如下:

data.defineSingleOutputOthersInput(outputColumn); 

EncogModel model = new EncogModel(data); 
model.selectMethod(data, MLMethodFactory.TYPE_FEEDFORWARD);
model.setReport(new ConsoleStatusReportable()); 
data.normalize(); 

任何命令行输入或输出都按如下方式编写:

$ java -cp moa.jar -javaagent:sizeofag.jar moa.gui.GUI

粗体:表示一个新术语、一个重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词在文本中以粗体显示。下面是一个例子:“我们可以通过点击文件|另存为,并在保存对话框中选择 CSV,将其转换为逗号分隔值 ( CSV )格式。”

警告或重要提示如下所示。

提示和技巧是这样出现的。

取得联系

我们随时欢迎读者的反馈。

总体反馈:如果您对这本书的任何方面有疑问,请在邮件主题中提及书名,并在customercare@packtpub.com发送电子邮件给我们。

勘误表:虽然我们已经尽力确保内容的准确性,但错误还是会发生。如果你在这本书里发现了一个错误,请告诉我们,我们将不胜感激。请访问 www.packt.com/submit-errata,选择您的图书,点击勘误表提交表格链接,并输入详细信息。

盗版:如果您在互联网上遇到我们作品的任何形式的非法拷贝,如果您能提供我们的地址或网站名称,我们将不胜感激。请通过copyright@packt.com联系我们,并提供材料链接。

如果你有兴趣成为一名作者:如果有一个你擅长的主题,并且你有兴趣写作或投稿,请访问 authors.packtpub.com。

复习

请留下评论。一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢?潜在的读者可以看到并使用您不带偏见的意见来做出购买决定,我们 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们的书的反馈。谢谢大家!

更多关于 Packt 的信息,请访问packt.com

一、应用机器学习快速入门

本章介绍了机器学习的基础知识,列出了常见的主题和概念,并使其易于遵循逻辑和熟悉主题。目标是快速学习应用机器学习的分步过程,掌握主要的机器学习原理。在本章中,我们将讨论以下主题:

  • 机器学习和数据科学
  • 数据和问题定义
  • 数据收集
  • 数据预处理
  • 无监督学习
  • 监督学习
  • 概括和评价

如果你已经熟悉机器学习,并渴望开始编码,那么请快速跳转到这一章之后的章节。但是,如果您需要刷新您的记忆或澄清一些概念,那么强烈建议您重温本章中介绍的主题。

机器学习和数据科学

现在大家都在说机器学习和数据科学。那么,到底什么是机器学习呢?它与数据科学有什么关系?这两个术语经常被混淆,因为它们经常使用相同的方法并且有很大的重叠。所以,我们先明确一下它们是什么。乔希·威尔斯在推特上写道:

“数据科学家是比任何软件工程师都更擅长统计,比任何统计学家都更擅长软件工程的人。”

-乔希·威尔斯

更具体地说,数据科学包括通过整合统计学、计算机科学和其他领域的方法从数据中获得洞察力来获取知识的整个过程。在实践中,数据科学包含数据采集、清理、分析、可视化和部署的迭代过程。

另一方面,机器学习主要关注在数据科学过程的分析和建模阶段使用的通用算法和技术。

用机器学习解决问题

在不同的机器学习方法中,有三种主要的学习方法,如下表所示:

  • 监督学习
  • 无监督学习
  • 强化学习

给定一组示例输入 X 及其输出 Y,监督学习旨在学习通用映射函数 f,该函数将输入转换为输出,如 f: (X,Y)。

监督学习的一个例子是信用卡欺诈检测,其中学习算法用标记为正常或可疑(向量 Y)的信用卡交易(矩阵 X)来表示。学习算法产生一个决策模型,将看不见的交易标记为正常或可疑(这是 f 函数)。

相比之下,无监督学习算法不假设给定的结果标签,因为它们专注于学习数据的结构,例如将相似的输入分组到聚类中。因此,无监督学习可以发现数据中隐藏的模式。无监督学习的一个例子是基于物品的推荐系统,其中学习算法发现一起购买的相似物品;比如买了书 A 的人也买了书 b。

强化学习从一个完全不同的角度处理学习过程。它假设一个智能体,可以是机器人、机器人或计算机程序,与动态环境交互以实现特定目标。环境用一组状态来描述,代理可以采取不同的动作从一个状态移动到另一个状态。一些状态被标记为目标状态,如果代理实现了这个状态,它将获得一大笔奖励。在其他州,奖励更少,不存在,甚至是负的。强化学习的目标是找到一个最佳策略或映射函数,指定在每个状态下采取的行动,而无需教师明确告知这是否会导致目标状态。强化学习的一个例子是用于驾驶车辆的程序,其中状态对应于驾驶条件,例如当前速度、路段信息、周围交通、速度限制和道路上的障碍物;这些动作可以是驾驶操作,例如左转或右转、停止、加速和继续。该学习算法产生一个策略,该策略规定了在特定的驾驶条件配置中要采取的行动。

在本书中,我们将只关注监督学习和非监督学习,因为它们有许多相同的概念。如果强化学习激发了你的兴趣,那么一本好书是《强化学习:导论》,作者理查德·萨顿和安德鲁·巴尔托,麻省理工出版社(2018)。

应用机器学习工作流

这本书的重点是应用机器学习。我们希望为您提供让学习算法在不同环境中工作所需的实用技能。代替机器学习中的数学和理论,我们将花更多的时间在实践、动手技能(和肮脏的把戏)上,以使这些东西在应用程序中很好地工作。我们将关注监督和非监督机器学习,并学习数据科学中的基本步骤,以建立应用机器学习工作流程。

应用机器学习应用中的典型工作流由回答一系列问题组成,这些问题可以概括为以下步骤:

  1. 数据和问题定义:第一步,提出有趣的问题,比如:你正在尝试解决的问题是什么为什么重要哪种格式的结果回答了你的问题这是一个简单的是/否答案是否需要从可用问题中选择一个
  2. 数据收集:一旦你有问题要解决,你将需要数据。问问你自己什么样的数据会帮助你回答这个问题。你能从现有的来源得到数据吗你必须结合多种来源吗一定要生成数据吗?有没有抽样偏倚需要多少数据
  3. 数据预处理:第一个数据预处理任务是数据清理。一些示例包括填充缺失值、平滑噪声数据、移除异常值和解决一致性问题。随后通常会集成多个数据源,并将数据转换到特定范围(标准化)、值箱(离散化区间),并减少维数。
  4. 数据分析和建模:数据分析和建模包括无监督和有监督的机器学习、统计推断和预测。有各种各样的机器学习算法可用,包括 k-最近邻、朴素贝叶斯分类器、决策树、支持向量机 ( 支持向量机)、逻辑回归、k-means 等等。要部署的方法取决于第一步中讨论的问题定义和收集的数据类型。这一步的最终产品是从数据中推断出的模型。
  5. 评估:最后一步是模型评估。用机器学习建立的模型面临的主要问题是它们对底层数据的建模有多好;例如,如果一个模型过于具体或者过于适合用于训练的数据,那么它很可能在新数据上表现不佳。该模型可能过于一般化,这意味着它对训练数据的拟合不足。比如,当被问及加州的天气如何时,它总是回答晴朗,这在大多数时候确实是正确的。然而,这样的模型对于做出有效的预测并不真正有用。这一步的目标是正确评估模型,并确保它也适用于新数据。评估方法包括单独的测试和训练集、交叉验证和留一交叉验证。

我们将在接下来的几节中仔细研究每个步骤。我们将尝试理解在应用机器学习工作流程中我们必须回答的问题类型,并查看数据分析和评估的相关概念。

数据和问题定义

当提出问题定义时,我们需要问一些问题,这些问题将有助于从数据中理解目标和目标信息。我们可以问一些非常常见的问题,例如:一旦研究了数据,预期的发现是什么? 数据探索后能提取出什么样的信息?或者,需要什么样的格式才能回答问题?提出正确的问题会让你更清楚地了解下一步该怎么做。数据只是数字、文字、观察、事物描述、图像等形式的测量值的集合。

测量标度

表示数据最常见的方式是使用一组属性-值对。考虑下面的例子:

Bob = { 
height: 185cm, 
eye color: blue, 
hobbies: climbing, sky diving 
} 

例如,Bob具有名为heighteye colorhobbies的属性,其值分别为185cmblueclimbingsky diving

一组数据可以简单地表示为表格,其中列对应于属性或特征,行对应于特定的数据示例或实例。在监督机器学习中,我们希望根据其他属性 X 的值来预测结果 Y 的值的属性被表示为类或目标变量,如下表所示:

| 名称 | 身高【厘米】 | 眼睛颜色 | 兴趣爱好 |
| 上下移动 | One hundred and eighty-five | 蓝色 | 爬山,跳伞 |
| 安娜;安那(anna 旧时印度货币名) | One hundred and sixty-three | 褐色的 | 阅读 |
| ... | ... | ... | ... |

我们注意到的第一件事是属性值变化有多大。例如,身高是一个数字,眼睛的颜色是文本,爱好是一个列表。为了更好地理解值类型,让我们仔细看看不同类型的数据或度量尺度。斯坦利·史密斯·史蒂文斯(1946 年)定义了以下四种具有越来越多表达性质的衡量尺度:

  • 名义数据由互斥但不有序的数据组成。例子包括眼睛的颜色、婚姻状况、拥有的汽车类型等等。
  • 顺序数据对应于顺序重要的类别,而不是值之间的差异,例如疼痛程度、学生信等级、服务质量评级、IMDb 电影评级等等。
  • 区间数据由两个值之间的差异有意义的数据组成,但没有零的概念,例如标准化考试分数、华氏温度等。
  • 比率数据具有区间变量的所有性质,也有零的明确定义;当变量等于零时,这个变量将会丢失。身高、年龄、股票价格和每周食物支出等变量是比率变量。

我们为什么要关心测量尺度?嗯,机器学习很大程度上依赖于数据的统计属性;因此,我们应该意识到每种数据类型的局限性。一些机器学习算法只能应用于测量尺度的子集。

下表总结了每种测量类型的主要操作和统计属性:

| 属性 | 标称值 | 序数 | 间隔 | 比率 |
| one | 分布频率 | 真实的 | 真实的 | 真实的 | 真实的 |
| Two | 众数和中位数 | | 真实的 | 真实的 | 真实的 |
| three | 值的顺序是已知的 | | 真实的 | 真实的 | 真实的 |
| four | 可以量化每个值之间的差异 | | | 真实的 | 真实的 |
| five | 可以增加或减少数值 | | | 真实的 | 真实的 |
| six | 可以乘除数值 | | | | 真实的 |
| seven | 具有真零点 | | | | 真实的 |

此外,标称和序数数据对应于离散值,而间隔和比率数据也可以对应于连续值。在监督学习中,我们想要预测的属性值的度量尺度决定了可以使用的机器算法的种类。例如,从有限的列表中预测离散值称为分类,可以使用决策树来实现,而预测连续值称为回归,可以使用模型树来实现。

数据收集

一旦问题问对了方向,数据探索的目标就很明确了。所以,下一步就是看数据从哪里来。收集的数据可能非常杂乱,格式也非常多样,这可能涉及到从数据库、互联网、文件系统或其他文档中读取数据。大多数机器学习工具要求数据以特定的格式呈现,以便生成正确的结果。我们有两种选择:观察现有来源的数据,或者通过调查、模拟和实验生成数据。让我们仔细看看这两种方法。

查找或观察数据

数据可以在很多地方找到或观察到。一个明显的数据来源是互联网。随着社交媒体使用的增加,以及随着移动数据计划变得更便宜甚至提供无限数据,移动电话渗透得更深,用户消费的数据呈指数级增长。

现在,在线流媒体平台已经出现——下图显示,花费在视频数据上的时间也在快速增长:

要从 internet 获取数据,有多个选项,如下表所示:

  • 从维基百科、https://labrosa.ee.columbia.edu/millionsong/、百万歌曲数据集(可以在这里找到:)等网站批量下载。
  • 通过 API(如谷歌、推特、脸书和 YouTube)访问数据。
  • 搜集公开的、非敏感的、匿名的数据是可以的。请务必检查条款和条件,并充分参考信息。

收集数据的主要缺点是,积累数据需要时间和空间,而且它只涵盖发生的事情;例如,没有收集意图以及内部和外部动机。最后,这样的数据可能是嘈杂的、不完整的、不一致的,甚至可能随时间而改变。

另一种选择是从传感器收集测量值,例如移动设备中的惯性和位置传感器、环境传感器以及监控关键性能指标的软件代理。

生成数据

另一种方法是由您生成数据,例如,通过调查。在调查设计中,我们必须注意数据抽样;也就是回答调查的回答者是谁。我们只从容易接近并愿意回答的受访者那里获得数据。此外,受访者可以提供符合其自我形象和研究者预期的答案。

或者,可以通过模拟来收集数据,其中领域专家在微观层面上指定用户的行为模型。例如,人群模拟需要指定不同类型的用户在人群中的行为方式。一些例子可以是跟随人群,寻找一个逃避的方法,等等。然后可以在不同的条件下运行模拟,看看会发生什么(Tsai 等人,2011)。模拟适用于研究宏观现象和突发行为;然而,它们通常很难用经验来验证。

此外,您可以设计实验来彻底涵盖所有可能的结果,其中您保持所有变量不变,并且一次只操纵一个变量。这是最昂贵的方法,但通常提供最好的质量。

取样陷阱

数据收集可能涉及许多陷阱。为了演示一个,我来分享一个故事。学生之间免费发送普通邮件应该有一个全球性的不成文的规则。如果你在应该贴邮票的地方写上"学生对学生",邮件就会免费送到收件人手中。现在,假设 Jacob 给 Emma 发送了一套明信片,并且 Emma 确实收到了一些明信片,她得出结论所有的明信片都已送达,并且该规则确实成立。艾玛的理由是,当她收到明信片时,所有的明信片都被投递了。然而,她不知道 Jacob 寄出的明信片没有被投递;因此,她无法在她的推论中解释这一点。艾玛经历的是生存偏见;也就是她根据留存下来的数据得出的结论。供您参考,附有学生对学生邮票的明信片上有一个圈起来的黑色字母 T 邮票,这意味着邮资是到期的,收件人应该支付它,包括一笔小额罚款。然而,邮件服务在收取这些费用时通常会有更高的成本,因此不会这样做。(magal hes,2010 年)。

另一个例子是一项研究发现,平均死亡年龄最低的职业是学生。当学生并不会导致你早夭;相反,作为一名学生意味着你年轻。这就是平均水平如此之低的原因。(盖尔曼和诺兰,2002 年)。

此外,一项研究发现,在事故中只有 1.5%的司机报告说他们在使用手机,而 10.9%的人报告说车里的另一个乘客分散了他们的注意力。我们能得出使用手机比与另一个居住者交谈更安全的结论吗?(悉尼科技大学,2003 年)要回答这个问题,我们需要知道手机使用的普遍性。在收集数据期间,很可能有更多的人在开车时与车内的另一位乘客交谈,而不是打手机。

数据预处理

数据预处理任务的目标是以尽可能好的方式为机器学习算法准备数据,因为并非所有算法都能够解决丢失数据、额外属性或反规格化值的问题。

数据清理

数据清理,也称为数据清理或数据清理,是一个由以下步骤组成的过程:

  1. 识别不准确、不完整、不相关或损坏的数据,以将其从进一步处理中移除
  2. 解析数据,提取感兴趣的信息,或者验证数据字符串的格式是否可以接受
  3. 将数据转换为通用编码格式,例如 UTF-8 或 int32、时间刻度或标准化范围
  4. 将数据转换成公共数据模式;例如,如果我们从不同类型的传感器收集温度测量值,我们可能希望它们具有相同的结构

填充缺失值

机器学习算法通常不能很好地处理缺失值。罕见的例外包括决策树、朴素贝叶斯分类器和一些基于规则的学习器。理解一个值缺失的原因是非常重要的。它可能由于多种原因而丢失,例如随机误差、系统误差和传感器噪声。一旦我们确定了原因,就有多种方法来处理丢失的值,如下表所示:

  • 删除实例:如果有足够的数据,并且只有几个不相关的实例有一些丢失的值,那么删除这些实例是安全的。
  • 删除属性:当大部分值缺失、值不变或者一个属性与另一个属性强相关时,删除一个属性是有意义的。
  • 指定一个特殊值 ( N/A ):有时由于有效的原因,某个值会丢失,例如该值超出范围,离散属性值未定义,或者无法获取或测量该值。例如,如果一个人从不评价一部电影,那么他对这部电影的评价就是不存在的。
  • 以平均属性值为例:如果我们的实例数量有限,我们可能无法承担删除实例或属性的费用。在这种情况下,我们可以通过分配平均属性值来估计缺失值。
  • 预测其他属性的值:如果属性具有时间相关性,则预测之前条目的值。

正如我们所看到的,值的丢失可能有多种原因,因此,理解值丢失、缺失或损坏的原因是很重要的。

移除异常值

数据中的异常值是不同于序列中任何其他值的值,并在不同程度上影响所有学习方法。这些可能是极端值,可以用置信区间来检测,并通过使用阈值来消除。最好的方法是将数据可视化,并检查可视化以检测不规则性。下图显示了一个示例。可视化仅适用于低维数据:

数据转换

数据转换技术将数据集驯服为机器学习算法期望作为输入的格式,甚至可以帮助算法更快地学习并实现更好的性能。它也被称为数据管理或数据争论。例如,标准化假设数据遵循高斯分布,并以平均值为 0、偏差为 1 的方式转换值,如下所示:

另一方面,规范化将属性值调整到一个较小的指定范围,通常在 0 和 1 之间:

很多机器学习工具箱会自动为你将数据规格化、标准化。

最后一种转换技术是离散化,它将连续属性的范围划分为多个区间。我们为什么要关心?一些算法,如决策树和朴素贝叶斯更喜欢离散属性。选择间隔的最常见方法如下:

  • 等宽:连续变量的区间分为 k 等宽区间
  • 等频:假设有 N 个实例,每个 k 个区间包含大约 Nk 个实例
  • 最小熵(Min entropy):这种方法递归地分割区间,直到度量无序度的熵减少超过区间分割引入的熵增加(法耶兹和伊拉尼,1993)

前两种方法需要我们指定区间数,后一种方法自动设置区间数;然而,它需要 class 变量,这意味着它不适用于无监督的机器学习任务。

数据整理

数据约简处理丰富的属性和实例。属性的数量对应于数据集中的维数。预测能力低的维度对整体模型的贡献很小,造成的危害很大。例如,具有随机值的属性可以引入一些随机模式,这些模式将被机器学习算法拾取。可能会出现数据包含大量缺失值的情况,我们必须找到大量缺失值的原因,并在此基础上用一些替代值填充,或者估算或完全删除属性。如果缺少 40%或更多的值,那么建议删除这些属性,因为这会影响模型性能。

另一个因素是方差,其中常数变量可能具有较低的方差,这意味着数据彼此非常接近,或者数据中没有太大的变化。

为了处理这个问题,第一组技术去除这样的属性并选择最有希望的属性。这一过程被称为特征选择或属性选择,包括诸如 ReliefF、信息增益和基尼指数等方法。这些方法主要关注离散属性。

另一组工具侧重于连续属性,将数据集从原始维度转换到低维空间。例如,如果我们在三维空间中有一组点,我们可以将其投影到二维空间中。一些信息丢失了,但是在第三维度不相关的情况下,我们不会丢失太多,因为数据结构和关系几乎被完美地保留了下来。这可以通过以下方法实现:

  • 奇异值分解 ( SVD )
  • 主成分分析 ( PCA )
  • 向后/向前特征消除
  • 要素分析
  • 线性判别分析 ( LDA )
  • 神经网络自动编码器

数据简化的第二个问题与过多的实例有关;例如,它们可能是重复的或者来自非常频繁的数据流。主要思想是选择实例的子集,使得所选数据的分布仍然类似于原始数据分布,更重要的是,类似于观察到的过程。减少实例数量的技术包括随机数据采样、分层等。一旦准备好数据,我们就可以开始数据分析和建模了。

无监督学习

无监督学习是关于分析数据和发现未标记数据中的隐藏结构。因为没有给出正确标签的概念,所以也没有错误度量来评估学习模型;然而,无监督学习是一个极其强大的工具。你有没有想过亚马逊如何预测你会喜欢什么书?或者网飞如何在你之前知道你想看什么?答案可以在无监督学习中找到。我们将在下一节看一个类似的无监督学习的例子。

查找相似的项目

许多问题可以表述为寻找相似的元素集合,例如,购买相似产品的客户、具有相似内容的网页、具有相似对象的图像、访问相似网站的用户等等。

如果两个项目相距很小,则认为它们是相似的。主要问题是如何表示每个项目,以及如何定义项目之间的距离。距离测量主要有两类:

  • 欧几里德距离
  • 非欧几里德距离

欧几里德距离

在欧几里得空间中,在维度为 n 的情况下,两个元素之间的距离是基于元素在这样一个空间中的位置,表示为p-范数距离。两种常用的距离度量是 L2 范数距离和 L1 范数距离。

L2 范数,也称为欧几里德距离,是最常用的距离度量,用于测量二维空间中两个项目之间的距离。其计算方法如下:

L1 定额,也称为曼哈顿距离、城市街区距离和出租车定额,简单地对每个维度的绝对差异求和,如下所示:

非欧几里德距离

非欧几里得距离是基于元素的属性,而不是基于它们在空间中的位置。一些众所周知的距离是雅克卡距离、余弦距离、编辑距离和汉明距离。

Jaccard distance 用于计算两组之间的距离。首先,我们将两个集合的 Jaccard 相似性计算为它们的交集大小除以它们的并集大小,如下所示:

然后根据以下公式定义 Jaccard 距离:

两个向量之间的余弦距离关注的是方向而不是大小,因此,两个方向相同的向量的余弦相似度为 1,而两个垂直的向量的余弦相似度为 0。假设我们有两个多维点,把一个点想象成一个从原点出发的向量( 0,0,...,0 到它的位置。两个向量构成一个角度,其余弦距离是向量的归一化点积,如下所示:

余弦距离常用于高维特征空间;例如,在文本挖掘中,文本文档代表一个实例,对应于不同单词的特征,它们的值对应于单词在文档中出现的次数。通过计算余弦相似度,我们可以测量两个文档在描述相似内容时匹配的可能性。

编辑距离在我们比较两个字符串的时候是有意义的。a=a1,a2,a3 之间的距离,...an 和 b=b1,b2,b3,...bn strings 是将字符串从 a 转换为 b 所需的单个字符的插入/删除操作的最小次数,例如,a = abcd,b = abbd。要将 a 转换成 b,我们必须删除第二个 b,并在它的位置插入 c。没有任何最小数量的运算会将 a 转换成 b,因此距离为 d(a,b) =2。

汉明距离比较两个大小相同的向量,并计算它们不同的维数。换句话说,它测量将一个载体转换成另一个载体所需的替换次数。

有许多侧重于各种属性的距离度量,例如,相关性度量两个元素之间的线性关系; Mahalanobis distance 度量一个点与其他点的分布之间的距离 SimRank 基于图论,度量元素出现的结构的相似性,等等。正如您所想象的,为您的问题选择和设计正确的相似性度量是成功的一半以上。A. A. Goshtasby,Springer Science and Business Media(2012)所著的《图像配准:原理、工具和方法》一书的第二章相似性和不相似性度量 中收集了对相似性度量的令人印象深刻的概述和评估。

维度的诅咒

维数灾难指的是这样一种情况,我们有大量的特征,通常有数百个或数千个,这导致数据稀疏的空间非常大,从而导致距离异常。例如,在高维空间中,几乎所有的点对彼此之间的距离都是相等的;事实上,几乎所有配对的距离都接近平均距离。诅咒的另一个表现是任何两个向量几乎是正交的,这意味着所有的角度都接近 90 度。这实际上使任何距离测量变得无用。

一种解决维数灾难的方法可能会在一种数据简化技术中找到,在这种技术中,我们希望减少特征的数量;例如,我们可以运行一个特征选择算法,比如 ReliefF,或者一个特征提取或简化算法,比如 PCA。

使聚集

聚类是一种根据一些距离度量将相似的实例分组到簇中的技术。主要思想是将相似(即彼此靠近)的实例放入同一个群集中,同时将不相似的点(即彼此远离的点)放在不同的群集中。下图显示了群集的一个示例:

聚类算法遵循两种根本不同的方法。第一种是分层或凝聚方法,首先将每个点视为其自己的聚类,然后迭代地将最相似的聚类合并在一起。当进一步的合并达到预定义的聚类数量时,或者如果要合并的聚类分布在很大的区域上时,它停止。

另一种方法是基于点分配。首先,例如随机地估计初始聚类中心(即质心),然后,将每个点分配给最近的聚类,直到所有的点都被分配。这一组中最著名的算法是 k 均值聚类。

k-均值聚类或者选取初始聚类中心作为彼此尽可能远的点,或者(分层地)对数据样本进行聚类,并选取最接近每个 k-聚类中心的点。

监督学习

监督学习是语音识别、垃圾邮件过滤、照片中的人脸识别以及检测信用卡欺诈等惊人事情背后的关键概念。更正式地说,给定一组用特征 X 描述的学习示例 D,监督学习的目标是找到预测目标变量 Y 的函数。描述特征 X 和类 Y 之间关系的函数 f 称为模型:

监督学习算法的一般结构由以下决定定义(Hand 等人,2001 年):

  1. 定义任务
  2. 决定引入特定归纳偏差的机器学习算法;也就是说,它对目标概念的先验假设
  3. 决定分数或成本函数,例如,信息增益、均方根误差等等
  4. 决定优化/搜索方法以优化得分函数
  5. 找出一个描述 X 和 Y 之间关系的函数

许多决定已经由我们所拥有的任务和数据集的类型为我们做出了。在接下来的几节中,我们将进一步了解分类和回归方法以及相应的得分函数。

分类

当我们处理一个离散类时,可以应用分类,其中的目标是预测目标变量中的一个互斥值。信用评分就是一个例子,它的最终预测是这个人是否有信用责任。最流行的算法包括决策树、朴素贝叶斯分类器、支持向量机、神经网络和集成方法。

决策树学习

决策树学习构建分类树,每个节点对应其中一个属性;边对应于节点源自的属性的可能值(或区间);每片叶子对应一个类别标签。决策树可用于直观和明确地表示预测模型,这使其成为非常透明的(白盒)分类器。值得注意的算法是 ID3 和 C4.5,尽管存在许多替代实现和改进(例如,Weka 中的 J48)。

概率分类器

给定一组属性值,概率分类器能够预测一组类的分布,而不是准确的类。这可以作为一定程度的确定性;也就是说,分类器对其预测有多大把握。最基本的分类器是朴素贝叶斯,当且仅当属性条件独立时,它才是最佳分类器。不幸的是,这在实践中极为罕见。

有一个被称为概率图形模型的巨大子领域,包括数百种算法,例如贝叶斯网络、动态贝叶斯网络、隐马尔可夫模型和条件随机场,它们不仅可以处理属性之间的特定关系,还可以处理时间依赖性。Kiran R Karkera 写了一本关于这个主题的优秀入门书籍,用 Python 构建概率图形模型,Packt 出版社(2014),而柯勒和弗里德曼则出版了一本全面的理论圣经,概率图形模型,麻省理工出版社(2009)。

核心方法

任何线性模型都可以通过对模型应用核技巧(用核函数替换其特征(预测值))而变成非线性模型。换句话说,内核隐式地将我们的数据集转换到更高维度。内核技巧利用了这样一个事实,即在更多维度上分离实例通常更容易。能够对内核进行操作的算法包括内核感知器、支持向量机、高斯过程、PCA、典型相关分析、岭回归、谱聚类、线性自适应滤波器等等。

人工神经网络

人工神经网络受生物神经网络结构的启发,能够进行机器学习和模式识别。它们通常用于回归和分类问题,包括各种算法和各种问题类型的变体。一些流行的分类方法有感知器受限玻尔兹曼机 ( RBM )、深度信念网络

集成学习

集成方法由一组不同的较弱模型组成,以获得更好的预测性能。单独训练各个模型,然后以某种方式将它们的预测组合起来,以进行整体预测。因此,集成包含多种建模数据的方式,这有望带来更好的结果。这是一类非常强大的技术,因此非常受欢迎。这个类包括 boosting、bagging、AdaBoost 和 random forest。他们之间的主要区别是要结合的弱学习者的类型和结合他们的方式。

评估分类

我们的分类器做得好吗?这个比另一个好吗?在分类中,我们会计算我们对某件事进行正确和错误分类的次数。假设有“是”和“否”两种可能的分类标签,则有四种可能的结果,如下表所示:

| | 预测为阳性? |
| | |
| 真的阳性? | | TP-真阳性 | FN-假阴性 |
| | FP-假阳性 | TN-真阴性 |

四个变量:

  • 真肯定 ( 击中):这表示一个 yes 实例被正确预测为 yes
  • 真否定 ( 正确拒绝):这表示没有实例被正确预测为否
  • 误报 ( 误报):表示没有实例预测为是
  • 假阴性 ( 错过):这表示一个是实例被预测为否

分类器的两个基本性能指标是,首先,分类误差:

其次,分类准确性是另一个性能衡量指标,如下所示:

这两种方法的主要问题是它们不能处理不平衡的类。对信用卡交易是否滥用进行分类是不平衡分类问题的一个例子:有 99.99%的正常交易,只有极小比例的滥用。说每笔交易都是正常交易的分类器有 99.99%的准确率,但我们主要对那些很少出现的少数分类感兴趣。

精确度和召回率

解决办法是使用不涉及真正负面的方法。两项此类措施如下:

  • Precision :正确预测为正( TP )的正例占所有预测为正( TP + FP )的正例的比例;

  • 回忆:这是所有正例( TP + FN )中被正确预测为正例( TP )的正例的比例;

通常将两者结合起来并报告 F-measure ,它同时考虑了精确度和召回率,以加权平均值的形式计算得分,其中得分在 1 时达到最佳值,在 0 时达到最差值,如下所示:

Roc 曲线

大多数分类算法返回表示为 f(X) 的分类置信度,该置信度又用于计算预测。以信用卡滥用为例,规则可能如下所示:

阈值决定了错误率和真阳性率。所有可能阈值的结果可以绘制成接收器工作特性 ( ROC ),如下图所示:

随机预测值用红色虚线绘制,理想预测值用绿色虚线绘制。为了比较 A 分类器是否优于 C ,我们比较曲线下的面积。

大多数工具箱都提供了所有现成的先前测量。

回归

回归处理的是连续的目标变量,而分类处理的是离散的目标变量。例如,为了预测未来几天的室外温度,我们将使用回归,而分类将用于预测是否会下雨。一般来说,回归是一个估计特征之间关系的过程,即改变一个特征如何改变目标变量。

线性回归

最基本的回归模型假设特征和目标变量之间的线性相关性。该模型通常使用最小二乘法拟合,即最佳模型使误差的平方最小化。在许多情况下,线性回归不能模拟复杂的关系;例如,下图显示了具有相同线性回归线的四组不同的点。左上角的模型捕捉到了总体趋势,可被视为合适的模型,而左下角的模型拟合点更好(除了一个异常值,应仔细检查),右上角和右下角的线性模型完全错过了数据的基本结构,不能被视为合适的模型:

逻辑回归

当因变量连续时,线性回归有效。然而,如果因变量本质上是二元的,即 0 或 1、成功或失败、是或否、真或假、存活或死亡等等,那么就用逻辑回归来代替。一个这样的例子是药物的临床试验,其中被研究的对象或者对药物有反应或者没有反应。它还用于欺诈检测,即交易是欺诈还是非欺诈。通常,逻辑函数用于衡量因变量和自变量之间的关系。它被视为伯努利分布,当绘制时,看起来类似于字符形状的曲线。

评估回归

在回归中,我们从输入 X 预测数字 Y,而预测通常是错误的或不准确的。我们必须问的主要问题是:增加多少?换句话说,我们想要测量预测值和真实值之间的距离。

均方误差

均方误差 ( MSE )是预测值和真实值之间的平方差的平均值,如下:

该度量对异常值非常敏感,例如,99 个精确预测和 1 个相差 10 的预测与所有相差 1 的预测得分相同。此外,该度量对均值敏感。因此,通常使用相对平方误差来比较我们的预测值的 MSE 和平均预测值(总是预测平均值)的 MSE。

绝对平均误差

平均绝对误差 ( MAS )是预测值和真实值之间绝对差值的平均值,如下所示:

MAS 对异常值不太敏感,但对平均值和标度也很敏感。

相关系数

相关系数 ( CC )比较相对于平均值的预测平均值,乘以相对于平均值的训练值。如果数字为负,说明弱相关;正数表示相关性强;零表示没有相关性。真实值 X 和预测值 Y 之间的相关性定义如下:

CC 测量对平均值和标度完全不敏感,对异常值不太敏感。它能够捕捉相对排序,这使得它对于排序任务非常有用,例如文档相关性和基因表达。

概括和评价

一旦建立了模型,我们如何知道它将在新数据上执行?这个模型好吗?为了回答这些问题,我们将首先研究模型的泛化,然后看看如何获得对新数据的模型性能的估计。

欠拟合和过拟合

预测器训练可能导致模型过于复杂或过于简单。低复杂度的模型(下图中最左边的模型)可以简单到预测最频繁或平均的类值,而高复杂度的模型(最右边的模型)可以表示定型实例。过于僵硬的模式,显示在左手边,不能捕捉复杂的模式;而右侧所示的过于灵活的模型适合训练数据中的噪声。主要的挑战是选择适当的学习算法及其参数,以便学习的模型将在新数据上表现良好(例如,中间的列):

下图显示了定型集中的错误如何随着模型复杂性的增加而减少。简单的刚性模型对数据拟合不足,误差较大。随着模型复杂性的增加,它可以更好地描述训练数据的基础结构,因此,误差会减小。如果模型太复杂,它会过度拟合训练数据,并且其预测误差会再次增加:

根据任务的复杂性和数据的可用性,我们希望将我们的分类器调整到更复杂或更简单的结构。大多数学习算法都允许这样的调整,如下所示:

  • 回归:这是多项式的阶
  • 朴素贝叶斯:这是属性的数量
  • 决策树:这是树中节点的数量——修剪置信度
  • K-最近邻居:这是邻居的数量——基于距离的邻居权重
  • SVM :这是内核类型;成本参数
  • 神经网络:这是神经元和隐含层的数量

通过调整,我们希望最小化泛化误差;也就是说,分类器对未来数据的表现如何。不幸的是,我们永远无法计算真实的泛化误差;但是,我们可以估算一下。然而,如果模型在训练数据上表现良好,但在测试数据上表现差得多,则模型很可能过拟合。

训练和测试集

为了估计泛化误差,我们将数据分成两部分:训练数据和测试数据。一个通用的经验法则是按照培训:测试的比例(即 70:30)将它们分开。我们首先根据训练数据训练预测器,然后预测测试数据的值,最后计算误差,即预测值和真实值之间的差异。这给了我们一个真实的泛化误差的估计。

该估计基于以下两个假设:首先,我们假设测试集是来自数据集的无偏样本;第二,我们假设实际的新数据将重组分布作为我们的训练和测试示例。第一个假设可以通过交叉验证和分层来缓解。此外,如果它是稀缺的,人们不能为一个单独的测试集遗漏大量的数据,因为如果学习算法没有接收到足够的数据,它们就不会执行得很好。在这种情况下,使用交叉验证来代替。

交叉验证

交叉验证将数据集分成大小大致相同的 k 个集合,例如,在下图中,分成五个集合。首先,我们使用集合 2 到 5 进行学习,集合 1 进行训练。然后,我们重复该过程五次,每次留出一组进行测试,并平均五次重复的误差:

这样,我们也可以使用所有的数据进行学习和测试,同时避免使用相同的数据来训练和测试模型。

留一验证

交叉验证的一个极端例子是留一验证。在这种情况下,折叠的数量等于实例的数量;我们在除了一个实例之外的所有实例上学习,然后在省略的实例上测试模型。我们对所有实例重复这一过程,以便每个实例只被用于一次验证。当我们只有有限的一组学习示例时,例如少于 50 个,推荐使用这种方法。

分层

分层是一个选择实例子集的过程,其方式是每个文件夹大致包含相同比例的类值。当一个类是连续的时,选择的折叠使得平均响应值在所有的折叠中近似相等。分层可以与交叉验证或单独的训练和测试集一起应用。

摘要

在这一章中,我们更新了机器学习基础知识。我们回顾了应用机器学习的工作流程,并阐明了主要任务、方法和算法。我们学习了不同类型的回归以及如何评估它们。我们还探索了交叉验证及其应用。

在下一章,我们将学习 Java 库,它们可以执行的任务,以及不同的机器学习平台。

二、用于机器学习的 Java 库和平台

自己实现机器学习算法可能是学习机器学习的最佳方式,但如果你站在巨人的肩膀上,利用现有的开源库之一,你可以进步得更快。

本章回顾了 Java 中用于机器学习的各种库和平台。目标是了解每个库带来了什么,以及它能够解决什么样的问题。

在本章中,我们将讨论以下主题:

  • 实现机器学习应用程序对 Java 的要求
  • Weka,一个通用机器学习平台
  • Java 机器学习库,机器学习算法的集合
  • Apache Mahout,一个可扩展的机器学习平台
  • Apache Spark,一个分布式机器学习库
  • Deeplearning4j,深度学习库
  • MALLET,一个文本挖掘库

我们还将讨论如何通过将这些库与其他组件一起使用,为单机和大数据应用设计完整的机器学习应用堆栈。

对 Java 的需求

新的机器学习算法通常首先在大学实验室编写脚本,将 shell 脚本、Python、R、MATLAB、Scala 或 C++等几种语言粘合在一起,以提供一个新概念,并从理论上分析其属性。一个算法在进入一个具有标准化输入或输出和接口的程序库之前,可能要经历一个漫长的重构过程。虽然 Python、R 和 MATLAB 相当流行,但它们主要用于编写脚本、研究和实验。另一方面,Java 是事实上的企业语言,这可以归功于静态类型、健壮的 IDE 支持、良好的可维护性以及体面的线程模型和高性能并发数据结构库。此外,已经有许多 Java 库可用于机器学习,这使得在现有的 Java 应用程序中应用它们并利用强大的机器学习功能非常方便。

机器学习库

MLOSS.org 网站上列出了超过 70 个基于 Java 的开源机器学习项目,可能还有更多未列出的项目位于大学服务器、GitHub 或 Bitbucket 上。在这一节中,我们将回顾主要的库和平台,它们可以解决的问题类型,它们支持的算法,以及它们可以处理的数据类型。

新西兰黑秧鸡

怀卡托知识分析环境 ( WEKA )是新西兰怀卡托大学开发的机器学习库,可能是最知名的 Java 库。这是一个通用库,能够解决各种各样的机器学习任务,如分类、回归和聚类。它具有丰富的图形用户界面、命令行界面和 Java API。你可以在 http://www.cs.waikato.ac.nz/ml/weka/的查看 Weka。

在写这本书的时候,Weka 总共包含了 267 种算法:数据预处理(82),属性选择(33),分类回归(133),聚类(12),关联规则挖掘(7)。图形界面非常适合探索您的数据,而 Java API 允许您开发新的机器学习方案,并在应用程序中使用这些算法。

Weka 是在 GNU 通用公共许可证 ( GNU GPL )下发布的,这意味着只要你跟踪源文件中的变化并保持在 GNU GPL 下,你就可以复制、发布和修改它。你甚至可以在商业上发布它,但是你必须公开源代码或者获得商业许可。

除了几种受支持的文件格式之外,Weka 还有自己的默认数据格式,即 ARFF,通过属性-数据对来描述数据。它由两部分组成。第一部分包含一个头,它指定了所有属性及其类型,例如,名义、数字、日期和字符串。第二部分包含数据,其中每一行对应一个实例。头中的最后一个属性被隐式地认为是目标变量,缺少的数据用问号标记。例如,回到来自第一章、应用机器学习快速入门的示例,以 ARFF 文件格式编写的Bob实例如下:

@RELATION person_dataset @ATTRIBUTE `Name` STRING @ATTRIBUTE `Height` NUMERIC @ATTRIBUTE `Eye color`{blue, brown, green} @ATTRIBUTE `Hobbies` STRING @DATA 'Bob', 185.0, blue, 'climbing, sky diving' 'Anna', 163.0, brown, 'reading' 'Jane', 168.0, ?, ? 

该文件由三部分组成。第一部分以@RELATION <String>关键字开始,指定数据集名称。下一部分以关键字@ATTRIBUTE开始,后面是属性名和类型。可用的类型有STRINGNUMERICDATE和一组分类值。最后一个属性被隐含地假定为我们想要预测的目标变量。最后一部分以关键字@DATA开始,每行一个实例。实例值由逗号分隔,并且必须遵循与第二部分中的属性相同的顺序。

更多 Weka 示例将在第三章、基本算法——分类、回归和聚类以及第四章、用系综进行客户关系预测中演示。

要了解关于 Weka 的更多信息,请阅读一本快速入门书籍,由卡鲁扎出版社出版的 Weka How-to、开始编写代码,或者查阅由 Witten 和 Frank摩根考夫曼出版社出版的数据挖掘:实用的机器学习工具和 Java 实现技术,了解理论背景和深入的解释。

Weka 的 Java API 被组织成以下顶级包:

  • weka.associations:这些是关联规则学习的数据结构和算法,包括先验预测先验过滤关联器FP-Growth广义序列模式 ( GSP )、热点Tertius
  • 这些是监督学习算法、评估器和数据结构。该包进一步分为以下几个部分:
    • weka.classifiers.bayes:实现了贝叶斯方法,包括朴素贝叶斯、贝叶斯网、贝叶斯逻辑回归等等。
    • weka.classifiers.evaluation:这些是针对名义和数值预测的监督评估算法,比如评估统计、混淆矩阵、ROC 曲线等等。
    • weka.classifiers.functions:这些是回归算法,包括线性回归、保序回归、高斯过程、支持向量机(SVM)、多层感知器、投票感知器等。
    • weka.classifiers.lazy:这些是基于实例的算法,比如 K-最近邻、K*和懒惰贝叶斯规则。
    • weka.classifiers.meta:这些是监督学习元算法,包括 AdaBoost、bagging、加法回归、随机委员会等等。
    • weka.classifiers.mi:这些都是多实例学习算法,比如引文 k 近邻、多样性密度、AdaBoost 等等。
    • 这些是基于分而治之方法、RIPPER、PART、PRISM 等的决策表和决策规则。
    • weka.classifiers.trees:这些是各种决策树算法,包括 ID3,C4.5,M5,功能树,逻辑树,随机森林等等。
    • weka.clusterers:这些是聚类算法,包括 k-means,CLOPE,Cobweb,DBSCAN 层次聚类,FarthestFirst。
    • 这些是各种实用程序类,例如属性类、统计类和实例类。
    • 这些是用于分类、回归和聚类算法的数据生成器。
    • weka.estimators:这些是离散/名义域的各种数据分布估计器,条件概率估计等等。
    • 这些是一组类,支持运行实验所必需的配置、数据集、模型设置和统计。
    • weka.filters:这些是基于属性和基于实例的选择算法,用于监督和非监督数据预处理。
    • 这些是实现资源管理器、实验器和知识流应用程序的图形界面。Weka Explorer 允许您研究数据集、算法及其参数,并使用散点图和其他可视化方式可视化数据集。Weka 实验器用于设计批量实验,但只能用于分类和回归问题。Weka KnowledgeFlow 实现了一个可视化的拖放用户界面来构建数据流,例如,加载数据、应用过滤器、构建分类器以及评估它。

Java 机器学习

Java 机器学习库 ( Java-ML )是一个机器学习算法的集合,具有同类型算法的通用接口。它只以 Java API 为特色,因此主要面向软件工程师和程序员。Java-ML 包含数据预处理、特征选择、分类和聚类的算法。此外,它还具有几个 Weka 桥,可以通过 Java-ML API 直接访问 Weka 的算法。可以从 http://java-ml.sourceforge.net下载。

Java-ML 也是一个通用的机器学习库。与 Weka 相比,它提供了更一致的接口和其他软件包中不存在的最新算法的实现,例如一组广泛的最先进的相似性度量和特征选择技术,例如,动态时间扭曲()、随机森林属性评估等等。Java-ML 在 GNU GPL 许可下也是可用的。

**Java-ML 支持所有类型的文件,只要它们每行包含一个数据样本,并且用逗号、分号或制表符等符号分隔。

该库是围绕以下顶级包组织的:

  • net.sf.javaml.classification:这些是分类算法,包括朴素贝叶斯、随机森林、bagging、自组织地图、k 近邻等等
  • 这些是聚类算法,如 k-means、自组织映射、空间聚类、蛛网、ABC 等
  • 这些是代表实例和数据集的类
  • 这些是测量实例距离和相似性的算法,例如,切比雪夫距离、余弦距离/相似性、欧几里德距离、雅克卡距离/相似性、马哈拉诺比斯距离、曼哈顿距离、闵可夫斯基距离、皮尔逊相关系数、斯皮尔曼尺距、DTW 等等
  • net.sf.javaml.featureselection:这些是用于特征评估、评分、选择和排序的算法,例如,增益率、ReliefF、Kullback-Leibler 散度、对称不确定性等等
  • 这些是通过过滤、删除属性、设置类或属性值等操作实例的方法
  • net.sf.javaml.matrix:这实现了内存或基于文件的数组
  • net.sf.javaml.sampling:这实现了采样算法来选择数据集的子集
  • 这些是关于数据集、实例操作、序列化、Weka API 接口等等的实用方法
  • 这些是算法的实用方法,例如,统计、数学方法、列联表等等** **

阿帕奇看象人

Apache Mahout 项目旨在建立一个可扩展的机器学习库。它使用 MapReduce 范式构建在可扩展的分布式架构(如 Hadoop)之上,MapReduce 范式是一种使用服务器集群通过并行分布式算法处理和生成大型数据集的方法。

Mahout 具有一个控制台界面和 Java API,作为集群、分类和协作过滤的可伸缩算法。它能够解决三个业务问题:

  • 物品推荐:推荐 等物品喜欢这部电影的人也喜欢
  • 聚类:将文本文档分类成与主题相关的文档组
  • 分类:学习将哪个主题分配给未标记的文档

Mahout 是在商业友好的 Apache 许可下发布的,这意味着只要您保留 Apache 许可并在程序的版权声明中显示它,您就可以使用它。

Mahout 具有以下库:

  • org.apache.mahout.cf.taste:这些是基于基于用户和基于项目的协同过滤和基于 ALS 的矩阵分解的协同过滤算法
  • org.apache.mahout.classifier:这些是内存和分布式实现,包括逻辑回归、朴素贝叶斯、随机森林、隐马尔可夫模型 ( HMM )和多层感知器
  • org.apache.mahout.clustering:这些是聚类算法,例如树冠聚类、k-均值、模糊 k-均值、流式 k-均值和谱聚类
  • 这些是算法的实用方法,包括距离、MapReduce 操作、迭代器等等
  • org.apache.mahout.driver:实现了一个通用的驱动程序来运行其他类的 main 方法
  • org.apache.mahout.ep:这是使用记录步骤变异的进化优化
  • 这些是 Hadoop 中的各种数学工具方法和实现
  • 这些是用于数据表示、操作和 MapReduce 作业的类

阿帕奇火花

Apache Spark,或简称 Spark,是一个基于 Hadoop 构建的大规模数据处理平台,但是与 Mahout 不同,它不依赖于 MapReduce 范式。相反,它使用内存缓存来提取一组工作数据,对其进行处理,并重复查询。据报道,这比直接处理存储在磁盘中的数据的 Mahout 实现快 10 倍。可以从spark.apache.org抢。

Spark 上构建了许多模块,例如,GraphX 用于图形处理,Spark Streaming 用于处理实时数据流,MLlib 用于机器学习库,具有分类、回归、协同过滤、聚类、降维和优化功能。

Spark 的 MLlib 可以使用基于 Hadoop 的数据源,例如, Hadoop 分布式文件系统()或 HBase,以及本地文件。支持的数据类型包括:

*** 本地向量存储在一台机器上。密集向量表示为双类型值的数组,例如(2.0,0.0,1.0,0.0),而稀疏向量表示为向量的大小、索引数组和值数组,例如[4,(0,2),(2.0,1.0)]。

  • 标记点用于监督学习算法,由标记有双类型类值的局部向量组成。标签可以是类别索引、二进制结果或多个类别索引的列表(多类别分类)。例如,标记的密集向量表示为[1.0,(2.0,0.0,1.0,0.0)]。
  • 局部矩阵在单台机器上存储一个密集矩阵。它由矩阵维数和以列主顺序排列的单个双数组定义。
  • 分布式矩阵对存储在 Spark 的弹性分布式数据集 ( RDD )中的数据进行操作,该数据集代表了可以并行操作的元素集合。有三种表示法:行矩阵,其中每一行都是可以存储在单台机器上的局部向量,行索引没有意义;索引行矩阵,类似于行矩阵,但行索引是有意义的,即行可以被标识,连接可以被执行;和坐标矩阵,当一行不能存储在单台机器上并且矩阵非常稀疏时使用。

Spark 的 MLlib API 库为各种学习算法和实用程序提供了接口,如下表所示:

  • 这些是二元和多类分类算法,包括线性支持向量机、逻辑回归、决策树和朴素贝叶斯
  • 这些是 k 均值聚类算法
  • 这些是数据表示,包括密集向量、稀疏向量和矩阵
  • org.apache.spark.mllib.optimization:这些是在 MLlib 中用作底层原语的各种优化算法,包括梯度下降、随机梯度下降 ( SGD )、分布式 SGD 的更新方案以及有限内存Broyden–Fletcher–Goldfarb–Shanno(BFGS算法
  • 这些是基于模型的协同过滤技术,通过交替最小二乘矩阵分解来实现
  • 这些是回归学习算法,比如线性最小二乘法、决策树、Lasso 和岭回归
  • org.apache.spark.mllib.stat:这些是稀疏或密集向量格式的样本的统计函数,用于计算平均值、方差、最小值、最大值、计数和非零计数
  • org.apache.spark.mllib.tree:这实现了分类和回归决策树学习算法
  • 这些是用于加载、保存、预处理、生成和验证数据的方法的集合** **

深度学习 4j

Deeplearning4j,或 DL4J,是一个用 Java 编写的深度学习库。它具有分布式以及单机深度学习框架,包括并支持各种神经网络结构,如前馈神经网络、RBM、卷积神经网络、深度信念网络、自动编码器等。DL4J 可以解决不同的问题,例如识别面孔、声音、垃圾邮件或电子商务欺诈。

Deeplearning4j 也在 Apache 2.0 许可下发布,可以从deeplearning4j.org下载。该库的组织结构如下:

  • 这些是加载类
  • 这些是数学实用方法
  • org.deeplearning4j.clustering:这是 k-means 聚类的实现
  • org.deeplearning4j.datasets:这是数据集操作,包括导入、创建、迭代等
  • 这些是分配的实用方法
  • 这些是评估类,包括混淆矩阵
  • org.deeplearning4j.exceptions:实现异常处理程序
  • 这些是监督学习算法,包括深度信念网络、堆叠自动编码器、堆叠去噪自动编码器和 RBM
  • org.deeplearning4j.nn:这些是基于神经网络的组件和算法的实现,比如神经网络、多层网络、卷积多层网络等等
  • org.deeplearning4j.optimize:这些是神经网络优化算法,包括反向传播、多层优化、输出层优化等等
  • 这些是渲染数据的各种方法
  • 这是一个随机数据发生器
  • 这些是助手和实用程序方法

木槌

语言工具包 ( 木槌)是一个大型的自然语言处理算法和实用程序库。它可用于多种任务,如文档分类、文档聚类、信息提取和主题建模。它有一个命令行界面和一个 Java API,用于一些算法,如朴素贝叶斯、HMM、潜在狄利克雷主题模型、逻辑回归和条件随机字段。

MALLET 在通用公共许可证 1.0 下可用,这意味着您甚至可以在商业应用程序中使用它。可以从mallet.cs.umass.edu下载。MALLET 实例由名称、标签、数据和源表示。但是,有两种方法可以将数据导入 MALLET 格式,如下表所示:

  • 每个文件的实例:每个文件或文档对应一个实例,MALLET 接受输入的目录名。
  • 每行一个实例:每行对应一个实例,假设格式如下——instance_name标签令牌。数据将是一个特征向量,由作为标记出现的不同单词及其出现次数组成。

该库由以下软件包组成:

  • cc.mallet.classify:这些是用于训练和分类实例的算法,包括 AdaBoost、bagging、C4.5,以及其他决策树模型、多元逻辑回归、朴素贝叶斯和 Winnow2。
  • cc.mallet.cluster:这些是无监督聚类算法,包括贪婪凝聚、爬山、k-best、k-means 聚类。
  • 这个实现了记号赋予器、文档提取器、文档查看器、清理器等等。
  • cc.mallet.fst:这个实现了序列模型,包括条件随机场,HMM,最大熵马尔可夫模型,以及相应的算法和评估器。
  • cc.mallet.grmm:这实现了图形模型和因子图,比如推理算法、学习和测试,例如,loopy 信念传播、Gibbs 抽样等等。
  • cc.mallet.optimize:这些是寻找函数最大值的优化算法,比如梯度上升、有限记忆 BFGS、随机元上升等等。
  • 这些方法是将数据处理成 MALLET 实例的管道。
  • 这些是主题建模算法,比如潜在狄利克雷分配,四级弹球分配,分级 PAM,DMRT 等等。
  • cc.mallet.types:实现数据集、特征向量、实例、标签等基本数据类型。
  • 这些是各种各样的实用函数,比如命令行处理、搜索、数学、测试等等。

Encog 机器学习框架

Encog 是由数据科学家杰夫·希顿开发的 Java/C#机器学习框架。它支持标准化和处理数据以及各种高级算法,如 SVM、神经网络、贝叶斯网络、隐马尔可夫模型、遗传编程和遗传算法。从 2008 年开始积极开发。它支持多线程,可提升多核系统的性能。

可以在www.heatonresearch.com/encog/找到。MLMethod 是基本接口,它包括模型的所有方法。以下是它包含的一些接口和类:

  • MLRegression:该接口定义回归算法
  • MLClassification:该接口定义了分类算法
  • MLClustering:该接口定义了聚类算法
  • 这个类代表了一个模型中使用的向量,用于输入或输出
  • MLDataPair:该类的功能与MLData类似,但可用于输入和输出
  • MLDataSet:表示培训师的MLDataPair实例列表
  • 这个类被用作一个神经元
  • FreeformConnection:显示神经元之间的加权连接
  • FreeformContextNeuron:这代表一个上下文神经元
  • InputSummation:该值指定如何对输入求和以形成单个神经元
  • 这是所有输入神经元的简单总和
  • BasicFreeConnection:这是神经元之间的基本加权连接
  • BasicFreeformLayer:该界面提供了一个图层

埃尔基

ELKI 为开发由索引结构支持的 KDD 应用程序创建了一个环境,重点是无监督学习。它为聚类分析和离群点检测提供了各种实现。它提供了诸如 R*树的索引结构来提高性能和可伸缩性。到目前为止,它已被学生和教师广泛应用于研究领域,最近也引起了其他方面的关注。

ELKI 使用 AGPLv3 许可证,可以在elki-project.github.io/找到。它由以下软件包组成:

  • de.lmu.ifi.dbs.elki.algorithm:包含聚类、分类、项目集挖掘等各种算法
  • de.lmu.ifi.dbs.elki.outlier:定义基于异常值的算法
  • de.lmu.ifi.dbs.elki.statistics:定义统计分析算法
  • 这是 ELKI 数据库层
  • de.lmu.ifi.dbs.elki.index:这是为了实现索引结构
  • de.lmu.ifi.dbs.elki.data:定义各种数据类型和数据库对象类型

恐鸟

大规模在线分析 ( MOA )包含大量各种机器学习算法,包括分类、回归、聚类、离群点检测、概念漂移检测和推荐系统的算法,以及评估工具。所有算法都是为大规模机器学习而设计的,有漂移的概念,处理实时数据流。它还可以很好地与 Weka 一起工作和集成。

它以 GNU 许可证的形式提供,可以从 https://moa.cms.waikato.ac.nz/下载。以下是它的主要软件包:

  • moa.classifiers:包含分类的算法
  • moa.clusters:包含聚类的算法
  • moa.streams:包含与流相关的类
  • moa.evaluation:用于评估

比较库

下表总结了所有提供的库。该表绝不是详尽的,还有更多涵盖特定问题领域的库。这篇综述应该作为 Java 机器学习世界中的大人物的概述:

| 图书馆 | 问题域 | 执照 | 架构 | 算法 |
| 新西兰黑秧鸡 | 通用 | GNU GPL | 单机 | 决策树、朴素贝叶斯、神经网络、随机森林、AdaBoost、层次聚类等等 |
| Java-ML | 通用 | GNU GPL | 单机 | K-means 聚类、自组织映射、马尔可夫链聚类、蛛网、随机森林、决策树、bagging、距离度量等等 |
| 象夫 | 分类、推荐和聚类 | Apache 2.0 许可证 | 分布式单机 | 逻辑回归、朴素贝叶斯、随机森林、HMM、多层感知器、k 均值聚类等等 |
| 火花 | 通用 | Apache 2.0 许可证 | 分布的 | SVM、逻辑回归、决策树、朴素贝叶斯、k 均值聚类、线性最小二乘法、Lasso、岭回归等等 |
| DL4J | 深度学习 | Apache 2.0 许可证 | 分布式单机 | RBM、深度信念网络、深度自动编码器、递归神经张量网络、卷积神经网络和堆叠去噪自动编码器 |
| 木槌 | 文本挖掘 | 通用公共许可证 1.0 | 单机 | 朴素贝叶斯、决策树、最大熵、HMM 和条件随机场 |
| Encog | 机器学习框架 | Apache 2.0 许可证 | 跨平台 | SVM、神经网络、贝叶斯网络、hmm、遗传编程和遗传算法 |
| 埃尔基 | 数据挖掘 | AGPL | 分布式单机 | 聚类检测、异常检测、评估、索引 |
| 恐鸟 | 机器学习 | GNU GPL | 分布式单机 | 分类、回归、聚类、离群点检测、推荐系统、频繁模式挖掘 |

构建机器学习应用程序

机器学习应用程序,尤其是那些专注于分类的应用程序,通常遵循下图所示的相同高级工作流程。工作流由两个阶段组成—训练分类器和新实例的分类。这两个阶段有共同的步骤,如下所示:

首先,我们使用一组训练数据,选择一个有代表性的子集作为训练集,对缺失数据进行预处理,并提取其特征。使用选定的监督学习算法来训练模型,在第二阶段部署该模型。第二阶段使新的数据实例通过相同的预处理和特征提取过程,并应用所学习的模型来获得实例标签。如果您能够收集新的标记数据,请定期重新运行学习阶段来重新训练模型,并在分类阶段用重新训练的模型替换旧的模型。

传统机器学习架构

结构化数据,如交易、客户、分析和市场数据,通常驻留在本地关系数据库中。给定一种查询语言,如 SQL,我们可以查询用于处理的数据,如上图中的工作流所示。通常,所有的数据都可以存储在内存中,并用机器学习库如 Weka、Java-ML 或 MALLET 进行进一步处理。

架构设计中的一个常见做法是创建数据管道,其中工作流中的不同步骤被拆分。例如,为了创建一个客户数据记录,我们可能必须从不同的数据源中删除数据。然后,可以将记录保存在中间数据库中,以供进一步处理。

为了了解大数据架构的高级别方面有何不同,我们先来澄清一下数据何时被视为大数据。

处理大数据

大数据早在这个词被发明之前就存在了。例如,多年来,银行和证券交易所每天处理数十亿笔交易,航空公司拥有全球实时基础设施,用于乘客预订的运营管理,等等。那么,大数据到底是什么?Doug Laney (2001)提出,大数据由三个 v 定义:数量、速度和多样性。因此,要回答你的数据是否庞大的问题,我们可以将此转化为以下三个子问题:

  • :你能把你的数据存在内存里吗?
  • 速度:你能用一台机器处理新的输入数据吗?
  • 多样性:你的数据来源单一吗?

如果您对所有这些问题的回答都是肯定的,那么您的数据可能并不大,您只是简化了您的应用程序架构。

如果你对所有这些问题的答案都是否定的,那么你的数据是巨大的!然而,如果你有混合的答案,那就复杂了。有人可能会说一个 V 很重要;其他人可能会说其他 v 更重要。从机器学习的角度来看,为了处理内存中的数据或来自分布式存储的数据,在算法实现上有根本的区别。因此,一个经验法则是:如果您无法将数据存储在内存中,那么您应该查看大数据机器学习库。

确切的答案取决于你试图解决的问题。如果你正在开始一个新的项目,我建议你从一个单机库和你的算法原型开始,如果整个数据不适合内存,可能用你的数据的一个子集。一旦你建立了良好的初步结果,考虑转移到一些更重的任务,如驯象或火花。

大数据应用架构

文档、博客、社交网络、传感器数据等大数据存储在 NoSQL 数据库(如 MongoDB)或分布式文件系统(如 HDFS)中。如果我们处理结构化数据,我们可以使用构建在 Hadoop 之上的 Cassandra 或 HBase 等系统来部署数据库功能。数据处理遵循 MapReduce 范式,该范式将数据处理问题分解为更小的子问题,并将任务分布在处理节点上。机器学习模型最后用 Mahout、Spark 等机器学习库进行训练。

MongoDB is a NoSQL database, which stores documents in a JSON-like format. You can read more about it at www.mongodb.org. Hadoop is a framework for the distributed processing of large datasets across a cluster of computers. It includes its own filesystem format, HDFS, job scheduling framework, YARD, and implements the MapReduce approach for parallel data processing. We can learn more about Hadoop at hadoop.apache.org/. Cassandra is a distributed database management system that was built to provide fault-tolerant, scalable, and decentralized storage. More information is available at cassandra.apache.org/. HBase is another database that focuses on random read/write access for distributed storage. More information is available at hbase.apache.org/.

摘要

选择机器学习库对你的应用架构有重要影响。关键是要考虑你的项目需求。你有什么样的数据?你想解决什么样的问题?你的数据大吗?您需要分布式存储吗?你打算用什么样的算法?一旦你发现你需要什么来解决你的问题,选择一个最适合你需要的库。

在下一章,我们将介绍如何通过使用一些提供的库来完成基本的机器学习任务,如分类、回归和聚类。****

三、基本算法——分类、回归和聚类

在前一章中,我们回顾了用于机器学习的关键 Java 库以及它们带来了什么。在这一章中,我们将最终弄脏我们的手。我们将进一步了解基本的机器学习任务,如分类、回归和聚类。每个主题将介绍分类、回归和聚类的基本算法。示例数据集将会很小、简单且易于理解。

本章将涵盖以下主题:

  • 加载数据
  • 过滤属性
  • 构建分类、回归和聚类模型
  • 评估模型

开始之前

在开始之前,从www.cs.waikato.ac.nz/ml/weka/downloading.html下载 Weka 的最新稳定版本(在撰写本文时为 Weka 3.8)。

有多种下载选项可供选择。您将希望在源代码中使用 Weka 作为库,因此请确保跳过自解压可执行文件并下载 ZIP 存档,如下面的屏幕截图所示。解压缩归档文件,并在解压缩的归档文件中找到weka.jar:

我们将使用 Eclipse IDE 来展示例子;请遵循以下步骤:

  1. 开始一个新的 Java 项目。
  2. 右键单击项目属性,选择 Java 构建路径,单击库选项卡,然后选择添加外部 jar。
  3. 导航至提取 Weka 档案并选择weka.jar文件。

就是这样;我们已经准备好实现基本的机器学习技术了!

分类

我们将从最常用的机器学习技术开始:分类。正如我们在第一章中回顾的,主要思想是自动建立输入变量和结果之间的映射。在接下来的小节中,我们将看看如何加载数据、选择特性、在 Weka 中实现一个基本的分类器,并评估它的性能。

数据

对于这个任务,我们将看一下ZOO数据库。该数据库包含用 18 个属性描述的 101 个动物数据条目,如下表所示:

| 动物 | 水生的 | 散热片 |
| 头发 | 食肉动物 | 腿 |
| 羽毛 | 锯齿状的 | 尾巴 |
| 蛋 | 毅力 | 国内的 |
| 牛奶 | 呼吸 | 猫的大小 |
| 空运的 | 有毒的 | 类型 |

数据集中的一个示例条目是一只狮子,具有以下属性:

  • animal:狮子
  • hair:真
  • feathers:假
  • eggs:假
  • milk:真
  • airborne:假
  • aquatic:假
  • predator:真
  • toothed:真
  • backbone:真
  • breathes:真
  • venomous:假
  • fins:假
  • legs : 4
  • tail:真
  • domestic:假
  • catsize:真
  • type:哺乳动物

我们的任务是建立一个模型来预测结果变量animal,给定所有其他属性作为输入。

加载数据

在开始分析之前,我们将加载 Weka 的属性关系文件格式 ( ARFF )的数据,并打印加载实例的总数。每个数据样本保存在一个DataSource对象中,而完整的数据集,伴随着元信息,由Instances对象处理。

为了加载输入数据,我们将使用接受各种文件格式的DataSource对象,并将它们转换成Instances:

DataSource source = new DataSource("data/zoo.arff"); 
Instances data = source.getDataSet(); 
System.out.println(data.numInstances() + " instances loaded."); 
System.out.println(data.toString()); 

这将提供已加载实例的数量作为输出,如下所示:

101 instances loaded.

我们还可以通过调用data.toString()方法来打印完整的数据集。

我们的任务是学习一个模型,它能够在我们知道其他属性,但不知道animal标签的未来示例中预测animal属性。因此,我们将从训练集中删除animal属性。我们将通过使用Remove()过滤器过滤出动物属性来实现这一点。

首先,我们设置一个参数字符串表,指定必须删除第一个属性。剩余的属性用作训练分类器的数据集:

Remove remove = new Remove(); 
String[] opts = new String[]{ "-R", "1"}; 

最后,我们调用Filter.useFilter(Instances, Filter)静态方法对所选数据集应用过滤器:

remove.setOptions(opts); 
remove.setInputFormat(data); 
data = Filter.useFilter(data, remove); 
System.out.println(data.toString()); 

特征选择

如第一章、应用机器学习快速入门中介绍的,预处理步骤之一集中在特征选择,也称为属性选择。目标是选择将在学习模型中使用的相关属性子集。为什么特征选择很重要?一组较小的属性简化了模型,使用户更容易理解。这通常需要较短的训练,并减少过度拟合。

属性选择可以考虑类值,也可以不考虑。在第一种情况下,属性选择算法评估不同的特征子集,并计算表示所选属性质量的分数。我们可以使用不同的搜索算法,如穷举搜索和最佳优先搜索,以及不同的质量分数,如信息增益、基尼指数等。

Weka 用一个AttributeSelection对象支持这个过程,它需要两个额外的参数:一个计算属性信息量的赋值器,和一个根据赋值器分配的分数对属性进行排序的排序器。

我们将使用以下步骤来执行选择:

  1. 在本例中,我们将使用信息增益作为评估器,并根据信息增益得分对要素进行排名:
InfoGainAttributeEval eval = new InfoGainAttributeEval(); 
Ranker search = new Ranker(); 
  1. 我们将初始化一个AttributeSelection对象,并设置赋值器、排序器和数据:
AttributeSelection attSelect = new AttributeSelection(); 
attSelect.setEvaluator(eval); 
attSelect.setSearch(search); 
attSelect.SelectAttributes(data); 
  1. 我们将打印属性indices的订单列表,如下所示:
int[] indices = attSelect.selectedAttributes(); 
System.out.println(Utils.arrayToString(indices)); 

该过程将提供以下结果作为输出:

12,3,7,2,0,1,8,9,13,4,11,5,15,10,6,14,16 

最能提供信息的属性是12(鳍)3(蛋)7(水生)2(毛发),等等。基于该列表,我们可以移除额外的、非信息性的特征,以便帮助学习算法实现更准确和更快速的学习模型。

什么会最终决定要保留多少属性?没有与确切数字相关的经验法则;属性的数量取决于数据和问题。属性选择的目的是选择更好地服务于你的模型的属性,所以最好把重点放在属性是否在改善模型上。

学习算法

我们已经加载了数据并选择了最佳特征,我们准备学习一些分类模型。让我们从基本的决策树开始。

在 Weka 中,决策树是在J48类中实现的,这是 Quinlan 著名的 C4.5 决策树学习器(Quinlan,1993)的重新实现。

我们将通过以下步骤制作一个决策树:

  1. 我们初始化一个新的J48决策树学习器。我们可以用字符串表传递额外的参数——例如,控制模型复杂性的树修剪(参见第一章、应用机器学习快速入门)。在我们的例子中,我们将构建一棵未修剪的树;因此,我们将传递单个-U参数,如下所示:
J48 tree = new J48(); 
String[] options = new String[1]; 
options[0] = "-U"; 

tree.setOptions(options); 
  1. 我们将调用buildClassifier(Instances)方法来初始化学习过程:
tree.buildClassifier(data); 
  1. 构建的模型现在存储在一个tree对象中。我们可以通过调用toString()方法来提供整个J48未修剪的树:
System.out.println(tree); 

输出如下所示:

    J48 unpruned tree
    ------------------

    feathers = false
    |   milk = false
    |   |   backbone = false
    |   |   |   airborne = false
    |   |   |   |   predator = false
    |   |   |   |   |   legs <= 2: invertebrate (2.0)
    |   |   |   |   |   legs > 2: insect (2.0)
    |   |   |   |   predator = true: invertebrate (8.0)
    |   |   |   airborne = true: insect (6.0)
    |   |   backbone = true
    |   |   |   fins = false
    |   |   |   |   tail = false: amphibian (3.0)
    |   |   |   |   tail = true: reptile (6.0/1.0)
    |   |   |   fins = true: fish (13.0)
    |   milk = true: mammal (41.0)
    feathers = true: bird (20.0)

    Number of Leaves  : 9

    Size of the tree : 17

输出中的树总共有17个节点,其中9个是终端(Leaves)。

展示树的另一种方式是利用内置的TreeVisualizer树查看器,如下所示:

TreeVisualizer tv = new TreeVisualizer(null, tree.graph(), new PlaceNode2()); 
JFrame frame = new javax.swing.JFrame("Tree Visualizer"); 
frame.setSize(800, 500); 
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
frame.getContentPane().add(tv); 
frame.setVisible(true); 
tv.fitToScreen(); 

上述代码会产生以下输出帧:

决策过程从顶层节点开始,也称为根节点。节点标签指定将被检查的属性值。在我们的例子中,首先,我们检查feathers属性的值。如果羽毛存在,我们跟随右边的分支,这将我们带到标有bird的叶子,表明有20的例子支持这个结果。如果羽毛不存在,我们沿着左边的分支,这将我们带到milk属性。我们再次检查属性值,然后沿着与属性值匹配的分支前进。我们重复这个过程,直到到达一个叶节点。

我们可以按照相同的步骤构建其他分类器:初始化一个分类器,传递控制模型复杂性的参数,并调用buildClassifier(Instances)方法。

在下一节中,您将学习如何使用训练好的模型为标签未知的新示例分配一个类标签。

分类新数据

假设我们记录一种动物的属性,但我们不知道它的标签;我们可以从学习到的分类模型中预测它的标签。我们将在此过程中使用以下动物:

首先,我们构建描述新样本的特征向量,如下所示:

double[] vals = new double[data.numAttributes()]; 
vals[0] = 1.0; //hair {false, true} 
vals[1] = 0.0;  //feathers {false, true} 
vals[2] = 0.0;  //eggs {false, true} 
vals[3] = 1.0;  //milk {false, true} 
vals[4] = 0.0;  //airborne {false, true} 
vals[5] = 0.0;  //aquatic {false, true} 
vals[6] = 0.0;  //predator {false, true} 
vals[7] = 1.0;  //toothed {false, true} 
vals[8] = 1.0;  //backbone {false, true} 
vals[9] = 1.0;  //breathes {false, true} 
vals[10] = 1.0;  //venomous {false, true} 
vals[11] = 0.0;  //fins {false, true} 
vals[12] = 4.0;  //legs INTEGER [0,9] 
vals[13] = 1.0;  //tail {false, true} 
vals[14] = 1.0;  //domestic {false, true} 
vals[15] = 0.0;  //catsize {false, true} 
DenseInstance myUnicorn = new DenseInstance(1.0, vals);
myUnicorn.setDataset(data); 

然后,我们在模型上调用classify(Instance)方法,以获取类值。方法返回标签索引,如下所示:

double result = tree.classifyInstance(myUnicorn); 
System.out.println(data.classAttribute().value((int) result)); 

这将提供mammal类标签作为输出。

评估和预测误差度量

我们建立了一个模型,但我们不知道它是否可信。为了评估它的性能,我们可以应用一种交叉验证技术,这在第一章、应用机器学习快速入门中有所解释。

Weka 提供了一个Evaluation类来实现交叉验证。我们传递模型、数据、折叠数和初始随机种子,如下所示:

Classifier cl = new J48(); 
Evaluation eval_roc = new Evaluation(data); 
eval_roc.crossValidateModel(cl, data, 10, new Random(1), new Object[] {}); 
System.out.println(eval_roc.toSummaryString()); 

评估结果存储在Evaluation对象中。

通过调用toString()方法,可以调用最常见指标的组合。请注意,输出不会区分回归和分类,因此请务必注意有意义的指标,如下所示:

    Correctly Classified Instances          93               92.0792 %
    Incorrectly Classified Instances         8                7.9208 %
    Kappa statistic                          0.8955
    Mean absolute error                      0.0225
    Root mean squared error                  0.14  
    Relative absolute error                 10.2478 %
    Root relative squared error             42.4398 %
    Coverage of cases (0.95 level)          96.0396 %
    Mean rel. region size (0.95 level)      15.4173 %
    Total Number of Instances              101  

在分类中,我们感兴趣的是正确/错误分类的实例的数量。

混乱矩阵

此外,我们可以通过检查混淆矩阵来检查特定的错误分类发生在哪里。混淆矩阵显示了特定类别值是如何预测的:

double[][] confusionMatrix = eval_roc.confusionMatrix(); 
System.out.println(eval_roc.toMatrixString()); 

由此产生的混淆矩阵如下:

    === Confusion Matrix ===

      a  b  c  d  e  f  g   <-- classified as
     41  0  0  0  0  0  0 |  a = mammal
      0 20  0  0  0  0  0 |  b = bird
      0  0  3  1  0  1  0 |  c = reptile
      0  0  0 13  0  0  0 |  d = fish
      0  0  1  0  3  0  0 |  e = amphibian
      0  0  0  0  0  5  3 |  f = insect
      0  0  0  0  0  2  8 |  g = invertebrate

第一行中的列名对应于由分类节点分配的标签。然后,每个额外的行对应于一个实际的真实类值。例如,第二行对应于带有mammal真实类标签的实例。在栏行中,我们读到所有的哺乳动物都被正确地归类为哺乳动物。在第四行reptiles中,我们注意到三个被正确分类为reptiles,而一个被分类为fish,一个被分类为insect。混淆矩阵让我们深入了解我们的分类模型可能产生的各种错误。

选择分类算法

朴素贝叶斯是机器学习中最简单、高效和有效的归纳算法之一。当特征是独立的时,这在现实世界中很少是真实的,它在理论上是最优的,即使具有依赖的特征,它的性能也是惊人地有竞争力的(张,2004)。主要的缺点是它不能学习特性之间是如何相互作用的;例如,尽管你喜欢加柠檬或牛奶的茶,但你讨厌同时加柠檬或牛奶的茶。

决策树的主要优势在于它是一个易于解释和说明的模型,正如我们在示例中所研究的那样。它既可以处理名义特征,也可以处理数值特征,你不用担心数据是否是线性可分的。

分类算法的一些其他示例如下:

  • weka.classifiers.rules.ZeroR:这预测了多数类,并被视为基线;也就是说,如果您的分类器的性能比平均值预测器差,就不值得考虑它。
  • weka.classifiers.trees.RandomTree:这构建了一个树,考虑了在每个节点随机选择的 K 属性。
  • 这构建了一个随机树的集合(森林),并使用多数投票来分类一个新的实例。
  • weka.classifiers.lazy.IBk:这是 k-最近邻分类器,能够基于交叉验证选择适当的邻居值。
  • 这是一个基于神经网络的分类器,使用反向传播对实例进行分类。网络可以手工构建,或通过算法创建,或两者兼而有之。
  • weka.classifiers.bayes.NaiveBayes:这是一个朴素贝叶斯分类器,它使用估计器类,其中基于对训练数据的分析来选择数字估计器精度值。
  • weka.classifiers.meta.AdaBoostM1:这是使用AdaBoost M1方法提升一个名义类分类器的类。只能解决名义上的阶级问题。这通常会极大地提高性能,但有时会适得其反。
  • weka.classifiers.meta.Bagging:该类用于打包分类器以减少方差。这可以根据基础学习者执行分类和回归。

使用 Encog 分类

在上一节中,您看到了如何使用 Weka 库进行分类。在这一节中,我们将快速了解如何通过使用 Encog 库实现同样的功能。Encog 要求我们建立一个模型来做分类。从 https://github.com/encog/encog-java-core/releases 下载 Encog 库。下载完成后,在 Eclipse 项目中添加.jar文件,如本章开头所述。

对于这个例子,我们将使用iris数据集,它以.csv格式提供;可以从archive.ics.uci.edu/ml/datasets/Iris下载。从下载路径,复制iris.data.csv文件到你的数据目录。这个文件包含了 150 种不同花的数据。它包含了花的四个不同的尺寸,最后一列是标签。

我们现在将使用以下步骤执行分类:

  1. 我们将使用VersatileMLDataSet方法加载文件并定义所有四列。下一步是调用analyze方法,该方法将读取整个文件并找到统计参数,比如平均值、标准偏差等等:
File irisFile = new File("data/iris.data.csv");
VersatileDataSource source = new CSVDataSource(irisFile, false, CSVFormat.DECIMAL_POINT);

VersatileMLDataSet data = new VersatileMLDataSet(source); 
data.defineSourceColumn("sepal-length", 0, ColumnType.continuous); 
data.defineSourceColumn("sepal-width", 1, ColumnType.continuous); 
data.defineSourceColumn("petal-length", 2, ColumnType.continuous); 
data.defineSourceColumn("petal-width", 3, ColumnType.continuous); 

ColumnDefinition outputColumn = data.defineSourceColumn("species", 4, ColumnType.nominal);
data.analyze(); 
  1. 下一步是定义输出列。然后,是时候对数据进行归一化处理了;但在此之前,我们需要决定数据将被规范化的模型类型,如下所示:
data.defineSingleOutputOthersInput(outputColumn); 

EncogModel model = new EncogModel(data); 
model.selectMethod(data, MLMethodFactory.TYPE_FEEDFORWARD);

model.setReport(new ConsoleStatusReportable()); 
data.normalize(); 
  1. 下一步是在训练集上拟合模型,留出测试集。我们将持有 30%的数据,如第一个参数0.3所指定的;下一个参数指定我们想要随机地将数据放入。1001表示有一个种子值 1001,所以我们使用一个holdBackValidation模型:
model.holdBackValidation(0.3, true, 1001);
  1. 现在,是时候根据测量值和标签来训练模型并对数据进行分类了。交叉验证将训练数据集分成五种不同的组合:
model.selectTrainingType(data); 
MLRegression bestMethod = (MLRegression)model.crossvalidate(5, true); 
  1. 下一步是显示每次折叠的结果和错误:
System.out.println( "Training error: " + EncogUtility.calculateRegressionError(bestMethod, model.getTrainingDataset())); 
System.out.println( "Validation error: " + EncogUtility.calculateRegressionError(bestMethod, model.getValidationDataset())); 
  1. 现在,我们将开始使用模型来预测值,使用以下代码块:
while(csv.next()) { 
            StringBuilder result = new StringBuilder(); 
            line[0] = csv.get(0); 
            line[1] = csv.get(1); 
            line[2] = csv.get(2); 
            line[3] = csv.get(3); 
            String correct = csv.get(4); 
            helper.normalizeInputVector(line,input.getData(),false); 
            MLData output = bestMethod.compute(input); 
            String irisChosen = helper.denormalizeOutputVectorToString(output)[0]; 

            result.append(Arrays.toString(line)); 
            result.append(" -> predicted: "); 
            result.append(irisChosen); 
            result.append("(correct: "); 
            result.append(correct); 
            result.append(")"); 

            System.out.println(result.toString()); 
        } 

这将产生类似如下的输出:

Encog 在MLMethodFactory中支持许多其他选项,比如 SVM、PNN 等等。

使用大量在线分析进行分类

海量在线分析 ( MOA ),如第二章、用于机器学习的 Java 库和平台所讨论的,是另一个可以用来实现分类的库。它主要被设计成与流一起工作。如果它与流一起工作,大量的数据将会在那里;那么,我们如何评价这个模型呢?在传统的批量学习模式中,我们通常将数据分为训练集和测试集,如果数据有限,则首选交叉验证。在流处理中,数据似乎是无限的,交叉验证被证明是昂贵的。我们可以使用的两种方法如下:

  • Holdout :当数据已经被分为两部分时,这是很有用的,这是预先定义的。它给出当前分类器的估计,如果它与当前数据相似的话。很难保证维持集和当前数据之间的相似性。
  • 先测试后训练,或先训练后训练:在这种方法中,模型在用于训练之前,先在示例上进行测试。因此,模型总是被测试它从未见过的数据。在这种情况下,不需要维持方案。它使用可用的数据。随着时间的推移,这种方法将提高分类的准确性。

MOA 提供了多种生成数据流的方法。首先,从 https://moa.cms.waikato.ac.nz/downloads/的下载恐鸟库。将下载的.jar文件添加到 Eclipse,就像我们在本章开始时为 Weka 所做的那样。我们将使用 MOA 提供的 GUI 工具来了解如何使用 MOA 处理流。要启动 GUI,确保moa.jarsizeofag.jar在当前路径中;然后,在命令提示符下运行以下命令:

$ java -cp moa.jar -javaagent:sizeofag.jar moa.gui.GUI

它将显示以下输出:

我们可以看到它有分类、回归、聚类、异常值等选项。单击配置按钮将显示用于制作分类器的屏幕。它提供了各种学习者和流,如下面的屏幕截图所示:

下面是用NaiveBayesHoeffdingTree运行RandomTreeGenerator的例子:

估价

在开发出模型之后,评估是下一个重要的任务。它让您决定模型是否在给定的数据集上运行良好,并确保它能够处理它从未见过的数据。评估框架主要使用以下功能:

  • 误差估计:使用维持或交错测试训练方法来估计误差。还使用了 k 倍交叉验证。
  • 性能测量:使用 Kappa 统计量,它对流式分类器更敏感。
  • 统计验证:在比较评估分类器时,一定要看随机和非随机实验的差异。McNemar 检验是流中最流行的检验,用于评估两个分类器之间差异的统计显著性。如果我们使用一个分类器,参数估计的置信区间表示可靠性。
  • 流程的成本衡量:由于我们正在处理流数据,这可能需要访问第三方或基于云的解决方案来获取和处理数据,因此每小时的使用和内存成本将被视为评估目的。

基线分类

批量学习已经导致了许多不同范式的分类器的发展,例如分而治之、懒惰学习器、内核方法、图形模型等等。现在,如果我们转移到流中,我们需要了解如何使它们对于流中的大型数据集是增量的和快速的。我们必须考虑模型的复杂性和模型更新的速度,这是需要考虑的主要权衡因素。

多数类算法是最简单的分类器之一,它被用作基线。它也用作决策树叶子的默认分类器。另一个是 - 变化分类器,它预测新实例的标签。朴素贝叶斯算法以其在计算能力和简单性方面的低成本而闻名。这是一个增量算法,最适合流。

决策图表

决策树是一种非常流行的分类器技术,使得解释和可视化模型变得容易。它以树木为基础。它基于属性值划分或分割节点,并且树的叶子通常落在多数类分类器上。在流数据中,Hoeffding 树是一种非常快速的决策树算法;它等待新的实例,而不是重用实例。它为大数据建立了一棵树。概念-适应非常快的决策树 ( CVFDT )处理漂移的概念,在滑动窗口中保持模型与实例的一致性。其他的树是超快速森林树 ( UFFT )、Hoeffding 自适应树、穷举二叉树等等。

懒惰的学习

在流式上下文中, k 近邻()是最方便的批处理方法。滑动窗口用于确定尚未分类的新实例的 KNN。它通常使用滑动窗口的 1000 个最新实例。当滑动窗口滑动时,它也处理概念漂移。

**

主动学习

我们都知道分类器对带标签的数据工作得很好,但是对于流数据并不总是这样。例如,来自流的数据可能是未标记的。标记数据的成本很高,因为它需要人工干预来标记未标记的数据。我们知道数据流会产生大量数据。主动学习算法只对选择性数据进行标记。要标记的数据由适合基于池的设置的历史数据决定。需要定期进行再培训,以决定传入实例是否需要标签。标记数据的一个简单策略是使用随机策略。它也被称为基线策略,它要求为每个传入的实例添加一个标签,并给出标签预算的概率。另一个策略是为当前分类器最不确定的实例请求一个标签。这可能工作得很好,但很快,分类器将耗尽其预算或达到其阈值。

回归

我们将通过对能效数据集的分析来探索基本的回归算法(Tsanas 和 Xifara,2012)。我们将根据建筑的构造特征(如表面、墙壁和屋顶面积)调查建筑的热负荷和冷负荷要求;身高;玻璃区域;和紧凑性。研究人员使用模拟器设计了 12 种不同的房屋结构,同时改变了 18 种建筑特征。总共模拟了 768 座不同的建筑。

我们的第一个目标是系统地分析每个建筑特征对目标变量(即热负荷或冷负荷)的影响。第二个目标是比较经典线性回归模型与其他方法(如 SVM 回归、随机森林和神经网络)的性能。对于这个任务,我们将使用 Weka 库。

加载数据

archive.ics.uci.edu/ml/datasets/Energy+efficiency下载能效数据集。

数据集是 Excel 的 XLSX 格式,Weka 无法读取。我们可以通过点击文件|另存为,在保存对话框中勾选.csv,将其转换为逗号分隔值 ( CSV )格式,如下图截图所示。确认仅保存活动工作表(因为所有其他工作表都是空的),确认继续,将丢失一些格式设置功能。现在,该文件已准备好由 Weka 加载:

在文本编辑器中打开文件,检查文件是否被正确转换。可能有些小问题会引起问题。例如,在我的导出中,每一行都以双分号结束,如下所示:

X1;X2;X3;X4;X5;X6;X7;X8;Y1;Y2;; 
0,98;514,50;294,00;110,25;7,00;2;0,00;0;15,55;21,33;; 
0,98;514,50;294,00;110,25;7,00;3;0,00;0;15,55;21,33;; 

要删除重复的分号,可以使用查找和替换功能:查找;;并用;替换它。

第二个问题是我的文件在文档末尾有一长串空行,可以删除,如下所示:

0,62;808,50;367,50;220,50;3,50;5;0,40;5;16,64;16,03;; 
;;;;;;;;;;; 
;;;;;;;;;;; 

现在,我们准备加载数据。让我们打开一个新文件,并使用 Weka 的转换器编写一个简单的数据导入函数来读取 CSV 格式的文件,如下所示:

import weka.core.Instances; 
import weka.core.converters.CSVLoader; 
import java.io.File; 
import java.io.IOException; 

public class EnergyLoad { 

  public static void main(String[] args) throws IOException { 

    // load CSV 
    CSVLoader loader = new CSVLoader();
    loader.setFieldSeparator(","); 
    loader.setSource(new File("data/ENB2012_data.csv")); 
    Instances data = loader.getDataSet(); 

    System.out.println(data); 
  } 
} 

数据加载完毕!我们继续吧。

分析属性

在我们分析属性之前,让我们试着理解我们在处理什么。总共有八个描述建筑特征的属性,还有两个目标变量,即热负荷和冷负荷,如下表所示:

| 属性 | 属性名 |
| X1 | 相对紧性 |
| X2 | 表面面积 |
| X3 | 墙壁面积 |
| X4 | 屋顶面积 |
| X5 | 总高 |
| X6 | 方向 |
| X7 | 玻璃区域 |
| X8 | 玻璃面积分布 |
| Y1 | 供热量 |
| Y2 | 冷负荷 |

构建和评估回归模型

我们将通过在要素位置设置类属性来开始学习热负荷模型:

data.setClassIndex(data.numAttributes() - 2); 

现在可以删除第二个目标变量,即冷负荷:

//remove last attribute Y2 
Remove remove = new Remove(); 
remove.setOptions(new String[]{"-R", data.numAttributes()+""}); 
remove.setInputFormat(data);
data = Filter.useFilter(data, remove); 

线性回归

我们将从一个基本的线性回归模型开始,用LinearRegression类实现。与分类示例类似,我们将初始化一个新的模型实例,传递参数和数据,并调用buildClassifier(Instances)方法,如下所示:

import weka.classifiers.functions.LinearRegression; 
... 
data.setClassIndex(data.numAttributes() - 2);
LinearRegression model = new LinearRegression(); 
model.buildClassifier(data); 
System.out.println(model);

存储在对象中的学习模型可以通过调用toString()方法来提供,如下所示:

    Y1 =

        -64.774  * X1 +
         -0.0428 * X2 +
          0.0163 * X3 +
         -0.089  * X4 +
          4.1699 * X5 +
         19.9327 * X7 +
          0.2038 * X8 +
         83.9329

线性回归模型构建了一个函数,该函数线性组合输入变量来估计热负荷。特征前面的数字说明了该特征对目标变量的影响:符号对应的是正面/负面影响,幅度对应的是其显著性。例如,特征X1的相对紧密度与热负荷呈负相关,而玻璃面积呈正相关。这两个特征也显著影响最终的热负荷估计。该模型的性能同样可以用交叉验证技术进行评估。

十重交叉验证如下:

Evaluation eval = new Evaluation(data); 
eval.crossValidateModel(model, data, 10, new Random(1), new String[]{}); 
System.out.println(eval.toSummaryString()); 

我们可以提供常用的评估指标,包括相关性、平均绝对误差、相对绝对误差等,作为输出,如下所示:

Correlation coefficient                  0.956  
Mean absolute error                      2.0923 
Root mean squared error                  2.9569 
Relative absolute error                 22.8555 % 
Root relative squared error             29.282  % 
Total Number of Instances              768      

使用 Encog 进行线性回归

现在,我们将快速查看如何使用 Encog 来制作回归模型。我们将使用我们在上一节中使用的数据集,加载数据。以下步骤显示了如何制作模型:

  1. 为了加载数据,我们将使用VersatileMLDataSet函数,如下所示:
File datafile = new File("data/ENB2012_data.csv");
VersatileDataSource source = new CSVDataSource(datafile, true, CSVFormat.DECIMAL_POINT);
VersatileMLDataSet data = new VersatileMLDataSet(source); 
data.defineSourceColumn("X1", 0, ColumnType.continuous); 
data.defineSourceColumn("X2", 1, ColumnType.continuous); 
data.defineSourceColumn("X3", 2, ColumnType.continuous); 
data.defineSourceColumn("X4", 3, ColumnType.continuous);
data.defineSourceColumn("X5", 4, ColumnType.continuous);
data.defineSourceColumn("X6", 5, ColumnType.continuous);
data.defineSourceColumn("X7", 6, ColumnType.continuous);
data.defineSourceColumn("X8", 7, ColumnType.continuous);
  1. 由于我们有两个输出,Y1Y2,它们可以通过使用defineMultipleOutputsOthersInput函数相加,如下所示:
ColumnDefinition outputColumn1 = data.defineSourceColumn("Y1", 8,    ColumnType.continuous);
ColumnDefinition outputColumn2 = data.defineSourceColumn("Y2", 9,  ColumnType.continuous);
ColumnDefinition outputscol [] = {outputColumn1, outputColumn2};
data.analyze();

data.defineMultipleOutputsOthersInput(outputscol);
  1. 下一步是通过使用FEEDFORWARD实例开发一个简单的回归模型:
EncogModel model = new EncogModel(data); 
model.selectMethod(data, MLMethodFactory.TYPE_FEEDFORWARD);
model.setReport(new ConsoleStatusReportable());

data.normalize();
model.holdBackValidation(0.3, true, 1001);
model.selectTrainingType(data);
MLRegression bestMethod = (MLRegression)model.crossvalidate(5, true);            
NormalizationHelper helper = data.getNormHelper(); 

System.out.println(helper.toString()); 
System.out.println("Final model: " + bestMethod); 

现在,我们的回归模型准备好了。下面的屏幕截图给出了输出的最后几行:

使用 MOA 进行回归

使用 MOA 进行回归需要我们使用 GUI。可以从www . cs . waikato . AC . NZ/~ Bernhard/Halifax 17/census . ARFF . gz下载数据集。

以下步骤显示了如何执行回归:

  1. 使用以下命令启动 MOA GUI:
$ java -cp moa.jar -javaagent:sizeofag-1.0.4.jar moa.gui.GUI
  1. 选择回归选项卡并单击配置,如下面的屏幕截图所示:

  1. 我们将使用下载的.arff文件进行回归。当我们在前面的步骤中单击“配置”时,将显示“配置任务”窗口,如下面的屏幕截图所示:

  1. 在流选项中,单击编辑并选择 ArffFileStream 选择我们下载的.arff文件,如下图所示:

  1. 在 classIndex 中,指定-1,它将第一个属性设置为目标。在所有弹出窗口中单击确定,然后单击运行。这需要一些时间,因为人口普查文件有大量数据要处理,如下面的屏幕截图所示:

回归树

另一种方法是构建一组回归模型,每个模型都有自己的数据部分。下图显示了回归模型和回归树之间的主要区别。回归模型构建了最适合所有数据的单一模型。另一方面,回归树构建了一组回归模型,每个模型对数据的一部分进行建模,如右侧所示。与回归模型相比,回归树可以更好地拟合数据,但该函数是一个分段线性图,在建模区域之间有跳跃,如下图所示:

Weka 中的回归树是在M5类中实现的。模型构建遵循相同的范例:初始化模型,传递参数和数据,并调用buildClassifier(Instances)方法,如下所示:

import weka.classifiers.trees.M5P; 
... 
M5P md5 = new M5P(); 
md5.setOptions(new String[]{""}); 
md5.buildClassifier(data);  
System.out.println(md5); 

归纳模型是在叶节点中具有等式的树,如下所示:

    M5 pruned model tree:
    (using smoothed linear models)

    X1 <= 0.75 : 
    |   X7 <= 0.175 : 
    |   |   X1 <= 0.65 : LM1 (48/12.841%)
    |   |   X1 >  0.65 : LM2 (96/3.201%)
    |   X7 >  0.175 : 
    |   |   X1 <= 0.65 : LM3 (80/3.652%)
    |   |   X1 >  0.65 : LM4 (160/3.502%)
    X1 >  0.75 : 
    |   X1 <= 0.805 : LM5 (128/13.302%)
    |   X1 >  0.805 : 
    |   |   X7 <= 0.175 : 
    |   |   |   X8 <= 1.5 : LM6 (32/20.992%)
    |   |   |   X8 >  1.5 : 
    |   |   |   |   X1 <= 0.94 : LM7 (48/5.693%)
    |   |   |   |   X1 >  0.94 : LM8 (16/1.119%)
    |   |   X7 >  0.175 : 
    |   |   |   X1 <= 0.84 : 
    |   |   |   |   X7 <= 0.325 : LM9 (20/5.451%)
    |   |   |   |   X7 >  0.325 : LM10 (20/5.632%)
    |   |   |   X1 >  0.84 : 
    |   |   |   |   X7 <= 0.325 : LM11 (60/4.548%)
    |   |   |   |   X7 >  0.325 : 
    |   |   |   |   |   X3 <= 306.25 : LM12 (40/4.504%)
    |   |   |   |   |   X3 >  306.25 : LM13 (20/6.934%)

    LM num: 1
    Y1 = 
      72.2602 * X1 
      + 0.0053 * X3 
      + 11.1924 * X7 
      + 0.429 * X8 
      - 36.2224

    ...

    LM num: 13
    Y1 = 
      5.8829 * X1 
      + 0.0761 * X3 
      + 9.5464 * X7 
      - 0.0805 * X8 
      + 2.1492

    Number of Rules : 13

树有13片叶子,每片叶子对应一个线性方程。下图显示了上述输出:

该树可以类似于分类树来阅读。最重要的特性在树的顶部。终端节点(叶节点)包含一个线性回归模型,解释到达树的这一部分的数据。

评估将提供以下结果作为输出:

    Correlation coefficient                  0.9943
    Mean absolute error                      0.7446
    Root mean squared error                  1.0804
    Relative absolute error                  8.1342 %
    Root relative squared error             10.6995 %
    Total Number of Instances              768     

避免常见回归问题的技巧

首先,我们必须使用先前的研究和领域知识来确定哪些特性要包含在回归中。查阅文献、报告和以前的研究,了解什么样的功能起作用,以及为您的问题建模的一些合理变量。假设您有一个包含随机数据的大型要素集;很有可能几个特征将与目标变量相关联(即使数据是随机的)。

为了避免过度拟合,我们必须保持模型简单。奥卡姆剃刀原则指出,你应该选择一个模型,最好地解释你的数据,用最少的假设。实际上,该模型可以简单到只有两到四个预测特征。

使聚集

与监督分类器相比,聚类的目标是识别一组未标记数据中的内在组。它可以用于识别同类组的代表性示例,找到有用和合适的分组,或者找到异常的示例,如异常值。

我们将通过分析一个银行数据集来演示如何实现聚类。该数据集由 11 个属性组成,描述了 600 个实例,包括年龄、性别、地区、收入、婚姻状况、子女、汽车拥有状况、储蓄活动、当前活动、抵押贷款状况和 PEP。在我们的分析中,我们将尝试通过应用期望最大化 ( EM )聚类来识别常见的客户群。

EM 的工作方式如下:给定一组聚类,EM 首先为每个实例分配属于特定聚类的概率分布。例如,如果我们从三个集群(即 A、B 和 C)开始,一个实例可能得到概率分布 0.70、0.10 和 0.20,分别属于 A、B 和 C 集群。第二步,EM 重新估计每类概率分布的参数向量。该算法迭代这两个步骤,直到参数收敛或达到最大迭代次数。

EM 中使用的聚类数可以手动设置,也可以通过交叉验证自动设置。确定数据集中聚类数量的另一种方法包括肘方法。这种方法着眼于用特定数量的分类解释的方差百分比。该方法建议增加聚类的数量,直到附加的聚类不会添加太多信息,也就是说,它解释了很少的附加变化。

聚类算法

构建聚类模型的过程与构建分类模型的过程非常相似,即加载数据并构建模型。聚类算法在weka.clusterers包中实现,如下所示:

import java.io.BufferedReader; 
import java.io.FileReader; 

import weka.core.Instances; 
import weka.clusterers.EM; 

public class Clustering { 

  public static void main(String args[]) throws Exception{ 

    //load data 
    Instances data = new Instances(new BufferedReader
       (new FileReader("data/bank-data.arff"))); 

    // new instance of clusterer 
    EM model = new EM(); 
    // build the clusterer 
    model.buildClusterer(data); 
    System.out.println(model); 

  } 
} 

该模型确定了以下六个集群:

    EM
    ==

    Number of clusters selected by cross validation: 6

                     Cluster
    Attribute              0        1        2        3        4        5
                       (0.1)   (0.13)   (0.26)   (0.25)   (0.12)   (0.14)
    ======================================================================
    age
      0_34            10.0535  51.8472 122.2815  12.6207   3.1023   1.0948
      35_51           38.6282  24.4056  29.6252  89.4447  34.5208   3.3755
      52_max          13.4293    6.693   6.3459  50.8984   37.861  81.7724
      [total]         62.1111  82.9457 158.2526 152.9638  75.4841  86.2428
    sex
      FEMALE          27.1812  32.2338  77.9304  83.5129  40.3199  44.8218
      MALE            33.9299  49.7119  79.3222  68.4509  34.1642   40.421
      [total]         61.1111  81.9457 157.2526 151.9638  74.4841  85.2428
    region
      INNER_CITY      26.1651  46.7431   73.874  60.1973  33.3759  34.6445
      TOWN            24.6991  13.0716  48.4446  53.1731   21.617  17.9946
    ...

该表如下所示:第一行表示六个分类,而第一列显示属性及其范围。例如,属性age被分成三个范围:0-3435-5152-max。左侧的列指示每个群集中有多少实例属于特定范围;例如,0-34年龄组中的客户主要在聚类 2 中(122 个实例)。

估价

聚类算法的质量可以通过使用logLikelihood度量来估计,该度量测量所识别的聚类的一致性。数据集被拆分成多个文件夹,并对每个文件夹运行聚类。这样做的动机是,如果聚类算法为没有用于拟合参数的相似数据分配了很高的概率,那么它可能在捕获数据结构方面做得很好。Weka 提供了CluterEvaluation类来估计它,如下所示:

double logLikelihood = ClusterEvaluation.crossValidateModel(model, data, 10, new Random(1));
System.out.println(logLikelihood);  

它提供以下输出:

-8.773410259774291 

使用 Encog 进行聚类

Encog 支持 k 均值聚类。让我们考虑一个非常简单的例子,数据显示在下面的代码块中:

DATA = { { 28, 15, 22 }, { 16, 15, 32 }, { 32, 20, 44 }, { 1, 2, 3 }, { 3, 2, 1 } };

为了从该数据生成BasicMLDataSet,使用了一个简单的for循环,它将数据添加到数据集:

BasicMLDataSet set = new BasicMLDataSet();

for (final double[] element : DATA) {
    set.add(new BasicMLData(element));
}

使用KMeansClustering函数,让我们将数据集分成两个集群,如下所示:

KMeansClustering kmeans = new KMeansClustering(2, set);

kmeans.iteration(100);

// Display the cluster
int i = 1;
for (MLCluster cluster : kmeans.getClusters()) {
    System.out.println("*** Cluster " + (i++) + " ***");
    final MLDataSet ds = cluster.createDataSet();
    final MLDataPair pair = BasicMLDataPair.createPair(ds.getInputSize(), ds.getIdealSize());
    for (int j = 0; j < ds.getRecordCount(); j++) {
        ds.getRecord(j, pair);
        System.out.println(Arrays.toString(pair.getInputArray()));
        }
    }

这将生成以下输出:

*** Cluster 1 ***
[16.0, 15.0, 32.0]
[1.0, 2.0, 3.0]
[3.0, 2.0, 1.0]
*** Cluster 2 ***
[28.0, 15.0, 22.0]
*** Cluster 3 ***
[32.0, 20.0, 44.0]

使用 ELKI 进行聚类

ELKI 支持许多聚类算法。下面列出了一些例子:

  • 仿射传播聚类算法:这是一个使用仿射传播的聚类分析。
  • DBSCAN :这是一种基于密度的聚类,尤其适用于有噪声的应用;它根据密度在数据库中查找集合。
  • EM :该算法基于期望最大化算法创建聚类。
  • AGNES : 层次凝聚聚类(HAC),凝聚嵌套(AGNES),是一种经典的层次聚类算法。
  • SLINK :这是单链路算法。
  • 叮当:用于完全联动。
  • HDBSCAN :这是一个抽取集群层次。

此外,KMeansSort、KMeansCompare、KMedianLloyd、KMediodsEM、KMeansBisecting 等等都是 KMean 家族中的一些例子。

elki-project.github.io/algorithms/可以找到聚类算法的详细列表,包括 ELKI 支持的所有算法。

我们需要从elki-project.github.io/releases/那里得到所需的.jar文件。下载可执行文件,从elki-project.github.io/datasets/下载鼠标数据集。

在终端或命令提示符下,运行以下命令:

$ java -jar elki-bundle-0.7.1.jar 

上述命令会生成以下输出:

我们可以看到橙色的两个选项:dbc.inalgorithm。我们需要指定值。在dbc.in中,点击圆点(...,选择我们下载的mouse.csv文件。在algorithm中,点击加号(+)选择k-Mean Clustering algorithm,找到kmean.k,填入数值3。单击“运行任务”按钮,该按钮现已启用。它将生成以下输出:

摘要

在本章中,您学习了如何使用 Weka 实现基本的机器学习任务:分类、回归和聚类。我们简要讨论了属性选择过程和训练模型,并评估了它们的性能。

下一章将关注如何应用这些技术来解决现实生活中的问题,比如客户保持。**

四、集成的客户关系预测

提供服务、产品或体验的任何类型的公司都需要对他们与客户的关系有充分的了解;因此,客户关系管理 ( CRM )是现代营销策略的关键要素。企业面临的最大挑战之一是需要准确理解是什么导致客户购买新产品。

在本章中,我们将使用由法国电信公司 Orange 提供的真实世界营销数据库。该任务将评估以下客户行为的可能性:

  • 交换机提供商(流失)
  • 购买新产品或服务(欲望)
  • 购买向他们建议的升级或附加产品,以提高销售利润(追加销售)

我们将参加 2009 年知识发现和数据挖掘 ( KDD )杯挑战赛,并展示使用 Weka 处理数据的步骤。首先,我们将解析和加载数据,并实现基本的基线模型。稍后,我们将讨论高级建模技术,包括数据预处理、属性选择、模型选择和评估。

The KDD Cup is the leading data mining competition in the world. It is organized annually by the ACM Special Interest Group on Knowledge Discovery and Data Mining. The winners are announced at the Conference on Knowledge Discovery and Data Mining, which is usually held in August. Yearly archives, including all of the corresponding datasets, are available at www.kdd.org/kdd-cup.

客户关系数据库

建立客户行为知识的最实用的方法是产生解释目标变量的分数,如客户流失、亲和力或追加销售。分数是由模型使用描述客户的输入变量计算的;例如,他们当前的订阅、购买的设备、消耗的分钟数等等。这些分数随后被信息系统用于提供相关的个性化营销活动。

在大多数基于客户的关系数据库中,客户是主要实体;了解顾客的行为很重要。客户的行为会产生一个与客户流失、欲望或追加销售相关的分数。基本思想是使用计算模型产生分数,该计算模型可以使用不同的参数,例如客户的当前订购、购买的设备、消耗的分钟数等等。分数一旦形成,信息系统就根据他或她的行为决定下一步的策略,这是专门为顾客设计的。

2009 年,KDD 会议组织了一次关于客户关系预测的机器学习挑战。

挑战

给定一大组客户属性,挑战中的任务是估计以下目标变量:

  • 流失概率:这是客户更换提供商的可能性。流失率也称为损耗率或参与者周转率,是一种用于计算在给定时间段内进出给定集合的个人、对象、术语或项目数量的方法。该术语在由客户驱动并使用基于订户的模型的行业中大量使用;例如,手机行业和有线电视运营商。
  • 欲望概率:这是购买服务或产品的倾向。
  • 追加销售概率:这是客户购买附加产品或升级产品的可能性。追加销售意味着销售客户已经使用的产品之外的产品。就像大多数手机运营商提供的增值服务一样。使用销售技巧,销售人员试图让客户选择增值服务,这将带来更多的收入。很多时候,客户不知道还有其他选择,销售人员说服他们使用或考虑这些选择。

挑战在于击败 Orange Labs 开发的内部系统。对于参与者来说,这是一个证明他们能够处理大型数据库的机会,包括异构的、有噪声的数据和不平衡的类分布。

资料组

为了应对这一挑战,Orange 发布了一个大型客户数据数据集,包含大约 100 万个客户,在十个包含数百个字段的表中进行描述。第一步,他们对数据进行了重新采样,以选择一个不太不平衡的子集,其中包含 100,000 名客户。在第二步中,他们使用了一个自动特征构造工具,该工具生成了 20,000 个描述客户的特征,然后这些特征被缩小到 15,000 个。第三步,通过随机化要素顺序、丢弃属性名称、用随机生成的字符串替换名义变量以及将连续属性乘以随机因子,对数据集进行匿名化。最后,所有实例被随机分成训练和测试数据集。

KDD 杯提供了两组数据,一组大数据和一组小数据,分别对应于快挑战和慢挑战。训练集和测试集都包含 50,000 个示例,数据的划分类似,但是每个集的样本排序不同。

在本章中,我们将使用由 50,000 个实例组成的小型数据集,每个实例都用 230 个变量来描述。50,000 行数据中的每一行都对应于一个客户,它们与三个二元结果相关联,三个挑战(向上销售、流失和欲望)中的每一个都有一个。

为了使这一点更清楚,下表说明了数据集:

该表描述了前 25 个实例,即客户,每个用 250 个属性描述。对于本例,只显示了 10 个属性的选定子集。数据集包含许多缺失值,甚至空属性或常量属性。表格的最后三列对应于涉及基本事实的三个不同的类别标签,即客户是否确实更换了提供商(流失)、购买了服务(欲望)或购买了升级(追加销售)。但是,请注意,标签是与三个不同文件中的数据分开提供的,因此保留实例和相应类标签的顺序以确保正确对应是非常重要的。

估价

根据三项任务(流失、亲和力和追加销售)的 ROC 曲线下面积的算术平均值对提交的内容进行评估。ROC 曲线将模型的性能显示为一条曲线,该曲线通过绘制用于确定分类结果的各种阈值的灵敏度对特异性而获得(参见第一章、应用机器学习快速入门、章节 ROC 曲线)。现在,ROC 曲线 ( AUC )下的面积与该曲线下的面积相关——面积越大,分类器越好)。包括 Weka 在内的大多数工具箱都提供了计算 AUC 分数的 API。

基本朴素贝叶斯分类器基线

根据挑战赛的规则,参与者必须超越基本的朴素贝叶斯分类器才能有资格获得奖励,它假设特征是独立的(参见第一章、应用机器学习快速入门)。

KDD 杯组织者运行了香草朴素贝叶斯分类器,没有任何特征选择或超参数调整。对于大型数据集,朴素贝叶斯在测试集上的总体得分如下:

  • 流失问题 : AUC = 0.6468
  • 亲和力问题 : AUC = 0.6453
  • 追加销售问题 : AUC=0.7211

请注意,基线结果仅针对大型数据集进行报告。此外,虽然训练和测试数据集都在 KDD 杯网站上提供,但测试集的实际真实标签并未提供。因此,当我们用模型处理数据时,没有办法知道模型在测试集上的表现如何。我们要做的只是使用训练数据,并通过交叉验证来评估我们的模型。结果不会是直接可比的,但是尽管如此,我们将对 AUC 评分的合理幅度有所了解。

获取数据

在 KDD 杯的网页上(kdd.org/kdd-cup/view/kdd-cup-2009/Data,你应该会看到一个类似下面截图的页面。一、下小版(230 var。)头,下载orange_small_train.data.zip。接下来,下载与该训练数据相关联的三组真实标签。以下文件位于 Real binary targets (small)标题下:

  • orange_small_train_appentency.labels
  • orange_small_train_churn.labels
  • orange_small_train_upselling.labels

保存并解压缩红框中标记的所有文件,如屏幕截图所示:

在接下来的部分中,首先,我们将数据加载到 Weka 中,并用朴素贝叶斯分类器应用基本建模,以获得我们自己的基线 AUC 分数。稍后,我们将研究更高级的建模技术和技巧。

加载数据

我们将直接从.csv格式加载数据到 Weka。为此,我们将编写一个函数来接受数据文件和真实标签文件的路径。该函数将加载并合并两个数据集,并移除空属性。我们将从下面的代码块开始:

public static Instances loadData(String pathData, String 
  pathLabeles) throws Exception { 

首先,我们使用CSVLoader()类加载数据。此外,我们将\t选项卡指定为字段分隔符,并强制将最后 40 个属性解析为名义属性:

// Load data 
CSVLoader loader = new CSVLoader(); 
loader.setFieldSeparator("\t"); 
loader.setNominalAttributes("191-last"); 
loader.setSource(new File(pathData)); 
Instances data = loader.getDataSet(); 

CSVLoader类接受许多附加参数,指定列分隔符、字符串包围符、是否存在标题行等等。完整的文档可在WEKA . SourceForge . net/doc . dev/WEKA/core/converters/CSV loader . html获得。

有些属性不包含单个值,Weka 会自动将它们识别为String属性。我们实际上并不需要它们,所以我们可以使用RemoveType过滤器安全地删除它们。此外,我们指定了-T参数,它删除了一个特定类型的属性,并指定了我们想要删除的属性类型:

// remove empty attributes identified as String attribute  
RemoveType removeString = new RemoveType(); 
removeString.setOptions(new String[]{"-T", "string"}); 
removeString.setInputFormat(data); 
Instances filteredData = Filter.useFilter(data, removeString); 

或者,我们可以使用在Instances类中实现的void deleteStringAttributes()方法,它具有相同的效果;例如,data.removeStringAttributes()

现在,我们将加载数据并为其分配类别标签。我们将再次使用CVSLoader,这里我们指定文件没有任何标题行,即setNoHeaderRowPresent(true):

// Load labeles 
loader = new CSVLoader(); 
loader.setFieldSeparator("\t"); 
loader.setNoHeaderRowPresent(true); 
loader.setNominalAttributes("first-last"); 
loader.setSource(new File(pathLabeles)); 
Instances labels = loader.getDataSet(); 

一旦我们加载了两个文件,我们就可以通过调用Instances.mergeInstances (Instances, Instances)静态方法将它们合并在一起。该方法返回一个新的数据集,该数据集包含第一个数据集中的所有属性,以及第二个数据集中的属性。请注意,两个数据集中的实例数量必须相同:

// Append label as class value 
Instances labeledData = Instances.mergeInstances(filteredData, 
   labeles); 

最后,我们设置最后一个属性,也就是我们刚刚添加的标签属性,作为目标变量,并返回结果数据集:

// set the label attribute as class  
labeledData.setClassIndex(labeledData.numAttributes() - 1); 

System.out.println(labeledData.toSummaryString()); 
return labeledData; 
} 

该函数提供一个摘要作为输出,如下面的代码块所示,并返回带标签的数据集:

    Relation Name:  orange_small_train.data-weka.filters.unsupervised.attribute.RemoveType-Tstring_orange_small_train_churn.labels.txt
    Num Instances:  50000
    Num Attributes: 215

    Name          Type  Nom  Int Real     Missing      Unique  Dist
    1 Var1        Num   0%   1%   0% 49298 / 99%     8 /  0%    18 
    2 Var2        Num   0%   2%   0% 48759 / 98%     1 /  0%     2 
    3 Var3        Num   0%   2%   0% 48760 / 98%   104 /  0%   146 
    4 Var4        Num   0%   3%   0% 48421 / 97%     1 /  0%     4
    ...

基本建模

在这一部分,我们将按照 KDD 杯组织者采用的方法来实现我们自己的基线模型。然而,在我们进入模型之前,让我们首先实现评估引擎,它将返回所有三个问题的 AUC。

评估模型

现在,让我们仔细看看评估函数。评估函数接受一个初始化的模型,在所有三个问题上交叉验证该模型,并以 ROC 曲线下面积(AUC)的形式报告结果,如下所示:

public static double[] evaluate(Classifier model) 
   throws Exception { 

  double results[] = new double[4]; 

  String[] labelFiles = new String[]{ 
    "churn", "appetency", "upselling"}; 

  double overallScore = 0.0; 
  for (int i = 0; i < labelFiles.length; i++) { 

首先,我们调用前面实现的Instance loadData(String, String)函数来加载训练数据,并将其与选定的标签合并:

    // Load data 
    Instances train_data = loadData( 
     path + "orange_small_train.data", 
      path+"orange_small_train_"+labelFiles[i]+".labels.txt"); 

接下来,我们初始化weka.classifiers.Evaluation类并传递我们的数据集。(数据集仅用于提取数据属性;不考虑实际数据。)我们调用void crossValidateModel(Classifier, Instances, int, Random)方法开始交叉验证,我们创建了五个折叠。因为验证是在数据的随机子集上进行的,所以我们也需要传递一个随机种子:

    // cross-validate the data 
    Evaluation eval = new Evaluation(train_data); 
    eval.crossValidateModel(model, train_data, 5,  
    new Random(1)); 

评估完成后,我们通过调用double areUnderROC(int)方法读取结果。因为度量取决于我们感兴趣的目标值,所以该方法需要一个类值索引,可以通过搜索 class 属性中的"1"值的索引来提取该索引,如下所示:

    // Save results 
    results[i] = eval.areaUnderROC( 
      train_data.classAttribute().indexOfValue("1")); 
    overallScore += results[i]; 
  } 

最后,计算结果的平均值并返回:

  // Get average results over all three problems 
  results[3] = overallScore / 3; 
  return results; 
}

实现朴素贝叶斯基线

现在,当我们有了所有的成分,我们可以复制朴素贝叶斯方法,我们有望超越它。这种方法不包括任何额外的数据预处理、属性选择或模型选择。由于我们没有测试数据的真实标签,我们将应用五重交叉验证来评估小数据集上的模型。

首先,我们初始化一个朴素贝叶斯分类器,如下:

Classifier baselineNB = new NaiveBayes(); 

接下来,我们将分类器传递给我们的评估函数,该函数加载数据并应用交叉验证。该函数返回所有三个问题的 ROC 曲线得分下的面积,以及总体结果:

double resNB[] = evaluate(baselineNB); 
System.out.println("Naive Bayes\n" +  
"\tchurn:     " + resNB[0] + "\n" +  
"\tappetency: " + resNB[1] + "\n" +  
"\tup-sell:   " + resNB[2] + "\n" +  
"\toverall:   " + resNB[3] + "\n"); 

在我们的例子中,模型返回以下结果:

    Naive Bayes
      churn:     0.5897891153549814
      appetency: 0.630778394752436
      up-sell:   0.6686116692438094
      overall:   0.6297263931170756

当我们用更高级的建模来应对挑战时,这些结果将作为基线。如果我们用更加精密、耗时和复杂的技术来处理数据,我们希望结果会好得多。否则,我们只是在浪费资源。一般来说,在解决机器学习问题时,创建一个简单的基线分类器作为方向点总是一个好主意。

集成高级建模

在前面的部分中,我们实现了一个方向基线;现在,我们来关注一下重型机械。我们将遵循 IBM 研究团队(Niculescu-Mizil 等人)开发的 2009 年 KDD 杯获奖解决方案所采用的方法。

为了应对这一挑战,他们使用了集合选择算法(Caruana 和 Niculescu-Mizil,2004)。这是一种集成方法,这意味着它构建了一系列模型,并以特定的方式组合它们的输出,以便提供最终的分类。它有几个令人满意的特性,非常适合这一挑战,如下所示:

  • 它被证明是健壮的,产生出色的性能。
  • 它可以针对特定的性能指标进行优化,包括 AUC。
  • 它允许将不同的分类器添加到库中。
  • 这是一种随时可用的方法,意味着如果我们没有时间了,我们有一个可用的解决方案。

在本节中,我们将大致按照他们报告中描述的步骤进行操作。请注意,这并不是他们方法的精确实现,而是一个解决方案概述,其中包括深入研究的必要步骤。

这些步骤的概述如下:

  1. 首先,我们将通过删除显然不会带来任何值的属性来预处理数据——例如,所有缺失值或常量值;修复缺失值,以帮助无法处理它们的机器学习算法;并将分类属性转换成数字属性。
  2. 接下来,我们将运行属性选择算法,仅选择有助于任务预测的属性子集。
  3. 在第三步中,我们将用各种各样的模型实例化集成选择算法,最后,我们将评估性能。

开始之前

对于这个任务,我们将需要一个额外的 Weka 包,ensembleLibrary。Weka 3.7.2 及更高版本支持外部包,主要由学术界开发。WEKA 软件包列表可在weka.sourceforge.net/packageMetaData获得,如下图所示:

pr downloads . SourceForge . net/WEKA/ensemble library 1 . 0 . 5 . zip 找到并下载最新版本的ensembleLibrary包?下载

解压软件包后,找到ensembleLibrary.jar并将其导入到代码中,如下所示:

import weka.classifiers.meta.EnsembleSelection; 

数据预处理

首先,我们将利用 Weka 的内置weka.filters.unsupervised.attribute.RemoveUseless过滤器,正如其名称所暗示的那样。它会删除变化不大的属性,例如,所有常量属性都会被删除。仅适用于名义属性的最大方差由-M参数指定。默认参数为 99%,这意味着如果 99%以上的实例都具有唯一的属性值,则该属性将被删除,如下所示:

RemoveUseless removeUseless = new RemoveUseless(); 
removeUseless.setOptions(new String[] { "-M", "99" });// threshold 
removeUseless.setInputFormat(data); 
data = Filter.useFilter(data, removeUseless); 

接下来,我们将使用weka.filters.unsupervised.attribute.ReplaceMissingValues过滤器,用训练数据中的模式(名义属性)和均值(数值属性)替换数据集中所有缺失的值。一般而言,在考虑属性的含义和上下文时,应谨慎进行缺失值替换:

ReplaceMissingValues fixMissing = new ReplaceMissingValues(); 
fixMissing.setInputFormat(data); 
data = Filter.useFilter(data, fixMissing); 

最后,我们将离散化数值属性,也就是说,我们将使用weka.filters.unsupervised.attribute.Discretize过滤器将数值属性转换为区间。使用-B选项,我们将数值属性分割成四个区间,-R选项指定属性的范围(只有数值属性会被离散化):

Discretize discretizeNumeric = new Discretize(); 
discretizeNumeric.setOptions(new String[] { 
    "-B",  "4",  // no of bins 
    "-R",  "first-last"}); //range of attributes 
fixMissing.setInputFormat(data); 
data = Filter.useFilter(data, fixMissing); 

属性选择

在下一步中,我们将只选择信息属性,即更有可能帮助预测的属性。解决这个问题的标准方法是检查每个属性携带的信息增益。我们将使用weka.attributeSelection.AttributeSelection过滤器,它需要两个额外的方法:赋值器(如何计算属性有用性)和搜索算法(如何选择属性子集)。

在我们的例子中,首先,我们初始化weka.attributeSelection.InfoGainAttributeEval,它实现了信息增益的计算:

InfoGainAttributeEval eval = new InfoGainAttributeEval(); 
Ranker search = new Ranker(); 

为了只选择高于阈值的顶部属性,我们初始化weka.attributeSelection.Ranker,以便对信息增益高于特定阈值的属性进行排序。我们用-T参数指定它,同时保持阈值较低,以便保持属性至少包含一些信息:

search.setOptions(new String[] { "-T", "0.001" }); 

The general rule for setting this threshold is to sort the attributes by information gain and pick the threshold where the information gain drops to a negligible value.

接下来,我们可以初始化AttributeSelection类,设置赋值器和排序器,并将属性选择应用于数据集,如下所示:

AttributeSelection attSelect = new AttributeSelection(); 
attSelect.setEvaluator(eval); 
attSelect.setSearch(search); 

// apply attribute selection 
attSelect.SelectAttributes(data); 

最后,我们通过调用reduceDimensionality(Instances)方法来删除上次运行中没有选择的属性:

// remove the attributes not selected in the last run 
data = attSelect.reduceDimensionality(data); 

最后,我们只剩下 230 个属性中的 214 个。

型号选择

多年来,机器学习领域的从业者开发了各种各样的学习算法,并对现有算法进行了改进。有这么多独特的监督学习方法,很难跟踪所有这些方法。由于数据集的特征各不相同,没有一种方法在所有情况下都是最好的,但是不同的算法能够利用给定数据集的不同特征和关系。

首先,我们需要通过初始化weka.classifiers.EnsembleLibrary类来创建模型库,这将帮助我们定义模型:

EnsembleLibrary ensembleLib = new EnsembleLibrary(); 

接下来,我们将模型及其参数作为字符串值添加到库中;例如,我们可以添加三个具有不同参数的决策树学习器,如下所示:

ensembleLib.addModel("weka.classifiers.trees.J48 -S -C 0.25 -B -M 
   2"); 
ensembleLib.addModel("weka.classifiers.trees.J48 -S -C 0.25 -B -M 
   2 -A"); 

如果您熟悉 Weka 图形界面,您还可以在那里探索算法及其配置,并复制配置,如下图所示。右键单击算法名称并导航至编辑配置|复制配置字符串:

为了完成此示例,我们添加了以下算法及其参数:

  • 用作默认基线的朴素贝叶斯:
ensembleLib.addModel("weka.classifiers.bayes.NaiveBayes"); 
  • 基于懒惰模型的 k-最近邻:
ensembleLib.addModel("weka.classifiers.lazy.IBk"); 
  • 具有默认参数的简单逻辑回归:
ensembleLib.addModel("weka.classifiers.functions.SimpleLogi
   stic"); 
  • 具有默认参数的支持向量机:
ensembleLib.addModel("weka.classifiers.functions.SMO"); 
  • AdaBoost,其本身是一种集合方法:
ensembleLib.addModel("weka.classifiers.meta.AdaBoostM1"); 
  • LogitBoost,基于逻辑回归的集成方法:
ensembleLib.addModel("weka.classifiers.meta.LogitBoost"); 
  • DecisionStump,一种基于一级决策树的集成方法:
ensembleLib.addModel("classifiers.trees.DecisionStump"); 

由于EnsembleLibrary实现主要关注 GUI 和控制台用户,我们必须通过调用saveLibrary(File, EnsembleLibrary, JComponent)方法将模型保存到一个文件中,如下所示:

EnsembleLibrary.saveLibrary(new 
   File(path+"ensembleLib.model.xml"), ensembleLib, null); 
System.out.println(ensembleLib.getModels()); 

接下来,我们可以通过实例化weka.classifiers.meta.EnsembleSelection类来初始化集成选择算法。首先,让我们回顾一下以下方法选项:

  • -L </path/to/modelLibrary>:指定modelLibrary文件,继续所有型号列表。
  • -W </path/to/working/directory>:指定工作目录,所有的模型都存储在这里。
  • -B <numModelBags>:设置行李数量,即运行集合选择算法的迭代次数。
  • -E <modelRatio>:设置随机选择的库模型的比例,以填充每个模型包。
  • -V <validationRatio>:设置将被保留用于验证的训练数据集的比率。
  • -H <hillClimbIterations>:设置每个模型包要进行的爬山迭代次数。
  • -I <sortInitialization>:设置在初始化每个模型行李的集合时,分拣初始化算法能够选择的集合库的比率。
  • -X <numFolds>:设置交叉验证的折叠次数。
  • -P <hillclimbMetric>:指定在爬山算法中用于模型选择的度量。有效的指标包括准确性、rmse、roc、精确度、召回率、fscore 和所有指标。
  • -A <algorithm>:指定用于集合选择的算法。有效的算法包括用于正向选择的正向(默认)、用于反向消除的反向、用于正向和反向消除的反向、仅打印来自集合库的最佳表现者、以及仅训练集合库中的模型的库。
  • -R:该标志表示是否可以为一个组合多次选择模型。
  • -G:表示当性能下降时,排序初始化是否贪婪地停止添加模型。
  • -O:这是详细输出的标志。这将打印所有选定型号的性能。
  • -S <num>:这是一个随机数种子(默认为1)。
  • -D:如果设置,分类器在调试模式下运行,并且可以向控制台提供附加信息作为输出。

我们用以下初始参数初始化算法,其中我们指定优化 ROC 度量:

EnsembleSelection ensambleSel = new EnsembleSelection(); 
ensambleSel.setOptions(new String[]{ 
  "-L", path+"ensembleLib.model.xml", // </path/to/modelLibrary>
     "-W", path+"esTmp", // </path/to/working/directory> -  
"-B", "10", // <numModelBags>  
  "-E", "1.0", // <modelRatio>. 
  "-V", "0.25", // <validationRatio> 
  "-H", "100", // <hillClimbIterations>  
"-I", "1.0", // <sortInitialization>  
  "-X", "2", // <numFolds> 
  "-P", "roc", // <hillclimbMettric> 
  "-A", "forward", // <algorithm>  
  "-R", "true", // - Flag to be selected more than once 
  "-G", "true", // - stops adding models when performance degrades 
  "-O", "true", // - verbose output. 
  "-S", "1", // <num> - Random number seed. 
  "-D", "true" // - run in debug mode  
}); 

性能赋值

计算和内存方面的评估都很繁重,所以确保用额外的堆空间初始化 JVM(例如,java -Xmx16g)。根据模型库中包含的算法数量,计算可能需要几个小时或几天。该示例在一个 12 核英特尔至强 E5-2420 CPU 上运行了 4 小时 22 分钟,该 CPU 配有 32 GB 内存,平均使用了 10%的 CPU 和 6 GB 的内存。

我们调用我们的评估方法并提供结果作为输出,如下所示:

double resES[] = evaluate(ensambleSel); 
System.out.println("Ensemble Selection\n"  
+ "\tchurn:     " + resES[0] + "\n" 
+ "\tappetency: " + resES[1] + "\n"  
+ "\tup-sell:   " + resES[2] + "\n"  
+ "\toverall:   " + resES[3] + "\n"); 

模型库中的特定分类器集实现了以下结果:

    Ensamble
      churn:     0.7109874158176481
      appetency: 0.786325687118347
      up-sell:   0.8521363243575182
      overall:   0.7831498090978378

总的来说,与我们在本章开始时设计的初始基线相比,这种方法为我们带来了超过 15 个百分点的显著改进。虽然很难给出一个明确的答案,但这种改进主要归因于三个因素:数据预处理和属性选择,探索各种各样的学习方法,以及使用集成构建技术,该技术能够利用各种基分类器而不会过度拟合。然而,这种改进需要显著增加处理时间和工作记忆。

整体方法 MOA

顾名思义,就是一起或同时观看。它用于组合多个学习算法,以获得更好的结果和性能。有各种各样的技巧可以用于合奏。一些常用的集成技术或分类器包括装袋、提升、堆叠、一桶模型等等。

海量在线分析 ( MOA )支持集成分类器,比如准确度加权集成、准确度更新集成等等。在本节中,我们将向您展示如何利用 bagging 算法:

  1. 打开终端并执行以下命令:
java -cp moa.jar -javaagent:sizeofag-1.0.4.jar moa.gui.GUI
  1. 选择分类选项卡,然后单击配置按钮:

这将打开配置任务选项。

  1. 在“学习者”选项中,选择贝叶斯。NaiveBayes,然后在 stream 选项中,点击 Edit,如下图截图所示:

  1. 选择 ConceptDriftStream,并在 Stream 和 DriftStream 中选择 AgrawalGenerator 它将为流生成器使用 Agrawal 数据集:

  1. 关闭所有窗口,然后单击运行按钮:

这将运行任务并生成以下输出:

  1. 让我们使用 LeveragingBag 选项。为此,打开“配置任务”窗口并选择 baseLearner 中的“编辑”选项,这将显示以下内容:从第一个下拉框中选择 LeveragingBag。您可以在第一个下拉框中找到其他选项,如增强和平均重量组合:

将流保留为 AgrawalGenerator,如下面的屏幕截图所示:

  1. 关闭“配置任务”窗口,然后单击“运行”按钮;这将需要一些时间来完成:

输出显示了每 10,000 个实例后的评估、分类正确性花费的 RAM 时间以及 Kappa 统计。正如您所看到的,随着时间的推移,分类的正确性会随着实例的增加而增加。前面截图中的图表显示了实例的正确性和数量。

摘要

在这一章中,我们解决了 2009 年 KDD 杯客户关系预测挑战,实现了数据预处理步骤,并解决了丢失值和冗余属性的问题。我们跟踪了获胜的 KDD 杯解决方案,并研究了如何通过使用一篮子学习算法来利用集成方法,这可以显著提高分类性能。

在下一章,我们将解决另一个关于顾客行为的问题:购买行为。您将学习如何使用算法来检测频繁出现的模式。

五、亲和力分析

亲和力分析是购物篮分析 ( MBA )的核心。它可以发现由特定用户或组执行的活动之间的共现关系。在零售业,亲和力分析可以帮助你了解顾客的购买行为。这些见解可以通过智能交叉销售和追加销售策略增加收入,并帮助您制定忠诚度计划、促销和折扣计划。

在本章中,我们将探讨以下主题:

  • 工商管理硕士
  • 关联规则学习
  • 各种领域中的其他应用

首先,我们将修改核心关联规则学习的概念和算法,如支持和提升 Apriori 算法和 FP-Growth 算法。接下来,我们将使用 Weka 对一个超市数据集执行我们的第一次相似性分析,并研究如何解释产生的规则。我们将通过分析关联规则学习如何应用于其他领域(如 IT 运营分析和医学)来结束本章。

市场篮子分析

自从引入电子销售点以来,零售商已经收集了数量惊人的数据。为了利用这些数据产生业务价值,他们首先开发了一种方法来整合和聚合数据,以了解业务的基础。

最近,焦点转移到最底层的粒度——市场篮交易。在这种详细程度下,零售商可以直接看到在他们商店购物的每个顾客的购物篮,不仅了解特定购物篮中购买的商品数量,还了解这些商品是如何相互搭配购买的。这可用于驱动关于如何区分商店分类和商品的决策,以及有效地组合多个类别内和跨类别的产品,以推动更高的销售额和利润。这些决策可以在整个零售链中、通过渠道、在当地商店层面、甚至针对特定客户实施,通过所谓的个性化营销,为每个客户提供独特的产品:

MBA 涵盖各种各样的分析:

  • 物品相似度:定义两件(或多件)物品被一起购买的可能性。
  • 驱动项的识别:可以识别驱动人们到商店并且总是需要库存的项目。
  • 行程分类:分析购物篮的内容,并将购物行程分类为一个类别:每周一次的购物行程、特殊场合等等。
  • 店与店比较:了解购物篮的数量可以让任何指标除以购物篮的总数,从而有效地创建一种方便简单的方法来比较不同特征的商店(每个顾客售出的数量、每笔交易的收入、每个购物篮的商品数量等等)。
  • 收入优化:这有助于确定这家商店的神奇价位,增加购物篮的规模和价值。
  • 营销:这有助于识别更有利可图的广告和促销活动,更精确地定位优惠以提高投资回报率,通过纵向分析产生更好的会员卡促销活动,并为商店吸引更多流量。
  • 运营优化:这有助于根据贸易区人口统计数据定制商店和商品组合,并优化商店布局,从而使库存与需求相匹配。

预测模型有助于零售商将正确的报价引导到正确的客户群或客户群,并了解什么对哪个客户有效,预测客户对该报价做出响应的概率得分,以及了解客户从接受报价中获得的价值。

亲和力分析

相似性分析用于确定一组商品被一起购买的可能性。在零售业,有天然的产品亲缘关系;例如,对于买汉堡肉饼的人来说,很典型的是买汉堡卷,以及番茄酱、芥末、西红柿和其他组成汉堡体验的物品。

虽然有些产品相似性可能看起来微不足道,但有些相似性并不十分明显。一个经典的例子是牙膏和金枪鱼。似乎吃金枪鱼的人更倾向于在吃完饭后马上刷牙。那么,为什么对零售商来说,很好地掌握产品亲缘关系很重要呢?该信息对于适当地计划促销是至关重要的,因为降低某些项目的价格可能会导致相关高亲和力项目的价格飙升,而无需进一步促销这些相关项目。

在下一节中,我们将研究关联规则学习的算法:Apriori 和 FP-Growth。

关联规则学习

关联规则学习已经成为在大型数据库中发现项目间有趣关系的流行方法。它最常用于零售业,以揭示产品之间的规律性。

关联规则学习方法使用不同的兴趣度在数据库中发现作为有趣的强规则的模式。例如,下面的规则表明,如果客户一起购买洋葱和土豆,他们可能也会购买汉堡肉:{洋葱,土豆}--> {汉堡}。

另一个可能在每个机器学习课上都会讲的经典故事是啤酒和尿布的故事。对超市购物者行为的分析显示,购买尿布的顾客,大概是年轻男性,也倾向于购买啤酒。它立即成为一个流行的例子,说明如何从日常数据中发现意想不到的关联规则;然而,对于这个故事有多少是真实的,人们有不同的看法。在《DSS 新闻 2002》中,丹尼尔·鲍尔 s 这样说道:

“1992 年,Teradata 零售咨询集团经理 Thomas Blischok 和他的工作人员对大约 25 家 Osco 药店的 120 万个市场篮进行了分析。开发数据库查询是为了识别亲缘关系。分析确实发现,在下午 5 点到 7 点之间,消费者会购买啤酒和尿布。Osco 经理没有利用啤酒和尿布的关系,将货架上的产品靠得更近。

除了前面 MBA 的例子之外,关联规则现在还应用于许多应用领域,包括 web 使用挖掘、入侵检测、连续生产和生物信息学。我们将在本章的后面更仔细地研究这些领域。

基本概念

在我们深入研究算法之前,让我们先回顾一下基本概念。

交易数据库

在关联规则挖掘中,数据集的结构与第一章中介绍的方法稍有不同。首先,没有类值,因为这不是学习关联规则所必需的。接下来,数据集被表示为一个事务表,其中每个超市商品对应一个二进制属性。因此,特征向量可能非常大。

考虑下面的例子。假设我们有四张收据,如下所示。每张收据对应一项采购交易:

为了以交易数据库的形式书写这些收据,我们首先识别收据中出现的所有可能的项目。这些物品是洋葱土豆汉堡啤酒蘸料。每一次购买,即交易,都在一行中呈现,如果是在交易中购买的,则有 1 ,否则有 0 ,如下表所示:

| 交易 ID | 洋葱 | 土豆 | 汉堡 | 啤酒 | 铲斗 |
| one | Zero | one | one | Zero | Zero |
| Two | one | one | one | one | Zero |
| three | Zero | Zero | Zero | one | one |
| four | one | Zero | one | one | Zero |

这个例子真的很小。在实际应用中,数据集通常包含数千或数百万个交易,这使得学习算法能够发现统计上显著的模式。

项目集和规则

Itemset 简单来说就是一组项目,例如,{洋葱,土豆,汉堡}。规则由两个项目集 X 和 Y 组成,格式如下:

X -> Y

这表明当观察 X 项目集时,也观察 Y 的模式。为了选择感兴趣的规则,可以使用各种显著性度量。

支持

对于项集,支持度被定义为包含该项集的事务的比例。上表中的{potatoes, burger}项集具有以下支持,因为它出现在 50%的事务中(四个事务中的两个):supp({ potatos,burger}) = 2/4 = 0.5。

直观地说,它表明了支持该模式的事务的份额。

电梯

Lift 是对目标模型(关联规则)在预测或分类具有增强响应(相对于总体而言)的病例时的性能的度量,根据随机选择目标模型进行测量。使用以下公式定义:

信心

规则的可信度表明它的准确性。使用以下公式定义:

![]

比如{洋葱,汉堡}--> {啤酒}规则,上表中的置信度 0.5/0.5 = 1.0 ,表示洋葱和汉堡一起买的时候,100%的时候啤酒也买。

Apriori 算法

Apriori 算法是一种经典算法,用于事务上的频繁模式挖掘和关联规则学习。通过识别数据库中频繁出现的单个项目并将其扩展到更大的项目集,Apriori 可以确定关联规则,这些规则突出了数据库的总体趋势。

Apriori 算法构造一组项集,例如 itemset1= {Item A,Item B},并计算支持度,支持度计算数据库中出现的次数。Apriori 然后使用自底向上的方法,在这种方法中,频繁项集被一次一项地扩展,它的工作方式是通过首先查看较小的集并认识到除非其所有子集都是频繁的,否则大型集不可能是频繁的,从而排除最大的集作为候选集。当没有找到进一步的成功扩展时,算法终止。

虽然 Apriori 算法是机器学习中的一个重要里程碑,但它存在许多低效和折衷之处。在下一节中,我们将研究一种更新的 FP-Growth 技术。

FP-增长算法

FP-Growth (其中 FP 是频繁模式)将事务数据库表示为后缀树。首先,该算法计算数据集中项目的出现次数。在第二遍中,它构建一个后缀树,这是一个有序的树数据结构,通常用于存储字符串。下图显示了基于上一个示例的后缀树示例:

如果许多事务共享最频繁的项目,后缀树提供接近树根的高压缩。大项集直接增长,而不是生成候选项并对整个数据库进行测试。通过找到匹配最小支持度和置信度的所有项目集,从树的底部开始增长。一旦递归过程完成,所有具有最小覆盖的大项目集都被找到,关联规则创建开始。

FP-Growth 算法有几个优点。首先,它构建了一个 FP-tree,以一种非常紧凑的方式对原始数据集进行编码。其次,它利用 FP-tree 结构和分治策略高效地构建频繁项集。

超市数据集

位于data/supermarket.arff的超市数据集描述了超市顾客的购物习惯。大多数属性代表特定的项目组,例如,乳制品、牛肉和土豆;或者它们代表一个部门,例如,79 部门、81 部门等等。下表显示了数据库的摘录,其中如果客户购买了一件商品,则值为 t ,否则将丢失。每个客户有一个实例。数据集不包含类属性,因为这不是学习关联规则所必需的。下表显示了一个数据示例:

发现模式

为了发现购物模式,我们将使用我们之前研究过的两种算法:Apriori 和 FP-Growth。

推测的

我们将使用 Weka 中实现的Apriori算法。它迭代地减少最小支持度,直到找到所需数量的具有给定最小置信度的规则。我们将使用以下步骤实现该算法:

  1. 我们将使用以下代码行导入所需的库:
import java.io.BufferedReader; 
import java.io.FileReader; 
import weka.core.Instances; 
import weka.associations.Apriori; 
  1. 首先,我们将加载supermarket.arff数据集:
Instances data = new Instances(new BufferedReader(new FileReader("data/supermarket.arff"))); 
  1. 我们将初始化一个Apriori实例并调用buildAssociations(Instances)函数来开始频繁模式挖掘,如下所示:
Apriori model = new Apriori(); 
model.buildAssociations(data); 
  1. 我们可以输出发现的项目集和规则,如下面的代码所示:
System.out.println(model); 

输出如下所示:

   Apriori
    =======

    Minimum support: 0.15 (694 instances)
    Minimum metric <confidence>: 0.9
    Number of cycles performed: 17

    Generated sets of large itemsets:
    Size of set of large itemsets L(1): 44
    Size of set of large itemsets L(2): 380
    Size of set of large itemsets L(3): 910
    Size of set of large itemsets L(4): 633
    Size of set of large itemsets L(5): 105
    Size of set of large itemsets L(6): 1

    Best rules found:

     1\. biscuits=t frozen foods=t fruit=t total=high 788 ==> bread and cake=t 723    <conf:(0.92)> lift:(1.27) lev:(0.03) [155] conv:(3.35)
     2\. baking needs=t biscuits=t fruit=t total=high 760 ==> bread and cake=t 696    <conf:(0.92)> lift:(1.27) lev:(0.03) [149] conv:(3.28)
     3\. baking needs=t frozen foods=t fruit=t total=high 770 ==> bread and cake=t 705    <conf:(0.92)> lift:(1.27) lev:(0.03) [150] conv:(3.27)
    ...

该算法根据置信度输出 10 个最佳规则。让我们看看第一条规则,并解释输出,如下所示:

biscuits=t frozen foods=t fruit=t total=high 788 ==> bread and cake=t 723    <conf:(0.92)> lift:(1.27) lev:(0.03) [155] conv:(3.35)

上面说当biscuitsfrozen foodsfruits一起购买,购买总价高的时候,也很有可能breadcake也被购买。{biscuits, frozen foods, fruit, total high}项集出现在788事务中,而{bread, cake}项集出现在723事务中。该规则的置信度是0.92,这意味着该规则在存在{biscuits, frozen foods, fruit, total high}项集的 92%的事务中成立。

输出还报告了额外的度量,如提升、杠杆和信念,这些度量根据我们的初始假设来估计准确性;例如,3.35信念值表明,如果关联纯粹是随机的,则该规则将错误3.35倍。如果 X 和 Y 在统计上是独立的(lift=1),Lift 测量它们一起出现的次数。X - > Y 法则中的2.16升力意味着 X 的概率比 Y 的概率大2.16

FP-增长

现在,让我们试着用更有效的 FP-Growth 算法得到同样的结果。
FP-Growth 也在weka.associations包中实现:

import weka.associations.FPGrowth; 

FP-Growth 算法的初始化类似于我们前面所做的:

FPGrowth fpgModel = new FPGrowth(); 
fpgModel.buildAssociations(data); 
System.out.println(fpgModel); 

输出显示 FP-Growth 发现了16 rules:

    FPGrowth found 16 rules (displaying top 10)

    1\. [fruit=t, frozen foods=t, biscuits=t, total=high]: 788 ==> [bread and cake=t]: 723   <conf:(0.92)> lift:(1.27) lev:(0.03) conv:(3.35) 
    2\. [fruit=t, baking needs=t, biscuits=t, total=high]: 760 ==> [bread and cake=t]: 696   <conf:(0.92)> lift:(1.27) lev:(0.03) conv:(3.28) 
    ...

我们可以观察到 FP-Growth 发现了与 Apriori 相同的一套规则;然而,处理较大数据集所需的时间可以大大缩短。

各种领域的其他应用

我们研究了亲和力分析来揭开超市购物行为模式的神秘面纱。虽然关联规则学习的基础是分析销售点交易,但它们也可以应用于零售业之外,以发现其他类型的购物篮之间的关系。购物篮的概念可以很容易地扩展到服务和产品,例如,分析使用信用卡购买的项目,如租赁汽车和酒店房间,以及分析电信客户购买的增值服务的信息(呼叫等待、呼叫转移、DSL、快速呼叫等),这可以帮助运营商确定改进服务套餐捆绑的方法。

此外,我们将研究以下潜在跨行业应用的示例:

  • 医疗诊断
  • 蛋白质序列
  • 普查数据
  • 客户关系管理
  • IT 运营分析

医疗诊断

在医疗诊断中应用关联规则可以用来在治疗病人时帮助医生。归纳可靠的诊断规则的一般问题是困难的,因为从理论上讲,没有归纳过程可以保证归纳假设本身的正确性。实际上,诊断不是一个简单的过程,因为它涉及不可靠的诊断测试和训练样本中存在的噪声。

然而,关联规则可以用来识别一起出现的可能症状。在这种情况下,事务对应于医学案例,而症状对应于项目。当患者接受治疗时,症状列表被记录为一个交易。

蛋白质序列

许多研究已经深入了解蛋白质的组成和性质;然而,许多事情仍有待于令人满意地理解。现在普遍认为蛋白质的氨基酸序列不是随机的。

使用关联规则,可以识别蛋白质中不同氨基酸之间的关联。蛋白质是由 20 种氨基酸组成的序列。每种蛋白质都有独特的三维结构,这取决于氨基酸序列;序列的微小变化可能会改变蛋白质的功能。应用关联规则,一个蛋白质对应一个事务,氨基酸及其结构对应条目。

这种关联规则对于增强我们对蛋白质组成的理解是可取的,并且有可能提供关于蛋白质中出现的一些特定氨基酸组之间的全局相互作用的线索。人工蛋白质的合成非常需要这些关联规则或约束的知识。

普查数据

人口普查为研究人员和公众提供了大量关于社会的一般统计信息。与人口和经济普查相关的信息可以在规划公共服务(教育、卫生、交通和资金)以及商业(建立新工厂、购物中心或银行,甚至营销特定产品)时进行预测。

为了发现频繁模式,每个统计区域(例如,自治市、城市和街区)对应于一个事务,收集的指标对应于项目。

客户关系管理

正如我们在前面章节中简要讨论的那样,客户关系管理是一个丰富的数据来源,公司希望通过它来识别不同客户群、产品和服务的偏好,以增强他们的产品、服务和客户之间的凝聚力。

关联规则可以强化知识管理过程,让营销人员更好地了解客户,提供更优质的服务。例如,可以应用关联规则从客户档案和销售数据中检测不同时间快照的客户行为变化。基本思想是从两个数据集发现变化,从每个数据集生成规则,进行规则匹配。

IT 运营分析

基于大量交易的记录,关联规则学习非常适合应用于日常 IT 运营中定期收集的数据,使 IT 运营分析工具能够检测频繁模式并识别关键变化。IT 专家需要看到全局并理解,例如,数据库上的问题如何影响应用服务器。

对于特定的一天,IT 运营部门可能会接收各种警报,并将它们呈现在事务性数据库中。使用关联规则学习算法,IT 运营分析工具可以关联和检测频繁出现的警报模式。这有助于更好地理解一个组件如何影响另一个组件。

通过识别警报模式,可以应用预测分析。例如,一个特定的数据库服务器托管了一个 web 应用程序,突然触发了一个关于数据库的警报。通过研究由关联规则学习算法识别的频繁模式,这意味着 IT 人员需要在 web 应用程序受到影响之前采取行动。

关联规则学习还可以发现源自同一 IT 事件的警报事件。例如,每次添加新用户时,都会检测到 Windows 操作系统中的六个变化。接下来,在应用组合管理 ( APM )中,它可能会面临多个警报,显示数据库中的事务时间很长。如果所有这些问题都源于同一个来源(例如,收到数百个关于全部由 Windows 更新引起的更改的警报),这种频繁的模式挖掘有助于快速排除大量警报,使 IT 操作员能够专注于真正关键的更改。

摘要

在本章中,您学习了如何利用事务数据集上的关联规则学习来深入了解频繁模式。我们在 Weka 中进行了关联性分析,并了解到困难在于结果分析——在解释规则时需要仔细注意,因为关联(即相关性)与因果关系不同。

在下一章中,我们将看看如何使用可扩展的机器学习库 Apache Mahout 将商品推荐问题提升到一个新的水平,Apache Mahout 能够处理大数据。

六、Apache Mahout 推荐引擎

推荐引擎是当今初创公司中应用最广泛的数据科学方法之一。构建推荐系统有两种主要技术:基于内容的过滤和协同过滤。基于内容的算法使用项目的属性来查找具有相似属性的项目。协同过滤算法采用用户评级或其他用户行为,并根据具有相似行为的用户喜欢或购买的内容进行推荐。

在本章中,我们将首先解释理解推荐引擎原理所需的基本概念,然后我们将演示如何利用 Apache Mahout 的各种算法的实现来快速获得可扩展的推荐引擎。

本章将涵盖以下主题:

  • 如何构建推荐引擎
  • 准备好 Apache Mahout
  • 基于内容的方法
  • 协作过滤方法

在本章结束时,你将会学到适合我们问题的推荐引擎的种类,以及如何快速实现这个引擎。

基本概念

推荐引擎旨在向用户展示感兴趣的项目。与搜索引擎不同的是,相关内容通常在没有被请求的情况下出现在网站上,用户不必构建查询,因为推荐引擎会观察用户的行为,并在用户不知情的情况下为用户构建查询。

可以说,推荐引擎最著名的例子是 www.amazon.com,它以多种方式提供个性化推荐。下面的屏幕截图显示了购买了该商品的客户也购买了该商品的示例。稍后您将会看到,这是一个基于项目的协作推荐示例,其中推荐与特定项目相似的项目:

在本节中,我们将介绍与理解和构建推荐引擎相关的关键概念。

关键概念

推荐引擎需要以下输入才能做出推荐:

  • 项目信息,用属性描述
  • 用户资料,如年龄范围、性别、位置、朋友等
  • 用户交互,以评级、浏览、标记、比较、保存和发送电子邮件的形式
  • 将显示项目的上下文;例如,项目的类别和项目的地理位置

该输入然后被推荐引擎组合以帮助获得以下内容:

  • 购买、观看、查看或书签标记了该项目的用户也购买、观看、查看或书签标记了该项
  • 与此项类似的项目
  • 您可能认识的其他用户
  • 与您相似的其他用户

现在,让我们仔细看看这种组合是如何工作的。

基于用户和基于项目的分析

构建推荐引擎取决于当试图推荐特定项目时,引擎是否搜索相关项目或用户。

在基于项目的分析中,引擎侧重于识别与特定项目相似的项目,而在基于用户的分析中,首先确定与特定用户相似的用户。例如,确定具有相同简档信息(年龄、性别等)或行为历史(购买、观看、观看等)的用户,然后将相同的项目推荐给其他类似的用户。

这两种方法都需要我们计算相似性矩阵,这取决于我们是在分析物品属性还是用户行为。让我们更深入地了解一下这是如何做到的。

计算相似度

有三种计算相似性的基本方法,如下所示:

  • 协同过滤算法采用用户评级或其他用户行为,并根据具有相似行为的用户喜欢或购买的内容进行推荐
  • 基于内容的算法使用项目的属性来查找具有相似属性的项目
  • 一种混合方法结合了协作过滤和基于内容的过滤

让我们在接下来的小节中详细了解一下每种方法。

协同过滤

协同过滤完全基于用户评分或其他用户行为,根据具有相似行为的用户喜欢或购买的内容进行推荐。

协同过滤的一个关键优势是它不依赖于项目内容,因此,它能够准确地推荐复杂的项目,例如电影,而无需了解项目本身。潜在的假设是,过去同意的人将来也会同意,并且他们会喜欢与他们过去喜欢的东西相似的东西。

这种方法的一个主要缺点是所谓的冷启动,这意味着如果我们想要建立一个精确的协同过滤系统,算法往往需要大量的用户评级。这通常会在产品的第一个版本中去掉协同过滤,然后在收集了大量数据后再引入。

基于内容的过滤

另一方面,基于内容的过滤是基于项目的描述和用户偏好的简档,其组合如下。首先,用属性描述项目,为了找到相似的项目,我们使用距离度量来测量项目之间的距离,例如余弦距离或皮尔逊系数(在第一章、应用机器学习快速入门中有更多关于距离度量的内容)。现在,用户配置文件开始起作用了。给定关于用户喜欢的项目种类的反馈,我们可以引入权重,指定特定项目属性的重要性。例如,Pandora 广播流媒体服务应用基于内容的过滤来创建电台,使用 400 多个属性。用户最初挑选具有特定属性的歌曲,并且通过提供反馈,强调重要的歌曲属性。

最初,这种方法需要很少的用户反馈信息;因此,它有效地避免了冷启动问题。

混合工艺

现在,在协作和基于内容之间,你应该选择哪一个?协同过滤能够从用户关于一个内容源的动作中学习用户偏好,并在其他内容类型中使用它们。基于内容的过滤仅限于推荐用户已经在使用的相同类型的内容。这在某些用例中提供了价值;比如基于新闻浏览推荐新闻文章是有用的,但是如果基于新闻浏览可以推荐不同的来源,比如书籍、电影,那就有用得多了。

协同过滤和基于内容的过滤并不相互排斥;在某些情况下,它们可以结合起来更有效。例如,网飞使用协同过滤来分析相似用户的搜索和观看模式,以及基于内容的过滤来提供与用户高度评价的电影具有共同特征的电影。

有各种各样的杂交技术:加权、切换和混合、特征组合、特征增强、级联、元级等等。推荐系统是机器学习和数据挖掘社区中的一个活跃领域,在数据科学会议上有专门的跟踪。Adomavicius 和 Tuzhilin (2005)在论文中对技术进行了很好的概述,该论文讨论了不同的方法和底层算法,并为后续论文提供了参考。为了获得更多的技术知识并理解某个特定方法有意义时的所有微小细节,你应该看看 Ricci 等人编辑的书:推荐系统手册(2010 年第一版,出版社

开发与探索

在推荐系统中,根据我们对用户的了解,在推荐落入用户最佳位置的项目(开发)和推荐不落入用户最佳位置的项目之间总是有一个权衡,目的是向用户展示一些新奇的东西(探索)。很少探索的推荐系统将仅推荐与先前用户评级一致的项目,从而防止显示其当前气泡之外的项目。在实践中,从用户的甜蜜点中获得新项目的意外收获通常是可取的,这导致了令人愉快的惊喜,并且潜在地发现了新的甜蜜点。

在本节中,我们讨论了开始构建推荐引擎所需的基本概念。现在,让我们看看如何用 Apache Mahout 实际构建一个。

获取 Apache Mahout

Mahout 是在第二章、 Java 库和机器学习平台中介绍的,作为一个可扩展的机器学习库。它提供了一组丰富的组件,您可以使用这些组件从选择的算法中构建一个定制的推荐系统。Mahout 的创建者说它是为企业准备的;它专为性能、可扩展性和灵活性而设计。

Mahout 可以被配置为以两种方式运行:使用或不使用 Hadoop,以及分别用于单机和分布式处理。我们将着重于在没有 Hadoop 的情况下配置 Mahout。关于 Mahout 更高级的配置和进一步的使用,我推荐两本最近的书:学习 Apache Mahout,作者 C handramani Tiwary,Packt 出版社,学习 Apache Mahout 分类,作者 Ashish Gupta,Packt 出版社。

因为 Apache Mahout 的构建和发布系统是基于 Maven 的,所以您需要学习如何安装它。我们将研究最方便的方法;使用 Eclipse 和 Maven 插件。

用 Maven 插件在 Eclipse 中配置 Mahout

你需要一个最新版本的 Eclipse,可以从它的主页(www.eclipse.org/downloads/)下载。在本书中,我们将使用 Eclipse Luna。打开 Eclipse 并使用默认设置启动一个新的 Maven 项目,如下面的屏幕截图所示:

将出现新的 Maven 项目屏幕,如下面的屏幕截图所示:

现在,我们需要告诉项目将 Mahout JAR 文件及其依赖项添加到项目中。找到pom.xml文件,用文本编辑器打开它(左键单击用|文本编辑器打开),如下面的截图所示:

找到以<dependencies>开始的行,并在下一行添加以下代码:

<dependency> 
 <groupId>org.apache.mahout</groupId> 
  <artifactId>mahout-mr</artifactId> 
  <version>0.10.0</version> 
</dependency> 

就是这样;已经添加了看象人,我们准备开始了。

构建推荐引擎

为了演示基于内容的过滤和协作过滤方法,我们将构建一个图书推荐引擎。

图书评分数据集

在这一章中,我们将使用一个图书评级数据集(齐格勒等人,2005 年),该数据集是在为期四周的搜索中收集的。它包含了图书交叉网站的 278,858 个成员的数据和 1,157,112 个隐含和明确的评级,涉及 271,379 个不同的 ISBNs。用户数据是匿名的,但带有人口统计信息。数据集摘自通过主题多样化改进推荐列表C ai-Nicolas Ziegler、Sean M. McNee、Joseph A. Konstan、Georg Lausen: 第 14 届国际万维网会议论文集 (WWW '05) 2005 年 5 月 10-14 日,日本千叶(www2.informatik.uni-freiburg.de/~cziegler/BX/)。

跨帐簿数据集由以下三个文件组成:

  • BX-Users:包含用户。注意,用户 ID(User-ID)已经被匿名化并映射到整数。如果可能,提供人口统计数据(位置和年龄)。否则,这些字段包含空值。
  • 书籍由它们各自的 ISBNs 来识别。无效的 ISBNs 已从数据集中删除。此外,给出了一些基于内容的信息(书名、作者、出版年份和出版商),这些信息是从 Amazon Web Services 获得的。注意,在有几个作者的情况下,只提供第一作者。还给出链接到封面图片的 URL,以三种不同的风格出现(Image-URL-S、Image-URL-M 和 Image-URL-L),分别指小型、中型和大型 URL。这些网址指向亚马逊网站。
  • BX-Book-Ratings:包含图书评级信息。评级(书籍评级)要么是显性的,用 1-10 的等级表示(数值越高表示欣赏程度越高),要么是隐性的,用 0 表示。

加载数据

根据数据存储的位置,有两种加载数据的方法:文件或数据库。首先,我们将详细了解如何从文件中加载数据,包括如何处理自定义格式。最后,我们将快速看一下如何从数据库加载数据。

从文件加载数据

从文件中加载数据可以通过FileDataModel类来实现。我们将期待一个逗号分隔的文件,其中每行包含一个userID、一个itemID、一个可选的preference值和一个可选的timestamp,顺序相同,如下所示:

userID,itemID[,preference[,timestamp]] 

可选偏好适应具有二进制偏好值的应用,也就是说,用户要么表达对项目的偏好,要么不表达,没有偏好程度;例如,喜欢或不喜欢。

以散列(#)或空行开头的行将被忽略。这些行包含附加字段也是可以接受的,这些字段将被忽略。

DataModel类采用以下类型:

  • userIDitemID可以被解析为long
  • preference值可以解析为double
  • timestamp可以解析为long

如果您能够以前面的格式提供数据集,您可以简单地使用下面的行来加载数据:

DataModel model = new FileDataModel(new File(path)); 

此类不适用于大量数据;比如几千万行。为此,一个 JDBC 支持的DataModel和一个数据库更合适。

然而,在现实世界中,我们不能总是确保提供给我们的输入数据只包含userIDitemID的整数值。例如,在我们的例子中,itemID对应于 ISBN 图书编号,它惟一地标识了项目,但是这些不是整数,FileDataModel默认值不适合处理我们的数据。

现在,让我们考虑如何处理我们的itemID是一个字符串的情况。我们将通过扩展FileDataModel并覆盖长的readItemIDFromString(String)方法来定义我们的自定义数据模型,以便将itemID作为字符串读取并将其转换为long,并返回唯一的long值。为了将一个String转换成一个唯一的long,我们将扩展另一个 Mahout AbstractIDMigrator助手类,它正是为此任务而设计的。

现在,让我们看看FileDataModel是如何扩展的:

class StringItemIdFileDataModel extends FileDataModel { 

  //initialize migrator to covert String to unique long 
  public ItemMemIDMigrator memIdMigtr; 

  public StringItemIdFileDataModel(File dataFile, String regex) 
     throws IOException { 
    super(dataFile, regex); 
  } 

  @Override 
  protected long readItemIDFromString(String value) { 

    if (memIdMigtr == null) { 
      memIdMigtr = new ItemMemIDMigrator(); 
    } 

    // convert to long 
    long retValue = memIdMigtr.toLongID(value); 
    //store it to cache  
    if (null == memIdMigtr.toStringID(retValue)) { 
      try { 
        memIdMigtr.singleInit(value); 
      } catch (TasteException e) { 
        e.printStackTrace(); 
      } 
    } 
    return retValue; 
  } 

  // convert long back to String 
  String getItemIDAsString(long itemId) { 
    return memIdMigtr.toStringID(itemId); 
  } 
} 

其他可以被覆盖的有用方法如下:

  • readUserIDFromString(String value),如果用户 id 不是数字
  • readTimestampFromString(String value),改变timestamp的解析方式

现在,让我们看看AbstractIDMIgrator是如何扩展的:

class ItemMemIDMigrator extends AbstractIDMigrator { 

  private FastByIDMap<String> longToString; 

  public ItemMemIDMigrator() { 
    this.longToString = new FastByIDMap<String>(10000); 
  } 

  public void storeMapping(long longID, String stringID) { 
    longToString.put(longID, stringID); 
  } 

  public void singleInit(String stringID) throws TasteException { 
    storeMapping(toLongID(stringID), stringID); 
  } 

  public String toStringID(long longID) { 
    return longToString.get(longID); 
  } 
} 

现在,一切就绪,我们可以用下面的代码加载数据集:

StringItemIdFileDataModel model = new StringItemIdFileDataModel( 
  new File("datasets/chap6/BX-Book-Ratings.csv"), ";"); 
System.out.println( 
"Total items: " + model.getNumItems() +  
"\nTotal users: " +model.getNumUsers()); 

这提供了用户和项目的总数作为输出:

    Total items: 340556
    Total users: 105283

我们准备继续前进,并开始提出建议。

从数据库加载数据

或者,我们可以使用一个 JDBC 数据模型从数据库中加载数据。在这一章中,我们将不会深入到如何设置数据库、连接等的详细说明中,但是我们将给出一个如何实现这一点的草图。

数据库连接器已经被移动到一个单独的包中,mahout-integration;因此,我们必须将这个包添加到我们的dependency列表中。打开pom.xml文件,添加以下dependency:

<dependency> 
  <groupId>org.apache.mahout</groupId> 
  <artifactId>mahout-integration</artifactId> 
  <version>0.7</version> 
</dependency> 

假设我们想要连接到一个 MySQL 数据库。在这种情况下,我们还需要一个处理数据库连接的包。将以下内容添加到pom.xml文件中:

<dependency> 
  <groupId>mysql</groupId> 
  <artifactId>mysql-connector-java</artifactId> 
  <version>5.1.35</version> 
</dependency> 

现在,我们有了所有的包,所以我们可以创建一个连接。首先,让我们用连接细节初始化一个DataSource类,如下所示:

MysqlDataSource dbsource = new MysqlDataSource(); 
  dbsource.setUser("user"); 
  dbsource.setPassword("pass"); 
  dbsource.setServerName("hostname.com"); 
  dbsource.setDatabaseName("db"); 

Mahout 集成实现了可以通过 JDBC 访问的各种数据库。默认情况下,该类假设在 JNDI 名jdbc/taste下有一个可用的DataSource,它允许访问一个带有
taste_preferences表的数据库,模式如下:

CREATE TABLE taste_preferences ( 
  user_id BIGINT NOT NULL, 
  item_id BIGINT NOT NULL, 
  preference REAL NOT NULL, 
  PRIMARY KEY (user_id, item_id) 
) 
CREATE INDEX taste_preferences_user_id_index ON taste_preferences 
   (user_id); 
CREATE INDEX taste_preferences_item_id_index ON taste_preferences 
   (item_id); 

数据库支持的数据模型初始化如下。除了 DB 连接对象,我们还可以指定自定义表名和表列名,如下所示:

DataModel dataModel = new MySQLJDBCDataModel(dbsource, 
   "taste_preferences",  
  "user_id", "item_id", "preference", "timestamp"); 

内存数据库

最后但同样重要的是,数据模型可以动态创建并保存在内存中。可以从一组偏好中创建一个数据库,该数据库将保存一组项目的用户评级。

我们可以如下进行。首先,我们创建一个偏好数组的FastByIdMap散列映射PreferenceArray,它存储一个偏好数组:

FastByIDMap <PreferenceArray> preferences = new FastByIDMap 
   <PreferenceArray> ();  

接下来,我们可以为用户创建一个新的偏好数组来保存他们的评级。该数组必须用一个 size 参数初始化,该参数在内存中保留这么多的槽:

PreferenceArray prefsForUser1 =  
  new GenericUserPreferenceArray (10);   

接下来,我们在位置0设置当前偏好的用户 ID。这将实际设置所有首选项的用户 ID:

prefsForUser1.setUserID (0, 1L);  

在位置0为当前偏好设置一个itemID,如下所示:

prefsForUser1.setItemID (0, 101L);  

0设置首选项的首选项值,如下所示:

prefsForUser1.setValue (0, 3.0f);   

继续其他项目评级,如下所示:

prefsForUser1.setItemID (1, 102L);  
prefsForUser1.setValue (1, 4.5F);  

最后,将用户preferences添加到哈希映射中:

preferences.put (1L, prefsForUser1); // use userID as the key  

偏好散列图现在可以用来初始化GenericDataModel:

DataModel dataModel = new GenericDataModel(preferences); 

这段代码演示了如何为单个用户添加两个首选项;在实际应用中,您会希望为多个用户添加多个首选项。

协同过滤

Mahout 中的推荐引擎可以用org.apache.mahout.cf.taste包构建,它以前是一个名为Taste的独立项目,并在 Mahout 中继续开发。

基于 Mahout 的协作过滤引擎获取用户对项目的偏好(口味),并返回对其他项目的估计偏好。例如,一个销售书籍或 CD 的网站可以很容易地使用 Mahout,在以前购买数据的帮助下,找出客户可能有兴趣听的 CD。

顶层包将 Mahout 接口定义为以下关键抽象:

  • 数据模型(data model):这是一个关于用户和他们对项目的偏好的信息库
  • 用户相似性:这定义了两个用户之间相似性的概念
  • 项目相似性:这定义了两个项目之间相似性的概念
  • UserNeighborhood :计算给定用户的邻居用户
    • 推荐者:为用户推荐商品

下图显示了上述概念的一般结构:

基于用户的过滤

最基本的基于用户的协同过滤可以通过初始化前面描述的组件来实现,如下所示:

首先,加载数据模型:

StringItemIdFileDataModel model = new StringItemIdFileDataModel( 
    new File("/datasets/chap6/BX-Book-Ratings.csv", ";"); 

接下来,定义如何计算用户之间的相关性;例如,使用皮尔逊相关性:

UserSimilarity similarity =  
  new PearsonCorrelationSimilarity(model); 

接下来,定义如何根据用户的评级来区分哪些用户是相似的,即哪些用户彼此很接近:

UserNeighborhood neighborhood =  
  new ThresholdUserNeighborhood(0.1, similarity, model); 

现在,我们可以用modelneighborhood和类似对象的数据初始化一个GenericUserBasedRecommender默认引擎,如下所示:

UserBasedRecommender recommender =  
new GenericUserBasedRecommender(model, neighborhood, similarity); 

就是这样。我们的第一个基本推荐引擎已经准备好了。让我们讨论如何调用建议。首先,让我们打印用户已经评分的项目,以及对该用户的十条推荐:

long userID = 80683; 
int noItems = 10; 

List<RecommendedItem> recommendations = recommender.recommend( 
  userID, noItems); 

System.out.println("Rated items by user:"); 
for(Preference preference : model.getPreferencesFromUser(userID)) { 
  // convert long itemID back to ISBN 
  String itemISBN = model.getItemIDAsString( 
  preference.getItemID()); 
  System.out.println("Item: " + books.get(itemISBN) +  
    " | Item id: " + itemISBN +  
    " | Value: " + preference.getValue()); 
} 

System.out.println("\nRecommended items:"); 
for (RecommendedItem item : recommendations) { 
  String itemISBN = model.getItemIDAsString(item.getItemID()); 
  System.out.println("Item: " + books.get(itemISBN) +  
    " | Item id: " + itemISBN +  
    " | Value: " + item.getValue()); 
} 

这将提供以下建议及其分数作为输出:

    Rated items:
    Item: The Handmaid's Tale | Item id: 0395404258 | Value: 0.0
    Item: Get Clark Smart : The Ultimate Guide for the Savvy Consumer | Item id: 1563526298 | Value: 9.0
    Item: Plum Island | Item id: 0446605409 | Value: 0.0
    Item: Blessings | Item id: 0440206529 | Value: 0.0
    Item: Edgar Cayce on the Akashic Records: The Book of Life | Item id: 0876044011 | Value: 0.0
    Item: Winter Moon | Item id: 0345386108 | Value: 6.0
    Item: Sarah Bishop | Item id: 059032120X | Value: 0.0
    Item: Case of Lucy Bending | Item id: 0425060772 | Value: 0.0
    Item: A Desert of Pure Feeling (Vintage Contemporaries) | Item id: 0679752714 | Value: 0.0
    Item: White Abacus | Item id: 0380796155 | Value: 5.0
    Item: The Land of Laughs : A Novel | Item id: 0312873115 | Value: 0.0
    Item: Nobody's Son | Item id: 0152022597 | Value: 0.0
    Item: Mirror Image | Item id: 0446353957 | Value: 0.0
    Item: All I Really Need to Know | Item id: 080410526X | Value: 0.0
    Item: Dreamcatcher | Item id: 0743211383 | Value: 7.0
    Item: Perplexing Lateral Thinking Puzzles: Scholastic Edition | Item id: 0806917695 | Value: 5.0
    Item: Obsidian Butterfly | Item id: 0441007813 | Value: 0.0

    Recommended items:
    Item: Keeper of the Heart | Item id: 0380774933 | Value: 10.0
    Item: Bleachers | Item id: 0385511612 | Value: 10.0
    Item: Salem's Lot | Item id: 0451125452 | Value: 10.0
    Item: The Girl Who Loved Tom Gordon | Item id: 0671042858 | Value: 10.0
    Item: Mind Prey | Item id: 0425152898 | Value: 10.0
    Item: It Came From The Far Side | Item id: 0836220730 | Value: 10.0
    Item: Faith of the Fallen (Sword of Truth, Book 6) | Item id: 081257639X | Value: 10.0
    Item: The Talisman | Item id: 0345444884 | Value: 9.86375
    Item: Hamlet | Item id: 067172262X | Value: 9.708363
    Item: Untamed | Item id: 0380769530 | Value: 9.708363

基于项目的过滤

ItemSimilarity属性是这里要讨论的最重要的一点。基于项目的推荐器是有用的,因为它们可以很快地利用某些东西;他们的计算基于项目相似性,而不是用户相似性,并且项目相似性是相对静态的。它可以预先计算,而不是实时重新计算。

因此,如果您打算使用这个类,强烈建议您使用预计算相似性的GenericItemSimilarity。您也可以使用PearsonCorrelationSimilarity,它可以实时计算相似性,但是您可能会发现对于大量数据来说,这非常慢:

StringItemIdFileDataModel model = new StringItemIdFileDataModel( 
  new File("datasets/chap6/BX-Book-Ratings.csv"), ";"); 

ItemSimilarity itemSimilarity = new 
   PearsonCorrelationSimilarity(model); 

ItemBasedRecommender recommender = new 
   GenericItemBasedRecommender(model, itemSimilarity); 

String itemISBN = "0395272238"; 
long itemID = model.readItemIDFromString(itemISBN); 
int noItems = 10; 
List<RecommendedItem> recommendations = 
   recommender.mostSimilarItems(itemID, noItems); 

System.out.println("Recommendations for item: 
   "+books.get(itemISBN)); 

System.out.println("\nMost similar items:"); 
for (RecommendedItem item : recommendations) { 
  itemISBN = model.getItemIDAsString(item.getItemID()); 
  System.out.println("Item: " + books.get(itemISBN) + " | Item id: 
     " + itemISBN + " | Value: " + item.getValue()); 
} 
Recommendations for item: Close to the BoneMost similar items:Item: Private Screening | Item id: 0345311396 | Value: 1.0Item: Heartstone | Item id: 0553569783 | Value: 1.0Item: Clockers / Movie Tie In | Item id: 0380720817 | Value: 1.0Item: Rules of Prey | Item id: 0425121631 | Value: 1.0Item: The Next President | Item id: 0553576666 | Value: 1.0Item: Orchid Beach (Holly Barker Novels (Paperback)) | Item id: 0061013412 | Value: 1.0Item: Winter Prey | Item id: 0425141233 | Value: 1.0Item: Night Prey | Item id: 0425146413 | Value: 1.0Item: Presumed Innocent | Item id: 0446359866 | Value: 1.0Item: Dirty Work (Stone Barrington Novels (Paperback)) | Item id: 
   0451210158 | Value: 1.0

结果列表返回一组与我们选择的特定项目相似的项目。

向建议添加自定义规则

经常发生的情况是,一些业务规则要求我们提高所选项目的分数。例如,在图书数据集中,如果一本书是最近的,我们希望给它一个较高的分数。这可以通过使用IDRescorer接口来实现,如下所示:

  • rescore(long, double)itemId和原始分数作为参数,并返回修改后的分数
  • isFiltered(long)返回true从建议中排除特定项目,否则返回false

我们的示例可以实现如下:

class MyRescorer implements IDRescorer { 

  public double rescore(long itemId, double originalScore) { 
    double newScore = originalScore; 
    if(bookIsNew(itemId)){ 
      originalScore *= 1.3; 
    } 
    return newScore; 
  } 

  public boolean isFiltered(long arg0) { 
    return false; 
  } 

} 

调用recommender.recommend时提供了IDRescorer的实例:

IDRescorer rescorer = new MyRescorer(); 
List<RecommendedItem> recommendations =  
recommender.recommend(userID, noItems, rescorer); 

估价

您可能想知道如何确保返回的建议有意义。真正确定推荐有多有效的唯一方法是在一个真实用户的系统中使用 A/B 测试。例如,A 组收到一个随机的项目作为推荐,而 B 组收到一个由我们的引擎推荐的项目。

由于这并不总是可能的(也不实际),我们可以通过离线统计评估得到一个估计值。一种方法是使用 k-fold 交叉验证,这在第一章、应用机器学习快速入门中有介绍。我们将一个数据集划分成多个集合;一些用于训练我们的推荐引擎,其余的用于测试它向未知用户推荐商品的效果如何。

Mahout 实现了RecommenderEvaluator类,它将数据集分成两部分。第一部分(默认为 90%)用于产生推荐,而其余的数据与估计的偏好值进行比较,以测试匹配情况。该类不直接接受recommender对象;您需要构建一个实现RecommenderBuilder接口的类,它为给定的DataModel对象构建一个recommender对象,然后用于测试。让我们来看看这是如何实现的。

首先,我们创建一个实现RecommenderBuilder接口的类。我们需要实现buildRecommender方法,它将返回一个recommender,如下所示:

public class BookRecommender implements RecommenderBuilder  { 
  public Recommender buildRecommender(DataModel dataModel) { 
    UserSimilarity similarity =  
      new PearsonCorrelationSimilarity(model); 
    UserNeighborhood neighborhood =  
      new ThresholdUserNeighborhood(0.1, similarity, model); 
    UserBasedRecommender recommender =  
      new GenericUserBasedRecommender( 
        model, neighborhood, similarity); 
    return recommender; 
  } 
} 

现在我们有了一个返回推荐者对象的类,我们可以初始化一个RecommenderEvaluator实例。这个类的默认实现是AverageAbsoluteDifferenceRecommenderEvaluator类,它为用户计算预测和实际评分之间的平均绝对差值。下面的代码显示了如何将各个部分组合在一起,并运行保持测试:

首先,加载一个数据模型,如下所示:

DataModel dataModel = new FileDataModel( 
  new File("/path/to/dataset.csv")); 

接下来,初始化一个evaluator实例,如下所示:

RecommenderEvaluator evaluator =  
  new AverageAbsoluteDifferenceRecommenderEvaluator(); 

初始化BookRecommender对象,实现RecommenderBuilder接口,如下所示:

RecommenderBuilder builder = new MyRecommenderBuilder(); 

最后,调用evaluate()方法,该方法接受以下参数:

  • RecommenderBuilder:这是实现RecommenderBuilder的对象,可以构建recommender进行测试
  • DataModelBuilder:表示要使用的DataModelBuilder;如果为空,将使用默认的DataModel实现
  • DataModel:这是将用于测试的数据集
  • trainingPercentage:这表示用于产生推荐的每个用户偏好的百分比;其余的与估计的偏好值进行比较,以评估recommender的性能
  • evaluationPercentage:这是评估中使用的用户百分比

该方法的调用如下:

double result = evaluator.evaluate(builder, null, model, 0.9, 
   1.0); 
System.out.println(result); 

该方法返回一个double,其中0代表可能的最佳评价,意味着推荐者完全符合用户偏好。一般来说,值越低,匹配越好。

在线学习引擎

在任何一个在线平台,新用户都会不断增加。前面讨论的方法对现有用户很有效。为每个添加的新用户创建一个推荐实例是非常昂贵的。我们不能忽视在推荐引擎制作完成后添加到系统中的用户。为了处理类似的情况,Apache Mahout 能够向数据模型添加一个临时用户。
一般设置如下:

  • 使用当前数据定期重新创建整个建议(例如,每天或每小时,取决于需要多长时间)
  • 在寻求建议之前,请始终检查用户是否存在于系统中
  • 如果用户存在,则完成建议
  • 如果用户不存在,则创建一个临时用户,填写首选项,然后进行推荐

第一步似乎很棘手,涉及到使用当前数据生成整个推荐的频率。如果系统很大,将会有存储器限制,因为当新的推荐器被生成时,旧的、工作的推荐器应该被保存在存储器中,所以请求从旧的拷贝被提供,直到新的推荐器准备好。

对于临时用户,我们可以用一个PlusAnonymousConcurrentUserDataModel实例包装我们的数据模型。这个类允许我们获得一个临时用户 ID;ID 必须在以后被释放,以便可以重用(这种 ID 的数量是有限的)。获得 ID 后,我们必须填写首选项,然后我们可以继续推荐,一如既往:

class OnlineRecommendation{ 

  Recommender recommender; 
  int concurrentUsers = 100; 
  int noItems = 10; 

  public OnlineRecommendation() throws IOException { 

    DataModel model = new StringItemIdFileDataModel( 
      new File /chap6/BX-Book-Ratings.csv"), ";"); 
    PlusAnonymousConcurrentUserDataModel plusModel = new 
       PlusAnonymousConcurrentUserDataModel
         (model, concurrentUsers); 
    recommender = ...; 

  } 

  public List<RecommendedItem> recommend(long userId, 
     PreferenceArray preferences){ 

    if(userExistsInDataModel(userId)){ 
      return recommender.recommend(userId, noItems); 
    } 

    else{ 

      PlusAnonymousConcurrentUserDataModel plusModel = 
        (PlusAnonymousConcurrentUserDataModel) 
           recommender.getDataModel(); 

      // Take an available anonymous user form the poll 
      Long anonymousUserID = plusModel.takeAvailableUser(); 

      // Set temporary preferences 
      PreferenceArray tempPrefs = preferences; 
      tempPrefs.setUserID(0, anonymousUserID); 
      tempPrefs.setItemID(0, itemID); 
       plusModel.setTempPrefs(tempPrefs, anonymousUserID); 

      List<RecommendedItem> results = 
         recommender.recommend(anonymousUserID, noItems); 

      // Release the user back to the poll 
      plusModel.releaseUser(anonymousUserID); 

      return results; 

    } 

  } 
} 

基于内容的过滤

基于内容的过滤超出了 Mahout 框架的范围,主要是因为如何定义类似的项目取决于您。如果我们想做一个基于内容的项目相似性,我们需要实现我们自己的ItemSimilarity。例如,在我们的书的数据集中,我们可能想要为书的相似性建立以下规则:

  • 如果类型相同,将0.15添加到similarity
  • 如果作者相同,在similarity上加上0.50

我们现在可以实现我们自己的similarity度量,如下所示:

class MyItemSimilarity implements ItemSimilarity { 
 ... 
 public double itemSimilarity(long itemID1, long itemID2) { 
  MyBook book1 = lookupMyBook (itemID1); 
  MyBook book2 = lookupMyBook (itemID2); 
  double similarity = 0.0; 
  if (book1.getGenre().equals(book2.getGenre())  
   similarity += 0.15; 
  } 
  if (book1.getAuthor().equals(book2\. getAuthor ())) { 
   similarity += 0.50; 
  } 
  return similarity; 
 } 
 ... 
} 

然后我们可以使用这个ItemSimilarity,而不是类似LogLikelihoodSimilarity的东西,或者其他带有GenericItemBasedRecommender的实现。大概就是这样。这是我们在 Mahout 框架中执行基于内容的推荐所必须做的。

我们在这里看到的是一种最简单的基于内容的推荐形式。另一种方法是基于项目特征的加权向量,创建基于内容的用户简档。权重表示每个特征对用户的重要性,并且可以根据单独评级的内容向量来计算。

摘要

在本章中,您了解了推荐引擎的基本概念,协作过滤和基于内容的过滤之间的区别,以及如何使用 Apache Mahout,这是创建推荐器的一个很好的基础,因为它是非常可配置的,并且提供了许多扩展点。我们研究了如何选择正确的配置参数值、设置重新评分以及评估推荐结果。

在本章中,我们完成了对用于分析客户行为的数据科学技术的概述,首先是第四章中的客户关系预测、中的客户关系预测,然后是第五章、亲和力分析中的亲和力分析。在下一章,我们将继续讨论其他主题,如欺诈和异常检测。

七、欺诈和异常检测

异常值检测用于识别异常、罕见事件和其他异常情况。这种异常现象可能是大海捞针,但其后果却可能相当惊人;例如,信用卡欺诈检测、识别网络入侵、制造过程中的故障、临床试验、投票活动以及电子商务中的犯罪活动。因此,异常在被发现时代表着高价值,如果没有被发现则代表着高成本。将机器学习应用于异常检测问题可以带来新的见解和更好的异常事件检测。机器学习可以考虑许多不同的数据源,并可以找到人类分析难以识别的相关性。

以电子商务欺诈检测为例。有了机器学习算法,购买者的在线行为,即网站浏览历史,就成为欺诈检测算法的一部分,而不仅仅是持卡人的购买历史。这涉及分析各种数据源,但这也是一种更强大的电子商务欺诈检测方法。

在本章中,我们将讨论以下主题:

  • 问题和挑战
  • 可疑模式检测
  • 异常模式检测
  • 使用不平衡的数据集
  • 时间序列中的异常检测

可疑和异常行为检测

从传感器数据中学习模式的问题出现在许多应用中,包括电子商务、智能环境、视频监控、网络分析、人机交互、环境辅助生活等等。我们专注于检测偏离常规行为的模式,这些模式可能代表安全风险、健康问题或任何其他异常行为。

换句话说,异常行为是不符合预期行为(异常行为)或符合先前定义的不想要的行为(可疑行为)的数据模式。异常行为模式也被称为异常值、例外、怪癖、意外、误用等等。这种模式出现的频率相对较低;然而,当它们真的发生时,其后果可能是相当戏剧性的,而且往往是负面的。典型的例子包括信用卡欺诈、网络入侵和工业破坏。在电子商务中,据估计欺诈每年给商家造成的损失超过 2000 亿美元;在医疗保健领域,据估计,欺诈每年要花费纳税人 600 亿美元;对银行来说,成本超过 120 亿美元。

未知的未知

2002 年 2 月 12 日,美国国防部长唐纳德·拉姆斯菲尔德在一次新闻发布会上说,没有证据表明伊拉克政府向恐怖组织提供大规模杀伤性武器,这立即成为人们议论纷纷的话题。拉姆斯菲尔德声明如下(国防部新闻,2012 年):

“我总是对那些说某事尚未发生的报道感兴趣,因为正如我们所知,有已知的已知;有些事情我们知道我们知道。我们也知道有已知的未知;也就是说,我们知道有些事情我们不知道。但也有未知的未知——那些我们不知道我们不知道的。如果纵观我们国家和其他自由国家的历史,后一类往往是困难的。”

这种说法乍一看似乎令人困惑,但未知的未知的概念在处理风险、NSA 和其他情报机构的学者中得到了很好的研究。该声明的基本含义如下:

  • 已知已知:这些都是众所周知的问题;我们知道如何识别它们,如何处理它们
  • 已知未知:这些是预期或可预见的问题,可以合理地预见,但以前没有发生过
  • 未知的未知:这些是意料之外和不可预见的问题,根据以往的经验,这些问题是无法预测的,因此会带来重大风险

在下面的部分中,我们将研究处理前两种类型的已知和未知的两种基本方法:处理已知的可疑模式检测,以及针对已知未知的异常模式检测。

可疑模式检测

第一种方法涉及一个行为库,它对否定模式进行编码,在下图中显示为红色减号,并识别观察到的行为对应于识别库中的匹配。如果一个新模式可以与否定模式匹配,那么它就被认为是可疑的:

例如,当你去看医生时,他/她会检查各种健康症状(体温、疼痛程度、患处等),并将症状与已知疾病进行匹配。用机器学习的术语来说,医生收集属性并进行分类。

这种方法的一个优点是,我们可以立即知道哪里出了问题;例如,假设我们知道疾病,我们可以选择一个适当的治疗程序。

这种方法的一个主要缺点是它只能检测预先已知的可疑模式。如果一个模式没有被插入到否定模式库中,那么我们将不能识别它。因此,这种方法适用于已知知识的建模。

异常模式检测

第二种方法以相反的方式使用模式库,这意味着该库只对积极的模式进行编码,这些模式在下图中用绿色加号标记。当观察到的行为(蓝色圆圈)无法与库匹配时,它被认为是异常的:

这种方法要求我们只对过去所见的建模,即正常模式。如果我们回到医生的例子,我们首先去看医生的主要原因是因为我们感觉不舒服。我们感觉到的感觉状态(例如,头痛和皮肤疼痛)与我们通常的感觉不符,因此,我们决定去看医生。我们不知道是哪种疾病导致了这种状态,也不知道治疗方法,但我们能够观察到这与通常的状态不符。

这种方法的一个主要优点是它不需要我们说任何关于异常模式的事情;因此,它适用于建模已知的未知和未知的未知。另一方面,它并没有告诉我们到底出了什么问题。

分析类型

已经提出了几种方法来解决这个问题。我们将异常和可疑行为检测大致分为以下三类:模式分析、交易分析和计划识别。在接下来的几节中,我们将快速了解一些实际应用。

模式分析

从模式中检测异常和可疑行为的活跃区域是基于视觉模态的,例如照相机。张等人(2007)提出了一种从视频序列中进行视觉人体运动分析的系统,该系统基于行走轨迹识别异常行为;林等人(2009)描述了一种基于颜色特征、距离特征和计数特征的视频监控系统,其中进化技术用于测量观察相似性。该系统跟踪每个人,并通过分析他们的轨迹模式对他们的行为进行分类。该系统在图像的不同部分提取一组视觉低级特征,并用支持向量机进行分类,以便检测攻击性、愉快、陶醉、紧张、中性和疲劳行为。

交易分析

与连续观察相反,事务分析假设离散的状态/事务。一个主要的研究领域是入侵检测 ( ID ),目的是检测对信息系统的攻击。有两种类型的 ID 系统,基于特征的和基于异常的,大体上遵循前面部分描述的可疑和异常模式检测。Gyanchandani 等人(2012 年)发表了一篇关于 ID 方法的综合综述。

此外,基于可穿戴传感器的环境辅助生活中的应用也适合于交易分析,因为感测通常是基于事件的。林贝洛保罗斯等人(2008)提出了一个系统,用于自动提取用户的时空模式,编码为来自他们家中部署的传感器网络的传感器激活。所提出的方法基于位置、时间和持续时间,能够使用 Apriori 算法提取频繁模式,并以马尔可夫链的形式编码最频繁的模式。相关工作的另一个领域包括隐马尔可夫模型 ( HMMs ),这些模型在传统的活动识别中被广泛用于对一系列动作进行建模,但这些主题已经超出了本书的范围。

计划识别

计划识别侧重于一种机制,用于识别一个代理的不可观察状态,给定其与环境的相互作用的观察(Avrahami-Zilberbrand,2009)。大多数现有的调查假设离散的观察活动的形式。为了执行异常和可疑行为检测,计划识别算法可以使用混合方法。符号计划识别器用于过滤一致的假设,并将它们传递给评估引擎,该引擎关注排名。

这些是应用于各种现实生活场景的高级方法,旨在发现异常。在接下来的几节中,我们将深入探讨更基本的可疑和异常模式检测方法。

使用 ELKI 的异常检测

ELKI 代表采用 KDD 应用索引结构的环境,其中 KDD 代表数据库中的知识发现 。这是一个开源软件,主要用于数据挖掘,重点是无监督学习。它支持聚类分析和离群点检测的各种算法。以下是一些异常值算法:

  • 基于距离的异常值检测:用于指定两个参数。对于距离 c 大于 d 的所有数据对象,如果其分数 p,则对象被标记为离群值,有许多算法,如DBOutlierDetectionDBOutlierScoreKNNOutlierKNNWeightOutlierParallelKNNOutlierParallelKNNWeightOutlierReferenceBasedOutlierDetection等等。
  • LOF 家族方法:计算特定参数上基于密度的局部异常因子。包括LOFParallelLOFALOCICOFLDFLDOF等算法。
  • 基于角度的异常值检测:使用角度的方差分析,主要使用高维数据集。常见的算法有ABODFastABODLBABOD
  • 基于聚类的离群点检测:使用 EM 聚类;如果该对象不属于某个聚类,则将其视为异常值。这包括EMOutlierKMeansOutlierDetection等算法。
  • 子空间离群点检测:这使用轴平行子空间的离群点检测方法。它有SODOutRankS1OUTRESAggrawalYuNaiveAggrawalYuEvolutionary等算法。
  • 空间异常值检测:这是一个大型数据集,基于从不同来源收集的位置和相对于邻居极端的数据点。它有CTLuGLSBackwardSearchAlgorithmCTLuMeanMultipleAttributesCTLuMedianAlgorithmCTLuScatterplotOutlier等算法。

使用 ELKI 的示例

在第三章、基本算法——分类、回归和聚类中,您已经看到了如何获得 ELKI 所需的.jar文件。我们将遵循类似的流程,如下所示:

打开命令提示符或终端,并执行以下命令:

java -jar elki-bundle-0.7.1.jar

这将提供 GUI 界面,如下面的屏幕截图所示:

在 GUI 中,dbc.in 和算法参数会突出显示,并且需要进行设置。我们将使用pov.csv文件作为 dbc.in。这个 CSV 文件可以从github . com/elki-project/elki/blob/master/data/synthetic/ABC-publication/POV . CSV下载。

对于算法,选择 outlier.clustering.EMOutlier,在 em.k 中,传递3作为值。以下屏幕截图显示了所有已填写的选项:

单击 Run Task 按钮,它将处理并生成以下输出:

这显示了聚类和可能的异常值。

保险索赔中的欺诈检测

首先,我们将看看可疑行为检测,其目标是了解欺诈模式,这对应于对已知知识进行建模。

资料组

我们将使用描述保险交易的数据集,该数据集在 Oracle 数据库在线文档中公开提供,网址为docs . Oracle . com/CD/b 28359 _ 01/data mine . 111/b 28129/anomalies . htm

该数据集描述了一家未披露的保险公司对车辆事故的保险索赔。它包含 15,430 项索赔;每个索赔由 33 个属性组成,描述了以下组件:

  • 客户人口统计信息(年龄、性别、婚姻状况等)
  • 购买的保单(保单类型、车辆类别、补充数量、代理类型等)
  • 索赔情况(索赔日/月/周、提交的保单报告、证人在场、事故-保单报告、事故索赔之间的过去天数等)
  • 其他客户数据(汽车数量、以前的索赔、驾驶评级等)
  • 发现欺诈(是或否)

以下屏幕截图中显示的数据库示例描述了已经加载到 Weka 中的数据:

现在的任务是创建一个模型,将来能够识别可疑的索赔。这项任务的挑战性在于,只有 6%的声明是可疑的。如果我们创建一个虚拟分类器,说没有索赔是可疑的,它将在 94%的情况下是准确的。因此,在这个任务中,我们将使用不同的准确性度量:精确度和召回率。

让我们回忆一下第一章、应用机器学习快速入门中的结果表,其中有四种可能的结果,分别表示为真阳性、假阳性、假阴性和真阴性:

| | | 归类为 |
| 实际 | | 诈骗 | 没有欺诈 |
| 诈骗 | TP -真阳性 | FN -假阴性 |
| 没有欺诈 | FP -假阳性 | TN -真阴性 |

精确度和召回率定义如下:

  • 精度等于正确发出警报的比例,如下:

  • 召回等于异常签名的比例,正确识别如下:

  • 通过这些衡量标准——我们的虚拟分类器得分——我们发现 Pr = 0Re = 0 ,因为它从未将任何实例标记为欺诈( TP = 0 )。在实践中,我们希望通过两个数字来比较分类器;因此,我们使用 F 值。这是一个事实上的度量,它计算精度和召回率之间的调和平均值,如下所示:

现在,让我们继续设计一个真正的分类器。

模拟可疑模式

要设计一个分类器,我们可以遵循标准的监督学习步骤,如第一章、应用机器学习快速入门中所述。在这个方法中,我们将包括一些额外的步骤来处理不平衡的数据集,并基于精度和召回率评估分类器。计划如下:

  1. 加载.csv格式的数据。
  2. 分配类属性。
  3. 将所有属性从数值转换为标称值,以确保没有错误加载的数值。
  4. 实验一:用 k 重交叉验证评估模型。
  5. 实验二:将数据集重新平衡到更均衡的类分布,手动进行交叉验证。
  6. 通过召回率、精确度和 f-measure 比较分类器。

首先,让我们使用CSVLoader类加载数据,如下所示:

String filePath = "/Users/bostjan/Dropbox/ML Java Book/book/datasets/chap07/claims.csv"; 

CSVLoader loader = new CSVLoader(); 
loader.setFieldSeparator(","); 
loader.setSource(new File(filePath)); 
Instances data = loader.getDataSet(); 

接下来,我们需要确保所有的属性都是名义上的。在数据导入过程中,Weka 应用一些试探法来猜测最可能的属性类型,即数字、名义、字符串或日期。由于试探法不能总是猜测正确的类型,我们可以手动设置类型,如下所示:

NumericToNominal toNominal = new NumericToNominal(); 
toNominal.setInputFormat(data); 
data = Filter.useFilter(data, toNominal); 

在我们继续之前,我们需要指定我们将尝试预测的属性。我们可以通过调用setClassIndex(int)函数来实现这一点:

int CLASS_INDEX = 15; 
data.setClassIndex(CLASS_INDEX); 

接下来,我们需要删除描述策略编号的属性,因为它没有预测价值。我们简单地应用Remove过滤器,如下所示:

Remove remove = new Remove(); 
remove.setInputFormat(data); 
remove.setOptions(new String[]{"-R", ""+POLICY_INDEX}); 
data = Filter.useFilter(data, remove); 

现在,我们准备开始建模。

普通的方法

普通的方法是直接应用经验教训,就像在第三章、中演示的那样——基本算法——分类、回归、聚类,没有任何预处理,也没有考虑数据集细节。为了演示普通方法的缺点,我们将简单地用默认参数构建一个模型,并应用 k-fold 交叉验证。

首先,让我们定义一些我们想要测试的分类器,如下所示:

ArrayList<Classifier>models = new ArrayList<Classifier>(); 
models.add(new J48()); 
models.add(new RandomForest()); 
models.add(new NaiveBayes()); 
models.add(new AdaBoostM1()); 
models.add(new Logistic()); 

接下来,我们需要创建一个Evaluation对象,并通过调用crossValidate(Classifier, Instances, int, Random, String[])方法执行 k 重交叉验证,提供precisionrecallfMeasure作为输出:

int FOLDS = 3; 
Evaluation eval = new Evaluation(data); 

for(Classifier model : models){ 
  eval.crossValidateModel(model, data, FOLDS,  
  new Random(1), new String[] {}); 
  System.out.println(model.getClass().getName() + "\n"+ 
    "\tRecall:    "+eval.recall(FRAUD) + "\n"+ 
    "\tPrecision: "+eval.precision(FRAUD) + "\n"+ 
    "\tF-measure: "+eval.fMeasure(FRAUD)); 
} 

评估提供以下分数作为输出:

    weka.classifiers.trees.J48
      Recall:    0.03358613217768147
      Precision: 0.9117647058823529
      F-measure: 0.06478578892371996
    ...
    weka.classifiers.functions.Logistic
      Recall:    0.037486457204767065
      Precision: 0.2521865889212828
      F-measure: 0.06527070364082249

我们可以看到结果不太乐观。召回率,即发现的欺诈在所有欺诈中所占的份额,仅为 1-3%,这意味着只有 1-3/100 的欺诈被检测到。另一方面,精确度,即警报的准确性,是 91%,这意味着在 9/10 的情况下,当一个索赔被标记为欺诈时,该模型是正确的。

数据集重新平衡

因为与正面例子相比,反面例子(即欺诈的实例)的数量非常少,所以学习算法难以进行归纳。我们可以通过给他们一个数据集来帮助他们,在这个数据集里,正面和负面例子的比例是可以比较的。这可以通过数据集重新平衡来实现。

Weka 有一个内置的过滤器Resample,它使用替换或不替换的采样来产生数据集的随机子样本。过滤器还可以使分布偏向统一的类别分布。

我们将通过手动实现 k 倍交叉验证来继续。首先,我们将把数据集分成 k 等份。折叠 k 将用于测试,而其他折叠将用于学习。为了将数据集分割成折叠,我们将使用StratifiedRemoveFolds过滤器,该过滤器保持折叠内的类分布,如下所示:

StratifiedRemoveFolds kFold = new StratifiedRemoveFolds(); 
kFold.setInputFormat(data); 

double measures[][] = new double[models.size()][3]; 

for(int k = 1; k <= FOLDS; k++){ 

  // Split data to test and train folds 
  kFold.setOptions(new String[]{ 
    "-N", ""+FOLDS, "-F", ""+k, "-S", "1"}); 
  Instances test = Filter.useFilter(data, kFold); 

  kFold.setOptions(new String[]{ 
    "-N", ""+FOLDS, "-F", ""+k, "-S", "1", "-V"}); 
    // select inverse "-V" 
  Instances train = Filter.useFilter(data, kFold); 

接下来,我们可以重新平衡训练数据集,其中-Z参数指定要重新采样的数据集的百分比,而-B使类分布偏向均匀分布:

Resample resample = new Resample(); 
resample.setInputFormat(data); 
resample.setOptions(new String[]{"-Z", "100", "-B", "1"}); //with 
   replacement 
Instances balancedTrain = Filter.useFilter(train, resample); 

接下来,我们可以构建分类器并执行评估:

for(ListIterator<Classifier>it = models.listIterator(); 
   it.hasNext();){ 
  Classifier model = it.next(); 
  model.buildClassifier(balancedTrain); 
  eval = new Evaluation(balancedTrain); 
  eval.evaluateModel(model, test); 

// save results for average 
  measures[it.previousIndex()][0] += eval.recall(FRAUD); 
  measures[it.previousIndex()][1] += eval.precision(FRAUD); 
 measures[it.previousIndex()][2] += eval.fMeasure(FRAUD); 
} 

最后,我们计算平均值,并使用以下代码行提供最佳模型作为输出:

// calculate average 
for(int i = 0; i < models.size(); i++){ 
  measures[i][0] /= 1.0 * FOLDS; 
  measures[i][1] /= 1.0 * FOLDS; 
  measures[i][2] /= 1.0 * FOLDS; 
} 

// output results and select best model 
Classifier bestModel = null; double bestScore = -1; 
for(ListIterator<Classifier> it = models.listIterator(); 
   it.hasNext();){ 
  Classifier model = it.next(); 
  double fMeasure = measures[it.previousIndex()][2]; 
  System.out.println( 
    model.getClass().getName() + "\n"+ 
    "\tRecall:    "+measures[it.previousIndex()][0] + "\n"+ 
    "\tPrecision: "+measures[it.previousIndex()][1] + "\n"+ 
    "\tF-measure: "+fMeasure); 
  if(fMeasure > bestScore){ 
    bestScore = fMeasure; 
    bestModel = model; 

  } 
} 
System.out.println("Best model:"+bestModel.getClass().getName()); 

现在,模型的性能有了显著提高,如下所示:

    weka.classifiers.trees.J48
      Recall:    0.44204845100610574
      Precision: 0.14570766048577555
      F-measure: 0.21912423640160392
    ...
    weka.classifiers.functions.Logistic
      Recall:    0.7670657247204478
      Precision: 0.13507459756495374
      F-measure: 0.22969038530557626
    Best model: weka.classifiers.functions.Logistic

我们可以看到所有的模型都得到了显著的提高;例如,最好的模型,逻辑回归,正确地发现了 76%的欺诈,同时产生了合理数量的假警报——只有 13%被标记为欺诈的索赔确实是欺诈的。如果未被发现的欺诈比调查假警报的成本高得多,那么处理越来越多的假警报是有意义的。

整体性能很可能仍有一些改进的空间;我们可以执行属性选择和特征生成,并应用更复杂的模型学习,我们在第三章、中讨论了基本算法——分类、回归、聚类

网站流量中的异常检测

在第二个例子中,我们将重点关注与前一个例子相反的建模。我们将讨论系统的正常预期行为,而不是讨论什么是典型的无欺诈案例。如果某些东西不能与我们预期的模型相匹配,它将被认为是异常的。

资料组

我们将使用雅虎发布的公开数据集。实验室,这对于讨论如何检测时间序列数据中的异常非常有用。对雅虎来说,主要的用例是检测雅虎服务器上不寻常的流量。

即使雅虎已经宣布他们的数据是公开的,你必须申请使用它,并且在批准之前需要大约 24 小时。数据集在webscope.sandbox.yahoo.com/catalog.php?datatype=s&did = 70可用。

该数据集由雅虎服务的真实流量和一些合成数据组成。该数据集总共包含 367 个时间序列,每个时间序列包含 741 至 1,680 个观测值,这些观测值是定期记录的。每个系列都写在自己的文件中,每行一个观察值。一个系列伴随着第二个列指示器,如果观察值是异常值,则使用 1,否则使用 0。真实数据中的异常是由人的判断确定的,而合成数据中的异常是通过算法生成的。下表显示了合成时间序列数据的一个片段:

在下一节中,您将了解如何将时间序列数据转换为属性表示,以便我们应用机器学习算法。

时间序列数据中的异常检测

检测原始流时间序列数据中的异常需要一些数据转换。最明显的方法是选择一个时间窗口,以固定长度对时间序列进行采样。下一步,我们希望将新的时间序列与之前收集的时间序列进行比较,以检测是否有异常。

可以使用各种技术进行比较,如下所示:

  • 预测最可能的后续值以及置信区间(例如,霍尔特-温特斯指数平滑)。如果一个新值超出了预测的置信区间,则被认为是异常的。
  • 互相关将新样本与阳性样本库进行比较,寻找完全匹配的样本。如果没有找到匹配,则标记为异常。
  • 动态时间缠绕类似于互相关,但相比之下允许信号失真。
  • 将信号离散化为带,每个带对应一个字母。例如,A=[min, mean/3]B=[mean/3, mean*2/3]C=[mean*2/3, max]将信号转换为字母序列,例如aAABAACAABBA....这种方法减少了存储空间,并允许我们应用文本挖掘算法,我们将在第十章、使用 Mallet 进行文本挖掘-主题建模和垃圾邮件检测中讨论这些算法。
  • 基于分布的方法估计特定时间窗口中值的分布。当我们观察一个新的样本时,我们可以比较这个分布是否与之前观察到的分布相匹配。

这份清单绝非详尽无遗。不同的方法侧重于检测不同的异常(例如,值、频率和分布)。在这一章中,我们将集中讨论一种基于分布的方法。

对时间序列使用 Encog

我们必须从 https://solarscience.msfc.nasa.gov/greenwch/spot_num.txt下载时间序列数据,并将文件保存在data文件夹中。在.java文件中,我们将指定文件路径,然后我们将使用以下代码块指示文件的格式:

File filename = new File("data/spot_num.txt");
CSVFormat format = new CSVFormat('.', ' ');
VersatileDataSource source = new CSVDataSource(filename, true, format);
VersatileMLDataSet data = new VersatileMLDataSet(source);
data.getNormHelper().setFormat(format);
ColumnDefinition columnSSN = data.defineSourceColumn("SSN", ColumnType.continuous);
ColumnDefinition columnDEV = data.defineSourceColumn("DEV", ColumnType.continuous);
data.analyze();
data.defineInput(columnSSN);
data.defineInput(columnDEV);
data.defineOutput(columnSSN);

现在,我们将创建窗口大小为1的前馈网络。当处理一个时间序列时,你应该记住它不应该被打乱。我们将保留一些数据进行验证。我们将使用下面几行代码来实现这一点:

EncogModel model = new EncogModel(data);
model.selectMethod(data, MLMethodFactory.TYPE_FEEDFORWARD);

model.setReport(new ConsoleStatusReportable());
data.normalize();

// Set time series.
data.setLeadWindowSize(1);
data.setLagWindowSize(WINDOW_SIZE);
model.holdBackValidation(0.3, false, 1001);
model.selectTrainingType(data);

下一步是使用以下代码行运行带有五重交叉验证的培训:

MLRegression bestMethod = (MLRegression) model.crossvalidate(5, false);

现在,是时候显示错误和最终模型了。我们将通过使用以下代码行来实现这一点:

System.out.println("Training error: " + model.calculateError(bestMethod, model.getTrainingDataset()));
System.out.println("Validation error: " + model.calculateError(bestMethod, model.getValidationDataset()));

NormalizationHelper helper = data.getNormHelper();
System.out.println(helper.toString());

// Display the final model.
System.out.println("Final model: " + bestMethod);

输出将类似于下面的屏幕截图:

现在,我们将使用以下代码块测试该模型:

while (csv.next() && stopAfter > 0) {
                StringBuilder result = new StringBuilder();

                line[0] = csv.get(2);// ssn
                line[1] = csv.get(3);// dev
                helper.normalizeInputVector(line, slice, false);

                if (window.isReady()) {
                    window.copyWindow(input.getData(), 0);
                    String correct = csv.get(2); // trying to predict SSN.
                    MLData output = bestMethod.compute(input);
                    String predicted = helper
                            .denormalizeOutputVectorToString(output)[0];

                    result.append(Arrays.toString(line));
                    result.append(" -> predicted: ");
                    result.append(predicted);
                    result.append("(correct: ");
                    result.append(correct);
                    result.append(")");

                    System.out.println(result.toString());
                }

                window.add(slice);

                stopAfter--;
            }

输出将类似于下面的屏幕截图:

基于直方图的异常检测

在基于直方图的异常检测中,我们通过选定的时间窗口分割信号,如下图所示。

对于每个窗口,我们计算直方图;也就是说,对于选定数量的桶,我们计算每个桶中有多少个值。直方图捕获了所选时间窗口中值的基本分布,如图表中心所示。

然后直方图可以直接呈现为实例,其中每个柱对应于一个属性。此外,我们可以通过应用降维技术来减少属性的数量,例如主成分分析 ( PCA ),它允许我们在图中可视化降维直方图,如图表的右下角所示,其中每个点对应一个直方图。

在我们的例子中,想法是观察几天的网站流量,然后创建直方图;例如,四个小时的时间窗,来建立一个积极行为库。如果新的时间窗口直方图不能与阳性库匹配,我们可以将其标记为异常:

为了将新直方图与一组现有直方图进行比较,我们将使用基于密度的 k-最近邻算法,局部异常因子 ( LOF ) (Breunig 等人,2000)。该算法能够处理不同密度的分类,如下图所示。例如,右上侧的集群较大且分布广泛,而左下侧的集群较小且密度较大:

我们开始吧!

加载数据

第一步,我们需要将文本文件中的数据加载到一个 Java 对象中。这些文件存储在一个文件夹中,每个文件包含一个时间序列,每行有一个值。我们将它们加载到一个Double列表中,如下所示:

String filePath = "chap07/ydata/A1Benchmark/real"; 
List<List<Double>> rawData = new ArrayList<List<Double>>(); 

我们将需要minmax值来进行直方图归一化;所以,让我们在这个数据通道中收集它们:

double max = Double.MIN_VALUE; 
double min = Double.MAX_VALUE; 

for(int i = 1; i<= 67; i++){ 
  List<Double> sample = new ArrayList<Double>(); 
  BufferedReader reader = new BufferedReader(new 
     FileReader(filePath+i+".csv")); 

  boolean isAnomaly = false; 
  reader.readLine(); 
  while(reader.ready()){ 
    String line[] = reader.readLine().split(","); 
    double value = Double.parseDouble(line[1]); 
    sample.add(value); 

    max = Math.max(max, value); 
    min = Double.min(min, value); 

    if(line[2] == "1") 
      isAnomaly = true; 

  } 
  System.out.println(isAnomaly); 
  reader.close(); 

  rawData.add(sample); 
} 

数据已经加载。接下来,让我们继续讨论直方图。

创建直方图

我们将用WIN_SIZE宽度为选定的时间窗口创建一个直方图。

直方图将保存HIST_BINS值桶。由双精度列表组成的直方图将存储在数组列表中:

int WIN_SIZE = 500; 
int HIST_BINS = 20; 
int current = 0; 

List<double[]> dataHist = new ArrayList<double[]>(); 
for(List<Double> sample : rawData){ 
  double[] histogram = new double[HIST_BINS]; 
  for(double value : sample){ 
    int bin = toBin(normalize(value, min, max), HIST_BINS); 
    histogram[bin]++; 
    current++; 
    if(current == WIN_SIZE){ 
      current = 0; 
      dataHist.add(histogram); 
      histogram = new double[HIST_BINS]; 
    } 
  } 
  dataHist.add(histogram); 
} 

直方图现已完成。最后一步是将它们转换成 Weka 的Instance对象。每个直方图值对应一个 Weka 属性,如下所示:

ArrayList<Attribute> attributes = new ArrayList<Attribute>(); 
for(int i = 0; i<HIST_BINS; i++){ 
  attributes.add(new Attribute("Hist-"+i)); 
} 
Instances dataset = new Instances("My dataset", attributes, 
   dataHist.size()); 
for(double[] histogram: dataHist){ 
  dataset.add(new Instance(1.0, histogram)); 
} 

数据集现在已经加载完毕,可以插入异常检测算法了。

基于密度的 k 近邻

为了演示 LOF 如何计算分数,我们将首先使用testCV(int, int)函数将数据集分成训练集和测试集。第一个参数指定折叠的次数,而第二个参数指定要返回哪个折叠:

// split data to train and test 
Instances trainData = dataset.testCV(2, 0); 
Instances testData = dataset.testCV(2, 1); 

LOF 算法不是默认 Weka 发行版的一部分,但可以通过 Weka 的包管理器下载,网址是Weka . SourceForge . net/package metadata/localooutlierfactor/index . html

LOF 算法有两个实现的接口:作为计算 LOF 值(已知未知)的非监督过滤器,以及作为监督的 k-最近邻分类器(已知已知)。在我们的例子中,我们想要计算异常因子,因此,我们将使用无监督过滤器接口:

import weka.filters.unsupervised.attribute.LOF; 

该过滤器的初始化方式与普通过滤器相同。我们可以用-min-max参数指定k个邻居(例如k=3)。LOF允许我们指定两个不同的k参数,在内部用作上限和下限,以找到最小或最大数量的lof值:

LOF lof = new LOF(); 
lof.setInputFormat(trainData); 
lof.setOptions(new String[]{"-min", "3", "-max", "3"}); 

接下来,我们将训练实例加载到过滤器中,该过滤器将充当正面示例库。完成加载后,我们将调用batchFinished()方法来初始化内部计算:

for(Instance inst : trainData){ 
  lof.input(inst); 
} 
lof.batchFinished(); 

最后,我们可以将过滤器应用于测试数据。Filter()函数将处理实例并在末尾附加一个附加属性,包含 LOF 分数。我们可以简单地在控制台中提供分数作为输出:

Instances testDataLofScore = Filter.useFilter(testData, lof); 

for(Instance inst : testDataLofScore){ 
  System.out.println(inst.value(inst.numAttributes()-1)); 
} 

前几个测试实例的 LOF 分数如下:

    1.306740014927325
    1.318239332210458
    1.0294812291949587
    1.1715039094530768

为了理解LOF值,我们需要一些 LOF 算法的背景知识。它将一个实例的密度与其最近邻居的密度进行比较。这两个分数相除,产生 LOF 分数。LOF 值约为 1 表示密度大致相等,而较高的 LOF 值表示实例的密度远低于其相邻实例的密度。在这种情况下,实例可以被标记为异常。

摘要

在这一章中,我们研究了异常和可疑模式的检测。我们讨论了两种基本的方法,集中在库编码上,无论是积极的还是消极的模式。接下来,我们接触了两个真实的数据集,并讨论了如何处理不平衡的类分布以及如何对时间序列数据执行异常检测。

在下一章中,我们将更深入地研究模式和更高级的方法来构建基于模式的分类器,并讨论如何使用深度学习自动为图像分配标签。

八、使用 DL4J 的图像识别

图像在网络服务、社交网络和网络商店中无处不在。与人类相反,计算机很难理解图像中的内容及其代表的意义。在这一章中,我们将首先看看教计算机如何理解图像背后的挑战,然后重点关注一种基于深度学习的方法。我们将查看配置深度学习模型所需的高级理论,并讨论如何使用 Java 库 Deeplearning4j 实现能够对图像进行分类的模型。

本章将涵盖以下主题:

  • 图像识别简介
  • 讨论深度学习基础知识
  • 建立图像识别模型

图像识别简介

图像识别的典型目标是检测和识别数字图像中的对象。图像识别在工厂自动化中应用于监控产品质量;识别潜在危险活动的监视系统,如移动人员或车辆;通过指纹、虹膜或面部特征提供生物识别的安全应用;自动驾驶汽车重建道路和环境条件;诸如此类。

数字图像不是以基于属性的描述的结构化方式呈现的;相反,它们被编码为不同通道中的颜色量,例如,黑白和红绿蓝通道。学习目标是识别与特定对象相关联的模式。传统的图像识别方法包括将图像转换成不同的形式,例如,识别物体的角、边缘、同色斑点和基本形状。这样的模式然后被用来训练学习者区分物体。下面列出了一些传统算法的著名例子:

  • 边缘检测找到图像中对象的边界
  • 角点检测识别两条边缘的交点或其他感兴趣的点,例如线的末端、曲率最大值或最小值等等
  • 斑点检测识别与其周围区域相比在诸如亮度或颜色的属性上不同的区域
  • 脊线检测使用平滑函数识别图像中的其他感兴趣点
  • 尺度不变特征变换 ( SIFT )是一种鲁棒的算法,可以匹配对象,即使它们的尺度或方向不同于数据库中的代表性样本
  • 霍夫变换识别图像中的特定模式

最近的一种方法基于深度学习。深度学习是神经网络的一种形式,它模仿大脑处理信息的方式。深度学习的主要优势在于,有可能设计出能够自动提取相关模式的神经网络,进而可以用来训练学习者。随着神经网络的最新进展,图像识别的准确性显著提高。例如, ImageNet 挑战赛向参赛者提供了来自 1000 个不同对象类别的 120 多万张图像,据报道,使用支持向量机 ( SVM )的最佳算法的错误率从 2010 年的 28%下降到 2014 年的 7%,使用了深度神经网络。

在这一章中,我们将快速浏览一下神经网络,从基本的构建模块,感知器,开始,逐渐引入更复杂的结构。

神经网络

第一个神经网络是在六十年代被引入的,它受到了生物神经网络的启发。神经网络的想法是绘制生物神经系统,即大脑如何处理信息。它由相互连接的神经元层组成。在计算机术语中,它们也被称为人工神经网络 ( )。对于计算机,需要训练才能让这个模型学习,就像人脑一样。大脑中的神经元在接收到来自附近相互连接的神经元的信号时被激活,这同样适用于人工神经网络。神经网络的最新进展已经证明,深度神经网络非常适合模式识别任务,因为它们能够自动提取有趣的特征并学习底层呈现。在这一节中,我们将刷新自己的基本结构和组件,从一个单一的感知器到深层网络。

感知器

感知器是基本的神经网络构建模块,也是最早的监督算法之一。它被定义为特征的总和,乘以相应的权重和偏差。当接收到输入信号时,它与指定的权重相乘。这些权重是为每个输入信号或输入定义的,并且在学习阶段不断调整权重。权重的调整取决于最后结果的误差。在与各自的权重相乘后,所有的输入与称为偏差的某个偏移值相加。偏差值也通过权重来调整。因此,它从随机权重和偏差开始,并且随着每次迭代,权重和偏差被调整,以便下一个结果向期望的输出移动。最后,最终结果被转换成输出信号。将所有这些加在一起的函数被称为和传递函数,它被输入到激活函数中。如果二元阶跃激活函数达到一个阈值,则输出为 1,否则为 0,这就给了我们一个二元分类器。下图显示了一个示意图:

训练感知器涉及相当简单的学习算法,该算法计算计算的输出值和正确的训练输出值之间的误差,并使用该误差来创建对权重的调整,从而实现梯度下降的形式。这种算法通常被称为 delta 规则

单层感知器不是很先进,非线性可分离函数,如 XOR,不能用它来建模。为了解决这个问题,引入了一种具有多个感知器的结构,称为多层感知器,也称为前馈神经网络

前馈神经网络

前馈神经网络是由几个感知器组成的人工神经网络,这些感知器按层组织,如下图所示:输入层、输出层以及一个或多个隐藏层。隐层与外界无关,故名。每一层感知器,也称为神经元,都与下一层的感知器直接相连,而两个神经元之间的连接则具有与感知器权重类似的权重。因此,一层中的所有感知器都与下一层中的感知器相连,信息被前馈到下一层。该图显示了一个具有四单元输入层,对应于长度4的特征向量的大小,四单元隐藏层,以及两单元输出层的网络,其中每个单元对应于一个类值:

前馈神经网络通过找到输入和输出值之间的关系来学习,这些值被多次输入到网络中。训练多层网络最流行的方法是反向传播。在反向传播中,计算的输出值与正确的值进行比较,其方式与 delta 规则中的方式相同。然后通过各种技术通过网络反馈该误差,调整每个连接的权重以减少误差值。使用网络输出值和原始输出值之间的平方差来计算误差。误差表明我们离原始输出值有多远。这个过程重复足够多的训练周期,直到误差低于某个阈值。

前馈神经网络可以有一个以上的隐藏层,其中每个附加的隐藏层在前面的层之上建立一个新的抽象。这通常会产生更精确的模型;然而,增加隐藏层的数量会导致两个已知的问题:

  • 消失梯度问题:随着隐藏层越来越多,反向传播的训练对于将信息传递给前面的层变得越来越没用,导致这些层的训练非常缓慢
  • 过拟合:模型与训练数据拟合得太好,在真实例子上表现不佳

让我们看看解决这些问题的其他一些网络结构。

自动编码器

自动编码器是一个前馈神经网络,旨在学习如何压缩原始数据集。它的目的是将输入复制到输出。因此,我们不会将要素映射到输入图层,也不会将标注映射到输出图层,而是将要素映射到输入图层和输出图层。隐藏图层中的单元数通常不同于输入图层中的单元数,这迫使网络扩展或减少原始要素的数量。这样,网络将学习重要的特征,同时有效地应用降维。

下图显示了一个示例网络。三单位输入层首先扩展为四单位层,然后压缩为单单位层。网络的另一端将单层单元恢复到四单元层,然后恢复到原始的三输入层:

一旦训练好网络,我们就可以像传统的图像处理一样,从左侧提取图像特征。它由编码器和解码器组成,其中编码器的工作是创建或隐藏一个或多个捕捉输入本质的层,解码器从层中重建输入。

自动编码器也可以组合成堆叠式自动编码器,如下图所示。首先,我们将讨论基本自动编码器中的隐藏层,如前所述。然后,我们将学习隐藏层(绿色圆圈)并重复这个过程,这实际上学习了一个更抽象的表达。我们可以多次重复这一过程,将原始特征转换成越来越小的维度。最后,我们将所有的隐藏层叠加到一个常规的前馈网络中,如图右上方所示:

受限玻尔兹曼机

受限玻尔兹曼机 ( RBM )是一种无向神经网络,也称为生成随机网络 ( GSNs ),可以学习其输入集合上的概率分布。顾名思义,它们起源于玻尔兹曼机器,这是一种在八十年代推出的递归神经网络。在玻尔兹曼机器中,每个节点或神经元都与所有其他节点相连,当节点计数增加时,这使得处理变得困难。受限意味着神经元必须形成两个完全连接的层,一个输入层和一个隐藏层,如下图所示:

与前馈网络不同,可见层和隐藏层之间的连接是无向的,因此这些值可以在可见到隐藏和隐藏到可见两个方向上传播。

训练 RBMs 基于对比散度算法,该算法使用类似于反向传播的梯度下降过程来更新权重,并且在马尔可夫链上应用 Gibbs 采样来估计梯度,即如何改变权重的方向。

RBM 也可以被堆叠起来创建一个被称为深度信念网络 ( DBNs )的类。在这种情况下,RBM 的隐藏图层充当 RBM 图层的可见图层,如下图所示:

在这种情况下,训练是递增的:一层一层地训练。

深度卷积网络

最近在图像识别基准测试中取得非常好结果的一种网络结构是卷积神经网络 ( CNN )或 ConvNet。CNN 是一种前馈神经网络,其构造方式模仿视觉皮层的行为,利用输入图像的 2D 结构,即表现出空间局部相关性的模式。它的工作原理是大脑如何回忆或记忆图像。作为人类,我们只根据特征来记忆图像。鉴于这些特征,我们的大脑将开始自己形成图像。在计算机中,考虑下图,该图显示了如何检测功能:

同样,从图像中检测到许多特征,如下图所示:

CNN 由许多卷积层和二次采样层组成,后面可选地是全连接层。下图显示了一个这样的例子。输入层读取图像中的所有像素,然后我们应用多个过滤器。在下图中,应用了四种不同的过滤器。每个过滤器被应用于原始图像;例如,6×6 过滤器的一个像素被计算为 6×6 平方的输入像素和相应的 6×6 权重的加权和。这有效地引入了类似于标准图像处理的滤波器,例如平滑、相关、边缘检测等等。产生的图像被称为特征图。在下图的示例中,我们有四个特征映射,每个过滤器一个。

下一层是子采样层,它减小了输入的大小。每个特征图通常在 2×2(对于大图像高达 5×5)的连续区域上用平均值或最大值池进行二次抽样。例如,如果特征图大小为 16×16,子采样区域为 2×2,则减小的特征图大小为 8×8,其中通过计算最大值、最小值、平均值或一些其他函数将 4 个像素(2×2 的正方形)组合成单个像素:

网络可能包含几个连续的卷积和子采样层,如上图所示。特定的特征地图被连接到下一个简化/回旋的特征地图,而同一层的特征地图彼此不连接。

在最后一个子采样或卷积层之后,通常有一个完全连接的层,与标准多层神经网络中的层相同,表示目标数据。

使用修改的反向传播算法来训练 CNN,该算法考虑了子采样层,并且基于应用该滤波器的所有值来更新卷积滤波器权重。

一些好的 CNN 设计可以在 ImageNet 竞赛结果页面找到:www.image-net.org/。一个例子是 AlexNet ,它在 A. Krizhevsky 等人ImageNet 分类与深度协变神经网络论文中有所描述。

这就结束了我们对主要神经网络结构的回顾。在下一节中,我们将继续讨论实际的实现。

图像分类

在本节中,我们将讨论如何使用 Deeplearning4j 库实现一些神经网络结构。让我们开始吧。

深度学习 4j

正如我们在第二章、中讨论的,用于机器学习的 Java 库和平台,Deeplearning4j 是一个开源的、分布式的 Java 和 Scala 深度学习项目。Deeplearning4j 依靠 Spark 和 Hadoop for MapReduce,并行训练模型,并在一个中心模型中迭代平均它们产生的参数。详细的库总结在第二章、用于机器学习的 Java 库和平台中给出。

获取 DL4J

获取 Deeplearning4j 最便捷的方式是通过 Maven 资源库:

  1. 启动一个新的 Eclipse 项目并选择 Maven 项目,如下面的屏幕截图所示:

  1. 打开pom.xml文件,并在<dependencies>部分下添加以下依赖项:
<dependency> 
    <groupId>org.deeplearning4j</groupId> 
    <artifactId>deeplearning4j-nlp</artifactId> 
   <version>${dl4j.version}</version> 
</dependency> 

<dependency> 
    <groupId>org.deeplearning4j</groupId> 
    <artifactId>deeplearning4j-core</artifactId> 
    <version>${dl4j.version}</version> 
</dependency> 
  1. 最后,右键单击 Project,选择 Maven,然后选择 Update project。

MNIST 数据集

最著名的数据集之一是 MNIST 数据集,它由手写数字组成,如下图所示。该数据集由 60,000 幅训练图像和 10,000 幅测试图像组成:

该数据集通常用于图像识别问题,以对算法进行基准测试。记录的最差错误率是 12%,没有预处理,并且在单层神经网络中使用 SVM。目前截至 2016 年,错误率最低的只有 0.21%,使用的是 DropConnect 神经网络,其次是深度卷积网络 0.23%,深度前馈网络 0.35%。

现在,让我们看看如何加载数据集。

加载数据

Deeplearning4j 提供了现成的 MNIST 数据集加载器。加载程序被初始化为DataSetIterator。首先让我们导入DataSetIterator类和所有受支持的数据集,它们是impl包的一部分,例如,iris、MNIST 和其他:

import org.deeplearning4j.datasets.iterator.DataSetIterator; 
import org.deeplearning4j.datasets.iterator.impl.*; 

接下来,我们将定义一些常数,例如,图像由 28 x 28 像素组成,有 10 个目标类和 60,000 个样本。我们将初始化一个新的MnistDataSetIterator类,它将下载数据集及其标签。这些参数是迭代批次大小、样本总数以及数据集是否应该二进制化:

int numRows = 28; 
int numColumns = 28; 
int outputNum = 10;
int numSamples = 60000;
int batchSize = 100;
int iterations = 10;
int seed = 123;
DataSetIterator iter = new MnistDataSetIterator(batchSize, 
numSamples,true);  

拥有一个已经实现的数据导入器确实很方便,但是它不能处理您的数据。让我们快速看一下它是如何实现的,以及需要修改什么来支持您的数据集。如果您急于开始实现神经网络,您可以安全地跳过本节的其余部分,并在需要导入您自己的数据时返回。

To load the custom data, you'll need to implement two classes: DataSetIterator, which holds all of the information about the dataset, and BaseDataFetcher, which actually pulls the data either from a file, database, or the web. Sample implementations are available on GitHub at github.com/deeplearning4j/deeplearning4j/tree/master/deeplearning4j-core/src/main/java/org/deeplearning4j/datasets/iterator/impl
Another option is to use the Canova library, which was developed by the same authors, at deeplearning4j.org/canovadoc/.

建筑模型

在这一节中,我们将讨论如何建立一个实际的神经网络模型。我们将从一个基本的单层神经网络开始,建立一个基准,并讨论基本操作。稍后,我们将使用 DBN 和多层卷积网络来改进这个初始结果。

构建单层回归模型

我们先从建立基于 softmax 激活函数的单层回归模型开始,如下图所示。由于我们有一个单层,输入到神经网络的将是所有的图形像素,即 28×28 =748 个神经元。输出神经元的数量为 10 ,每个数字一个。网络层完全连接,如下图所示:

神经网络通过NeuralNetConfiguration.Builder()对象定义如下:

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder() 

我们将定义梯度搜索的参数,以便用共轭梯度优化算法执行迭代。momentum参数决定了优化算法收敛到局部最优的速度。momentum越高,训练越快;但是更高的速度会降低模型的准确性:

.seed(seed) 
.gradientNormalization(GradientNormalization.ClipElementWiseAbsolu
   teValue) 
   .gradientNormalizationThreshold(1.0) 
   .iterations(iterations) 
   .momentum(0.5) 
   .momentumAfter(Collections.singletonMap(3, 0.9)) 
   .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT) 

接下来,我们将指定网络有一层,并定义误差函数NEGATIVELOGLIKELIHOOD、内部感知器激活函数softmax,以及对应于总
图像像素和目标变量数量的输入和输出层数,如以下代码块所示:

.list(1) 
.layer(0, new  
OutputLayer.Builder(LossFunction.NEGATIVELOGLIKELIHOOD) 
.activation("softmax") 
.nIn(numRows*numColumns).nOut(outputNum).build()) 

最后,我们将网络设置为pretrain,禁用反向传播,实际构建未训练的网络结构:

   .pretrain(true).backprop(false) 
   .build(); 

一旦定义了网络结构,我们就可以用它来初始化一个新的MultiLayerNetwork,如下:

MultiLayerNetwork model = new MultiLayerNetwork(conf); 
model.init(); 

接下来,我们将通过调用setListeners方法将模型指向训练数据,如下所示:

model.setListeners(Collections.singletonList((IterationListener) 
   new ScoreIterationListener(listenerFreq))); 

我们还将调用fit(int)方法来触发端到端网络训练:

model.fit(iter);  

为了评估模型,我们将初始化一个新的Evaluation对象,它将存储批处理结果:

Evaluation eval = new Evaluation(outputNum); 

然后,我们可以批量迭代数据集,以便将内存消耗保持在合理的水平,并将结果存储在一个eval对象中:

DataSetIterator testIter = new MnistDataSetIterator(100,10000); 
while(testIter.hasNext()) { 
    DataSet testMnist = testIter.next(); 
    INDArray predict2 =  
    model.output(testMnist.getFeatureMatrix()); 
    eval.eval(testMnist.getLabels(), predict2); 
} 

最后,我们可以通过调用stats()函数得到结果:

log.info(eval.stats()); 

一个基本的单层模型可以达到以下精度:

    Accuracy:  0.8945 
    Precision: 0.8985
    Recall:    0.8922
    F1 Score:  0.8953

在 MNIST 数据集上获得 89.22%的准确率,即 10.88%的错误率是非常糟糕的。我们将通过使用受限玻尔兹曼机器和多层卷积网络,从简单的一层网络到适度复杂的深度信念网络来改进这一点。

建立一个深度的信念网络

在这一部分,我们将基于 RBM 构建一个深度信念网络(DBN),如下图所示。该网络由四层组成。第一层将 748 输入减少到 500 神经元,然后到 250 ,接着是 200 ,最后到最后的 10 目标值:

由于代码与前面的示例相同,我们来看看如何配置这样一个网络:

MultiLayerConfiguration conf = new 
   NeuralNetConfiguration.Builder() 

我们将定义梯度优化算法,如下面的代码所示:

    .seed(seed) 
    .gradientNormalization( 
    GradientNormalization.ClipElementWiseAbsoluteValue) 
    .gradientNormalizationThreshold(1.0) 
    .iterations(iterations) 
    .momentum(0.5) 
    .momentumAfter(Collections.singletonMap(3, 0.9)) 
    .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT) 

我们还将指定我们的网络将有四层:

   .list(4) 

第一层的输入将是748神经元,输出将是500神经元。我们将使用均方根误差交叉熵和 Xavier 算法,通过基于输入和输出神经元的数量自动确定初始化的规模来初始化权重,如下所示:

.layer(0, new RBM.Builder() 
.nIn(numRows*numColumns) 
.nOut(500)          
.weightInit(WeightInit.XAVIER) 
.lossFunction(LossFunction.RMSE_XENT) 
.visibleUnit(RBM.VisibleUnit.BINARY) 
.hiddenUnit(RBM.HiddenUnit.BINARY) 
.build()) 

接下来的两层将具有相同的参数,除了输入和输出神经元的数量:

.layer(1, new RBM.Builder() 
.nIn(500) 
.nOut(250) 
.weightInit(WeightInit.XAVIER) 
.lossFunction(LossFunction.RMSE_XENT) 
.visibleUnit(RBM.VisibleUnit.BINARY) 
.hiddenUnit(RBM.HiddenUnit.BINARY) 
.build()) 
.layer(2, new RBM.Builder() 
.nIn(250) 
.nOut(200) 
.weightInit(WeightInit.XAVIER) 
.lossFunction(LossFunction.RMSE_XENT) 
.visibleUnit(RBM.VisibleUnit.BINARY) 
.hiddenUnit(RBM.HiddenUnit.BINARY) 
.build()) 

现在,最后一层将把神经元映射到输出,我们将使用softmax激活函数,如下所示:

.layer(3, new OutputLayer.Builder() 
.nIn(200) 
.nOut(outputNum) 
.lossFunction(LossFunction.NEGATIVELOGLIKELIHOOD) 
.activation("softmax") 
.build()) 
.pretrain(true).backprop(false) 
.build(); 

其余的训练和评估与单层网络示例中的相同。请注意,与单层网络相比,训练深层网络可能需要更多的时间。准确率应该在 93%左右。

现在,让我们来看看另一个深层网络。

构建多层卷积网络

在最后一个例子中,我们将讨论如何构建卷积网络,如下图所示。该网络将由七层组成。首先,我们将使用最大池重复两对卷积和子采样层。最后的二次采样层然后连接到密集连接的前馈神经网络,在最后三层中分别由 120 个神经元、84 个神经元和 10 个神经元组成。这样的网络有效地形成了完整的图像识别管道,其中前四层对应于特征提取,后三层对应于学习模型:

网络配置如我们之前所做的那样初始化:

MultiLayerConfiguration.Builder conf = new 
   NeuralNetConfiguration.Builder() 

我们将指定梯度下降算法及其参数,如下所示:

.seed(seed) 
.iterations(iterations) 
.activation("sigmoid") 
.weightInit(WeightInit.DISTRIBUTION) 
.dist(new NormalDistribution(0.0, 0.01)) 
.learningRate(1e-3) 
.learningRateScoreBasedDecayRate(1e-1) 
.optimizationAlgo( 
OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT) 

我们还将指定七个网络层,如下所示:

.list(7) 

第一卷积层的输入是完整的图像,而输出是六个特征图。卷积层将应用 5×5 过滤器,结果将存储在 1×1 单元中:

.layer(0, new ConvolutionLayer.Builder( 
    new int[]{5, 5}, new int[]{1, 1}) 
    .name("cnn1") 
    .nIn(numRows*numColumns) 
    .nOut(6) 
    .build()) 

第二层是二次采样层,它将占用 2 x 2 区域,并将最大结果存储在 2 x 2 元素中:

.layer(1, new SubsamplingLayer.Builder( 
SubsamplingLayer.PoolingType.MAX,  
new int[]{2, 2}, new int[]{2, 2}) 
.name("maxpool1") 
.build()) 

接下来的两层将重复前两层:

.layer(2, new ConvolutionLayer.Builder(new int[]{5, 5}, new 
   int[]{1, 1}) 
    .name("cnn2") 
    .nOut(16) 
    .biasInit(1) 
    .build()) 
.layer(3, new SubsamplingLayer.Builder
   (SubsamplingLayer.PoolingType.MAX, new 
   int[]{2, 2}, new int[]{2, 2}) 
    .name("maxpool2") 
    .build()) 

现在,我们将把二次采样层的输出连接到由120神经元组成的密集前馈网络,然后通过另一层连接到84神经元,如下所示:

.layer(4, new DenseLayer.Builder() 
    .name("ffn1") 
    .nOut(120) 
    .build()) 
.layer(5, new DenseLayer.Builder() 
    .name("ffn2") 
    .nOut(84) 
    .build()) 

最后一层连接84神经元和10输出神经元:

.layer(6, new OutputLayer.Builder
   (LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD) 
    .name("output") 
    .nOut(outputNum) 
    .activation("softmax") // radial basis function required 
    .build()) 
.backprop(true) 
.pretrain(false) 
.cnnInputSize(numRows,numColumns,1); 

为了训练这种结构,我们可以重用我们在前两个例子中开发的代码。同样,培训可能需要一些时间。网络准确率应该在 98%左右。

Since model training significantly relies on linear algebra, training can be significantly sped up by using a graphics processing unit (GPU) for an order of magnitude. As the GPU backend, at the time of writing this book, is undergoing a rewrite, please check the latest documentation at deeplearning4j.org/documentation.

正如我们在不同的例子中看到的,越来越复杂的神经网络允许我们自动提取相关特征,从而完全避免了传统的图像处理。然而,我们为此付出的代价是增加处理时间和大量的学习示例,以使这种方法有效。

摘要

在本章中,我们讨论了如何识别图像中的模式,以便通过涵盖深度学习的基本原则来区分不同的类别,并讨论了如何使用 Deeplearning4j 库来实现它们。我们从更新基本的神经网络结构开始,并讨论了如何实现它们来解决手写数字识别问题。

在下一章,我们将进一步研究模式;然而,代替图像中的模式,我们将处理具有时间依赖性的模式,这可以在传感器数据中找到。

九、利用手机传感器进行活动识别

虽然上一章关注的是图像中的模式识别,但本章讨论的是识别传感器数据中的模式,与图像不同,传感器数据具有时间依赖性。我们将讨论如何使用手机惯性传感器识别日常活动,如走路、坐着和跑步。本章还提供了相关研究的参考,并强调了活动识别社区的最佳实践。

本章涵盖的主题将包括以下内容:

  • 介绍活动识别,涵盖手机传感器和活动识别渠道
  • 从移动设备收集传感器数据
  • 讨论活动分类和模型评估
  • 部署活动识别模型

介绍活动识别

活动识别是行为分析的基础步骤,涉及健康的生活方式、健康跟踪、远程协助、安全应用、老年人护理等。活动识别将来自加速度计、陀螺仪、压力传感器和 GPS 位置等传感器的低级传感器数据转换为行为原语的高级描述。

在大多数情况下,这些都是基本的活动,例如,走、坐、躺、跳等等,如下图所示,或者它们可以是更复杂的行为,例如上班、准备早餐和购物:

在本章中,我们将讨论如何将活动识别功能添加到移动应用程序中。我们将首先看看活动识别问题是什么样的,我们需要收集什么样的数据,主要的挑战是什么,以及如何解决它们。

稍后,我们将通过一个示例来了解如何在一个 Android 应用程序中实际实现活动识别,包括数据收集、数据转换和构建分类器。

开始吧!

手机传感器

我们先来回顾一下手机传感器都有哪些种类,报的是什么。大多数智能设备现在都配备了几个内置传感器,可以测量周围环境的运动、位置、方向和条件。由于传感器提供高精度、高频率和高准确度的测量,因此有可能重建复杂的用户动作、手势和移动。传感器经常被结合到各种应用中;例如,陀螺仪读数用于操纵游戏中的对象,GPS 数据用于定位用户,加速度计数据用于推断用户正在进行的活动,例如骑自行车、跑步或步行。

下图显示了传感器能够检测到的交互类型的几个示例:

移动电话传感器可分为以下三大类:

  • 运动传感器:该传感器测量沿三个垂直轴的加速度和旋转力。这类传感器的例子包括加速度计、重力传感器和陀螺仪。
  • 环境传感器:该传感器测量各种环境参数,如光照、空气温度、压力和湿度。这一类包括气压计、光度计和温度计。
  • 位置传感器:该传感器测量设备的物理位置。这一类包括方位传感器和磁力计。

有关不同移动平台的更多详细说明,请访问以下链接:

在这一章中,我们将只使用 Android 的传感器框架。

活动识别管道

正如我们在前面章节中看到的,对多维时间序列传感器数据进行分类本质上比对传统名义数据进行分类更复杂。首先,每个观测值在时间上与前一个和后一个观测值相关,这使得很难只对一组观测值进行简单的分类。第二,传感器在不同时间点获得的数据是随机的,也就是说,由于传感器噪声、环境干扰和许多其他因素的影响,这些数据是不可预测的。此外,一项活动可以由以不同方式执行的各种子活动组成,每个人执行活动的方式稍有不同,这导致了较高的组内差异。最后,所有这些原因使得活动识别模型不精确,导致新数据经常被错误分类。活动识别分类器的一个非常理想的特性是确保所识别的活动序列的连续性和一致性。

为了应对这些挑战,活动识别应用于管道,如下图所示:

第一步,我们尽可能多地衰减噪声,例如,通过降低传感器采样速率、移除异常值、应用高通或低通滤波器等。在下一阶段,我们构建一个特征向量。例如,我们通过应用离散傅立叶变换 ( DFT )将传感器数据从时域转换到频域。DFT 是一种将样本列表作为输入并返回按频率排序的正弦系数列表的方法。它们表示原始样本列表中存在的频率组合。

A gentle introduction to the Fourier transform was written by Pete Bevelacqua at www.thefouriertransform.com/. If you want to get a more technical and theoretical background on the Fourier transform, take a look at the eighth and ninth lectures in the class by Robert Gallager and Lizhong Zheng at this MIT open course: theopenacademy.com/content/principles-digital-communication.

接下来,基于特征向量和训练数据集,我们可以构建一个活动识别模型,为每个观察分配一个原子动作。因此,对于每个新的传感器读数,模型将输出最可能的活动标签。然而,模型也会出错。因此,最后一个阶段通过删除实际上不可能发生的转换来平滑活动之间的转换;例如,躺-站-躺活动之间的转换在少于半秒的时间内发生在物理上是不可行的,因此,活动之间的这种转换被平滑为躺-躺-躺。

活动识别模型是用监督学习方法构建的,该方法包括训练和分类步骤。在训练步骤中,提供一组标记数据来训练模型。第二步是由训练好的模型给新的看不见的数据分配一个标签。两个阶段中的数据必须用相同的工具集进行预处理,例如过滤和特征向量计算。

后处理阶段,即虚假活动去除,也可以是模型本身,因此也需要学习步骤。在这种情况下,预处理步骤还包括活动识别,这使得分类器的这种排列成为元学习问题。为了避免过度拟合,重要的是用于训练后处理阶段的数据集不同于用于训练活动识别模型的数据集。

这个计划

该计划包括培训阶段和部署阶段。培训阶段可归结为以下步骤:

  1. 安装 Android Studio,导入MyRunsDataCollector.zip
  2. 在你的 Android 手机上加载应用程序。
  3. 收集您的数据,例如站立、行走和跑步,并将数据转换为由 FFT 组成的特征向量。不要慌;FFT 等低级信号处理函数将不会从头开始编写,因为我们将使用现有代码来完成。这些数据将被保存在手机上一个名为features.arff的文件中。
  4. 使用导出的数据创建和评估活动识别分类器,并实施过滤器以消除虚假的活动转换。
  5. 将分类器插回到移动应用程序中。

如果你没有 Android 手机,或者如果你想跳过所有与移动应用程序相关的步骤,只需抓取位于data/features.arff的收集数据集,并直接跳转到构建分类器部分。

从手机收集数据

本节描述了该计划的前三个步骤。如果您想直接处理数据,您可以跳过这一部分,继续到构建分类器部分。该应用程序实现了收集不同活动类别(例如,站立、行走、跑步等)的传感器数据的基本要素。

让我们从准备 Android 开发环境开始。如果您已经安装了它,请跳到加载数据采集器部分。

安装 Android Studio

Android Studio 是 Android 平台的开发环境。我们将快速回顾在手机上启动应用程序所需的安装步骤和基本配置。关于 Android 开发的更详细的介绍,我推荐一本入门书,Packt 出版社的 Kyle Mew 的《Android 5 编程示例》。

developer.android.com/studio/为开发者下载最新的 Android Studio,并按照 http://developer.android.com/sdk/installing/index.html?的安装说明进行操作 pkg =工作室。安装大约需要 10 分钟,占用大约 0.5 GB 的空间。

按照说明选择您喜欢的安装选项,最后单击 Finish 开始安装,如下面的屏幕截图所示:

加载数据采集器

首先从 GitHub 上抓取MyRunsDataCollector的源代码。安装 Android Studio 后,选择打开一个现有的 Android Studio 项目选项,如下图所示,并选择MyRunsDataCollector文件夹。这将把项目导入 Android Studio:

项目导入完成后,您应该能够看到项目文件结构,如下图所示。采集器由CollectorActivity.javaGlobals.javaSensorsService.java组成。该项目还显示了FFT.java实现低电平信号处理:

myrunscollector包包含以下类:

  • Globals.java:定义全局常量,如活动标签和 id,以及数据文件名。
  • CollectorActivity.java:实现用户界面动作,也就是按下特定按钮时发生的事情。
  • 这实现了一个收集数据、计算特征向量的服务,我们将在下面的章节中讨论,并将数据存储到手机上的一个文件中。

我们要解决的下一个问题是如何设计功能。

特征抽出

找到一个人的活动的适当表示可能是活动识别中最具挑战性的部分。行为需要用简单和通用的特征来表示,以便使用这些特征的模型也将是通用的,并且在不同于学习集中的行为上工作良好。

事实上,在一个训练集中设计特定于捕获的观察的特征并不困难;这样的功能在他们身上会很好用。然而,由于训练集仅捕获人类行为的整个范围的一部分,过于具体的特征可能会在一般行为上失败:

让我们看看这是如何在MyRunsDataCollector中实现的。当应用程序启动时,一个名为onSensorChanged()的方法获取带有特定时间戳的三个加速度计传感器读数( xyz ),并根据传感器读数计算大小。该方法在计算 FFT 系数之前缓冲多达 64 个连续的幅度标记。

现在,让我们继续实际的数据收集。

收集培训数据

我们现在可以使用收集器来收集活动识别的训练数据。默认情况下,收集器支持三种活动:站立、行走和跑步,如下图所示。

您可以选择一个活动,即目标类值,并通过单击 START COLLECTING 按钮开始记录数据。确保每项活动至少记录三分钟;例如,如果选择了步行活动,请按开始收集并步行至少三分钟。活动结束时,按停止收集。对每个活动重复这一步骤。

您还可以收集涉及这些活动的不同场景,例如,在厨房里行走、在外面行走、排队行走等等。通过这样做,您将拥有每个活动类的更多数据和一个更好的分类器。有道理,对吧?数据越多,分类器就越不混乱。如果只有少量数据,将会出现过度拟合,分类器会混淆类别-站立与行走,行走与跑步,等等。然而,数据越多,他们越不会感到困惑。当你调试的时候,你可能每个类收集不到三分钟的数据,但是对于你最终的成品来说,数据越多越好。多个记录实例将简单地累积在同一个文件中。

请注意,删除数据按钮会删除存储在手机文件中的数据。如果你想重新开始,在开始前点击删除数据;否则,新收集的数据将被附加到文件的末尾:

收集器实现了前面几节中讨论的图表:它收集加速度计样本,计算幅度,使用FFT.java类计算系数,并产生特征向量。然后,数据被存储在 Weka 格式的features.arff文件中。特征向量的数量会根据您收集的数据量而有所不同。收集数据的时间越长,积累的特征向量就越多。

一旦您停止使用收集器工具收集训练数据,我们就需要获取数据来继续工作流。我们可以使用 Android 设备监视器中的文件浏览器从手机上传features.arff文件,并将其存储在电脑上。您可以通过单击 Android robot 图标来访问您的 Android 设备监视器,如下图所示:

通过在左侧选择您的设备,您的手机存储内容将显示在右侧。浏览mnt/shell/emulated/Android/data/edu.dartmouth.cs.myrunscollector/files/features.arff,如下图所示:

要将此文件上传到您的计算机,您需要选择该文件(突出显示)并单击上传。

现在,我们准备构建一个分类器。

构建分类器

一旦传感器样本被表示为特征向量并分配了类别,就可以应用标准技术进行监督分类,包括特征选择、特征离散化、模型学习、k-fold 交叉验证等。本章不会深入研究机器学习算法的细节。可以应用任何支持数字特征的算法,包括支持向量机、随机森林、AdaBoost、决策树、神经网络、多层感知器等等。

因此,让我们从一个基本的开始:决策树。在这里,我们将加载数据集,构建 set class 属性,构建决策树模型,并输出模型:

String databasePath = "/Users/bostjan/Dropbox/ML Java Book/book/datasets/chap9/features.arff"; 

// Load the data in arff format 
Instances data = new Instances(new BufferedReader(new 
   FileReader(databasePath))); 

// Set class the last attribute as class 
data.setClassIndex(data.numAttributes() - 1); 

// Build a basic decision tree model 
String[] options = new String[]{}; 
J48 model = new J48(); 
model.setOptions(options); 
model.buildClassifier(data); 

// Output decision tree 
System.out.println("Decision tree model:\n"+model); 

该算法首先输出模型,如下所示:

    Decision tree model:
    J48 pruned tree
    ------------------

    max <= 10.353474
    |   fft_coef_0000 <= 38.193106: standing (46.0)
    |   fft_coef_0000 > 38.193106
    |   |   fft_coef_0012 <= 1.817792: walking (77.0/1.0)
    |   |   fft_coef_0012 > 1.817792
    |   |   |   max <= 4.573082: running (4.0/1.0)
    |   |   |   max > 4.573082: walking (24.0/2.0)
    max > 10.353474: running (93.0)

    Number of Leaves  : 5

    Size of the tree : 9

该树非常简单,看起来很准确,因为终端节点中的多数类分布非常高。让我们运行一个基本分类器评估来验证结果,如下所示:

// Check accuracy of model using 10-fold cross-validation 
Evaluation eval = new Evaluation(data); 
eval.crossValidateModel(model, data, 10, new Random(1), new 
   String[] {}); 
System.out.println("Model performance:\n"+ 
   eval.toSummaryString()); 

这将输出以下模型性能:

    Correctly Classified Instances         226               92.623  %
    Incorrectly Classified Instances        18                7.377  %
    Kappa statistic                          0.8839
    Mean absolute error                      0.0421
    Root mean squared error                  0.1897
    Relative absolute error                 13.1828 %
    Root relative squared error             47.519  %
    Coverage of cases (0.95 level)          93.0328 %
    Mean rel. region size (0.95 level)      27.8689 %
    Total Number of Instances              244     

分类准确率得分非常高,92.62%,这是一个惊人的结果。结果这么好的一个重要原因在于我们的评价设计。我在这里的意思是:连续的实例彼此非常相似,所以如果我们在 10 倍交叉验证期间随机地将它们分开,那么我们很有可能在训练和测试中使用几乎相同的实例;因此,直接的 k 倍交叉验证产生了对模型性能的乐观估计。

更好的方法是使用对应于不同测量集甚至不同人的折叠。例如,我们可以使用应用程序收集五个人的学习数据。然后,运行 k 人交叉验证是有意义的,其中模型在四个人身上训练,在第五个人身上测试。对每个人重复该过程,并对结果进行平均。这将使我们对模型性能有一个更加真实的估计。

抛开评价评论,让我们看看如何处理分类器错误。

减少杂散跃迁

在活动识别管道的最后,我们希望确保分类不会太不稳定,也就是说,我们不希望活动每毫秒都发生变化。一个基本的方法是设计一个忽略活动序列中快速变化的滤波器。

我们构建了一个过滤器,它能记住最后的窗口活动并返回最频繁的活动。如果有多个得分相同的活动,它将返回最近的一个。

首先,我们创建一个新的SpuriousActivityRemoval类,它将保存一个活动列表和window参数:

class SpuriousActivityRemoval{ 

  List<Object> last; 
  int window; 

  public SpuriousActivityRemoval(int window){ 
    this.last = new ArrayList<Object>(); 
    this.window = window; 
  } 

接下来,我们创建Object filter(Object)方法,该方法将接受一个活动并返回一个经过过滤的活动。该方法首先检查我们是否有足够的观察值。如果没有,它只存储观察值并返回相同的值,如下面的代码所示:

  public Object filter(Object obj){ 
    if(last.size() < window){ 
      last.add(obj); 
      return obj; 
  } 

如果我们已经收集了window观察值,我们只需返回最频繁的观察值,删除最早的观察值,并插入新的观察值:

    Object o = getMostFrequentElement(last); 
    last.add(obj); 
    last.remove(0); 
    return o; 
  } 

这里缺少的是一个从对象列表中返回最频繁元素的函数。我们使用哈希映射来实现这一点,如下所示:

  private Object getMostFrequentElement(List<Object> list){ 

    HashMap<String, Integer> objectCounts = new HashMap<String, 
       Integer>(); 
    Integer frequntCount = 0; 
    Object frequentObject = null; 

现在,我们遍历列表中的所有元素,将每个唯一的元素插入到一个哈希映射中,或者更新它的计数器(如果它已经在哈希映射中的话)。在循环结束时,我们存储目前为止找到的最频繁的元素,如下所示:

    for(Object obj : list){ 
      String key = obj.toString(); 
      Integer count = objectCounts.get(key); 
      if(count == null){ 
        count = 0; 
      } 
      objectCounts.put(key, ++count); 

      if(count >= frequntCount){ 
        frequntCount = count; 
        frequentObject = obj; 
      } 
    } 

    return frequentObject; 
  } 

} 

让我们运行一个简单的例子:

String[] activities = new String[]{"Walk", "Walk", "Walk", "Run", 
   "Walk", "Run", "Run", "Sit", "Sit", "Sit"}; 
SpuriousActivityRemoval dlpFilter = new 
   SpuriousActivityRemoval(3); 
for(String str : activities){ 
  System.out.println(str +" -> "+ dlpFilter.filter(str)); 
} 

该示例输出以下活动:

    Walk -> Walk
    Walk -> Walk
    Walk -> Walk
    Run -> Walk
    Walk -> Walk
    Run -> Walk
    Run -> Run
    Sit -> Run
    Sit -> Run
    Sit -> Sit

结果是一系列连续的活动,也就是说,我们没有快速的变化。这增加了一些延迟,但除非这对应用程序绝对重要,否则这是可以接受的。

可以通过将由分类器识别的 n 个先前活动附加到特征向量来增强活动识别。附加先前活动的危险在于,机器学习算法可能知道当前活动总是与先前活动相同,因为这将是经常的情况。该问题可以通过具有两个分类器 A 和 B 来解决:分类器 B 的属性向量包含由分类器 A 识别的 n 个先前活动。分类器 A 的属性向量不包含任何先前活动。这样,即使 B 对之前的活动赋予了很大的权重,A 认可的之前的活动也会因为 A 没有背负 B 的惯性而改变。

剩下要做的就是将分类器嵌入并过滤到我们的移动应用程序中。

将分类器插入移动应用程序

有两种方法可以将分类器集成到移动应用程序中。第一个包括以 Weka 格式导出模型,在我们的移动应用程序中使用 Weka 库作为依赖项,加载模型,等等。该过程与我们在第三章、中看到的示例相同——分类、回归和聚类。第二种方法更加轻量级:我们将模型导出为源代码,例如,我们创建一个实现决策树分类器的类。然后,我们可以简单地将源代码复制并粘贴到我们的移动应用程序中,甚至不需要导入任何 Weka 依赖项。

幸运的是,一些 Weka 模型可以通过toSource(String)函数轻松导出到源代码:

// Output source code implementing the decision tree 
System.out.println("Source code:\n" +  
  model.toSource("ActivityRecognitionEngine")); 

这将输出一个与我们的模型相对应的ActivityRecognitionEngine类。现在,让我们仔细看看输出代码:

class ActivityRecognitionEngine { 

  public static double classify(Object[] i) 
    throws Exception { 

    double p = Double.NaN; 
    p = ActivityRecognitionEngine.N17a7cec20(i); 
    return p; 
  } 
  static double N17a7cec20(Object []i) { 
    double p = Double.NaN; 
    if (i[64] == null) { 
      p = 1; 
    } else if (((Double) i[64]).doubleValue() <= 10.353474) { 
    p = ActivityRecognitionEngine.N65b3120a1(i); 
    } else if (((Double) i[64]).doubleValue() > 10.353474) { 
      p = 2; 
    }  
    return p; 
  } 
... 

输出的ActivityRecognitionEngine类实现了我们之前讨论过的决策树。机器生成的函数名,如N17a7cec20(Object []),对应决策树节点。分类器可以通过classify(Object[])方法调用,在这里我们应该传递一个通过与我们在前面章节中讨论的相同过程获得的特征向量。像往常一样,它返回一个double,表示一个类标签索引。

摘要

在本章中,我们讨论了如何为移动应用程序实现一个活动识别模型。我们研究了完整的过程,包括数据收集、特征提取、模型构建、评估和模型部署。

在下一章,我们将继续讨论另一个针对文本分析的 Java 库:Mallet。

十、基于 Mallet 主题建模的文本挖掘和垃圾邮件检测

在这一章中,我们将首先讨论什么是文本挖掘,它能够提供什么样的分析,以及为什么你可能想在你的应用程序中使用它。然后我们将讨论如何使用 Mallet ,一个用于自然语言处理的 Java 库,包括数据导入和文本预处理。之后,我们将研究两个文本挖掘应用程序:主题建模,在这里我们将讨论如何使用文本挖掘来识别在文本文档中发现的主题,而无需单独阅读它们;以及垃圾邮件检测,在这里我们将讨论如何自动将文本文档分类。

本章将涵盖以下主题:

  • 文本挖掘简介
  • 安装和使用木槌
  • 主题建模
  • 垃圾邮件检测

文本挖掘简介

文本挖掘或文本分析是指从文本文档中自动提取高质量信息的过程,这些文档通常用自然语言编写,其中高质量信息被认为是相关的、新颖的和有趣的。

虽然典型的文本分析应用程序用于扫描一组文档以生成搜索索引,但文本挖掘可以用于许多其他应用程序,包括特定领域的文本分类;自动组织一组文档的文本聚类;情感分析以识别和提取文档中的主观信息;能够从文档中识别人、地点、组织和其他实体的概念或实体提取;文档摘要自动提供原始文档中最重要的点;和学习命名实体之间的关系。

基于统计模式挖掘的过程通常包括以下步骤:

  1. 信息检索和提取
  2. 将非结构化文本数据转换成结构化数据;例如,解析、去除干扰词、词法分析、计算词频以及导出语言特征
  3. 从结构化数据和标记或注释中发现模式
  4. 结果的评估和解释

在本章的后面,我们将研究两个应用领域:主题建模和文本分类。让我们看看他们带来了什么。

主题建模

主题建模是一种无人监督的技术,如果您需要分析一个大型的文本文档存档,并且希望了解存档中包含的内容,而不需要亲自阅读每一个文档,那么主题建模可能会很有用。文本文档可以是博客帖子、电子邮件、推文、文档、书籍章节、日记条目等等。主题建模在文本语料库中寻找模式;更准确地说,它将主题识别为以统计上有意义的方式出现的单词列表。最著名的算法是潜在狄利克雷分配 ( LDA ),它假设作者通过从可能的词篮中选择词来组成一段文本,其中每个词篮对应一个主题。使用这个假设,就有可能将文本数学地分解成单词最可能出现的篮子。然后,该算法迭代这个过程,直到它收敛到单词在篮子中最可能的分布,我们称之为主题

例如,如果我们对一系列新闻文章使用主题建模,该算法将返回最有可能包含这些主题的主题和关键字的列表。以新闻文章为例,该列表可能如下所示:

  • 赢家,进球,足球,得分,第一名
  • 公司、股票、银行、信用、商业
  • 选举,对手,总统,辩论,即将到来

通过查看关键字,我们可以识别出新闻文章关注的是体育、商业、即将到来的选举等等。在本章的后面,我们将学习如何使用新闻文章的例子来实现主题建模。

文本分类

在文本分类或文本分类中,目标是根据文本文档的内容将其分配到一个或多个类别或范畴,这些类别或范畴往往是更一般的主题领域,例如车辆或宠物。这样的一般类称为主题,分类任务则称为文本分类文本分类主题分类主题定位。虽然可以根据其他属性(如文档类型、作者和出版年份)对文档进行分类,但本章只关注文档内容。文本分类的示例包括以下组件:

  • 电子邮件、用户评论、网页等中的垃圾邮件检测
  • 色情内容的检测
  • 情感检测,自动将产品或服务评论分为正面或负面
  • 根据内容分类电子邮件
  • 特定主题搜索,搜索引擎将搜索限制在特定的主题或类型,从而提供更准确的结果

这些例子显示了文本分类在信息检索系统中的重要性;因此,大多数现代信息检索系统使用某种文本分类器。在本书中,我们将使用的分类任务是用于检测垃圾邮件的文本分类。

本章我们将继续介绍 Mallet,这是一个基于 Java 的包,用于统计自然语言处理、文档分类、聚类、主题建模、信息提取和其他文本的机器学习应用。然后,我们将介绍两个文本分析应用程序,即主题建模和作为文本分类的垃圾邮件检测。

安装木槌

Mallet 可以在 http://mallet.cs.umass.edu/download.php 大学的网站上下载。导航到下载部分,如下图所示,并选择最新的稳定版本( 2.0.8 ,在撰写本书时):

下载 ZIP 文件并提取内容。在解压后的目录中,您应该会找到一个名为dist的文件夹,其中有两个 JAR 文件:mallet.jarmallet-deps.jar。第一个包含所有打包的 Mallet 类,而第二个包含所有的依赖项。我们将把这两个 JARs 文件作为引用库包含在您的项目中,如下面的屏幕截图所示:

如果您正在使用 Eclipse,右键单击 Project,选择 Properties,然后选择 Java Build Path。选择“库”选项卡,然后单击“添加外部 jar”。现在,选择两个 jar 文件并确认,如下面的屏幕截图所示:

现在我们准备开始使用木槌。

使用文本数据

文本挖掘的主要挑战之一是将非结构化的书面自然语言转换成结构化的基于属性的实例。该过程包括许多步骤,如下所示:

首先,我们从互联网、现有文档或数据库中提取一些文本。在第一步结束时,文本仍然可以以 XML 格式或其他专有格式呈现。下一步是提取实际的文本并将其分割成文档的各个部分,例如标题、大标题、摘要和正文。第三步是规范化文本编码,以确保字符以相同的方式呈现;例如,以 ASCII、ISO 8859-1 和 Windows-1250 等格式编码的文档被转换为 Unicode 编码。接下来,标记化将文档拆分成特定的单词,而下一步将删除通常预测能力较低的常用单词,例如,the、a、I 和 we。

可以包括词性 ( 词性)标记和词条化步骤,通过移除词尾和修饰语,将每个单词转换为其基本形式,这被称为词条。比如跑步变成了跑,更好变成了好。一种简化的方法是词干化,它对单个单词进行操作,而不考虑该特定单词如何使用的任何上下文,因此不能根据词性来区分具有不同含义的单词,例如,axes 作为 axes 以及 axis 的复数。

最后一步将记号转换到特征空间。最常见的情况是,特征空间是一个单词袋 ( 鞠躬)表示。在这个演示中,创建了出现在数据集中的所有单词的集合。然后,每个文档以一个向量的形式呈现,该向量计算某个特定单词在文档中出现的次数。

考虑以下两个句子的例子:

  • 雅各布喜欢乒乓球。艾玛也喜欢乒乓球
  • 雅各布也喜欢篮球

这个例子中的 BoW 由{Jacob,likes,table,tennis,Emma,too,also,basketball}组成,它有八个不同的单词。现在可以使用列表的索引将这两个句子表示为向量,指示特定索引处的单词在文档中出现的次数,如下所示:

  • [1, 2, 2, 2, 1, 0, 0, 0]
  • [1, 1, 0, 0, 0, 0, 1, 1]

这样的向量最终成为进一步学习的实例。

另一个基于 BoW 模型的非常强大的演示是 word2vec 。Word2vec 于 2013 年由谷歌的托马斯·米科洛夫领导的研究团队推出。Word2vec 是一个学习单词的分布式表示的神经网络。这个演示的一个有趣的特性是单词以簇的形式出现,因此一些单词关系,例如类比,可以使用向量数学来重现。一个著名的例子说明了国王男人+女人回报王后。进一步的细节和实施可在以下链接获得:【https://code.google.com/archive/p/word2vec/】??。

导入数据

在这一章中,我们不会研究如何从网站上删除一组文档或者从数据库中提取它们。相反,我们将假设我们已经将它们收集为一组文档,并以.txt文件格式存储它们。现在让我们来看看加载它们的两个选项。第一个选项解决了每个文档存储在自己的.txt文件中的情况。第二个选项通过每行获取一个文档来解决所有文档都存储在一个文件中的情况。

从目录导入

Mallet 支持用cc.mallet.pipe.iterator.FileIterator类从目录中读取。文件迭代器由以下三个参数构成:

  • 包含文本文件的File[]目录列表
  • 指定在目录中选择哪些文件的文件过滤器
  • 应用于文件名以产生类别标签的模式

考虑结构化到文件夹中的数据,如下面的屏幕截图所示。我们将文档按文件夹(techentertainmentpoliticssportbusiness)组织成五个主题。每个文件夹都包含特定主题的文档,如下面的屏幕截图所示:

在这种情况下,我们初始化iterator如下:

FileIterator iterator = 
  new FileIterator(new File[]{new File("path-to-my-dataset")}, 
  new TxtFilter(), 
  FileIterator.LAST_DIRECTORY); 

第一个参数指定了我们的根文件夹的路径,第二个参数将迭代器限制为仅针对.txt文件,而最后一个参数要求方法使用路径中的最后一个目录名作为类标签。

从文件导入

加载文档的另一个选项是通过cc.mallet.pipe.iterator.CsvIterator.CsvIterator(Reader, Pattern, int, int, int),它假设所有的文档都在一个文件中,并在正则表达式提取的每一行返回一个实例。该类由以下组件初始化:

  • Reader:这是指定如何从文件中读取的对象
  • Pattern:这是一个正则表达式,提取三组:数据、目标标签、文档名
  • int, int, int:这些是出现在正则表达式中的数据、目标和名称组的索引

考虑以下格式的文本文档,指定文档名称、类别和内容:

AP881218 local-news A 16-year-old student at a private 
   Baptist...  
AP880224 business The Bechtel Group Inc. offered in 1985 to...  
AP881017 local-news A gunman took a 74-year-old woman hostage...  
AP900117 entertainment Cupid has a new message for lovers 
   this...  
AP880405 politics The Reagan administration is weighing w...  

要将一行解析为三组,我们可以使用以下正则表达式:

^(\\S*)[\\s,]*(\\S*)[\\s,]*(.*)$

括号()中出现了三组,其中第三组包含数据,第二组包含目标类,第一组包含文档 ID。iterator初始化如下:

CsvIterator iterator = new CsvIterator ( 
fileReader, 
Pattern.compile("^(\\S*)[\\s,]*(\\S*)[\\s,]*(.*)$"), 
  3, 2, 1)); 

这里,正则表达式提取由空格分隔的三个组,它们的顺序是3, 2, 1

现在让我们转到数据预处理管道。

预处理文本数据

一旦我们初始化了一个遍历数据的迭代器,我们就需要通过一系列的转换来传递数据,如本节开头所述。Mallet 通过管道和管道中可能包含的各种各样的步骤来支持这个过程,这些都被收集在cc.mallet.pipe包中。一些例子如下:

  • Input2CharSequence:这是一个可以从各种文本源(URL、文件或阅读器)读入CharSequence的管道
  • CharSequenceRemoveHTML:这个管道从CharSequence中移除 HTML
  • MakeAmpersandXMLFriendly:将令牌序列的令牌中的&转换为&amp
  • TokenSequenceLowercase:将数据字段中令牌序列中每个令牌的文本转换成小写
  • TokenSequence2FeatureSequence:将每个实例的数据字段中的令牌序列转换为特征序列
  • TokenSequenceNGrams:将数据字段中的令牌序列转换为 ngrams 的令牌序列,即两个或多个单词的组合

处理步骤的完整列表可在以下 Mallet 文档中找到:mallet.cs.umass.edu/api/index.html?cc/mallet/pipe/iterator/package-tree . html

现在我们准备构建一个导入数据的类。我们将通过以下步骤实现这一点:

  1. 让我们构建一个管道,其中每个处理步骤在 Mallet 中都表示为一个管道。管道可以用一系列ArrayList<Pipe>对象以串行方式连接在一起:
ArrayList<Pipe> pipeList = new ArrayList<Pipe>(); 
  1. 让我们从从 file 对象读取数据并将所有字符转换成小写开始:
pipeList.add(new Input2CharSequence("UTF-8")); 
pipeList.add( new CharSequenceLowercase() );
  1. 我们将使用正则表达式来标记原始字符串。以下模式包括 unicode 字母和数字以及下划线字符:
Pattern tokenPattern = 
Pattern.compile("[\\p{L}\\p{N}_]+"); 

pipeList.add(new CharSequence2TokenSequence(tokenPattern)); 
  1. 我们现在将使用标准的英语停用词表删除停用词,即没有预测能力的常用词。两个额外的参数指示停用字词的删除是否应该区分大小写并标记删除,而不仅仅是删除字词。我们将它们都设置为false:
pipeList.add(new TokenSequenceRemoveStopwords(new File(stopListFilePath), "utf-8", false, false, false));
  1. 我们可以不存储实际的单词,而是将它们转换成整数,在 BoW 中表示一个单词索引:
pipeList.add(new TokenSequence2FeatureSequence()); 
  1. 我们将为类标签做同样的事情;我们将使用一个整数来代替标签字符串,表示标签在单词包中的位置:
pipeList.add(new Target2Label()); 
  1. 我们还可以通过调用PrintInputAndTarget管道来打印特征和标签:
pipeList.add(new PrintInputAndTarget()); 
  1. 我们将管道列表存储在一个SerialPipes类中,该类将通过一系列管道转换实例:
SerialPipes pipeline = new SerialPipes(pipeList); 

现在让我们看看如何在文本挖掘应用程序中应用它!

BBC 新闻的主题建模

如前所述,主题建模的目标是识别文本语料库中对应于文档主题的模式。在这个例子中,我们将使用来自 BBC 新闻的数据集。该数据集是机器学习研究中的标准基准之一,可用于非商业和研究目的。

我们的目标是构建一个分类器,能够将一个主题分配给一个未分类的文档。

BBC 数据集

2006 年,格林和坎宁安收集了英国广播公司的数据集来研究一个特定的文档— 使用支持向量机的聚类挑战。该数据集由 2004 年至 2005 年 BBC 新闻网站的 2225 份文件组成,对应于从五个主题领域收集的故事:商业、娱乐、政治、体育和技术。数据集可以在以下网站看到:【http://mlg.ucd.ie/datasets/bbc.html

我们可以在 Dataset: BBC 部分下载原始文本文件。您还会注意到,该网站包含一个已经处理过的数据集,但是,对于本例,我们希望自己处理数据集。ZIP 包含五个文件夹,每个主题一个。实际的文档放在相应的主题文件夹中,如下面的屏幕截图所示:

现在,让我们构建一个主题分类器。

建模

我们将使用以下步骤开始建模阶段:

  1. 我们将从导入数据集开始,并使用以下代码行处理文本:
import cc.mallet.types.*; 
import cc.mallet.pipe.*; 
import cc.mallet.pipe.iterator.*; 
import cc.mallet.topics.*; 

import java.util.*; 
import java.util.regex.*; 
import java.io.*; 

public class TopicModeling { 

  public static void main(String[] args) throws Exception { 

String dataFolderPath = "data/bbc"; 
String stopListFilePath = "data/stoplists/en.txt"; 
  1. 然后我们将创建一个默认的pipeline对象,如前所述:
ArrayList<Pipe> pipeList = new ArrayList<Pipe>(); 
pipeList.add(new Input2CharSequence("UTF-8")); 
Pattern tokenPattern = Pattern.compile("[\\p{L}\\p{N}_]+"); 
pipeList.add(new CharSequence2TokenSequence(tokenPattern)); 
pipeList.add(new TokenSequenceLowercase()); 
pipeList.add(new TokenSequenceRemoveStopwords(new File(stopListFilePath), "utf-8", false, false, false)); 
pipeList.add(new TokenSequence2FeatureSequence()); 
pipeList.add(new Target2Label()); 
SerialPipes pipeline = new SerialPipes(pipeList); 
  1. 接下来,我们将初始化folderIterator对象:
FileIterator folderIterator = new FileIterator( 
    new File[] {new File(dataFolderPath)}, 
    new TxtFilter(), 
    FileIterator.LAST_DIRECTORY);
  1. 我们现在将使用我们想要用来处理文本的pipeline构建一个新的实例列表:
InstanceList instances = new InstanceList(pipeline);
  1. 我们处理由iterator提供的每个实例:
instances.addThruPipe(folderIterator); 
  1. 现在让我们使用实现简单线程 LDA 模型的cc.mallet.topics.ParallelTopicModel.ParallelTopicModel类创建一个包含五个主题的模型。LDA 是一种常见的主题建模方法,它使用狄利克雷分布来估计所选主题生成特定文档的概率。在这一章中,我们不会深究细节;读者可参考 D. Blei 等人(2003 年)的原始论文。

注:机器学习中还有另一种同样初始化的分类算法是指线性判别分析 ( LDA )。除了常见的缩写,它与 LDA 模型没有任何共同之处。

该类是用参数 alpha 和 beta 实例化的,可以大致解释如下:

  • 高 alpha 值意味着每个文档可能包含大部分主题的混合,而不是特定的单个主题。较低的 alpha 值对文档施加的约束较少,这意味着文档更有可能包含几个甚至一个主题的混合。
  • 高 beta 值意味着每个主题可能包含大多数单词的混合,而不是任何特定的单词;而低值意味着一个主题可能只包含几个单词的组合。

在我们的例子中,我们最初将两个参数都保持为低(alpha_t = 0.01,beta_w = 0.01),因为我们假设数据集中的主题没有混合太多,并且每个主题都有许多单词:

int numTopics = 5; 
ParallelTopicModel model =  
new ParallelTopicModel(numTopics, 0.01, 0.01); 
  1. 我们将把instances添加到模型中,由于我们使用并行实现,我们将指定并行运行的线程数量,如下所示:
model.addInstances(instances); 
model.setNumThreads(4);
  1. 我们现在将运行模型一个选定的迭代次数。每次迭代用于更好地估计内部 LDA 参数。对于测试,我们可以使用较小的迭代次数,例如 50 次;而在实际应用中,使用10002000迭代。最后,我们将调用实际构建 LDA 模型的void estimate() 方法:
model.setNumIterations(1000); 
model.estimate(); 

该模型输出以下结果:

    0 0,06654  game england year time win world 6 
    1 0,0863  year 1 company market growth economy firm 
    2 0,05981  people technology mobile mr games users music 
    3 0,05744  film year music show awards award won 
    4 0,11395  mr government people labour election party blair 

    [beta: 0,11328] 
    <1000> LL/token: -8,63377

    Total time: 45 seconds

LL/token表示模型的对数似然性,除以标记总数,表示数据给定模型的可能性。值增加意味着模型在改进。

输出还显示了描述每个主题的热门词汇。这些词非常符合最初的主题:

  • Topic 0 : game , england , year , time , win , world , 6  ⇒ sport
  • 话题一 : year1companymarketgrowtheconomyfirm财务
  • 话题二 : peopletechnologymobilemrgamesusersmusictech
  • 话题三 : filmyearmusicshowawardsawardwon娱乐
  • 话题四 : mrgovernmentpeoplelaborelectionpartyblair政治

还有一些词没有多大意义,比如mr16。我们可以把它们包含在停用词列表中。还有,有些词会出现两次,例如,awardawards。这是因为我们没有应用任何词干分析器或词尾化管道。

在下一节中,我们将看看这个模型是否有任何优点。

评估模型

由于统计主题建模具有无监督的性质,这使得模型选择困难。对于某些应用程序,可能会有一些外部任务,如信息检索或文档分类,可以对这些任务的性能进行评估。然而,一般来说,我们希望评估模型概括主题的能力,而不考虑任务。

在 2009 年,Wallach 等人介绍了一种方法,通过计算模型下被拒绝的文档的对数概率来测量模型的质量。看不见文档的可能性可用于比较模型——更高的可能性意味着更好的模型。

我们将使用以下步骤评估模型:

  1. 让我们将文档划分为训练集和测试集(即延期文档),其中 90%用于训练,10%用于测试:
// Split dataset 
InstanceList[] instanceSplit= instances.split(new Randoms(), new 
   double[] {0.9, 0.1, 0.0}); 
  1. 现在让我们仅使用我们的文档来重建我们的模型:
// Use the first 90% for training 
model.addInstances(instanceSplit[0]); 
model.setNumThreads(4); 
model.setNumIterations(50); 
model.estimate(); 
  1. 我们将初始化一个estimator对象,该对象实现 Wallach 的持有文档的对数概率,MarginalProbEstimator:
// Get estimator 
MarginalProbEstimator estimator = model.getProbEstimator(); 

Annalyn Ng 在她的博客中总结了对 LDA 的直观描述:annalyzin . WordPress . com/2015/06/21/laymans-explain-of-topic-modeling-with-LDA-2/。要更深入地了解 LDA 算法、其组件及其工作原理,请查看 David Blei 等人(2003 年)在 http://jmlr.csail.mit.edu/papers/v3/blei03a.html 发表的论文 LDA 原文,或者查看布朗大学的 D. Santhanam 在www . cs . Brown . edu/courses/csci 2950-p/spring 2010/lectures/2010-03-03 _ Santhanam . pdf发表的摘要。

该类实现了许多估计器,这些估计器需要对 LDA 方法如何工作有相当深入的理论知识。我们将选择从左到右的评估器,它适用于广泛的应用程序,包括文本挖掘和语音识别。从左到右赋值器被实现为double evaluateLeftToRight方法,接受以下组件:

  • Instances heldOutDocuments:测试实例。
  • int numParticles:该算法参数表示从左到右的令牌数,默认值为 10。
  • boolean useResampling:表示是否在从左到右的评估中对主题进行重采样;重采样更精确,但会导致文档长度的二次缩放。
  • PrintStream docProbabilityStream:这是一个文件或stdout,我们在其中写入每个文档的推断对数概率。
  1. 让我们运行estimator,如下所示:
double loglike = estimator.evaluateLeftToRight( 
  instanceSplit[1], 10, false, null);); 
System.out.println("Total log likelihood: "+loglike); 

在我们的特殊情况下,estimator输出下面的log likelihood,这在与使用不同参数、管道或数据构建的其他模型相比时是有意义的——对数似然性越高,模型越好:

    Total time: 3 seconds
    Topic Evaluator: 5 topics, 3 topic bits, 111 topic mask
    Total log likelihood: -360849.4240795393

现在让我们来看看如何利用这个模型。

重用模型

由于我们通常不会动态地构建模型,因此训练一次模型并重复使用它来分类新数据通常是有意义的。

请注意,如果您想要对新文档进行分类,它们需要像其他文档一样通过相同的管道——训练和分类的管道需要相同。在训练期间,管道的数据字母表随着每个训练实例而更新。如果使用相同的步骤创建新管道,则不会产生相同的管道,因为其数据字母表为空。因此,要在新数据上使用模型,我们必须保存或加载管道和模型,并使用该管道添加新实例。

保存模型

Mallet 支持基于序列化保存和恢复对象的标准方法。

我们简单地创建一个ObjectOutputStream类的新实例,并将对象写入一个文件,如下所示:

String modelPath = "myTopicModel"; 

//Save model 
ObjectOutputStream oos = new ObjectOutputStream( 
new FileOutputStream (new File(modelPath+".model"))); 
oos.writeObject(model); 
oos.close();    

//Save pipeline 
oos = new ObjectOutputStream( 
new FileOutputStream (new File(modelPath+".pipeline"))); 
oos.writeObject(pipeline); 
oos.close(); 

恢复模型

恢复通过序列化保存的模型只是一个使用ObjectInputStream类的逆向操作:

String modelPath = "myTopicModel"; 

//Load model 
ObjectInputStream ois = new ObjectInputStream( 
  new FileInputStream (new File(modelPath+".model"))); 
ParallelTopicModel model = (ParallelTopicModel) ois.readObject(); 
ois.close();    

// Load pipeline 
ois = new ObjectInputStream( 
  new FileInputStream (new File(modelPath+".pipeline"))); 
SerialPipes pipeline = (SerialPipes) ois.readObject(); 
ois.close();    

我们讨论了如何构建一个 LDA 模型来将文档自动分类到主题中。在下一个例子中,我们将研究另一个文本挖掘问题——文本分类。

检测垃圾邮件

垃圾邮件或电子垃圾邮件是指未经请求的消息,通常带有广告内容、受感染的附件、指向网络钓鱼或恶意软件站点的链接等。虽然最广泛认可的垃圾邮件形式是电子邮件垃圾邮件,但垃圾邮件滥用也出现在其他媒体中:网站评论、即时消息、互联网论坛、博客、在线广告等等。

在本章中,我们将讨论如何建立朴素贝叶斯垃圾邮件过滤,使用 BoW 表示来识别垃圾邮件。朴素贝叶斯垃圾邮件过滤是在第一个商业垃圾邮件过滤器中实现的基本技术之一;例如,Mozilla Thunderbird 邮件客户端使用这种过滤的本地实现。虽然本章中的示例将使用电子邮件垃圾邮件,但基本方法也可以应用于其他类型的基于文本的垃圾邮件。

垃圾邮件数据集

2000 年,Androutsopoulos 等人收集了第一批垃圾邮件数据集之一,用于测试垃圾邮件过滤算法。他们研究了如何使用朴素贝叶斯分类器来检测垃圾邮件,如果额外的管道(如非索引列表、词干分析器和词汇化)有助于提高性能。该数据集由吴恩达在 OpenClassroom 的机器学习课上进行了重组,可在open classroom . Stanford . edu/main folder/document page . PHP 下载?课程=机器学习&doc =练习/ex6/ex6.html

选择并下载第二个选项ex6DataEmails.zip,如下图所示:

ZIP 包含以下文件夹:

  • nonspam-trainspam-train文件夹包含您将用于培训的预处理电子邮件。他们每人有 350 封电子邮件。
  • nonspam-testspam-test文件夹构成测试集,包含 130 封垃圾邮件和 130 封非垃圾邮件。这些是你将要做预测的文件。请注意,即使单独的文件夹告诉您正确的标签,您也应该在没有这些知识的情况下对所有的测试文档进行预测。做出预测后,您可以使用正确的标注来检查您的分类是否正确。

为了利用 Mallet 的文件夹迭代器,让我们按如下方式重新组织文件夹结构。我们将创建两个文件夹,traintest,并将spam/nospam文件夹放在相应的文件夹下。初始文件夹结构如下图所示:

最终的文件夹结构如下图所示:

下一步是将电子邮件转换成特征向量。

特征生成

我们将使用以下步骤执行特征生成:

  1. 我们将创建一个默认管道,如前所述:
ArrayList<Pipe> pipeList = new ArrayList<Pipe>(); 
pipeList.add(new Input2CharSequence("UTF-8")); 
Pattern tokenPattern = Pattern.compile("[\\p{L}\\p{N}_]+"); 
pipeList.add(new CharSequence2TokenSequence(tokenPattern)); 
pipeList.add(new TokenSequenceLowercase()); 
pipeList.add(new TokenSequenceRemoveStopwords(new 
   File(stopListFilePath), "utf-8", false, false, false)); 
pipeList.add(new TokenSequence2FeatureSequence()); 
pipeList.add(new FeatureSequence2FeatureVector()); 
pipeList.add(new Target2Label()); 
SerialPipes pipeline = new SerialPipes(pipeList); 

注意,我们添加了一个额外的FeatureSequence2FeatureVector管道,将一个特征序列转换成一个特征向量。当我们在特征向量中有数据时,我们可以使用任何分类算法,就像我们在前面的章节中看到的那样。我们将继续 Mallet 中的例子来演示如何构建分类模型。

  1. 我们初始化一个文件夹迭代器,将我们的示例加载到由spamnonspam子文件夹中的电子邮件示例组成的train文件夹中,这些示例将被用作示例标签:
FileIterator folderIterator = new FileIterator( 
    new File[] {new File(dataFolderPath)}, 
    new TxtFilter(), 
    FileIterator.LAST_DIRECTORY); 
  1. 我们将用我们想要用来处理文本的pipeline对象构建一个新的实例列表:
InstanceList instances = new InstanceList(pipeline); 
  1. 我们将处理迭代器提供的每个实例:
instances.addThruPipe(folderIterator); 

我们现在已经加载了数据,并将其转换为特征向量。让我们在训练集上训练我们的模型,并在test集上预测spam/nonspam分类。

培训和测试

Mallet 在cc.mallet.classify包中实现了一组分类器,包括决策树、朴素贝叶斯、AdaBoost、bagging、boosting 等等。我们将从一个基本的分类器开始,即朴素贝叶斯分类器。分类器由ClassifierTrainer类初始化,当我们调用它的train(Instances)方法时,它返回一个分类器:

ClassifierTrainer classifierTrainer = new NaiveBayesTrainer(); 
Classifier classifier = classifierTrainer.train(instances); 

现在让我们看看这个分类器是如何工作的,并在一个单独的数据集上评估它的性能。

模型性能

要在单独的数据集上评估分类器,我们将使用以下步骤:

  1. 让我们从导入位于test文件夹中的电子邮件开始:
InstanceList testInstances = new 
   InstanceList(classifier.getInstancePipe()); 
folderIterator = new FileIterator( 
    new File[] {new File(testFolderPath)}, 
    new TxtFilter(), 
    FileIterator.LAST_DIRECTORY); 
  1. 我们将通过我们在培训期间初始化的同一管道传递数据:
testInstances.addThruPipe(folderIterator); 
  1. 为了评估分类器的性能,我们将使用cc.mallet.classify.Trial类,它由一个分类器和一组测试实例初始化:
Trial trial = new Trial(classifier, testInstances); 
  1. 初始化时立即执行评估。然后,我们可以简单地删除我们关心的措施。在我们的示例中,我们希望检查垃圾邮件分类的精确度和召回率,或 F-measure,它返回两个值的调和平均值,如下所示:
System.out.println( 
  "F1 for class 'spam': " + trial.getF1("spam")); 
System.out.println( 
  "Precision:" + trial.getPrecision(1)); 
System.out.println( 
  "Recall:" + trial.getRecall(1)); 

评估对象输出以下结果:

    F1 for class 'spam': 0.9731800766283524
    Precision: 0.9694656488549618
    Recall: 0.9769230769230769

结果表明,该模型正确发现了 97.69%的垃圾邮件(召回),当它将一封邮件标记为垃圾邮件时,它在 96.94%的情况下是正确的。换句话说,它每 100 封垃圾邮件中会漏掉大约 2 封,每 100 封有效邮件中会有 3 封被标记为垃圾邮件。所以,它并不真的完美,但它不仅仅是一个好的开始!

摘要

在这一章中,我们讨论了文本挖掘与传统的基于属性的学习有何不同,它需要大量的预处理步骤来将书面自然语言转换为特征向量。此外,我们讨论了如何通过将 Mallet 应用于两个实际问题来利用它,Mallet 是 NLP 的一个基于 Java 的库。首先,我们使用 LDA 模型对新闻语料库中的主题进行建模,以建立能够将主题分配给新文档的模型。我们还讨论了如何使用 BoW 表示构建朴素贝叶斯垃圾邮件过滤分类器。

本章总结了如何应用各种库来解决机器学习任务的技术演示。由于我们无法涵盖更多有趣的应用,也无法在许多方面给出更多细节,下一章将给出一些关于如何继续学习和深入特定主题的进一步指导。

十一、下一步是什么?

本章结束了我们回顾 Java 库中机器学习的旅程,并讨论了如何利用它们来解决现实生活中的问题。然而,这绝不应该是你旅程的终点。这一章会给你一些实用的建议,告诉你如何在现实世界中开始部署你的模型,有哪些陷阱,以及到哪里去加深你的知识。它还为您提供了进一步的指导,告诉您在哪里可以找到更多的资源、材料、场所和技术来更深入地研究机器学习。

本章将涵盖以下主题:

  • 现实生活中机器学习的重要方面
  • 标准和标记语言
  • 云中的机器学习
  • 网络资源和竞赛

现实生活中的机器学习

论文、会议报告和讲座通常不会讨论模型在生产环境中是如何实际部署和维护的。在这一节中,我们将探讨一些应该考虑的方面。

噪声数据

在实践中,由于各种原因,例如测量误差、人为错误以及在对训练样本进行分类时专家判断的误差,数据通常包含误差和缺陷。我们将所有这些称为噪声。当具有未知属性值的示例被一组对应于缺失值的概率分布的加权示例替换时,噪声也可能来自缺失值的处理。学习数据中的噪声的典型后果是新数据中的学习模型的低预测精度以及用户难以解释和理解的复杂模型。

阶级不平衡

类别不平衡是我们在第七章、欺诈和异常检测中遇到的一个问题,我们的目标是检测欺诈性保险索赔。面临的挑战是,数据集的很大一部分(通常超过 90%)描述了正常活动,只有一小部分数据集包含欺诈示例。在这种情况下,如果模型总是预测正常,那么它在 90%的情况下是正确的。这个问题在实践中非常普遍,并且可以在各种应用中观察到,包括欺诈检测、异常检测、医疗诊断、漏油检测和面部识别。

现在,知道了什么是阶级失衡问题,为什么它是一个问题,我们来看看如何处理这个问题。第一种方法是关注分类准确度之外的度量,比如召回率、精确度和 f-measure。这样的衡量标准集中在一个模型在预测少数类(回忆)时有多准确,以及虚警的比例是多少(精度)。另一种方法是基于重采样,其主要思想是以这样一种方式减少过度表示的例子的数量,即新的集合包含两类的平衡比率。

特征选择

特征选择可以说是建模中最具挑战性的部分,需要领域知识和对手头问题的深刻见解。然而,良好特性的属性如下:

  • 可重用性:特性应该可以在不同的模型、应用程序和团队中重用。
  • 可转换性:你应该能够通过一个操作转换一个特征,例如log()max(),或者通过一个自定义计算将多个特征组合在一起。
  • 可靠性:特性应该易于监控,并且应该有适当的单元测试来最小化错误或问题。
  • 可解释性:要执行前面的任何操作,您需要能够理解特性的含义并解释它们的值。

捕捉特征的能力越强,结果就越准确。

模型链接

某些模型可能会产生输出,用作另一个模型中的特征。此外,我们可以使用多个模型——集合——将任何模型转化为一个特征。这是一个获得更好结果的好方法,但是这也会导致问题。必须注意模型的输出已经准备好接受依赖关系。此外,尽量避免反馈循环,因为它们会在管道中产生依赖性和瓶颈。

评估的重要性

另一个重要的方面是模型评估。除非你将你的模型应用于新数据,并衡量一个业务目标,否则你就不是在做预测分析。评估技术,如交叉验证和分离的训练/测试集,只是简单地分割您的测试数据,这只能为您提供模型将如何执行的估计。生活中通常不会给你一个定义了所有案例的训练数据集,所以在现实世界的数据集中定义这两个数据集需要很大的创造性。

在一天结束时,我们希望提高一个业务目标,例如提高广告转化率,并在推荐的项目上获得更多的点击。为了测量改进,执行 A/B 测试,测量统计上相同的群体中的度量差异,每个群体都经历不同的算法。关于产品的决策总是由数据驱动的。

A/B 检验是一种有两个变量的随机实验的方法:A,对应原始版本,控制实验;B 对应于一个变量。该方法可用于确定变体是否优于原始版本。它可以用来测试从网站变更到销售电子邮件到搜索广告的一切。Udacity 提供免费课程,涵盖在www.udacity.com/course/ab-testing-ud 257的 A/B 测试的设计和分析。

将模型投入生产

从在实验室构建准确的模型到将其部署到产品中的过程涉及数据科学和工程的协作,如以下三个步骤所示:

  1. 数据研究和假设构建包括问题建模和执行初始评估。
  2. 解决方案构建和实现是您的模型通过将其重写为更高效、稳定和可伸缩的代码而进入产品流程的地方。
  3. 在线评估是对业务目标进行 A/B 测试,利用实时数据评估模型的最后阶段。

下图更好地说明了这一点:

模型维护

我们需要解决的另一个方面是如何维护模型。这是一种不会随时间而改变的模式吗?建模一个动态现象需要模型随时间调整它的预测吗?

该模型通常是在离线批处理定型中构建的,然后用于实时数据以提供预测,如下图所示。如果我们能够收到关于模型预测的反馈,例如,股票是否如模型预测的那样上涨,以及候选人是否对竞选活动做出了回应,那么反馈应该用于改进初始模型:

反馈对于改进初始模型非常有用,但是一定要注意你正在采样的数据。例如,如果您有一个模型来预测谁会对某个活动做出响应,那么您最初将使用一组随机联系的客户端,这些客户端具有特定的响应/未响应分布和特征属性。该模型将只关注最有可能做出回应的客户子集,您的反馈将为您返回做出回应的客户子集。通过包含这些数据,该模型在特定的子群中更加准确,但是可能会完全遗漏一些其他的群。我们称这个问题为探索与剥削。Osugi 等人(2005 年)和 Bondu 等人(2010 年)提出了解决这个问题的一些方法。

标准和标记语言

随着预测模型变得越来越普遍,共享模型和完成建模过程的需要导致开发过程和可互换格式的形式化。在这一节中,我们将回顾两个事实上的标准,一个涵盖数据科学过程,另一个指定应用程序之间共享模型的可互换格式。

CRISP-DM

跨行业数据挖掘标准流程 ( CRISP-DM )描述了数据科学家在行业中常用的数据挖掘流程。CRISP-DM 将数据挖掘科学过程分为六个主要阶段:

  • 业务理解
  • 数据理解
  • 数据准备
  • 建模
  • 评估
  • 部署

在下图中,箭头表示流程流,它可以在各个阶段之间来回移动。此外,这个过程不会随着模型部署而停止。外部箭头表示数据科学的循环性质。在此过程中吸取的经验教训可以引发新的问题,并在改进之前结果的同时重复此过程:

SEMMA 方法论

另一种方法是采样、探索、修改、建模和评估 ( 塞马)。SEMMA 描述了数据科学中的主要建模任务,同时撇开了数据理解和部署等业务方面。SEMMA 由 SAS Institute 开发,SAS Institute 是最大的统计软件供应商之一,旨在帮助其软件用户执行数据挖掘的核心任务。

预测模型标记语言

预测 模型标记语言 ( PMML )是一种基于 XML 的交换格式,允许机器学习模型在应用程序和系统之间轻松共享。支持的模型包括逻辑回归、神经网络、决策树、朴素贝叶斯、回归模型和许多其他模型。典型的 PMML 文件由以下部分组成:

  • 包含一般信息的标题
  • 数据字典,描述数据类型
  • 数据转换,指定规范化、离散化、聚合或自定义函数的步骤
  • 模型定义,包括参数
  • 列出模型使用的属性的挖掘架构
  • 允许对预测结果进行后处理的目标
  • 输出列出要输出的字段和其他后处理步骤

生成的 PMML 文件可以导入任何使用 PMML 的应用程序,如 Zementis adaptive decision 和****predictive analytics(ADAPA)和 universal PMML 插件 ( UPPI )评分引擎;Weka,内置支持回归、一般回归、神经网络、TreeModel、RuleSetModel、支持向量机 ( SVM )模型;Spark,可以导出 k-means 聚类、线性回归、岭回归、lasso 模型、二元 logistic 模型、SVM;和级联,可以将 PMML 文件转换成 Apache Hadoop 上的应用程序。

下一代 PMML 是一种被称为可移植分析格式 ( PFA )的新兴格式,它提供了一个通用界面来跨环境部署完整的工作流。

云中的机器学习

建立一个能够随着数据量的增加而扩展的完整的机器学习堆栈可能是一个挑战。最近的一波软件即服务(SaaS)基础设施即服务 ( IaaS )范式也蔓延到了机器学习领域。如今的趋势是将实际的数据预处理、建模和预测转移到云环境中,只关注建模任务。

在本节中,我们将回顾一些有前途的服务,这些服务提供算法、已经在特定领域中训练的预测模型,以及支持数据科学团队中的协作工作流的环境。

机器学习即服务

第一类是算法即服务,为您提供 API 甚至图形用户界面,将数据科学管道的预编程组件连接在一起:

  • 谷歌预测 API(Google Prediction API):它是首批通过其 web API 推出预测服务的公司之一。该服务与作为数据存储的谷歌云存储集成在一起。用户可以建立一个模型并调用 API 来获得预测。
  • BigML :它实现了一个用户友好的图形界面,支持许多存储提供商(例如,亚马逊 S3),并提供了各种各样的数据处理工具、算法和强大的可视化。
  • 微软 Azure 机器学习:这提供了一个大型的机器学习算法和数据处理功能库,以及图形用户界面,将这些组件连接到一个应用程序。此外,它还提供了完全托管的服务,您可以使用该服务将预测模型部署为现成的 web 服务。
  • 亚马逊机器学习:进入市场相当晚。它的主要优势是与其他亚马逊服务的无缝集成,而算法和用户界面的数量需要进一步改进。
  • IBM Watson Analytics(IBM Watson Analytics):它专注于为特定领域(如语音识别、机器翻译和异常检测)提供已经手工制作的模型。它通过解决特定的用例,面向广泛的行业。
  • 预测。IO :它是一个自托管的开源平台,提供从数据存储到建模再到服务预测的完整堆栈。预测。IO 可以与 Apache Spark 对话,以利用其学习算法。此外,它还附带了针对特定领域的各种模型,例如推荐系统、流失预测等。

预测 API 是一个新兴的新领域,所以这些只是一些众所周知的例子;kdnuggeswww . kdnugges . com/2015/12/machine-learning-data-science-APIs . html整理了 50 个机器学习 API 的列表。

要了解更多信息,你可以访问 PAPI,预测 API 和应用国际会议,网址为 www.papi.io 或者看看这本书:引导机器学习, L Dorard ,Createspace 独立出版社,2014 年。

网络资源和竞赛

在本节中,我们将回顾在哪里可以找到用于学习、讨论、演示或强化我们的数据科学技能的其他资源。

资料组

加州大学欧文分校是最著名的机器学习数据集仓库之一。UCI 存储库包含 300 多个数据集,涵盖各种挑战,包括扑克、电影、葡萄酒质量、活动识别、股票、出租车服务轨迹、广告等。每个数据集通常都配有一篇使用该数据集的研究论文,它可以提示您如何开始以及预测基线是什么。

可以在 https://archive.ics.uci.edu访问 UCI 机器学习库,如下所示:

GitHub 上还有一个保存完好的陈的收藏:/github.com/caesar0301/awesome-public-datasets

awesome 公共数据集存储库维护着 400 多个数据源的链接,这些数据源来自各个领域,包括农业、生物学、经济学、心理学、博物馆和交通运输。专门针对机器学习的数据集收集在图像处理、机器学习和数据挑战部分。

在线课程

由于在线课程的提供,学习如何成为数据科学家变得更加容易。以下是在线学习不同技能的免费资源列表:

了解机器学习更多信息的一些在线课程如下:

竞争

提高知识的最佳方式是解决实际问题,如果你想建立一个经过验证的项目组合,机器学习竞赛是一个可行的起点:

网站和博客

除了在线课程和竞赛,还有许多网站和博客发布数据科学社区的最新发展、他们解决不同问题的经验或最佳实践。一些好的起点如下:

场地和会议

以下是几个顶级学术会议的最新算法:

  • 数据库中的知识发现 ( KDD
  • 计算机视觉与模式识别 ( CVPR )
  • 神经信息处理系统年会 ( NIPS
  • 机器学习国际会议 ( ICML )
  • 数据挖掘国际会议 ( ICDM )
  • 普适计算国际联合会议 ( UbiComp )
  • 国际人工智能联合会议 ( IJCAI )

一些商务会议如下:

  • 奥莱利地层会议
  • Strata + Hadoop 世界大会
  • 预测分析世界
  • MLconf

你也可以去当地的聚会团体看看。

摘要

在本章中,我们通过讨论模型部署的某些方面来结束本书,并研究了数据科学过程和可互换预测模型格式的标准。我们还回顾了在线课程、竞赛、网络资源和会议,这些都可以帮助你掌握机器学习的艺术。

我希望这本书能启发您更深入地研究数据科学,并激励您动手尝试各种库,了解如何解决不同的问题。请记住,所有的源代码和其他资源都可以在 Packt Publishing 网站上找到:www.packtpub.com/

posted @ 2024-08-19 17:25  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报