聚类这件事,做久了会发现一个挺尴尬的现实:标准Kmeans跑出来的结果,从指标上看往往不差,轮廓系数、簇内平方和都挺体面,但把簇的边界摊开一看,很多本该分开的细节被糊成了一团。问题不在于Kmeans本身有多差,而在于它的假设太"干净"——每个样本对簇心的贡献是等权的,残差被当作噪声直接丢弃。可实际数据里,残差往往藏着最值钱的信息:那些偏离主流的局部结构、那些被均值掩盖的细节特征,恰恰是区分不同群体的关键。RQ-Kmeans(Residual-Quantized Kmeans,残差量化Kmeans)就是冲着这个痛点来的,它把残差从"要丢弃的噪声"重新定义为"要二次利用的信号",通过多层残差量化把细节特征一层层剥出来。这篇内容适合已经用过Kmeans、对聚类效果有过"指标好看但业务不买账"经历的读者,也适合想理解残差思想如何迁移到聚类场景的同行。我会从为什么标准Kmeans会丢细节讲起,拆解RQ-Kmeans的核心机制,给出可复现的实现路径,再聊聊实测中那些文档里不会写的坑。
1. 标准Kmeans为什么会在细节上翻车
1.1 等权残差假设埋下的隐患
Kmeans的优化目标是最小化所有样本到其所属簇心的欧氏距离平方和,这个目标函数隐含了一个非常强的假设:每个样本对簇心更新的贡献权重是一样的。换句话说,一个位于簇边缘、特征上明显偏离主流的样本,和一个位于簇核心、特征典型的样本,在计算新簇心时被同等对待。这个假设在数据分布接近球形、各簇密度均匀时问题不大,但真实数据几乎从不满足这个条件。
举个具体的场景。假设你在做一个用户行为分群,某个群体里绝大多数用户的行为模式高度一致,但有一小撮用户在主特征上接近这个群体、在某个次要特征上却表现出完全不同的偏好。标准Kmeans在迭代时,这一小撮用户的残差会被大量"典型用户"的贡献稀释掉,最终他们要么被强行并入大簇,要么在簇心漂移的过程中被推到边界模糊的地带。残差在这里不是噪声,它是这批用户区别于主流的唯一信号,但Kmeans的等权机制把它抹平了。
更麻烦的是,这种抹平是不可逆的。一旦迭代收敛,你拿到的是K个簇心,每个簇心是簇内所有样本的算术平均。这个平均值在数学上是最优的平方误差解,但在业务语义上可能什么都不是——它既不代表主流用户,也不代表那批特殊用户,而是一个谁都不像的"平均人"。细节特征在求平均的那一刻就已经丢失了,后续再怎么分析簇心都找不回来。
1.2 单层量化对局部结构的天然盲区
Kmeans本质上是一种单层向量量化:用K个码字(簇心)去近似整个数据分布,每个样本被映射到离它最近的码字。这个过程的误差就是量化误差,也就是残差。标准Kmeans的做法是让这层量化误差尽量小,然后就结束了。但量化误差再小也不可能为零,尤其是在簇数量K受限于业务解释性、不能随意增大的时候,单层量化的表达能力是有天花板的。
打个比方,这就像用一把固定刻度的尺子去量东西,尺子最小刻度是1毫米,那所有小于1毫米的差异你都测不出来。Kmeans的"刻度"就是簇的数量和簇心的位置,当数据里存在比"刻度"更细的结构时,这些结构就落在残差里,被单层量化直接忽略了。残差量化(Residual Quantization)的思路很直接:既然第一层量化之后还剩残差,那就对残差再做一次量化,用第二层的码字去逼近第一层的残差,如此层层递进。这个思路在向量压缩领域用了很多年,搬到聚类场景里,就变成了RQ-Kmeans:第一层Kmeans捕捉全局的粗粒度结构,后续层对残差做聚类,专门捕捉那些被第一层漏掉的细节。
关键在于,残差层的聚类和第一层不是简单的重复。第一层是在原始特征空间里找大簇,残差层是在"第一层没解释好的部分"里找结构。这两者的关注点完全不同,前者看的是全局分布,后者看的是局部偏差。把多层的结果组合起来,你得到的不是一个更细的划分,而是一个带层次信息的表示:每个样本既有它在大簇里的归属,也有它在残差结构里的偏移方向。这个偏移方向,就是细节特征的具体载体。
1.3 细节特征在业务语义中的真实价值
很多人会问,细节特征丢了就丢了,大簇分对了不就行了?这个想法在粗粒度分群时成立,但在需要精细化运营或异常识别的场景里会出大问题。举个电商的例子:两个用户在大簇层面都属于"高频复购用户",第一层Kmeans把他们分到一起完全合理。但其中一个用户的复购集中在少数几个品类、购买节奏稳定,另一个用户的复购横跨十几个品类、每次购买都伴随大量浏览但转化率低。这两个人在残差层面的表现截然不同,前者是"深度忠诚",后者是"广泛比价"。如果只看第一层,运营策略会一刀切,效果自然打折。
残差信息在这里扮演的角色,是把"同簇内的异质性"显式地表达出来。标准Kmeans把同簇内的差异当作需要最小化的误差,RQ-Kmeans则把它当作需要保留和利用的信号。这个视角的转换,是理解整个方法的关键。残差不是要被消灭的敌人,它是数据在告诉你"我还有一些你没看到的结构"。顺着残差往下挖,往往能挖出比主簇更有业务价值的细分群体。
2. RQ-Kmeans的残差量化机制拆解
2.1 第一层量化:先把粗粒度骨架搭起来
RQ-Kmeans的第一层和标准Kmeans没有本质区别,就是在原始特征空间上跑一次Kmeans,得到K1个簇心,每个样本被分配到最近的簇心,同时记录下量化残差。这一步的目的是建立全局骨架,把数据里最显著的结构先抓出来。第一层的簇数量K1不需要很大,通常取业务上能解释的粗粒度分组数即可,比如用户分群场景里取5到10个。
这里有个容易被忽略的细节:第一层的残差计算方式直接决定了后续层能捕捉到什么。残差定义为样本原始向量减去它所属簇心的向量,是一个和原始特征同维度的向量。这个残差向量里,既有该样本偏离簇心的方向信息,也有偏离的幅度信息。方向决定了它偏向哪个局部结构,幅度决定了这个偏离有多显著。后续的残差层就是在这个残差空间里做聚类,把方向相近、幅度相当的样本聚到一起。
第一层跑完之后,建议先做一件事:把残差的范数分布画出来看看。如果残差范数整体很小且集中,说明第一层已经解释得比较好,残差层能挖的东西有限;如果残差范数分布很宽、存在明显的长尾,说明数据里有相当一部分样本偏离主簇较远,残差层大有可为。这个分布形态是判断要不要上多层残差量化的第一手依据,比拍脑袋决定层数靠谱得多。
2.2 残差层的独立聚类与码字复用
第二层开始就是RQ-Kmeans和标准Kmeans分道扬镳的地方。第二层不再在原始空间里聚类,而是在第一层的残差空间里跑Kmeans,得到K2个残差码字。每个样本现在有两个表示:第一层的簇心归属,加上第二层残差码字的归属。这两层是独立聚类的,第二层完全不知道第一层的簇心长什么样,它只对残差负责。
这种独立性是有意为之的。如果让第二层在聚类时参考第一层的簇心,残差层就会倾向于去"修正"第一层的划分,而不是去"补充"第一层漏掉的结构。独立聚类保证了残差层捕捉的是纯粹的局部偏差模式,不受第一层全局结构的干扰。代价是两层之间的对应关系需要事后组合,但这个组合逻辑很简单:一个样本的完整表示就是(第一层簇心,第二层残差码字)这个二元组。
码字复用是另一个值得说的点。残差层的码字是全局共享的,不是每个第一层簇各自维护一套残差码字。这意味着同一个残差码字可以作用于不同的第一层簇,表达的是"相对于所属簇心的偏移模式"。这个设计让参数量可控——总码字数等于K1加K2,而不是K1乘以K2。对于第一层簇数量不大、但残差模式跨簇通用的场景,这个设计能显著降低过拟合风险。当然,如果不同第一层簇的残差模式差异极大,共享码字会损失一些表达能力,这时候可以考虑分组残差码字,但那是另一个复杂度层级的事了,初上手不建议。
2.3 多层堆叠时残差如何逐级收敛
第三层及以后,逻辑是递归的:对上一层的残差再做聚类,得到新的残差码字,再算新的残差。每一层都在逼近上一层没解释好的部分,残差的范数理论上应该逐层递减。实际跑下来,通常两到三层就能把残差压到一个比较低的水平,再往上加层收益递减明显,而且过拟合风险上升。
这里要强调一个实操中的判断标准:不要只看残差范数的绝对值,要看残差范数的下降比例。如果从第一层到第二层,残差范数中位数下降了30%以上,说明第二层确实抓到了实质性的结构;如果只下降了5%,那第二层基本是在拟合噪声,加这一层没有意义。我一般会画一张逐层残差范数分布的箱线图,看中位数和四分位距的变化,比看单一均值指标直观得多。
多层堆叠还有一个隐含的好处:它天然给出了一种层次化的样本表示。一个样本可以用它在各层的码字索引组成一个元组,这个元组就像一个多级地址,从粗到细地定位了这个样本在数据分布中的位置。这个表示在后续做相似度计算、做检索、做异常检测时都很好用,因为它同时编码了全局归属和局部偏移。标准Kmeans只给你一个簇ID,RQ-Kmeans给你一串码字,信息密度完全不是一个量级。
3. 从零实现一个可跑的RQ-Kmeans
3.1 环境与依赖的最小化准备
实现RQ-Kmeans不需要什么重型框架,numpy加scikit-learn的KMeans就够了。核心逻辑就是循环:聚类、算残差、再聚类、再算残差。我倾向于自己写这个循环而不是找现成库,原因有两个:一是残差层的层数、每层簇数量这些超参需要灵活调整,现成库往往把这些写死了;二是残差的计算和存储方式直接影响后续分析,自己写能控制残差的保存格式。
依赖清单很简单:numpy负责向量运算,scikit-learn只用来调KMeans的底层实现(它的初始化用k-means++,比纯随机初始化稳),matplotlib用来画残差分布做诊断。不需要深度学习框架,这东西的计算量在中等规模数据上完全可控。数据规模上,十万级样本、几十维特征,单机跑两三层残差量化,几分钟内能出结果,不需要分布式。
有一点要提前说清楚:特征标准化这一步不能省。残差是在原始特征空间里算的,如果各特征量纲差异大,残差会被大量纲特征主导,小量纲特征上的细节偏移就被淹没了。标准化的方式用z-score还是min-max,取决于你对特征分布的假设,但无论哪种,所有层必须用同一套标准化参数,不能每层重新拟合,否则残差空间会错位。
3.2 逐层聚类的核心循环写法
核心循环的伪代码逻辑是这样的:初始化当前输入为原始标准化数据,初始化残差累积为零向量,对每一层执行聚类,把当前输入减去该层簇心得到该层残差,把该层残差累加到总残差里,然后把该层残差作为下一层的输入。注意这里有个容易写错的地方:下一层的输入应该是"当前层的残差",而不是"累积残差"。累积残差是用来做最终诊断的,不是用来喂给下一层的。
import numpy as np from sklearn.cluster import KMeans def rq_kmeans(X, layer_ks, random_state=42): """ X: 标准化后的特征矩阵 (n_samples, n_features) layer_ks: 每层的簇数量列表,如 [8, 16, 32] 返回: 每层的码字、每层每个样本的码字索引、逐层残差 """ codebooks = [] codes = [] residuals_per_layer = [] current_input = X.copy() cumulative_residual = np.zeros_like(X) for k in layer_ks: km = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=random_state) labels = km.fit_predict(current_input) layer_residual = current_input - km.cluster_centers_[labels] codebooks.append(km.cluster_centers_) codes.append(labels) residuals_per_layer.append(layer_residual) cumulative_residual += layer_residual current_input = layer_residual # 下一层吃的是本层残差 return codebooks, codes, residuals_per_layer, cumulative_residual这段代码里,layer_ks的长度就是层数,每个元素是该层的簇数量。第一层簇数量建议取业务粗分组的数量,后续层可以逐层放大,因为残差空间里的结构通常更细碎,需要更多码字来覆盖。但也不要无脑放大,第二层簇数量取第一层的1.5到2倍是个比较稳的起点,再往上要结合残差范数下降比例来调。
3.3 残差范数的逐层诊断与层数决策
跑完循环之后,第一件事是诊断。对每一层,计算该层残差的L2范数,看它的分布随层数怎么变。我习惯用三个统计量:中位数、75分位数、以及范数超过某个阈值的样本占比。中位数反映整体残差水平,75分位数反映长尾情况,超阈值占比反映有多少样本还没被解释好。
def diagnose_residuals(residuals_per_layer): for i, res in enumerate(residuals_per_layer): norms = np.linalg.norm(res, axis=1) print(f"Layer {i+1}: median={np.median(norms):.4f}, " f"p75={np.percentile(norms, 75):.4f}, " f"p95={np.percentile(norms, 95):.4f}")判断层数够不够,看相邻两层的中位数下降比例。下降超过20%到30%,说明这一层有实质贡献;下降低于10%,基本可以停手了。还有一个辅助信号:如果某一层的残差范数分布和上一层几乎重合,说明这一层没抓到新东西,纯属浪费。我踩过一次坑,层数设了五层,结果第三层开始残差范数几乎不动,白白增加了计算量和过拟合风险,后来把层数砍到两层,效果反而更稳。
4. 残差码字怎么用才算没白算
4.1 组合码字做样本的层次化表示
跑出多层码字之后,最直接的用法是把各层码字索引拼成一个元组,作为样本的层次化ID。比如第一层8个簇、第二层16个残差码字,那每个样本的ID就是(簇ID,残差码字ID)这样的二元组,最多有8乘16种组合,但实际出现的组合远少于这个上界,因为残差码字和第一层簇之间不是均匀对应的。这个稀疏性本身就是信息:某些(簇,残差码字)组合样本密集,说明这个局部结构是真实存在的;某些组合样本极少,可能是噪声或者极端个例。
这个层次化ID在业务上很好用。拿用户分群举例,第一层给你粗分群,第二层残差码字给你"同群内的行为偏移模式"。你可以把(簇3,残差码字7)这样的组合单独拎出来看,它代表的是"属于第三大群、但行为偏移模式是第七种"的那批用户。这批用户可能只占总量的百分之几,但他们的行为一致性往往比整个第三大群高得多,做定向策略时命中率明显更好。
组合的时候有个细节要注意:残差码字的语义是相对于簇心的偏移,所以同样的残差码字在不同第一层簇下含义可能不同。解读(簇3,码字7)和(簇5,码字7)时不能想当然认为码字7的行为模式一样,要分别看。如果发现某个残差码字在多个簇下表现出相似的偏移方向,那说明这个偏移模式是跨簇通用的,这时候可以把它提升为一个独立的分析维度,这是残差分析里比较有价值的发现。
4.2 用残差范数做异常样本的初筛
残差范数本身就是一个现成的异常分数。一个样本如果经过多层量化之后,累积残差范数仍然很大,说明它的特征模式用现有的码字体系解释不了,要么是真正的异常,要么是数据里存在还没被覆盖的新模式。这个信号比很多专门的异常检测方法来得直接,因为它是在聚类框架内自然产生的,不需要额外建模。
实操中我会把累积残差范数排个序,取最高的百分之一到百分之五作为候选异常集,然后人工过一遍。这里的关键是区分"解释不了的异常"和"解释得很好但业务上罕见的样本"。前者是残差范数大,后者是残差范数小但落在稀疏的码字组合里。这两种要分开处理:前者可能是数据质量问题或者新模式,后者往往是业务上的长尾但合理的群体。混在一起看容易误判。
还有一个用法是把残差范数作为聚类的置信度。一个样本的残差范数小,说明它被码字体系解释得好,它的簇归属可信度高;残差范数大,说明它的归属比较勉强,后续做决策时应该降低它的权重或者单独复核。这个置信度信号在标准Kmeans里是拿不到的,因为标准Kmeans只给硬分配,不给"这个分配有多可信"的度量。
4.3 残差空间里的二次结构发现
残差层聚类本身就是在发现结构,但残差空间里往往还有更细的结构值得单独看。一个实用的做法是:把某一层的残差单独拿出来,在残差空间里做一次密度估计或者层次聚类,看残差是不是均匀分布的。如果残差在某个方向上明显聚集,说明这个方向上有系统性的偏移模式,值得单独建模。
我做过一个实验,把第二层残差拿出来做PCA,看前几个主成分的解释方差。如果第一主成分解释方差很高,说明残差主要沿着一个方向偏移,这个方向往往对应着某个被第一层忽略的关键特征组合。把这个方向找出来,回头去看原始特征,经常能发现一些之前没注意到的特征交互。这个发现过程比直接看特征重要性更有指向性,因为它是从"模型解释不了的部分"反推回去的,天然聚焦在盲区上。
残差空间的二次结构还有一个用途是做码字的精简。如果发现某些残差码字在残差空间里几乎重合,说明它们表达的是同一种偏移模式,可以合并。码字精简之后,层次化ID的组合数下降,可解释性上升,而且对过拟合的抵抗力更强。这个精简过程建议在初步跑通之后做一轮,不要一上来就追求码字数量最优,先把结构看清楚再优化。
5. 实测中那些文档不会写的坑
5.1 层数贪多导致残差拟合噪声
最常见的坑就是层数设太多。理论上残差可以无限量化下去,残差范数总能再降一点,但降到一定程度之后,降的就不是结构而是噪声了。我见过有人设了六七层,残差范数确实一路降,但把每层的码字拿出来看,从第四层开始码字几乎就是在拟合个别样本的随机波动,没有任何泛化价值。
判断是不是在拟合噪声,有个简单的交叉验证思路:把数据分两半,一半跑RQ-Kmeans得到码字,另一半用这套码字做量化,看残差范数。如果训练半边的残差范数降得很快但验证半边降得很慢甚至不降,说明层数已经过头了。这个验证不需要很严格,粗略分一下就能看出趋势。我一般会在层数决策时做这个检查,比单纯看训练残差靠谱。
另一个信号是码字的样本支撑数。如果某一层里有个别码字只对应极少数样本(比如少于总样本的0.1%),这些码字大概率是在拟合特例。可以考虑把这些码字合并或者直接丢弃,让这一层的码字都有足够的样本支撑。码字没有支撑,它在后续分析里就没有统计意义,留着只会干扰解读。
5.2 特征尺度不一致让残差失真
前面提过标准化不能省,但这里要展开说一个更隐蔽的问题:标准化方式的选择会影响残差的语义。z-score标准化之后,残差是在"多少个标准差"的尺度上度量的,这个尺度对异常值敏感;min-max标准化之后,残差是在"占特征值域多少比例"的尺度上度量的,对异常值不敏感但受极值影响大。两种方式下,同样的偏移在残差范数上的表现可能差很多。
更麻烦的是,如果你的特征里混有类别型特征做了独热编码,这些特征的方差天然就比连续特征小,标准化之后它们的残差贡献会被系统性压低。结果就是残差层主要捕捉连续特征上的偏移,类别特征上的细节被忽略。解决办法要么是对独热特征单独加权,要么是在残差分析时按特征组分开看残差贡献。我倾向于后者,因为加权会引入新的超参,而分组看残差贡献不改变模型,只是改变解读方式。
还有一个实操细节:标准化参数必须在第一层聚类之前拟合好,然后所有层复用。如果每层重新拟合标准化参数,残差空间会被反复拉伸,层与层之间的残差就不可比了,逐层诊断也就失去意义。这个错误不常犯但一旦犯了很难察觉,因为代码跑起来不报错,只是结果悄悄失真。
5.3 码字初始化随机性带来的结果漂移
KMeans本身对初始化敏感,RQ-Kmeans把这个敏感性放大了,因为残差层的输入依赖于第一层的结果,第一层稍有不同,残差就不同,后续层全跟着变。用k-means++初始化能缓解一部分,但不能完全消除。实测中,同样的数据跑两次,第一层簇心可能只差一点点,但到第二层残差码字,差异可能就被放大了。
应对办法是固定随机种子并且多次初始化取最优。scikit-learn的KMeans有n_init参数,设成10到20,让它内部多跑几次取惯性最小的。这个成本可以接受,因为单次KMeans在中等数据上很快。另外,如果对结果稳定性要求高,可以整体跑多次RQ-Kmeans(每次不同的随机种子),看层次化ID的稳定性。如果某个样本的ID在多次运行中频繁变化,说明它处在簇的边界地带,归属本身就不确定,后续分析时应该标记出来。
还有一个减少漂移的技巧:第一层的簇数量不要设得太贴近数据的真实结构数。如果第一层簇数量刚好等于真实簇数,初始化稍有偏差就可能导致簇合并或分裂,后续全乱。稍微多设一点(比如真实簇数是5,第一层设8),给初始化留点冗余,结果反而更稳。这个经验和标准Kmeans的调参直觉是反的,但在RQ-Kmeans里很实用。
5.4 残差层簇数量与第一层的耦合陷阱
很多人会直觉地认为残差层的簇数量应该和第一层差不多,或者按固定比例放大。这个直觉在残差结构比较均匀时成立,但残差往往是不均匀的:某些第一层簇的残差很集中(说明这个簇内部很一致),某些簇的残差很分散(说明这个簇内部异质性大)。如果残差层用统一的簇数量,集中残差的区域码字过剩,分散残差的区域码字不足。
更合理的做法是让残差层的码字数量根据残差分布自适应,但这会显著增加实现复杂度。一个折中方案是:先跑一个统一的残差层,然后看每个第一层簇下的残差码字使用分布。如果某个簇下大量样本挤在少数几个残差码字上,说明这个簇的残差结构简单,码字够用;如果某个簇下残差码字使用很分散,说明这个簇的残差结构复杂,可能需要更多码字。根据这个分布,可以考虑对残差结构复杂的簇单独加一层残差量化,形成非均匀的层结构。
这个非均匀结构实现起来麻烦,但对异质性强的数据效果明显。我的建议是初上手先用均匀结构跑通,把残差分布诊断做扎实,等确认了哪些簇的残差确实复杂,再考虑非均匀扩展。一上来就搞非均匀,调试成本会高很多,而且不容易判断效果提升是来自结构改进还是随机波动。
6. 什么场景下RQ-Kmeans真正值得上
6.1 同质大簇里藏异质小群的典型数据
RQ-Kmeans最适合的场景,是数据里存在"大簇套小群"的结构:全局看有几个明显的大簇,但每个大簇内部还有若干行为模式不同的小群体。这种结构在用户分群、设备状态监测、文本主题细分里都很常见。标准Kmeans能把大簇分对,但小群会被平均掉;RQ-Kmeans的残差层专门负责把小群从大簇里剥出来。
判断你的数据是不是这种结构,有个简单的预检:先跑标准Kmeans,然后对每个簇内部单独做一次聚类(簇内再聚类),看簇内是否能分出稳定的子结构。如果能,而且子结构在不同簇之间表现出相似性,那RQ-Kmeans的共享残差码字设计正好对口。如果簇内再聚类分不出稳定子结构,那残差层大概率也挖不出东西,不如把精力放在特征工程上。
这个预检的成本很低,但能省掉很多无效尝试。我见过有人在不适合的数据上硬上RQ-Kmeans,结果残差层跑出来的码字全是噪声,还以为是实现有问题,其实是数据本身没有残差结构可挖。数据有没有料,预检一下就知道,别急着上模型。
6.2 需要可解释层次标签的业务分析
RQ-Kmeans的另一个优势是输出的可解释性。标准Kmeans给你K个簇ID,簇ID本身没有语义,要靠事后分析去贴标签。RQ-Kmeans给你的是层次化ID,第一层是粗分组,后续层是偏移模式,这个层次结构本身就带语义:第一层回答"你属于哪个大群",残差层回答"你在这个大群里偏向了哪个方向"。对于需要向业务方解释分群逻辑的场景,这个层次结构比单一簇ID好讲得多。
比如做用户运营,你可以说"这批用户属于高价值群,但他们的残差码字显示购买节奏偏慢,属于高价值但低频的类型",这个描述比"这批用户属于簇3"信息量大得多,业务方也能直接据此设计策略。层次化标签的价值在于它把"归属"和"偏移"分开表达,而归类和偏移在业务上往往对应不同的动作:归属决定资源投入级别,偏移决定具体触达方式。
不过要注意,层次化ID的可解释性依赖于你对残差码字的解读。残差码字本身也是无监督出来的,它的语义需要你去看它对应的样本特征偏移方向来定。这个解读工作量不小,但一旦解读清楚,后续复用价值很高。建议在第一次跑通之后,花时间把每个残差码字的典型偏移方向整理成一张表,后面所有分析都基于这张表,效率会高很多。
6.3 不适合上的场景也要说清楚
RQ-Kmeans不是万能的,有些场景上了反而添乱。第一种是数据本身簇结构就很清晰、簇内高度同质的情况,这时候残差层没有结构可挖,跑出来全是噪声,白白增加复杂度。第二种是特征维度极高但样本量不足的情况,高维空间里残差本身就很稀疏,残差层聚类容易过拟合,效果不如直接降维后再做标准聚类。第三种是对实时性要求极高的在线场景,RQ-Kmeans的多层结构意味着推理时要逐层算残差、查码字,延迟比单层Kmeans高,如果业务对延迟敏感,这个开销要提前评估。
还有一种情况是簇数量本身就不该固定。RQ-Kmeans和标准Kmeans一样,需要预先指定每层的簇数量,如果你的数据里簇的数量本身是未知且变化的,那基于密度的聚类方法可能更合适。RQ-Kmeans的残差思想可以迁移到其他聚类框架上,但那是另一个话题了,不在这次讨论范围内。
说到底,RQ-Kmeans的价值在于它改变了我们对残差的看法:残差不是要最小化的误差,而是要分层利用的信号。这个视角转换带来的不仅是更好的细节捕捉能力,更是一种分析思路——当主模型解释不了某些东西时,别急着调参或者换模型,先看看残差里有什么。很多时候,答案就藏在被忽略的那部分里。我在实际项目里养成了一个习惯:任何聚类或者降维做完,都把残差拿出来看一眼分布,这个习惯帮我发现过好几次被主分析漏掉的关键子群。残差不会说话,但它从不撒谎。