ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

电信客户流失预测:朴素贝叶斯实战全流程解析

电信客户流失预测:朴素贝叶斯实战全流程解析 简介面向电信行业数据分析与数据挖掘初学者本资源提供一套完整的基于朴素贝叶斯算法的电信客户流失分析预测模型。原始数据取自Kaggle开源社区含7043条客户记录、21项属性可细分为客户个人信息、账户信息、订阅服务与流失评价指标四类例如性别、老年客户、合作伙伴等基础画像合同期限、付费方式、月消费等账户行为以及电话、网络、在线安全保障等服务属性便于直接开展特征理解与分类建模。压缩包共9个文件、3.1MB核心内容包括CSV/TSV数据文件、Jupyter Notebook完整代码、HTML运行结果、TensorBoard训练日志及8000字实验报告兼顾复现流程与文档解读。已有297人学习下载适合课程设计、毕业设计或入门实战演练使用者可获得可复用代码、实验数据与撰写范本快速掌握数据预处理、模型训练和结果分析的关键步骤。1. 电信客户流失分析预测模型朴素贝叶斯在数据挖掘实战里怎么用基于朴素贝叶斯算法的电信客户流失分析预测模型是数据挖掘课程里出现频率最高的实战题目之一也是电信运营里真正能落到业务上的分类场景。但多数教程只讲算法推导不讲数据怎么收拾。这份资源在 7043 条客户样本上跑完了完整流程从 Kaggle 原始数据清洗、21 个字段分类、特征编码到模型训练、结果评估附带可运行的 ipynb 代码和 8000 字实验报告。适合三类人课程设计要交完整报告的学生、刚入门分类算法想找全程范例的初学者、想在电信场景验证朴素贝叶斯效果的从业者。核心价值在于每步操作和参数都摊开写在代码里照着复现就能出结果不用自己拼凑零散教程。2. 数据预处理把 7043 条样本和 21 个字段整理成模型能吃的形状2.1 字段分类与业务含义四组特征决定编码策略拿到 data.csv 之后别急着建模先花十分钟把 21 个字段按业务含义分组这个动作直接决定后面的编码方案。数据集来自 Kaggle 的电信客户流失公开数据7043 条样本每条记录一位客户的套餐和消费情况。21 个属性大致分成四组个人信息CustomerID、gender、SeniorCitizen、Partner、Dependents账户信息tenure、Contract、PaperlessBilling、PaymentMethod、MonthlyCharges、TotalCharges订阅服务PhoneService、MultipleLines、InternetService、OnlineSecurity、OnlineBackup、DeviceProtection、TechSupport、StreamingTV、StreamingMovies以及目标变量 Churn。分组不是走形式。个人信息里的性别、是否老年人和流失概率是弱相关账户信息里的 tenure、Contract 是强信号——在网时间越短、月付合约流失倾向越高订阅服务里的 OnlineSecurity、TechSupport 能反映客户对服务的依赖度。不同组的字段类型差异很大tenure、MonthlyCharges、TotalCharges 是连续数值gender、Partner 这类是二值InternetService、PaymentMethod 是多值无序类别编码策略必须分开处理。分组字段取值特征编码建议个人信息CustomerID唯一字符串直接删除个人信息gender、Partner、Dependents二值 Yes/NoLabelEncoder 转 0/1个人信息SeniorCitizen0/1保留原值账户信息tenure、MonthlyCharges、TotalCharges连续数值标准化后直接使用账户信息Contract3 种取值One-Hot账户信息PaymentMethod4 种取值One-Hot账户信息PaperlessBilling二值 Yes/NoLabelEncoder 转 0/1订阅服务PhoneService 等 8 个二值字段Yes/NoLabelEncoder 转 0/1订阅服务InternetService3 种取值One-Hot目标ChurnYes/No映射 1/0有个细节容易忽略Kaggle 原始数据里 DeviceProtection、StreamingTV、StreamingMovies 字段名自带拼写错误原始 CSV 里是 DeciveProtection、SteamingTV、SteamingMovies。这是数据集的原始状态读取时不用纠正改了反而跟后续代码对不上。做数据挖掘项目原始数据的别名要留个记录报告里写清楚也是一处加分细节。2.2 数据分布检查流失和非流失客户差在哪编码之前先看一眼两组客户的差异这一步能验证数据是否符合业务直觉也能帮你判断特征有没有被搞反。常用的方法是按 Churn 分组算连续变量均值再对类别变量做交叉表。import pandas as pd # 读取原始数据先看类型和缺失 df pd.read_csv(data.csv, encodingutf-8) print(df.dtypes) print(df.isnull().sum()) # 按是否流失分组看连续变量的均值差异 print(df.groupby(Churn)[[tenure, MonthlyCharges, TotalCharges]].mean()) # 合约类型与流失的交叉占比 print(pd.crosstab(df[Contract], df[Churn], normalizeindex))逻辑说明groupby 的 mean 能快速暴露连续变量和流失的关系。这个数据集里通常能看到流失客户的平均 tenure 明显低于不流失客户月费则偏高说明在网时间短、客单价高的客户更容易走。crosstab 加 normalizeindex 算的是行占比能看出月付合约的流失率远高于年付和两年付。这些规律如果和业务常识对不上说明数据清洗出了问题要回头查字段映射。这里同时也是查缺失的时机isnull().sum() 打印出来TotalCharges 大概率有几十个空值原因下一节讲。2.3 缺失值与类型转换TotalCharges 的空值不是坏数据TotalCharges 在原始 CSV 里是 object 类型不是数值类型原因是部分行存的是空字符串。这些空字符串来自 tenure 为 0 的新客户——刚入网还没产生账单累计费用为空是正常业务状态不是数据损坏。# TotalCharges 转数值无法解析的转成 NaN df[TotalCharges] pd.to_numeric(df[TotalCharges], errorscoerce) # 确认缺失数量再和 tenure0 的客户数对比 missing_charges df[TotalCharges].isnull() print(fTotalCharges 缺失数: {missing_charges.sum()}) print(ftenure0 的客户数: {(df[tenure] 0).sum()}) # 按业务逻辑填充为 0 df.loc[missing_charges, TotalCharges] 0 # 删除 CustomerID它对分类预测没有贡献 df df.drop(columns[CustomerID])pd.to_numeric 加 errorscoerce 会把空字符串转成 NaN缺失数量一下就暴露了。关键是不要 dropna——直接删行会损失一批新客户样本模型也学不到新客户更容易流失这个模式。按 0 填充保留了样本tenure 本身的数值也能让模型区分新老客户。至于 CustomerID每条记录一个唯一值放进训练集只会让模型当噪声特征学删掉干净。提示处理缺失前先打印缺失行和 tenure 的交叉情况确认缺失是业务规则导致的而不是随机丢失再决定填充策略。2.4 特征编码LabelEncoder 与 One-Hot 的边界在哪编码这一步的错误率最高错在把无序类别当成有序类别处理。二值字段 gender、Partner、Dependents 这类 Yes/No用 LabelEncoder 编成 0/1 没问题。但 PaymentMethod 有四种取值本身没有大小关系如果也用 LabelEncoder 编成 0、1、2、3GaussianNB 会把相邻数值当成相似特征概率估计被带偏。from sklearn.preprocessing import LabelEncoder # 二值字段统一转 0/1 binary_cols [gender, Partner, Dependents, PhoneService, MultipleLines, OnlineSecurity, OnlineBackup, DeviceProtection, TechSupport, StreamingTV, StreamingMovies, PaperlessBilling] label_enc LabelEncoder() for col in binary_cols: df[col] label_enc.fit_transform(df[col]) # 多值无序字段 One-Hotdrop_first 去掉一列避免冗余 multi_cols [InternetService, Contract, PaymentMethod] df pd.get_dummies(df, columnsmulti_cols, drop_firstTrue) print(f编码后特征数量: {df.shape[1]}) print(df.head())逻辑说明二值字段编码顺序无所谓模型只认 0 和 1 两个状态。多值无序字段必须 One-HotContract 虽然看起来有顺序月付、年付、两年付但朴素贝叶斯本身不做顺序假设One-Hot 更稳妥。drop_firstTrue 对三个多值字段各去掉一列特征矩阵少三列冗余条件概率也少算几组。编码完成后特征数量从 20 左右涨到 20 多这是正常的 One-Hot 结果。还有个业务细节MultipleLines 在原始数据里除了 Yes/No 还有 No phone service 取值它和 PhoneServiceNo 是对应关系。LabelEncoder 会把三个取值当作三个独立状态处理对朴素贝叶斯来说没问题两个字段的相关性由模型自己去学不需要手动合并。3. 朴素贝叶斯原理条件独立假设为什么能在流失预测里立足3.1 从贝叶斯公式到流失概率先验 26% 是怎么来的朴素贝叶斯的出发点是贝叶斯公式P(ChurnYes | X) P(X | ChurnYes) × P(ChurnYes) / P(X)P(ChurnYes) 是先验概率不看任何特征时客户流失的概率。这个数据集里流失客户大约占四分之一约 26%。P(X | ChurnYes) 是似然表示在流失客户群体里出现特征组合 X 的概率。P(X) 是归一化常数所有类别共用同一个值分类时可以直接忽略。朴素贝叶斯算的是后验概率 P(ChurnYes | X)超过阈值就判为流失。朴素二字体现在一个强假设给定类别后所有特征条件独立。于是 P(X | ChurnYes) 拆成每个特征条件概率的乘积P(X | ChurnYes) P(x1 | ChurnYes) × P(x2 | ChurnYes) × ... × P(xn | ChurnYes)这个假设在电信数据里明显不成立用了光纤宽带InternetServiceFTTH的客户大概率同时没买 OnlineSecurity两个特征有业务相关性。但实战里朴素贝叶斯依然能打原因是分类任务关心的是后验概率的相对排序不是绝对数值。特征相关性主要扭曲概率幅值对排序影响有限这就是朴素贝叶斯理论上有硬伤、实战中能用的玄学之处。举个例子帮助理解。假设训练集中流失客户有 1800 人其中月付合约 1500 人那么 P(月付 | 流失) ≈ 0.83不流失客户 5200 人其中月付合约 2500 人P(月付 | 不流失) ≈ 0.48。新来一个月付客户两个似然乘上各自的先验再归一化流失概率就明显高于不流失概率。这个计算过程完全可解释每一步都能在报告里写清楚这是朴素贝叶斯比黑匣子模型更适合课程设计的原因。3.2 三种变体怎么选Gaussian、Bernoulli、Multinomialsklearn 里朴素贝叶斯有三个常用变体选哪个取决于特征类型。变体分布假设适用特征关键参数GaussianNB正态分布连续数值var_smoothingBernoulliNB二值分布0/1 字段alphaMultinomialNB多项式分布计数特征alphaGaussianNB 用每个类别下特征的均值和方差估计条件概率适合 tenure、MonthlyCharges、TotalCharges 这类连续字段。BernoulliNB 假设特征是二值 0/1适合 gender、Partner、Dependents 这类 Yes/No 字段。MultinomialNB 面向计数特征常用于文本词频统计电信流失数据里没有合适的计数特征基本用不上。这份资源里的 ipynb 用 GaussianNB 直接吃全特征矩阵二值字段也按连续值处理。优点是代码短、流程简单缺点是二值特征的 0/1 分布离正态分布很远概率估计有些偏差。想更讲究的话可以把连续字段和二值字段拆开分别训练 GaussianNB 和 BernoulliNB选测试集 AUC 更高的那个。对课程设计来说GaussianNB 直接跑已经足够报告里解释一句二值特征近似按连续值处理就能说清楚。3.3 拉普拉斯平滑与 var_smoothing零概率问题的后悔药条件概率计算有个经典坑某个特征取值在某类别里从未出现P(xi | Churn) 就是 0连乘之后整个后验概率归零。比如训练集里所有流失客户都是月付合约那年付合约 流失组合的概率就是 0年付费客户会被武断判为不流失。拉普拉斯平滑给每个计数分子加一个 alphaP(xi | class) (count_i alpha) / (count alpha × n_values)alpha 默认 1.0属于中等强度的起步值越大概率越往均匀方向拉。比如某特征取值在流失类里出现 5 次流失类总样本 1800n_values 取 2 时平滑后概率是 (51)/(18002) ≈ 0.0033而不是 5/1800 里的零概率。这个参数就是你处理零概率问题的后悔药换成 BernoulliNB 或 MultinomialNB 时记得显式传 alpha。GaussianNB 没有 alpha 参数对应的是 var_smoothing默认 1e-9。它往每个特征的方差上加极小值防止方差为 0 时概率密度爆炸。如果发现预测概率全挤在 0 或 1 附近先别怀疑模型试着把 var_smoothing 调到 1e-5 甚至 1e-3看概率分布是否恢复合理。这两个参数是你在 ipynb 里最常需要动的地方。4. 模型训练与预测跑通 ipynb 的完整流程和参数设置4.1 数据集划分stratify 分层抽样保住流失比例划分数据集是第一个决定结果可复现性的环节。流失样本只占四分之一如果不分层抽样随机划分的测试集流失占比可能忽高忽低后面所有指标都没法对比。from sklearn.model_selection import train_test_split # X 去掉目标列y 取 Churn 并映射为 0/1 X df.drop(columns[Churn]) y df[Churn].map({Yes: 1, No: 0}) # 分层抽样训练集和测试集都保持约 26% 的流失占比 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.25, random_state42, stratifyy ) print(f训练集样本数: {X_train.shape[0]}, 流失占比: {y_train.mean():.4f}) print(f测试集样本数: {X_test.shape[0]}, 流失占比: {y_test.mean():.4f})stratifyy 是最容易漏的参数。加了之后训练集和测试集的流失占比都会稳定在 26% 左右模型评估才有可比性。random_state42 固定随机种子保证每次运行划分结果一致——这是报告里数字能复现的前提。test_size0.25 是常见比例7043 条样本切出约 1760 条测试集足够评估用。如果你觉得测试集太小可以用 0.2但样本量变化会影响后续指标的稳定性选定一个就不动。提示想复现实验结果random_state 必须写进报告。换了种子结果可能差一到两个百分点评审老师最常问的就是这个。4.2 训练 GaussianNB参数与预测结果保存from sklearn.naive_bayes import GaussianNB from sklearn.metrics import classification_report, confusion_matrix # 高斯朴素贝叶斯var_smoothing 先用默认值 1e-9 model GaussianNB() model.fit(X_train, y_train) # 测试集预测 y_pred model.predict(X_test) y_proba model.predict_proba(X_test)[:, 1] # 输出评估结果 print(classification_report(y_test, y_pred, target_names[不流失, 流失])) print(confusion_matrix(y_test, y_pred)) # 保存预测明细后续业务分析要用 result X_test.copy() result[Churn_actual] y_test.values result[Churn_pred] y_pred result[Churn_prob] y_proba result.to_csv(result.csv, indexFalse)predict 返回 0/1 类别predict_proba 返回每个类别的概率取 [:, 1] 就是不流失。做业务分析一定要用概率列而不是只看类别标签。result.csv 同时保留实际值、预测值和概率方便后面做阈值扫描和流失名单排序。classification_report 里重点看流失类别正类的召回率它代表真正会流失的客户被抓住多少。这里的整体准确率看着还行但这是因为不流失客户占多数模型即使全部判不流失也有约 74% 的基线准确率。所以评估必须结合正类的精确率和召回率一起看不能只盯 accuracy。4.3 阈值调整0.5 不是唯一选择朴素贝叶斯默认按后验概率是否大于 0.5 判类别但流失数据不平衡0.5 未必是最优分界。from sklearn.metrics import f1_score import numpy as np # 扫描阈值选出 F1 最高的分界点 best_f1 0 best_thr 0.5 for thr in np.arange(0.2, 0.8, 0.05): y_pred_adj (y_proba thr).astype(int) f1 f1_score(y_test, y_pred_adj) if f1 best_f1: best_f1 f1 best_thr thr print(f最优阈值: {best_thr:.2f}, 对应 F1: {best_f1:.4f})阈值调低会误报变多但能抓出更多潜在流失客户调高则相反。在电信业务里把高概率客户提前圈出来做挽留比漏掉一个真正要流失的客户代价小所以很多人会故意把阈值往低调。扫描区间和步长根据业务容忍度调整评估指标也可以换成召回率看你的侧重点。这一步不用改模型改的是业务决策边界。5. 避坑指南朴素贝叶斯实战里的五个翻车点这五个坑是我复现这类资源时实际踩过的每一条都对应 ipynb 里能看到的真实报错或结果异常。5.1 现象测试集准确率比训练集低一大截原因最常见的有两个一是划分数据集时没加 stratify测试集里流失样本占比和训练集差很远二是 LabelEncoder 或 One-Hot 在 train_test_split 之前就用全量数据 fit测试集的信息通过编码器泄漏进了训练过程。解决划分必须加 stratifyy所有编码器只 fit 训练集测试集只做 transform。这样写# 先划分 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.25, random_state42, stratifyy ) # 再编码编码器只 fit 训练集 label_enc LabelEncoder() X_train[gender] label_enc.fit_transform(X_train[gender]) X_test[gender] label_enc.transform(X_test[gender])看到 fit_transform 出现在 train_test_split 之前基本就是这个坑。泄漏会让测试集指标虚高真实场景里根本达不到这也是评审最容易挑出的问题。5.2 现象TotalCharges 转 float 直接报错原因data.csv 里 TotalCharges 是 object 类型tenure 为 0 的新客户该字段是空字符串直接 astype(float) 抛 ValueError。# 错误写法 df[TotalCharges] df[TotalCharges].astype(float) # ValueError # 正确写法 df[TotalCharges] pd.to_numeric(df[TotalCharges], errorscoerce) df[TotalCharges] df[TotalCharges].fillna(0)血泪经验不要看到空字符串就当成脏数据删行先查 tenure 分布。很多所谓缺失是业务规则的一部分删掉反而让模型学到错误的样本分布。5.3 现象预测概率全挤在 0 或 1 附近原因GaussianNB 的方差估计接近 0概率密度变成尖峰或者特征里混进了常量列比如某字段所有样本取值都相同方差为 0。# 检查常量列 constant_cols [c for c in X.columns if X[c].nunique() 1] print(常量列:, constant_cols) # 调整方差平滑 model GaussianNB(var_smoothing1e-3)解决分两步先删掉方差为 0 的列再把 var_smoothing 从默认 1e-9 往上调看概率分布是否恢复正常。调到多少没有标准答案以验证集表现为准我一般从 1e-6 开始试。5.4 现象特征编码后模型反而变差原因把无序类别用 LabelEncoder 强加顺序。PaymentMethod 的四种取值没有大小关系编成 0/1/2/3 之后 GaussianNB 会把相邻数值当相似特征概率估计被带偏。解决无序多取值字段必须 One-HotContract 虽然看起来有序在朴素贝叶斯下也没差别One-Hot 更稳。特征编码选择错误是数据预处理环节最隐蔽的问题指标下降时优先怀疑这里。5.5 现象报告里的数字自己复现不出来原因train_test_split 没有固定 random_state每次划分结果不同或者 ipynb 中间 cell 被改过从某个位置继续运行时变量状态和从头跑不一致。解决固定 random_state42复现实验时先 Restart Run All保证从头到尾完整执行。8000 字实验报告里写准确率时把随机种子和数据集划分比例一起写进去这是评审老师最爱追问的细节也是很多数据挖掘项目被质疑结果不可信的根源。6. 结果验证ROC 曲线、阈值调整与流失名单的业务落地6.1 ROC 与 AUC评估指标怎么读from sklearn.metrics import roc_auc_score, roc_curve import matplotlib.pyplot as plt # 计算 AUC auc roc_auc_score(y_test, y_proba) print(fAUC: {auc:.4f}) # 绘制 ROC 曲线 fpr, tpr, _ roc_curve(y_test, y_proba) plt.plot(fpr, tpr, labelfGaussianNB (AUC{auc:.4f})) plt.plot([0, 1], [0, 1], k--, label随机猜测) plt.xlabel(假正率) plt.ylabel(真正率) plt.legend() plt.savefig(roc_curve.png, dpi120)AUC 是不依赖阈值的综合指标0.5 等于随机猜0.8 以上算有实用价值。朴素贝叶斯在这个数据集上跑到 0.8 上下是正常水平如果明显低于这个数回头查编码和阈值两个环节大概率问题出在数据准备而不是算法本身。6.2 从预测概率到流失名单业务侧怎么用result.csv 里的 Churn_prob 就是流失概率。业务落地时我会按概率降序排列取前 20% 的客户作为重点挽留名单再叠加 MonthlyCharges 过滤出高价值客户优先分配客服资源。资源包里还带了 catboost_info 目录说明作者实际训练过 CatBoost 做对比实验。如果你的报告需要提升准确率可以把 CatBoost 结果作为强基线写进对比朴素贝叶斯作为可解释性强的基线模型两者互补地呈现。从那以后我每次做分类项目都强制自己过三关划分数据集先查 stratify、编码器只 fit 训练集、随机种子写死在代码里。朴素贝叶斯的理论门槛不高翻车几乎都翻在数据准备和复现细节上。这份资源的 ipynb 和实验报告把全过程都留了底照着走一遍你就能在 7043 条样本上拿到自己的完整结果。希望帮到你。本文还有配套的精品资源点击获取
返回列表