
简介面向保险行业数据分析与机器学习初学者这份实战资源基于神经网络与支持向量机SVM提供了一套完整的保险风险预测方案。资源完整覆盖数据预处理、模型训练、参数调优与评估环节帮助读者掌握用机器学习处理高维、非线性风险数据的方法理解保诚公司真实保险数据中的关键风险因子。压缩包共7个文件包含3个Python脚本、3个CSV数据文件和1个txt说明文档整体约37.1MB脚本分别对应数据清洗与特征构造、SVM建模、神经网络建模数据文件提供训练集、测试集及预测集便于直接复现实验。已有43人学习浏览。通过实操可快速搭建可运行的保险风险预测流程学会用混淆矩阵、精确率、召回率等指标评估模型效果并在此基础上优化保费定价、识别高风险客户提升实际业务决策能力。1. 保险风险预测不是黑匣子保诚数据想让你预测什么把保险申请人的风险评级交给机器学习算法听起来像玄学但保诚这份数据把它做成了非常具体的监督学习任务。训练集接近六万条每行对应一个申请人的体检指标、产品信息和医疗史目标列 Response 是 1 到 8 的整数风险分级。保险公司核保员每天做的就是这件事判断这个人该不该加费、能不能承保。前馈神经网络能自动从几十列特征里找组合SVM 在样本量不算夸张时能给出稳定边界两个模型在同一份验证集上对比 PR-AUC才是这场建模真正见真章的地方。这套流程适合正拿着分类数据想落地、又不愿意一上来就堆深度模型的从业者照着走能直接跑通。2. 读数据先于选模型字段含义与 SVM、神经网络路线的取舍2.1 保诚数据长什么样Response 是序数标签不是回归目标这份源自保诚公司竞赛的保险风险预测数据集训练集大约六万条test 集两万条左右字段可以粗略分成三组。第一组是连续型体检指标比如申请人的年龄、身高、体重第二组是类别型的产品与就业信息保诚把很多字段脱敏成了整数编号有些编号只是身份代号大小没有连续含义第三组是医疗史和家族病史列数非常多缺失率也很高。目标变量 Response 的取值范围是 1 到 8数字越大表示承保风险越高它是一个有序多分类标签不是连续值。import pandas as pd df pd.read_csv(train.csv) print(df.shape) # 总行数和总列数 print(df[Response].value_counts(sortFalse))这段代码打印的计数通常会显示低风险段1 到 4占大头5 到 8 的高风险段明显稀疏尤其是 8 类样本很少。Response 的序数特性意味着 3 和 4 的差距不能简单等同于 4 和 7 的差距直接把它当回归目标去拟合是新手最常见的翻车操作。常见做法是压成二分类4 以下算低风险5 及以上算高风险这样 SVM 和神经网络都能在类别相对清晰的边界上工作如果业务上必须保留 1 到 8 的细粒度就要走有序多分类路线而不是普通 8 分类 softmax。先处理掉 Id 列也很关键它只是行号放进特征里会让模型学出「某一行编号对应某个风险」的假规律。另外保诚的医疗史列缺失并不全是噪声很多申请人整块医疗史为空可能本身就是一种低风险信号。所以缺失值的处理策略不是一刀切删列而是要保留「缺失状态」这个信息后面的特征工程会用到这个判断。2.2 评估协议先行PR-AUC 比准确率更诚实一旦决定做二分类就要先定评估指标。准确率在这个数据集上是不可信的因为类别天然倾斜。假设少数类只占两成即使全部样本都预测为低风险准确率也能到七成以上表面分数好看实际对高风险人群完全失效。更合理的指标是 PR-AUCPrecision-Recall AUC或 ROC-AUCROC 在极度不平衡时容易过于乐观PR 曲线能更直接反映「在低命中率预算下的召回收益」所以这类保险核保场景习惯优先看 PR-AUC。PR-AUC 有一个很实用的锚点随机模型的 PR-AUC 等于正样本比例。也就是说如果正样本占 0.2那么模型分数低于 0.2 还不如随机猜模型从 0.2 提升到 0.5等于把少数类的命中效率提升了 2.5 倍。这个数字比 accuracy 从 0.7 到 0.72 要诚实得多也方便给业务方解释。定义好评估协议再动手还有一层意义SVM 和神经网络在保诚数据集上的差距往往不是天壤之别真正的分化点在于谁对少数类的召回更高、谁的概率曲线更平滑。两个模型对比时用同一个划分、同一个评估函数才有可比性。这个习惯会在第 4.3 节落到代码上。2.3 为什么是 SVM 加神经网络一个吃小样本边界一个吃特征交叉SVM 的核心能力是在特征空间中找一个最大间隔超平面它依赖核函数把非线性问题映射到高维空间。对六万条、几十列的数据量RBF 核依然可行只是计算成本偏高SVM 对特征标准化极为敏感未归一化的连续列会让支持向量机被个别大数值列带走这也是我在工程上坚持在特征工程阶段统一做 StandardScaler 的原因。神经网络的优势则是自动做特征交叉比如「年龄偏大且医疗史缺失」这种组合对风险的联合影响前馈网络可以通过隐藏层自己学出来。但它需要关注学习率、批次和早停训练代价更大。两个模型不是竞争关系而是接力SVM 先给出一个可解释、可复现的基线神经网络再去尝试抬高原有的 PR-AUC。周志华《机器学习》里那句「没有免费的午餐」在这里很适用——不同数据规模、不同特征质量下模型没有绝对优劣先跑通基线再谈优化。提示数据读出后先不要急着建模写下任务定义二分类或有序分类、评估指标PR-AUC和验证协议分层切分。这三点定了后面的建模和调参才不是无效劳动。3. 特征工程决定上限缺失值填补、分箱、标准化与筛选3.1 删 ID、填缺失、把类别列变成模型认得的数字保诚数据里有很多脱敏的类别编号列它们大多是整数。这里有个容易踩的坑整数不一定是连续量比如产品信息列里的 1、2、3 可能只是三个互不相关的产品代码直接当数值用会给 SVM 制造不存在的顺序关系。# 读取之后第一步Id 只是行号直接删掉 df df.drop(columns[Id]) # 把 Response 拆出来当标签不参与特征工程 y (df[Response] 5).astype(int) X df.drop(columns[Response]) # 看看哪些列缺失率超过 30% missing_ratio X.isna().mean().sort_values(ascendingFalse) print(missing_ratio[missing_ratio 0.3])这条逻辑的关键是先把 Response 从特征表里摘出去防止后续任何转换「不小心」用到标签信息。缺失率打印出来以后你会发现医疗史那批列大多集中在高位这是保诚数据非常典型的形态后面会统一处理。# 类别型列缺失就填 missing数值型列缺失统一填 -1 for col in X.columns: if X[col].dtype object: X[col] X[col].fillna(missing) else: X[col] X[col].fillna(-1)为什么数值缺失不填均值而填 -1因为缺失在这里有业务含义均值填充会把「申请人没有医疗记录」这种信息抹平成「正常数值」SVM 会因此丢失一个非常强的判别信号。-1 作为一个明显的离群占位值能让支持向量机在边界学习时把它当作一个独立状态对待。类别型缺失填missing字符串是为了后续编码时保留这个类别。3.2 连续列标准化SVM 和神经网络对尺度的敏感度不一样SVM 的间隔计算依赖特征向量的内积如果一个列的量纲是另一个的几百倍那个大数值列就会主导距离计算。神经网络这边虽然 Batch Normalization 能缓解一部分问题但输入层尺度差距过大仍然会让 Adam 在早期走很多弯路。所以标准化要放在模型训练之前而不是之后。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)参数说明stratifyy保证切分后训练集和验证集的类别比例与全量一致这对少数类极少的数据集至关重要random_state42固定随机种子让后面所有对比实验都能复现。fit_transform只在训练集上调用测试集只transform是为了避免验证集统计量泄漏进特征分布。需要注意的是这里返回的是 NumPy 数组列名丢了。为了后面特征筛选方便我会把它重新包回 DataFrame保留原列名。这是一个小习惯能帮你少走很多弯路。3.3 先用 L1 或树模型筛特征让 SVM 的核矩阵更干净保诚数据的列数不少医疗史那几十列里很多是稀疏的。RBF 核要把每个样本两两计算相似度特征维度越高噪声越多核矩阵越容易被不相关维度污染。常见做法是先做一轮特征筛选选出最重要的 40 列左右再喂给模型。import pandas as pd from sklearn.ensemble import RandomForestClassifier X_train_df pd.DataFrame(X_train_scaled, columnsX_train.columns) X_test_df pd.DataFrame(X_test_scaled, columnsX_test.columns) rf RandomForestClassifier(n_estimators200, random_state42, n_jobs-1) rf.fit(X_train_df, y_train) importance pd.Series(rf.feature_importances_, indexX_train_df.columns) top40 importance.sort_values(ascendingFalse).head(40).index print(top40.tolist()) X_train_top X_train_df[top40] X_test_top X_test_df[top40]n_estimators200是经验值树太少重要性估计不稳树太多训练时间线性上涨对六万样本、几十列特征200 棵已经完全够用。为什么要选 40 而不是全部列因为保诚的医疗史列高度稀疏头部 40 列通常已经覆盖了 95% 以上的重要性剩下的列留着只会增加核矩阵噪声。除了随机森林也可以换 Lasso 或带 L1 惩罚的线性 SVM 做筛选思路完全一样——先筛出重要维度再让 RBF 核在更干净的几何空间里工作。如果发现筛选后 PR-AUC 反而下降说明被删掉的列里有和标签弱相关但与现有特征组合后有用的列这时可以把候选列数量从 40 放宽到 80 再试一轮。4. 从 SVM 基线到 MLP两类模型的实现、调参与公平对比4.1 SVM 的两个必调旋钮C 和 gamma以及概率输出怎么开SVM 在保诚数据上的第一个版本我一般用线性核跑因为六万样本对这个量级来说已经不算小RBF 核的核矩阵计算会随样本数平方增长直接全量跑容易让调参周期变成几天。线性 SVM 几秒就能出一个基线先拿它验证特征工程有没有问题。from sklearn.svm import LinearSVC linear_svm LinearSVC(class_weightbalanced, random_state42) linear_svm.fit(X_train_top, y_train)这里有个关键细节LinearSVC没有predict_proba要出概率得包一层概率校准。另一种常见做法是直接用SVC(kernellinear, probabilityTrue)但它的训练用的是 libsvm在六万样本上线性核也会比LinearSVC慢很多所以我通常先用LinearSVC跑分数最后统一校准。from sklearn.calibration import CalibratedClassifierCV calibrated_svm CalibratedClassifierCV(linear_svm, cv3) calibrated_svm.fit(X_train_top, y_train) y_prob_svm calibrated_svm.predict_proba(X_test_top)[:, 1]cv3表示用 3 折交叉验证来拟合概率校准器训练量会放大三倍但六万样本完全扛得住。概率校准对保险场景很有用核保员不仅要排序还要知道「这个人的高风险概率到底是多少」。RBF 核的正确打开方式不是全量硬跑而是先抽样调参。from sklearn.svm import SVC X_sub X_train_top.sample(n5000, random_state42) y_sub y_train.loc[X_sub.index] rbf_svm SVC( kernelrbf, class_weightbalanced, probabilityTrue, random_state42, C10, gammascale, ) rbf_svm.fit(X_sub, y_sub)这段示例代码讲解了「先子集、后全量」的调参顺序。gammascale会让 libsvm 根据特征方差自动缩放核宽是稳妥的默认值C10表示软间隔的惩罚强度C 越大越强调分类正确越小越允许误分对不平衡数据先从 1 到 10 试起。SVM 的训练本质上是合页损失加上正则项做二次规划求解和神经网络一样能找到梯度下降视角的解释但在 sklearn 里你不需要手写优化器只需要知道 C 和 gamma 是主要旋钮就够了。4.2 三层前馈网络ReLU、Dropout、批次与早停怎么摆神经网络这边我用的是 Keras 搭一个三层前馈网络输入维度由上一轮的X_train_top.shape[1]决定。为什么从三层开始一层太浅学不出特征交叉四层以上在六万样本上容易过拟合三层是这类表格数据的稳妥起点。import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers model keras.Sequential([ layers.Input(shape(X_train_top.shape[1],)), layers.Dense(128, activationrelu), layers.Dropout(0.3), layers.Dense(64, activationrelu), layers.Dropout(0.3), layers.Dense(1, activationsigmoid), ]) model.compile( optimizerkeras.optimizers.Adam(learning_rate1e-3), lossbinary_crossentropy, metrics[AUC], )128和64是两个隐藏层的宽度取 2 的幂是为了让 GPU/CPU 的矩阵分块更高效但没有硬性规定必须这样。Dropout(0.3)放在每个隐藏层后面意思是每轮训练随机丢弃 30% 的神经元防止网络死记训练集。binary_crossentropy对应二分类 sigmoid 输出如果之后改做 8 分类有序问题输出层要换成Dense(8, activationsoftmax)损失换成sparse_categorical_crossentropy。history model.fit( X_train_top, y_train, epochs30, batch_size256, validation_data(X_test_top, y_test), callbacks[keras.callbacks.EarlyStopping(patience5, restore_best_weightsTrue)], )batch_size256对六万样本来说每个 epoch 大约 230 步既不会因为批次太小导致梯度震荡也不会因为批次太大吃满内存。patience5表示验证损失连续 5 个 epoch 不下降就停restore_best_weightsTrue会把权重回滚到验证集上最好的那一步这是对抗过拟合的后悔药。4.3 同一份验证集、同一个 PR-AUC两个模型的分数才算数模型都训完以后真正要做的是把它们放在同一个裁判面前。我见过太多项目 SVM 和神经网络各跑各的验证集最后分数根本没法比。from sklearn.metrics import precision_recall_curve, auc def pr_auc(y_true, y_score): precision, recall, _ precision_recall_curve(y_true, y_score) return auc(recall, precision) print(Calibrated LinearSVM PR-AUC:, pr_auc(y_test, y_prob_svm)) print(RBF-SVM PR-AUC:, pr_auc(y_test, rbf_svm.predict_proba(X_test_top)[:, 1])) print(MLP PR-AUC:, pr_auc(y_test, model.predict(X_test_top).ravel()))这段代码的要点是三个模型全部在X_test_top和y_test上计算没有谁有特权。precision_recall_curve会按不同阈值算出一串精确率和召回率auc(recall, precision)的曲线下面积就是 PR-AUC。如果 RBF 模型是用子集训的记得先在全量X_train_top上重训再评估否则分数会被低估。5. 避坑自查清单五个让保险风险预测翻车的常见错误5.1 数据泄漏StandardScaler 在全量 fit验证分数一路虚高现象是交叉验证的 PR-AUC 高得离谱但换成新数据立刻跌回普通水平。原因几乎都是同一个有人在划分训练集和测试集之前就拿着全量数据做了StandardScaler.fit_transform测试集的均值和方差已经混进了模型训练的信息SVM 对尺度又极其敏感所以每个折都被「剧透」了。解决方法是把标准化放进 Pipeline或者严格先切分再 fit。我个人更推荐 Pipeline因为它在交叉验证内部会对每一折重新拟合 scaler从机制上杜绝泄漏。from sklearn.pipeline import make_pipeline pipe make_pipeline( StandardScaler(), LinearSVC(class_weightbalanced, random_state42), ) pipe.fit(X_train_top, y_train)5.2 准确率陷阱全预测低风险也能有 70% 准确率现象是测试准确率 0.72业务方很高兴但 PR-AUC 只有 0.3高风险人群基本没抓住。原因是类别分布天然倾斜准确率被多数类完全主导一个全预测低风险的傻瓜模型也能拿高分。解决方式是关注少数类的召回和 PR-AUC同时给模型加类别权重。class_weightbalanced会自动按反比给少数类更高的惩罚权重让 SVM 的边界不整体偏向多数类。神经网络里也一样可以在model.fit里传class_weight{0: 1.0, 1: pos_ratio}。5.3 SVM 核方法训练慢到像卡死先跑子集再全量现象是 RBF 核的 SVC 丢进去以后CPU 跑了几小时没有动静控制台看起来像死机。原因是 libsvm 的核矩阵计算随样本量平方增长六万样本、几十个特征加概率输出时间是线性核的好几十倍。解决方式是先用 5000 条随机子集把 C 和 gamma 调好再决定要不要全量重训。如果全量还是太慢就退回线性核或改用SGDClassifier(losshinge)用合页损失的梯度下降方式训练线性 SVM六万样本跑起来也很快。5.4 神经网络损失震荡先查输入尺度再降学习率现象是训练 loss 前几步降得很快后面开始上下弹验证集 AUC 跟着一起抖。原因通常是两个一是输入特征没有标准化某些大数值列把梯度方向带偏二是学习率 1e-3 对这个任务来说偏高Adam 的动量在陡峭的 loss 面上刹不住车。解决方式是重新确认标准化是否在 Pipeline 里生效然后把学习率降到 3e-4 再跑一轮同时把早停 patience 调到 5 以上。如果验证 loss 能稳定下降但训练集和验证集差距越拉越大说明 Dropout 比例过低可以试着从 0.3 提到 0.5。5.5 Response 被当连续回归序数标签不是回归目标现象是有人直接把 Response 1 到 8 当回归目标训练完预测出 3.7 这种不存在的风险等级MSE 还看不出问题。原因是序数标签之间的距离没有数学意义3 到 4 的差距不等于 5 到 7 的差距而且大多数样本集中在低风险段回归模型会被众数拉偏。解决方式是回到第 2.1 节的定义要么做二分类要么做有序多分类。硬间隔 SVM 要求数据完全线性可分保诚这批数据几乎不可能所以必须走软间隔加类别权重的路线如果把序数信息丢掉直接做 8 分类 softmax也要在评估时按「预测等级是否落在相邻区间」来折算误差而不是只算精确匹配率。6. 验证曲线与三个进阶动作判断模型能不能上线6.1 学习曲线欠拟合还是数据不够一画便知把训练集大小从 10% 到 100% 拉一个梯度分别记录训练集和验证集的 PR-AUC。如果两条曲线最终收敛到同一个低分是欠拟合加数据没用要加特征或换模型如果训练集分数高、验证集分数低且差距随数据量增加而收窄是典型过拟合加数据或加大正则项有效。train_sizes, train_scores, val_scores learning_curve( pipe, X_train_top, y_train, cv5, train_sizesnp.linspace(0.1, 1.0, 5), scoringroc_auc, )6.2 校准曲线SVM 的概率输出不是概率SVC(probabilityTrue)内部用 Platt 缩放把决策边界的距离映射成概率排序没问题但绝对值会过度自信。上线前用CalibratedClassifierCV包裹一层或对神经网络做 Temperature Scaling把预测概率拉回真实置信区间。核保场景里概率要用来定保费这一步不能省。6.3 融合与兜底把 MLP 和 SVM 的分数做加权平均我的习惯是先算两个模型预测分数的相关性相关性低于 0.8 时做加权平均才有意义否则融合只是取了个平均数。等权融合之后再叠一个规则兜底比如原始特征里Response 7历史样本极少如果 SVM 和 MLP 给的概率都高但置信度矛盾就按更保守的一侧出结果。这个习惯已经帮我救回过两次上线前的高风险漏判。希望这些验证步骤和踩坑记录能帮你在保险风险预测这条路上少走弯路也祝你跑通自己的管线。本文还有配套的精品资源点击获取