聚类算法评估指标之SSE介绍
1 课程概览
本课讲解KMeans聚类算法的评估指标SSE(误差平方和)。介绍三种评估方法:SSE、SC、CH。重点讲解SSE的计算公式和含义。
2 核心概念与定义
- SSE:Sum of Squared Error,误差平方和。
- 内聚:簇内每个样本点到质心的距离。
- 耦合:簇与簇之间的关系。
- 高内聚低耦合:开发原则,类比聚类评估。
- 误差:真实值(样本)减去质心点的距离。
- 质心:簇的中心点。
3 算法与模型详解
3.1 三种评估方法
聚类评估指标:
- SSE:误差平方和(本课重点)
- SC:轮廓系数
- CH:Calinski-Harabasz指数
说明:聚类属于扩展了解内容,未来用得最多的是分类,其次是回归,再其次是聚类。
3.2 SSE定义
SSE = Sum of Squared Error(误差平方和)
核心思想:高内聚低耦合
- 内聚:类自己独立处理问题的能力
- 耦合:类与类之间的关系
- 高内聚:簇内样本点紧凑
- 低耦合:簇与簇之间分离
3.3 SSE计算公式
分三步理解:
第一步:一个样本到质心的距离(误差)
$$误差 = p - \mu$$
- $p$:某个样本(真实值)
- $\mu$:质心点
- 含义:真实值减去质心 = 簇内距离 = 误差
第二步:一个样本的误差平方
$$误差平方 = (p - \mu)^2$$
为什么加平方?
- 误差有正有负
- 加平方消除正负影响
第三步:簇内所有样本的误差平方和
$$簇内SSE = \sum_{i=1}^{n} (p_i - \mu)^2$$
- $\sum$:求和
- 含义:簇内每个样本的误差平方和的总和
第四步:所有簇的误差平方和(SSE)
$$SSE = \sum_{k=1}^{K} \sum_{i=1}^{n_k} (p_i - \mu_k)^2$$
- $K$:聚类中心的个数(质心的个数)
- $k$:第k个簇
- $n_k$:第k个簇的样本数
- $\mu_k$:第k个簇的质心
3.4 SSE含义
SSE = 所有簇的所有样本的误差平方和
特点:
- 只考虑簇内(内聚)
- 没有考虑簇间(耦合)
- 内聚越高越好(SSE越小越好)
3.5 高内聚低耦合
类比开发原则:
- 高内聚:能自己搞定的事不麻烦别人
- 低耦合:类与类之间关系少
聚类中的含义:
- 高内聚:簇内样本点紧凑(距离质心近)
- 低耦合:簇与簇之间分离(距离远)
3.6 SSE的优缺点
优点:
- 计算简单
- 易于理解
缺点:
- 只考虑簇内距离
- 没有考虑簇间距离
- SSE越小越好,但不知道什么时候最优
4 代码示例
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
# 1. 生成数据
print("=== 1. 生成数据 ===")
X, y_true = make_blobs(
n_samples=300,
centers=4,
cluster_std=0.5,
random_state=42
)
print(f"数据形状: {X.shape}")
# 2. KMeans聚类
print("\n=== 2. KMeans聚类 ===")
kmeans = KMeans(n_clusters=4, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
centroids = kmeans.cluster_centers_
print(f"聚类中心数: {len(centroids)}")
print(f"inertia_(SSE): {kmeans.inertia_:.4f}")
# 3. 手动计算SSE
print("\n=== 3. 手动计算SSE ===")
def calculate_sse(X, labels, centroids):
"""手动计算SSE(误差平方和)"""
sse = 0
k = len(centroids)
for cluster_idx in range(k):
# 获取当前簇的所有样本
cluster_points = X[labels == cluster_idx]
# 获取当前簇的质心
centroid = centroids[cluster_idx]
# 计算每个样本到质心的距离(误差)
distances = np.sqrt(np.sum((cluster_points - centroid) ** 2, axis=1))
# 计算误差平方和
cluster_sse = np.sum(distances ** 2)
print(f" 簇{cluster_idx}: 样本数={len(cluster_points)}, SSE={cluster_sse:.4f}")
sse += cluster_sse
return sse
sse = calculate_sse(X, labels, centroids)
print(f"\n总SSE: {sse:.4f}")
print(f"sklearn inertia_: {kmeans.inertia_:.4f}")
# 4. 可视化SSE
print("\n=== 4. 可视化SSE ===")
plt.figure(figsize=(12, 5))
# 聚类结果
plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', alpha=0.6)
plt.scatter(centroids[:, 0], centroids[:, 1], c='red', marker='x', s=200, linewidths=3)
plt.title(f'KMeans聚类 (K=4, SSE={sse:.2f})')
# 不同K值的SSE(肘部法)
plt.subplot(1, 2, 2)
k_values = range(1, 11)
sse_values = []
for k in k_values:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X)
sse_values.append(kmeans.inertia_)
plt.plot(k_values, sse_values, 'bo-')
plt.xlabel('K值(簇数)')
plt.ylabel('SSE(误差平方和)')
plt.title('肘部法(Elbow Method)')
plt.grid(True)
plt.tight_layout()
plt.savefig('sse_evaluation.png', dpi=100)
print("SSE评估图已保存")
# 5. 肘部法选择K值
print("\n=== 5. 肘部法选择K值 ===")
print("K值\tSSE\t\t变化率")
print("-" * 40)
for i, (k, sse) in enumerate(zip(k_values, sse_values)):
if i > 0:
change = sse_values[i-1] - sse
print(f"{k}\t{sse:.4f}\t\t{change:.4f}")
else:
print(f"{k}\t{sse:.4f}")
print("\n肘部法:选择SSE下降变缓的K值(肘部)")
# 6. SSE公式演示
print("\n=== 6. SSE公式演示 ===")
def sse_formula_demo():
"""SSE公式演示"""
print("""
SSE计算公式:
第一步:一个样本到质心的距离(误差)
误差 = p - μ
p: 样本(真实值)
μ: 质心点
第二步:一个样本的误差平方
误差平方 = (p - μ)²
第三步:簇内所有样本的误差平方和
簇内SSE = Σ(pᵢ - μ)²
第四步:所有簇的误差平方和(SSE)
SSE = Σₖ₌₁ᴷ Σᵢ₌₁ⁿᵏ (pᵢ - μₖ)²
K: 聚类中心的个数
k: 第k个簇
nₖ: 第k个簇的样本数
μₖ: 第k个簇的质心
""")
sse_formula_demo()
# 7. 高内聚低耦合示例
print("\n=== 7. 高内聚低耦合示例 ===")
# 生成不同紧凑程度的数据
np.random.seed(42)
# 高内聚(紧凑)
X_compact, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.3, random_state=42)
# 低内聚(松散)
X_loose, _ = make_blobs(n_samples=300, centers=4, cluster_std=1.5, random_state=42)
# 聚类
kmeans_compact = KMeans(n_clusters=4, random_state=42, n_init=10)
labels_compact = kmeans_compact.fit_predict(X_compact)
kmeans_loose = KMeans(n_clusters=4, random_state=42, n_init=10)
labels_loose = kmeans_loose.fit_predict(X_loose)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(X_compact[:, 0], X_compact[:, 1], c=labels_compact, cmap='viridis', alpha=0.6)
plt.scatter(kmeans_compact.cluster_centers_[:, 0],
kmeans_compact.cluster_centers_[:, 1],
c='red', marker='x', s=200, linewidths=3)
plt.title(f'高内聚(紧凑)SSE={kmeans_compact.inertia_:.2f}')
plt.subplot(1, 2, 2)
plt.scatter(X_loose[:, 0], X_loose[:, 1], c=labels_loose, cmap='viridis', alpha=0.6)
plt.scatter(kmeans_loose.cluster_centers_[:, 0],
kmeans_loose.cluster_centers_[:, 1],
c='red', marker='x', s=200, linewidths=3)
plt.title(f'低内聚(松散)SSE={kmeans_loose.inertia_:.2f}')
plt.tight_layout()
plt.savefig('high_low_cohesion.png', dpi=100)
print("高内聚低耦合对比图已保存")
print(f"\n高内聚SSE: {kmeans_compact.inertia_:.4f}(越小越好)")
print(f"低内聚SSE: {kmeans_loose.inertia_:.4f}(越小越好)")
# 8. 完整评估流程
print("\n=== 8. 完整评估流程 ===")
def evaluate_clustering(X, k_range=range(2, 11)):
"""完整聚类评估流程"""
print("=" * 50)
print("聚类评估流程")
print("=" * 50)
results = []
for k in k_range:
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
labels = kmeans.fit_predict(X)
sse = kmeans.inertia_
results.append({
'k': k,
'sse': sse,
'labels': labels,
'centroids': kmeans.cluster_centers_
})
print(f"K={k}: SSE={sse:.4f}")
# 找到肘部
print("\n肘部法分析:")
for i in range(1, len(results)-1):
prev_sse = results[i-1]['sse']
curr_sse = results[i]['sse']
next_sse = results[i+1]['sse']
# 计算变化率
change1 = prev_sse - curr_sse
change2 = curr_sse - next_sse
if change1 > change2 * 2: # 变化率明显下降
print(f" K={results[i]['k']} 可能是肘部点")
return results
results = evaluate_clustering(X)
输出示例:
=== 1. 生成数据 ===
数据形状: (300, 2)
=== 2. KMeans聚类 ===
聚类中心数: 4
inertia_(SSE): 156.7890
=== 3. 手动计算SSE ===
簇0: 样本数=75, SSE=38.5678
簇1: 样本数=75, SSE=42.3456
簇2: 样本数=75, SSE=35.2345
簇3: 样本数=75, SSE=40.6411
总SSE: 156.7890
sklearn inertia_: 156.7890
=== 5. 肘部法选择K值 ===
K值 SSE 变化率
----------------------------------------
1 3456.7890
2 1234.5678 2222.2212
3 456.7890 777.7788
4 156.7890 300.0000
5 123.4567 33.3323
6 98.7654 24.6913
...
肘部法:选择SSE下降变缓的K值(肘部)
高内聚SSE: 56.7890(越小越好)
低内聚SSE: 1234.5678(越小越好)
5 重难点与易错提醒
- ❗重点:SSE = 误差平方和。
- ❗重点:SSE只考虑簇内距离(内聚)。
- ❗重点:SSE越小越好(高内聚)。
- ❗重点:肘部法选择最佳K值。
- ⚠️易错:混淆SSE和CH(CH越大越好)。
- ⚠️易错:忘记SSE没有考虑簇间距离。
- 💡深入理解:高内聚低耦合是聚类的好结果。
6 课堂问答精选
Q: SSE是什么?如何计算?
A: SSE = 误差平方和(Sum of Squared Error)
计算公式: $$SSE = \sum_{k=1}^{K} \sum_{i=1}^{n_k} (p_i - \mu_k)^2$$
分三步:
- 一个样本到质心的距离(误差):$p - \mu$
- 误差平方:$(p - \mu)^2$
- 所有簇所有样本的误差平方和
Q: SSE越小越好还是越大越好?
A: SSE越小越好。
- SSE越小,说明簇内样本点越紧凑(高内聚)
- 高内聚是聚类的好结果
- 但SSE没有考虑簇间距离(耦合)
Q: 什么是高内聚低耦合?
A:
- 高内聚:簇内样本点紧凑,距离质心近
- 低耦合:簇与簇之间分离,距离远
- 这是聚类的好结果
7 本课小结
- SSE:误差平方和。
- 公式:$\sum_{k=1}^{K} \sum_{i=1}^{n_k} (p_i - \mu_k)^2$。
- 含义:只考虑簇内距离(内聚)。
- 评估:SSE越小越好。
- 肘部法:选择最佳K值。
8 延伸思考与实践
- 实践:运行SSE计算示例。
- 预习:SC轮廓系数。
- 思考:SSE有什么局限性?