ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

KKBox用户流失预测实战:Python建模源码全解析

KKBox用户流失预测实战:Python建模源码全解析 简介这套源码基于KKBox音乐网站真实业务场景面向数据分析初学者、机器学习实践者及音乐平台运营人员用Python完成用户流失预测全流程开发为制定用户留存策略提供数据支撑。压缩包共47个文件、5.18MB以33个IPython Notebook为主系统记录了数据探索、特征工程、模型训练与评估等关键环节2个Python脚本如xgb.py、data_helpers.py封装了数据处理与XGBoost建模逻辑另有Markdown项目总结、readme说明及Git管理文件便于理解项目结构与复现实验。目前已有276人学习下载适合希望掌握分类预测实战、对比多种算法效果的学习者。资源内含逻辑回归、LightGBM、XGBoost等多套建模方案覆盖缺失值处理、异常值检测、特征筛选、模型调参与结果对比等完整流程通过研读这些笔记可快速上手KKBox流失预测题目并迁移到其他用户行为分析场景是一份兼具教学与工程参考价值的完整设计源码。1. 用户流失预测KKBox音乐数据集上的Python建模源码拆解用户流失预测是音乐流媒体平台最头疼的问题之一。KKBox公开过一套包含会员注册、交易流水和听歌行为的真实数据集适合练手实际场景下的二分类建模。这份Python设计源码包同时给出XGBoost、LightGBM和逻辑回归三套方案外加33个Notebook文件覆盖从EDA、特征工程到预测输出的完整流程。包的结构很典型多个工作目录按贡献者划分每套代码独立探索、独立建模最后集中到模型对比与预测环节接近真实团队协作形态。适合正在学机器学习、想找一份能直接跑通的数据分析与建模项目的人也适合想了解音乐网站流失预测数据形态、准备自己复现的从业者。下面从文件结构入手把建模主线理清楚。2. 文件结构拆解33个Notebook与三条建模主线解压后先别急着开Notebook用一条命令把文件结构摸清楚这一步能省掉后面大量找文件的功夫。# 解压后先按目录梳理文件理清三条建模主线 unzip upload.zip -d kkbox_churn cd kkbox_churn find . -name *.ipynb | sortunzip解压到指定目录find只列出Notebook并按路径排序一眼就能看出每个人负责的方案在哪个目录、文件链路的先后顺序。命名再乱排序之后结构也能看清。2.1 三套方案并存选型理由与文件归属从文件列表能明显看到三套并列的建模方案。XGBoost目录下是xgb.py和data_helpers.py两个Python源码文件配合EDAFE目录的1_EDA_FE_KKBox.ipynbLightGBM目录下是music_member_category_1.ipynb、model_compare_lgb_rate.ipynb、test_predict.ipynb、model_compare_lgb_category.ipynbLR目录和LGBM目录里则是按数字编号的完整流程Notebook。命名里带着成员名李伟、金策、张峰诚、陈霞飞说明这是典型的多分支开发结构每个人维护自己的一条技术路线最后在模型对比环节合流。三套方案的选择逻辑很清晰逻辑回归打基线XGBoost和LightGBM做梯度提升树主力。KKBox这个数据集有百万行会员级数据特征里大量是类别变量和时间窗口统计量树模型能自动处理类别特征的非线性关系逻辑回归则胜在可解释性和训练速度。实际项目中我一般会先跑LR拿一个AUC基线再上LightGBM看能提升多少最后用模型对比Notebook记录差异。方案关键文件定位逻辑回归6_LR_KKBox_Music_Train.ipynb、7_LR_KKBox_Music_Test.ipynb基线模型可解释性强XGBoostxgb.py、data_helpers.py梯度提升树主力特征重要性可解释LightGBMmodel_compare_lgb_rate.ipynb、model_compare_lgb_category.ipynb大样本下训练快适合迭代2.2 Notebook编号链路从预处理到预测输出张峰诚那份LR目录的编号最有参考价值0_Pre是预检查1_FE给songs和members做特征提取2_Create_Index建立索引3和4分别对训练集测试集做EDAFE5合并数据6训练7测试预测。这一串数字本身就是流水线的执行顺序。陈霞飞那份则是train_Logist、trian_propressing、lightGBM_music_train、light_gbm_test这种命名职责清楚但拼写不统一复现时得靠文件名语义去猜。KKBox挑战赛的原始数据分为members、transactions、user_logs等几张表。members是会员画像transactions是交易流水每次续费、升级套餐都算一条记录user_logs是用户每天听歌行为的聚合日志。预测目标通常是会员在最后一次交易后的30天内是否取消服务。这三张表要分别做特征工程再按msno关联成宽表。编号链路里的1_FE、2_Create_Index、5_Merge_Data对应的就是这个过程任何一个环节漏跑后面的模型训练都会报错。2.3 辅助文件的价值.gitignore、readme与Markdown笔记项目里5个.gitignore分布在各个子目录说明最初每个工作目录都是独立管理的后来才合并到一起。对复现者来说这几个文件帮助不大但能反映仓库的演化过程。readme.txt和project_summary.md、kkbox-music-recommendation-challenge.md是文档部分陈霞飞那份project_summary.md记录了项目总结建议先读它再进Notebook能少走弯路。一个明显的缺口是仓库里没有requirements.txt或environment.yml。xgb.py、data_helpers.py用到了sklearn、xgboost、lightgbm、pandas、numpy这些库依赖版本不一致会直接导致报错或结果漂移。复现时第一件事就是自己补一份依赖清单把版本锁住。提示仓库没锁定依赖版本复现前先自己补requirements.txt否则后面的坑大概率出在这里。3. EDA与特征工程会员、交易与听歌日志的三维处理3.1 数据探索的核心维度会员画像、交易流水与听歌行为EDA阶段主要看三块。members表关注注册时间、注册渠道、城市、性别、年龄的分布流失用户和留存用户的年龄分布往往有差异注册渠道也能反映用户质量。transactions表关注支付方式、套餐类型、实际支付金额、自动续费标志尤其是is_auto_renew这个字段开启自动续费的会员流失率明显更低。user_logs表是核心行为数据记录了每天听歌次数、去重歌曲数、总时长是判断活跃度的直接依据。一个常用做法是先做分群对比把流失和留存两个群体在各维度的分布叠在一起看。比如用pandas的groupby算不同渠道下的流失率再看样本量是否足够支撑结论import pandas as pd # 假设train已合并好会员与标签 train pd.read_csv(train.csv) members pd.read_csv(members.csv) df train.merge(members, onmsno, howleft) # 按注册渠道看流失率 churn_rate_by_channel df.groupby(registered_via)[is_churn].mean() print(churn_rate_by_channel.sort_values(ascendingFalse)) # 按城市看样本量与流失率 city_stats df.groupby(city).agg( member_count(msno, count), churn_rate(is_churn, mean) ) print(city_stats)这段代码把训练标签和会员画像按msno关联然后分别按注册渠道、城市统计流失率。groupby的agg里写(is_churn, mean)表示对标签列取均值因为is_churn是0/1变量均值就是该分组的流失比例。这一步能快速暴露哪些渠道的用户质量差为后续特征筛选提供方向。注意city这种类别字段不适合直接进线性模型EDA阶段先观察特征阶段再决定编码方式。3.2 特征工程实践时间窗口统计与活跃度指标KKBox数据特征工程的核心是时间窗口。原始user_logs是每天一行直接merge进宽表会让行数爆炸所以要先按msno聚合把每天的听歌行为汇总成一段时间的统计量。常见做法是分成近7天、近30天、全量三个窗口分别计算听歌总时长、去重歌曲数、活跃天数、平均每天听歌次数。import pandas as pd # user_logs: msno, date, num_25, num_50, num_75, num_985, num_100, num_unq, total_secs logs pd.read_csv(user_logs.csv) logs[date] pd.to_datetime(logs[date], format%Y%m%d) # 取每个会员最晚一条日志日期作为参考点 latest_date logs.groupby(msno)[date].transform(max) logs[days_diff] (latest_date - logs[date]).dt.days # 近30天窗口的聚合特征 window_30 logs[logs[days_diff] 30].groupby(msno).agg( play_days_30(date, nunique), total_secs_30(total_secs, sum), unique_songs_30(num_unq, sum), avg_daily_plays_30(num_100, mean) ).reset_index()这里有个容易忽略的细节date列在原始CSV里是字符串格式必须显式转成datetime再算差值否则减出来是object类型直接报错。days_diff用transform(max)给每一行都附上该会员的最新日期布尔索引筛出近30天的记录。窗口聚合之后还可以继续做衍生特征比如近30天听歌时长占全量时长的比例这个比例能反映用户是否在快速降温是流失预测里区分度很高的特征。3.3 训练集与测试集的特征一致性处理多人协作的仓库里最容易翻车的就是训练集和测试集特征不一致。字段名相同不代表口径相同。测试集没有标签是正常的但members、transactions、user_logs的结构必须和训练时完全一致。源码里张峰诚用3、4两个Notebook分别对训练集测试集做EDAFE陈霞飞那边有trian_propressing和test_proprecessing两个文件都是在解决这个问题。我的习惯是把特征工程抽成函数训练集和测试集走同一份代码def build_log_features(logs): 训练集和测试集共用的日志特征函数不依赖标签列 logs logs.copy() logs[date] pd.to_datetime(logs[date], format%Y%m%d) latest logs.groupby(msno)[date].transform(max) logs[days_diff] (latest - logs[date]).dt.days # 近30天窗口 window logs[logs[days_diff] 30].groupby(msno).agg( play_days_30(date, nunique), total_secs_30(total_secs, sum), ).reset_index() # 全量窗口 all_time logs.groupby(msno).agg( play_days_all(date, nunique), total_secs_all(total_secs, sum), ).reset_index() feat window.merge(all_time, onmsno, howleft) feat[secs_ratio_30] feat[total_secs_30] / feat[total_secs_all] return feat这个函数不引用标签列train和test都能直接调用。secs_ratio_30是近30天时长占比数值越低说明用户冷却越快对流失预测很有用。两个窗口的列名后缀要区分清楚否则merge的时候会产生重复列后面对齐特征时又是一轮排查。测试集没有is_churn这一列特征函数里不出现标签列正是为了这个边界。4. 模型训练与评估LR、XGBoost、LightGBM的对比实验4.1 逻辑回归基线可解释性优先的起步方案逻辑回归在这个项目里的定位是基线。sklearn的LogisticRegression用起来简单但有两个坑一是对数值特征尺度敏感训练前要先做标准化二是类别特征要么做独热编码展开要么先转成数值标签不能直接把字符串丢进去。源码里张峰诚的6_LR_KKBox_Music_Train.ipynb就是这类流程的标准写法。from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import roc_auc_score # X_train、X_val是前面特征工程产出的宽表已做缺失值填充 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_val_scaled scaler.transform(X_val) lr LogisticRegression(C1.0, max_iter500, random_state42) lr.fit(X_train_scaled, y_train) val_auc roc_auc_score(y_val, lr.predict_proba(X_val_scaled)[:, 1]) print(fLR val AUC: {val_auc:.4f})C是正则化强度的倒数C越小正则越强防止过拟合max_iter要设大一点标准化后的特征维度多时默认100次迭代可能不收敛训练时弹出ConvergenceWarning就是要加大迭代次数。predict_proba取第二列是正类概率AUC只关心排序能力不依赖阈值所以评估用AUC比直接用predict的0/1结果更稳。4.2 LightGBM与XGBoost梯度提升树的参数要点LightGBM在百万行数据上训练速度远快于XGBoost这是它在这个项目里被反复使用的原因。源码里model_compare_lgb_rate.ipynb和model_compare_lgb_category.ipynb两个文件一个按流失率口径直接建模一个按会员类别分组建模对比思路值得学。import lightgbm as lgb params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 63, max_depth: 7, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 1, verbose: -1 } d_train lgb.Dataset(X_train, labely_train) d_val lgb.Dataset(X_val, labely_val) model lgb.train( params, d_train, num_boost_round1000, valid_sets[d_val], callbacks[lgb.early_stopping(100), lgb.log_evaluation(50)] )num_leaves是LightGBM里最关键的参数控制树的复杂度叶子数越大越容易过拟合一般从31到127之间调。feature_fraction和bagging_fraction是列采样和行采样能显著缓解过拟合代价是训练时间略微增加。early_stopping(100)表示验证集AUC连续100轮不提升就停训避免无效迭代。XGBoost的xgb.py走的是另一套API风格用xgb.DMatrix封装数据参数名不一样对应关系要记清楚参数LightGBMXGBoost作用树复杂度num_leavesmax_depth控制模型容量越大越易过拟合列采样feature_fractioncolsample_bytree每棵树随机用部分特征行采样bagging_fractionsubsample每轮用部分样本训练学习率learning_rateeta步长越小越稳从LightGBM转XGBoost时最容易踩的坑就是参数名对不上同一套参数逻辑换个名字就报Invalid parameter。4.3 预测输出解析rate与category两条路径LightGBM目录里两个model_compare文件rate和category分别代表两种建模口径。rate路径直接预测每个会员的流失概率输出是连续的0到1评估用AUC。category路径是把会员按类别分组比如按注册渠道或套餐类型分成几个子群体每个群体单独建模最后再汇总预测结果。这两条路径各有适用场景。rate路径简单直接样本利用率高category路径能捕捉不同群体间的行为差异比如学生群体和家庭用户的听歌习惯完全不同。代价是子群体样本量不足时小模型容易过拟合。test_predict.ipynb和light_gbm_test.ipynb是对应的预测环节把训练好的模型应用到测试集输出预测概率再按提交格式保存。实际项目里我一般先跑rate路径确认整体AUC达标后再尝试按会员类别拆模型。拆模型前一定要确认每个子群体的样本量低于几千行的类别直接并入全量模型硬拆只会放大噪声这是血泪经验。5. 避坑指南KKBox流失预测复现中的五个高频问题5.1 时间穿越特征泄露导致评估虚高现象验证集AUC冲到0.95以上换到时间外样本或测试集之后跌回0.85以下差距明显。原因建模时用了全局聚合特征比如全量听歌总时长、全量交易次数这些统计量包含预测时点之后的信息属于特征泄露。更隐蔽的是把整个历史窗口的均值引进来而预测目标只覆盖最后一次交易后的30天口径直接错位。解决按时间切分训练集和验证集比如用2017年之前的数据训练、2017年的数据验证。构造特征时只允许使用截止到某个时点之前的user_logs3.2节里的days_diff就是为这个目的设计的。自查方法很朴素把验证集AUC和按时间切分的AUC对比差超过0.05就先怀疑泄露。5.2 会员标签重复与冲突现象同一个msno在训练数据里出现多行标签既有0又有1模型训练时loss不下降或者验证集表现飘忽。原因会员在观察期内多次续费每次交易都对应一个新的是否流失标签。按会员聚合做宽表时如果直接去重可能随机保留了一条记录标签口径就乱了。更常见的情况是用户某次交易后流失过几个月又回归续费前后两条记录的标签天然相反。解决按msno聚合时只保留最后一次交易对应的标签。如果业务上想预测未来30天是否流失预测起点就是最后一条交易记录更早的记录可以丢弃。历史是否流失过这个信息反而适合做成特征比如过去一年流失次数它比当前标签更有预测力。5.3 缺失值处理尺度不一致现象多个Notebook对同一字段的缺失值处理方式不同gender有的填-1有的填众数birthday有的填19000101有的直接dropna导致两个模型的结果对不上。原因多人协作、多文件并行的仓库没有统一的预处理层。每个人在自己的Notebook里顺手处理缺失值习惯不同口径就分裂了。解决把缺失值填充、类型转换、日期解析全部收拢到data_helpers.py或单独一个preprocess.py里所有Notebook统一import。推荐数值缺失填-1类别缺失填unknown至少保证全仓库口径一致。每次跑实验前检查一遍预处理函数有没有被改过省得结果漂移了还查不到原因。5.4 Notebook执行顺序混乱导致结果不可复现现象重启内核后重新跑一遍Notebook中间某个cell报KeyError或者结果和仓库里记录的对不上。原因Notebook被跳过Cell执行过某些变量是上一个Cell运行后才存在的。特别是带编号的流水线Notebook比如5_KKBox_Music_Merge_Data.ipynb直接读2_Create_Index产出的中间文件如果没先跑25必然报文件不存在的错。解决每次实验前强制Restart Run All确保从头到尾按顺序执行。Notebook之间不要互相依赖变量每个Notebook开头重新读原始数据文件而不是引用上一个Notebook的内存变量。这个习惯能省掉大量排查时间。5.5 依赖版本不一致导致同样的代码不同的结果现象xgb.py在别人机器上能跑到自己机器上报Invalid parameter或者LightGBM的early_stopping回调API直接抛异常。原因仓库没锁定依赖版本xgboost、lightgbm、sklearn在不同版本间有破坏性接口变更。比如lightgbm 3.x之后callbacks参数写法和2.x完全不同照着旧代码抄必然翻车。解决复现前先补一个requirements.txt把主要依赖版本锁住。我一般用conda单独建环境跑这个项目避免和日常开发环境的包互相污染conda create -n kkbox python3.9 -y conda activate kkbox pip install pandas2.0.3 numpy1.24.3 scikit-learn1.3.0 pip install lightgbm4.1.0 xgboost2.0.0安装sklearn库用pip install scikit-learn即可但装完一定要验证import成功再往下走。环境不一致导致的复现问题九成属于这类玄学问题解法朴素锁定版本、隔离环境。6. 把训练流程脚本化一份可复用的LightGBM骨架Notebook适合探索不适合重复执行。模型定下来之后训练流程一定要收拢成一个脚本。复现这类多人协作仓库时我最后都会写一个训练入口把读数据、建特征、切分、训练、保存串成一条线每次跑出来的结果完全一致调参只改params字典# train_churn.py —— KKBox流失预测训练骨架 import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score import joblib def build_features(): # 读入members/transactions/user_logs按第3节特征函数产出X和y pass def main(): X, y build_features() X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42) params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 63, feature_fraction: 0.8, bagging_fraction: 0.8, verbose: -1 } d_train lgb.Dataset(X_train, labely_train) d_val lgb.Dataset(X_val, labely_val) model lgb.train(params, d_train, num_boost_round1000, valid_sets[d_val], callbacks[lgb.early_stopping(100)]) print(fval AUC: {roc_auc_score(y_val, model.predict(X_val)):.4f}) joblib.dump(model, lgb_churn_model.joblib) if __name__ __main__: main()脚本化的意义在于可复现和可回溯。AUC评估的是排序能力落到业务上还要选阈值。流失预测的常见做法是设定一个概率阈值比如0.5以上标记为高流失风险再算该阈值下的精确率和召回率。召回率决定能捞回多少用户精确率决定运营资源的浪费程度具体取哪个点要看平台能承受多大的打扰成本。如果你准备下载这份源码复现我的建议是先读readme.txt和project_summary.md再按第4章的顺序跑LR基线和LightGBM对比最后强制做一次时间切分验证。从那以后我每次拿到多人协作的建模项目都先读文档、再锁环境、最后按时间切分验证时间泄露这一关不过掉就不算复现成功。希望帮到你。本文还有配套的精品资源点击获取
返回列表