K-means算法在无监督学习中的应用
K-means算法在无监督学习中的应用
K-means算法是一种典型的无监督学习算法,广泛用于聚类分析。在无监督学习中,模型并不依赖于标签数据,而是根据输入数据的特征进行分组。K-means的目标是将数据集分成K个簇,使得同一簇内的数据点相似度较高,而不同簇之间的数据点相似度较低。它通过寻找数据中的潜在结构来自动划分数据。
K-means算法广泛应用于聚类分析、图像分割和异常检测等领域。本文将详细介绍K-means算法的原理、数学模型、实现步骤,并从这三个应用方向举例讲解K-means在无监督学习中的应用。
1. K-means算法的原理和数学模型
1.1 K-means算法的基本思想
K-means算法是一种基于划分的聚类方法,目标是将数据集分成K个簇,并通过最小化簇内误差平方和(SSE, Sum of Squared Errors)来实现数据的聚类。K-means算法的核心在于通过不断地更新簇的中心(质心)和重新分配数据点,直到算法收敛。
1.2数学模型
假设我们有一个包含n
个数据点的数据集,数据点的特征是d
维的。K-means算法的目标是将数据集划分为K
个簇,最小化每个簇内的误差平方和。具体的数学模型如下:
-
初始化: 随机选择
K
个数据点作为初始簇的中心(质心),记为 C 1 , C 2 , … , C K C_1, C_2, \dots, C_K C1,C2,…,CK。 -
分配步骤: 将每个数据点 x i x_i xi 分配到距离它最近的簇中心。距离度量通常使用欧氏距离:
d ( x i , C k ) = ∥ x i − C k ∥ 2 d(x_i, C_k) = \| x_i - C_k \|^2 d(xi,Ck)=∥xi−Ck∥2
数据点 x i x_i xi 被分配到最近的簇中心 C k C_k Ck。 -
更新步骤: 更新每个簇的质心,新的质心是该簇所有数据点的均值:
C k = 1 ∣ S k ∣ ∑ x i ∈ S k x i C_k = \frac{1}{|S_k|} \sum_{x_i \in S_k} x_i Ck=∣Sk∣1xi∈Sk∑xi
其中, S k S_k Sk 是簇 k k k 中的所有数据点, ∣ S k ∣ |S_k| ∣Sk∣ 是簇的大小。 -
停止条件: 重复分配和更新步骤,直到簇中心不再发生变化或达到最大迭代次数。
K-means的目标是最小化簇内误差平方和(SSE),公式如下:
S
S
E
=
∑
k
=
1
K
∑
x
i
∈
S
k
∥
x
i
−
C
k
∥
2
SSE = \sum_{k=1}^K \sum_{x_i \in S_k} \| x_i - C_k \|^2
SSE=k=1∑Kxi∈Sk∑∥xi−Ck∥2
K-means通过迭代更新簇中心来减少SSE,从而实现最优的簇划分。
2. K-means的实现步骤
K-means算法的实现过程可以分为以下几个主要步骤:
- 数据预处理:
- 在应用K-means之前,通常需要对数据进行预处理,包括标准化和降维,尤其是当数据特征维度较高时。
- 选择K值:
- K值表示簇的数量。在实际应用中,K的选择非常重要,常用的方法有肘部法则(Elbow Method)来确定最佳K值。
- 初始化簇中心:
- 选择K个数据点作为初始簇中心,常用的初始化方法有
随机选择
和K-means++
。
- 选择K个数据点作为初始簇中心,常用的初始化方法有
- 迭代更新:
- 分配每个数据点到最近的簇中心。
- 更新簇中心为簇内所有数据点的均值。
- 重复步骤直到簇中心不再变化。
- 评估聚类效果:
- 可以使用SSE、轮廓系数等指标来评估聚类效果。
3. KMeans类的参数解释
from sklearn.cluster import KMeans
# n_clusters: 簇的数量,即 K 值
# init: 初始化中心点的方法,默认为 'k-means++',一种启发式方法来选择初始簇中心以加速收敛
# n_init: 运行 k-means 算法的次数,选择具有最小 SSE 的结果,默认为 'auto' 根据数据集大小自动选择
# max_iter: 单次运行的最大迭代次数,达到最大迭代次数或收敛时停止迭代
# tol: 收敛阈值,两次迭代误差小于此值即认为算法已收敛
# verbose: 是否输出详细信息,默认为 0,不输出
# random_state: 随机数生成器的种子,用于初始化中心点,保持结果可重复
# copy_x: 是否复制输入数据,默认为 True,表示复制输入数据
# algorithm: 计算的算法,'auto', 'full', 'elkan','auto'根据数据自动选择算法,'full'使用传统的 EM 算法,'elkan'是一种更有效的 K-means 算法
kmeans = KMeans(
n_clusters=8,
init='k-means++',
n_init='auto',
max_iter=300,
tol=0.0001,
verbose=0,
random_state=None,
copy_x=True,
algorithm='lloyd'
)
kmeans.fit(X)
4. 确定最优K值
4.1 数据预处理(前摇)
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False}) # 设置主题和字体
# 加载数据集
iris = load_iris()
X = iris.data
# 使用PCA降维到二维,以便可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
4.2 肘部法(Elbow Method)
通过绘制不同K值下的SSE(误差平方和),并寻找SSE快速下降的“肘部”位置。
# 计算不同K值下的SSE
sse = []
for k in range(1, 11):
kmeans = KMeans(n_clusters=k)
kmeans.fit(X_pca) # 这里X_pca是经过PCA降维后的数据
sse.append(kmeans.inertia_)
# 绘制SSE曲线
plt.figure(figsize=(8, 6)) # 设置图表大小
plt.plot(range(1, 11), sse, marker='o', color='b', linestyle='-', markersize=8)
# 设置中文标题和标签
plt.title('肘部法则选择最佳K值', fontsize=14)
plt.xlabel('簇的数量 (K)', fontsize=12)
plt.ylabel('SSE', fontsize=12)
# 显示图表
plt.show()
4.3 轮廓系数(Silhouette Score)
衡量聚类质量的指标,轮廓系数越大,表示聚类效果越好。
# 计算不同K值下的轮廓系数
silhouette_scores = []
for k in range(2, 11): # 由于轮廓系数要求至少有两个簇
kmeans = KMeans(n_clusters=k)
cluster_labels = kmeans.fit_predict(X_pca)
silhouette_avg = silhouette_score(X_pca, cluster_labels)
silhouette_scores.append(silhouette_avg)
# 绘制轮廓系数曲线
plt.figure(figsize=(8, 6)) # 设置图表大小
plt.plot(range(2, 11), silhouette_scores, marker='o', color='b', linestyle='-', markersize=8)
# 设置中文标题和标签
plt.title('轮廓系数选择最佳K值', fontsize=14)
plt.xlabel('簇的数量 (K)', fontsize=12)
plt.ylabel('轮廓系数', fontsize=12)
# 显示图表
plt.show()
4.4 遍历K值(肉眼比较法)
通过遍历不同K值,并比较它们的可视化效果。
(当使用 肘部法 和 轮廓系数 无法确定K值时候,可遍历K值根据可视化结果比较作为参考)
# 设置图表的整体大小
fig, axes = plt.subplots(2, 4, figsize=(18, 8), sharex=True, sharey=True) # 2行5列的子图排布
# 遍历聚类数量1到8
for k in range(1, 9):
kmeans = KMeans(n_clusters=k)
kmeans.fit(X_pca) # 用降维后的数据进行聚类
labels = kmeans.labels_ # 聚类标签
# 选择子图位置
ax = axes[(k - 1) // 4, (k - 1) % 4]
# 绘制每个子图
ax.scatter(X_pca[:, 0], X_pca[:, 1], c=labels, cmap='viridis', s=50, alpha=0.6)
ax.set_title(f'k = {k}', fontsize=12)
ax.set_xlabel('特征1', fontsize=10)
ax.set_ylabel('特征2', fontsize=10)
ax.grid(True)
# 调整布局,防止标签重叠
plt.tight_layout()
plt.show()
5. K-means算法的应用
K-means算法广泛应用于多个领域,以下文中三个常见的应用方向进行举例:聚类分析、图像分割和异常检测。
5.1 聚类分析
聚类分析旨在将数据集划分为多个簇,使得簇内的数据点相似度高,簇间的数据点相似度低。
5.1.1 数据加载、降维并聚类
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False}) # 设置主题和字体
# 加载数据集
iris = load_iris()
X = iris.data
# 使用PCA降维到二维,以便可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
# 聚类
kmeans = KMeans(n_clusters=3, random_state=42)
clusters = kmeans.fit_predict(X_pca)
5.1.2 绘制聚类散点图
# 可视化聚类结果
plt.figure(figsize=(10, 8)) # 调整画布大小
sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False}) # 设置主题和字体
# 绘制散点图
scatter = sns.scatterplot(
x=X_pca[:, 0],
y=X_pca[:, 1],
hue=clusters,
palette='bright',
style=clusters,
markers=["o", "s", "D"],
s=100, # 调整点的大小
edgecolor='black', # 添加边框颜色
linewidth=0.8, # 边框线宽
alpha=0.8, # 透明度
)
# 设置标题和轴标签
plt.title("K-Means聚类结果", fontsize=18, fontweight="bold", pad=10)
plt.xlabel("主成分1", fontsize=14, labelpad=10)
plt.ylabel("主成分2", fontsize=14, labelpad=10)
# 设置轴刻度字体大小
plt.tick_params(axis='x', labelsize=12)
plt.tick_params(axis='y', labelsize=12)
# 设置图例样式
legend = plt.legend(
title="聚类类别",
title_fontsize=13,
fontsize=12,
loc='lower right', # 调整图例位置
bbox_to_anchor=(0.975, 0.025), # 向左上偏移0.1
frameon=True, # 添加图例边框
shadow=True, # 添加阴影
fancybox=True, # 圆角边框
borderpad=1 # 内边距
)
legend.get_frame().set_linewidth(1.2) # 设置图例边框线宽
# 添加网格线样式
plt.grid(color='gray', linestyle='--', linewidth=0.8, alpha=0.3)
# 调整布局
plt.tight_layout()
# 显示图表
plt.show()
5.1.3 添加轮廓线
# 绘制轮廓线
sns.kdeplot(
x=X_pca[:, 0],
y=X_pca[:, 1],
hue=clusters,
palette='bright',
levels=5,
alpha=0.5,
linewidths=1.5,
ax=scatter,
)
5.1.4 添加簇中心点
# 将中心点转换到PCA空间
centers = kmeans.cluster_centers_
plt.scatter(
centers[:, 0], centers[:, 1],
c='red', marker='X', s=200, label='聚类中心', edgecolor='black', linewidth=2
) # 绘制中心点
5.2 图像分割
图像分割是指将图像划分为多个区域,每个区域的像素在颜色、纹理等特征上具有相似性。K-means算法可以用于图像分割,将图像的每个像素聚类到不同的簇中,从而实现对图像的分割。
5.2.1 导入图片
from sklearn.datasets import load_sample_image
import matplotlib.pyplot as plt
# 加载“chinajpg”样本图像
flower = load_sample_image('china.jpg')
# 显示图片
plt.figure(figsize=(6, 6))
plt.imshow(flower)
plt.axis('off') # 禁用轴
plt.show()
5.2.2 分割过程
from sklearn.cluster import KMeans
import numpy as np
# 加载图像并转换为numpy数组
china = load_sample_image("china.jpg")
china = np.array(china)
# 将图像转为二维数组(每个像素为一个数据点)
X = china.reshape(-1, 3) # 3是RGB通道
# K-means聚类,设定聚类数为4
kmeans = KMeans(n_clusters=3, random_state=1)
kmeans.fit(X)
# 获取聚类标签
labels = kmeans.labels_
# 将标签重新变为图像的形状
segmented_image = labels.reshape(china.shape[0], china.shape[1])
5.2.3 可视化
import seaborn as sns
from mpl_toolkits.mplot3d import Axes3D # 可视化RGB三维散点图
sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False}) # 设置主题和字体
# 提取RGB通道数据
R = X[:, 0]
G = X[:, 1]
B = X[:, 2]
# 创建3D散点图
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 使用透明度(alpha)调整点的透明度
scatter = ax.scatter(R, G, B, c=labels, cmap='viridis', s=5, alpha=0.5) # alpha为透明度
# 设置坐标轴标签和标题
ax.set_xlabel('R', fontsize=12, labelpad=10)
ax.set_ylabel('G', fontsize=12, labelpad=10)
ax.set_zlabel('B', fontsize=12, labelpad=10)
ax.set_title('K-means 聚类 RGB 三维散点图', fontsize=14)
# 设置更精致的坐标轴刻度
ax.tick_params(axis='both', which='major', labelsize=10)
# 显示图形
plt.show()
# 可视化图像分割结果
plt.figure(figsize=(8, 6))
plt.imshow(segmented_image, cmap='viridis')
plt.title('图像分割:K-means聚类', fontsize=14)
# plt.axis('off') # 禁用坐标轴
plt.show()
5.2.4 拆分结果
# 将标签重新变为图像的形状
segmented_image_list = []
# 创建每个簇的分割图像
for cluster in range(3): # 假设我们有3个簇
# 创建一个全为0的图像(与原图尺寸一致)
segmented_image = np.zeros_like(china)
# 将属于该簇的像素赋值为原图像素,其他像素设置为黑色
# 获取属于该簇的像素的索引
cluster_pixels = labels == cluster
# 将符合条件的像素赋值
segmented_image.reshape(-1, 3)[cluster_pixels] = china.reshape(-1, 3)[cluster_pixels]
# 添加到分割图像列表中
segmented_image_list.append(segmented_image)
# 创建子图布局
fig, axes = plt.subplots(1, 3, figsize=(12, 10))
# 绘制每个聚类的分割图像
for i, ax in enumerate(axes.flat):
ax.imshow(segmented_image_list[i]) # 显示分割后的图像
ax.set_title(f'Cluster {i+1}', fontsize=14) # 设置标题为聚类编号
ax.axis('off') # 禁用坐标轴
# 调整布局,防止重叠
plt.tight_layout()
plt.show()
5.3 异常检测
K-means算法也可用于异常检测,尤其是在数据集中存在噪声或异常点时。通过计算数据点到簇中心的距离,可以识别那些远离簇中心的点作为异常点。这些异常点可能代表了异常行为或错误数据。
5.3.1 生成原始数据集并可视化
from sklearn.cluster import KMeans
import numpy as np
from scipy.spatial.distance import cdist
import matplotlib.pyplot as plt
import seaborn as sns
# 设置主题和字体
sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False})
# 生成模拟数据:两个明显簇和少量离群点
np.random.seed(42)
cluster_1 = np.random.normal(loc=[5, 5], scale=1.5, size=(100, 2))
cluster_2 = np.random.normal(loc=[15, 15], scale=1.5, size=(100, 2))
outliers = np.random.uniform(low=[0, 0], high=[20, 20], size=(10, 2))
X = np.vstack([cluster_1, cluster_2, outliers])
# 可视化样本
plt.figure(figsize=(8, 6))
x = X[:, 0]
y = X[:, 1]
plt.scatter(x, y, s=20, c='gray', alpha=0.7, label='数据点')
plt.title("数据点分布图", fontsize=16)
plt.xlabel("X轴", fontsize=12)
plt.ylabel("Y轴", fontsize=12)
plt.grid(color='gray', linestyle='--', linewidth=0.8, alpha=0.3) # 添加网格线样式
plt.legend()
plt.show()
5.3.2 聚类并进行异常值比较
# KMeans算法拟合
kmeans_model = KMeans(n_clusters=2, random_state=42).fit(X)
# 聚类中心
centroids = kmeans_model.cluster_centers_
# 每个样本到聚类中心的欧式距离
D = cdist(X, centroids, 'euclidean')
# 每个样本的最近聚类中心索引
cluster_labels = D.argmin(axis=1)
# 每个样本到所属聚类中心的距离
distances_to_cluster = np.min(D, axis=1)
# 设置异常点的距离阈值
threshold = 4.0
# 异常点判定:仅比较点与其所属簇中心的距离
outliers = X[distances_to_cluster > threshold]
array([[15.77257153, 20.77909724],
[18.47198785, 12.19910221],
[10.13809899, 13.46341854],
[18.19955006, 12.0718683 ],
[ 1.12750993, 17.29444753],
[16.25802018, 19.99435347],
[19.93273674, 11.10863411],
[16.99294781, 4.94696203],
[ 9.01088271, 2.5831883 ],
[19.08102055, 12.12349269],
[ 4.57285611, 13.43401369],
[12.36256481, 7.16325436]])
5.3.3 可视化异常值检测结果
# 可视化
plt.figure(figsize=(10, 8))
# 绘制簇的点
palette = sns.color_palette("husl", 2)
for i in range(2): # 遍历每个簇
plt.scatter(X[cluster_labels == i, 0], X[cluster_labels == i, 1],
s=50, color=palette[i], alpha=0.7, label=f"簇 {i+1}", edgecolor='k', marker='o')
# 绘制聚类中心
plt.scatter(centroids[:, 0], centroids[:, 1],
s=200, c='black', marker='X', label='聚类中心', zorder=5)
# 绘制离群点
if len(outliers) > 0:
plt.scatter(outliers[:, 0], outliers[:, 1],
s=100, c='red', label='离群点', edgecolor='k', zorder=6)
# 绘制分界线
for center in centroids:
circle = plt.Circle(center, threshold, color='gray', alpha=0.3, fill=True, linestyle='--')
plt.gca().add_artist(circle)
# 图表设置
plt.title("KMeans 聚类与优化后的异常值检测", fontsize=16)
plt.xlabel("X轴", fontsize=12)
plt.ylabel("Y轴", fontsize=12)
plt.grid(color='gray', linestyle='--', linewidth=0.8, alpha=0.3) # 添加网格线样式
plt.legend(loc='upper left', fontsize=10)
plt.tight_layout()
plt.show()