# LODA: 轻量级在线异常检测器 (Lightweight On-line Detector of Anomalies)

## 算法简介

LODA 是一种高效、轻量级的无监督异常检测算法。它属于集成方法，其核心思想是：**通过大量随机的、低维的投影，一个异常点在多数投影上的投影值会落入密度极低的区域。**

与依赖复杂模型的方法不同，LODA 使用了大量的、非常简单的“弱检测器”的集合。每个弱检测器就是一个在一维随机投影上的直方图。通过汇集所有这些弱检测器的判断，LODA 能够快速且准确地识别异常。

该算法的主要步骤如下：
1.  **生成随机投影**: 创建大量稀疏的随机投影向量。
2.  **构建直方图模型**: 将高维数据投影到每个一维向量上，并为每个投影后的一维数据构建一个直方图模型。这个直方图近似地表示了数据在该投影方向上的概率分布。
3.  **计算异常分数**: 对于一个数据点，计算其在所有投影上的负对数概率的平均值。如果一个点在多个投影上都频繁落入低概率（直方图的低矮区域）的区间，那么它将被判定为异常。

---

## 核心思想与图解

算法的精髓在于“从多个简单的角度观察，异常总会显得格格不入”。高维空间中的异常点，在被随机投影到一维直线后，很大概率会落在数据主体的“外部”或“稀疏”区域。

我们可以用一个简单的二维图来解释这个“为什么”。

```
      ^ Y 轴
      |
      |             P_outlier (异常点)
      |
 (数据主体) |
* * * * |
* *P_inlier*
* * * * * |
      +--------------------------------------> X 轴
      . . . . . . . . . . . . . . . . . . .
      |         投影向量 w1 (Projection Vector w1)
      |        /
      |       /

[--------------|----|--|---------|--|-------------]  直方图 1 (Histogram 1)
              ^ P_inlier 投影点 (高密度区)
                               ^ P_outlier 投影点 (低密度区)

----------------------------------------------------
      \         投影向量 w2 (Projection Vector w2)
       \
        \

[-----|---|----|-------------|---|---|----------]  直方图 2 (Histogram 2)
              ^ P_inlier 投影点 (高密度区)
     ^ P_outlier 投影点 (低密度区)

```
在这个图中，LODA 做了两件事：

#### 1. 随机投影与建模
- **投影**: 算法随机生成了两个投影向量 `w1` 和 `w2`。所有数据点（包括正常点 `P_inlier` 和异常点 `P_outlier`）都被分别投影到这两条直线上。
- **建模**: 在每个投影后的一维数据上建立一个直方图。直方图的“柱子”高度代表了该区域的数据密度（或概率）。高柱子表示正常数据聚集区，矮柱子表示稀疏区。

#### 2. 计算异常分数
- **对于正常点 `P_inlier`**: 我们可以看到，无论是在 `w1` 还是 `w2` 投影上，`P_inlier` 的投影点都落在了直方图的高柱子区域。这意味着它在这些视角下都表现得很“正常”，因此它的 `-log(概率)` 值会很小。
- **对于异常点 `P_outlier`**: 我们可以看到，`P_outlier` 在两个投影上都落在了直方图的矮柱子区域（低密度区）。这意味着从多个随机角度看，它都显得很“孤立”。因此，它的 `-log(概率)` 值会很高，累加起来的平均异常分数也就会很高。

通过对成百上千个这样的随机投影的结果进行平均，算法能够非常稳健地识别出那些在**绝大多数视角**下都表现异常的点。

---

## 算法步骤详解

1.  **初始化 (Initialization)**
    - 创建 `k` (即 `n_random_cuts`) 个稀疏的随机投影向量 $w_1, w_2, ..., w_k$。每个向量的维度与原始数据相同。

2.  **构建模型 (Model Building)**
    - 对于每一个投影向量 $w_i$（从 $i=1$ 到 $k$）：
        - 将所有训练数据 $X$ 投影到该向量上，得到一维数据集 $Z_i = X \cdot w_i$。
        - 在一维数据集 $Z_i$ 上构建一个直方图 $H_i$。这个直方图 $H_i$ 就是第 $i$ 个弱检测器。

