高维数据的LOF算法实现
在之前代码中实现的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 分,最终超过票数阈值的样本认为是离群样本。
- localoutlierfactor(data, predict, k)
输入:训练样本,测试样本,k值
输出:每一个测试样本 LOF 值及对应的第k距离 - plot_lof(result,method)
输入:LOF 值、阈值
输出:以索引为横坐标的 LOF 值分布图 - ensemble_lof(data, predict=None, k=5, groups=[], method=1, vote_method = 'auto')
输入:训练样本,测试样本,k值,组合特征索引,离群阈值,票数阈值
组合特征索引(列位置 - 1):如第一列数据与第二列数据作为同类型数据,则传入groups = [[0, 1]]
离群阈值:每一个特征的离群阈值,缺少位数则以 1 替代
票数阈值:正常点离群因子得票数上限
输出:离群点、正常点分类情况 - 没有输入测试样本时,默认测试样本=训练样本
![](https://img.haomeiwen.com/i9134174/3aa7f134c1744f71.png)
![](https://img.haomeiwen.com/i9134174/c543db3fea514584.png)
由于在这个问题里面参数一共就只有两个维度,所以无法体现出来这种方式的优点。
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异常分数加权模式里面考虑了不同参数的影响程度。
在这个方法中,权重的确定是至关重要的,很大程度上决定了模型的可行性和拟合程度。因此,模型确认的重点放在了权重的确认上。
*在这里采用的方法是 熵权法
![](https://img.haomeiwen.com/i9134174/4f7f912d83246a27.png)
与上一个方法相比,差距在对ensemble_lof函数中进行了调整。
![](https://img.haomeiwen.com/i9134174/b3064a28d1116071.png)
在这里加了一个权重而已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')
![](https://img.haomeiwen.com/i9134174/4377733b9bed3215.png)
![](https://img.haomeiwen.com/i9134174/c980d1e7535f55b5.png)
效果有所改善。
三、混合模式
将前两种方法进行综合使用。
(不过我觉得LOF异常分数加权模式已经能够实现这个功能了。)