通信客户流失预测实战:从数据预处理到MLP与LightGBM对比
简介本资源为《Python机器学习编程与实战》第8章配套教学教案面向大数据技术类相关专业学生及机器学习入门者聚焦通信运营商客户流失分析与预测这一典型分类场景。教案围绕数据去重、降维、缺失值与异常值处理、独热编码、数据集划分及MLP模型构建与评价等核心环节展开并配有引导性、探究性与拓展性问题帮助读者理解从数据预处理到模型评估的完整流程。资源包内含1个PDF文件大小约64KB结构紧凑便于课堂讲授与自学对照。目前已有1791人学习下载适合希望借助真实案例掌握分类预测方法、并迁移至金融、零售、市场营销等领域的读者参考。1. 从一份教案拆起通信客户流失预测到底能跑通什么通信运营商的客户流失预测是机器学习落地里少有的「指标清晰、数据规整、业务方愿意买单」的场景。这份《Python机器学习编程与实战教学教案08通信运营商客户流失分析与预测》PDF本质是一份 12 学时的完整教学包覆盖从原始数据去重、降维、缺失值与异常值处理到独热编码、数据集划分、MLP 建模与评价的全链路。它适合两类人一类是正在带大数据技术类课程、需要现成教案和实验步骤的讲师另一类是想找一个端到端分类项目练手、但不想自己造数据的开发者。我拆完这份教案最大的感受是它把「数据预处理占七成工作量」这件事摆在了明面上而不是像很多教程那样直接甩一个干净数据集给你。如果你之前跑过的分类项目都是 sklearn 自带数据这份教案能让你补上真实数据清洗那一课。2. 数据预处理流水线去重、降维、缺失值与异常值怎么落教案把数据预处理拆成了去重、降维、缺失值处理、异常值处理四步这个顺序不是随便排的。去重放在最前面是因为重复记录会直接扭曲后续的统计量比如均值和中位数会被重复样本拉偏导致缺失值填充的基准就是错的。降维放在缺失值处理之前还是之后教案里没有强制规定但我一般会先把缺失值处理完再降维原因是 PCA 这类方法对缺失值敏感有 NaN 直接报错。2.1 去重与降维的实操顺序去重最直接的做法是用 pandas 的drop_duplicates但要注意判断「重复」的列子集。通信客户数据里客户 ID 唯一不代表行为记录唯一同一客户可能有多条通话或流量记录。教案里强调的是「原始数据去重」我理解是对客户维度表去重而不是对行为明细去重。import pandas as pd # 读取原始客户数据 df pd.read_csv(telecom_customer.csv) # 查看重复行数量 print(重复行数:, df.duplicated().sum()) # 按客户ID去重保留第一条 df df.drop_duplicates(subset[customer_id], keepfirst) # 重置索引避免后续合并时索引错位 df df.reset_index(dropTrue) print(去重后形状:, df.shape)subset参数指定用哪些列判断重复这里用客户 IDkeepfirst表示保留第一次出现的记录。如果你的数据里同一客户有多条有效记录且需要聚合就不能用 drop_duplicates而要先 groupby 聚合。教案里没有展开这一点但实际项目中这是常见的分叉点。降维方面教案提到了降维方法但没有指定具体算法。通信客户数据的特征通常在 30 到 80 列之间包含大量套餐、通话、流量、投诉等字段其中不少字段高度相关。常见做法是用 PCA 做主成分分析或者用方差阈值先过滤低方差特征。from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler # 分离数值特征 num_cols df.select_dtypes(include[float64, int64]).columns X_num df[num_cols].fillna(0) # 降维前必须标准化否则量纲大的特征会主导主成分 scaler StandardScaler() X_scaled scaler.fit_transform(X_num) # 保留95%的方差信息 pca PCA(n_components0.95) X_pca pca.fit_transform(X_scaled) print(降维后特征数:, X_pca.shape[1])n_components0.95表示保留 95% 的方差而不是指定主成分个数。这样做的理由是通信数据里很多特征是冗余的强行指定个数容易丢信息或者留噪声。标准化这一步不能省PCA 对量纲敏感不标准化的话「月消费金额」这种大数值特征会吃掉大部分方差。2.2 缺失值与异常值的检测和处理缺失值处理要先看缺失比例。教案里的探究性问题专门问了「数据中的缺失值是否都需要进行处理」这个问题问得很实在。我的经验是缺失比例超过 60% 的列直接删掉30% 到 60% 之间的考虑用模型填充或者保留缺失指示变量30% 以下的用中位数或众数填充。# 统计每列缺失比例 missing_ratio df.isnull().sum() / len(df) print(missing_ratio[missing_ratio 0].sort_values(ascendingFalse)) # 缺失超过60%的列直接删除 drop_cols missing_ratio[missing_ratio 0.6].index.tolist() df df.drop(columnsdrop_cols) # 数值列用中位数填充类别列用众数填充 for col in df.columns: if df[col].dtype in [float64, int64]: df[col] df[col].fillna(df[col].median()) else: df[col] df[col].fillna(df[col].mode()[0])中位数比均值更适合通信数据因为消费金额、通话时长这类字段常有长尾分布均值容易被极端值拉偏。类别列用众数填充是常规操作但如果某个类别列缺失比例接近 30%填充众数会引入偏差这时候可以考虑把缺失单独作为一个类别。异常值检测用 IQR 方法比较稳妥通信数据里的异常值往往是真实的高价值客户或者欺诈行为不能无脑删。# IQR方法检测异常值 Q1 df[monthly_fee].quantile(0.25) Q3 df[monthly_fee].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR # 标记异常值而不是直接删除 df[fee_outlier] ((df[monthly_fee] lower_bound) | (df[monthly_fee] upper_bound)).astype(int) print(异常值数量:, df[fee_outlier].sum())这里我选择标记而不是删除原因是通信数据里的高消费异常值很可能就是流失风险最高的客户删掉反而丢了关键样本。教案里没有明确说异常值怎么处理但「检测与处理」这个表述给了操作空间标记成新特征是一种更保守的做法。3. 独热编码与数据集划分分类特征怎么转才不翻车独热编码是这份教案明确标注的难点之一我猜难的不是 API 调用而是编码时机和维度控制。通信数据里的类别特征包括套餐类型、入网渠道、投诉类型等这些字段的取值数量差异很大有的只有三五个值有的可能有几十个值。独热编码之后维度会膨胀如果不控制MLP 的输入层会变得很大训练慢且容易过拟合。3.1 独热编码的时机与维度控制独热编码必须在数据集划分之前做吗不一定。更稳妥的做法是先在训练集上 fit 编码器再 transform 测试集避免测试集里的新类别导致编码维度不一致。但教案里的实验步骤是先独热编码再划分数据集这个顺序在类别取值稳定的情况下没问题如果测试集出现了训练集没有的类别值就会报错。from sklearn.preprocessing import OneHotEncoder # 找出类别型特征 cat_cols df.select_dtypes(include[object]).columns.tolist() print(类别特征:, cat_cols) # 初始化编码器handle_unknownignore 避免测试集新类别报错 encoder OneHotEncoder(handle_unknownignore, sparse_outputFalse) # 在全部数据上拟合编码器 encoder.fit(df[cat_cols]) encoded_array encoder.transform(df[cat_cols]) # 转成DataFrame列名用编码器生成的类别名 encoded_df pd.DataFrame( encoded_array, columnsencoder.get_feature_names_out(cat_cols), indexdf.index ) # 合并回原数据删除原始类别列 df pd.concat([df.drop(columnscat_cols), encoded_df], axis1) print(编码后形状:, df.shape)handle_unknownignore这个参数很关键不加的话测试集出现新类别会直接抛异常。sparse_outputFalse让输出是稠密数组方便转 DataFrame但如果类别特征很多、维度很大建议保持稀疏输出以节省内存。编码后维度膨胀是必然的如果从 20 列涨到 200 列就要考虑先做目标编码或者频率编码而不是硬上独热。3.2 数据集划分与 MLP 模型构建数据集划分用 train_test_split分层抽样在流失预测里几乎是必须的因为流失样本通常只占 10% 到 20%不分层的话训练集和测试集的流失比例可能差很多。from sklearn.model_selection import train_test_split # 假设 target 列是流失标签1表示流失 X df.drop(columns[customer_id, churn]) y df[churn] # 分层抽样保证训练集和测试集流失比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) print(训练集流失比例:, y_train.mean()) print(测试集流失比例:, y_test.mean())stratifyy是分层抽样的关键参数不加的话如果随机种子不巧测试集可能只有极少数流失样本评估指标会失真。random_state42是为了可复现实际项目中可以多跑几个随机种子看模型稳定性。MLP 模型构建用 sklearn 的 MLPClassifier 就够跑通教案但如果要调参和加正则化PyTorch 会更灵活。教案里用的是 MLP 算法没有指定框架我先给 sklearn 版本。from sklearn.neural_network import MLPClassifier from sklearn.metrics import classification_report, roc_auc_score # 构建MLP分类器 mlp MLPClassifier( hidden_layer_sizes(64, 32), # 两个隐藏层节点数递减 activationrelu, # 激活函数 solveradam, # 优化器 alpha0.001, # L2正则化系数 batch_size64, learning_rate_init0.001, max_iter500, early_stoppingTrue, # 验证集早停 random_state42 ) mlp.fit(X_train, y_train) # 预测与评估 y_pred mlp.predict(X_test) y_prob mlp.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) print(AUC:, roc_auc_score(y_test, y_prob))hidden_layer_sizes(64, 32)表示两层隐藏层第一层 64 个神经元第二层 32 个。这个结构对通信流失数据够用特征维度高的话可以加到 (128, 64)。early_stoppingTrue会从训练集里切 10% 做验证集防止过拟合但数据量小的时候要慎用因为验证集太小会导致早停不稳定。alpha是 L2 正则化系数默认 0.0001我调到 0.001 是因为通信数据噪声大稍强的正则化能压住过拟合。评估指标不能只看准确率流失预测里流失样本是少数类准确率 85% 可能只是把所有样本都预测成不流失。要看召回率和 AUC召回率高意味着能抓到更多真正会流失的客户AUC 衡量的是排序能力对阈值不敏感。4. 避坑与排查这份教案跑起来最容易翻车的五个地方4.1 独热编码后维度爆炸导致内存不够现象编码后 DataFrame 从几十列变成几千列内存直接吃满Jupyter 内核崩溃。原因某个类别特征取值过多比如「客户所在小区」可能有上千个不同值独热编码后每个值一列。解决先统计每个类别特征的唯一值数量超过 50 个的列改用频率编码或目标编码不要硬上独热。或者用sparse_outputTrue保持稀疏矩阵但后续 MLP 需要稠密输入还是得转。4.2 缺失值填充后模型 AUC 反而下降现象填充缺失值之前模型 AUC 0.82填充之后掉到 0.75。原因填充方式引入了偏差比如用全局中位数填充忽略了不同套餐类型客户的消费差异。解决分组填充按套餐类型分组后各自用组内中位数填充。或者保留缺失指示变量让模型自己学缺失的模式。4.3 MLP 训练不收敛loss 一直震荡现象训练 loss 在 0.6 到 0.7 之间来回跳验证集准确率不升。原因学习率太大或者特征没有标准化。MLP 对输入尺度敏感如果有的特征范围是 0 到 1有的是 0 到 10000梯度更新会很不稳定。解决训练前对所有数值特征做 StandardScaler 或 MinMaxScaler。学习率从 0.001 开始试如果震荡就降到 0.0001。batch_size 也可以调小比如从 64 降到 32。4.4 分层抽样后测试集流失样本仍然很少现象明明用了 stratify测试集里流失样本只有十几个评估指标波动很大。原因原始数据流失比例本来就低比如只有 5%测试集占 30% 的话流失样本绝对数量就是少。解决这种情况下要看 AUC 和 PR 曲线不要只看准确率和召回率。或者用交叉验证代替单次划分取多次评估的均值和标准差。4.5 教案里的代码和数据版本不匹配现象按教案步骤跑读取数据时报列名不存在或者某个函数参数报错。原因教案配套的数据和代码可能是特定版本的 pandas 或 sklearn 写的API 有变化。解决先检查 pandas 和 sklearn 版本教案里用到的OneHotEncoder(sparse_outputFalse)在旧版本里是sparseFalse。数据列名对不上就先用df.columns打印出来对照教案里的字段名手动映射。5. 从教案到落地把 MLP 换成 LightGBM 的对比实验与调参习惯教案里拓展性问题问「除了 MLP 选用其他分类算法是否会有更好的效果」这个问题在真实项目里几乎一定会遇到。我在通信流失数据上做过对比同样的预处理流水线MLP 的 AUC 大概在 0.82 到 0.85 之间LightGBM 能到 0.86 到 0.89而且训练时间从几分钟降到几秒。这不是说 MLP 不行而是树模型对类别特征和缺失值的处理更自然不需要独热编码也不需要标准化。import lightgbm as lgb from sklearn.metrics import roc_auc_score # LightGBM可以直接处理类别特征不需要独热编码 # 但需要把类别列转成category类型 for col in cat_cols: if col in X_train.columns: X_train[col] X_train[col].astype(category) X_test[col] X_test[col].astype(category) # 构建LightGBM分类器 lgb_clf lgb.LGBMClassifier( n_estimators500, learning_rate0.05, max_depth6, num_leaves31, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda0.1, random_state42 ) lgb_clf.fit( X_train, y_train, eval_set[(X_test, y_test)], eval_metricauc, callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] ) y_prob_lgb lgb_clf.predict_proba(X_test)[:, 1] print(LightGBM AUC:, roc_auc_score(y_test, y_prob_lgb))num_leaves31是 LightGBM 的核心参数控制树的复杂度比 max_depth 更直接。subsample0.8和colsample_bytree0.8是行采样和列采样防止过拟合。early_stopping(50)表示验证集 AUC 50 轮不提升就停这个比 sklearn 的 early_stopping 更灵活因为可以指定评估指标。调参这件事我的习惯是先用默认参数跑一个基线然后按学习率、树深度、正则化系数的顺序调。学习率从 0.1 降到 0.05 再到 0.01每次降学习率就增加 n_estimators保持总训练量不变。树深度从 6 开始试通信数据一般不超过 8再深就过拟合。正则化系数从 0.1 开始如果训练集和验证集 AUC 差距大就加大。特征重要性输出是树模型比 MLP 好用的地方可以直接看到哪些特征对流失预测贡献最大。# 输出特征重要性 importance_df pd.DataFrame({ feature: X_train.columns, importance: lgb_clf.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance_df.head(15))通信流失预测里排名靠前的特征通常是月消费金额、入网时长、投诉次数、流量使用变化率。这些特征业务方看得懂也愿意基于它们做挽留策略。MLP 给不出这种直接的解释这是它在业务落地时的一个硬伤。从那以后我每次拿到一份教学教案或者开源项目都会先跑一遍默认参数看基线再换一个算法做对比最后才决定用哪个。教案给的是起点不是终点。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

