K-Means是我们最常用的基于欧氏距离的聚类算法,它的基本思想是,两个目标的距离越近,相似度越大。
K-Means算法的目标函数
K-Means算法要实现的目标函数是:
其中,C1,C2,...,Ck是分别是k个cluster的中心点;C(Xi)表示Xi这个点所属于的cluster的中心点;d²( , )表示求两个点的距离的平方。
用通俗的话说,K-Means算法要实现的目的是:使得样本空间所有的非中心点,到它们各自所属的cluster的中心点的距离的平方,的和最小。
上述问题是一个NP-Hard问题。因此,一般是采用坐标下降(Coordinate Decendet)的方法求解。坐标下降法属于非梯度优化的方法,它在每一步迭代中沿着一个坐标轴的方向进行探索,通过循环使用不同的坐标来达到求解目标函数的局部最小值。坐标下降算法。来源:https://zh.wikipedia.org/zh-cn/%E5%9D%90%E6%A0%87%E4%B8%8B%E9%99%8D%E6%B3%95
如上图所示,假设总共有两个维度x,y:我们首先选择一个初始位置(x,y),然后假设x未知并将y的值代入目标函数中,令目标函数的导数为0,可以求得此时最佳的x的值。
然后又假设y未知并将刚刚求得的x的值代入目标函数,令目标函数的导数为0,可以求得此时最佳的y。
重复执行第1步和第2步,目标函数会逐渐接近极小值点,直到达到了极小值点后停止。
收敛的过程如上图中的红色箭头所示。在有更多维度的时候,也是同样的道理,一次只对于一个维度进行最优化。
坐标下降法一般要求目标函数是可微的凸函数,此时我们求得的极小值才能是全局最小值。
在使用K-Means之前,可以先用PCA,使得各个变量之间尽可能独立。否则如果两个变量之间有较强的关联性的时候,函数收敛的速度会非常慢。
K-Means算法的过程从样本中所有的n个点中,随机选取其中的k个做中心点(见第1行的第1、2张图;k的大小是事先指定的);
将样本中剩余n-k个样本点分别划分到离它们最近的中心点上,这里的划分完成后,就做完了一次聚类(见第1行的第3张图);
计算每一个类的平均值,并将每一个平均值作为新的中心点(见第2行的第1张图);
重复执行步骤2和3,直到中心点的位置不再发生变化(收敛;见最后四张图)。K-Means算法的实现过程
K-Means的Python实现
1.数据预处理,去离群点。因为k-means很容易受到离群点的影响。我的dataset比较干净,因此没有处理,直接载入数据。
import pandas as pd
import os
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
import numpy as np
from sklearn.cluster import KMeans
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
from sklearn.cluster import MiniBatchKMeans
# load the d
os.chdir(r'D:\')
data = pd.read_csv('transactions_n100000.csv')
2.对于离散的变量进行编码,例如进行one-hot encoding。
def enco(variable,data):
data[variable]
dummies = pd.get_dummies(data[variable], prefix=variable,dtype='float')
dummies
data = data.join(dummies)
return data
# one-hot encoding
data=enco('location',data)
data=enco('item_name',data)
3.对于数据划分训练集和测试集。注意,必须要先划分数据,再进行归一化。如果你把整个数据集一起归一化再划分的话,会把训练集的信息加入到测试集中,造成data leakage。
# train-test split
train, test = train_test_split(data,test_size=0.3)
4.对于数据进行归一化,防止scale比较大的变量在里面欺凌弱小、一家独大。具体过程为:对于训练集用k-means算法进行fit,得到包含训练集均值和标准偏差的scaler
用fit出来的结果分别transform训练集和测试集
注意这个流程,测试集也是用训练集的fit结果来transform的
# Min-Max scale
# fit
scaler = MinMaxScaler()
scaler.fit(train)
# transform
train_scaled = pd.DataFrame(scaler.transform(train),columns=data.columns)
test_scaled = pd.DataFrame(scaler.transform(test),columns=data.columns)
5.用elbow method选择最佳的k值。这个地方要注意,由于K-Means的聚类结果是局部最优,因此我们一般可以多聚类几次,求SSE的均值作为纵坐标,避免局部最优解。
# KMeans
# choose k value with elbow method
K = range(1, 20)
meanDispersions = []
for k in K:
kmeans = KMeans(n_clusters=k)
# kmeans = MiniBatchKMeans(n_clusters=k) # 如果数据量大的话,例如十万条以上,可以用mini-batch kmeans
kmeans.fit(train_scaled)
#理解为计算某个与其所属类聚中心的欧式距离
#最终是计算所有点与对应中心的距离的平方和的均值
meanDispersions.append(sum(np.min(cdist(train_scaled, kmeans.cluster_centers_, 'euclidean'), axis=1)) / train_scaled.shape[0])
plt.plot(K, meanDispersions, 'bx-')
plt.xlabel('k')
plt.ylabel('Average Dispersion')
plt.title('Selecting k with the Elbow Method')
plt.show() # k=8
在运行上面的代码块后,我们得到了Elbow的图如下。用elbow method选择最佳的k值
如上图所示,在k>8以后,认为折线的斜率几乎不变了,因此根据elbow method,可以选的k=8。
6. 进行k=8的kmeans聚类。
# cluster
kmeans = KMeans(n_clusters=8).fit(train)
train['type']=kmeans.labels_
train['type'].unique()
K-Means的优点:原理简单,收敛速度快,这个是业界用它最多的重要原因之一。
调参的时候只需要改变k一个参数。
算法的原理简单,可解释性好
K-Means的缺点:对于离群点和噪音点敏感。例如在距离中心很远的地方手动加一个噪音点,那么中心的位置就会被拉跑偏很远。
k值的选择很难确定。
只能发现球状的簇。在k-means中,我们用单个点对cluster进行建模,这实际上假设了各个cluster的数据是呈高维球型分布的,但是在生活中出现这种情况的概率并不算高。例如,每一个cluster是一个一个的长条状的,k-means的则根本识别不出来这种类别(这种情况可以用GMM)。实际上,k-means是在做凸优化,因此处理不了非凸的分布。
如果两个类别距离比较近,k-means的效果也不会太好。
初始值对结果影响较大,可能每次聚类结果都不一样。
结果可能只是局部最优而不是全局最优。
K-Means模型确定k的值的其他方法把样本的二维、三维散点图画出来,观察一下样本的分布,然后再决定k的值。如果维度大于三维,可以使用PCA降维到三维。
使用轮廓系数(Sihouette Coefficient)判断。轮廓系数结合了聚类的凝聚度(Cohesion,下图中的a(i))和分离度(Separation,下图中的b(i)),可以用来评价聚类的效果。该值处于[-1,1]之间。越接近1,表示聚类效果越好。来源:https://blog.csdn.net/wangxiaopeng0329/article/details/53542606来源:http://studio.galaxystatistics.com/report/cluster_analysis/article4/
更多的内容建议看一下 @微调 这篇回答微调:如何正确使用「K均值聚类」?zhuanlan.zhihu.com