news 2026/8/22 6:27:05

K-means聚类算法全解析:从原理到Python实战与客户细分应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
K-means聚类算法全解析:从原理到Python实战与客户细分应用

1. 从“物以类聚”到K-means:一个聚类问题的直观引入

想象一下,你是一家大型超市的运营经理,手头有过去一年所有顾客的购物数据,包括他们每次购物的总金额、购买频次、偏好的商品类别等等。老板给你下达了一个任务:基于这些数据,将顾客分成几个有意义的群体,以便针对不同群体制定差异化的营销策略,比如对高频高消费的顾客推送高端新品,对低频低消费的顾客发送优惠券刺激消费。面对成千上万条杂乱无章的数据记录,你不可能手动去给每个顾客贴标签。这时候,你需要一种能够自动发现数据内在分组结构的工具,这就是聚类分析。

聚类,顾名思义,就是“物以类聚,人以群分”。它是一种无监督学习方法,目标是在没有预先给定标签的情况下,将数据集中的样本划分成若干个“簇”或“类”,使得同一个簇内的样本彼此相似,而不同簇的样本差异较大。在数学建模、数据分析、市场细分、图像分割等领域,聚类都是基础且强大的工具。而在众多聚类算法中,K-means以其思想直观、实现简单、效率较高的特点,成为了最经典、应用最广泛的入门算法,没有之一。很多复杂的聚类模型,其核心思想也往往源于对K-means的改进。

今天,我们就来彻底拆解K-means算法。我不会只给你一个干巴巴的公式或者几行代码,而是会带你走完从理解核心思想、手动推导迭代过程、用Python一步步实现,再到分析结果、规避常见陷阱的完整路径。你会发现,这个看似简单的算法背后,藏着不少值得深思的细节。无论你是正在备战数模竞赛的学生,还是初涉数据分析领域的开发者,掌握K-means都将是你的重要技能基石。

2. K-means算法的核心思想与迭代过程拆解

K-means算法的目标非常明确:给定一个包含n个数据点的数据集和预设的簇数量K,算法要将n个点划分到K个簇中,使得每个点到其所属簇的“中心点”的距离平方和最小。这个“中心点”就是我们常说的“质心”。最小化距离平方和,本质上是在寻找一种划分,使得簇内尽可能紧凑,簇间尽可能分离。

2.1 算法步骤的“白话”解读

标准的K-means算法描述通常包括以下几步,我们用人话翻译一下:

  1. 初始化:随机选择K个数据点作为初始的“质心”。你可以理解为,先拍脑袋指定K个“队长”。
  2. 分配阶段:对于数据集中的每一个点,计算它到K个质心的距离(通常是欧氏距离)。然后,将这个点分配给距离它最近的那个质心所在的簇。这一步就是“站队”,所有点找到离自己最近的“队长”并归入其队伍。
  3. 更新阶段:所有点都分配完毕后,重新计算每个簇的质心。计算方法是取该簇内所有数据点各维度的平均值。新的质心可能不再是原始的数据点。这一步是“改选队长”,根据当前队伍里所有成员的平均位置,推举出新的“队长”。
  4. 迭代:重复步骤2和步骤3,直到满足停止条件。常见的停止条件有:质心的位置不再发生显著变化(即移动距离小于某个阈值),或者簇的分配情况不再改变,或者达到了预设的最大迭代次数。

这个过程听起来很简单,但其背后优化的是一个称为“簇内误差平方和”的目标函数。这个目标函数的值会在每次迭代后单调递减(或不变),最终收敛到一个局部最优解。这里有个关键点:K-means找到的通常是局部最优解,而非全局最优。初始质心的选择会极大影响最终结果,这也是算法的一个主要痛点,我们后面会详细讨论。

2.2 距离度量:不仅仅是“直线距离”

