【机器学习】Kmeans聚类(含代码)

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越小表示数据点越接近它们的质心,聚类效果也越好。因为对误差取了平方,因此更加重视那些远离中心的点。

算法优缺点:

链接:k-means聚类算法优缺点?

优点:

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的缺点。


  1. 人工确定初始K值
  2. 受初始值的位置和个数的影响较大
  3. 容易受到离群点和噪声点的影响


完整的^(* ̄(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?

MapReduce实现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实现

KMEANS

K-means算法及文本聚类实践

  • 6
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值