跟我一起学scikit-learn22:K-均值算法

K-均值(K-means)算法是一种典型的无监督机器学习算法,用来解决聚类问题(Clustering)。由于数据标记需要耗费巨大的人力物力,无监督或者半监督学习算法不需要对数据进行标记,可以大大减少工作量。

1.K-均值算法原理

我们需要注意聚类问题和分类问题的区别。针对监督式学习算法,如K-近邻算法,其输入数据是已经标记了的(x^{(1)},y^{(1)})(x^{(2)},y^{(2)}),...,(x^{(m)},y^{(m)}),目标是找出分类边界,然后对新的数据进行分类。而无监督式学习算法,如K-均值算法,只给出一组无标记的数据集x^{(1)}x^{(2)},...,x^{(m)},目标是找出这组数据的模式特征,如哪些数据是同一种类型的,哪些数据是另外一种类型。典型的无监督式学习包括市场细分,即通过分析用户数据,把一个产品的市场进行细分,找出细分人群。另外一个是社交网络分析,分析社交网络中参与人员的不同特点,根据特点区分出不同群体。这些都是无监督式学习里的聚类(Clustering)问题。

K-均值算法包含以下两个步骤:
(1)给聚类中心分配点。计算所有的训练样本,把每个训练样例分配到距离其最近的聚类中心所在的类别里。
(2)移动聚类中心。新的聚类中心移动到这个聚类所有的点的平均值处。

一直重复上面的动作,直到聚类中心不再移动为止,这时就探索出了数据集的结构了。

我们也可以用数学方式来描述K-均值算法。算法有两个输入:一个是K,表示选取的聚类的个数;另外一个是训练数据集x^{(1)}x^{(2)},...,x^{(m)}
(1)随机选择K个初始聚类中心u_1u_2,...,u_k
(2)计算数据集中的每个点x^{(i)}分别到这K个聚类中心u_1u_2,...,u_k的距离,记录距离最短的聚类中心u_j (1 \leq j \leq k),然后把x^{(i)}归入这个类,即令x^{(i)}的类别标记c^{(i)}=j。这里一般使用||x^{(i)}-u_j||来计算距离。
(3)重新计算聚类中心,移动聚类中心到这个聚类的均值处。即u_j=\frac{1}{c}(\sum_{d=1}^{c}x^{(d)}),其中c表示该聚类样本点个数,x^{(d)}表示该聚类第d个样本点。
(4)重复上述过程,直到所有聚类中心不再移动为止。

1.K-均值算法的成本函数

根据成本函数的定义,成本即模型预测值与实际值的误差,据此不难得出K-均值算法的成本函数:
J=\frac{1}{m}\sum_{i=1}^{m}||x^{(i)}-u_{c^{(i)}}||^2

其中,c^{(i)}是训练样例x^{(i)}分配的聚类序号;u_{c^{(i)}}x^{(i)}所属聚类的中心。K-均值算法的成本函数的物理意义就是,训练样本到其所属的聚类中心的平均距离。

2.随机初始化聚类中心点

假设K是聚类的个数,m是训练样本的个数,那么必定有K<m。在随机初始化时,随机从m个训练样本中选择K个样本作为聚类中心点。这是正式推荐的随机初始化聚类中心的做法。

在实际解决问题时,最终的聚类结果会和随机初始化的聚类中心点有关。即不同的随机初始化的聚类中心点可能得到不同的最终聚类结果。因为成本函数可能会收敛在一个局部最优解,而不是全局最优解上。有一个解决办法就是多做几次随机初始化的动作,然后训练出不同的聚类中心及聚类节点分配方案,然后使用这些值算出成本函数,从中选择成本最小的那个方案。

假设我们做100次K-均值算法,每次都执行如下步骤:
(1)随机选择K个聚类中心点
(2)运行K-均值算法,算出c^{(1)}c^{(2)},...,c^{(m)}u_1u_2,...,u_k
(3)使用c^{(1)}c^{(2)},...,c^{(m)}u_1u_2,...,u_k算出最终的成本值。
找出成本最小的方案。这样就可以适当加大运算次数,从而求出全局最优解。

