1. 聚类到底在解决什么业务问题很多人第一次接触聚类脑子里浮现的都是教科书上那张把散点分成三堆的示意图觉得这玩意儿不就是画圈圈嘛。但真到了业务场景里聚类要回答的问题其实非常具体在没有标签的情况下怎么把一堆看起来差不多的东西按照它们内在的相似性自动归堆。举个我实际遇到过的场景。某电商平台手里攥着几十万条用户行为日志每条日志包含浏览时长、加购次数、下单频次、客单价、退货率等十几个字段。运营团队想针对不同人群做差异化触达但问题是——没人知道这几十万人应该分成几类更不知道每一类长什么样。这时候聚类就派上用场了它不需要你提前告诉它这是高价值用户这是薅羊毛党它自己会根据数据本身的分布把相似的用户聚到一起然后你再去看每一堆人的特征给他们起名字、定策略。这就是聚类和分类最本质的区别。分类是监督学习你得先有标注好的训练集告诉模型这条是A类那条是B类聚类是无监督学习你只给它一堆特征让它自己找结构。所以聚类的核心价值在于探索性分析——当你对数据的内部结构一无所知时用它来发现隐藏的分组模式。聚类能做的事情大致可以归为四类。第一类是客户分群刚才说的电商用户分层就是典型第二类是异常检测那些离所有簇都很远的点往往就是异常样本比如信用卡欺诈、设备故障第三类是数据压缩与预处理把几十万个样本聚成几百个簇心后续用簇心代表原始数据能大幅降低计算量第四类是图像与文本的粗分类比如把一堆没标注的新闻自动归成几个主题簇人工再去看每个簇的关键词。适合学聚类的人我觉得有三类。一类是刚入门数据挖掘、想找一个能快速看到效果的算法练手的同学一类是业务侧的分析师手里有数据但没标签想快速摸清用户或商品的分布结构还有一类是已经会用K-Means但总被K到底取几结果不稳定这些问题困扰、想系统搞明白背后原理的从业者。这篇文章我会从算法选型、原理拆解、实操步骤、踩坑经验几个角度把聚类这件事讲透。2. 主流聚类算法的选型逻辑与适用边界聚类算法不是一个算法而是一大家子。我见过太多人上来就用K-Means结果数据是环形分布K-Means怎么调都分不对。所以选型这一步比调参重要得多。2.1 基于划分的方法K-Means与K-MedoidsK-Means是聚类界的Hello World几乎所有人第一个学的就是它。它的逻辑非常朴素先随机选K个点当簇心然后把每个样本分配给离它最近的簇心再重新计算每个簇的中心点如此反复直到簇心不再移动。它的优点是快。时间复杂度是O(n×K×t)n是样本数K是簇数t是迭代次数对于几十万条数据、几十个特征普通机器几分钟就能跑完。但它的缺点也很明显它假设簇是球形的、大小相近的。如果你的数据是长条形、环形、或者密度差异极大K-Means会强行用球形去切结果一塌糊涂。K-Medoids是K-Means的变体区别在于它不用均值当簇心而是用真实存在的样本点当簇心。这样做的好处是对异常值更鲁棒——均值会被极端值拉偏但中位数式的真实点不会。代价是计算量更大因为每次迭代都要算所有点到所有候选簇心的距离。2.2 基于密度的方法DBSCANDBSCAN是我个人在探索性分析里最常用的算法。它的核心思想是簇是密度相连的点的最大集合。它有两个关键参数——邻域半径eps和最小点数minPts。如果一个点的eps邻域内至少有minPts个点它就是核心点核心点之间如果互相在eps范围内就归为同一个簇那些不属于任何簇的点就是噪声。DBSCAN最大的优势是不需要预先指定簇的数量而且能发现任意形状的簇环形、S形都能搞定。它还能天然地识别出噪声点这在异常检测场景里非常有用。但它的坑也很深eps和minPts极其敏感eps稍微变一点结果可能天差地别。而且当数据密度不均匀时一组参数很难同时适配高密度区和低密度区。2.3 基于层次的方法AGNES与BIRCH层次聚类走的是另一条路。AGNES是自底向上的凝聚层次聚类一开始每个点自成一簇然后不断合并最近的两个簇直到所有点合并成一个大簇。这个过程会生成一棵树状图你可以通过切树来决定最终要几个簇。层次聚类的好处是不需要预先指定K而且树状图能直观展示簇的合并过程对理解数据结构很有帮助。缺点是计算复杂度高O(n²)甚至O(n³)数据量一大就跑不动。BIRCH是对层次聚类的改进它先用一棵CF树对数据做压缩摘要再在摘要上做聚类能处理大规模数据但代价是牺牲了一定的精度。2.4 基于模型的方法高斯混合模型GMM假设数据是由K个高斯分布混合生成的每个样本以一定概率属于每个高斯分量。它和K-Means最大的区别是软分配——K-Means说这个点属于第3簇GMM说这个点有70%概率属于第3簇25%属于第1簇5%属于第2簇。这种软分配在很多场景下更合理。比如一个用户既像高价值用户又像潜力用户硬切到某一类会丢失信息软分配能保留这种模糊性。GMM的缺点是计算量大而且对初始值敏感容易陷入局部最优。算法簇形状假设是否需要指定K对噪声鲁棒性适用数据规模典型场景K-Means球形、大小相近是差大用户分群、图像压缩K-Medoids任意但偏紧凑是较好中含异常值的分群DBSCAN任意形状否好中异常检测、空间聚类AGNES任意否一般小小数据集探索GMM椭圆形是一般中软分配、密度估计选型的核心原则是先看数据分布再看业务需求。如果你对数据的形状一无所知我建议先用DBSCAN跑一遍看看密度结构再用K-Means做快速分群。如果数据量特别大K-Means或BIRCH是首选。如果需要软分配或概率解释GMM更合适。3. K-Means的数学内核与手写实现选型定了之后真正要落地绕不开对算法内部机制的理解。这一章我拿K-Means开刀把它的数学原理和代码实现掰开揉碎讲清楚。理解了K-Means其他算法的学习曲线会平缓很多。3.1 目标函数与迭代优化K-Means的目标函数叫簇内平方误差和英文缩写SSESSE Σ(i1到K) Σ(x属于Ci) ||x - μi||²其中Ci是第i个簇μi是第i个簇的均值向量。这个公式的意思是把所有点到它们各自簇心的距离平方加起来我们希望这个和越小越好。优化这个目标函数用的是坐标下降法分两步交替进行第一步固定簇心把每个点分配给最近的簇心。这一步能保证SSE不增因为每个点都选了离自己最近的簇心。第二步固定分配结果重新计算每个簇的均值作为新簇心。这一步也能保证SSE不增因为均值是使簇内平方误差最小的点。两步交替SSE单调递减而且SSE有下界0所以算法一定收敛。但注意收敛到的是局部最优不是全局最优。这就是为什么K-Means结果不稳定——不同的初始簇心会导向不同的局部最优。3.2 用Python从零实现K-Means光看公式容易飘直接上代码。下面是我常用的一个K-Means实现去掉了sklearn的黑盒每一步都看得见import numpy as np def kmeans_from_scratch(X, k, max_iter300, tol1e-4, random_state42): np.random.seed(random_state) n_samples, n_features X.shape # 第一步随机选k个样本作为初始簇心 indices np.random.choice(n_samples, k, replaceFalse) centroids X[indices].copy() for iteration in range(max_iter): # 第二步计算每个点到每个簇心的距离 # 用广播机制避免显式循环 distances np.linalg.norm(X[:, np.newaxis] - centroids, axis2) # 第三步分配每个点到最近的簇 labels np.argmin(distances, axis1) # 第四步重新计算簇心 new_centroids np.array([ X[labels i].mean(axis0) if np.sum(labels i) 0 else centroids[i] for i in range(k) ]) # 第五步判断是否收敛 shift np.linalg.norm(new_centroids - centroids) centroids new_centroids if shift tol: print(f在第{iteration1}次迭代收敛) break # 计算SSE sse sum( np.sum((X[labels i] - centroids[i]) ** 2) for i in range(k) ) return labels, centroids, sse这段代码有几个细节值得说。第一距离计算用了广播机制X[:, np.newaxis] - centroids把形状从(n,1,d)和(k,d)广播成(n,k,d)然后沿axis2求范数得到(n,k)的距离矩阵。这比写双重循环快得多。第二重新计算簇心时加了一个判断if np.sum(labels i) 0防止某个簇变成空簇导致除零错误。第三收敛判断用的是簇心位移的范数小于tol就停。3.3 K值选择的量化方法K-Means最让人头疼的就是K取几。我见过有人拍脑袋定K也见过有人把K从2试到20然后凭感觉选。其实有几个量化方法可以参考。手肘法是最常用的。把K从1到10分别跑一遍画出SSE随K变化的曲线。随着K增大SSE一定下降但下降速度会逐渐变缓。那个拐点就是比较合适的K。原理是当K小于真实簇数时增加K能显著降低SSE当K超过真实簇数后再增加K只是把本来就很紧凑的簇拆得更细SSE下降幅度变小。轮廓系数是另一个常用指标。对每个样本计算它到同簇其他点的平均距离a凝聚度以及它到最近的其他簇所有点的平均距离b分离度。轮廓系数s (b-a) / max(a,b)取值范围[-1,1]越接近1说明聚类效果越好。把所有样本的轮廓系数平均就得到整体轮廓系数。选使平均轮廓系数最大的K。from sklearn.metrics import silhouette_score def find_best_k(X, k_rangerange(2, 11)): results [] for k in k_range: labels, centroids, sse kmeans_from_scratch(X, k) sil silhouette_score(X, labels) results.append({k: k, sse: sse, silhouette: sil}) return results实际用的时候我一般两个指标一起看。手肘法给一个大致范围轮廓系数在范围内选最优。如果两个方法结论冲突优先信轮廓系数因为它同时考虑了凝聚度和分离度。注意轮廓系数在簇形状非凸时比如环形会偏低这时候不要死磕这个指标结合业务解释性来判断。4. 从原始数据到可用簇的完整实操链路算法原理搞明白之后真正的工作量其实在数据准备和结果解读上。我做过好几个聚类项目代码跑通只占两成时间剩下八成都在处理数据、调参、解释结果。这一章我把完整链路拆开讲。4.1 特征工程聚类成败的第一道关聚类是基于距离的算法所以特征的尺度和类型直接决定结果。如果某个特征取值范围是0到10000另一个是0到1那距离计算时第一个特征会完全主导第二个特征等于白给。标准化是必须的。最常用的是Z-score标准化把每个特征变成均值0、方差1from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X)但标准化不是万能的。如果数据里有极端异常值均值和方差会被拉偏这时候用RobustScaler更合适它用中位数和四分位距来缩放对异常值更鲁棒。特征选择也很关键。不是特征越多越好无关特征会引入噪声稀释真正有区分度的特征。我一般会先算特征之间的相关性把高度相关的特征合并或剔除然后用方差过滤掉那些几乎不变的特征如果特征维度特别高还会考虑PCA降维。还有一个容易被忽略的点特征的类型。K-Means只能处理数值特征如果原始数据里有性别、城市、品类这种类别特征需要先做编码。独热编码是最直接的但会让维度爆炸对于高基数类别特征可以用目标编码或者嵌入向量不过那就复杂了。我的经验是如果类别特征基数不高比如少于10个取值独热编码够用如果基数很高考虑换用K-Prototypes这类能同时处理数值和类别特征的算法。4.2 距离度量的选择与陷阱默认情况下大家用的都是欧氏距离但它不是唯一选择也不总是最优。欧氏距离适合特征都是连续数值、且各维度重要性相当的情况。曼哈顿距离各维度差的绝对值之和对异常值更鲁棒因为它是线性的不像欧氏距离那样把差值平方放大。余弦距离衡量的是方向差异而非绝对距离适合文本向量、用户行为向量这种更关注比例而非绝对值的场景。我踩过的一个坑是在用户行为数据上用欧氏距离结果发现消费金额这个特征完全主导了聚类因为它的数值范围远大于其他特征。后来换成对金额取对数再标准化或者改用曼哈顿距离效果才正常。还有一个陷阱是维度灾难。当特征维度很高时所有点之间的距离都会趋于相近距离度量失去区分度。这时候要么降维要么换用余弦距离这类对高维更友好的度量。4.3 聚类结果的业务解读方法跑出labels只是开始真正的价值在于解读每个簇代表什么。我的做法是对每个簇计算各个特征在该簇内的均值然后和全局均值对比看哪些特征显著偏高或偏低。import pandas as pd def interpret_clusters(df, labels, feature_cols): df df.copy() df[cluster] labels global_mean df[feature_cols].mean() cluster_profile df.groupby(cluster)[feature_cols].mean() # 计算每个簇相对全局的偏离程度 deviation (cluster_profile - global_mean) / global_mean return cluster_profile, deviation拿到偏离矩阵后每个簇找出偏离最大的两三个特征就能给它起个业务名字。比如某个簇的下单频次和客单价都显著高于全局那它就是高价值用户某个簇的浏览时长高但下单频次低那就是只逛不买型。这里有个经验不要只看均值还要看分布。有时候两个簇的均值差不多但一个簇内部很集中另一个很分散业务含义完全不同。我会对关键特征画箱线图看看簇内的离散程度。4.4 稳定性验证换种子、换样本、换特征聚类结果不稳定是常态尤其是K-Means。我判断一个聚类结果是否可信会做三组验证。第一组是换随机种子。用不同的random_state跑十次看每次的簇划分是否一致。如果十次结果差异很大说明数据本身没有清晰的簇结构或者K选得不对。可以用调整兰德指数ARI来量化两次聚类结果的一致性。第二组是换样本。把数据随机分成两半各自聚类然后看两半得到的簇结构是否相似。如果相似说明结构稳定如果差异大说明结果对样本敏感。第三组是换特征。故意去掉一两个特征再跑看结果变化大不大。如果去掉某个特征后簇结构完全变了说明结果高度依赖那个特征需要审视这个特征是否合理。from sklearn.metrics import adjusted_rand_score def stability_check(X, k, n_runs10): all_labels [] for seed in range(n_runs): labels, _, _ kmeans_from_scratch(X, k, random_stateseed) all_labels.append(labels) ari_scores [] for i in range(n_runs): for j in range(i1, n_runs): ari adjusted_rand_score(all_labels[i], all_labels[j]) ari_scores.append(ari) return np.mean(ari_scores), np.std(ari_scores)平均ARI高于0.8我认为结果比较稳定0.6到0.8之间需要谨慎低于0.6基本说明这个K下的聚类不可靠。5. 那些年我在聚类上踩过的坑这一章我专门讲踩坑经历。有些坑是技术层面的有些是认知层面的但每一个都让我付出了实打实的时间代价。5.1 不做标准化直接跑K-Means这是我最早踩的坑也是最蠢的一个。当时拿到的数据里有一个年收入字段范围是0到500000还有一个年龄字段范围是18到80。我直接丢进K-Means跑出来的簇完全由收入决定年龄几乎没起作用。原因很简单收入差1万距离就增加1万年龄差10岁距离才增加10。距离计算完全被收入主导了。后来我养成了一个习惯拿到数据第一件事就是看各特征的量纲和范围。只要量纲不一致标准化就是必须的。现在我会在代码里加一个检查def check_scale(X, feature_names): ranges X.max(axis0) - X.min(axis0) for name, r in zip(feature_names, ranges): if r 100: print(f警告特征 {name} 范围过大 ({r:.0f})建议标准化)5.2 用K-Means处理环形数据有一次我拿到一批地理位置数据用户分布在一个城市中心周围形成明显的环形——市中心是商业区人少中间环是住宅区人多外环是郊区人中等。我用K-Means分3类结果它把环形从中间竖着切了一刀分成左右两半完全不符合业务直觉。这就是K-Means的球形假设在作祟。它只会用直线超平面去切分空间遇到环形、月牙形这种非凸结构就无能为力。换成DBSCAN之后环形结构被完美识别出来。这个坑教会我一件事跑聚类之前先用PCA或t-SNE把数据降到二维看一眼分布。如果看到明显的非凸结构直接放弃K-Means。from sklearn.decomposition import PCA import matplotlib.pyplot as plt def visualize_clusters(X, labels, title聚类结果): pca PCA(n_components2) X_2d pca.fit_transform(X) plt.scatter(X_2d[:, 0], X_2d[:, 1], clabels, cmapviridis, s10) plt.title(title) plt.show()5.3 把聚类结果当成分类标签用这个坑比较隐蔽。我曾经做过一个用户分群项目聚出5个簇之后直接把簇标签当成用户类别训练了一个分类模型去预测新用户的类别。结果上线后发现效果很差。问题出在聚类得到的簇是数据驱动的不是业务定义的。簇的边界是算法根据特征距离划出来的不一定对应真实的业务类别。而且聚类结果会随数据分布变化而漂移今天这个簇代表高价值用户明天数据分布变了同一个簇可能代表别的群体。正确的做法是把聚类结果作为探索工具用来发现人群结构然后人工定义清晰的业务规则再用分类模型去学习这些规则。聚类负责发现分类负责固化。5.4 忽略空簇和单点簇K-Means跑着跑着有时候会出现某个簇只有一个点或者干脆变成空簇。空簇会导致除零错误单点簇则说明这个点离其他所有点都很远可能是个异常值。我在代码里加了保护重新计算簇心时如果某个簇为空就保留原来的簇心不动。但更好的做法是在聚类前先做异常值处理把那些明显离群的点剔掉或者用K-Medoids这种对异常值更鲁棒的算法。# 检测单点簇 def check_singleton_clusters(labels, min_size5): from collections import Counter counts Counter(labels) for cluster_id, count in counts.items(): if count min_size: print(f警告簇 {cluster_id} 只有 {count} 个样本可能是异常值)5.5 用轮廓系数选K却忽略了业务可解释性轮廓系数是数学指标它衡量的是簇的紧凑度和分离度但它不知道业务含义。我遇到过轮廓系数最高的K8但8个簇的业务解释非常牵强运营团队根本没法针对8类人做差异化策略。反而K4时轮廓系数略低但四个簇的业务含义非常清晰高价值、潜力、流失预警、低价值。所以我的原则是数学指标定范围业务解释定最终值。先用手肘法和轮廓系数把K缩小到两三个候选然后和业务方一起看每个候选的业务解释性选那个最能讲清楚故事的。6. 聚类效果的量化评估与调优策略聚类没有标签所以评估比分类难得多。但这不代表没法量化。除了前面提到的SSE和轮廓系数还有几个指标和调优思路值得掌握。6.1 内部指标与外部指标的选用内部指标不需要真实标签只依赖数据本身和聚类结果。常用的有轮廓系数综合考虑凝聚度和分离度范围[-1,1]越大越好。Calinski-Harabasz指数簇间离散度与簇内离散度的比值越大越好计算比轮廓系数快。Davies-Bouldin指数簇内散度与簇间距离的比值越小越好。外部指标需要真实标签适合你有一小部分标注数据可以用来验证的场景。常用的有调整兰德指数ARI和归一化互信息NMI取值范围都是[0,1]或[-1,1]越接近1说明聚类结果和真实标签越一致。from sklearn.metrics import calinski_harabasz_score, davies_bouldin_score def evaluate_clustering(X, labels): sil silhouette_score(X, labels) ch calinski_harabasz_score(X, labels) db davies_bouldin_score(X, labels) return {silhouette: sil, calinski_harabasz: ch, davies_bouldin: db}实际项目中我一般以轮廓系数为主Calinski-Harabasz为辅。Davies-Bouldin对簇形状比较敏感参考价值有限。6.2 K-Means初始化解决结果不稳定的利器K-Means结果不稳定的根源是随机初始化。如果初始簇心选得不好比如两个簇心落在同一个真实簇里算法就很难跳出来。K-Means就是专门解决这个问题的。它的策略是第一个簇心随机选后续每个簇心的选择概率与它到已选簇心距离的平方成正比。也就是说离已选簇心越远的点越有可能被选为下一个簇心。这样能保证初始簇心尽量分散。def kmeans_plus_plus_init(X, k, random_state42): np.random.seed(random_state) n_samples X.shape[0] centroids [X[np.random.randint(n_samples)]] for _ in range(1, k): # 计算每个点到最近已选簇心的距离平方 distances np.array([ min(np.linalg.norm(x - c) ** 2 for c in centroids) for x in X ]) # 按距离平方的概率分布选下一个簇心 probabilities distances / distances.sum() next_centroid X[np.random.choice(n_samples, pprobabilities)] centroids.append(next_centroid) return np.array(centroids)实测下来K-Means能把不稳定问题改善很多。sklearn的KMeans默认用的就是K-Means所以如果你用sklearn这个问题已经帮你解决了。但理解它的原理在需要自己实现或调优时很有用。6.3 用MiniBatchKMeans处理百万级数据标准K-Means每次迭代都要遍历所有样本数据量上到百万级就很吃力。MiniBatchKMeans的思路是每次迭代只用一小批样本比如1024条来更新簇心而不是用全量数据。这样做的好处是速度快内存占用小能处理超大规模数据。代价是结果精度略低于标准K-Means因为每次更新只基于部分样本。但在实际项目中这个精度损失通常可以接受而速度提升是数量级的。from sklearn.cluster import MiniBatchKMeans mbk MiniBatchKMeans( n_clusters8, batch_size1024, max_iter100, random_state42 ) labels mbk.fit_predict(X_scaled)我的经验是数据量小于10万用标准K-Means10万到100万可以先用MiniBatchKMeans快速探索再用标准K-Means精调超过100万直接上MiniBatchKMeans或者考虑BIRCH。6.4 聚类集成的思路单个聚类算法容易受初始化和参数影响一个提升稳定性的思路是聚类集成跑多次聚类每次用不同的算法、参数或样本子集然后通过投票或共现矩阵来融合结果。最简单的一种做法是用不同的随机种子跑K-Means十次得到一个n×n的共现矩阵其中第(i,j)个元素表示样本i和样本j被分到同一个簇的次数。然后对这个共现矩阵再做一次层次聚类得到最终划分。def ensemble_clustering(X, k, n_runs20): n_samples X.shape[0] co_occurrence np.zeros((n_samples, n_samples)) for seed in range(n_runs): labels, _, _ kmeans_from_scratch(X, k, random_stateseed) for i in range(n_samples): for j in range(i1, n_samples): if labels[i] labels[j]: co_occurrence[i, j] 1 co_occurrence[j, i] 1 co_occurrence / n_runs # 用共现矩阵的1-相似度作为距离做层次聚类 from scipy.cluster.hierarchy import linkage, fcluster from scipy.spatial.distance import squareform distance_matrix 1 - co_occurrence np.fill_diagonal(distance_matrix, 0) condensed squareform(distance_matrix) Z linkage(condensed, methodaverage) final_labels fcluster(Z, k, criterionmaxclust) return final_labels这个方法我在几个项目里试过稳定性确实比单次K-Means好不少代价是计算时间成倍增加。适合对稳定性要求高、数据量不太大的场景。7. 聚类在真实业务中的落地形态聊完算法和调优最后说说聚类在实际业务里到底怎么用。我做过几个不同类型的项目每个项目的落地形态都不太一样。7.1 用户分群与精细化运营这是聚类最经典的应用。某内容平台手里有用户的内容消费数据日均阅读篇数、平均阅读时长、互动率、分享率、付费率等。用K-Means聚成5类后运营团队给每类人定了不同策略。第一类是重度沉浸型阅读篇数和时长都高互动率也高策略是推送深度内容和会员权益第二类是轻度浏览型阅读篇数高但时长短策略是推送短平快的内容第三类是互动沉默型阅读量中等但几乎不互动策略是用互动激励活动激活第四类是付费潜力型付费率中等但消费频次高策略是推送付费内容试用第五类是流失预警型各项指标都在下降策略是召回活动。这里的关键是聚类给出的是数据分组业务策略需要人工映射。算法不会告诉你哪一类该推什么内容它只告诉你这些人相似。解读和策略制定是人的工作。7.2 异常检测与风险识别聚类的另一个重要应用是异常检测。思路很简单正常样本会聚成几个大簇异常样本要么自成一簇单点簇要么离所有簇都很远。我在一个设备监控项目里用过这个方法。把设备的各项运行指标温度、振动、电流、转速等做标准化后跑DBSCAN那些被标记为噪声的点就是运行状态异常的设备。相比传统的阈值告警聚类能发现多个指标组合起来才异常的情况比如温度略高、振动略大、电流略低单独看每个指标都没超阈值但组合起来就是异常模式。from sklearn.cluster import DBSCAN dbscan DBSCAN(eps0.5, min_samples10) labels dbscan.fit_predict(X_scaled) # 标签为-1的是噪声点即异常样本 anomalies X[labels -1] print(f检测到 {len(anomalies)} 个异常样本)DBSCAN的eps和minPts需要根据数据密度调。我的经验是先用k-距离图找eps的拐点对每个点计算它到第k个最近邻的距离排序后画曲线拐点处的距离值就是比较合适的eps。7.3 商品聚类与推荐冷启动推荐系统有个经典难题叫冷启动新商品没有用户行为数据怎么推荐一个思路是用商品的内容特征品类、价格、品牌、描述文本的嵌入向量等做聚类新商品落到哪个簇就把它推荐给喜欢那个簇的用户。我在一个电商项目里试过这个思路。把商品的标题和描述用预训练模型转成向量然后对向量做K-Means聚类。每个簇代表一类商品新商品上架时先归到某个簇然后用该簇的历史行为数据做推荐。实测下来比随机推荐的效果好很多虽然不如有充分行为数据的商品但至少解决了零数据的问题。7.4 图像与文本的无监督分组聚类在非结构化数据上也有广泛应用。图像方面可以把每张图的特征向量用CNN提取做聚类实现自动相册分组、商品图片归类。文本方面可以把文档的TF-IDF向量或句嵌入做聚类实现新闻主题发现、工单自动分类。这类场景的挑战在于特征提取的质量。原始像素或词袋特征往往区分度不够需要用预训练模型提取语义特征。而且高维特征的距离度量需要特别注意余弦距离通常比欧氏距离更合适。我在一个工单分类项目里用过这个方案把工单文本用句嵌入模型转成768维向量然后用DBSCAN聚类。结果发现有些工单自成一类点进去看都是描述特殊问题的这些就是需要人工重点关注的长尾工单。这个发现帮业务方识别出了一批之前被忽略的问题类型。8. 写在最后一些个人体会聚类这个工具入门容易精通难。会调sklearn的KMeans只要五分钟但要在真实业务里用好需要理解数据、理解算法、理解业务三者缺一不可。我最大的体会是聚类不是终点而是起点。它帮你发现数据里的结构但结构意味着什么、该怎么用需要人来判断。我见过太多人跑完聚类就结束了拿着一堆簇标签不知道下一步干什么。真正有价值的做法是聚类发现分组人工解读分组业务定义策略最后用分类模型固化策略。另一个体会是不要迷信数学指标。轮廓系数高不代表业务价值大SSE低不代表分群合理。数学指标只能帮你排除明显不好的结果最终决策还是要结合业务解释性。我现在的习惯是每次聚类结果出来先不看指标先看每个簇的特征画像看能不能讲出一个合理的业务故事。讲得通再看指标确认讲不通指标再好看也不用。最后一个建议多试几种算法。不要一上来就K-Means跑到黑。花十分钟用DBSCAN和层次聚类也跑一遍对比一下结果。不同算法的视角不同有时候DBSCAN能发现K-Means忽略的噪声结构有时候层次聚类的树状图能让你对数据的层次关系有新的理解。这些额外的信息往往比调参带来的提升更有价值。
阅读完成 · 觉得有帮助?