在分配阶段,我们提到了“距离”。最常用的是欧氏距离,也就是我们中学学的两点间直线距离。在二维或三维空间里,这很直观。但在高维数据中,欧氏距离可能不是唯一或最佳的选择。

  • 欧氏距离dist = sqrt((x1-y1)^2 + (x2-y2)^2 + ...)。它对所有维度一视同仁,且受量纲影响大。如果数据中一个特征是“年薪(单位:万元)”,另一个特征是“年龄”,直接计算欧氏距离会让“年薪”完全主导结果。因此,对数据进行标准化或归一化预处理,是使用欧氏距离进行K-means聚类前几乎必不可少的步骤
  • 曼哈顿距离dist = |x1-y1| + |x2-y2| + ...。想象在城市网格中行走,不能斜穿,只能沿街道走。它对异常值不如欧氏距离敏感。
  • 余弦相似度:通过计算两个向量夹角的余弦值来衡量方向上的相似性,在文本聚类(如TF-IDF向量)中非常常用。

对于K-means,我们通常默认使用欧氏距离,因为它与最小化误差平方和的目标函数在数学上是一致的。但在具体应用中,理解你的数据特征并选择合适的距离度量(或进行合适的数据预处理)至关重要。

2.3 手动模拟:看透一次迭代

让我们用一个极简的二维例子手动走一遍。假设我们有6个点:A(1,1), B(1,2), C(2,1), D(8,8), E(8,9), F(9,8)。我们设定K=2。

  • 初始化:随机选A(1,1)和D(8,8)作为初始质心C1和C2。
  • 第一次分配
    • 计算所有点到C1和C2的欧氏距离。
    • A到C1距离为0,到C2距离约为9.9,故A属于簇1。
    • B到C1距离为1,到C2距离约为9.2,故B属于簇1。
    • C到C1距离为1,到C2距离约为9.2,故C属于簇1。
    • D到C1距离约为9.9,到C2距离为0,故D属于簇2。
    • E到C1距离约为10.6,到C2距离为1,故E属于簇2。
    • F到C1距离约为10.6,到C2距离约为1.4,故F属于簇2。
    • 分配结果:簇1: {A, B, C};簇2: {D, E, F}。
  • 第一次更新
    • 新质心C1' = ( (1+1+2)/3, (1+2+1)/3 ) = (1.33, 1.33)
    • 新质心C2' = ( (8+8+9)/3, (8+9+8)/3 ) = (8.33, 8.33)
  • 第二次分配
    • 用新的质心重新计算距离并分配,你会发现点的归属没有变化。
  • 停止:因为分配不再变化,算法收敛。我们得到了一个很直观的聚类结果。

这个例子很理想,数据本身分离得就很开。但现实中数据往往纠缠在一起,初始质心如果没选好,可能就会得到奇怪的聚类结果,比如把本该属于两个簇边界上的点都划给了一个质心,导致聚类效果不佳。

3. Python实战:从零实现与sklearn调库双视角

理解了原理,我们就要动手实现。我会带你用两种方式完成:一种是使用NumPy从零开始构建,帮助你吃透每一个细节;另一种是使用scikit-learn库,这是实际工作中最高效、最稳健的做法。

3.1 环境准备与数据生成

首先,确保你的Python环境安装了必要的库:numpy,matplotlib,scikit-learn。可以通过pip install numpy matplotlib scikit-learn来安装。

为了演示,我们使用sklearnmake_blobs函数生成一份模拟数据。它专门用于生成适合聚类测试的、具有明显球形簇的数据。

import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_blobs # 设置随机种子,确保结果可复现 np.random.seed(42) # 生成数据 # n_samples: 样本数 # centers: 簇的中心点 # cluster_std: 簇的标准差,控制点的分散程度 # random_state: 随机状态 X, y_true = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=42) # 可视化生成的数据 plt.figure(figsize=(8, 6)) plt.scatter(X[:, 0], X[:, 1], s=50, alpha=0.7) plt.title("Generated Data for Clustering") plt.xlabel("Feature 1") plt.ylabel("Feature 2") plt.grid(True, linestyle='--', alpha=0.5) plt.show()

运行这段代码,你会看到300个点大致分布在4个区域。y_true是每个点真实的簇标签,但在无监督学习中我们假装不知道它,只使用X进行聚类。

3.2 核心实现:手搓一个K-means

自己实现一遍,是对算法理解最好的检验。我们将遵循之前描述的步骤。

class MyKMeans: def __init__(self, n_clusters=3, max_iter=300, tol=1e-4, random_state=None): """ 初始化MyKMeans类 Args: n_clusters (int): 簇的数量K max_iter (int): 最大迭代次数 tol (float): 容忍度,质心移动距离小于此值则停止 random_state (int): 随机种子 """ self.n_clusters = n_clusters self.max_iter = max_iter self.tol = tol self.random_state = random_state self.centroids = None # 质心 self.labels = None # 每个点的簇标签 self.inertia_ = None # 最终的簇内误差平方和 def _initialize_centroids(self, X): """随机初始化质心""" np.random.seed(self.random_state) # 从数据点中随机选择K个作为初始质心 indices = np.random.choice(X.shape[0], self.n_clusters, replace=False) return X[indices] def _compute_distances(self, X, centroids): """计算每个点到所有质心的欧氏距离平方(节省开方计算)""" # X: (n_samples, n_features) # centroids: (n_clusters, n_features) # 利用广播机制计算距离矩阵 (n_samples, n_clusters) distances = np.sum((X[:, np.newaxis, :] - centroids) ** 2, axis=2) return distances def fit(self, X): """ 训练模型,找到质心并分配标签 Args: X (np.ndarray): 形状为 (n_samples, n_features) 的训练数据 """ n_samples = X.shape[0] # 1. 初始化质心 self.centroids = self._initialize_centroids(X) for i in range(self.max_iter): # 2. 分配阶段:计算距离,分配标签 distances = self._compute_distances(X, self.centroids) self.labels = np.argmin(distances, axis=1) # 每个点距离最近的质心索引 # 3. 更新阶段:计算新的质心 new_centroids = np.zeros_like(self.centroids) for k in range(self.n_clusters): # 获取属于第k簇的所有点 cluster_points = X[self.labels == k] if len(cluster_points) > 0: new_centroids[k] = cluster_points.mean(axis=0) else: # 如果某个簇没有点,则重新随机初始化该质心(处理空簇问题) new_centroids[k] = X[np.random.randint(0, n_samples)] # 4. 检查收敛条件:质心移动是否很小 centroid_shift = np.sqrt(((new_centroids - self.centroids) ** 2).sum(axis=1)).max() if centroid_shift < self.tol: print(f"Converged at iteration {i+1}") break self.centroids = new_centroids else: print(f"Reached maximum iteration {self.max_iter}") # 计算最终的簇内误差平方和 self.inertia_ = 0 for k in range(self.n_clusters): cluster_points = X[self.labels == k] if len(cluster_points) > 0: self.inertia_ += ((cluster_points - self.centroids[k]) ** 2).sum() return self def predict(self, X): """对新数据点预测所属簇""" distances = self._compute_distances(X, self.centroids) return np.argmin(distances, axis=1)

代码要点解析与避坑指南:

  1. 距离计算优化:在_compute_distances函数中,我们计算的是距离的平方,没有进行开方。因为开方是一个单调递增函数,比较距离大小时,比较平方和与比较实际距离是等价的,但计算平方和更快。这是常见的优化技巧。
  2. 空簇问题:在更新质心的循环中,我们检查了if len(cluster_points) > 0。如果一个簇在分配阶段没有分配到任何点(空簇),直接计算均值会出错。我们的处理方式是随机选择一个数据点作为该簇的新质心。这是一种简单的处理策略,其他策略包括将空簇的质心设置为离当前质心最远的点等。
  3. 收敛判断:我们监控所有质心移动的最大值(centroid_shift)。当这个最大值小于预设的容忍度tol时,认为算法已经收敛。tol通常设为一个很小的数,如1e-4
  4. inertia_属性:我们仿照sklearn的计算方式,在拟合完成后计算了总的簇内误差平方和。这个值越小,说明簇内越紧凑,是评估聚类效果的一个内部指标(但需谨慎使用,后面会讲)。

现在,让我们用自己写的类来聚类之前生成的数据,并可视化结果。

