☰
客户流失预测实战:从特征工程到模型落地的完整流程
2026/10/10 10:18:16 网站建设 项目流程

接到一个金融App的客户流失预测需求时,业务方给我的第一句话是:"你能不能提前一个月告诉我哪些用户要跑?"这句话听着简单,做起来却是一整套从数据清洗、特征工程、可视化探索到模型训练和业务落地的实战流程。客户流失行为分析预测,本质就是把人"快要离开"的信号变成可量化的字段,用python、jupyter notebook、机器学习、数据可视化、数据分析这套经典技术栈,从用户历史行为中找出流失前的共性轨迹,再预测未来一段时间内哪些用户大概率流失。这篇文章就把我实际做过的完整流程拆开讲,从最容易被忽略的业务定义开始,到最终怎么把预测结果变成运营动作,没有省略关键坑。适合正在做数据分析与机器学习项目的朋友参考,也适合准备做客户流失方向课题的同学当复现模板。

1. 先搞清楚:金融科技里的"流失"到底怎么定义

1.1 流失的业务场景和判断标准

做客户流失分析,最容易犯的第一个错误就是一上来就开代码。我见过不少同学拿到一份"客户数据集",里面连流失列都有了,于是直接开始训练模型。但现实项目里,很少会有现成的"是否流失"标签躺在Excel里。金融科技场景下,用户流失的表现形态其实非常多样:

  • 一个做小额借贷的App,用户连续90天没有借款记录,但还在每天签到看看额度,算不算流失?
  • 一个理财平台,用户把资金全部转出但还在登录看收益,算不算流失?
  • 一张信用卡,持卡人6个月没刷卡,但账单还在正常还款,算流失吗?

不同业务线的答案完全不同。我当时的做法是拉着产品经理和数据运营开了半小时会,把"流失"明确成三档:高流失风险 = 连续30天无登录且无交易;中流失风险 = 15到30天内登录频次下降超过50%且余额出现转出;低流失风险 = 只登录不交易超过14天。然后根据项目目标,优先建模"高流失风险"这一档。

这一步为什么重要?因为Y标签的定义直接决定了后面所有模型的结果。定义定偏了,模型再漂亮,给业务的判断也是错的。比如在借贷场景,单纯"不再借款"不一定是流失,可能是客户负债率下降、需求暂时消失;但如果平台上还有理财、支付等其他业态,而这个客户的交易习惯转移到了别处,那就要认真对待了。

1.2 流失前的渐变信号:为什么这件事"可预测"

有人对机器学习预测流失有天然怀疑:流失不是一个瞬间决定吗?怎么能提前预测?实际上,我拆过上千条流失用户的轨迹后得出的结论是:绝大多数流失不是突然发生的,而是一个渐进过程。这些渐变的信号就是模型学习和捕捉的核心。

一个典型的理财用户流失轨迹可能是这样的:注册后前三个月频繁登录,每周都会打开收益页面看两眼;第四个月登录频次从每周7次降到每周2次;第五个月资金从5万逐步降到1.2万;第六个月彻底不再登录。整个过程充满可供建模的量化信号——登录频率下降、浏览时长缩短、持仓金额缩水、客服投诉次数上升,这些都是常见且有效的特征。金融科技和电商、内容产品最大的不同,就是数据颗粒度极细,几乎每个用户动作都会被后端系统记录下来,这决定了用机器学习做流失预测是完全可行、而且真的能落地的事。

1.3 为什么选python + jupyter notebook这套组合

定完业务定义,接着就是技术选型。这个项目我把技术栈锁定在python + jupyter notebook + pandas/sklearn/lightgbm + matplotlib/seaborn,没有为了炫技上更复杂的架构。原因主要有三个。

第一,数据探索阶段大量时间花在"看一眼数据长什么样"上。Jupyter Notebook的单元格交互模式特别适合这种边看边想的节奏:看一个维度的分布,写一段结论,再画一张图,改个参数重新跑,整个过程非常顺滑,而不是像流水线脚本那样从头跑到底。

第二,客户流失项目通常需要产出分析报告。Notebook天然把代码、可视化图表、文字结论放在同一个文档里,直接导出就是一份带完整推理过程的说明文档,这比后期截图拼Word高效得多。项目做完回头看,这份记录本身的价值甚至不低于模型。

第三,机器学习的生态基本都在Python这边,sklearn提供从数据切分、预处理、建模到评估的完整链路,lightgbm补上树模型的性能短板,可视化用seaborn和matplotlib完全够用,不用额外引入重型平台。