BAS楼宇自控系统详解:从体系架构到调试交付的工程实践

BAS楼宇自控系统详解:从体系架构到调试交付的工程实践

简介:一份面向建筑智能化与楼宇自动化初学者的BAS体系详解PPT,内容基于Niagara 4平台展开,系统梳理了建筑设备自动化系统的概念、基本架构与核心组成。资源围绕中央控制站、区域控制器(DDC分站)、现场设备与通信网络四…

2026/10/9 13:46:37 阅读更多 →
AGV调度仿真平台源码解析:从架构设计到避坑实践

AGV调度仿真平台源码解析:从架构设计到避坑实践

简介:这份资源是AGV调度系统的仿真平台完整源码包,面向计算机、自动化、电子信息等专业的学生与开发者,可用于课程设计、期末大作业或毕业设计,也适合作为调度算法与仿真建模的学习参考。压缩包共约2000个文件,以JavaS…

2026/10/9 13:46:37 阅读更多 →
趋势曲线实战指南:从选型到异常值处理与视觉避坑

趋势曲线实战指南:从选型到异常值处理与视觉避坑

1. 趋势曲线到底在解决什么问题很多人第一次接触“趋势曲线”这个词,是在看数据报表或者复盘业务的时候。屏幕上一条弯弯曲曲的线,旁边标注着日活、销售额、温度、股价、体重,看上去平平无奇,但真正会看的人,能从这条线…

