在学习聚类算法得时候并没有涉及到评估指标,主要原因是聚类算法属于非监督学习,并不像分类算法那样可以使用训练集或测试集中得数据计算准确率、召回率等。那么如何评估聚类算法得好坏呢?好的聚类算法,一般要求类簇具有:
对于聚类算法大致可分为 2类度量标准:
当一个聚类结果是基于数据聚类自身进行评估的,这一类叫做内部评估方法。如果某个聚类算法聚类的结果是类间相似性低,类内相似性高,那么内部评估方法会给予较高的分数评价。不过内部评价方法的缺点是:
这些内部评估方法可以基于特定场景判定一个算法要优于另一个,不过这并不表示前一个算法得到的结果比后一个结果更有意义。这里的意义是假设这种结构事实上存在于数据集中的,如果一个数据集包含了完全不同的数据结构,或者采用的评价方法完全和算法不搭,比如k-means只能用于凸集数据集上,许多评估指标也是预先假设凸集数据集。在一个非凸数据集上不论是使用k-means还是使用假设凸集的评价方法,都是徒劳的。
该统计参数计算的是拟合数据和原始数据对应点的误差的平方和,计算公式如下:
SSE越接近于0,说明模型选择和拟合更好,数据预测也越成功。
#断崖碎石图选取最优K值import pandas as pd from sklearn.cluster import KMeans import matplotlib.pyplot as plt '利用SSE选择k' SSE = [] # 存放每次结果的误差平方和 for k in range(1,9): estimator = KMeans(n_clusters=k) # 构造聚类器 estimator.fit(df[['calories','sodium','alcohol','cost']]) SSE.append(estimator.inertia_) N = range(1,9) plt.xlabel('k') plt.ylabel('SSE') plt.plot(N,SSE,'o-') plt.show()
轮廓系数适用于实际类别信息未知的情况。对于单个样本,设a是与它同类别中其他样本的平均距离,b是与它距离最近不同类别中样本的平均距离,其轮廓系数为:
对于一个样本集合,它的轮廓系数是所有样本轮廓系数的平均值。轮廓系数的取值范围是[-1,1],同类别样本距离越相近不同类别样本距离越远,分数越高。缺点:不适合基高密度的聚类算法DBSCAN。
from sklearn import metricsfrom sklearn.metrics import pairwise_distancesfrom sklearn import datasetsdataset = datasets.load_iris()X = dataset.datay = dataset.target import numpy as npfrom sklearn.cluster import KMeanskmeans_model = KMeans(n_clusters=3, random_state=1).fit(X)labels = kmeans_model.labels_metrics.silhouette_score(X, labels, metric='euclidean')
在真实的分群label不知道的情况下,Calinski-Harabasz可以作为评估模型的一个指标。Calinski-Harabasz指标通过计算类中各点与类中心的距离平方和来度量类内的紧密度,通过计算各类中心点与数据集中心点距离平方和来度量数据集的分离度,CH指标由分离度与紧密度的比值得到。从而,CH越大代表着类自身越紧密,类与类之间越分散,即更优的聚类结果。
其中m为训练样本数,k是类别个数,是类别之间协方差矩阵,是类别内部数据协方差矩阵,为矩阵的迹。也就是说,类别内部数据的协方差越小越好,类别之间的协方差越大越好,这样的Calinski-Harabasz分数会高。同时,数值越小可以理解为:组间协方差很小,组与组之间界限不明显。
优点
缺点
import numpy as npfrom sklearn.cluster import KMeanskmeans_model = KMeans(n_clusters=3, random_state=1).fit(X)labels = kmeans_model.labels_print(metrics.calinski_harabaz_score(X, labels))
CP计算每一个类各点到聚类中心的平均距离CP越低意味着类内聚类距离越近。著名的 K-Means 聚类算法就是基于此思想提出的。缺点:没有考虑类间效果。
SP计算各聚类中心两两之间平均距离,SP越高意味类间聚类距离越远。缺点:没有考虑类内效果。
DB计算任意两类别的类内距离平均距离(CP)之和除以两聚类中心距离求最大值。DB越小意味着类内距离越小同时类间距离越大。该指标的计算公式:
其中n是类别个数,是第i个类别的中心,是类别i中所有的点到中心的平均距离;
中心点和之间的距离。算法生成的聚类结果越是朝着类内距离最小(类内相似性最大)和类间距离最大(类间相似性最小)变化,那么Davies-Bouldin指数就会越小。缺点:因使用欧式距离所以对于环状分布聚类评测很差。
from sklearn import datasets from sklearn.cluster import KMeans from sklearn.metrics import davies_bouldin_score from sklearn.datasets.samples_generator import make_blobs # loading the dataset X, y_true = make_blobs(n_samples=300, centers=4, cluster_std=0.50, random_state=0) # K-Means kmeans = KMeans(n_clusters=4, random_state=1).fit(X) # we store the cluster labels labels = kmeans.labels_ print(davies_bouldin_score(X, labels))
DVI计算任意两个簇元素的最短距离(类间)除以任意簇中的最大距离(类内)。DVI越大意味着类间距离越大同时类内距离越小。
其中
表示类别,之间的距离;
表示类别内部的类内距离:
因为内部评估方法是搜寻类内相似最大,类间相似最小,所以算法生成的聚类结果的Dunn指数越高,那么该算法就越好。缺点:对离散点的聚类测评很高、对环状分布测评效果差。
import pandas as pd from sklearn import datasets from jqmcvi import base # loading the dataset X = datasets.load_iris() df = pd.DataFrame(X.data) # K-Means from sklearn import cluster k_means = cluster.KMeans(n_clusters=3) k_means.fit(df) #K-means training y_pred = k_means.predict(df) # We store the K-means results in a dataframe pred = pd.DataFrame(y_pred) pred.columns = ['Type'] # we merge this dataframe with df prediction = pd.concat([df, pred], axis = 1) # We store the clusters clus0 = prediction.loc[prediction.Species == 0] clus1 = prediction.loc[prediction.Species == 1] clus2 = prediction.loc[prediction.Species == 2] cluster_list = [clus0.values, clus1.values, clus2.values] print(base.dunn(cluster_list))
在外部评估方法中,聚类结果是通过使用没被用来做训练集的数据进行评估。例如已知样本点的类别信息和一些外部的基准。这些基准包含了一些预先分类好的数据,比如由人基于某些场景先生成一些带label的数据,因此这些基准可以看成是金标准。这些评估方法是为了测量聚类结果与提供的基准数据之间的相似性。然而这种方法也被质疑不适用真实数据。
纯度(Purity)是一种简单而透明的评估手段,为了计算纯度(Purity),我们把每个簇中最多的类作为这个簇所代表的类,然后计算正确分配的类的数量,然后除以N。形式化表达如下:
其中:
上述过程即给每个聚类簇分配一个类别,且这个类别的样本在该簇中出现的次数最多,然后计算所有 K 个聚类簇的这个次数之和再归一化即为最终值。Purity值在0~1之间 ,越接近1表示聚类结果越好。
如图认为x代表一类文档,o代表一类文档,方框代表一类文档。如上图的purity = ( 3+ 4 + 5) / 17 = 0.71,其中第一类正确的有5个,第二个4个,第三个3个,总文档数17。
当簇的数量很多的时候,容易达到较高的纯度——特别是,如果每个文档都被分到独立的一个簇中,那么计算得到的纯度就会是1。因此,不能简单用纯度来衡量聚类质量与聚类数量之间的关系。另外Purity无法用于权衡聚类质量与簇个数之间的关系。
def purity(result, label): # 计算纯度 total_num = len(label) cluster_counter = collections.Counter(result) original_counter = collections.Counter(label) t = [] for k in cluster_counter: p_k = [] for j in original_counter: count = 0 for i in range(len(result)): if result[i] == k and label[i] == j: # 求交集 count += 1 p_k.append(count) temp_t = max(p_k) t.append(temp_t) return sum(t)/total_num
互信息(Normalized Mutual Information)是用来衡量两个数据分布的吻合程度。也是一有用的信息度量,它是指两个事件集合之间的相关性。互信息越大,词条和类别的相关程度也越大。NMI (Normalized Mutual Information) 即归一化互信息:
其中,表示互信息(Mutual Information),为熵,当 log 取 2 为底时,单位为 bit,取 e 为底时单位为 nat。
其中,
可以分别看作样本 (document) 属于聚类簇, 属于类别, 同时属于的概率。第二个等价式子则是由概率的极大似然估计推导而来。
互信息
表示给定类簇信息的前提条件下,类别信息的增加量,或者说其不确定度的减少量。直观地,互信息还可以写出如下形式:
互信息的最小值为 0, 当类簇相对于类别只是随机的, 也就是说两者独立的情况下,对于未带来任何有用的信息.如果得到的与关系越密切, 那么
值越大。如果完整重现了, 此时互信息最大:
当K=N时,即类簇数和样本个数相等,MI 也能达到最大值。所以 MI 也存在和纯度类似的问题,即它并不对簇数目较大的聚类结果进行惩罚,因此也不能在其他条件一样的情况下,对簇数目越小越好的这种期望进行形式化。NMI 则可以解决上述问题,因为熵会随着簇的数目的增长而增大。当K=N时,
会达到其最大值
, 此时就能保证 NMI 的值较低。之所以采用
作为分母是因为它是
的紧上界, 因此可以保证
。
示例:
gnd 是 ground truth 的意思,grps 表示聚类后的 groups. 问题:计算序列 gnd 和 grps 的 NMI.
先计算联合概率分布
代码实现:
def NMI(result, label): # 标准化互信息 total_num = len(label) cluster_counter = collections.Counter(result) original_counter = collections.Counter(label) # 计算互信息量 MI = 0 eps = 1.4e-45 # 取一个很小的值来避免log 0 for k in cluster_counter: for j in original_counter: count = 0 for i in range(len(result)): if result[i] == k and label[i] == j: count += 1 p_k = 1.0*cluster_counter[k] / total_num p_j = 1.0*original_counter[j] / total_num p_kj = 1.0*count / total_num MI += p_kj * math.log(p_kj /(p_k * p_j) + eps, 2) # 标准化互信息量 H_k = 0 for k in cluster_counter: H_k -= (1.0*cluster_counter[k] / total_num) * math.log(1.0*cluster_counter[k] / total_num+eps, 2) H_j = 0 for j in original_counter: H_j -= (1.0*original_counter[j] / total_num) * math.log(1.0*original_counter[j] / total_num+eps, 2) return 2.0 * MI / (H_k + H_j)
sklearn中自带的方法:
from sklearn.metrics.cluster import normalized_mutual_info_scoreprint(normalized_mutual_info_score([0, 0, 1, 1], [0, 0, 1, 1]))
已知聚类标签与真实标签,互信息(mutual information)能够测度两种标签排列之间的相关性,同时忽略标签中的排列。有两种不同版本的互信息以供选择,一种是Normalized Mutual Information(NMI),一种是Adjusted Mutual Information(AMI)。
假设U与V是对N个样本标签的分配情况,则两种分布的熵(熵表示的是不确定程度)分别为:
其中:
U与V之间的互信息(MI)定义为:
其中
是随机选择的对象落入两个类的概率和。
调整互信息(Adjusted mutual information)定义为:
MI的期望可以用以下公式来计算。在这个方程式中,
为元素的数量,
为元素的数量:
利用基于互信息的方法来衡量聚类效果需要实际类别信息,MI与NMI取值范围为[0,1],AMI取值范围为[-1,1],它们都是值越大意味着聚类结果与真实情况越吻合。
优点
缺点:
from sklearn import metricslabels_true = [0, 0, 0, 1, 1, 1]labels_pred = [0, 0, 1, 1, 2, 2] print(metrics.adjusted_mutual_info_score(labels_true, labels_pred))
兰德指数 (Rand index, RI), 将聚类看成是一系列的决策过程,即对文档集上所有N(N-1)/2个文档 (documents) 对进行决策。当且仅当两篇文档相似时,我们将它们归入同一簇中。
Positive:
Negative:
RI 则是计算「正确决策」的比率(精确率, accuracy):
RI取值范围为[0,1],值越大意味着聚类结果与真实情况越吻合。
def contingency_table(result, label): total_num = len(label) TP = TN = FP = FN = 0 for i in range(total_num): for j in range(i + 1, total_num): if label[i] == label[j] and result[i] == result[j]: TP += 1 elif label[i] != label[j] and result[i] != result[j]: TN += 1 elif label[i] != label[j] and result[i] == result[j]: FP += 1 elif label[i] == label[j] and result[i] != result[j]: FN += 1 return (TP, TN, FP, FN) def rand_index(result, label): TP, TN, FP, FN = contingency_table(result, label) return 1.0*(TP + TN)/(TP + FP + FN + TN)
对于随机结果,RI并不能保证分数接近零。为了实现“在聚类结果随机产生的情况下,指标应该接近零”,调整兰德系数(Adjusted rand index)被提出,它具有更高的区分度:
ARI取值范围为[-1,1],值越大意味着聚类结果与真实情况越吻合。从广义的角度来讲,ARI衡量的是两个数据分布的吻合程度。
优点:
缺点:
from sklearn import metricslabels_true = [0, 0, 0, 1, 1, 1]labels_pred = [0, 0, 1, 1, 2, 2] print(metrics.adjusted_rand_score(labels_true, labels_pred))
这是基于上述RI方法衍生出的一个方法,我们可以 FN 罚更多,通过取
中的大于 1, 此时实际上也相当于赋予召回率更大的权重:
RI方法有个特点就是把准确率和召回率看得同等重要,事实上有时候我们可能需要某一特性更多一点,这时候就适合F值方法。
def precision(result, label): TP, TN, FP, FN = contingency_table(result, label) return 1.0*TP/(TP + FP) def recall(result, label): TP, TN, FP, FN = contingency_table(result, label) return 1.0*TP/(TP + FN) def F_measure(result, label, beta=1): prec = precision(result, label) r = recall(result, label) return (beta*beta + 1) * prec * r/(beta*beta * prec + r)
Fowlkes-Mallows Scores(FMI) FMI是成对的precision(精度)和recall(召回)的几何平均数。取值范围为 [0,1],越接近1越好。定义为:
代码实现:
from sklearn import metricslabels_true = [0, 0, 0, 1, 1, 1]labels_pred = [0, 0, 1, 1, 2, 2] print(metrics.fowlkes_mallows_score(labels_true, labels_pred))
说V-measure之前要先介绍两个指标:
同质性和完整性分数基于以下公式得出:
其中
是给定给定簇赋值的类的条件熵,由以下公式求得:
是类熵,公式为:
其中,n是样本总数,和分别属于类c和类k的样本数,而是从类c划分到类k的样本数量。条件熵H(K|C)和类熵H(K),根据以上公式对称求得。
V-measure是同质性homogeneity和完整性completeness的调和平均数,V-measure取值范围为 [0,1],越大越好,但当样本量较小或聚类数据较多的情况,推荐使用AMI和ARI。公式:
代码实现:
from sklearn import metricslabels_true = [0, 0, 0, 1, 1, 1]labels_pred = [0, 0, 1, 1, 2, 2] print(metrics.homogeneity_score(labels_true, labels_pred))print(metrics.completeness_score(labels_true, labels_pred))print(metrics.v_measure_score(labels_true, labels_pred))
优点:
缺点:
该指数用于量化两个数据集之间的相似性,该值得范围为0-1.其中越大表明两个数据集越相似:
该指数和近年来的IOU计算方法一致
该指数是基于jaccard指数上将TP的权重置为2倍。