有人会问,为什么不直接用Pandas Profiling这类自动化EDA工具?我的经验是,自动化报告能帮你快速发现缺失值和分布情况,但流失项目的核心是"对业务信号的解释",这一步必须人来判断,自动化工具替代不了。所以我会把它们当辅助,主体分析还是自己一步一步来。

2. 从原始数据到训练集:特征工程里的关键动作

2.1 原始数据长什么样

金融科技公司的底层数据一般分布在好几张表里:用户信息表、登录日志表、交易流水表、资产快照表、客服工单表。建模之前需要先把它们拉出来拼在一起。拿我当时接触的消费金融App举例,核心字段大致如下:

类型字段示例说明
用户基础信息user_id, age, 注册渠道, 注册天数注册渠道是渠道质量的重要代理变量
登录行为近7/30天登录次数, 最近登录距今天数活跃度衰减是流失最直接的信号
交易行为近30天交易笔数、金额,平均单笔金额交易频次和金额变化反映使用深度
资产情况当前余额, 持仓金额, 近30天转入转出总额资产转出是强预警信号
客服交互近90天工单数, 投诉次数交互体验变差往往预兆离开

数据量不用太大,我当时清洗后保留了几十万行级别,对训练模型来说已经足够。关键是要把每个字段的来源问清楚,尤其分清哪些是"注册以来的累计值",哪些是"某个时间窗口内的值",这两种口径混在一起就是灾难。

2.2 Y标签怎么构造

没有现成标签时,需要自己用业务规则打标签。在Python里写起来非常简单,但要牢牢记住"观察期+表现期"的框架:以T日为基准,取过去90天的特征,看未来30天内是否发生"流失"事件。示例代码如下:

import pandas as pd import numpy as np # 假设 df 包含每个用户截止 T 日的 last_login_date 和 last_txn_date ref_date = pd.Timestamp('2024-06-30') df['silent_days'] = (ref_date - df['last_login_date']).dt.days df['no_txn_days'] = (ref_date - df['last_txn_date']).dt.days # 流失定义:连续30天无登录 且 连续30天无交易 df['churn'] = ((df['silent_days'] >= 30) & (df['no_txn_days'] >= 30)).astype(int) print(df['churn'].value_counts(normalize=True))

注意观察期和表现期必须严格错开,不能把表现期的事件拿到特征里去,否则就会产生标签泄漏,这是后期做验证时最容易被业务方质疑的点。

2.3 特征工程:把原始字段变成模型能用的信号

客户流失的特征工程,核心思路就是把行为日志压缩成时间窗口内的数字特征。我的经验法则是:一切能反映"活跃度下降""资产规模变化""使用深度变化"的字段都值得造。常用做法有:

  • 时间窗口聚合:近7天、14天、30天、90天的登录次数、交易笔数、交易金额、提现次数等,用pandas的groupby加agg一次算出来。窗口要覆盖短、中、长三个尺度,这样模型既能捕捉短期的突变信号,比如突然一周不用了,也能看出长期的衰减趋势,比如连续三个月使用频次稳步下滑。
  • 比值和变化率:近30天登录次数除以近90天登录次数,形成"活跃度衰减比";近7天交易金额除以近30天交易金额,形成"金额衰减幅度"。这类比率特征比绝对值更稳定,对高价值用户和普通用户都有解释力。
  • 状态类特征:当前是否有未结清订单、是否开通自动还款、是否绑定银行卡、是不是通过某个特定渠道注册。这些强分类变量在树模型里往往贡献很高。

聚合逻辑的示例:

# 登录日志聚合出窗口特征 login_features = ( login_log.groupby('user_id')['login_time'] .agg( login_cnt_7d='count', login_cnt_30d='count', last_login_gap=lambda x: (ref_date - x.max()).days ) .reset_index() )

每次做完聚合,一定要检查结果的行数是否和用户表对齐、缺失值比例是否异常。聚合时一个很常见的坑是日志表本身的时间范围不对,比如只导出了一个月的日志,导致"近90天登录次数"被严重低估,这种问题往往要等画特征分布图时才暴露出来。

2.4 两次让我印象深刻的踩坑

