Python機器學習筆記:異常點檢測算法——One Class SVM


完整代碼及其數據,請移步小編的GitHub

  傳送門:請點擊我

  如果點擊有誤:https://github.com/LeBron-Jian/MachineLearningNote

前言

  最近老板有一個需求,做單樣本檢測,也就是說只有一個類別的數據集與標簽,因為在工廠設備中,控制系統的任務是判斷是是否有意外情況出現,例如產品質量過低,機器產生奇怪的震動或者機器零件脫落等。相對來說容易得到正常場景下的訓練數據,但故障系統狀態的收集示例數據可能相當昂貴,或者根本不可能。如果可以模擬一個錯誤的系統狀態,問題就好解決多了,但無法保證所有的錯誤狀態都被模擬到,所以只能尋找單樣本檢測相關的算法。

  所幸了解到一些單樣本檢測的算法,比如Isolation Forest,One-Class Classification,所以這篇文章就記錄一下自己做的關於One-Class SVM 的筆記。

一,單分類算法簡介

  One Class Learning 比較經典的算法是One-Class-SVM,這個算法的思路非常簡單,就是尋找一個超平面將樣本中的正例圈出來,預測就是用這個超平面做決策,在圈內的樣本就認為是正樣本。由於核函數計算比較耗時,在海量數據的場景用的並不多;

  另一個算法是基於神經網絡的算法,在深度學習中廣泛使用的自編碼算法可以應用在單分類的問題上,自編碼是一個BP神經網絡,網絡輸入層和輸出層是一樣,中間層數可以有多層,中間層的節點個數比輸出層少,最簡單的情況就是中間只有一個隱藏層,如下圖所示,由於中間層的節點數較少,這樣中間層相當於是對數據進行了壓縮和抽象,實現無監督的方式學習數據的抽象特征。

 

   如果我們只有正樣本數據,沒有負樣本數據,或者說只關注學習正樣本的規律,那么利用正樣本訓練一個自編碼器,編碼器就相當於單分類的模型,對全量數據進行預測時,通過比較輸入層和輸出層的相似度就可以判斷記錄是否屬於正樣本。由於自編碼采用神經網絡實現,可以用GPU來進行加速計算,因此比較適合海量數據的場景。

  還有Robust covariance 方法,基於協方差的穩健估計,假設數據是高斯分布的,那么在這樣的案例中執行效果將優於One-Class SVM ;

  最后就是Isolation Forest方法,孤立森林是一個高效的異常點檢測算法。Sklearn提供了ensemble.IsolatuibForest模塊。該模塊在進行檢測時,會隨機選取一個特征,然后在所選特征的最大值和最小值隨機選擇一個分切面。該算法下整個訓練集的訓練就像一棵樹一樣,遞歸的划分。划分的次數等於根節點到葉子節點的路徑距離d。所有隨機樹(為了增強魯棒性,會隨機選取很多樹形成森林)的d的平均值,就是我們檢測函數的最終結果。

  孤立森林相關筆記可以參考這里:請點擊我

Python機器學習筆記:異常點檢測算法——Isolation Forest

sklearn中關於異常檢測的說法

Novelty and Outlier Detection

  Many applications require being able to decide whether a new observation belongs to the same distribution as existing observations(it is an inlier), or should be considered as different (it is an outlier).Often, this ability is used to clean real data sets.Two import distinction must be made:

  許多應用程序需要能夠決定是否一個新的觀察屬於與存在觀察點有相同分布(這是同類),或者應該被認為是不同的(它是個異類),通常這種能力是用來清潔真實數據集。兩個差別如下:

novelty detection

  The training data is not polluted by outliers, and we are interested in detecting anomalies in new observations.

  當訓練數據中沒有離群點,我們的目標是用訓練好的模型去檢測另外發現的新樣本