3.选择聚类的个数

怎样选择合适的聚类个数呢?实际上聚类个数和业务有紧密的关联,例如我们要对运动鞋的尺码大小进行聚类分析,那么是分成5个尺寸等级好还是分成10个尺寸等级好呢?这是个业务问题而非技术问题。5个尺寸等级可以给生产和销售带来便利,但客户体验可能不好;10个尺寸等级客户体验好了,可能会给生产和库存造成不便。

从技术角度来讲,也有一些方法可以用来做一些判断的。比如,我们可以把聚类个数作为横坐标,成本函数作为纵坐标,把成本和聚类个数的关系画出来。大体的趋势是随着K值越来越大,成本会越来越低。我们找出一个拐点,即在这个拐点之前成本下降比较快,在这个拐点之后成本下降比较慢,那么很可能这个拐点所在的K值就是要寻求的最优解。

当然,这个技术并不总是有效的,因为很可能会得到一个没有拐点的曲线,这样,就必须和业务结合以便选择合适的聚类个数。

2.scikit-learn里的K-均值算法

scikit-learn里的K-均值算法由sklearn.cluster.KMeans类实现。下面通过一个简单的例子,来学习怎样在scikit-learn里使用K-均值算法。

我们生成一组包含两个特征的200个样本:

from sklearn.datasets import make_blobs
X,y = make_blobs(n_samples=200,
                n_features=2,
                centers=4,
                cluster_std=1,
                center_box=(-10.0,10.0),
                shuffle=True,
                random_state=1)

然后把样本画在二维坐标系上,以便直观地观察:

import matplotlib.pyplot as plt
plt.figure(figsize=(6,4),dpi=144)
plt.xticks(())
plt.yticks(())
plt.scatter(X[:,0],X[:,1],s=20,marker='o')

结果如图所示:


image.png

接着使用KMeans模型来拟合。我们设置类别个数为3,并计算出其拟合后的成本。

from sklearn.cluster import KMeans
n_clusters = 3
kmeans = KMeans(n_clusters=n_clusters)
kmeans.fit(X)
print("kmeans: k = {}, cost = {}".format(n_clusters,int(kmeans.score(X))))

输出如下:

kmeans: k = 3, cost = -668

KMeans.score()函数计算K-均值算法拟合后的成本,用负数表示,其绝对值越大,说明成本越高。前面介绍过,K-均值算法成本的物理意义为训练样本到其所属的聚类中心的距离平均值,在scikit-learn里,其计算成本的方法略有不同,它是计算训练样本到其所属的聚类中心的距离的总和。

当然我们还可以把分类后的样本及其所属的聚类中心都画出来,这样可以更直观地观察算法的拟合效果。

labels = kmean.labels_
centers = kmean.cluster_centers_
markers = ['o', '^', '*']
colors = ['r', 'b', 'y']

plt.figure(figsize=(6,4), dpi=144)
plt.xticks(())
plt.yticks(())

# 画样本
for c in range(n_clusters):
    cluster = X[labels == c]
    plt.scatter(cluster[:, 0], cluster[:, 1], 
                marker=markers[c], s=20, c=colors[c])
# 画出中心点
plt.scatter(centers[:, 0], centers[:, 1],
            marker='o', c="white", alpha=0.9, s=300)
for i, c in enumerate(centers):
    plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

输出结果如图所示:


image.png

前面说过,K-均值算法的一个关键参数是K,即聚类个数。从技术角度来讲,K值越大,算法成本越低,这个很容易理解。但从业务角度来看,不是K值越大越好。针对本节的例子,分别选择K=[2,3,4]这三种不同的聚类个数,来观察一下K-均值算法最终拟合的结果及其成本。