第一个坑是时间窗口的口径不统一。我把登录频次和交易金额放进同一张训练表里,后来才意识到登录日志表是从流量平台导出的,时间精确到天,还丢了一些低活跃用户的记录;而交易流水表来自核心系统,精确到秒。两张表对"近30天"的统计口径差了小半天,直接导致部分用户的登录特征值明显偏低。检查方式其实很简单:分别看两个表里"近30天有记录的日期数"分布,一眼就能看出异常。后来我全部统一用"注册日期+相对天数"来对齐时间窗口,问题才彻底解决。

第二个坑是特征泄漏。做特征重要性分析时,模型给出的最高贡献特征居然是一个"用户是否已注销"的状态字段——答案本身就混进特征里了。那当然预测得准,但没有任何实际意义。当时发现这个问题靠的是直觉:为什么状态字段的贡献比活跃度高这么多?一查上游数据链路,果然是某张状态表被提前关联了。从那以后,我会给每个字段标注"数据可获取时点",凡是特征形成时间晚于预测时间点的,一律排除。

3. EDA和可视化:模型跑之前,先用眼睛找信号

3.1 Jupyter Notebook里做探索分析的实际体验

建好特征表,我习惯先不做任何建模,而是用可视化把数据集摸一遍。这个阶段,Jupyter Notebook的优势体现得特别明显:每次跑一个单元格,图表立刻渲染在代码下方,我可以随手用Markdown记下一句结论,下次审阅时还能想起当时的推理链路。整套分析文档导出去,就是一份带完整思路的"万字报告"基础。

探索性分析的流程通常分三步:先看整体数据质量和分布,再看核心特征和流失标签的关系,最后做特征间的相关性排查。我的建议是不要在第一步花太多时间事无巨细地看图,先挑10个和业务最相关的特征做深度查看,其余用摘要统计批量过一遍就行。

3.2 三张必看的图

第一张是流失用户与非流失用户的注册天数分布对比。用KDE分布图叠加的方式,能快速判断"是新手期用户容易流失,还是老用户也容易流失"。这对后续策略影响很大,因为如果是新手期流失,重心就放在首月体验;如果是老用户流失,那更多要考虑竞品分流和产品老化问题。

import seaborn as sns import matplotlib.pyplot as plt sns.kdeplot(data=df[df['churn'] == 1], x='tenure_days', label='churn', fill=True, alpha=0.4) sns.kdeplot(data=df[df['churn'] == 0], x='tenure_days', label='not_churn', fill=True, alpha=0.4) plt.xlim(0, 2000) plt.legend() plt.title('注册天数分布:流失 vs 未流失') plt.show()

我见过的大多数金融场景都会有典型的双峰现象:注册前几周流失率居高不下,熬过某个黏性节点之后流失率才明显下降。这说明首月激活体验是留存的第一道生命线。

第二张是不同注册渠道的流失率柱状图。渠道质量不均是个普遍现象,有的渠道用户群天然精准,流失率低;有的是投放拉新带来的羊毛党,装完App领完福利就走。这张图直接指导投放策略怎么调,也能让模型学到"注册渠道"这个强分类特征。

第三张是近30天登录次数和交易金额的散点图,按是否流失着色。流失用户通常会明显聚集在左下角——低频、低额。这张图最大的启发是:活跃度下降和资产撤走高度相关,所以在特征工程里,"活跃度衰减比"和"资产净流出"这两类特征要优先保留。

3.3 缺失值和异常值的可视化排查

金融数据往往存在大量缺失值,比如用户没绑卡、没持仓,对应字段自然为空。缺失值不能简单删除,要先区分是"真实缺失"还是"数据采集问题"。我会画一张缺失值比例条形图,然后按比例分类处理:

  • 缺失率超过80%的字段直接剔除;
  • 缺失率在30%到80%之间的字段,看是否属于"用户未开通某服务"。如果属于,就保留并补充一个分类标记;如果属于采集异常,则要做数据修复;
  • 缺失率低于30%的字段,常用中位数或众数填充,树模型也可以直接保留缺失让模型自己学习。

异常值方面,交易金额最容易被几个大额用户拉出一条长长的尾巴,箱线图基本看不出有效分布。处理原则不是删用户,而是做对数变换,或者用分位数裁剪。我个人偏好对数变换,因为既能保留趋势信息,又不至于让一两个大客户把整体特征尺度带偏。

4. 建模全流程:从逻辑回归到LightGBM的实战选择

4.1 先把数据切分做对

客户行为数据的切分与普通比赛不一样,不能直接随机打乱。用户流失行为会随时间变化,比如某个月产品版本更新、补贴政策调整,都会让数据分布漂移。严谨做法是按时间切分:取前70%时间段的用户特征做训练,后30%时间段做验证。即便只有横截面数据,也至少要保证同一用户的特征和标签不跨到训练集和测试集两侧。