outlier  dection

  The training data contains outliers, and we need to fit the central mode of the training data, ignoring the deviant observations.

  當訓練數據中包含離群點,模型訓練時要匹配訓練數據的中心樣本,忽視訓練樣本中的其他異常點。

  sklearn提供了一些機器學習方法,可用於奇異(Novelty)點或者異常(Outlier)點檢測,包括OneClassSVM,Isolation Forest,Local Outlier Factor(LOF)等,其中OneCLassSVM可以用於Novelty Dection,而后兩者可用於Outlier Detection。

One-Class SVM 算法簡介

  嚴格來說,OneCLassSVM不是一種outlier detection,而是一種novelty detection方法:它的訓練集不應該摻雜異常點,因為模型可能會去匹配這些異常點。但在數據維度很高,或者對相關數據分布沒有任何假設的情況下,OneClassSVM也可以作為一種很好的outlier detection方法。

  在one-class classification中,僅僅只有一類的信息是可以用於訓練,其他類別的(總稱outlier)信息是缺失的,也就是區分兩個類別的邊界線是通過僅有的一類數據的信息學習得到的。

OneClass 與二分類,多分類的區別

  如果將分類算法進行划分,根據類別個數的不同可以分為單分類,二分類,多分類。常見的分類算法主要解決二分類和多分類問題,預測一封郵件是否是垃圾郵件是一個典型的二分類問題,手寫體識別是一個典型的多分類問題,這些算法並不能很好的應用在單分類上,但是單分類問題在工業界廣泛存在,由於每個企業刻畫用戶的數據都是有限的,很多二分類問題很難找到負樣本,即使用一些排除法篩選出負樣本,負樣本也會不純,不能保證負樣本中沒有正樣本。所以在只能定義正樣本不能定義負樣本的場景中,使用單分類算法更合適。

  單分類算法只關注與樣本的相似或者匹配程度,對於未知的部分不妄下結論。

  典型的二類問題:識別郵件是否是垃圾郵件,一類“是”,一類“不是”。

  典型的多類問題:人臉識別,每個人對應的臉就是一個類,然后把待識別的臉分到對應的類去。

  而OneClassClassification,它只有一個類,屬於該類就返回結果“是”,不屬於就返回結果“不是”。

  其區別就是在二分類問題中,訓練集中就由兩個類的樣本組成,訓練出的模型是一個二分類模型;而OneClassClassification中的訓練樣本只有一類,因此訓練出的分類器將不屬於該類的所有其他樣本判別為“不是”即可,而不是由於屬於另一類才返回“不是”的結果。

  現實場景中的OneCLassClassification例子:現在有一堆某商品的歷史銷售數據,記錄着買該產品的用戶信息,此外還有一些沒有購買過該產品的用戶信息,想通過二分類來預測他們是否會買該產品,也就是兩個類,一類是“買”,一類是“不買”。當我們要開始訓練二分類器的時候問題來了,一般來說沒買的用戶數會遠遠大於已經買了的用戶數,當將數據不均衡的正負樣本投入訓練時,訓練出的分類器會有較大的bisa(偏向值)。因此,這時候就可以使用OneClassClassification 方法來解決,即訓練集中只有已經買過該產品的用戶數據,在識別一個新用戶是否會買該產品時,識別結果就是“會”或者“不會”。

