Press "Enter" to skip to content

手把手教你如何利用K均值聚类实现异常值的识别!

作者:刘顺祥本期将从K均值聚类的角度,帮助大家理解该方法在异常值识别过程中的优势!
(本文涉及的代码可以在文末链接中下载)

 

首先,借助于Python随机生成两组二维数据,用于后文的实战。为了能够更加直观地洞察该数据,我们将其绘制成散点图。

 

# 导入第三方包
import numpy as np
import matplotlib.pyplot as plt
# 随机生成两组二元正态分布随机数
np.random.seed(1234)
mean1 = [0.5, 0.5]
cov1 = [[0.3, 0], [0, 0.1]]
x1, y1 = np.random.multivariate_normal(mean1, cov1, 5000).T
mean2 = [0, 8]
cov2 = [[0.8, 0], [0, 2]]
x2, y2 = np.random.multivariate_normal(mean2, cov2, 5000).T
# 绘制两组数据的散点图
plt.rcParams['axes.unicode_minus'] = False
plt.scatter(x1, y1)
plt.scatter(x2, y2)
# 显示图形
plt.show()

 

 

如上图所示,图中蓝色和红色之间形成鲜明的簇,其中每个簇内包含5000个数据。如果数据中存在异常点,
目测蓝色的簇可能会包含更多异常
,因为数据点相对分散一些。

 

K均值聚类的介绍

 

K均值聚类算法的思路非常通俗易懂,就是不断地计算各样本点与簇中心之间的距离,直到收敛为止,其具体的步骤如下:

 

(1)从数据中随机挑选k个样本点作为原始的簇中心。

 

(2)计算剩余样本与簇中心的距离,并把各样本标记为离k个簇中心最近的类别。

 

(3)重新计算各簇中样本点的均值,并以均值作为新的k个簇中心。

 

(4)不断重复(2)和(3),直到簇中心的变化趋于稳定,形成最终的k个簇。

 

也许上面的4个步骤还不足以让读者明白Kmeans的执行过程,可以结合下图更进一步地理解其背后的思想。

 

 

如上图所示,通过9个子图对Kmeans聚类过程加以说明:
子图1
,从原始样本中随机挑选两个数据点作为初始的簇中心,即子图中的两个五角星;
子图2
,将其余样本点与这两个五角星分别计算距离(距离的度量可选择欧氏距离、曼哈顿距离等),然后将每个样本点划分到离五角星最近的簇,即子图中按虚线隔开的两部分;
子图3
,计算两个簇内样本点的均值,得到新的簇中心,即子图中的五角星;
子图4
,根据新的簇中心,继续计算各样本与五角星之间的距离,得到
子图5
的划分结果和
子图6
中新的簇内样本均值;以此类推,最终得到理想的聚类效果,如子图9所示,图中的五角星即最终的簇中心点。

 

在上文中,我们生成了两组随机数据,从图中一眼就可以看出需聚为两类,然而在实际应用中,很多数据都无法通过可视化或直觉判断聚类的个数(即K值)。但这不代表没有方法锁定最佳的K值,
在书《从零开始学Python数据分析与挖掘》的第十五章介绍了“拐点法”、“轮廓系数法”和“间隔统计量法”
,感兴趣的朋友可以去了解一下。这里就使用书中的自定义函数,测试一下K应该对应的值:

 

# 将两组数据集汇总到数据框中
X = pd.DataFrame(np.concatenate([np.array([x1, y1]), np.array([x2, y2])], axis=1).T)
X.rename(columns = {0:'x1',1:'x2'}, inplace = True)
# 自定义函数的调用
k_SSE(X, 10)

 

 

如上图所示,当簇的个数为2时形成了一个明显的“拐点”,因为 K值从1到2时,折线的斜率都比较大,但是值为3时斜率突然就降低了很多,并且之后的簇对应的斜率都变动很小。所以,合理的值应该为2,与模拟的两个簇数据相吻合。

 

异常点识别原理

 