from sklearn.model_selection import train_test_split # 建议按时间切分,这里用 register_month 字段做示例 train = df[df['register_month'] < 12] test = df[df['register_month'] >= 12] X_train, y_train = train.drop('churn', axis=1), train['churn'] X_test, y_test = test.drop('churn', axis=1), test['churn']

如果样本量很小,可以再用分层抽样,保证训练集和测试集里的流失占比一致。但要注意,时间切分的优先级永远高于随机切分,因为模型的真正价值是预测未来。

4.2 逻辑回归:先做基线,而且不要小看它

流失预测项目我习惯先跑逻辑回归,不只是因为快,更重要的是逻辑回归的系数天然可解释。对业务方来说,"注册天数每增加100天,流失风险下降3%"这种表达,比一棵黑箱树更容易建立信任。逻辑回归还能帮忙筛特征:和流失标签相关系数低、又和其他特征高度共线的,可以先从候选集里剔除。

from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) lr = LogisticRegression(max_iter=1000, class_weight='balanced') lr.fit(X_train_scaled, y_train)

注意这里设置了class_weight='balanced'。在流失占比只有10%到15%的数据里,这个参数能让模型不过度偏向多数类,对少数类样本的记忆能力会好很多。

4.3 树模型和集成模型:LightGBM是默认选择

逻辑回归打好基线之后,真正的主力模型我一般直接上LightGBM。原因很实际:特征里既有连续变量又有大量分类变量,LightGBM对缺失值有原生处理机制,不需要提前填充;训练速度快;调参相对简单,配合早停基本不会出现严重过拟合。如果安装环境没有LightGBM,退而求其次用sklearn的GradientBoostingClassifier也能跑,只是训练时间会明显变长。

