基于特征子空间的高维异常检测:一种高效且可解释的方法

本文将重点探讨一种替代传统单一检测器的方法:不是采用单一检测器分析数据集的所有特征,而是构建多个专注于特征子集(即子空间)的检测器系统。

在表格数据的异常检测实践中,我们的目标是识别数据中最为异常的记录,这种异常性可以相对于同一数据集中的其他记录衡量,也可以相对于历史数据进行评估。

在实际应用中,寻找最具意义的异常面临着多重挑战。首要的问题是缺乏一个标准的统计异常性定义,无法明确界定哪些数据异常应被视为最显著。此外,最相关(不一定是统计上最异常)的异常往往依赖于具体项目背景,且可能随时间动态变化。

从技术层面来看,异常检测还面临着一系列挑战,其中最显著的是处理高维数据时遇到的问题。维度灾难对异常检测的影响是多方面的,其中最关键的是使距离度量变得不可靠。大量异常检测算法依赖于计算记录间的距离来识别异常 - 即找出那些与少数记录相似但与大多数记录显著不同的数据点:具体而言,就是与少量其他记录距离近但与大多数记录距离远的数据点。

举例来说,在一个包含40个特征的数据表中,每条记录可以被视为40维空间中的一个点,其异常程度可以通过与该空间中其他点的距离关系来评估。这就需要一种计算记录间距离的方法。常用的度量方式包括欧氏距离等(假设数据为数值型或已转换为数值形式)。因此每条记录的异常程度通常基于其与数据集中其他记录的欧氏距离来衡量。

这种距离计算方法在处理高维数据时往往会失效。因为即使在特征数量仅为十到二十个时就可能出现问题,而在三十或四十个以上特征的情况下,这个问题几乎必然存在。

,目前主流的异常检测器大多属于数值型多变量检测器 - 这类检测器假设所有特征均为数值型,且通常同时处理全部特征。典型如LOF(局部异常因子)和KNN(k近邻),这两种使用最广泛的检测器都基于记录在高维空间中的距离关系来评估其异常性。

基于数据点间距离的异常检测示例

下面的可视化图表。这是一个包含六个特征的数据集的三个二维散点图展示。其中包含两个可以合理判定为异常值的点:P1和P2。

先看点P1,其在特征A维度上显著偏离其他数据点的分布。从单一特征A的角度来看,P1可以明确地被标识为异常值。但是当检测器同时考虑所有六个维度计算点间距离时,由于高维空间距离计算的特性,P1的异常性可能不再那么显著。这是因为P1在其他五个特征维度上表现出正常的分布特征,因此在6维空间中其与其他点的整体距离可能落在正常范围内。

这种基于点间距离的异常检测方法仍具有其合理性:P1和P2之所以被认定为异常值,是因为它们至少在某些维度上与数据集的主体显著分离。

图片

KNN与LOF算法的技术实现

考虑到KNN和LOF是应用最为广泛的异常检测算法,我们将深入分析这两种方法,并具体探讨如何在这些算法中实现子空间策略。

在KNN异常检测器中,首先需要确定参数k值,该参数决定了每条记录需要比较的邻居数量。在实际应用中k=10是一个较为常见的选择。

对于每条记录,算法计算其与最近k个邻居的距离,这提供了评估每个数据点孤立程度的量化指标。随后需要将这k个距离值转换为单一的异常分数。通常采用这些距离的均值或最大值来实现这一转换。

假设我们采用最大值作为评分标准(使用均值、中位数或其他统计量也是可行的,但各有其技术特点)。如果某条记录到其第k个最近邻居的距离异常大,这表明最多只有k-1条记录与之相对接近,而与绝大多数其他记录都保持较远距离,因此可以将其识别为异常值。

LOF异常检测器采用了类似但更为复杂的方法。同样考察每个点与其k个最近邻居的距离,但进一步将这个距离与这k个邻居各自的k近邻距离进行比较。这使得LOF能够基于局部密度特征来评估每个点的异常程度。

KNN使用全局标准来判定什么是异常的邻居距离,而LOF则采用局部化的标准来进行这一判断。

KNN和LOF都基于记录到其最近邻居的距离来评估异常性。这些距离度量在同时处理大量特征时的可靠性会显著降低,而通过每次处理较少特征(子空间)的方式可以大幅缓解这一问题。

