异常检测系列:局部异常因子(LOF)

img
本地离群因子(LOF)是另一种有效的无监督学习方法,用于离群点检测。自从它在2000年代初被发明以来(Breunig等人,2000年[1]),它已被广泛应用于不同类型的问题。它是一种基于密度的技术,使用最近邻搜索来识别异常点。在本章中,我将首先解释其动机和算法,然后引导您通过在PyOD中的实践。在上一章的k-NN算法中,我展示了任何基于密度的算法对离群值都很敏感,并且很容易过拟合。解决方案是训练多个模型,然后汇总得分。通过汇总多个模型,过拟合的机会大大减少,预测准确性将得到提高。PyOD模块提供了几种方法来汇总结果:平均值,最大最大值(MOM),最大平均值(AOM)和平均最大值(MOA)。在本章中,我将仅演示平均方法。

(A)离群值可以是全局的或局部的

很容易察觉到离群值,但很难用数学定义来定义。一个与其他数据“相距较远”的数据点是一个离群值。但从什么意义上说呢?有一次我坐在一个海滩的岩石上看日落。有一群海鸥站在我旁边的岩石上,我注意到有一只灰色的海鸥独自站在另一块岩石上。从我坐的地方看,那只灰色的海鸥是一个离群值。然而,海滩很长,沿着海滩还有许多其他单独的海鸥。如果从局部到海滩的远处看,那只灰色的海鸥看起来并不像一个离群值。

我希望上面的故事能说明一个观点:离群值可以是全局离群值或局部离群值。如果一个数据点远离其邻域中的数据主体,那么它被认为是一个离群值,但如果从整个数据宇宙来看,它可能不是一个离群值。图(A)说明了局部和全局离群值。有两个数据簇,由蓝色和绿色簇表示。点O1O2是全局离群值,点a1a2是局部离群值。只关注全局视图的算法可能只能捕捉到O1O2。如果我们需要识别局部离群值a1a2,我们需要一种可以关注局部邻域的算法。我们意识到局部邻域中数据点的密度是关键。一个远离密集簇的数据点很容易被识别为局部离群值。但如果它靠近一个密度较低的簇,将一个数据点分类为局部离群值就不那么容易了。LOF将局部邻域的不同密度考虑在内,以识别局部离群值。LOF是离群值所在位置附近邻域的密度与离群值附近数据簇的密度之比。例如,点a1的邻域密度与蓝色簇的密度不同。点a1被识别为局部离群值。尽管绿色簇中的密度不同,LOF可以发现点a2
img
(B)LOF是如何工作的?

LOF计算数据点与其邻居之间的密度偏差。如果一个数据点的密度远低于其邻居,则被视为异常值。在图(A)中,点a1所在的区域比蓝色聚类中的其他点密度要低得多。在图(A)中,蓝色聚类看起来比绿色聚类更密集。当数据具有不同密度时,LOF特别有效。点a2是绿色聚类的局部异常值。LOF可以将a2识别为局部异常值。

局部离群因子(LOF)专门设计用于调整不同局部密度的变化。我将LOF的形成分为五个步骤,按顺序逐步解释LOF。在这五个步骤中,第四步是涉及局部密度的步骤。第五步是LOF,它将一个点所在位置的邻域密度与附近数据聚类的密度进行比较。

  • 第一步:K个邻居

  • 第二步:K距离

  • 第三步:可达距离(RD)

  • 第四步:局部可达密度(LRD)

  • 第五步:K邻居的局部离群因子:LOF(k)
    img
    图(B):可达距离(作者提供的图像)

首先,K-最近邻是指到达K个最近邻居的圆形区域。在图(B)中,点O的第一个、第二个和第三个最近邻居分别是p1p2p3。虚线圆是当K=3时的K个最近邻居的区域。这个定义与KNN相同。

其次,K-距离(o)是从点O到K个最近邻居的距离。距离可以用欧几里得距离或曼哈顿距离来衡量。