3.  **计算异常分数 (Anomaly Score Calculation)**
    - 对于一个新的数据点 $x$：
        - 初始化总异常分数为 0。
        - 对于每一个弱检测器 $H_i$（从 $i=1$ 到 $k$）：
            - 将 $x$ 投影到对应的向量 $w_i$ 上，得到投影值 $z = x \cdot w_i$。
            - 在直方图 $H_i$ 中查找 $z$ 所在的区间的概率（即归一化的高度）$p_i(z)$。
            - 计算该弱检测器给出的分数：$-log(p_i(z))$。
            - 将此分数累加到总分上。
        - 最终的异常分数是所有弱检测器分数的平均值。

---

## 主要参数解析

- `n_random_cuts`: 随机投影的数量，也即集成中弱检测器（直方图）的数量。值越大，模型越稳定，但计算成本也越高。默认值为 100。
- `n_bins`: 每个直方图的箱数（bins）。这个值决定了密度估计的粒度。可以是一个固定的整数，也可以设置为 `"auto"`，此时算法会使用 Birge-Rozenblac 方法为每个投影自动确定最优的箱数。
- `contamination`: 数据集中异常点的比例估计值。这个参数用来决定划分正常/异常的阈值。

---

## 总结

LODA 算法的优势在于其极致的速度和较低的内存消耗。由于其核心操作仅为向量点积和构建一维直方图，使得它在处理大规模、高维度数据集时表现出色。它不需要计算点与点之间的距离，因此对特征缩放不敏感。作为一个强大的集成模型，它在多种异常检测任务中都具有很强的竞争力。

---

## Python 代码示例

下面的代码使用 `pyimgano` 内置的 `core_loda` 来演示 LODA 算法。该实现是轻量级的
随机投影 + 一维直方图密度集成（不依赖任何第三方异常检测库）。

```python
import numpy as np
import matplotlib.pyplot as plt
from pyimgano.models import create_model

# -- 1. 生成样本数据 --
# 生成两组正常的二维高斯分布数据（代表两个大簇）
X_inliers1 = 0.3 * np.random.randn(100, 2)
X_inliers2 = 0.3 * np.random.randn(100, 2) + np.array([5, 5])
X_inliers = np.r_[X_inliers1, X_inliers2]

# 生成一组异常数据
X_outliers = np.random.uniform(low=-4, high=4, size=(20, 2))

# 组合数据
X = np.r_[X_inliers, X_outliers]

# -- 2. 初始化并拟合 LODA 模型 --
# 假设数据集中有大约10%的异常点
contamination_rate = 0.1

# 初始化LODA检测器
# 使用100个随机投影和20个bins
detector = create_model(
    "core_loda",
    n_random_cuts=100,
    n_bins=20,
    contamination=contamination_rate,
)
detector.fit(X)

# -- 3. 获取预测结果 --
# y_pred 是二元标签 (0: 正常, 1: 异常)
# 注意：labels_ 是训练集上的阈值划分结果；对新数据请使用 predict(X_new)
y_pred = detector.labels_
# decision_scores_ 是原始异常分数
scores = detector.decision_scores_
# 阈值
threshold = detector.threshold_

# -- 4. 可视化结果 --
plt.figure(figsize=(10, 8))
plt.title("LODA Anomaly Detection")

# 绘制正常点 (蓝色)
plt.scatter(X[y_pred == 0, 0], X[y_pred == 0, 1], c='blue', label='Inliers')

# 绘制异常点 (红色)
plt.scatter(X[y_pred == 1, 0], X[y_pred == 1, 1], c='red', label='Outliers')

plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.legend()
plt.grid(True)
plt.show()

# 打印一些信息
print(f"在 {len(X)} 个点中检测到 {np.sum(y_pred)} 个异常点。")
print(f"用于区分正常/异常点的阈值为: {threshold:.4f}")

```
