Skip to content

无监督学习:聚类

使用 Python 进行 AI – 无监督学习:聚类

Section titled “使用 Python 进行 AI – 无监督学习:聚类”

无监督机器学习算法在没有标记数据形式的明确指导下运行。它们不像监督学习那样预测一个已知结果,而是旨在发现数据本身固有的结构、模式或关系。这使得它们成为探索性数据分析和真正的人工智能(AI)的基石,在这些领域中,系统通过观察进行学习。

在无监督学习中,没有预定义的“正确答案”或“教师”来指导学习过程。算法必须独立识别所提供数据集中的有趣模式、分组或异常。

聚类是一种基础的无监督学习技术。它涉及将一组观测(数据点)划分到称为“簇”的子集中,使得同一簇内的观测彼此之间更相似(根据选定的相似度度量),而不是与其他簇中的观测相似。主要目标是根据共同的特征对数据进行分组,揭示潜在的结构。

想象一下分类一堆混合的水果。聚类就像根据它们的视觉和纹理属性将它们分成苹果、香蕉、橘子等,而事先不知道水果的类型。在数据中,这些属性就是特征。

聚类可视化图示描述:想象一个散点图,上面有数据点。聚类算法将为这些点绘制边界或分配颜色,将它们分组到不同的区域。例如,一个区域可能包含密集的蓝色点集合,另一个区域是红色点,第三个区域是绿色点,它们在视觉上彼此分离。

存在几种用于聚类数据的算法,每种算法都有其优点和缺点。我们将使用 Python 的 scikit-learn 库探索几种流行的算法。

K-均值(K-Means)是使用最广泛和最简单的聚类算法之一。它是一种“平面”或“划分聚类”方法。一个关键假设是您需要预先指定簇的数量,即 ‘K’。

该算法迭代地执行以下步骤:

  1. 指定 K:选择所需的簇数量。
  2. 初始化质心:随机选择 K 个数据点作为初始簇质心(或使用更智能的初始化方法,如 ‘k-means++’)。
  3. 分配点:将每个数据点分配给其质心最近的簇(例如,使用欧几里得距离)。
  4. 更新质心:将分配给每个簇的所有数据点的均值重新计算为该簇的质心。
  5. 重复:重复步骤 3 和 4,直到质心不再显著移动或达到最大迭代次数。

让我们在 Python 中实现 K-Means。首先,如果您尚未安装,请安装必要的库:

pip install scikit-learn matplotlib seaborn numpy

导入包并生成样本数据:

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs # Updated import
# Generate synthetic 2D data with 4 distinct clusters
X, y_true = make_blobs(n_samples=500, centers=4,
cluster_std=0.60, random_state=0)
# Visualize the generated data
plt.figure(figsize=(8, 6))
plt.scatter(X[:, 0], X[:, 1], s=50, cmap='viridis')
plt.title('用于聚类的数据生成')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.show()

该图将显示 2D 空间中四个不同的数据点组,我们的K-均值算法将尝试识别它们。

现在,应用 K-Means:

# Initialize KMeans with K=4 clusters
# n_init='auto' is recommended for scikit-learn versions >= 1.4 to use an intelligent default
# For older versions, n_init=10 is a common default for 'k-means++'
kmeans = KMeans(n_clusters=4, init='k-means++', n_init='auto', random_state=0)
# Fit K-Means to the data and predict cluster labels
kmeans.fit(X)
y_kmeans = kmeans.predict(X) # Or equivalently: y_kmeans = kmeans.labels_
# Get the cluster centers
centers = kmeans.cluster_centers_
# Visualize the clustering results
plt.figure(figsize=(10, 7))
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis', alpha=0.7, label='数据点')
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=200, marker='X', label='质心')
plt.title('K-均值聚类结果 (K=4)')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend()
plt.show()

这个可视化将显示按分配的簇着色的原始数据点,以及标记的最终簇质心(例如,用红色的 ‘X’)。您应该会看到 K-Means 已成功识别出这四个组。

K-Means 的实际应用:客户细分、文档聚类、图像压缩。

均值漂移(Mean Shift)是另一种强大的聚类算法,与 K-Means 不同,它不需要事先指定簇的数量。它是一种基于密度的算法,旨在找到数据分布中的“模态”或峰值。