第三,点O的可达距离是K-距离(o)和任意点PO之间距离的最大值。在上面的图(B)中,当K=3时,K-距离是到点P3的距离。从P2O的距离是d(P2, O),小于点P3O的距离,即K-距离(o)。因此,P2的可达距离是K-距离(o),即从P3O的距离。
img
步骤1和2只是帮助定义步骤3中的可达性距离。可达性距离公式是为了减少与点O接近的所有点P的*d(p,o)*的统计波动。暂时只需记住它是任意点到点O的距离。可达性距离越长,点O越有可能是异常值。

在步骤4中,LRD是点O从其邻居处的平均可达性距离的倒数。LRD的低值意味着最近的数据体离点O很远。点的LRD用于与其K个邻居的平均LRD进行比较。
img
最后,在第5步中,LOF是点O的K个邻居的平均LRD与其自身LRD之比,如下公式所示。第一项是K个邻居的平均LRD。LOF是点p的LRD与其K个最近邻居的LRD之比的平均值。第二项是点O的LRD。
img
如果点O不是异常值,则邻居的平均LRD大致等于点O的LRD。点和其邻居的密度大致相等。在这种情况下,LOF几乎等于1。另一方面,如果点O是异常值,则第一项邻居的平均LRD将高于第二项。LOF将大于1。

LOF中距离比的使用确保可以考虑不同的局部密度。簇中数据点的LOF值通常接近1,而不管该簇的密度如何。例如,在图(A)中,无论是蓝色簇还是绿色簇中的数据点的LOF值都接近1,即使两个簇的密度不同。

一般来说,如果LOF> 1,则被视为异常值。该数据点与邻居的距离比预期的要远。另一方面,如果数据点位于密集的数据区域中,则不是异常值。其LOF值将接近1。

© 建模过程

在本书中,我采用以下建模过程进行模型开发、评估和结果解释。

  1. 模型开发

  2. 阈值确定

  3. 正常组和异常组的描述统计

通过异常值分数,您可以选择一个阈值,将具有高异常值分数的异常观测值与正常观测值分开。如果有任何先前的知识表明异常值的百分比不应超过1%,则可以选择一个导致约1%异常值的阈值。

两组之间特征的描述统计(如均值和标准差)对于传达模型的合理性非常重要。如果预期异常组中某个特征的均值高于正常组的均值,而结果却相反,那么这将是违反直觉的。在这种情况下,您应该调查、修改或删除该特征,并重新进行建模。
img
(C.1) 第一步:构建LOF模型

和之前一样,我将使用PyOD的generate_data()实用函数生成百分之十的异常值。为了使情况更有趣,数据生成过程(DGP)将创建六个变量。虽然这个模拟数据集有目标变量Y,但无监督模型只使用X变量。Y变量仅用于验证。我将异常值的百分比设置为5%,即“contamination=0.05”。


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from pyod.utils.data import generate_data
contamination = 0.05 # percentage of outliers
n_train = 500       # number of training points
n_test = 500        # number of testing points
n_features = 6      # number of features
X_train, X_test, y_train, y_test = generate_data(
    n_train=n_train, 
    n_test=n_test, 
    n_features= n_features, 
    contamination=contamination, 
    random_state=123)

X_train_pd = pd.DataFrame(X_train)
X_train_pd.head()

六个变量的前五条记录如下所示:
img
让我在散点图中绘制前两个变量。散点图中的黄色点是10%的异常值。正常观测值是紫色点。

# Plot
plt.scatter(X_train_pd[0], X_train_pd[1], c=y_train, alpha=0.8)
plt.title('Scatter plot')
plt.xlabel('x0')
plt.ylabel('x1')
plt.show()

img
使用PyOD构建模型非常容易,这要归功于其统一的API。下面我们声明并拟合模型,然后使用函数decision_functions()为训练和测试数据生成异常值得分。

