無監督算法與異常檢測

一、整體概覽

       反欺詐往往看做是二分類問題,但是仔細想想是多分類問題,因爲每種不同類型的欺詐都當做是一種單獨的類型。欺詐除了多樣並且不斷變化,欺詐檢測還面臨一下問題:

      1). 由於大部分情況數據是沒有標籤的,各種成熟的監督學習是沒有辦法應用

      2). 區分噪音和異常點時難度比較大,甚至需要一點點經驗

      3). 當多種不同的欺詐結合在一起時,區分欺詐類型比較難,因爲不瞭解每一種欺詐定義

      4). 即使有真的欺詐數據,即在有標籤的情況下用監督學習,也存在很大的不確定性。用這種歷史數據學出的模型只能檢測當時存在以及比較相似的欺詐,而對於不同的詐騙和從未見過的詐騙,模型預測效果一般比較差。

       因此,在實際情況中,一般不直接用任何監督學習,或者說不能至少單獨依靠一個監督學習模型來奢求檢測到所有的欺詐。沒有歷史標籤和對詐騙的理解,無法做出對詐騙細分的模型。一般常見的做法是使用無監督模型,且需要不同專家根據經驗來驗證預測,提供反饋,便於及時調整模型。

      拿到含有欺詐的數據之後,一般做法有通過關聯矩陣分析以及多維尺度變換。

 

二、無監督學習

        當一個場景需要做預判的時候,有沒有標籤,往往能做的主要有遷移學習、專家模型、無監督算法。

   遷移學習

    源域樣本和目標域樣本分佈有比較大的區別,目標域樣本量不夠的話,通過算法縮小邊緣分佈之間和條件分佈之間的差異。

  • 基於特徵的遷移
  • 基於模型的遷移
  • 基於實例的遷移

   不足之處:需要擁有與當前目標場景相關的源域數據

   專家模型

    主要是根據主觀經驗進行評判,而不是根據統計分析或者模型算法來進行客觀的計算。常規操作過程如下所示:

  •  根據相關經驗判斷特徵重要性
  •  根據相關經驗進行變量加權

   不足之處:需要大量的行業經驗積累,有時候不太具有說服性

   無監督算法

       缺乏足夠的先驗知識,無法對數據進行標記時,使用的一種機器學習方法,代表聚類、降維等。在風控領域中主要使用的是聚類和無監督異常檢測。聚類是發現樣本之間的相似性,異常檢測是發現樣本之間的差異性。

    聚類

  •  K-Means
  • DBSCAN
  • 層次聚類
  • 社區發現

        一般主要是對負樣本聚類,將有問題的用戶細分爲幾類。社區發現算法一般是識別團伙欺詐的主要手段之一,主要方法是通過知識圖譜將小團體篩選出來。在知識圖譜中,聚集意味着風險。

     異常檢測

       異常點檢測,通常也被稱爲離羣點檢測,是找出與預期對象的行爲差異較大的對象的一個檢測過程。檢測出來的數據被稱爲異常點或者離羣點。異常點在生產生活中有比較多的應用,例如廣告點擊反作弊、設備損壞等。異常點是一個數據對象,明顯不同於其他的數據對象。如下所示,N1,N2爲區域內的正常數據,而離這兩個正常區域比較遠的地方的點爲異常點。

                                                          

        異常檢測和不均衡學習的區別是,異常檢測一般是無監督的;與普通的二分類區別是異常檢測往往看作爲是二分類,但是其實是多分類(由於造成異常的原因各不相同)

       異常檢測算法的前提假設:1) 異常數據跟樣本中大多數數據不太一樣  2) 異常數據在整體數據樣本中佔比相對比較小

       異常檢測的主要思想是基於樣本(小羣體)之間的相似度: 距離、密度、簇

       異常檢測算法的意義:

        1). 很多場景沒有標籤或者標籤比較少,不能訓練監督模型

        2). 樣本總是在發生變換,只能從一個小羣體內部發現異常

        3). 異常檢測假設異常樣本數據量佔比較少,並且在某種維度上遠離其他樣本,符合個體欺詐的先驗知識。在團體欺詐不太適用。

        4). 樣本羣體有異構成分,可以對樣本進行篩選