我们可以把画出K-均值聚类结果的代码稍微改造一下,变成一个函数。这个函数会使用K-均值算法来进行聚类拟合,同时会画出按照这个聚类个数拟合后的分类情况:

def fit_plot_kmean_model(n_clusters, X):
    plt.xticks(())
    plt.yticks(())

    # 使用 k-均值算法进行拟合
    kmean = KMeans(n_clusters=n_clusters)
    kmean.fit_predict(X)

    labels = kmean.labels_
    centers = kmean.cluster_centers_
    markers = ['o', '^', '*', 's']
    colors = ['r', 'b', 'y', 'k']

    # 计算成本
    score = kmean.score(X)
    plt.title("k={}, score={}".format(n_clusters, (int)(score)))

    # 画样本
    for c in range(n_clusters):
        cluster = X[labels == c]
        plt.scatter(cluster[:, 0], cluster[:, 1], 
                    marker=markers[c], s=20, c=colors[c])
    # 画出中心点
    plt.scatter(centers[:, 0], centers[:, 1],
                marker='o', c="white", alpha=0.9, s=300)
    for i, c in enumerate(centers):
        plt.scatter(c[0], c[1], marker='$%d$' % i, s=50, c=colors[i])

函数代码略微有点长,但通过解释应该不难理解函数的意图。函数接受两个参数,一个是聚类个数,即K的值,另一个是数据样本。有了这个函数,接下来就简单了,可以很容易分别对[2,3,4]这三种不同的K值情况进行聚类分析,并把聚类结果可视化。

from sklearn.cluster import KMeans

n_clusters = [2, 3, 4]

plt.figure(figsize=(10, 3), dpi=144)
for i, c in enumerate(n_clusters):
    plt.subplot(1, 3, i + 1)
    fit_plot_kmean_model(c, X)

输出图形如下所示:


image.png

3.使用K-均值对文档进行聚类分析

本节介绍如何使用K-均值算法对文档进行聚类分析。假设有一个博客平台,用户在平台上发布博客,我们如何对博客进行聚类分析,以方便展示不同类别下的热门文章呢?

1.准备数据集

为了简化问题,避免进行中文分词,我们仍然使用之前介绍朴素贝叶斯算法时使用的数据集:即mlcomp.org上的20news-18828(这个数据集是分好词的,单词之间以空格分隔)。并且这里只选择语料库里的部分内容来进行聚类分析。假设选择sci.crypt、sci.electronics、sci.med和sci.space这4个类别的文档进行聚类分析。到mlcomp原始语料库里的raw文件夹下,复制对应的文件夹到datasets/clustering/data目录下。

2.加载数据集

准备好数据集后,我们的任务就是把datasets/clustering/data目录下的文档进行聚类分析。你可能有疑问:这些文档不是按照文件夹已经分好类了吗?是的,这是人工标记了的数据。有了人工标记的数据,就可以检验K-均值算法的性能。

首先需要导入数据:

from time import time
from sklearn.datasets import load_files
print("loading documents ...")
t = time()
docs = load_files('datasets/clustering/data')
print("summary: {0} documents in {1} categories.".format(
    len(docs.data), len(docs.target_names)))
print("done in {0} seconds".format(time() - t))

输出如下:

loading documents ...
summary: 3949 documents in 4 categories.
done in 26.920000076293945 seconds

总共有3949篇文章,人工标记在4个类别里。接着把文档转化为TF-IDF向量:

from sklearn.feature_extraction.text import TfidfVectorizer

max_features = 20000
print("vectorizing documents ...")
t = time()
vectorizer = TfidfVectorizer(max_df=0.4, 
                             min_df=2, 
                             max_features=max_features, 
                             encoding='latin-1')
X = vectorizer.fit_transform((d for d in docs.data))
print("n_samples: %d, n_features: %d" % X.shape)
print("number of non-zero features in sample [{0}]: {1}".format(
    docs.filenames[0], X[0].getnnz()))
print("done in {0} seconds".format(time() - t))

