程序员

高维数据的LOF算法实现

2018-12-03  本文已影响68人  对月流珠_0c9f

在之前代码中实现的LOF算法完成的是二维数据的离群点剔除,那么往往我们的数据都是高维的,在这种情况下可以有怎样的处理方式呢?
源自:https://zhuanlan.zhihu.com/p/37753692
ps:不得不说,这位大佬对问题逐步深入的思考很令人佩服,思路清洗明了,值得研究。

数据维度的增大会导致的几个问题:
1、增加了计算难度,不能仅仅通过距离度量的方式来进行判断
2、不同参数对问题的影响因子不同,可能会放大一些因素的影响
3、k的选取增加了新的难度

可以考虑的处理方法
1、采用马氏距离作为距离方式
2、采用随机森林的决策思想,考虑在多个维度投影上采用LOF并将结果结合起来,以提高高维数据的检测质量。

初期工作:
1、对数据进行简单的清洗,去除一些意义较差的参数
2、数据的规约处理(就是将同一类的特征整合为同一个特征数据进行考虑)
3、数据维度必须大于1,如果只有1维可以进行增维处理

data = list(zip(data, np.zeros_like(data)))

或者

data = pd.DataFrame(data)

改进方式:
1、投票表决模式
2、LOF异常分数加权模式
3、混合模式

一、投票表决模式

投票表决模式认为每一个维度的数据都是同等重要,单独为每个维度数据设置 LOF 阈值并进行比对,样本的 LOF 值超过阈值则异常票数积 1 分,最终超过票数阈值的样本认为是离群样本。



由于在这个问题里面参数一共就只有两个维度,所以无法体现出来这种方式的优点。

def localoutlierfactor(data, predict, k, group_str):
    from sklearn.neighbors import LocalOutlierFactor
    clf = LocalOutlierFactor(n_neighbors=k + 1, algorithm='auto', contamination=0.1, n_jobs=-1)
    clf.fit(data)
    # 记录 LOF 离群因子,做相反数处理
    predict['local outlier factor %s' % group_str] = -clf._decision_function(predict.iloc[:, eval(group_str)])
    return predict

def plot_lof(result, method, group_str):
    import matplotlib.pyplot as plt
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    plt.figure('local outlier factor %s' % group_str)
    try:
        plt.scatter(result[result > method].index,
                    result[result > method], c='red', s=50,
                    marker='.', alpha=None,
                    label='离群点')
    except Exception:
        pass
    try:
        plt.scatter(result[result <= method].index,
                    result[result <= method], c='black', s=50,
                    marker='.', alpha=None, label='正常点')
    except Exception:
        pass
    plt.hlines(method, -2, 2 + max(result.index), linestyles='--')
    plt.xlim(-2, 2 + max(result.index))
    plt.title('LOF局部离群点检测', fontsize=13)
    plt.ylabel('局部离群因子', fontsize=15)
    plt.legend()
    plt.show()

def ensemble_lof(data, predict=None, k=5, groups=[], method=1, vote_method = 'auto'):
    import pandas as pd
    import numpy as np
    # 判断是否传入测试数据,若没有传入则测试数据赋值为训练数据
    try:
        if predict == None:
            predict = data.copy()
    except Exception:
        pass
    data = pd.DataFrame(data); 
    predict = pd.DataFrame(predict)
    # 数据标签分组,默认独立自成一组
    for i in range(data.shape[1]):
        if i not in pd.DataFrame(groups).values:
            groups += [[i]]
    # 扩充阈值列表
    if type(method) != list:
        method = [method]
        method += [1] * (len(groups) - 1)
        print(method)
    else:
        method += [1] * (len(groups) - len(method))
        print(method)
    vote = np.zeros(len(predict))
    # 计算LOF离群因子并根据阈值进行票数统计
    for i in range(len(groups)):
        predict = localoutlierfactor(pd.DataFrame(data).iloc[:, groups[i]], predict, k, str(groups[i]))
        plot_lof(predict.iloc[:, -1], method[i], str(groups[i]))
        vote += predict.iloc[:, -1] > method[i]
    # 根据票数阈值划分离群点与正常点
    predict['vote'] = vote
    if vote_method == 'auto':
        vote_method = len(groups)/2
    outliers = predict[vote > vote_method].sort_values(by='vote')
    inliers = predict[vote <= vote_method].sort_values(by='vote')
    return outliers, inliers

import numpy as np
import pandas as pd

posi = pd.read_excel(r'已结束项目任务数据.xls')
lon = np.array(posi["任务gps经度"][:])  # 经度
lat = np.array(posi["任务gps 纬度"][:])  # 纬度
A = list(zip(lat, lon))  # 按照纬度-经度匹配

posi = pd.read_excel(r'会员信息数据.xlsx')
lon = np.array(posi["会员位置(GPS)经度"][:])  # 经度
lat = np.array(posi["会员位置(GPS)纬度"][:])  # 纬度
B = list(zip(lat, lon))  # 按照纬度-经度匹配

# 获取会员对任务密度,取第5邻域,阈值分别为 1.5,2,得票数超过 1 的认为是异常点 
outliers4, inliers4 = ensemble_lof(A, B, k=5, method=[1.5,2], vote_method = 1)