# 使用自定义K-means聚类 my_kmeans = MyKMeans(n_clusters=4, max_iter=300, random_state=42) my_kmeans.fit(X) labels_custom = my_kmeans.labels centroids_custom = my_kmeans.centroids # 可视化聚类结果 plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.scatter(X[:, 0], X[:, 1], c=labels_custom, s=50, cmap='viridis', alpha=0.7) plt.scatter(centroids_custom[:, 0], centroids_custom[:, 1], c='red', s=200, marker='X', label='Centroids') plt.title("Clustering Result (Custom Implementation)") plt.xlabel("Feature 1") plt.ylabel("Feature 2") plt.legend() plt.grid(True, linestyle='--', alpha=0.5) plt.subplot(1, 2, 2) plt.scatter(X[:, 0], X[:, 1], c=y_true, s=50, cmap='viridis', alpha=0.7) plt.title("True Labels (Ground Truth)") plt.xlabel("Feature 1") plt.ylabel("Feature 2") plt.grid(True, linestyle='--', alpha=0.5) plt.tight_layout() plt.show() print(f"Custom K-means inertia (SSE): {my_kmeans.inertia_:.2f}")

将自定义结果与真实标签对比,如果数据生成得好且随机种子合适,你应该能看到两者几乎一致。inertia_值是一个具体的数值。

3.3 工业级实践:使用scikit-learn

在实际项目和数模竞赛中,我们几乎不会从头写K-means,而是使用经过高度优化的sklearn.cluster.KMeans。它的API简洁、功能强大、效率极高。

from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 使用sklearn的KMeans sklearn_kmeans = KMeans(n_clusters=4, init='k-means++', n_init=10, max_iter=300, random_state=42) sklearn_kmeans.fit(X) labels_sklearn = sklearn_kmeans.labels_ centroids_sklearn = sklearn_kmeans.cluster_centers_ print(f"Sklearn K-means inertia: {sklearn_kmeans.inertia_:.2f}") print(f"Sklearn K-means iterations: {sklearn_kmeans.n_iter_}") # 计算轮廓系数(Silhouette Score),一种评估聚类效果的指标 silhouette_avg = silhouette_score(X, labels_sklearn) print(f"Silhouette Score: {silhouette_avg:.3f}")

关键参数详解:

  • n_clusters: 最重要的参数,即K值。
  • init: 初始化质心的方法。
    • 'random': 随机选择K个点。这是最原始的方法,效果不稳定。
    • 'k-means++':默认且推荐的方法。它是一种智能初始化方法,能有效降低算法对初始质心的敏感性,提高收敛速度和最终结果的质量。其核心思想是让初始质心彼此尽量远离。
  • n_init: 用不同的初始质心运行算法的次数。K-means最终结果依赖于初始质心,为了得到一个相对稳定的结果,sklearn默认会运行10次(n_init=10),并选择inertia_最小的那次作为最终模型。这是一个非常重要的实践技巧,能极大提升结果的鲁棒性。
  • max_iter: 单次运行的最大迭代次数。
  • random_state: 随机种子,保证结果可复现。
  • algorithm: 内部使用的算法,'lloyd'是经典算法,'elkan'是一种利用三角不等式的优化算法,适用于数据维度不高的情况,通常更快。

使用sklearn,我们只需几行代码就能获得一个稳定、高效的聚类模型。silhouette_score是另一个常用的聚类评估指标,取值范围在[-1, 1],越接近1表示聚类效果越好。它同时考虑了簇内的凝聚度和簇间的分离度。

4. 核心挑战与进阶思考:K值选择、评估与局限

实现算法只是第一步。在实际应用中,以下几个问题才是真正的挑战和考验你理解深度的地方。

4.1 如何确定最佳的K值?

