IsolationForest或“iForest”是一种美观、简单且优雅的算法,只需几个参数即可检测异常值。原始论文中仅包含最基本的数学知识,因此可供广大读者阅读。在这篇文章中,我将总结这个算法,它的历史,并分享我的实现代码来解释为什么iForest是目前最好的大数据异常检测算法。为什么iForest是现在处理大数据最好的异常检测算法综上所述,它在同类算法中性能最好。iForest在各种数据集上的ROC性能和准确性优于大多数其他异常检测算法。我从PythonOutlierDetection包的作者那里获取了基准数据,并在Excel中逐行使用了带有绿-红渐变的条件格式。使用深绿色标识在该数据集上具有最佳性能的算法,使用深红色标识性能最差的算法:绿色表示“好”,红色表示“差”。我们看到IForest在许多数据集和整体观点上处于领先地位,如均值、中值和标准差的颜色所示。资料来源:作者。数据来源:https://pyod.readthedocs.io/en/latest/benchmark.html我们看到IForest在许多数据集中处于领先地位,并且在一般情况下,因为我计算了平均值,median,如标准差的颜色所示.从precision@N(最重要的N个指标的准确度)的表现来看,iForest也能得到同样优异的成绩。图片来源:作者。数据来源:https://pyod.readthedocs.io/en/latest/benchmark.html可扩展性。基于其表现出的性能,iForest是最快的算法。正如预期的那样,PCA和基于直方图的离群值检测(HBOS)在所有数据集上都更快。k最近邻算法(KNN)的速度要慢得多,并且随着数据量的增加而变慢。我已经成功地在1亿个样本和36个特征的数据集上构建了一个隔离森林,这在集群环境中花费了几分钟。而这也是我认为sklearn的KNN算法做不到的。来源:author.Data来源:https://pyod.readthedocs.io/en/latest/benchmark.htmlAlgorithmHighlights/Summary我非常简洁地总结了原始的10页论文,概述如下:大多数其他异常检测(OD)算法尝试建立“正常”数据的范围,从而将不属于这个正常范围的个体标记为异常。iForest通过利用离群值的一个隐含属性直接分离离群值:它们在协变量集上具有不寻常的值。由于计算成本,现有方法只能用于低维数据或小数据集。一个典型的例子是:你会在大数据上使用sklearn.neighbor.KNeighborsClassifier吗?此外,iForest需要很少的常量和低内存要求。即,低开销。具体来说:外部节点的数量为n,因为每个观察值n都将被隔离。内部节点的总数显然是n-1,所以节点的总数是2n-1。因此,我们可以理解为什么所需的内存是有界的,并且随着样本数n线性增加。孤儿树节点的定义:T要么是没有子节点的叶节点,要么是经过验证的内部节点,有两个子节点(Tl,Tr)。我们通过递归执行以下过程来构造一棵iTree:随机选择一个特征q和一个分裂值p来划分X,直到发生以下情况之一:(i)树达到限制高度,(ii)所有样本被隔离成一个只有自己的外部节点,或者(iii)所有数据的所有特征都具有相同的值。路径长度:样本x的路径长度h(x)是指从iTree的根节点到叶子节点所经历的边数。E(h(x))是一组孤立树的h(x)的平均值。从这个路径长度的平均值,我们可以通过公式E(h(x))得到异常:s(x,n)=2^[^[?E(h(x))/c(n)]得分s(x,n)。基本上,s和E(h(x))之间存在单调关系。(想了解详情请参考文末附录,有一张图描述了它们之间的关系)。我不会在这里讨论c(n),因为对于任何给定的静态数据集,它都是一个常量。用户只需要设置两个变量:孤立树的数量和训练单棵树的子采样大小。作者通过对高斯分布生成的数据进行实验表明,只需要少量的树和少量的子样本就可以使平均路径长度非常快地收敛。少量子样本(逐个样本)解决了沼泽和掩蔽问题。造成这两个问题的原因是输入数据量太大,无法解决异常检测的问题。Swamping是指一个“正常”的样本点被错误地标记为“异常”,因为它被异常点包围,而masking则相反。也就是说,如果样本中有很多离群点来建树,一个正常的数据点反而会看起来不正常。作者使用乳房X线照相数据作为这种现象的示例。少量的子样本使得每棵孤立的树都是独一无二的,因为每个子样本都包含一组不同的异常值,甚至没有异常值。iForest不依赖距离或密度测量来识别异常值,因此它的计算成本低且速度快。这就引出了下一个问题。线性时间复杂度,O(n)。通俗地说,这意味着运行时间充其量随输入大小线性增加。这是一个很好的特性:了解算法历史的消息灵通的读者应该知道,一个绝妙的新想法的出现和它的广泛应用之间可能会有几十年的滞后。例如,逻辑函数于1845年被发现,并于1922年重新发现(有关更多信息,请参见此处),直到现在才被数据科学家频繁用于逻辑回归。近几十年来,一个新想法到广泛应用的时间越来越短,但仍然需要相对较长的时间。iForest于2008年首次公开,但直到2018年底才出现可行的商业应用。这是它的时间表:12/2008-iForest的原始论文发表(论文)07/2009-iForest作者最后修改了他们的代码实现(代码)10/2018-h2ogroup实现了Python和R版本的iForest(代码)01/2019-PyOD发布了Python上的异常检测工具包(代码,论文)08/2019-Linkedln工程团队发布了iForest的Spark/Scala实现(代码,newsletter)代码实现感谢这篇文章是关于大数据的,我用的是AWS集群环境。这里省略了大部分脚手架(软件质量保证和测试代码)代码。如果需要帮助配置AWS集群环境,可以参考我的文章:如何搭建高效的AWSEMR集群和JupyterNotebooksforSparkSQL我发现iForest可以轻松快速的处理750万行数据,36个特性,仅仅一个few计算在几分钟内完成。Python(h2o):importh2o#h2oautomateddatacleaningwellformydatasetimportpkg_resources#################################################################打印包+版本调试/未来可重复性#################################################################dists=[dfordinpkg_resources.working_set]#Filteroutdistributionsyoudon'tcareaboutanduse.dists.reverse()dists####################################################################################################################################################号号################################初始化h2ocluster和加载数据##################################################################h2o.init()#importpyarrow.parquetaspq#allowloadingofparquetfilesimports3fs#forworkinginAWSs3s3=s3fs.S3FileSystem()df=pq.ParquetDataset('s3a://datascience-us-east-1/anyoung/2_processedData/stack_parquetFiles',filesystem=s3).read_pandas().to_pandas()#checkinputdataloadedcorrectly;prettyprint.shapeprint('('+';'.join(map('{:,.0f}'.format,df.shape))+')')#ifyouneedtosampledatadf_samp_5M=df.sample(n=5000000,frac=None,replace=False,weights=None,random_state=123,axis=None)#convertPandasDataFrameobjecttoh2oDataFrameobjecthf=h2o.H2OFrame(df)#dropprimarykeycolumnhf=hf.drop('referenceID',axis=1)#referenceIDcauseserrorsinsubsequentcode#youcanomitrowswithnasforafirstpassshf_clean=hf.na_omit()#prettyprint.shapewiththousandscommaseparatorprint('('+';'.join(map('{:,.0f}'.format,hf.shape))+')')fromh2o.estimatorsimportH2OIsolationForestEstimatorfromh2o.estimatorsimportH2OIsolationForestEstimatorfullX=['v1','v2','v3']#splith2oDataFrameinto80/20train/testtrain_hf,valid_hf=hf.split_frame(ratios=[.8],seed=123)#specifyiForestestimatormodelsisolation_model_fullX=H2OIsolationForestEstimator(model_id="isolation_forest_fullX.hex",seed=123)isolation_model_cv=fullX_H2OIsolationForestEstimator(model_id="isolation_forest_fullX_cv.hex",seed=123)#trainiForestmodelsisolation_model_fullX.train(training_frame=hf,x=fullX)isolation_model_fullX_cv.train(training_frame=train_hf,x=fullX)#savemodels(还没有弄清楚如何从s3w/opermissionissuesyet加载)modelfile=isolation_model_fullX.download_mojo(path="~/",get_genmodel_jar=True)print("Modelsavedto"+modelfile)#predictmodelspredictions_fullX=isolation_model_full_fullX.prediction("izehmean_length").hist()如果你使用iForest来验证你的标签数据,你可以通过比较数据集中正常数据的分布、异常数据的分布和原始数据集的分布来进行进一步的推断。例如,你可以看一下原始数据集中的不同特征组合,像这样:N=df.count()df[['v1','v2','id']].groupby(['v1','v2']).count()/Ndf[['v1','v3','id']].groupby(['v1','v3']).count()/N...并与正常/离群数据集进行比较。正如下面所显示的那样:#################################################################从iForest到原始h2oDataFrame的列绑定预测##################################################################hf_X_y_fullX=hf.cbind(predictions_fullX)###################################################################Sliceusingabooleanmask.Theoutputdatasetwillincluderows#withcolumnvaluemeetingcondition##################################################################mask=hf_X_y_fullX["label"]==0hf_X_y_fullX_0=hf_X_y_fullX[掩码,:]掩码=hf_X_y_fullX["标签"]==1hf_X_y_fullX_1=hf_X_y_fullX[掩码,:]#####################################################################Filtertoonlyincluderecordsthatareclearlynormal##################################################################hf_X_y_fullX_ml7=hf_X_y_fullX[hf_X_y_fullX['mean_length']>=7]hf_X_y_fullX_0_ml7=hf_X_y_fullX_1[hf_X_y_fullX_0['mean_length']>=7]hf_X_y_fullX_1_ml7=hf_X_3[hf_X_3[hf_X_3y_fullX_1['mean_length']>=7]###################################################################ConverttoPandasDataFrame更容易计数/熟悉度/熟悉度####################################################################hf_X_y_fullX_ml7_df=h2o。as_list(hf_X_y_fullX_ml7,use_pandas=True)hf_X_y_fullX_0_ml7_df=h2o.as_list(hf_X_y_fullX_0_ml7,use_pandas=True)hf_X_y_fullX_1_ml7_df=h2o.as_list(hf_X_y_##########ru=pandas7,Tuse###################################################通过变量级别的组合查看计数以进行推理##################################################################hf_X_y_fullX_ml7_df[['v1','v2','id']].groupby(['v1','v2']).count()hf_X_y_fullX_0_ml7_df=h2o.as_list(hf_X_y_fullX_0_ml7,use_pandas=True)...#Repeataboveforanomalousrecords:#################################################################过滤器只包含明显异常的记录##################################################################Hf_X_y_fullX_ml3=hf_X_y_fullX[hf_X_y_fullX['mean_length']<3]hf_X_y_fullX_0_ml3=hf_X_y_fullX_1[hf_X_y_fullX_0['mean_length']<3]hf_X_y_fullX_1_ml3=hf_X_y_fullX_3[hf_X_y_fullX_1['mean_length']<3]#####################################################################ConverttoPandasDataFrame更容易计数/熟悉##################################################################hf_X_y_fullX_ml3_df=h2o.as_list(hf_X_y_fullX_ml3,use_pandas=True)hf_X_y_fullX_0_ml3_df=h2o.as_list(hf_X_y_fullX_0_ml3,use_pandas=True_full_full)=h2o.as_list(hf_X_y_fullX_1_ml3,use_pandas=True)我已经完全实现了上面的代码,并将我的数据导出到Excel,很快就可以得到一些累积分布函数如下:来源:作者自己的作品绿线表示数据标记为1,即正常样本:红线代表标记为0的样本,被认为是异常的。参考文献F.T.Liu、K.M.Ting和Z.-H。周。隔离森林。In:DuringtheEighthIEEEInternationalConferenceonDataMining(ICDM'08),Pisa,Italy,2008,pp.413-422.[代码]本文获得IEEEICDM'08最佳理论/算法论文奖二等奖Zhao,Y.、Nasrullah,Z.和Li,Z.,2019。PyOD:用于异常检测框的可量化Python工具。机器学习研究杂志(JMLR),20(96),pp.1-7。本文转载自雷锋网。如需转载,请到雷风官网申请授权。
