
行为聚类这东西听起来挺玄乎但说白了就是把用户的行为记录变成向量塞进聚类算法里跑一遍试图找出“哪些人行为模式差不多”。我做推荐和用户增长已经好几年了每次带新人做分群发现大家最容易卡住的地方根本不是算法不懂而是三个极其具体的问题k 到底取几聚类出来的编号怎么变成人能看懂的标签以及模型上线后新用户来了应该塞进哪个桶这篇文章就是《模型不玄学》第10章的实战笔记我把跑通行为聚类的完整链路拆给你看选 k、给群体起名、冷启动安置每一步都会讲清楚当时为什么这么选、踩过什么坑、最后怎么补的。内容不挑行业电商、内容社区、工具类产品都能直接套思路。适合正在做用户分群、行为画像、推荐冷启动的算法工程师和数据分析师也适合那些已经会用 sklearn 但还没真正上过线的同学。1. 行为聚类到底在解决什么问题1.1 先想清楚你要聚的是“人”还是“行为”我第一次做行为聚类时犯过一个低级错误上来就取数把用户每一次点击行为当成一行样本结果聚类出来一堆“行为片段”根本没法对应到业务上。后来才明白行为聚类前必须先回答一个问题特征矩阵的行到底代表什么。大多数业务场景下我们想做的是“用户分群”也就是把用户当作聚类的基本单位。每个用户的原始行为日志先按时间窗口聚合成特征比如近 7 天登录次数、近 30 天购买金额、平均单次浏览时长、品类偏好 top3 等等。这样每个用户就是一条特征向量聚类后每个簇代表一类行为模式相似的用户。但也有另一种做法行的粒度是“单次会话”或者“单次行为”。比如把一次完整的 App 会话作为一个样本特征包括会话时长、访问页面数、是否下单等聚类得到的是“行为模式”而不是“人”。这种结果通常用于识别异常路径或者优化产品流程和用户分群的目标差别很大。我自己的经验是如果你想做人群画像或者个性化推荐就老老实实按用户聚合如果你想分析行为路径本身的规律再考虑按会话聚合。两种不要混着用否则特征口径会乱。这里还有个特别容易被忽视的点聚合窗口怎么选。窗口太短比如只看近 1 天数据稀疏很多用户甚至没有行为窗口太长比如 90 天数据稳定了但反应不了用户当前状态流失用户和活跃用户会被混在一起。我测试过常用的做法是同时维护 7 天和 30 天两套特征聚类时用 30 天窗口保证稳定性冷启动判断时用 7 天窗口保证时效性。后面章节会细说冷启动怎么利用这个组合。1.2 聚类本身不产生价值后续动作才产生价值很多新手容易陷进一个误区觉得聚类跑完、画出个二维散点图就完事了。但真实业务里聚类的输出只是一个中间产物它的价值取决于下游怎么用它。常见的使用方式有三种。第一种是运营侧的精细化策略把“用户群 A”的人定向推送 A 类内容或者发放 A 类权益提升转化和留存。第二种是推荐侧的冷启动替代方案新用户没有足够行为时直接把他归到某个已有群体然后用这个群体的偏好作为推荐依据。第三种是分析侧的异动监控当某个群体的规模和特征发生明显偏移时说明产品策略或者外部环境出现了变化。所以你在做行为聚类的时候从一开始就要想清楚下游是谁、他们拿着聚类结果要干什么。这个思考会直接影响你选 k 的思路。比如下游运营只能同时维护三套差异化的活动方案那 k 就算算出来是 7你也得想办法往 3 靠。这不是算法妥协而是业务现实。2. 选 k从“拍脑袋”到“有章法”2.1 肘部法则为什么经常“没肘”选 k 是一道经典难题教科书上排名第一的方法就是肘部法则对不同 k 计算簇内误差平方和 SSE然后画折线图找那个拐点。听起来很简单但你在真实数据上画一次就会发现问题——大部分时候根本没有明显的“肘”。为什么因为用户行为数据的分布通常是连续且重叠的不存在天然的分离结构。你从 k2 一直算到 k12SSE 一直在平滑下降没有一个点出现剧烈的斜率变化。你盯着那张图看半天它就是在那里平滑地滑下去仿佛在嘲笑你。遇到这种情况我的做法是不再依赖单次曲线而是看“SSE 下降率的边际收益”。具体来说计算每个 k 相对于上一个 k 的 SSE 下降比例当下降比例首次低于某个阈值比如 8%时把这个 k 作为候选。这个阈值可以根据业务数据量调整数据量越大阈值可以设得越低。这样至少把“找肘”变成了一个可复现的流程而不是靠肉眼硬看。另外还有一个很实用的技巧不要只用 KMeans 一种算法算 SSE。试一下 MiniBatchKMeans 和 BisectingKMeans如果两种算法算出来的 SSE 曲线形态差异很大说明你的特征空间本身就不适合做球形聚类这个时候与其纠结 k不如先去调整特征。2.2 轮廓系数能帮上忙但别全信轮廓系数Silhouette Coefficient是比肘部法则更常用的一种内部评估指标它同时衡量簇内紧密度和簇间分离度取值范围在 -1 到 1 之间越大说明聚类效果越好。用 scikit-learn 就是一行的事from sklearn.metrics import silhouette_score score silhouette_score(feature_matrix, labels)但我必须提醒你轮廓系数在行为聚类里有一个天生的短板它对噪声和异常点极其敏感。用户行为数据里总有一批“什么都干”的超级活跃用户或者“只来过一次”的僵尸用户这些样本会让轮廓系数变得很低而且无论你怎么调 k 都低。如果你一看到轮廓系数只有 0.2 就以为聚类失败了那你可能错过了正确的 k。我的经验是轮廓系数不要看全局均值而是看每个簇的单独得分分布。如果一个簇的内部样本得分普遍为正、只有少量异常为负这个簇就是健康的。如果某个簇一半样本得分都是负的说明这个簇本身就是个“杂物箱”需要回头检查特征或者回归第 2.1 节重新选 k。还有一点很重要轮廓系数的绝对大小没有统一标准。不同特征工程方案下0.15 可能已经是很不错的分离效果了而另一些数据下 0.4 也未必代表可用。你真正要比较的是“同一套特征下不同 k 的相对表现”而不是拿这个数字去对标别人的项目。2.3 用 k 折交叉验证的思路检验稳定性选 k 还有一个经常被忽略的维度——稳定性。有些 k 值在训练集上跑出来效果很好但换一批样本或者换一个时间段就完全变形这种 k 在线上是没法用的。这里可以借用 k 折交叉验证的思路但目的是检验聚类结构是否稳定。具体做法把样本随机分成 5 份轮流取其中 4 份训练聚类模型然后用训练好的模型预测剩下 1 份的簇标签再对比直接用全部数据聚类时这些样本的归属计算一致性比例。或者更简单一点对同一批数据跑多次 KMeans每次不同的随机种子看样本的簇归属在不同运行之间的变化程度也就是簇分配的 Jaccard 相似度或者 Adjusted Rand Index。这个做法的价值在于它能把“这个 k 是不是碰巧效果好”和“这个 k 是不是本来就稳”区分开。我遇到过不少情况k5 时轮廓系数最高但每次重跑标签切换率高达 30%k8 时轮廓系数稍低但切换率只有 5%。这种情况下我会毫不犹豫选 k8因为模型上线后要面对的是源源不断的新数据稳定性比那一丢丢的指标优势重要得多。2.4 业务约束才是真正的 k 上限前面讲了一堆纯算法层面的 k 值选择方法但实战中真正决定 k 的往往是业务约束。你的运营团队能同时维护几种差异化策略你的推荐系统有多少个候选策略位你的报表体系能承载多少个人群标签这些问题的答案直接就是 k 的天花板。我见过一个特别典型的项目算法团队用肘部法则和轮廓系数选出了 k9结果运营负责人说团队只有 4 个人没法同时维护 9 套运营方案。最后还是把 9 个簇合并到 5 个簇按簇间距离做层次聚类一步一步向上合并直到数量满足业务上限。这里我提供一个比较务实的操作顺序先用轮廓系数和稳定性分析圈定一个 k 的合理区间比如算法告诉你 4 到 9 都还行然后去和业务确认他们能承接的上限最后在业务上限内选择最接近算法最优解的 k。顺序不能反反了你就是在脱离业务做纯学术研究上线那天一定会被打回来。3. 给聚类结果起名把“群号”翻译成人话3.1 先看特征中心再看区分度模型跑完之后你会得到一组编号cluster 0、cluster 1、cluster 2……如果直接把这种编号传给运营运营一定一脸懵。所以起名是行为聚类里必不可少的环节而不是什么锦上添花。起名的第一步是输出每个簇的特征中心向量。比如你有 20 个特征k5那就输出一个 5 行 20 列的表格看每个簇在哪几个特征上显著高于或者低于全局均值。这里我建议计算每个簇的“特征偏离度”也就是簇均值减去全局均值再除以全局标准差。偏离度大于 1 或者小于 -1 的特征才值得拿出来作为命名的依据。第二步是看区分度。一个特征是簇群命名依据条件是它必须在不同簇之间差异明显。如果所有簇在某个特征上都很高那这个特征没有区分度不能用来命名。我之前做过一个内容平台的项目所有簇的“日均使用时长”都偏高因为这个指标本身就没有筛选掉低活用户用它命名就会导致五个簇看起来都是“重度用户”完全没法区分。3.2 命名结构行为特征 业务含义给聚类起名不是写诗关键是让人一看就懂而且能对应上运营动作。我常用的命名格式是“行为特征 业务含义”比如高频搜索低价品用户 → 我简单记为“高频比价型”深夜浏览但不互动用户 → “夜间潜水型”短期内高频登录但持续无转化 → “高意向流失风险型”注意命名里要带一个可以量化的行为特征方便运营理解这个群的人到底干了什么同时带一个业务判断方便运营决定怎么触达。如果只有行为特征比如“高频搜索型”运营会问然后呢如果只有业务判断比如“高价值用户”运营会问凭什么实际操作中我会先按特征偏离度排序把每个簇最突出的 3 到 5 个特征列出来然后结合对这个簇用户数占比、人均价值贡献的理解写成一句话描述。比如某个簇的特征是“客单价高、复购率高、浏览深度深、客服咨询少”我就命名为“高价值静默购买型”运营看到这个名字就知道不需要太多打扰保持供给和权益稳定就行。3.3 起名是个迭代过程接受它要改三轮很多团队把起名当成一次性工作觉得聚类跑完、特征表出来、名字起好就结束了。实际上起名至少会经历三轮迭代。第一轮是基于特征中心的“机器起名”你看到的都是干巴巴的特征描述名字大概率是“特征A高特征B低型”。第二轮是拉出每个簇的真实用户样本随机抽几十个用户人工看他们的行为日志验证簇内的人是不是真的像名字描述的那样。这一步非常关键我经常在这一轮发现某个簇里有大量“异常样本”比如按特征中心应该全是女性用户但抽样发现很多男性用户的行为也符合这个簇的模式说明特征工程里漏掉了一个关键变量。第三轮是让业务方参与进来他们看了名字后会提出自己的理解。你能明显感觉到同一组用户算法说“高频活跃型”运营会说“这不是我们的核心付费人群吗”这种视角碰撞会倒逼你更精确地命名。所以不要害怕起名过程反复这个过程本身就是让算法结果被业务接受的过程。4. 冷启动安置新用户该进哪个桶4.1 冷启动的本质把“聚类问题”变成“分类问题”聚类是 unsupervised它只对已有的数据生效。一旦模型上线新用户不断进来你不可能每次有新数据就把全量数据重新聚类一遍那样代价太大而且聚类结果会抖动。所以你需要一个“安置策略”新用户到达时快速判断他应该归入哪个已有的簇。这一步的本质是把聚类问题转化为分类问题。你已经有了 k 个簇把每个簇当作一个类别然后建立一个从用户特征到簇标签的映射。这个映射可以简单到就是一个最近邻判断也可以复杂到是一个训练好的分类模型。这里有一个容易踩的坑用聚类时的特征矩阵去训练一个分类器拿它来做冷启动预测听起来合理但实际效果往往很差。原因在于聚类特征和分类特征的口径不一致。聚类用的是 30 天聚合特征新用户根本没有 30 天数据。所以你需要准备一套“可实时计算”的冷启动特征通常是基于当前会话、注册时填写的信息、首次打开时的行为这些维度虽然浅但能cover住新用户。4.2 近邻质心法最省事也最稳妥的安置方案冷启动安置最简单的方案就是计算新用户特征向量与每个簇质心的距离取最近的那个簇作为归属。使用欧氏距离还是余弦距离取决于你的特征标准化方式。如果特征是标准化的连续值欧氏距离就好如果特征稀疏且带有明显的量纲差异余弦距离往往更稳定。我给出一个非常简化的 Python 实现import numpy as np from sklearn.preprocessing import StandardScaler # cluster_centers_ 是聚类时得到的质心矩阵shape 为 (k, n_features) scaler StandardScaler() centers scaler.fit_transform(cluster_centers_) def assign_cluster(user_vector): user_vec scaler.transform(user_vector.reshape(1, -1)) dists np.linalg.norm(centers - user_vec, axis1) return int(np.argmin(dists)), dists.min()注意这里有个细节新用户的特征向量用的一定得是聚类时的同一套 StandardScaler 来变换不能重新 fit否则特征空间就错位了。这个 bug 我见过好几个同学犯表现就是模型离线验证准确率很高线上却乱归最后发现是 scaler 不一致。近邻质心法的好处是零训练成本、逻辑透明、方便排查。只要聚类模型本身没有更新质心矩阵就是固定的安置逻辑就是一次矩阵减法和一次 argmin压测下来单个请求微秒级就能返回完全够用。如果你的流量特别大也可以把质心矩阵存储到 Redis 或者本地内存进一步省去计算成本。4.3 阈值兜底宁可不归也不要乱归近邻质心法有一个致命缺点无论新用户长什么样它都会硬性分配一个簇。哪怕这个用户和所有簇质心的距离都非常远算法也会强行塞进距离最近的那个。这个行为在业务上很危险比如一个明显是机器人或者境外异常访问的用户被硬塞进“高价值用户”簇那么他后续收到的推荐策略就完全错乱。解决方案是设一个距离阈值当最小距离超过阈值时不给该用户分配任何现有簇而是标记为“待观察”进入另一条冷启动探索逻辑。阈值的确定方法我会用训练集里所有样本到所属质心的距离分布取 95 分位数作为阈值。也就是说在训练数据里 95% 的正常用户都能被正确覆盖剩下 5% 以及未来的异常用户会落到“未分类”区域。这个“不归”策略看起来损失了一部分用户的个性化能力但换回来的是整个聚类体系的纯净度。我做过一个对比实验硬分配方案下聚类簇内噪声率大约 12%而加了阈值兜底后噪声率降到 4% 左右。簇内更纯净下游策略的命中率才有保障。4.4 特征不足时的冷启动策略新用户最麻烦的地方不是距离计算而是根本凑不齐特征。一个刚注册的用户可能只有一个设备型号、一个注册渠道、一个首次落地页你要怎么把他塞进聚类体系这种场景下我推荐的方式是做一个“渐进式安置”把冷启动分成三个阶段。0 到 1 天内用户特征极少直接用注册来源渠道、设备价位、首访落地页这类基础画像特征做一个简单的粗分群规则可以人工设定比如“安卓低端机 首访活动页”归入一个经验性的初始群。1 到 7 天内积累了一些行为特征用这些浅层特征跑一次近邻质心法给一个临时簇。超过 7 天特征基本稳定正式进入聚类体系替换掉临时标签。过程中要特别注意标签的切换通知机制。如果你的下游系统订阅了用户簇标签切换时一定要发送变更事件否则推荐系统还在用旧标签服务用户策略一致性就乱了。我在实际项目里就遇到过用户已经进入稳定聚类了但因为标签更新链路是 T1 的推荐系统多用了两天临时标签导致用户收到的推荐内容和真实兴趣脱节转化率明显下降。5. 实操中的坑与排查记录5.1 特征没标准化聚类等于白做KMeans 这类基于距离的聚类算法对特征的量纲极其敏感。用户行为特征里有的变量是“登录次数”个位数到几十有的是“消费金额”几十到几万有的是“浏览时长”几十秒到几小时。如果不做标准化消费金额会直接主导距离计算其他特征全部失去意义。所以特征标准化是行为聚类里绝对不能省略的一步。通常使用 StandardScaler 做 Z-score 标准化。但这里也有一个需要判断的点如果你的特征里包含大量 0 值比如“是否购买过某品类”这种 0/1 特征Z-score 后依然能工作但如果是极度稀疏的计数特征比如“搜索关键词次数”用 MinMaxScaler 或者先做 log1p 变换再标准化效果更好。我自己的习惯是对偏态严重的计数特征先做 log1p再 StandardScaler对 0/1 特征直接保留或者以二值方式参与计算。还有个坑是标准化要在划分验证集之前做。严格来说应该先对全量训练数据拟合 scaler然后再对任何新样本使用同一个 scaler 变换。很多人图省事在 pipeline 里让 scaler 重新 fit结果每次跑出的聚类结果都不一样排查半天才发现是这个问题。5.2 KMeans 对初始化随机性敏感KMeans 的聚类结果受初始质心选择影响很大尤其是 k 比较大或者数据本身簇结构不明显时不同 random_state 跑出来的结果可能差异显著。我最早跑行为聚类时同一个 k 值跑三次得到三个完全不同的簇标签分布一度以为是代码写错了。排查后发现是 KMeans 本身的局部最优问题。scikit-learn 默认的 KMeans 使用 KMeans 初始化已经在很大程度上缓解了这个问题但并不能完全消除。我的建议是正式确定 k 值之前对每个候选 k 跑至少 10 次每次用不同 random_state把多次运行中目标函数值最小的一次作为最终结果同时在最终确定模型参数时固定 random_state保证结果可复现。如果是海量数据跑 10 次 KMeans 成本太高可以用 MiniBatchKMeans它会牺牲少量精度换取速度但要注意 MiniBatch 的稳定性比 KMeans 差更需要多次运行取最优。我通常的做法是先用 MiniBatch 快速扫描多个 k 值锁定候选区间后再用标准 KMeans 在候选 k 上跑次数取最优。5.3 聚类标签随时间漂移需要定期校准行为聚类最容易被忽视的问题是标签漂移。用户的真实行为模式会随着产品版本迭代、运营活动、季节因素发生缓慢变化。今天聚出来的 5 个簇三个月后可能已经名存实亡但系统还在按旧标签给用户打策略。我的建议是建立一个定期校准机制每周跑一次数据分布监控对比当前特征分布和建模时的分布每个月用最新数据全量重跑一次聚类对比新旧簇结构的映射关系。如果簇数量变了或者簇质心移动超过一定阈值比如特征空间中的欧氏距离大于原来的 2 倍标准差就触发模型更新。标签漂移的另一面是老用户的标签更新。不是每个用户每个周期都要重新打标签但至少对活跃用户要低频更新。我的经验是对活跃用户按周更新簇标签对非活跃用户按月更新这样既控制计算成本又能保证策略是贴近用户当前状态的。这个频率可以根据你的数据量调整核心是建立机制而不是靠人工定期手动重跑。5.4 冷启动特征和聚类特征口径不一致怎么协调前面提到冷启动用浅层特征聚类用深层特征这必然会带来一个协调问题浅层特征算出来的簇归属和深层特征算出来的簇归属可能完全不同。处理这个问题的核心原则是把浅层判定当作“临时档位”把深层判定当作“正式档位”两个体系之间通过迁移规则关联。比如你在浅层特征上做了一套粗分群划分成 3 个粗群深层特征聚类出了 6 个细簇。你需要训练一个映射模型学习“粗群 X 的用户大概率属于细簇 Y 和 Z”这样新用户来了之后先落到粗群再按概率分布进入细簇而不是在浅层直接拟合细簇。这个方案比“强行用浅层特征模拟深层聚类”靠谱得多。我试过直接拿浅层特征去拟合聚类模型效果很差因为浅层特征的信息量不足以支撑细粒度的聚类结构。而分层映射的方式等于把不确定性显式建模出来每层只做自己有能力做的事。6. 一套可以直接照搬的落地流程6.1 最小可用的实现路线如果你现在要在一个新业务里跑通行为聚类我建议你严格按下面这个顺序来每一步都有明确产出第一步确定分析粒度。先回答“行是用户还是会话”绝大多数情况选用户。 第二步梳理特征。列出所有能代表用户行为的字段设计 7 天和 30 天两套聚合特征注意对计数特征做 log1p对连续特征做标准化。这里要记录每一套特征的 scaler 对象后续冷启动要用。 第三步选 k。按照第 2 节的方法先看 SSE 边际收益圈定候选区间再用轮廓系数和跨随机种子的稳定性分析缩小范围最后跟业务确认 k 的上限。 第四步聚类与命名。跑最终模型输出特征偏离度表给每个簇起“行为特征 业务含义”的名字抽样验证簇内一致性让业务方参与命名评审。 第五步构建冷启动安置模块。保存聚类质心建立近邻质心分配函数设置距离阈值兜底再根据用户已积累的行为天数做渐进式安置。 第六步配置监控与复盘。每周看簇规模变化每月触发一次重聚类对比建立标签漂移预警每次模型更新都保留旧版本方便回滚。这套流程最早我在一个日活百万级的内容产品上跑通过从数据准备到上线大约用了三周核心代码量其实不大大头全在数据清洗、特征口径对齐和业务评审这些“非算法”的事情上。6.2 什么时候必须重新训练什么时候不用最后一个话题也是运营和算法之间最容易起分歧的点聚类模型到底多久重训一次我的原则是有明确事件驱动时重训没有事件驱动时定期重训。事件驱动包括产品做了一次大的改版、上线了新的核心功能、运营策略发生了根本性调整、监测到用户行为分布发生明显偏移。定期重训的节奏我推荐月度因为月度能覆盖大部分常见的业务波动又不会频繁到让下游策略无所适从。重训的时候有一个必须注意的合规问题新聚类结果上线前要对比新旧标签的迁移情况。如果某个旧簇的用户被大规模打散到多个新簇说明这个旧簇本身就不稳定下游依赖这个簇的策略需要重点排查。我一般是生成一个新旧标签的交叉表人工过一遍确认没有奇怪的迁移后再切换线上模型。从我个人的使用体验来看行为聚类这条链路真正难的不是任何一个单独环节而是整条链路的闭环算法选出来的 k 要能让业务接受聚类结果要能用业务语言表达冷启动安置要能接住源源不断的新用户还要有一整套监控机制保证模型不腐烂。这个闭环没有哪一步是可以跳过的你跳过哪一步后面就一定会在线上加倍还回来。把基础环节一个一个磨扎实行为聚类就会成为你工具箱里一个既稳定又高效的分群武器。