首先,借助于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均值聚类的应用;
- 基于聚类的结果,计算簇内每个点到簇中心的距离;
- 将距离跟阈值相比较,如果其大于阈值则认为是异常,否则正常;
案例实战
为了验证我们在前文所说的的直觉(“目测蓝色的簇可能会包含更多异常”),接下来通过构造自定义函数,计算簇内的每个点与簇中心的距离,并判断其是否超过阈值的异常点下方代码可能有点长,但仔细阅读并查看对应的注释内容,相信你一定能够理解代码的思想。
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()
如上图所示,蓝色的点即为异常点。从蓝色点的分布来看,上面那一簇所对应的异常点比较多(与之前的预判一致),而下面簇的异常点较少,且全部集中在散点的右侧。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69923331/viewspace-2655003/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/69923331/viewspace-2655003/