import lightgbm as lgb from sklearn.metrics import roc_auc_score model = lgb.LGBMClassifier( n_estimators=300, learning_rate=0.05, num_leaves=31, max_depth=-1, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model.fit( X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='auc', callbacks=[lgb.early_stopping(stopping_rounds=50)] ) y_pred = model.predict_proba(X_test)[:, 1] print('AUC:', roc_auc_score(y_test, y_pred))

这里有一个关键细节:early_stopping要用独立验证集。如果直接在测试集上做早停,AUC会被高估,因为你无形中在用测试集信息选择迭代轮数。更稳妥的做法是从训练集里再切一小块出来专门做早停,测试集从头到尾只碰一次。

4.4 类别不平衡:别急着上SMOTE

流失数据天然不平衡,常见流失率在8%到20%之间。处理方式我按优先级排列:

  1. 修改评估指标,不用准确率,改用AUC、召回率、TopK命中率等更贴合流失场景的指标;
  2. 在模型里加class_weight或者调大正样本权重,成本极低,效果往往很明显;
  3. 如果前两步还不够,再考虑SMOTE等过采样方法。

我的经验是,很多场景到第二步就够用了,SMOTE并不必是必选项。SMOTE在高维稀疏特征上容易生成不符合真实业务逻辑的样本,调起来反而费时间。真正的关键还是和业务对齐目标:宁可多召回一些高风险用户,也不要漏掉高价值用户,这个取舍比单纯追求指标更重要。

5. 模型评估别只看准确率:AUC、KS和阈值的业务打开方式

5.1 准确率是流失预测的陷阱

流失率15%的数据里,一个"全部预测不流失"的瞎猜模型,准确率也有85%。所以准确率在流失预测里没有任何意义,我基本不看。平时最关注两个维度:模型区分度指标,包括AUC和KS;业务决策指标,包括混淆矩阵和TopK召回率。

AUC衡量的是模型把流失用户排在非流失用户前面的概率。0.5是瞎猜水平,0.8以上基本可用,0.85以上就算表现很好了。KS是累计正负样本分布之间的最大差值,在金融风控领域常用,用在流失预测场景一样有效,一般超过0.3就说明有不错的区分能力。

5.2 混淆矩阵和阈值选择

模型输出的概率本身不是决策,决策需要设定一个阈值。阈值设得低,召回率高,误伤多;阈值设得高,准确率高,但会漏掉大量潜在流失用户。我一般会把"阈值-召回率-误伤率"曲线画出来,再结合用户价值分档来选阈值。举个例子:高价值用户的流失损失可能是普通用户的10倍,所以对高价值用户用低阈值,召回优先;对长尾用户用高阈值,减少不必要打扰。

from sklearn.metrics import precision_recall_curve precision, recall, thresholds = precision_recall_curve(y_test, y_pred) for thr in [0.3, 0.5, 0.7]: idx = (thresholds >= thr).sum() print(f'thr={thr}, precision={precision[idx]:.3f}, recall={recall[idx]:.3f}')

在业务交付环节,我会同时给出三档名单:高风险名单、中风险名单、观察名单,分别对应不同的运营动作,而不是扔给业务一个"流失概率预测大于0.5"的笼统结论。

5.3 特征重要性和SHAP可解释性

模型好用还不够,业务方一定会追问:"你说这个用户要流失,为什么?"这个问题的答案不能只是"模型算出来的"。我推荐用SHAP来拆解单样本预测。SHAP能给出每个特征对某个具体用户预测结果的正负贡献,比如"近30天登录次数减少,贡献了+0.12的流失概率"、"月交易金额下降,贡献了+0.08的流失概率",这样业务人员不仅知道谁要走,还知道用户到底是因为什么开始冷淡了,后续挽留话术才有方向。

同时也不能忽略全局特征重要性排序。我跑过的流失模型里,排在前面的特征通常集中在:近30天登录次数、最近登录距今天数、近30天交易金额、余额变化幅度、客服工单数这几类。如果发现某个奇怪字段,比如用户ID编号,冲到了重要性榜首,基本可以断定是数据泄漏,要回头查特征拼接逻辑。

6. 预测结果落地:从得分到运营动作

6.1 把用户分层,而不是抛出一堆概率

模型预测完,我最终交给业务方的不是一张概率表单,而是一份"流失风险名单"。名单按概率降序排列,再叠加用户当前资产量级和近90天交易频率做价值分层,形成四象限:

  • 高价值-高流失风险:最高优先级,专属客服电话、定向优惠券、产品体验官邀请,所有动作的核心是快速重建用户使用习惯;
  • 高价值-低流失风险:维持现有权益即可,防止过度营销引起反感;
  • 低价值-高流失风险:不投入高成本人工干预,最多发一条App推送,或用自动化触达策略;
  • 低价值-低流失风险:基本不处理,保持系统自动运营。

这种做法最大的好处是让业务方的资源投放到刀刃上。流失概率再高,如果没有价值,也不值得用高成本的短信和人工客服去挽留。

6.2 用A/B实验验证效果

预测模型效果的真伪,最终要靠业务实验说话。把高流失风险用户随机分成实验组和对照组,实验组施加挽留策略,对照组维持原有运营动作,然后观察未来60天两组实际流失率是否有显著差异。

这个环节有两点容易出问题:一是实验设计阶段就要定好观测口径和样本量,否则两组天然有差异,对比结果没有说服力;二是挽留动作本身要给足时间,如果只观察两周就说策略无效,往往是因为策略的挽留效果还没有完全显现,就提前下结论了。

6.3 Notebook在整个项目里的角色

整套项目走下来,Jupyter Notebook始终是贯穿全程的载体。它不只是代码编辑器,更是分析过程的完整记录。从最初整理的业务假设,到特征工程每一步的处理逻辑,再到模型评估表、特征重要性图、阈值选择曲线,全部沉淀在一个文档里。项目交付时,把Notebook导出成带目录结构的网页版报告,附上关键图表和结论,业务方和技术同学都能在同一份文档里找到自己要的信息。

这也是我给所有做数据分析实践的人的建议:过程和结论都要完整记录,而不是只把模型文件丢给对方。分析过程中那些"为什么排除这个字段""为什么要在这个位置切分样本"的记录,往往比最终的预测结果更有长期参考价值。一个模型三个月后可能被替代,但一份完整思路的分析文档,能让下一个接手的人少走大量弯路。

最后分享一点实际体会:客户流失预测成功的标志,从来不是AUC冲到0.9,而是业务人员真的拿着名单去拨了电话、发了权益包,并且在下个季度看到流失率有了肉眼可见的下降。机器学习在这件事里占的权重可能只有四成,剩下六成来自数据质量和业务理解的持续交互。如果让我给刚开始做同类项目的人提一条最实在的建议,那就是开工前一定花够时间把"流失"这两个字和业务方掰扯清楚,并且把每一次探索和思考都留在Notebook里——这套沉淀下来的东西,会比模型权重更值钱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询