用余弦相似度做用户流失预测:行为模式偏移的向量化建模
1. 项目概述用余弦相似度做流失预测不是炫技是解决真实业务痛点在客户生命周期管理中“谁会走”永远比“谁来了”更难回答。我做过三年电信行业的用户行为分析最常被业务方拍桌子问的一句话就是“上个月活跃的50万用户里下个月到底有多少人会销户能不能提前两周告诉我”——不是要一个模糊的百分比而是要一份可干预、可触达、可验证的精准名单。这时候传统逻辑回归或XGBoost模型常陷入两难特征工程越深业务解释性越差解释性越强AUC又掉到0.68以下连基本筛选门槛都过不去。而这个项目标题里的Cosine Similarity Classification Algorithm For Churn Prediction表面看是算法名词堆砌实则直指一个被长期忽视的底层逻辑用户流失不是孤立事件而是行为模式在向量空间中发生系统性偏移的结果。我们不预测“这个人会不会走”而是判断“这个人的当前行为轨迹和已知流失用户的典型轨迹在方向上有多接近”。余弦相似度不关心绝对数值大小比如某用户本月通话时长是200分钟还是2000分钟只聚焦于行为维度间的相对比例关系——这恰恰匹配了真实业务场景一个高价值用户突然减少APP内消息点击、降低视频观看完成率、但保持基础通话时长其向量方向已悄然滑向流失簇而一个低频用户偶然多刷了两天短视频向量方向却未必偏移。这种对“行为结构”的敏感让模型在冷启动、小样本、跨业务线迁移时异常稳健。它适合三类人直接抄作业一是业务侧需要快速上线可解释预警名单的产品经理二是数据工程师想绕过复杂特征衍生、用原始行为日志直接建模的实施者三是算法同学想构建轻量级基线模型、用于AB测试对照组的开发者。这不是替代深度学习的方案而是把预测问题拉回业务语义层的第一步扎实落地。2. 核心设计思路与方案选型逻辑为什么是余弦相似度而不是欧氏距离或Jaccard2.1 从流失本质出发行为模式偏移 ≠ 数值衰减先说一个我踩过的坑早期用欧氏距离做相似度计算结果发现模型总把“沉默用户”所有行为指标接近0误判为高风险。为什么因为欧氏距离衡量的是两点在空间中的绝对距离当一个用户所有行为归零时它天然靠近坐标原点而原点附近恰好聚集了大量历史流失用户毕竟停机后行为清零。但这完全违背业务直觉——一个连续三个月无任何APP登录、无话费充值、无短信收发的用户根本不需要模型预测系统自动标记为“失联”即可真正需要预警的是那些“还在用但用法变了”的灰度用户。余弦相似度完美规避了这个问题它只计算两个向量夹角的余弦值公式为$$\text{cos}(\theta) \frac{\mathbf{A} \cdot \mathbf{B}}{|\mathbf{A}| |\mathbf{B}|}$$分子是点积反映方向一致性分母是模长乘积消除向量长度影响。这意味着即使用户A本月通话时长100分钟、APP使用时长30分钟用户B对应为1000分钟、300分钟只要比例一致100:30 ≈ 1000:300余弦值就接近1。而流失用户的典型模式正是“高频基础通信保留高价值互动行为坍塌”——比如语音通话时长微降5%但视频播放完成率暴跌70%直播打赏频次归零。这种结构性坍塌在余弦空间中表现为向量方向剧烈旋转而非简单收缩。2.2 对比Jaccard为什么行为序列不能当集合处理有同事提议用Jaccard相似度交集/并集理由是“用户行为就是一系列事件集合”。这在点击流去重场景有效但对流失预测是灾难性的。举个真实案例某银行APP用户流失前最后30天行为序列为【登录→查余额→转账→退出】重复12次【登录→理财页面→返回】出现3次【登录→贷款计算器→关闭】出现1次。Jaccard会把它和另一个健康用户行为序列高度重合但多出【登录→信用卡还款】算出高相似度因为交集很大。但业务方一眼就能看出问题前者所有行为都止步于“信息获取”后者有明确交易闭环。余弦相似度通过将行为编码为带权重的向量如转账次数3理财页面停留时长120秒贷款计算器使用频次1天然保留了行为强度和持续时间的语义信息而Jaccard把一切压缩成0/1存在性标签丢失了最关键的“行为深度”维度。2.3 方案架构三层漏斗式分类器拒绝端到端黑箱我们最终采用的不是单点余弦计算而是三层递进结构第一层动态基准簇构建不预设固定流失用户库而是按月滚动更新“近期流失簇”。例如取T-3月到T-1月期间实际流失的用户定义为T月未产生任何计费行为且未登录APP将其T-1月的行为向量聚类K-meansK3生成3个典型流失模式中心向量C₁、C₂、C₃。这样做的好处是捕捉业务变化——Q4电商大促期间流失用户可能集中表现为“频繁比价但零下单”而Q1则更多是“理财到期未续投”。第二层多中心余弦打分对每个待预测用户U计算其T-1月行为向量U_vec与三个中心向量的余弦相似度sim₁cos(U_vec, C₁)sim₂cos(U_vec, C₂)sim₃cos(U_vec, C₃)。这里的关键技巧是不取最大值而取加权平均。因为不同簇代表不同流失动因C₁价格敏感型C₂服务体验型C₃需求转移型单一最高分可能只是偶然匹配。我们按各簇在历史流失中的占比赋予权重w₁、w₂、w₃如C₁占45%则w₁0.45最终得分S w₁×sim₁ w₂×sim₂ w₃×sim₃。第三层业务规则熔断得分S0.85直接标红预警0.7S≤0.85进入人工复核池S≤0.7但满足“近7天APP登录频次下降超60%且无任何交易行为”则触发二级预警。这一层把算法输出锚定在业务动作上避免纯数学结果脱离实际。提示不要试图用余弦相似度直接替代所有模型。它最擅长的是“初筛归因”即快速锁定高风险人群并指出“最像哪类流失用户”后续再用XGBoost对初筛名单做精细化概率校准效果提升显著。我们实测在某省电信项目中初筛召回率92%误报率仅18%为运营团队节省了65%的人工外呼成本。3. 核心细节解析与实操要点行为向量化不是填表是业务语义翻译3.1 行为特征选择必须包含“反向信号”否则模型会自我欺骗很多团队一上来就堆砌“APP启动次数”“页面浏览时长”“按钮点击量”等正向指标结果模型学到了一个危险规律所有指标越高流失概率越低。这看似合理实则埋雷。真实流失用户中有相当比例是“高粘性沉默者”——他们每天打开APP5次但只看首页新闻从不点击任何功能入口或连续30天登录网银却只查询余额从未进行转账、理财、缴费等任一交易。如果特征中缺失“交易深度”“功能渗透率”等反向信号模型会把这类用户误判为超级忠诚用户。我们强制要求特征集包含三类指标基础活跃度正向日均登录频次、周均使用时长、月均设备启动次数价值转化度双向交易类行为占比如转账次数/总点击次数、高价值功能使用率如理财页面访问时长/总浏览时长、ARPU分位数相对于同套餐用户行为坍塌度反向关键路径中断率如进入购物车但未结算的次数/进入购物车总次数、功能使用断层如上月使用过5个核心功能本月仅剩1个、行为熵值基于行为序列的香农熵衡量行为多样性流失前常急剧降低。特别强调“行为熵值”的计算将用户一个月内所有行为事件按类型编码登录1查询2转账3…生成长度为N的序列计算其概率分布P(xᵢ)熵H -ΣP(xᵢ)log₂P(xᵢ)。健康用户熵值通常在2.1~2.8之间行为丰富而流失前3周熵值常跌破1.5行为僵化。这个指标在电信场景中比单纯看“APP使用时长下降”敏感3倍以上。3.2 向量标准化L2归一化是必须步骤但需避开“伪归一化”陷阱余弦相似度公式分母含向量模长因此输入向量必须L2归一化即每个向量除以其欧氏长度。但新手常犯一个致命错误对整个特征矩阵做全局标准化如sklearn的StandardScaler再归一化。这会导致业务语义丢失。举个例子特征A是“月通话时长分钟”范围0~5000特征B是“APP内消息发送次数”范围0~200。全局标准化后两者量纲统一但“发送1条消息”的业务意义被稀释——在电信场景中主动发送消息是强留存信号其权重本应高于被动接听电话。正确做法是先按业务重要性对原始特征加权再L2归一化。权重设定依据三条原则1业务方共识度如“是否完成首次充值”权重2.0因运营团队确认这是关键转化节点2历史单特征IV值Information Value0.3的特征权重≥1.53特征稳定性月波动率15%的特征权重上浮0.2。我们最终采用的加权公式为$$\mathbf{V}{\text{weighted}} [w_1 \times v_1, w_2 \times v_2, ..., w_n \times v_n]$$然后计算 $$\mathbf{V}{\text{norm}} \frac{\mathbf{V}{\text{weighted}}}{|\mathbf{V}{\text{weighted}}|_2}$$实测表明加权归一化比简单归一化在F1-score上提升0.11且高风险用户名单与业务方人工标注的吻合度从68%升至89%。3.3 相似度阈值设定拒绝一刀切用业务成本倒推数学边界几乎所有教程都教“余弦相似度0.8即判定为相似”但在流失预测中这是自杀行为。原因很简单业务干预有真实成本。给1000个用户发优惠券成本2万元外呼100人成本5000元而错失1个高价值流失用户损失可能超5万元。因此阈值必须由业务ROI反向计算。我们建立了一个成本函数$$\text{Cost} C_{\text{FP}} \times \text{FP Rate} C_{\text{FN}} \times \text{FN Rate}$$其中C_FP是单次误报成本如发券成本C_FN是单次漏报成本如用户离网导致的ARPU损失×预期留存月数。在某省移动项目中C_FP20元C_FN32000元高端合约用户。通过遍历相似度阈值0.5~0.95计算对应FP/FN率找到使总成本最低的阈值点——结果是0.73。这意味着只有当用户向量与流失簇中心的余弦相似度≥0.73时才触发预警。这个数字看起来很低但它让整体干预成本下降了41%同时保住了93%的高价值流失用户。记住没有普适阈值只有业务驱动的最优解。4. 实操过程与核心环节实现从原始日志到可执行预警名单4.1 数据准备原始日志清洗的四个生死关卡算法效果70%取决于数据质量。我们处理某运营商12TB原始日志时发现四个必过关卡关卡一行为事件去噪原始日志包含大量无效事件APP闪退自动上报的“页面停留0秒”、网络抖动导致的重复点击、测试账号产生的模拟行为。我们采用双阈值过滤1单次事件停留时长0.3秒且无后续交互直接剔除2同一用户10分钟内相同事件重复出现5次合并为1次并标记“疑似异常”。实测过滤后有效行为事件量提升2.3倍模型训练速度加快40%。关卡二时间窗口对齐流失预测必须严格限定时间粒度。我们定义T月为预测月T-1月为行为分析月T-3至T-1月为流失基准期。关键陷阱在于“自然月”不等于“用户生命周期月”。例如用户6月15日入网其“T-1月”行为应统计6月15日至7月14日而非6月1日至30日。我们开发了动态窗口切片脚本按用户入网日期生成个性化时间轴避免新用户被错误纳入“沉默用户”池。关卡三稀疏特征填充行为向量中必然存在大量0值如用户从未使用过5G消息功能。简单填0会扭曲余弦计算0向量与任何向量余弦值为0但业务上“未使用”不等于“排斥”。我们采用业务感知填充法对功能类特征如“5G消息发送次数”用该用户所在套餐类型的历史平均使用频次填充对强度类特征如“视频播放完成率”用其个人过去3个月的移动平均值填充。这比全局均值填充使模型AUC提升0.07。关卡四向量维度裁剪初始特征达217维但余弦相似度在高维稀疏空间中易失效维度灾难。我们用两种方式降维1业务强相关性筛选剔除与历史流失率相关性|r|0.15的特征如“天气预报页面访问次数”2PCA主成分分析保留累计方差贡献率≥85%的主成分。最终稳定在42维既保证业务可解释性每维对应明确行为含义又避免数学失真。4.2 核心代码实现三步构建可复用的余弦分类器以下为生产环境部署的核心Python代码已脱敏可直接运行import numpy as np import pandas as pd from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from scipy.spatial.distance import cosine class CosineChurnClassifier: def __init__(self, n_clusters3, weight_configNone): self.n_clusters n_clusters self.weight_config weight_config or self._default_weights() self.churn_centers None self.cluster_weights None def _default_weights(self): # 按业务重要性预设权重可后续由运营团队调整 return { login_freq: 1.8, # 登录频次强活跃信号 transact_ratio: 2.5, # 交易类行为占比核心价值指标 entropy: 1.2, # 行为熵值反向坍塌信号 msg_send_cnt: 1.5 # 消息发送次数主动互动指标 } def _build_behavior_vector(self, user_df): 将用户行为日志转为加权向量 # 提取关键特征示例 features [ user_df[login_count].mean(), # 日均登录频次 user_df[transact_cnt].sum() / user_df[click_cnt].sum() if user_df[click_cnt].sum() 0 else 0, # 交易占比 self._calculate_entropy(user_df), # 行为熵值 user_df[msg_send_cnt].sum() # 消息发送总次数 ] # 应用业务权重 weighted_features [f * self.weight_config.get(k, 1.0) for f, k in zip(features, [login_freq, transact_ratio, entropy, msg_send_cnt])] # L2归一化 vec np.array(weighted_features) return vec / np.linalg.norm(vec) if np.linalg.norm(vec) 0 else np.zeros(len(vec)) def _calculate_entropy(self, user_df): 计算行为序列香农熵 if len(user_df) 0: return 0 # 将行为类型编码为整数 behavior_codes user_df[behavior_type].map({login:1, query:2, transact:3, msg:4, other:5}) # 计算概率分布 counts np.bincount(behavior_codes.dropna().astype(int), minlength6)[1:] probs counts / counts.sum() if counts.sum() 0 else np.zeros(len(counts)) # 计算熵 entropy -np.sum([p * np.log2(p) for p in probs if p 0]) return entropy def fit(self, churn_users_df): 拟合流失簇中心 # 构建流失用户向量矩阵 churn_vectors [] for uid in churn_users_df[user_id].unique(): user_log churn_users_df[churn_users_df[user_id]uid] vec self._build_behavior_vector(user_log) if len(vec) 0: churn_vectors.append(vec) if len(churn_vectors) self.n_clusters: raise ValueError(f流失用户不足{n_clusters}个无法聚类) # K-means聚类 kmeans KMeans(n_clustersself.n_clusters, random_state42, n_init10) cluster_labels kmeans.fit_predict(churn_vectors) # 计算各簇权重按簇内用户数占比 cluster_counts np.bincount(cluster_labels, minlengthself.n_clusters) self.cluster_weights cluster_counts / cluster_counts.sum() # 存储簇中心已归一化 self.churn_centers kmeans.cluster_centers_ # 对每个中心向量再次L2归一化KMeans输出未归一化 self.churn_centers np.array([c / np.linalg.norm(c) for c in self.churn_centers]) def predict(self, target_users_df, threshold0.73): 预测目标用户流失概率 results [] for uid in target_users_df[user_id].unique(): user_log target_users_df[target_users_df[user_id]uid] user_vec self._build_behavior_vector(user_log) if len(user_vec) 0 or np.all(user_vec 0): # 无效向量标记为低风险 score 0.0 pred 0 else: # 计算与各簇中心的余弦相似度 similarities [] for center in self.churn_centers: # 余弦相似度 1 - 余弦距离 sim 1 - cosine(user_vec, center) similarities.append(sim) # 加权平均得分 score np.dot(similarities, self.cluster_weights) pred 1 if score threshold else 0 results.append({ user_id: uid, cosine_score: score, is_churn_risk: pred, similar_cluster: np.argmax(similarities) if similarities else -1 }) return pd.DataFrame(results) # 使用示例 # 1. 加载流失用户日志T-3至T-1月实际流失者 churn_logs pd.read_parquet(churn_users_T-3_to_T-1.parquet) # 2. 初始化分类器 classifier CosineChurnClassifier(n_clusters3) # 3. 拟合流失簇 classifier.fit(churn_logs) # 4. 预测T月待评估用户T-1月行为日志 target_logs pd.read_parquet(target_users_T-1.parquet) predictions classifier.predict(target_logs, threshold0.73) # 5. 输出高风险名单 high_risk predictions[predictions[is_churn_risk]1].sort_values(cosine_score, ascendingFalse) print(f识别高风险用户 {len(high_risk)} 名)这段代码的关键设计点在于_build_behavior_vector方法中嵌入了业务权重和L2归一化fit方法中对KMeans中心向量做了二次归一化因KMeans输出的是欧氏空间中心非余弦空间predict方法中用1 - cosine()转换为相似度scipy的cosine距离是1-cosθ。实测在Spark集群上处理500万用户单次预测耗时8分钟。4.3 模型验证不用AUC用“运营可行动性”指标我们弃用传统AUC、KS等统计指标改用三个业务可验证指标触达率预警名单中实际在T1月流失的用户占比。要求≥85%干预响应率对预警用户发放定向优惠券后7日内产生交易行为的用户占比。要求≥35%证明名单精准成本收益比干预总成本 / 挽留用户带来的ARPU增量 × 挽留月数。要求≥1:3。在某省电信试点中首月触达率89%干预响应率41%成本收益比1:4.2。更重要的是运营团队反馈“终于能看清用户为什么走——系统提示‘相似簇2服务体验型’我们立刻调取该用户近3个月的投诉记录发现其宽带故障报修3次未解决针对性补偿后用户留存。” 这种可归因、可行动的输出才是算法落地的价值锚点。5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 问题一相似度普遍偏低90%用户得分0.3模型像没开现象训练完成后所有用户余弦得分集中在0.1~0.4区间无法区分风险等级。排查路径检查向量归一化打印np.linalg.norm(user_vec)若大量为0或极小值1e-8说明特征全为0或权重设置错误验证流失簇质量计算各簇中心向量间的余弦距离若任意两簇距离0.1说明聚类失败K值过大或流失模式单一需减少K或增加行为维度审查特征填充逻辑重点检查“行为熵值”计算若用户行为序列过短5个事件熵值恒为0导致整个向量权重坍塌。根治方案我们在熵值计算中加入最小长度保护——序列长度10时用该用户历史平均熵值填充并添加“低熵警告”标志位。同时将K值从5降至3强制模型聚焦最典型的流失模式。修复后得分分布变为0.2~0.85标准差扩大2.1倍。5.2 问题二高价值用户总被漏报模型偏向“沉默者”现象ARPU排名前10%的用户中预警覆盖率仅52%远低于全量用户的89%。根本原因高价值用户行为基数大L2归一化后向量长度趋近但其“行为坍塌”的绝对变化量小。例如月均转账100次的用户流失前降到80次-20%而月均转账5次的用户降到1次-80%后者在向量中体现为更大方向偏移。解决方案引入相对变化加权。对每个特征v计算其相对于用户自身历史均值的变化率Δv (v_current - v_mean) / v_mean再用Δv替代原始v参与向量构建。这样高价值用户的20%下降与低价值用户的80%下降在向量中获得同等方向权重。实测后高价值用户覆盖率升至86%且未显著增加误报。5.3 问题三模型效果随时间衰减快每月需重新训练现象模型上线首周AUC 0.82第三周跌至0.71运营抱怨“不准了”。诊断结论流失模式具有强时效性但基准簇更新滞后。原方案用T-3至T-1月数据当T月发生重大营销活动如暑期流量包促销T-1月用户行为已受干扰不再代表“自然流失”模式。优化机制建立双周期基准库。主基准库仍用T-3至T-1月但新增“应急基准库”——当监测到T月某类流失率环比突增30%如携号转网用户激增立即提取T月流失用户行为聚类生成临时中心向量与主库加权融合主库权重0.7应急库权重0.3。该机制使模型衰减周期从7天延长至21天。5.4 问题四业务方质疑“余弦值0.75和0.76有什么区别”本质矛盾算法指标与业务决策颗粒度不匹配。破局实践我们放弃解释数学差异改为输出可操作归因报告。对每个高风险用户系统自动生成“最相似流失簇”名称如“价格敏感型”“关键偏离行为”TOP3如“视频会员续订率下降62%”“竞品APP使用时长增加210%”“推荐干预动作”如“推送10元视频会员抵扣券”“发送竞品对比说明书”。这份报告直接嵌入CRM系统一线客服看到的不是数字而是“该用户因视频会员贵而想走建议立即发券”。当算法输出变成业务动作指令质疑自然消失。注意余弦相似度分类器不是万能钥匙。它在以下场景会失效1用户行为数据缺失严重如仅能获取月账单无日志2业务处于剧烈变革期如APP全面重构旧行为模式作废3流失定义模糊如“沉默30天”与“销户”混用。遇到这些情况优先补数据、明规则再谈算法。6. 扩展应用与经验沉淀从单点预测到用户健康度体系这个算法的价值远不止于流失预警。在落地过程中我们逐步将其扩展为用户健康度评估框架横向扩展多业务线健康度图谱将同一套向量构建逻辑迁移到不同业务线金融APP行为向量侧重“理财持仓变动”“贷款申请频次”“客服咨询主题”电商APP侧重“搜索关键词多样性”“购物车放弃率”“评价撰写字数”视频平台侧重“内容类型切换频次”“弹幕发送密度”“跳过片头时长”。通过统一余弦计算框架可横向对比用户在各业务线的健康度识别“主阵地流失风险”如视频用户在电商APP健康度骤降预示泛娱乐需求转移。纵向深化流失路径动态追踪不止看T-1月单点向量而是构建“健康度时间序列”。对每个用户计算其过去6个月每月与流失簇的余弦得分生成趋势曲线。我们发现典型流失路径有三类1阶梯式坍塌每月得分递减0.052悬崖式崩塌某月得分突降0.33震荡式恶化得分在0.6~0.7间反复横跳。不同路径对应不同干预策略——阶梯式需长期培育悬崖式要紧急挽留震荡式则需诊断具体断点。终极沉淀业务语义词典将42维行为向量中的每一维与业务动作强绑定形成可查询词典。例如向量第7维 “直播打赏金额/总充值金额”业务含义 “付费意愿强度”健康阈值 0.15向量第12维 “客服咨询中提及‘取消’‘退订’的频次”业务含义 “离网意向信号”预警阈值 2次/月。这份词典成为产品、运营、算法三方的通用语言彻底终结“算法说A业务听B”的沟通黑洞。我在实际操作中发现最有效的推广方式不是讲算法而是带业务方一起看一个真实案例调出一个高风险用户逐行解读其行为向量中哪几维偏离、偏离多少、对应什么业务动作、建议如何干预。当他们亲手在CRM里点击“发送定制优惠券”并看到用户24小时内完成交易时所有关于“余弦相似度”的疑虑都在那一刻烟消云散。技术的价值从来不在公式多美而在它能否让一线人员多挽留一个用户。