三、異常檢測的常用算法

         常見的異常檢測算法有Z-score算法、KNN算法、Local Outlier Factor、孤立森林等

    Z-score算法

        假設樣本服從正態分佈,用於描述樣本偏離正態分佈的程度。通過計算μ和σ得到當前樣本所屬於的正態分佈的表達式,然後分別計算每個樣本在這個概率密度函數下被生成的概率,當概率小於某一閾值我們認爲這個樣本是不屬於這個分佈的,因此定義爲異常值。計算公式如下所示:

       

  根據獲取平均值和方差的估計值,然後給定一個新的訓練實例,根據模型計算p(x):

  當p(x) < ep(x) < e時候,數據未異常

  缺點是需要假設樣本滿足正態分佈,而大部分場景不滿足正態分佈假設條件

      KNN異常檢測

       KNN算法專注於全局異常檢測,所以無法檢測到局部異常。

       首先,對於數據集中的每條記錄,必須找到k個最近的鄰居。然後使用這K個鄰居計算異常分數。

我們有三種方法

  • 最大:使用到第k個鄰居的距離作爲離羣值得分
  • 平均值:使用所有k個鄰居的平均值作爲離羣值得分
  • 中位數:使用到k個鄰居的距離的中值作爲離羣值得分

       在實際方法中後兩種的應用度較高。然而,分數的絕對值在很大程度上取決於數據集本身、維度數和規範化。參數k的選擇當然對結果很重要。如果選擇過低,記錄的密度估計可能不可靠。(即過擬合)另一方面,如果它太大,密度估計可能太粗略。K值的選擇通常在10<k<50這個範圍內。所以在分類方法中,選擇一個合適的K值,可以用交叉驗證法。但是,事實上基於KNN的算法都是不適用於欺詐檢測的,因爲他們本身就對噪聲比較敏感。

     Local Outlier Factor

      基於密度的LOF算法相對比較更加簡單、直觀以及不需要對數據的分佈有太多的要求,還能量化每個數據點的異常程度。該算法是基於密度的算法,主要核心的部分是關於數據點密度的刻畫。算法的整個流程涉及如下概念:

     1)k-近鄰距離(k-distance): 在距離數據點p最近的幾個點中,第k個最近的點跟點p之間的距離稱爲點p的k-近鄰距離,記爲k-distance(p).

     2)  可達距離(rechability distance): 可達距離的定義跟k-近鄰距離是相關的,給定參數k時,數據點p到數據點o的可達距離reach-dist(p,o)爲數據點o的K-近鄰距離和數據點p與點o之間的直接距離的最大值。即:

                    reachdist-k(p,o) = max{k-distance(o),d(p,o)}

     3) 局部可達密度(local rechablity density):局部可達密度的定義是基於可達距離的,對於數據點p,那些跟點p的距離小於等於k-distance(p)的數據點稱爲它的k-nearest-neighor,記爲Nk(p),數據點p的局部可達密度爲它與鄰近的數據點的平均可達距離的倒數,即:

 

     4) 局部異常因子: 根據局部可達密度的定義,如果一個數據點跟其他點比較疏遠的話,那麼顯然它的局部可達密度就小。但LOF算法衡量一個數據點的異常程度,並不是看它的絕對局部密度,而是看它跟周圍鄰近的數據點的相對密度。這樣做的好處是可以允許數據分佈不均勻、密度不同的情況。局部異常因子即是用局部相對密度來定義的。數據點 p 的局部相對密度(局部異常因子)爲點p的鄰居們的平均局部可達密度跟數據點p的局部可達密度的比值,即:

      根據局部異常因子的定義,如果數據點 p 的 LOF 得分在1附近,表明數據點p的局部密度跟它的鄰居們差不多;如果數據點 p 的 LOF 得分小於1,表明數據點p處在一個相對密集的區域,不像是一個異常點;如果數據點 p 的 LOF 得分遠大於1,表明數據點p跟其他點比較疏遠,很有可能是一個異常點。

       小結:整個LOF算法,首先對於每個數據點,計算它與其它所有點的距離,並按從近到遠排序,然後對於每個數據點,找到它的k-nearest-neighbor,最後計算LOF得分。