One Class SVM算法步驟

  One Class SVM也是屬於支持向量機大家族的,但是它和傳統的基於監督學習的分類回歸支持向量機不同,它是無監督學習的方法,也就是說,它不需要我們標記訓練集的輸出標簽。

  那么沒有類別標簽,我們如何尋找划分的超平面以及尋找支持向量機呢?One Class SVM這個問題的解決思路有很多。這里只講解一種特別的思想SVDD,對於SVDD來說,我們期望所有不是異常的樣本都是正類別,同時它采用一個超球體而不是一個超平面來做划分,該算法在特征空間中獲得數據周圍的球形邊界,期望最小化這個超球體的體積,從而最小化異常點數據的影響。

  假設產生的超球體參數為中心 o 和對應的超球體半徑 r >0,超球體體積V(r) 被最小化,中心 o 是支持行了的線性組合;跟傳統SVM方法相似,可以要求所有訓練數據點xi到中心的距離嚴格小於r。但是同時構造一個懲罰系數為 C 的松弛變量 ζi ,優化問題入下所示:

  采用拉格朗日對偶求解之后,可以判斷新的數據點 z 是否在內,如果 z 到中心的距離小於或者等於半徑 r ,則不是異常點,如果在超球體以外,則是異常點。

  在Sklearn中,我們可以采用SVM包里面的OneClassSVM來做異常點檢測。OneClassSVM也支持核函數,所以普通SVM里面的調參思路在這里也使用。

 

SVDD算法介紹

  SVDD(support  vector domain description),中文翻譯為:支持向量域描述。

  其基本思想是:既然只有一個class,那我就訓練出一個最小的超球面(超球面是指三維以上的空間中的球面,對應的二維空間中就是曲線,三維空間中就是球面),將這堆數據全部“包起來”,識別一個新的數據點時,如果這個數據點落在超球面內,就屬於這個類,否則不是。

  例如對於二維(維數依據特征提取而定,提取的特征多,維數就搞,為了方便展示,舉二維的例子,實際用時不可能維數這么低)數據,大概像下面這個樣子:

  有人可能會說:圖上的曲線並沒有把點全都包住,為什么會這樣呢?其實看原理就懂了,下面學習一下SVDD的原理。

  SVDD(support vector domain description)的原理和SVM(support vector machine)很像,可以用來做one class SVM,如果之前你看過SVM原理,那么下面的過程就很熟悉。

  凡是將模型,都會有一個優化目標,SVDD的優化目標就是,求一個中心為a,半徑為R的最小球面:

  使得這個球面滿足:

  滿足這個條件就是說要把training set中的數據點都包在球面里。

  這里的是松弛變量,和經典的SVM中的松弛變量的作用相同,它的作用就是,使得模型不會被個別極端的數據點給“破壞”了,想象一下,如果大多數的數據點都在一個小區域內,只有少數幾個異常數據在離他們很遠的地方,如果要找一個超球面把他們包住,這個超球面會很大,因為要包住那幾個很遠的點,這樣就使得模型對離群點很敏感,說的通俗一點就是,那幾個異常的點,雖然沒法判斷它是否真的是噪聲數據,它是因為大數點都在一起,就少數幾個不在這里,寧願把那幾個少數的數據點看成是異常的,以免模型為了迎合那幾個少數的數據點會做出過大的犧牲,這就是所謂的過擬合(overfitting)。所以容忍一些不滿足的硬性約束的數據點,給它一些彈性,同時又要包住training set中每個數據點都要滿足約束,這樣在后面才能使用Lagrange乘子法求解,因為Lagrange乘子法中是要包含約束條件的,如果你的數據都不滿足約束條件,那就沒法用了。注意松弛變量是帶有下標 i 的,也就是說它是和每個數據點是有關系的,每個數據點都有對應的松弛變量,可以理解為:

  對於每個數據點來說,那個超球面可以是不一樣的,根據松弛變量來控制,如果松弛變量的值一樣,那超球面就一樣的,哪個C就是調節松弛變量的影響大小,說的通俗一點就是,給那些需要松弛的數據點多少松弛空間,如果C很大的話,那么在cost function中,由松弛變量帶來的cost就大,那么training的時候會把松弛變量調小,這樣的結果就是不怎么容忍那些離群點,硬是要把他們包起來,反之如果C比較小,那會給離群點較大的彈性,使得它們可以不被包含進行。現在就可以明白上面圖為什么並沒把點全都包住了。

  下面展示兩張圖,第一張是C較小時候的情形,第二張圖是C較大時的情形:

  現在有了要求解的目標,又有了約束,接下來的求解方法和SVM幾乎一樣,用的是Lagrangian乘子法:

  注意,對參數求導並令導數等於0得到:

  把上面公式帶入Lagrangian函數,得到:

  注意此時,其中是由共同退出來的。上面的向量內積也可以像SVM那樣用核函數解決:

  之后的求解步驟就和SVM的一樣了,具體請參考SVM原理。

  訓練結束后,判斷一個新的數據點Z是否是這個類,那么就看這個數據點是否在訓練出來的超球面里面,如果在里面,即,則判定為屬於這個類,將超球面的中心用支持向量來表示,那么判定新數據是否屬於這個類的判定條件就是:

  如果使用核函數那就是:

 

 深度學習圖片分類增強數據集的方法匯總