这里需要注意TfidfVectorizer的几个参数的选择。max_df=0.4表示如果一个单词在40%的文档里都出现过,则认为是一个高频词,对文档聚类没有帮助,在生成词典时就会剔除这个词。min_df=2表示,如果一个单词的词频太低,小于等于2个,则也把这个单词从词典里剔除。max_features可以进一步过滤词典的大小,它会根据TF-IDF权重从高到低进行排序,然后取前面权重高的单词构成词典。输出如下:

vectorizing documents ...
n_samples: 3949, n_features: 20000
number of non-zero features in sample [datasets/clustering/data\sci.electronics\11902-54322]: 56
done in 1.9150002002716064 seconds

从输出可知,每篇文章构成的向量都是一个稀疏向量,其大部分元素都为0。这也容易理解,我们的词典大小为20000个词,而示例文章中不重复的单词却只有56个。

3.文本聚类分析

接着使用KMeans算法对文档进行聚类分析:

from sklearn.cluster import KMeans

print("clustering documents ...")
t = time()
n_clusters = 4
kmean = KMeans(n_clusters=n_clusters, 
               max_iter=100,
               tol=0.01,
               verbose=1,
               n_init=3)
kmean.fit(X);
print("kmean: k={}, cost={}".format(n_clusters, int(kmean.inertia_)))
print("done in {0} seconds".format(time() - t))

选择聚类个数为4个。max_iter=100表示最多进行100次K-均值迭代。tol=0.1表示中心点移动距离小于0.1时就认为算法已经收敛,停止迭代。verbose=1表示输出迭代过程的详细信息。n_init=3表示进行3遍K-均值运算后求平均值。前面介绍过,在算法刚开始迭代时,会随机选择聚类中心点,不同的中心点可能导致不同的收敛效果,因此多次运算求平均值的方法可以提高算法的稳定性。由于开启了迭代过程信息显示,输出了较多的信息:

clustering documents ...
Initialization complete
Iteration  0, inertia 7488.362
Iteration  1, inertia 3845.708
Iteration  2, inertia 3835.369
Iteration  3, inertia 3828.959
Iteration  4, inertia 3824.555
Iteration  5, inertia 3820.932
Iteration  6, inertia 3818.555
Iteration  7, inertia 3817.377
Iteration  8, inertia 3816.317
Iteration  9, inertia 3815.570
Iteration 10, inertia 3815.351
Iteration 11, inertia 3815.234
Iteration 12, inertia 3815.181
Iteration 13, inertia 3815.151
Iteration 14, inertia 3815.136
Iteration 15, inertia 3815.120
Iteration 16, inertia 3815.113
Iteration 17, inertia 3815.106
Iteration 18, inertia 3815.104
Converged at iteration 18: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration  0, inertia 7494.329
Iteration  1, inertia 3843.474
Iteration  2, inertia 3835.570
Iteration  3, inertia 3828.511
Iteration  4, inertia 3823.826
Iteration  5, inertia 3819.972
Iteration  6, inertia 3817.714
Iteration  7, inertia 3816.666
Iteration  8, inertia 3816.032
Iteration  9, inertia 3815.778
Iteration 10, inertia 3815.652
Iteration 11, inertia 3815.548
Iteration 12, inertia 3815.462
Iteration 13, inertia 3815.424
Iteration 14, inertia 3815.411
Iteration 15, inertia 3815.404
Iteration 16, inertia 3815.402
Converged at iteration 16: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration  0, inertia 7538.349
Iteration  1, inertia 3844.796
Iteration  2, inertia 3828.820
Iteration  3, inertia 3822.973
Iteration  4, inertia 3821.341
Iteration  5, inertia 3820.164
Iteration  6, inertia 3819.181
Iteration  7, inertia 3818.546
Iteration  8, inertia 3818.167
Iteration  9, inertia 3817.975
Iteration 10, inertia 3817.862
Iteration 11, inertia 3817.770
Iteration 12, inertia 3817.723
Iteration 13, inertia 3817.681
Iteration 14, inertia 3817.654
Iteration 15, inertia 3817.628
Iteration 16, inertia 3817.607
Iteration 17, inertia 3817.593
Iteration 18, inertia 3817.585
Iteration 19, inertia 3817.580
Converged at iteration 19: center shift 0.000000e+00 within tolerance 4.896692e-07
kmean: k=4, cost=3815
done in 39.484999895095825 seconds

