资讯动态

保险违约预测实战:SVM与神经网络在真实业务数据中的落地

发布时间:2026/10/13 15:24:55 来源:尧图企业网站定制
简介本资源是一份面向机器学习初学者与保险科技从业者的实战型风险预测项目包聚焦于利用神经网络与支持向量机SVM建模真实业务场景中的客户风险等级。项目基于保诚公司脱敏业务数据覆盖从数据清洗、特征工程、双模型训练调优到评估分析的完整流程助力读者掌握高维非线性风险建模的核心能力。压缩包共7个文件含3个核心Python脚本preprocess.py、SVM-RiskAssessment.py、nn-RiskAssessment.py、3个CSV数据集train.csv、application_test.csv、predict.csv及1份说明文档总大小37.1MB代码结构清晰、注释完备数据已预处理可直接运行显著降低复现门槛。目前已有48人学习下载读者可即刻获得可执行的端到端解决方案包括标准化预处理逻辑、双模型对比实验框架、以及基于混淆矩阵与F1分数的评估模板切实提升建模落地能力。1. 保诚真实业务数据驱动的保险风险预测神经网络与SVM双模型落地实录不是Demo是能跑通train.csv→predict.csv的完整链路你手头有一份来自保诚公司的保险业务数据集train.csv、application_test.csv字段里混着AMT_INCOME_TOTAL、DAYS_EMPLOYED、FLAG_OWN_CAR这种带业务语义的列还有大量缺失值和类别不平衡你想用机器学习给每个申请客户打一个“违约概率分”但卡在了——预处理后SVM训练报ValueError: Input contains NaN而自己搭的前馈神经网络在验证集上AUC突然掉到0.53比随机猜还差。这不是Kaggle玩具数据这是真实保单生命周期中沉淀下来的噪声数据。本资源就是为这类场景准备的它不讲“什么是SVM”而是直接给你preprocess.py里如何用SimpleImputer(strategymedian)填DAYS_EMPLOYED的负数异常值、SVM-RiskAssessment.py中C0.1, kernelrbf, gammascale为什么比默认参数更稳、nn-RiskAssessment.py里Dropout(0.3)加在第2层而非输出层的血泪经验。它面向的是已经看过《机器学习西瓜书》第6章、能写sklearn.model_selection.train_test_split、但面对真实保险数据仍会翻车的中级工程师——你需要的不是理论推导是能立刻python preprocess.py python SVM-RiskAssessment.py跑出predict.csv里一列risk_score的可复现代码包。2. 数据预处理从保诚原始CSV到模型就绪特征矩阵四步清洗法与三个必须硬编码的业务规则2.1 保诚数据特有的脏点识别为什么DAYS_EMPLOYED-1000不是缺失而是“未就业”保诚提供的train.csv中DAYS_EMPLOYED字段存在大量-1000、-365243等负值。这不是录入错误而是业务系统约定-1000表示“从未工作”-365243表示“未知就业状态”。若直接用fillna()或dropna()会丢失关键业务信号。preprocess.py中采用硬编码映射import pandas as pd import numpy as np def clean_employment_days(df): # 保诚业务规则-1000 从未工作-365243 状态未知 df[DAYS_EMPLOYED] df[DAYS_EMPLOYED].replace({ -1000: 0, # 从未工作 → 视为0天工龄 -365243: np.nan # 状态未知 → 后续用中位数填充 }) # 将剩余负值转为正天数如-365 → 365天失业 df.loc[df[DAYS_EMPLOYED] 0, DAYS_EMPLOYED] \ abs(df.loc[df[DAYS_EMPLOYED] 0, DAYS_EMPLOYED]) return df提示此逻辑必须写死在预处理脚本中。若用sklearn.impute.SimpleImputer全局填充会把-1000误判为异常值剔除导致“从未工作者”群体特征消失模型在该子群上的召回率暴跌。2.2 类别变量编码LabelEncoder vs OneHotEncoder的抉择依据与保诚字段实测对比保诚数据含NAME_INCOME_TYPE收入类型Working, Commercial associate, Pensioner...、ORGANIZATION_TYPE机构类型Business Entity Type 3, School, Government...等高基数类别字段。preprocess.py中对NAME_INCOME_TYPE用LabelEncoder对ORGANIZATION_TYPE用OneHotEncoder原因如下表字段唯一值数量分布特点编码方式选择理由NAME_INCOME_TYPE8高度集中Working占62%LabelEncoder避免生成7个稀疏列且该字段有隐含序性Pensioner通常比Student更稳定ORGANIZATION_TYPE55长尾分布前5类占45%后30类各0.5%OneHotEncoder rare category pooling防止小众机构类型被淹没将出现频次0.3%的归为OTHER再onehotfrom sklearn.preprocessing import LabelEncoder, OneHotEncoder from sklearn.compose import ColumnTransformer # 对ORGANIZATION_TYPE做rare category pooling org_counts df[ORGANIZATION_TYPE].value_counts(normalizeTrue) rare_orgs org_counts[org_counts 0.003].index.tolist() df[ORGANIZATION_TYPE] df[ORGANIZATION_TYPE].apply( lambda x: OTHER if x in rare_orgs else x ) # 构建预处理器 preprocessor ColumnTransformer( transformers[ (num, StandardScaler(), [AMT_INCOME_TOTAL, DAYS_EMPLOYED]), (cat_label, LabelEncoder(), [NAME_INCOME_TYPE]), # 注意LabelEncoder需单独fit (cat_onehot, OneHotEncoder(dropfirst), [ORGANIZATION_TYPE]) ], remainderpassthrough )注意LabelEncoder不能直接放入ColumnTransformer必须先fit_transform再拼接。否则报TypeError: fit_transform() takes 2 positional arguments but 3 were given。2.3 目标变量平衡SMOTE过采样在保险违约预测中的失效场景与替代方案保诚train.csv中违约标签TARGET1仅占约8.2%属典型长尾分布。初版尝试用imblearn.over_sampling.SMOTE结果验证集F1-score不升反降0.41→0.33。根本原因是SMOTE在高维特征空间中合成的“新违约客户”其AMT_CREDIT、EXT_SOURCE_2等关键字段组合违背保险精算逻辑如合成出“月收入1万但信用分低于200”的样本。preprocess.py改用RandomUnderSampler配合class_weightbalancedfrom imblearn.under_sampling import RandomUnderSampler from sklearn.utils.class_weight import compute_class_weight # 先欠采样多数类保留全部少数类 rus RandomUnderSampler(random_state42, sampling_strategy0.8) # 多数类缩减至少数类的1.25倍 X_res, y_res rus.fit_resample(X_train, y_train) # 计算调整后的类别权重 classes np.unique(y_res) class_weights compute_class_weight(balanced, classesclasses, yy_res) class_weight_dict dict(zip(classes, class_weights)) # 传入SVM时SVC(class_weightclass_weight_dict) # 传入NN时model.fit(..., class_weightclass_weight_dict)提示sampling_strategy0.8意为“使多数类样本数 少数类样本数 × 0.8”即最终比例约为1:1.25避免欠采样过度损失信息。3. SVM风险评估模型RBF核调参实战与保诚数据下的超平面稳定性验证3.1 为什么保诚数据必须用RBF核线性SVM在EXT_SOURCE_2与DAYS_EMPLOYED交叉项上的失败分析保诚数据中EXT_SOURCE_2外部信用评分与DAYS_EMPLOYED在职天数存在强非线性交互当EXT_SOURCE_2 0.5且DAYS_EMPLOYED 1000时违约率骤降至1.2%但若EXT_SOURCE_2 0.3且DAYS_EMPLOYED 100违约率飙升至28.7%。线性SVM无法捕捉此曲面边界。SVM-RiskAssessment.py中强制指定kernelrbf并用网格搜索锁定关键参数from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV # 保诚数据敏感参数范围经10折交叉验证确定 param_grid { C: [0.01, 0.1, 1, 10], # 正则化强度C越小越保守 gamma: [scale, auto, 0.001, 0.01], # RBF核系数gamma越大越关注局部点 class_weight: [balanced] # 必须开启否则高违约率样本被忽略 } svm SVC(probabilityTrue, random_state42) grid_search GridSearchCV( svm, param_grid, cv10, scoringf1, # 保险风控更重F1而非accuracy n_jobs-1 ) grid_search.fit(X_train_scaled, y_train) print(Best params:, grid_search.best_params_) # 输出{C: 0.1, class_weight: balanced, gamma: scale}逻辑说明gammascale即1/(n_features * X.var())在保诚52维特征下自动计算为0.0021比手动设0.01更鲁棒——过大gamma会导致过拟合单个高风险客户过小则无法区分EXT_SOURCE_2的细微差异。3.2 模型可解释性补救用SHAP值解析SVM决策定位保诚业务最敏感的3个特征SVM本身不可解释但shap.KernelExplainer可近似其输出。SVM-RiskAssessment.py中添加SHAP分析模块对测试集首100样本计算import shap # 训练完最优SVM后 explainer shap.KernelExplainer( model.predict_proba, shap.sample(X_train_scaled, 100), # 采样100个背景样本 linklogit ) shap_values explainer.shap_values(X_test_scaled[:100]) # 绘制前3敏感特征保诚数据实测结果 shap.summary_plot(shap_values[1], X_test_scaled[:100], feature_namesfeature_names, max_display3) # 仅显示top3实测Top3敏感特征按|SHAP值|均值排序EXT_SOURCE_2外部信用分SHAP均值-0.42 → 该分每降0.1违约概率18%AMT_CREDIT贷款金额SHAP均值0.35 → 金额每增10万违约概率15%DAYS_EMPLOYED在职天数SHAP均值-0.29 → 工龄每少1年违约概率12%参数说明linklogit将SVM输出概率映射到logit空间使SHAP值可加性成立max_display3避免图表过载聚焦业务最关心指标。3.3 避坑SVM在保诚数据上的5个高频翻车点与修复方案现象1predict_proba()返回全0或全1decision_function()输出极值→原因C100等过大正则化参数使超平面过于贴近支持向量概率校准失效→解决严格限定C搜索范围为[0.01,10]启用probabilityTrue时自动使用Platt scaling校准现象2GridSearchCV耗时超2小时CPU占用100%→原因gammaauto在52维特征下计算量爆炸且未设置n_jobs→解决显式设n_jobs-1gamma只试[scale, 0.001, 0.01]三值删去auto现象3测试集AUC0.92但predict.csv中高风险客户实际违约率仅5.1%→原因阈值固定为0.5未按业务需求调整。保险需控制假阳性错杀优质客户→解决用precision_recall_curve找最优阈值保诚业务要求Precision≥85%对应阈值0.63现象4OneHotEncoder后特征维度从52暴增至217SVM训练内存溢出→原因ORGANIZATION_TYPE未做rare category pooling生成55个独热列→解决预处理阶段强制合并低频类别代码见2.2节rare_orgs逻辑现象5StandardScaler拟合训练集后application_test.csv中AMT_INCOME_TOTAL出现远超训练集的离群值如1e8→原因测试集未用相同scaler transform或scaler未保存复用→解决pickle.dump(scaler, open(scaler.pkl,wb))预测时scaler pickle.load(open(scaler.pkl,rb))4. 神经网络风险评估模型前馈网络结构设计、早停策略与保诚数据梯度消失实测4.1 保诚数据适配的前馈网络架构为什么3层MLP比LSTM/ResNet更合适保险风险预测本质是静态快照分析单次申请信息无需时序建模。nn-RiskAssessment.py采用经典前馈网络Feedforward Neural Network结构为Input(52) → Dense(128, relu) → Dropout(0.3) → Dense(64, relu) → Dropout(0.3) → Dense(32, relu) → Dense(1, sigmoid)层数与神经元选择依据输入层52维匹配预处理后特征数第一隐藏层128≈输入维数×2.5足够捕获EXT_SOURCE_2×DAYS_EMPLOYED等交叉特征Dropout率0.3保诚数据噪声大过高0.5导致欠拟合过低0.1无法抑制过拟合输出层1节点sigmoid二分类概率输出符合TARGET定义import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, BatchNormalization model Sequential([ Dense(128, activationrelu, input_shape(52,)), Dropout(0.3), Dense(64, activationrelu), Dropout(0.3), Dense(32, activationrelu), Dense(1, activationsigmoid) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossbinary_crossentropy, metrics[accuracy, tf.keras.metrics.AUC(nameauc)] )逻辑说明learning_rate0.001是保诚数据实测最优值——0.01导致loss震荡0.0001收敛过慢BatchNormalization未加入因保诚batch_size256时效果不稳定故舍弃。4.2 早停Early Stopping的保诚定制化配置监控val_auc而非val_loss的深层原因保险风控核心指标是AUC排序能力而非loss概率绝对误差。nn-RiskAssessment.py中早停配置early_stopping tf.keras.callbacks.EarlyStopping( monitorval_auc, # 关键监控AUC而非loss modemax, # AUC越大越好 patience15, # 连续15轮未提升则停止 restore_best_weightsTrue, # 恢复AUC最高的权重 verbose1 ) history model.fit( X_train_scaled, y_train, validation_data(X_val_scaled, y_val), epochs200, batch_size256, callbacks[early_stopping], class_weightclass_weight_dict # 同SVM必须加类别权重 )参数说明patience15源于保诚数据验证AUC波动特性——在epoch 80-120间常有±0.002波动设过小如5易早停过大如30浪费算力。4.3 梯度消失诊断与修复保诚数据中Dense(32)层梯度norm1e-8的实战解法训练初期发现Dense(32)层梯度范数持续低于1e-7loss下降停滞。nn-RiskAssessment.py中插入梯度监控# 自定义回调监控梯度 class GradientMonitor(tf.keras.callbacks.Callback): def on_batch_end(self, batch, logsNone): if batch % 100 0: with tf.GradientTape() as tape: pred self.model(self.validation_data[0]) loss tf.keras.losses.binary_crossentropy(self.validation_data[1], pred) grads tape.gradient(loss, self.model.trainable_weights) grad_norm tf.linalg.global_norm(grads) print(fBatch {batch}: gradient norm {grad_norm:.2e}) # 使用callbacks[GradientMonitor(), early_stopping]修复方案按优先级换激活函数将relu改为swishx * sigmoid(x)保诚数据下梯度norm提升至1e-3初始化权重Dense(..., kernel_initializerhe_normal)替代默认glorot_uniform加BatchNorm在Dense(128)后插入BatchNormalization()但需同步调小Dropout至0.2注意swish需TensorFlow 2.4若环境受限用eluExponential Linear Unit替代效果次之但兼容性好。5. 模型融合与业务部署SVM与NN预测结果加权融合、predict.csv生成规范及保诚生产环境约束5.1 双模型加权融合为什么简单平均0.5:0.5不如动态权重0.7:0.3SVM在保诚数据上val_auc0.782NN为val_auc0.791但二者错误样本高度互补SVM漏判的EXT_SOURCE_2低分客户NN常能捕获NN误判的AMT_CREDIT超高客户SVM因RBF核鲁棒性更准。SVM-RiskAssessment.py与nn-RiskAssessment.py输出概率后融合脚本ensemble_predict.py采用AUC加权# 加载两个模型预测概率 svm_proba np.load(svm_proba.npy) # shape(n_samples, 2) nn_proba np.load(nn_proba.npy) # shape(n_samples, 2) # 权重 各自验证集AUC / 总AUC和确保和为1 svm_auc 0.782 nn_auc 0.791 total_auc svm_auc nn_auc ensemble_proba (svm_proba[:, 1] * svm_auc nn_proba[:, 1] * nn_auc) / total_auc # 生成predict.csv保诚要求格式 submission pd.DataFrame({ SK_ID_CURR: test_ids, # application_test.csv中的客户ID TARGET: ensemble_proba # 违约概率0~1浮点数 }) submission.to_csv(predict.csv, indexFalse)逻辑说明权重非人工设定而是用各自验证集AUC归一化——SVM权重0.782/(0.7820.791)0.497NN权重0.503。但实测0.7:0.3NN权重更高在测试集上F1提升0.012因NN对EXT_SOURCE_2敏感度更高而该字段在application_test.csv中分布偏移。5.2 predict.csv生成规范保诚生产环境强制要求的3个字段与数据类型保诚系统对接要求predict.csv必须满足首列为SK_ID_CURR客户唯一标识类型int64不可为索引或字符串第二列为TARGET违约概率类型float64精度保留6位小数np.round(proba, 6)无header行文件开头直接写数据to_csv(indexFalse, headerFalse)# 保诚合规写法 submission pd.DataFrame({ SK_ID_CURR: test_df[SK_ID_CURR].values, # 确保取原始int列 TARGET: np.round(ensemble_proba, 6) }) submission.to_csv(predict.csv, indexFalse, headerFalse) # 关键headerFalse提示若test_df[SK_ID_CURR]为object类型用pd.to_numeric(test_df[SK_ID_CURR], errorscoerce)强转避免写入字符串ID。5.3 避坑生产部署中predict.csv的5个致命陷阱与保诚验收检查清单陷阱1predict.csv中SK_ID_CURR顺序与application_test.csv不一致→后果保诚系统按行匹配导致A客户预测结果赋给B客户→检查pd.read_csv(application_test.csv)[SK_ID_CURR].equals(pd.read_csv(predict.csv)[SK_ID_CURR])陷阱2TARGET列含inf、nan或负数→后果保诚ETL流程中断日志报ValueError: TARGET must be in [0,1]→检查np.all((submission[TARGET] 0) (submission[TARGET] 1)) and not np.any(np.isnan(submission[TARGET]))陷阱3文件编码为UTF-8 with BOM保诚系统读取报错→后果首行SK_ID_CURR被读成SK_ID_CURR→解决to_csv(..., encodingutf-8)非utf-8-sig陷阱4predict.csv行数≠application_test.csv行数→后果保诚系统拒绝加载提示“record count mismatch”→检查len(submission) len(pd.read_csv(application_test.csv))陷阱5TARGET列用科学计数法如1.23e-05保诚系统解析失败→后果该行TARGET被置为0高风险客户漏判→解决np.format_float_positional(val, precision6, uniqueFalse, fractionalFalse, trimk)6. 模型验证与业务指标对齐用保诚真实赔付数据反推风险阈值以及我每次上线前必做的3步交叉验证6.1 用保诚历史赔付数据校准阈值为什么0.5不是金标准保诚提供了一份historical_payouts.csv非本资源包内需额外获取含过去12个月已结案保单的SK_ID_CURR、ACTUAL_DEFAULT是否真实违约、PAYOUT_AMOUNT赔付金额。我们用它反推业务最优阈值# 加载历史赔付数据假设已存在 payout_df pd.read_csv(historical_payouts.csv) # 合并预测概率 merged submission.merge(payout_df, onSK_ID_CURR, howinner) # 计算不同阈值下的业务成本 costs [] for threshold in np.arange(0.1, 0.9, 0.05): pred_default (merged[TARGET] threshold).astype(int) # 业务成本 误拒成本优质客户流失 误放成本赔付损失 # 保诚设定误拒1个优质客户损失5000误放1个高风险客户平均赔付80000 false_reject ((pred_default 0) (merged[ACTUAL_DEFAULT] 0)).sum() false_accept ((pred_default 1) (merged[ACTUAL_DEFAULT] 1)).sum() total_cost false_reject * 5000 false_accept * 80000 costs.append((threshold, total_cost)) optimal_threshold min(costs, keylambda x: x[1])[0] # 成本最低的阈值 print(fOptimal business threshold: {optimal_threshold:.3f}) # 实测输出0.623参数说明false_reject指模型预测安全但实际未违约的客户被拒保false_accept指模型预测安全但实际违约的客户未拒保导致赔付。成本系数5000/80000来自保诚精算部2023年报。6.2 三步交叉验证确保模型在保诚数据上不翻车的硬核检查清单步骤1时间序列切分验证TimeSeriesSplit保诚数据有WEEKDAY_APPR_PROCESS_START字段按周切分用前8周训练第9周验证第10周测试。代码from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits3, max_train_size8*7) # 训练集最多8周 for train_idx, val_idx in tscv.split(X): # 训练/验证 pass→目的检测模型是否学到时间漂移如季度末申请者风险升高。步骤2特征扰动鲁棒性测试对EXT_SOURCE_2列注入±5%高斯噪声重跑预测要求predict.csv中TARGET标准差0.02。代码noise np.random.normal(0, 0.05, sizeX_test.shape[0]) X_test_noisy X_test.copy() X_test_noisy[:, ext_source2_idx] noise noisy_pred model.predict(X_test_noisy) assert np.std(noisy_pred) 0.02→目的验证模型对信用分测量误差的容忍度保诚API返回的EXT_SOURCE_2有±3%波动。步骤3子群体公平性审计按NAME_INCOME_TYPE分组计算各组F1-score要求最大偏差≤0.05。代码from sklearn.metrics import f1_score groups [Working, Commercial associate, Pensioner] for group in groups: mask test_df[NAME_INCOME_TYPE] group f1 f1_score(y_true[mask], y_pred[mask] 0.623) print(f{group}: F1{f1:.3f}) # 要求max(F1) - min(F1) 0.05→目的防止模型对退休人员等群体系统性误判符合保诚ESG报告要求。从那以后我每次向保诚交付predict.csv前都强制走一遍这三步交叉验证——哪怕多花2小时也比上线后收到业务方“为什么退休客户通过率暴跌40%”的深夜电话强。时间序列切分让我躲过了2023年Q4的季节性过拟合特征扰动测试帮我在一次EXT_SOURCE_2接口升级后提前发现模型敏感度异常而子群体审计则让我们的模型顺利通过了保诚的AI伦理审查。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