“我应该把数据分成几类?”这是使用K-means时最常被问到,也最难回答的问题。因为K-means本身不会告诉你K是多少。以下是几种常用的方法:

  1. 肘部法则:最直观的方法。原理是计算不同K值下的簇内误差平方和(inertia_),并绘制折线图。随着K增大,每个簇更小更紧凑,inertia_会下降。我们希望找到一个点,增加K带来的inertia_下降幅度突然变缓,这个拐点就像“肘部”,对应的K值可能就是最佳选择。

    inertias = [] K_range = range(1, 11) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42) kmeans.fit(X) inertias.append(kmeans.inertia_) plt.figure(figsize=(8, 5)) plt.plot(K_range, inertias, 'bo-') plt.xlabel('Number of clusters (K)') plt.ylabel('Inertia (SSE)') plt.title('The Elbow Method') plt.grid(True) plt.show()

    观察图像,寻找那个明显的“拐点”。但很多时候,拐点并不明显,需要主观判断。

  2. 轮廓系数法:计算不同K值下所有样本的平均轮廓系数。轮廓系数越接近1,说明聚类效果越好。我们可以选择使平均轮廓系数最大的K值。

    silhouette_scores = [] K_range = range(2, 11) # 轮廓系数要求至少2个簇 for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42) labels = kmeans.fit_predict(X) score = silhouette_score(X, labels) silhouette_scores.append(score) plt.figure(figsize=(8, 5)) plt.plot(K_range, silhouette_scores, 'go-') plt.xlabel('Number of clusters (K)') plt.ylabel('Silhouette Score') plt.title('Silhouette Score Method') plt.grid(True) plt.show()
  3. 业务理解与可视化:在低维(2维或3维)情况下,将数据可视化,观察其自然分布,结合你对业务背景的理解来确定K值。例如,在市场细分中,你可能根据经验将客户分为“高价值”、“中价值”、“低价值”三类。

注意:没有一种方法是绝对正确的。肘部法则和轮廓系数法都是启发式方法,需要结合具体数据和业务场景综合判断。有时,你可能需要尝试多个K值,并对比不同结果在业务上的解释性。

4.2 如何评估聚类效果?

在无监督学习中,因为没有真实标签,评估一直是个难题。我们通常从两个角度评估:

  • 内部评估:不借助外部信息,仅基于数据本身和聚类结果。常用的指标有:

    • inertia_(簇内误差平方和):值越小越好,但会随K增大而单调减小,不能单独用来确定K。
    • 轮廓系数:综合考虑了簇内凝聚度和簇间分离度,值在[-1,1]之间,越大越好。
    • 戴维森堡丁指数:值越小表示聚类越好。
    • 卡林斯基-哈拉巴斯指数:值越大表示聚类越好。
  • 外部评估:如果有真实标签(就像我们生成数据时的y_true),可以像监督学习一样评估。常用指标有:

    • 调整兰德指数:衡量两个聚类结果(预测和真实)的相似度,取值范围[-1,1],1表示完全一致,0表示随机,负数表示差于随机。
    • 互信息:衡量两个聚类结果共享的信息量。
    • 同质性、完整性和V-measure:一组相关的指标。

在数模竞赛或实际分析中,如果问题背景能提供一些“应该”的类别信息,可以将其作为外部评估的参考。更多时候,我们需要结合内部评估指标和聚类结果的可解释性(即聚类出的每个簇在业务上是否说得通)来综合判断。

4.3 K-means的“阿喀琉斯之踵”:你必须知道的局限性

K-means强大但并非万能,理解它的局限性才能正确使用它。

  1. 必须预先指定K:这是它最大的限制,如上所述。
  2. 对初始值敏感:虽然k-means++初始化大大改善了这个问题,但算法仍然可能收敛到局部最优。多次运行(n_init)是标准操作。
  3. 对噪声和异常值敏感:质心的计算是求均值,均值受极端值影响很大。一个远离群体的异常点会严重扭曲质心的位置。
  4. 假设簇是凸形和球形:K-means使用欧氏距离,它隐含地假设簇是球状的,且各个簇的方差相近。对于非球形(如流形、环形)或大小差异很大的簇,K-means效果会很差。
  5. 不适合处理分类数据:K-means基于距离度量,天然适用于数值型数据。对于分类数据,需要先进行编码(如独热编码),并且要谨慎选择距离度量。

