K-均值(K-means)算法是一种典型的无监督机器学习算法,用来解决聚类问题(Clustering)。由于数据标记需要耗费巨大的人力物力,无监督或者半监督学习算法不需要对数据进行标记,可以大大减少工作量。
1.K-均值算法原理
我们需要注意聚类问题和分类问题的区别。针对监督式学习算法,如K-近邻算法,其输入数据是已经标记了的,,...,,目标是找出分类边界,然后对新的数据进行分类。而无监督式学习算法,如K-均值算法,只给出一组无标记的数据集,,...,,目标是找出这组数据的模式特征,如哪些数据是同一种类型的,哪些数据是另外一种类型。典型的无监督式学习包括市场细分,即通过分析用户数据,把一个产品的市场进行细分,找出细分人群。另外一个是社交网络分析,分析社交网络中参与人员的不同特点,根据特点区分出不同群体。这些都是无监督式学习里的聚类(Clustering)问题。
K-均值算法包含以下两个步骤:
(1)给聚类中心分配点。计算所有的训练样本,把每个训练样例分配到距离其最近的聚类中心所在的类别里。
(2)移动聚类中心。新的聚类中心移动到这个聚类所有的点的平均值处。
一直重复上面的动作,直到聚类中心不再移动为止,这时就探索出了数据集的结构了。
我们也可以用数学方式来描述K-均值算法。算法有两个输入:一个是K,表示选取的聚类的个数;另外一个是训练数据集,,...,。
(1)随机选择K个初始聚类中心,,...,。
(2)计算数据集中的每个点分别到这K个聚类中心,,...,的距离,记录距离最短的聚类中心,然后把归入这个类,即令的类别标记。这里一般使用来计算距离。
(3)重新计算聚类中心,移动聚类中心到这个聚类的均值处。即,其中c表示该聚类样本点个数,表示该聚类第d个样本点。
(4)重复上述过程,直到所有聚类中心不再移动为止。
1.K-均值算法的成本函数
根据成本函数的定义,成本即模型预测值与实际值的误差,据此不难得出K-均值算法的成本函数:
其中,是训练样例分配的聚类序号;是所属聚类的中心。K-均值算法的成本函数的物理意义就是,训练样本到其所属的聚类中心的平均距离。
2.随机初始化聚类中心点
假设K是聚类的个数,m是训练样本的个数,那么必定有K<m。在随机初始化时,随机从m个训练样本中选择K个样本作为聚类中心点。这是正式推荐的随机初始化聚类中心的做法。
在实际解决问题时,最终的聚类结果会和随机初始化的聚类中心点有关。即不同的随机初始化的聚类中心点可能得到不同的最终聚类结果。因为成本函数可能会收敛在一个局部最优解,而不是全局最优解上。有一个解决办法就是多做几次随机初始化的动作,然后训练出不同的聚类中心及聚类节点分配方案,然后使用这些值算出成本函数,从中选择成本最小的那个方案。
假设我们做100次K-均值算法,每次都执行如下步骤:
(1)随机选择K个聚类中心点
(2)运行K-均值算法,算出,,...,和,,...,。
(3)使用,,...,和,,...,算出最终的成本值。
找出成本最小的方案。这样就可以适当加大运算次数,从而求出全局最优解。
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')
结果如图所示:
接着使用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])
输出结果如图所示:
前面说过,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)
输出图形如下所示:
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为其所有样本的轮廓系数的平均值。轮廓系数的数值介于[-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,这些都是没有特征的单词,即使人工标记,也无法判断这些单词应该属于哪种类别的文章。