即使对于不使用距离度量的检测器,子空间方法也具有其价值,并且在使用基于距离计算的检测器时,子空间方法的优势表现得更为明显。类似KNN和LOF这样使用距离的方法在检测器中非常普遍。

适度维度数据的技术挑战

除了处理高维数据带来的挑战外,即使在处理维度适中的数据集时,识别异常记录的能力也可能受到显著影响。

虽然非常高的维度会导致记录间距离计算失去意义,但即使是适度数量的维度也会使那些仅在少数特征上表现异常的记录难以被准确识别。

让我们再次分析前面展示的散点图。点P1在特征A上表现出明显的异常性(但在其他五个特征上表现正常)。点P2在特征C和D上呈现异常模式,但在其余四个特征上保持正常分布。当我们在6维空间中计算这些点与其他点的欧氏距离时,它们的异常性可能无法被可靠地检测到。这种现象同样适用于曼哈顿距离和大多数其他距离度量方法。

左侧图展示了P1点在二维数据空间中的表现。该点在特征A上确实异常,但如果在完整的6维数据空间中使用欧氏距离,甚至在图中显示的二维空间中,其异常性都会被显著稀释。这个例子很好地说明了为什么引入额外特征有时会适得其反。在中间图中,我们观察到点P2在C-D子空间中明显是异常值,但在A-B或E-F子空间中则表现正常。这说明我们仅需特征C和D就能识别这个异常值,而包含其他特征反而会干扰异常检测的效果。

以P1为例,即使在左侧图所示的二维空间中,其与大多数其他点的距离也并不特别异常。其异常性主要体现在周围没有其他近邻点(这正是KNN和LOF能够检测到的特征),但P1到该二维空间中其他点的距离并不特别大:与大多数点对之间的典型距离相似。

使用KNN算法时,如果将k值设置得相对较小(如5或10),可能能够检测到这种异常,因为大多数记录的第5个(或第10个)最近邻居都比P1的最近邻居要近得多。但当计算中包含所有六个特征时,这种异常性就不如仅查看特征A或仅关注左侧图(只包含特征A和B)时那么明显。

点P2在仅考虑特征C和D时表现出明显的异常特征。使用k=5的KNN检测器时,可以识别其5个最近邻居,发现到这些邻居的距离显著大于数据集中的典型值。

同样使用k=5的LOF检测器时,可以将P1或P2到其5个最近邻居的距离与这些邻居各自到它们5个最近邻居的距离进行比较。在这种情况下,我们也会发现P1或P2到其最近邻居的距离异常地大。

这种检测在仅考虑特征A和B,或特征C和D时相对容易实现,但当考虑完整的6维空间时,异常检测的难度显著增加。

虽然许多异常检测器在维度为六或稍高时可能仍能识别P1和P2,但显然使用更少的特征会使检测更可靠、更高效。要检测P1,实际上只需要考虑特征A;要识别P2,只需要关注特征C和D。在检测过程中引入其他特征不仅没有帮助,反而会增加检测的复杂度和不确定性。

这实际上反映了异常检测中的一个普遍现象:处理的数据集通常包含大量特征,且这些特征都可能具有其价值。例如一个包含50个特征的数据表中,这50个特征都可能是相关的:任何特征上的罕见值都可能具有分析价值,或者这50个特征中任意两个或更多特征的罕见组合都可能值得关注,因此在分析过程中保留所有50个特征是合理的。

但是,要识别单个异常值,我们通常只需要较少的特征维度。实际上,一条记录在所有特征上都表现异常的情况极为罕见。基于多个特征的罕见组合而产生的异常也相对少见。

任何给定的异常通常表现为一到两个特征上的罕见值,或者是两个到四个特征组合的异常模式。通常只需要这些关键特征就足以识别该记录中的异常,尽管对于识别其他记录中的异常,其他特征可能同样必要。

子空间理论与实践

为了应对上述挑战,特征子空间的应用成为异常检测中的一项关键技术。术语子空间指的是特征的子集。以上述例子为例,如果构造子空间:A-B、C-D、E-F、A-E、B-C、B-D-F和A-B-E,就得到了七个子空间(五个二维子空间和两个三维子空间)。在实际应用中会在每个子空间上运行一个或多个检测器,这意味着每条记录至少要经过七个检测器的评估。