针对局限性的应对策略:

  • 数据预处理至关重要:标准化/归一化处理数值特征;对于异常值,可以考虑使用更稳健的算法(如K-medoids),或在聚类前进行清洗。
  • 尝试其他算法:如果数据形状复杂,可以考虑DBSCAN(基于密度,能发现任意形状簇,且能识别噪声点)、层次聚类、高斯混合模型等。
  • 特征工程:有时通过特征变换(如PCA降维)可以将数据映射到新的空间,使其更符合K-means的假设。

5. 综合案例:客户细分实战

让我们用一个更贴近实际的例子来整合所有知识。假设我们有一份客户消费数据集(customer_data.csv),包含“年收入(千元)”和“消费分数(1-100)”两个特征。我们的目标是对客户进行细分。

import pandas as pd # 假设我们加载了数据 # customer_df = pd.read_csv('customer_data.csv') # 这里我们用生成的数据模拟 np.random.seed(123) income = np.random.normal(50, 15, 200) # 年收入,均值50,标准差15 spending = income * 0.7 + np.random.normal(0, 5, 200) # 消费分数与收入正相关 customer_data = np.column_stack((income, spending)) # 1. 数据标准化(非常重要!) from sklearn.preprocessing import StandardScaler scaler = StandardScaler() customer_data_scaled = scaler.fit_transform(customer_data) # 2. 使用肘部法则和轮廓系数确定K inertias = [] silhouettes = [] K_range = range(2, 11) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42) labels = kmeans.fit_predict(customer_data_scaled) inertias.append(kmeans.inertia_) if k >= 2: # 轮廓系数需要至少2个簇 silhouettes.append(silhouette_score(customer_data_scaled, labels)) # 绘制肘部法则图 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) ax1.plot(range(2, 11), inertias, 'bo-') ax1.set_xlabel('Number of clusters (K)') ax1.set_ylabel('Inertia') ax1.set_title('Elbow Method for Customer Data') ax1.grid(True) # 绘制轮廓系数图 ax2.plot(range(2, 11), silhouettes, 'go-') ax2.set_xlabel('Number of clusters (K)') ax2.set_ylabel('Silhouette Score') ax2.set_title('Silhouette Score for Customer Data') ax2.grid(True) plt.tight_layout() plt.show()

观察两个图,假设我们判断K=3或K=4是一个合理的折中点。我们选择K=3进行后续分析。

# 3. 使用K=3进行最终聚类 final_kmeans = KMeans(n_clusters=3, random_state=42) customer_labels = final_kmeans.fit_predict(customer_data_scaled) customer_centroids_scaled = final_kmeans.cluster_centers_ # 将质心反标准化回原始尺度,便于业务解释 customer_centroids_original = scaler.inverse_transform(customer_centroids_scaled) # 4. 可视化结果(原始尺度) plt.figure(figsize=(10, 7)) scatter = plt.scatter(customer_data[:, 0], customer_data[:, 1], c=customer_labels, s=50, cmap='Set2', alpha=0.7, edgecolors='k') plt.scatter(customer_centroids_original[:, 0], customer_centroids_original[:, 1], c='red', s=300, marker='X', label='Cluster Centers') plt.xlabel('Annual Income (k$)') plt.ylabel('Spending Score (1-100)') plt.title('Customer Segmentation using K-means (K=3)') plt.legend() plt.grid(True, linestyle='--', alpha=0.5) # 为每个簇添加注释 for i, centroid in enumerate(customer_centroids_original): plt.annotate(f'Cluster {i}\n({centroid[0]:.1f}, {centroid[1]:.1f})', xy=centroid, xytext=(10, 10), textcoords='offset points', ha='center', bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8)) plt.show() # 5. 分析聚类结果 customer_df = pd.DataFrame(customer_data, columns=['Income', 'Spending']) customer_df['Cluster'] = customer_labels cluster_profile = customer_df.groupby('Cluster').agg({'Income': ['mean', 'std'], 'Spending': ['mean', 'std'], 'Cluster': 'count'}) cluster_profile.columns = ['Income_Mean', 'Income_Std', 'Spending_Mean', 'Spending_Std', 'Count'] print(cluster_profile)

