与基于规则的测试和指标监控相比,机器学习是一种具有许多优势的统计方法:它具有可扩展性,可以检测未知的变化,尽管有对人格化的风险,但它是智能的。它可以从先前的输入中学习,利用上下文信息来减少误报,并且随着时间的推移越来越好地理解您的数据。
在前面的章节中,我们探讨了何时以及如何将机器学习与数据质量监控策略相结合。现在是时候探索核心机制了:您如何训练、开发和使用模型来检测数据质量问题,甚至解释其严重性以及在数据中发生的位置等方面。
在本章中,我们将解释哪种机器学习方法最适合数据质量监控,并向您展示可以遵循的算法(一系列步骤)来实现这种方法。我们将回答一些问题,比如您应该采样多少数据,以及如何使模型的输出可解释。需要注意的是,按照这里的步骤不会导致一个可以监控实际数据的模型。在第五章中,我们将转向调整和测试系统的实际方面,以确保其在企业环境中可靠运行。
需求
对于给定问题,您可以应用许多机器学习技术。为了找到适合您用例的正确方法,有必要在开始时定义要求。我们认为用于数据质量监控的模型应具备四个特性:敏感性、特异性、透明度和可扩展性。
敏感性
敏感性是衡量机器学习模型能够检测真正阳性的能力。为了有效,算法应该能够在真实的表格数据中检测到各种各样的数据质量问题。一个好的基准是能够检测到影响1%或更多记录的变化。
在实践中,我们发现试图检测影响少于1%的记录的变化会产生一个噪音太大的系统。即使检测到的变化在统计上是显著的,但要对它们进行分类和理解是非常困难的,特别是在规模化到大量复杂表格时。我们的经验表明,影响1%或更多记录的变化是数据生成或转换过程中的重大结构性变化,可能是主要的新冲击和伤痕。
要找到小于1%的变化,您可以使用确定性方法(如验证规则),或者您可以通过在查询最重要的记录的视图上运行模型来将机器学习集中在数据的子集上。
例如,社交媒体平台可能在单个大型事件处理表中跟踪数百种不同类型的事件。在整个表上运行机器学习模型将捕捉到格式和结构方面的粗略问题,以及收集的最常见事件类型。但如果您想密切关注所有事件,您可以选择在每个事件特定的子集上运行模型。
特异性
特异性是敏感性的对应物,它告诉您模型在不触发误报警报方面的表现有多好。这在数据质量监控中特别重要,因为警报疲劳可能会威胁整个方法的采用和有效性。
通常,监控系统往往会出现过多的警报,原因有几个。一个原因可能是季节性------如果数据中有每日、每周或每年重复的模式,数据看起来可能会发生变化,但实际上并没有以异常或意外的方式变化。如果监视器无法聚类受同一数据变化影响的相关列,那么监视器也会出现噪音。或者,如果监视器对数据进行的样本量太小或对数据评估的时间窗口太小,那么它可能会发送误报警报。此外,有些数据集比其他数据集更"混乱",因此检查的敏感性阈值需要根据每个数据集进行校准(并且可能需要随着时间的推移而发展)。
在第五章中,我们将探讨模型如何学习和考虑季节性、相关性和其他真实世界数据的挑战。
透明度
当问题出现时,模型应该是透明的,并帮助用户理解和排查问题。您可能认为这与模型本身无关------毕竟,任何精美的可视化和根本原因分析都将在检测到数据问题之后发生。但您的选择确实取决于您使用的机器学习方法。您的模型架构和实现将决定您能够解释和归因其预测的程度。例如,一些机器学习特性可能有助于提高准确性,但在数据质量的背景下很难向用户解释。
可扩展性
为了在数据仓库中每天运行数十亿行数据,您的系统还必须具有可扩展性------在人力、存储和计算成本方面。它不应该需要管理员进行前期配置或调整即可运行,因为这只会创建另一种手写规则,而我们在第2章已经显示这不是一种可扩展的解决方案。它应该对数据仓库的查询足迹最小,并且能够在仓库之外的廉价硬件上快速执行。这些约束将影响我们建模决策的许多方面,并且我们将在本章中讨论如何使解决方案更具可扩展性。
非要求
定义系统不需要做什么与定义它应该做什么一样有用。您可能还记得第2章中提到的,无监督的机器学习模型应该是一个三柱数据质量方法的一部分,该方法还包括基于规则的测试和度量监控。这是因为期望自动化解决每个数据质量问题是不可行的。以下是我们模型的非要求列表:
• 它不需要识别不良的单个记录(这是基于规则的测试的作用:当您需要数据完美无缺时)。相反,我们期望它寻找意义记录中的结构性变化。
• 它不需要实时处理数据。实时评估用于数据质量检测的机器学习模型不仅难以扩展,而且可能被迫评估单个记录,这不在我们的范围内。相反,我们期望它在每天或每小时的批处理中评估数据。
• 我们不能指望它能够确定数据是否一直都是损坏的------这不是机器学习的工作方式,因为模型必须基于历史数据进行训练。如果历史数据是错误的,我们对此无能为力!这就是为什么机器学习方法只能用于识别数据中的新变化。
• 我们不能指望它在没有时间概念的情况下分析数据。该模型将随着时间跟踪数据以检测变化。如果数据本身没有内置时间戳,我们将需要开发其他方法来识别数据生成的时间(稍后将详细介绍)。
数据质量监控不是异常检测
在我们讨论数据质量监控模型的要求时,值得花点时间解决一个常见的困惑:异常检测与数据质量监控之间的区别。 异常检测是理解复杂数据集的一种有用方法。有许多识别异常值的方法,但其中一种最具可扩展性和灵活性的方法是使用随机森林的一种变体,称为隔离森林,来识别数据行远离多变量分布"中心"的情况,如图4-1所示。
异常检测可以通过机器学习实现,其目的是发现数据中的异常情况。但是与数据质量监控的相似之处就到此为止了。毕竟,每个数据集都会有异常观察结果------即使是正态分布也会有极端值!这些异常值可能很有趣(它们可能是欺诈记录,也可能只是非常罕见的事件或数据组合),但它们不一定会是数据质量问题,因为无论是常见记录还是罕见记录,它们都可能受到同等概率的影响。
要识别数据质量问题,我们需要知道数据进入表格时分布是否突然发生了结构性变化。我们需要知道过去记录是否总是以某种分布、模式或关系出现,而现在突然之间发生了显著变化。另一方面,每个数据集都有异常值。异常检测正在解决一个根本不同的问题。
ML方法和算法
既然我们已经介绍了需求,接下来我们将分享我们推荐的方法以及您可以遵循的实施步骤。我们不敢断言这是使用ML检测数据质量问题的唯一方法,但在实践中,我们尚未遇到能够更有效地满足需求的方法。和往常一样,细节决定成败。诸如特征工程和参数调整/抑制之类的事项,在有效实施和在实际数据上过度或不足警报之间产生了很大的区别,我们将在第5章中进一步讨论。
请回想一下,我们想要开发一个ML模型来检测数据中的意外变化,而不需要任何人对数据进行标记,并告诉我们什么构成了数据质量问题。这使得这种类型的ML问题成为一种无监督学习任务。然而,数据中确实存在一种我们可以像人类标签一样使用的特征,那就是数据进入表格的时间。
这种方法的关键洞见就在于此。每天,我们都会对数据进行快照。然后,每天我们都尝试训练一个分类器来预测数据是否来自今天。
如果今天的数据在统计上没有什么显著的特点,那么我们尝试训练分类器就会失败------预测数据是否来自今天应该是一项不可能的任务,基本上就像是抛硬币一样!
另一方面,如果我们能够构建一个能够相当准确地预测一条数据是否来自今天的分类器,那么我们可以相当确定今天的数据确实有些不寻常。而且这种不寻常是有意义的------因为几条随机更改的记录并不足以训练模型来做出一种或另一种预测。事实上,我们甚至能够利用这种方法来判断变化的重要性,并设定适当的阈值,以避免警报疲劳。通过解释模型的预测,我们可以解释数据内部最可能发生的情况。
现在您已经了解了主要思路,让我们更详细地探讨每个步骤:
数据采样
如何构建用于训练模型的数据集,以及适当的样本大小是多少?
特征编码
如何将您表中的一行转换为模型可以用来进行预测的特征集?
模型开发
对于这种算法,什么是正确的模型架构,以及如何训练模型?
模型可解释性
一旦您训练了一个模型,如何使用它来解释数据质量问题?
数据采样
构建任何模型的起点是通过从总体数据池中进行采样来创建一个训练数据集。对于我们刚刚描述的算法,您需要从"今天"(标签为1,表示我们试图预测的类别)和"不是今天"(标签为0)中随机抽样出一组稳健的数据。 "不是今天"的数据应该是先前的时间比较期间的混合:昨天(或上次数据更新的时间)用于突然变化,以及其他时间的一周或一年,以控制季节性(参见第91页的"季节性"部分)。 例如,在图4-2中,我们看到了一个示例数据集,每天有15万至25万行数据。一个稳健的样本可能包括来自以下每个日期的1万行:
- 2021-01-16:您想评估数据质量问题的日期
- 2021-01-15:昨天,这将有助于确定任何突然变化
- 2021-01-09:一周前,以控制每周的季节性
- 2021-01-02:两周前,以防上周出现异常情况
样本大小
实践中,我们发现这种算法每天至少需要100条记录才有可能发现相对复杂数据中的有意义的变化。但这引发了一个问题------什么是对算法有用的记录数量的上限? 您的采样率可以选择平衡计算成本和准确性。我们已经对每天增加数十亿行数据的数据集运行了这个算法。实际上,根据严格的测试,我们发现每天的记录数为10,000条(如果随机抽样)提供了足够的数据来捕获大多数数据质量问题,即使是影响1-5%记录的问题也是如此。随着样本量超过100,000条,质量改进会下降。
大样本量(比如每天1,000,000条记录)是可以使用的,但计算成本尚未被证明具有价值。数据集需要非常非常稳定(背景噪音很小),变化必须在非常小的记录百分比(例如0.1%的记录)上,这种增加样本量才有价值。
采样固定样本大小(10,000条记录)似乎是个错误,而不是采样数据的10%。毕竟,如果我有10亿条记录,那么10,000条如何能代表如此庞大的人口呢? 或许反直觉的是,因为样本完全是随机选择的,它的准确性并不取决于数据的总量,而只取决于绝对的样本量。例如,考虑估算一个国家的平均收入。仅仅因为中国的人口是14亿,卢森堡的人口是60万,这是否意味着我们需要在中国采样更多的人来估算平均收入?不。在这两种情况下,我们都可以抽样1,000人,并得到一个非常好的平均收入估算。
偏差和效率
样本必须从表中随机抽取。如果抽样中存在任何偏差,那么算法将能够发现该偏差,并将其表示为数据中的错误变化,这将使用户感到困惑。
保证采样尽可能高效也至关重要。实际上,在这种类型的系统中,从数据仓库中获取随机记录通常是最昂贵的操作。这是因为表中可能有数十亿甚至数万亿的记录,并且有数百或数千列。如果一个查询天真地要求读取每条记录到内存或通过网络发送它以进行采样,那么这对性能来说将是灾难性的,并会产生大量的数据仓库成本。
偏差和效率有时会有一个跷跷板的关系。例如,有效地在现代数据仓库中扩展随机抽样的一种方法是使用TABLESAMPLE操作符而不是random()调用。TABLESAMPLE操作符是以一种使数据仓库能够在查询执行期间高效地抽样随机记录的方式实现的,而不必读取记录到内存中,但在某些情况下,可能会对偏差产生负面影响。
在BigQuery中,TABLESAMPLE操作符的实现是通过"随机选择表中的数据块的百分比,并读取所选块中的所有行"来完成的。文档继续解释说,通常,"如果表或表分区的大小大于约1 GB,则BigQuery会将其分割成块。"这意味着,实际上,TABLESAMPLE操作符返回的结果通常在BigQuery中并不是随机的,而可能完全在一个分区中。
如果您对您经常用于连接的标识符(例如客户ID)对数据进行了分区,那么您将具有特定的客户子集,这些子集在您的随机样本中出现的可能性将远远大于其他子集。这可能会对您的机器学习结果造成实质性的偏差,导致您在时间上持续看到用户群体的转变,而这完全是由于采样实施方式而不是数据本身的任何真实漂移造成的。
那么,如何有效地进行采样并避免偏差呢?以下是我们的建议:
• 确保每次运行算法时只使用少量天数的数据。这些天可以存储为快照,以便不必再次查询它们(尽管重新查询它们可能是有意义的,因为表中的历史数据可能已经发生了变化)。
• 确保表在您用来选择数据的日期列上进行了分区。这使得数据仓库能够有效地导航到表示这些日期的磁盘文件,并且只读取和处理这些日期的数据,而不必访问其他无关的日期。
• 使用TABLESAMPLE操作符高效地采样一个近似随机样本,其大小大于您需要的样本量(例如,如果您需要0.3%,那么抽样1%)。通常,可以使用此操作符采样的下限百分比为1%,尽管实现会因数据仓库而异。请注意,并非所有数据库或数据仓库都以健壮的方式支持TABLESAMPLE操作符,请参阅上文关于BigQuery的段落。
• 计算您查询的日期上将会有的总记录数,以了解您需要查询的确切样本百分比。
• 使用以下形式的代码进行最终随机采样:random() <= X,其中X是在TABLESAMPLE操作后每个日期上的行数的正确数目。这比类似于order by random() limit 10,000的方法要高效得多,后者需要将所有数据加载到数据仓库中的主节点中,并根据随机数进行排序,然后应用限制。random() <= X方法的好处是它可以在每个工作计算节点中以分布式方式应用于仓库。请注意,这种方法的小缺点是您的随机样本不太可能恰好是10,000行,而可能是一个非常接近的数字。
查询数据时的另一个重要考虑因素是确保WHERE SQL过滤器的实施效率。例如,对于具有日期列created_date的表,日期列采用YYYY-MM-DD格式的字符串,以下代码将非常低效:
WHERE cast(created_date as date) = cast('2023-06-01' as date)
该代码需要数据库读取每个分区并将created_date列在内存中进行转换,以决定是否应包含记录。相反,尝试:
WHERE created_date = '2023-06-01'
现在,数据仓库可以使用关于每个分区的元数据来决定哪些分区完全被查询排除。对于以不同日期或时间分区的表来说,这可能会非常具有挑战性。在Anomalo,我们不得不为图4-3中的所有类型添加支持。
特征编码
通常情况下,机器学习模型不会直接在原始数据上进行训练,而是使用数字特征进行学习,这些特征是将原始数据转换为模型可以使用的信号。原始数据的转换方式对模型的性能有着重大影响,通常需要机器学习方面的专业知识以及数据和问题领域的专业知识。在我们的异常检测算法中,这个过程称为特征工程,必须完全自动化。
其工作原理如下:样本中的每条记录都有多列,每列可能是整数、浮点数、字符串、布尔值、日期或时间戳,或者是JSON或数组等复杂类型。您需要一个自动化的过程,遍历每一列(必要时将复杂类型如JSON展开为子列------有关此内容的更多信息,请参见第44页的"半结构化数据"),提取可能对您的模型有用的信息,并将这些信息编码为机器学习特征的浮点矩阵。
您需要开发一系列候选编码器类型的库,以便根据您认为能够告诉您数据是否以有意义的方式发生变化的特征来应用(参见图4-4)。以下是我们推荐的一些编码器:
Numeric
将布尔、整数和浮点值转换为浮点数
Frequency
每个值在数据样本中出现的频率
IsNull
一个二进制指示器,用于表示列是否为空值
TimeDelta
记录创建时间与某一时间之间的秒数
SecondOfDay
记录创建的时间的一天中的时间
OneHot
一种独热编码器,允许您将特征值(如类别或频繁整数值)映射到每一列中唯一值的二进制"是"或"否"指示变量上
您必须小心地制作您的编码器,因为最终,您需要使用这些编码器来向用户解释数据质量问题。例如,我们测试了一种针对时间、整数和数值字段的"间隙"编码器,该编码器将每个观察结果与该列中下一个最大值之间的间隙进行计算。在实践中,这种方法能够检测到某些类型的数据质量问题,但它也会检测到许多其他数据中的变化,这些变化对我们的目的来说可能难以理解和/或不相关,例如数据记录方式的变化或观测量(因此密度)的不相关变化。
模型开发
为了满足可扩展性要求并在实际环境中运行,您需要一个模型架构,它在推断和训练上速度快,可以在相对较小的样本上进行训练,并且将可以推广到任何类型的表格数据(当特征编码得当时)。梯度提升决策树非常适合这种用例,您会发现像XGBoost这样的库可以轻松用于模型开发。
梯度提升决策树以迭代的方式工作,通过在数据集上构建一系列决策树,其中每棵树(或"步骤")都旨在纠正之前所有树的错误。最终,模型的预测考虑了在每个步骤训练的所有树的结果(这被称为集成模型)。请参见图4-5。
梯度提升决策树具有非常少的真正重要的参数可供调整(主要是学习率和每棵树的复杂性,尽管还有其他参数),并且可以在包含数千甚至数百万条记录的数据集上非常快速地进行训练。一些替代方法,如线性模型,对于学习大多数结构化数据集中的复杂模式来说过于简单。
而另一些方法,如神经网络,对于这类问题来说通常过于复杂,并且需要极大量的异构数据才能变得非常强大(例如图像和语言模型)。与任何结构化机器学习技术一样,梯度提升决策树的缺点在于它们需要进行特征工程:人类专家必须告诉模型在进行预测时应该考虑数据的哪些方面,而这可能需要大量的时间和精力。
训练和评估
理论上,梯度提升决策树可以无休止地进行迭代,因此将步数限制在某个限制之内至关重要。为此,您通常会希望在每个步骤之后评估模型的性能。选择您的数据的一个随机部分作为保留集进行评估(而不是训练),并在每次迭代之后测试模型。您模型的性能将表明今天的数据是否存在异常。
具体来说,我们在实践中通常看到三种模型性能模式,如图4-6所示。在这些图表中,x轴表示添加到模型中的树木数量(迭代次数),而y轴绘制了模型准确度的一种度量(损失函数的对数)。请注意,这里,y轴技术上绘制了模型预测中的"错误"量,因此较低的值表示较高的准确度。
第一种情况,"无异常",是指在训练数据上几乎没有取得进展,而测试数据的性能却很快开始变差。这意味着新数据不太可能存在任何异常。
第二种情况,"不完全",发生在模型没有足够时间收敛的情况下。您达到了树木的最大数量(设置为防止模型无限期运行),但仍然发现训练误差和测试误差在下降。您需要添加更多的树,或者更谨慎地增加学习率,这会导致梯度提升算法在评估每棵树时朝着更大的"步长"前进。
第三种情况,"最佳",发生在模型在训练和测试中取得了良好的进展,直到测试损失开始增加的一个点。这表明您可以在测试损失达到最小值的地方停止。在那一点上,模型将尽可能多地了解到区分这两个数据集的内容,考虑到学习算法的其他参数。
实际上,为了提供一致可解释的模型统计信息和可解释性结果,您需要在优化模型适用于单个数据集和构建能够推广到不同时间段的多个数据集的模型之间取得平衡。
计算效率
许多组织拥有包含数十亿条记录的重要数据表。例如:
• 金融服务行业的交易数据
• 高流量应用程序或网站的原始事件数据
• 数字广告展示和事件级数据
• 物理传感器数据
• 社交平台的消息信息
在这种规模的数据情况下,很容易创建一个监控策略,成本过高,或者甚至在现代数据仓库中也无法成功运行。
由于我们对每天采样的记录数量设置了限制,模型中大部分计算和内存使用量将与添加的列数成线性关系。例如,在每个节点扩展决策树时搜索最佳分裂,将随着需要搜索的列数呈线性增长。尽管典型的表格通常有 10 到 50 列,但常见的表格可能有 200 列,有些表格甚至可能有数千列。此外,表格可能包含 JSON 数据,您需要自动将其扩展为新的合成列,在某些情况下可能导致表格有 10,000 列。
以下优化措施可以使您的算法在计算上更加高效:
• 确保一次只查询一天的数据,并尽可能快地构建历史快照。请注意,这是有代价的,因为算法在第一天将有更少的历史数据可用,并且在"冷启动"场景下不会像预期的那样有效。
• 使用数据仓库随机抽样记录(使用"偏差和效率"部分中早期提到的高效技术),并在随机样本上计算更复杂的概要或 ML 结果。
• 如果使用梯度提升决策树,请限制深度和总树数,因为我们通常不在寻找非常复杂的交互作用,并且在训练过程中,如果测试错误显著增加,则提前停止。
• 优化学习过程本身,具体取决于您的计算平台和学习算法,可能包括使用稀疏编码、通过多进程进行分布式学习或利用 GPU 等步骤。
模型解释性
如果您的模型在测试集上表现良好,这表明您发现了潜在的数据质量问题。下一步是解释模型发现了什么。解释性对于几个原因至关重要。首先,它告诉您今天的数据有多异常。这使您可以执行多种类型的调整,以避免警报疲劳(在第 5 和第 6 章中详细介绍)。对于那些触发警报的问题,了解严重程度将有助于最终用户优先处理响应。
其次,解释性告诉您异常位于数据的哪个位置。这使您可以将调查员指向数据的正确部分,并创建各种有趣的根本原因分析辅助工具,如坏数据的样本(第 6 章中有更多详细信息)。
那么模型解释性是如何工作的呢?其思想是得出一个分数,表明数据集中每个单独的 {行,列} 单元格对模型预测的贡献程度。虽然有几种方法,但我们使用的是 SHAP,它本质上是对数据集中每个单元格的算法进行局部线性估计的近似。
要了解这在实践中是如何运作的,假设我们试图检测信用卡交易数据表中的数据质量问题,并已从昨天和今天抽样了 10,000 条记录,对特征进行了编码,并建立了我们的模型来预测每条记录到达的日期。然后让我们通过 SHAP 解释过程跟踪以下四条记录:
金额 | 类型 | FICO 信用评分 | 品牌 | 类型 | 信用额度 | 来源 |
---|---|---|---|---|---|---|
$18 | 刷卡 | 684 | Discover | 借记 | $12,564 | 今天 |
$59 | 芯片 | 578 | Mastercard | 信用 | $7,600 | 今天 |
-$445 | 芯片 | 689 | Visa | 信用 | $6,700 | 昨天 |
$137 | 芯片 | 734 | Mastercard | 信用 | $7,100 | 昨天 |
在这种情况下,我们有两条来自昨天的记录和两条来自今天的记录。(请回忆一下,来源列不用于预测数据到达的日期;相反,它是我们训练模型来预测的响应。)然后,假设我们使用我们的模型为每一行做出预测,判断它可能是哪一天到达的:
Amount | Type | FICO score | Brand | Type | Credit limit | Source | Predicted Pr( Today ) |
---|---|---|---|---|---|---|---|
$18 | Swipe | 684 | Discover | Debit | $12,564 | Today | 51% |
$59 | Chip | 578 | Mastercard | Credit | $7,600 | Today | 78% |
-$445 | Chip | 689 | Visa | Credit | $6,700 | Yesterday | 45% |
$137 | Chip | 734 | Mastercard | Credit | $7,100 | Yesterday | 52% |
在这种情况下,我们发现我们的模型认为第二条记录有78%的概率是来自今天,而其他三条记录的预测值都在50%的平均预测值的±5%范围内,这表明模型对数据来自哪一天没有明显偏差。
与直接使用预测概率相比(由于概率在0%和100%之间自然有界,因此很难表示为线性关系),我们将概率转换为它们的对数几率,使用公式对数几率 = ln [ 概率 / (1 - 概率) ]:
金额 | 类型 | FICO 分数 | 品牌类型 | 信用额度 | 来源 | 预测的概率(今天) | 对数几率 |
---|---|---|---|---|---|---|---|
$18 | 刷卡 | 684 | Discover | 借记卡 | $12,564 | 今天 | 51% |
$59 | 芯片 | 578 | Mastercard | 信用卡 | $7,600 | 今天 | 78% |
--$445 | 芯片 | 689 | Visa | 信用卡 | $6,700 | 昨天 | 45% |
$137 | 芯片 | 734 | Mastercard | 信用卡 | $7,100 | 昨天 | 52% |
然后,我们可以运行 SHAP 算法,它将把这些对数几率统计数据分解成对 ML 模型中每个列的贡献的线性组合(实际上,我们需要在特征级别获取 SHAP 值,然后对其进行汇总,但你明白我的意思)。
Amount | Type | FICO score | Brand | Type | Credit limit | Predicted Pr( Today ) | Predicted Pr( Today ) | Log odds |
---|---|---|---|---|---|---|---|---|
0.01 | 0.02 | 0.02 | -0.01 | 0.00 | -0.01 | Today | 51% | 0.02 |
-0.03 | 0.01 | 0.41 | 0.19 | -0.01 | -0.03 | Today | 78% | 0.55 |
0.02 | -0.03 | -0.05 | 0.02 | -0.03 | -0.02 | Yesterday | 45% | -0.09 |
0.01 | -0.02 | -0.01 | 0.01 | 0.02 | 0.02 | Yesterday | 52% | 0.03 |
这种情况下,我们发现 FICO SCORE 和 BRAND 列的值对模型预测第二条记录是今天的贡献显著。从上面的数据值可以看出:
- FICO SCORE = 578
- BRAND = 'Mastercard'
这表明可能在 Mastercard 交易的低信用评分分布中发生了异常情况(尽管我们在这里仅检查了几条记录 - 在实践中,我们会查看每天所有 10,000 条记录的 SHAP 值分布摘要)。
在根据第5章的技术对 SHAP 值进行归一化和适当调整后,最终结果是我们称之为"异常分数"。重要的是,这个分数可以被聚合和/或切片,以提供许多不同级别的细粒度。
在最低级别,您可以查看对于采样数据中的每个个别 {行,列} 单元格的异常分数。从这里,您可以聚合行的异常分数,以找到最异常的条目,或者按行集合找到异常段。您可以按列计算平均异常分数,以找到最异常的列。或者您可以计算整个表的异常分数。您还可以对异常分数进行聚类,以找到跨列的相关性(有关详细信息,请参见第5章)。
了解异常分数不仅对于发生显著变化的数据很重要。通过为表中的每条记录计算分数,您可以创建可视化效果,将异常情况置于上下文中,如图4-7所示。
如您在图4-7中所见,您可以为异常分数分配易于理解的人类可读类别,以帮助解释。根据我们在处理各种数据集时的经验,我们将异常分数分为六个不同的桶,从最小到极端。这些类别基于总体异常分数的对数 - 每两个桶代表得分增加一个数量级:
最小
数据变化很小或没有明显变化。
弱
少量数据受到影响,需要解释和仔细研究才能检测到。
适度
少量数据受到明显变化的影响,或者适度数量的数据受到需要简单解释的变化的影响。
强
数据的相当大比例受到明显变化的影响,或者大多数数据受到容易解释的变化的影响(尽管乍看可能不明显)。
严重
大多数数据受到显而易见的变化的影响。
极端
有一个显而易见地影响几乎整个今天数据的变化。 您可能会在图4-7中注意到一个阈值 - 使用每个表的异常分数来学习何时触发警报的自定义阈值非常重要,因为某些表中的数据变化比其他表中的数据变化频繁。我们将在第5章中讨论这一点。
综合伪代码示例
以下是Python伪代码示例,演示了如何应用本章描述的方法来查找两天数据之间的异常,并按列进行汇总。但不要对这段代码过于字面解读;它只是为了简单地说明概念以及它们在高层次上的结合方式。特别要注意的是,这忽略了更复杂的问题,如季节性、多次回顾和相关特征,并且没有详细实现采样、特征工程或异常分数计算部分。
python
# General imports
import pandas as pd
import datetime as dt
import xgboost as xgb
from sklearn.model_selection import train_test_split
from shap import TreeExplainer
# Import hypothetical sub-modules that perform more detailed tasks
from data_access import query_random_sample
from feature_engineering import determine_features, encode_feature
from explainability import compute_column_scores
def detect_anomalies( table: str, time_column: str, current_date: dt.date, prior_date: dt.date, sample_size: int
) -> dict[str, float]:
这里我们定义了一个伪代码方法,用于通过比较来自两个不同日期的样本,训练模型并计算异常分数来检测数据中的异常。它接受以下参数:
- table:要查询的表的名称
- time_column:用于筛选数据的时间列的名称
- current_date:要对数据进行采样的当前日期
- prior_date:要对数据进行采样的先前日期
- sample_size:每个日期随机抽样的行数
它返回一个字典,其中每个键是列名,每个值是该列的异常分数。伪代码的下一部分实现了方法的主体,并将我们从数据抽样步骤一直带到解释模型的预测结果的步骤:
ini
# Obtain random samples of data for the specified dates
data_current = query_random_sample( table, time_column, current_date, sample_size) data_prior = query_random_sample( table, time_column, prior_date, sample_size) # Create a binary response variable indicating the date
y = [1] * len(data_current) + [0] * len(data_prior) # Concatenate the data, ensuring the order of concatenation
data_all = pd.concat([data_current, data_prior], ignore_index=True) # Determine the features to build based on the data columns
feature_list = { column: determine_features(data_all, column) for column in data_all.columns
} # Encode the features, here assuming that encode_feature returns a DataFrame
encoded_features = [ encode_feature(data_all, column, feature) for column, feature in feature_list
]
# Combine the encoded features into a single DataFrame
X = pd.concat(encoded_features, axis=1) # Split data into training and evaluation sets
X_train, X_eval, y_train, y_eval = train_test_split( X, y, test_size=0.2, random_state=42
) # Train a machine learning model using the features and response variable
model = xgb.XGBClassifier() model.fit( X_train, y_train, early_stopping_rounds=10, eval_set=[(X_eval, y_eval)], verbose=False, ) # Obtain SHAP values to explain the model's predictions
explainer = TreeExplainer(model) shap_values = explainer.shap_values(X) # Compute anomaly scores for each column based on the SHAP values
column_scores = compute_column_scores(shap_values, feature_list) return column_scores
其他应用
我们着重介绍了无监督机器学习如何帮助您持续检测数据中的突然结构变化。然而,本章概述的机器学习方法还有两个额外的应用案例值得一提。
第一个是发现遗留数据质量问题,这些问题将出现在您数据历史中的冲击和痕迹中。这可以通过在一系列历史日期上运行本章概述的算法,并调查您发现的异常来实现。事实上,在第5章中,我们将概述我们如何使用这个过程,我们称之为回测,来衡量我们的模型的有效性。
但是要注意,这种方法可能会带来一些复杂性。首先,您可能会发现一些非常难以解释的问题。组织中经常会发生没有人记得的变化,并验证它们是否令人担忧将需要昂贵和繁琐的侦查工作。第二个复杂性是,您可能期望发现一些根本不存在的问题。当已知的数据质量问题得到修复,并且团队补充数据以修复痕迹时,通常会发生这种情况。一旦发生这种情况,您就不应再能在数据历史中检测到该问题。
第二个用例更为重要,我们在这里仅简要涉及。与其使用无监督机器学习随时间比较同一表中的数据,您可以比较来自同一表的两个数据样本(或来自具有相同列模式的不同表)以找到它们之间的有意义的差异。在这种情况下,无监督机器学习算法将检测并帮助解释两组数据之间的任何重要分布或关系差异。由于使用了抽样,这种方法可以应用于庞大的表格,甚至可以应用于位于不同源仓库或数据库中的表格!
这种方法允许以下类型的应用:
- 比较来自源数据库的原始数据与目标仓库中清理和转换后的数据
- 比较当前ETL管道版本的数据与新提出版本产生的数据
- 比较当前数据样本与遥远过去的样本数据
- 比较来自不同业务部门、地理位置、产品类别或营销活动的数据
图4-8显示了监控平台如何将此功能公开为用户可以配置和按需运行的自定义检查,以比较和对比感兴趣的数据集。