1,隨機切割,圖片反轉,旋轉等等很多方法都可以增加訓練集,提高泛化能力

2,Resampling 或者增加噪音等等,人工合成更多的樣本

3,對於小樣本數據進行仿射變換,切割,旋轉,加噪等各種處理,可以生成更多樣本

4,用GAN生成數據提供給數據集

5,找個Imagenet數據集上訓練好的模型,凍結最后一層或者最后幾層,然后遷移學習 + fine tuning,圖片數量少,做一些翻轉,變化,剪切,白化等等。

6,水平翻轉Flip,隨機裁剪,平移變換Crops/Scales

  所以最常用的方法就是:像素顏色抖動,旋轉,剪切,隨機裁剪,水平翻轉,鏡頭拉伸和鏡頭矯正等等

 sklearn實現:OneClasssSVM

  根據已有支持向量機的理解,算法並非對已有標簽的數據進行分類判別,而是通過回答:yes  or  no 的方法去根據支持向量域(support  vector domaindescription SVDD),將樣本數據訓練出一個最小的超球面(大於三維特征),其中在二維中是一個曲線,將數據全部包起來,即將異常點排除。

 OneClass SVM 主要參數和方法

1
2
3
class  sklearn.svm.OneClassSVM(kernel=’rbf’, degree=3, gamma=’auto’,
coef0=0.0, tol=0.001, nu=0.5, shrinking=True, cache_size=200, verbose=False,
  max_iter=-1, random_state=None)

參數:

  kernel:核函數(一般使用高斯核)

  nu:設定訓練誤差(0, 1],表示異常點比例,默認值為0.5

 

屬性:

 

方法:

  fit(X):訓練,根據訓練樣本和上面兩個參數探測邊界。(注意是無監督)

  predict(X):返回預測值,+1就是正常樣本,-1就是異常樣本。

  decision_function(X):返回各樣本點到超平面的函數距離(signed distance),正的維正常樣本,負的為異常樣本。

  set_params(**params):設置這個評估器的參數,該方法適用於簡單估計器以及嵌套對象(例如管道),而后者具有表單<component>_<parameter>的參數,,因此可以更新嵌套對象的每個組件。

  get_params([deep]):獲取這個評估器的參數。

  fit_predict(X[, y]):在X上執行擬合並返回X的標簽,對於異常值,返回 -1 ,對於內點,返回1。

 

 

 One-Class SVM with  non-linear kernel (RBF)

  下面使用OneClass SVM 進行奇異點檢測。

  OneClass SVM 是一個無監督算法,它用於學習奇異點檢測的決策函數:將新數據分類為與訓練集相似或者不同的數據。

 數據結構

  訓練數據集 :X_train——2*2

1
2
3
4
5
6
array([[ 2.21240237  2.05677141],
  [ 2.2027219   1.58136665],
  [ 1.99770721  1.93131038],
                   ......
  [ 1.73608122  2.0932801 ]]
)

  這次訓練,我們不用喂給分類器label,而是無監督的。

  驗證數據集 :X_test——2*2