通过分析cluster_profile表格和可视化图,我们可以对客户群体进行描述:

  • 簇0(高收入高消费):收入和消费分数都最高,是核心价值客户。
  • 簇1(低收入低消费):收入和消费分数都较低,可能是价格敏感型或新客户。
  • 簇2(中等收入中等消费):最大的客户群体,是基本盘。

基于这个分析,市场部门可以制定策略:对簇0推送高端服务和忠诚度计划;对簇1发送优惠券和入门产品推荐,尝试提升其价值;对簇2维持现有服务,并尝试交叉销售。

这个案例展示了从数据预处理、确定K值、实施聚类到结果分析和业务解读的完整流程。K-means在这里表现良好,因为我们的模拟数据大致呈球形分布,且经过了标准化处理。在实际项目中,你可能会遇到更复杂的数据分布,那时就需要结合我们前面讨论的局限性,考虑更高级的聚类方法了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 6:27:05

Python生存分析实战:用lifelines库处理用户流失与删失数据

1. 从数据到洞察&#xff1a;为什么生存分析值得你投入时间 如果你处理过用户流失、设备故障、客户复购或者疾病复发这类数据&#xff0c;你大概率会感到一丝别扭。传统的分析方法&#xff0c;比如计算平均留存时间或者故障率&#xff0c;在面对一个关键事实时往往会失效&#…

作者头像 李华
网站建设 2026/8/22 6:26:08

VMware Workstation Pro 虚拟机安装与配置 Linux 系统全流程指南

这次我们来看 VMware Workstation Pro 虚拟机安装和激活 Linux 系统的完整流程。对于开发者、运维人员或学生来说&#xff0c;在本地 Windows 系统上搭建一个隔离的 Linux 环境进行学习、测试和开发&#xff0c;是刚需且高效的选择。VMware 作为老牌虚拟机软件&#xff0c;其稳…

作者头像 李华
网站建设 2026/8/22 6:25:58

OpenAI内部文化动荡对开发者生态的影响与应对策略

这次我们来看一个关于 OpenAI 内部文化的话题。项目标题“前员工&#xff1a;OpenAI 员工言论自由空前”并非指一个技术工具或模型&#xff0c;而是一则关于 OpenAI 公司内部管理文化的评论。对于技术社区的读者而言&#xff0c;这背后反映的可能是公司治理、技术伦理、开源与闭…

作者头像 李华
网站建设 2026/8/22 6:23:22

数学建模竞赛C题解题心法:从问题抽象到模型求解的完整实战指南

1. 项目概述&#xff1a;从“解题”到“建模”的思维跃迁又到了一年一度的华数杯数学建模竞赛季&#xff0c;相信不少同学&#xff0c;尤其是第一次接触这类竞赛的朋友&#xff0c;拿到C题题目时&#xff0c;心里多少会有些发怵。题目描述可能涉及一堆数据、几个看似矛盾的目标…

作者头像 李华
网站建设 2026/8/22 6:19:26

本地文档脱敏工具Sanitizer:保障LLM数据安全与隐私

你好&#xff0c;我是CSDN的一名技术博主。在日常开发中&#xff0c;尤其是处理企业级数据时&#xff0c;我们常常需要借助大语言模型&#xff08;LLM&#xff09;来分析文档、生成摘要或提取信息。然而&#xff0c;直接将包含敏感信息&#xff08;如身份证号、手机号、邮箱、密…

作者头像 李华
网站建设 2026/8/22 6:19:12

ContextBudget:资源受限下智能体的上下文管理优化策略

1. 长视野搜索智能体的核心挑战&#xff1a;信息过载与资源瓶颈在构建能够执行复杂、多步骤任务的智能体时&#xff0c;我们常常会遇到一个看似矛盾的问题&#xff1a;为了让智能体做出更明智的决策&#xff0c;我们需要给它提供尽可能多的上下文信息&#xff1b;但信息越多&am…

作者头像 李华