从输出信息中可以看到,总共进行了3次K-均值聚类分析,分别作了18,16,19次迭代后收敛。这样就把3949个文档进行自动分类了。kmean.labels_里保存的就是这些文档的类别信息。如我们所料,len(kmean.labels_)的值是3949。

len(kmean.labels_)

输出如下:

3949

查看1000到1010这10个文档的聚类情况及其对应的文件名:

kmean.labels_[1000:1010]

输出如下:

array([2, 2, 2, 1, 0, 1, 0, 2, 1, 1])

接着查看对应的文件名:

docs.filenames[1000:1010]

输出如下:

array(['datasets/clustering/data\\sci.crypt\\10888-15289',
       'datasets/clustering/data\\sci.crypt\\11490-15880',
       'datasets/clustering/data\\sci.crypt\\11270-15346',
       'datasets/clustering/data\\sci.electronics\\12383-53525',
       'datasets/clustering/data\\sci.space\\13826-60862',
       'datasets/clustering/data\\sci.electronics\\11631-54106',
       'datasets/clustering/data\\sci.space\\14235-61437',
       'datasets/clustering/data\\sci.crypt\\11508-15928',
       'datasets/clustering/data\\sci.space\\13593-60824',
       'datasets/clustering/data\\sci.electronics\\12304-52801'],
      dtype='<U52')

对比两个输出可以看到,这10个文档基本上正确地归类了。需要说明的是,这里类别1表示sci.crypt,但这不是必然的对应关系。重新进行一次聚类分析可能就不是这个对应关系了。我们还可以选择K为3或者2进行聚类分析,从而彻底打乱原来标记的类别关系。

我们好奇的是:在进行聚类分析的过程中,哪些单词的权重最高,从而较容易地决定一个文章的类别?我们可以查看每种类别文档中,其权重最高的10个单词分别是什么?

from __future__ import print_function
print("Top terms per cluster:")
order_centroids = kmean.cluster_centers_.argsort()[:, ::-1]
terms = vectorizer.get_feature_names()
for i in range(n_clusters):
    print("Cluster %d:" % i, end='')
    for ind in order_centroids[i, :10]:
        print(' %s' % terms[ind], end='')
    print()

理解这段代码的关键在于argsort()函数,它的作用是把一个Numpy数组进行升序排列,返回的是排序后的索引。例如下面的示例代码:

import numpy as np
a = np.array([10, 30, 20, 40])
a.argsort()

输出如下:

array([0, 2, 1, 3], dtype=int32)

即索引为0的元素(10)最小,其次是索引为2的元素(20),再次是索引为1的元素(30),最大的是索引为3的元素(40)。又比如:

a = np.array([10, 30, 20, 40])
a.argsort()[::-1]

输出如下:

array([3, 1, 2, 0], dtype=int32)

[::-1]运算是把升序变为降序,a.argsort()[::-1]的输出为array([3,1,2,0])。

回到我们的代码里,由于kmean.cluster_centers_是二维数组,因此kmean.cluster_centers_.argsort()[:,::-1]语句的含义就是把聚类中心点的不同分量,按照从大到小的顺序进行排序,并且把排序后的元素索引保存在二维数组order_centroids里。vectorizer.get_feature_names()将得到我们的词典单词,根据索引即可得到每个类别里权重最高的那些单词了。输出如下:

Top terms per cluster:
Cluster 0: space henry nasa toronto moon pat zoo shuttle gov orbit
Cluster 1: my any me by know your some do so has
Cluster 2: key clipper encryption chip government will keys escrow we nsa
Cluster 3: geb pitt banks gordon shameful dsl n3jxp chastity cadre surrender

4.聚类算法性能评估

聚类性能评估比较复杂,不像分类那样直观。针对分类问题,我们可以直接计算被错误分类的样本数量,这样可以直接算出分类算法的准确率。聚类问题不能使用绝对数量的方法进行性能评估,原因是,聚类分析后的类别与原来已标记的类别之间不存在必然的一一对应关系。更典型的,针对K-均值算法,我们可以选择K的数值不等于已标记的类别个数。

前面介绍决策树的时候简单介绍过“熵”的概念,它是信息论中最重要的基础概念。熵表示一个系统的有序程度,而聚类问题的性能评估,就是对比经过聚类算法处理后的数据的有序程度,与人工标记的有序程度之间的差异。下面介绍几个常用的聚类算法性能评估指标

1.Adjust Rand Index

Adjust Rand Index是一种衡量两个序列相似性的算法。它的优点是,针对两个随机序列,它的值为负数或接近0。而针对两个结构相同的序列,它的值接近1。而且对类别标签不敏感。下面来看一个简单的例子。

from sklearn import metrics

label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("Adjusted Rand-Index for random sample: %.3f"
      % metrics.adjusted_rand_score(label_true, label_pred))
label_true = [1, 1, 3, 3, 2, 2]
label_pred = [3, 3, 2, 2, 1, 1]
print("Adjusted Rand-Index for same structure sample: %.3f"
      % metrics.adjusted_rand_score(label_true, label_pred))

输出如下:

Adjusted Rand-Index for random sample: -0.239
Adjusted Rand-Index for same structure sample: 1.000
2.齐次性和完整性

根据条件熵分析,可以得到另外两个衡量聚类算法性能的指标,分别是齐次性(homogeneity)和完整性(completeness)。齐次性表示一个聚类元素只由一种类别的元素组成。完整性表示给定的已标记的类别,全部分配到一个聚类里。它们的值均介于[0,1]之间。下面通过一个简单的例子来解释这两个概念。