使用K均值聚类的思想识别数据中的异常点还是非常简单的,具体步骤如下:

 

利用 “拐点法”、“轮廓系数法”、“间隔统计量法”
或者“经验法”确定聚类的个数;

 

基于具体的K值,对数据实施K均值聚类的应用;

 

基于聚类的结果,计算簇内每个点到簇中心的距离;

 

将距离跟阈值相比较,如果其大于阈值则认为是异常,否则正常;

 

案例实战

 

为了验证我们在前文所说的的直觉(“ 目测蓝色的簇可能会包含更多异常
”),接下来通过构造自定义函数,计算簇内的每个点与簇中心的距离,并判断其是否超过阈值的异常点(阈值的计算是《
Python数据清洗–异常值识别与处理01
》为中介绍的sigma法)。下方代码可能有点长,但
仔细阅读并查看对应的注释内容
,相信你一定能够理解代码的思想。

 

def kmeans_outliers(data, clusters, is_scale = True):
# 指定聚类个数,准备进行数据聚类
    kmeans = KMeans(n_clusters=clusters)
# 用于存储聚类相关的结果
    cluster_res = []
# 判断是否需要对数据做标准化处理
if is_scale:
        std_data = scale(data) # 标准化
        kmeans.fit(std_data)  # 聚类拟合
# 返回簇标签
        labels = kmeans.labels_
# 返回簇中心
        centers = kmeans.cluster_centers_
for label in set(labels):
# 计算簇内样本点与簇中心的距离
            diff = std_data[np.array(labels) == label,] - \
                   - np.array(centers[label])
            dist = np.sum(np.square(diff), axis=1)
# 计算判断异常的阈值
            UL = dist.mean() + 3*dist.std()
# 识别异常值,1表示异常,0表示正常
            OutLine = np.where(dist > UL, 1, 0) 
            raw_data = data.loc[np.array(labels) == label,]
            new_data = pd.DataFrame({'Label':label,'Dist':dist,'OutLier':OutLine})
# 重新修正两个数据框的行编号
            raw_data.index = new_data.index = range(raw_data.shape[0])
# 数据的列合并
            cluster_res.append(pd.concat([raw_data,new_data], axis = 1))
else:
        kmeans.fit(data)  # 聚类拟合
# 返回簇标签
        labels = kmeans.labels_
# 返回簇中心
        centers = kmeans.cluster_centers_
for label in set(labels):
# 计算簇内样本点与簇中心的距离
            diff = np.array(data.loc[np.array(labels) == label,]) - \
                   - np.array(centers[label])
            dist = np.sum(np.square(diff), axis=1)
            UL = dist.mean() + 3*dist.std()
            OutLine = np.where(dist > UL, 1, 0)
            raw_data = data.loc[np.array(labels) == label,]
            new_data = pd.DataFrame({'Label':label,'Dist':dist,'OutLier':OutLine})
            raw_data.index = new_data.index = range(raw_data.shape[0])
            cluster_res.append(pd.concat([raw_data,new_data], axis = 1))
# 返回数据的行合并结果    
return pd.concat(cluster_res)
# 调用函数,返回异常检测的结果
res = kmeans_outliers(X,2,False)
# res
# 绘图
sns.lmplot(x="x1", y="x2", hue='OutLier', data=res,
           fit_reg=False, legend=False)
plt.legend(loc='best')
plt.show()

 

 

如上图所示,蓝色的点即为异常点。从蓝色点的分布来看,上面那一簇所对应的异常点比较多(与之前的预判一致),而下面簇的异常点较少,且全部集中在散点的右侧。

 

OK,今天的内容就分享到这里,下一期将会跟大家分享如何基于密度聚类,针对非球形簇的数据做异常点检测。如果你有任何问题,欢迎在公众号的留言区域表达你的疑问。

 

代码链接:

 

https://pan.baidu.com/s/1tNG3IGRx7HsVqzjelHDSNg

 

提取码:yew1

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注