参数contamination=0.05声明了一个5%的污染率。污染率是异常值的百分比。在大多数情况下,我们不知道异常值的百分比,所以可以根据任何先前的知识来赋值。PyOD默认的污染率为10%。这个参数不影响异常值得分的计算。PyOD使用它来推导出异常值得分的阈值,并应用函数predict()来分配标签(1或0)。我在下面的代码中编写了一个名为count_stat()的简短函数来显示预测的“1”和“0”值的计数。语法.threshold_显示了在指定污染率下的阈值。任何高于阈值的异常值得分都被视为异常值。

from pyod.models.lof import LOF
lof = LOF(contamination=0.05) 
lof.fit(X_train)

# Training data
y_train_scores = lof.decision_function(X_train)
y_train_pred = lof.predict(X_train)

# Test data
y_test_scores = lof.decision_function(X_test)
y_test_pred = lof.predict(X_test) # outlier labels (0 or 1)

def count_stat(vector):
    # Because it is '0' and '1', we can run a count statistic. 
    unique, counts = np.unique(vector, return_counts=True)
    return dict(zip(unique, counts))

print("The training data:", count_stat(y_train_pred))
print("The training data:", count_stat(y_test_pred))
# Threshold for the defined comtanimation rate
print("The threshold for the defined comtanimation rate:" , lof.threshold_)

img

lof.get_params()

img
(C.2) 第二步 — 确定LOF模型的合理阈值

PyOD有一个内置函数threshold_,它根据污染率contamination计算训练数据的阈值。由于默认的污染率是0.10,训练数据的阈值为1.2311,如下所示。它表示任何具有异常分数大于1.2311的观测值都被视为异常值。

确定阈值的第二种方法是使用PCA异常值分数的直方图,这样我们可以根据业务情况选择阈值。图©展示了分数的直方图。我们可以选择更保守的方法,选择一个较高的阈值,这将导致异常组中的异常值更少,但希望更精细。


import matplotlib.pyplot as plt
plt.hist(y_train_scores, bins='auto')  # arguments are passed to np.histogram
plt.title("Histogram with 'auto' bins")
plt.xlabel('LOF outlier score')
plt.show()

img
(C.3) 第三步 — 展示LOF模型中正常组和异常组的汇总统计信息

正如第1章所解释的那样,两组之间特征的描述性统计信息(如均值和标准差)对于证明模型的合理性非常重要。

我创建了一个名为descriptive_stat_threshold()的简短函数,用于显示基于阈值的正常组和异常组的特征大小和描述性统计信息。下面我仅仅使用了5%的阈值。您可以测试一系列阈值,以找到一个合理的异常组大小。

threshold = lof.threshold_ # Or other value from the above histogram

def descriptive_stat_threshold(df,pred_score, threshold):
    # Let's see how many '0's and '1's.
    df = pd.DataFrame(df)
    df['Anomaly_Score'] = pred_score
    df['Group'] = np.where(df['Anomaly_Score']< threshold, 'Normal', 'Outlier')

    # Now let's show the summary statistics:
    cnt = df.groupby('Group')['Anomaly_Score'].count().reset_index().rename(columns={'Anomaly_Score':'Count'})
    cnt['Count %'] = (cnt['Count'] / cnt['Count'].sum()) * 100 # The count and count %
    stat = df.groupby('Group').mean().round(2).reset_index() # The avg.
    stat = cnt.merge(stat, left_on='Group',right_on='Group') # Put the count and the avg. together
    return (stat)

descriptive_stat_threshold(X_train,y_train_scores, threshold)

img
表格(C.3)

上表展示了正常组和异常组的特征。它显示了正常组和异常组的计数和计数百分比。 “异常分数”是平均异常分数。提醒您使用特征名称标记特征以进行有效的演示。该表告诉我们几个重要的结果:

  • **异常组的大小:**异常组约为5%。请记住,异常组的大小由阈值确定。如果您选择更高的阈值,大小将缩小。

  • **平均异常分数:**异常组的平均异常分数远高于正常组(1.77>1.07)。您不需要过多解释分数。

  • **每个组中的特征统计:**上面显示异常组中特征的平均值小于正常组的平均值。异常组中特征的平均值应该更高还是更低取决于业务应用。重要的是,所有平均值都应与领域知识一致。