基本步骤是:

  1. 初始化窗口:在每个数据点周围放置一个窗口(例如 2D 中的圆形)。
  2. 计算均值:对于每个窗口,计算其中数据点的均值。
  3. 移动窗口:将窗口的中心移动到计算出的均值位置。
  4. 重复:重复步骤 2 和 3,直到窗口位置收敛(不再显著移动)。
  5. 分配聚类:窗口收敛到同一位置(模态)的点被分配到同一簇。

让我们实现 Mean Shift:

from sklearn.cluster import MeanShift, estimate_bandwidth
# Generate different sample data for Mean Shift
centers_ms = [[1, 1], [5, 5], [3, 10]]
X_ms, _ = make_blobs(n_samples=300, centers=centers_ms, cluster_std=0.8, random_state=42)
plt.figure(figsize=(8, 6))
plt.scatter(X_ms[:, 0], X_ms[:, 1], s=50)
plt.title('用于均值漂移的数据生成')
plt.show()

该图将显示数据点,可能形成几个组。

# Estimate bandwidth (a crucial parameter for Mean Shift)
# Bandwidth determines the size of the window.
# quantile can be adjusted, 0.2-0.3 often works well.
bandwidth = estimate_bandwidth(X_ms, quantile=0.2, n_samples=len(X_ms))
# Initialize MeanShift
# If bandwidth is not provided, it's estimated, but providing it can give more control.
mean_shift = MeanShift(bandwidth=bandwidth, bin_seeding=True) # bin_seeding can speed up
# Fit Mean Shift to the data
mean_shift.fit(X_ms)
labels_ms = mean_shift.labels_
cluster_centers_ms = mean_shift.cluster_centers_
n_clusters_estimated = len(np.unique(labels_ms))
print(f"均值漂移估计的聚类数量:{n_clusters_estimated}")
print(f"聚类中心:\n{cluster_centers_ms}")
# Visualize the Mean Shift clustering results
plt.figure(figsize=(10, 7))
colors = ['r', 'g', 'b', 'c', 'm', 'y', 'k']
for i in range(len(X_ms)):
plt.scatter(X_ms[i, 0], X_ms[i, 1], color=colors[labels_ms[i] % len(colors)], s=50, alpha=0.7)
plt.scatter(cluster_centers_ms[:, 0], cluster_centers_ms[:, 1],
marker='X', s=200, linewidths=3, color='black', zorder=10, label='聚类中心')
plt.title(f'均值漂移聚类结果(估计聚类数量为 {n_clusters_estimated})')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend()
plt.show()

预期输出(文本):

均值漂移估计的聚类数量: (例如,3)
聚类中心:
[[x1 y1]
[x2 y2]
[x3 y3]]

该可视化将显示按分配的簇着色的数据点,以及算法识别的聚类中心。聚类数量由 Mean Shift 自动确定。

Mean Shift 的实际应用:图像分割、目标跟踪、数据点模态寻找。

实际数据很少带有预定义的簇,这使得评估聚类结果的质量具有挑战性,特别是当真实簇的数量未知时。轮廓分析(Silhouette Analysis)是一种通过衡量簇的分离程度来评估聚类质量的常用方法。

单个数据点的轮廓系数(Silhouette Score)衡量其与其自身簇的相似度与它和其它簇的相似度之间的比较。它的取值范围从 -1 到 +1:

  • 得分接近 +1:样本远离相邻的簇(良好的聚类)。
  • 得分接近 0:样本位于或非常接近两个相邻簇之间的决策边界。
  • 得分接近 -1:样本可能被分配到错误的簇(聚类效果差)。

一个聚类结果的整体轮廓系数是所有样本轮廓系数的平均值。

单个样本 ‘i’ 的公式为:s(i) = (b(i) - a(i)) / max(a(i), b(i))

  • a(i):平均簇内距离(样本 ‘i’ 到其自身簇中所有其他点的平均距离)。
  • b(i):平均最近簇距离(样本 ‘i’ 到次近簇中所有点的平均距离)。

计算轮廓系数以寻找 K-均值的最优 K 值

Section titled “计算轮廓系数以寻找 K-均值的最优 K 值”

我们可以使用轮廓系数来帮助确定 K-Means 等算法的最优 K 值。我们将迭代地遍历不同的 K 值,执行 K-Means,并计算每个 K 值的轮廓系数。

