码迷,mamicode.com
首页 > 其他好文 > 详细

基于密度的局部异常值检测

时间:2015-10-22 19:28:12      阅读:528      评论:0      收藏:0      [点我收藏+]

标签:

今天介绍基于密度的异常点检测,我们以LOF算法为例。先看下图

技术分享

技术分享

 技术分享

 

C1和C2为两个cluster,cluster里面的密度都很高,只是不同cluster里点的距离不太一样。b相对于C1为一个局部( local )的outlier。a为一个整体( global )的outlier。所以这里的outlier检测,并非是基于distance或者基于近邻的。而是比较点和点的密度的差距如果某个点和周围点比起来密度明显小,则该点很可能是outlier!

 

问题是如何定义密度?为了引出密度的概念。这里我们来介绍 reachability distance这个距离的定义是这样的:

技术分享


这个距离有一个方向性A-->B。它取的是B的K近邻的距离以及A和B距离之间的最大值。所以reachability distance( A, B ), reachability distance( B, A ) 很可能不相等。因为A和B的k近邻距离很可能不一样. 这里又有一个k-distance。它就是每个点的k近邻点的最大值。我们先来解决这个函数:

import numpy as np
import scipy.spatial.distance as ssd
from sklearn.neighbors import NearestNeighbors

    
def kDistances( X, k = 5 ):
    n = X.shape[0]
    k = min( k, n - 1 )
    nbhrInfos = range( n )

    kNN = NearestNeighbors( metric = ssd.euclidean  ).fit( X )
    kDists = np.zeros( n, dtype = np.float )
    
    for i in range(n):
        # Max k nearest neighbor distance
        dist, _ = kNN.kneighbors( X[ i ], n_neighbors = k + 1 )
        kDists[ i ] = dist[ 0 ][ -1 ]  
        
        # The set of k nearest neighbors as Nk(A) and distances.
        dists, neighbors = kNN.radius_neighbors( X[ i ], radius = kDists[ i ] )
        mask = neighbors[ 0 ] != i # exclude self from k nearest neighbor
            
        nbhrInfos[ i ] = [ neighbors[ 0 ][ mask ], dists[ 0 ][ mask ] ]

    return kDists, nbhrInfos

这里,我们不仅仅计算了每个点的K-Distances距离,我们还计算了每个点在其K-Distances半径内的点以及其距离( 去除了每个点自身)。这就会后续计算reachability Distance做好了准备。接着就是计算reachability Distance的代码:

def reachabilityDistance( A, B, kDistB, dist = None ):
    if dist is None:
        dist = ssd.euclidean( A, B )
    
    return max( kDistB, dist )

 

我们用图片来解释下reachability Distance的意义:

技术分享

对于点A来说,它的3个近邻的k-Distance就是点A到点B的距离。也就是以A为圆心,AB为半径的一个圆。那D到A的reachability Distance( D, A )是多少呢。注意这里说得是D到A,有方向性!因为AD之间的距离,大于A的k-distance( k = 3 )。所以reachability Distance( D, A ) = AD之间的距离。

 

由这个定义我们再来看一张图:

技术分享

A点到它的3个近邻的reachability Distance用红色标出。而大弧线虚线是A的k-Distance。小的弧线虚线是A近邻的k-distance的距离。这里很明显,如果一个点是outlier,那么它到近邻的reachability Distance会远远超出其近邻到各自近邻的reachability Distance。这里是有点绕的。不过reachability Distance是一个很重要的度量标志我们是明确了。我们实现这种度量后,就会明白LOF会怎么使用它。作者用了每个点到其所有Nk-distance近邻的 reachability Distance总和。然后取倒数并乘以Nk-distance近邻的数量。作者称之为 local reachability density它的意义是单位可达距离里包含的近邻数。包含的越多,说明密度越高。公式如下:

技术分享

代码如下,这里我们的函数是针对一个样本点来定义的。这样更加接近文献中的定义。

def lrd( i, X, kDists, nbhrInfos ):
    A = X[ i ]
    kNeighborsOfA, kNNDistsOfA = nbhrInfos[ i ]
    sumOfReachabilityDistances = 0.
    
    # A and its NA neighbors reachbility distances 
    for j in range( len( kNeighborsOfA ) ):
        B = X[ kNeighborsOfA[ j ] ]        
        kDistB = kDists[ kNeighborsOfA[ j ] ]
        distAB = kNNDistsOfA[ j ]
        
        reachDistance = reachabilityDistance( A, B, kDistB, distAB  )
        sumOfReachabilityDistances += reachDistance
    
    # A‘s density     
    lrd = len( kNeighborsOfA ) / sumOfReachabilityDistances
    return lrd

这里利用前面kDistances获取的近邻信息可以计算出每个点的lrd。一般离群点的lrd较其近邻的lrd明显会小。所以利用lrd,作者构造出一个Outlier的离群度分数LOF。公式如下:

技术分享

公式体现了作者的意图。作者就是用A点近邻的平均lrd值去和lrdA作比。因为如果A是outlier的话,其lrd会很小,导致分数会明显的高!而作者也证明了一般密集的点,其LOF得分会往1收敛。得分越小密度越高。当然这里的LOF分数的设置也是经验性的,而这也是LOF的缺陷。分数不是那么统一和容易确认。下面是代码:

def calLOF( i, X, kDists, nbhrInfos, lrdCache = None ):    
    A = X[i]
    kNeighborsOfA, _  = nbhrInfos[ i ]
    
    if lrdCache is None :
        lrdCache = {}
        
    if i not in lrdCache:
        lrdCache[ i ] = lrd( i, X, kDists, nbhrInfos )
         
    lrdA = lrdCache[ i ]

    # A‘s neighbors densities
    sumOfNeighborsLrd = 0.
    for j in kNeighborsOfA:
        # print i, j
        if j not in lrdCache:
            lrdCache[ j ] =  lrd( j, X, kDists, nbhrInfos )
            
        lrdB = lrdCache[ j ]
        
        sumOfNeighborsLrd += lrdB

    # A‘s neighbors average density and A‘s density ---> LOF
    LOF =  sumOfNeighborsLrd / ( len( kNeighborsOfA ) *  lrdA )
    return LOF

最后我们把代码整合到一个函数里去,这个函数需要指定2个参数: minPts就是LOF去看的近邻数, lofSpec是LOF得分超过多少算是outlier。

def detectOutliers( X, minPts = 5, lofSpec = 3 ):
    n = X.shape[ 0 ]       
    kDists, nbhrInfos = kDistances( X, minPts )
    
    lrdCache = {}
    lofs = np.array( [ calLOF( i, X, kDists, nbhrInfos, lrdCache ) for i in xrange( n ) ] )
    labels = np.array( lofs >= lofSpec, dtype = np.int )
    
    return labels, lofs

 

我们看下测试的结果:

技术分享

这三个点的LOF分数分别是4.09888044, 11.33202877, 15.17396308.  

 

基于密度的局部异常值检测

标签:

原文地址:http://www.cnblogs.com/zhuyubei/p/4898388.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!