
1. 项目背景我们为什么要做这个系统大概三年前我所在的数据团队接到一项任务某电信运营商的客户运营部门一直有一个头疼的问题用户流失始终发生在“被看见”之前。等到客服收到大量投诉、或者用户干脆不来电了通常已经进入流失倒计时运营能做的基本就是事后发券挽回成功率低得可怜。当时我们想做一件事把散落在各个业务系统里的用户行为日志变成一套可以指导运营动作的“预警雷达”。于是就有了这个基于Python的电信用户行为分析系统——项目不追求大而全核心诉求是三句话知道用户现在处于什么状态、预测用户下一步会不会流失、给运营提供一个可以每天打开来看的工作台。这个系统覆盖了从数据采集、清洗、特征计算、模型训练到服务化接口和可视化看板的完整链路。感兴趣的朋友可以分为两类一类是数据开发或算法工程师可以重点看数据分层、特征工程和模型监控的部分另一类是数据分析师或运营方向的读者可以重点看标签体系怎么落地、以及怎么把模型分数真正变成运营动作。我会尽量把两层内容都照顾到既有技术细节也有业务层面的考量和坑。项目上线后大概运行了大半年整体效果达到了预期高风险用户识别的提前周期从“用户发完牢骚才干预”提前到了“用户开始低频使用就介入”后端运营的主动触达执行率从不到三成提升到了七成以上。这篇文章把设计和实现过程完整复盘一遍希望能给正在做类似系统的人一些参考。2. 整体设计从散乱日志到可执行标签的四个层次2.1 先把数据格局盘清楚做行为分析系统之前最忌讳的就是一上来就搭模型。我们做的第一件事是把这家运营商的数据资产摸了个底。电信行业的数据形态其实很典型大体上分成四类上网行为数据用户通过基站上网产生的流量话单包含起始时间、时长、上下行流量、访问类型等字段。语音通话数据主被叫号码、通话时长、呼叫类型本地、长途、漫游、通话时段。业务办理数据套餐变更、增值业务订购退订、缴费记录、余额变动。服务接触数据客服热线来电记录、投诉工单、营业厅到访记录、App端操作日志。这四类数据散落在不同系统里格式不统一有的是T1的离线文件有的走消息队列实时上报。体量上每天新增的行为记录量级是几千万到上亿条不等原始数据压缩后也要占用数TB的空间。用一句话概括特点量大、维度多、质量参差不齐。我们最终确定的处理原则是“分层消化”——原始层只做清洗和归档汇总层做宽表特征层做指标聚合服务层才面向业务。这样做的好处是每一层各司其职血缘清晰出了问题能快速定位是在哪一步挂掉的。2.2 为什么选Python作为主技术栈技术选型是项目启动时被问得最多的问题。数据量大、行为分析、电信行业这些词凑在一起很多人第一反应是Java或者Scala配合大数据组件。但我们的现实情况是团队核心成员全部是Python背景项目周期只有四个月而且业务方要求的交付物更多是“看得见的标签和分数”而不是一套需要重兵维护的底层平台。所以最终架构是Python为主语言离线计算用PySpark跑在已有集群上在线服务用FastAPI提供接口存储层用Hive做离线宽表、MySQL存业务配置和元数据、Redis做在线特征缓存。这个选型有几个实际好处第一团队上手快。写特征计算逻辑、训练模型、做接口全程用Python不需要交叉维护两套技术栈。第二Python生态的ML链路完整pd、sklearn、LightGBM、joblib串起来非常顺模型产物直接可以序列化成文件加载到服务端省掉了很多模型转换的麻烦。第三项目迭代节奏快业务方今天提一个新标签需求我们下午写个Python脚本算完就能上线。当然纯Python方案也有明显的边界。如果你每天的数据量是百亿级、要求秒级响应、并且需要一个超大规模的在线特征平台那Python做主力确实吃力。但在我们的场景下几千万到上亿的日增数据PySpark配合列式存储完全够用没必要为了“大数据”三个字引入整套重型技术栈。2.3 系统四个层次的功能边界整个系统从逻辑上拆成四个层次每一层的产出都是下一层的输入我们在设计时就刻意划清了边界数据接入与清洗层负责处理各类日志上报、格式解析、去重、字段补齐、脱敏。产出是“干净明细表”和“用户主档表”。指标聚合与标签层基于明细表做滑动窗口聚合生成活跃度、消费力、偏好、生命周期等指标和标签。产出是“用户标签宽表”。算法模型层基于标签宽表训练离网预警模型、营销响应模型定期重跑并输出用户风险分。产出是“风险评分表”。服务与展示层面向运营人员提供用户查询接口、风险名单、可视化看板和干预工单回写功能。产出是“可用的工作台”。层次划分看似简单真正的价值在于“不让业务直接碰原始表”。运营方经常提“能不能给我一份最近一周所有低流量用户的名单”如果允许他们直接查明细表很容易因为口径不同产生对不上的数字。统一从标签宽表取数至少保证大家拿到的口径是一致的。3. 核心指标与标签体系口径共识是项目的命根子3.1 先把“流失”这个词定义清楚项目进行到第一个月我们被一个听起来很简单的问题绊住了到底什么样算“流失用户”数据团队说连续90天无活跃行为算流失运营团队说用户办了8元保号套餐就是流失客服团队的说法更直接打过投诉电话说“再也不用了”的就是流失。这个问题不解决后面的模型、标签、看板全都建立在一个沙子上。我们花了整整两周拉了各个业务方开了三轮会最后定下来的口径是分场景的沉默用户连续30天无上网记录且无语音通话记录的用户。这个口径用于日常预警留给运营5到10天去干预。准流失用户连续60天使用频次持续下降且最近7天无充值记录的用户。这个口径用于月度重点维系。流失用户连续90天无活跃行为且未办理停机保号或携号转出。这个口径用于复盘统计不用于实时干预。这套定义核心的思考是每个定义都要对应一个运营动作。纯粹统计意义上的流失定义毫无意义只有当业务方愿意为某一个定义去执行触达的时候这个口径才值得做成模型标签。经验是定义指标时一定要问一句话“这个标签出现后你打算做什么”3.2 RFM模型在电信场景里的变体经典的用户价值分析框架RFM最近消费时间、消费频率、消费金额在电商领域耳熟能详但在电信场景下直接照搬是有问题的。电信用户的“消费”行为不一定是付费很多用户是后付费月结消费金额的波动并不能及时反映行为变化。我们做了一套变体RFE模型R最近活跃距今天数上网或通话最后一次出现的时间F近30天活跃天数包含上网天数和通话天数两个子指标E近30天等效消费额度参考ARPU值即每用户平均收入来评估贡献度。这套定义的好处是实时性更强。某个月账单金额没变不代表用户还保持着同样的使用热度但活跃天数一旦开始逐周缩水接下来离网的概率就会明显上升。在标签层我们把RFE三档打分组合成八个用户群体比如“高价值高频活跃”“低贡献低频沉默”“高价值低频沉默”等运营人员看群体名称就能知道该采取什么策略。3.3 生命周期标签的设计思路生命周期标签是我们整个标签体系里最有业务价值也是争议最大的部分。最初运营方想用“新用户、成长用户、成熟用户、衰退用户、流失用户”这五个阶段看起来很简单但新用户到底按什么定义注册后7天算新用户还是激活SIM卡后30天算新用户成长到什么指标才算成熟我们最后用的是一套组合规则入网时长、近30天活跃天数、近90天ARPU趋势、当前在网套餐档位四个维度。规则引擎先粗分模型概率再修正。比如一个用户入网不足3个月近30天活跃天数15天以上会被判定为“成长期用户”但如果模型给出的离网风险分高于阈值自动降级为“衰退预警用户”运营优先干预。这个设计背后的逻辑是生命周期不能只看画像相似度还要融入动态的行为趋势。用户在成长阶段同样可能流失——比如竞争对手的套餐更便宜。如果我们只按入网时长分类就完全看不到这种风险信号。4. 数据处理与特征工程几个会被细节拖死的坑4.1 原始日志清洗的三个顽疾数据清洗阶段我们真正体会到了电信数据的“野”。三个典型的顽疾几乎是每个做类似项目的人都会遇到的头疼事。第一个是设备关联问题。同一个用户可能因为换手机、换SIM卡在日志里出现多个不同的设备标识。有的日志用手机号作为主键有的日志只有设备ID两边很难直接关联。我们最后以“用户主档表”为准每天跑一遍主体映射把设备级行为归并到用户级。第二个是时区错乱。基站日志的时间戳有时候是设备本地时间有时候是服务器时间夏令时切换后还会混入时间偏移。如果直接用日期字段做分组特征计算就全乱了。我们的处理是统一转成UTC存储所有窗口计算以UTC为准展示层再转成本地时区。这个规则听起来基础但真的有很多项目挂在“日期少了一天”这种低级错误上。第三个是重复与缺失并存。部分来源系统存在重复上报比如一条通话记录在交换机和计费系统各记了一次按通话ID做唯一性校验才能去掉重复。另一个极端是字段缺失率高比如国际漫游场景下缺少套餐信息。我们的兜底策略是关键字段缺失的记录进入“待修复区”不参与特征计算非关键字段缺失的用上一个周期的值填充并打上“填充标记”让模型知道这个值不是真实的。4.2 特征计算的设计要点特征工程是整个系统里耗时最长的部分在我在前面的项目里这部分占到总工时的四成以上。电信用户的特征可以从几个角度划分活跃度特征近1、7、30天的上网天数、流量消耗、活跃小时段分布以及这些指标同比上一周期的变化率。语音行为特征日均呼出次数、去话时长、长途漫游通话占比、主叫被叫比例。呼出次数减少往往比总通话时长更敏感因为用户被动接听的电话可能是骚扰电话或服务通知而主动呼出更多反映社交活跃度。套餐与消费特征当前套餐档位、月功能费、近3个月ARPU、余额变化、充值间隔。充值间隔拉长是重要风险信号。服务触达特征近30天客服来电次数、投诉工单类别、账单争议记录。这些特征需要从文本工单里抽取我们做了一个简单的关键词分类器把投诉归成资费、信号、服务、携号转网意向几类。渠道偏好特征用户偏好App办理业务还是线下营业厅是否使用过自助终端近30天渠道访问次数。时间窗口方面我们统一采用滑窗机制避免重复扫描原始明细。具体做法是每天增量计算每日聚合结果然后通过一个滑动窗口的Python函数把近N天的聚合结果滚动求和而不是每次都全量扫描历史分区。def rolling_aggregate(daily_df, window_days): 基于每日聚合结果计算滑动窗口特征 daily_df: 包含 user_id, stat_date, active_days, data_volume 等字段的DataFrame window_days: 窗口天数列表如 [1, 7, 30] df daily_df.sort_values([user_id, stat_date]) for w in window_days: df[factive_days_{w}d] ( df.groupby(user_id)[active_days] .transform(lambda x: x.rolling(w, min_periods1).sum()) ) return df这段代码看起来简单但它让整个特征计算资源消耗降低了大约六成。实践证明先做日汇总再做滑窗比每次直接扫全量表快得多特别是数据量到了亿级之后差异非常明显。4.3 特征穿越一个让模型看起来“神准”的致命陷阱这里必须单独讲一个教训。项目中期我们训练了一版离网预测模型离线验证AUC高达0.92当时整个团队都有点兴奋觉得马上可以上线了。但多留了一个心眼排查特征工程的时候发现了一个致命问题样本标签的定义用了未来信息。具体地说我们的标签是“用户在未来90天内是否流失”而在离线训练集里我们不小心把用户在第60天到第90天的行为特征也放进了特征集。也就是说模型“偷看”了答案。这种特征穿越问题在离线评估里会带来虚高的绩效指标线上却绝对不会复现白白浪费很多精力。后来我们立了一条规矩特征计算截止日和标签观察日之间必须设一道不可突破的时间间隔。比如预测“未来90天流失”那么特征只取“当天之前已经发生的数据”从T0开始算第T1天到T90天发生的事一律不允许出现在特征里。这个规则写进了整个数据管道的核心检查项每次跑实验前自动校验一遍。5. 模型怎么设计从离网预警到营销响应5.1 离网风险预测模型的搭建细节离网风险预测是整个系统的主模型我们分了两步走。第一步是一个逻辑回归基线模型不为追求好效果而是为了建立一个可解释的特征影响基线第二步是LightGBM用同样的特征集做更高精度的预测两个模型并行运行逻辑回归用于输出可解释性报告LightGBM用于日常风险打分。样本定义遵循之前确定的90天口径取某一天的活跃用户池观察接下来的90天里有没有发生“销户、连续沉默超过某阈值、降档到最低保号套餐”三类事件。正负样本比例大概是1比12类别不算极度不平衡但也不适合直接用准确率评估。我们主要看AUC和KS辅助看召回率在10%阈值下的数值——因为运营资源有限一天最多只能处理排名最靠前的10%用户。关键特征里真正起作用的按重要性排序是这几个近30天活跃天数变化率、近7天充值间隔增长比、通话主叫次数下降幅度、优惠券核销历史响应率、投诉工单类别是否为资费类。LightGBM的核心参数我们调了几轮早期用比较激进的参数导致训练时间长且特征重要性不稳定最后定下来的是一个偏保守的配置params { objective: binary, metric: auc, learning_rate: 0.02, num_leaves: 31, max_depth: 6, min_child_samples: 100, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, lambda_l1: 0.1, lambda_l2: 1.0, verbosity: -1, }选择学习率0.02而不是常用的0.1是因为电信用户行为特征之间相关度较高学习率太大会让模型在特征交叉上出现过拟合。配合早停轮数100单次训练时长控制在合理范围内。5.2 营销响应模型别只做“会流失”的识别离网预警模型解决的是“用户要不要救”的问题营销响应模型解决的是“用户救了有没有用”的问题。运营资源是有限的把高价值用户从流失边缘捞回来和对一个本就不想走的用户多发几条短信成本结构完全不同。营销响应模型的训练标签定义为在推送特定权益优惠券、免费流量包后7日内是否产生了业务行为领取、使用、套餐变更。这个模型覆盖的用户池是离网预警模型打分排名前20%的用户特征里除了基本行为标签还加入了“历史营销响应率”和“最近一次营销触达渠道”两个特征。这两类模型组合起来的使用逻辑是离网分高但响应分低→放弃营销做服务关怀比如打电话回访离网分高且响应分高→优先推送权益离网分低但响应分高→日常维系不打搅。实际跑下来运营推荐任务的成功转化率比之前“按ARPU值降序群发”的方式高了两倍。5.3 模型监控与再训练机制模型上线不是终点。我们观察到的最明显的问题是随着时间推移模型预测的分布会出现偏移。夏天旅游旺季漫游通话特征整体上升年底促销期套餐变更行为大幅增加。这些变化会导致同一个用户在不同季节拿到完全不同的风险分。所以我们在服务层加了一个“分布漂移监测”的环节每天统计线上风险分的分布与训练集的PSI群体稳定性指数当PSI连续7天超过0.25时自动告警并触发一次增量训练。增量训练不改变基础特征逻辑只用最近90天的数据重跑模型然后用A/B对比的方式验证新版模型是否真的优于旧版。实测下来这个机制让模型在季节切换期没有出现明显的效果滑坡。6. 系统落地怎么让运营真正用起来6.1 服务层与接口设计后台跑出的标签和风险分最终要通过服务层变成运营手里可用的东西。我们用一个Python的FastAPI服务暴露了两类接口单用户画像查询接口和批量的风险名单接口。单用户接口主要提供给客服坐席使用。客服接到用户来电时系统在3秒内返回这个用户的标签摘要是否属于高价值用户、近30天活跃度变化、风险等级、最近一次投诉内容这样客服在对话前心里有个数。批量接口则供运营每天早上下载“今日待干预用户清单”清单里按风险分排序并附带每个用户建议的触达策略。批量接口设计之初遇到的一个问题是性能。风险分Top10%的用户也有几万人每个用户要关联标签宽表里的几百个字段如果纯靠实时查MySQL会非常慢。我们的方案是把Top榜用户的风险分和关键标签在每天凌晨离线计算结果同步到Redis里批量接口读Redis即可单用户查询才回源MySQL。这样把接口响应时间从秒级压到了百毫秒级。6.2 可视化看板少堆图表多给动作可视化看板是运营每天打开最频繁的界面做一个“好看但没用”的大屏是最容易的事情。我们的原则是看板上的每一个模块必须对应一个运营动作。看板的首页只有三个区域今日风险用户总数及变化趋势、高风险用户Top榜点击可进入用户详情页、昨日干预执行率和有效触达率。运营主管每天早上打开看板只需三分钟就能判断当天的工作重点。第二页才是标签分布和用户群画像分析供策略人员做月度复盘使用。初期有一版看板放了十几个图表包括各种流量趋势、ARPU分布、语音时长堆叠图结果运营反馈说“打开不知道要看哪”。后来我们砍掉了一多半图表只留下可执行的信息使用率反而上去了。这是一条值得写进项目总结的教训给业务方做的系统与其信息过载不如直击要害。6.3 干预闭环从名单到触达到效果回收系统最终的效果取决于干预闭环能不能转起来。我们和运营方一起把流程定成了五步每日早8点看板出名单→运营班组长分发触达任务→客服或外呼系统执行触达→用户在CRM系统里记录反馈→次日系统自动核对“触达后是否提升活跃度”。提示 触达不等于短信群发。初期运营图省事对所有高风险用户统一发“5元优惠券”结果响应率很快掉到5%以下。后来我们按用户群拆了策略——资费敏感型用户送话费补贴流量偏好型用户送夜间流量包服务投诉型用户优先安排人工回访响应率回升到15%以上。这背后的道理不复杂行为分析系统给出的标签要对应差异化的运营策略同一个模板打天下再准的模型也白搭。7. 常见问题与排查真实项目里最费时间的那些事7.1 数据质量问题的速查表做数据类项目大部分时间消耗在数据问题上。我们整理过一张速查表后来成了团队内部处理数据问题的标准动作这里分享出来现象可能原因排查方向用户量突然减少上游主题表切换、新的过滤条件引入对比前后两天的源表记录数检查上游任务是否变更特征缺失率突增某类日志上报中断、字段解析失败查消息队列积压情况确认解析脚本是否有异常分支时间字段整体偏移时区配置变更、服务器时间异常核对UTC转换逻辑用已知用户验证关键时间点同一用户多条主档记录号码携转、设备更换导致身份映射混乱检查主体映射表的合并规则是否缺少最近更新时间戳标签覆盖率长期偏低特征计算窗口过窄、冷启动用户占比过高针对新用户建立特殊的短窗口特征替代策略每条问题的共同排查原则是“先定位到数据链路的具体环节再修改不要直接改结果”。我们有过几次直接改结果表的操作短期看起来数字对了但下一次计算又被覆盖回错误值反而是白忙一场。7.2 模型效果不达标时的排查清单当模型评估指标达不到预期或者线上效果和离线效果差异很大我建议按这个顺序排查先查标签泄漏。这是最常见也最隐蔽的问题检查特征截止日和标签观察日的时间隔离是否严格到位。再查训练集与线上数据分布差异。如果训练数据来自促销季线上是平淡期模型效果自然会退化。查特征时效性。有些特征采用月度批量更新但用户行为已经日新月异模型的输入信息滞后了。查样本定义是否稳定。不同批次打标签的人可能用了不同的口径尤其是“高价值用户”这种主观性较强的定义。查评估方式本身。在类别不平衡问题下准确率没有参考意义必须看AUC、KS和特定召回率下的精确率。7.3 让运营团队主动用起来的经验最后聊一个非技术问题。很多数据项目失败不是模型烂、也不是代码差而是业务团队压根不碰。我们的经验是在系统设计阶段就拉运营进来看模型输出的样本而不是等系统做完了再让他们验收。项目中期我们每个周五都会给运营团队发一份“本周风险用户抽查报告”里面随机挑10个被模型标记为高风险的用户附上他们的行为特征摘要和风险原因请运营同事凭经验判断“这个用户会不会流失”。这样做了三个星期后运营主管主动跟我们说“你们的标签基本靠谱可以上名单了。”这种信任磨合的过程比重做十个好看的界面都有用。最后一点推进建议如果让我重来一次我最想提前做的不是优化模型而是更早地统一口径、更早地和运营团队建立联合评审机制。技术上踩过的坑都有解法但业务侧“不认这个数、不用这个系统”的问题靠一个人埋头是解不开的。这个系统目前的版本已经稳定运行后续我们把精力放在了两个地方一是把文本类的客服工单做得更深尝试做一些语义级别的投诉原因归类二是把营销响应模型做成实时版本在用户进App的瞬间就能给出当前最合适的推荐动作。数据类项目的迭代永远做不完但只要底层的数据管道干净、标签口径稳定、模型监控闭环建好后续功能扩展就是水到渠成的事。