LOF 是一位“密度侦探”,通过比较每个数据点与其邻居的人口密度差异,精准锁定那些“人烟稀少区域”的孤独离群者,像用显微镜观察数据社会的异常个体。
通过局部密度相对值而非绝对距离判断异常,能发现隐藏在数据荒漠中的异常绿洲
import java.util.*;
public class LOFDetector {
// 计算两点欧氏距离
static double distance(double[] a, double[] b) {
double sum = 0;
for (int i=0; i<a.length; i++)
sum += Math.pow(a[i]-b[i], 2);
return Math.sqrt(sum);
}
// 获取k近邻索引
static List<Integer> getKNN(double[][] data, int targetIdx, int k) {
PriorityQueue<Map.Entry<Double, Integer>> pq = new PriorityQueue<>(
(a,b) -> Double.compare(b.getKey(), a.getKey()));
for (int i=0; i<data.length; i++) {
if (i == targetIdx) continue;
double dist = distance(data[targetIdx], data[i]);
pq.offer(new AbstractMap.SimpleEntry<>(dist, i));
if (pq.size() > k) pq.poll();
}
List<Integer> neighbors = new ArrayList<>();
while (!pq.isEmpty()) neighbors.add(pq.poll().getValue());
return neighbors;
}
// 计算局部可达密度
static double lrd(double[][] data, int idx, int k) {
List<Integer> neighbors = getKNN(data, idx, k);
double sum = 0;
for (int n : neighbors) {
double reachDist = Math.max(
distance(data[idx], data[n]),
distance(data[n], data[getKNN(data, n, k).get(0)])
);
sum += reachDist;
}
return neighbors.size() / sum;
}
// 计算LOF值
static double lof(double[][] data, int idx, int k) {
double lrdP = lrd(data, idx, k);
List<Integer> neighbors = getKNN(data, idx, k);
double sum = 0;
for (int n : neighbors)
sum += lrd(data, n, k) / lrdP;
return sum / neighbors.size();
}
public static void main(String[] args) {
double[][] data = loadSensorData(); // 加载工业传感器数据
int k = 20; // 邻居数量
for (int i=0; i<data.length; i++) {
double score = lof(data, i, k);
System.out.printf("点%d LOF=%.3f %s\n",
i, score, score > 1.5 ? "⚠异常" : "");
}
}
}
维度 | 基础实现 | 优化方案(使用KD树) |
---|---|---|
时间复杂度 | O(n²·d) | O(n·log(n)·d) |
空间复杂度 | O(n²) | O(n·d) |
n=数据点数,d=数据维度,k=近邻数
新手三步曲:
调参实战:k值选择
高手突破方向:
// 距离计算优化:提前缓存距离矩阵
double[][] distMatrix = new double[n][n];
Arrays.parallelSetAll(distMatrix, i ->
Arrays.stream(data).mapToDouble(d -> distance(data[i], d)).toArray()
);
// 并行化处理
List<Double> scores = data.parallelStream()
.mapToDouble(p -> lof(data, indexOf(p), k))
.boxed().collect(Collectors.toList());
最佳实践:当处理高维数据时(维度>20),建议先进行PCA降维再应用LOF,就像用筛子过滤掉干扰信号后再进行精密检测。k值选择建议从log(n)开始实验,并通过轮廓系数验证聚类质量!