因为我们在数据生成中有地面真实性y_test,我们可以生成混淆矩阵来了解模型性能。该模型表现良好,识别出了所有25个异常值。


Actual_pred = pd.DataFrame({'Actual': y_test, 'Anomaly_Score': y_test_scores})
Actual_pred['Pred'] = np.where(Actual_pred['Anomaly_Score']< threshold,0,1)
pd.crosstab(Actual_pred['Actual'],Actual_pred['Pred'])

img
表格(C.4)

(D) 通过聚合多个模型实现模型稳定性

许多异常检测算法,特别是基于邻近性和分布的算法,对异常值非常敏感,并容易出现过拟合。我们如何产生一个稳定的模型结果?解决办法是使用一系列超参数训练模型,然后聚合得分。这样可以大大降低过拟合的可能性,并提高预测准确性。PyOD模块提供了四种方法来聚合结果。记得使用pip install combo安装相应的函数。你只需要使用一种方法来产生聚合结果。

因为任何基于密度的模型都可能对训练数据过拟合,所以仅依赖一个模型不是一个好主意。我将为一系列最近邻居创建20个LOF模型。这些模型的平均预测将成为最终的模型预测。

下面的“y_by_average”是训练数据的20个模型预测的平均值,这些值存储在数据框“train_scores_norm”中。我在图(D)中创建了它的直方图。

from pyod.models.combination import aom, moa, average, maximization
from pyod.utils.utility import standardizer
from pyod.models.lof import LOF
# Standardize data
X_train_norm, X_test_norm = standardizer(X_train, X_test)
# Test a range of k-neighbors from 10 to 200. There will be 20 models.
n_clf = 20
k_list = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 
 120, 130, 140, 150, 160, 170, 180, 190, 200]
# Just prepare data frames so we can store the model results
train_scores = np.zeros([X_train.shape[0], n_clf])
test_scores = np.zeros([X_test.shape[0], n_clf])
train_scores.shape
# Modeling
for i in range(n_clf):
    k = k_list[i]
    lof = LOF(n_neighbors=k) 
    lof.fit(X_train_norm)

    # Store the results in each column:
    train_scores[:, i] = lof.decision_scores_
    test_scores[:, i] = lof.decision_function(X_test_norm) 
# Decision scores have to be normalized before combination
train_scores_norm, test_scores_norm = standardizer(train_scores,test_scores)

下面的“y_by_average”是训练数据的20个模型预测的平均值,这些值存储在数据框“train_scores_norm”中。我在图(D)中创建了它的直方图。


# Combination by average
# The test_scores_norm is 500 x 20. The "average" function will take the average of the 20 columns. The result "y_by_average" is a single column: 
y_by_average = average(train_scores_norm)
import matplotlib.pyplot as plt
plt.hist(y_by_average, bins='auto') # arguments are passed to np.histogram
plt.title("Combination by average")
plt.show()

img
Figure (D): 训练数据的平均预测直方图

从**Figure (D)中,我可以将阈值设为0.5。然后我在Table (D)中生成了摘要统计表。它将25个数据点标识为异常值。读者应该对Table (C.3)**应用类似的解释。

descriptive_stat_threshold(X_train,y_train_by_average, .5)

img
表格(D)

(D) LOF摘要

  • LOF是一种基于密度的技术,使用最近邻搜索来识别异常点。

  • 通常,如果LOF> 1,则被认为是异常值。该数据点距离邻居的距离比预期更远。另一方面,如果数据点位于密集的数据区域中,则不是异常值。其LOF值将接近1。

参考文献

  • [1] Breunig, M.M.; Kriegel, H.-P.; Ng, R.T.; Sander, J. LOF: Identifying density-based local outliers. In Proceedings of the 2000 ACM. SIGMOD international conference on Management of data — SIGMOD ’00, Dallas, TX, USA, 16–18 May 2000.

  • [2] He, Z., Xu, X., & Deng, S. (2003). Discovering cluster-based local outliers. Pattern Recognit. Lett., 24, 1641–1650.

