ElasticStack-机器学习-全-

ElasticStack 机器学习(全)

原文:annas-archive.org/md5/cc649150a9588552cb7634c49784719c

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Elastic Stack,之前被称为 ELK Stack,是一种日志分析解决方案,帮助用户有效地摄取、处理和分析搜索数据。随着机器学习这一关键商业功能的加入,Elastic Stack 使这一过程变得更加高效。本更新的第二版《使用 Elastic Stack 进行机器学习》全面概述了 Elastic Stack 的机器学习功能,包括时间序列数据分析以及分类、回归和异常检测。

本书首先以直观的方式解释机器学习概念。然后,你将对不同类型的数据进行时间序列分析,例如日志文件、网络流量、应用指标和金融数据。随着你通过章节的进展,你将在 Elastic Stack 中部署机器学习以进行日志记录、安全和指标。最后,你将发现数据框分析如何开启一系列全新的用例,机器学习可以帮助你解决这些问题。

在阅读完这本 Elastic Stack 书籍之后,你将具备实际操作的机器学习和 Elastic Stack 经验,以及将机器学习融入你的分布式搜索和数据分析平台所需的知识。

本书面向的对象

如果你是一名数据专业人士,希望在不依赖机器学习专家或定制开发的情况下深入了解 Elasticsearch 数据,那么这本 Elastic Stack 机器学习书籍就是为你准备的。如果你希望将机器学习与你的可观察性、安全和分析应用程序集成,这本书也会很有用。为了充分利用这本书,你需要具备 Elastic Stack 的工作知识。

本书涵盖的内容

第一章IT 中的机器学习,作为对 IT 和安全操作中手动数据分析历史挑战的介绍和背景入门。本章还提供了对 Elastic 机器学习操作理论的全面概述,以便深入了解底层发生的事情。

第二章启用和实施,解释了在 Elastic Stack 中启用机器学习功能,并详细说明了 Elastic 机器学习算法的操作理论。此外,还详细解释了 Elastic 机器学习的后勤操作。

第三章异常检测,详细介绍了时间序列分析核心的无监督自动化异常检测技术。

第四章预测,解释了 Elastic 机器学习复杂的时间序列模型不仅可以用于异常检测。预测能力使用户能够将趋势和行为外推到未来,以帮助诸如容量规划等用例。

第五章解读结果,解释了如何全面理解异常检测和预测的结果,并利用它们在可视化、仪表板和信息图表中发挥优势。

第六章基于机器学习分析的警报,解释了将 Elastic 警报的主动通知能力与机器学习揭示的见解相结合的不同技术,以使异常检测更具可操作性。

第七章AIOps 和根本原因分析,解释了如何利用 Elastic 机器学习从不同的数据源全面检查和分析数据,以关联视图的形式提供分析人员的优势。

第八章在其他 Elastic Stack 应用中的异常检测,解释了其他 Elastic Stack 应用如何利用异常检测为数据分析带来额外价值。

第九章介绍数据帧分析,涵盖了数据帧分析的概念,它与时间序列异常检测的不同之处,以及用户可用的工具来加载、准备、转换和分析数据,使用 Elastic 机器学习。

第十章离群值检测,涵盖了数据帧分析中的离群值检测分析能力以及 Elastic 机器学习。

第十一章分类分析,涵盖了数据帧分析中的分类分析能力以及 Elastic 机器学习。

第十二章回归,涵盖了数据帧分析中的回归分析能力以及 Elastic 机器学习。

第十三章推理,涵盖了使用训练好的机器学习模型进行“推理”——以实际操作的方式预测输出值。

附录**:异常检测技巧,包含了一系列适合其他章节的实用建议主题。这些有用的信息将帮助您充分利用 Elastic ML。

要充分利用这本书

您需要一个网络连接良好的系统和一个 Elastic 账户。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有其他来自我们丰富的书籍和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一份包含本书中使用的截图/图表彩色图像的 PDF 文件。您可以从这里下载:static.packt-cdn.com/downloads/9781801070034_ColorImages.pdf

使用的约定

本书中使用了多种文本约定。

文本中的代码:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“分析还可以通过设置partition_field_name来沿分类字段拆分。”

代码块设置如下:

18/05/2020 15:16:00 DB Not Updated [Master] Table

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

export DATABRICKS_AAD_TOKEN=<azure-ad-token>

粗体:表示新术语、重要词汇或您在屏幕上看到的词汇。例如,菜单或对话框中的文字会以这种方式显示。以下是一个示例:“现在让我们点击查看 结果按钮,详细了解异常检测作业在数据中找到的内容。”

小贴士或重要注意事项

看起来像这样。

联系我们

读者反馈始终欢迎。

customercare@packtpub.com

勘误表:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您向我们报告。请访问www.packtpub.com/support/errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。

使用链接到材料的copyright@packt.com

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

评论

请留下评论。一旦您阅读并使用过这本书,为何不在您购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,Packt 公司可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!

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

第一部分 – 使用 Elastic Stack 开始机器学习

本节提供了对 Elastic ML 工作方式的直观理解 – 不仅包括算法所做的工作,还包括 Elastic Stack 中软件操作的物流。

本节涵盖了以下章节:

  • 第一章, IT 领域的机器学习

  • 第二章, 启用和实施

第一章:IT 领域的机器学习

十年前,使用基于机器学习ML)的技术在 IT 运营或 IT 安全中似乎有点像科幻小说。然而,如今,它已成为软件供应商最常用的热门词汇之一。显然,对技术需求的认知和技术最先进实现所能带来的能力都发生了重大转变。这种演变对于充分理解 Elastic ML 的出现及其旨在解决的问题至关重要。

本章致力于回顾 Elastic ML 背后的历史和概念。它还讨论了可以进行的不同类型的分析和可以解决的问题的类型。具体来说,我们将涵盖以下主题:

  • 克服 IT 历史上的挑战

  • 应对数据繁多的问题

  • 自动化异常检测的出现

  • 无监督机器学习与监督机器学习

  • 使用无监督机器学习进行异常检测

  • 将监督机器学习应用于数据框分析

克服 IT 历史上的挑战

IT 应用支持专家和应用架构师的工作要求很高,期望值也很高。他们不仅要负责将新的创新项目引入业务,还要尽可能平稳地保持当前部署的应用程序运行。今天的应用程序比以往任何时候都要复杂得多——它们高度组件化、分布式,并且可能是虚拟化/容器化的。它们可能使用敏捷方法或外包团队开发。此外,它们很可能不断变化。一些 DevOps 团队声称他们通常每天可以对实时生产系统进行 100 多次更改。试图理解现代应用程序的健康和行为就像机械师在汽车行驶时检查汽车一样。

IT 安全运营分析师在跟上日常运营方面面临类似的挑战,但他们显然有不同的重点,那就是确保企业安全并减轻新兴威胁。黑客、恶意软件和恶意内部人员已经变得如此普遍和复杂,以至于普遍的观点是,不再是一个组织是否会受到攻击的问题——而是他们何时会发现这个问题。显然,尽早(在造成太多损害之前)了解攻击情况,比从执法机构或晚间新闻首次得知攻击情况要好。

那么,他们如何得到帮助?问题的关键是应用专家和安全分析师缺乏数据来帮助他们有效地完成工作吗?实际上,在大多数情况下,情况正好相反。许多 IT 组织正陷入数据海洋。

应对数据繁多的问题

IT 部门在监控工具上的投资已有几十年,拥有十几个或更多工具积极收集和存档每天可测量为太字节甚至拍字节的数据并不罕见。这些数据可以从基本的基础设施和网络级数据到深入的诊断数据以及/或系统和应用程序日志文件。

也可以跟踪业务层面的关键绩效指标KPIs),有时包括关于最终用户体验的数据。在某些方面,可用的数据深度和广度比以往任何时候都要全面。为了检测隐藏在数据中的新兴问题或威胁,传统上有几种主要方法可以将数据提炼成信息洞察:

  • 筛选/搜索:一些工具允许用户定义搜索条件,以帮助将数据缩减到更易于管理的集合。虽然这一功能非常有用,但通常只在怀疑有问题时以临时方式使用。即便如此,使用这种方法的成功通常取决于用户知道他们在寻找什么以及他们的经验水平——这包括对先前知识的了解、通过类似过去情况的经历以及在对搜索技术本身的专长。

  • 可视化:仪表板、图表和小部件也非常有用,可以帮助我们了解数据的行为和趋势。然而,可视化是被动的方式,需要有人监视以检测到有意义的偏差。一旦收集和绘制的指标数量超过了可供监视的眼睛数量(甚至屏幕显示它们的空间),仅视觉分析变得越来越没有用。

  • 阈值/规则:为了绕过需要有人实时监视数据才能使其具有主动性这一要求,许多工具允许用户定义在已知条件或已知项目之间的已知依赖关系触发规则或条件。然而,在当今复杂和分布式的应用程序中,您可能无法现实地定义所有适当的操作范围或模拟所有实际依赖关系。此外,应用程序或环境中的变化数量和速度可能会迅速使任何静态规则集变得无用。分析师发现自己正在追逐许多误报警报,建立了一个“狼来了”的范式,导致对生成警报的工具的怨恨和对警报可能提供的价值的怀疑。

最终,需要一种不同的方法——这种方法并不一定完全否定过去的技术,但可以以有意义的方式带来自动化和经验数据的评估。让我们面对现实,人类是不完美的——我们有着隐藏的偏见和记忆信息的能力限制,我们很容易分心和疲劳。如果正确使用,算法可以轻松弥补这些不足。

自动化异常检测的出现

机器学习(ML),虽然是一个非常广泛的话题,涵盖了从自动驾驶汽车到赢得比赛的计算机程序的一切,但它是寻找解决方案的自然选择。如果你意识到,有效的应用监控或安全威胁搜索的大多数要求仅仅是“找出与正常情况不同的事物”这一主题的变体,那么异常检测学科就自然成为开始使用机器学习技术解决这些问题的起点,这对于 IT 专业人员来说。

异常检测的科学当然不是什么新鲜事物。许多非常聪明的人已经研究并使用了多年各种算法和技术。然而,对于 IT 数据的异常检测的实际应用提出了一些有趣的限制条件,使得其他在学术上值得称道的算法不适合这项工作。以下是一些包括:

  • 及时性:对于停电、违规或其他重大异常情况的通知应尽可能快地得知,以便减轻其影响。如果能够迅速修复或控制,停机时间或持续安全妥协的风险就会最小化。无法跟上当今 IT 数据实时性的算法价值有限。

  • 可扩展性:如前所述,IT 环境中的 IT 数据量、速度和变化继续爆炸式增长。检查这些大量数据的算法必须能够与数据成线性比例扩展,以便在实际意义上可用。

  • 效率:IT 预算经常受到高度审查,以防止浪费支出,许多组织不断被要求以更少的资源做更多的事情。添加一整个超级计算机集群来运行算法并不实际。相反,必须能够使用具有典型规格的普通硬件作为解决方案的一部分。

  • 通用性:虽然高度专业化的数据科学通常是解决特定信息问题的最佳方式,但 IT 环境中的数据多样性推动了对一种可以广泛适用于大多数用例的东西的需求。在长期来看,相同技术的可重用性要更加经济高效。

  • 适应性:不断变化的 IT 环境将很快使脆弱的算法变得无用。训练和重新训练机器学习模型只会引入另一个浪费时间且无法承担的冒险。

  • 准确性:我们已经知道,来自传统阈值和基于规则的系统的警报疲劳是一个真正的问题。用一个假警报生成器替换另一个不会给任何人留下印象。

  • 易用性:即使所有之前提到的限制条件都可以满足,任何需要大量数据科学家来实施其解决方案的解决方案都会过于昂贵,并且会立即被淘汰。

因此,我们现在正进入挑战的核心——创建一个快速、可扩展、准确、低成本的反常检测解决方案,每个人都将使用并喜爱它,因为它工作得完美无瑕。没问题!

虽然听起来很令人畏惧,但 Prelert 的创始人兼首席技术官 Steve Dodson 在 2010 年就接受了这个挑战。虽然 Dodson 确实带来了他的学术才能,但最终成为 Elastic ML 的技术起源于解决真实 IT 应用问题的努力——首先是解决一家主要伦敦金融公司交易平台上的一个讨厌的间歇性故障。Dodson 和几位加入这个项目的工程师帮助银行的团队使用异常检测技术自动识别出麦田里的针,使分析师能够专注于那些出现问题的少量相关指标和日志消息。识别根本原因(一个故障服务,其恢复导致一系列后续网络问题,造成了混乱)最终使应用程序稳定,并防止银行在之前解决方案上花费大量金钱,那个解决方案是一个未计划的、昂贵的网络升级。

随着时间的推移,然而,变得明显的是,即使是那个初始的成功也只是个开始。几年和几千个实际应用案例之后,Prelert 与 Elastic 的结合是自然而然的——这是一个使大数据易于访问的平台与帮助克服人类分析局限性的技术的结合。

快进到 2021 年,自联合以来已经过去了整整 5 年,Elastic ML 在成熟和扩展 ML 平台功能方面已经取得了长足的进步。本书的第二版总结了多年来对 Elastic ML 所做的更新,包括将集成引入 Elastic 在可观察性和安全方面的几个解决方案。第二版还包括了“数据帧分析”的引入,这在书的第三部分有详细的讨论。为了对 Elastic ML 的工作有一个扎实、内在的理解,我们首先需要掌握一些术语和概念,以便进一步了解事物。

无监督学习与监督学习的比较

尽管机器学习有许多子类型,但其中两个非常突出(并且与 Elastic ML 相关)的是无监督学习和监督学习。

在无监督机器学习中,没有来自人类的外部指导或方向。换句话说,算法必须完全依靠自身学习(并建模)数据的模式。一般来说,这里最大的挑战是让算法能够准确地揭示输入数据正常模式的检测偏差,为用户提供有意义的见解。如果算法无法做到这一点,那么它就无用了,也不适合使用。因此,算法必须非常稳健,能够考虑到输入数据可能表现出的所有复杂性。

在监督机器学习中,输入数据(通常是多元数据)用于帮助建模期望的结果。与无监督机器学习的关键区别在于,人类事先决定使用哪些变量作为输入,并提供预期目标变量的“真实”示例。然后,算法评估输入变量如何相互作用并影响已知的输出目标。为了准确地获得期望的输出(例如预测),算法不仅必须“正确类型的数据”确实表达了情况,而且输入数据必须足够多样化,以便有效地学习输入数据与输出目标之间的关系。

因此,这两种情况都需要良好的输入数据、良好的算法方法和良好的机制,以允许机器学习既学习数据的模式,又将这种学习应用于评估该数据的后续观察。让我们更深入地探讨 Elastic ML 如何利用无监督和监督学习的具体细节。

使用无监督机器学习进行异常检测

为了更直观地理解 Elastic ML 如何使用无监督机器学习进行异常检测,我们将讨论以下内容:

  • 关于技术的异常的严格定义

  • 无监督学习中的学习直观示例

  • 技术模型、去趋势和评分数据的描述

定义异常

异常检测是我们大多数人都有基本直觉的东西。人类在模式识别方面非常擅长,所以如果我问 100 个街头的人以下图表中有什么不寻常之处,绝大多数人(包括非技术人员)都会识别出绿色线上的峰值:

![图 1.1 – 一张显示异常的折线图img/B17040_01_001.jpg

图 1.1 – 一张显示异常的折线图

同样,假设我们询问以下照片中有什么不寻常之处:

![图 1.2 – 一张展示海豹在企鹅中的照片img/B17040_01_002.jpg

图 1.2 – 一张展示海豹在企鹅中的照片

我们可能会再次得到大多数正确指出海豹是不寻常事物的结论。但人们可能难以用显著的语言描述得出这些结论所使用的实际启发式方法。

我们可以使用两种不同的启发式方法来定义这些图像中显示的不同类型的异常:

  • 如果某个实体的行为与其过去历史中建立的模式或范围有显著偏差,那么这种情况是不寻常的。

  • 如果某个实体的某个特征与集合或群体中其他成员的相同特征有显著差异,那么这种情况是不寻常的。

这些关键定义将对 Elastic ML 的异常检测相关,因为它们形成了异常检测算法的两个主要基本操作模式(时间分析 versus 群体分析,将在第三章异常检测中探讨)。正如我们将看到的,用户将能够控制针对特定用例采用的操作模式。

学习什么是正常

正如我们所言,Elastic ML 的异常检测使用无监督学习,这意味着学习过程没有进行任何教学。没有人类协助来塑造学习的决策;它只是通过检查呈现给它的数据自行进行。这与通过沉浸式学习语言的过程略为相似,而不是通过词汇书和语法规则书来学习。

要从一个完全无知的状态过渡到一个可以以相当大的确定性做出预测的状态,需要构建一个关于该情况的情况模型。这个模型是如何创建的极其重要,因为基于此模型采取的所有后续行动的有效性将高度依赖于模型的准确性。该模型需要根据新信息灵活调整并持续更新,因为这就是在这个无监督范式下它所依赖的一切。

概率模型

概率分布可以很好地发挥这种作用。有许多基本的分布类型(Elastic ML 使用多种分布类型,例如泊松分布、高斯分布、对数正态分布,甚至是模型混合),但泊松分布是一个很好的起点,因为它适用于存在离散事件(“计数”)与时间相关的场景:

![图 1.3 – 展示泊松分布的图表(来源:https://en.wikipedia.org/wiki/Poisson_distribution#/media/File:Poisson_pmf.svg)]

![img/B17040_01_003.jpg]

图 1.3 – 展示泊松分布的图表(来源:https://en.wikipedia.org/wiki/Poisson_distribution#/media/File:Poisson_pmf.svg)

这里显示了分布的三个不同变体,每个变体都有不同的平均值(λ)和最高的预期值k。我们可以做一个类比,说这些分布模拟了一个人每天在家收到的预期邮件量,用x轴上的k表示:

  • 对于λ = 1,每天收到零件或一件邮件的概率大约为 37%。这可能适合一个收到的邮政邮件不多的大学学生。

  • 对于λ = 4,收到三件或四件物品的概率大约为 20%。这可能是一个年轻专业人士的良好模型。

  • 对于λ = 10,每天收到 10 件物品的概率大约为 13%——这可能代表一个大家庭或者某个方式下发现自己处于许多邮件列表中的家庭!

每条曲线上的离散点也给出了k的其他值的可能性(概率)。因此,该模型可以提供信息并回答诸如“收到 15 件邮件的可能性大吗?”等问题。正如我们所见,对于学生(λ = 1)或年轻专业人士(λ = 4)来说,这不太可能,但对于大家庭(λ = 10)来说,可能性就稍微大一些。显然,这里简单声明了所展示的模型适用于所描述的某些人——但应该很明显,需要有一种机制来学习每个个体情况下的模型,而不仅仅是断言它。学习这个过程是直观的。

学习模型

坚持使用邮政邮件的类比,本能地意识到确定特定家庭最适合的模型的方法可能只是每天在邮箱旁等待并记录邮递员放入邮箱的物品。也应该很明显,观察到的越多,你对模型准确性的信心应该越高。换句话说,只在邮箱旁待 3 天提供的信息和信心不如待 30 天,更不用说 300 天了。

算法上,可以设计一个类似的过程,根据观察来自动选择合适的模型。仔细审查算法对模型类型本身的选择(即泊松、高斯、对数正态等)以及该模型类型的特定系数(如前例中的λ)也必须作为自我选择过程的一部分。为此,需要不断评估模型的适用性。贝叶斯技术也被用来评估整个数据集给出的模型的可能参数值,但允许根据在特定时间点之前看到的信息量来调整这些决策。机器学习算法自动完成这项工作。

注意

对于那些想要深入了解幕后一些代表性数学的人,请参阅学术论文www.ijmlc.org/papers/398-LC018.pdf

最重要的是,所进行的建模是连续的,因此新信息会与旧信息一起考虑,对较新的信息给予指数加权。这样的模型在 60 次观察后可能如下所示:

![Figure 1.4 – 60 次观察后的样本模型img/B17040_01_004.jpg

图 1.4 – 60 次观察后的样本模型

在 400 次观察之后,数据将呈现出非常不同的样子,因为数据呈现出一连串新的观察值,其值在510之间:

![Figure 1.5 – 400 次观察后的样本模型img/B17040_01_005.jpg

图 1.5 – 400 次观察后的样本模型

此外,请注意,模型可能具有多个模式或高概率区域/簇。所学习模型(以蓝色曲线表示)与理论理想模型(黑色)的复杂性和拟合度非常重要。模型越准确,该数据集的正常状态表示就越准确,从而最终更准确地预测未来值如何与该模型相符。

模型的连续性也推动了这一要求,即该模型能够序列化到长期存储中,以便如果模型创建/分析被暂停,可以在稍后时间重新启动并继续。正如我们将看到的,模型创建、存储和利用的运营是一个复杂的协调过程,幸运的是,这个过程由弹性机器学习自动处理。

去趋势

在忠实模拟现实世界数据方面,另一个重要方面是考虑自然出现的显著泛音趋势和模式。数据是否每小时和/或每天波动,在办公时间和工作日有更多活动?如果是这样,这需要被考虑进去。弹性机器学习自动在数据中寻找显著的趋势(线性增长、周期性谐波等)并将它们排除。让我们观察以下图表:

![Figure 1.6 – 实际中的周期性检测img/B17040_01_006.jpg

图 1.6 – 实际中的周期性检测

在这里,周期性的每日周期被学习,然后被排除。模型预测边界(以浅蓝色信封围绕深蓝色信号表示)在自动检测到该周期的三个连续迭代后显著调整。

因此,随着时间的推移观察到更多的数据,模型在概率分布函数变得更加成熟以及通过自动识别和去趋势其他常规模式(如工作日、周末等)方面都获得了准确性,这些模式可能不会在几天或几周内出现。在以下示例中,随着时间的推移发现了几个趋势,包括每日、每周以及整体线性斜率:

![Figure 1.7 – 检测到多个趋势img/B17040_01_007.jpg

图 1.7 – 检测到多个趋势

这些模型变化被记录为系统注释。注释作为一个一般概念,将在后面的章节中介绍。

不寻常性评分

一旦构建了模型,就可以在概率分布中找到任何未来观察值的可能性。之前,我们提出了问题“收到 15 封邮件的可能性大吗?”现在,根据模型,这个问题可以通过一个介于 0(不可能)和 1(绝对确定)之间的数字来经验性地回答。Elastic ML 将使用模型来计算这个分数值,精确到大约 300 位有效数字(这在处理非常低的概率时可能很有帮助)。让我们观察以下图表:

![图 1.8 – 异常评分图 1.8 – 异常评分

图 1.8 – 异常评分

在这里,实际值为 921 的观察概率现在计算为 1.444e-9(或者更常见的是,仅有 0.0000001444%的机会)。这个非常小的值可能对大多数人来说并不直观。因此,ML 将进行这个概率计算,并通过分位数归一化的过程,将这个观察结果重新评估在 0 到 100 的严重性量表上,其中 100 是该特定数据集可能出现的最高异常水平。在前一个例子中,1.444e-9 的概率计算归一化到 94 分。这个归一化分数将后来作为评估异常严重性的手段,用于警报和/或分类。

时间元素

在 Elastic ML 中,本书后面将要讨论的所有异常检测都将与数据和分析相关的内在时间元素。换句话说,对于异常检测,Elastic ML 期望数据是时间序列数据,并且数据将按时间增量进行分析。这是一个关键点,也有助于区分异常检测和数据框分析,以及无监督/监督范式。

你将看到,在人口分析(在第第三章异常检测中介绍)和异常值检测(在第第十章异常值检测中介绍)方面存在细微差别。虽然它们都有效地找到了与同伴明显不同的实体,但在异常检测中的人口分析是关于时间的,而异常值检测分析不受时间的限制。随着这些主题在后面的章节中深入讨论,更多的内容将变得明显。

将监督机器学习应用于数据框分析

除了异常值检测(在第第十章异常值检测中介绍,实际上是一种无监督方法)之外,其余的数据框分析都使用监督方法。具体来说,Elastic ML 数据框分析允许你解决两种主要类型的问题:

  • 回归:用于预测连续数值(如价格、持续时间、温度等)

  • 分类:用于预测某物是否属于某个类别标签(欺诈交易与非欺诈交易等)

在这两种情况下,模型都是通过使用训练数据来映射输入变量(可以是数值或分类的)通过训练决策树来预测输出。Elastic ML 使用的特定实现是 XGBoost 的一个自定义变体,这是一个开源的梯度提升决策树框架,最近在数据科学家中因其允许他们赢得 Kaggle 比赛的能力而声名鹊起。

监督式学习的过程

监督式机器学习的整体过程与无监督方法非常不同。在监督方法中,你明确地将训练阶段与预测阶段分开。这个过程的一个非常简化的版本如下:

图 1.9 – 监督式机器学习过程

图 1.9 – 监督式机器学习过程

在这里,我们可以看到在训练阶段,从原始训练数据中提取特征以创建特征矩阵(也称为数据框)来提供给机器学习算法并创建模型。模型可以通过数据的一部分来验证其效果,并且可以采取后续的细化步骤来调整提取哪些特征,或者细化机器学习算法的参数,以提高模型预测的准确性。

一旦用户决定模型有效,该模型就会被“移动”到预测工作流程中,在那里它被用于新数据。一次一个,单个新的特征向量被推断与模型相匹配以形成预测。

为了直观地理解这个过程,想象一个场景:你想出售你的房子,但不知道应该标什么价格。你研究了你所在地区的先前销售情况,并注意到基于不同因素(卧室数量、浴室数量、面积、学校/购物场所的邻近性、房屋年龄等)的房价差异。这些因素是“特征”,在考虑每个先前销售时都是整体考虑的(而不是单独考虑)。

这个历史销售语料库是你的训练数据。它很有帮助,因为你确切地知道每处房产的售价(这正是你最终希望对你自己的房子进行预测的事情)。如果你足够研究,你可能会对房价如何强烈地受到某些特征(例如,卧室数量)的影响有所了解,而其他特征(可能是房屋的年龄)可能对定价影响不大。这是一个称为“特征重要性”的概念,将在后面的章节中再次讨论。

凭借足够的训练数据,你可能会对一套三居室、两卫、1700 平方英尺、30 岁的房子的价值有一个很好的估计。换句话说,你已经根据过去一年左右售出的类似房屋的研究,在你的脑海中构建了一个模型。如果过去的销售是“训练数据”,那么你的房屋规格(卧室、浴室等)将是定义预期价格的特征向量,基于你学到的“模型”。

你的简单心理模型显然不如使用机器学习进行回归分析时构建的模型那么严谨,该分析使用了数十个相关输入特征,但这个简单的类比希望巩固了从先前已知情况中学习,然后将该知识应用于当前新情况的过程。

摘要

总结本章所讨论的内容,我们涵盖了 IT 中机器学习的起源故事——它源于在企业环境中对收集的大量、不断增长的数据进行自动分析的需求。我们还对 Elastic ML 中的不同类型机器学习有了更直观的了解,这包括无监督的异常检测和监督数据框分析。

在我们浏览剩余的章节时,我们经常会将我们试图解决的问题的用例映射到 Elastic ML 的不同操作模式。

记住,如果数据是时间序列,意味着它在时间上定期出现(指标/性能数据、日志文件、交易等),那么 Elastic ML 的异常检测可能就是你所需要的全部。正如你将看到的,它非常灵活且易于使用,能够在广泛的数据类型上完成许多用例。它就像瑞士军刀!本书的大部分内容(第三章至第八章)将致力于如何利用异常检测(以及相关的预测能力)来最大限度地发挥 Elastic Stack 中时间序列数据的价值。

如果你更感兴趣于在人群/队列(用户/实体行为)中寻找异常实体,你可能会在异常检测中的群体分析和使用数据框分析中的异常检测之间做出艰难的选择。主要因素可能在于你是否需要在近实时进行这项操作——在这种情况下,你可能会选择群体分析。如果近实时不是必需的,或者你需要同时考虑多个特征,那么你会选择异常检测。参见第十章,以获取关于每种方法比较和优点的更详细信息。

这留下了许多其他需要多变量建模方法的应用场景。这不仅与之前提到的房地产定价示例相一致,还包括语言检测、客户流失分析、恶意软件检测等用例。这些将完全属于数据框分析的有监督机器学习的范畴,并在第十一章至第十三章中进行介绍。

在下一章中,我们将深入探讨如何启用 Elastic ML 以及它在实际操作中的工作原理。系好安全带,享受这次旅程吧!

第二章:启用和操作化

我们刚刚学习了 Elastic ML 的基本功能,它能够实现无监督的自动异常检测和监督数据帧分析。现在,是时候详细了解 Elastic ML 在 Elastic Stack(Elasticsearch 和 Kibana)内部的工作原理了。

本章将重点介绍 Elastic ML 功能的安装(实际上,是启用)以及操作流程的详细讨论,特别是关于异常检测的部分。具体来说,我们将涵盖以下主题:

  • 启用 Elastic ML 功能

  • 理解操作化

技术要求

本章中的信息将使用 v7.10 版本的 Elastic Stack 以及截至 2020 年 11 月的 Elastic Cloud 的 Elasticsearch 服务的工作流程。

启用 Elastic ML 功能

在 Elastic Stack 内部启用 Elastic ML 功能的流程,如果你是在自管理集群内操作,与使用 Elastic Cloud 的Elasticsearch 服务ESS)相比,略有不同。简而言之,在自管理集群上,ML 功能通过许可证密钥(无论是商业密钥还是试用密钥)启用。在 ESS 中,需要在集群内配置一个专门的 ML 节点,以便利用 Elastic ML。在接下来的章节中,我们将解释这两种场景下如何实现这一过程。

在自管理集群上启用 ML

如果你有一个通过下载 Elastic 的默认 Elasticsearch 和 Kibana 分发(可在elastic.co/downloads/找到)创建的自管理集群,通过许可证密钥启用 Elastic ML 功能非常简单。请确保不要使用不包含 X-Pack 代码库的 Apache 2.0 许可的开源分发版。

与 Elastic Stack 的大部分功能不同,Elastic ML 不是免费的——它需要一个商业(具体来说,是铂金级别)许可证。然而,它是开源的,因为源代码在 GitHub 上公开(github.com/elastic/ml-cpp),用户可以查看代码、提交问题、发表评论,甚至执行拉取请求。但是,Elastic ML 的使用受 Elastic 公司商业协议的约束。

当 Elastic ML 首次发布(在 v5.x 时代)时,它是作为名为X-Pack的闭源功能的一部分,需要单独的安装步骤。然而,从版本 6.3 开始,X-Pack 的代码被“开放”(elastic.co/what-is/open-x-pack)并整合到 Elasticsearch 和 Kibana 的默认分发中。因此,不再需要单独的 X-Pack 安装步骤,只需通过商业许可证(或试用许可证)启用功能。

Elasticsearch 和 Kibana 的安装过程本身超出了本书的范围,但通过遵循 Elastic 网站上的在线文档(可在elastic.co/guide/找到)可以轻松完成。

当 Elasticsearch 和 Kibana 启动后,从左侧导航菜单中选择堆栈选项,然后选择许可证管理。您将看到如下屏幕:

图 2.1 – Kibana 中的许可证管理屏幕

图 2.1 – Kibana 中的许可证管理屏幕

注意,默认情况下,应用的许可证级别是免费的基本级别。这使您能够使用一些在 Apache 2.0 许可的开源分发版或第三方服务(如 Amazon Elasticsearch 服务)中找不到的高级功能。有关比较不同许可证级别上存在的功能的实用指南,可以在 Elastic 网站上的elastic.co/subscriptions找到。

如前所述,Elastic ML 需要铂金级别的许可证。如果您从 Elastic 购买了铂金许可证,您可以通过点击屏幕上的更新许可证按钮来应用该许可证,如图 2.1 所示。如果您没有铂金许可证,您可以通过点击开始我的试用按钮来启用 Elastic ML 和其他铂金功能(假设您同意许可证条款和条件):

图 2.2 – 开始免费 30 天试用

图 2.2 – 开始免费 30 天试用

完成此操作后,许可证屏幕将指示您现在处于 Elastic Stack 铂金功能的活跃试用状态:

图 2.3 – 试用许可证已激活

图 2.3 – 试用许可证已激活

完成此操作后,您就可以立即开始使用 Elastic ML。要利用其他铂金功能,还需要进行额外的配置步骤,但这些步骤超出了本书的范围。有关配置这些功能的进一步帮助,请参阅 Elastic 文档。

在云中启用 ML – Elasticsearch 服务

如果下载、安装和自行管理 Elastic Stack 不如直接获取作为服务提供的 Elastic Stack 平台有趣,那么请前往 Elastic Cloud (cloud.elastic.co)并注册免费试用,只需使用您的电子邮件:

图 2.4 – Elastic Cloud 欢迎屏幕

图 2.4 – Elastic Cloud 欢迎屏幕

您可以执行以下步骤:

  1. 登录 Elastic Cloud 界面后,您将能够通过点击开始您的免费试用按钮来启动免费试用:图 2.5 – Elastic Cloud 主页

    图 2.5 – Elastic Cloud 主页

    点击按钮后,您将看到您的 ESS 14 天免费试用已开始:

    ![图 2.6 – Elasticsearch 服务试用已启用 图片 B17040_02_006.jpg

    图 2.6 – Elasticsearch 服务试用已启用

  2. 当然,为了尝试 Elastic ML,你首先需要一个配置好的 Elastic Stack 集群。ESS 提供了创建所谓部署的几种选项,其中一些是为特定用例定制的。在这个例子中,我们将使用 图 2.6 左侧的 Elastic Stack 模板,并选择 I/O 优化的硬件配置,但请随意在试用期间尝试其他选项:![图 2.7 – 创建 ESS 部署 图片 B17040_02_007.jpg

    图 2.7 – 创建 ESS 部署

  3. 你还可以选择在哪个云提供商和哪个区域启动你的集群,但最重要的是,如果你想使用 ML 功能,你必须首先通过点击底部右下角的 自定义 按钮来启用一个 ML 节点。

  4. 点击 自定义 按钮,你将看到一个新屏幕,允许你添加一个 ML 节点:![图 2.8 – 自定义部署以添加 ML 节点 图片 B17040_02_008.jpg

    图 2.8 – 自定义部署以添加 ML 节点

  5. 图 2.8 的底部附近有一个链接,可以 添加机器学习节点 到你的集群。点击此链接将显示 ML 节点配置:![图 2.9 – 添加 ML 节点(s) 图片 B17040_02_009.jpg

    图 2.9 – 添加 ML 节点(s)

    注意

    在 ESS 的免费 14 天试用期间,你只能添加一个 1 GB 的 ML 节点(在一个或两个可用区)。如果你从免费试用转为付费订阅,显然可以创建更多或更大的 ML 节点。

  6. 将 ML 节点添加到配置后,点击 创建部署 按钮以启动 ESS 为你创建集群的过程,这可能需要几分钟。在此期间,你将看到你将用于访问集群的默认凭证:![图 2.10 – 默认分配的凭证 图片 B17040_02_010.jpg

    图 2.10 – 默认分配的凭证

    你可以下载这些凭证以备将来使用。如果你忘记下载它们,不要担心——如果需要,你总是可以在稍后重置密码。

  7. 一旦集群如 图 2.11 所示启动并运行(通常只需几分钟),你将看到以下部署视图,其中有一个 打开 Kibana 按钮允许你启动到你的部署:

![图 2.11 – 部署成功创建图片 B17040_02_011.jpg

图 2.11 – 部署成功创建

点击 打开 Kibana 按钮后,你将自动登录到 Kibana,在那里你将可以直接使用 ML,无需额外的配置步骤。

在这个阶段,从想要使用 Elastic ML 的用户的角度来看,之前展示的自托管配置和 ESS 中创建的设置之间几乎没有区别。然而,一个主要的区别是,在这里 ESS 中的配置将 Elastic ML 始终隔离到同一节点上的dataingestml角色)。我们将在本章后面讨论这个概念。

现在我们已经有一个启用了机器学习的 Elastic Stack,我们正在接近能够开始分析数据,这将在第三章 异常检测 中开始。但首先,让我们了解 Elastic ML 的运营化。

理解运营化

在你使用 Elastic ML 的旅程中,在某个时候了解有关 Elastic ML 如何在 Elastic Stack 中运营化的几个关键概念将是有帮助的。这包括有关在集群节点上运行的分析信息以及要由机器学习分析的数据如何检索和处理的详细信息。

注意

在这个部分中,有些概念在你实际开始在一些真实示例上使用 Elastic ML 之前可能并不直观。如果你现在觉得更喜欢浏览(甚至跳过)这一节,稍后再根据一些真实的 Elastic ML 使用经验返回,请不要担心。

机器学习节点

首先,由于 Elasticsearch 本质上是分布式多节点解决方案,Elastic Stack 的机器学习功能作为一个遵循许多相同运营概念的本地插件工作,这是很自然的。如文档(elastic.co/guide/en/elasticsearch/reference/current/ml-settings.html)所述,机器学习可以在任何或所有节点上启用,但在生产系统中,最好有专门的机器学习节点。我们在 Elastic Cloud ESS 中看到了这种最佳实践被强制执行——如果想要使用机器学习,用户必须创建专门的机器学习节点。

拥有专门的机器学习节点也有助于优化机器学习所需的具体资源类型。与因索引和搜索而涉及大量磁盘 I/O 负载的数据节点不同,机器学习节点更注重计算和内存密集型。有了这些知识,你可以为专门的机器学习节点适当地配置硬件。

一个需要注意的关键点——机器学习算法不在autodetect(用于异常检测)和data_frame_analyzer(用于数据框分析)中运行,这可以在进程列表中看到(例如,如果你在 Linux 上运行ps命令)。对于每个正在运行的机器学习作业,将有一个进程。在多节点设置中,机器学习将作业分配给每个启用了机器学习的节点以平衡工作负载。

Elastic ML 遵循一个名为xpack.ml.max_machine_memory_percent的设置,该设置控制 ML 作业可以使用的系统内存量。此设置的默认值为 30%。限制基于机器的总内存量,而不是当前空闲的内存量。别忘了 Elasticsearch JVM 可能占用高达约 50%的可用机器内存,因此为 ML 保留 30%,剩余 20%用于操作系统和其他辅助进程是谨慎的,尽管是保守的。如果这样做会导致 ML 作业的估计内存使用量超过此设置定义的限制,则不会将作业分配给节点。

虽然没有经验公式来确定专用 ML 节点的大小和数量,但以下是一些好的经验法则:

  • 对于最多 10 个数据节点的集群大小,应有一个专用的 ML 节点(如果单个节点不可用,则为高可用性/容错提供两个节点)。

  • 对于最多 20 个节点的集群,至少要有两个 ML 节点。

  • 对于每个额外的 10 个数据节点,添加一个额外的 ML 节点。

这种保留大约 10-20%的集群容量用于专用 ML 节点的一般方法当然是一个合理的建议,但这并不免除您进行自己的尺寸、特征测试和资源监控的需要。正如我们将在后面的几个章节中看到的那样,您的 ML 任务对资源的需求将极大地取决于正在调用的分析类型以及正在分析的数据的密度和体积。

作业

在 Elastic ML 中,作业是工作单元。既有异常检测作业,也有数据帧分析作业。两者都使用某种类型的数据作为输入,并以新的信息作为输出。可以使用 Kibana 中的 ML UI 创建作业,也可以通过 API 编程创建。它们还需要启用 ML 的节点。

通常,异常检测作业可以作为单次批量分析(在历史数据的一段时间内)运行,或者连续在实时时间序列数据上运行——这些数据由您的 Elastic Stack(或两者)不断索引。实际上,两者都可以。

或者,数据帧分析作业不是连续的——它们是一次性执行,产生输出结果和/或用于后续推断的输出模型,这些内容在 9 到 13 章中进行了更深入的讨论。

因此,从实际操作的角度来看,异常检测作业要复杂一些——因为可能同时运行多个作业,执行独立的事情并分析来自不同索引的数据。换句话说,异常检测作业在典型的集群中可能会持续忙碌。

正如我们稍后将更深入地看到的那样,异常检测作业的主要配置元素如下:

  • 作业名称/ID

  • 分析桶化窗口(即桶跨度

  • 获取要分析的原始数据的查询定义和设置(即数据馈送

  • 异常检测配置配方(即检测器

理解了工作的概念后,我们接下来将关注时间序列数据的分桶是如何在实时数据分析中成为一个重要的概念。

时间序列分析中的数据分桶

在 Elastic ML 的异常检测中,理解输入数据的分桶是一个重要的概念。通过在作业级别设置一个称为bucket_span的关键参数,数据馈送(下文将描述)中的输入数据被收集到小批量中进行处理。将桶跨度视为一个预分析聚合间隔——用于分析目的的数据部分聚合的时间窗口。bucket_span的持续时间越短,分析越细粒度,但也可能导致数据中存在更多的噪声伪迹。

为了说明,以下图表显示了在三个不同间隔内聚合的相同数据集:

![图 2.12 – 不同时间间隔内相同数据的聚合

![图片 B17040_02_012.jpg]

图 2.12 – 不同时间间隔内相同数据的聚合

注意到,在 5 分钟间隔聚合的版本中看到的突出异常峰值,如果数据是 60 分钟间隔聚合的,由于峰值持续时间短(<2 分钟),几乎会消失。事实上,在这个 60 分钟间隔中,峰值甚至不再显得那么异常。

这是选择bucket_span背后的一个实际考虑。一方面,拥有较短的聚合周期是有帮助的,因为它会增加分析的频率(如果存在异常,则减少通知间隔),但使其过于短暂可能会突出数据中你并不真正关心的特征。如果前面数据中显示的短暂峰值对你来说是一个有意义的异常,那么数据的 5 分钟视图就足够了。然而,如果数据中的非常短暂的扰动看起来像是不必要的干扰,那么请避免设置过低的bucket_span值。

注意

一些额外的实际考虑可以在 Elastic 的博客上找到:elastic.co/blog/explaining-the-bucket-span-in-machine-learning-for-elasticsearch

将数据馈送到 Elastic ML

异常检测作业显然需要分析数据(以及用于构建和成熟统计模型的数据)。这些数据来自 Elasticsearch 中的时间序列索引。数据馈送是这种数据定期检索(搜索)并呈现给机器学习算法的机制。其配置大部分对用户是隐藏的,除非在 UI 中创建一个高级作业(或通过使用异常检测 API)。然而,了解数据馈送在幕后做什么是很重要的。

Watcher 中的 Watch 输入概念类似,数据源将定期查询一个包含要分析数据的索引模式(或 已保存的搜索)。数据源查询数据的频率(以及每次查询多少数据)取决于多个因素:

  • query: 实际的查询(以 Elasticsearch DSL 表达)将用于从源索引中检索数据以进行分析。用户可以选择查询源索引中的所有文档,或者选择性地过滤和/或聚合数据。

  • bucket_span: 我们已经确定 bucket_span 控制当前分析窗口的宽度。因此,数据源的任务是确保桶中充满了按时间顺序排列的数据。因此,你可以看到数据源将向 Elasticsearch 执行日期范围查询。

  • frequency: 一个控制原始数据物理查询频率的参数。如果这个值在 2 到 20 分钟之间,frequency 将等于 bucket_span(即,每 5 分钟查询一次过去 5 分钟的数据)。如果 bucket_span 更长,默认情况下,frequency 将是一个更小的数字(更频繁),这样就不会期望一次性查询整个长间隔。这对于数据集相当庞大时很有帮助。换句话说,长 bucket_span 的间隔将被切割成更小的间隔,仅为了查询的目的。

  • query_delay: 这控制数据源查询一个桶跨度值的数据时“现在”之后的时间量。当通过 API 配置作业时,默认值为 60 秒,或者当通过 UI 配置作业时,默认值为 60 秒到 120 秒之间的随机值。因此,当桶跨度值为 5 分钟,query_delay 值为 60 秒,在中午 12:01 时,数据源将请求从上午 11:55 到午夜的数据。这个额外的延迟允许在摄入管道中存在延迟,以确保不会因为任何原因的摄入延迟而排除分析中的数据。如果系统检测到异常检测作业由于可能的摄入延迟而缺少数据,可能需要增加系统生成的 query_delay 来纠正。

  • scroll_size:在大多数情况下,数据源执行到 Elasticsearch 的搜索类型使用滚动 API(elastic.co/guide/en/elasticsearch/reference/current/scroll-api.html)。滚动大小定义了数据源每次查询 Elasticsearch 的数据量。例如,如果数据源被设置为每 5 分钟查询一次日志数据,但在典型的 5 分钟窗口中有 100 万事件,滚动这些数据的概念意味着并不是期望在一次巨大的查询中获取所有 100 万事件。相反,它将以scroll_size的增量进行多次查询。默认情况下,这个滚动大小保守地设置为 1,000。因此,为了获取 1 百万条记录返回给机器学习,数据源将向 Elasticsearch 请求 1,000 行,共请求 1,000 次。将scroll_size增加到 10,000 将减少滚动次数到 100。一般来说,更强大的集群应该能够处理更大的scroll_size,从而在整体过程中更加高效。

然而,对于单一指标作业来说,存在一个例外。单一指标作业(在第三章异常检测)是一个简单的机器学习作业,它只允许分析一个时间序列指标。在这种情况下,不使用滚动 API 来获取原始数据——相反,数据源将自动创建一个查询聚合(使用date_histogram聚合)。这种聚合技术也可以用于任何异常检测作业,但目前需要直接编辑作业的 JSON 配置,并且应该仅限于专家用户使用。

在将数据馈送到 Elastic ML 进行数据框分析作业时,其范式与异常检测不同,因为数据不是连续、实时地馈送到分析中。如何将数据馈送到数据框分析作业的具体细节将在第九章到第十三章中介绍。

现在我们已经对数据如何流入 Elastic ML 进行分析有了更深入的了解,接下来让我们看看一些用于支持 Elastic ML 操作的索引。

支持索引

为了 Elastic ML 能够正常运行,存在几个支持索引,它们各自承担特定的功能:

  • .ml-config

  • .ml-state-*

  • .ml-notifications-*

  • .ml-annotations-*

  • .ml-stats-*

  • .ml-anomalies-*

所有这些索引都是系统索引(并且大多数是隐藏索引),这意味着它们不是旨在被最终用户写入或操作的。然而,了解它们的功能/角色通常很有帮助,所以让我们逐一介绍。

.ml-config

.ml-config 索引包含了系统中当前定义的所有机器学习作业的配置信息。该索引中的信息可以被普通用户读取和理解。

.ml-state-*

.ml-state索引是 Elastic ML 存储有关特定数据集的数据帧分析作业和已学习的异常检测统计模型进度的内部信息的地方,以及额外的物流信息。此索引不是旨在让用户理解——它是 ML 的后端算法将在此索引中读取和写入条目。

.ml-notifications-*

此索引是 Elastic ML 存储出现在elasticsearch.log文件中的审计消息的地方。

.ml-annotations-*

此索引存储与异常检测作业相关的注释记录。这包括用户创建的注释,这些注释可以使用异常检测 UI 定义,但也包括系统创建的注释,例如摄入延迟警告和模型快照通知。

.ml-stats-*

此索引保留有关数据帧分析作业进度和性能的信息。

.ml-anomalies-*

.ml-anomalies-*索引包含 ML 作业的详细结果。这些索引在利用 ML 算法的输出方面至关重要。ML UI 中显示的所有信息都将由这些结果数据驱动。此外,通过针对这些索引配置查询来实现对异常的主动警报。关于这一点将在第六章中详细介绍,ML 分析的警报

既然我们已经知道了 Elastic ML 拥有的和管理系统索引的名称和角色,那么接下来让我们具体看看.ml-state.ml-anomalies以及它们如何有助于异常检测作业的运行时编排。

异常检测编排

由于异常检测作业可以在实时的时间序列数据上持续运行,因此发生了一个相当复杂的编排。这个过程的一个简化图示在图 2.13中显示:

图 2.13 – 异常检测作业操作的简化序列

图 2.13 – 异常检测作业操作的简化序列

autodetect过程,这是异常检测作业的物理表现,在图 2.13中由分析对比模型步骤所表示。.ml-state索引偶尔会被autodetect过程读取和写入(如下一节所述)。autodetect过程的输出(分析的结果)存储在.ml-anomalies-*索引中。

通常,上述过程在每个bucket_span(除了从.ml-state的实际读写操作)中只执行一次。关键点是这种编排使得异常检测作业可以在线(即,不是离线/批量)并且持续学习新摄入的数据。这个过程也由 Elastic ML 自动处理,因此用户不必担心实现这一切所需的复杂物流。

异常检测模型快照

如前所述,异常检测模型的“状态”存储在.ml-state索引中。然而,它并不是在每个桶跨度中实际读取或写入。相反,模型状态主要保存在autodetect进程的内存中,并且仅定期序列化到.ml-state。如果异常检测作业被要求在大量历史数据上运行,或者实时运行,那么模型将按以下方式序列化:

  • 定期,大约每 3 到 4 小时(或如果明确设置,则由background_persist_interval定义的间隔)

  • 当异常检测作业被置于关闭状态时

由于模型定期序列化,旧的快照会自动在夜间系统维护作业中删除。默认情况下,如果.ml-state索引中最新的快照超过 1 天,它们将被删除,但每天的第一张快照除外。此外,所有超过最新快照 10 天的快照都将被删除。如果您想免除特定的快照并无限期地保留它,请使用 Kibana 中的 UI 或更新的模型快照 API 将retain设置的值设置为true

可能也很明显,现在保存快照允许用户在操作过程中出现问题时,或出现意外情况时,将作业回滚到之前拍摄的模型快照之一。在附录的“技巧与窍门”部分之一,我们将通过一个示例演示如何忽略时间段并将作业回滚到使用模型快照。

摘要

总结来说,在本章中,我们涵盖了在自管理的本地 Elastic Stack 和 Elastic Cloud 的 Elasticsearch 服务中启用 Elastic ML 功能的流程。此外,我们还深入了解了与 Elastic Stack 其他部分的深度集成点以及 Elastic ML 从操作角度是如何工作的。

随着我们展望未来的章节,现在的焦点将不再是从概念和背景信息转向实际应用领域。从下一章开始,我们将直接进入 Elastic ML 的全面功能,我们将学习如何配置作业以解决日志分析、指标分析和用户行为分析中的某些实际用例。

第二部分 – 时间序列分析 – 异常检测和预测

本节的目标是理解从 Prelert 收购的遗留时间序列分析技术,以及如何将其整合到 Elastic Stack 中。

本节涵盖了以下章节:

  • 第三章, 异常检测

  • 第四章, 预测

  • 第五章, 结果解释

  • 第六章, 基于机器学习的警报

  • 第七章, AIOps 和根本原因分析

  • 第八章, 其他 Elastic Stack 应用中的异常检测

第三章: 异常检测

异常检测是 Elastic ML 的原始功能,也是最成熟的,其根源可以追溯到 Prelert 时代(在 2016 年被 Elastic 收购之前)。这项技术稳健、易于使用、功能强大,并且广泛适用于所有时间序列数据的使用案例。

这本内容丰富的章节将专注于使用 Elastic ML 检测文档/事件发生率的异常、罕见事件以及超出预期正常操作的数值。我们将通过一些简单但有效的示例来展示 Elastic ML 的功效及其易用性。

具体来说,我们将涵盖以下内容:

  • Elastic ML 作业类型

  • 拆解检测器

  • 检测事件率的变化

  • 检测指标值的变化

  • 理解高级检测器功能

  • 沿着分类特征进行拆分分析

  • 理解时间序列分析与人口分析的区别

  • 非结构化消息的分类分析

  • 通过 API 管理 Elastic ML

技术要求

本章中的信息基于 v7.10 版本的 Elastic Stack。与所有章节一样,所有示例代码都可以在 GitHub 上找到:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition.

Elastic ML 作业类型

当我们开始使用 Elastic ML UI 来配置异常检测作业时,我们会看到有五个不同的作业向导被展示:

![Figure 3.1 – 创建作业 UI 显示不同的配置向导img/B17040_03_001.jpg

图 3.1 – 创建作业 UI 显示不同的配置向导

这些不同配置向导的存在意味着存在不同的“类型”的作业。实际上,实际上只有一个作业类型——只是异常检测作业有许多选项,而且许多向导使配置的某些方面更容易。你可以通过高级向导(或 API)完成你可能希望配置的所有内容。实际上,当 Elastic ML 首次在 v5.4 版本中以 beta 版发布时,这就是所有存在的。从那时起,其他向导已经添加,以简化特定用例的可用性。

异常检测作业有许多配置设置,但其中最重要的两个是分析配置数据源

分析配置是作业将检测的异常的配方。它包含一个检测配置(称为检测器)以及一些其他设置,例如桶跨度。数据源是 Elasticsearch 将要执行的查询配置,用于检索检测器将要分析的数据。

关于不同的作业向导,以下说法是正确的:

  • 使用单一指标向导创建的作业只有一个检测器。它们的数据源包含一个查询和聚合,因此只向机器学习算法发送汇总数据。聚合是自动为您根据向导中的配置参数创建的。作业还使用一个名为summary_count_field_name的标志(设置为doc_count的值)来表示预期的将是聚合数据(而不是来自源索引的原始数据)。

  • 使用多指标向导创建的作业可以有一个或多个检测器。分析还可以通过设置partition_field_name(在章节后面描述)来按分类字段拆分。它们的数据源不包含聚合(因为机器学习代码需要看到每个字段值的每个可能实例的所有文档,并将自行进行聚合),因此将完整的 Elasticsearch 文档传递给机器学习算法。

  • 使用人口向导创建的作业可以有一个或多个检测器。向导还设置了over_field_name(在章节后面描述),表示将使用人口分析。分析还可以通过设置by_field_name(在章节后面描述)来按分类字段拆分。它们的数据源不包含聚合,因此将完整的 Elasticsearch 文档传递给机器学习算法。

  • 使用分类向导创建的作业只有一个检测器。向导还设置了categorization_field_name(在章节后面描述),表示将使用分类分析。分类分析还将by_field_name(在章节后面描述)设置为mlcategory的值。分析还可以通过设置partition_field_name(在章节后面描述)来按分类字段拆分。它们的数据源不包含聚合,因此将完整的 Elasticsearch 文档传递给机器学习算法。

  • 使用高级向导创建的作业可以利用所有可用的选项。用户需要知道自己在做什么,并正确配置作业。然而,UI 可以防止用户犯大多数错误。有经验的用户可以专门使用高级向导创建任何异常检测作业。

根据刚才描述的,作业创建的选项可能看起来令人畏惧。但不要担心——一旦我们熟悉了术语并走过了几个示例,你会发现作业配置是非常合理的;随着经验的积累,作业的配置将变得自然而然。让我们继续下一步,分解检测器的组件。

解构检测器

异常检测作业的核心是分析配置和检测器。检测器有几个关键组件:

  • 函数

  • 字段

  • 分区字段

  • 按字段

  • 覆盖字段

我们将逐一介绍它们,以便完全理解它们。请注意,然而,在接下来的几节中,我们经常会引用作业配置中的实际设置名称,就像我们正在使用高级作业编辑器或 API 一样。尽管完全理解命名法是好的,但随着你通过本章,你也会注意到许多作业配置的细节都被抽象化了,或者比实际的设置名称有更多的“UI 友好”标签。

函数

检测器函数描述了数据将在分析间隔(桶跨度)内如何聚合或测量。有许多函数,但它们可以被归类为以下几类:

![图 3.2 – 检测器函数表图片

图 3.2 – 检测器函数表

带有星号(*)的项目也有高/低单侧变体(如low_distinct_count),这允许仅在一个方向上检测异常。

字段

检测器中的一些函数需要在数据中操作一个字段。以下是一些例子:

  • max(bytes)

  • mean(products.price)

  • high_distinct_count(destination.port)

因此,该函数直接操作的字段名称简单地称为field_name

分配字段

经常会有这样的情况,检测分析需要沿着一个分类字段进行拆分,以便对该字段的所有唯一实例分别进行分析。在这种情况下,partition字段(设置称为partition_field_name)定义了要拆分的字段。例如,在电子商务中,你可能想查看每个类别的平均收入(男士服装、女士配饰等)。在这种情况下,category字段将是partition字段。我们将在本章后面探讨分析拆分。

分区字段

partition字段类似,by字段(设置称为by_field_name)是另一种拆分分析的手段,但在如何建模和评分结果方面表现不同。此外,如果使用rarefreq_rare,则by字段是强制性的。关于使用by字段进行拆分与使用partition字段进行拆分的差异的更多细节将在本章后面讨论。

超字段

over_field_name)向异常检测算法发出信号,希望进行总体分析,其中实体与其同伴进行比较(而不是与自己的过去行为进行比较)。总体分析将在本章后面进行深入讨论。

“公式”

如果我们要记录一个检测器的所有可能的配置选项,然后创建一个类似于流程图的地图,它将看起来如下:

![图 3.3 – 从零开始构建检测器的“公式”图片

图 3.3 – 从零开始构建检测器的“公式”

以下是一些关于图 3.3中所示图表的注意事项:

  • 大写文本是解释,斜体文本是检测器配置设置(by_field_namepartition_field_nameover_field_name简化为bypartitionover)。

  • 方括号中的项是可选的(高、低、非零、非空)。

  • 只选择一个退出分支(注意rare/freq_rare中只有一个退出分支,因为by是强制性的)。

通过不选择over字段,简单地比较某事物与其自身历史记录。

在对检测器结构有全面理解之后,我们现在将进入使用检测器针对不同用例的实用示例。首先,我们将探索允许我们检测事件率随时间变化的计数函数。

检测事件率的变化

有许多重要的用例围绕着事件变化检测的概念。以下是一些例子:

  • 在日志文件中突然出现大量错误消息

  • 检测在线系统处理的订单数量的突然下降

  • 确定对某事物进行访问尝试的突然过多(例如,特定用户 ID 上登录尝试数量的突然增加)

为了我们能够找到异常,我们首先必须有一个机制来理解正常发生率。但是,依赖我们易出错的人类观察和直觉并不总是最容易(或最可靠)的方法。

探索计数函数

第二章中所述,启用和实施,Elastic ML 作业有一个称为检测器的异常检测“配方”。检测器是定义用户想要检测的异常的关键。在检测器中是函数,它选择要检测的“特征”。在计数函数的情况下,特征是某事物随时间发生的频率。我们将看到三个主要的计数函数:

  • count:计算从原始数据索引查询中得到的桶中文档的数量

  • high_count:与count相同,但只有当计数高于预期时才会标记异常

  • low_count:与count相同,但只有当计数低于预期时才会标记异常

我们将看到 Elastic ML 中有许多单侧函数(仅用于检测某一方向上的异常)。此外,重要的是要知道计数函数并不是在计数字段或文档中字段的存不存在;它们只是在索引中随时间计数文档。

为了更直观地了解计数函数的作用,让我们通过 Kibana 内的示例数据来举一个简单的例子:

  1. 要启用示例数据,从 Kibana 主屏幕点击添加数据按钮(任一位置),如图 3.4 所示:![图 3.4 – 带有添加数据选项的 Kibana 主屏幕

    ![img/B17040_03_004.jpg]

    图 3.4 – 带有添加数据选项的 Kibana 主屏幕

  2. 点击添加数据后,选择样本数据以显示三组数据:图 3.5 – 添加样本数据

    图 3.5 – 添加样本数据

  3. 点击每个部分的三个添加数据按钮,将样本数据集加载到你的 Elastic Stack 中。一旦加载完成,我们将通过选择三个横线菜单图标(文本    描述自动生成) 在 Kibana 的左上角以显示应用程序列表,然后选择机器学习图 3.6 – 从 Kibana 应用程序菜单中选择机器学习

    图 3.6 – 从 Kibana 应用程序菜单中选择机器学习

  4. 一旦点击,我们将进入 ML 概览页面,在那里我们可以立即看到我们可以创建我们的第一个异常检测作业。点击创建作业按钮,如图 3.7 所示:图 3.7 – Elastic Cloud 欢迎屏幕

    图 3.7 – Elastic Cloud 欢迎屏幕

  5. 我们接下来的任务是选择索引模式(带有索引分片图标标记)或一个已保存的搜索(带有放大镜图标标记),其中包含我们想要分析的数据。如果选择了一个已保存的搜索,那么一个之前在 Kibana 的kibana_sample_data_logs索引中创建并保存的过滤查询,因为我们希望将那个索引中的每个文档都通过 Elastic ML:图 3.8 – 选择 kibana_sample_data_logs 索引进行分析

    图 3.8 – 选择 kibana_sample_data_logs 索引进行分析

  6. 在下一屏幕上,我们将选择单个度量作业向导,因为此时我们只对分析数据的单一方面感兴趣:其随时间的变化计数:图 3.9 – 选择单个度量作业

    图 3.9 – 选择单个度量作业

  7. 在下一屏幕上,为了跟随这个示例,你必须选择 使用完整的 kibana_sample_logs_data 按钮,以便将样本异常包含在这个数据集中图 3.10 – 选择使用索引中的所有数据

    图 3.10 – 选择使用索引中的所有数据

    注意

    当此演示数据安装时,实际上将大约一半的数据放在过去,另一半放在未来(通过动态修改摄取时间戳)。这样做是为了提供一个机制,使得静态数据在查看“过去一小时”的数据仪表板时看起来像是“实时”的。因此,我们实际上会要求 Elastic ML 分析过去和未来的数据,而通常情况下,未来的数据是无法获得的。为了这个示例,现在暂时放下怀疑,因为我们想展示的异常在数据集的第二部分。

    现在,点击下一步按钮以进入配置向导的下一步。

  8. 点击下一步按钮后,我们需要从选择字段下拉框中选择我们想要分析的内容。我们将选择事件计数(事件率),以关注我们的原始目标,即检测在此索引中事件率随时间的变化:![图 3.11 – 选择事件计数随时间变化作为我们的检测 图片

    图 3.11 – 选择事件计数随时间变化作为我们的检测

    注意,查看这个下拉框会显示,根据数据中字段的数据类型,可以进行其他分析。我们将在后续示例中探索这些其他选项。

    点击下一步按钮继续,现在保留其他选项为默认值。

  9. 现在,我们需要为我们的异常检测工作命名。在web_logs_rate中使用:![图 3.12 – 为异常检测工作命名 图片

    图 3.12 – 为异常检测工作命名

    再次,将其他选项保留为默认值,并点击下一步按钮。

  10. 进行验证步骤以确保分析合理:![图 3.13 – 工作验证步骤 图片

    图 3.13 – 工作验证步骤

    点击下一步按钮继续。

  11. 在这一点上,工作已经准备好创建(注意在图 3.14中,一些合理的默认选项,例如模型内存限制启用模型绘图,已经为您选择了):图 3.14 – 准备创建的异常检测工作

    图 3.14 – 准备创建的异常检测工作

  12. 点击创建工作按钮后,您将看到结果动画预览叠加在数据上方,如下所示:![图 3.15 – 显示工作执行结果的预览 图片

    图 3.15 – 显示工作执行结果的预览

    现在我们点击查看结果按钮,以详细了解异常检测工作在数据中发现了什么。

  13. 使用主图下方的刮擦器,调整查看区域的定位和宽度,以便放大查看大峰值:

![图 3.16 – 显示关键异常的结果图片

图 3.16 – 显示关键异常的结果

注意

当您放大、缩小和移动时,请注意图表聚合间隔与工作桶跨度(如图 3.16 中圈出所示)的比较。如果您放大到一个更宽的视图,图表聚合间隔可以大于工作桶跨度,使得图表上绘制的异常位置不够精确。

在这里,在图 3.16中,我们可以看到事件的大幅上升被标记为两个不同的异常,因为日志中实际看到的 Web 请求数量比预期的高出大约 11 倍(考虑到直到那个时间点之前学习到的数据模型)。你可能注意到图表显示了两个相邻的异常,因为显然事件的上升跨越了多个 15 分钟的桶间隔。你也可能注意到,默认情况下,图表下面的表格中只显示了一个异常。这是因为间隔默认设置为自动,时间相邻的异常被汇总在一起,只显示最高分。如果间隔改为显示所有,那么两个异常记录都会列在表格中:

图 3.17 – 设置为显示所有异常

图 3.17 – 设置为显示所有异常

在这个例子中,还有一点需要注意,那就是数据集中较早出现的其他低分异常:

图 3.18 – 多桶异常

图 3.18 – 多桶异常

关于这些不太明显的异常,有几个关键点需要注意:

  • 他们的分数低于我们刚刚调查的大幅波动,因为相对而言,这些并不那么异常,但有趣的是,它们具有显著性。

  • 这里的异常是“预期值”的“缺乏”。换句话说,count函数将“无数据”解释为 0,如果通常情况下预期事件应该发生,那么这可能是异常的。

  • 这些异常不是单个桶的异常,而是多桶异常。在 UI 中,多桶异常用不同的符号表示(一个十字而不是一个点)。它们表示实际的单个值可能并不一定是异常的,但在 12 个连续桶的滑动窗口中存在一个趋势。在这里,你可以看到有几个相邻桶出现了一个明显的下滑。

    注意

    关于解释多桶异常的更多信息,请参阅elastic.co/blog/interpreting-multi-bucket-impact-anomalies-using-elastic-machine-learning-features上的详细博客文章。

通过这个例子,我们已经看到了count函数如何使我们能够轻松地检测到一组明显(和不那么明显)的异常,这些异常与索引中事件(文档)随时间发生的总体发生率有关。让我们继续我们的旅程,通过查看其他基于计数和发生率的函数。

其他计数函数

除了我们之前描述的函数之外,还有几个其他计数函数,可以支持更广泛的使用场景。

非零计数

非零计数函数(non_zero_countlow_non_zero_counthigh_non_zero_count)允许处理基于计数的分析,同时允许在数据可能稀疏且您不希望将数据不存在明确地视为零,而是将其视为空值的情况下进行准确建模——换句话说,一个看起来像以下的时间序列数据集:

4,3,0,0,2,0,5,3,2,0,2,0,0,1,0,4

使用non_zero_count函数的数据将被解释为以下内容:

   4,3,2,5,3,2,2,1,4

将零视为空值在预期到在常规间隔内不存在测量值的情况下是有用的。以下是一些实际例子:

  • 每月个人购买的航空机票数量

  • 每天服务器重启的次数

  • 每小时系统登录尝试的次数

要在作业向导中选择计数函数的非零计数版本,只需在设置期间切换稀疏数据选项:

图 3.19 – 添加稀疏数据选项以选择非零计数

图 3.19 – 添加稀疏数据选项以选择非零计数

我们将在本章后面看到,当我们通过高级作业向导或 API 配置作业时,我们将明确使用函数名(如high_non_zero_count)而不是使用更具概念描述的选项切换。

独特计数

独特计数函数(distinct_countlow_distinct_counthigh_distinct_count)测量唯一性(在kibana_sample_data_logs索引的最后一个示例中,distinct_count(url.keyword)作为检测器配置,我们会捕捉到相同的时间段异常,但原因不同——不仅请求的整体量很高,正如我们在图 3.16中看到的,而且在图 3.20中,我们看到请求的 URL 多样性很高):

图 3.20 – 独特计数检测器示例

图 3.20 – 独特计数检测器示例

在理解基于计数的功能之后,我们现在转向基于度量的功能,这些功能使我们能够分析数据中的数值字段。

检测度量值的变化

显然,并非所有从系统中发出的数据都将是有文本或分类性质的——其中大量的是数值数据。检测度量值随时间的变化非常适合用于异常检测,因为正如在第一章《IT 机器学习》中提到的,通过静态阈值在数值值上的异常警报的历史范式已经困扰了数十年。让我们一起来探索 Elastic ML 在帮助您检测数据中数值字段变化的功能方面所能提供的一切。

度量函数

度量函数在数值字段上操作并返回数值。它们可能是最容易理解的检测器函数。

最小值,最大值,平均值,中位数和度量

这些函数确实如您预期的那样工作:它们返回桶跨度内感兴趣字段所有数值观察值的最低值、最高值、平均值/均值和中位数。

metric 函数有点独特,因为它实际上只是指定 minmaxmean 一起使用的一种简写方式。

应该注意的是,如果数据的频率(例如,来自采样源(如 Metricbeat)的数据)与桶跨度完全匹配,那么每个桶跨度只有一个样本。这意味着感兴趣字段的最低值、最高值、平均值/均值和中位数都是相同的值(即单个观察值本身的值)。因此,如果你想要使用这些函数进行区分,通常最好在每个桶跨度内有多于一个数值样本。

另一个需要注意的事实是,这些指标函数将数据缺失视为 null。换句话说,如果你的数据稀疏,并且在某些桶跨度中没有观察到观察值,数据缺失不会“拖累”感兴趣字段的统计数据。这就是为什么基于指标的函数没有“非零”或“非空”对应物。

varp

varp 函数测量一个指标随时间变化的总体方差——其波动性。使用此函数可能适用于寻找字段数值通常应该相当一致的情况,但你希望检测是否有变化。

求和与非空求和

sum 函数将返回桶跨度内感兴趣字段所有数值观察值的总和。如果你有稀疏数据且不希望将数据缺失视为 ,这将不可避免地“拖累”总和的值,请使用“非空”版本。

如果我们在上一个例子中在 kibana_sample_data_logs 索引中将 sum(bytes) 作为检测器配置,我们会捕捉到相同的时间段异常,但原因不同——我们注意到请求也导致了从网络服务器传输的字节数量增加:

![图 3.21 – 求和检测器示例图 3.21 – 求和检测器示例

图 3.21 – 求和检测器示例

这完全是有道理的,因为对网络服务器的请求增加将与传输的字节数量增加相关。

现在我们已经欣赏了简单的检测器函数,让我们继续探讨更复杂、更高级的函数。

理解高级检测器函数

除了之前提到的检测器函数外,还有一些其他更高级的函数,允许一些非常独特的功能。其中一些函数只有在通过高级作业向导或通过 API 配置 ML 作业时才可用。

rare

在时间信息流(如日志文件)的上下文中,某事物在统计上稀有(以低频率发生)的概念既直观又难以理解。例如,如果我被要求浏览日志文件并找到一条稀有信息,我可能会倾向于将我看到的第一条新颖信息标记为稀有。但如果我们几乎每条信息都是新颖的怎么办?它们都是稀有的吗?或者什么都不是稀有的?

为了在时间事件流的环境中定义稀有性以使其有用,我们需要同意,将某事物声明为稀有必须考虑其存在的上下文。如果有许多其他常规事物和少量独特事物,那么我们可以认为独特事物是稀有的。如果有许多独特事物,那么我们将认为没有什么事物是稀有的。

在 ML 作业中应用rare函数时,需要声明rare函数关注的字段。该字段随后被定义为by_field_namerare函数的配置在 Elastic ML UI 中没有自己的向导,因此您需要使用高级作业向导来定义它。例如,为了找到引用稀有国家名称的日志条目,将探测器结构设计如下:

![图 3.22 – 稀有探测器示例

![img/B17040_03_022.jpg]

图 3.22 – 稀有探测器示例

这对于查找意外的地理访问(例如,“我们的管理员几乎每天都从纽约和伦敦办公室登录,但从未从莫斯科登录!”)可能很有用。

频率稀有

freq_rare函数是rare的一个专用版本,它寻找导致by_field_name出现稀有值的群体成员。例如,您可以定位一个试图访问许多罕见 URL 的特定 IP 地址,这些 URL 在整个客户端 IP 地址群体中通常看不到。这个 IP 地址可能正在以恶意的方式尝试访问网站的隐藏部分,或者可能正在尝试 SQL 注入等攻击。

信息含量

info_content函数可能是 Elastic ML 工具箱中最专业的探测器函数。它最初被编写为测量文本字符串中的量(字符的数量和多样性)。这是因为已知在恶意软件中有加密指令和/或有效载荷数据以进行命令和控制C2)和数据泄露活动的技术。通过数据的这一特征检测此活动比查看其他特征(如发送的字节数或计数不同的实体)更可靠。

使用的算法将基本上执行以下步骤:

  1. 将独特的字符串按字母顺序排序。

  2. 将那些独特的字符串连接成一个长字符串。

  3. 对那个长字符串执行gzip算法以压缩它。

    信息含量是压缩数据的长度。

Elastic SIEM 中的一些 ML 作业使用了info_content函数——敬请期待第八章《其他 Elastic Stack 应用中的异常检测》以获取更多详细信息。

地理

如果您发现一个地理位置与地球上已学习到的地理位置区域不寻常,那么lat_long函数将很有帮助,它接受一个field_name参数,该参数是一个范围在-180 到 180(例如,40.75, -73.99,纽约市时代广场的坐标)的逗号分隔的数字对。lat_long函数还可以在geo_point字段、包含点值的geo_shape字段或geo_centroid聚合上操作。一个示例用例可能是标记一个对于特定用户、交易等来说不正常(可能是有欺诈或恶意)的位置。

时间

并非所有事物在时间上都是随机发生的,尤其是涉及人类行为的事物。我们可能在一天或一周的某个可预测的时间吃饭、通勤或登录某些系统。使用time_of_daytime_of_week函数,您可以检测从学习到的时间常规中的行为变化。如果行为在 24 小时时间框架内是可预测的,那么time_of_day更合适。如果常规依赖于一周中的某一天,那么time_of_week应该是一个更合理的选项。

注意

不要混淆这些时间函数的使用与异常检测作业中所有检测器的自然时间学习。如第一章《IT 机器学习》中所述,建模的去趋势能力将考虑某事发生的时间。这些函数只是将事件的时间戳建模在一天或一周之内。例如,如果某事每天晚上 2:00 A.M.都会例行发生,该函数将学习到这种事情发生的正常时间是每天的第 7,200 秒。

现在我们已经了解了所有检测函数的整个目录,让我们展望一下,看看我们如何通过拆分由分类字段表示的实体之间的建模来扩展分析的范围。

沿分类特征拆分分析

我们已经看到了异常检测作业在揭示单个时间序列数据集中有趣异常方面的力量。然而,分析可以通过分类字段进行拆分,从而在成千上万的独特实体上调用并行分析。

设置拆分字段

当使用一些工作向导(例如多指标和人口向导)时,您将看到一个选项来拆分分析:

图 3.23 – 在分类字段上拆分

图 3.23 – 在分类字段上拆分

在这里,图 3.23中,使用多度量向导构建针对kibana_sample_data_ecommerce索引的作业,我们看到taxful_total_price字段上的高总和函数正在按实例分割在名为category.keyword的字段上(同时开启稀疏数据选项)。换句话说,分析将针对这个电子商务店中的每个商品类别(男装、女装配饰等)进行。如果运行分析并使用异常探索器 UI 检查结果,结果可能看起来像以下这样:

图 3.24 – 分割分析的结果

图 3.24 – 分割分析的结果

注意在图 3.24中,异常探索视图与我们迄今为止在单度量查看器中看到的不同。异常探索显示了随时间变化的 10 个最异常的类别(我们分割的字段)。注意,并不是每个类别都显示出来,只有具有异常的类别——显然,男装类别在 11 月 9 日的收入为$2,250,是最不寻常的(在这个数据集版本中)。我们将学习更多关于理解多度量作业结果的知识,并在第五章 解释结果中广泛使用异常探索器。

使用分区和 by_field 分割的区别

作为提醒,当使用多度量向导并调用分割时,partition_field_name设置被设置为 UI 中选择的字段值。

然而,当在人口向导中选择分割时,by_field_name被选中以分割分析。如果使用高级向导,则可以定义partition_field_name和/或by_field_name(如果两者都定义,则实际上是一个双重分割)。因此,了解这两个实际上分割分析的设置如何彼此不同将是有帮助的。

如果你想“硬分割”分析,使用partition_field_name

  • 通常,所选字段在每个工作项中应具有<10,000 个不同的值,因为需要更多的内存来分区。

  • 字段的每个实例就像一个独立变量。

一个分区中异常的评分与其他分区更独立。

如果你想进行“软分割”,使用by_field_name

  • 通常,所选字段在每个工作项中应具有<100,000 个不同的值。

  • 更适合实体的属性(因变量)。

  • 评分考虑其他by字段的历史。

让我们深入探讨最后列出的项目——与“历史”相关的其他by字段。这究竟意味着什么?

通常,在异常检测工作分析中有一个概念,与实体首次发生的时间相关,我们可以称之为host:Xerror_code:Y,可能存在两种情况:

  • 新实体被视为“新颖”的,这本身就很引人注目,并且可能值得将其标记为异常。为此,您需要将“时间的黎明”设定为作业开始的时候。

  • 新实体只是数据正常“扩展”的一部分——也许新服务器被添加到混合中,或者新的product_id被添加到目录中。在这种情况下,只需开始对该新实体进行建模,不要对它出现而大惊小怪。为此,您需要将“时间的黎明”设定为该实体首次出现的时候。

当使用by_field_name分析拆分时,“时间的黎明”是 ML 作业开始的时候,而当使用partition_field_name进行拆分时,“时间的黎明”是那个分区首次出现在数据中的时候。因此,如果您在出现“新事物”的情况下以不同方式拆分,您将得到不同的结果。

双重拆分是极限吗?

如前所述,在高级作业向导中使用partition_field_nameby_field_name,您可以有效地实现双重拆分。但是,如果您需要拆分更多,您将不得不依赖其他方法。具体来说,您需要创建一个脚本字段,它是两个(或更多)字段的连接。使用脚本字段是附录中某个示例中涵盖的内容。

现在我们已经了解了分析拆分的概念,让我们关注异常检测中时间序列分析和人口分析之间的差异。

理解时间序列分析 versus 人口分析

我们在第一章中学习了《IT 机器学习》,了解到将某事物视为异常实际上有两种有效的方法:

  • 是否随着时间的推移,某事物的行为发生了剧烈变化

  • 与其他同质群体中的同类相比,某事物是否发生了剧烈的不同

默认情况下,前者(我们将其简单地称为时间序列分析)是检测器配置中未指定over_field_name设置时使用的模式。

人口分析在寻找各种重要用例中的异常值时非常有用。例如,我们可能希望在下述场景中找到记录更多(或更少)的机器:

  • 不正确的配置更改导致系统或应用程序的日志文件中突然出现更多错误。

  • 可能被恶意软件破坏的系统可能实际上被指示在某些情况下抑制日志记录,从而大幅减少日志量。

  • 一个失去连接或操作失败的系统,因此其日志量减少。

  • 对日志级别设置(从正常改为调试)的更改,原本是无害的,但现在却令人烦恼地使日志占用更多的磁盘空间。

另一种人口分析经常使用的方式是与用户/实体行为分析EUBA)相关,其中将实体或人类的行为与他们的同龄人进行比较,可能会揭示以下情况:

  • 自动化用户:与典型的人类行为或使用模式不同,自动化脚本可能表现出在事件的速度、持续时间和多样性方面看起来相当不同的行为模式。无论是寻找试图收集在线目录中的产品和价格的网络爬虫,还是检测可能在社会媒体上传播虚假信息的机器人,自动识别自动化用户可能是有帮助的。

  • distinct_count 函数可以帮助找到间谍。

  • 恶意/滥用用户:在侦察阶段之后,恶意用户或恶意软件将转向积极造成破坏,并采取主动措施,如拒绝服务、暴力破解或窃取有价值的信息。再次强调,与典型用户相比,恶意和滥用用户在每单位时间的行为量、多样性和强度方面有明显的差异。

找到一个客户比他们的同龄人花费多得多的实际例子。无论你是否在主动调查潜在欺诈的情况下做这件事,或者你是否对增加对最富裕客户的营销感兴趣,你仍然需要找到这些异常值。如果我们使用本章早期添加的kibana_sample_data_ecommerce索引,我们可以通过选择customer_full_name.keyword字段为taxful_total_price字段创建一个群体任务,这是每个个人订单的总收入:

图 3.25 – 用户收入群体分析

图片

图 3.25 – 用户收入群体分析

执行此任务后,你应该看到以下结果:

图 3.26 – 最大支出者的群体分析结果

图片

图 3.26 – 最大支出者的群体分析结果

在这里,在图 3.26中,我们看到最不寻常的用户列表(在这种情况下,每单位时间的最大支出者)主要由名为Wagdi Shaw的用户主导,他显然订购了价值 2250 美元的商品。你们中敏锐的人会从早期示例中识别出这个异常——但这次,我们分析的重点是用户,而不是库存类别。

如您所见,人口分析可以非常强大,并且在针对单个实体的用例中得到了广泛的应用。因此,它在安全分析用例中非常有用。现在,让我们转向关注 Elastic ML 的异常检测的一个额外但强大的功能——通过称为分类的过程有效地分析非结构化日志消息的能力。

非结构化消息的分类分析

想象一下,你正在通过查看特定的日志文件来排查问题。你看到日志中的一行,看起来如下:

   18/05/2020 15:16:00 DB Not Updated [Master] Table

除非你对创建此日志的应用程序的内部工作原理有深入了解,否则你可能不知道这条消息是否重要。数据库被标记为“未更新”可能听起来像是一种负面情况。然而,如果你知道该应用程序例行地每小时写这条消息几百次,那么你自然会意识到这条消息是无害的,可能应该被忽略,因为显然应用程序每天都能正常工作,尽管这条消息被写入日志文件。

显然,问题是人类解释。检查消息文本和阅读一个负面短语(“未更新”)可能会使一个人倾向于认为这条消息值得关注,因为可能存在问题。然而,消息的频率(它例行发生)应该让这个人知道这条消息可能并不重要,因为应用程序正在运行(也就是说,没有报告故障),尽管这些消息被写入日志。

对于日志文件中仅几种类型的消息,人类处理这些信息(评估消息内容/相关性以及随时间的变化频率)可能很困难。想象一下,如果每天有数百万条独特的消息类型以每天数百万条日志行的总速率发生。即使是最有经验的在应用程序内容和搜索/可视化方面的专家,也会发现这既不实际,甚至可能不可能处理。

Elastic ML 通过允许对消息内容的唯一性和相对发生频率进行经验评估的能力来提供帮助。

适合分类的消息类型

我们需要对我们定义的消息日志行类型进行一定的严谨性,以适用于这种分析。我们不考虑的是完全自由形式的日志行/事件/文档,这些可能是人类创作的结果(电子邮件、推文、评论等)。这类消息在构建和内容上过于随意和多变。

相反,我们将专注于机器生成的消息,这些消息在应用程序遇到不同情况或异常时显然会被发出,从而将它们的构建和内容限制在相对离散的可能性的集合中(理解到消息确实可能有一些可变方面)。例如,让我们看看以下几行应用程序日志:

18/05/2016 15:16:00 S ACME6 DB Not Updated [Master] Table
18/05/2016 15:16:00 S ACME6 REC Not INSERTED [DB TRAN] Table 
18/05/2016 15:16:07 S ACME6 Using: 10.16.1.63!svc_prod#uid=demo;pwd=demo 
18/05/2016 15:16:07 S ACME6 Opening Database = DRIVER={SQL Server};SERVER=10.16.1.63;network=dbmssocn;address=10.16.1.63,1433;DATABASE=svc_prod;uid=demo;pwd=demo;AnsiNPW=No 
18/05/2016 15:16:29 S ACME6 DBMS ERROR : db=10.16.1.63!svc_prod#uid=demo;pwd=demo Err=-11 [Microsoft][ODBC SQL Server Driver][TCP/IP Sockets]General network error. Check your network documentation.

在这里,我们可以看到有各种消息,每个消息中的文本都不同,但这里有一些结构。在日期/时间和消息来源的服务器名称(这里为ACME6)之后,是消息的实际内容,其中应用程序正在向外界告知当时正在发生的事情——是正在尝试某事还是发生了错误。

分类过程使用的算法

为了使日志文件中消息的无序流动变得有序,Elastic ML 将采用一种通过使用字符串相似度聚类算法将相似消息分组在一起的技术。该算法背后的启发式方法大致如下:

  • 重点关注(英语)词典中的单词,因为networkaddress是词典中的单词,但dbmssocn可能是一个可变/变量的字符串)。

  • 将不可变的词典单词通过字符串相似度算法(类似于Levenshtein 距离)传递,以确定日志行与过去日志行的相似程度。

  • 如果当前日志行与现有类别之间的差异很小,那么将现有日志行分组到该类别中。否则,为当前日志行创建一个新类别。

作为简单的例子,考虑以下三条消息:

   Error writing file "foo" on host "acme6"
   Error writing file "bar" on host "acme5"
   Opening database on host "acme7"

该算法会将前两条消息聚类到同一个类别中,因为它们会被视为Error writing file on类型的消息,而第三条消息将获得其自己的(新)类别。

这些类别的命名很简单:ML 将简单地称它们为mlcategory N,其中N是一个递增的整数。因此,在这个例子中,前两行将与mlcategory 1相关联,而第三行将与mlcategory 2相关联。在现实机器日志中,可能由于日志消息的多样性,会有成千上万(甚至数十万)个类别被生成,但可能的类别集合应该是有限的。然而,如果类别的数量开始达到数十万,可能就会明显看出日志消息不是一个受约束的可能消息类型的集合,并且不会是这种分析的好候选。

分析类别

现在消息将要被前面描述的算法进行分类,接下来过程的下一步是对其进行分析(使用countrare)。在这种情况下,我们不会计算日志行(以及 Elasticsearch 索引中的文档)本身;相反,我们将计算算法输出的不同类别的出现频率。例如,给定前一个部分中的示例日志行,如果它们发生在同一个桶跨度内,分类算法的输出如下:

   mlcategory 1: 2
   mlcategory 2: 1

换句话说,在最后一个桶跨度间隔中,出现了两次Error writing file on类型的消息和一次Opening database on host类型的消息。这些信息最终将由 ML 作业建模,以确定其是否异常。

分类作业示例

在 UI 中的分类作业向导中,配置此类作业的过程非常简单。让我们首先假设我们有一个未结构化的日志文件被摄取(例如,GitHub 上example_data文件夹中的secure.log文件):

注意

关于如何使用文件可视化器摄取数据的更多信息,请参阅elastic.co上的详细博客文章。

  1. 在选择感兴趣的索引和选择分类向导后,然后选择分析的时间范围,我们看到向导将询问我们哪个@timestampmessage)。因此,message字段是我们希望 Elastic ML 进行分类的字段。在这个例子中,我们还将选择计数检测器:![图 3.27 – 分类作业配置

    ![图片 B17040_03_027.jpg]

    图 3.27 – 分类作业配置

    注意在图 3.27中,对所选类别字段进行了检查,以确保它将产生合理的结果。同时注意,在示例部分,你可以通过视觉确认 Elastic ML 专注于日志消息的非可变文本。

  2. 一旦确认配置并在向导中启动作业,你将看到结果的预览,这些结果正在被发现和分析:![图 3.28 – 分类作业执行预览

    ![图片 B17040_03_028.jpg]

    图 3.28 – 分类作业执行预览

    注意,在这个简单的例子中,数据中共发现了 23 个类别。当在异常探索器中查看结果时,我们看到这里的顶级异常是mlcategory编号 7。

  3. 当你点击Received disconnect消息时。

  4. 通过单击图 3.29 中显示的齿轮图标,我们可以选择查看示例,这会将我们带到 Kibana Discover UI,但仅过滤到适当的消息并缩放到相关的时间段:![图 3.30 – 检查分类作业结果中的原始日志行

    ![图片 B17040_03_030.jpg]

    图 3.30 – 检查分类作业结果中的原始日志行

    注意,发现查询栏已自动填充了适当的 KQL 查询,以限制我们的视图仅限于异常的消息类型。

  5. 如果我们删除那个查询过滤器,我们将看到这个异常发生时的日志文件中的所有消息,并且我们会看到更大的故事,即有人或某物正在尝试进行大量身份验证:

图 3.31 – 检查异常发生时的所有日志行

图 3.31 – 检查异常发生时的所有日志行

正如我们在图 3.31中看到的,似乎有大量的使用知名用户名(如usertest等)的认证尝试。看起来我们只是通过分类就找到了暴力认证尝试!

当避免使用分类时

尽管分类非常有用,但它并非没有局限性。具体来说,以下是一些尝试使用分类可能返回较差结果的情况:

  • 使用自由形式的文本字段,这些文本很可能是由人类创建的。例如,包括推文、评论、电子邮件和笔记等。

  • 对于应该被解析成正确的名称/值对的日志行,例如 Web 访问日志。

  • 对于包含大量多行文本的文档。这包括堆栈跟踪、XML 等。

话虽如此,我们可以看到,在分析非结构化文本可能会给人类分析师带来额外负担的情况下,分类仍然可以非常有用。

通过 API 管理 Elastic ML

就像 Elastic Stack 中的几乎所有事情一样,ML 也可以通过 API 调用完全自动化——包括作业配置、执行和结果收集。实际上,您在 Kibana UI 中的所有交互都利用了背后的 ML API。例如,如果您想编写自己的 UI,并且有特定的工作流程或可视化需求,您完全可以这样做。

注意

有关异常检测 API 的更多信息,请参阅elastic.co/guide/en/machine-learning/current/ml-api-quickref.html。Elastic ML 的数据帧分析部分有一个完全独立的 API,将在第九章到第十三章中进行讨论。

我们不会深入到每个 API 调用,但我们想强调一些值得注意的部分。

首先要提到的明显 API 是作业创建 API,它允许创建 ML 作业配置。例如,如果您想重新创建图 3.25中显示的种群分析作业,以下调用将创建该作业,我们将称之为revenue_over_users_api

PUT _ml/anomaly_detectors/revenue_over_users_api
{
  "job_id": "revenue_over_users_api",
  "analysis_config": {
    "bucket_span": "15m",
    "detectors": [
      {
        "detector_description": "high_sum(taxful_total_price) over customer_full_name.keyword",
        "function": "high_sum",
        "field_name": "taxful_total_price",
        "over_field_name": "customer_full_name.keyword"
      }
    ],
    "influencers": [
      "customer_full_name.keyword"
    ]
  },
  "data_description":{
         "time_field":"order_date",
         "time_format":"epoch_ms"
      }
}

注意,创建作业时job_id字段必须是唯一的。

为了为这个作业创建配套的数据源配置,我们会发出这个单独的 API 调用:

PUT _ml/datafeeds/datafeed-revenue_over_users_api
{
  "datafeed_id": "datafeed-revenue_over_users_api",
  "job_id": "revenue_over_users_api",
  "query_delay": "60s",
  "indices": [
    "kibana_sample_data_ecommerce"
  ],
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ]
    }
  },
  "scroll_size": 1000,
  "chunking_config": {
    "mode": "auto"
  }
}

注意,默认的索引查询是match_all,这意味着不会进行任何过滤。当然,我们可以在查询块中插入任何有效的 Elasticsearch DSL 来执行自定义过滤或聚合。这个概念将在本书的后续部分进行介绍。

有其他 API 可以用来提取结果或修改 ML 作业的其他操作方面。有关更多信息,请参阅在线文档。

摘要

我们已经看到,Elastic ML 可以突出指标和日志消息中的数量、多样性和独特性的变化,包括那些需要先进行一些分类的。此外,我们还展示了当重点更多地放在寻找最不寻常的实体时,人口分析可以成为时间异常检测的一个极其有趣的替代方案。这些技术有助于解决我们之前描述的挑战,在这些挑战中,人类可能难以识别真正不寻常且值得注意和调查的事物。

本章学到的技能将在后续章节中有所帮助,其中我们将看到机器学习如何协助找到复杂 IT 问题的根本原因,识别应用程序性能的放缓,或者当机器学习可以协助识别恶意软件和/或恶意活动时。

在下一章中,我们将看到如何利用异常检测作业构建的表达式时间序列模型来预测数据未来的趋势。

第四章: 预测

预测是 Elastic ML 的时间序列建模的自然扩展。由于在幕后构建了非常表达性的模型,并描述了数据的历史行为,因此可以预测这些信息在时间上的前进,并预测某物在未来某个时间点应该如何表现。

我们将花时间学习预测背后的概念,以及通过一些实际示例进行操作。

具体来说,本章将涵盖以下主题:

  • 对比预测与预言

  • 预测用例

  • 运营预测理论

  • 单个时间序列预测

  • 查看预测结果

  • 多个时间序列预测

技术要求

本章中展示的信息和示例与 Elastic Stack 的 v7.11 版本相关。

对比预测与预言

过去的表现并不能预示未来的结果。这个免责声明是金融公司在引用如共同基金等产品表现时所使用的。但这个免责声明有点自相矛盾,因为过去是我们唯一可以工作的东西。如果构成共同基金的这些公司在过去八个季度中一直有持续的正季度业绩,这能保证他们接下来八个季度也会有正面的业绩组合,并且他们的公开估值会持续上升吗?概率可能支持这种情况,但这可能不是全部的故事。在我们过于乐观地认为 Elastic ML 的预测能力是我们股市发财的关键之前,我们应该对以下关键警告保持现实——总有一些不可控的因素。

金融公司使用上述免责声明的原因是,通常存在一些未知、不可控的因素出现,并且这些因素可能对某物的轨迹产生非常大的影响。例如,政府可能会改变法规或贸易政策,这可能会极大地帮助或阻碍公司的运营和盈利能力,或者可能发生内部欺诈会计丑闻,其中高管密谋伪造公司业绩,这最终变得难以维持并最终导致公司破产。

由于以下原因,这些因素被认为是未知的外部因素:

  • 它们超出了实体的控制范围(例如,政府独立于公司的活动制定政策)。

  • 它们被隐藏在关于系统的可用信息中(例如,外部投资者在实时情况下只能访问公开的性能报告,而无法了解可能伪造这些性能报告的欺诈活动)。

因此,预测的准确性仅取决于你所拥有的信息和消除或减轻影响预测的外部未知因素的能力。在信息技术(IT)数据的世界中也是如此。如果存在未知的外部因素(例如,有人错误地进行了配置更改、硬件故障等),则可能无法预测趋势或故障。然而,我们可以使用概率分析来给出我们对未来的最佳猜测,除了那些可能的外部因素之外。理解这个注意事项使我们能够在不陷入预言期望的情况下满足一些良好的预测用例。现在,让我们将重点转向如何以实际的方式使用预测。

预测用例

在 Elastic ML 的背景下,实际上只有两种——有些相似——的使用场景,人们会使用预测。这些场景在此概述:

  • 价值导向:这是将时间序列外推到未来,以了解可能的未来值。这类似于回答以下问题:“两个月后我每天将卖出多少个部件?”

  • 时间导向:这是理解预期值可能达到的时间。这将回答类似以下问题:“我预计在下周达到 80%的利用率吗?”

这两种使用场景之间的差异可能不仅仅是提问方式(如何搜索数据),还包括你如何解释输出。然而,在我们深入探讨如何使用预测功能的一些示例之前,让我们花点时间讨论一下它在逻辑上的工作方式。

预测操作理论

首先要认识到,对数据进行预测的行为实际上是对现有异常检测工作的扩展。换句话说,你需要配置一个异常检测工作,并且该工作需要在预测数据之前分析历史数据。这是因为预测过程使用了异常检测工作创建的模型。要预测数据,你需要遵循在其他章节中描述的创建异常检测工作所使用的相同步骤。如果该工作执行过程中生成了异常,你可以忽略它们,如果你的唯一目的是执行预测。一旦工作在历史数据上学习完毕,与该工作关联的模型或模型(如果工作配置为分析来自多个时间序列的数据)就是当前的和最新的,如下面的图所示:

图 4.1 – 从历史学习中获得模型的象征性表示

图 B17040_04_1.jpg

图 4.1 – 从历史学习中获得模型的象征性表示

我们将考虑现在之前的时间作为历史学习——模型在真实数据上学习的时间。当用户希望在特定时间调用预测时,会创建模型的一个副本,并使用一个独立的过程来将这些模型外推到“未来”。这个过程与原始模型及其演变并行运行,以避免干扰。以下图表展示了这一过程:

![图 4.2 – 将模型复制用于未来预测的符号表示]

![图片 B17040_04_2.jpg]

![图 4.2 – 将模型复制用于未来预测的符号表示]

预测值将写入与异常检测相同的索引,但作为特殊类型的结果(稍后将有更多细节),并且可以在用户界面UI)中查看或通过应用程序编程接口API)访问。

重要的是要注意,分析实际真实数据的机器学习ML)作业的正常路径将继续(如果它在实时运行),因此经过一段时间后,预测值(在预测时做出的)和到达该时间时的实际值之间可能会有差异,如下面的图表所示:

![图片 B17040_04_3.jpg]

![图 4.3 – 预测误差的符号表示]

![图 4.3 – 预测误差的符号表示]

这种预测误差是可以预见的,但希望它将是微小的。两者之间的差异目前没有被 Elastic ML 使用,但也许在未来它可以向模型提供关于更准确后续预测的信息。当然,也有可能未知的外部因素(如前所述)会导致一定数量的预测误差。

另一种(可能更简单)思考预测不确定性的方法是考虑预测抛硬币的结果。你可以观察一系列之前的抛硬币,但如果你不考虑抛硬币的物理特性(速度、高度、旋转等)并且只依赖于过去观察的结果,那么你永远无法在结果上做出超过 50/50 的预测。此外,Elastic ML 在学习期间可能没有看到行为上完全一致的数据。因此,在数据中存在一定量的噪声的情况下,我们也应该预期预测中存在一定量的变化或不确定性。

用户还可以在其他时间做出多个预测。这些预测将被分别存储,如下面的图表所示:

![图 4.4 – 在不同时间调用多个预测的符号表示]

![图片 B17040_04_4.jpg]

![图 4.4 – 在不同时间调用多个预测的符号表示]

预测#1预测#2之间的区别将基于每个预测实例的内部唯一 ID。当我们查看预测结果在索引中的存储方式时,这一点将变得明显。

现在我们对预测过程的物流操作有了基本了解,让我们通过一个例子来了解如何使用 Elastic ML 对单一时间序列进行预测。

单一时间序列预测

为了说明预测过程,我们将从一个单一时间序列的数据集开始。虽然这个数据集是通用的,你可以想象它可能代表一个系统性能指标、系统处理的交易数量,甚至销售收入的统计数据。这个数据集的重要之处在于它包含几个不同的基于时间的变化趋势——日趋势、周趋势和整体增长趋势。Elastic ML 将发现所有这三个趋势,并将有效地预测它们到未来。值得注意的是,数据集还包含一些异常值,但(当然)未来的异常值无法预测,因为它们按定义是意外事件。由于我们在这里的讨论纯粹是关于预测,因此我们在构建预测模型时将忽略数据集中发现的任何异常值。

话虽如此,让我们通过使用 GitHub 仓库中的forecast_example数据集来举一个例子。一旦下载,数据就可以通过 Elastic ML 的数据可视化器轻松导入到您的 Kibana 中。我们继续如下操作:

  1. 要上传示例数据,从 Kibana 主屏幕点击上传文件按钮,如下截图所示:图 4.5 – 在 Kibana 中上传文件的选项

    图 4.5 – 在 Kibana 中上传文件的选项

  2. 从您的本地机器中选择forecast_example.json文件。数据可视化器将显示文件的前 1000 行,以供您预览文件内容,以及不同字段的分解。这体现在以下截图中:图 4.6 – 预览要上传的文件内容

    图 4.6 – 预览要上传的文件内容

  3. 点击导入按钮,然后为要上传的数据指定目标索引名称,如下截图所示:图 4.7 – 为上传的目标索引命名

    图 4.7 – 为上传的目标索引命名

  4. 为您的目标索引提供一个名称,如果这是您第一次上传,请确保勾选创建索引模式选项,然后再次点击导入按钮以完成上传。您应该看到上传成功的提示,如下截图所示:图 4.8 – 上传完成

    图 4.8 – 上传完成

  5. 数据上传完成后,导航到机器学习并创建一个异常检测作业,选择你在上一步中创建的索引模式的索引名称。这在上面的屏幕截图中表示:![图 4.9 – 首先创建一个异常检测作业 图 4.12 – 选择在时间上对金额字段求和作为我们的检测

    图 4.9 – 首先创建一个异常检测作业

  6. 这个特定的数据集仅包含一个单一的时间序列指标(一个名为amount的字段),因此我们将简单地使用单个指标作业向导来构建作业,如以下屏幕截图所示:![图 4.10 – 为此数据选择“单个指标作业向导” 图 4.10 – 为此数据选择“单个指标作业向导”

    图 4.10 – 为此数据选择“单个指标作业向导”

  7. 在下一屏幕和此示例中,我们只想分析到 2017 年 3 月 1 日 00:00:00.000,以便为我们留下一些数据来比较我们的预测。你可以通过首先点击使用完整的 forecast_example 数据按钮,然后手动更改结束日期以匹配以下屏幕截图所示的内容:![图 4.11 – 选择只使用到特定日期的数据 图 4.12 – 选择在时间上对金额字段求和作为我们的检测 图 4.11 – 选择只使用到特定日期的数据 注意 此示例数据集记录了从 2017 年 1 月 31 日到 2017 年 3 月 1 日的数据。尽管这些数据来自过去,我们可以设想一个场景,即我们假装处于那个时间段,并宣布今天的日期是 2017 年 3 月 1 日。因此,我们希望有一个机器学习作业分析 1 月 31 日到“今天”之间的数据,然后使用机器学习预测未来 10 天的数据。我们稍后将看到我们的预测与剩余数据的准确性。如果你的 Kibana 时区设置为本地时间,本章中的日期可能看起来略有不同,因为截图是在将 Kibana 设置为美国东部时区版本的 Kibana 上拍摄的。(美国)(US) 现在,点击下一步按钮以进入配置向导的下一步。1. 点击“金额”字段后,它只是一个随时间变化的数值。这在上面的屏幕截图中表示:![图 4.12 – 选择在时间上对金额字段求和作为我们的检测 图 4.12 – 选择在时间上对金额字段求和作为我们的检测

    图 4.12 – 选择在时间上对金额字段求和作为我们的检测

    点击下一步按钮继续,暂时保留其他选项为默认设置。

  8. 现在,我们需要命名我们的异常检测作业——在forecast_example中使用:![图 4.13 – 命名异常检测作业 图 4.12 – 选择在时间上对金额字段求和作为我们的检测

    图 4.13 – 命名异常检测作业

    再次,保留其他选项为默认设置,并点击下一步按钮。

  9. 进行验证步骤以确保一切设置正确,以便分析可以正常工作,如以下屏幕截图所示:![图 4.14 – 作业验证步骤 图 4.11 – 选择只使用到特定日期的数据

    图 4.14 – 作业验证步骤

    点击下一步按钮继续。

  10. 在此阶段,工作已准备好创建,如下截图所示:图 4.15 – 准备创建的异常检测工作

    图 4.15 – 准备创建的异常检测工作

  11. 点击创建工作按钮后,您将看到结果动画预览叠加在数据上方,如下述截图所示:图 4.16 – 显示作业执行结果预览

    图 4.16 – 显示作业执行结果预览

    要访问预测功能,我们需要点击查看结果按钮,这将带我们到单指标查看器。在单指标查看器中,我们可以看到整体数据集,并可以欣赏到数据的行为方式及其形状和复杂性;既有日周期成分,也有周周期成分,以及一个逐渐上升的正斜率/趋势,导致数据随时间上升。这在下述截图中有展示:

    图 4.17 – 显示注释的结果

    图 4.17 – 显示注释的结果

    如果我们暴露并探索注释,我们实际上可以看到 Elastic ML 在数据中检测到的不同趋势的确切位置。此外,请记住,尽管我们可能只对在此数据上预测感兴趣,但工作仍会在数据的历史中指出来异常。在这种情况下,如果我们选择,我们可以简单地忽略它们。

  12. 要在此数据上调用预测,请点击10d),如下述截图所示:

图 4.18 – 启动新的 10 天预测

图 4.18 – 启动新的 10 天预测

注意

不应尝试请求比 ML 工作已分析的数据持续时间更长的预测持续时间。换句话说,如果 ML 工作只看到一周的数据,就不要请求两周的预测。至少历史数据与想要预测的数量的比例应该是一对一(理想情况下,历史数据的比例会更高)。最后,提供足够一致的数据来了解主要模式。例如,使用至少三个周期性的模式循环来达到最佳预测效果。

点击图 4.18中显示的运行按钮后,预测将在后台调用并运行。我们可以几乎立即看到预测结果,如下述截图所示:

图 4.19 – 预测结果

图 4.19 – 预测结果

预测/预测区域周围的阴影区域是 95% 置信区间。换句话说,Elastic ML 估计未来值有 95% 的可能性将在这个范围内(同样,只有 2.5% 的可能性未来值将高于或低于置信区间)。目前 95% 的置信区间是一个固定值,用户尚不能设置。

现在我们有了从 UI 创建简单预测的能力,在继续更复杂的示例之前,让我们更深入地探索预测的结果。

查看预测结果

现在我们已经运行了一个预测,我们可以更深入地查看预测过程生成的结果。我们可以在 UI 中通过两种方法查看之前创建的预测的结果。第一种方法是点击单指标查看器中的预测按钮,以显示之前预测的列表,如下所示:

图 4.20 – 在单指标查看器中查看之前创建的预测

图 4.20 – 在单指标查看器中查看之前创建的预测

或者,您可以在该作业的预测选项卡下的作业管理页面中查看它们,如下面的截图所示:

图 4.21 – 在作业管理页面查看之前创建的预测

图 4.21 – 在作业管理页面查看之前创建的预测

注意

在 Kibana 中构建的预测结果默认寿命为 14 天。之后,预测结果将被永久删除。如果需要不同的过期持续时间,则必须通过_forecast API 端点调用预测,这将在稍后讨论,但已在www.elastic.co/guide/en/elasticsearch/reference/current/ml-forecast.html中记录。

当你在单指标查看器中查看预测结果时,注意当你将鼠标悬停在预测数据点上时,一个弹出显示将列出关于该数据点的三个关键信息——预测值、上限值和下限值,如下面的截图所示:

图 4.22 – 预测弹出显示中显示的信息

图 4.22 – 预测弹出显示中显示的信息

回想一下,上限和下限定义了一个 95%置信区间的范围。预测值是最有可能的值(概率)。这三个关键值存储在.ml-anomalies-*结果索引中,以下列名称:

  • forecast_prediction

  • forecast_upper

  • forecast_lower

第五章 解释结果 中,我们将学习如何查询.ml-anomalies-*索引以定位预测信息,以及我们如何利用这些信息用于其他目的,例如仪表板或警报。

如果我们想看看 Elastic ML 的预测与数据集接下来的 10 天实际数据相比做得如何(记住——机器学习作业的模型还没有真正看到那些天),我们可以回到作业管理页面并开始作业的数据馈送以继续并分析剩余的数据。为此,点击右侧菜单中的启动数据馈送链接,如图下所示:

![图 4.23 – 从作业管理页面开始数据馈送

![img/B17040_04_23.jpg]

图 4.23 – 从作业管理页面开始数据馈送

一旦弹出对话框,将搜索开始时间字段设置为从 2017-03-01 00:00:00 继续(或它显示的您本地时区的时间),并将搜索结束时间字段指定为 2017 年 3 月 11 日午夜 12:00,如图下所示:

![图 4.24 – 从上次停止的地方继续数据馈送

![img/B17040_04_24.jpg]

图 4.24 – 从上次停止的地方继续数据馈送

完成此操作后,返回作业的单一度量查看器,确保您使用 Kibana 时间选择器查看正确的时间范围,然后点击预测按钮查看本章前面描述的先前创建的预测。现在您将能够看到预测值叠加在数据实际值上,如图下所示:

![图 4.25 – 将预测与实际数据比较

![img/B17040_04_25.jpg]

图 4.25 – 将预测与实际数据比较

如本章前面所述,Elastic ML 对数据的预测与未来到达的实际值之间将存在轻微的差异。这是因为预测是概率性的,而概率伴随着一定的不确定性。然而,这并不减少预测的有用性。结合主动警报(如第六章在机器学习分析上进行警报)中所述),我们可能会被警告有违规的可能性。这种主动通知在用户无法单独跟踪数百或数千个实体时特别有用。在下一节中,我们将看到多度量预测如何使我们能够自动跟踪这些实体。

多时间序列预测

要在多个时间序列上调用预测,您只需要一个正在建模多个时间序列的机器学习作业。让我们假设我们有一个分析了按国家划分的 Web 请求的机器学习作业。实际上,使用我们在第三章异常检测中使用的内置样本网络日志(kibana_sample_data_logs),我们可以轻松创建一个多度量作业,该作业按请求的源国家代码(该字段称为geo.src)分割事件计数,如图下所示:

图 4.26 – 创建用于预测的多指标作业

图 4.26 – 创建用于预测的多指标作业

在这个数据集中有 183 个独特的源国家。在创建并运行这个异常检测作业以为所有 183 个国家建立基线模型之后,我们现在可以调用预测。如果我们以与之前相同的方式(通过单指标查看器)调用预测,我们可能会错误地认为预测只会为显示的序列执行,如下面的截图所示:

图 4.27 – 从单指标查看器中调用多指标预测

图 4.27 – 从单指标查看器中调用多指标预测

在前面的截图(尽管在弹出窗口后面的褪色背景中可能有点难以看清),我们可以看到为geo.src字段选择的国代码是CN(中国)。但是,正如预测弹出窗口的警告消息所指出的,点击运行按钮将调用预测,并将运行作业中所有现有的分区(在这里,它知道有超过 100 个分区)。

或者,我们可以使用_forecast API 端点来调用预测。为此,在开发工具控制台中,我们可以发出以下请求:

  POST _ml/anomaly_detectors/web_traffic_per_country/_forecast
   {
    “duration”: “1d”
   }

注意

在您可以通过 API 调用预测之前,异常检测作业必须处于“打开”状态。

API 调用的即时响应如下:

   {
    “acknowledged” : true,
    “forecast_id” : “ sm7AF3cBpc7Wt6MbTWYg”
}

我们的预测请求的结果将可以在单指标查看器中查看,或者通过查询结果索引以编程方式获取,这些索引将在第五章“解释结果”中描述。使用单指标查看器,我们可以通过点击预测按钮,选择我们之前运行的预测,然后选择我们选择的国代码,如下面的截图所示:

图 4.28 – 从多指标预测中查看单个分区的预测

图 4.28 – 从多指标预测中查看单个分区的预测

注意

在 v7.10 版本中,在单指标查看器中查看多指标预测的结果并不完全理想。这是因为视图只显示记录了异常的分区。此功能将在 v7.11 版本中添加。

在多个时间序列上进行预测对于容量规划用例可能非常有用,在这些用例中,可能需要分析数百甚至数千个实体,并且预测以查看在不久的将来是否可能发生任何待处理的违规行为。

摘要

弹性机器学习(Elastic ML)在异常检测功能之上,还具备一项额外特性:能够将时间序列模型外推到未来,用于预测目的。这一特性涵盖了包括高级入侵检测和容量规划在内的用例,减轻了人们手动绘制图表、跟踪和预测未来趋势的负担,这些趋势是基于过去的行为来预测的。

在下一章中,我们将更深入地探讨异常检测和预测为我们带来的结果,并更好地理解如何利用这些结果来构建仪表板和主动警报。

第五章:解释结果

如我们在前几章中看到的,Elastic ML 在异常检测和预测方面都创建出极其有用的分析。但是,直到现在,我们只以相对肤浅的方式查看 Elastic ML 创建的结果。在本章中,我们将更深入地了解创建的结果,它们是如何存储的,以及您如何以不同的方式利用这些结果来获得额外的洞察。

具体来说,本章将涵盖以下主题:

  • 查看 Elastic ML 结果索引

  • 异常分数

  • 结果索引模式细节

  • 多桶异常

  • 预测结果

  • 结果 API

  • 自定义仪表板和 Canvas 工作台

技术要求

本章中的信息基于 v7.10 版本的 Elastic Stack。

查看 Elastic ML 结果索引

随着我们讨论用户应该如何解释 Elastic ML 异常检测作业的结果,将有助于将所传达的信息与 Elastic ML 内部结果索引中存储的信息联系起来。为了快速初步查看该索引,您可以直接使用 Elasticsearch 中的 _search API 直接查询索引模式,或者更直观地,将索引模式添加到 Kibana 中,并使用原生 Kibana 工具查看索引。为了做到这一点,我们首先必须使用以下程序将 Elastic ML 的内部结果索引暴露给 Kibana:

  1. 在 Kibana 中,点击侧菜单,然后从列表中选择堆栈管理:![图 5.1 – 选择堆栈管理

    ![图片 B17040_05_1.jpg]

    图 5.1 – 选择堆栈管理

  2. 选择索引模式:![图 5.2 – 选择索引模式

    ![图片 B17040_05_2.jpg]

    图 5.2 – 选择索引模式

  3. 选择创建索引模式:![图 5.3 – 选择创建索引模式按钮

    ![图片 B17040_05_3.jpg]

    图 5.3 – 选择创建索引模式按钮

  4. 索引模式名称输入为 .ml-anomalies-*,然后切换包括系统和隐藏索引开关到开启状态。然后,点击下一步按钮:![图 5.4 – 命名索引模式

    ![图片 B17040_05_4.jpg]

    图 5.4 – 命名索引模式

  5. 选择时间字段timestamp,然后点击创建索引模式按钮:![图 5.5 – 定义时间字段

    ![图片 B17040_05_5.jpg]

    图 5.5 – 定义时间字段

  6. 确认索引模式已定义:

![图 5.6 – 确认索引模式已定义

![图片 B17040_05_6.jpg]

图 5.6 – 确认索引模式已定义

现在,.ml-anomalies-* 的索引模式已定义,我们可以使用 Kibana 的 Discover 来探索结果索引的内容(从主 Kibana 菜单中选择Discover):

![图 5.7 – 在 Kibana Discover 中查看结果索引

![图片 B17040_05_7.jpg]

图 5.7 – 在 Kibana Discover 中查看结果索引

现在我们能够在 Kibana Discover 中查看结果索引,我们可以使用 Discover 的搜索和过滤功能以任何我们想要的方式探索结果。例如,您可以检索具有特定异常检测作业名称的所有记录级异常,其中记录的异常分数超过某个值:

图 5.8 – 使用 Kibana Discover 搜索和过滤异常

图 5.8 – 使用 Kibana Discover 搜索和过滤异常

在 KQL 中,此查询的语法如下:

job_id:"web_traffic_per_country" and result_type:"record" and record_score>90

在这里,我们可以看到两个与我们查询匹配的具体事件。结果索引中包含大量信息,我们将系统地学习在本章中解析这些信息的大部分。

注意

虽然查看和查询.ml-anomalies-*索引模式内的结果和查询是安全的,但我们应记住,与此索引模式匹配的索引是系统索引,尝试手动修改或删除这些索引的内容是不明智的。

首先要理解的概念是,存在不同类型的结果(因此有result_type字段)以及不同类型的分数,它们从不同的角度或层面反映了分析。因此,让我们从更好地理解不同类型的评分以及这些分数是如何在结果索引中计算和存储的入手。

异常分数

首先解读 Elastic ML 异常检测作业的结果需要具备识别以下事实的能力:结果中有几个不同级别的异常评分,如下所示:

  • result_type:bucket): 此级别总结了每个时间桶内整个异常检测作业的结果。本质上,它表示了给定作业配置的时间桶有多不寻常。

  • result_type:influencer): 这用于更好地理解在特定时间段内最不寻常的实体(影响者)。

  • result_type:record): 这是关于每个时间桶内每个异常发生或异常实体的最详细信息。同样,根据作业配置(多个检测器、拆分等),每个时间桶可能有多个记录级文档。

此外,为了完全理解评分是如何进行的,我们还需要完全理解以下概念:

  • 归一化:将异常性投射到 0 到 100 之间的固定尺度上的概念。

  • 影响者:在异常发生时,通过对其数据集的有影响力贡献而引发异常创建的实体。

让我们在本节中更深入地研究这五个概念。

桶级别评分

桶级别的异常得分类似于回答这样的问题:“相对于这个作业的所有其他时间间隔,这个时间间隔有多不寻常?”,其中这个时间间隔由异常检测作业的bucket_span定义。如果你的作业有多个检测器或分析中的分割,导致可能同时为许多实体生成结果,那么每个桶级别的结果都是所有这些事物的聚合表示。桶级别的异常得分可以通过几种方式查看,第一种是在异常探索器 UI 顶部的总体泳道:

图 5.9 – 异常探索器的泳道

图 5.9 – 异常探索器的泳道

在这里,我们可以看到“最大异常得分”为88。在此需要注意的是,在此视图中显示的时间范围涵盖了从 1 月 6 日到 2 月 5 日的数据;因此,泳道中大约有 30 个“瓷砖”,每个代表一天。异常检测作业被配置为 15 分钟的桶跨度,因此每个瓷砖显示的是整个一天的最大得分。如果我们使用 Kibana 的时间选择器(屏幕右上角的时间/日期范围控制)仅放大显示一天,我们会看到更多细节,具体来说,总体泳道中的桶级别异常发生在凌晨 02:00 至 02:30 之间:

图 5.10 – 放大后的异常探索器泳道

图 5.10 – 放大后的异常探索器泳道

同样重要的是要注意总体泳道如何(或不如何)与下面的泳道网格相关联。这个泳道网格显示了影响者级别的评分(将在下一节中讨论),因此它并不直接与桶级别的评分相关。这是一个常见的误解,因为许多人认为总体泳道是其下方网格列(例如,最大得分)的一种组合。你当然可以在图 5.10中看到这显然是不正确的,因为网格第二行(大约 07:00 AM)的影响者级别评分在总体(桶)级别没有相应的得分。为什么是这样?简短的答案是总体泳道是时间桶相互之间的比较。因此,最不寻常的时间桶得到最高的得分,而那些非常不寻常的时间桶(由于该时间桶内单个异常的数量和严重性)得到较小的得分,甚至没有得分。确定这种相对评分的过程称为正则化。这是所有级别评分的重要部分,值得独立解释。

正则化

如在第一章中首次介绍,即《IT 领域的机器学习》,我们了解到特定异常的原始概率值在 0 到 100 的范围内进行了标准化。这个过程使得异常的相对排名成为可能,同时也将值限制在一个固定区间内,这对于评估严重性以进行分类和/或警报是有用的。

最后一句中的关键方面是这个相对排名的概念。换句话说,标准化值考虑了异常检测工作到目前为止所看到的东西,并据此进行排名。这也意味着,随着新异常的发现,先前分配的标准化分数可能会随时间而改变。因此,您会注意到结果索引中的分数既有“初始”值,也有当前值,例如:

  • initial_anomaly_score: 在异常创建时记录的桶级别异常分数

  • anomaly_score: 当前桶级别的标准化异常分数

这两个值可能相同,但确实可能会随着时间的推移而分歧。初始分数是一个固定值,但当前分数可能会随着时间调整,例如,可能会遇到更多的严重异常。标准化过程在实时操作中每几个小时发生一次,或者在分析检测到标准化表中的剧烈变化时自发发生。如果异常检测工作被关闭(置于关闭状态),也会进行标准化。标准化可能会重新评分时间上可以追溯到renormalization_window_days设置配置的任何时间(默认值为 30 天或 100 个桶跨度,并且只有在通过 API 或高级作业向导直接修改作业的配置 JSON 创建作业时,该值才可更改)。

影响者级别评分

在影响者级别的异常分数类似于回答问题,“在这段时间里,哪些实体最不寻常?”,其中我们现在正在将这些实体相互比较。影响者级别的分数可以通过几种方式查看,第一种是在异常探索器 UI 中间的主网格泳道,第二种是在左侧的顶级影响者列表:

![Figure 5.11 – 异常探索器中的影响者

![img/B17040_05_11.jpg]

图 5.11 – 异常探索器中的影响者

在这里,我们看到在泳道的主网格中,geo.src字段的国籍代码按总影响因素分数递减排列。请注意,尽管网格设置为每页显示 10 行,但只列出了六个异常的国籍代码(在此时间段内没有更多具有显著影响因素分数的代码)。此外,主要影响因素列在左侧,显示每个实体的最大影响因素分数(geo.src:IN 为 99)以及此时间范围内所有影响因素分数的总和(geo.src:IN 为 223)。在这种情况下,由于此作业只定义了一个影响因素,所以这些信息可能看起来是多余的。然而,许多作业定义了多个影响因素,所以在这种情况下,视图变得更加合理。例如,如果我们查看在 kibana_sample_data_logs 索引上进行的群体分析作业,我们选择 distinct_count("url.keyword") over clientip 作为检测器,并选择 clientipresponse.keyword 作为影响因素,异常检测探索器视图可能看起来像这样:

图 5.12 – 异常检测探索器中的多个影响因素

图 5.12 – 异常检测探索器中的多个影响因素

注意到网格的 clientip,但左边的主要影响因素列表显示了两个影响因素列表。

异常检测探索器是交互式的,所以如果我们选择 2 月 19 日总体泳道中的关键异常瓷砖,影响因素网格和列表会随着该时期的过滤器隐式应用而改变:

图 5.13 – 对特定一天进行过滤的异常检测探索器

图 5.13 – 对特定一天进行过滤的异常检测探索器

我们现在只看到所选日期的相关实体。既然我们已经对影响因素有了一定的了解,你可能会问,如果它们可以以这种方式表示,哪些字段是好的影响因素候选者?让我们快速偏离一下,更深入地讨论一下影响因素。

影响因素

在异常检测作业配置中,可以定义字段作为影响因素。影响因素的概念是描述一个实体的字段,你想要知道它是否是异常存在的原因,或者至少是否做出了重大贡献。请注意,任何被选为候选影响因素的字段不需要是检测逻辑的一部分,尽管选择用作拆分或群体的字段作为影响因素是很自然的。

如果我们重新审视图 5.13中展示的例子,我们会看到clientipresponse.keyword字段都被声明为作业的影响因子(其中clientip是检测器配置的一部分,但response.keyword不是)。客户端 IP 地址30.156.16.164被识别为顶级影响因子。这种声明似乎有点冗余,因为异常正是针对该客户端 IP 地址的——但这是在为定义人口或分割字段选择影响因子时预期的情况。另一个顶级影响因子(response.keyword)的值为404。这个特定的信息非常相关,因为它为用户提供了一个关于30.156.16.164 IP 地址在异常期间所做事情的即时线索。如果我们调查异常发生时的异常 IP 地址,我们会看到 100%的请求都导致了404响应代码:

图 5.14 – 异常发生时,影响字段值 404 主导了结果

图 5.14 – 异常发生时,影响字段值 404 主导了结果

因此,404的值具有高影响因子得分(如图 5.13 所示为50)。你可能认为,由于 100%的请求都是404,影响因子得分也应该为 100,但事实并非如此简单。影响因子得分是相对于其他影响因子得分进行归一化的,影响因子得分也表达了404值随时间变化的不寻常程度。在这个特定的示例数据集中,随着时间的推移,有数百次404的发生,但其中大多数并没有与异常相关联。因此,这个特定异常的影响因子得分受到了这一事实的调和。对于 Elastic ML 来说,可能有一个有说服力的论点来区分这两个概念——一个分数表示实体随时间的不寻常程度,另一个分数表示字段值对特定异常的影响程度——但到目前为止,这些概念被融合到了影响因子得分中。

还需要理解的是,寻找潜在影响因子的过程发生在 Elastic ML 找到异常之后。换句话说,它不会影响作为检测一部分所做的任何概率计算。一旦确定异常,机器学习将系统地遍历每个候选影响因子字段的每个实例,并移除该实例在时间桶中的数据贡献。如果移除后剩余的数据不再异常,那么通过反事实推理,该实例的贡献必须是具有影响力的,并且会相应地进行评分(结果中的influencer_score)。

影响者可以成为在查看单个机器学习作业的结果时非常强大的工具,甚至可能是几个相关作业。在第七章,“AIOps 和根本原因分析”中,我们将看到如何有效地使用影响者来协助根本原因分析。

记录级评分

记录级别的异常得分是结果中的最低抽象级别,包含最多的细节。在异常探索器 UI 中,记录级结果显示在底部的表格中:

![图 5.15 – 显示记录级结果的异常表图片

图 5.15 – 显示记录级结果的异常表

注意,如果间隔选择器设置为自动,则任何在时间上连续相邻的异常将被合并,只显示得分最高的异常。将间隔字段设置为显示所有将揭示每个单独的异常,如果需要的话。

一个常见的误解是记录级异常得分与在 41x higher) 中阐述的偏差直接相关。得分完全由概率计算驱动,使用之前描述的相同归一化过程。描述字段,甚至典型值,都是简化后的上下文信息,以便更容易理解异常。实际上,正如你稍后将会看到的,描述字段并没有存储在结果索引中——它只是在 Kibana 中即时计算的。

当查看 .ml-anomalies-* 索引中的不同级别异常记录时,我们可以看到那里有许多字段可供我们使用。其中一些可能是明显的,而另一些可能不明显。在下一节中,我们将系统地介绍结果索引的模式,并将描述重要字段的含义。

结果索引模式细节

正如我们已经暗示的,在结果索引内部,有各种不同的文档,每个文档都有其自身的用途,有助于理解异常检测作业的结果。在本节中,我们将讨论的文档是与我们在本章先前讨论的三个抽象级别直接相关的。它们被恰当地命名为以下内容:

  • result_type:bucket:以提供桶级结果

  • result_type:record:以提供记录级结果

  • result_type:influencer:以提供影响者级结果

这些文档类型的分布将取决于机器学习作业的配置以及正在分析的数据集的特征。这些文档类型是根据以下启发式方法编写的:

  • result_type:bucket:对于每个桶跨度的时间,都会写入一个文档。换句话说,如果桶跨度是 15 分钟,那么每 15 分钟就会有一个这种类型的文档被写入。其时间戳将等于桶的前沿。例如,对于包含 11:30 到 11:45 范围的时间桶,此类型的结果文档将有一个 11:30 的时间戳。

  • result_type:record:对于时间桶内的每个异常情况,都会写入一个文档。因此,对于包含许多实体(IP 地址、主机名等)的大数据集,在重大异常事件或大规模故障期间,一个特定的时间桶可能会有数百甚至数千个异常记录。此文档也将有一个时间戳,等于桶的前沿。

  • result_type:influencer:对于每个异常记录中找到的每个影响者,都会写入一个文档。因为每个异常记录可能找到多个影响者类型,所以这种类型的文档可能比记录结果更庞大。此文档也将有一个时间戳,等于桶的前沿。

当我们进入 第六章基于机器学习的警报 时,理解这些文档类型中的字段尤其重要,因为不可避免地会在警报细节(通常,越多越好)和单位时间内的单个警报数量(通常,越少越好)之间取得平衡。我们将在开始编写实际警报时重新审视这一点。

桶结果

在最高抽象级别上,是桶级别的结果。请记住,这是整个作业随时间聚合的结果,本质上回答了“这个时间桶有多不寻常?”的问题。

让我们通过使用 Kibana Discover 和执行以下 KQL 查询来查看 .ml-anomalies-* 索引中的一个示例文档:

result_type :"bucket" and anomaly_score >98

这将产生以下输出:

图 5.16 – Kibana Discover 中看到的桶级别结果文档

图 5.16 – Kibana Discover 中看到的桶级别结果文档

点击图 5.16 中文档时间戳旁边的 > 图标,可以将其展开,以便您可以看到所有详细信息:

图 5.17 – Kibana Discover 中的桶级别文档细节

图 5.17 – Kibana Discover 中的桶级别文档细节

您可以从图 5.16 中看到,我们的查询只返回了一个桶级别的文档,一个单独的异常时间桶(时间戳为 1613824200000,或者在我的时区中,2021 年 2 月 20 日上午 07:30:00 GMT-05:00),其 anomaly_score 大于 98。换句话说,在这个时间范围内没有其他时间桶有如此大的异常。让我们看看关键字段:

  • timestamp: 时间桶的前沿时间戳。在 Kibana 中,此字段将默认以您的本地时区显示(尽管它在索引中以 UTC 时区的纪元格式存储)。

  • anomaly_score: 基于整个作业中看到的概率范围,桶的当前标准化分数。随着作业处理新数据和新异常的发现,此分数的值可能会随时间波动。

  • initial_anomaly_score: 桶的标准化分数,即当该桶首次由分析分析时。与 anomaly_score 不同,随着更多数据的分析,此分数不会改变。

  • event_count: 在桶跨度内由 ML 算法看到的原始 Elasticsearch 文档的数量。

  • is_interim: 一个标志,表示桶是否已最终确定,或者是否仍在等待桶跨度内的所有数据接收。对于在实时中运行的工作,此字段是相关的。对于某些类型的分析,即使桶中并非所有数据都已看到,也可能有临时结果。

  • job_id: 创建此结果的异常检测作业的名称。

  • processing_time_ms: 分析处理此桶数据所需处理时间(以毫秒为单位)的内部性能度量。

  • bucket_influencers: 为当前桶识别出的影响因子(及其详细信息)的数组。即使在工作配置中没有选择影响因子,或者分析中没有影响因子,也始终会存在一个默认的 influencer_field_name:bucket_time 类型的影响因子,这主要是一个内部记录工具,以便在无法确定显式影响因子的情况下对桶级别的异常进行排序。

如果作业具有命名和识别的影响因子,那么 bucket_influencers 数组可能看起来像 图 5.17 所示。

注意,除了默认的 influencer_field_name:bucket_time 类型条目外,在这种情况下,还有一个针对 geo.src 字段的由分析识别出的影响因子的字段名条目。这是一个提示,表明 geo.src 是在此次异常发生时发现的有关影响因子类型。由于在作业配置中可以选择多个影响因子候选人,应注意的是,在这种情况下,geo.src 是唯一的影响因子字段,没有发现其他具有影响力的字段。还应注意的是,在这个细节级别,geo.src 的特定实例(即哪一个)没有公开;当在较低层次抽象查询时,将公开该信息,我们将在下一节讨论。

记录结果

在较低层次的抽象级别,存在记录级别的结果。提供最大量的细节,记录结果显示了异常的具体实例,并基本上回答了问题:“哪个实体不寻常以及程度如何?”

让我们通过使用 Kibana Discover 和执行以下 KQL 查询来查看 .ml-anomalies-* 索引中的一个示例文档:

result_type :"record" and record_score >98

这将导致类似以下内容:

图 5.18 – 在 Kibana Discover 中看到的记录级别结果文档

图 5.18 – 在 Kibana Discover 中看到的记录级别结果文档

点击文档时间戳旁边的 > 图标将展开它,以便您可以看到所有详细信息:

图 5.19 – 在 Kibana Discover 中的记录级别文档详情

图 5.19 – 在 Kibana Discover 中的记录级别文档详情

你可以在 图 5.18 中看到,我们的查询返回了一些桶级别文档。让我们看看关键字段:

  • timestamp:时间桶的前沿时间戳,其中发生了此异常。这与前面解释的类似。

  • job_id:创建此结果的事务检测作业的名称。

  • record_score:基于整个作业中看到的概率范围,异常记录的当前归一化分数。随着作业处理新数据并发现新的异常,此分数的值可能会随时间波动。

  • initial_record_score:异常记录的归一化分数,即当该桶首次由分析处理时的分数。与 record_score 不同,随着更多数据的分析,此分数不会改变。

  • detector_index:一个内部计数器,用于跟踪此异常所属的检测器配置。显然,对于单检测器作业,此值将为零,但在具有多个检测器的作业中,此值可能不为零。

  • function:一个引用,用于跟踪用于创建此异常的检测器函数。

  • is_interim:一个标志,表示桶是否已最终确定,或者桶是否仍在等待接收桶跨度内的所有数据。对于在实时中运行的作业,此字段是相关的。对于某些类型的分析,即使桶中尚未看到所有数据,也可能有临时结果。

  • actual:在此桶中分析的数据的实际观察值。例如,如果函数是 count,则这表示在此时间桶中遇到(并计数)的文档数量。

  • typical:基于此数据集的机器学习模型的预期或预测值的表示。

  • multi_bucket_impact:一个测量值(从 -5 到 +5 的范围),用于确定此特定异常受后续多桶分析(本章后面解释)影响的程度,从无影响(-5)到全部影响(+5)。

  • influencers:一个数组,其中包含哪些影响者(以及这些影响者的值)与这个异常记录相关。

如果作业定义了拆分(无论是使用by_field_name和/或partition_field_name)并确定了影响者,那么记录结果文档将包含更多信息,例如图 5.19中所示:

  • partition_field_name:一个提示,表明已定义分区字段,并且对于分区字段值之一发现了异常。

  • partition_field_value:发生异常的分区字段的值。换句话说,这是发现异常的实体名称。

除了这里提到的字段(如果作业配置为使用by字段,则将是by_field_nameby_field_value),我们还看到了geo.src字段的显式实例。这只是一个快捷方式——结果中的每个分区、byover_field_value都将有一个直接的字段名。

如果你的作业正在进行人口分析(通过使用over_field_name),那么记录结果文档将组织得略有不同,因为报告是以人口中不寻常成员的取向进行的。例如,如果我们查看在kibana_sample_data_logs索引上的人口分析作业,并选择distinct_count("url.keyword") over clientip作为检测器,那么一个示例记录级结果文档也将包含一个原因数组:

图 5.20 – 显示人口作业原因数组的记录级文档

图 5.20 – 显示人口作业原因数组的记录级文档

causes数组旨在紧凑地表达该 IP 在该桶中做的所有异常事情。再次强调,许多事情看起来似乎是多余的,但这主要是因为在仪表板或警报中展示结果时,可能存在不同的信息聚合方式。

此外,在本人口分析案例中,我们发现influencers数组中既包含clientip字段,也包含response.keyword字段:

图 5.21 – 显示人口作业影响者数组的记录级文档

图 5.21 – 显示人口作业影响者数组的记录级文档

让我们通过查看影响者级别的结果来结束我们对结果索引模式的调查。

影响者结果

通过影响者这一视角来查看结果,我们可以回答问题:“在我的机器学习作业中,哪些实体最不寻常,以及它们何时变得不寻常?”为了理解影响者级别结果的结构和内容,让我们通过使用 Kibana Discover 和执行以下 KQL 查询来查看.ml-anomalies-*索引中的一个示例文档:

result_type :"influencer" and response.keyword:404

图 5.22 – 在 Kibana Discover 中看到的影响者级别结果文档

图 5.22 – 在 Kibana Discover 中看到的影响者级别结果文档

注意,在这种情况下,我们没有查询分数(influencer_score),而是查询一个预期的实体名称和值。列出的最后一份文档(influencer_score50.174)与我们在图 5.13中看到的一致。

让我们看看关键字段:

  • timestamp: 包含此推广者异常活动的时间桶的前沿时间戳。这与之前解释的类似。

  • job_id: 创建此结果的异常检测作业的名称。

  • influencer_field_name: 在作业配置中声明的推广者字段的名称。

  • influencer_field_value: 该结果相关的推广者字段值。

  • influencer_score: 推广者在此点对异常的贡献程度和异常性的当前标准化分数。

  • initial_influencer_score: 分析首次分析该桶时推广者的标准化分数。与influencer_score不同,此分数在分析更多数据时不会改变。

  • is_interim: 一个标志,表示桶是否已最终确定,或者桶是否仍在等待桶跨度内的所有数据接收。对于实时运行的作业,此字段相关。对于某些类型的分析,即使桶中尚未看到所有数据,也可能有临时结果。

现在我们已经详尽地解释了用户可用的相关字段,我们可以在后续章节中构建自定义仪表板、可视化以及复杂的警报时将这些信息存档。但是,在我们离开这一章之前,我们还有一些重要的概念需要探讨。接下来是关于一种特殊类型的异常——多桶异常的讨论。

多桶异常

到目前为止,我们研究的大多数关于由 Elastic ML 的异常检测作业生成的异常,都是关于在特定时间查看特定异常,但以bucket_span的间隔进行量化。然而,我们当然可以有一些情况,其中桶跨度内的特定观察可能并不那么异常,但一个扩展的时间窗口,如果整体来看,可能比任何单个观察更显著地异常。让我们看一个例子。

多桶异常示例

第三章([B17040_03_Epub_AM.xhtml#_idTextAnchor049])的示例中首次展示的异常检测,在图 3.17中,我们在此重复该图,以展示多桶异常如何在 Elastic ML UI 中展现:

![图 5.23 – 多桶异常首次在第三章展示

![图片 B17040_05_23.jpg]

图 5.23 – 多桶异常首次在第三章展示

如在第三章中讨论的,异常检测,多桶异常在 UI 中以不同的符号表示(一个十字而不是一个点)。它们表示实际的单个值可能不一定异常,但在 12 个连续桶的滑动窗口中存在一个趋势。在这里,您可以注意到几个相邻桶中存在一个明显的下滑。

然而,请注意,一些多桶异常标记有时会在数据“恢复”之后放置在数据上。这可能会让用户感到有些困惑,直到你意识到,由于多桶异常的确定是一种二级分析(除了桶级分析之外)并且由于这种分析是一个向后看的滑动窗口,当异常被记录时,窗口的前沿可能会在情况恢复之后。

多桶评分

正如之前提到的,多桶分析是一种二级分析。因此,对于每个桶跨度,计算两个概率——当前桶中观察到的观测值的概率,以及多桶特征的概率——这是当前桶和前 11 个桶的加权平均值。如果这两个概率大致处于相同的数量级,那么multi_bucket_impact将较低(在-5 到+5 的负值范围内)。另一方面,如果多桶特征的概率明显较低(因此更不寻常),那么multi_bucket_impact将较高。

图 5.23中显示的示例中,UI 将向用户显示多桶影响为,但不会给出实际的评分:

![图 5.24 – 多桶异常,显示影响评分图片 B17040_05_24.jpg

图 5.24 – 多桶异常,显示影响评分

然而,如果您查看原始记录级结果,您将看到multi_bucket_impact确实被赋予了+5 的值:

![图 5.25 – 多桶异常记录,显示原始分数图片 B17040_05_25.jpg

图 5.25 – 多桶异常记录,显示原始分数

多桶异常为您提供了对数据行为的不同视角。您需要记住它们是如何通过multi_bucket_impact字段来表示和评分的,以便您可以根据需要将它们包含或排除在报告或警报逻辑中。

让我们现在来看看(是的,这里有意使用了双关语)预测结果在结果索引中的表示方式。

预测结果

如在第四章中详细解释的,预测,我们可以让 Elastic ML 将分析过的数据趋势外推到未来。回想一下我们在图 4.21中展示了什么:

![图 5.26 – 预测结果首次在第四章展示图片 B17040_05_26.jpg

图 5.26 – 预测结果首次在第四章展示

记住,预测值是最有可能的值(概率),阴影区域是置信度 95% 的范围。这三个关键值存储在 .ml-anomalies-* 结果索引中,名称如下:

  • forecast_prediction

  • forecast_upper

  • forecast_lower

查询预测结果

当在 .ml-anomalies-* 结果索引中查询预测结果时,重要的是要记住预测结果是短暂的——它们在创建后的默认生命周期为 14 天,尤其是在从 Kibana UI 创建时。如果需要不同的过期持续时间,则必须通过 _forecast API 端点调用预测并显式设置 expires_in 持续时间。

另一件需要注意的事情是,可能在同一数据集的不同时间点调用了多个预测。正如之前在 图 4.4 中所示并在此重复,多次调用预测会产生多个预测结果:

![Figure 5.27 – 在不同时间调用多个预测的符号表示

![img/B17040_05_27.jpg]

图 5.27 – 在不同时间调用多个预测的符号表示

因此,我们需要一种方法来区分结果。在 Kibana UI 中,只需查看 创建 日期即可简单区分:

![Figure 5.28 – 查看多次运行的预测结果

![img/B17040_05_28.jpg]

图 5.28 – 查看多次运行的预测结果

然而,当查看结果索引时,应注意每个调用的预测都有一个唯一的 forecast_id

![Figure 5.29 – 在 Kibana Discover 中查看预测结果

![img/B17040_05_29.jpg]

图 5.29 – 在 Kibana Discover 中查看预测结果

当使用 _forecast API 调用预测时,这个 forecast_id 才是明显的,因为 forecast_id 是 API 调用负载的一部分返回的。

因此,如果创建了跨越相同时间框架的多个预测,将会有多个具有不同 ID 的结果。

当查询预测结果时,你可以考虑两种可能的查询方向:

  • 值聚焦:查询提供一个日期和时间,结果返回该时间的特定值。例如,“5 天后我的利用率是多少?”就是一个很好的例子。

  • 时间聚焦:查询提供一个值,结果是一个实现该值的时间。例如,“我的利用率何时达到 80%?”就是一个很好的例子。

显然,任何一种查询都是可能的。例如,为了满足以时间为重点的查询,我们需要稍微调整查询,要求它返回预测值满足某些标准的时间(或日期)。用户可以使用其他传统查询方法(KQL、Elasticsearch DSL)查询预测结果,但为了稍微变化一下,我们将在 Kibana Dev Tools 控制台中使用 Elastic SQL 提交查询:

POST _sql?format=txt
{
   "query": "SELECT forecast_prediction,timestamp FROM \".ml-anomalies-*\" WHERE job_id='forecast_example' AND forecast_id='Fm5EiHcBpc7Wt6MbaGcw' AND result_type='model_forecast' AND forecast_prediction>'16890' ORDER BY forecast_prediction DESC"
}

在这里,我们询问是否存在任何预测值超过我们设定的 16,890 值限制的时间。响应如下:

forecast_prediction|       timestamp        
-------------------+------------------------
16893.498325784924 |2017-03-17T09:45:00.000Z

换句话说,我们可能在 3 月 17 日上午 9:45 GMT 时突破阈值(尽管请记住,从第四章预测中,所使用的样本数据来自过去,因此预测预测也属于过去)。现在我们已经很好地理解了如何查询预测结果,我们可以将它们包含在仪表板和可视化中,我们将在本章后面部分介绍——甚至可以在警报中,正如我们在第六章基于机器学习的警报中看到的。

但是,在我们查看如何在自定义仪表板和可视化中包含结果之前,让我们再简要介绍一个主题——Elastic ML 结果 API。

结果 API

如果程序化访问结果是你的事情,除了直接查询结果索引外,你还可以选择查询 Elastic ML 的结果 API。API 的一些部分与我们之前探索的内容重复,而一些部分是独特的。我们现在将在接下来的部分中检查它们。

结果 API 端点

有五个不同的结果 API 端点可用:

  • 获取桶

  • 获取影响因素

  • 获取记录

  • 获取整体桶

  • 获取类别

前三个 API 端点提供的结果与我们通过直接查询结果索引(通过 Kibana 或使用 Elasticsearch _search API)在本章中已经涵盖的内容重复,这种方法实际上提供了更多的灵活性,所以我们在这里不会详细讨论它们。然而,最后两个 API 端点是新颖的,每个都值得解释。

获取整体桶 API

整体桶 API 调用是一种以编程方式返回多个异常检测作业的汇总结果的方法。我们不会探索请求体的每个参数,也不会描述响应体中的每个字段,因为你可以参考文档。但我们将讨论这个 API 调用的重要功能,即请求任意数量的作业的结果,并接收一个单一的结果评分(称为overall_score),它封装了每个请求作业的最大桶anomaly_scoretop_n平均值。如文档所示,一个示例调用是请求桶异常评分平均高于50.0的前两个作业(在以job-开头的作业集中),从特定的时间戳开始:

GET _ml/anomaly_detectors/job-*/results/overall_buckets
{
  "top_n": 2,
  "overall_score": 50.0,
  "start": "1403532000000"
}

这将导致以下样本返回:

{
  "count": 1,
  "overall_buckets": [
    {
      "timestamp" : 1403532000000,
      "bucket_span" : 3600,
      "overall_score" : 55.0,
      "jobs" : [
        {
          "job_id" : "job-1",
          "max_anomaly_score" : 30.0
        },
        {
          "job_id" : "job-2",
          "max_anomaly_score" : 10.0
        },
        {
          "job_id" : "job-3",
          "max_anomaly_score" : 80.0
        }
      ],
      "is_interim" : false,
      "result_type" : "overall_bucket"
    }
  ]
}

注意,在这种情况下,overall_score是两个最高分数的平均值(overall_score55.0job-3分数80.0job-1分数30.0的平均值),尽管有三个异常检测作业匹配job-*的查询模式。虽然这确实很有趣,也许是为了构建复合警报,你应该意识到这种报告的限制,特别是如果你只能访问桶级别的异常分数,而不能访问记录或影响因素级别的任何信息。在第六章,“基于机器学习分析的警报”,我们将探讨一些关于复合警报的选项。

获取类别 API

categories API 调用仅适用于使用分类的作业,如第三章“异常检测”中详细描述。categories API 返回在文档文本分析过程中发现的类别的一些有趣的内部定义。如果我们对在第三章“异常检测”中创建的分类作业运行 API(为了简洁起见,仅返回一条记录),输出如下:

GET _ml/anomaly_detectors/secure_log/results/categories
{
  "page":{
    "size": 1
  }
}

我们将看到以下响应:

{
  "count" : 23,
  "categories" : [
    {
      "job_id" : "secure_log",
      "category_id" : 1,
      "terms" : "localhost sshd Received disconnect from port",
      "regex" : ".*?localhost.+?sshd.+?Received.+?disconnect.+?from.+?port.*",
      "max_matching_length" : 122,
      "examples" : [
        "Oct 22 15:02:19 localhost sshd[8860]: Received disconnect from 58.218.92.41 port 26062:11:  [preauth]",
        "Oct 22 22:27:20 localhost sshd[9563]: Received disconnect from 178.33.169.154 port 53713:11: Bye [preauth]",
        "Oct 22 22:27:22 localhost sshd[9565]: Received disconnect from 178.33.169.154 port 54877:11: Bye [preauth]",
        "Oct 22 22:27:24 localhost sshd[9567]: Received disconnect from 178.33.169.154 port 55723:11: Bye [preauth]"
      ],
      "grok_pattern" : ".*?%{SYSLOGTIMESTAMP:timestamp}.+?localhost.+?sshd.+?%{NUMBER:field}.+?Received.+?disconnect.+?from.+?%{IP:ipaddress}.+?port.+?%{NUMBER:field2}.+?%{NUMBER:field3}.*",
      "num_matches" : 595
    }
  ]
}

回复中包含以下几个元素:

  • category_id: 这是消息类别的编号(从 1 开始递增)。它对应于结果索引中mlcategory字段的值。

  • terms: 这是一个从消息中提取的静态、不可变的单词列表。

  • examples: 一个包含完整、未修改的样本日志行的数组,这些行属于此类。这些用于向用户展示一些真实日志行的样子。

  • grok_pattern: 一个正则表达式样式的模式匹配,可以用于 Logstash 或一个可以用来匹配此消息类别的摄取管道。

  • num_matches: 在此数据集上运行的异常检测作业中,此消息类别在日志中出现的次数。

也许这个 API 最有趣的使用不是用于异常检测,而是仅仅为了理解你的非结构化日志中类别类型的唯一数量及其分布——以回答诸如“我的日志中有什么类型的消息,每种类型有多少?”等问题。这些功能中的一些可能在将来被利用来创建一个“数据准备”管道,以帮助用户更容易地将非结构化日志摄取到 Elasticsearch 中。

现在我们来探讨如何利用从 Elastic ML 的异常检测和预测作业中获得的结果,在自定义仪表板、可视化以及 Canvas 工作垫中发挥作用。

自定义仪表板和 Canvas 工作垫

很明显,现在我们知道了结果索引的来龙去脉,该索引存储了 Elastic ML 的异常检测和预测分析的精华,我们关于如何以对我们自己的目标有意义的方 式表达这些结果的想象力是无限的。本节将简要探讨一些概念和想法,您可以使用它们将 Elastic ML 的结果带到您附近的大屏幕上!

仪表板“可嵌入内容”

Elastic ML 功能的一个最近增加是能够将异常探索器时间线(“泳道”)嵌入到现有的自定义仪表板中。要完成此操作,只需点击异常时间线右上角的“三个点”菜单,并选择添加到仪表板选项:

![图 5.30 – 将异常时间线添加到另一个仪表板图片

图 5.30 – 将异常时间线添加到另一个仪表板

在这一点上,选择您想要包含的泳道视图的哪个部分,并选择您希望将它们添加到哪个仪表板(们):

![图 5.31 – 将异常时间线添加到特定仪表板图片

图 5.31 – 将异常时间线添加到特定仪表板

点击添加和编辑仪表板按钮,然后会将用户带到目标仪表板,并允许他们移动和调整嵌入的面板大小。例如,我们可以将异常与其他可视化并排显示:

![图 5.32 – 新仪表板现在包含异常泳道可视化图片

图 5.32 – 新仪表板现在包含异常泳道可视化

在 TSVB 中的异常作为注释

kibana_sample_data_logs具有以下面板选项:

![图 5.33 – 创建新的 TSVB 可视化 – 面板选项图片

图 5.33 – 创建新的 TSVB 可视化 – 面板选项

然后,这是geo.src的以下配置):

![图 5.34 – 创建新的 TSVB 可视化 – 数据选项图片

图 5.34 – 创建新的 TSVB 可视化 – 数据选项

然后,我们有以下配置的web_traffic_per_country以选择记录分数超过90的异常:

![图 5.35 – 创建新的 TSVB 可视化 – 注释选项图片

图 5.35 – 创建新的 TSVB 可视化 – 注释选项

注意record_scorepartition_field_value)以及Anomaly:{{record_score}} for {{partition_field_value}})。一旦完成,我们就得到了最终结果:

![图 5.36 – 创建新的 TSVB 可视化,包含异常注释图片

图 5.36 – 创建新的 TSVB 可视化,包含异常注释

我们现在有一个很好的可视化面板,异常数据叠加在原始原始数据上。

自定义画布工作垫

Kibana Canvas 是创建由 Elasticsearch 驱动的像素完美信息图的终极工具。您可以使用一组可定制的元素创建高度定制化的报告。Canvas 中的体验与标准的 Kibana 仪表板非常不同。Canvas 为您提供了一个工作区,您可以在其中构建一系列幻灯片(在概念上类似于 Microsoft PowerPoint),称为工作垫

要在 Canvas 工作垫中利用异常检测和/或预测结果,不需要做任何特别的事情——本章迄今为止学到的所有内容都适用。这是因为使用 essql 命令在 Canvas 中查询 .ml-anomalies-* 索引模式并提取我们关心的信息非常容易。

当我们安装 Kibana 样本数据时,我们也会得到一些样本 Canvas 工作垫来享受:

![图 5.37 – 样本 Canvas 工作垫图片 B17040_05_37

图 5.37 – 样本 Canvas 工作垫

点击[日志]网络流量样本工作垫,我们可以打开它进行编辑:

![图 5.38 – 样本网络流量工作垫图片 B17040_05_38

图 5.38 – 样本网络流量工作垫

选择页面上的一个元素(可能是 324),然后在 Canvas 的右下角选择表达式编辑器,将显示该元素的详细信息:

![图 5.39 – 在表达式编辑器中编辑 Canvas 元素图片 B17040_05_39

图 5.39 – 在表达式编辑器中编辑 Canvas 元素

注意,获取实时数据的真正“魔法”嵌入在 essql 命令中——表达式其余部分仅仅是格式化。作为一个简单的例子,我们可以使用以下语法调整 SQL:

SELECT COUNT(timestamp) as critical_anomalies FROM \".ml-anomalies-*\" WHERE job_id='web_logs_rate' AND result_type='record' AND record_score>'75'

注意,由于 .ml-anomalies-* 索引模式的名字以非字母字符开头,因此名字需要用双引号括起来,并且这些双引号需要用反斜杠字符转义。

这将返回特定数据集上特定异常检测作业中关键异常(record_score 大于 75 的异常)的总数:

![图 5.40 – 显示关键异常的数量图片 B17040_05_40

图 5.40 – 显示关键异常的数量

简而言之,使用 Canvas 创建非常美丽和有意义的数据可视化以及利用异常检测结果或预测结果中的信息非常容易。

摘要

Elastic ML 的异常检测和预测分析通过 Kibana 中提供的丰富 UI 或通过直接查询结果索引和 API 的编程方式创建出奇妙而有意义的结果。理解您的异常检测和预测作业的结果,并能够适当地利用这些信息进行进一步的定制可视化或警报,使这些定制资产更加强大。

在下一章中,我们将利用这些结果来创建复杂且实用的主动警报,以进一步提高 Elastic ML 的操作价值。

第六章:基于机器学习分析的警报

前一章(第五章**,结果解释)深入讲解了异常检测和预测结果是如何存储在 Elasticsearch 索引中的。这为我们现在创建主动的、可操作的、信息丰富的警报提供了适当的背景。

在撰写本书时,我们发现我们正处于一个转折点。多年来,Elastic ML 一直依赖于 Watcher(Elasticsearch 的一个组件)的警报功能,因为这是唯一可以针对数据进行警报的机制。然而,一个新的警报平台已经被设计为 Kibana 的一部分(并在 v7.11 中被认为是 GA),这种新的方法将成为未来警报的主要机制。

Watcher 仍然提供一些有趣的功能,这些功能在 Kibana 警报中尚未提供。因此,本章将展示使用 Kibana 警报和 Watcher 创建警报的使用方法。根据您的需求,您可以选择您想使用的方法。

具体来说,本章将涵盖以下主题:

  • 理解警报概念

  • 从机器学习 UI 构建警报

  • 使用 watch 创建警报

技术要求

本章中的信息将使用 v7.12 版本的 Elastic Stack。

理解警报概念

希望不会过于咬文嚼字,这里可以提出一些关于警报和某些警报方面(尤其是关于异常检测)的重要性声明,在我们深入研究配置这些警报的机制之前,这些方面是非常重要的。

异常不一定是警报

这需要明确指出。通常,最初接受异常检测的用户一旦意识到可以针对异常发出警报,就会感到有必要对一切发出警报。如果异常检测部署在数百、数千甚至数万个实体上,这可能会是一个极具挑战性的情况。异常检测虽然确实可以释放用户从定义特定的、基于规则的异常或硬编码的阈值警报中解脱出来,但也可能在大规模数据中广泛部署。我们需要意识到,如果我们不小心,对每一个小异常的详细警报可能会非常嘈杂。

幸运的是,我们在第五章**,结果解释中已经了解了一些机制,这些机制有助于我们减轻这种情况:

  • 总结:我们了解到异常性不仅报告了单个异常(在“记录级别”),还在桶级别和影响因素级别进行了总结。如果我们愿意,这些总结分数可以促进更高层次的抽象警报。

  • 标准化 评分:由于每个异常检测作业都有一个定制的标准化范围,它是专门为特定的检测配置和正在分析的数据集构建的,这意味着我们可以利用 Elastic ML 输出的标准化评分来限制典型的警报频率。也许对于您创建的特定作业,以至少 10 分的异常分数进行警报通常每天会收到大约一打警报,50 分的分数每天会收到大约一个警报,90 分的分数每周会收到大约一个警报。换句话说,您可以有效地调整警报,以适应您对每单位时间内希望接收的警报数量的容忍度(当然,除了意外系统级故障的情况,这可能会产生比平时更多的警报)。

  • 相关性/组合:也许对单个指标异常(例如,主机的 CPU 异常高)的警报不如对一组相关异常(CPU 高、空闲内存低、响应时间也高)的警报有说服力。在某些情况下,对复合事件或序列的警报可能更有意义。

事实是,尽管没有一种适合所有情况的哲学来结构警报并提高警报的有效性,但用户仍然有一些选项可供选择,以确定最适合您的方法。

在实时警报中,时间很重要

第二章**,启用和实施中,我们了解到异常检测作业是一个相对复杂的原始数据查询、数据分析以及结果报告的持续过程,这个过程可以在接近实时的情况下运行。因此,作业配置的几个关键方面决定了该过程的节奏,即bucket_spanfrequencyquery_delay参数。这些参数定义了结果何时“可用”以及值将具有哪个时间戳。这一点非常重要,因为对异常检测作业的警报将涉及对结果索引(.ml-anomalies-*)的后续查询,显然,查询何时运行以及使用的时间范围将决定您是否真正找到了所寻找的异常。

为了说明这一点,让我们看看以下内容:

图 6.1 – 当前时间下桶跨度、查询延迟和频率的表示

图 6.1 – 当前时间下桶跨度、查询延迟和频率的表示

图 6.1中,我们看到一个特定的时间桶(由等于t2-t1的时间宽度表示),滞后于当前系统时间(query_delay)。在桶内,可能存在由frequency参数定义的时间细分。关于这个桶的结果如何写入结果索引(.ml-anomalies-*),我们应该记住,为这个桶编写的文档的timestamp值都将等于桶的前沿时间t1

为了讨论一个实际例子,让我们想象以下情况:

  • bucket_span = 15 分钟

  • frequency = 15 分钟

  • query_delay = 2 分钟

如果query_delay)并且结果文档在不久之后写入.ml-anomalies-*(但带有 11:45 A.M.的时间戳)。因此,如果在 12:05 P.M.我们查看.ml-anomalies-*以查看 11:45 A.M.的结果是否存在,我们相当有信心它们会存在,并且我们可以检查内容。然而,如果现在只有 12:01 P.M.,那么对应于 11:45 A.M.-12:00 P.M.的桶的结果文档尚不存在,并且将在大约一分钟之后写入。我们可以看到,事情的时间安排非常重要。

如果在我们的示例场景中,我们将frequency的值降低到 7.5 分钟或 5 分钟,那么我们确实可以更早地访问桶的结果,但结果会被标记为临时,并且当桶最终确定时,结果可能会发生变化。

注意

如果频率是桶跨度的一个子倍数,则会在桶内创建临时结果,但并非所有检测器都生成临时结果。例如,如果你有一个maxhigh_count检测器,那么显示高于典型值的临时结果是有可能且合理的——你不需要看到整个桶的内容就能知道你已经超过了预期。然而,如果你有一个mean检测器,你确实需要在确定平均值之前看到整个桶的观测值——因此,不会生成临时结果,因为它们没有意义。

所以,说到这里,如果我们现在从图 6.1的图中稍微推进时间,但也画出这个桶之前和之后的桶,它看起来会像以下这样:

![图 6.2 – 连续桶的表示

![img/B17040_06_2.jpg]

图 6.2 – 连续桶的表示

在这里,图 6.2中,我们看到当前系统时间(再次,由is_interim:true标志表示,如第五章**,解释结果中首次展示)。

如果我们想要调用一个基本询问“自上次我查看以来是否有任何新的异常产生?”的警报搜索,并且我们调用该搜索的时间是在图 6.2中所示的时间现在,那么我们应该注意以下情况:

  • “回顾”的时间段应该是 bucket_span 宽度的两倍左右。这是因为这保证了我们将看到当前时间桶(此处为 bucket t2)可能发布的任何中间结果,以及前一个时间桶(此处为 bucket t1)的任何最终结果。来自 bucket t0 的结果将不会匹配,因为 bucket t0 的时间戳在查询的时间窗口之外——只要我们确保警报查询能够按照适当的计划重复执行(参见以下要点)。

  • 运行此查询选择的时间可能几乎在任何时间桶 t2 的时间窗口内,并且这仍然会按描述的方式工作。这很重要,因为警报查询的运行计划可能与异常检测作业运行的计划(以及写入结果)不同步。

  • 我们可能会将警报搜索的重复操作间隔安排在等于 bucket_span 的最大间隔内,但如果我们对捕捉当前尚未最终确定的时间桶中的中间异常感兴趣,它可能会更频繁地执行。

  • 如果我们不希望考虑中间结果,我们需要修改查询,使得 is_interim:false 成为查询逻辑的一部分,以避免匹配它们。

考虑到所有这些条件,你可能会认为需要某种类型的黑暗魔法才能正确且可靠地实现这一点。幸运的是,当我们使用 Elastic ML UI 从 Kibana 构建警报时,这些考虑因素会为你处理。然而,如果你觉得自己是一位巫师并且完全理解这一切是如何工作的,那么你可能不会对使用 Watcher 构建非常定制的警报条件感到太害怕,在那里你将拥有完全的控制权。

在以下主要部分中,我们将使用每种方法进行一些示例,以便您可以比较和对比它们的工作方式。

从 ML UI 构建警报

随着 v7.12 版本的发布,Elastic ML 将其默认警报处理程序从 Watcher 更改为 Kibana 警报。在 v7.12 之前,如果用户从 ML UI 选择警报,可以选择接受默认的 watch(Watcher 脚本的一个实例),或者用户可以从头创建一个 watch。本节将重点介绍自 v7.12 以来使用 Kibana 警报的新工作流程,它提供了灵活性和易用性之间的良好平衡。

为了创建一个工作且具有说明性的实时警报示例,我们将使用 Kibana 样本网络日志数据集构建一个场景,这是我们首先在 第三章**,异常检测 中使用的。

本节概述的过程如下:

  1. 在样本数据上定义一些样本异常检测作业。

  2. 在两个异常检测作业上定义两个警报。

  3. 运行异常行为的模拟,以便在警报中捕捉这种行为。

让我们先定义一些样本异常检测作业。

定义样本异常检测作业

当然,在我们能够构建警报之前,我们需要实时运行的工作。我们可以利用与同一 Kibana 网络日志数据集一起提供的示例 ML 工作。

注意

如果你仍然在你的集群中加载了这个数据集,你应该删除它并重新添加。这将重置数据集的时间戳,使得大约一半的数据在过去,另一半在将来。有一些数据在未来将允许我们假装数据是实时出现的,因此我们的实时异常检测工作和针对这些工作的警报将表现得像是真正的实时。

要开始,让我们重新加载示例数据并构建一些示例工作:

  1. 从 Kibana 首页,点击尝试我们的示例数据图 6.3 – Kibana 首页

    图 6.3 – Kibana 首页

  2. 示例网络日志部分点击索引模式(如果已经加载,请删除并重新添加):图 6.4 – 添加示例网络日志数据

    图 6.4 – 添加示例网络日志数据

  3. 查看数据菜单下,选择ML 工作来创建一些示例工作:图 6.5 – 选择创建一些示例 ML 工作

    图 6.5 – 选择创建一些示例 ML 工作

  4. 给三个示例工作分配一个工作 ID 前缀(这里选择了alert-demo-),并确保你取消选择使用完整的kibana_sample_data_logs数据,并选择结束时间为你当前系统时间(在你所在的时区)最近的 15 分钟:图 6.6 – 使用前缀命名示例工作并选择当前时间作为结束时间

    图 6.6 – 使用前缀命名示例工作并选择当前时间作为结束时间

  5. 注意在图 6.6中,2021 年 4 月 8 日 11:00:00.00被选为结束时间,而 11 天前的日期(2021 年 3 月 28 日 00:00:00.00)被选为开始时间(示例数据从你安装它的时候开始大约 11 天)。这个截图的时间是 4 月 8 日上午 11:10。这在尝试使这个示例数据看起来是实时数据的精神上很重要。点击创建工作按钮开始工作创建。一旦工作创建完成,你将看到以下屏幕:图 6.7 – 示例工作完成初始运行

    图 6.7 – 示例工作完成初始运行

  6. 我们现在还不需要查看结果。相反,我们需要确保这三个工作正在实时运行。让我们点击顶部的异常检测返回到工作 管理页面。在那里我们可以看到我们的三个工作已经分析了一些数据,但现在处于关闭状态,数据源目前已停止:图 6.8 – 工作管理屏幕中的示例工作

    图 6.8 – 工作管理屏幕中的示例工作

  7. 现在我们需要启用这三个作业以实时运行。点击每个作业旁边的方框,然后选择齿轮图标以弹出菜单来为所有三个作业选择启动数据流图 6.9 – 启动所有三个示例作业的数据流

    图 6.9 – 启动所有三个示例作业的数据流

  8. 在弹出窗口中,选择搜索开始时间搜索结束时间的最高选项,确保作业将继续实时运行。现在,我们将保留数据流启动后创建警报未勾选,因为我们将在稍后创建自己的警报:图 6.10 – 启动三个示例作业的数据流以实时运行

    图 6.10 – 启动三个示例作业的数据流以实时运行

  9. 点击启动按钮后,我们会看到我们的三个作业现在处于打开/启动状态:

图 6.11 – 示例作业现在实时运行

图 6.11 – 示例作业现在实时运行

现在我们已经让作业运行起来,接下来定义一些针对它们的警报。

对示例作业创建警报

我们的作业现在正在实时运行,我们可以为我们的作业定义一些警报:

  1. 对于alert-demo-response_code_rates作业,点击图标并选择创建警报图 6.12 – 为示例作业创建警报

    图 6.12 – 为示例作业创建警报

  2. 现在出现创建警报弹出窗口,我们可以开始填写我们想要的警报配置:图 6.13 – 创建警报配置

    图 6.13 – 创建警报配置

  3. 图 6.13中,我们将命名我们的警报,但也会定义我们希望这个警报每 10 分钟检查一次异常。这个作业的bucket_span设置为 1 小时,但频率设置为 10 分钟——因此中间结果将比完整桶时间更早可用。这也是我们选择在警报配置中包含中间结果的原因,这样我们就可以尽快收到通知。我们还设置了结果类型类型,以便我们得到异常性的总结处理,如前所述。最后,我们将严重程度阈值设置为51,以便只有得分超过该值的异常才会生成警报。

  4. 在我们继续之前,我们可以检查历史数据上的警报配置。将30d输入测试框中,我们可以看到在过去 30 天的数据中只有一个其他警报符合这个警报条件:图 6.14 – 在历史数据上测试警报配置

    图 6.14 – 在历史数据上测试警报配置

  5. 最后,我们可以在警报触发时配置一个要调用的操作。在这种情况下,我们的系统预先配置为使用 Slack 作为警报操作,因此我们将选择它,但用户还有许多其他选项可供考虑(请参阅www.elastic.co/guide/en/kibana/current/action-types.html以探索所有选项以及如何自定义警报消息):![图 6.15 – 配置警报操作 图片

    图 6.15 – 配置警报操作

  6. 点击保存按钮显然会保存警报,然后可以通过 Kibana 的堆栈管理 | 警报和操作区域查看和修改:![图 6.16 – 警报管理 图片

    图 6.16 – 警报管理

  7. 我们将创建一个额外的警报,针对alert-demo-url_scanning作业。这次,我们将创建一个记录警报,但与其他配置参数类似先前的示例:

![图 6.17 – 在 URL 扫描作业上配置另一个警报图片

图 6.17 – 在 URL 扫描作业上配置另一个警报

现在我们已经配置了两个警报,让我们继续模拟一个实际实时异常情况来触发我们的警报。

模拟一些实时异常行为

在这些示例网络日志的上下文中触发模拟的异常行为有点棘手,但并不太难。这将涉及一些 Elasticsearch API 的使用,通过 Kibana 中的开发工具控制台执行几个命令。控制台是您可以发出 API 调用到 Elasticsearch 并查看这些 API 调用输出(响应)的地方。

注意

如果您不熟悉控制台,请参阅www.elastic.co/guide/en/kibana/current/console-kibana.html

我们将要模拟的是两方面的内容——我们将向异常检测工作正在监控的索引中注入几个假文档,然后等待警报触发。这些文档将显示来自虚构 IP 地址0.0.0.0的请求激增,这将导致响应代码为404,并且还会请求随机的 URL 路径。

让我们开始吧:

  1. 我们需要确定您当前的 UTC 时间。我们必须知道 UTC 时间(而不是您本地时区的时间),因为存储在 Elasticsearch 索引中的文档是以 UTC 存储的。为了确定这一点,您可以使用在线工具(例如搜索“当前 UTC 时间”)。在撰写本文时,当前的 UTC 时间是 2021 年 4 月 8 日下午 4:41。转换成 Elasticsearch 期望的kibana_sample_data_logs索引的格式,这将呈现如下:

      "timestamp": "2021-04-08T16:41:00.000Z"
    
  2. 现在,让我们在当前时间(可能加上一点缓冲 – 在这种情况下,向上取整到下一个半小时,即 17:00)将一些新的虚假文档插入到kibana_sample_data_logs索引中。相应地替换timestamp字段值,并在 Dev Tools 控制台中至少调用以下命令 20 次来插入:

       POST kibana_sample_data_logs/_doc
       {
         "timestamp": "2021-04-08T17:00:00.000Z",
         "event.dataset" : "sample_web_logs",
         "clientip": "0.0.0.0",
         "response" : "404",
         "url": ""
       }
    
  3. 我们可以通过动态修改我们刚刚插入的文档(特别是url字段)来模拟所有 URL 都是唯一的,通过使用一个小脚本在_update_by_query API 调用中随机化字段值:

       POST kibana_sample_data_logs/_update_by_query
       {
         "query": {
           "term": {
             "clientip": {
               "value": "0.0.0.0"
             }
           }
         },
         "script": {
           "lang": "painless",
           "source": "ctx._source.url = '/path/to/'+ UUID.randomUUID().toString();"
         }
       }
    
  4. 我们可以通过查看 Kibana Discover 中的适当时间来验证我们是否正确地从一个虚假 IP 地址创建了一大批独特、随机的请求:![图 6.18 – 在 Discover 中显示的我们人为制造的异常事件爆发 图片

    图 6.18 – 在 Discover 中显示的我们人为制造的异常事件爆发

  5. 注意在图 6.18中,我们不得不稍微看看未来,以看到我们人为插入的文档(如时间轴上 12:45 附近的红色垂直线是当地时间区的实际当前系统时间)。同时注意,我们插入的文档有一个看起来很不错的随机url字段。现在我们已经为异常检测设置了“陷阱”以供寻找,并且我们的警报已经准备好了,我们必须现在坐下来耐心等待警报触发。

接收和审查警报

由于我们插入的异常行为现在正在等待我们的异常检测工作和警报来发现,我们可以思考我们应该什么时候期望看到那个警报。我们应该认识到,鉴于我们的工作桶跨度为 1 小时,频率为 10 分钟,查询延迟约为 1-2 分钟(并且我们的警报确实会寻找中间结果 – 以及我们的警报是以 10 分钟的频率运行的,这与异常检测工作不同步),我们应该期望在当地时间下午 1:12 到 1:20 之间看到我们的警报。

准时地,两个工作的警报消息在当地时间下午 1:16 和 1:18 出现在 Slack 上:

![图 6.19 – 在 Slack 客户端收到的警报图片

图 6.19 – 在 Slack 客户端收到的警报

图 6.19中,最上面的警报当然是为那个正在计算每个response.keyword的事件数量的异常检测工作(因此看到 404 文档的峰值超过了预期),下面的警报是为另一个注意到请求的独特 URL 的高区分计数的其他工作。注意,两个工作都正确地将clientip = 0.0.0.0识别为影响异常的因素。警报文本中包含了跟随链接直接查看异常 探索器中信息的功能。在图 6.20中,我们可以看到,通过跟随第二个警报中的链接,我们到达了一个可以进一步调查异常的熟悉地方:

![图 6.20 – 从警报钻取链接的异常探索器图片

图 6.20 – 来自警报钻取链接的异常探索器

希望通过这个示例,你不仅能看到如何使用 Kibana 警报框架在异常检测作业上使用警报,现在也能欣赏到作业和警报实时操作的复杂性。作业数据源和警报采样间隔的设置确实会影响警报的实时性。例如,我们可以减少数据源的频率和警报的检查间隔设置,以节省几分钟。

在下一节中,我们不会尝试使用 Watcher 复制实时警报检测,但我们将努力理解监控中的等效设置以完成我们需要的工作,将 Watcher 与异常检测作业接口,并展示一些有趣的示例监控。

使用监控创建警报

在版本 7.12 之前,Watcher 被用作 Elastic ML 发现异常的警报机制。Watcher是一个非常灵活的 Elasticsearch 原生插件,可以处理许多自动化任务,警报当然也是其中之一。在版本 7.11 及更早版本中,用户可以从头创建自己的监控(Watcher 中的自动化任务实例)以警报异常检测作业结果,或者选择使用 Elastic ML UI 为他们创建的默认监控模板。我们首先将查看提供的默认监控,然后讨论一些关于自定义监控的想法。

理解遗留默认机器学习监控的结构

现在异常检测作业的警报处理由新的 Kibana 警报框架处理,因此遗留的监控默认模板(以及一些其他示例)被保存在此 GitHub 仓库中:github.com/elastic/examples/tree/master/Alerting/Sample%20Watches/ml_examples

在分析默认机器学习监控(default_ml_watch.json)及其伴随版本(具有电子邮件操作default_ml_watch_email.json),我们看到有四个主要部分:

让我们深入讨论每个部分。

触发器部分

在默认的机器学习监控中,触发器部分定义如下:

    "trigger": {
      "schedule": {
        "interval": "82s"
      }
    },

这里,我们可以看到实时触发 watch 的间隔是每 82 秒。这通常应该是一个介于 60 到 120 秒之间的随机值,这样如果节点重启,所有的 watch 都不会同步,它们将会有更均匀的执行时间,以减少对集群的潜在负载。同样重要的是,这个间隔值应该小于或等于工作的 bucket 跨度。正如本章前面所解释的,如果这个值大于 bucket 跨度,可能会导致最近写入的异常记录被 watch 错过。当间隔小于(甚至远小于)工作的 bucket 跨度时,你也可以利用当有中间结果时提供的先进通知,即使没有看到 bucket 跨度内的所有数据,也可以确定异常。

输入部分

input部分从search部分开始,其中定义了以下query针对.ml-anomalies-*索引模式:

            "query": {
              "bool": {
                "filter": [
                  {
                    "term": {
                      "job_id": "<job_id>"
                    }
                  },
                  {
                    "range": {
                      "timestamp": {
                        "gte": "now-30m"
                      }
                    }
                  },
                  {
                    "terms": {
                      "result_type": [
                        "bucket",
                        "record",
                        "influencer"
                      ]
                    }
                  }
                ]
              }
            },

在这里,我们要求 Watcher 查询过去 30 分钟内一个工作(你将用感兴趣的异常检测工作的实际job_id替换<job_id>)的bucketrecordinfluencer结果文档。正如我们在本章前面的内容中了解到的,这个回溯窗口应该是 ML 工作bucket_span值的两倍(这个模板必须假设工作的 bucket 跨度是 15 分钟)。虽然要求了所有结果类型,但稍后我们会看到,只有 bucket 级别的结果用于评估是否创建警报。

接下来是一系列三个聚合。当它们折叠时,看起来如下:

图 6.21 – watch 输入中的查询聚合

图 6.21 – watch 输入中的查询聚合

bucket_results聚合首先筛选出异常分数大于或等于 75 的 bucket:

             "aggs": {
               "bucket_results": {
                 "filter": {
                   "range": {
                     "anomaly_score": {
                       "gte": 75
                      }
                  }
               },

然后,一个子聚合要求按anomaly_score排序的前 1 个 bucket:

                "aggs": {
                  "top_bucket_hits": {
                    "top_hits": {
                      "sort": [
                        {
                          "anomaly_score": {
                            "order": "desc"
                          }
                        }
                      ],
                      "_source": {
                        "includes": [
                          "job_id",
                          "result_type",
                          "timestamp",
                          "anomaly_score",
                          "is_interim"
                        ]
                      },
                      "size": 1,

然后,仍然在top_bucket_hits子聚合中,有一系列定义的script_fields

                      "script_fields": {
                        "start": {
                          "script": {
                            "lang": "painless",
                            "source": "LocalDateTime.ofEpochSecond((doc[\"timestamp\"].value.getMillis()-((doc[\"bucket_span\"].value * 1000)\n * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()+\":00.000Z\"",
                            "params": {
                              "padding": 10
                            }
                          }
                        },
                        "end": {
                          "script": {
                            "lang": "painless",
                            "source": "LocalDateTime.ofEpochSecond((doc[\"timestamp\"].value.getMillis()+((doc[\"bucket_span\"].value * 1000)\n * params.padding)) / 1000, 0, ZoneOffset.UTC).toString()+\":00.000Z\"",
                            "params": {
                              "padding": 10
                            }
                          }
                        },
                        "timestamp_epoch": {
                          "script": {
                            "lang": "painless",
                            "source": """doc["timestamp"].value.getMillis()/1000"""
                          }
                        },
                        "timestamp_iso8601": {
                          "script": {
                            "lang": "painless",
                            "source": """doc["timestamp"].value"""
                          }
                        },
                        "score": {
                          "script": {
                            "lang": "painless",
                            "source": """Math.round(doc["anomaly_score"].value)"""
                          }
                        }
                      }

这些新定义的变量将被 watch 用于提供更多功能和上下文。其中一些变量仅仅是重新格式化值(score只是anomaly_score的一个四舍五入版本),而startend将后来通过定义一个等于异常 bucket 时间+/- 10 个 bucket 跨度的开始和结束时间来发挥功能作用。这后来被 UI 用于在异常 bucket 之前和之后显示适当的时间范围,以便用户可以更清楚地看到事物。

influencer_resultsrecord_results聚合要求前三个影响者分数和记录分数,但只有record_results聚合的输出用于 watch 的后续部分(并且仅在default_ml_watch_email.jsonaction部分中,其中包含一些默认电子邮件文本)。

条件部分

condition部分是评估input以确定是否执行action部分的地方。在这种情况下,condition部分如下:

    "condition": {
      "compare": {
        "ctx.payload.aggregations.bucket_results.doc_count": {
          "gt": 0
        }
      }
    },

我们使用这个来检查bucket_results聚合是否返回了任何文档(其中doc_count大于 0)。换句话说,如果bucket_results聚合确实返回了非零结果,那么这表明确实存在anomaly_score大于 75 的文档。如果是这样,那么将调用action部分。

action部分

我们默认监视的action部分在我们的案例中有两个部分:一个用于将信息记录到文件的log动作和一个用于发送电子邮件的send_email动作。为了简洁,这里不会重复监视的文本(它有很多文本)。log动作将消息打印到输出文件,默认情况下是 Elasticsearch 日志文件。请注意,消息的语法使用的是名为Mustache的模板语言(因其大量使用花括号而得名)。简单来说,Mustache 的双花括号中的变量将被它们的实际值替换。因此,对于我们在本章前面创建的一个示例作业,写入文件的日志文本可能如下所示:

Alert for job [alert-demo-response_code_rates] at [2021-04-08T17:00:00.000Z] score [91] 

这个警报应该看起来很熟悉,就像我们在本章前面的 Slack 消息中看到的那样——当然,因为它是从相同的信息中派生出来的。动作的电子邮件版本可能如下所示:

Elastic Stack Machine Learning Alert
    Job: alert-demo-response_code_rates
    Time: 2021-04-08T17:00:00.000Z
    Anomaly score: 91
    Click here to open in Anomaly Explorer.
    Top records:
    count() [91]

很明显,警报 HTML 格式的格式并不是围绕为用户提供信息摘要,而是通过在电子邮件中点击链接来诱使用户进一步调查。

此外,值得注意的是,前三条记录在电子邮件响应的文本中报告。在我们的例子中,只有一个记录(一个得分为 91 的count检测器)。这部分信息来自我们在监视的input部分之前描述的record_results聚合。

这个默认监视是一个很好的、可用的警报,它提供了关于数据集随时间异常性的总结信息,但了解使用它的含义也很重要:

  • 触发警报的主要条件是桶异常分数超过某个值。因此,在桶内个别异常记录的分数没有将整体桶分数提升到所声明的阈值的情况下,不会对它们发出警报。

  • 默认情况下,输出中只报告桶中前三条记录的最高分数,并且仅在电子邮件版本中。

  • 这些例子中唯一的动作是记录和电子邮件。添加其他动作(Slack 消息、webhook 等)需要手动编辑监视。

了解这些信息后,在某个时候可能需要创建一个功能更全面、更复杂的手表,以完全自定义手表的行为和输出。在下一节中,我们将讨论一些从头开始创建手表的更多示例。

定制手表可以提供一些独特的功能

对于那些感到自信并想深入了解 Watcher 的一些高级功能的人来说,让我们看看 GitHub 仓库中其他一些样本的一些亮点。这些包括同时查询多个作业的结果的示例、程序化组合异常分数,以及动态收集与时间相关联的其他异常的潜在根本原因证据。

连接输入和脚本条件

一个有趣的定制手表示例是multiple_jobs_watch.json,它展示了进行连接输入(对多个作业的结果进行多次查询)的能力,但同时也使用脚本执行更动态的条件:

  "condition" : {
    "script" : {
// return true only if the combined weighted scores are greater than 75
      "source" : "return ((ctx.payload.job1.aggregations.max_anomaly_score.value * 0.5) + (ctx.payload.job2.aggregations.max_anomaly_score.value * 0.2) + (ctx.payload.job3.aggregations.max_anomaly_score.value * 0.1)) > 75"
    }
  },

这基本上意味着只有当三个不同作业的加权异常分数总和大于 75 的值时,警报才会被触发。换句话说,并不是每个作业都被视为同等重要,并且权重考虑了这一点。

在连接输入之间传递信息

连接输入的另一个独特之处在于,从一条输入链中获得的信息可以被传递到另一条。如chained_watch.json所示,第二条和第三条输入链使用从第一次查询中学习的timestamp值作为range过滤器的一部分:

{ "range": { "timestamp": {"gte": "{{ctx.payload.job1.hits.hits.0._source.timestamp}}||-{{ctx.metadata.lookback_window}}", "lte": "{{ctx.payload.job1.hits.hits.0._source.timestamp}}"}}},

这实际上意味着该手表正在收集作为证据的异常,这些异常是从第一个作业中一个可能的重要异常之前的时间窗口中提取的。这种警报与我们在第七章**,AIOps 和根本原因分析中将要讨论的情况非常吻合,其中通过在 KPI 异常周围的时间窗口中寻找相关异常来解决一个真实的应用程序问题。因此,这个手表的样本输出可能看起来像这样:

[CRITICAL] Anomaly Alert for job it_ops_kpi: score=85.4309 at 2021-02-08 15:15:00 UTC
Possibly influenced by these other anomalous metrics (within the prior 10 minutes):
job:it_ops_network: (anomalies with at least a record score of 10):
field=In_Octets: score=11.217614808972602, value=13610.62255859375 (typical=855553.8944717721) at 2021-02-08 15:15:00 UTC
field=Out_Octets: score=17.00518, value=1.9079535783333334E8 (typical=1116062.402864764) at 2021-02-08 15:15:00 UTC
field=Out_Discards: score=72.99199, value=137.04444376627606 (typical=0.012289061361553099) at 2021-02-08 15:15:00 UTC
job:it_ops_sql: (anomalies with at least a record score of 5):
hostname=dbserver.acme.com field=SQLServer_Buffer_Manager_Page_life_expectancy: score=6.023424, value=846.0000000000005 (typical=12.609336298838242) at 2021-02-08 15:10:00 UTC
hostname=dbserver.acme.com field=SQLServer_Buffer_Manager_Buffer_cache_hit_ratio: score=8.337633, value=96.93249340057375 (typical=98.93088463835487) at 2021-02-08 15:10:00 UTC
hostname=dbserver.acme.com field=SQLServer_General_Statistics_User_Connections: score=27.97728, value=168.15000000000006 (typical=196.1486370757187) at 2021-02-08 15:10:00 UTC

在这里,管理来自每个三个有效载荷的结果的输出格式化的是一个强大的transform脚本,该脚本利用类似 Java 的Painless脚本语言。

注意

如需了解更多关于 Painless 脚本语言的信息,请参阅 Elastic 文档,网址为www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-painless.html

如果你不会被 Watcher 代码密集的格式吓倒,你可以将其用作一个非常强大的工具来实施一些非常有趣和有用的警报方案。

摘要

异常检测作业本身当然很有用,但与近乎实时的警报结合使用时,用户可以真正利用自动化分析的力量——同时也可以对只收到有意义的警报感到自信。

在对如何通过实时警报有效捕捉异常检测工作结果进行实际研究之后,我们通过一个全面的示例展示了如何使用新的 Kibana 警报框架轻松定义一些直观的警报,并在一个现实警报场景中测试了它们。然后,我们见证了如果 Kibana 警报无法满足复杂的警报需求,专家用户如何利用 Watcher 的全部功能来实现高级警报技术。

在下一章中,我们将看到异常检测工作如何不仅能够帮助警报重要的关键性能指标,而且还将展示 Elastic ML 在特定应用场景中对大量数据的自动分析是如何实现追踪应用问题并确定其根本原因的“AI”手段。

第七章:AIOps 和根本原因分析

到目前为止,我们已经广泛解释了分别检测指标和日志中异常的价值。这当然非常有价值。然而,在某些情况下,关于特定指标或日志文件出现问题的知识可能并不能完全说明正在发生的事情。例如,它可能只是指向问题的症状而不是原因。为了更好地理解一个新兴问题的全貌,通常需要从系统或情况的多个方面进行整体分析。这涉及到智能地分析多种相关数据集。

在本章中,我们将涵盖以下主题:

  • 揭秘“AIOps”术语

  • 理解 KPI 的重要性及其局限性

  • 超越 KPI

  • 为更好的分析组织数据

  • 利用上下文信息

  • 将所有内容整合进行根本原因分析(RCA)

技术要求

本章中展示的信息和示例适用于 Elastic Stack 的 v7.11 版本,并使用 GitHub 仓库中的示例数据集,该仓库位于github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition

揭秘“AIOps”术语

我们在第一章IT 机器学习中了解到,许多公司在被要求“用更少的资源做更多的事情”(更少的人,更少的成本等)的同时,正陷入不断增长的 IT 数据洪流中。其中一些数据被收集和/或存储在专用工具中,但一些可能被收集在通用数据平台中,如 Elastic Stack。但问题仍然存在:有多少比例的数据被关注?通过这种方式,我们指的是被人类积极检查或通过某种类型的自动化手段(基于规则、阈值等)监视的数据收集比例。即使是非常宽泛的估计,这个比例也可能只有个位数。因此,90%或更多的收集数据未被关注,我们错过了什么?正确的答案可能就是我们实际上并不了解。

在我们谴责 IT 组织收集大量数据却不去关注它们的罪过之前,我们需要了解与此类操作相关的挑战的规模。一个典型的面向用户的应用程序可能执行以下操作:

  • 涵盖数百个物理服务器

  • 拥有数十(如果不是数百)个微服务,每个微服务可能有数十或数百个操作指标或日志条目来描述其操作

这种组合的数学组合可以轻松达到六位数或七位数的独特测量点。此外,在 IT 组织的管理之下,可能有数十个甚至数百个这样的应用。因此,这些系统每天收集的数据量很容易以太字节来衡量。

因此,所期望的解决方案很自然地可能涉及自动化和人工智能的结合,以减轻对人类分析师的负担。某个聪明的市场营销人员想到了创造“AIops”这个术语,它封装了对问题的预期解决方案——通过一定程度的智能自动化来增强人类无法(或没有时间或能力手动完成)的事情。现在,AIops 解决方案实际上如何实现这一目标,通常留给有洞察力的用户去解释。

因此,让我们通过不关注术语本身(让我们把那留给市场营销人员)来消除这个术语的神秘感,而是明确我们希望这种智能技术为我们的情况做些什么:

  • 自动检查数据并基于自动学习的一组约束、规则和行为,评估其相关性、重要性和显著程度。

  • 过滤掉无关行为的噪音,以免分散人类分析师对真正重要的事情的注意力。

  • 获得有关可能正在酝酿但尚未导致停机的问题的某些主动早期预警。

  • 自动收集围绕问题的相关/相关证据,以协助进行根本原因分析RCA)。

  • 揭示运营中的低效率,以最大化基础设施性能。

  • 基于以往修复措施及其有效性,建议一个行动或下一步的修复步骤。

虽然这个列表远非详尽无遗,但我们已经看到了我们在这里想要表达的核心——那就是智能自动化和分析可以带来巨大的回报,并允许 IT 部门推动效率,从而最大化业务成果。

除了前面列表中第六点提到的建议修复措施(至少在目前这个阶段),Elastic 机器学习ML)可以非常重要的是实现本列表上所有其他目标的一部分。我们已经看到 Elastic ML 如何自动发现异常行为、预测趋势、主动警报等等。但我们也必须认识到 Elastic ML 是一个通用的 ML 平台——它并非专门为 IT 运营/可观察性或安全分析而构建。因此,仍然需要确定 Elastic ML 在运营环境中的使用方向,这一点将在本章中讨论。

还需要注意的是,仍有大量 IT 运维团队目前没有使用智能自动化和分析。他们经常声称他们希望采用基于 AI 的方法来改善当前状况,但他们还没有准备好采取行动。因此,让我们挑战这种观念,即从 AI 中获益的唯一方式是在第一天就做所有可能的事情。相反,让我们在 IT 运维的背景下构建一些 Elastic ML 的实际应用,以及它如何能够满足前面列出的大多数目标。

我们首先从关键 绩效 指标KPI)的概念开始,并解释为什么它是开始使用 Elastic ML 的合理选择。

理解 KPI 的重要性和局限性

由于规模问题和使收集的数据具有行动力的愿望,自然地,在主动检查中首先解决的指标是那些最能指示性能或操作的指标。IT 组织选择的用于测量、跟踪和标记的 KPI 可以跨越各种指标,包括以下内容:

  • 客户体验:这些指标衡量客户体验,例如应用程序响应时间或错误率。

  • 可用性:如正常运行时间或平均 修复 时间MTTR)等指标通常需要跟踪。

  • 业务:在这里,我们可能有直接衡量业务绩效的指标,例如每分钟的订单数或活跃用户数。

因此,这类指标通常在大多数高级操作仪表板上或从技术人员到高管的各种员工的工作报告中,以最显眼的位置展示。在谷歌图片搜索中输入“KPI 仪表板”将返回无数图表、仪表、旋钮、地图和其他视觉吸引物的示例。

虽然这种信息展示有很大的价值,只需一眼就能消费,但手动检查仍然存在基本挑战:

  • 解释:除非人类已经本能地理解了这种差异,否则在理解正常操作和异常操作之间的差异可能会有困难。

  • 规模挑战:尽管 KPI 已经是将所有指标浓缩成一组重要指标的过程,但可能仍有比在仪表板显示的屏幕空间内可行更多的 KPI 需要展示。最终结果可能是拥挤的视觉展示或需要滚动/分页的冗长仪表板。

  • 缺乏主动性:许多像这样的仪表板没有将它们的指标与警报联系起来,因此如果主动知道一个表现不佳的 KPI 很重要,就需要持续的监督。

  • 重要的是,KPIs 是识别和跟踪 IT 系统健康和行为有意义指标过程中的一个极其重要的步骤。然而,很明显,仅仅通过视觉范式识别和跟踪一组 KPIs,将会在成功的 IT 运维计划策略中留下一些重大的缺陷。

显然,KPIs 是 Elastic ML 的异常检测可以跟踪的度量标准的好候选者。例如,假设我们有一些看起来如下所示的数据(来自 GitHub 仓库中的it_ops_kpi样本数据集):

      {
        ''_index'' : ''it_ops_kpi'',
        ''_type'' : ''_doc'',
        ''_id'' : ''UqUsMngBFOh8A28xK-E3'',
        ''_score'' : 1.0,
        ''_source'' : {
          ''@timestamp'' : ''2021-01-29T05:36:09.000Z'',
          ''events_per_min'' : 28,
          ''kpi_indicator'' : ''online_purchases''
        }
      },

在这个例子中,关键绩效指标(称为events_per_min的字段)代表某些在线交易处理系统每分钟购买的汇总总数。我们可以通过在events_per_min字段上使用sum函数以及 15 分钟的桶跨度,轻松地随时间跟踪这个 KPI。检测到在线销售额(降至921)的意外下降,并将其标记为异常:

![Figure 7.1 – 使用典型异常检测作业分析的关键绩效指标

![img/B17040_07_1.jpg]

图 7.1 – 使用典型异常检测作业分析的关键绩效指标

在这个例子中,KPI 只是一个单一的、总体的指标。如果数据中存在另一个可以对其进行分割的类别字段(例如,按产品 ID、产品类别、地理区域等销售的销售额),那么机器学习可以轻松地沿着该字段分割分析,以并行方式扩展分析(如我们在第三章异常检测)中看到的那样)。但让我们不要忽视我们在这里所取得的成果:对某人可能关心的关键指标的主动分析。单位时间内的在线销售额直接与收入相关,因此是一个明显的 KPI。

然而,尽管了解我们的 KPI 出现异常情况的重要性,但我们仍然没有关于为什么它发生的原因。是否支持该面向客户的应用的某个后端系统存在操作问题?是否在最新版本中存在用户界面编码错误,使得用户完成交易更困难?是否依赖于第三方支付处理提供商存在问题?仅通过审查 KPI 无法回答这些问题。

要获得这种洞察力,我们需要扩大我们的分析范围,包括其他相关和相关的信息集。

超越关键绩效指标(KPIs)

通常,选择 KPI 的过程应该是相对简单的,因为很可能很明显哪些指标是最好的指标(如果在线销售额下降,那么应用程序可能没有正常工作)。但如果我们想更全面地了解可能对运营问题有贡献的因素,我们必须将我们的分析扩展到支持应用程序的底层系统和技术的指标之外。

幸运的是,有大量的方法可以收集各种数据并将其集中到 Elastic Stack 中。例如,Elastic Agent是一个单一的、统一的代理,您可以将其部署到主机或容器中,以收集数据并将其发送到 Elastic Stack。在幕后,Elastic Agent 运行所需的 Beats shippers 或 Elastic Endpoint。从版本 7.11 开始,Elastic Agent 在 Kibana 的Fleet用户界面中进行管理,并可用于添加和管理流行服务和平台的集成:

图 7.2 – Kibana 中 Fleet 用户界面的集成部分

图 7.2 – Kibana 中 Fleet 用户界面的集成部分

使用这些不同的集成,用户可以轻松收集数据并将其集中到 Elastic Stack 中。虽然本章的目的不是介绍 Fleet 和 Elastic Agent,但重要的是,无论您使用什么工具来收集底层应用程序和系统数据,有一点很可能是真的:最终会有大量的数据。记住,我们的最终目标是主动和全面地关注更大比例的整体数据集。为了做到这一点,我们必须首先组织这些数据,以便我们可以使用 Elastic ML 有效地分析它。

组织数据以进行更好的分析

通过 Elastic Agent 摄取数据的一个非常好的特点是默认情况下,收集的数据使用Elastic Common SchemaECS)进行标准化。ECS 是一个开源规范,它定义了存储在 Elastic Stack 中的数据的一个通用分类法和命名约定。因此,数据变得更容易管理、分析、可视化和跨不同数据类型(包括性能指标和日志文件)进行关联。

即使您没有使用 Elastic Agent 或其他遗留的 Elastic 摄取工具(如 Beats 和 Logstash),而是依赖其他第三方数据收集或摄取管道,仍然建议您将数据符合 ECS,因为当用户期望使用这些数据进行查询、仪表板和当然,机器学习作业时,这将带来巨大的回报。

注意

在网站的参考部分可以找到有关 ECS 的更多信息,请参阅www.elastic.co/guide/en/ecs/current/ecs-reference.html

在 ECS 中的许多重要字段中,host.name字段定义了数据是从哪个宿主机收集的。默认情况下,Elastic Stack 中的大多数数据收集策略都涉及将数据放入以数据类型为中心的索引中,因此可能包含来自许多不同宿主机的交错文档。也许我们环境中的一些宿主机支持一个应用程序(即在线购物),而其他宿主机支持不同的应用程序(如发票处理)。如果所有宿主机都将数据报告到单个索引中,如果我们对针对一个或两个应用程序的数据报告和分析感兴趣,显然仅基于索引进行分析是不合适的——我们需要我们的分析以应用程序为中心。

为了实现这一点,我们有几种选择:

  • 修改异常检测作业的基本查询,以便仅过滤与感兴趣的应用程序相关的宿主机的数据

  • 在摄取数据时修改数据以丰富它,向每个文档中插入额外的上下文信息,这些信息将随后被用于过滤异常检测作业发出的查询

这两种方法都需要对异常检测作业对源索引中的原始数据进行的数据流查询进行定制。第一种方法可能会导致查询相对复杂,第二种方法则需要使用自定义摄取管道进行数据丰富化的中间步骤。让我们简要讨论一下每种方法。

用于异常检测数据流的自定义查询

在异常检测 UI 中创建新作业时,第一步是选择索引模式或 Kibana 保存的搜索。如果选择前者,则将调用一个{''match_all'':{}} Elasticsearch 查询(返回索引中的每条记录)。如果通过 API 或高级作业向导创建作业,则用户可以指定几乎任何有效的 Elasticsearch DSL 来过滤数据。对于非专家用户来说,自由组合 Elasticsearch DSL 可能会有些容易出错。因此,更直观的方法是从 Kibana 通过保存的搜索来处理这个问题。

例如,假设我们有一个日志文件索引,我们想要监控和分析的应用程序相关的适当宿主机由两个服务器组成,esxserver1.acme.comesxserver2.acme.com。在 Kibana 的发现页面上,我们可以使用用户界面顶部的搜索框构建一个使用KQL的过滤查询:

图 7.3 – 使用 KQL 构建过滤查询

图 7.3 – 使用 KQL 构建过滤查询

此 KQL 查询的文本如下:

physicalhost_name:''esxserver1.acme.com'' or physicalhost_name:''esxserver2.acme.com''

如果你对 Kibana 调用的实际 Elasticsearch DSL 以获取此过滤查询感到好奇,可以点击右上角的检查按钮并选择请求选项卡以查看 Elasticsearch DSL:

图 7.4 – 检查为 KQL 过滤器运行的 Elasticsearch DSL

图 7.4 – 检查为 KQL 过滤器运行的 Elasticsearch DSL

可能值得注意,尽管在这个特定示例中 KQL 查询被转换成 Elasticsearch DSL 的方式(例如使用match_phrase),但这并不是实现预期结果唯一的方式。使用terms的查询过滤器是另一种方式,但评估这两种方式的优劣超出了本书的范围。

不论后台运行的 Elasticsearch DSL 是什么,关键是我们有一个查询,它过滤原始数据,仅识别我们希望使用 Elastic ML 进行分析的应用程序感兴趣的服务器。为了保持这个过滤搜索,需要在右上角点击保存按钮并命名搜索:

图 7.5 – 在 Elastic ML 中保存搜索以供以后使用

图 7.5 – 在 Elastic ML 中保存搜索以供以后使用

之后,您可以在配置新的异常检测作业时选择此保存的搜索:

图 7.6 – 在异常检测作业中使用保存的搜索

图 7.6 – 在异常检测作业中使用保存的搜索

因此,我们的 ML 作业现在将仅针对此特定应用程序感兴趣的主机运行。因此,我们已经能够有效地限制和分割数据分析,仅限于我们定义的为该应用程序做出贡献的主机。

数据摄取时的数据丰富

另一个选项是将关于哪些主机属于哪些应用的决策进一步上移到数据摄取的时间。如果 Logstash 是摄取管道的一部分,您可以使用过滤器插件根据资产列表(文件、数据库等)的查找来向数据添加额外的字段。请参阅 Logstash 文档www.elastic.co/guide/en/logstash/current/lookup-enrichment.html,其中展示了如何使用额外字段动态丰富索引文档以提供上下文。如果您没有使用 Logstash(仅使用 Beats/Elastic Agent 和摄取节点),可能更简单的方法是使用 enrich 处理器。请参阅www.elastic.co/guide/en/elasticsearch/reference/current/ingest-enriching-data.html文档。

例如,您可以让这种丰富添加一个application_name字段,并动态填充该字段的值,例如以下内容(这里截断的 JSON):

''host'': ''wasinv2.acme.com'',
      ''application_name'': ''invoice_processing'',

或者您还可以有如下内容:

      ''host'': ''www3.acme.com'',
      ''application_name'': ''online_purchases'',

一旦设置了该字段的值并将其插入到索引文档中,然后您就可以使用application_name字段,结合之前描述的过滤查询异常检测作业的能力,将数据分析限制在感兴趣的相关应用程序上。添加数据丰富步骤可能看起来需要更多的前期努力,但从长远来看,它应该会带来回报,因为当资产名称更改或演变时,这将更容易维护,因为第一种方法需要在机器学习作业的搜索中硬编码资产名称。

现在我们已经组织了我们的数据,也许甚至丰富了它,那么现在让我们看看我们如何可以利用这些上下文信息来使我们的异常检测作业更有效。

利用上下文信息

在我们的数据组织/丰富后,我们可以利用上下文信息的两种主要方式是通过分析拆分和统计影响因素

分析拆分

我们已经看到,异常检测作业可以根据任何分类字段进行拆分。因此,我们可以为该字段的每个实例单独建模行为。这可能会非常有价值,尤其是在每个实例需要其自己的单独模型的情况下。

以我们拥有世界不同地区的数据为例:

图 7.7 – 根据区域不同的数据行为

图 7.7 – 根据区域不同的数据行为

图 7.7 – 根据区域不同的数据行为

无论这些数据是什么(销售 KPI、利用率指标等),显然它们具有非常独特的模式,这些模式是每个区域独有的。在这种情况下,将任何分析拆分到每个区域进行异常检测以利用这种独特性是有意义的。我们将能够检测到特定于每个区域的行为异常。

让我们也想象一下,在每一个区域内,一组服务器支持应用程序和事务处理,但它们是负载均衡的,并且对性能/操作的贡献是相等的。这样,每个服务器对区域的贡献就没有什么独特之处。因此,按服务器进行分析可能没有意义。

我们自然得出结论,按区域拆分比按服务器拆分更有效。但如果区域内的某个服务器在向被检测到的异常贡献方面存在问题呢?我们难道不想立即获得这些信息,而不是手动进一步诊断吗?这是通过影响因素可以做到的。

统计影响因素

我们在第五章“解释结果”中介绍了影响因素的概念。作为提醒,影响因素是一个字段,它描述了一个实体,你想要知道它是否“影响”(是异常的原因)或至少对异常有重大贡献。记住,任何被选为候选影响因素的字段不需要是检测逻辑的一部分,尽管选择用作拆分的字段作为影响因素是自然的。同样重要的是,在创建异常检测作业时选择影响因素,因为以后无法将其添加到配置中。

还需要理解的是,在异常检测作业发现异常之后,才会进行寻找潜在影响因素的过程。换句话说,它不会影响检测过程中所做的任何概率计算。一旦确定了异常,机器学习将系统地遍历每个候选影响因素字段的每个实例,并从该时间桶中的数据中移除该实例的贡献。如果移除后,剩余的数据不再异常,那么通过反事实推理,该实例的贡献必须是具有影响力的,并且会相应地进行评分(结果中的influencer_score值)。

然而,在下一节中,我们将看到如何利用影响者在查看单个异常检测作业的结果时,以及可能的相关作业的结果。现在,让我们继续讨论如何将作业分组并一起查看,以协助根本原因分析。

将所有内容整合进行根本原因分析

现在,我们已经到了可以讨论如何将所有事情整合在一起的程度。在我们希望提高 IT 运营效率并更全面地查看应用程序健康状态的需求下,我们现在需要将之前章节中准备的内容进行操作化,并相应地配置我们的异常检测作业。为此,让我们通过一个真实场景来探讨,在这个场景中,Elastic ML 帮助我们找到了运营问题的根本原因。

故障背景

这个场景大致基于一个真实的应用程序故障,尽管数据已经被简化并清洗过,以掩盖原始客户的信息。问题出在一个处理礼品卡交易的零售应用程序上。偶尔,该应用程序会停止工作,交易无法处理。这种情况只有在各个门店向总部投诉时才会被发现。问题的根本原因未知,客户无法轻易确定。由于他们从未找到根本原因,而且只需重新启动应用程序服务器即可解决问题,因此问题会随机复发,困扰了他们数月。

收集并包含在分析中的以下数据有助于理解问题的起源。这些数据包括以下内容(并在 GitHub 仓库中提供):

  • 交易量的总结(1 分钟计数)(主要 KPI)

  • 来自交易处理引擎的应用程序日志(基于半结构化文本的消息)

  • 来自支持交易处理引擎的数据库的 SQL Server 性能指标

  • 来自交易处理引擎运行的网络的网络利用率性能指标

因此,针对数据配置了四个机器学习(ML)任务。它们如下所示:

  • 每分钟处理交易数量的sum

  • 使用mlcategory检测器按类型计数日志消息的数量,但使用基于动态机器学习的分类来区分不同的消息类型

  • 指数中每个 SQL Server 指标的mean分析

  • 指数中每个网络性能指标的mean分析

当应用程序出现问题时,这四个作业在数据上进行了配置和运行。发现了异常,尤其是在跟踪正在处理的交易数量的 KPI 中。实际上,这正是我们在本章开头看到的 KPI,其中订单处理的意外下降是问题发生的首要指标:

图 7.8 – 处理的交易数量关键绩效指标(KPI)

图 7.8 – 处理的交易数量关键绩效指标(KPI)

然而,直到这个 KPI 的异常与查看底层技术和基础设施数据的其他三个 ML 任务的异常相关联,才理解了根本原因。让我们看看视觉关联和共享影响因素的力量是如何帮助发现根本原因的。

相关性和共享影响因素

除了处理交易 KPI 中的异常(其中出现意外的下降)之外,其他三个异常检测作业(针对网络指标、应用程序日志和 SQL 数据库指标)被叠加到异常探索器的相同时间框架中。以下截图显示了这些结果:

图 7.9 – 异常探索器显示多个作业的结果

图 7.9 – 异常探索器显示多个作业的结果

特别注意,KPI 出现问题的那天(2021 年 2 月 8 日,如图 7.8 所示),图 7.9 中的其他三个作业表现出相关的异常,如图中圈出的区域所示。通过点击it_ops_sql作业的红色瓷砖进行更仔细的检查,可以看到有几个 SQL Server 指标同时出现混乱:

图 7.10 – 异常探索器显示 SQL Server 的异常

图 7.10 – 异常探索器显示 SQL Server 的异常

备注

图表中的阴影区域突出了与泳道中选定瓷砖宽度相关的时间窗口。这个时间窗口可能比分析的桶跨度要大(正如这里的情况),因此阴影区域可能包含该时间段内的许多单个异常。

如果我们查看应用程序日志异常检测作业中的异常,会发现大量错误都指向数据库(进一步证实了 SQL 服务器不稳定):

图 7.11 – 异常探索器显示应用程序日志的异常

图 7.11 – 异常探索器显示应用程序日志的异常

然而,网络上也发生了有趣的事情:

图 7.12 – 异常探索器显示网络数据的异常

图 7.12 – 异常探索器显示网络数据的异常

具体来说,网络流量(通过Out_Octets指标显示)出现了大幅上升,并且网络接口处丢弃的数据包数量也急剧增加(通过Out_Discards指标显示)。

到这个时候,很明显怀疑这个网络峰值可能与数据库问题有关。虽然相关性不总是因果关系,但这足以吸引运维团队回顾一些历史数据,以了解之前的故障情况。在其他每次故障中,都存在这种大的网络峰值和丢包模式。

网络峰值的最根本原因是 VMware 将虚拟机移动到新的 ESX 服务器。有人错误配置了网络交换机,VMware 将这股巨大的流量发送到应用程序 VLAN 而不是管理 VLAN。当然,当这种情况发生时(随机发生),事务处理应用程序会暂时失去与数据库的连接并尝试重新连接。然而,在这个重新连接代码中存在一个关键缺陷,即它不会尝试连接到属于 SQL 服务器的远程 IP 地址。相反,它尝试连接到本地主机(IP 地址127.0.01),当然,那里没有这样的数据库。这个错误的线索在 Elastic ML 在示例部分显示的一个示例日志行中可以看到(以下截图中的圆圈所示):

图 7.13 – 异常探索器显示重新连接问题的根本原因

图 7.13 – 异常探索器显示重新连接问题的根本原因

一旦问题发生,只有当应用程序服务器完全重启、重新读取启动配置文件以及重新学习 SQL 服务器的 IP 地址时,才能连接到 SQL 服务器。这就是为什么完整的重启总是能解决问题。

一个需要注意的关键点是用户界面中的影响因素如何帮助缩小异常责任人的范围:

![图 7.14 – 异常探索器显示顶级影响因素图 B17040_07_14.jpg

图 7.14 – 异常探索器显示顶级影响因素

在仪表板上选择的时间范围内得分最高的影响因素列在左侧的顶级影响因素部分。对于每个影响因素,显示最大影响因素得分(在任何桶中),以及仪表板时间范围内的总影响因素得分(跨所有桶求和)。如果同时显示多个作业,那么在作业中共同的那些影响因素具有更高的总和,从而推动它们的排名更高。

这是一个非常重要的观点,因为现在很容易在多个作业中看到违规实体之间的共性。如果 esxserver1.acme.com 是在查看多个作业时作为影响因素出现的唯一物理主机,那么我们立即知道应该关注哪台机器;我们知道这不是一个普遍的问题。

最后,客户通过纠正网络配置错误和解决数据库重连代码中的错误,成功地缓解了系统。他们能够迅速缩小根本原因,因为 Elastic ML 允许他们缩小调查范围,从而节省时间并防止未来发生。

摘要

Elastic ML 确实可以增加 IT 组织关注的数据量,从而从他们的数据中获得更多洞察和主动价值。能够组织、关联并全面查看跨数据类型的相关异常对于问题隔离和根本原因识别至关重要。它减少了应用程序的停机时间,并限制了问题再次发生的可能性。

在下一章中,我们将看到 Elastic Stack(APM、安全和日志)中的其他应用程序如何利用 Elastic ML 提供即插即用的体验,该体验针对特定用例进行了定制。

第八章:其他 Elastic Stack 应用中的异常检测

当本书的第一版两年前编写时,堆栈中还没有其他应用利用 Elastic ML 进行特定领域解决方案的概念。然而,自那时以来,Elastic ML 已经成为特定领域解决方案的异常检测提供者,提供用户只需一键即可启用的定制作业配置。

在本章中,我们将探讨 Elastic ML 为各种 Elastic Stack 应用带来的好处:

  • Elastic APM 中的异常检测

  • 日志应用中的异常检测

  • 指标应用中的异常检测

  • Uptime 应用中的异常检测

  • Elastic Security 应用中的异常检测

技术要求

本章信息适用于 Elastic Stack 的 v7.12 版本。

Elastic APM 中的异常检测

Elastic APM 通过允许用户对其应用程序代码进行仪器化,以获取对单个微服务和事务性能的深入了解,将应用程序监控和性能管理提升到了全新的水平。在复杂环境中,这可能会生成大量测量数据,并可能导致一种潜在的矛盾情况——在这种详细测量水平上获得更高的可观察性,同时可能使分析师难以从结果中筛选出可操作的见解。

幸运的是,Elastic APM 和 Elastic ML 是天作之合。异常检测不仅可以通过无监督机器学习自动适应每种事务类型的独特性能特征,而且可以扩展以处理 APM 可能生成的庞大数据量。

虽然用户始终可以自由地针对任何索引中的任何类型的时间序列数据创建异常检测作业,无论类型如何,但有一个强有力的论据是,简单地提供针对 Elastic APM 数据的预制、开箱即用的作业配置,因为数据格式已经为人所知。

启用 APM 的异常检测

为了利用 APM 数据进行异常检测,显然需要收集一些声明的服务的 APM 数据,并且收集到的数据需要存储在可以通过 apm-* 索引模式访问的索引中:

  1. 如果您尚未在 APM 数据上设置异常检测,您将在屏幕顶部看到一个指示器,告知您它仍然需要设置:![图 8.1 – 当异常检测尚未在 APM 上启用时的指示器

    ![img/B17040_08_1.jpg]

    图 8.1 – 当异常检测尚未在 APM 上启用时的指示器

  2. 为了展示异常检测配置将如何,创建了一个简单的Hello World Node.js 应用程序,并使用 Elastic APM 进行了配置。此应用程序(称为myapp)还标记了environment标签为dev,以表示该应用程序是开发应用程序(所有这些都是在 Node.js 配置的 APM 代理内完成的):图 8.2 – 使用 Elastic APM 进行配置的示例 Node.js 应用

    图 8.2 – 使用 Elastic APM 进行配置的示例 Node.js 应用

  3. 在 Elastic APM 内部查看时,服务将如下所示:图 8.3 – 在 Elastic APM UI 中查看的示例 Node.js 应用

    图 8.3 – 在 Elastic APM UI 中查看的示例 Node.js 应用

  4. 要启用此服务的异常检测,只需转到设置,点击异常检测,然后点击创建 ML 作业按钮:图 8.4 – 为 APM 数据创建 ML 作业

    图 8.4 – 为 APM 数据创建 ML 作业

  5. 接下来,指定您想要为该作业构建的环境名称:图 8.5 – 指定构建 ML 作业的环境

    图 8.5 – 指定构建 ML 作业的环境

  6. 在这里,我们将选择dev,因为它是我们应用唯一可用的选项。一旦选择并点击创建作业按钮,我们将收到确认我们的作业已创建,并在表中列出:图 8.6 – APM 中创建的异常检测作业列表

    图 8.6 – APM 中创建的异常检测作业列表

  7. 如果我们转到 Elastic ML 应用,查看为我们创建的作业的详细信息,我们将看到以下内容:图 8.7 – ML 中创建的异常检测作业列表

    图 8.7 – ML 中创建的异常检测作业列表

  8. 进一步检查,我们可以看到作业的实际检测器配置如下:图 8.8 – APM 作业的检测器配置

    图 8.8 – APM 作业的检测器配置

    注意,这利用了transaction.duration.us字段上的high_mean函数,同时在transaction.type上分割,并在service.name上分区。同时注意,作业的bucket_span值为 15 分钟,这可能或可能不是您环境的理想设置。

    注意

    应该注意的是,当使用 APM UI 的此一键方法时,此配置是唯一可能的,因为配置是硬编码的。如果您想自定义或创建自己的配置,您可以从头创建它们,或者可能克隆此作业并设置自己的桶跨度以及/或检测逻辑。

  9. 从查询的数据视角来看,我们可以点击我们在 APM UI 设置作业时指示的"service.environment" : "dev"

  10. 我们可以点击数据源预览选项卡来查看将馈送到 Elastic ML 的此特定作业的观测样本,如图所示:

图 8.10 – APM 作业的数据源预览

图 8.10 – APM 作业的数据源预览

现在 APM 作业已配置并运行,让我们关注在 APM UI 中异常检测作业结果将如何反映。

在 APM UI 中查看异常检测作业结果

除了在 Elastic ML UI 中查看作业结果外,还有三个关键位置在 APM UI 中显示了异常检测作业的结果:

  • 服务概览:APM UI 中的此视图提供了一个高级健康指标,对应于特定服务的异常检测结果的最高异常分数:

图 8.11 – APM UI 中的服务概览

图 8.11 – APM UI 中的服务概览

  • 服务图:这个动态视图显示了应用程序事务依赖关系,基于特定服务的异常检测结果的最高异常分数进行颜色编码的异常指示器:

图 8.12 – APM UI 中的服务图

图 8.12 – APM UI 中的服务图

  • 事务持续时间图表:此图表位于 APM 中的主要事务视图下,当特定事务类型的异常分数超过 75 时,显示预期的边界和彩色注释:

图 8.13 – APM UI 中的事务持续时间图表

图 8.13 – APM UI 中的事务持续时间图表

这些异常性指示器有助于用户进一步调查并使用 APM 和 ML UI 的深度功能来解决问题。让我们看看一种将 Elastic ML 与 APM 数据集成的更多方法,使用 数据识别器

通过数据识别器创建 ML 作业

虽然“数据识别器”不是 Elastic ML 中实际功能的官方营销名称,但它实际上在帮助用户为已识别的数据创建预配置作业时非常有帮助。

注意

预配置的数据识别器作业定义和存储在以下 GitHub 仓库中:github.com/elastic/kibana/tree/master/x-pack/plugins/ml/server/models/data_recognizer/modules

实际上,使用识别器创建新作业的工作流程如下:

如果在创建异常检测作业的过程中,为作业输入(索引模式或保存的搜索)选择的数据与预定义识别模块之一已知搜索模式匹配,那么你可以向用户提供创建这些预定义作业之一的能力。我们可以通过使用本章早期提到的简单 Node.js 示例来查看这个示例。我们不是从 APM UI 创建异常检测作业,而是通过 Elastic ML UI 选择apm-*索引模式:

  • 通过选择索引模式,我们将看到除了正常作业向导外,我们还提供了两个预配置的作业:

![Figure 8.14 – 数据识别器提供的职位

![img/B17040_08_14..jpg]

图 8.14 – 数据识别器提供的职位

  • 第一个“APM”作业与我们本章早期从 APM UI 创建的作业类型相同。第二个选项(APMNode.js)实际上是一个包含三个作业的集合:

![Figure 8.15 – 数据识别器提供的 Node.js 作业

![img/B17040_08_15..jpg]

图 8.15 – 数据识别器提供的 Node.js 作业

  • 第三个是,再次,与我们本章早期从 APM UI 创建的相同类型的职位,但其他两个是独特的。如果源数据被“识别”,向用户提供建议性职位这一概念并不仅限于 APM 数据,你可能在其他情况或用例(如选择样本 Kibana 数据的索引、nginx 数据等)中看到这些建议性职位。

现在我们已经看到了 Elastic ML 是如何嵌入到 APM 应用中的,让我们继续探索,以了解异常检测是如何在日志应用中被利用的。

日志应用中的异常检测

Kibana 中的可观察性部分内的日志应用提供了与 Discover 应用类似的数据视图。然而,对于那些更喜欢查看日志的实时尾部视图的用户,无论数据存储在哪个索引中,都会喜欢日志应用:

![Figure 8.16 – Kibana 的可观察性部分中的日志应用

![img/B17040_08_16..jpg]

图 8.16 – Kibana 的可观察性部分中的日志应用

注意,这里既有异常标签页,也有类别标签页。让我们首先讨论类别部分。

日志类别

Elastic ML 的分类能力,最早在第三章**,异常检测中展示,以通用方式应用于任何非结构化日志数据的索引。然而,在日志应用中,分类使用了一些对数据更严格的约束。简而言之,数据预期位于event.dataset中)。

注意

来自第七章**,AIOps 和根本原因分析的日志数据集在此章节的 GitHub 仓库中进行了复制,用于与 Logs 应用一起使用,并增加了event.dataset字段。如果您通过 ML 中的文件上传功能导入,请确保将字段名称覆盖为event.dataset,而不是默认的event_dataset

可以想象出这种限制背后的原因,因为 Logs 应用正在尝试以模板化的方式为您创建分类作业。因此,它需要确定字段的命名约定。如果这要在 ML 应用中完成,那么情况显然不会是这样,在那里用户负责声明分类字段和消息字段的名称。

如果您配置 Logs 应用以调用分类,那么输出将类似于以下图示,它显示了每个不同的日志类别,按最大异常分数排序:

图 8.17 – Logs 应用显示 Elastic ML 的分类结果

图 8.17 – Logs 应用显示 Elastic ML 的分类结果

用户可以点击在 ML 中分析按钮,导航到 ML UI 中的异常检测器进行进一步检查。

日志异常

Logs 应用的异常部分提供了一个类似于异常检测器的视图:

图 8.18 – Logs 应用显示类似于 ML 异常检测器的视图

图 8.18 – Logs 应用显示类似于 ML 异常检测器的视图

如果用户点击管理 ML 作业按钮,它还允许用户管理异常检测作业:

图 8.19 – Logs 应用允许管理 ML 作业

图 8.19 – Logs 应用允许管理 ML 作业

count检测器,按event.dataset字段分区。

用户应注意,这些 ML 作业可以在此处重新创建,但不能永久删除 – 您必须转到 ML 应用中的异常检测作业管理页面来删除作业。

显然,一个高级用户可能会仅仅在 ML 应用中创建和管理他们的日志异常检测作业。但很棒的是,Logs 应用以一种使功能明显且易于实施的方式展示了 Elastic ML 的能力。让我们继续这一趋势,看看 Elastic ML 在 Metrics 应用中的使用情况。

Metrics 应用中的异常检测

Metrics 应用也是 Kibana 中可观察性部分的组成部分,它允许用户以库存和指标驱动的视图查看他们的数据:

  • 库存视图中,用户可以看到监控资源的整体地图。实体,如主机、Pod 或容器,可以组织并筛选以自定义视图 – 包括以下图示中的颜色编码健康量表:

图 8.20 – Metrics 应用显示库存视图

图 8.20 – 指标应用显示库存视图

注意,如果检测到异常,底部面板将显示异常。这是目前在指标应用中查看异常的唯一位置。

  • 要通过指标应用启用内置的异常检测作业,请点击顶部的异常检测按钮以启用配置飞出窗口:

![图 8.21 – 指标应用显示异常检测作业的管理

![img/B17040_08_21..jpg]

图 8.21 – 指标应用显示异常检测作业的管理

  • 例如,如果我们选择为主机启用异常检测,点击相应的启用按钮将显示此内容:

![图 8.22 – 指标应用显示主机上的异常检测配置

![img/B17040_08_22..jpg]

图 8.22 – 指标应用显示主机上的异常检测配置

  • 注意,如果需要,配置中会提供分区字段。当你点击启用作业按钮时,会为你创建三个不同的异常检测作业,你可以在 Elastic ML 的作业管理部分查看:

![图 8.23 – 指标应用创建的主机异常检测作业

![img/B17040_08_23..jpg]

图 8.23 – 指标应用创建的主机异常检测作业

  • 要设置指标应用显示异常所需的最小异常分数,请转到设置选项卡并配置异常严重程度阈值设置:

![图 8.24 – 指标应用中的异常严重程度阈值设置

![img/B17040_08_24..jpg]

图 8.24 – 指标应用中的异常严重程度阈值设置

Elastic ML 与指标应用的集成非常简单直接 – 它允许用户快速开始使用异常检测来处理他们的指标数据。现在让我们快速看一下 Uptime 应用中的集成。

Uptime 应用中的异常检测

Uptime 应用允许通过多种网络协议(包括HTTP/STCPICMP)对服务的简单可用性和响应时间进行监控:

  1. 通常被归类为合成监控,Uptime 应用使用心跳从一个或多个位置主动探测网络端点:![图 8.25 – Kibana 中的 Uptime 应用

    ![img/B17040_08_25..jpg]

    图 8.25 – Kibana 中的 Uptime 应用

  2. 如果你想要在监控器上启用异常检测,只需点击监控器名称以查看监控器详细信息。在监控持续时间面板中,注意启用异常检测按钮:![图 8.26 – 启用 Uptime 监控器的异常检测

    ![img/B17040_08_26..jpg]

    图 8.26 – 启用 Uptime 监控器的异常检测

  3. 点击启用异常检测按钮将在后台创建作业,并允许用户为作业中出现的异常创建警报:![图 8.27 – 在 Uptime 应用中创建异常检测作业的警报

    ![img/B17040_08_27..jpg]

    图 8.27 – 在 Uptime 应用中创建异常检测作业的警报

  4. 一旦异常检测作业可用,发现的任何异常也将显示在监控器的详细页面中的监控持续时间面板内:

![图 8.28 – Uptime 应用中显示的异常]

图片

图 8.28 – Uptime 应用中显示的异常

再次强调,Elastic ML 与堆栈中另一个可观察性应用的集成使得用户利用复杂的异常检测变得极其简单。但我们也知道,Elastic ML 在人口和罕见分析方面也能做一些有趣的事情。下一节将介绍 ML 与 Elastic SIEM 的集成——让我们开始检测吧!

Elastic Security 应用中的异常检测

Elastic Security 确实是 Elastic Stack 中一个以目标为导向的应用的精髓。从头开始创建,考虑到安全分析师的工作流程,Elastic Security 应用的全面性足以填满一本整本书。然而,Elastic Security 应用的核心是检测功能,其中用户和 Elastic 创建的规则在满足规则条件时执行以创建警报。正如我们将看到的,Elastic ML 在检测功能中发挥着重要作用。

预构建的异常检测作业

Elastic Security 中的大多数检测规则是静态的,但许多规则背后是由预构建的异常检测作业支持的,这些作业在 Elastic Agent 或 Beats 收集的数据上运行,或者符合适用于每种作业类型的应用 ECS 字段的数据。要查看 Elastic 提供的异常检测作业的完整列表,请查看以下 GitHub 仓库中的数据源和作业配置定义:github.com/elastic/kibana/tree/7.12/x-pack/plugins/ml/server/models/data_recognizer/modules

(您可以在目前占据 7.12 版本号的空白处添加最新发布版本号。)

精明的读者会注意到,许多预构建的作业利用了人口分析或罕见检测器。这些异常检测风格与安全分析师的目标非常一致——找到新颖的行为和/或使用户或实体与众不同的行为,通常与入侵指标有关。

预构建的异常检测作业可在 Elastic Security 的检测标签页中查看,并带有 ML 标签:

![图 8.29 – 安全应用检测规则部分的 ML 作业]

图片

图 8.29 – 安全应用检测规则部分的 ML 作业

点击屏幕右上角的ML 作业设置将显示设置列表,用户可以查看库中的所有作业 – 即使是那些不可用的作业(用警告图标标记):

![图 8.30 – 安全应用中 ML 作业设置部分的全部作业img/B17040_08_30..jpg

图 8.30 – 安全应用中 ML 作业设置部分的全部作业

如果作业所需的数据尚未在 Elasticsearch 中索引,作业将被标记为不可用。如果作业可用,您可以通过点击切换开关来激活作业,异常检测作业将在后台配置。当然,您始终可以查看在 Elastic ML 作业 管理 UI 中创建的作业:

![图 8.31 – 安全作为作业管理 UI 中的视图创建的 Elastic ML 作业img/B17040_08_31..jpg

图 8.31 – 安全作为作业管理 UI 中的视图创建的 Elastic ML 作业

现在我们知道了如何启用异常检测作业,是时候看看它们如何为安全应用创建检测警报了。

异常检测作业作为检测警报

当我们回到图 8.28中显示的检测视图时,如果您点击创建新规则按钮,我们可以看到我们可以选择机器学习作为规则类型:

![图 8.32 – 创建基于 ML 的检测规则img/B17040_08_32..jpg

图 8.32 – 创建基于 ML 的检测规则

如果您要通过下拉列表选择特定的机器学习作业,您还将被要求选择需要超过以触发检测警报的异常分数阈值值:

![图 8.33 – 选择检测规则的 ML 作业和分数阈值img/B17040_08_33.jpg

图 8.33 – 选择检测规则的 ML 作业和分数阈值

显然,如果作业当前没有运行,警告将指示需要启动作业。值得注意的是,如果您创建了自己的自定义作业(即,不是使用预构建的作业),但在 Elastic ML 应用中将它分配给名为“安全”的作业组,那么这个自定义作业也将是此视图中下拉框中可选的候选之一。检测规则配置的其余部分可以留给读者 – 因为其余部分并不特定于机器学习。

Elastic Security 应用显然严重依赖异常检测来增强传统的静态规则,通过动态的用户/实体行为分析揭示值得调查的显著事件。随着时间的推移,将很有趣地看到机器学习功能如何继续加强这一新兴用例,以及其影响的深度和广度!

摘要

Elastic ML 显然已经渗透到 Elastic Stack 中的许多其他应用中,将易于使用的功能带到了用户的手指之间。这证明了 Elastic ML 确实是堆栈本身的核心功能,类似于其他关键堆栈功能,如聚合。

恭喜你,你已经到达了这本书第一部分的终点,希望你现在感觉武装到了牙齿,拥有了关于 Elastic ML 异常检测所需的所有知识。

现在,我们将探索 Elastic ML 的“另一面”——数据帧分析——在这里,你将学习如何将其他机器学习技术(包括基于监督的模型创建和推理)应用到广泛的用例中,以开启新的分析解决方案。

第三部分 – 数据帧分析

本节介绍了数据帧分析的基础知识,它的用途以及如何实现它。它涵盖了各种分析类型以及它们之间的区别。最后,本节还介绍了一些有用的技巧,可以帮助您充分利用 Elastic ML。

本节涵盖了以下主题:

  • 第九章介绍数据帧分析

  • 第十章离群值检测

  • 第十一章分类分析

  • 第十二章回归分析

  • 第十三章推理

  • 附录**:异常检测技巧

第九章:介绍数据框分析

在本书的第一部分,我们深入探讨了异常检测,这是第一个直接集成到 Elastic Stack 中的机器学习功能。在本章和下一章中,我们将深入研究集成到堆栈中的新机器学习功能。这些包括异常检测,一种用于检测非时间序列索引中异常数据点的创新无监督学习技术,以及两个监督学习功能,分类和回归。

监督学习算法使用标记数据集——例如,描述组织样本各个方面以及组织是否为恶性的数据集——来学习模型。然后,该模型可以用于对先前未见过的数据点(或组织样本,继续我们的例子)进行预测。当预测目标是离散变量或类别,如恶性或非恶性组织样本时,监督学习技术被称为分类。当目标是连续的数值变量,如公寓的销售价格或电力的每小时价格时,监督学习技术被称为回归。这三个新的机器学习功能统称为数据框分析。我们将在接下来的章节中更深入地讨论这些内容。

虽然每个功能都解决不同的问题,具有不同的目的,但它们在底层都由一个共同的数据转换技术驱动,即转换技术,它使我们能够将基于事务或流的数据格式转换为基于实体的格式。这种以实体为中心的格式是我们用于数据框分析的许多算法所必需的,因此,在我们深入研究每个新的机器学习功能之前,我们将在本章中深入探讨如何使用转换将我们的数据转换为更适合下游机器学习技术的格式。在这个过程中,我们还将简要介绍嵌入到 Elasticsearch 中的脚本语言 Painless,这对于在 Elastic Stack 中使用机器学习的任何数据科学家或工程师来说都是一款好工具。

在 Elastic Stack 之外,也存在一个丰富的库生态系统,这些库既适用于数据处理,也适用于机器学习。推动这些应用的主要动力之一是 Python。由于其在大数据和数据工程社区中的普遍性,我们在本章的第二部分将专注于使用 Python 与 Elastic Stack 结合,特别关注新的数据科学原生 Elasticsearch 客户端,Eland。在本章中,我们将探讨以下主题:

  • 学习使用转换

  • 使用 Painless 进行高级转换配置

  • 使用 Python 和 Elasticsearch 进行操作

技术要求

本章的材料需要 Elasticsearch 版本 7.9 或更高版本以及 Python 3.7 或更高版本。本章所需的代码示例和片段将被添加到书籍 GitHub 仓库的Chapter 9 - 数据框分析简介文件夹下(github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%209%20-%20Introduction%20to%20Data%20Frame%20Analytics)。在这些需要特定较新版本 Elasticsearch 的示例中,示例展示之前将提到这一点。

学习如何使用转换

在本节中,我们将深入探讨将流或事件数据(如日志)转换为以实体为中心的索引的世界。

为什么转换是有用的?

考虑一下通常被摄入到 Elasticsearch 中的最常见数据类型。这些通常是一些记录基于时间或顺序事件的文档,例如,来自 Web 服务器的日志、来自 Web 商店的客户购买、在社交媒体平台上发布的评论等等。

虽然这类数据有助于理解我们的系统随时间的行为,并且非常适合与异常检测等技术一起使用,但在对它们进行某种形式的聚合或转换之前,要使基于流或事件的数据集与数据框分析功能协同工作会更困难。例如,考虑一个记录客户购买的电子商务商店。在一年内,每个客户可能有数十或数百笔交易。如果电子商务商店想要找到一种方法来使用异常检测来检测不寻常的客户,他们必须转换每个客户的全部交易数据点,并总结某些关键指标,例如每次购买的平均金额或日历月份的购买次数。在图 9.1中,我们有一个简化的插图,描述了从两个客户的电子商务购买中提取交易记录并将其转换为描述这些客户购买的总商品数量的以实体为中心的索引的过程,以及每笔订单的平均价格。

![图 9.1 – 一个说明将电子商务交易转换为以实体为中心的索引过程的图表]

图 B17040_09_001.jpg

图 9.1 – 一个说明将电子商务交易转换为以实体为中心的索引过程的图表

为了执行 图 9.1 中展示的转换,我们必须按客户名字对事务索引中的每个文档进行分组,然后执行两个计算:将每个事务文档中的商品数量加起来得到总数,并为每个客户计算购买的平均价格。手动为成千上万的潜在客户中的每个客户的每笔交易做这件事将会非常困难,这就是 转换 发挥作用的地方。

转换的解剖结构

虽然我们将会从简单的示例开始我们的转换之旅,但许多实际应用场景可能会很快变得复杂。记住以下两点将有助于你在应用转换到自己的数据项目时保持方向:数据透视聚合

让我们来看看这两个实体如何相互补充,帮助我们将基于流的文档转换为以实体为中心的索引。在我们的客户分析用例中,我们有许多不同的特征来描述每个客户:客户的名字,他们在结账时为每个产品支付的总价,他们购买的商品列表,购买日期,客户的地理位置等等。

我们首先想要选择的是我们将为其构建以实体为中心的索引的实体。让我们从一个非常简单的例子开始,比如说我们的目标是找出在特定时间段内每个客户平均每笔消费的金额以及他们总共花费了多少钱。因此,我们想要为它构建索引的实体 – 我们的 数据透视 – 是客户的名字。

我们源索引中的大多数客户都与多个交易相关联。因此,如果我们尝试按客户名字对索引进行分组,对于每个客户我们都会有多个文档。为了成功使用这个实体进行数据透视,我们需要决定我们想要将哪些聚合数量(例如,客户每笔订单的平均支付价格)带入我们的以实体为中心的索引中。这将反过来决定我们在转换配置中定义哪些聚合。让我们通过一个实际例子来看看这是如何工作的。

使用转换来分析电子商务订单

在本节中,我们将使用 Kibana 电子商务样本数据集来说明上一节中概述的一些基本转换概念:

  1. 导入 kibana_sample_data_ecommerce 并用数据集填充它。![图 9.2 – 从 Kibana 样本数据面板导入 Sample eCommerce 订单数据集

    ![img/B17040_09_002.jpg]

    图 9.2 – 从 Kibana 样本数据面板导入 Sample eCommerce 订单数据集

  2. 通过从左上角的汉堡按钮打开 Kibana 滑出面板菜单,导航到 堆栈管理,然后在 数据 菜单下点击 Transforms 来访问 Transforms 向导。

  3. kibana_sample_data_ecommerce索引中,你应该在图 9.3所示的面板中选择。你在 Kibana 中显示的源索引可能会根据你的 Elasticsearch 集群中当前可用的索引而有所不同。![Figure 9.3 – For this tutorial, please select kibana_sample_data_ecommerce

    ![img/B17040_09_003.jpg]

    图 9.3 – For this tutorial, please select kibana_sample_data_ecommerce

  4. 选择我们的源索引后,customer_full_name。![Figure 9.4 – Select the entity you want to pivot your source index by in the Group by menu

    ![img/B17040_09_4.jpg]

    图 9.4 – 在分组依据菜单中选择你想要以之旋转源索引的实体

  5. 现在我们已经定义了旋转索引的实体,我们将继续到转换构建的下一部分:聚合。在这种情况下,我们感兴趣的是找出客户在每个订单中在电子商务商店花费的平均金额。在每次交易中,交易记录在源索引的文档中,客户支付的总金额存储在字段taxful_total_price中。

    因此,我们定义的聚合将作用于这个字段。在聚合菜单中,选择taxful_total_price.avg。一旦点击了这个选择,该字段将出现在聚合下的框中,你将看到如图 9.5所示的旋转索引的预览。

    ![Figure 9.5 – A preview of the transformed data is displayed to allow a quick check that everything is configured as desired.

    ![img/B17040_09_5.jpg]

    图 9.5 – 转换数据的预览显示,以便快速检查一切是否按预期配置。

  6. 最后,我们将配置最后两个项目:转换作业的 ID 和将包含描述我们旋转实体的文档的目标索引的名称。像图 9.6中所示,勾选创建索引模式复选框是个好主意,这样你就可以轻松地在发现选项卡中导航到目标索引以查看结果。![Figure 9.6 – Each transform needs a transform ID

    ![img/B17040_09_006.jpg]

    图 9.6 – 每个转换都需要一个转换 ID

    转换 ID 将用于识别转换作业和包含由转换作业产生的实体为中心索引的文档的目标索引。

  7. 要开始转换作业,记得在完成第 6 步中描述的说明后,在转换向导中点击下一步,然后点击创建并启动。这将启动转换作业并创建以实体为中心的索引。

  8. 转换完成后(如果一切顺利,你会看到进度条达到 100%),你可以在转换向导的底部点击发现按钮,查看你的转换文档。

    如本节开头所述,我们从图 9.7中的一个样本文档中看到,转换作业已经将一个以交易为中心的索引(记录了我们电子商务商店中每位客户所做的每次购买)转换成了一个以实体为中心的索引,该索引描述了特定的分析转换(计算客户支付的平均价格),并按客户的完整姓名进行分组。

图 9.7 – 转换作业的结果是一个目标索引,其中每个文档描述了每个旋转实体的聚合。在本例中,每位客户支付的含税总价平均值

图 9.7 – 转换作业的结果是一个目标索引,其中每个文档描述了每个旋转实体的聚合。在本例中,每位客户支付的含税总价平均值

恭喜您 – 您现在已创建并启动了您的第一个转换作业!尽管它本质上相当简单,但这个基本作业配置是用于更复杂转换的良好构建块,我们将在接下来的章节中探讨。

探索更高级的旋转和聚合配置

在上一节中,我们探讨了转换的两个部分:旋转和聚合。在随后的示例中,我们的目标是使用转换在 Kibana 样本电子商务数据集上找到每位客户每笔订单的平均花费金额。为了解决这个问题,我们了解到记录交易的每个文档都有一个名为customer.full_name的字段,我们使用这个字段来旋转我们的源索引。我们的聚合是记录客户在订单上花费的总金额的平均值。

然而,并非我们想要对电子商务数据提出的所有问题都适合使用之前讨论过的简单旋转或分组配置。让我们探索一些更高级的旋转配置,这些配置可以通过转换实现,借助一些我们可能想要在电子商务数据集上进行的样本调查。如果您想发现所有可用的旋转配置,请查看此 URL 上的旋转对象 API 文档:www.elastic.co/guide/en/elasticsearch/reference/master/put-transform.html

假设我们想要在我们的数据集中找出每周每笔订单的平均花费金额,以及有多少独特的客户进行了购买。为了回答这些问题,我们需要构建一个新的转换配置:

  1. 我们不想通过客户的名称进行旋转,而是想构建一个order_date,正如其名称所暗示的,它记录了订单的放置时间。转换向导使这变得简单,因为date_histogram(order_date)将是分组下拉菜单中显示的预配置选项之一。

  2. 一旦你在分组下拉菜单中选择了date_histogram(order_date),将你的注意力转向面板的右侧,如图 9.8 所示。右侧应该包含用于日期直方图分组间隔的缩写(例如,1m表示 1 分钟的间隔)。在我们的例子中,我们想要按周旋转我们的索引,所以我们需要从下拉菜单中选择1w图 9.8 – 从下拉菜单调整日期直方图的频率

    图 9.8 – 从下拉菜单调整日期直方图的频率

  3. 接下来,对于我们的聚合,让我们选择熟悉的avg(total_taxful_price)。在我们做出选择后,转换向导将显示一个预览,它将显示客户按不同周次订单的平均支付价格,并显示几行样本数据。预览的目的是作为一个检查点。由于转换作业可能非常消耗资源,在这个阶段停下来检查预览,以确保数据已经转换成你想要的格式是很好的。

  4. 有时候我们可能希望以不适合简单单层分组配置的方式查询我们的数据,就像我们在前面的步骤中探索的那样。我们可以嵌套分组配置,正如我们马上就会看到的。假设在我们的假设电子商务商店示例中,我们还想看到按周和按地理区域平均花费的金额。

    为了解决这个问题,让我们回到转换向导,并添加一个第二个分组字段。在这种情况下,我们想要按geoip.region_name分组。和之前一样,向导在我们选择分组字段后会显示转换的预览。和之前的情况一样,花点时间看看预览中显示的行,以确保数据已经按照期望的方式转换。

    小贴士

    点击转换预览表上方的切换按钮以重新排列列的顺序。

    除了创建多个分组配置外,我们还可以向我们的转换中添加多个聚合。假设除了每个客户每周和每个地区平均花费的金额外,我们还想了解在我们商店下单的独特客户数量。让我们看看我们如何将这个聚合添加到我们的转换中。

  5. customer.full_name.keyword)上点击它以选择它。结果聚合将被添加到你的转换配置中,并且预览现在应该显示一个额外的列。

    现在,你可以遵循上一节教程中的步骤为转换分配一个 ID 和目标索引,以及创建和启动作业。这些将留给你作为练习。

在前两节中,我们检查了转换的两个关键组件:旋转和聚合,并进行了两次不同的演练,展示了如何使用简单和高级的旋转和聚合组合来查询我们的数据以获得各种见解。

在跟随第一个转换的过程中,你可能已经注意到在图 9.6中,我们没有勾选连续模式复选框。我们将在下一节中更深入地探讨在连续模式下运行转换意味着什么。

发现批处理和连续转换之间的区别

在上一节中,我们创建的第一个转换很简单,只运行了一次。转换作业读取了配置在kibana_sample_data_ecommerce中的源索引kibana_sample_data_ecommerce,转换作业运行后发生的任何更改将不会反映在目标索引中的数据中。这种只运行一次的转换被称为批处理转换

在许多实际用例中,如我们虚构的电子商务商店示例,会不断向源索引添加新记录。这意味着我们通过运行转换作业获得以实体为中心的旋转索引几乎会立即过时。一个保持目标索引与源索引同步的解决方案是定期删除目标索引并重新运行批处理转换作业。然而,这并不实用,需要大量的手动工作。这就是连续转换介入的地方。

如果我们有一个正在更新的源索引,并且想使用它来创建一个以实体为中心的旋转索引,那么我们必须使用连续转换而不是批处理转换。让我们更详细地探讨连续转换,以了解它们与批处理转换有何不同,以及运行连续转换时应考虑哪些重要的配置参数。

首先,让我们为我们要解决的问题设定场景。假设我们有一个虚构的微博社交媒体平台,用户可以发布简短更新,为更新分配类别,并与其他用户以及预定义的主题互动。可以分享帖子并点赞帖子。每个帖子的统计数据也会被记录。我们已经编写了一些 Python 代码来帮助生成这个数据集。这段代码以及如何运行此代码的说明可以在本书 GitHub 仓库的第九章 - 数据帧分析简介文件夹下找到(github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%209%20-%20Introduction%20to%20Data%20Frame%20Analytics)。运行生成器后,您将有一个名为social-media-feed的索引,其中将包含多个文档。

数据集中的每个文档都记录了用户在社交媒体平台上发布的帖子。为了简洁起见,我们已从文档中省略了帖子的文本。"图 9.9"显示了social-media-feed索引中的一个示例文档。

![图 9.9 – 社交媒体信息流索引中的示例文档记录了用户名、帖子提交到平台的时间以及帖子收到的参与度的一些基本统计数据

![img/B17040_09_009.jpg]

图 9.9 – 社交媒体信息流索引中的示例文档记录了用户名、帖子提交到平台的时间以及帖子收到的参与度的一些基本统计数据

在下一节中,我们将看到如何使用这个虚构的社交媒体平台数据集来了解连续变换。

使用连续变换分析社交媒体信息流

在本节中,我们将使用之前介绍的数据集来探索连续变换的概念。正如我们在上一节中讨论的,批量变换适用于一次性分析,在这种情况下,我们可能愿意分析在特定时间点的数据集快照,或者我们没有正在变化的数据集。在大多数实际应用中,情况并非如此。日志文件是持续摄入的,许多社交媒体平台全天候活跃,电子商务平台服务于所有时区的客户,因此生成一系列交易数据。这就是连续变换介入的地方。

让我们看看如何使用连续变换分析社交媒体用户收到的平均参与度水平(点赞和分享):

  1. 导航到变换向导。在堆栈管理页面,在数据部分下向左查看,并选择变换

  2. 正如我们在前面的章节中所做的那样,让我们首先创建转换。对于源索引,选择 social-media-feed 索引模式。这应该会给你一个类似于 图 9.10 中的视图。图 9.10 – 转换向导显示了 social-media-feed 索引的示例

    图 9.10 – 转换向导显示了 social-media-feed 索引的示例

  3. 在这种情况下,我们将对计算每个用户名下每篇帖子的参与度指标聚合感兴趣。因此,我们的 按组 配置将包括用户名,而我们的聚合将计算每个用户的总点赞和分享数,以及每个用户的平均点赞和分享数以及每个用户发布的总帖数。最终的 按组聚合 配置应该类似于 图 9.11图 9.11 – 我们连续转换的按组与聚合配置

    图 9.11 – 我们连续转换的按组与聚合配置

  4. 最后,如 图 9.12 所示,勾选 timestamp图 9.12 – 选择连续模式以确保转换过程定期检查    源索引并将新文档纳入目标索引

    图 9.12 – 选择连续模式以确保转换过程定期检查源索引并将新文档纳入目标索引

  5. 一旦你点击 social-media-feed 索引运行。注意作业描述中的连续标签。图 9.13 – 在转换页面上显示的连续转换。注意模式被标记为连续

    图 9.13 – 在转换页面上显示的连续转换。注意模式被标记为连续

  6. 让我们在索引 social-media-feed 中插入一些新帖子,并查看在将新文档添加到转换的源索引后,用户 Carl 的统计数据如何变化。要插入新帖子,打开书籍 GitHub 仓库中的 chapter9 的 Kibana chapter9 版本,如果你正在跟随,你可以轻松地将它复制并粘贴到自己的 Kibana 开发控制台中:

    POST social-media-feed/_doc
    {
        "username": "Carl",
        "statistics": {
          "likes": 320,
          "shares": 8000
        },
        "timestamp": "2021-01-18T23:19:06"
      }
    
  7. 现在,我们已经将一个新文档添加到源索引 social-media-feed 中,我们预计这个文档将被连续转换作业选中并纳入我们的转换目标索引 social-media-feed-engagement图 9.14 展示了用户名 Carl 的转换条目。

图 9.14 – 连续转换作业的目标索引包含一个新用户名 Carl 的条目,这是我们通过 Kibana 开发控制台手动添加的

图 9.14 – 连续转换作业的目标索引包含一个新用户名 Carl 的条目,这是我们通过 Kibana 开发控制台手动添加的

以下示例提供了一个非常简化的连续转换工作原理的概述,以及如何使用 Kibana 中可用的转换向导创建自己的连续转换。在第十三章“推理”中,当我们展示如何结合训练好的机器学习模型、推理和转换时,我们将回到连续转换的主题。

目前,我们将简要地进入脚本语言 Painless的世界。虽然转换向导和它提供的许多预构建的按组聚合配置足以满足许多常见的数据分析用例,但对于更高级的用户来说,他们可能希望定义自己的聚合。实现这一目标的一种常见方式是借助 Elasticsearch 内嵌的脚本语言 Painless。

在下一节中,我们将简要游览 Painless 的世界,这将为你创建自己的高级转换配置做好准备。

使用 Painless 进行高级转换配置

正如我们在许多前面的章节中看到的,内置的交叉和聚合选项允许我们以各种方式分析和查询我们的数据。然而,对于更定制或高级的用例,内置函数可能不够灵活。对于这些用例,我们需要编写自定义的交叉和聚合配置。集成在 Elasticsearch 中的灵活脚本语言Painless允许我们做到这一点。

在本节中,我们将介绍 Painless,展示一些在处理 Painless 时有用的工具,然后展示如何将 Painless 应用于创建自定义转换配置。

介绍 Painless

Painless 是一种集成在 Elasticsearch 中的脚本语言。我们将从变量、控制流结构、操作和函数的角度来探讨 Painless。这些是帮助你开发自己的自定义脚本来与转换一起使用的基石。现在,让我们直接进入介绍部分。

很可能这本书的许多读者都来自某种编程语言背景。你可能使用 Python 编写过数据清洗脚本,使用 bash 脚本编程 Linux 机器,或者使用 Java 开发企业软件。尽管这些语言在许多方面都有所不同,并且适用于不同的目的,但它们都有共同的构建块,有助于语言的人类读者理解它们。尽管教授编程语言的方法几乎无限,但我们将采取的方法将基于理解以下关于 Painless 的基本主题:变量、操作(如加法、减法和各种布尔测试)、控制流(if-else 构造和 for 循环)和函数。这些是与熟悉其他编程语言的用户相关联的类似概念。除了这些概念之外,我们还将探讨一些特定于 Painless 的方面,例如不同的执行上下文。

当学习一门新的编程语言时,拥有一个可以用来实验语法的游乐场非常重要。幸运的是,随着 Elasticsearch 7.10 版本的推出,Dev Tools 应用现在包含新的 Painless Lab 游乐场,您可以在其中尝试本章中展示的代码示例,以及您自己编写的任何代码示例。

可以通过导航到如图 9.15 所示的 Dev Tools 来访问 Painless 实验室,然后在 Dev Tools 页面的顶部菜单中选择 Painless Lab

![图 9.15 – Dev Tools 页面的链接位于 Kibana 侧菜单的下半部分。选择它以访问交互式的 Painless 实验室环境

![img/B17040_09_015.jpg]

图 9.15 – Dev Tools 页面的链接位于 Kibana 侧菜单的下半部分。选择它以访问交互式的 Painless 实验室环境

这将打开如图 9.16 所示的嵌入式 Painless 代码编辑器。

![图 9.16 – Dev Tools 中的无痛苦实验室特色嵌入式代码编辑器。输出窗口显示了代码编辑器中代码的评估结果

![img/B17040_09_016.jpg]

图 9.16 – Dev Tools 中的 Painless 实验室特色嵌入式代码编辑器。输出窗口显示了代码编辑器中代码的评估结果

Painless 实验室中的代码编辑器预先配置了一些示例函数和变量声明,以说明如何使用 Painless 在如图 9.16 所示的 输出 窗口中绘制图形。目前,您可以删除此代码,为阅读本章剩余部分时进行的实验腾出空间。

小贴士

Painless 语言的完整规范可在以下网址在线获取:www.elastic.co/guide/en/elasticsearch/painless/master/painless-lang-spec.html。您可以用它作为参考和资源,以获取关于后面涵盖主题的更多信息。

变量、运算符和控制流

在编程语言中,我们通常想要做的第一件事就是操作值。为了有效地做到这一点,我们给这些值分配名称或变量。Painless 有类型,并且在一个变量被分配之前,必须声明它的类型。声明变量的语法如下:type_identifier variable_name ;.

如何在实际中应用这种语法,在下面的代码块中得到了展示,其中我们声明变量ab来存储整数值,变量my_string来存储字符串值,以及变量my_float_array来存储浮点值数组:

int a;
int b;
String my_string;
float[] my_float_array;

到目前为止,这些变量还没有存储任何非 null 值。它们只是初始化,为赋值语句做准备,该语句将为每个变量分配适当的类型值。因此,如果你尝试将前面的代码块复制到 Painless Lab 代码编辑器中,你将在输出窗口中看到null,如图图 9.17所示。

图 9.17 – 在左侧,初始化了各种类型的 Painless 变量。在右侧,输出面板显示 null,因为这些变量尚未分配任何值

图 9.17 – 在左侧,初始化了各种类型的 Painless 变量。在右侧,输出面板显示 null,因为这些变量尚未分配任何值

重要提示

Painless Lab 代码编辑器只显示最后一条语句的结果。

接下来,让我们给这些变量分配一些值,这样我们就可以用它们做一些有趣的事情。这些赋值在下面的代码块中显示。在前两行中,我们将整数值赋给我们的整型变量ab。在第三行中,我们将字符串"hello world"赋给字符串变量my_string,在最后一行中,我们初始化一个新的包含浮点值的数组:

a = 1;
b = 5;
my_string = "hello world";
my_double_array = new double[] {1.0, 2.0, 2.5};

让我们用这些变量做一些有趣的事情来展示 Painless 中可用的运算符。我们只能覆盖其中的一些可用运算符。有关可用运算符的完整列表,请参阅 Painless 语言规范(www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators.html)。以下代码块展示了基本的数学运算:加法、减法、除法和乘法,以及取模运算或取余数:

int a;
int b;
a = 1;
b = 5;
// Addition 
int addition;
addition = a+b;
// Subtraction 
int subtraction;
subtraction = a-b;
// Multiplication
int multiplication;
multiplication = a*b;
// Integer Division 
int int_division;
int_division = a/b;
// Remainder
int remainder;
remainder = a%b;

在 Painless Lab 中亲自尝试这些代码示例,你应该能够看到你评估的结果,就像在图 9.18中添加示例所展示的那样。

图 9.18 – 使用 Painless Lab 代码编辑器和控制台在 Painless 中进行加法。左侧代码编辑器中存储在名为"addition"的变量中的结果在右侧的输出选项卡中显示

图 9.18 – 在 Painless 中使用 Painless Lab 代码编辑器和控制台进行加法。在左侧代码编辑器中存储在名为 "addition" 的变量中的结果在右侧的输出选项卡中显示

除了数学运算之外,我们还将探讨 布尔运算符。这些运算符对于许多 Painless 脚本和配置以及控制流语句至关重要,我们将在之后进行探讨。

下面的代码片段说明了如何声明一个变量来存储布尔(真/假)值,以及如何使用比较运算符来确定值是小于、大于、小于等于还是大于等于。有关 Painless 中布尔运算符的完整列表,请参阅此处可用的 Painless 规范:www.elastic.co/guide/en/elasticsearch/painless/current/painless-operators-boolean.html

boolean less_than = 4<5;
boolean greater_than = 4>5;
boolean less_than_or_equal = 4 <=5;
boolean greater_than_or_equal = 4 >= 5;

作为练习,将前面的代码块复制到 Painless Lab 代码编辑器中。如果你愿意,可以在 Painless Lab 代码编辑器的最后一行输入变量的名称后跟一个分号,这样就可以在右侧的 输出 窗口中打印出变量存储的值,如图 9.19 所示。

图 9.19 – 在 Painless Lab 代码编辑器中输入变量名后跟一个分号,将在输出选项卡中输出变量的内容

图 9.19 – 在 Painless Lab 代码编辑器中输入变量名后跟一个分号,将在输出选项卡中输出变量的内容

虽然这里展示的布尔运算符在许多数值计算中很有用,但我们可能无法没有等式运算符 ==!= 来编写有效的控制流语句,这两个运算符用于检查两个变量是否相等。以下代码块通过几个实际示例说明了如何使用这些运算符:

// boolean operator for testing for equality
boolean two_equal_strings = "hello" == "hello";
two_equal_strings;
// boolean operator for testing for inequality
boolean not_equal = 5!=6;
not_equal;

最后但同样重要的是,在我们对 Painless 中的布尔运算符的探索中,我们将查看一个代码块,展示了如何使用 instanceof 运算符,该运算符检查给定变量是否是某个类型的实例,并返回 truefalse。当你编写只想在指定类型的变量上操作的 Painless 代码时,这是一个非常有用的运算符:

// boolean operator instanceof tests if a variable is an instance of a type
// the variable is_integer evaluates to true 
int int_number = 5;
boolean is_integer = int_number instanceof int;
is_integer; 

在本节的最后部分,让我们看看 Painless 脚本中最重要构建块之一:以下代码块通过一个示例展示了 if-else 语句的使用:

int a = 5;
int sum;
if (a < 6){
   sum = a+5;
    }
else {
    sum = a-5;
    }
sum;

在前面的代码块中,我们声明了一个整数变量a,并将其赋值为整数5。然后我们声明另一个整数变量sum。这个变量的值将根据if-else语句中采取的执行分支而变化。最后,我们看到if-else语句首先检查整数变量a是否小于 6,如果是,则将a和整数 5 相加的结果存储在变量sum中。如果不是,则变量sum中存储的值是a减去 5 的结果。

如果您在 Painless Lab 代码编辑器中输入此代码,sum的值为 10(如图9.20所示),这是基于之前的分析我们所预期的。

![Figure 9.20 – if-else 语句导致 sum 变量设置为值 10]

![img/B17040_09_020.jpg]

![Figure 9.20 – if-else 语句导致 sum 变量设置为值 10]

最后,我们将看看如何编写for循环,这对于使用 Painless 进行各种数据分析和处理任务非常有用。在我们的for循环中,我们将遍历一个字符串变量并计算字符串中字母a出现的次数。这是一个非常简单的例子,但希望它能帮助您理解语法,以便您可以在自己的例子中应用它:

// initialize the string and the counter variable
String sample_string = "a beautiful day";
int counter = 0;
for (int i=0;i<sample_string.length();i++){
 // get a letter from the string using the substring method
 String letter = sample_string.substring(i, i+1);
 //use an if-statement to check if the current letter being processed is an a
 if (letter=="a") {
 // is yes, increment the counter
 counter++
   }
 }

counter;

将此代码示例(可以在 GitHub 仓库 github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition 中的Chapter 9 - Introduction to Data Frame Analytics文件夹下找到此书的副本)复制并粘贴到您的 Painless Lab 中,您将看到counter变量在3,正如我们所预期的,因为字符串“a beautiful day”中有三个“a”字母的出现。

函数

现在我们已经涵盖了变量、运算符和控制流语句,让我们暂时将注意力转向函数。有时,我们可能会发现自己反复在不同的脚本和配置中编写相同的代码行。在这种情况下,将我们反复使用的代码行打包成一个可重用的代码块,并从我们的 Painless 脚本中通过名称引用它可能更经济。

让我们回到我们编写带有if语句的for循环来计算给定字符串中字母“a”实例的例子。假设我们想重用这个功能并使其稍微更通用。这是一个将此代码块打包成 Painless 函数的绝佳机会。

在 Painless 中编写函数有三个部分。首先,我们编写函数头,它指定了函数返回值的类型以及函数的名称。这是我们将在脚本或letterCounter中使用的内容,如下面的代码块所示:

int letterCounter(){
}

函数名前的 int 决定了该函数返回值的类型。由于我们感兴趣的是特定字符串中特定字母出现的次数,我们将返回一个整数计数。名称 letterCounter 后面的括号将包含函数接受的参数。目前,我们尚未指定任何参数,因此括号之间没有内容。最后,两个大括号表示函数体的位置——这是函数所有逻辑将驻留的地方。

现在我们已经研究了创建基本函数头所需的所有元素,让我们填充函数体(即上节学习 for 循环时编写的代码所在的大括号之间的空间)。现在我们的函数看起来应该像以下代码块:

int letterCounter(){
    // initialize the string and the counter variable
    String sample_string = "a beautiful day";
    int counter = 0;
    for (int i=0;i<sample_string.length();i++){
    // get a letter from the string using the substring method
    String letter = sample_string.substring(i, i+1);
    //use an if-statement to check if the current letter being processed is an a
    if (letter=="a") {
    // is yes, increment the counter
    counter++
    }
    }
    return counter;
}
letterCounter();

如果你看到函数体的末尾,你会注意到我们 for 循环与函数体中的唯一区别是现在我们添加了一个 return 语句。这允许我们存储在变量 counter 中的感兴趣值返回到调用函数的代码中,这把我们带到了下一个问题。现在我们已经编写了第一个 Painless 函数,并且它做了些有趣的事情,我们如何调用这个函数呢?

在 Painless Lab 环境中,我们只需像 图 9.21 所示那样键入 letterCounter();。根据我们对这个代码样本之前的分析,这个函数返回的结果是,正如我们所预期的,3

图 9.21 – 样本函数 letterCounter 的定义与在 Painless Lab 环境中调用函数的示例并排显示

图 9.21 – 样本函数 letterCounter 的定义与在 Painless Lab 环境中调用函数的示例并排显示

现在我们已经有一个可工作的函数了,让我们来谈谈如何使这个函数更加通用,这在处理我们本章讨论的转换,或者将在 第十三章 “推理”中讨论的各种摄取管道和脚本处理器时,你经常会需要这样做。目前,我们的函数 letterCounter 非常具体。它只计算特定字母(字母 a)在特定字符串(短语“a beautiful day”)中出现的次数。

假设为了使这段代码真正有用,我们希望改变短语和被计数的字母。通过函数,我们可以通过最小化代码重复来实现这一点,通过配置 函数参数。由于我们希望改变字母和短语,让我们将这两个变成函数参数。更改后,我们的函数定义将如下所示:

int letterCounter(String sample_string, String count_letter){
    // initialize the string and the counter variable
    int counter = 0;
    for (int i=0;i<sample_string.length();i++){
    // get a letter from the string using the substring method
    String letter = sample_string.substring(i, i+1);
    //use an if-statement to check if the current letter being processed is an a
    if (letter==count_letter) {
    // is yes, increment the counter
    counter++
    }
    }
    return counter;
}

注意,现在在函数标题的括号中,我们已经定义了两个参数:一个 sample_string 参数表示我们想要在其中计算第二个参数 count_letter 发生次数的短语。

要调用此函数,我们首先定义新的变量来存储我们的短语(“一个美好的一天”,再次)和我们的感兴趣字母——这次是字母“b”而不是“a”。随后,我们将这两个变量作为以下代码块中所示传递给函数调用:

String phrase = "a beautiful day";
String letter_of_interest = "b";
letterCounter(phrase, letter_of_interest);

由于我们感兴趣的短语中只有一个“b”字母的出现,我们预计执行此函数的结果将是 1,正如 图 9.22 中所示。

图 9.22 – 函数调用的结果显示在右侧的输出面板中

图 9.22 – 函数调用的结果显示在右侧的输出面板中

现在你应该已经准备好编写自己的 Painless 代码了!这在第十三章“推理”中会很有用,我们将使用高级 Painless 功能来进行特征提取和编写脚本处理器。

使用 Python 和 Elasticsearch

近年来,Python 已成为许多数据密集型项目的首选语言。得益于其易于使用的机器学习和数据分析库,许多数据科学家和数据工程师现在正大量依赖 Python 来完成他们的大部分日常操作。因此,在 Elastic Stack 中关于机器学习的讨论如果没有探讨数据分析师如何使用 Python 与 Elastic Stack 一起工作,将是不完整的。

在本节中,我们将探讨三个官方 Python Elasticsearch 客户端,了解它们之间的区别,并讨论何时可能需要使用其中一个而不是另一个。我们将演示如何通过使用 Elasticsearch 客户端来自动化 Elastic Stack ML 的使用。此外,我们将深入了解 Eland,这是一个新的数据科学原生客户端,它支持由 Elasticsearch 支持的高效内存数据分析。在探索 Eland 的工作原理后,我们将展示如何将 Eland 与 Jupyter notebooks 结合使用,这是一个开源的交互式数据分析环境,用于分析存储在 Elasticsearch 中的数据。

Python Elasticsearch 客户端的简要概述

任何使用过 Kibana Dev Tools 控制台与 Elasticsearch 通信的人都知道,大多数操作都是通过 REST API 完成的。你可以通过调用正确的端点并传递正确的参数来插入、更新和删除文档。不出所料,在编写调用这些 REST API 端点的客户端程序时,存在几个抽象级别。低级客户端elasticsearch-py([elasticsearch-py.readthedocs.io/en/v7.10.1/](https://elasticsearch-py.readthedocs.io/en/v7.10.1/))为通常通过 Kibana Dev 控制台或能够发送 HTTP 请求的应用程序执行的 REST API 调用提供了一个薄的 Python 包装器。下一个抽象级别由 Elasticsearch DSL 客户端([https://elasticsearch-dsl.readthedocs.io/en/latest/](https://elasticsearch-dsl.readthedocs.io/en/latest/))捕捉。最后,最抽象的客户端是Eland([https://eland.readthedocs.io/en/7.10.1b1/](https://eland.readthedocs.io/en/7.10.1b1/)),其中数据帧,数据的表格表示,是一个一等公民。在随后的示例中,我们将看到这对于希望使用 Eland 与 Elasticsearch 一起工作的数据科学家意味着什么。

除了可用的 Elasticsearch 客户端外,我们还将花一点时间讨论任何希望使用 Python 和 Elasticsearch 的数据工程师或数据科学家可用的各种执行环境。这反过来又将引导我们介绍 Jupyter 笔记本和整个 Jupyter 生态系统,这对于任何希望使用机器学习和 Elastic Stack 的人来说都是一个需要了解的工具。

执行 Python 程序的第一种,也可能是最熟悉的方法,是将我们的程序逻辑写入文本文件——或脚本——保存为.py扩展名以表示它包含 Python 代码,然后使用命令行调用脚本,如图 9.23 所示。

图 9.23 – 使用 Python 的一种方法是,将我们的程序存储在文本文件或脚本中,然后从命令行执行它

图 9.23 – 使用 Python 的一种方法是,将我们的程序存储在文本文件或脚本中,然后从命令行执行它

第二种方法是使用交互式 Python REPL,如图 9.24 所示。在命令行上调用python(或python3)将启动一个交互式 Python 环境,我们可以在此环境中编写函数、定义变量并执行各种 Python 代码。虽然这个环境对于小规模或快速实验很有用,但在实践中,在 REPL 环境中进行长期或更大规模的数据分析项目会很困难。因此,对于大多数涉及 Python、数据分析和 Elastic Stack 的项目,首选的环境是某种集成开发环境,它提供代码编辑器以及支持编程和执行的各种工具。

![图 9.24 – 展示一个示例 Python 交互式外壳或 REPL 的截图,非常适合在 Python 编程语言中进行快速实验图片

图 9.24 – 展示一个示例 Python 交互式外壳或 REPL 的截图,非常适合在 Python 编程语言中进行快速实验

一个专门为数据分析设计的开发环境是 Jupyter 笔记本,如图9.25所示。

![图 9.25 – 一个示例 Jupyter 笔记本图片

图 9.25 – 一个示例 Jupyter 笔记本

笔记本可以通过中央包管理服务,如pip,安装到 Python 环境中,并在命令行中通过输入jupyter notebook来启动。启动的环境在浏览器中运行,如 Chrome 或 Firefox,提供了一个可以并排存在代码片段、文本段落、图表和可视化(包括交互式和静态)的环境。许多作者已经比我们在本章中拥有的空间和时间更好地涵盖了 Jupyter Notebook 及其周围的库生态系统,因此我们鼓励那些想要或预期将在数据分析、Elasticsearch 和 Python 交叉领域工作更多的读者查看本章末尾的进一步阅读部分中列出的材料。

理解 Eland 背后的动机

上一节的读者可能会想知道,当社区已经有两个客户端可供选择时,为什么还要构建另一个 Elasticsearch Python 客户端的动机是什么? 此外,为什么围绕数据框对象的概念构建整个软件库? 这两个问题的答案可能足以填满一本书,所以这里提供的答案必然会有一些细微差别未被探索。尽管如此,我们希望对于感兴趣的读者,本节中的讨论将提供一些有趣的背景,了解 Eland 是如何产生的以及为什么它围绕数据框的概念进行设计。

尽管 Python 现在似乎是数据分析、机器学习许多领域的支配力量,但这并非一直如此。特别是,在 2010 年代初,生态系统由统计处理语言 R 主导,它有一个非常有用的结构——一个 dataframe 对象,允许用户在类似表格的结构中分析数据行(这个概念无疑对 Excel 用户来说是熟悉的)。大约在同一时间,Wes McKinney 当时在纽约金融公司 AQR Capital 工作,开始开发一个库,使 Python 数据分析师的生活更加轻松。这项工作最终导致了pandas的发布,这是一个开源的数据分析库,被成千上万的科学家和数据工程师使用。

使 pandas 变得有用且易于使用的关键特性之一是DataFrame对象。类似于 R 对象,这个对象使得以表格方式操作和执行数据分析变得简单直接。尽管 pandas 非常强大,并包含大量内置函数和方法,但当想要分析的数据集太大而无法适应内存时,它开始遇到限制。

在这些情况下,数据分析师通常会从各种数据库中采样数据,例如存储在 Elasticsearch 中的数据,将其导出为平面文件,然后将其读入 Python 进程,以便可以使用 pandas 或其他库进行分析。虽然这种方法确实有效,但如果 pandas 能够直接与数据库接口,工作流程将变得更加流畅。如果我们能够透明地将DataFrame对象与 Elasticsearch 接口,那么数据分析师就可以专注于数据分析,而无需担心管理连接和从 Elasticsearch 导出数据?这正是 Eland 的核心理念。希望接下来的章节能够展示这一理念如何在库的设计中具体实现。

使用 Eland 迈出第一步

由于 Eland 是一个第三方库,您首先需要安装它,以便您的 Python 安装能够使用它。关于各种操作系统的安装说明可以在本书的 GitHub 仓库中的第九章 - 数据帧分析简介部分找到:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%209%20-%20Introduction%20to%20Data%20Frame%20Analytics。我们将假设希望跟随本书本节内容的读者已经按照链接中的说明完成了库的安装。本章中的示例和截图将使用 Jupyter 笔记本环境进行说明,但也可以在独立环境中运行本章中提供的代码示例(例如,从 Python REPL 或 Python 脚本中)。需要 Jupyter 笔记本环境的特定示例将明确标注:

  1. 我们必须采取的第一步是导入 Eland 到我们的环境中。在 Python 中,这是通过import语句完成的,如图 9.26所示。请注意,当使用像 Eland 这样的库时,通常会给库分配一个别名。在图 9.26中显示的代码片段中,我们使用as关键字将eland分配给别名ed。这将节省我们未来的输入,因为我们将在访问其对象和方法时多次调用库名称。图 9.26 – 将 Eland 导入笔记本

    图 9.26 – 将 Eland 导入笔记本

  2. 在将代码导入我们的 Jupyter 笔记本之后,我们可以自由地开始探索。让我们从 Eland 可以做的最基本的事情开始:创建一个 Eland DataFrame。要创建 DataFrame,我们需要指定两件事:我们 Elasticsearch 集群的 URL(例如,如果我们在本地的默认端口 9200 上运行 Elasticsearch,则为 localhost)以及包含我们希望分析的数据的 Elasticsearch 索引的名称。这两个参数作为 图 9.27 中所示的那样传递给 DataFrame 构造函数。图 9.27 – 在 Eland 中创建 DataFrame 涉及到 Elasticsearch 集群的 URL 和包含我们希望分析的数据的索引名称

    图 9.27 – 在 Eland 中创建 DataFrame 涉及到 Elasticsearch 集群的 URL 和包含我们希望分析的数据的索引名称

  3. 当我们开始检查一个新的数据集时,我们感兴趣做的第一项任务之一是了解数据看起来像什么(通常,查看几行示例就足以了解数据的总体情况)以及数据集的一些一般统计属性。我们可以通过在 Eland DataFrame 对象上调用 head 方法来学习前者,如图 图 9.28 所示。图 9.28 – 在 Eland DataFrame 对象上调用 head 方法    这将显示数据集中的前 5 行

    图 9.28 – 在 Eland DataFrame 对象上调用 head 方法将显示数据集中的前 5 行

  4. 另一方面,了解后者是通过调用 describe 方法获得的,这对于 pandas 用户来说很熟悉,并在 图 9.29 中展示。图 9.29 – "describe" 总结了数据集中数值列的统计属性

    图 9.29 – "describe" 总结了数据集中数值列的统计属性

  5. 除了获得数据集的基本概述外,我们还可以通过使用与字段名称一起的 get 命令轻松访问索引中给定字段的单个值,如图 图 9.30 所示。图 9.30 – 我们可以通过使用 get 方法处理索引中的单个字段值

    图 9.30 – 我们可以通过使用 get 方法处理索引中的单个字段值

  6. 最后,我们可以使用 aggregate 方法在我们的数值列上计算聚合。在 图 9.31 中展示的示例中,我们选择了两个数值列 total_unique_productstaxful_total_price,并计算了这些字段在索引中所有文档中的总和、最小值和最大值。

图 9.31 – 在选定的列上计算聚合是可能的在 Eland 中使用 aggregate 方法

图 9.31 – 在 Eland 中使用 aggregate 方法可以在选定的列上计算聚合

虽然这里展示的步骤相对简单,但我们希望它们展示了如何无缝地将 Elasticsearch、Python 工作以及如 Jupyter Notebook 这样的数据分析环境集成到一个无缝的数据分析工作流程中。我们将在第十三章“推理”中进一步基于这个基础构建 Eland,届时我们将探讨更多高级用例。第十三章。

摘要

在本节中,我们涉足数据框分析的世界,这是机器学习和数据转换工具的一个全新分支,它解锁了使用存储在 Elasticsearch 中的数据解决问题的强大方式。除了概述我们将在未来章节中涵盖的新无监督和监督机器学习技术外,我们还研究了三个重要主题:转换、使用 Painless 脚本语言以及 Python 和 Elasticsearch 之间的集成。这些主题将构成我们未来章节工作的基础。

在我们关于转换的阐述中,我们研究了构成转换的两个组件——枢轴和聚合——以及运行转换的两种可能模式:批处理和连续。批处理转换只运行一次,并在特定时间点的源索引快照上生成转换。这对于变化不大的数据集或在需要仅在特定时间点执行数据转换时非常适用。对于许多现实世界的用例,例如日志记录或我们熟悉的电子商务商店示例,被监控和分析的系统是不断变化的。应用程序不断记录其用户的操作,电子商务商店不断记录新的交易。连续转换是分析此类流数据集的首选工具。

虽然我们在示例中展示的转换向导中的预配置选项适用于大多数情况,但更高级的用户可能希望配置自己的聚合。为了做到这一点(以及在一般意义上,为了能够执行我们在后续章节中将要讨论的许多更高级的配置),用户需要熟悉嵌入在 Elasticsearch 中的脚本语言 Painless。特别是,我们探讨了如何在 Painless 中声明变量,如何通过操作来操作这些变量,如何使用控制流语句构建更高级的程序,以及最后如何将有用的代码片段打包成函数。所有这些都将有助于我们在后续章节中的探索!

最后但同样重要的是,我们简要介绍了如何使用 Python 分析存储在 Elasticsearch 中的数据。我们查看了两款现有的 Elasticsearch Python 客户端,elasticsearch-pyelasticsearch-dsl,并阐述了第三个也是最新的客户端 Eland 的开发动机。

在下一节中,我们将深入探讨 Elastic Stack 中新增的三种机器学习方法中的第一种:异常检测。

进一步阅读

关于 Jupyter 生态系统,特别是 Jupyter Notebook 的更多信息,请查看 Project Jupyter 的全面文档,链接如下:jupyter.org/documentation.

如果你刚接触 Python 开发并且想要了解语言生态系统以及可用的各种工具,请查看《Python 漫游指南》,链接如下:docs.python-guide.org/.

想要了解更多关于 pandas 项目的信息,请参阅官方文档,链接如下:pandas.pydata.org/.

关于 Painless 内嵌脚本语言的更多信息,请参阅官方的 Painless 语言规范,链接如下:www.elastic.co/guide/en/elasticsearch/painless/current/painless-lang-spec.html.

第十章:异常值检测

在本书的第一部分,我们深入讨论了异常检测,这是一个允许我们以无监督方式检测时间序列数据中异常行为的特性。当我们想要检测我们的某个应用程序在特定时间是否经历了异常延迟,或者我们公司网络上的主机是否传输了异常数量的字节时,这种方法效果很好。

在本章中,我们将了解 Elastic Stack 中的第二个无监督学习特性:异常值检测,它允许我们在非时间序列索引中检测异常实体。异常值检测的一些有趣应用可能包括,例如,检测组织样本中的异常细胞,调查异常房屋或当地房地产市场中的区域,以及检测您计算机上安装的异常二进制文件。

Elastic Stack 中的异常值检测功能基于四种不同的异常值检测技术的集成或分组。其中两种技术基于密度——也就是说,它们试图确定在您的索引中哪些数据点远离数据的大多数——另外两种基于距离——也就是说,它们试图确定哪些点远离所有其他点。虽然,单独来看,这四个算法各有其优势和劣势,但作为一个整体(或分组),它们能够执行稳健的异常值检测。我们将在本章后面讨论每个算法在概念层面上的作用。

除了探索驱动异常值检测的技术之外,我们还将探讨异常值检测与异常检测的区别,如何在 Elasticsearch 中配置异常值检测作业,如何解释异常值检测的结果,以及如何理解哪些特征导致了某个点被宣布为异常值。我们将在本章探讨以下主题:

  • 探索异常值检测的工作原理

  • 在实践中应用异常值检测

  • 使用 Evaluate API 评估异常值检测

  • 异常值检测的超参数调整

技术要求

本章的材料依赖于使用 Elasticsearch 版本 7.9 或更高版本。本章中的图表是使用 Elasticsearch 7.10 生成的。本章中使用的代码片段和代码示例位于书籍 GitHub 仓库的chapter10文件夹中:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition

探索异常值检测的工作原理

异常值检测可以通过发现哪些点是不同或异常的来对数据集提供洞察,但 Elastic Stack 中的异常值检测是如何工作的呢?为了理解如何构建异常值检测功能,让我们首先从概念上思考你将如何设计算法,然后看看我们的概念想法如何被形式化为构成 Elasticsearch 中异常值检测集合的四个独立算法。

假设我们有一组关于南瓜重量和周长的二维数据集,并且我们希望发现哪些南瓜是这一群体中的异常值(也许我们想利用这些信息来找出它们为何是异常值)。一个很好的第一步是绘制数据,看看是否有任何明显的数据点看起来与其他数据点相距甚远:

图 10.1 – 点 A 和点 B 似乎是这个数据集中的异常值,因为它们位于数据总体分布之外

图 10.1 – 点 A 和点 B 似乎是这个数据集中的异常值,因为它们位于数据总体分布之外

人类眼睛在捕捉模式方面非常出色,快速浏览一下图 10.1就可以告诉你,点 A 和点 B 看起来像是异常值。是什么内在的直观推理让我们得出这个结论呢?我们的视觉系统告诉我们,从某种意义上说,点 A 和点 B 似乎远离二维空间中另外两个不同的点群。这一观察及其形式化是 Elastic Stack 中使用的异常值检测技术的基础。

发现用于异常值检测的四种技术

如前节所述,Elastic Stack 中的异常值检测算法是一个由四种不同的异常值检测技术组成的集合,或分组。这些技术可以进一步细分为两类:基于距离的技术基于密度的技术。我们将在接下来的章节中逐一考察它们。

基于距离的技术

如前节所述,人类的视觉系统在从二维图像中捕捉异常值方面非常擅长,而我们能够做到这一点的原因在于我们能够捕捉到那些似乎远离数据总体分布的点。这一观察结果正是两种基于距离的技术,即到第 k 个最近邻的距离到第 k 个最近邻的平均距离,所试图捕捉的。

假设我们有一个在空间上分布的两维数据集,如图图 10.2所示,并且假设我们选择k的值为 3。此时,我们只是为了说明目的而选择一个任意低的k值。这意味着对于图 10.2中的点 A,我们找到第三个最近点并计算点 A 到它的距离(在图 10.2中以较粗的箭头标记)。这种方法在简单性方面很出色,但也容易受到噪声的影响。为了使这种方法更加稳健,我们还可以计算到第 k 个最近邻的平均距离(如图图 10.2所示):

图 10.2 – 点 A 到第 k 个最近邻的距离以及点 A 到第 k 个最近邻的平均距离,当 k=3 时

图 10.2 – 点 A 到第 k 个最近邻的距离以及点 A 到第 k 个最近邻的平均距离,当 k=3 时

虽然基于距离的方法在简单性和可解释性方面很出色,但它们无法捕捉数据空间分布中的某些细微差别,特别是每个数据点的邻域是稀疏还是密集。为了捕捉这些属性,我们必须查看基于密度的技术。

基于密度的技术

基于距离的方法未能充分捕捉的一个因素是,我们感兴趣点的邻域中点的密度与其邻居周围点的密度的差异。计算一个点的局部异常因子恰好捕捉了这一点:给定点的邻域与其他邻域中的点有多大的不同。

图 10.3 展示了当 k=3 时该技术的基本思想。在这种情况下,我们比较点 A 的邻域与其三个最近邻的邻域(如图图 10.3中的虚线圆所示)。局部异常因子测量的值为 1 意味着点 A 的邻域与其邻居的邻域相当 – 它既不更稀疏也不更密集。大于 1 的值意味着 A 的邻域比其邻居的邻域更稀疏,它可能是一个异常值。相反,小于 1 的值意味着该点被其邻居密集包围,因此不太可能是异常值:

图 10.3 – 局部异常因子比较点 A 的邻域与其第 k 个最近邻的邻域

图 10.3 – 局部异常因子比较点 A 的邻域与其第 k 个最近邻的邻域

我们四种方法组合中的最后一种方法是基于局部距离的异常值因子LDOF)。与局部异常因子LOF)类似,LDOF的目标是将给定点 A 的邻域与 A 的邻居的邻域进行比较。在这种情况下,我们计算 A 的第 k 个最近邻的平均距离,对于某个固定的k,计算avg(A)。然后,对于 A 的第 k 个最近邻中的每一个,我们计算成对距离并取它们的平均值,avgkk(A)

最后,我们检查avg(A)/avgkk(A)的比率,看看它接近 1 的程度。如果比率接近 1,这意味着点 A 被其他点的局部密度所包围,因此不太可能是异常值。

分配给每个数据点的最终整体异常值分数是上述四种方法得出的值的组合。该值越接近 1,该点成为异常值的可能性就越大。

有时我们只是想找出数据集中哪些点是异常值,而在其他情况下,我们还想了解为什么异常值检测算法建议某个特定点是异常值。是否存在某个特定的特征或字段值,或者可能是一组值,使得该点变得不寻常?这就是我们将在下一节中讨论的主题。

理解特征影响力

让我们暂时回到本章开头提到的虚构南瓜数据集。假设我们正在使用异常值检测分析这个数据集。分析完成后,对于每个南瓜,我们都有一个从 0 到 1 的分数,衡量南瓜的异常性。除了知道分数外,我们还可能对了解哪些特征——南瓜的重量或南瓜的周长——对其异常性做出了贡献感兴趣。

这正是特征影响力旨在解决的问题。简而言之,特征影响力将 0 到 1 的分数分配给每个特征(如果我们从通常用来描述 Elasticsearch 文档的词汇来考虑,则是字段),该分数描述了该特征在确定数据点是异常值方面的重要性。所有特征的特征影响力分数总和为 1。

让我们借助图 10.4 中的虚构南瓜数据集来更仔细地看看特征影响力。假设我们的异常值检测算法已经确定 A 和 B 是该数据集中的异常值。现在,让我们考虑南瓜重量和南瓜周长在 A 和 B 点上的特征影响力值是如何相对的:

![图 10.4 – 特征影响力分数衡量给定特征在确定数据点异常性方面的影响]

![图片 B17040_10_004.jpg]

![图 10.4 – 特征影响力分数衡量给定特征在确定数据点异常性方面的影响]

南瓜 A 的重量远超出南瓜的正常重量范围,但其周长位于左侧簇南瓜周长值的中部。因此,我们预计南瓜重量的特征影响值对于点 A 会很高,但周长的特征影响值会很低。

现在,让我们来看看异常南瓜 B,情况正好相反。虽然南瓜 B 的重量位于数据集的中段,但南瓜 B 的周长比几乎任何其他数据点都要高。因此,对于南瓜 B,周长的特征影响值将高于重量的值。

每个点的特征影响是如何计算的?

在解释这些特征影响分数时,了解计算中具体包含的内容通常很有帮助。让我们暂时回到我们的二维南瓜数据集,以说明计算特征影响所涉及的步骤。我们想要确定的是,特定特征,比如南瓜的重量X,对最终异常值分数的影响有多大。尝试量化这种影响的一种自然方式是想象我们根本不包括这个特征在我们的异常值计算中。图 10.5展示了在我们的南瓜示例中这会是什么样子。对于每个南瓜数据点,我们将权重特征的值投影到一个固定的值 0,并观察每个数据点的异常性变化了多少。

图 10.5中我们可以看出,对于点 A,移除或投影权重值到 0,最终会导致点 A 变成一个内点。因此,我们可以得出结论,权重特征对点 A 的不寻常性有重大影响。另一方面,如果我们观察图 10.5右侧图中的点 B,我们可以看到,由于将其权重特征投影到 0,其不寻常性没有改变。因此,我们可以得出结论,权重特征值对点 B 的不寻常性影响不大,因此其特征影响值将较低:

![图 10.5 – 通过询问如果将给定特征投影到固定值或移除,给定数据点的异常性将如何变化来计算特征影响图片 B17040_10_005.jpg

图 10.5 – 通过询问如果将给定特征投影到固定值或移除,给定数据点的异常性将如何变化来计算特征影响

异常值检测与异常检测有何不同?

在阅读本章时,你可能已经注意到异常检测和异常检测都是无监督学习方法,它们试图实现一个相似的目标:找到不寻常或异常的数据点。那么一个自然的问题就是询问,异常检测与异常检测有何不同? 在本节中,我们将概述并解释这两种方法之间的主要区别。主要观点的总结见图 10.6,我们很快就会看到。

基于概率模型与基于实例

为了在我们心中更清晰地区分异常检测和异常检测,让我们首先简要地看一下可用的异常检测方法。异常检测功能使我们能够检测基于时间序列数据中的异常特征。它是通过将时间序列分割成离散的时间单元,称为,然后对桶中的单个值应用检测函数(如平均值或总和)来实现的。然后,每个桶的值被用作概率分布中的单个数据点,该概率分布随着异常检测器看到越来越多的数据而持续更新。在概率分布下发生概率低的桶被标记为异常。

与构建一个追踪数据随时间演变的概率模型不同,异常检测使用一组四种技术——两种基于距离的技术和两种基于密度的技术,这些技术在前面的章节中已有介绍。数据点与数据集中一般数据质量越远,它成为异常值的可能性就越大。对于数据集没有构建概率模型。

评分

这两种技术之间的主要差异导致我们在评分上也有差异。在异常检测中,桶的异常性是通过异常检测器从数据中学到的模型下发生的不可能性来确定的。概率越低,桶的异常性就越大。

相反,在异常检测中,我们计算异常分数,而不是概率。异常分数是一个从 0 到 1 的连续度量,它捕捉了给定数据点与整个数据集中一般数据质量距离的汇总度量。正如我们在本章前面所看到的,这个度量是通过四种不同的技术来计算的。异常分数越高,数据点在数据集中的异常或不寻常程度就越高。

数据特征

除了评分之外,这两种技术之间的另一个主要区别是它们旨在处理的数据类型。异常检测仅适用于时间序列数据,而异常检测可以用于单维或多维数据集,这些数据集可能包含或不包含基于时间组件。

在线与批量

最后,这两种无监督学习技术之间最后一个主要区别是,当新数据被索引时,它们对更新的适应性。熟悉异常检测的用户会知道,这种技术非常适合流数据。一旦新数据桶到达集群并被处理,概率模型就可以更新以反映新数据。

与异常检测不同,离群点检测不能像异常检测那样进行在线更新。如果一组新数据点被摄入源索引,我们必须再次在源索引上重新运行离群点检测作业。原因在于,离群点检测是一种基于实例的方法,它使用数据点的空间和密度分布来确定哪些是正常的,哪些是离群点。任何被摄入源索引的新点都可能改变数据的空间分布,以至于之前被分类为离群点的点将不再是离群点,因此,对新数据的重新评估需要重新计算整个数据集的离群点分数。在以下表格之前添加一个引言句。

图 10.6 – 异常检测与离群点检测的主要区别概述

图 10.6 – 异常检测与离群点检测的主要区别概述

实际应用离群点检测

在本节中,我们将通过一个使用描述葡萄酒物理化学性质的公共数据集的离群点检测的实际示例来探讨。此数据集可从加州大学欧文分校(UCI)的存储库下载(archive.ics.uci.edu/ml/datasets/wine+quality)。

葡萄酒数据集由两个 CSV 文件组成:一个描述白葡萄酒的物理化学性质,另一个描述红葡萄酒的性质。在本教程中,我们将重点关注白葡萄酒数据集,但您也可以使用红葡萄酒的数据,因为本章中描述的大多数步骤都适用于两者。

首先,让我们使用 winequality-white 将数据集导入我们的 Elasticsearch 集群:

![图 10.7 – 数据可视化工具可在 Kibana 的机器学习应用中找到,便于导入用于实验的小型数据文件]

![图 10.7 – 数据可视化工具可在 Kibana 的机器学习应用中找到,便于导入用于实验的小型数据文件]

图 10.7 – 数据可视化工具可在 Kibana 的机器学习应用中找到,便于导入用于实验的小型数据文件

简单查看 发现 选项卡中的数据将显示,每份文档代表一款单独的葡萄酒,并包含有关酒精含量、酸度、pH 值和糖含量等化学测量值的信息,以及由人类品酒师分配的定性评分。我们调查的目标将是使用异常检测来检测哪些葡萄酒在化学成分方面是异常的,然后看看这是否与人类品酒师给出的评分相关。我们的假设是,在化学成分方面不寻常的葡萄酒也将是质量评分方面的异常者。按照以下步骤进行操作以探索这个假设:

  1. 让我们从使用如图 图 10.8 所示的 数据帧分析 向导创建异常检测作业开始:![图 10.8 – 使用数据帧分析向导创建异常检测作业。 img/B17040_10_008.jpg

    图 10.8 – 使用数据帧分析向导创建异常检测作业。

  2. 因为我们对比较化学成分异常的葡萄酒和评分异常的葡萄酒感兴趣,所以我们将从异常检测作业中排除评分,如图 图 10.9 所示:![图 10.9 – 通过取消选中字段名称旁边的框来排除异常检测作业中的评分 img/B17040_10_009.jpg

    图 10.9 – 通过取消选中字段名称旁边的框来排除异常检测作业中的评分

  3. 我们将使用默认设置配置其余的配置选项。一旦作业完成,我们可以使用 数据帧分析 结果查看器检查结果:![图 10.10 – 使用数据帧分析作业管理 UI 查看异常检测作业何时完成 img/B17040_10_010.jpg

    图 10.10 – 使用数据帧分析作业管理 UI 查看异常检测作业何时完成

  4. ml.outlier_score 的视图。异常分数是一个介于 0 和 1 之间的浮点值,它捕捉了给定数据点相对于数据集的异常程度。一个给定点得分越接近 1,它就越异常,反之亦然。

    表格中剩余的列显示了数据集中其他字段选择值的数值。每个单元格根据从 0 到 1 的渐变值着色,这捕捉了特征影响,换句话说,特征在确定数据点异常性方面的重要性。一个给定单元格的蓝色越深,该特征对点异常性的重要性就越大。

    例如,通过查看 图 10.11 中的 ml.outlier_score 列的值,我们可以看到数据集中最不寻常的四款葡萄酒各自得分为 0.998:

    ![图 10.11 – 异常检测的结果 UI 显示一个摘要表,捕获每个数据点的异常评分以及一些字段(按字母顺序排序)用特征影响评分着色 图片

    图 10.11 – 异常检测的结果 UI 显示一个摘要表,捕获每个数据点的异常评分以及一些字段(按字母顺序排序)用特征影响评分着色

    重要提示

    在这个阶段提出的一个有趣的问题是我们宣布一个点为异常的阈值是多少?我们是说所有得分高于 0.5 的点都是异常点吗?或者我们设置一个更保守的阈值,只说得分高于 0.9 的点才是异常点?将连续得分设置为阈值的过程,将每个数据点分类为正常或异常,称为二值化,通常是通过结合领域知识和用户的目标来确定的。然而,在有标签的数据集(例如,每个数据点已经用正常/异常的地面真实值标记的数据集)的情况下,可以执行一个稍微更系统的过程来选择阈值。我们将在下一节中回到这个话题,当我们查看评估 API 时。

  5. 接下来,让我们回到结果用户界面,看看单元格的阴影,看看我们是否能从中获取一些关于哪些因素使某种葡萄酒不寻常的有趣信息。我们可以在 UI 中切换隐藏列开关,添加所有剩余的特征,以便我们可以看到最顶层异常数据点的特征影响的全貌,如图图 10.12所示:![图 10.12 – 在葡萄酒质量数据集的所有字段中显示的特征影响 图片

    图 10.12 – 在葡萄酒质量数据集的所有字段中显示的特征影响

    如我们从注释区域中可以看到的,异常葡萄酒因不同的原因而显得不寻常。对于第一个数据点,特征影响得分最高的字段是氯化物和柠檬酸含量,而对于接下来的三个点,似乎最重要的特征是密度和葡萄酒的 pH 值。

  6. 最后,我们可以回到本节开头提出的问题。葡萄酒的不寻常性与人类品酒师分配给它的定量质量评分相关吗?为了看看我们是否能快速地发现任何相关性,让我们将质量评分添加到数据集中,与异常评分并列(图 10.13):

![图 10.13 – 按异常评分降序排列的最不寻常的白葡萄酒,以及人类品酒师分配的定性质量评分图片

图 10.13 – 按异常评分降序排列的最不寻常的白葡萄酒,以及人类品酒师分配的定性质量评分

如我们所见,前 10 个最异常的白葡萄酒在最佳类别(质量分数为 9)中的得分并不高。相反,它们大多数得分在 3-6 的较低范围内。虽然这并不是决定性的证据,但我们有理由相信,最化学异常的葡萄酒通常不是最好喝的!

使用 Evaluate API 评估异常检测

在上一节中,我们提到了用户可能很难知道如何设置异常分数的阈值,以便将数据集中的数据点分组到正常和异常类别中。在本节中,我们将展示如果你有一个标记的数据集,其中每个点都包含记录该点是否为异常的地面真实值,如何处理这个问题。在我们深入实际演示之前,让我们花一点时间来了解一些在评估异常检测算法性能时使用的关键性能指标。

我们可以衡量算法性能的最简单方法之一是计算它正确预测为异常值的数据点的数量;换句话说,就是真正例TPs)的数量。此外,我们还想了解真正例TNs)的数量:有多少正常数据点被正确预测为正常。通过扩展,我们还想记录异常检测算法犯两种可能错误之一的次数:要么将正常点错误标记为异常(假正例FPs)),要么相反(假反例FNs))。

这四个度量可以方便地总结在一个称为混淆矩阵的表中。一个示例混淆矩阵显示在图 10.14中:

![图 10.14 – 显示真正例、真正例、假正例和假反例率的混淆矩阵img/B17040_10_014.jpg

图 10.14 – 显示真正例、真正例、假正例和假反例率的混淆矩阵

以下两个度量,精确度召回率,可以建立在前面描述的四个度量之上。

精确度是所有预测为正或异常的点中真正例的比例。另一方面,召回率是所有实际为正的点中真正例的比例。这些量可以用以下方程概括:

img/Formula_10_001.jpgimg/Formula_10_002.jpg

根据前面段落中给出的定义,似乎为了计算真正例、真正例等数量,我们需要将我们的目标索引中的每个点分配一个类别标签。

然而,异常检测作业的结果,正如我们在几个段落之前所描述的,不是一个将每个点分配为异常或正常的二元类别标签,而是一个范围从 0 到 1 的数值异常分数。

当涉及到计算我们所需的指标时,这给我们带来了一个问题,因为我们必须做出决定并指定一个截止点。所有得分高于截止点的都被分配到异常类别,而所有得分低于截止点的都被分配到正常类别。我们将此称为二值化阈值

确定设置此阈值的准确值可能具有挑战性,这让我们回到了本章的原始目标——使用 Evaluate API 来理解不同阈值下的不同性能指标,以便我们可以做出明智的选择。

现在我们来进行一次实际操作,看看我们如何将所学知识应用于实践:

  1. 让我们检查我们将用于本节的公共数据集。原始数据集的来源是 UCI 仓库,请参阅此处:archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+%28Diagnostic%29。为了更好地适应本练习的目的,我们略微修改了数据集,创建了一个名为 Outlier 的新字段,该字段记录了给定的数据点是否为异常值。修改后的数据集在一个名为breast-cancer-wisconsin-outlier.csv的文件中,可在本书的 GitHub 仓库中下载,请参阅此处 github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2010%20-%20Outlier%20Detection%20Analysis

    下载此数据集后,您可以使用数据可视化器中的数据导入功能导入数据集。有关数据可视化器和如何导入数据的复习,请参阅应用异常检测实践的章节。

    该数据集描述了从恶性和良性乳腺癌组织中测量的特征,并包含一个类别字段,该字段可以取值为 2(良性)或 4(恶性)。在本节的目的上,我们将标记为恶性的数据点视为异常值:

    ![图 10.15 – 数据集中的每个数据点都标记了类别标签。我们将类别标签转换为布尔标签,并存储在名为 Outlier 的新字段中。]

    ![图片 B17040_10_015.jpg]

    图 10.15 – 数据集中的每个数据点都标记了类别标签。我们将类别标签转换为布尔标签,并存储在名为 Outlier 的新字段中。

    在这一点上值得提及的是,Evaluate API (www.elastic.co/guide/en/elasticsearch/reference/current/evaluate-dfanalytics.html),我们将使用它来了解异常检测算法与真实标签的匹配程度,要求真实标签为布尔值 0(表示正常数据点)和 1(表示异常数据点)。因此,我们对原始数据集进行了轻微调整,增加了一个名为 Outlier 的额外字段,将 Class 字段转换为 Evaluate API 可消费的合适格式。数据集的一个示例文档显示在 图 10.15 中。

  2. 让我们使用 Data Frame Analytics 向导使用此数据集创建一个异常检测作业。我们将排除包含 Class 标签的字段、包含真实标签的字段以及样本代码号,如图 图 10.16 所示:![Figure 10.16 – 从异常检测作业中排除 Class、Outlier 和 Sample_code_number 字段

    ![img/B17040_10_016.jpg]

    图 10.16 – 从异常检测作业中排除 Class、Outlier 和 Sample_code_number 字段

  3. 一旦作业完成,我们可以使用包含异常检测作业结果的目标索引以及 Evaluate API 来计算我们的异常检测算法与真实标签相比的表现如何。

  4. 我们将通过 actual_field 与 Evaluate API 进行交互。这是包含我们数据真实标签的字段。在我们的例子中,这是名为 Outlier 的字段。最后,我们继续定义我们希望 API 为我们返回的指标以及这些指标应该计算的阈值。

    REST API 调用中的参数允许我们指定各种可能的阈值或截止点,用于计算性能指标。在先前的例子中,我们要求 Evaluate API 在三个不同的二值化阈值(0.25、0.5 和 0.75)处返回性能指标值,但同样,我们也可以选择另一组值。

  5. 接下来,我们将检查 Evaluate API 返回的结果。响应如下:

    {
      "outlier_detection" : {
        "confusion_matrix" : {
          "0.25" : {
            "tp" : 0,
            "fp" : 15,
            "tn" : 429,
            "fn" : 239
          },
          "0.5" : {
            "tp" : 0,
            "fp" : 5,
            "tn" : 439,
            "fn" : 239
          },
          "0.75" : {
            "tp" : 0,
            "fp" : 1,
            "tn" : 443,
            "fn" : 239
          }
        },
        "precision" : {
          "0.25" : 0.0,
          "0.5" : 0.0,
          "0.75" : 0.0
        },
        "recall" : {
          "0.25" : 0.0,
          "0.5" : 0.0,
          "0.75" : 0.0
        }
      }
    }
    

    如我们所见,Evaluate API 返回了一个响应,其中每个指标都计算了三次——一次对应于 REST API 调用中指定的每个阈值。

混淆矩阵的不同值表明,在特定数据集方面,异常检测算法表现相当差。没有任何一个阈值产生任何真阳性,这意味着我们无法使用默认设置检测到任何异常。在下一节中,我们将看到如何通过 超参数调整 来帮助我们获得更好的异常检测结果。

异常检测的超参数调整

对于更高级的用户,数据帧分析向导提供了一个配置和调整超参数的机会 – 这些是微调异常检测算法工作方式的各种旋钮和开关。可用的超参数在图 10.17中显示。例如,我们可以指导异常检测作业仅使用某种类型的异常检测方法而不是集成方法,使用一定数量的最近邻值进行集成计算,并假设数据中有一部分是异常的。

请注意,虽然玩转这些设置以进行实验并了解它们如何影响最终结果是个好主意,但如果您想为生产用例定制这些设置,您应该仔细研究您数据的特点,并了解这些特点将如何与您选择的超参数设置相互作用。有关每个超参数的更多信息,请参阅此处文档www.elastic.co/guide/en/elasticsearch/reference/current/put-dfanalytics.html

在我们的案例中,我们知道数据集包含大约 30%的恶意样本。因此,我们预期的异常值数量也接近这个比例。我们可以将此配置为异常比例的值,并重新运行我们的作业。这如图10.17所示:

图 10.17 – 通过数据帧分析向导调整超参数,可以微调异常检测作业的行为

图 10.17 – 通过数据帧分析向导调整超参数,可以微调异常检测作业的行为

让我们使用这个新的超参数重新创建我们的异常检测作业,并将结果与图 10.17中的结果进行比较:

  1. 按照在评估异常检测与 Evaluate API 部分中创建我们的第一个异常检测作业时概述的步骤进行操作,但在超参数对话框中调整异常比例设置,如图10.17所示。创建并运行异常检测作业。

  2. 作业运行完成后,我们可以为这个新的结果索引重新运行 Evaluate API 命令。我们使用breast-cancer-wisconsin-outlier-fraction作为包含调整超参数作业结果的目标索引的名称。因此,我们新的 Evaluate API 调用如下:

    POST _ml/data_frame/_evaluate
    {
      "index": "breast-cancer-wisconsin-outlier-fraction", 
      "evaluation": {
        "outlier_detection": {
          "actual_field": "Outlier", 
          "predicted_probability_field": "ml.outlier_score",
          "metrics" : {
            "confusion_matrix" : {"at": [0.25, 0.5, 0.75]}, 
            "precision" : {"at": [0.25, 0.5, 0.75]}, 
            "recall" : {"at": [0.25, 0.5, 0.75]} 
          }
        }
      }
    }
    
  3. 让我们看看三个不同阈值下我们的混淆矩阵发生了多少变化。我们从 Evaluate API 收到的响应在这里显示:

    {
      "outlier_detection" : {
        "confusion_matrix" : {
          "0.25" : {
            "tp" : 239,
            "fp" : 210,
            "tn" : 234,
            "fn" : 0
          },
          "0.5" : {
            "tp" : 86,
            "fp" : 48,
            "tn" : 396,
            "fn" : 153
          },
          "0.75" : {
            "tp" : 0,
            "fp" : 13,
            "tn" : 431,
            "fn" : 239
          }
        },
        "precision" : {
          "0.25" : 0.532293986636971,
          "0.5" : 0.6417910447761194,
          "0.75" : 0.0
        },
        "recall" : {
          "0.25" : 1.0,
          "0.5" : 0.3598326359832636,
          "0.75" : 0.0
        }
      }
    }
    

    从混淆矩阵的值中我们可以看出,我们在检测真正阳性、真正异常值方面做得稍微好一些,但在检测假阴性方面稍微差一些。

将先前异常值检测作业的评估指标与我们创建的“使用 Evaluate API 评估异常值检测”部分中的异常值检测作业进行比较,可以说明超参数选择对异常值检测作业的结果有重大影响。那么,在选择合理的超参数时,你应该如何进行?

在选择超参数时,有许多细微之处和高级主题可以深入研究,但一个好的指导原则是记住过程的迭代性质。从标记数据集开始并使用默认设置(换句话说,在数据帧分析向导的“超参数”对话框中不进行任何调整)尝试结果质量是一个好主意。

默认值通常已经合理选择并测试过多种数据集。如果结果质量不满意,你可以开始制定一个计划来调整和微调各种超参数设置。一个好的第一步是修复已知问题并检查这如何影响结果质量。例如,我们根据以往的经验知道,乳腺癌数据集包含大约 30%或 0.3 的异常值,这使我们能够调整此设置并实现略微更好的真正阳性率。

摘要

为了结束本章,让我们回顾一下 Elastic Stack 中第二个无监督学习特征的要点:异常值检测。异常值检测可以用来检测单维或多维数据集中的异常数据点。

该算法基于四个独立的度量:两个基于 k 近邻的距离度量,以及两个基于密度的度量。这些度量的组合捕捉了给定数据点与其邻居以及数据集总体数据质量之间的距离。这种异常性通过一个介于 0 到 1 之间的数值异常值分数来捕捉。给定数据点的分数越接近 1,它在数据集中的异常性就越大。

除了异常值分数之外,对于每个点的每个特征或字段,我们计算一个称为特征影响力的量。对于给定字段的特征影响力越高,该字段对给定点异常性的贡献就越大。这些特征影响力分数可以用来理解为什么某个特定点得到了特定的异常值分数。

与 Elastic Stack 中其他无监督学习功能(异常检测)相比,离群值检测不需要数据具有时间组件或任何类型的时间序列。此外,与异常检测不同,离群值检测不是通过学习概率模型来理解哪些数据点发生的概率较低。相反,它使用基于距离和密度的度量来计算异常性。由于这种方法上的差异,离群值检测无法以在线方式运行,无法在将新数据添加到源索引时即时更新其离群值计算。相反,如果我们希望在将新点添加到索引时预测其异常性,我们必须以批量模式重新运行整个源索引的离群值检测计算。

在下一章中,我们将放下无监督学习方法,深入探索令人兴奋的监督学习世界,从分类开始。

第十一章:分类分析

当我们谈论机器学习领域以及具体的机器学习算法类型时,我们往往会提到三种不同类别的算法的分类法:监督学习、无监督学习和强化学习。第三种超出了本书的范围以及当前 Elastic Stack 可用的功能,而第二种在异常检测章节以及之前的离群值检测章节中一直是我们的研究主题。在本章中,我们最终将开始涉足监督学习的世界。Elastic Stack 提供两种监督学习类型:分类和回归。本章将致力于理解前者,而随后的章节将解决后者。

监督学习的目标是接受一个标记的数据集,从中提取模式,将数据集获得的知识编码在我们称之为模型的结构中,然后使用这个训练好的模型对之前未见过的数据实例进行预测。这在分类和回归中都很常见。前者用于预测离散标签或类别,而后者用于连续值。

分类问题无处不在。一位病理学家在观察患者样本时,被要求将每个样本分类为恶性或良性,一位装配线工人观察机器部件时,被要求将每个部件分类为故障或功能正常,一位业务分析师在观察客户流失数据时,试图预测客户是否会续订或取消一项服务的订阅,等等。

了解分类及其在 Elastic Stack 中的工作原理的过程,也会让我们接触到许多其他主题,对这些主题的理解对于任何希望充分利用分类来解决问题的从业者来说都是相关的。这些主题包括特征工程、将数据集划分为训练集和测试集、理解如何衡量分类器的性能以及为什么相同的性能指标在应用于训练集时与应用于测试集时测量的结果不同,如何使用特征重要性来理解每个特征如何贡献到模型分配给数据点的类别标签,以及许多其他将在本章中讨论的内容。

在本章中,我们将涵盖以下主题:

  • 分类:从数据到训练好的模型

  • 开始使用分类

  • 分类内部机制:梯度提升决策树

  • 超参数

  • 解释结果

技术要求

本章内容需要 Elasticsearch 7.9+。示例已使用 Elasticsearch 版本 7.10.1 进行测试,但应适用于任何高于 7.9 版本的 Elasticsearch。请注意,运行本章中的示例需要铂金许可证。如果某个特定示例或部分需要 Elasticsearch 的更高版本,文本中将会提及。

分类:从数据到训练模型

从源数据集训练分类模型的过程是一个多步骤的过程,涉及许多步骤。在本节中,我们将从图 11.1(图中所示)的角度概述整个过程,该过程从标记的训练数据集开始(图 11.1部分 A)。

![图 11.1 – 监督学习过程概述,该过程接受标记数据集并输出训练模型

![img/B17040_11_1.jpg]

图 11.1 – 监督学习过程概述,该过程接受标记数据集并输出训练模型

这个训练数据集通常被分为训练部分,这部分将被输入到训练算法中(图 11.1部分 B)。训练算法的输出是一个训练模型(图 11.1部分 C)。然后,使用训练模型对测试数据集进行分类(图 11.1,部分 D),该测试数据集最初是从整个数据集中分离出来的。模型在测试数据集上的性能被捕获在一系列评估指标中,这些指标可以用来确定模型是否足够泛化,以适应之前未见过的示例。

本章中,我们将进一步阐述这些步骤,通过本章提供的实际操作演示。为了将实际示例与机器学习的更理论方面联系起来,我们还将介绍对监督学习的概念理解。这将引导我们进入关于特征工程——什么是特征以及它们如何影响分类器性能的讨论。最后,我们将探讨如何评估分类器的性能以及如何通过将数据分为训练集和测试集来帮助我们衡量两种不同的性能。

分类模型从数据中学习

机器学习社区中有一个常见的说法,即机器学习分类器是一种从数据中学习的软件工件。软件工件从数据中学习究竟意味着什么呢?一种思考方式是,当模型在看到或经历越来越多的数据时,它越来越擅长进行分类(在分类模型的情况下),这时学习就发生了。但毕竟,机器学习模型不是生物体,那么当我们说模型从数据中学习时,我们究竟指的是什么呢?

这个问题的答案将引导我们到重要的决策边界概念。让我们通过一个记录南瓜重量和周长的虚构二维数据集来检验这个概念。假设我们的数据集包含不同变种南瓜的测量值,我们的目标是教会我们的分类模型根据南瓜的重量和周长将南瓜分类到不同的变种中。

如果我们将南瓜数据集绘制在二维空间中,它可能看起来像图 11.2

![Figure 11.2 – 一个虚构的二维数据集,描述了两种不同类型南瓜的周长和重量测量,这些南瓜分别由圆圈和正方形表示img/B17040_11_2.jpg

图 11.2 – 一个虚构的二维数据集,描述了两种不同类型南瓜的周长和重量测量,这些南瓜分别由圆圈和正方形表示

重量位于水平轴上,周长位于垂直轴上。如果一个数据点由一个圆表示,它代表一个第一变种的南瓜。如果由一个正方形表示,它代表另一个变种的南瓜。花点时间看看图 11.2中正方形和圆的分布,并想象你需要创建一个简单的规则来区分方形变种南瓜和圆形变种南瓜,并以此方式记录下来,以便当另一个人在以后的时间查看你的绘图时,他们可以使用它来确定他们刚刚测量的新南瓜属于两个变种中的哪一个。

完成这个任务的一个非常简单的方法是检查你面前的二维表示,并用笔画一条线,大致将方形变种与圆形变种分开,例如,如图11.3所示。

![Figure 11.3 – 一个决策边界将两个南瓜类别——在二维空间中以数据点表示——彼此分开。img/B17040_11_3.jpg

图 11.3 – 一个决策边界将两个南瓜类别(在二维空间中以数据点表示)彼此分开。

一些数据点最终会落在线的错误一侧,但这只是设计分类器的必然性质——很少的分类最终是完美的!你刚刚记录的是决策边界——一条线或超维平面(如果我们正在处理具有多个变量的数据集),它将一个类别的成员与另一个类别的成员分开。现在,任何想要对新的测量南瓜进行分类的人都可以拿起你的图表,并在图表上绘制他们自己南瓜的测量值,以查看他们的数据点落在决策边界的哪一侧,如图11.4所示。

![Figure 11.4 – 由三角形表示的新数据点落在决策边界的右侧,因此很可能是方形变种的一部分img/B17040_11_4.jpg

图 11.4 – 由三角形表示的新数据点落在决策边界的右侧,因此很可能是正方形变体的一个部分

这是对分类算法所采取过程的近似概念。它们接受一组训练数据(我们的原始南瓜测量值以及每个测量南瓜所属的变体)并应用各种测试和转换来学习决策边界。从训练数据中学习的过程称为训练,这不仅适用于分类算法,也适用于回归算法。这个边界被编码在训练模型中,然后可以用来对未来的、之前未见过的数据点进行预测。

从上述虚构的例子中要注意的一点是,我们相当幸运。我们的数据(南瓜重量与南瓜周长的二维图)产生了一幅图,我们可以轻松地画出决策边界,虽然并不完美,但对于大多数数据点来说,结果仍然很好。然而,在现实世界中,很少有数据集会如此整洁地适合机器学习分析,而且正如我们将学到的,我们选择用来表示数据点的属性可以极大地影响数据集的可分离性,从而影响分类模型在其上的表现。

假设我们不是使用南瓜的特征重量和周长,而是选择了在南瓜中发现化学化合物 X 的百分比以及生长过程中用于灌溉的水量。在这种情况下,使用这些特征的数据点的二维图(如图 11.5 所示)根本无法画出任何类型的决策边界!

图 11.5 – 这些特征并没有为两个类别提供有意义的分离

图片 B17040_11_5.jpg

图 11.5 – 这些特征并没有为两个类别提供有意义的分离

正如我们将在下一节中看到的,特征工程是一个庞大的主题,也是预处理的主要步骤之一,在开始任何机器学习项目时都应予以考虑,无论是在 Elastic Stack 中还是在其他地方。

特征工程

在上一节中,我们通过一个虚构的南瓜数据集说明了决策边界的概念,这是分类器从数据中学习的结果。现在让我们通过一个更现实的例子来探讨下一个主题,即如何选择和操纵特征以便它们适合分类:恶意软件分类。由于几乎每天都有数百万新的恶意软件变种被发布到野外,传统的基于规则的分类方法很难将良性二进制文件与恶意二进制文件区分开来并保持有效性。因为我们处理的是大量数据和各种输入,这些输入无法用僵化的规则来捕捉,因此机器学习为这个问题提供了一个完美的解决方案。我们究竟需要如何预处理训练数据——在这个例子中是恶意和良性二进制文件——以便机器学习算法能够理解它们。这个问题及其答案将我们引向机器学习中的一个子领域,称为特征工程。

特征工程的过程涉及将领域专家对问题的了解应用于训练数据。例如,一个恶意软件分析师可能能够告诉我们,正常二进制文件中的字符串通常比恶意二进制文件中的字符串长,这可以作为区分两者的一个有用特征。作为特征工程过程的一部分,我们随后开发了一种方法来计算训练数据中每个二进制文件的平均字符串长度,并将其用作特征。

最好留出一些时间来了解知识领域专家对问题的了解以及为了实现最先进的结果所使用的特征。这很重要,因为正如我们在上面的虚构南瓜例子中所看到的,特征的选择决定了我们的模型是否能够学会区分两个类别的特征。例如,假设我们训练我们的恶意软件分类模型使用二进制文件的大小(以字节为单位)和二进制文件名称中字母 a 的存在作为特征。如果结果是恶意和良性二进制文件在字节大小上表现出相似性,并且两者名称中都包含字母 a,那么我们得到的分类器将毫无用处,因为它会关注那些在区分恶意二进制文件和良性二进制文件时没有差别的特征。

特征工程通常涉及迭代。我们应该从一个可能产生良好结果的特征的猜测开始,训练一个模型,并在测试集上评估它,然后逐渐添加、减少或操纵特征,直到达到期望的结果质量。在章节的后面部分,我们将探讨如何精确地衡量模型的性能,以及如何使用 Elastic Stack 中的机器学习来实现这一点。

评估模型

在本节分类的介绍中,我们将讨论的最后一个主题是评估。我们如何知道我们的模型表现如何?有各种方法可以量化模型性能,我们将在本章的后续部分详细探讨这些技术和它们的意义。与衡量模型性能相关的一个重要概念是性能是在什么数据上衡量的。

为了衡量一个模型的表现如何,我们需要使用该模型在标记的数据集上进行预测,以便我们可以将模型预测的类别标签与真实标签进行比较,并计算模型犯了多少错误。这些数据集之一是训练数据,但如果我们使用这些数据来估计模型的表现,我们实际上是在向模型展示我们在训练时使用的数据。虽然这样做会给我们一个很好的训练误差估计,但它不会告诉我们模型在预测先前未见过的例子时表现如何。

为了估计模型在模型之前未见过的数据上的表现,我们必须将一部分训练数据留出。这部分数据,也常被称为测试数据集,不会用于模型的训练。相反,在训练过程完成后,我们将使用模型在测试数据集上进行预测,并查看测试数据集中有多少数据点被错误分类。这个度量将给我们一个关于模型在预测先前未见过的数据点时表现如何的印象。模型在预测这个数据集时犯的错误数量被称为泛化误差

将几个指标如准确率精确度召回率(其中一些我们在第十章异常检测中见过)用于衡量这些误差。请参考第十章使用 Evaluate API 评估异常检测的部分,以快速回顾基本概念。

我们将在后面的章节中更详细地讨论上述探索的每个想法,但就目前而言,让我们看看这些概念在实际中是如何发挥作用的。我们将使用威斯康星州乳腺癌公共数据集来创建一个示例分类数据帧分析作业。

开始进行分类的第一步

在本节中,我们将使用公开的威斯康星州乳腺癌数据集创建一个示例分类作业。原始数据集在此处可用:(archive.ics.uci.edu/ml/datasets/breast+cancer+wisconsin+(original)). 对于这个练习,我们将使用数据集的一个稍微清洗过的版本,这将消除数据清洗(机器学习项目生命周期中的重要步骤,但不是本书中我们有机会讨论的内容)的需要,并使我们能够专注于创建分类作业的基本知识:

  1. 从本书 GitHub 存储库中的“第十一章 - 分类分析”文件夹下载清洗过的数据集文件breast-cancer-wisconsin-outlier.csvgithub.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2011%20-%20Classification%20Analysis),并将其存储在您的本地机器上。在您的 Kibana 实例中,从左侧菜单导航到Machine Learning 应用程序,并点击数据可视化器标签。这将带您进入文件 上传器。点击上传文件并选择您下载的 CSV 文件。

    上传成功后(在发现下检查您的索引模式,并简要浏览一些文档以确保数据集一切正常),返回到机器学习应用程序,而不是选择数据可视化器标签,选择数据帧分析。这应该会显示类似于图 11.6中显示的视图。

    ![图 11.6 – 此数据帧分析作业概览目前为空 图 11.7 – 数据帧分析向导帮助创建三种不同类型的作业。对于我们的当前案例,选择分类

    图 11.6 – 当前此数据帧分析作业概览为空

  2. 点击蓝色创建作业按钮。这将带您进入数据帧分析向导,它有点类似于我们在第九章,“介绍数据帧分析”中介绍的转换向导,并允许您轻松创建分类、回归或异常检测作业。在这种情况下,我们将在图 11.7中的下拉菜单中选择分类。

    图 11.7 – 数据帧分析向导帮助创建三种不同类型的作业。对于我们的当前案例,选择分类

    图 11.7 – 数据帧分析向导帮助创建三种不同类型的作业。对于我们的当前案例,选择分类

  3. 接下来,我们将继续选择因变量。如果您还记得我们之前的讨论,分类的目标是学会预测一个给定的、之前未见过的数据点属于哪个类别。表示这个类别的变量被称为Class变量,因此我们从下拉菜单中选择它。

    一旦我们做出选择,向导将显示一个包含和排除字段列表。请注意这里。如果您的数据集中存在名为Class的字段。如果样本数量少于 30,则预测为恶性。如果不是,则预测为良性。因此,谨慎行事,提前检查数据,然后删除所有预期不会携带有助于预测因变量的有意义信息的变量是很好的。

    即使表示样本数量的变量不携带这样的代理效应,我们也不期望它能传达很多有助于推断因变量的有意义信息,因此,它只会增加我们任务的内存负担,最好从一开始就排除它。

    如您在图 11.8中看到的,我们已经通过取消勾选复选框排除了变量Sample_code_number

    ![图 11.8 – 从分类任务中排除 Sample_code_number 以避免引入代理效应

    ![图片/B17040_11_8.jpg]

    图 11.8 – 从分类任务中排除 Sample_code_number 以避免引入代理效应

  4. 在此之后,我们就可以继续配置的下一部分,这涉及到选择训练百分比。回想一下,在本节的开头,我们讨论了分类器的性能如何可以在从父数据集派生出的两个不同的数据集上评估:训练数据集和测试数据集。在 Elasticsearch 中手动进行这种拆分可能会很繁琐,尤其是当处理大型数据集时。为了使这个过程更容易,DataFrame Analytics 作业向导包括一个配置选项,允许我们设置我们想要为训练模型保留的数据集比例以及我们想要为测试模型保留的数据集比例。这个数量是通过向导中显示的滑块来设置的,如图 11.9所示。![图 11.9 – 训练百分比滑块

    ![图片/B17040_11_9.jpg]

    图 11.9 – 训练百分比滑块

    应该如何确定训练百分比?正如在构建机器学习程序的许多阶段一样,这个答案将很大程度上取决于您数据集的大小以及您是否更关心对训练数据集还是测试数据集的性能进行准确估计。我们将在稍后更详细地讨论这两种性能指标之间的差异,但就目前而言,可以说,模型在测试数据集上的性能为您提供了模型一旦开始应用于先前未见过的示例时的性能估计,因此,在大多数情况下,将一部分数据用于估计模型部署到生产环境后的性能是非常值得的。

    另一个方面是数据集的大小。一般来说,如果你的数据集包含超过 100,000 个文档,开始时使用较小的训练百分比——大约 10%或 15%,然后根据结果的质量进行迭代。

    由于在这种情况下,整个数据集只有不到 700 个文档,我们将训练百分比设置为 60%。

  5. 在选择训练百分比后,向导将引导我们进入附加选项,如图图 11.10所示。我们现在将保留这些默认设置,但将在后续的更高级示例中返回讨论它们。![Figure 11.10 – Additional options for classification Data Frame Analytics jobs

    ![img/B17040_11_10.jpg]

    图 11.10 – Additional options for classification Data Frame Analytics jobs

  6. 最后,在点击继续后,我们将设置作业 ID,并将其他所有内容保留为默认设置。勾选立即开始复选框,最后通过点击创建按钮创建并启动作业。

    返回到数据帧分析主页面。你应该能在作业管理面板中看到你刚刚创建的作业,如图图 11.11所示。

    ![Figure 11.11 – The Data frame analytics job overview panel shows a summary of the current Data frame analytics jobs

    ![img/B17040_11_11.jpg]

    图 11.11 – The Data frame analytics job overview panel shows a summary of the current Data frame analytics jobs

    一旦作业状态显示为停止,并且进度条显示表明作业的所有阶段都已完成,使用操作菜单导航到结果。这是通过在菜单中点击查看来实现的。

  7. 结果页面显示了大量的信息,这些信息对于理解我们在数据集上训练的分类器表现至关重要。一个示例结果页面如图图 11.12所示。

    当你查看这个图表时,注意一下analyzed_fields部分,你会看到一个名为Outlier的字段。这是我们根据第十章异常值检测中的Class字段创建的重复字段。这意味着它也是一个使我们的结果看起来比实际情况更好的代理字段。

  8. 让我们重新创建一个重复的作业,但排除字段Outlier和字段Sample_code_number。在这个作业完成后,测试数据集上的新结果看起来像图 11.14

![Figure 11.14 – The confusion matrix displays the results from the classification job after excluding the Outlier variable

![img/B17040_11_14.jpg]

图 11.14 – 混淆矩阵显示了在排除异常值变量后的分类作业结果

图 11.14所示,从结果中我们可以看到,排除Outlier变量和Sample_code_number变量后,真正阳性率达到了 98%,以及一些假阳性假阴性,这比我们之前完美的分类评分更接近实际情况。

在我们进一步探讨分类问题之前,了解当我们训练分类模型时,Elastic 机器学习堆栈内部到底发生了什么,将是非常有益的。这就是下一节将致力于解决的问题:理解决策树是如何在内部工作的。

内部工作原理:梯度提升决策树

对于分类任务,最终目标是解决一个需要我们尝试推断未见数据点属于几个可能类别之一的问题。我们通过使用包含代表性数据点的标记训练数据集,提取允许我们学习决策边界的相关特征,然后将关于这个决策边界的知识编码到分类模型中来实现这一点。然后,该模型对给定数据点属于哪个类别做出决定。模型是如何学习做到这一点的?这是我们将在本节中尝试回答的问题。

按照我们在整本书中的习惯,让我们首先从概念上探索人类用来导航一系列复杂决策的工具。许多人在涉及多个可能复杂的因素时,都曾使用过的一个熟悉工具是流程图。图 11.15显示了一个示例流程图,人们可能会构建这个流程图,以便在知道当天的天气条件下,决定穿哪种夹克。

图 11.15 – 一个基本的流程图

图 11.15 – 一个基本的流程图

在每个阶段,流程图都会提出一个问题(例如天气是暖和还是寒冷,或者是否在下雨)并根据我们的回答,将我们引导到流程图的另一部分和一组新问题。最终,通过在流程图中回答问题并遵循流程,我们得出一个决定或类别标签。

作为 Elastic Stack 训练过程的一部分产生的模型,在概念上,做的是非常相似的事情。在机器学习领域,这个算法被称为决策树。尽管 Elastic ML 堆栈中使用的版本比这里描述的要复杂得多,但基本概念是适用的。

让我们更仔细地看看决策树。

决策树简介

我们是如何构建决策树的?我们首先通过某个特征和某个阈值将数据集分为两组。我们确定这个特征和这个阈值的方法是查看所有可用的特征(或者如果我们想使用 Elasticsearch 术语,则是字段),然后找到产生最纯分割的那个特征阈值对。我们所说的最纯分割是什么意思?为了理解节点纯度的概念以及它如何影响决策树的构建和随后的决策树对新未见数据点的分类使用,我们必须退后一步,审视整体情况。

如你所回忆,我们的目标是设计一个决策流程图,当我们需要分类一个新未知数据点时可以遍历它。我们在流程图中确定分类的方式是通过查看我们的遍历最终到达的最终节点。在决策树中,这个最终节点被称为叶节点,它由所有在训练数据中由于使用特定特征和特定阈值进行的连续分裂而最终到达那里的数据点组成。由于分类过程通常是不完美的,终端叶节点不可能只包含属于一个类别的数据点,而将是混合的。这种混合的程度可以通过各种度量来量化,称为节点的纯度或叶节点的纯度。只包含一个类别的数据点的叶节点或节点是最纯的。

在决策树中拥有纯节点是非常好的,因为这意味着一旦我们达到了“流程图”中的终端节点,我们可以相当自信地认为我们目前正在尝试分类的数据点属于与终端节点中已有的数据点相同的类别。在实践中,优化纯节点可能会导致决策树过度分裂(毕竟,我们可以通过简单地创建与数据点数量一样多的叶节点来衡量这个指标——这样,每个叶节点将恰好包含一个确切类别的确切数据点,从而具有完美的纯度),因此叶节点总是混合的。

计算叶节点中属于给定类别的数据点的比例,可以给我们一个估计,即我们正在尝试分类的数据点属于该类别的概率。例如,假设我们有一个决策树来将数据点分类到类别 A 和类别 B。其中一个终端节点有 80 个来自类别 A 的示例和 20 个来自类别 B 的示例。在分类过程中,如果我们的新数据点最终到达这个节点,那么它属于类别 A 的概率就是 0.8。

这当然是对实际发生情况的一种简化表示,但应该能帮助你从概念上理解决策树是如何工作的。

梯度提升决策树

通常,单个决策树本身不会产生强大的分类器。这就是为什么多年来,数据科学家和机器学习从业者发现,如果结合特殊的训练方案,基于树的分类器可以非常强大。其中一种方案被称为提升。

不深入技术细节,提升的过程是训练一系列决策树,并且每个决策树都会改进前一个决策树。提升过程通过选择前一个决策树分类错误的那些数据点,并重新训练一个新的决策树来改进这些先前分类错误点的分类。

超参数

在上一节中,我们概述了决策树是如何构建的。特别是,我们确定确定决策树应该在哪里分裂(换句话说,何时应该向我们的概念流程图添加新路径)的一个标准是查看结果节点的纯度。我们还指出,如果让算法仅以节点纯度为标准构建决策树,将很快导致过度拟合训练数据的树。这些决策树调整得如此之好,以至于它们不仅捕捉了用于分类给定数据点的最显著特征,甚至将数据中的噪声建模为真实信号。因此,虽然这种允许无限制优化特定指标的决策树在训练数据上表现良好,但它既不会在测试数据集上表现良好,也不会很好地泛化到分类先前未见过的数据点,这是训练模型的目标。

为了减轻这些陷阱,从训练数据集生成决策树的训练过程有几个超参数。超参数是一种可以旋转和调整的旋钮或高级配置设置,直到找到训练模型的最佳设置。这些旋钮控制着诸如在提升序列中训练的树的数量、每棵树的生长深度、用于训练树的特性数量等等。

数据框分析 API 公开的用于分类的超参数如下:etafeature_bag_fractiongammalambdamax_trees。我们将依次查看这些参数,但在深入探讨它们的意义以及它们如何影响从我们的数据集中训练出的决策树之前,让我们花一点时间来讨论超参数优化

如果你是一个高级用户,并且已经使用其他框架训练了梯度提升树,你可能对你的特定训练数据集和问题中每个参数的最佳值有一个概念。然而,如果你是从零开始,你如何设置这些值呢?寻找超参数最佳值的系统过程称为超参数优化。在这个过程中,训练数据集被分成组或交叉验证折。为了确保每个交叉验证折代表整个训练数据集,生成这些折的采样过程确保每个交叉验证折中每个类别的成员比例与整个数据集中的比例大致相同。

一旦数据集被分层到这些折中,我们就可以继续进行超参数优化的下一部分。我们有五个超参数,每个都可以取很大的值范围。我们如何设计一个系统性的方法来找到最佳的超参数组合?一个选项是创建一个多维网格,其中网格上的每个点对应于一组特定的超参数值。例如,我们可以有eta 0.5,feature_bag_fraction为 0.8,gamma为 0.7,lambda为 0.6,以及max_trees为 50。一旦我们选择了这些参数,我们就在 K 个交叉验证折中训练一个模型,同时将 K 个折保留用于测试。然后我们针对同一组参数重复此过程 K 次,以确保每次我们都会留出一个不同的 K 个折用于测试。

然后,我们重复此过程,直到找到在保留的测试集上表现最佳的超参数值组合。正如你可能想象的那样,即使对于少量超参数组合重复此过程,在时间和计算上也可能相当昂贵。因此,在实践中,我们使用各种优化技术来获得足够好的超参数集,同时考虑到计算的成本。

如果你想了解使用超参数优化为你的模型选择了哪些超参数,你可以导航到 Kibana 中的数据帧分析页面并点击管理作业。这将带你到数据帧分析作业管理页面。找到你想要检查的分类作业的 ID,然后在作业 ID 旁边的左侧点击向下箭头。这将显示一个包含作业详细信息的下拉菜单。点击如图 11.17 所示的工作统计面板。这将显示关于作业的信息以及分析统计。

![图 11.16 – 工作统计面板显示关于分类工作以及由超参数优化确定的超参数的基本信息img/B17040_11_16.jpg

图 11.16 – 工作统计面板显示关于分类工作以及由超参数优化确定的超参数的基本信息

最后,在我们关闭本章的这一部分之前,让我们简要地浏览一下前面提到的五个超参数:etafeature_bag_fractiongammalambdamax_trees,以了解它们的意义以及它们控制决策树训练过程的哪个方面。在我们深入探讨每个这些参数的含义之前,让我们简要地看看梯度提升决策树是如何构建的。这将有助于在上下文中设定这些超参数的定义,并使每个这些如何影响梯度提升产生的决策树序列的最终形式变得更加清晰。

如您可能从之前的章节中回忆起来,梯度提升决策树的基本构建块是一个简单的决策树。决策树是通过递归地将数据集根据某些特征的阈值划分为越来越小的部分来构建的。例如,如果我们正在对代表各种测量值的鸢尾花数据点进行分类,我们可能会决定根据花瓣长度来分割数据集。所有花瓣长度小于 2 厘米的数据点被分割到左节点,其余的到右节点。我们选择这个特征——在这个例子中是花瓣长度——的方法是遍历数据集中的所有可能特征,并检查使用该特征进行分割产生的节点的纯度。正如您可能想象的那样,在多维数据集中,遍历每个特征并测试使用该特征分割产生的节点纯度可能需要非常长的时间。为了减少所需的计算时间,我们可以选择只测试特征的一部分,而这个比例是由feature_bag_fraction参数控制的。

在我们根据训练数据集训练出第一个决策树之后,提升过程要求我们选取第一个决策树错误分类的数据点,并构建一个后续迭代,目的是改进第一个决策树。因此,到这个过程的结束时,我们将有一系列决策树,在某些文献中这也可以称为森林。换句话说,这个森林的增长速度,即最终决策树序列的长度,是由参数eta控制的。eta的值越小,森林就会越大,而且这个森林对之前未见过的数据点的泛化能力也会越好。

在我们介绍决策树的章节中,我们讨论了当仅优化以适应训练数据集时,树可以生长到完美适应训练数据。虽然这可能听起来很理想,但现实中允许树过度拟合训练数据集将导致最终模型泛化能力差。因此,为了控制单个决策树的生长,我们有两个超参数,gammalambdagamma的值越高,训练过程就越倾向于生成更小的树,这有助于减轻过拟合。与gamma类似,lambda的值越高,决策树就会越小。

尽管在实践中,你可以始终依赖超参数优化过程来为这些参数中的每一个选择好的值,但在概念层面上至少了解每个参数的含义以及它们如何影响最终训练好的模型是很好的。

结果解释

在上一节中,我们探讨了决策树的理论基础,并从概念上了解了它们的构建方式。在本节中,我们将回到本章早期考察的分类示例,并更仔细地查看结果的格式以及如何解释它们。

在本章早期,我们创建了一个训练好的模型来预测给定的乳腺组织样本是恶性还是良性(作为提醒,在这个数据集中恶性用类别 2 表示,良性用类别 4 表示)。该模型的分类结果片段显示在图 11.18中。

![Figure 11.17 – 威斯康星乳腺癌数据集中一个样本数据点的分类结果img/B17040_11_17.jpg

Figure 11.17 – 威斯康星乳腺癌数据集中一个样本数据点的分类结果

使用这个训练好的模型,我们可以对之前未见过的数据点进行预测。这些预测是什么形式?在最简单的情况下,一个数据点被分配一个类别标签(图 11.18中的ml.Class_prediction字段展示了这一点的例子)。在我们的示例案例中,这个标签可以取两个值之一:2(恶性)和 4(良性)。

然而,这种对数据点进行分类的方式掩盖了预测过程中的一个问题:我们对预测的信心程度。如果你考虑像日常天气预报这样的事情,你会看到量化与我们的预测相关的不确定性的重要性。一个人可以预测周三有 80%的概率下雨,周四有 16%的概率下雨。这两天都被分配了“雨天”的标签,但你在周三比在周四更不可能忘记带伞。这一切都说明,当我们评估我们的机器学习分类器时,我们不仅关心分配给特定数据点的标签,还关心模型对这个标签的信心程度。在 Elastic Stack 中,有两个度量标准可以衡量我们的分类模型对分配的类别标签的信心程度:类别概率和类别得分。我们将在下面更详细地讨论这两个度量标准。

除了知道模型对给定预测的信心程度外,了解哪些数据点的特征在推动该点分类向某一类而非另一类转变中起到了重要作用,通常非常有用。数据点特征对其预测标签的贡献可以通过其特征重要性值来总结。

类别概率

如上所述,仅仅检查机器学习分类器分配给数据点的标签通常是不够的。我们还需要知道分配的概率。在图 11.18中,类别 2 和类别 4 的概率显示在ml.top_classes字段下的嵌套结构中。从图 11.18中我们可以看出,分配给数据点的类别标签是 4,该数据点属于这个类别的概率是 0.814(四舍五入)。模型对此数据点确实属于类别 4 有信心。

类别得分

对于许多情况,根据获得更高概率的类别分配类别标签是一个足够好的规则,但并不是所有数据集都适用。例如,对于类别高度不平衡的数据集,更好的选择可能是使用类别得分。这个值在图 11.18中记录为ml.prediction_score,在类别标签和类别概率的嵌套分解中记录为更精确的ml.top_classes.class_score

类别得分是从类别概率计算得出的,但这种方式会考虑到是否希望最大化准确度或最小化召回率。换句话说,一个人对训练数据集中代表性较弱的类别中的误分类有多大的容忍度。

小贴士

有关如何计算类别得分的详细解释,请参阅此处 Elastic 文档:www.elastic.co/guide/en/machine-learning/current/dfa-classification.html#dfa-classification-class-score,以及更详细的说明,请参阅此处的 Jupyter 笔记本:github.com/elastic/examples/blob/master/Machine%20Learning/Class%20Assigment%20Objectives/classification-class-assignment-objective.ipynb

特征重要性

在检查机器学习模型的预测时,我们不仅关心预测的类别标签、该类别标签的概率,以及可能还有类别得分,我们通常还想知道哪些特征导致了模型做出特定的决策。这通过特征重要性来体现。在训练过程中使用的每个字段(在 使用分类开始您的第一步 部分的 分类 作业配置中选择为 包含 的字段)可以分配一个潜在的特征重要性值,但通常我们只想了解最重要的特征,即具有最高特征重要性值的字段。

因此,为了避免将每个机器学习作业的结果写入我们集群的 Elasticsearch 结果索引中造成混乱,分类作业配置允许我们选择为每个分类数据点写入的顶级特征重要性值的数量。在 图 11.19 中显示的示例中,此配置设置为 4。

图 11.18 – 此配置将为每个文档输出 4 个特征重要性值

图 11.18 – 此配置将为每个文档输出 4 个特征重要性值

一旦分类作业完成,结果索引中的每个文档除了预测类别外,还将包含类别概率分解、类别得分,以及针对给定数据点的四个最高特征重要性值的条目。一个样本数据点的特征重要性值的简略片段显示在 图 11.20 中。

图 11.19 – 一个样本数据点的两个特征重要性值

图 11.19 – 一个样本数据点的两个特征重要性值

此数据点被分配类别标签 4。在导致此预测标签的特征中,包括此数据点在字段 Bare_Nuclei 中的值和它在 Marginal_Adhesion 中的值。

除了检查每个数据点的特征重要性值之外,我们还可以检查哪些特征对于整个数据集的分类是显著的。此图表显示在图 11.21中,并在数据帧分析结果视图中可用(您可以通过访问数据帧分析作业管理页面,选择您已配置应输出多少特征重要性值的作业,然后点击查看来访问此视图)。

![图 11.20 – 整个数据集的总特征重要性值

![img/B17040_11_20.jpg]

图 11.20 – 整个数据集的总特征重要性值

摘要

在本章中,我们深入探讨了监督学习。我们考察了监督学习的含义,训练数据在构建模型中扮演的角色,训练监督学习模型的意义,特征是什么以及它们应该如何工程化以获得最佳性能,以及如何评估模型以及各种模型性能指标的含义。

在了解监督学习的基本知识之后,我们更深入地研究了分类,并考察了如何在 Elastic Stack 中创建和运行分类作业,以及如何评估这些作业产生的训练模型。除了查看基本概念,如混淆矩阵之外,我们还考察了在结果看似过于完美时应该持怀疑态度的情况,以及分类结果有时看似完美背后的潜在原因,以及这并不一定意味着底层训练模型有任何优点。

此外,我们还更深入地研究了驱动 Elastic Stack 中分类功能的引擎:梯度提升决策树。为了了解决策树在底层是如何工作的,我们概念性地考察了单个决策树的构建方式,以及我们在决策树上下文中所说的纯度是什么,以及无约束的决策树如何导致训练数据集上的过度拟合和泛化不良的训练模型。为了调整从数据集生长决策树的过程,训练过程暴露了几个高级配置参数或超参数。默认情况下,这些是通过称为超参数优化的过程设置的,但高级用户也可以选择手动调整这些参数。

在本章的最后部分,我们回到了原始的乳腺癌分类数据集,进一步考察了结果格式以及类概率、类得分以及如何使用特征重要性来确定哪些特征导致数据点被分配为某一类而不是另一类。

在下一章中,我们将在此基础上构建,学习如何使用决策树来解决依赖变量不是离散值(如分类情况)而是连续值的问题。

进一步阅读

关于如何计算课程分数的更多信息,请参阅本 Jupyter 笔记本中的文本和代码示例:github.com/elastic/examples/blob/master/Machine%20Learning/Class%20Assigment%20Objectives/classification-class-assignment-objective.ipynb

第十二章: 回归分析

在上一章中,我们研究了分类——Elastic Stack 中可用的两种监督学习技术之一。然而,并非所有监督学习的实际应用都适合分类所需的格式。例如,如果我们想预测我们邻里的公寓销售价格?或者我们在线商店中客户将花费的金额?请注意,我们这里感兴趣的不是离散类别,而是一个可以在一定范围内取各种连续值的值。

这正是回归分析解决的问题。我们不是预测给定数据点属于哪个类别,而是预测一个连续值。尽管最终目标与分类中的目标略有不同,但用于回归的底层算法与我们在上一章中检查的分类算法相同。因此,我们已经从第十一章**,分类分析中建立的基础中了解了回归的工作原理。

由于回归分析的结果是连续值而不是分类中的离散类别标签,因此我们评估回归模型性能的方式与上一章中检查分类的方式略有不同。我们不是使用混淆矩阵和从正确标记和错误标记的示例数量计算的各种指标,而是计算汇总指标,这些指标捕捉了预测数据集中连续值与数据集中实际值之间的差距。我们将在本章后面更详细地探讨这一过程以及所使用的措施。

本章将涵盖以下主题:

  • 使用回归分析预测地理位置的房价

  • 理解决策树如何应用于创建回归模型

技术要求

本章中的材料需要运行 7.10.1 或更高版本的 Elasticsearch 集群。一些示例可能包括截图或关于仅在 Elasticsearch 后续版本中可用的详细信息的指导。在这种情况下,文本将明确指出需要运行示例的后续版本。

使用回归分析预测房价

在上一章中,我们探讨了 Elastic Stack 中的两种监督学习方法中的第一种——分类。分类分析的目标是使用标记的数据集来训练一个模型,该模型可以预测先前未见过的数据点的类别标签。例如,我们可以在细胞样本的历史测量数据上训练一个模型,并附带有关细胞是否恶性的信息,然后使用它来预测先前未见过的细胞的恶性。在分类中,我们感兴趣预测的类别或因变量始终是一个离散量。另一方面,在回归中,我们感兴趣的是预测一个连续变量。

在我们更深入地探讨回归的理论基础之前,让我们直接深入实践,看看如何在 Elasticsearch 中训练一个回归模型。我们将使用的数据集可在 Kaggle 上找到(www.kaggle.com/harlfoxem/housesalesprediction),描述了 2014 年至 2015 年间在美国华盛顿州某个地区出售的房价。原始数据集已稍作修改,以便更容易地导入 Elasticsearch,并在本书的 GitHub 存储库中提供([https://github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/blob/main/Chapter 12 - Regression Analysis/kc_house_data_modified.csv])。我们将按照以下步骤开始:

  1. 使用您首选的方法将数据集导入 Elasticsearch。如果您愿意,可以使用机器学习应用中的数据可视化器提供的上传文件功能,如图图 12.1所示:图 12.1 – 机器学习应用的数据可视化器中的上传文件功能

    图 12.1 – 机器学习应用的数据可视化器中的上传文件功能

  2. 一旦我们吸收了这些数据,让我们花一点时间在机器学习应用中的数据可视化器中检查它。在这个视图中,我们可以一眼看出数据中存在哪些字段以及每个字段的值分布情况。例如,对于我们的房价数据集,我们可以在图 12.2中看到价格值的直方图可视化,这个数据集中大多数房价介于 20 万至 90 万美元之间,只有少数房子的售价超过 90 万美元:图 12.2 – 数据可视化器中显示的价格字段的值分布

    图 12.2 – 数据可视化器中显示的价格字段的值分布

    数据可视化器中查看数据值和分布可以迅速提醒我们潜在的问题,例如数据集中某个字段中的无效或缺失值。

    接下来,让我们导航到数据帧分析向导。在 Kibana 的左侧滑动菜单中,点击机器学习以导航到机器学习应用的主页。在此页面上,在数据帧分析作业部分,如果您尚未创建数据帧分析作业,请点击创建作业按钮。如果您已经有了,首先点击管理作业按钮。这将带您进入数据帧分析页面,在那里您将找到现有数据帧分析作业的列表(例如,如果您在上一章的演练中创建了任何作业)以及创建作业按钮。

    这将带您进入我们已经在第十章“异常检测”以及第十一章“分类分析”中见过的数据帧分析向导。

  3. 一旦您已选择您的源索引模式(这应该与您在步骤 1中导入或上传数据时选择的索引模式名称相匹配),请从数据帧分析向导的作业类型选择器中选择回归,如图 12.3 所示:![图 12.3 – 在数据帧分析作业向导选择器中选择“回归”作为作业类型 图片

    图 12.3 – 在数据帧分析作业向导选择器中选择“回归”作为作业类型

  4. 接下来,让我们配置价格字段。

    在选择因变量之后,让我们继续选择将要包含或排除在分析中的字段。虽然数据集中的许多字段提供了对预测因变量(价格)有用的信息,但有一些字段我们从一开始就知道它们与价格不相关,因此应该被移除。这些字段中的第一个是数据点的 ID(id)。它只是一个表示数据点在原始数据文件中位置的行号,因此不期望它包含有用的信息。实际上,包括它可能会带来更多的坏处而不是好处。例如,如果原始数据文件是以这样的方式组织的,即所有低价房屋都位于文件的开头,具有低的id编号,而所有高价房屋都位于文件末尾,那么模型可能会认为id数据点在确定房屋价格时很重要,尽管我们知道这仅仅是数据集的一个特征。这反过来又会损害模型在未来的、尚未看到的观测点上的性能。如果我们假设数据点的id值在增长,任何添加到数据集的新数据点都会自动具有比训练数据中任何数据点更高的id编号,因此会导致模型认为任何新的数据点价格更高。

    此外,我们还将排除关于房屋位置纬度和经度的信息,因为这些变量不能被机器学习算法解释为地理位置,因此将被简单地解释为数字。最终的配置应如图12.4所示:

    img/B17040_12_4.jpg

    img/B17040_12_4.jpg

    图 12.4 – 不包含在分析中的字段

  5. 在配置了因变量以及包含和排除的字段后,我们可以继续进行其他配置项。虽然我们将在这个部分保留大部分值设置为默认值,但我们将特征重要性值的数量从0更改为4。如果设置为0,则不会输出任何特征重要性值。当设置为4时,每个数据点将输出四个最重要的特征值。如在第11 章“分类分析”中简要讨论的,特征重要性值将分别针对每个文档输出,有助于确定模型为何以特定方式对特定文档进行分类。我们将在本章稍后回到这一点:![图 12.5 – 特征重要性值配置 img/B17040_12_5.jpg

    图 12.5 – 特征重要性值配置

    我们将其他设置保留为默认值,并通过滚动到向导底部并点击创建并启动按钮来开始运行作业。

  6. 在完成向导中的步骤并创建并启动作业后,返回到数据帧分析页面。这将显示您创建的所有数据帧分析作业的概述,包括我们在前面的步骤中创建的回归作业。一旦作业完成,点击右侧菜单,然后点击查看,如图12.6:所示:![图 12.6 – 选择查看数据帧分析作业的结果 img/B17040_12_6.jpg

    图 12.6 – 选择视图查看数据帧分析作业的结果

    这将带我们进入探索页面,该页面允许我们探索新训练的回归模型的各项指标。在这个页面上需要特别注意的第一件事是训练/测试数据集切换,如图12.7所示:

    img/B17040_12_7.jpg

    img/B17040_12_7.jpg

    图 12.7 – 数据帧分析结果查看器中的训练/测试切换

    在查看探索页面上的指标时,要注意这个切换,因为模型的评估指标根据所选的指标而意味着不同的事情。在这种情况下,我们感兴趣的是当模型试图对之前未见过的数据点进行预测时,其表现如何。最接近这一点的数据集是测试数据集——换句话说,即未用于训练过程的数据集。

  7. 让我们向下滚动并查看测试数据集的模型评估指标,如图图 12.8所示:![图 12.8 – 泛化误差

    ![img/B17040_12_8.jpg]

    图 12.8 – 泛化误差

    我们将在本章后面更详细地探讨这些指标的含义,但到目前为止,你可以将它们视为模型对房屋价格预测与实际房屋价格接近程度的综合度量。

  8. 最后,许多用户可能感兴趣的是,我们数据集中哪些字段在确定模型的最终预测中最为重要。我们可以通过查看探索页面上的总特征重要性部分来了解这一点,如图图 12.9所示:![图 12.9 – 总特征重要性

    ![img/B17040_12_9.jpg]

图 12.9 – 总特征重要性

从图中可以看出,华盛顿州金郡的房屋销售价格最重要的因素是zip code,即房屋的位置。在不太重要的因素中包括房屋翻新年份yt_renovated和楼层数量(floors)。

值得注意的是,图中显示的特征是整个数据集中最重要的特征。然而,确定数据集中单个数据点的销售价格的特征可能会有很大不同。在这次讲解的早期,在创建4中的工作期间。这意味着在模型训练完成后,四个最重要的特征重要性值将被写入结果索引。让我们看一下结果索引中的样本文档,king-county-houses-regression。该文档如图图 12.10所示:

![图 12.10 – 结果索引中样本文档的特征重要性值

![img/B17040_12_10.jpg]

图 12.10 – 结果索引中样本文档的特征重要性值

如我们在图 12.10中可以看到,对于这所特定的房子,最重要的四个特征值是grade(评级)、sqft_living(以平方英尺计的房屋居住面积)、yr_built(房屋建造年份)和zipcode(房屋位置的数值表示)。需要注意的是,这里所有的特征重要性值都是负数,这意味着它们有助于降低房屋的价格。

让我们看看另一份文档(如图 图 12.11 所示)中的前四个特征重要性值在结果索引中的情况,并将它们与之前的特征重要性值进行比较,以了解可能存在多少变化:

图 12.11 – 确定这所房子价格最重要的四个特征

图 12.11 – 确定这所房子价格最重要的四个特征

如我们所见,图 12.11 中的房屋与 图 12.10 中的房屋有一些共同的特征,但也有些独特的特征。此外,这个房屋的特征重要性值对其价格有积极的影响。

现在你已经简要地看到了如何训练一个回归模型并评估其结果,一个自然的问题就是接下来该做什么?如果你使用这个模型进行实际应用,例如预测金县或其他地理位置尚未售出的房屋的潜在房价,你可以使用推理来检查和部署这个模型。这将在 第十三章**,推理 中更详细地介绍。在下一节中,我们将简要回顾我们在 第十一章**,分类分析 中开始的决策树讨论,并看看我们当时提出的想法如何应用于回归问题。

使用决策树进行回归

正如我们在前面的章节中讨论的,回归是一种监督学习技术。正如在 第十一章**,分类分析 中讨论的,监督学习的目标是接受一个标记的数据集(例如,具有房屋特征及其销售价格 – 因变量)的数据集,并将这些数据中的知识提炼成一个称为训练模型的人工制品。然后,这个训练模型可以用来预测模型之前未见过的房屋的销售价格。当我们试图预测的因变量是一个连续变量,而不是分类领域的离散变量时,我们就处于回归的状态。

回归 – 从现实世界的观察或数据中提炼信息的过程 – 是机器学习的一个领域,它包含的技术远比在 Elasticsearch 的机器学习功能中使用的决策树技术更广泛。然而,在这里我们将限制自己只讨论如何使用决策树(Elastic Stack 内部发生过程的简化版本)进行回归的概念性讨论,并将对学习更多关于回归的读者推荐到相关文献。例如,书籍 机器学习数学https://mml-book.github.io/)对回归有很好的介绍。

第十一章**,分类分析中,我们开始讨论决策树在分类问题中的应用,通过介绍如果你想要尝试推断房屋的销售价格,可能会构建的流程图的概念。一个可能构建的虚构流程图的例子在图 12.12中展示:

![Figure 12.12 – A sample flowchart that illustrates the decisions that you might make to try to predict the sales price of a house](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-els-stk-2e/img/Figure 12.12 – A sample flowchart that illustrates the decisions that you might make to try to predict the sales price of a house)

img/B17040_12_12.jpg

图 12.12 – A sample flowchart that illustrates the decisions that you might make to try to predict the sales price of a house

图 12.12所示的虚构流程图中,我们有一个由左上角方框表示的样本房屋。这个房屋有 5,000 平方英尺的居住空间,5 个卧室和 5 个浴室。我们希望根据这些属性预测房屋的最终销售价格,并打算使用流程图来帮助我们。我们在实践中这样做的方式是,从流程图的顶部(或决策树的根)开始,逐步向下工作,直到终端节点(不连接任何下游节点的节点)。我们的样本房屋有 5,000 平方英尺的居住区,所以我们对于第一个节点回答,对于第二个子节点再次回答。这导致了一个终端或叶节点,其中包含我们房屋的预测价格:956,000 美元。

一个自然的问题是要问,我们如何创建像图 12.12那样的流程图?创建此流程图(或决策树)需要两个要素:标记的数据集,其中包含每栋房屋的特征及其销售价格,以及训练算法,该算法将使用此数据集,并构建一个填充的流程图或决策树,然后可以用来对先前未见过的房屋进行价格预测(这是通过推理完成的,这在第十三章**,推理中详细说明)。

基于标记数据集训练决策树的过程涉及创建像图 12.12中展示的节点。这些节点将数据集分成越来越小的子集,直到我们达到每个子集满足某些标准的情况。在分类的情况下,当子集达到一定的纯度时,逐步分割训练数据集的过程就会停止。这可以通过使用几个不同的指标来衡量,这些指标捕捉了节点中属于某一类的数据点的比例。最纯的节点是只包含属于某一类的数据点的节点。

由于回归处理连续值,我们不能使用纯度作为确定何时递归分割数据集的停止标准的度量。相反,我们有一个称为损失函数的另一个度量。回归的一个示例损失函数是均方误差。这个度量捕捉了预测值与每个节点中每个点的实际误差之间的距离。

最后,如果我们多次递归分割数据集,我们将得到如图 12.13 所示的简化决策树:

图 12.13 – 一个简化的训练决策树。叶节点包含多个数据点

图 12.13 – 一个简化的训练决策树。叶节点包含多个数据点

如我们在图 12.13 中看到的,在训练过程的最后,终端节点叶包含每个节点多个数据点。

摘要

回归是 Elastic Stack 中两种监督学习方法中的第二种。回归的目标是将一个训练好的数据集(包含一些特征和我们要预测的因变量的数据集)提炼成一个训练好的模型。在回归中,因变量是一个连续值,这使得它与处理离散值的分类方法不同。在本章中,我们使用了 Elastic Stack 的机器学习功能,利用回归根据房屋的位置和卧室数量等属性预测房屋的销售价格。虽然有许多回归技术可用,但 Elastic Stack 使用梯度提升决策树来训练模型。

在下一章中,我们将探讨如何将监督学习模型与推理处理器和摄取管道结合使用,以创建强大的、由机器学习驱动的数据分析管道。

进一步阅读

关于如何计算特征重要性值的更多信息,请参阅此处博客文章 使用 Elastic 机器学习进行数据框分析的特征重要性https://www.elastic.co/blog/feature-importance-for-data-frame-analytics-with-elastic-machine-learning。

如果你正在寻找回归的更数学化的介绍,请参阅此处可用的书籍 机器学习数学 mml-book.github.io/

第十三章:推理

在本章中,我们将深入探讨在 Elastic Stack 中使用训练好的监督模型所能做的所有令人着迷的事情。首先,我们将看到如何使用训练模型 API 来查看我们集群中可用的模型信息,查看单个模型的详细信息,以及导出模型以便它们可以被移植到其他 Elasticsearch 集群。我们还将简要地看看如何使用 eland 将外部模型,例如由第三方机器学习库训练的模型,导入到 Elasticsearch 中。

在本章的第二部分,我们将深入探讨如何在各种环境中使用训练好的监督模型进行推理,以丰富数据。为此,我们将学习推理处理器和摄取管道,以及这些如何与连续转换、重新索引以及在使用各种 beats 或其他将数据摄取到 Elasticsearch 时的时间进行组合。

在本章中,我们将涵盖以下主题:

  • 使用训练模型 API 和 Python 检查、导入和导出训练好的机器学习模型

  • 理解推理处理器和摄取管道以及如何配置和使用它们

  • 使用 eland 将外部模型导入 Elasticsearch

技术要求

本章中的材料需要 Elasticsearch 集群版本 7.10 或更高版本,以及安装 Python 3.7 或更高版本,并已安装elandelasticsearch-pyscikit-learn库。有关如何配置 Python 安装以与本章一起使用的详细说明,请参阅书中 GitHub 仓库中的Chapter 13 - Inference and Advanced Transforms文件夹下的 README 部分:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2013%20-%20Inference

使用训练模型 API 检查、导出和导入您的训练模型

您已经准备好了您的数据集,训练了您的分类或回归模型,检查了其性能,并确定您希望使用它来丰富您的生产数据集。在您深入到摄取管道、推理处理器以及您可以为使用训练模型配置的其他众多组件之前,熟悉训练模型 APIwww.elastic.co/guide/en/elasticsearch/reference/7.10/get-trained-models.html),一组 REST API 端点,您可以使用它来获取有关模型的信息,甚至将它们导出到其他集群。让我们来浏览一下这个 API,看看它能告诉我们关于我们模型的信息。

训练模型 API 的概览

在本节中,我们将通过 Kibana Dev Console 实际查看关于我们的训练有监督模型的一些信息:

  1. 让我们从 Kibana Dev Console 开始。简而言之,对于那些还不熟悉这个工具的人来说,Kibana Dev Console 可以通过点击左侧滑动菜单并向下滚动到 model_id 来访问。这个字段包含分配给集群中每个模型的唯一标识符,并在稍后用于在推理处理器和摄取管道中使用模型时引用该模型:

    Trained Models API 还提供了一项信息,即 analyzed_fields,这是一个包含包含或排除字段列表的字典。最好进行双重检查,以确保只包含用于训练的预期字段:

    图 13.1 – Inference API 响应片段,展示了集群中训练模型的数量以及其中一个模型的信息

    图 13.1 – Inference API 响应片段,展示了集群中训练模型的数量以及其中一个模型的信息

  2. 我们的演示集群仅包含三个训练好的模型,因此 API 返回的信息量并不大,但如果你正在处理包含数十个或数百个模型的集群,使用 API 调用查看单个模型的详细信息可能会有所帮助。注意,如果你正在跟随教程并想在你的 Kibana 实例中运行后续的 API 调用,你必须查找并使用位于你集群中的模型的 model_id

    GET _ml/trained_models/breast-cancer-wisconsin-classification-1612270856116
    

    或者,使用带有通配符的 API 调用:

    GET _ml/trained_models/breast-cancer-wisconsin-classification-*
    

    可以通过 _cat API 获取一个更简洁的摘要。我们可以使用以下 API 调用来查看可用的模型简要信息:

    GET _cat/ml/trained_models
    

    我们从集群收到的响应显示在 图 13.2 中。你会注意到,在乳腺癌数据集上训练了两个模型,还有一个标识为 lang_ident_model_1 的第三个模型。这是一个默认随 Elasticsearch 一起提供的语言识别模型,可以用来识别给定字符串可能属于哪种语言。我们将在本章后面部分探讨这个语言识别模型的工作原理以及如何使用它:

图 13.2 – _cat API 的响应

图 13.2 – _cat API 的响应

现在,我们已经简要地了解了如何检查和提取集群中可用的训练模型的信息,让我们更深入地研究 Trained Models API 的最后一个强大功能——从 Elasticsearch 集群中导出模型。由于这个程序比上一个程序涉及更多的部分,我们专门用下一节来介绍它。

使用 Trained Models API 和 Python 导出和导入训练好的模型

你为什么可能想要导出在 Elasticsearch 中训练好的模型呢?你可能想要导出你的模型以便将其存储在外部、与同事分享,或者稍后导入到另一个 Elasticsearch 集群中。由于训练机器学习模型可能需要大量资源,你可能想要为训练配置一个临时的 Elasticsearch 集群,在这个集群上训练和评估模型,然后将模型导出并重新导入到另一个较小的集群中,以便执行推理——这是一个资源消耗较少的过程。

要使用 Python 导出模型并遵循步骤,你需要安装 Python 3.7 或更高版本,以及 elasticsearch-py 库版本 7.10.1。有关如何配置 Python 环境和安装所需依赖项的详细说明和进一步资源,请参阅本书 GitHub 仓库中的此 README 文件(github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2013%20-%20Inference)。从 Elasticsearch 集群中导出模型所需的所有步骤和逻辑都将在本书 GitHub 仓库中的 export_model.py Python 脚本中提供,github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2013%20-%20Inference/model_import_export。在本节中,我们将查看脚本中的每个步骤,以了解导出模型所需的组件。希望这种处理能为你提供使用 elasticsearch-py 客户端在 Python 中构建自己的机器学习工作流的构建块:

  1. 几乎所有与 Elasticsearch 集群交互的 Python 脚本的构建块是构建 Elasticsearch 客户端对象。首先必须从库中导入客户端对象的类,如下所示:

    from elasticsearch import Elasticsearch
    

    一旦类被导入,我们就可以创建该对象的实例并将其分配给 es_client 变量:

    es_client = Elasticsearch(es_url, http_auth=(ES_USERNAME, ES_PASSWORD))
    

    注意,我们将包含 Elasticsearch 实例 URL 的变量传递给对象的构造函数。这可以是类似于 localhost:9200 的内容,如果你在你的本地机器上运行 Elasticsearch 实例,或者对于基于云的部署,是一个更长的 URL。此外,我们传递了两个变量,ES_USERNAMEES_PASSWORD,它们包含我们 Elasticsearch 实例的用户名和密码。如果你在本地运行一个未受保护的 Elasticsearch 集群以进行开发目的,则这不是必需的,但请注意,在生产环境中运行未加密的 Elasticsearch 集群是极其危险的。

  2. 要与机器学习 API 交互,这些 API 我们需要从集群中导出模型,我们需要导入 MlClient 类:

    from elasticsearch.client import MlClient
    

    然后,创建它的一个实例:

    ml_client = MlClient(es_client)
    

    一旦创建了这两个客户端,我们就可以继续导出我们的模型。为此,我们将使用 get_trained_models 方法。该方法的文档在此处,elasticsearch-py.readthedocs.io/en/v7.13.0/api.html#x-pack,在处理此库时,最好将此参考信息随时备好,以防需要双重检查参数和配置选项的含义。需要传递给该方法的三个重要参数是:model_id,它指定了你希望导出的模型的名称,decompress_definition 标志,应设置为 False,以及 include_model_definition 标志,应设置为 True

    model_id = breast-cancer-wisconsin-classification-1612270856116 
    compressed_model = ml.client.get_trained_models(model_id, decompress_definition=False, include_model_definition=True, for_export=True)
    Figure 13.3. Note that the actual response returned is much longer and is not shown to save space:
    

    图 13.3 – 捕获模型压缩定义以及元数据的 Python 字典片段

    图 13.3 – 捕获模型压缩定义以及元数据的 Python 字典片段

  3. 一旦我们将此模型定义存储在我们的 Python 脚本中的 compressed_model 变量中,我们就可以将字典转换为 JSON 格式的字符串,并将其写入文件,该文件可以存储在版本控制中或导入到另一个 Elasticsearch 集群中。

    要将字典转换为 JSON 格式,我们必须导入内置的 Python json 库:

    import json 
    

    然后,我们可以将导出的模型写入存储在 filename 变量中的文件路径:

    with open(filename, 'w') as handle:
        handle.write(json.dumps(compressed_model))
    

所有前面的步骤都总结在 export_model.py 脚本中,该脚本可在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2013%20-%20Inference/model_import_export

现在我们已经看到了如何从 Elasticsearch 集群中导出训练好的模型,让我们看看如何从文件中导入模型。与之前一样,我们将逻辑分解成步骤,但完整的有效脚本将存储在本书的 GitHub 仓库中:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2013%20-%20Inference

  1. 这个流程中的许多步骤与我们在之前详细介绍的导出脚本类似。特别是,创建ElasticsearchMlClient对象,以及解析命令行参数,都遵循与前面脚本相似的步骤,因此我们不会详细解释它们。因此,第一步是读取模型文件,并使用内置json库的loads函数将字符串内容转换为 Python 字典:

    With open(filename, 'r') as handle:
        model_definition = json.loads(handle.read())
    
  2. 一旦我们将压缩的model_definition和所需的元数据加载到我们的 Python 字典中,我们就可以使用put_trained_model方法将其上传到我们的集群。

  3. 最后,导航到你的集群的 Kibana 实例,并使用训练模型 API 来双重检查模型是否确实已导入到你的集群中。

现在我们已经学会了如何查看模型的详细信息,以及如何导出和导入模型,我们可以继续学习构建没有模型的更复杂的机器学习基础设施。一旦你有一个训练好的模型,模型的可能性几乎是无限的——你可以将模型与转换结合使用,在摄取时使用它来丰富你的数据,等等。这个基础设施的构建块是推理处理器和摄取管道。我们将在下一章详细探讨这两个构建块,以便你为构建自己的机器学习基础设施做好准备。

理解推理处理器和摄取管道

你已经有一个训练好的机器学习模型,接下来该做什么呢?记得从第十一章分类分析,和第十二章回归,机器学习模型令人兴奋的一点是,它们可以从标记的训练数据集中学习,然后以某种方式编码知识,以便它们可以用于对先前未见过的数据点进行预测。对先前未见过的数据点进行标记或预测的过程就是我们所说的推理

这在 Elastic Stack 中是如何实际发生的?

你可能构建出许多不同的架构来利用 Elastic Stack 中的推理功能,但所有这些架构的基本构建块都是推理处理器和摄取管道。这是我们本章探索的主要主题。

摄取管道是一个特殊组件,它允许你在数据写入 Elasticsearch 索引之前以各种方式操纵和转换你的数据。摄取管道通常由各种处理器组成,这些处理器是子单元或可配置的任务,每个处理器对正在摄取的数据执行单一类型的操纵或转换。摄取管道可以由多个处理器组成,这些处理器按顺序对每个正在摄取的数据文档执行。

例如,典型的摄取管道架构可能涉及一个脚本处理器,它能够对通过管道摄取的每个文档执行 Painelss 脚本,然后是推理处理器,再然后是另一个脚本处理器。对于许多机器学习应用,例如我们将在本章稍后探讨的应用,这是执行特征工程或将特征转换为机器学习模型可消费的适当格式的完美位置,或者在文档被摄取到 Elasticsearch 之前删除不必要的字段。

有多种内置处理器可以组合和定制,以创建复杂的数据转换管道。例如,GeoIP 处理器可以添加关于 IP 地址的地理位置信息,脚本处理器允许用户编写自定义的简便代码,以对现有文档字段进行计算和操作,而 CSV 处理器可以解析并从 CSV 值中提取数据以创建字段。完整的处理器列表可在 Elasticsearch 文档中找到:www.elastic.co/guide/en/elasticsearch/reference/master/processors.html。我们鼓励您查看一下,看看您可以使用它们构建哪些可能的数据架构。

对于我们的目的,在探索机器学习方面,最重要的处理器是推理处理器。当文档通过此处理器时,它们会根据处理器配置中引用的机器学习模型的预测进行标注。让我们通过一个实际示例来看看如何配置我们自己的推理处理器,并在摄取管道中使用它。

在这个例子中,我们将使用我们在 第九章,“介绍数据帧分析”中首次考察的虚构社交媒体数据集。这次,我们将使用语言识别模型来识别这些虚构微博网站帖子中的文本是用哪种语言编写的。让我们开始吧!

  1. 如果您在本章开头讨论的 Trained Models API 上进行过尝试,您可能会注意到,即使您在特定的 Elasticsearch 集群中没有训练任何模型,您仍然会在集群中看到一个模型,lang_ident_model_1。由 Trained Models API 返回的与此模型相关的元数据在 图 13.4 中显示:![图 13.4 – 语言识别模型 lang_ident_model_1

    ![img/B17040_13_4.jpg]

    图 13.4 – 语言识别模型 lang_ident_model_1

    该模型默认随 Elasticsearch 集群一起提供,可以在推理处理器和摄取管道中使用,就像您在 Elasticsearch 集群上训练的任何其他模型一样!

  2. 接下来,让我们看看如何创建一个包含引用此语言识别模型的推理处理器的摄取管道配置。回想一下,处理器是摄取管道内的子单元,它处理每个进入管道的文档,并在将其写入 Elasticsearch 索引之前。尽管你永远不能将推理处理器作为一个独立的单元在 Elasticsearch 中使用——它必须始终是管道的一部分——让我们首先单独检查处理器的配置,然后看看它是如何适应摄取管道的。

    以下代码片段显示了为我们的计划语言识别文本管道配置的推理处理器:

    {
      "inference": {
        "model_id": " lang_ident_model_1",
        "target_field": "text_language_prediction",
        "field_map": {
          "post": "text"
        },
        "inference_config": { "classification": {} }
      }
    }
    

    让我们花点时间回顾一下最重要的配置参数及其含义。有关推理处理器的所有可用配置选项的完整 API 参考,请参阅以下 Elasticsearch 文档:www.elastic.co/guide/en/elasticsearch/reference/master/inference-processor.html

    任何推理处理器的关键部分是用于在我们文本文档上做出预测的训练机器学习模型。推理处理器通过model_id配置字段来了解它应该使用哪个模型来分类传入的文档。在我们的案例中,model_id(可以在通过使用训练模型 API 查看的模型元数据中找到)是lang_ident_model_1

          "input" : {
            "field_names" : [
              "text"
            ]
          }
    

    在模型元数据下列出的field_names字段名指定了在模型中使用的特征字段名为文本,这意味着我们希望在推理文档中使用的特征需要有一个包含我们希望应用语言识别的文本的字段。

    由于模型的训练与推理过程解耦,完全有可能在训练过程中为特征选择的字段名在我们希望通过推理处理器传递的数据中不可用。在这种情况下,如果无法或不想更改我们数据的字段名,我们可以在推理处理器的field_map配置块中使用它来将数据中我们将用于推理的字段名映射到我们的监督模型期望的字段名。以下是我们配置的映射,将包含我们虚构的微博社交媒体数据集中的文本的post字段名与模型期望的text字段名之间进行映射:

        "field_map": {
          "post": "text"
        },
    

    最后,我们来到了配置的最后部分——inference_config块。这个块的配置选项决定了我们是使用分类还是回归。由于在语言识别的情况下,我们进行的是多类分类,因此我们将选择分类,并将所有其他配置选项保留为它们的默认设置。在本节稍后,我们将更详细地查看inference_config中可用的字段以及调整它们如何确定结果的最终格式。

  3. 现在我们已经检查了配置推理处理器所涉及的各个部分,让我们继续配置摄取管道。这将是顶级容器,或者说组件,它将容纳我们的推理处理器,以及可能的其他处理器。

    包含我们配置的推理处理器的摄取管道的配置如下:

    PUT _ingest/pipeline/language-identification-pipeline
    {
      "description": "Pipeline for classifying language in social media posts",
      "processors": [
    {
      "inference": {
        "model_id": " lang_ident_model_1",
        "target_field": "text_language_prediction",
        "field_map": {
          "post": "text"
        },
        "inference_config": { "classification": {} }
      }
    }
      ]
    }
    
  4. 配置的大部分内容是由我们之前详细研究的推理处理器的规格组成。在摄取管道的配置中,唯一值得注意的附加功能是管道的名称,它是 REST API 端点的一部分,以及配置体中的processors数组。在这种情况下,我们选择将管道命名为language-identification-pipelineprocessors数组包含我们处理器的配置规格。在这种情况下,我们只有一个处理器。

    您可以将以下配置复制并粘贴到 Kibana Dev Console 中,或者,作为替代,使用 Kibana 中的堆栈管理面板中可用的摄取管道向导,如图13.5所示:

    图 13.5 – 创建管道向导

    图 13.5 – 创建管道向导

  5. 一旦我们配置了我们的管道——无论是通过 Dev Console 还是向导,我们就准备好开始使用它来识别我们虚构的社会媒体微博平台帖子中的语言。

    虽然通常我们会使用摄取管道与转换或packetbeat之类的 beat 一起使用,但在这个案例中,我们将使用 Kibana Dev Console 将文档摄取到索引中,因为它更容易在那里说明我们想要教授的概念。在本章稍后,我们将查看更高级和更现实的示例。

    让我们通过管道索引我们的第一个文档。实现这一点的 REST API 命令如下:

    POST social-media-feed-inference/_doc?pipeline=language-identification-pipeline
    {
      "username": "Sanna",
        "statistics": {
          "likes": 320,
          "shares": 8000
        },
        "timestamp": "2021-01-20T23:19:06",
        "post" : "Terveisiä Suomesta! Täällä olen talvilomalla!"
    }
    

    我们正在向索引文档发送POST请求,并将我们在前面的步骤中创建的管道名称作为可选的pipeline参数的参数传递。在这种情况下,用户"Sanna"已经写了一条更新(如芬兰语帖子字段所示)。让我们检查social-media-feed-inference索引,看看摄取的文档看起来像什么。

    如果您还没有这样做,请为 social-media-feed-inference 索引创建一个索引模式,并导航到 Kibana 中的发现应用。现在,social-media-feed-inference 索引中只包含我们之前使用 REST API 调用索引的一个文档。文档如图 13.6 所示:

    ![Figure 13.6 – 在 social-media-feed-inference 索引中的摄取文档

    ![img/B17040_13_6.jpg]

    图 13.6 – 在 social-media-feed-inference 索引中的摄取文档

    如我们从图 13.6 中的文档中可以看到,推理处理器在文档的原始字段旁边添加了四个新字段。所有这些字段都以 text_language_prediction_model 为前缀,这是我们推理处理器配置中设置的。如我们所见,这些字段记录了用于预测的模型的 model_idpredicted_value(在这种情况下将包含模型预测帖子所在语言的标识符)、prediction_probability 以及 prediction_score。这些在 第十一章 分类分析 中已有介绍。

    如我们所见,在这种情况下,模型已经正确地确定原始帖子是用芬兰语编写的。

  6. 在上一个示例中,我们创建了推理处理器和摄取管道配置,然后直接通过管道将文档索引到我们的索引中。如果我们希望在索引之前先查看我们管道的一些干运行,我们可以使用 _simulate 端点:

    POST _ingest/pipeline/language-identification-pipeline/_simulate
    {
      "docs": [
        {"_source": {
      "username": "Sanna",
        "statistics": {
          "likes": 320,
          "shares": 8000
        },
        "timestamp": "2021-01-20T23:19:06",
        "post" : "Terveisiä Suomesta! Täällä olen talvilomalla!"
    }
    }
      ]
    }
    

    API 对此调用的响应包含模型预测的结果,如下面的代码片段所示:

    {
          "doc" : {
            "_index" : "_index",
            "_type" : "_doc",
            "_id" : "_id",
            "_source" : {
              "post" : "Terveisiä Suomesta! Täällä olen talvilomalla!",
              "text_language_prediction" : {
                "prediction_score" : 0.9999995958245499,
                "model_id" : "lang_ident_model_1",
                "prediction_probability" : 0.9999995958245499,
                "predicted_value" : "fi"
              },
              "username" : "Sanna",
              "statistics" : {
                "shares" : 8000,
                "likes" : 320
              },
              "timestamp" : "2021-01-20T23:19:06"
            },
            "_ingest" : {
              "timestamp" : "2021-03-29T01:35:07.492629377Z"
            }
          }
        }
      ]
    }
    
  7. 最后,在开始摄取之前,也可以使用摄取管道 UI 测试文档,以确保一切按预期工作。不幸的是,在 UI 中,这只能在创建新的摄取管道时进行,而不能在现有的管道中进行,因此,为了演示的目的,您可以使用向导开始创建我们之前创建的 language-identification-pipeline 的克隆。

    然后,在向导中 处理器 选择器的右侧,如图 13.7 所示,找到 测试管道 文本,并点击 添加文档 链接:

![Figure 13.7 – 创建摄取管道向导

![img/B17040_13_7.jpg]

图 13.7 – 创建摄取管道向导

这将在向导的右侧触发一个菜单,允许您从索引中添加文档或手动在提供的文本框中指定它。在这种情况下,我们将手动将我们的芬兰语测试文档添加到文本框中,如图 13.8 所示:

![Figure 13.8 – 摄取管道中的样本文档

![img/B17040_13_8.jpg]

图 13.8 – 摄取管道中的样本文档

一旦您配置了您的文档,点击运行管道按钮,您应该会看到文档通过推理管道后的预览。

处理导入管道中的缺失或损坏的数据

许多,或许甚至大多数,现实世界的应用将不会有整洁的数据集。相反,数据可能会缺失、标签错误,甚至可能被损坏。在这种情况下,花点时间看看推理处理器会发生什么,这样你就可以在自己的管道中识别并减轻这些问题:

  1. 让我们继续使用我们之前作为示例使用的虚构微博平台,并假设由于配置错误,我们将包含我们希望检测其语言的文本字符串的post字段重命名为post_text,如下所示:

    POST social-media-feed-inference/_doc?pipeline=language-identification-pipeline
    {
      "username": "Sanna",
        "statistics": {
          "likes": 320,
          "shares": 8000
        },
        "timestamp": "2021-01-20T23:19:06",
        "post_text" : "Terveisiä Suomesta! Täällä olen talvilomalla!"
    }
    

    一旦我们将这段文本通过language-identification-pipeline发送出去,会发生什么?让我们执行之前显示的 REST API 调用,然后查看在发现标签页中导入的文档,就像我们在上一节中所做的那样。

  2. 正如我们从图 13.9中看到的文档所示,模型无法正确预测post_text字段中文本的语言:

![图 13.9 – 导入文档中的警告消息图 B17040_13_9.jpg

图 13.9 – 导入文档中的警告消息

现实世界的用例和数据集通常很混乱,包含缺失和损坏的数据,所以请注意这个消息,以便捕捉并纠正推理设置中的潜在错误!

解决此消息出现的原因的第一步是将您通过导入管道尝试导入的文档中的字段与模型元数据中存储的analyzed_fields进行比较。请参考本章中关于如何查看模型元数据的提示,即使用 Trained Models API 检查、导出和导入您的训练模型部分。

使用推理处理器配置选项来深入了解您的预测

在前面的章节中,我们以这种方式配置了我们的推理处理器,使得该处理器处理的文档只包含四个字段:文档的预测类别标签、预测的概率、预测的分数和模型 ID。然而,当您可以看到模型做出了错误的预测,或者您希望了解模型分配给其他潜在类别的概率时,会发生什么?这有助于调试。

我们如何配置推理处理器以提供更多信息?让我们看看:

  1. 让我们从上一节中看到的推理处理器配置开始,并仔细看看我们留下的空的inference_config配置块。在这种情况下,因为我们想看到不同概率的更详细分解,我们想在配置块中添加一个num_top_classes字段。此配置参数控制要写入概率的类数量。例如,如果我们将其设置为 3,每个文档将包含它最有可能属于的前三个类的概率:

    PUT _ingest/pipeline/language-identification-pipeline-v2
    {
      "description": "Pipeline for classifying language in social media posts",
      "processors": [
    {
      "inference": {
        "model_id": " lang_ident_model_1",
        "target_field": "text_language_prediction",
        "field_map": {
          "post": "text"
        },
        "inference_config": { "classification": {"num_top_classes": 3} }
      }
    }
      ]
    }
    
  2. 接下来,让我们使用以下 REST API 调用通过这个新管道language-identification-pipeline-v2摄取一个文档:

    POST social-media-feed-inference/_doc?pipeline=language-identification-pipeline-v2
    {
      "username": "Sanna",
        "statistics": {
          "likes": 320,
          "shares": 8000
        },
        "timestamp": "2021-01-20T23:19:06",
        "post" : "Terveisiä Suomesta! Täällä olen talvilomalla!"
    }
    

    我们将看到,作为结果,推理处理器将详细列出可能的语言(或如果我们使用分类术语,则为类别)到哪个后缀属于,如图图 13.10所示。可能的候选包括芬兰语,由关键字fi表示,瑞典语,由关键字sv表示,以及爱沙尼亚语,由关键字eo表示:

![图 13.10 – 给定文档可能属于的类别的详细分解以及相关的概率]

![img/B17040_13_10.jpg]

图 13.10 – 给定文档可能属于的类别的详细分解以及相关的概率

我们现在将转向使用 eland 导入模型。

使用 eland 将外部模型导入 Elasticsearch

假设你已经使用其他框架之一训练了一个模型。是否可以使用我们在上一节中讨论的构建块来部署自己的外部训练模型?答案是肯定的,但有一些限制。在本节中,我们将探讨如何使用eland库,以及另一个机器学习库scikit-learn,用于创建和训练外部机器学习模型并将它们导入 Elasticsearch 进行推理。

了解 eland 支持的外部模型

不幸的是,Elastic Stack 中的推理功能目前尚不支持从任何库导入外部训练的机器学习模型(尽管将来可能会有所改变!)相反,eland 文档(eland.readthedocs.io/en/7.10.1b1/reference/api/eland.ml.MLModel.import_model.html#eland.ml.MLModel.import_model)包含了一组生成支持模型的第三方库。目前,支持模型类型的列表如下:

  • sklearn.tree.DecisionTreeClassifier

  • sklearn.tree.DecisionTreeRegressor

  • sklearn.ensemble.RandomForestRegressor

  • sklearn.ensemble.RandomForestClassifier

  • lightgbm.LGBMRegressor

  • lightgbm.LGBMClassifier

  • xgboost.XGBClassifier

  • xgboost.XGBRegressor

    注意

    请注意,使用前面库生成的模型有一些额外的限制,这些限制与所选目标函数的类型或必须对特征执行的编码类型有关,因此请务必检查 eland 文档以获取支持的第三方模型的最最新信息。

使用 eland 将 scikit-learn DecisionTreeClassifier 训练并导入 Elasticsearch

现在我们已经了解了重要的预备知识,让我们立即开始,看看我们如何使用 scikit-learn 库训练外部机器学习模型。本指南中使用的所有代码示例都将可在本书 GitHub 仓库中的 Jupyter 笔记本中找到:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/tree/main/Chapter%2013%20-%20Inference%20and%20Advanced%20Transforms/external_models

  1. 我们项目将外部模型导入 Elasticsearch 的第一步是检索一些训练数据并使用它来训练决策树模型。scikit-learn 库拥有一个用于学习和快速原型设计的内置数据集集合。为了继续我们在第十一章分类分析和本章中开发的数据主题,我们将使用内置的威斯康星乳腺癌数据集(这是我们一直在使用的数据集的变体)。

    在我们开始之前,让我们确保将所有必需的函数和库导入到我们的 Python 脚本(或 Jupyter 笔记本)中:

    # import a variation of the breast cancer dataset we have been using in earlier chapters
    from sklearn.datasets import load_breast_cancer
    # import the function that trains a DecisionTreeClassifier
    from sklearn.tree import DecisionTreeClassifier
    # import a helper function to generate the test/train split
    from sklearn.model_selection import train_test_split 
    
  2. 现在我们已经完成了导入,让我们通过调用load_breast_cancer函数来加载数据集。

    # let's load the dataset and store the datapoints in the variable X and the class labels in the variable y
    X, y = load_breast_cancer(return_X_y=True)
    

    注意,此函数返回两个值,我们将它们存储在变量Xy中。Elasticsearch 组织其训练数据的方式与 scikit-learn 中的约定不同。在 Elasticsearch 中,我们的训练数据存储在一个单独的 Elasticsearch 索引中。索引中的每个文档代表一个数据点,它是表示特征的字段和表示因变量的字段的组合(有关因变量的更多信息以及为什么它们在监督学习中很重要,请参阅第十一章分类分析第十二章回归)。

    与 Elasticsearch 的方法相比,scikit-learn 使用包含所有特征值的向量来表示每个数据点。这些向量构成了存储在变量X中的矩阵。我们可以使用 Python 的切片语法来查看一个样本数据点的外观。一个示例在图 13.11中显示:

    ![图 13.11 – 数据点表示为字段值的向量 图片

    图 13.11 – 数据点表示为字段值的向量

    因变量存储在单独的变量y中。与之前的例子类似,我们可以使用 Python 的切片语法来查看特征值(或特征向量)所描述的数据点属于哪个类别。这如图13.12所示:

    ![图 13.12 – 第一个数据点的类别标签是 0 图片

    图 13.12 – 第一个数据点的类别标签是 0

  3. 现在我们已经导入了数据集并验证了它看起来是可接受的,我们可以继续下一步,即训练我们的决策树模型。虽然 Elasticsearch 会自动将我们的训练数据分成训练集和测试集,但在 scikit-learn 中,我们必须手动执行这一步骤。尽管在这种情况下这不是严格必要的,因为我们并不特别感兴趣于系统地衡量我们模型的性能,但我们仍将此作为示例提供给有兴趣的读者,他们需要在自己的项目中执行此操作:

    # while Elasticsearch performs the train/test split for us during the training process # in scikit-learn, we have to perform this step manually using the train_test_split function
    X_train, X_test, y_train, y_test = train_test_split(X,y, random_state=12345)
    

    如前述代码片段所示,我们将包含每个数据点特征向量的变量和包含因变量值或类别标签的变量传递给 scikit-learn 函数train_test_split,该函数返回属于训练集的特征向量(X_train)和因变量(y_train)以及测试集的特征向量(X_test)和因变量(y_test)。

  4. 现在我们已经生成了训练集和测试集,我们可以继续训练决策树分类器。第一步是创建DecisionTreeClassifier类的实例,并用训练数据集中的特征向量和类别标签来拟合它:

    # now, let's create the decision tree classifier
    dec_tree = DecisionTreeClassifier(random_state=12345).fit(X_train, y_train)
    

    训练好的模型通过dec_tree变量引用。这是我们将在本教程稍后使用 eland 序列化和上传到 Elasticsearch 的变量。首先,让我们通过要求它对测试数据集中的数据点进行分类来快速检查我们的模型(记住,这些是在训练阶段模型之前没有见过的数据点),如图13.13所示:

    ![图 13.13 – 训练好的决策树模型的预测 图片

    图 13.13 – 训练好的决策树模型的预测

    模型预测我们测试数据集的第一个数据点属于类别 1。我们可以通过检查y_test变量中的第一个元素来双重验证这个数据点的实际标签,如图13.14所示:

    ![图 13.14 – 测试集中第一个数据点的因变量值 图片

    图 13.14 – 测试集中第一个数据点的因变量值

    在这种情况下,模型的预测与数据集的实际标签相匹配。

  5. 最后,让我们准备使用 eland 将此模型上传到我们的 Elasticsearch 集群。首先,我们必须导入所需的MLModel类,如下面的代码示例所示:

    # import the required eland class
    from eland.ml import MLModel
    

    一旦我们在脚本或 Jupyter 笔记本中有了这个类,我们就可以进行下一步,即从原始 scikit-learn 数据集中检索特征名称。完成此操作所需的步骤如下所示:

    data = load_breast_cancer() 
    feature_names = data.feature_names
    

    感兴趣的读者可以打印出feature_names变量(或查看此说明的配套 Jupyter 笔记本)以查看包含哪些类型的特征。为了节省空间,我们将省略特征名称列表。

  6. 最后,我们将在MLModel类上调用import_model方法,如下面的代码片段所示:

    es_model = MLModel.import_model(
        es_client,
        model_id=model_id,
        model=dec_tree,
        feature_names=list(feature_names),
        es_if_exists='replace'
    )
    

    如您所见,这是一个需要相当多参数的方法。第一个参数es_client是 Elasticsearch 客户端对象的实例,它指定了如何连接到我们的 Elasticsearch 集群。这在本章的使用推理 API 和 Python 导出和导入训练模型部分有更详细的讨论。

    第二个参数是model_id,这是一个标识符,一旦模型被上传到 Elasticsearch 集群,它将被用来识别该模型。在这种情况下,我们已经设置了model_id变量,如下面的代码片段所示:

    model_id = "external-model_breast-cancer-decision-tree"
    

    当然,然而,您可以将此标识符设置为您的选择之一。最后,我们传递包含对训练模型引用的变量名,dec_tree,从原始数据集中检索到的feature_names列表,并将es_if_exists标志设置为'replace',这意味着如果我们多次运行代码片段,具有相同model_id的现有模型将被覆盖。在某些情况下,这可能不是期望的行为,但在这个案例中,因为我们正在原型设计,这是一个有用的标志来设置。

  7. 一旦运行了前面章节中讨论的命令,我们可以使用训练模型 API 来确定此模型是否已成功导入到我们的集群中。为此,我们将运行以下命令:

    GET _ml/trained_models/external-model_breast-cancer-decision-tree
    

    如我们所见,基于返回的 API 响应,我们的模型确实已成功导入到集群中,现在可以在摄取管道中使用。

    提醒

    图中使用的所有代码示例都可在本书 GitHub 存储库中链接的 Jupyter 笔记本中找到:github.com/PacktPublishing/Machine-Learning-with-Elastic-Stack-Second-Edition/blob/main/Chapter%2013%20-%20Inference%20and%20Advanced%20Transforms/external_models/importing-external-models-into-es-using-eland.ipynb

摘要

在本章中,我们探讨了在 Elasticsearch 和外部库(如 scikit-learn)中训练的监督模型的各种可用选项。我们学习了 Trained Models API,它在管理并检查 Elasticsearch 集群中的训练好的监督学习模型时非常有用,以及如何借助推理处理器和摄取管道,利用这些模型对先前未见过的示例进行预测。在本章附录中,我们将提供一些技巧和窍门,使与 Elastic Machine Learning 堆栈协同工作变得更加容易。

第十四章:附录:异常检测技巧

当我们结束本书的内容时,我们突然想到,还有很多很好的、适合小部分的内容、示例和建议,它们并没有很好地融入其他章节的章节中。因此,在这里为它们提供一个单独的家是有意义的,就在附录中。享受这个技巧、窍门和建议的大杂烩!

附录中,以下主题将被涵盖:

  • 理解分拆与非分拆工作中的影响者

  • 利用单侧函数

  • 忽略时间段

  • 利用自定义规则和过滤器

  • 异常检测工作吞吐量考虑

  • 避免过度设计用例

  • 在运行时字段上使用异常检测

技术要求

本章中的信息将使用存在于 v7.12 的 Elastic Stack。

理解分拆与非分拆工作中的影响者

你可能会质疑是否有必要按字段分拆分析,或者仅仅希望使用影响者能够产生识别违规实体的预期效果。

让我们再次提醒自己,影响者与分拆工作目的之间的区别。如果一个实体被异常检测工作识别为影响者,那么它对异常的存在做出了显著贡献。这种决定影响实体的概念与工作是否分拆完全独立。只有当首先发生异常时,一个实体才能被认为对异常有影响。如果没有检测到异常,就没有必要确定是否存在影响者。然而,工作可能或可能不会发现某些异常,这取决于工作是否被分拆成多个时间序列。当分拆工作的时候,你正在为所选分拆字段中的每个实体建模(创建一个单独的分析)。

让我们看看 Elastic ML 开发团队最喜欢的一个演示数据集,称为farequote(可在本书的 GitHub 存储库中找到,文件名为farequote-2021.csv,并通过 Elastic ML 的文件上传功能轻松上传到数据可视化器)。这个数据集来自一个真实客户,该客户运行了一个旅游门户应用程序。应用程序的访问日志记录了当它向第三方航空公司请求航班报价时,中间件被调用的次数。JSON 文档看起来如下:

   {
       "@timestamp": "2021-02-11T23:59:54.000Z",
       "responsetime": 251.573,
       "airline": "FFT"
}

每单位时间的事件数对应于正在进行的请求数量,而responsetime字段是针对该航空公司的航班报价 Web 服务的单个请求的响应时间。

让我们看看以下案例:

  • 案例 1:对随时间变化的计数进行分析,没有按航空公司分割,但使用航空公司作为影响因素。你可以使用如下配置的多度量向导来完成此操作:

![图 A.1 – 没有分割但有一个影响因素的工作

![图片 B17040_14_1.jpg]

图 A.1 – 没有分割但有一个影响因素的工作

分析运行后,数据中间出现的峰值(如图 A.1*中的工作配置预览屏幕所示)确实被标记为异常,得分为 27 分,AAL 航空公司被标记为影响因素:

![图 A.2 – 没有分割但发现影响因素的计数工作结果

![图片 B17040_14_2.jpg]

图 A.2 – 没有分割但发现影响因素的计数工作结果

让我们将这个结果与下一个案例中我们看到的结果进行比较。

  • airline,并使用airline作为影响因素。

    如果我们重复图 A.1*中的配置,但这次选择在airline上分割(从而设置partition_field_name:airline),我们肯定会看到航空公司 AAL 仍然是异常最不寻常的,并且其异常分数比案例 1 高得多:

![图 A.3 – 使用分割和发现的影响者airline的结果

![图片 B17040_14_3.jpg]

图 A.3 – 使用分割和发现的影响者airline的计数工作结果

换句话说,当工作分割以单独建模每个航空公司时,AAL 的异常行为更加突出。否则,当所有文档计数混合在一起时,AAL 的异常行为被某种程度上掩盖了。当我们查看下一个两个案例中responsetime字段的分割不分割的差异时,这一点更加明显。

  • airline上分割的responsetime字段。

    在这里,我们看到 AAL 也是关于responsetime字段分析的异常最不寻常的航空公司:

![图 A.4 – 使用分割和发现的影响者airline的响应时间工作结果

![图片 B17040_14_4.jpg]

图 A.4 – 使用分割和发现的影响者airline的响应时间工作结果

现在让我们将这个结果与下一个案例进行比较,在这个案例中,我们不会分割工作。

  • responsetime字段,没有分割,但仍然使用airline作为影响因素。

    在这种情况下,结果如下:

![图 A.5 – 没有分割但发现影响者的响应时间工作结果

![图片 B17040_14_5.jpg]

图 A.5 – 没有分割但发现影响者的响应时间工作结果

你可以看到我们知道的异常最不寻常的航空公司(AAL)不再被发现。在这种情况下,所有航空公司的响应时间在每个桶跨度内都被平均在一起,因为工作没有分割。现在,最突出的异常(尽管它是在正常水平之上的相对较小的变化)被显示出来,被认为是受airline=NKS的影响。然而,这可能是误导性的。你看,airline=NKS在此期间有非常稳定的响应时间,但请注意,其正常操作范围比其他组高得多:

![图 A.6 – 每家航空公司的平均响应时间图片 B17040_14_6.jpg

图 A.6 – 每家航空公司的平均响应时间

因此,NKS 对所有航空公司总聚合响应时间的贡献比其他因素更为显著。所以,当然,机器学习将 NKS 识别为最突出的影响因素。

由此可见:这里的教训是,如果你只是简单地依赖影响因素在多个实体的数据集中寻找异常实体,那么你应该深思熟虑。可能更有意义的是独立地对每个实体进行建模!

利用单侧函数的优势

许多人意识到单侧函数在机器学习中的有用性,例如low_counthigh_mean,这允许仅在高侧或低侧检测异常。当你只关心收入下降或响应时间峰值时,这很有用。

然而,当你关心两个方向的偏差时,你通常会倾向于只使用常规函数(如countmean)。然而,在某些数据集中,使用函数的高版本和低版本作为两个独立的检测器更为优化。你可能会问,为什么这种情况会发生,以及什么条件下会发生?

这种情况有意义的条件是可能的偏差动态范围不对称。换句话说,数据中潜在峰值的大小远大于潜在下降的大小,这可能是由于某个计数或总和不能小于零。让我们看看下面的截图:

![图 A.7 – 使用双向“求和”函数的分析图片 B17040_14_7.jpg

图 A.7 – 使用双向“sum”函数的分析

在这里,双向“求和”函数正确地识别了左侧的临界异常的大峰值,但中间缺少预期的双峰被仅以警告异常识别。再次强调,这是因为,使用双向函数,归一化过程将所有异常一起排序。峰值(因此是不太可能发生的)的幅度远大于 18:00 左右的数据缺失,因此异常评分被相对分配。

然而,如果数据集是用两个独立的检测器分析的,即使用高级作业,即low_sum(num_trx)high_sum(num_trx),那么结果将非常不同。这里是高侧的结果:

![图 A.8 – 使用单侧“high_sum”函数的分析图片 B17040_14_8.jpg

图 A.8 – 使用单侧“high_sum”函数的分析

这里是低侧的结果:

![图 A.9 – 使用单侧“low_sum”函数的分析图片 B17040_14_9.jpg

图 A.9 – 使用单侧“low_sum”函数的分析

注意到中间的异常现在被评分得更高(在这个例子中,最高评分为 47 个黄色)。

现在,当两个单边探测器在同一作业中一起运行时,你已经优化了每个探测器的动态范围(因为它们有自己的归一化表)!

忽略时间段

经常有人问如何让机器学习(ML)忽略某些事件已经发生的事实。可能是因为预期的维护窗口,或者可能是数据摄取管道中出现了故障,导致数据丢失了几分钟。有几种方法可以让机器学习忽略特定的时间段,为了区分,我们将它们分为两组:

  • 已知即将到来的时间段

  • 在事后才发现的不寻常的时间窗口

为了说明问题,我们将使用一个单一指标计数作业(来自图 A.1)在farequote数据集上,该作业在 2 月 9 日的日期上存在异常:

图 A.10 – 对 farequote 数据集的分析,其中包含我们希望忽略的异常情况

图 A.10 – 对 farequote 数据集的分析,其中包含我们希望忽略的异常情况

现在,让我们探讨我们可以使用不同情况忽略 2 月 9 日异常的方法。

忽略即将到来的(已知)时间段

可以使用两种方法来忽略即将到来的时间窗口,如下面的子节所示。一种涉及创建一个特殊的日历事件,另一种则是操纵数据源运行的时间。

创建日历事件

你可以通过点击设置,然后在日历部分下的创建来轻松创建一个事件。在这里,我为 2 月 9 日创建了一个日历条目:

图 A.11 – 创建日历事件以忽略特定的时间段

图 A.11 – 创建日历事件以忽略特定的时间段

如果创建了一个新的作业(在这个案例中,属于farequote_jobs组,以便遵守这个日历),那么如果作业在数据上运行,2 月 9 日整个一天将被完全忽略:

图 A.12 – 通过日历事件来忽略一个时间段

图 A.12 – 通过日历事件来忽略一个时间段

如你所见,整个一天都被屏蔽了,包括异常峰值的时间。

停止和启动数据源以忽略所需的时间段

通过在适当的时间停止和重新启动异常检测作业的数据源,你可以在分析中创建一个间隙。在这里,数据源在 2 月 9 日凌晨停止,并在 2 月 10 日凌晨重新启动:

图 A.13 – 通过操纵数据源来忽略一个时间段

图 A.13 – 通过操纵数据源来忽略一个时间段

就像 2 月 9 日从未发生过一样!现在,让我们讨论一下你可以在事后忽略时间窗口的方法。

事后忽略一个意外的时间窗口

回到过去忘记一个时间窗口发生了,我们可以使用两种方法。第一种涉及简单的克隆和重新运行历史数据,第二种涉及使用模型快照。

克隆作业并重新运行历史数据

与我们之前看到的类似,导致图 A.13,我们可以创建一个新的克隆作业,并仅让数据馈送避免你希望忽略的时间窗口。在窗口开始时停止它,并在窗口结束时恢复它。如果从现有(仍然可用)的历史数据重建模型不是那么繁重,这种方法效果很好。然而,如果你有真正成熟的模型,这些模型封装了从你不再能访问的数据(因为数据已过时并被从你的集群中删除)中获取的数据行为,那么你将需要使用下一节讨论的模型快照技术。

将作业还原到先前的模型快照

当不希望或实际上无法在现有历史数据上克隆和重新训练作业时,你可以通过利用运行作业定期获取模型快照的事实来有效地移除一段时间窗口。默认情况下,快照大约每 3 到 4 小时捕获一次。当你创建或更新作业时,你可以更改此间隔(background_persist_interval)。

注意

这些快照的保留由几个其他参数(如daily_model_snapshot_retention_after_daysmodel_snapshot_retention_days)控制。请参阅 Anomaly Detection API 文档www.elastic.co/guide/en/machine-learning/current/ml-api-quickref.html

将异常检测作业还原到先前快照的基本步骤如下:

  1. 如果作业正在运行,请停止作业的数据馈送。

  2. 使用www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-snapshot.html中记录的get snapshots API 调用,找到你希望擦除的时间窗口之前最近拍摄的模型快照。

  3. 通过点击还原图标 () 将作业还原到该快照,或者如果使用 API,则使用_revert命令。在 Kibana UI 中,你将看到如何删除数据以及如何在快照时间之后重新分析历史数据的选项,包括使用日历事件屏蔽问题时间段的能力:图 A.15 – 使用屏蔽时间段的能力还原到先前的模型快照

    图 A.15 – 使用屏蔽时间段的能力还原到先前的模型快照

  4. 如果需要,在忽略的时间段之后继续实时运行数据馈送。

在所有这些有用的选项中,您可以轻松地确定忽略时间间隔的正确方法,并防止异常检测作业受到问题操作问题或不受欢迎事件的影响。

利用自定义规则和过滤器

虽然异常检测作业非常有用,但它们对领域和原始数据的相关性却是无知的。换句话说,无监督的机器学习算法不知道 CPU 利用率增加十倍(例如,从 1% 增加到 10%)可能对应用程序的正常运行并不那么有趣,尽管在统计上可能是异常的/不太可能。同样,异常检测作业对每个分析实体都同等对待,但用户可能希望拒绝某些 IP 地址或用户 ID 的结果,因为用户知道这些实体发现的异常是不希望或不实用的。使用自定义规则和过滤器允许用户将领域知识注入异常检测作业配置中,从而对被认为或标记为异常的内容有相当的控制权——甚至是否将实体首先考虑为建模过程的一部分。

创建自定义规则

要定义自定义规则,您可以在作业创建时完成(但仅当使用创建作业 API 时)或使用异常探索器用户界面中的“操作”菜单中的“配置规则”菜单选项在作业揭示了一些异常后进行:

图 A.16 – 异常探索器用户界面中的“配置规则”菜单项

图 A.16 – 异常探索器用户界面中的“配置规则”菜单项

在定义规则时,大多数情况下是自我解释的。在这里,我们可能决定,尽管我们的响应时间异常为 282.025 毫秒(如 图 A.16 所示),但这并不那么有趣,我们希望忽略响应时间仍然低于 1 秒(1,000 毫秒)的异常。我们可以定义如下规则:

图 A.17 – 创建规则的用户界面

图 A.17 – 创建规则的用户界面

有额外的选项可以排除值用于建模,也可以将规则的适用范围限制在特定的过滤器列表中,以便规则仅适用于特定的实体(例如,仅限于位于特定位置的服务器)。过滤器列表可以在用户界面中的“设置”和“过滤器列表”下定义。

注意,规则定义适用于未来的分析(从规则定义点开始,时间向前推进)且不适用于过去异常。若要让规则适用于过去的异常,您需要克隆现有的作业(一旦定义了规则)然后对历史原始数据重新运行分析。

因此,通过规则和过滤器,用户对最终报告(并警报)为异常的内容有相当大的控制权。这允许在 IT 运营中存在了几十年的传统自下而上的警报创建哲学发生相当大的范式转变。在下一小节中描述了另一种方法。

从“自上而下”的警报哲学中受益于自定义规则

如果我们问,“你收集的数据中有多少比例是被关注的?”通常,一个现实的答案可能是小于 10%,甚至可能小于 1%。这种情况的原因是,传统的将数据变得主动的方法是从头开始,然后随着时间的推移建立阈值或基于规则的警报。这可能是一项令人畏惧且/或繁琐的任务,需要事先了解(或至少猜测)每个时间序列的预期行为。然后,一旦配置了警报,可能会有一个长期的调整过程,以平衡警报的敏感性与令人烦恼的误报。此外,也可能存在一些指标,其异常行为无法通过静态阈值捕捉到。

将这个挑战与规模结合起来;如果我每个服务器有 10 个指标,而我有 100 台服务器,那么就有 1,000 个单独的指标。为这些中的每一个创建单独的警报是不切实际的。

然而,针对这些数据创建单个异常检测任务可能不到 1 分钟。弹性机器学习在历史数据上的自学习,这也花费很少的时间,将通过独立适应每个时间序列的自然特征来最小化误报。然而,如果异常检测揭示了我们不关心的事情,我们可以简单地通过自定义规则排除它们。

这种自上而下的方法(覆盖所有内容,然后开始排除不需要的内容)比自下而上的方法(从头开始创建阈值警报)更快,并且为数据提供了更广泛的前瞻性覆盖。

异常检测任务吞吐量的考虑

弹性机器学习(Elastic ML)非常出色,毫无疑问非常快且可扩展,但任何异常检测任务每秒处理的事件数量仍将有一个实际的上限,这取决于几个不同的因素:

  • 数据可以传递给算法的速度(即查询性能)

  • 在给定所需分析的情况下,算法处理数据的速度

对于后者,大部分性能基于以下因素:

  • 为分析选择的功能,即 count 比较快于 lat_long

  • 选择的 bucket_span 值(较长的桶跨度比较小的桶跨度更快,因为每单位时间内分析的桶更多,这会累积每桶的处理开销,例如写入结果等)

然而,如果你已经定义了分析集并且由于其他原因无法更改,那么除非你富有创意地将数据分割成多个作业,否则你几乎无能为力。这是因为 ML 作业(至少目前)目前与单个 CPU 的分析部分(运行名为 autodetect 的 C++进程)绑定。因此,将数据分割成几个单独的 ML 作业,至少可以充分利用多个 CPU,可能是一个选择。但在那之前,让我们专注于前者,即查询的性能,因为这里有各种各样的可能性:

  • 避免进行跨集群搜索以限制网络上的数据传输。

  • 调整数据馈送参数以优化性能。

  • 使用 Elasticsearch 查询聚合将提取数据的任务分配给更小的 ML 算法集。

第一个似乎是显而易见的。只有当你将分析移得更接近原始数据时,你才能提高性能。

第二个可能需要进行一些实验。有一些参数,例如scroll_size,控制每个滚动的大小。默认值为 1,000,对于中等大小的集群,这个值可以安全地增加到 10,000。在不同的滚动大小下进行一些测试,看看它如何影响查询和集群性能。

我认为最后一个对性能的影响最大,但显然,要正确配置 ES 聚合以使其与 ML 正常工作,这有点棘手且容易出错,但还不算太糟糕。有关更多信息,请参阅www.elastic.co/guide/en/machine-learning/current/ml-configuring-aggregation.html。使用 ML 聚合的一般缺点是,你将失去访问数据中其他可能作为影响因素的字段。

总的来说,这些都是优化 ML 作业性能时需要考虑的几个方面。

避免过度设计用例

我曾经与一个用户合作,我们讨论了异常检测的不同用例。特别是,这位客户正在构建一个托管安全运营中心,作为他们托管安全服务提供商MSSP)业务的一部分,因此他们热衷于考虑 ML 可以帮助的用例。

他们的用例的一个高级主题是查看用户的行为并找到意外行为。讨论的一个例子是来自不寻常/罕见位置的登录活动,例如鲍勃刚刚从乌克兰登录,但他通常不会从那里登录

在思考实现过程时,讨论了他们拥有多个客户,每个客户都有多个用户的情况。因此,他们正在考虑如何分割/划分数据,以便为每个客户的每个用户执行按国家划分的稀有情况

我让他们退一步思考,问道:“如果任何人从乌克兰登录,不仅仅是鲍勃,这算不算一个异常?”得到的回答是“是的。”

因此,在这种情况下,没有必要按用户分割分析;也许只需在客户端级别保持分区,并将每个客户端的用户位置汇总到一个观察国家的单一池中。这实际上是一个更好的场景;整体数据更多,而且正如我们所知,rare函数在有大量常规数据可供对比新观察时工作得最好。

使用运行时字段进行异常检测

在某些情况下,可能有必要分析索引映射中不存在的字段的值,但这些值可以从其他字段的值动态计算得出。这种动态定义字段值的能力已经在 Elasticsearch 中存在了一段时间,被称为脚本字段,但从 v7.11 版本开始,脚本字段被一个更新的概念所取代,称为运行时字段。简而言之,运行时字段在 Elasticsearch 映射(如果在那里定义)中被视为一等公民,并最终允许用户将运行时字段提升为索引字段。

用户可以在映射中定义运行时字段,也可以只在搜索请求中定义。值得注意的是,在撰写本文时,异常检测作业的数据馈送中对运行时字段的定义没有支持。然而,如果运行时字段在映射中定义,那么异常检测作业可以无缝地利用它们。

注意

更多关于运行时字段的信息,请参阅 Elastic 文档www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html

虽然运行时字段的全部细节超出了本书的范围,但重要的是要知道,异常检测作业可以利用这些动态字段,就像它们是正常字段一样。让我们看看一个有趣但人为构造的例子。

假设我们回到图 A.1中所示的farequote示例,在这个论点中,我们宣布 2 月 9 日对airline:AAL来说是一个特殊的日子——可能是黑色星期五或网络星期一的大致等同,或者甚至只是我们知道事情会比正常情况稍微偏离已知量的那一天。我们将构造一个场景,其中我们知道 AAL 将经历可预测的更高响应时间,这可能会比正常情况慢 20%(意味着responsetime的测量应该比正常情况高 20%,以毫秒为单位)。我们不想让 2 月 9 日成为 Elastic ML 完全避免的日历事件,我们也不想停止查看 AAL 的数据。我们只是想将响应时间测量值降低 20%,以免影响我们的正常建模和/或警报。我们可以通过运行时字段来实现这一点:

  1. 首先要做的是在索引的映射中定义一个新的运行时字段,称为responsetime_adjusted

    PUT farequote/_mapping
    {
      "runtime": {
        "responsetime_adjusted": {
          "type": "double",
          "script": {
            "source": "emit(params._source.responsetime * 1.0)"
          }
        }
      }
    }
    

    这个字段(目前)将与其他航空公司的responsetime字段完全相同,只需通过将字段值乘以常数1.0即可实现。

  2. 接下来,我们将配置一个作业,使用新的responsetime_adjusted字段上的high_mean检测器,我们还将对airline字段进行拆分分析:![图 A.18 – 配置作业以分析运行时字段 图片 B17040_14_19.jpg

    图 A.18 – 配置作业以分析运行时字段

  3. 我们将运行数据馈送到 2 月 9 日的午夜,但在这里停止分析。为了将 AAL 数据的响应时间降低 20%(但不对其他航空公司的数据进行调整),我们将执行以下命令:

    PUT farequote/_mapping
    {
      "runtime": {
        "responsetime_adjusted": {
          "type": "double",
          "script": {
            "source": "if(doc['airline'].value.equals('AAL')) {emit(params._source.responsetime * 0.8)} else {emit(params._source.responsetime * 1.0)}"
          }
        }
      }
    }
    
  4. 接下来,我们将继续作业的数据馈送以分析特殊日子(2 月 9 日,但停止在 2 月 10 日开始前的午夜)。

  5. 一旦 2 月 9 日的数据被分析,我们将通过重新调用步骤 1中的命令,将 AAL 的数据的响应时间恢复到正常状态。

  6. 我们将允许作业继续分析剩余的数据,就像平常一样。

    最终结果是,我们能够成功地将 AAL 的响应时间值降低了 20%(如两个注释之间的低值所示),尽管我们对 AAL 进行了特殊处理,但我们仍然能够捕捉到显著的异常:

![图 A.19 – 分析动态更改的运行时字段的作业结果图片 B17040_14_20.jpg

图 A.19 – 分析动态更改的运行时字段的作业结果

这种技术可以在分析过程中动态地对数据进行任何数量的修改,以增强分析或支持对索引默认字段映射中可能不可用的数据方面的分析。

摘要

Elastic ML 是一个强大、灵活且易于使用的功能,它将数据科学的权力赋予了非数据科学家,使他们能够深入了解大量数据。在整个这本书中,用户可以利用技术以多种方式解决 IT 领域的现实世界挑战。我们希望您能将在这本书中学到的知识应用于一些您自己的优秀用例。不要担心第一天就解决所有可能的问题——从小处着手,获得一些实际的胜利,随着您获得更多信心,逐渐增加您的使用量。成功会孕育成功!

Packt.com

订阅我们的在线数字图书馆,全面访问超过 7,000 本书籍和视频,以及领先的工具来帮助你规划个人发展和职业进步。更多信息,请访问我们的网站。

第十五章:为什么订阅?

  • 使用来自超过 4,000 位行业专业人士的实用电子书和视频,节省学习时间,增加编码时间

  • 通过为你量身定制的技能计划提高你的学习效果

  • 每月免费获得一本电子书或视频

  • 完全可搜索,便于轻松访问关键信息

  • 复制粘贴、打印和收藏内容

你知道 Packt 为每本书提供电子书版本,包括 PDF 和 ePub 文件吗?你可以在packt.com升级到电子书版本,并且作为印刷书客户,你有权获得电子书副本的折扣。有关更多信息,请联系我们customercare@packtpub.com

www.packt.com,你还可以阅读一系列免费的技术文章,注册各种免费通讯,并享受 Packt 书籍和电子书的独家折扣和优惠。

你可能还会喜欢的其他书籍

如果你喜欢这本书,你可能对 Packt 的其他书籍也感兴趣:

高级 Elasticsearch 7.0

Wai Tak Wong

ISBN: 978-1-78995-775-4

  • 在摄取管道中索引之前预处理文档

  • 学习如何在现实世界中建模你的数据

  • 掌握使用 Elasticsearch 进行探索性数据分析

  • 了解如何构建分析和 RESTful 服务

  • 使用 Kibana、Logstash 和 Beats 进行仪表板应用程序

  • 通过 Spark 和 Elasticsearch 掌握实时分析

  • 探索 Spring Data Elasticsearch 的基础知识,并了解如何在 Spring 应用程序中索引、搜索和查询

学习 Elastic Stack 7.0

Pranav Shukla, Sharath Kumar M N

ISBN: 978-1-78995-439-5

  • 安装和配置 Elasticsearch 架构

  • 使用 Elasticsearch 解决全文搜索问题

  • 通过聚合使用 Elasticsearch 发现强大的分析功能

  • 构建数据管道,将来自各种来源的数据传输到 Elasticsearch 进行分析

  • 使用 Kibana 创建交互式仪表板,有效地用你的数据讲故事

  • 学习如何安全、监控和使用 Elastic Stack 的警报和报告功能

  • 使用 Elastic Stack 将应用程序部署到本地或基于云的生产环境

Packt 正在寻找像你这样的作者

如果您有兴趣成为 Packt 的作者,请访问authors.packtpub.com并今天申请。我们已与成千上万的开发者和技术专业人士合作,就像您一样,帮助他们将见解与全球技术社区分享。您可以提交一般申请,申请我们正在招募作者的特定热门话题,或者提交您自己的想法。

留下评论 - 让其他读者了解您的想法

请通过在您购买书籍的网站上留下评论,与其他人分享您对这本书的看法。如果您从亚马逊购买了这本书,请在该书的亚马逊页面上留下一个诚实的评论。这对其他潜在读者来说至关重要,他们可以看到并使用您的客观意见来做出购买决定,我们也可以了解客户对我们产品的看法,我们的作者也可以看到他们对与 Packt 合作创建的标题的反馈。这只需您几分钟的时间,但对其他潜在客户、我们的作者和 Packt 来说都非常有价值。谢谢!

posted @ 2025-09-03 10:08  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报