LOF局部离群点算法原理及scala实现
 (2020-06-13 11:03:21)
	
			
					(2020-06-13 11:03:21)		| 标签: lof杂谈 | 分类: 大数据处理 | 
			本文参考:
     
 在数据挖掘方面,经常需要在做特征工程和模型训练之前对数据进行清洗,剔除无效数据和异常数据。异常检测也是数据挖掘的一个方向,用于反作弊、伪基站、金融诈骗等领域。    

    
这次要介绍一下一种基于距离的异常检测算法,局部异常因子LOF算法(Local Outlier Factor)。  
   
 LOF算法的相关定义:   

  var
nodeName = inputDataName // 样本点名称 
  var
dataVector = inputDataVector // 样本点的数据向量 
  var
kDistance = 0.0 // k-距离 
  var
kNeighbor = new ArrayList[LOFDataNode]() // k-领域 
  var
distance = 0.0 // 到给定点的欧几里得距离 
  var
reachDensity = 0.0 // 可达密度 
  var
reachDistance = 0.0 // 可达距离 
  var lof =
0.0 // 局部离群因子 
  def
getNodeName(): String = { 
 
  this.nodeName  
 
} 
  def
getDataVector(): ArrayList[Int] = { 
 
  this.dataVector  
 
} 
  def
setKDistance(kDistance: Double) = { 
 
  this.kDistance = kDistance  
 
} 
  def
getKDistance(): Double = { 
 
  this.kDistance  
 
} 
  def
setKNeighbor(kNeighbor: ArrayList[LOFDataNode])= { 
 
  this.kNeighbor = kNeighbor  
 
} 
  def
getKNeightbor(): ArrayList[LOFDataNode] = { 
 
  this.kNeighbor  
 
} 
  def
setDistance(distance: Double) = { 
 
  this.distance = distance  
 
} 
  def
getDistance(): Double = { 
 
  this.distance  
 
} 
  def
setReachDensity(reachDensity: Double) = { 
 
  this.reachDensity =
reachDensity  
 
} 
  def
getReachDensity(): Double = { 
 
  this.reachDensity  
 
} 
  def
setReachDistance(reachDistance: Double) = { 
 
  this.reachDistance =
reachDistance  
 
} 
  def
getReachDistance(): Double = { 
 
  this.reachDistance  
 
} 
  def
setLof(lof: Double) = { 
 
  this.lof = lof  
 
} 
  def
getLof(): Double = { 
 
  this.lof  
 
} 
   //
获取离群点功能的主逻辑
 
 // 注意1:输入的值DataVector值不能重复  
 
 // 1.找到给定点与其他点的欧几里得距离  
 
 //
2.对欧几里得距离进行排序,找到前k位的点,并同时记下k距离  
 
 // 3.计算每个点的可达密度  
 
 // 4.计算每个点的局部离群点因子  
 
 // 5.对每个点的局部离群点因子进行排序,输出  
  def
getOutlierNode(allNodes: ArrayList[LOFDataNode], k: Int):
ArrayList[LOFDataNode] = { 
 
  val kdAndKnList =
getKDistanceAndKNeighbor(allNodes, k)   
 
  calcReachDistance(kdAndKnList)  
 
  calcReachDensity(kdAndKnList,
k)  
 
  calcLof(kdAndKnList, k)  
 
  // 降序排列  
 
  val lofComparator = new
Ordering[LOFDataNode]{  
 
    override def compare(x:
LOFDataNode, y: LOFDataNode): Int = {   
 
      val
distDiff = x.getLof() - y.getLof()    
 
     
if(distDiff < 0){    
 
     
  1     
 
      }else
if(distDiff > 0){    
 
     
  -1     
 
     
}else{    
 
     
  0     
 
     
}    
 
    }   
 
  }  
 
  kdAndKnList.sort(lofComparator)  
 
  kdAndKnList  
 
} 
 
//  计算每个点的局部离群点因子
  def
calcLof(kdAndKnList: ArrayList[LOFDataNode], k: Int) =
{ 
 
  for(i <- 0 until
kdAndKnList.size){  
 
    val tempNodes =
kdAndKnList.get(i).getKNeightbor()   
 
    var sum =
0.0   
 
    for(j <- 0 until
tempNodes.size){   
 
      val rd =
getReachDensity(tempNodes.get(j).getNodeName(),
kdAndKnList)    
 
      sum +=
DecimalCalculator.div(rd, kdAndKnList.get(i).getReachDensity(),
10)    
 
    }   
 
    sum =
DecimalCalculator.div(sum, k, 10)   
 
   
kdAndKnList.get(i).setLof(sum)   
 
  }  
 
} 
 
//  获取某个点的可达密度
  def
getReachDensity(nodeName: String, nodeList:
ArrayList[LOFDataNode]): Double = { 
 
  var kDis = 0.0  
 
  breakable({  
 
    for(i <- 0 until
nodeList.size()){   
 
     
if(nodeName.trim() ==
nodeList.get(i).getNodeName().trim()){    
 
     
  kDis =
nodeList.get(i).getReachDensity()     
 
     
  break     
 
     
}    
 
    }   
 
  })  
 
  kDis  
 
} 
 
//    计算每个点的可达密度
  def
calcReachDensity(kdAndKnList: ArrayList[LOFDataNode], k: Int) =
{ 
 
  for(i <- 0 until
kdAndKnList.size){  
 
    val tempNodes =
kdAndKnList.get(i).getKNeightbor()   
 
    var sum =
0.0   
 
    for(j <- 0 until
tempNodes.size){   
 
      sum +=
tempNodes.get(j).getReachDistance()    
 
    }   
 
    val rd =
DecimalCalculator.div(k, sum, 10)   
 
   
kdAndKnList.get(i).setReachDensity(rd)   
 
  }  
 
} 
 
//  计算每个点的可达密度,reachdis(p,o)=max{
k-distance(o),d(p,o)}
  def
calcReachDistance(kdAndKnList: ArrayList[LOFDataNode])=
{ 
 
  for(i <- 0 until
kdAndKnList.size){  
 
    val tempNodes =
kdAndKnList.get(i).getKNeightbor()   
 
    for(j <- 0 until
tempNodes.size){   
 
      val kDis =
getKDistance(tempNodes.get(j).getNodeName(),
kdAndKnList)    
 
      if(kDis
< tempNodes.get(j).getDistance()){    
 
     
 
tempNodes.get(j).setReachDistance(tempNodes.get(j).getDistance())     
 
     
}else{    
 
     
 
tempNodes.get(j).setReachDensity(kDis)     
 
     
}    
 
    }   
 
  }  
 
} 
 
//    获取某个点的k-距离
  def
getKDistance(nodeName: String, nodeList: ArrayList[LOFDataNode]):
Double = { 
 
  var kDis = 0.0  
 
  breakable({  
 
    for(i <- 0 until
nodeList.size()){   
 
     
if(nodeName.trim() ==
nodeList.get(i).getNodeName().trim()){    
 
     
  kDis =
nodeList.get(i).getKDistance()     
 
     
  break()     
 
     
}    
 
    }   
 
  })  
 
  kDis  
 
} 
 
//  计算给定点NodeA与其他点NodeB的欧几里得距离(distance),并找到NodeA点的前5位NodeB,然后记录到NodeA的k-领域(kNeighbor)变量。同时找到NodeA的k距离,然后记录到NodeA的k-距离(kDistance)变量中。
  def
getKDistanceAndKNeighbor(allNodes: ArrayList[LOFDataNode], k: Int):
ArrayList[LOFDataNode] = {  
 
  val kdAndKnList = new
ArrayList[LOFDataNode]()  
 
  for(i <- 0 until
allNodes.size){  
 
    val tempNodeList = new
ArrayList[LOFDataNode]()   
 
    val nodeA = new
LOFDataNode(allNodes.get(i).getNodeName(),
allNodes.get(i).getDataVector())   
 
    //
找到给定点NodeA与其他NodeB的欧几里得距离,并记录在NodeB的distance变量中   
 
    for(j <- 0 until
allNodes.size){   
 
      val nodeB
= new LOFDataNode(allNodes.get(j).getNodeName(),
allNodes.get(j).getDataVector())    
 
     
if(nodeB.getNodeName().trim() !=
nodeA.getNodeName().trim()){    
 
     
  val distance = getDistance(nodeA,
nodeB)     
 
     
  nodeB.setDistance(distance)     
 
     
  tempNodeList.add(nodeB)     
 
     
}    
 
    }   
 
    //
对所有NodeB点中的欧几里得距离进行升序排序   
 
    val distComparator = new
Ordering[LOFDataNode]{   
 
      override
def compare(x: LOFDataNode, y: LOFDataNode): Int =
{    
 
     
  val distDiff = x.getDistance() -
y.getDistance()     
 
     
  if(distDiff < 0){     
 
     
    -1      
 
     
  }else if(distDiff > 0){     
 
     
    1      
 
     
  }else{     
 
     
    0      
 
     
  }     
 
     
}    
 
    }   
 
   
tempNodeList.sort(distComparator)   
 
    //
找到NodeB中前k位的欧几里得距离点,并记录到NodeA的Neighbor变量中   
 
    for(i <- 0 until
k){   
 
     
nodeA.getKNeightbor().add(tempNodeList.get(i))    
 
      if(i == k
- 1){    
 
     
 
nodeA.setDistance(tempNodeList.get(i).getDistance())     
 
     
}    
 
    }   
 
   
kdAndKnList.add(nodeA)   
 
  }  
 
  kdAndKnList  
 
} 
 