1
2
3
4
5
array([[ 2.13162953  2.58399607],
  [ 2.15900993  1.96661511],
  [ 2.20516139  2.01599965],
                    ......
  [ 2.14009567  1.98644128]])

  離群點: X_outliers——2*2

1
2
3
4
array([[1.93385943 3.75727667],
  [0.75210085 1.14235702],
                ......
  [3.76820269 2.36662343])

  預測的結果: y_pred_train:

1
2
3
4
5
[ 1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1 -1  1  1  1  1  1  1  1
   1 -1  1  1 -1  1  1  1  1  1  1 -1 -1  1  1  1  1  1  1  1  1  1  1  1
   1  1  1  1 -1  1  1 -1  1  1  1 -1  1  1  1  1  1  1  1  1  1  1  1  1
            ......
   1  1  1  1  1  1  1  1]

  預測的結果為 -1 或 1 ,在這個群落中為 1, 不在為 -1.

 sklearn實現代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as  np
import matplotlib.pyplot as  plt
import matplotlib.font_manager
from  sklearn import svm
 
xx, yy = np.meshgrid(np.linspace(-5, 5, 500), np.linspace(-5, 5, 500))
# Generate train data
X = 0.3 * np.random.randn(100, 2)
X_train = np.r_[X + 2, X - 2]
X_test = np.r_[X + 2, X-2]
# Generate some abnormal novel observations
X_outliers = np.random.uniform(low=0.1, high=4, size=(20, 2))
# fit the model
clf = svm.OneClassSVM(nu=0.1, kernel= 'rbf' , gamma=0.1)
clf.fit(X_train)
y_pred_train = clf.predict(X_train)
y_pred_test = clf.predict(X_test)
y_pred_outliers = clf.predict(X_outliers)
n_error_train = y_pred_train[y_pred_train == -1].size
n_error_test = y_pred_test[y_pred_test == -1].size
n_error_outlier = y_pred_outliers[y_pred_outliers == 1].size
 
# plot the line , the points, and the nearest vectors to the plane
Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
 
plt.title( "Novelty Detection" )
plt.contourf(xx, yy, Z, levels=np.linspace(Z.min(), 0, 7), cmap=plt.cm.PuBu)
a = plt.contour(xx, yy, Z, levels=[0, Z.max()], colors= 'palevioletred' )
 
s =40
b1 = plt.scatter(X_train[:, 0], X_train[:, 1], c= 'white' , s=s, edgecolors= 'k' )
b2 = plt.scatter(X_test[:, 0], X_test[:, 1], c= 'blueviolet' , s=s, edgecolors= 'k' )
c = plt.scatter(X_outliers[:, 0], X_outliers[:, 1], c= 'gold' , s=s, edgecolors= 'k' )
 
plt.axis( 'tight' )
plt.xlim((-5, 5))
plt.ylim((-5, 5))
plt.legend([a.collections[0], b1, b2, c],
            [ "learned frontier" , 'training observations' ,
             "new regular observations" , "new abnormal observations" ],
            loc= "upper left" ,
            prop=matplotlib.font_manager.FontProperties(size=11))
plt.xlabel(
     "error train: %d/200; errors novel regular: %d/40; errors novel abnormal:%d/40" %(
         n_error_train, n_error_test, n_error_outlier)    )
plt.show()

  

 

OneClassSVM 代碼二

  根據對已有支持向量機的理解,算法並非對已有標簽的數據進行分類判別,而是通過回答“yes or no”的方式去根據支持向量域描述(support  vector domaindescription  SVDD),將樣本數據訓練出一個最小的超球面(大於三維特征),其中在二維中是一個曲線,將數據全部包起來,即將異常點排除。Sklearn包中給出的demo實驗結果如上:我們可以看出在不同的數據分布下會有一些不一樣的誤差,其中調整參數中有一個比較重要的nu,表示異常點比例,默認值為0.5。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from  sklearn import svm
import matplotlib.pyplot as  plt
plt.style.use( 'fivethirtyeight' )
from  numpy import genfromtxt
 
def read_dataset(filePath, delimiter= ',' ):
     return  genfromtxt(filePath, delimiter=delimiter)
 
# use the same dataset
tr_data = read_dataset( 'tr_data.csv' )
 
clf = svm.OneClassSVM(nu=0.05, kernel= 'rbf' , gamma=0.1)
'' '
OneClassSVM(cache_size=200, coef0=0.0, degree=3, gamma=0.1, kernel= 'rbf' ,
       max_iter=-1, nu=0.05, random_state=None, shrinking=True, tol=0.001,
       verbose=False)
'' '
clf.fit(tr_data)
pred = clf.predict(tr_data)
 
# inliers are labeled 1 , outliers are labeled -1
normal = tr_data[pred == 1]
abnormal = tr_data[pred == -1]
 
plt.plot(normal[:, 0], normal[:, 1], 'bx)
plt.plot(abnormal[:, 0], abnormal[:, 1], 'ro' )

  因為上面的代碼沒有數據,我這里在網上找了一張圖,可以基本說明問題,如下:

 

OneClassSVM 代碼親身實踐

  因為隱私原因,這里不貼出來數據,數據是475個正常的圖和25個異常的圖片,然后將圖片進行轉化,並進行灰度化處理,將其以矩陣的形式取出,因為OneClassSVM不需要標簽,這里直接進行訓練,然后預測原數據,得到了475個正常的結果和25個異常的結果,所以這里初步認為,OneClassSVM分類還是比較好的。

代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import numpy as  np
import os
import cv2
from  PIL import Image
import pandas as  pd
import matplotlib.pyplot as  plt
 
A = []
AA = []
 
def get_files(file_dir):
     for  file in  os.listdir(file_dir + '/AA475_B25' ):
        A.append(file_dir + '/AA475_B25/'  + file)
     length_A = len(os.listdir(file_dir + '/AA475_B25' ))
     for  file in  range(length_A):
         img = Image.open(A[file])
         new_img = img.resize((128, 128))
         new_img = new_img.convert( "L" )
         matrix_img = np.asarray(new_img)
         AA.append(matrix_img.flatten())
     images1 = np.matrix(AA)
     return  images1
 
def OneClassSVM_train(data1):
     from  sklearn import svm
 
     trainSet = data1
     # nu 是異常點比例,默認是0.5
     clf = svm.OneClassSVM(nu=0.05, kernel= 'linear' , gamma=0.1)
     clf.fit(trainSet)
     y_pred_train = clf.predict(trainSet)
     normal = trainSet[y_pred_train == 1]
     abnormal = trainSet[y_pred_train == -1]
     print(normal)
     print(abnormal)
     print(normal.shape)
     print(abnormal.shape)
     plt.plot(normal[:, 0], normal[:, 1], 'bx' )
     plt.plot(abnormal[:, 0], abnormal[:, 1], 'ro' )
     plt.show()
 
 
if  __name__ == '__main__' :
     train_dir = '../one_variable/train'
     data = get_files(train_dir)
     OneClassSVM_train(data)

  結果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[[ 86  86  84 ...  50  51  48]
  [ 83  83  83 ... 143  59  59]
  [ 80  83  85 ...  45  47  45]
  ...
  [ 78  78  80 ...  56  59  54]
  [ 78  80  80 ... 191 191 178]
  [ 76  78  78 ...  56  54  54]]
[[ 76  78  78 ...  62  62  64]
  [ 82  82  82 ...  82  82  82]
  [ 78  78  78 ...  51  51  51]
  ...
  [ 51  51  51 ...  31  31  31]
  [ 54  54  55 ...  29  29  29]
  [ 59  59  59 ... 185 185 185]]
 
(475, 16384)
(25, 16384)

  最后,畫出來展示的圖如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM