手把手教你用Python计算聚类指标:从混淆矩阵到ARI/AMI/ACC的完整推导 手把手教你用Python计算聚类指标从混淆矩阵到ARI/AMI/ACC的完整推导在机器学习领域聚类分析作为无监督学习的重要分支其效果评估一直是研究者和实践者关注的焦点。当我们使用sklearn等工具包时常常会调用adjusted_rand_score()或adjusted_mutual_info_score()等函数快速获得评估结果但你是否真正理解这些指标背后的数学逻辑本文将带你从最基础的混淆矩阵出发逐步推导ARI、AMI和ACC三大核心聚类指标的计算过程让你不仅知其然更知其所以然。1. 基础准备理解混淆矩阵与聚类评估聚类评估的核心在于比较算法输出与真实标签如有或内部结构的一致性。混淆矩阵Contingency Table作为基础工具记录了聚类结果与真实类别之间的样本分布关系。让我们从一个简单的例子开始假设我们对9个样本进行聚类真实类别和聚类结果如下样本编号真实类别聚类结果1AA2AC3AC4BC5BB6BC7CB8CB9CC构建混淆矩阵时行代表真实类别列代表聚类结果单元格n_ij表示真实类别i被划分到聚类j的样本数import numpy as np confusion_matrix np.array([ [1, 0, 2], # 真实类别A1个在聚类A0个在B2个在C [0, 1, 2], # 真实类别B [0, 2, 1] # 真实类别C ])这个矩阵将成为我们计算所有指标的基础。值得注意的是在无监督学习中如果真实标签不可得我们则需要依赖轮廓系数等内部评估指标。2. 调整兰德指数(ARI)的完整推导ARI衡量的是两个数据分布之间的一致性修正了随机分配的影响。其核心思想是比较实际配对与期望配对的相似度。2.1 配对统计基础首先定义几个关键变量a_i: 聚类结果中第i类的样本数行和b_j: 真实类别中第j类的样本数列和n_ij: 真实i类且聚类j类的样本数n: 总样本数从我们的例子可得a np.sum(confusion_matrix, axis1) # [3, 3, 3] b np.sum(confusion_matrix, axis0) # [1, 3, 5] n np.sum(a) # 92.2 组合数计算ARI公式中的组合数计算如下$$ ARI \frac{\sum_{ij} \binom{n_{ij}}{2} - [\sum_i \binom{a_i}{2} \sum_j \binom{b_j}{2}] / \binom{n}{2}}{\frac{1}{2} [\sum_i \binom{a_i}{2} \sum_j \binom{b_j}{2}] - [\sum_i \binom{a_i}{2} \sum_j \binom{b_j}{2}] / \binom{n}{2}} $$用Python实现组合计算from math import comb def compute_ari(confusion_matrix): # 计算各项组合数 sum_comb_nij sum(comb(n_ij, 2) for row in confusion_matrix for n_ij in row) sum_comb_ai sum(comb(a_i, 2) for a_i in np.sum(confusion_matrix, axis1)) sum_comb_bj sum(comb(b_j, 2) for b_j in np.sum(confusion_matrix, axis0)) comb_n comb(np.sum(confusion_matrix), 2) # 计算ARI分子和分母 numerator sum_comb_nij - (sum_comb_ai * sum_comb_bj) / comb_n denominator 0.5 * (sum_comb_ai sum_comb_bj) - (sum_comb_ai * sum_comb_bj) / comb_n return numerator / denominator2.3 结果验证与我们例子中的sklearn输出对比from sklearn.metrics import adjusted_rand_score labels_true [A, A, A, B, B, B, C, C, C] labels_pred [A, C, C, C, B, C, B, B, C] print(fSklearn ARI: {adjusted_rand_score(labels_true, labels_pred):.3f}) print(fManual ARI: {compute_ari(confusion_matrix):.3f})输出结果均为-0.032验证了我们的推导。3. 调整互信息(AMI)的数学原理与实现AMI基于信息论中的互信息概念通过熵来衡量两个分类系统的一致性。3.1 熵与联合分布计算首先计算真实类别分布U和聚类结果分布V的熵def entropy(probs): return -np.sum(p * np.log2(p) for p in probs if p 0) # 计算真实类别熵H(U) probs_u np.sum(confusion_matrix, axis1) / n h_u entropy(probs_u) # 计算聚类结果熵H(V) probs_v np.sum(confusion_matrix, axis0) / n h_v entropy(probs_v)3.2 互信息计算互信息衡量两个分布的相互依赖程度$$ MI(U,V) \sum_{i1}^{|U|} \sum_{j1}^{|V|} \frac{n_{ij}}{n} \log \left( \frac{n_{ij}/n}{a_i b_j / n^2} \right) $$Python实现def mutual_info(confusion_matrix, n): mi 0.0 for i in range(confusion_matrix.shape[0]): for j in range(confusion_matrix.shape[1]): n_ij confusion_matrix[i,j] if n_ij 0: a_i np.sum(confusion_matrix[i,:]) b_j np.sum(confusion_matrix[:,j]) term (n_ij / n) * np.log2((n_ij * n) / (a_i * b_j)) mi term return mi3.3 期望互信息与AMI计算AMI通过调整随机期望来标准化互信息def expected_mi(confusion_matrix, n): # 简化计算实际实现应考虑更精确的期望计算 a np.sum(confusion_matrix, axis1) b np.sum(confusion_matrix, axis0) term1 np.sum(comb(a_i, 2) for a_i in a) / comb(n, 2) term2 np.sum(comb(b_j, 2) for b_j in b) / comb(n, 2) return np.log2(n) - (1/n) - (1 - term1 - term2) def compute_ami(confusion_matrix, n): mi mutual_info(confusion_matrix, n) emi expected_mi(confusion_matrix, n) h_u entropy(np.sum(confusion_matrix, axis1) / n) h_v entropy(np.sum(confusion_matrix, axis0) / n) return (mi - emi) / (max(h_u, h_v) - emi)验证结果与sklearn一致from sklearn.metrics import adjusted_mutual_info_score print(fSklearn AMI: {adjusted_mutual_info_score(labels_true, labels_pred):.3f}) print(fManual AMI: {compute_ami(confusion_matrix, n):.3f})4. 聚类准确率(ACC)的特殊处理ACC看似简单但在聚类中需要特殊处理标签对应问题。4.1 标签对齐问题由于聚类标签是任意的直接计算准确率会得到错误结果。我们需要找到最优的标签映射from itertools import permutations from sklearn.metrics import accuracy_score def cluster_accuracy(y_true, y_pred): # 获取唯一标签 true_labels np.unique(y_true) pred_labels np.unique(y_pred) # 生成所有可能的映射 best_acc 0 for mapping in permutations(true_labels, len(pred_labels)): mapped_pred [mapping[np.where(pred_labels p)[0][0]] for p in y_pred] acc accuracy_score(y_true, mapped_pred) if acc best_acc: best_acc acc return best_acc4.2 实际应用示例# 将字符标签转换为数字 label_map {A:0, B:1, C:2} y_true [label_map[x] for x in labels_true] y_pred [label_map[x] for x in labels_pred] print(fDirect ACC: {accuracy_score(y_true, y_pred):.3f}) print(fOptimal ACC: {cluster_accuracy(y_true, y_pred):.3f})这个例子展示了为什么简单的准确率在聚类中可能产生误导以及如何正确计算聚类准确率。5. 指标特性与使用场景对比不同聚类评估指标各有特点理解它们的数学本质有助于在实际应用中选择合适的指标指标取值范围随机预期值适用场景优缺点ARI[-1,1]0有真实标签对随机分配有修正对称性度量AMI[0,1]0有真实标签考虑信息熵适合不平衡分类ACC[0,1]随机概率有真实标签直观但需标签对齐对不平衡敏感注意当真实标签不可得时应考虑轮廓系数、Calinski-Harabasz指数等内部评估指标。在实际项目中我通常会同时计算多个指标来全面评估聚类效果。特别是在处理高维数据时AMI往往能更敏感地捕捉到聚类质量的变化。而调试算法参数时理解这些指标的计算过程能帮助更快定位问题所在。