//   获取两个节点的欧几里得距离 
  def
getDistance(nodeA: LOFDataNode, nodeB: LOFDataNode): Double =
{ 
 
  var distance = 0.0  
 
  val nodeADataVector =
nodeA.getDataVector()  
 
  val nodeBDataVector =
nodeB.getDataVector()  
 
  if(nodeADataVector.size ==
nodeBDataVector.size){  
 
    for(i <- 0 until
nodeADataVector.size){   
 
      distance
+= Math.pow(nodeADataVector.get(i) - nodeBDataVector.get(i),
2)    
 
    }   
 
    distance = Math.pow(distance,
0.5)   
 
  }  
 
  distance  
 
} 
							
		
						
		
		
		
		
		
  异常检测方法,针对不同的数据形式,有不同的实现方法。常用的有基于分布的方法,在上、下α分位点之外的值认为是异常值(例如图1),对于属性值常用此类方法。基于距离的方法,适用于二维或高维坐标体系内异常点的判别,例如二维平面坐标或经纬度空间坐标下异常点识别,可用此类方法。

  用视觉直观的感受一下,如图2,对于C1集合的点,整体间距,密度,分散情况较为均匀一致,可以认为是同一簇;对于C2集合的点,同样可认为是一簇。o1、o2点相对孤立,可以认为是异常点或离散点。现在的问题是,如何实现算法的通用性,可以满足C1和C2这种密度分散情况迥异的集合的异常点识别。LOF可以实现我们的目标。
1、 d(p,o):两点p和o之间的距离 
2、k-distance:第k距离
比如下面,距离p第5远的点有2个在圆上
3、k-distance neighborhood of p: 第k距离领域
4、reach-distance:可达距离
这意味着,点o到点p的第k可达距离,至少是o的第k距离,或者为o、p间的真实距离。
5、local reachability density:局部可达密度
注意,是p的邻域点Nk(p)Nk(p)到p的可达距离,不是p到Nk(p)Nk(p)的可达距离,一定要弄清楚关系。并且,如果有重复点,那么分母的可达距离之和有可能为0,则会导致lrd变为无限大,下面还会继续提到这一点。
  这个值的含义可以这样理解,首先这代表一个密度,密度越高,我们认为越可能属于同一簇,密度越低,越可能是离群点。如果p和周围邻域点是同一簇,那么可达距离越可能为较小的dk(o)dk(o),导致可达距离之和较小,密度值较高;如果p和周围邻居点较远,那么可达距离可能都会取较大值d(p,o)d(p,o),导致密度较小,越可能是离群点
6、local outlier factor:局部离群因子

表示点p的邻域点Nk(p)Nk(p)的局部可达密度与点p的局部可达密度之比的平均数。
    如果这个比值越接近1,说明p的其邻域点密度差不多,p可能和邻域同属一簇;如果这个比值越小于1,说明p的密度高于其邻域点密度,p为密集点;如果这个比值越大于1,说明p的密度小于其邻域点密度,p越可能是异常点。
  现在概念定义已经介绍完了,现在再回过头来看一下lof的思想,主要是通过比较每个点p和其邻域点的密度来判断该点是否为异常点,如果点p的密度越低,越可能被认定是异常点。至于密度,是通过点之间的距离来计算的,点之间距离越远,密度越低,距离越近,密度越高,完全符合我们的理解。而且,因为lof对密度的是通过点的第k邻域来计算,而不是全局计算,因此得名为“局部”异常因子,这样,对于图1的两种数据集C1和C2,lof完全可以正确处理,而不会因为数据密度分散情况不同而错误的将正常点判定为异常点。
7、scala实现
// LOF的数据节点 
class
LOFDataNode(inputDataName: String, inputDataVector: ArrayList[Int])
{
}
// 
class LOFDetect
{
}
前一篇:生成泊松分布随机值实践
										后一篇:卷积线性滤波
					
 加载中…
加载中…