【机器学习】孤立森林-一个通过瞎胡乱分进行异常检测的算法
孤立森林(isolation Forest)算法,2008年由劉飛、周志華等提出,算法不借助類似距離、密度等指標去描述樣本與其他樣本的差異,而是直接去刻畫所謂的疏離程度(isolation),因此該算法簡單、高效,在工業界應用較多。
Isolation Forest算法的邏輯很直觀,算法采用二叉樹對數據進行分裂,樣本選取、特征選取、分裂點選取都采用隨機化的方式(我稱之為瞎胡亂分,誒,但是人家效果出奇的好,你說氣人不)。如果某個樣本是異常值,可能需要很少次數就可以切分出來,看看下面這個不大恰當的例子。
給你根棍子,還把你眼睛蒙上,要把下面的白棋巴拉出來,找個人在邊上給你計數,看多少次能分出來,A這一堆,需要的次數非常多,可能一早上你都巴拉不出來;而B這一堆,可能一次就巴拉出來了,需要的次數非常少,那我們就可以通過計算巴拉的次數來衡量一個點的異常程度了,巴拉的次數越少,越不合群,也就越異常。一個人巴拉可能存在隨機性,不大準,那我們找100個人來巴拉,然后將每個人巴拉的次數取的平均,那不就準了,孤立森林,大概也就是這個思想了。
瞎扯了這么多,我們開始正文吧。
一、理解孤立森林
再用一個例子來說明孤立森林的思想:假設現在有一組一維數據(如下圖),我們要對這組數據進行切分,目的是把點A和 B單獨切分出來,先在最大,值和最小值之間隨機選擇一個值 X,然后按照 <X ?和 ?>=X 可以把數據分成左右兩組,在這兩組數據中分別重復這個步驟,直到數據不可再分。
點B跟其他數據比較疏離,可能用很少的次數就可以把它切分出來,點 A 跟其他數據點聚在一起,可能需要更多的次數才能把它切分出來。
那么從統計意義上來說,相對聚集的點需要分割的次數較多,比較孤立的點需要的分割次數少,孤立森林就是利用分割的次數來度量一個點是聚集的(正常)還是孤立的(異常)。
直觀上來看,我們可以發現,那些密度很高的簇要被切很多次才會停止切割,即每個點都單獨存在于一個子空間內,但那些分布稀疏的點,大都很早就停到一個子空間內了。
先sklearn的孤立森林算法,搞個例子看看大概的算法流程和結果,模擬上面的數據構造一個數據集。異常檢測的工具還是很多的,主要有以下幾個,我們這次選用的是Scikit-Learn進行實驗。
1、PyOD:超過30種算法,從經典模型到深度學習模型一應俱全,和sklearn的用法一致
2、Scikit-Learn:包含了4種常見的算法,簡單易用
3、TODS:與PyOD類似,包含多種時間序列上的異常檢測算法
數據集是月工資的,單位為萬,看看哪些是異常的。
# 加載模型所需要的的包 import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt from sklearn.ensemble import IsolationForest# 構造一個數據集,只包含一列數據,假如都是月薪數據,有些可能是錯的 df = pd.DataFrame({'salary':[4,1,4,5,3,6,2,5,6,2,5,7,1,8,12,33,4,7,6,7,8,55]})#構建模型 ,n_estimators=100 ,構建100顆樹 model = IsolationForest(n_estimators=100, max_samples='auto', contamination=float(0.1),max_features=1.0) # 訓練模型 model.fit(df[['salary']])# 預測 decision_function 可以得出 異常評分 df['scores'] = model.decision_function(df[['salary']])# predict() 函數 可以得到模型是否異常的判斷,-1為異常,1為正常 df['anomaly'] = model.predict(df[['salary']]) print(df) salary scores anomaly 0 4 0.212235 1 1 1 0.095133 1 2 4 0.212235 1 3 5 0.225169 1 4 3 0.156926 1 5 6 0.227608 1 6 2 0.153989 1 7 5 0.225169 1 8 6 0.227608 1 9 2 0.153989 1 10 5 0.225169 1 11 7 0.212329 1 12 1 0.095133 1 13 8 0.170615 1 14 12 -0.010570 -1 15 33 -0.116637 -1 16 4 0.212235 1 17 7 0.212329 1 18 6 0.227608 1 19 7 0.212329 1 20 8 0.170615 1 21 55 -0.183576 -1我們可以看到,發現了三個異常的數據,和我們認知差不多,都是比較高的,并且異常值越大,異常分scores就越大,比如那個月薪55萬的,不是變態就是數據錯了。
其實到此,你基本上就會用這個算法了,但是,我們還需要更深入的理解的話,繼續往下看。
二、孤立森林算法詳解
孤立森林與隨機森林非常相似,它是基于給定數據集的決策樹集成而建立的,然而,也有一些區別,孤立森林將異常識別為樹上平均路徑較短的觀測結果,每個孤立樹都應用了一個過程:
隨機選擇m個特征,通過在所選特征的最大值和最小值之間隨機選擇一個值來分割數據點。觀察值的劃分遞歸地重復,直到所有的觀察值被孤立。
我們看看更加明細的訓練+預測過程
1、前提假設
異常樣本不能占比太高
異常樣本和正常樣本差異較大
2、算法思想
異常樣本更容易快速落入葉子結點或者說,異常樣本在決策樹上,距離根節點更近
3、算法訓練
1)訓練邏輯
從原始數據中,有放回或者不放回的抽取部分樣本,選取部分特征,構建一顆二叉樹(iTree即Isolation Tree),利用集成學習的思想,多次抽取樣本和特征,完成多棵iTree的構建。
2)iTree停止條件
樹達到指定的高度/深度,或者數據不可再分。
3)算法偽代碼如下
4)幾個小問題:
樹的最大深度=ceiling(log(subsimpleSize)),paper里說自動指定,sklearn也是在代碼中寫死:max_depth = int(np.ceil(np.log2(max(max_samples, 2))))這個值接近樹的平均深度,我們只關注那些小于平均深度的異常值,所以無需讓樹完全生長
Sub-sampling size,每次抽取的樣本數,建議256即可,大于256,性能上不會有大的提升
Number of tree,建議100,如果特征和樣本規模比較大,可以適當增加
4、算法預測
1)PathLength計算邏輯
類似二分類模型,預測時可輸出P ( y = 1 ) P(y=1)P(y=1),iforest最終也可以輸出一個異常得分。很容易想到用該樣本落入葉子結點時,split的次數(即樣本落入葉子結點經過的邊)作為衡量指標,對于t tt棵樹,取平均即可。先看PathLength的計算邏輯:
2)PathLength計算公式如下:
其中e為樣本x xx從樹的根節點到葉節點的過程中經歷的邊的個數,即split次數。T.size表示和樣本x同在一個葉子結點樣本的個數,C(T.size)可以看做一個修正值,表示T.size個樣本構建一個二叉樹的平均路徑長度,c(n)計算公式如下:
其中,0.5772156649為歐拉常數
3)為何加入這一修正值?
由于樹的深度設置為ceiling(log(subsimpleSize)) ,所以可能大部分樣本的PathLength都比較接近,而如果葉子結點的樣本數越多,該樣本是異常值的概率也較低(基于fewAndDifferent的假設)。另外c(n)是單調遞增的,總之,加了修正,使得異常和正常樣本的PathLength差異更大,可以簡單的理解,如果樣本快速落入葉子結點,并且該葉子結點的樣本數較少,那么其為異常樣本的可能性較大。
5、計算異常分
樣本落入葉子結點經過的邊數(split次數),除了和樣本本身有關,也和limit length和抽樣的樣本子集有關系。這里,作者采用歸一化的方式,把split length的值域映射到0-1之間。具體公式如下:
其 中:
h(x):為樣本在iTree上的PathLength
E(h(x)):為樣本在t棵iTree的PathLength的均值
c(n):為n個樣本構建一個BST二叉樹的平均路徑長度,為什么要算這個,因為iTree和BST的結構的等價性, 標準化借鑒BST(Binary Search Tree)去估計路徑的平均長度c(n)。
上述公式,指數部分值域為(?∞,0),因此s值域為(0,1)。當PathLength越小,s越接近1,此時樣本為異常值的概率越大。
我們知道了E(h(x))是根節點到外部節點x的路徑長度h(x)的平均值,而c(n)是給定n的h(x)的平均值,用于規范化h(x)。有三種可能的情況:
當E(h(x))= c(n), s(x,n)=1/2
當E(h(x))-> 0, ? ?s(x,n)= 1
當E(h(x))-> n-1,s(x,n)= 0
當觀測的得分接近1時,路徑長度非常小,那么數據點很容易被孤立,我們有一個異常。當觀測值小于0.5時,路徑長度就會變大,然后我們就得到了一個正常的數據點。如果所有的觀察結果都有0.5左右的異常值,那么整個樣本就沒有任何異常。
然后,孤立森林可以通過計算每棵樹的異常得分,并在孤立樹之間進行平均,從而在比正常觀測更少的步驟中隔離異常。事實上,得分較高的異常值路徑長度較低。
注:scikit-learn的隔離森林引入了異常分數的修改,異常值由負的分數表示,而正的分數意味著是正常的。
上圖就是對子樣本進行切割訓練的過程,左圖的 xi 處于密度較高的區域,因此切割了十幾次才被分到了單獨的子空間,而右圖的 x0 落在邊緣分布較稀疏的區域,只經歷了四次切分就被 “孤立” 了。
四、孤立森林參數
我們使用sklearn中的孤立森林,進行參數調節講解,一般任務默認參數即可,算法API地址:
https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.IsolationForest.html#sklearn.ensemble.IsolationForest
1、基本用法
sklearn.ensemble.IsolationForest(*, n_estimators=100, max_samples='auto', contamination='auto', max_features=1.0, bootstrap=False, n_jobs=None, random_state=None, verbose=0, warm_start=False)2、參數詳解
n_estimators : int, optional (default=100)
iTree的個數,指定該森林中生成的隨機樹數量,默認為100個
max_samples : int or float, optional (default=”auto”)
構建子樹的樣本數,整數為個數,小數為占全集的比例,用來訓練隨機數的樣本數量,即子采樣的大小
如果設置的是一個int常數,那么就會從總樣本X拉取max_samples個樣本來生成一棵樹iTree
如果設置的是一個float浮點數,那么就會從總樣本X拉取max_samples * X.shape[0]個樣本,X.shape[0]表示總樣本個數
如果設置的是"auto",則max_samples=min(256, n_samples),n_samples即總樣本的數量
如果max_samples值比提供的總樣本數量還大的話,所有的樣本都會用來構造數,意思就是沒有采樣了,構造的n_estimators棵iTree使用的樣本都是一樣的,即所有的樣本
contamination : float in (0., 0.5), optional (default=0.1)
取值范圍為(0., 0.5),表示異常數據占給定的數據集的比例,數據集中污染的數量,其實就是訓練數據中異常數據的數量,比如數據集異常數據的比例。定義該參數值的作用是在決策函數中定義閾值。如果設置為'auto',則決策函數的閾值就和論文中定義的一樣
max_features : int or float, optional (default=1.0)
構建每個子樹的特征數,整數位個數,小數為占全特征的比例,指定從總樣本X中抽取來訓練每棵樹iTree的屬性的數量,默認只使用一個屬性
如果設置為int整數,則抽取max_features個屬性
如果是float浮點數,則抽取max_features * X.shape[1]個屬性
bootstrap : boolean, optional (default=False)
采樣是有放回還是無放回,如果為True,則各個樹可放回地對訓練數據進行采樣。如果為False,則執行不放回的采樣。
n_jobs :int or None, optional (default=None)
在運行fit()和predict()函數時并行運行的作業數量。除了在joblib.parallel_backend上下文的情況下,None表示為1。設置為-1則表示使用所有可用的處理器
random_state : int, RandomState instance or None, optional (default=None)
每次訓練的隨機性
如果設置為int常數,則該random_state參數值是用于隨機數生成器的種子
如果設置為RandomState實例,則該random_state就是一個隨機數生成器
如果設置為None,該隨機數生成器就是使用在np.random中的RandomState實例
verbose : int, optional (default=0)
訓練中打印日志的詳細程度,數值越大越詳細
warm_start : bool, optional (default=False)
當設置為True時,重用上一次調用的結果去fit,添加更多的樹到上一次的森林1集合中;否則就fit一整個新的森林
3、屬性
base_estimator_:The child estimator template used to create the collection of fitted sub-estimators.
estimators_:list of ExtraTreeRegressor instances The collection of fitted sub-estimators.
estimators_:features_list of ndarray The subset of drawn features for each base estimator.
estimators_samples_:list of ndarray The subset of drawn samples for each base estimator.
max_samples_:The actual number of samples
n_features_:DEPRECATED: Attribute n_features_ was deprecated in version 1.0 and will be removed in 1.2.
n_features_in_:Number of features seen during fit.
feature_names_in_:Names of features seen during fit. Defined only when X has feature names that are all strings
4、方 法
fit(X[, y, sample_weight]):訓練模型
decision_function(X):返回平均異常分數
predict(X):預測模型返回1或者-1
fit_predict(X[, y]):訓練-預測模型一起完成
get_params([deep]):Get parameters for this estimator.
score_samples(X):Opposite of the anomaly score defined in the original paper.
set_params(**params):Set the parameters of this estimator.
五、應用案例
1、模型訓練
有了對原理的了解,算法參數的介紹,我們就可以進行應用了,為了方便大家跑通,沒有引入外部數據,直接使用簡單的內置的iris 數據集作為案例。
import plotly.express as px from sklearn.datasets import load_iris from sklearn.ensemble import IsolationForestdata = load_iris(as_frame=True) X,y = data.data,data.target df = data.frame df.head() sepal length (cm) sepal width (cm) ... petal width (cm) target 0 5.1 3.5 ... 0.2 0 1 4.9 3.0 ... 0.2 0 2 4.7 3.2 ... 0.2 0 3 4.6 3.1 ... 0.2 0 4 5.0 3.6 ... 0.2 0[5 rows x 5 columns]# 模型訓練 iforest = IsolationForest(n_estimators=100, max_samples='auto', contamination=0.05, max_features=4, bootstrap=False, n_jobs=-1, random_state=1)# fit_predict 函數 訓練和預測一起 可以得到模型是否異常的判斷,-1為異常,1為正常 df['label'] = iforest.fit_predict(X) # 預測 decision_function 可以得出 異常評分 df['scores'] = iforest.decision_function(X) dfsepal length (cm) sepal width (cm) ... anomaly_label scores 0 5.1 3.5 ... 1 0.177972 1 4.9 3.0 ... 1 0.148945 2 4.7 3.2 ... 1 0.129540 3 4.6 3.1 ... 1 0.119440 4 5.0 3.6 ... 1 0.169537 .. ... ... ... ... ... 145 6.7 3.0 ... 1 0.131967 146 6.3 2.5 ... 1 0.122848 147 6.5 3.0 ... 1 0.160523 148 6.2 3.4 ... 1 0.073536 149 5.9 3.0 ... 1 0.169074[150 rows x 7 columns]# 我們看看哪些預測為異常的 df[df.label==-1]sepal length (cm) sepal width (cm) ... anomaly_label scores 13 4.3 3.0 ... -1 -0.039104 15 5.7 4.4 ... -1 -0.003895 41 4.5 2.3 ... -1 -0.038639 60 5.0 2.0 ... -1 -0.008813 109 7.2 3.6 ... -1 -0.037663 117 7.7 3.8 ... -1 -0.046873 118 7.7 2.6 ... -1 -0.055233 131 7.9 3.8 ... -1 -0.064742[8 rows x 7 columns]2、模型可視化
我們可視化看看預測的結果,看看異常類型和非異常類型的直方圖分布,這里最好用notebook畫,其他地方畫不一定能顯示出來。
df['anomaly'] = df['label'].apply(lambda x: 'outlier' if x==-1 else 'inlier') fig = px.histogram(df,x='scores',color='anomaly') fig.show()在看看3D圖,紅色的是比較異常的,可以看看到,還是比較準確的,但是由于這個數據本身差異性就不大,所以沒那么明顯,實際業務中的異常數據,一般都具有更大的差異化。
fig = px.scatter_3d(df,x='petal width (cm)', y='sepal length (cm)', z='sepal width (cm)', color='anomaly') fig.show()六、結? ?論
1、每棵樹隨機采樣獨立生成,所以孤立森林具有很好的處理大數據的能力和速度,可以進行并行化處理,因此可部署在大規模分布式系統上來加速運算。
2、通常樹的數量越多,算法越穩定,樹的深度不易過深,但不是越多越好,通過下圖可以發現,當t>=100后,劃分上文提到的xi和x0的平均路徑長度都已經收斂了,故因此論文中推薦t設置為100。
3、不同于 KMeans、DBSCAN 等算法,孤立森林不需要計算有關距離、密度的指標,可大幅度提升速度,減小系統開銷;
4、為什么需要對樹的高度做限制?之所以對樹的高度做限制,是因為我們只關心路徑長度較短的點,它們更可能是異常點,而并不關心那些路徑很長的正常點
往期精彩回顧適合初學者入門人工智能的路線及資料下載(圖文+視頻)機器學習入門系列下載中國大學慕課《機器學習》(黃海廣主講)機器學習及深度學習筆記等資料打印《統計學習方法》的代碼復現專輯 AI基礎下載機器學習交流qq群955171419,加入微信群請掃碼:總結
以上是生活随笔為你收集整理的【机器学习】孤立森林-一个通过瞎胡乱分进行异常检测的算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: qml 鼠标点击_QML ListVie
- 下一篇: 转:javascript方法--bind