Python機器學習筆記——One Class SVM

戰爭熱誠發表於2019-05-11

前言

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

  所幸瞭解到一些單樣本檢測的演算法,比如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的平均值,就是我們檢測函式的最終結果。

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

 

One-Class SVM 演算法簡介

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

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

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

 

名詞解釋

novelty detection

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

outlier  dection

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

 

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 主要引數和方法

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

array([[ 2.21240237  2.05677141],
 [ 2.2027219   1.58136665],
 [ 1.99770721  1.93131038],
                  ......
 [ 1.73608122  2.0932801 ]]
)

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

  驗證資料集 :X_test——2*2

array([[ 2.13162953  2.58399607],
 [ 2.15900993  1.96661511],
 [ 2.20516139  2.01599965],
                   ......
 [ 2.14009567  1.98644128]])

  離群點: X_outliers——2*2

array([[1.93385943 3.75727667],
 [0.75210085 1.14235702],
               ......
 [3.76820269 2.36662343])

  預測的結果: y_pred_train:

[ 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實現程式碼如下:

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。

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分類還是比較好的。

程式碼如下:

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)

  結果如下:

[[ 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)

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

 

參考文獻:

https://blog.csdn.net/bbbeoy/article/details/79159652

https://blog.csdn.net/u013719780/article/details/53219997

https://www.cnblogs.com/damumu/p/7320334.html

 

(這篇筆記,取之網路,還之網路)

相關文章