/******** LOF算法 ********/

from pyod.models.lof import LOF
clf = LOF(n_neighbors=20, algorithm='auto', leaf_size=30, metric='minkowski', p=2, 
          metric_params=None, contamination=0.1, n_jobs=1)
clf.fit(x)

  計算出每一個數據點的LOF值,然後在二維空間中展示,如下圖所示,數據點的LOF值越大表示該點越異常

       

   Trick: LOF算法中關於局部可達密度的定義其實暗含了一個假設,即:不存在大於等於 k 個重複的點。當這樣的重複點存在的時候,這些點的平均可達距離爲零,局部可達密度就變爲無窮大,會給計算帶來一些麻煩。在實際應用時,爲了避免這樣的情況出現,可以把 k-distance 改爲 k-distinct-distance,不考慮重複的情況。或者,還可以考慮給可達距離都加一個很小的值,避免可達距離等於零。

      Isolation Forest

        先用一個簡單的例子來說明 Isolation Forest 的基本想法。假設現在有一組一維數據(如下圖所示),我們要對這組數據進行隨機切分,希望可以把點 A 和點 B 單獨切分出來。具體的,我們先在最大值和最小值之間隨機選擇一個值 x,然後按照 =x 可以把數據分成左右兩組。然後,在這兩組數據中分別重複這個步驟,直到數據不可再分。顯然,點 B 跟其他數據比較疏離,可能用很少的次數就可以把它切分出來;點 A 跟其他數據點聚在一起,可能需要更多的次數才能把它切分出來。

                                                           

       我們把數據從一維擴展到兩維。同樣的,我們沿着兩個座標軸進行隨機切分,嘗試把下圖中的點A'和點B'分別切分出來。我們先隨機選擇一個特徵維度,在這個特徵的最大值和最小值之間隨機選擇一個值,按照跟特徵值的大小關係將數據進行左右切分。然後,在左右兩組數據中,我們重複上述步驟,再隨機的按某個特徵維度的取值把數據進行細分,直到無法細分,即:只剩下一個數據點,或者剩下的數據全部相同。跟先前的例子類似,直觀上,點B'跟其他數據點比較疏離,可能只需要很少的幾次操作就可以將它細分出來;點A'需要的切分次數可能會更多一些。

                                                        

       按照先前提到的關於“異常”的兩個假設,一般情況下,在上面的例子中,點B和點B' 由於跟其他數據隔的比較遠,會被認爲是異常數據,而點A和點A' 會被認爲是正常數據。直觀上,異常數據由於跟其他數據點較爲疏離,可能需要較少幾次切分就可以將它們單獨劃分出來,而正常數據恰恰相反。這其實正是Isolation Forest(IF)的核心概念。IF採用二叉樹去對數據進行切分,數據點在二叉樹中所處的深度反應了該條數據的“疏離”程度。整個算法大致可以分爲兩步:

  • 訓練:抽取多個樣本,構建多棵二叉樹(Isolation Tree,即 iTree);
  • 預測:綜合多棵二叉樹的結果,計算每個數據點的異常分值。

      訓練:構建一棵 iTree 時,先從全量數據中抽取一批樣本,然後隨機選擇一個特徵作爲起始節點,並在該特徵的最大值和最小值之間隨機選擇一個值,將樣本中小於該取值的數據劃到左分支,大於等於該取值的劃到右分支。然後,在左右兩個分支數據中,重複上述步驟,直到滿足如下條件:

  • 數據不可再分,即:只包含一條數據,或者全部數據相同。
  • 二叉樹達到限定的最大深度。

      預測:計算數據 x 的異常分值時,先要估算它在每棵 iTree 中的路徑長度(也可以叫深度)。具體的,先沿着一棵 iTree,從根節點開始按不同特徵的取值從上往下,直到到達某葉子節點。假設 iTree 的訓練樣本中同樣落在 x 所在葉子節點的樣本數爲 T.size,則數據 x 在這棵 iTree 上的路徑長度 h(x),可以用下面這個公式計算:

                                                            h(x)=e+C(T.size)

公式中,e 表示數據 x 從 iTree 的根節點到葉節點過程中經過的邊的數目,C(T.size) 可以認爲是一個修正值,它表示在一棵用 T.size 條樣本數據構建的二叉樹的平均路徑長度。一般的,C(n) 的計算公式如下:

 

                                                     C(n)=2H(n−1)−2(n−1)/n

其中,H(n-1) 可用 ln(n-1)+0.577 估算,這裏的常數是歐拉常數。數據 x 最終的異常分值 Score(x) 綜合了多棵 iTree 的結果

公式中,E(h(x)) 表示數據 x 在多棵 iTree 的路徑長度的均值,φ表示單棵 iTree 的訓練樣本的樣本數,C(φ)表示用φ條數據構建的二叉樹的平均路徑長度,它在這裏主要用來做歸一化。

從異常分值的公式看,如果數據 x 在多棵 iTree 中的平均路徑長度越短,得分越接近 1,表明數據 x 越異常;如果數據 x 在多棵 iTree 中的平均路徑長度越長,得分越接近 0,表示數據 x 越正常;如果數據 x 在多棵 iTree 中的平均路徑長度接近整體均值,則打分會在 0.5 附近。

四、應用案例

1、清洗建模數據集,將異常樣本通過無監督算法進行篩選

from pyod.models.iforest import IForest
from matplotlib import pyplot as plt

clf = IForest(behaviour='new', bootstrap=False, contamination=0.1, max_features=1.0,
                max_samples='auto', n_estimators=500, n_jobs=-1, random_state=None,verbose=0)
clf.fit(x)

out_pred = clf.predict_proba(x,method ='linear')[:,1]
train['out_pred'] = out_pred

x = train[train.out_pred< 0.7][feature_lst]
y = train[train.out_pred < 0.7]['bad_ind']

val_x =  val[feature_lst]
val_y = val['bad_ind']

lr_model = LogisticRegression(C=0.1,class_weight='balanced')
lr_model.fit(x,y)
y_pred = lr_model.predict_proba(x)[:,1]
fpr_lr_train,tpr_lr_train,_ = roc_curve(y,y_pred)
train_ks = abs(fpr_lr_train - tpr_lr_train).max()
print('train_ks : ',train_ks)

y_pred = lr_model.predict_proba(val_x)[:,1]
fpr_lr,tpr_lr,_ = roc_curve(val_y,y_pred)
val_ks = abs(fpr_lr - tpr_lr).max()
print('val_ks : ',val_ks)

plt.plot(fpr_lr_train,tpr_lr_train,label = 'train LR')
plt.plot(fpr_lr,tpr_lr,label = 'evl LR')
plt.plot([0,1],[0,1],'k--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC Curve')
plt.legend(loc = 'best')
plt.show()

                                                                               

2、通過樣本異常程度進行分析

train.out_pred.groupby(train.obs_mth).mean()

train.out_pred.groupby(train.obs_mth).max()

train.out_pred.groupby(train.obs_mth).var()

train['for_pred'] = np.where(train.out_pred>0.7,1,0)

train.for_pred.groupby(train.obs_mth).sum()/train.for_pred.groupby(train.obs_mth).count()


#看一下badrate
train.bad_ind.groupby(train.for_pred).sum()/train.bad_ind.groupby(train.for_pred).count()

3、冷啓動,假設沒有標籤

y_pred = clf.predict_proba(x,method ='linear')[:,1]
fpr_lr_train,tpr_lr_train,_ = roc_curve(y,y_pred)
train_ks = abs(fpr_lr_train - tpr_lr_train).max()
print('train_ks : ',train_ks)

y_pred = clf.predict_proba(val_x,method ='linear')[:,1]
fpr_lr,tpr_lr,_ = roc_curve(val_y,y_pred)
val_ks = abs(fpr_lr - tpr_lr).max()
print('val_ks : ',val_ks)
from matplotlib import pyplot as plt
plt.plot(fpr_lr_train,tpr_lr_train,label = 'train LR')
plt.plot(fpr_lr,tpr_lr,label = 'evl LR')
plt.plot([0,1],[0,1],'k--')
plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('ROC Curve')
plt.legend(loc = 'best')
plt.show()

參考文獻:https://pyod.readthedocs.io/en/latest/pyod.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章