from sklearn import metrics
label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("Homogeneity score for same structure sample: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [0, 1, 2, 3]
print("Homogeneity score for each cluster come from only one class: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("Homogeneity score for each cluster come from two class: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("Homogeneity score for random sample: %.3f"
      % metrics.homogeneity_score(label_true, label_pred))

输出如下:

Homogeneity score for same structure sample: 1.000
Homogeneity score for each cluster come from only one class: 1.000
Homogeneity score for each cluster come from two class: 0.000
Homogeneity score for random sample: 0.315

针对第一组序列,其结构相同,因此其齐次性输出为1,表示完全一致。奇怪的事情来了,第二组样本[1,1,2,2]和[0,1,2,3]为什么也输出1呢?答案就是齐次性的定义上,聚类元素只由一种已标记的类别元素组成时,其值为1。在我们的例子里,已标记为2个类别,而输出了4个聚类,这样就满足每个聚类元素均来自一种已标记的类别这一条件。同样的道理,针对第三组样本,由于每个聚类元素都来自2个类别的元素,因此其值为0;而针对随机的元素序列,它不为0,这是与Adjust Rand Index不同的地方。

接下来看一组完整性的例子:

from sklearn import metrics
label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("Completeness score for same structure sample: %.3f"
      % metrics.completeness_score(label_true, label_pred))
label_true = [0, 1, 2, 3]
label_pred = [1, 1, 2, 2]
print("Completeness score for each class assign to only one cluster: %.3f"
      % metrics.completeness_score(label_true, label_pred))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("Completeness score for each class assign to two class: %.3f"
      % metrics.completeness_score(label_true, label_pred))
label_true = np.random.randint(1, 4, 6)
label_pred = np.random.randint(1, 4, 6)
print("Completeness score for random sample: %.3f"
      % metrics.completeness_score(label_true, label_pred))

输出如下:

Completeness score for same structure sample: 1.000
Completeness score for each class assign to only one cluster: 1.000
Completeness score for each class assign to two class: 0.000
Completeness score for random sample: 0.457

针对第一组序列,其结构相同,输出为1。针对第二组序列,由于符合完整性的定义,即每个类别的元素都被分配进了同一个聚类里,因此其完整性也为1。针对第三组序列,每个类别的元素都被分配进了两个不同的聚类里,因此其完整性为0。和齐次性一样,它对随机类别的判断能力也比较弱。

从上面的例子中可以看出,齐次性和完整性是一组互补的关系,我们可以把两个指标综合起来,称为V-measure分数。下面来看一个简单的例子:

from sklearn import metrics

label_true = [1, 1, 2, 2]
label_pred = [2, 2, 1, 1]
print("V-measure score for same structure sample: %.3f"
      % metrics.v_measure_score(label_true, label_pred))
label_true = [0, 1, 2, 3]
label_pred = [1, 1, 2, 2]
print("V-measure score for each class assign to only one cluster: %.3f"
      % metrics.v_measure_score(label_true, label_pred))
print("V-measure score for each class assign to only one cluster: %.3f"
      % metrics.v_measure_score(label_pred, label_true))
label_true = [1, 1, 2, 2]
label_pred = [1, 2, 1, 2]
print("V-measure score for each class assign to two class: %.3f"
      % metrics.v_measure_score(label_true, label_pred))

输出如下:

V-measure score for same structure sample: 1.000
V-measure score for each class assign to only one cluster: 0.667
V-measure score for each class assign to only one cluster: 0.667
V-measure score for each class assign to two class: 0.000

针对第一组序列,其结构相同,V-measure输出的值也为1,表示同时满足齐次性和完整性。第二行和第三行的输出,表明V-measure符合对称性法则。

3.轮廓系数

上面介绍的聚类性能评估方法都需要有已标记的类别数据,这个在实践中是很难做到的。如果已经标记了数据,就会直接使用有监督的学习算法,而无监督学习算法的最大优点就是不需要对数据集进行标记。轮廓系数可以在不需要已标记的数据集的前提下,对聚类算法的性能进行评估。

轮廓系数由以下两个指标构成:

  • a:一个样本与其所在相同聚类的点的平均距离;
  • b:一个样本与其距离最近的下一个聚类里的点的平均距离。

针对这个样本,其轮廓系数s的值为:
s = \frac{b-a}{max(a,b)}

针对一个数据集,其轮廓系数s为其所有样本的轮廓系数的平均值。轮廓系数的数值介于[-1,1]之间,-1表示完全错误的聚类,1表示完美的聚类,0表示聚类重叠。

针对前面的例子,可以分别计算本节介绍的几个聚类算法性能评估指标,综合来看聚类算法的性能:

from sklearn import metrics

labels = docs.target
print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels, kmean.labels_))
print("Completeness: %0.3f" % metrics.completeness_score(labels, kmean.labels_))
print("V-measure: %0.3f" % metrics.v_measure_score(labels, kmean.labels_))
print("Adjusted Rand-Index: %.3f"
      % metrics.adjusted_rand_score(labels, kmean.labels_))
print("Silhouette Coefficient: %0.3f"
      % metrics.silhouette_score(X, kmean.labels_, sample_size=1000))

输出如下:

Homogeneity: 0.459
Completeness: 0.519
V-measure: 0.487
Adjusted Rand-Index: 0.328
Silhouette Coefficient: 0.004

可以看到模型性能很一般。可能的一个原因是数据集质量不高,当然我们也可以阅读原始的语料库,检验一下如果通过人工标记,是否能够标记出这些文章的正确分类。另外,针对my、any、me、by、know、your、some、do、so、has,这些都是没有特征的单词,即使人工标记,也无法判断这些单词应该属于哪种类别的文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容