加载数据集
已标记好类别的四个文档数据集:(网络安全,电子学,医学medical,太空)
import matplotlib.pyplot as plt
import numpy as np
from time import time
from sklearn.datasets import load_files
t = time()
docs = load_files(r'C:\Users\Qiuyi\Desktop\scikit-learn code\code\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))
summary: 3949 documents in 4 categories.
done in 5.72968864440918 seconds
TF-IDF向量化
这里需要注意 TfidfVectorizer 的几个参数。
max_df=0.4 表示如果一个单词在40%的文档里都出现过,则认为这是一个高频词,对文档聚类没有帮助,在生成词典时就会剔除这个词。
min_df=2 表示,如果一个单词的词频太低,只在两个以下(包含两个)的文档里出现,则也把这个单词从词典里剔除。
max_features 可以进一步过滤词典的大小,它会根据 TF-IDF 权重从高到低进行排序,然后取前面权重高的单词构成词典。
from sklearn.feature_extraction.text import TfidfVectorizer
max_features = 20000
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))
n_samples: 3949, n_features: 20000
number of non-zero features in sample [C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.electronics\11902-54322]: 56
done in 2.2819015979766846 seconds
从输出可知,我们的一篇文章构成的向量是一个稀疏向量,其大部分元素都为0这也容易理解,我们的词典大小为20000个,而示例文章中不重复的单词却只有56个。
K-means 聚类
Parameters:
n_clusters:我们选择的聚类个数为4个。
max_iter=100表示最多进行100次k均值选代。
tol=0.1表示中心点移动距离小于0.1时就认为算法已经收敛,停止迭代。
verbose = 1表示输出送代的过程信息。
n_init=3表示进行3次k均值运算后求平均值,前面介绍过,在算法刚开始送代时,会随机选择聚类中心点,不同的中心点可能导致不同的收敛效果,因此多次运算求平均值的方法可以提供算法的稳定性。
Attributes:
inertia_ : float,Sum of squared distances of samples to their closest cluster center.
cluster_centers_ : array, [n_clusters, n_features],Coordinates of cluster centers.
labels_ : Labels of each point
from sklearn.cluster import KMeans
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))
Initialization complete
Iteration 0, inertia 7446.126
Iteration 1, inertia 3842.619
…
Iteration 11, inertia 3822.036
Iteration 12, inertia 3822.010
Converged at iteration 12: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration 0, inertia 7589.733
Iteration 1, inertia 3842.690
…
Iteration 49, inertia 3814.997
Iteration 50, inertia 3814.995
Converged at iteration 50: center shift 0.000000e+00 within tolerance 4.896692e-07
Initialization complete
Iteration 0, inertia 7565.903
Iteration 1, inertia 3852.316
…
Iteration 26, inertia 3818.030
Iteration 27, inertia 3818.028
Converged at iteration 27: center shift 0.000000e+00 within tolerance 4.896692e-07
kmean: k=4, cost=3814
done in 50.47910118103027 seconds
从输出信息中可以看到,总共进行了3次k均值聚类分析,分别做了12,50,27次迭代后收敛。这样就把3949个文档进行自动分类了。
kmean.labels_ 里保存的就是这些文档的类别信息。
len(kmean.labels_)
3949
如我们所预料,len(kmean.labels_) 的值是3949,还可以通过 kmean.labels_[1000:1010] 查看1000~1010这10个文档的分类情况。
kmean.labels_[1000:1010]
array([0, 0, 0, 1, 2, 1, 2, 0, 1, 1])
与真实所在类别进行对比:发现第9个分错了。实际在sci.space被分在了sci.electronics。
docs.filenames[1000:1010]
array([‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.crypt\10888-15289’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.crypt\11490-15880’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.crypt\11270-15346’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.electronics\12383-53525’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.space\13826-60862’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.electronics\11631-54106’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.space\14235-61437’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.crypt\11508-15928’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.space\13593-60824’,
‘C:\Users\Qiuyi\Desktop\scikit-learn code\code\datasets\clustering\data\sci.electronics\12304-52801’],
dtype=’<U98’)
单词权重分析
argsort():
把一个 numpy 数组进行升序排列,返回的是排序后的索引。[::-1]运算是把升序变为降序。
由于 kmean.cluster_centers_ 是二维数组,因此 kmean.cluster_centers_.argsort()[:, ::-1]语句的含义就是把聚类中心点的不同分量,按照从大到小的顺序进行排序,并且把排序后的元素索引保存在二维数组 order_centroids 里。
vectorizer.get_feature_names() 将得到我们的词典单词,根据索引即可得到每个类别里权重最高的那些单词了。
from __future__ import print_function
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()
Cluster 0: key clipper chip encryption government will keys escrow we nsa
Cluster 1: my any me by your know some do has so
Cluster 2: space henry nasa toronto pat shuttle zoo moon we orbit
Cluster 3: geb pitt banks gordon shameful dsl n3jxp chastity cadre surrender
Cluster 1的效果不好,因为这几个单词太没特点了,可以是任何类别。
Cluster 0的效果比较高,一看就知道是关于网络安全的,对应的是sci.crypt。
Cluster 2是sci.space,Cluster 3是sci.med。
聚类算法性能评估
分类问题,我们可以直接计算被错误分类的样本数量,这样可以直接算出分类算法的准确率。
聚类问题,由于没有标记,所以不能使用绝对数量的方法进行性能评估。
更典型地,针对k-均值算法,我们可以选择k的数值不等于己标记的类别个数。
“熵”,是信息论中最重要的基础概念。熵表示一个系统的有序程度,而聚类问题的性能评估,就是对比经过聚类算法处理后的数据的有序程度,与人工标记的类别的有序程度之间的差异。
1. Adjust Rand Index(调整兰德指数)
Adjust Rand Index是一种衡量两个序列相似性的算法。
优点:
对任意数量的聚类中心和样本数,随机聚类的ARI都非常接近于0;
取值在[-1,1]之间,负数代表结果不好,越接近于1越好;
可用于聚类算法之间的比较。
缺点:
ARI需要真实标签
2. Homogeneity,Completeness,V-measure
同质性homogeneity:每个群集只包含单个类的成员;
完整性completeness:给定类的所有成员都分配给同一个群集;
V-measure是同质性homogeneity和完整性completeness的调和平均数。
优点:
分数明确:从0到1反应出最差到最优的表现;
解释直观:差的调和平均数可以在同质性和完整性方面做定性的分析;
对簇结构不作假设:可以比较两种聚类算法如k均值算法和谱聚类算法的结果。
缺点:
以前引入的度量在随机标记方面没有规范化,这意味着,根据样本数,集群和先验知识,完全随机标签并不总是产生相同的完整性和均匀性的值,所得调和平均值V-measure也不相同。特别是,随机标记不会产生零分,特别是当簇的数量很大时。
当样本数大于一千,聚类数小于10时,可以安全地忽略该问题。对于较小的样本量或更大数量的集群,使用经过调整的指数(如调整兰德指数)更为安全。
3. Silhouette Coefficient
轮廓系数适用于实际类别信息未知的情况。对于单个样本,设a是与它同类别中其他样本的平均距离,b是与它距离最近不同类别中样本的平均距离,其轮廓系数为:
s
=
b
−
a
m
a
x
(
a
,
b
)
s=\frac{b-a}{max(a,b)}
s=max(a,b)b−a对于一个样本集合,它的轮廓系数是所有样本轮廓系数的平均值。轮廓系数的取值范围是[-1,1],同类别样本距离越相近不同类别样本距离越远,分数越高。
from sklearn import metrics
labels = docs.target
print("Adjusted Rand-Index: %.3f"
% metrics.adjusted_rand_score(labels, kmean.labels_))
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("Silhouette Coefficient: %0.3f"
% metrics.silhouette_score(X, kmean.labels_, sample_size=1000))
Adjusted Rand-Index: 0.237
Homogeneity: 0.375
Completeness: 0.554
V-measure: 0.447
Silhouette Coefficient: 0.004
参考:
argsort()
a = np.array([[20, 10, 30, 40], [100, 300, 200, 400], [1, 5, 3, 2]])
a.argsort()[:, ::-1]
#运行结果:
array([[3, 2, 0, 1],
[3, 1, 2, 0],
[1, 2, 3, 0]], dtype=int64)