异常检测系列文章导航

异常检测系列:异常检测基本介绍

异常检测系列:Histogram-based Outlier Score_HBOS算法

异常检测系列:基于经验累积分布的异常检测(ECOD)

异常检测系列:孤立森林(Isolation Forest)模型介绍

异常检测系列:主成分分析PCA异常值分数检测

异常检测系列:支持向量机(SVM)异常检测介绍

异常检测系列:单类支持向量机(OCSVM)

异常检测系列:高斯混合模型(GMM)

异常检测系列:局部异常因子(LOF)

异常检测系列:K最近邻算法KNN

异常检测系列:基于聚类的局部异常因子(CBLOF)

异常检测系列:基于极限梯度提升的异常检测(XGBOD)

异常检测系列:AutoEncoder模型介绍

  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: LOF局部异常因子)是一种用于异常检测算法,它可以用来识别数据集中的离群点。该算法的主要思想是通过比较每个数据点与其邻居数据点之间的密度来判断其异常程度。 以下是一个示例的LOF局部异常因子算法的MATLAB代码: ```matlab function lof = LOF(data, k) n = size(data, 1); % 数据点的数量 % 计算每个点的k距离 k_dist = zeros(n, 1); for i = 1:n distance = sqrt(sum((data - repmat(data(i, :), n, 1)).^2, 2)); k_dist(i) = sort(distance, 'ascend')(k + 1); % 对距离从小到大排序,取第k+1个值 end % 计算每个点的局部可达密度(LRD) lrd = zeros(n, 1); for i = 1:n neighbors = find(sqrt(sum((data - repmat(data(i, :), n, 1)).^2, 2)) <= k_dist(i)); sum_density = sum(k_dist(neighbors)); lrd(i) = length(neighbors) / sum_density; end % 计算每个点的LOFlof = zeros(n, 1); for i = 1:n neighbors = find(sqrt(sum((data - repmat(data(i, :), n, 1)).^2, 2)) <= k_dist(i)); lrd_ratio = sum(lrd(neighbors)) / lrd(i); lof(i) = lrd_ratio / length(neighbors); end end ``` 在这个代码中,输入参数`data`是一个n×d的矩阵,其中n是数据点的数量,d是每个数据点的维度。`k`是每个数据点的邻居数量。 算法首先计算每个点的k距离,即与该点距离第k近的点的距离。然后,通过计算每个点的邻居数据点的密度之和得到局部可达密度(LRD)。最后,通过将局部可达密度的比率与邻居数量计算得到LOF值。 该代码返回一个n×1的向量`lof`,其中每个元素是相应数据点的LOF值。LOF值越大,表示该数据点越异常。 ### 回答2: LOF局部异常因子)是一种用于检测数据集中离群点的算法。它通过比较每个数据点的局部密度与其邻居数据点的局部密度来计算异常因子。该算法的MATLAB代码如下: ```matlab function LOF = local_outlier_factor(data, k) [m,n] = size(data); % 获取数据集的大小 LOF = zeros(m, 1); % 初始化异常因子向量 for i=1:m % 找到数据点i的k个最近邻居 neighbors = knnsearch(data, data(i,:), 'K', k+1); % 最近邻的索引(包括自身) neighbors = neighbors(2:end); % 去除自身 % 计算每个邻居的局部可达密度 lrd_i = 0; % 数据点i的局部可达密度 for j=1:length(neighbors) lrd_n = local_reachability_density(data, neighbors(j), k); % 邻居的局部可达密度 lrd_i = lrd_i + lrd_n; end lrd_i = lrd_i / k; % 取平均值 % 计算数据点i的局部异常因子 lof_i = 0; % 数据点i的局部异常因子 for j=1:length(neighbors) lrd_n = local_reachability_density(data, neighbors(j), k); % 邻居的局部可达密度 lof_n = lrd_n / lrd_i; % 邻居的局部异常因子 lof_i = lof_i + lof_n; end lof_i = lof_i / k; % 取平均值 LOF(i) = lof_i; % 存储数据点i的局部异常因子 end end function lrd = local_reachability_density(data, idx, k) [m,n] = size(data); % 获取数据集的大小 idx_neighbors = knnsearch(data, data(idx,:), 'K', k+1); % 数据点idx的最近邻索引(包括自身) idx_neighbors = idx_neighbors(2:end); % 去除自身 % 计算数据点idx的k个最近邻居的可达距离 reach_dist = zeros(1, k); for i=1:k dist = norm(data(idx,:) - data(idx_neighbors(i),:)); reach_dist(i) = max([dist, k_distance(data, idx_neighbors(i), k)]); end % 计算数据点idx的局部可达密度 lrd = k / sum(reach_dist); end function k_dist = k_distance(data, idx, k) [m,n] = size(data); % 获取数据集的大小 dist = zeros(m, 1); % 存储数据点idx与其他数据点的距离 for i=1:m dist(i) = norm(data(idx,:) - data(i,:)); % 计算距离 end % 找到数据点idx的第k个最近距离 k_dist = min(nth_element(dist, k+1)); end ``` 该代码首先定义了一个`local_outlier_factor`函数,该函数接受一个数据集以及`k`,计算每个数据点的局部异常因子,并将结果存储在`LOF`向量中。其次,定义了一个`local_reachability_density`函数和一个`k_distance`函数,分别用于计算局部可达密度和第`k`个最近距离。 使用该代码,可以传入一个数据集和`k`的值来计算每个数据点的局部异常因子。结果中的值越大,表示对应数据点越是异常。 ### 回答3: LOF局部异常因子算法是一种用于异常检测机器学习算法。该算法通过计算每个样本点周围样本点的密度来确定其异常程度。 以下是LOF算法的简化版MATLAB代码示例: ```matlab function LOF_scores = LOF(data, k) % data为输入数据,每行代表一个样本 % k为k邻近的数目 [n, m] = size(data); % n为样本数量,m为特征数目 LOF_scores = zeros(n, 1); % 初始化LOF得分数组 for i = 1:n distances = sqrt(sum((repmat(data(i,:), n, 1) - data).^2, 2)); % 计算样本点与其他点的欧氏距离 [sorted_dist, idx] = sort(distances); % 按距离排序 k_distances = sorted_dist(2:k+1); % 获取k个最近邻距离 k_nearest_points = data(idx(2:k+1), :); % 获取k个最近邻的样本点 average_local_reachability = 0; % 平均局部可达密度 for j = 1:k distances_j = sqrt(sum((repmat(k_nearest_points(j,:), k, 1) - k_nearest_points).^2, 2)); % 计算k近邻点之间的欧氏距离 reachability_distances = max([distances_j, k_distances], [], 2); % 计算k近邻点的可达距离 local_reachability_density = 1 / (sum(reachability_distances) / k); % 计算局部可达密度 average_local_reachability = average_local_reachability + local_reachability_density; % 累加局部可达密度 end average_local_reachability = average_local_reachability / k; % 计算平均局部可达密度 LOF_scores(i) = average_local_reachability / (sum(k_distances) / k); % 计算LOF得分 end LOF_scores = LOF_scores / max(LOF_scores); % 标准化LOF得分 end ``` 以上代码中,首先通过计算样本点之间的欧氏距离,找出每个样本点的k个最近邻距离和对应的样本点。然后,计算每个样本点的k近邻点之间的欧氏距离,并计算k近邻点的可达距离。通过累加所有k近邻点的可达距离,计算局部可达密度。最后,将每个样本点的局部可达密度除以其k个最近邻距离的平均值,得到LOF得分,即该样本点的异常程度。 需要注意的是,以上代码是一种简化版的LOF算法实现,可能存在一些优化和改进的空间。在实际应用中,可以根据具体的数据和需求进行相应的调整和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数智笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值