KMeans算法思想
基本聚类方法
算法伪代码:
算法时间复杂度:
时间复杂度:O(T*n*k*m)
空间复杂度:O(n*m)
n:元素个数,k:第一步中选取的元素个数,m:每个元素的特征项个数,T:第5步中迭代的次数。
算法代码:
# 注意,这里采用的是完全随机初始化,这样的效果不是很好。因为可能会存在有病态的初始化结果。
# 正确方法应该是从样本中随机选择k个点作为初始点。
算法损失函数:
k-means的损失函数是平方误差:
中心点的选择
k-meams算法的能够保证收敛,但不能保证收敛于全局最优点,当初始中心点选取不好时,只能达到局部最优点,整个聚类的效果也会比较差。可以采用以下方法:k-means中心点
1、选择彼此距离尽可能远的那些点作为中心点;
2、先采用层次进行初步聚类输出k个簇,以簇的中心点的作为k-means的中心点的输入。
3、多次随机选择中心点训练k-means,选择效果最好的聚类结果
算法衡量指标:
SSE(误差平方和)用来度量聚类效果。SSE越小表示数据点越接近它们的质心,聚类效果也越好。因为对误差取了平方,因此更加重视那些远离中心的点。
算法优缺点:
优点:
1. 计算复杂度低,为O(Nmq),N是数据总量,m是类别(即k),q是迭代次数。一般来讲m、q会比N小得多,那么此时复杂度相当于O(N),在各种算法中是算很小的。
2. 思想很简单,实际上是一个优化全局MSE (Mean Square Error)和局部MSE的过程。
缺点:
1. 分类结果依赖于分类中心的初始化(可以通过进行多次k-means取最优来解决)。
2. 对类别规模差异太明显的数据效果不好。比如总共有A、B两类,ground truth显示A类有1000个点,B类有100个点。
3. 对噪声敏感。比如总共有A、B两类,每类500个点,而且相距不远。此时在离这两类很远的地方加一个点(可以看作噪声),对分类中心会有很大的影响(因为分类中心是取平均)。
4. 同理,k-means对于距离非常近的类别(blobs)的分类效果也不好。
5. 不适用于categorical的分类。这个不知道怎么翻译,举几个例子说明。比如我们的分类标准是性别,非男即女(为了简化讨论,不考虑其他情况),那么假设男=1,女=0,如果使用k-means的话,有可能出来两个中心的值在0-1之间,那么就不符合实际情况了。同理,国籍、使用的语言这些分类也不能用k-means;反之,身高、体重等连续变化的数值可以使用。
(注:我目前的理解就是离散分类不可用k-means,连续分类可用k-means,如果有误,欢迎指正)
6. 需要预先知道有几类。
总而言之,即便k-means有很多缺点(很多都是可以克服的),但它最大的优点是算法复杂度低,也就意味着它能够在短时间内处理海量的数据,这在如今这个数据爆炸的时代是非常重要的。因此,k-means目前仍然被各个企业广泛使用。科学家、工程师等也一直在研究不同的方法去克服k-means的缺点。
- 人工确定初始K值
- 受初始值的位置和个数的影响较大
- 容易受到离群点和噪声点的影响
完整的^(* ̄(oo) ̄)^
Kmeans实现Tensorflow版本
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sns
import tensorflow as tf
#随机生成数据,2000个点,随机分为两类
num_puntos=2000
conjunto_puntos=[]#数据存放在列表中
for i in range(num_puntos):
if np.random.random()<0.5:
conjunto_puntos.append([np.random.normal(0.0,0.9),np.random.normal(0.0,0.9)])
else:
conjunto_puntos.append([np.random.normal(3.0,0.5),np.random.normal(3.0,0.5)])
df=pd.DataFrame({'x':[v[0] for v in conjunto_puntos],
'y':[v[1] for v in conjunto_puntos]})
sns.lmplot('x','y',data=df,fit_reg=False,size=6)
plt.show()
#lmplot, 首先要明确的是:它的输入数据必须是一个Pandas的'DataFrame Like' 对象,
#然后从这个DataFrame中挑选一些参数进入绘图充当不同的身份.
#把数组装进tensor中
vectors=tf.constant(conjunto_puntos)
k=4
#tf.random_shuffle(value,seed=None,name=None):对value(是一个tensor)的第一维进行随机化。相当于把数据随机化
centroides=tf.Variable(tf.slice(tf.random_shuffle(vectors),[0,0],[k,-1]))
#随机选取K个点作为质心
#增加维度
expanded_vectors=tf.expand_dims(vectors,0)
expanded_centroides=tf.expand_dims(centroides,1)
diff=tf.sub(expanded_vectors,expanded_centroides)
sqr=tf.square(diff)
distance=tf.reduce_sum(sqr,2)
#挑选每一个点里的最近的质心(返回的是最小值索引)
assignments=tf.argmin(distance,0)
#tf.where()返回bool型tensor中为True的位置,
#tf.gather(params, indices, validate_indices=None, name=None)合并索引indices所指示params中的切片
means=tf.concat(0,[tf.reduce_mean(tf.gather(vectors,
tf.reshape(tf.where(tf.equal(assignments,c)),[1,-1])),
reduction_indices=[1])for c in range(k)])
#tf.assign()用means更新centroides
update_centroides=tf.assign(centroides,means)
init=tf.global_variables_initializer()
sess=tf.Session()
sess.run(init)
for step in range(100):
_,centroid_values,assignment_values=sess.run([update_centroides,centroides,assignments])
data={'x':[],'y':[],'cluster':[]}
for i in range(len(assignment_values)):
data['x'].append(conjunto_puntos[i][0])
data['y'].append(conjunto_puntos[i][1])
data['cluster'].append(assignment_values[i])
df=pd.DataFrame(data)
sns.lmplot('x','y',data=df,fit_reg=False,size=6,hue='cluster',legend=False)
#hue通过指定一个分组变量, 将原来的y~x关系划分成若干个分组;fit_reg:是否显示回归曲线
plt.show()
补充知识点:
MapReduce实现kmeans算法
k-means的每一次迭代都可以分为以下3个步骤。
第一步:Map:对于每一个点,将其对应的最近的聚类中心
第二步:Combine:刚完成map的机器在本机上都分别完成同一个聚类的点的求和,减少reduce操作的通信量和计算量。
第三步:reduce:将同一聚类中心的中间数据再进行求和,得到新的聚类中心
k-means 聚类算法进行 MapReduce 的基本思路:对串行算法中每 1 次迭代启 动对应的 1 次 MapReduce 计算过程,完成数据记录到聚类中心的距离计算以及新 的聚类中心的计算。
K-Means算法的收敛性和如何快速收敛超大的KMeans?
K-Means的收敛性
在EM框架下,求得的参数θ一定是收敛的,能够找到似然函数的最大值。那么K-Means是如何来保证收敛的呢?
目标函数
假设使用平方误差作为目标函数:
E-Step
固定参数μkμk, 将每个数据点分配到距离它本身最近的一个簇类中:
M-Step
固定数据点的分配,更新参数(中心点)μkμk:
为啥K-means会收敛呢?目标是使损失函数最小,在E-step时,找到一个最逼近目标的函数γ;在M-step时,固定函数γ,更新均值μ(找到当前函数下的最好的值)。
Kmeans python代码
kmeans算法收敛到了局部最小值,而非全局最小值。
class Kmeans():
"""Kmeans聚类算法.
Parameters:
-----------
k: int
聚类的数目.
max_iterations: int
最大迭代次数.
varepsilon: float
判断是否收敛, 如果上一次的所有k个聚类中心与本次的所有k个聚类中心的差都小于varepsilon,
则说明算法已经收敛
"""
def __init__(self, k=2, max_iterations=500, varepsilon=0.0001):
self.k = k
self.max_iterations = max_iterations
self.varepsilon = varepsilon
# 从所有样本中随机选取self.k样本作为初始的聚类中心
def init_random_centroids(self, X):
n_samples, n_features = np.shape(X)
centroids = np.zeros((self.k, n_features))
for i in range(self.k):
centroid = X[np.random.choice(range(n_samples))]
centroids[i] = centroid
return centroids
# 返回距离该样本最近的一个中心索引[0, self.k)
def _closest_centroid(self, sample, centroids):
distances = euclidean_distance(sample, centroids)
closest_i = np.argmin(distances)
return closest_i
# 将所有样本进行归类,归类规则就是将该样本归类到与其最近的中心
def create_clusters(self, centroids, X):
n_samples = np.shape(X)[0]
clusters = [[] for _ in range(self.k)]
for sample_i, sample in enumerate(X):
centroid_i = self._closest_centroid(sample, centroids)
clusters[centroid_i].append(sample_i)
return clusters
# 对中心进行更新
def update_centroids(self, clusters, X):
n_features = np.shape(X)[1]
centroids = np.zeros((self.k, n_features))
for i, cluster in enumerate(clusters):
centroid = np.mean(X[cluster], axis=0)
centroids[i] = centroid
return centroids
# 将所有样本进行归类,其所在的类别的索引就是其类别标签
def get_cluster_labels(self, clusters, X):
y_pred = np.zeros(np.shape(X)[0])
for cluster_i, cluster in enumerate(clusters):
for sample_i in cluster:
y_pred[sample_i] = cluster_i
return y_pred
# 对整个数据集X进行Kmeans聚类,返回其聚类的标签
def predict(self, X):
# 从所有样本中随机选取self.k样本作为初始的聚类中心
centroids = self.init_random_centroids(X)
# 迭代,直到算法收敛(上一次的聚类中心和这一次的聚类中心几乎重合)或者达到最大迭代次数
for _ in range(self.max_iterations):
# 将所有进行归类,归类规则就是将该样本归类到与其最近的中心
clusters = self.create_clusters(centroids, X)
former_centroids = centroids
# 计算新的聚类中心
centroids = self.update_centroids(clusters, X)
# 如果聚类中心几乎没有变化,说明算法已经收敛,退出迭代
diff = centroids - former_centroids
if diff.any() < self.varepsilon:
break
return self.get_cluster_labels(clusters, X)
def main():
# Load the dataset
X, y = datasets.make_blobs(n_samples=10000,
n_features=3,
centers=[[3,3, 3], [0,0,0], [1,1,1], [2,2,2]],
cluster_std=[0.2, 0.1, 0.2, 0.2],
random_state =9)
# 用Kmeans算法进行聚类
clf = Kmeans(k=4)
y_pred = clf.predict(X)
# 可视化聚类效果
fig = plt.figure(figsize=(12, 8))
ax = Axes3D(fig, rect=[0, 0, 1, 1], elev=30, azim=20)
plt.scatter(X[y==0][:, 0], X[y==0][:, 1], X[y==0][:, 2])
plt.scatter(X[y==1][:, 0], X[y==1][:, 1], X[y==1][:, 2])
plt.scatter(X[y==2][:, 0], X[y==2][:, 1], X[y==2][:, 2])
plt.scatter(X[y==3][:, 0], X[y==3][:, 1], X[y==3][:, 2])
plt.show()
if __name__ == "__main__":
main()
参考:
https://github.com/TingNie/Machine-learning-in-action/blob/master/kmeans/kmeans.ipynbK-Means算法理论及Python实现