# 绘图程序
plt.figure('投票集成 LOF 模式')
plt.scatter(np.array(B)[:, 0], np.array(B)[:, 1], s=10, c='b', alpha=0.5)
plt.scatter(np.array(A)[:, 0], np.array(A)[:, 1], s=10, c='green', alpha=0.3)
plt.scatter(outliers4[0], outliers4[1], s=10 + 1000, c='r', alpha=0.2)
plt.title('k = 5, method = [1.5, 2]')

后续在石油项目中我会应用这个方法进行尝试

二、LOF异常分数加权模式

异常分数加权模式则是对各维度数据的 LOF 值进行加权,获取最终的 LOF 得分作为整体数据的 LOF 得分。也就是说在LOF异常分数加权模式里面考虑了不同参数的影响程度。

在这个方法中,权重的确定是至关重要的,很大程度上决定了模型的可行性和拟合程度。因此,模型确认的重点放在了权重的确认上。

*在这里采用的方法是 熵权法


与上一个方法相比,差距在对ensemble_lof函数中进行了调整。

在这里加了一个权重而已2333
废话少说,上代码:
def localoutlierfactor(data, predict, k, group_str):
    from sklearn.neighbors import LocalOutlierFactor
    clf = LocalOutlierFactor(n_neighbors=k + 1, algorithm='auto', contamination=0.1, n_jobs=-1)
    clf.fit(data)
    # 记录 LOF 离群因子,做相反数处理
    predict['local outlier factor %s' % group_str] = -clf._decision_function(predict.iloc[:, eval(group_str)])
    return predict

def plot_lof(result, method):
    import matplotlib.pyplot as plt
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    plt.scatter(result[result > method].index,
                result[result > method], c='red', s=50,
                marker='.', alpha=None,
                label='离群点')
    plt.scatter(result[result <= method].index,
                result[result <= method], c='black', s=50,
                marker='.', alpha=None, label='正常点')
    plt.hlines(method, -2, 2 + max(result.index), linestyles='--')
    plt.xlim(-2, 2 + max(result.index))
    plt.title('LOF局部离群点检测', fontsize=13)
    plt.ylabel('局部离群因子', fontsize=15)
    plt.legend()
    plt.show()

def ensemble_lof(data, predict=None, k=5, groups=[], method='auto', weight=1):
    import pandas as pd
    # 判断是否传入测试数据,若没有传入则测试数据赋值为训练数据
    try:
        if predict == None:
            predict = data
    except Exception:
        pass
    data = pd.DataFrame(data);
    predict = pd.DataFrame(predict)
    # 数据标签分组,默认独立自成一组
    for i in range(data.shape[1]):
        if i not in pd.DataFrame(groups).values:
            groups += [[i]]
    # 扩充权值列表
    if type(weight) != list:
        weight = [weight]
        weight += [1] * (len(groups) - 1)
    else:
        weight += [1] * (len(groups) - len(weight))
    predict['local outlier factor'] = 0
    # 计算LOF离群因子并根据特征权重计算加权LOF得分
    for i in range(len(groups)):
        predict = localoutlierfactor(pd.DataFrame(data).iloc[:, groups[i]], predict, k, str(groups[i]))
        predict['local outlier factor'] += predict.iloc[:, -1] * weight[i]
    if method == 'auto':
        method = sum(weight)
    plot_lof(predict['local outlier factor'], method)
    # 根据离群阈值划分离群点与正常点
    outliers = predict[predict['local outlier factor'] > method].sort_values(by='local outlier factor')
    inliers = predict[predict['local outlier factor'] <= method].sort_values(by='local outlier factor')
    return outliers, inliers


import numpy as np
import pandas as pd

posi = pd.read_excel(r'E:\\CNOOC_drilling\\11month\\离群点异常点\\已结束项目任务数据.xls')
lon = np.array(posi["任务gps经度"][:])  # 经度
lat = np.array(posi["任务gps 纬度"][:])  # 纬度
A = list(zip(lat, lon))  # 按照纬度-经度匹配

posi = pd.read_excel(r'E:\\CNOOC_drilling\\11month\\离群点异常点\\会员信息数据.xlsx')
lon = np.array(posi["会员位置(GPS)经度"][:])  # 经度
lat = np.array(posi["会员位置(GPS)纬度"][:])  # 纬度
B = list(zip(lat, lon))  # 按照纬度-经度匹配

# 获取会员对任务密度,取第5邻域,阈值为 100,权重分别为5,1 
outliers5, inliers5 = ensemble_lof(A, B, k=5, method=100,weight = [5,1])

# 绘图程序
plt.figure('LOF 异常分数加权模式')
plt.scatter(np.array(B)[:, 0], np.array(B)[:, 1], s=10, c='b', alpha=0.5)
plt.scatter(np.array(A)[:, 0], np.array(A)[:, 1], s=10, c='green', alpha=0.3)
plt.scatter(outliers5[0], outliers5[1], s=10 + outliers5['local outlier factor'], c='r', alpha=0.2)
plt.title('k = 5, method = 100')


效果有所改善。

三、混合模式

将前两种方法进行综合使用。
(不过我觉得LOF异常分数加权模式已经能够实现这个功能了。)

上一篇 下一篇

猜你喜欢

热点阅读