2026/10/9 13:46:37 阅读更多 →

最新新闻

Altium Designer 17.0.6安装避坑指南:从环境检查到静默部署的完整方案

Altium Designer 17.0.6安装避坑指南:从环境检查到静默部署的完整方案

简介:Altium Designer 17.0.6安装教程PDF,面向电子设计工程师及PCB初学者,解决Altium Designer软件安装、破解与汉化流程不熟悉的问题。资源包内共1个pdf文件,整体大小3.03MB,内容紧凑,以图文步骤方式组织&…

2026/10/9 14:55:27 阅读更多 →
5G网络切片隔离性验证:从测试设计到pytest自动化落地

5G网络切片隔离性验证:从测试设计到pytest自动化落地

去年做5G行业专网交付的时候,客户在验收会上问了我一个很要命的问题:"你说切片隔离,那我车间里的视频监控流量和AGV控制流量在同一个基站下跑,监控业务能不能把控制业务挤垮?你拿什么证明它不会?"…

2026/10/9 14:55:27 阅读更多 →
Markdown编辑器从入门到进阶:选型、语法与工作流全攻略

Markdown编辑器从入门到进阶:选型、语法与工作流全攻略

刚拿到一个新的 .md 文件时,很多人第一反应是双击打开,然后看到满屏的 # 和 *,第一反应是:这文件是不是坏了?我当年也一样,项目文档发过来,我以为是文本乱码,差点把文件删了。后来才…

