
简介以预测用户购买车险为业务场景资源完整演示了朴素贝叶斯分类器的建模与分析流程适合数据分析初学者、高校学生和营销分析人员参考学习。项目围绕年龄、驾龄、事故记录等用户特征覆盖数据清洗、分类变量编码、标准化、模型训练与评估等关键步骤并结合多项式/高斯朴素贝叶斯变体对比说明如何将预测概率用于精准营销与风险控制。压缩包共93个文件大小约29.08MB主要包括56个csv数据文件、6个ipynb分析脚本、2个py算法实现、16个png可视化图以及论文LaTeX源文件、训练好的模型和README说明整体按“论文”与“建模”目录组织便于按阶段复现。资源还提供多层感知器与逻辑回归分类模型作为对照实验附有数据粗分析、数据数值化、贝叶斯拟合概率图等输出已有132人学习下载适合进一步改造为其他分类预测项目。1. 概率题背后的业务题为什么车险购买分析首选朴素贝叶斯车险续保和增量获客的场景里运营最想知道的不是“这个用户买不买”这种二元答案而是“他为什么买、哪个维度起了决定性作用”。决策树能给出规则但规则的碎片化让每个特征的边际贡献难以量化逻辑回归能给出系数但对多维离散特征做交叉解释时系数语义会漂移。朴素贝叶斯在这种需求下反而最顺手它把后验概率拆解成先验与似然的乘积每一维特征对购买决策的独立贡献都可以直接读出概率值这让“各维度概率”从口号变成了可落地的报表字段。这个标题背后是一个典型的分类任务以用户属性、车辆信息、历史保单、渠道触点等特征为输入预测购险行为的发生概率并拆解各特征的概率贡献。朴素贝叶斯的意义不在于赢得精度竞赛而在于它天然适配“概率可解释”的要求。做数据工程的人拿到这个标题第一反应不是调参炼丹而是先想清楚特征空间怎么离散化、先验怎么估计、平滑系数对概率表的影响边界在哪。下文的方案围绕 sklearn 的多项式朴素贝叶斯展开覆盖从数据清洗到概率表产出的完整链路中间穿插离散化、平滑、结果校验这些实际项目中一定绕不开的细节。2. 从原始数据到“各维度概率表”车险特征工程的三个关键动作2.1 车险购买预测到底需要哪些特征维度车险购买行为的数据在现实中通常散落在三个系统里承保系统的历史保单、CRM 系统的用户画像、渠道系统的行为日志。拼到一起后特征大致可以归为四组。用户属性组包括年龄、性别、驾龄、职业类别、收入档位车辆属性组包括车辆品牌、车型价位、车龄、新车或过户车、是否新能源历史行为组包括过去三年出险次数、理赔金额档位、上次续保距今天数、是否在其他保险公司承保过渠道触达组包括近 30 天登录 APP 次数、最近一次报价时间、报价后是否点击支付、业务员跟进次数。这四组特征的特点是有序类别多、连续变量少、取值基本稳定非常适合直接喂给朴素贝叶斯。建模前有个前提需要确认这里的“购买”到底指什么。如果是新客户目标变量是“首单是否成交”如果是存量客户目标变量是“续保是否成功”。两者的特征分布差异很大存量客户的续保概率通常远高于新客户首单因此后续讨论按存量续保场景处理这也是车险分析中最常见的落地场景。2.2 连续特征离散化分箱边界比模型本身更影响概率稳定性朴素贝叶斯处理连续变量有两种路线高斯朴素贝叶斯假设特征服从正态分布计算似然时直接用概率密度值多项式朴素贝叶斯则要求输入是非负计数。车险场景里高斯假设往往站不住脚。以“上次续保距今天数”为例它的分布集中在 90 天、180 天、365 天这几个续保节点上中间段稀疏强行套正态分布会让似然估计失真。更稳妥的做法是把连续特征分箱转换成类别特征然后统一走多项式或伯努利模型。分箱边界的设计不推荐直接用 pandas 的cut按等距切分因为车险特征天然带有业务语义。价格、天数、金额这些量需要按业务规则切。近 30 天登录次数按 0、1-2、3-5、6-10、10 以上分驾龄按不足 1 年、1-3 年、3-5 年、5-10 年、10 年以上分出险次数直接按 0、1、2、3 次及以上分不需要再细分。人工分箱的另一个好处是后续产出的概率表可以直接落到业务人员的 Excel 里不用解释“归一化之后的值”是什么。import pandas as pd def brand_discretize(df): df df.copy() # 基于业务语义分箱而不是等距切分 df[age_band] pd.cut(df[driver_age], bins[0, 25, 35, 45, 60, 100], labels[25, 26-35, 36-45, 46-60, 60]) df[claim_band] df[claim_count].map( lambda x: 0 if x 0 else (1 if x 1 else (2 if x 2 else 3))) df[days_since_last_insure] pd.cut( df[days_to_expire], bins[-1, 30, 60, 90, 180, 9999], labels[30, 30-60, 60-90, 90-180, 180]) return df这段代码的逻辑需要注意三点。pd.cut的bins参数是左开右闭区间所以 -1 到 30 会把第 0 天包含进去避免边界值被丢弃。claim_count的映射用字典加 lambda比replace更直观也方便后续追加新档位。days_to_expire这里指的是距离保险到期日的天数不是距离上次续保的天数业务含义完全不同特征命名要区分开。分箱完成后需要检查每一档的样本量。任何一个档位的样本量低于总样本的 1%就要考虑合并相邻档位否则该档位的条件概率会因样本稀疏而剧烈波动。例如“60”年龄档如果样本占比只有 0.5%应该与“46-60”合并。这个检查放在分箱之后、建模之前是防止概率表后期出现极端值的有效手段。2.3 训练集与验证集切分车险场景的特殊时间约束普通分类任务随机切分即可但车险购买预测必须考虑时间窗口。用户购买行为与保单到期日强相关假设用 2024 年 1 月到 6 月的数据随机抽 20% 做验证验证集里既包含 2 月到期用户也包含 11 月到期用户模型会学到“只要快到期就会买”这种分布规律而忽视了真正的特征差异。正确做法是按到期月份切分用 2024 年 1 月到 5 月到期用户做训练用 6 月到期用户做验证。train df[df[expire_month] 6] valid df[df[expire_month] 6] feature_cols [age_band, gender, vehicle_price_band, claim_band, days_since_last_insure, channel] X_train train[feature_cols] y_train train[is_purchase] X_valid valid[feature_cols] y_valid valid[is_purchase] print(train[is_purchase].mean(), valid[is_purchase].mean())这段代码隐含了一个常被忽略的检查训练集和验证集的购险转化率应该接近。如果差异超过 3 个百分点说明时间窗口内存在促销或费率调整等外部扰动这时的验证分数会失真需要进一步按业务节奏切分。特征全部是类别型且已经做过标签编码或因子化处理所以X_train可以直接作为分类器的输入。3. 训练与概率拆解朴素贝叶斯各维度概率的计算逻辑与代码实现3.1 为什么用 MultinomialNB 而不是 GaussianNBscikit-learn 提供了三种朴素贝叶斯变体GaussianNB、MultinomialNB、BernoulliNB。车险购买预测的场景里MultinomialNB 是首选。原因在于特征的业务属性分箱后的类别特征可以视为“该特征在样本中出现的次数”符合多项式分布的建模假设。BernoulliNB 适合二元特征对“是否有过理赔”这类字段有效但“年龄档位”这种多类别特征就无法表达。GaussianNB 直接假设连续特征服从正态分布这在车险数据里几乎不成立前面已经解释过。另一个现实因素是 MultinomialNB 的predict_proba输出可以直接对应到“各维度概率”的需求它的内部计算本质上是先算每个类别的先验概率再算每个特征值在每个类别下的条件概率然后用贝叶斯定理合并。对业务方而言这几个值可以直接搬进数据看板。from sklearn.naive_bayes import MultinomialNB from sklearn.preprocessing import OneHotEncoder encoder OneHotEncoder(handle_unknownignore) X_train_enc encoder.fit_transform(X_train) X_valid_enc encoder.transform(X_valid) model MultinomialNB(alpha1.0, fit_priorTrue) model.fit(X_train_enc, y_train) train_prob model.predict_proba(X_train_enc) valid_prob model.predict_proba(X_valid_enc)OneHotEncoder 的handle_unknownignore必须设置否则验证集里出现训练集中没见过的类别值时transform会直接抛异常。fit_priorTrue表示从训练数据中估计先验概率即购买与未购买样本的占比如果业务上已知整体续保率约为 70%且训练集比率与实际情况偏差较大可改为fit_priorFalse并手动指定class_prior[0.3, 0.7]。注意一个坑MultinomialNB 要求输入非负值OneHotEncoder 的稀疏矩阵满足这个条件。如果用户自己做了标准化或者归一化就会出现负值报错。这个错误在官方文档中写得很清楚但实际项目中经常有人踩到。3.2 验证集上的指标AUC 之外还要看概率校准度分类任务的常规指标是准确率、召回率、F1但对车险购买概率分析来说准确率不是最重要的。保险定价关心的是“预测概率与真实频率是否一致”。如果模型对 1000 个用户给出 0.6 的购买概率那么这 1000 人实际购买率应该接近 60%否则概率值就不能直接用于定价与营销预算分配。from sklearn.metrics import roc_auc_score, brier_score_loss auc roc_auc_score(y_valid, valid_prob[:, 1]) brier brier_score_loss(y_valid, valid_prob[:, 1]) print(fAUC: {auc:.4f}, Brier Score: {brier:.4f})Brier Score 是概率预测校准度的标准度量值越小越好。一个有效的经验参考车险续保场景里AUC 在 0.7 左右已属可用Brier Score 低于 0.18 基本满足业务需求。如果 Brier Score 偏高多数情况不是模型问题而是特征分箱过细导致条件概率波动回到第 2 章调整分箱边界会更有效。3.3 提取先验概率与条件概率表MultinomialNB 训练完成后class_log_prior_保存了每个类别的对数先验概率feature_log_prob_保存了每个特征的对数条件概率。需要把它们还原成自然概率并逐一映射回原始特征名供业务方查看“各维度概率”。import numpy as np import pandas as pd feature_names encoder.get_feature_names_out(input_featuresfeature_cols) prior_prob np.exp(model.class_log_prior_) cond_prob np.exp(model.feature_log_prob_) prob_df pd.DataFrame({ feature: feature_names, prob_purchase: cond_prob[1], prob_non_purchase: cond_prob[0] }) prob_df[prob_ratio] prob_df[prob_purchase] / prob_df[prob_non_purchase] prob_df.sort_values(prob_ratio, ascendingFalse, inplaceTrue) prob_df.head(10)这段代码输出的prob_ratio是核心业务指标含义是“该特征取值下购买与不购买的似然比”。比值大于 1 说明该特征值对购买有正向驱动比如“近 30 天登录 6-10 次”对应的比值可能就是 2.3说明这类用户购买可能性是未购买者的 2.3 倍。需要注意这个比值是未经过先验调整的似然比最终后验概率需要乘上先验概率。业务沟通时直接讲“该维度购买概率是多少”用prob_purchase这一列而不是prob_ratio。feature_log_prob_中的数值做了对数化处理取指数后会出现极小值建议在导出时统一保留 4 位小数或按百分比展示避免表格里出现一堆科学计数法的长尾值。3.4 各维度概率的归一化问题拿到条件概率表后一个容易误读的细节是所有特征的条件概率之和并不等于 1。因为每个特征被 OneHotEncoder 拆成了多列而feature_log_prob_是对所有展开后的列计算的。以“年龄档位”为例它被拆成 5 个二值列每个二值列在购买样本中的概率是“该年龄档位用户数 / 购买样本总数”而不是年龄维度的归一化概率。解读概率表时要对同一特征下不同取值做归一化处理prob_df[feature_base] prob_df[feature].str.split(_).str[0] prob_df[prob_purchase_norm] prob_df.groupby(feature_base)[prob_purchase].transform( lambda x: x / x.sum())加了归一化概率后业务方可以直接看到在“出险次数”这一维度上0 次出险的用户购买概率贡献达到 41%1 次出险 33%2 次 18%3 次以上 8%。这个值比原始条件概率更直观。需要注意的是归一化后不同特征之间不能直接比较数值大小因为每个特征的类别个数不同类别少的特征归一化后单值天然偏高。4. 从概率表到业务动作特征贡献排序与流量分层朴素贝叶斯最容易被低估的能力是“特征贡献排序”。树模型能给出 feature_importance但那是一个整体指标不能回答“某类用户为什么被预测为高概率”。朴素贝叶斯的条件概率表天然支持反事实分析把某个特征取值替换为基准值对比前后验概率的变化幅度即为该特征的边际贡献。def counterfactual_prob(feature_name, current_value, base_value, X_orig): X_counter X_orig.copy() # 将指定特征替换为基准值 X_counter[feature_name] base_value X_counter_enc encoder.transform(X_counter) prob_changed model.predict_proba(X_counter_enc)[:, 1] return prob_changed - model.predict_proba(encoder.transform(X_orig))[:, 1] # 示例将 近30天登录次数 从 6-10 次改为 0 次 effect counterfactual_prob(app_login_band, 6-10, 0, X_valid[[app_login_band]].head(100)) print(effect.mean())这种反事实分析可以直接帮助运营制定触达策略如果“报价后点击支付”特征的边际贡献为正 0.12那么投放资源推动报价未支付的用户完成支付就是性价比最高的动作。比无序推送优惠券更精准。实际项目交付时对应的 zip 包应该包含以下内容训练脚本train.py、特征分箱配置config.py、概率表导出probability_report.csv、模型持久化文件model.pkl、以及README.md说明运行顺序。模型持久化使用joblib.dump(model, model.pkl)加载时要注意 sklearn 版本一致性问题版本不一致会导致加载失败。如果不希望业务方安装 Python 环境可以将概率表导出到 Excel配合透视表做自助分析这也是 zip 工程包里最常见的交付形式。对“各维度概率”的结果做业务落地时建议按特征贡献度做一次聚类分群高概率高贡献用户、高概率低贡献用户、低概率低贡献用户。每类用户的运营策略差异很大高概率高贡献用户只需要在到期前做自动化续保提醒低概率低贡献用户则需要人工跟进或放弃。朴素贝叶斯在这里的价值不只是算概率而是把概率化结果变成清晰的操作指令。本文还有配套的精品资源点击获取