在浏览器中进行深度学习:TensorFlow.js (十二)异常检测算法
2019獨角獸企業重金招聘Python工程師標準>>>
異常檢測是機器學習領域常見的應用場景,例如金融領域里的信用卡欺詐,企業安全領域里的非法入侵,IT運維里預測設備的維護時間點等。我們今天就來看看異常檢測的基本概念,算法,然后看看如何利用TensorflowJS來進行異常檢測。
什么是異常點?
異常點是指數據中和其它點不一樣的點,異常檢測就是要找到這些點。通常有以下這些不同類型的異常:
- 點異常 Point Anomalies
單個點和其它數據顯著的不同 - 上下文異常 Contextual Anomalies
數據在所在的上下文環境中是個異常,例如下圖t1不是異常而t2是因為t2前后的數據和t2有顯著的差異。 - 集合異常 Collective Anomalies.
集合異常是指一組數據點和其它的數據有顯著的不同,這一組數據的集合構成異常
從數據維度的角度來看,異常也分為單變量(univariate)和多變量異常(multivariate)。
異常檢測的算法主要包括基于統計的算法和基于機器學習的算法。
異常檢測的統計學方法
利用統計方法來進行異常檢測有兩種,第一種是參數化的,就是假定正常的數據是基于某種參數分布的,那么我們可以通過訓練數據估計出數據的分布概率,那么對于每一個要分析的數據點都計算出該數據點在這個概率分布下生成的概率。這個值越高,說明該數據是正常點的可能性就越大,該數值越低,就說明這個點就越有可能是異常點。
最常見的方式就是ZScore,假定數據符合正態分布,ZScore計算數據點偏離均值多少個標準差。ZScore越大說明數據偏離均值越遠,那么它是異常的概率就越高。
非參數化的方法并不假定數據的先驗分布,數據的分布是從訓練數據中學習而來的。
其它還有一些統計方法諸如:
- 時間序列中的移動平均值
- 卡曼濾波器
利用統計方法做異常檢測非常容易理解,計算效率也很好。但是這種方法存在一些挑戰:
- 數據點中的噪聲和異常可能擁有類似的統計特征,那么就很難檢測出來。
- 異常的定義可能會發生變化,一個固定的伐值可能并不適用。例如應用zscore,到底是大于3是異常還是大于4是異常,這很難定義。
異常檢測的機器學習方法
從監督學習和非監督學習的角度來看,如果已經有了標記異常點的大量訓練數據,異常檢測可以簡單的轉化為分類問題,也就是數據分兩類,正常點和異常點。但是在現實中,往往很難找到大量標記好異常點的訓練數據,所以往往需要非監督學習來進行異常檢測。
利用數據的相似度來檢測異常的基本假設是,如果被檢測的數據和已有的數據相似度大,那么它是正常數據的可能性就大。相似度的學習主要有基于距離的(KNN)和基于密度的(LOF)。
基于聚類的異常檢測的基本假設是,正常數據聚集在一起,異常數據聚集在一起。
DBSCAN是異常檢測常用的聚類方法。關于DBSCAN算法的介紹,大家可以參考我的博客圖解機器學習
如上圖所示,DBSCAN可以學習出正常聚類的中心點A,邊緣點BC以及異常點N。
但是DBSCAN對于各個超參數的設定非常敏感,利用該方法雖然不需要標記異常點,但是找到合適的超參數并不容易。
支持向量機(SVM)是一種監督學習的分類方法,單類支持向量機(OneClassSVM)是SVM的一種擴展,可以用于非監督的檢測異常。
該算法可以學習出正常點和異常點之間的邊界。
隔離森林(isolation forests)是檢測數據中異常值或新穎性的一種有效方法。這是一種基于二元決策樹的方法。?
隔離森林的基本原則是異常值很少,而且與其他觀測結果相差甚遠。為了構建樹(訓練),算法從特征空間中隨機選取一個特征,并在最大值和最小值之間隨機選擇一個隨機分割值。這是針對訓練集中的所有觀察結果。為了建造森林,樹木整體被平均化為森林中的所有樹木。
然后,為了預測,它將觀察與“節點”中的分裂值進行比較,該節點將具有兩個節點子節點,在該子節點上將進行另一次隨機比較。由算法為實例做出的“分裂”的數量被命名為:“路徑長度”。正如預期的那樣,異常值的路徑長度將比其他觀察值更短。
?
利用深度學習進行異常檢測
好了我們了解了異常檢測的基本概念和方法,那么如何利用深度學習來進行異常檢測呢?
雖然神經網絡的主要應用是監督學習,但是其實也可以利用它來進行非監督學習,這里我們就需要了解自編碼器(Autoencoder)了。
自編碼器就是類似上圖的一個網絡,包含編碼和解碼兩個主要的部分,我們利用訓練數據集對該網絡進行訓練,輸出的目標等于輸入的數據。也就是說我們訓練了一個可以重建輸入數據的深度神經網絡。那么這樣做有什么用能。
我們可以看出編碼的過程其實類似一個PCA的降維過程,就是經過編碼,找到數據中的主要成分,利用該主要成份能夠重建原始數據,就好像數據壓縮和解壓縮的過程,用更少的數據來取代原始數據。對于一般的自編碼器的應用,訓練好的自編碼器不會全部用于構建網絡,一般是使用編碼的部分來進行數據的特征提取,降維,以達到更有效的計算。
利用自編碼器,我們假定正常數據通過自編碼器應該會還原,也就是輸入和輸出是一樣的,而對于異常數據,還原出來的數據和原始數據存在差異。基本假設就是還原出來的數據和輸入數據差異越小,那么它是正常數據的可能性就越大,反之它是異常數據的可能性就越大。
下面我們就來看一個利用自編碼器用tensorflowJS來檢測信用卡欺詐數據的例子。數據集來自Kaggle,考慮到TensorflowJS在瀏覽器中的性能問題,我對原始數據取樣10000條記錄來演示。
加載數據
該數據經過kaggle處理,包含Time交易時間,Amount交易數額,V1-V28是經過處理后的特征,Class表示交易的類別,1為欺詐交易。
async function loadData(path) {return await d3.csv(path); }const dataset = await loadData("https://cdn.jsdelivr.net/gh/gangtao/datasets@master/csv/creditcard_sample_raw.csv" );數據預處理
function standarize(val, min, max) {return (val - min) / (max - min); }function prepare(dataset) {const processedDataset = dataset.map(item => {const obj = {};for (let i = 1; i < 29; i++) {const key = `V${i}`;obj[key] = parseFloat(item[key]);}obj["Class"] = item["Class"];obj["Time"] = parseFloat(item["Time"]);obj["Amount"] = parseFloat(item["Amount"]);return obj;});const timeMax = d3.max(processedDataset.map(i => i.Time));const timeMin = d3.min(processedDataset.map(i => i.Time));const amountMax = d3.max(processedDataset.map(i => i.Amount));const amountMin = d3.min(processedDataset.map(i => i.Amount));processedDataset.forEach(item => {item.stdTime = standarize(item.Time, timeMax, timeMin);item.stdAmount = standarize(item.Amount, amountMax, amountMin);});return processedDataset; }const preparedDataset = prepare(dataset);在數據預處理階段我們對Time和Amount做標準化處理使它的值在(0-1)之間。
生成訓練數據集
function makeTrainData(dataset) {console.log(dataset.length);const normalData = dataset.filter(item => item.Class == "0");const anomalData = dataset.filter(item => item.Class == "1");const sliceIndex = normalData.length*0.8;const normalTrainData = normalData.slice(0,sliceIndex);const normalTestData = normalData.slice(sliceIndex+1, normalData.length);console.log(normalData.length);const trainData = { x: [], y: [] };normalTrainData.forEach(item => {const row = [];for (let i = 1; i < 29; i++) {const key = `V${i}`;row.push(item[key]);}row.push(item["stdAmount"]);row.push(item["stdTime"]);trainData.x.push(row);trainData.y.push(row);});const testData = normalTestData.map(item => {const row = [];for (let i = 1; i < 29; i++) {const key = `V${i}`;row.push(item[key]);}row.push(item["stdAmount"]);row.push(item["stdTime"]);return row;});const testAnomalData = anomalData.map(item => {const row = [];for (let i = 1; i < 29; i++) {const key = `V${i}`;row.push(item[key]);}row.push(item["stdAmount"]);row.push(item["stdTime"]);return row;});return [trainData, testData, testAnomalData]; }const [trainData, testData, testAnomalData] = makeTrainData(preparedDataset);我們選擇80%的正常數據做訓練,另外20%的正常交易數據和所有的異常交易數據做測試。
構建模型和訓練
function buildModel() {const model = tf.sequential();//encoder Layerconst encoder = tf.layers.dense({inputShape: [INPUT_NUM],units: FEATURE_NUM,activation: "tanh"});model.add(encoder);const encoder_hidden = tf.layers.dense({inputShape: [FEATURE_NUM],units: HIDDEN_NUM,activation: "relu"});model.add(encoder_hidden);//decoder Layerconst decoder_hidden = tf.layers.dense({units: HIDDEN_NUM,activation: "tanh"});model.add(decoder_hidden);//decoder Layerconst decoder = tf.layers.dense({units: INPUT_NUM,activation: "relu"});model.add(decoder);//compileconst adam = tf.train.adam(0.005);model.compile({optimizer: adam,loss: tf.losses.meanSquaredError});return model; }async function watchTraining() {const metrics = ["loss", "val_loss", "acc", "val_acc"];const container = {name: "show.fitCallbacks",tab: "Training",styles: {height: "1000px"}};const callbacks = tfvis.show.fitCallbacks(container, metrics);return train(model, data, callbacks); }async function trainBatch(data, model) {const metrics = ["loss", "val_loss", "acc", "val_acc"];const container = {name: "show.fitCallbacks",tab: "Training",styles: {height: "1000px"}};const callbacks = tfvis.show.fitCallbacks(container, metrics);console.log("training start!");tfvis.visor();// Save the model// const saveResults = await model.save('downloads://creditcard-model');const epochs = config.epochs;const results = [];const xs = tf.tensor2d(data.x);const ys = tf.tensor2d(data.y);const history = await model.fit(xs, ys, {batchSize: config.batchSize,epochs: config.epochs,validationSplit: 0.2,callbacks: callbacks});console.log("training complete!");return history; }const model = buildModel(); model.summary();const history = await trainBatch(trainData, model);我們的自編碼器的模型如下:
_________________________________________________________________ Layer (type) Output shape Param # ================================================================= dense_Dense1 (Dense) [null,16] 496 _________________________________________________________________ dense_Dense2 (Dense) [null,8] 136 _________________________________________________________________ dense_Dense3 (Dense) [null,8] 72 _________________________________________________________________ dense_Dense4 (Dense) [null,30] 270 ================================================================= Total params: 974 Trainable params: 974 Non-trainable params: 0前兩層是編碼,后兩層是解碼。
分析異常值
自編碼器模型訓練好了以后我們就可以用它來分析異常,我們對測試數據的正常交易記錄和異常交易記錄用該模型預測,理論上正常交易的輸出更接近原始值,而異常交易記錄應該偏離原始值比較多,我們利用歐式距離來分析自編碼器的輸出結果。
async function distance(a, b ){const axis = 1;const result = tf.pow(tf.sum(tf.pow(a.sub(b), 2), axis), 0.5);return result.data(); }async function predict(model, input) {const prediction = await model.predict(tf.tensor(input));return prediction; }const predictNormal = await predict(model, testData); const predictAnomal = await predict(model, testAnomalData);const distanceNormal = await distance(tf.tensor(testData), predictNormal); const distanceAnomal = await distance(tf.tensor(testAnomalData), predictAnomal);const resultData = []; distanceNormal.forEach(item => {const obj = {};obj.type = "normal";obj.value = item;obj.index = Math.random();resultData.push(obj); }) distanceAnomal.forEach(item => {const obj = {};obj.type = "outlier";obj.value = item;obj.index = Math.random();resultData.push(obj); })測試結果如下圖:
上圖綠色是異常交易,藍色是正常交易。因為正常交易的數量較多,我們可能看不太清楚,我們分別顯示如下圖:
我們看到異常交易的自編碼器輸出和原始結果的距離都是大于10的,而絕大部分正常交易集中在10以下的區域,如果我們以10為伐值,應該可以找到大部分的異常交易,當然會有大量的正常交易誤報。也就是該模型是無法做到完全的分辨正常和異常交易的。
完整的代碼見我的Codepen
總結
本文介紹了各種異常檢測的主要方法,無論是統計方法,機器學習的方法還是深度學習的方法,其中主要問題都是對于伐值或者參數的設置。
對于統計方法,需要確定究竟生成概率多少的事件是異常是百年一遇的洪水是異常,還是千年一遇的洪水是異常?
對于各種監督學習,我們往往缺乏異常點的標記,而對于非監督學習,調整各種參數會對異常點的判斷有很大的影響。
對于基于自編碼器的方法而言,我們看到,我們利用利用自編碼器的輸出和輸入的差異來判斷該事件是否為異常事件,然而究竟偏離多少來定義為異常,仍然需要用戶來指定。
我們希望的完全通過數據和算法來自動發現異常仍然是一個比較困難的問題。
參考
- Introduction to Anomaly Detection
- A Brief Overview of Outlier Detection Techniques
- A Density-based algorithm for outlier detection
- Introduction to Anomaly Detection: Concepts and Techniques
- Anomaly detection using deep learning to measure quality of Large Datasets
- Comparing anomaly detection algorithms for outlier detection on toy datasets
- 莫煩 Python 自編碼 (Autoencoder)
- Credit Card Fraud Detection Dataset
-
在瀏覽器中進行深度學習:TensorFlow.js (十一)時間序列預測
-
在瀏覽器中進行深度學習:TensorFlow.js (十)構建一個推薦系統?
- 在瀏覽器中進行深度學習:TensorFlow.js (九)訓練詞向量 Word Embedding
-
在瀏覽器中進行深度學習:TensorFlow.js (八)生成對抗網絡 (GAN)
- 在瀏覽器中進行深度學習:TensorFlow.js (七)遞歸神經網絡 (RNN)
-
在瀏覽器中進行深度學習:TensorFlow.js (六)構建一個卷積網絡 Convolutional Network
-
在瀏覽器中進行深度學習:TensorFlow.js (五)構建一個神經網絡
-
在瀏覽器中進行深度學習:TensorFlow.js (四)用基本模型對MNIST數據進行識別
-
在瀏覽器中進行深度學習:TensorFlow.js (三)更多的基本模型
-
在瀏覽器中進行深度學習:TensorFlow.js (二)第一個模型,線性回歸
-
在瀏覽器中進行深度學習:TensorFlow.js (一)基本概念
轉載于:https://my.oschina.net/taogang/blog/3055585
總結
以上是生活随笔為你收集整理的在浏览器中进行深度学习:TensorFlow.js (十二)异常检测算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: s7edge升级8.0后怎么降级
- 下一篇: CIKERS Shane 2019053