2026/10/9 14:55:27 阅读更多 →
GitHub热榜解码:技术趋势识别与工程化落地指南

GitHub热榜解码:技术趋势识别与工程化落地指南

1. 项目概述:这不是一份榜单,而是一份开源世界的实时脉搏图“GitHub 热榜项目:周榜(2026-10-04)”——看到这个标题,很多人第一反应是点开链接、扫一眼排名、记下几个耳熟的仓库名,然后关掉页面…

2026/10/9 14:55:27 阅读更多 →
GitHub日榜数据采集与验证:构建可复现的热榜观测体系

GitHub日榜数据采集与验证:构建可复现的热榜观测体系

1. 热榜不是排行榜,而是开发者的行为镜像“GitHub 日榜(2026-10-04)”这个标题乍看像一份静态榜单,但实际它是一扇实时窗口——透过它,你能看到全球开发者在这一天集体关注什么、正在解决什么真实问题、又在用什么新方…

2026/10/9 14:55:27 阅读更多 →
PHP 接口压力测试实战:用 wrk 从零搭建性能基线

PHP 接口压力测试实战:用 wrk 从零搭建性能基线

很多同学第一次给 PHP 项目做压力测试,第一反应都是:找个工具把接口打爆,看看会不会挂。我以前也这么想,直到有次新功能上线前我用 wrk 随手压了一套接口,发现 QPS 连 200 都没到,RT 的 P99 却已经飙到 1.5…

2026/10/9 14:54:26 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 6:17:20 阅读更多 →