当特征数量远超过六个时,子空间方法的价值会更加显著。而且通常即使子空间本身也会包含超过六个特征,而不仅仅是两个或三个。

使用这些子空间,我们能够更可靠地识别P1和P2作为异常值。P1可能会被运行在特征A-B、A-E以及A-B-E子空间上的检测器标识为高分异常。P2则可能被运行在特征C-D子空间和可能的B-C子空间上的检测器检测出来。

仅使用这七个子空间而不是一个覆盖所有特征的6维空间,可能会错过例如A和D、或C和E之间的罕见组合。这些组合可能在使用全维度检测器时能够被检测到,但在一个永远不会同时检查这些特征组合的检测器系统中必然会被忽略。

子空间方法虽然具有显著优势,但确实存在错过相关异常的风险。我们将在后文介绍一些生成子空间的技术来缓解这个问题,但在全维度数据上运行一些检测器仍然可能有其价值。在异常检测实践中,除非采用多种技术的组合,否则很难找到所有的异常。尽管子空间方法的重要性不容忽视,但使用多种技术通常是必要的,这可能包括在完整数据上运行某些检测器。

对于每个子空间,可能需要执行多个检测器。例如同时使用KNN和LOF检测器,以及Radius、ABOD等其他检测器。使用多种技术的组合能够更好地覆盖我们希望检测的各种类型的异常。

子空间方法的优势

基于上述分析,我们已经看到了使用子空间的几个主要动机:它可以有效缓解维度灾难,并且能够提高对少数特征异常的检测能力。除此之外使用子空间进行异常检测还具有以下关键优势:

  • 基于集成学习的准确性提升 — 使用多个子空间实际上构建了一个检测器集成系统,能够综合多个检测器的结果。检测器集成比单一检测器提供更高的准确性。这与分类和回归问题中的集成学习类似,尽管二者在技术细节上存在差异。在子空间方法中,每条记录都经过多次独立评估,从而提供了比单一检测器更稳健的异常评估结果。

  • 可解释性的提升 — 子空间方法产生的结果通常更易于解释,这在异常检测领域尤为重要。异常检测系统标记的异常记录通常需要进一步的人工审查,因此理解为什么某条记录被标记为异常至关重要。对于那些在高维空间中进行检测的系统,其结果往往难以解释;基于少量特征的检测器产生的结果更容易理解和评估。

  • 计算效率的优化 — 使用较少的特征能够构建更高效的检测器,无论是在内存使用还是计算速度方面都有显著优势。这种优化在拟合和推理阶段都能体现出来,特别是在使用那些计算复杂度与特征数量呈非线性关系的检测器时(许多检测器的执行时间与特征数量呈二次方关系)。使用20个检测器,每个覆盖8个特征的方案,可能比使用一个覆盖100个特征的检测器执行得更快。

  • 并行处理的可能性 — 采用多个小型检测器而非单个大型检测器的策略,使得系统可以并行执行拟合和预测步骤,在硬件资源充足的情况下能显著提高处理效率。

  • 系统调优的灵活性 — 使用多个简单检测器构建的系统更容易进行动态调整。虽然某些异常检测任务只需要对单一数据集进行评估,但在实际应用中,经常需要构建长期运行的异常检测系统,例如工业过程监控、网站活动分析、金融交易监督、机器学习系统输入输出监控等。在这些场景下,通常需要随时间推移不断改进异常检测系统,以更好地关注相关性更高的异常。采用基于少量特征的简单检测器集合使得这种调整更加可行。可以根据实际效果逐步调整各个检测器的权重,提高有效检测器的影响力,降低效果欠佳的检测器的权重。

子空间选择策略

如前所述,我们需要为每个待评估的数据集确定适当的子空间集合。找到最相关或最优的子空间集合是一个具有挑战性的问题。假设我们关注的是发现任何不寻常的值组合,那么确定哪些特征集合最可能包含有意义的异常组合就变得尤为困难。

比如说,如果一个数据集包含100个特征,可能会训练10个模型,每个模型覆盖10个特征。这会让第一个检测器使用前10个特征,第二个使用接下来的10个特征,依此类推。如果前两个特征之间存在异常组合,我们能够检测到。但如果异常存在于第一个特征与其他90个不在同一模型中的特征之间的组合中,这些异常就会被忽略。

所以可以通过增加子空间的数量来提高捕获相关特征组合的概率,但要确保所有应该一起考虑的特征集至少在一个子空间中共同出现仍然很困难。这在数据中存在基于三个、四个或更多特征的相关异常时尤其明显 — 这些特征必须至少在一个子空间中同时出现才能被检测到。例如,在员工费用报销表中,如果我们希望识别部门、费用类型和金额三个特征的罕见组合,那么这三个特征就必须至少在一个子空间中同时存在。

这就引出了三个核心问题:每个子空间应包含多少特征、哪些特征应该组合在一起、以及应该创建多少个子空间。

从组合数学的角度来看,可能的子空间数量是巨大的。对于20个特征,可能的子空间数量为2^20,超过一百万种组合。对于30个特征,这个数字将超过十亿。即使我们预先确定每个子空间的特征数量,组合数量虽然会减少但仍然很大。例如,对于20个特征,如果希望每个子空间包含8个特征,组合数量为C(20,8),即125,970种组合。对于30个特征构造包含7个特征的子空间,组合数量为C(30,7),达到2,035,800种。

一种可能的策略是保持子空间较小,这有助于提高可解释性。最简单的选择是每个子空间使用两个特征,这同时也便于可视化。但是如果有d个特征,这种方法需要d*(d-1)/2个模型来覆盖所有可能的组合,在特征数量较多时可能难以实现。例如,对于100个特征,我们需要4,950个检测器。因此在实践中,我们通常需要每个检测器使用多个特征,但不宜过多。

在构建子空间时,需要在以下目标之间寻找平衡:使用足够多的检测器和每个检测器中的特征,确保每对特征理想情况下至少在一个子空间中共同出现;同时保持每个检测器使用的特征数量适中,使得不同子空间之间的特征组合有显著差异。如果每个检测器使用100个特征中的90个,虽然能很好地覆盖所有特征组合,但子空间仍然过大(削弱了使用子空间的优势),而且所有子空间会非常相似(降低了集成学习的效果)。

虽然每个子空间的特征数量需要权衡多个因素,但子空间数量的选择相对直接:从准确性角度来看,使用更多子空间总是更好的,但需要在计算资源消耗方面做出相应权衡。

以下是几种主要的子空间构建方法,我们先概述这些方法,然后详细讨论其中的一些关键策略:

  • 基于领域知识的构建方法 — 通过分析哪些特征组合可能产生值得关注的异常模式来构建子空间。

  • 基于关联性的构建方法 — 异常的值组合通常只会出现在相互关联的特征之间。与预测问题不同(通常希望最小化特征间的相关性),在异常检测中,高度相关的特征往往最需要一起考虑。具有强关联的特征组合在出现违反常规模式时最可能产生有意义的异常。

  • 基于稀疏区域的构建方法 — 由于异常记录通常与数据集中的大多数记录显著不同,这意味着它们位于数据分布的稀疏区域。因此包含大片近似空白区域的特征组合可能构成有效的子空间。

  • 随机构建方法 — 这是后文将要讨论的特征装袋(Feature Bagging)技术所采用的方法。虽然这种方法可能不是最优的,但它避免了寻找关联性和稀疏区域时的大量计算开销,在使用足够多的子空间时仍能取得较好的效果。

  • 穷举搜索方法 — 这是计数异常检测器采用的策略。这种方法仅限于特征数量较少的子空间,但其结果具有极高的可解释性。它还避免了仅选择部分可能子空间时可能带来的计算偏差。

  • 基于已知异常的构建方法 — 如果我们有一组已知的异常样本,能够确定它们成为异常的原因(即相关特征),并且我们的目标仅限于识别这些特定类型的异常(而不是发现未知类型的异常),那么我们可以利用这些信息,识别与每个已知异常相关的特征集,并据此构建相应的检测模型。

接下来让我们深入探讨其中几种关键方法。

基于领域知识的构建方法

我们以下面展示的费用报销表为例。通过分析这张表,可以确定哪些类型的异常是值得关注的,哪些则可能不那么重要。例如,账户金额的异常组合以及部门和账户的异常组合可能具有重要的业务意义;而费用发生日期和时间的组合可能就不那么有意义。通过这种方式,我们可以构建数量适中的子空间,每个子空间包含两到四个特征,从而实现高效且可解释的异常检测,重点标识最相关的异常。

图片

但是这种方法可能会遗漏数据中存在但不那么明显的关联关系。除了利用领域知识外,主动搜索数据中的关联关系也很重要。我们可以通过多种方式发现特征之间的关系,例如使用简单的预测模型检验某些特征是否能被其他特征准确预测。当发现这种关联时,相应的特征组合可能值得进一步研究。

发现这些关联关系对某些目的可能有用,但对异常检测过程而言可能并非总是有价值。例如,如果发现账户和一天中的时间之间存在关联,这可能仅仅反映了员工提交费用报销的常规工作流程,偏离这种模式可能看起来有趣,但实际业务意义可能并不大。

随机特征子空间构建方法

在缺乏领域知识指导的情况下,随机构建子空间也可能是一种有效的策略。这种方法计算效率高,能够构建出一组有助于捕获最显著异常的子空间,尽管可能会遗漏一些重要的异常模式。

下面的代码展示了一种构建随机子空间集合的方法。这个示例使用了从A到H的八个特征,并基于这些特征构建子空间集合。

每个子空间的构建从选择使用最少的特征开始(如果有多个特征使用次数相同,则随机选择其中之一)。系统使用ft_used_counts变量来跟踪每个特征的使用频率。然后算法逐个添加特征到这个子空间中,每一步都选择在当前子空间中的特征组合中出现最少的特征。系统通过ft_pair_mtx变量来跟踪每对特征在已构建的子空间中共同出现的次数。通过这种方式,可以创建一个子空间集合,使得每对特征在整个系统中的共现频率大致均衡。

 import pandas as pd  
 import numpy as np  
       
 def get_random_subspaces(features_arr, num_base_detectors,  
                              num_feats_per_detector):  
     num_feats = len(features_arr)  
     feat_sets_arr = []  
     ft_used_counts = np.zeros(num_feats)   # 追踪每个特征的使用次数
     ft_pair_mtx = np.zeros((num_feats, num_feats))    # 追踪特征对的共现次数
       
     # 每次循环构建一个新的子空间
     for _ in range(num_base_detectors):    
         # 获取当前使用次数最少的特征集        
         min_count = ft_used_counts.min()  
         idxs = np.where(ft_used_counts == min_count)[0]      
       
         # 从最少使用的特征中随机选择一个作为子空间的起始特征  
         feat_set = [np.random.choice(idxs)]    
       
         # 继续添加特征直到达到目标数量
         while len(feat_set) < num_feats_per_detector:  
             mtx_with_set = ft_pair_mtx[:, feat_set]  
             sums = mtx_with_set.sum(axis=1)  
             min_sum = sums.min()  
             min_idxs = np.where(sums==min_sum)[0]  
             new_feat = np.random.choice(min_idxs)  
             feat_set.append(new_feat)  
             feat_set = list(set(feat_set))  
                   
             # 更新特征对的共现矩阵  
             for c in feat_set:  
                 ft_pair_mtx[c][new_feat] += 1  
                 ft_pair_mtx[new_feat][c] += 1  
                   
         # 更新各个特征的使用计数  
         for c in feat_set:  
             ft_used_counts[c] += 1  
       
         feat_sets_arr.append(feat_set)  
       
     return feat_sets_arr  
       
 np.random.seed(0)  
 features_arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']  
 num_base_detectors = 4  
 num_feats_per_detector = 5  
       
 feat_sets_arr = get_random_subspaces(features_arr,  
                                      num_base_detectors,  
                                      num_feats_per_detector)  
 for feat_set in feat_sets_arr:      
     print([features_arr[x] for x in feat_set])

这个算法将生成如下子空间组合:

 ['A', 'E', 'F', 'G', 'H']  
 ['B', 'C', 'D', 'F', 'H']  
 ['A', 'B', 'C', 'D', 'E']  
 ['B', 'D', 'E', 'F', 'G']

在实际应用中,我们通常会创建远多于这个示例中的基础检测器(每个子空间通常对应一个基础检测器,虽然也可以在每个子空间上运行多个检测器),但这里为了便于理解,仅使用了四个检测器。

让子空间中的特征数量保持一致有几个实际优势。首先它简化了模型调优过程,因为许多异常检测器的参数设置都与特征数量密切相关。当所有子空间具有相同数量的特征时,它们可以共用相同的参数设置。

使用统一数量的特征还带来另一个重要优势:它简化了不同检测器得分的组合过程。如果不同子空间使用不同数量的特征,可能会产生尺度不一致的得分,这些得分难以直接比较。以k-最近邻(KNN)算法为例,当特征数量增加时,我们预期邻居之间的距离也会相应增加,这使得基于不同维度空间的得分难以直接比较。

基于相关性的特征子空间构建方法

在构建子空间时,在其他条件相同的情况下,尽可能将相关联的特征组合在一起是一种有效的策略。下面我们将展示一个基于相关性选择子空间的具体实现方法。

检测特征关联性可以采用多种方法。我们可以构建预测模型,尝试用每个单一特征预测其他特征(这种方法能够捕捉到特征之间的复杂关系)。对于数值型特征,最简单的方法可能是计算斯皮尔曼相关系数,虽然这种方法可能会遗漏非单调的关系,但能够检测到大多数显著的关联。在下面的示例中,我们采用这种方法。

要使用这段代码,首先需要指定期望的子空间数量和每个子空间包含的特征数量。

实现过程首先计算所有特征对之间的相关性并存储在相关矩阵中。然后开始构建第一个子空间,从相关矩阵中找到最大的相关系数(这会将两个高度相关的特征添加到子空间中),然后循环添加所需数量的其他特征。每次添加特征时,都在相关矩阵中寻找最大的相关系数,其中一个特征已在当前子空间中,另一个尚未被选入。当一个子空间包含了足够数量的特征后,就开始构建下一个子空间,从相关矩阵中剩余的最大相关系数开始,如此往复。

在这个示例中,使用了一个真实数据集 - OpenML的棒球数据集(这是一个公开可用的数据集)。这个数据集展现出一些显著的相关性。例如,击数(At bats)和得分(Runs)之间的相关系数达到0.94,这表明任何显著偏离这种关系模式的值很可能代表异常情况。

 import pandas as pd  
 import numpy as np  
 from sklearn.datasets import fetch_openml  
       
 # 定义函数用于在相关矩阵中找到最高相关性的特征对
 def get_highest_corr():  
     return np.unravel_index(  
         np.argmax(corr_matrix.values, axis=None),  
         corr_matrix.shape)  
       
 def get_correlated_subspaces(corr_matrix, num_base_detectors,  
                                 num_feats_per_detector):  
     sets = []  
       
     # 迭代构建每个子空间
     for _ in range(num_base_detectors):  
         m1, m2 = get_highest_corr()  
       
         # 用剩余特征中相关性最高的一对作为子空间的起点
         curr_set = [m1, m2]  
         for _ in range(2, num_feats_per_detector):  
             # 获取剩余的相关性排序
             m = np.unravel_index(np.argsort(corr_matrix.values, axis=None),  
                                  corr_matrix.shape)  
             m0 = m[0][::-1]  
             m1 = m[1][::-1]  
             for i in range(len(m0)):  
                 d0 = m0[i]  
                 d1 = m1[i]  
                 # 如果特征对中的任一个已在当前子集中,则添加该对特征
                 if (d0 in curr_set) or (d1 in curr_set):  
                     curr_set.append(d0)  
                     curr_set = list(set(curr_set))  
                     if len(curr_set) < num_feats_per_detector:  
                         curr_set.append(d1)  
                         # 移除重复特征
                         curr_set = list(set(curr_set))  
                 if len(curr_set) >= num_feats_per_detector:  
                     break  
       
             # 更新相关矩阵,将已使用特征对应的相关性置零
             for i in curr_set:  
                 i_idx = corr_matrix.index[i]  
                 for j in curr_set:  
                     j_idx = corr_matrix.columns[j]  
                     corr_matrix.loc[i_idx, j_idx] = 0  
             if len(curr_set) >= num_feats_per_detector:  
                 break  
       
         sets.append(curr_set)  
     return sets  
 
 # 加载示例数据集
 data = fetch_openml('baseball', version=1)  
 df = pd.DataFrame(data.data, columns=data.feature_names)  
       
 # 计算特征间的斯皮尔曼相关系数矩阵
 corr_matrix = abs(df.corr(method='spearman'))  
 # 只保留上三角矩阵以避免重复计算
 corr_matrix = corr_matrix.where(  
     np.triu(np.ones(corr_matrix.shape), k=1).astype(np.bool))  
 corr_matrix = corr_matrix.fillna(0)  
       
 # 构建5个子空间,每个包含4个特征
 feat_sets_arr = get_correlated_subspaces(corr_matrix, num_base_detectors=5,  
                                          num_feats_per_detector=4)  
 # 输出构建的子空间
 for feat_set in feat_sets_arr:      
     print([df.columns[x] for x in feat_set])  

执行上述代码后,我们得到如下子空间组合:

 ['Games_played', 'At_bats', 'Runs', 'Hits']  
 ['RBIs', 'At_bats', 'Hits', 'Doubles']  
 ['RBIs', 'Games_played', 'Runs', 'Doubles']  
 ['Walks', 'Runs', 'Games_played', 'Triples']  
 ['RBIs', 'Strikeouts', 'Slugging_pct', 'Home_runs']

PyOD工具包的介绍

PyOD是目前Python环境中最为全面和广泛使用的异常检测工具包,特别适用于数值型表格数据的分析。它提供了丰富的检测器实现,从最基础的方法到最复杂的算法都有覆盖,其中还包含了几种基于深度学习的方法。

基于我们对子空间在异常检测中应用的理解,需要重点关注PyOD提供的两个核心工具:SOD(子空间异常检测)和FeatureBagging(特征装袋)。这两个工具都实现了子空间的识别、在每个子空间上执行检测器,并将结果合并为每条记录的单一异常分数。

以下给出一些示例代码:

 import pandas as pd  
 import numpy as np  
 from pyod.models.sod import SOD  
       
 np.random.seed(0)  
 d = np.random.randn(100, 35)  
 d = pd.DataFrame(d)  
       
 # 确保特征8和9相关,而所有其他特征无关  
 d[9] = d[9] + d[8]  
       
 # 插入一个单一异常值  
 d.loc[99, 8] = 3.5  
 d.loc[99, 9] = -3.8  
       
 # 执行SOD,只标记1个异常值  
 clf = SOD(ref_set=3, contamination=0.01)  
 d['SOD Scores'] = clf.fit(d)  
 d['SOD Scores'] = clf.labels_

在下面显示四个散点图,显示35个特征中的四对。已知的异常值在每个图中都显示为星形。我们可以在第二个窗格中看到特征8和9(两个相关特征),该点是一个明显的异常值,尽管它在所有其他维度上都是典型的。

图片

在应用这些工具时,无论是否使用子空间策略,选择合适的基础检测器都是关键。如果不采用子空间方法,我们需要选择一个或多个检测器直接在完整数据集上运行。如果采用子空间策略,我们同样需要选择检测器,但会在每个子空间上分别执行。LOF和KNN是不错的选择,但PyOD还提供了许多其他在子空间上表现良好的检测器,例如基于角度的异常检测器(ABOD)、基于高斯混合模型(GMMs)的检测器、核密度估计(KDE)等。除了PyOD提供的检测器外,其他第三方实现的检测器也可能在特定场景下表现出色。

长期运行的异常检测系统

在基于相关性或稀疏区域构建子空间时,需要特别注意的是,随着数据分布的变化,相关的子空间也可能需要随时间更新。特征之间可能会出现新的关联模式,新的稀疏区域可能形成并对异常检测变得重要。如果不定期重新计算子空间,这些变化带来的信息可能会被忽略。虽然基于这些方法找到相关子空间可能非常有效,但系统可能需要按照预定计划更新,或在发现数据发生显著变化时进行更新。

总结

在处理高维表格数据的异常检测项目中,考虑采用子空间方法通常是一个明智的选择。这是一个相对直接的技术方案,具有多个显著优势。

当面临大规模数据处理、执行时间约束或内存限制等实际问题时,使用PCA可能是另一个有效的技术选择,在某些场景下可能比构建子空间更有优势。但是使用子空间方法(即直接使用原始特征,而不是PCA生成的成分)通常能提供更好的可解释性,而这在异常检测应用中往往是至关重要的。

子空间方法也可以与其他技术结合使用来进一步改进异常检测效果。例如,可以将子空间方法与其他集成学习技术结合:创建更大的集成系统,同时利用子空间(不同检测器使用不同的特征子集)、不同的模型类型、不同的训练数据子集、不同的预处理方法等。这种组合可以带来更多优势,尽管也会增加计算开销。

作者:W Brett Kennedy


喜欢就关注一下吧:

点个 在看 你最好看!