from sklearn.metrics import silhouette_score
# Using the data X from the K-Means example
possible_k_values = range(2, 10) # Test K from 2 to 9
silhouette_scores = []
for k_val in possible_k_values:
kmeans_eval = KMeans(n_clusters=k_val, init='k-means++', n_init='auto', random_state=0)
cluster_labels_eval = kmeans_eval.fit_predict(X)
# Calculate silhouette score
score = silhouette_score(X, cluster_labels_eval)
silhouette_scores.append(score)
print(f"对于 K = {k_val},轮廓系数 = {score:.4f}")
# Plot silhouette scores for different K
plt.figure(figsize=(8, 5))
plt.plot(possible_k_values, silhouette_scores, marker='o')
plt.title('不同 K 值的轮廓系数 (K-均值)')
plt.xlabel('聚类数量 (K)')
plt.ylabel('轮廓系数')
plt.grid(True)
plt.show()
# Find the K with the highest silhouette score
optimal_k = possible_k_values[np.argmax(silhouette_scores)]
print(f"
基于轮廓系数的最佳聚类数量:{optimal_k}")

预期输出(文本):

对于 K = 2,轮廓系数 = 0.xxxx
对于 K = 3,轮廓系数 = 0.xxxx
对于 K = 4,轮廓系数 = 0.xxxx (对于拥有 4 个中心的 make_blobs 示例来说可能最高)
...
对于 K = 9,轮廓系数 = 0.xxxx
基于轮廓系数的最佳聚类数量:4

该图将在 y 轴上显示轮廓系数,x 轴上显示不同的 K 值。通常,您会在此图中寻找一个峰值,这表明一个最优 K 值。对于我们拥有 4 个中心的 make_blobs 数据,K=4 应该会产生最高的得分。

注意:轮廓系数只是一种度量。“肘部法则”(plotting inertia vs. K)是 K-Means 的另一种常见启发式方法。领域知识对于选择 K 也至关重要。

寻找数据点的最近邻是许多机器学习算法中的一个基本操作,特别是在基于实例的学习和推荐系统中。K-最近邻(KNN)算法及其各种形式都依赖于这个概念。

目标是找到给定数据集中与新的、未见过输入点最接近(根据距离度量,如欧几里得距离)的 ‘K’ 个数据点。

让我们使用 sklearn.neighbors.NearestNeighbors:

from sklearn.neighbors import NearestNeighbors
# Sample dataset
A = np.array([
[3.1, 2.3], [2.3, 4.2], [3.9, 3.5],
[3.7, 6.4], [4.8, 1.9], [8.3, 3.1],
[5.2, 7.5], [4.8, 4.7], [3.5, 5.1],
[4.4, 2.9]
])
# Number of neighbors to find
k_neighbors = 3
# Test data point for which we want to find neighbors
test_point = np.array([[3.3, 2.9]]) # Must be 2D for kneighbors
# Visualize the input data and the test point
plt.figure(figsize=(8, 6))
plt.scatter(A[:, 0], A[:, 1], marker='o', s=100, color='black', label='数据点')
plt.scatter(test_point[0, 0], test_point[0, 1], marker='x', s=150, color='red', label='测试点')
plt.title('输入数据和测试点')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend()
plt.grid(True)
plt.show()

该图显示了我们的数据集和标记测试点的红色 ‘x’。

# Initialize and fit the NearestNeighbors model
# algorithm='auto' will choose the most appropriate algorithm (e.g., 'ball_tree', 'kd_tree', 'brute')
nn_model = NearestNeighbors(n_neighbors=k_neighbors, algorithm='auto')
nn_model.fit(A)
# Find the K nearest neighbors for the test point
distances, indices = nn_model.kneighbors(test_point)
print(f"
点 {test_point[0]} 的 {k_neighbors} 个最近邻:")
for i in range(k_neighbors):
print(f"{i+1}. 点:{A[indices[0][i]]},距离:{distances[0][i]:.4f}")
# Visualize the nearest neighbors
plt.figure(figsize=(10, 7))
plt.scatter(A[:, 0], A[:, 1], marker='o', s=100, color='black', label='数据点')
plt.scatter(test_point[0, 0], test_point[0, 1], marker='x', s=150, color='red', label='测试点')
plt.scatter(A[indices[0], 0], A[indices[0], 1], marker='o', s=250,
edgecolor='blue', facecolors='none', linewidths=2, label=f'{k_neighbors} 个最近邻')
plt.title(f'{k_neighbors}-最近邻')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.legend()
plt.grid(True)
plt.show()

预期输出(文本):

点 [3.3 2.9] 的 3 个最近邻:
1. 点:[3.1 2.3],距离:0.6325
2. 点:[4.4 2.9],距离:1.1000
3. 点:[3.9 3.5],距离:0.8485
(注意:顺序可能会因距离相等的打破规则略有不同,但点应该是正确的。)

第二张图将突出显示测试点的 K 个最近邻,通常通过在它们周围绘制圆圈或使用不同的颜色。

K-最近邻(KNN)分类器是一种简单而有效的基于实例的学习算法,用于分类任务。它根据其在训练数据中 ‘K’ 个最近邻的多数类别来对新的数据点进行分类。

核心思想是:“告诉我你的邻居是谁,我就告诉你你是谁。”

  1. 选择 K:选择要考虑的最近邻的数量(K)。
  2. 计算距离:对于一个新的、未分类的数据点,计算它与训练数据集中所有点的距离(例如,欧几里得距离)。
  3. 识别邻居:找到与新点最接近的 K 个训练数据点。
  4. 多数投票:将这些 K 个邻居中最频繁出现的类别标签分配给新的数据点。(对于回归,可以使用邻居值的平均值)。

KNN 是一种非参数的、惰性学习算法。“非参数的”意味着它对潜在数据分布没有强烈的假设。“惰性学习”意味着它在训练期间不构建显式模型;所有计算都推迟到分类时进行。

示例:用于数字识别的 KNN 分类器

Section titled “示例:用于数字识别的 KNN 分类器”

我们将使用 scikit-learn 中的 load_digits 数据集,其中包含手写数字(0-9)的图像。

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
import numpy as np
# Load the digits dataset
digits = load_digits()
X_digits = digits.data # Feature matrix (flattened 8x8 images)
y_digits = digits.target # Target labels (0-9)
print(f"数据形状:{X_digits.shape}") # (1797 samples, 64 features)
print(f"目标形状:{y_digits.shape}")
# Display a few sample digits
fig, axes = plt.subplots(2, 5, figsize=(10, 5), subplot_kw={'xticks':[], 'yticks':[]})
for i, ax in enumerate(axes.flat):
ax.imshow(X_digits[i].reshape(8, 8), cmap='binary')
ax.set_title(f"标签: {y_digits[i]}")
plt.suptitle('数据集中的数字样本')
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()

这将显示 10 个数字样本图像及其真实标签。

# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X_digits, y_digits, test_size=0.3, random_state=42, stratify=y_digits)
print(f"训练样本:{X_train.shape[0]}")
print(f"测试样本:{X_test.shape[0]}")
# Initialize KNN classifier (e.g., with K=5 neighbors)
knn_classifier = KNeighborsClassifier(n_neighbors=5)
# Train the classifier (for KNN, this mostly involves storing the training data)
knn_classifier.fit(X_train, y_train)
# Make predictions on the test set
y_pred = knn_classifier.predict(X_test)
# Evaluate the classifier
accuracy = accuracy_score(y_test, y_pred)
print(f"
KNN 分类器在数字识别上的准确率:{accuracy * 100:.2f}%")
# Let's look at a few test predictions
fig, axes = plt.subplots(3, 4, figsize=(12, 10), subplot_kw={'xticks':[], 'yticks':[]})
for i, ax in enumerate(axes.flat):
if i < len(X_test):
ax.imshow(X_test[i].reshape(8, 8), cmap='binary')
true_label = y_test[i]
pred_label = y_pred[i]
ax.set_title(f"真实值: {true_label}, 预测值: {pred_label}", color='green' if true_label == pred_label else 'red')
plt.suptitle('测试预测样本 (KNN 分类器)')
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()

预期输出(文本):

数据形状:(1797, 64)
目标形状:(1797,)
训练样本:1257
测试样本:540
KNN 分类器在数字识别上的准确率:(例如,98.xx % - 会略有不同,但应较高)

第二张图将显示一些测试数字图像及其真实标签和 KNN 分类器的预测标签。正确预测的标题为绿色,不正确的为红色。使用 KNN 完成此任务应能看到非常高的准确率。

KNN 的考量:选择合适的 K 值(通常通过交叉验证)、特征缩放(由于 KNN 依赖于距离,这很重要),以及在处理大型数据集时的预测计算成本(因为它需要与所有训练数据点进行比较)。

进一步学习资源: