数据分类与聚类算法对比
任务描述:
- 生成模拟聚类数据。
- 调用不同的聚类算法,包括自定义的 K-Means 和 GMM 算法,以及 sklearn 中自带的算法,进行聚类提取。
- 在同一张图片中显示聚类结果和算法运行时间,对比不同聚类算法的效果和效率。
算法描述:
数据分类任务通常包括使用聚类算法将数据点分组。聚类算法旨在将数据划分为不同的组(簇),使得同一组中的数据点尽可能相似,而不同组之间的数据点差异较大。常用的聚类算法有 K-Means、Gaussian Mixture Models(GMM)、Mean Shift、DBSCAN、Agglomerative Clustering 等。
- K-Means 聚类:K-Means 是一种迭代的聚类方法,通过最小化每个点到其最近聚类中心的距离来实现数据点的分配。其主要步骤是随机选择 K 个初始聚类中心,然后进行迭代更新,直到聚类结果收敛。
- Gaussian Mixture Models(GMM):GMM 是基于概率模型的聚类方法,它假设数据点是从多个高斯分布中生成的。GMM 的优势是能够处理不规则形状的簇。
- Mean Shift:Mean Shift 是一种基于密度的聚类算法,自动发现数据的密集区域,并将数据点分配到这些区域中。它不需要预先指定簇的数量。
- DBSCAN:基于密度的空间聚类算法,可以发现任意形状的簇,并且不需要指定簇的数量。它通过指定最小样本数和半径来识别密度较高的区域。
具体步骤:
- 数据生成:首先,使用 sklearn.datasets 生成多种模拟数据集,如圆形、月亮形状、气泡等数据集。
- 聚类算法初始化:定义多个聚类算法,包括自定义的 K-Means 和 GMM 算法,以及 sklearn 提供的 Mean Shift、MiniBatchKMeans 等。
- 聚类执行:对每个数据集应用所有聚类算法,记录聚类结果。
- 结果展示与对比:在同一张图中绘制聚类结果,并显示每个算法的运行时间。
代码
class K_Means(object):
# k是分组数;tolerance‘中心点误差’;max_iter是迭代次数
def __init__(self, n_clusters=2, tolerance=0.0001, max_iter=300):
self.k_ = n_clusters
self.tolerance_ = tolerance
self.max_iter_ = max_iter
def fit(self, data):
# 作业1
# 从所有数据点钟随机选择K个点作为初始聚类中心
centers = data[random.sample(range(data.shape[0]),self.k_)]
old_centers = np.copy(centers)
# K 个列表,用于存储属于第K个聚类的数据点索引
labels = [ []for i in range(self.k_) ]
for iter_ in range(self.max_iter_):
# E step
for idx, point in enumerate(data):
diff = np.linalg.norm(old_centers - point, axis=1)
labels[np.argmin(diff)].append(idx)
# M step
for i in range(self.k_):
points = data[labels[i], :]
centers[i] = points.mean(axis=0)
if np.sum(np.abs(centers - old_centers)) < self.tolerance_ + self.k_:
break
old_centers = np.copy(centers)
self.centers = centers
self.fitted = True
def predict(self, p_datas):
result = []
# 作业2
if not self.fitted:
print('Unfitted.')
return result
for point in p_datas:
diff = np.linalg.norm(self.centers - point, axis=1)
result.append(np.argmin(diff))
return result
完整程序
# 文件功能:
# 1. 生成模拟聚类数据
# 2. 调用聚类算法,包括自编的K-Means、GMM,以及sklearn中自带的算法,进行聚类提取
# 3. 在同一张图片中显示聚类结果和算法运行时间,对比查看
import time
import warnings
import numpy as np
import matplotlib.pyplot as plt
from sklearn import cluster, datasets, mixture
from sklearn.neighbors import kneighbors_graph
from sklearn.preprocessing import StandardScaler
from itertools import cycle, islice
from KMeans import K_Means
from GMM import GMM
np.random.seed(0)
# ============
# 模拟原始数据
# ============
print('start generate datasets ...')
n_samples = 1500
noisy_circles = datasets.make_circles(n_samples=n_samples, factor=.5,
noise=.05)
noisy_moons = datasets.make_moons(n_samples=n_samples, noise=.05)
blobs = datasets.make_blobs(n_samples=n_samples, random_state=8)
no_structure = np.random.rand(n_samples, 2), None
# Anisotropicly distributed data
random_state = 170
X, y = datasets.make_blobs(n_samples=n_samples, random_state=random_state)
transformation = [[0.6, -0.6], [-0.4, 0.8]]
X_aniso = np.dot(X, transformation)
aniso = (X_aniso, y)
# blobs with varied variances
varied = datasets.make_blobs(n_samples=n_samples,
cluster_std=[1.0, 2.5, 0.5],
random_state=random_state)
print('datasets generated over')
# ============
# 设置聚类算法参数
# ============
plt.figure(figsize=(9 * 2 + 3, 12.5))
plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05,
hspace=.01)
plot_num = 1
default_base = {'quantile': .3,
'eps': .3,
'damping': .9,
'preference': -200,
'n_neighbors': 10,
'n_clusters': 3,
'min_samples': 20,
'xi': 0.05,
'min_cluster_size': 0.1}
datasets = [
(noisy_circles, {'damping': .77, 'preference': -240,
'quantile': .2, 'n_clusters': 2,
'min_samples': 20, 'xi': 0.25}),
(noisy_moons, {'damping': .75, 'preference': -220, 'n_clusters': 2}),
(varied, {'eps': .18, 'n_neighbors': 2,
'min_samples': 5, 'xi': 0.035, 'min_cluster_size': .2}),
(aniso, {'eps': .15, 'n_neighbors': 2,
'min_samples': 20, 'xi': 0.1, 'min_cluster_size': .2}),
(blobs, {}),
(no_structure, {})]
# 一共两层循环,实现每个仿真数据被每种聚类算法调用以此
# 此处是外层循环,遍历所有仿真数据
for i_dataset, (dataset, algo_params) in enumerate(datasets):
# update parameters with dataset-specific values
params = default_base.copy()
params.update(algo_params)
print('start deal dataset_' + str(i_dataset) + '...')
print('dataset params is:')
print(params)
X, y = dataset
# normalize dataset for easier parameter selection
X = StandardScaler().fit_transform(X)
# estimate bandwidth for mean shift
bandwidth = cluster.estimate_bandwidth(X, quantile=params['quantile'])
# connectivity matrix for structured Ward
connectivity = kneighbors_graph(
X, n_neighbors=params['n_neighbors'], include_self=False)
# make connectivity symmetric
connectivity = 0.5 * (connectivity + connectivity.T)
# ============
# 初始化所有聚类算法
# ============
# 自编的K-Means、GMM算法
my_kmeans = K_Means(n_clusters=params['n_clusters'])
my_gmm = GMM(n_clusters=params['n_clusters'])
# sklearn中自带的算法
ms = cluster.MeanShift(bandwidth=bandwidth, bin_seeding=True)
two_means = cluster.MiniBatchKMeans(n_clusters=params['n_clusters'])
ward = cluster.AgglomerativeClustering(
n_clusters=params['n_clusters'], linkage='ward',
connectivity=connectivity)
spectral = cluster.SpectralClustering(
n_clusters=params['n_clusters'], eigen_solver='arpack',
affinity="nearest_neighbors")
dbscan = cluster.DBSCAN(eps=params['eps'])
optics = cluster.OPTICS(min_samples=params['min_samples'],
xi=params['xi'],
min_cluster_size=params['min_cluster_size'])
affinity_propagation = cluster.AffinityPropagation(
damping=params['damping'], preference=params['preference'])
average_linkage = cluster.AgglomerativeClustering(
linkage="average", metric="cityblock",
n_clusters=params['n_clusters'], connectivity=connectivity)
birch = cluster.Birch(n_clusters=params['n_clusters'])
gmm = mixture.GaussianMixture(
n_components=params['n_clusters'], covariance_type='full')
clustering_algorithms = (
('My_KMeans', my_kmeans),
('My_GMM', my_gmm),
('MiniBatchKMeans', two_means),
('AffinityPropagation', affinity_propagation),
('MeanShift', ms),
('SpectralClustering', spectral),
('Ward', ward),
('AgglomerativeClustering', average_linkage),
('DBSCAN', dbscan),
('OPTICS', optics),
('Birch', birch),
('GaussianMixture', gmm)
)
# 此处是内层循环,遍历每种算法
for name, algorithm in clustering_algorithms:
t0 = time.time()
print('clustering with algorithm', name, '...')
# catch warnings related to kneighbors_graph
with warnings.catch_warnings():
warnings.filterwarnings(
"ignore",
message="the number of connected components of the " +
"connectivity matrix is [0-9]{1,2}" +
" > 1. Completing it to avoid stopping the tree early.",
category=UserWarning)
warnings.filterwarnings(
"ignore",
message="Graph is not fully connected, spectral embedding" +
" may not work as expected.",
category=UserWarning)
algorithm.fit(X)
t1 = time.time()
if hasattr(algorithm, 'labels_'):
y_pred = algorithm.labels_.astype(int)
else:
y_pred = algorithm.predict(X)
plt.subplot(len(datasets), len(clustering_algorithms), plot_num)
if i_dataset == 0:
plt.title(name, size=7)
colors = np.array(list(islice(cycle(['#377eb8', '#ff7f00', '#4daf4a',
'#f781bf', '#a65628', '#984ea3',
'#999999', '#e41a1c', '#dede00']),
int(max(y_pred) + 1))))
# add black color for outliers (if any)
colors = np.append(colors, ["#000000"])
plt.scatter(X[:, 0], X[:, 1], s=10, color=colors[y_pred])
plt.xlim(-2.5, 2.5)
plt.ylim(-2.5, 2.5)
plt.xticks(())
plt.yticks(())
plt.text(.99, .01, ('%.2fs' % (t1 - t0)).lstrip('0'),
transform=plt.gca().transAxes, size=15,
horizontalalignment='right')
plot_num += 1
plt.show()
结果

聚类算法优缺点对比
不同的聚类算法在处理不同类型的数据时具有不同的表现。以下是几种常见聚类算法的优缺点对比,帮助你根据数据特性选择合适的算法。
- K-Means 聚类
- 算法流程:
K-Means 算法通过随机选择 K 个初始聚类中心,然后迭代地分配数据点到最近的聚类中心,更新聚类中心的位置,直到收敛为止。算法的核心步骤包括:- 随机选择 K 个聚类中心。
- 对每个数据点,计算其到所有聚类中心的距离,并将其分配给最近的聚类中心。
- 计算每个簇内所有点的均值,并将其作为新的聚类中心。
- 重复上述过程,直到聚类中心不再发生变化或达到最大迭代次数。
- 优点:
- 效率高:K-Means 算法简单且计算效率较高,适用于大规模数据集。
- 易于理解和实现:算法直观、易于实现,适合初学者使用。
- 适用于球形簇:K-Means 假设簇是球形的,适合处理数据集中的簇具有类似形状的情况。
- 缺点:
- 对初始化敏感:K-Means 算法对初始聚类中心的选择非常敏感,可能会陷入局部最优解。常用的改进方法是使用 K-Means++ 来更智能地初始化中心。
- 需要预设聚类数目:K-Means 需要提前指定簇的数量 k,但在实际应用中,很难确定最合适的 k 值。
- 不适应非球形簇:对于形状不规则的簇(如椭圆形簇),K-Means 的效果较差。
- 对噪声和离群点敏感:离群点会影响簇的形成,尤其是对于高维数据。
- Gaussian Mixture Models(GMM)
- 算法流程:
GMM 是基于概率的聚类算法,假设数据是由多个高斯分布(高斯混合模型)生成的。算法的核心步骤包括:- 初始化各个高斯分布的均值、协方差和权重。
- 通过 EM 算法(期望最大化)迭代地优化高斯分布的参数。
- E步:计算每个点属于各个高斯分布的概率。
- M步:根据计算得到的概率,更新高斯分布的均值、协方差和权重。
- 优点:
- 适应复杂簇形状:GMM 通过拟合多个高斯分布,适用于形状不规则的簇。
- 软聚类:GMM 是基于概率的聚类方法,提供了每个点属于每个簇的概率(软标签),相比硬聚类方法(如 K-Means),能提供更多的信息。
- 自动估计簇的数量:通过 AIC/BIC(赤池信息准则/贝叶斯信息准则)等准则,GMM 可以自动估计最佳的簇数量。
- 缺点:
- 计算复杂度高:GMM 在训练时需要计算协方差矩阵,其计算复杂度比 K-Means 高得多,适用于数据集较小或中等规模的场景。
- 对初始化敏感:与 K-Means 类似,GMM 也对初始参数(例如均值、协方差矩阵等)较为敏感,容易陷入局部最优解。
- 假设数据符合高斯分布:GMM 假设簇是由高斯分布生成的,若簇形状较为复杂,可能不适用。
- DBSCAN(Density-Based Spatial Clustering of Applications with Noise)
- 算法流程:
DBSCAN 是一种基于密度的聚类算法,能够发现任意形状的簇。算法的核心步骤包括:- 对每个点,计算其邻域内的点数(根据 eps 和 min_samples 参数)。
- 如果点的邻域内点数大于或等于 min_samples,则将其标记为核心点,开始扩展簇。
- 对于核心点的邻域内所有点进行扩展,形成一个簇。
- 对于密度不够的区域,标记为噪声点。
- 优点:
- 无需预设簇数:DBSCAN 不需要指定簇的数量,可以自动识别簇的数量。
- 适应任意形状簇:DBSCAN 基于密度,可以识别任意形状的簇,不局限于球形或椭圆形。
- 抗噪声:DBSCAN 能够自动识别并排除噪声点(即“离群点”),这使得它在处理有噪声的数据时表现良好。
- 缺点:
- 对参数敏感:DBSCAN 需要两个参数:eps(距离阈值)和 min_samples(最小样本数)。选择不当的参数可能导致聚类效果不佳。
- 高维数据效果差:在高维数据中,DBSCAN 的表现可能较差,因为距离度量在高维空间中往往不再可靠(即“维度灾难”)。
- 不适合处理大小差异较大的簇:对于簇大小差异较大的数据集,DBSCAN 可能无法很好地聚类。
- 低密度连接会导致类合并:假设有两个明显分离的簇,簇 A 和簇 B,它们之间的点密度较低。假如 eps 参数设置较大,或者簇之间的边界区域恰好有一些点,这些点会被误认为是簇之间的桥梁。DBSCAN 会把这两个簇连接在一起,形成一个错误的聚类结果。(解决方法:缩小eps邻域半径,增大min_samples一个簇的最小点数;数据预处理时去除噪声)
- Mean Shift
-
算法流程:
Mean Shift 是基于密度的聚类算法,通过找到数据密度的“峰值”来进行聚类。算法的核心步骤包括:- 对数据中的每个点,计算其邻域内的密度中心。
- 将每个点移动到其密度峰值方向,直到收敛。
- 聚类由密度峰值所在的区域组成,每个区域即为一个簇。
-
优点:
- 无需预设簇数:Mean Shift 是基于密度的算法,不需要指定簇的数量,能够根据数据分布自动确定簇数。
- 适应任意形状簇:Mean Shift 可以发现任意形状的簇,适应性强。
- 能处理密度变化的簇:适用于簇的密度变化较大的数据。
-
缺点:
- 计算复杂度高:Mean Shift 需要对数据集中的每个点进行密度估计,计算量较大,效率较低。
- 对窗口大小敏感:Mean Shift 算法的效果依赖于窗口(带宽)大小,选择不当的窗口大小可能会导致结果不理想。
- Spectral Clustering
-
算法流程:
Spectral Clustering 是基于图论的聚类算法。它首先计算数据点之间的相似度矩阵,并使用图的拉普拉斯矩阵进行特征分解。核心步骤包括:- 计算相似度矩阵,表示数据点之间的关系。
- 计算图的拉普拉斯矩阵,并进行特征分解。
- 选择前 K 个特征向量,并将数据点嵌入到新的特征空间中。
- 在嵌入空间中使用 K-Means 进行聚类。
-
优点:
- 适应复杂簇形状:Spectral Clustering 是通过图论的方法来进行聚类,能够处理复杂形状的簇,尤其适合非凸形状的数据。
- 可以利用图的结构信息:通过构造相似度矩阵,Spectral Clustering 能够考虑数据点之间的全局关系,有时能找到一些基于局部结构的有趣模式。
-
缺点:
- 计算开销大:Spectral Clustering 的计算复杂度较高,特别是当数据集很大时,计算相似度矩阵的时间成本较高。
- 需要设置相似度度量:如何定义数据点之间的相似度(例如,使用欧式距离或其他度量)对结果有较大影响。
- Agglomerative Clustering(层次聚类)
-
算法流程:
层次聚类通过逐步合并(或分割)簇来生成树状图(树形结构)。其核心步骤包括:- 从每个数据点开始,每个数据点都属于一个独立的簇。
- 计算簇之间的距离,选择距离最小的两个簇合并。
- 重复以上过程,直到所有数据点都合并为一个簇。
-
优点:
- 无需预设簇数:层次聚类通过构建树形结构(树状图)来聚类,不需要预设簇数,可以根据树的高度选择合适的聚类数。
- 适用于不规则簇形:能处理形状复杂的簇,尤其适用于数据的层次结构。
-
缺点:
- 计算复杂度较高:层次聚类需要计算所有点对的距离,计算复杂度较高,特别是当数据集很大时。
- 对噪声敏感:层次聚类对数据中的噪声点比较敏感,可能会影响结果。
- Birch(Balanced Iterative Reducing and Clustering Using Hierarchies)
- 算法流程:
Birch 是一种高效的层次聚类算法,通过构建一个 CF(Clustering Feature)树来组织数据。核心步骤包括:- 将数据分成多个簇,每个簇都有一个 CF 树节点。
- 逐步合并簇,直到满足停止条件。
- 优点:
- 高效处理大规模数据:Birch 适用于大规模数据集,通过构建 CF(Clustering Feature)树进行聚类,计算效率较高。
- 能够处理不规则簇:通过对数据进行层次结构的划分,Birch 可以处理复杂形状的簇。
- 缺点:
- 需要预设簇数:Birch 需要事先指定簇的数量,可能无法自动选择最优簇数。
- 对离群点敏感:Birch 在处理有噪声或离群点的数据时,表现不如其他聚类算法。
图像压缩
图像压缩——色彩压缩算法实现
任务描述:
本任务的目标是通过对图像进行色彩压缩,保留 K 个颜色来实现图像压缩。具体而言,使用 K-Means 聚类算法,将图像中的像素分成 K 个簇,并用每个簇的中心点(聚类中心)来代表该簇内的所有像素,从而减少图像的色彩种类,并实现图像压缩。
算法流程:
- 加载图像:读取输入的图像并将其转换为 RGB 格式。
- 数据预处理:将图像数据转换为二维数组,每行表示一个像素点的 RGB 值。
- K-Means 聚类:使用 K-Means 聚类算法将图像的所有像素分为 K 个簇,并计算每个簇的中心点(即色彩代表)。
- 重建图像:根据 K-Means 聚类的结果,使用每个簇的中心点替换该簇中的所有像素,从而得到压缩后的图像。
- 显示与保存图像:将原始图像和压缩后的图像展示在同一图表中,同时保存压缩后的图像。
具体步骤:
- 加载图像数据:读取输入图像,并将其转换为 RGB 格式,以便进行后续处理。
- 点云数据转换:将图像转换为一个二维数组,每个元素代表一个 RGB 值。
- K-Means 聚类处理:使用 K-Means 算法对图像的颜色进行聚类,将原图的颜色压缩为 K 个颜色。
- 图像重建:用聚类中心的颜色替换原图像中的像素,从而实现图像的色彩压缩。
核心代码
import os.path
import numpy as np
import cv2
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
def load_image(image_path):
img = cv2.imread(image_path) # 读取图像
if img is None:
print("Error: Unable to load image. Check the file path or file format.")
return None # 图像加载失败
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转换为 RGB
return img_rgb
# 2. 将图像数据转化为二维数组(每行代表一个像素的RGB值)
def reshape_image(img_rgb):
pixels = img_rgb.reshape(-1, 3) # 将图像数据从 (height, width, 3) 转为 (num_pixels, 3)
return pixels
# 3. 使用 KMeans 对颜色进行聚类
def apply_kmeans(pixels, k=5):
kmeans = KMeans(n_clusters=k)
kmeans.fit(pixels)
return kmeans
# 4. 根据聚类结果重建图像
def reconstruct_image(kmeans, pixels, img_shape):
# 用聚类中心替换每个像素的颜色
new_pixels = kmeans.cluster_centers_[kmeans.labels_]
new_pixels = new_pixels.astype(np.uint8)
# 将一维数据恢复为图像的原始形状
new_img = new_pixels.reshape(img_shape)
return new_img
# 5. 显示并保存图像
def show_image(img_rgb, new_imgs, k_list):
# 获取原始图像的尺寸
height, width, _ = img_rgb.shape
# 自适应设置 figsize (根据图像的大小,自动设置图形窗口的大小)
figsize = (width / 100, height / 100) # 设置为每个像素约 1/100 英寸
# 根据 k_list 的长度创建子图
n_images = len(k_list) + 1 # 1 个是原始图像,剩下的是 KMeans 聚类后的图像
fig, ax = plt.subplots(1, n_images, figsize=figsize)
# 显示原始图像
ax[0].imshow(img_rgb)
ax[0].set_title('Original Image')
ax[0].axis('off') # 不显示坐标轴
# 显示每个降维后的图像
for i, new_img in enumerate(new_imgs):
ax[i + 1].imshow(new_img)
ax[i + 1].set_title(f'KMeans (k={k_list[i]})')
ax[i + 1].axis('off') # 不显示坐标轴
# 自适应展示图像
plt.tight_layout() # 调整布局,避免图片显示重叠
plt.show()
# 6. 主程序
def main(root_path, image_path, k):
img_rgb = load_image(image_path) # 读取图像
pixels = reshape_image(img_rgb) # 转换为二维像素数组
img_name, img_extension = os.path.splitext(os.path.basename(image_path))
new_imgs = []
for i in k:
new_img_name = f"{img_name}_k={i}{img_extension}"
output_path = os.path.join(root_path, new_img_name)
kmeans = apply_kmeans(pixels, i) # 进行KMeans聚类
new_img = reconstruct_image(kmeans, pixels, img_rgb.shape) # 重建图像
# 如果图像的深度不是 CV_8U(uint8),进行转换
if new_img.dtype != np.uint8:
# 将图像像素值限定在 [0, 255] 范围,并转换为 uint8 类型
new_img = np.clip(new_img, 0, 255).astype(np.uint8)
new_img_bgr = cv2.cvtColor(new_img, cv2.COLOR_RGB2BGR)
new_imgs.append(new_img)
cv2.imwrite(output_path, new_img_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 45])
show_image(img_rgb, new_imgs, k) # 显示图像
# 调用主程序
if __name__ == "__main__":
image_path = os.path.join('E:\\', 'WY', 'Dataset', 'pic', 'child.jpeg')
print("Image path:", image_path) # 打印路径
root_path = os.path.join('E:\\', 'WY', 'Dataset', 'pic')
# 替换成你的图像路径
k = [8,16,32]
main(root_path, image_path, k) # k=8 表示将图像的颜色降到8种颜色
注意点:
- cv2.imwrite(output_path, new_img_bgr, [int(cv2.IMWRITE_JPEG_QUALITY), 45]) 45用来调整压缩程序 过高会导致压缩图比原图存储更大
- opencv展示的是bgr 存储需要转rgb
结果展示

&spm=1001.2101.3001.5002&articleId=148796199&d=1&t=3&u=16044f9b007e4ba883ba2cf08ef229ae)
1238

被折叠的 条评论
为什么被折叠?



