K-Means聚类最优k值的选取

最近做一个文本分类的项目,在最开始的时候会用到K-means的聚类方法,因此需要在文本上找到最佳的聚类数。

1. 手肘法

1.1 理论

手肘法的评价K值好坏的标准是SSE(sum of the squared errors)

S S E = p C i | p m i | 2

其中 C i 代表第 i 个簇, p 是簇 C i 里的样本点, m i 是簇的质心。

手肘法的核心思想是:随着聚类数k的增大,样本划分会更加精细,每个簇的聚合程度会逐渐提高,那么误差平方和SSE自然会逐渐变小。并且,当k小于最佳聚类数时,由于k的增大会大幅增加每个簇的聚合程度,故SSE的下降幅度会很大,而当k到达最佳聚类数时,再增加k所得到的聚合程度回报会迅速变小,所以SSE的下降幅度会骤减,然后随着k值的继续增大而趋于平缓,也就是说SSE和k的关系图是一个手肘的形状,而这个肘部对应的k值就是数据的最佳聚类数。这也是该方法被称为手肘法的原因。

1.2 代码

输入数据格式是一个列表,每个元素都是一个n维度向量。

from sklearn.cluster import KMeans
import multiprocessing
def train_cluster(train_vecs, model_name=None, start_k=2, end_k=20):
    print('training cluster')
    SSE = []
    SSE_d1 = [] #sse的一阶导数
    SSE_d2 = [] #sse的二阶导数
    models = [] #保存每次的模型
    for i in range(start_k, end_k):
        kmeans_model = KMeans(n_clusters=kmeans_clusters, n_jobs=multiprocessing.cpu_count(), )
        kmeans_model.fit(train_vecs)
        SSE.append(kmeans_model.inertia_)  # 保存每一个k值的SSE值
        print('{} Means SSE loss = {}'.format(i, kmeans_model.inertia_))
        models.append(kmeans_model)
    # 求二阶导数,通过sse方法计算最佳k值
    SSE_length = len(SSE)
    for i in range(1, SSE_length):
        SSE_d1.append((SSE[i - 1] - SSE[i]) / 2)
    for i in range(1, len(SSE_d1) - 1):
        SSE_d2.append((SSE_d1[i - 1] - SSE_d1[i]) / 2)

    best_model = models[SSE_d2.index(max(SSE_d2)) + 1]
    return best_model

2. 轮廓系数法

2.1 理论

该方法的核心指标是轮廓系数(Silhouette Coefficient),某个样本点Xi的轮廓系数定义如下

S = b a m a x ( a , b )

a X i 和同簇的其他样本的平均距离,称为凝聚度。 b X i 和最近簇中所有样本的平均距离,称之为分离度。最近簇的定义如下

C j = a r g min C k 1 n p C k | p X i | 2

其中 p 是簇 C k 中的样本, 也就是说,计算出 X i 到所有簇的平均距离之后,选取最小的作为 b 即可。

2.2 代码

数据仍和1.2中的格式一样

from sklearn.cluster import KMeans
import multiprocessing
from sklearn.metrics import silhouette_score   
def train_cluster(train_vecs, model_name=None, start_k=2, end_k=20):
    print('training cluster')
    scores = []
    models = []
    for i in range(start_k, end_k):
        kmeans_model = KMeans(n_clusters=kmeans_clusters, n_jobs=multiprocessing.cpu_count(), )
        kmeans_model.fit(train_vecs)
        score = silhouette_score(train_vecs,kmeans_model.labels_,metric='euclidean')
        scores.append(score)  # 保存每一个k值的score值, 在这里用欧式距离
        print('{} Means score loss = {}'.format(i, score))
        models.append(kmeans_model)

    best_model = models[scores.index(max(scores))]
    return best_model

但是在实践的时候发现手肘图的最佳值和轮廓系数的最佳值是不一样的,轮廓系数的最佳值小于手肘图的最佳值,原因我猜测是轮廓系数考虑了分离度b,也就是样本与最近簇中所有样本的平均距离。
从定义上看,轮廓系数大,不一定是凝聚度a(样本与同簇的其他样本的平均距离)小,而可能是b和a都很大的情况下b相对a大得多,这么一来,a是有可能取得比较大的。a一大,样本与同簇的其他样本的平均距离就大,簇的紧凑程度就弱,那么簇内样本离质心的距离也大,从而导致SSE较大。所以,虽然轮廓系数引入了分离度b而限制了聚类划分的程度,但是同样会引来最优结果的SSE比较大的问题。

需要进一步了解的可以查看Wiki

猜你喜欢

转载自blog.csdn.net/harry_128/article/details/80523568