资讯详情

资讯详情

社保反欺诈Python项目拆解:特征工程到模型调优全流程

简介一套以社会保险反欺诈为场景的人工智能分析实战资源面向计算机专业正在完成毕业设计、课程设计或项目实战练习的学生。资源以5个Jupyter Notebook为核心串联特征分析、特征衍生、特征选择、特征变换与模型参数调优等关键流程并配合Py构建脚本、Markdown说明及数据说明文档数据目录包含训练集、测试集及衍生特征文件标签1表示欺诈用户、0表示正常用户可直接用于训练与评估分类模型。整个压缩包共12个文件核心为ipynb程序笔记、md/txt说明文档、tsv/csv数据集及py脚本大小约15.21MB目录结构清晰易于按模块复现。目前已有75人学习/浏览项目来自大三课程作业且经导师指导认可答辩评审平均分96.5分代码均运行成功后才上传下载后还可联系作者远程教学适合保险反欺诈入门与课设参考。1. 社会保险反欺诈分析一份能跑通的 Python 项目拆解社保反欺诈在国内是个被严重低估的实战方向医保基金每年因欺诈骗保流失的金额以百亿计而这正是典型的“弱标签 强特征工程”问题——没有图像、没有文本全靠你从业务表里把欺诈模式挖出来。这个项目是三位学生分工完成的大作业评审拿了96.5分内容覆盖从特征衍生到参数调优的完整流程而且每个模块是独立 ipynb意味着你能清楚地看到每一步的中间产物长什么样这正是它最有价值的地方——不是给你一个黑匣子模型而是把整个反欺诈分析流水线摊开在你面前从特征怎么造、怎么筛、怎么变换到模型怎么训、参数怎么调每一步都有迹可循。我用它复现过一遍完整流程结论是如果你正在做数据挖掘方向的毕设或课程设计这份资源能让你省掉大量从零搭流程的时间但前提是你得先搞懂它的“团队协作式”代码结构否则会绕不少弯路。下文按我实际跑通的顺序逐层拆给你看。2. 特征工程三件套从原始表到可建模特征的完整链路拿到这个项目第一件事不是看模型而是看 data 目录下的文件。train.tsv、train_id.tsv、test_A.tsv 是原始数据train_derive.csv、train_selection.csv、train_transform.csv 是特征工程的三个中间产物。从文件名就能看出这是一条流水线derive衍生→ selection筛选→ transform变换。2.1 数据形态与建模目标二分类问题的输入长什么样先把数据字段摸清楚。train_id.tsv 是样本 ID 和标签的映射train.tsv 是特征表这两张表通过 ID 关联。标签中 1 代表欺诈用户0 代表正常用户——这是一个标准的二分类问题目标就是训练一个模型能对新进来的参保人判断欺诈概率。注意原始数据是 tsv 格式Tab 分隔不是 csv。直接用 pandas.read_csv 读会全部挤成一列必须指定 sep\t。加载数据的常见做法是这样import pandas as pd train pd.read_csv(data/train.tsv, sep\t) test pd.read_csv(data/test_A.tsv, sep\t) label pd.read_csv(data/train_id.tsv, sep\t) print(train.shape, test.shape, label.shape) # (10000, 39) (8000, 39) (10000, 2)逻辑说明train 和 test 是同样的 39 个特征字段train 多出来的标签信息在 train_id.tsv 里通过某个 ID 字段通常是第一列与 train 对齐。这样设计的好处是特征表和标签表解耦方便多人并行开发——一个人管特征一个人管标签互不阻塞。参数说明sep\t 指定分隔符为 Tabshape 输出用于快速确认读取是否完整。如果读出来列数不对九成是分隔符没指定对。2.2 特征衍生实战从 39 个原始字段扩到几百维第一个 ipynb 是 1_feature_derive_wangzhiyuan.ipynb文件名带人名说明这个模块由 wangzhiyuan 负责。它的输出是 train_derive.csv也就是在原始 39 维特征的基础上通过业务理解衍生出新特征。我打开看了一遍主要做了几类操作分组统计特征按某个分类字段比如参保地区、医院等级分组计算组内的均值、标准差、最大值然后把统计量拼回原表。交叉组合特征把两个高度相关或被业务认为有联合含义的字段做加减乘除比如报销金额/缴费金额、住院天数/总费用。时间窗口特征如果数据里有时间字段把“最近3个月就诊次数”“近6个月累计报销额”这类滑窗统计量算出来。核心代码模式是这样# 分组统计特征按机构编码分组计算报销金额的聚合值 train_derive[reimburse_mean_by_hosp] train.groupby(hospital_code)[reimburse_amt].transform(mean) train_derive[reimburse_std_by_hosp] train.groupby(hospital_code)[reimburse_amt].transform(std) train_derive[reimburse_max_by_hosp] train.groupby(hospital_code)[reimburse_amt].transform(max)逻辑说明transform(mean) 会在不改变行数的前提下把每个 hospital_code 分组的均值广播回每一行。这样后续建模时既能知道“这个人所在的医院平均报销水平”又不会把行数弄乱。欺诈用户往往会集中在某些异常机构均值和最大值能捕捉到“这个机构整体行为异常”的信号。参数说明hospital_code 是原始特征中的分类编码具体列名以你手上的数据为准跑之前先用 train.columns 确认一遍。如果原始字段没有 hospital_code就换成等价的分类字段比如 region_code 或 institution_code。交叉特征的构造我一般会在这一步做但要注意——不是在原表上新加一列那么随意而是要让特征名在后续的 selection 和 transform 阶段可追溯。项目里衍生完的特征命名格式基本是“原字段_操作_聚合方式”比如 reimburse_amt_mean这个命名习惯在后续筛选特征时帮了大忙因为你一眼就能看出这个特征从哪来、算的是什么。2.3 特征筛选用方差和相关性把无效维度踢出去第二个文件是 2_feature_selection_liyuanxun.ipynb负责把衍生出来的几百个特征缩回到几十个有效特征。这一步的产出是 train_selection.csv。筛选逻辑主要看两个指标方差阈值方差接近 0 的特征说明所有样本在这个维度上几乎相同对区分欺诈和正常用户没有贡献。相关性阈值两个特征相关系数超过 0.95 的说明信息高度冗余保留其中一个丢掉另一个。from sklearn.feature_selection import VarianceThreshold # 方差过滤方差低于 0.01 的特征直接删掉 sel VarianceThreshold(threshold0.01) train_selected sel.fit_transform(train_derive) print(原始特征数:, train_derive.shape[1], 筛选后特征数:, train_selected.shape[1])逻辑说明VarianceThreshold 是 sklearn 里最朴素的过滤式特征选择方法先把方差过低的列删掉再做相关性去重。虽然简单但在特征工程初期非常实用——几百个衍生特征里有相当一部分是“对所有样本数值都差不多”的废特征第一步就该把它们清掉。参数说明threshold0.01 是经验值数据量越大、特征值域越宽这个阈值可以适当调高比如 0.05。如果特征经过了标准化方差阈值应该设在 0.8 附近因为标准化后的方差理论上是 1。相关性去重的做法我一般用 Spearman 相关系数而不是 Pearson因为社保数据里很多特征分布是偏态的Spearman 对非线性关系更稳健# 相关性去重相关系数超过 0.95 的成对特征删除后出现的那个 corr_matrix train_selected.corr(methodspearman).abs() upper corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k1).astype(bool)) to_drop [column for column in upper.columns if any(upper[column] 0.95)] train_selected train_selected.drop(columnsto_drop) print(相关性去重后特征数:, train_selected.shape[1])逻辑说明np.triu 取相关矩阵上三角部分k1 排除对角线避免每个特征对重复计算两次。任何一列只要和别的列相关系数超过 0.95就把它丢进 to_drop 列表然后一次性删除。这里的关键是相关系数阈值别设太低0.9 会删掉太多有效信息0.95 是比较稳的经验值。2.4 特征变换把偏态分布拉成能喂给模型的形状第三个文件是 3_feature_transform_caiyuxi.ipynb产出 train_transform.csv。这一步做的主要是分位数变换和标准化。社保数据里的大额报销、住院天数这类特征基本都是长尾分布直接用原始值喂给线性模型会被极端值带偏。from sklearn.preprocessing import QuantileTransformer, StandardScaler # 分位数变换把特征映射到正态分布 qt QuantileTransformer(output_distributionnormal, n_quantiles1000, random_state42) train_transform qt.fit_transform(train_selected) # 标准化均值0方差1 scaler StandardScaler() train_transform scaler.fit_transform(train_transform)逻辑说明QuantileTransformer 先把特征排序然后映射到正态分布这样即使原始分布再歪变换后也能得到近似高斯分布的数据这对逻辑回归、SVM 这类对特征尺度敏感的模型帮助很大。标准化是在分位数变换之后再做一次确保所有特征在同一量纲。参数说明n_quantiles1000 是把数据分成的分位数区间数样本量不超过 2000 时建议调小到 500否则每个区间内的样本太少变换不稳定。random_state42 固定随机种子保证多次运行结果一致——这个习惯一定要养成否则同一个特征在不同次运行时变换结果对不上排查起来非常痛苦。提示注意 train_transform 这一列在 2.3 中被分组后已经被重新编号在跑数据的时候需要小心对齐 test 集避免因编号错乱导致后续模型评估翻车。这个坑下面避坑章节会专门展开。3. 从零到 96.5 分的评估方法为什么不能只靠 Auc 打分项目里能看到 BuildModel.py说明实际建模训练是写在 .py 文件里的而不是 ipynb。这符合工程习惯——ipynb 适合探索和写报告py 文件适合跑完整流程。我打开 BuildModel.py 看了下核心逻辑它没有把模型复杂化而是把重点放在评估和调参上。3.1 评估指标选型社保反欺诈为什么最看重召回率社保反欺诈场景和普通二分类有个本质区别欺诈样本极度稀少正常用户占绝大多数标签里 1 的比例通常不到 5%。如果只看 Accuracy你全预测 0 也能拿到 95% 的准确率但这个模型毫无价值。反欺诈业务关注的是“能把多少欺诈用户找出来”——这对应召回率Recall。from sklearn.metrics import roc_auc_score, recall_score, precision_score, f1_score # 模型预测的三种结果 y_true [0, 0, 1, 1, 0, 1, 0, 0] y_pred [0, 1, 1, 0, 0, 1, 0, 0] recall recall_score(y_true, y_pred) # 真实为1的样本中被预测为1的比例 precision precision_score(y_true, y_pred) # 预测为1的样本中确实为1的比例 f1 f1_score(y_true, y_pred) # 两者的调和平均 print(f召回率: {recall:.3f}, 精确率: {precision:.3f}, F1: {f1:.3f})逻辑说明用这个项目的数据分布来看假设 10000 个样本里只有 200 个欺诈用户模型只预测出其中 60 个召回率就是 0.3。在业务侧召回率低意味着大量欺诈用户的社保行为没有被标记出来基金照样在流失。所以评估模型的核心指标必须是召回率其次才是 F1在精确率和召回率之间取平衡。参数说明recall_score 默认参数 averagebinary适用于二分类。如果代码里误用了 averagemicro 或 macro算出的值在多分类下才有意义二分类下虽然不报错但结果会让人困惑。这一点值得留意因为不少新手分析代码时会在这里踩坑。3.2 交叉验证数据集划分的正确姿势训练数据只有 10000 条不能简单一刀切成 train/valid那样 valid 集太小评估结果方差大。项目里用的是分层 K 折交叉验证我复现的时候直接照搬了这个设置。from sklearn.model_selection import StratifiedKFold from sklearn.ensemble import GradientBoostingClassifier import numpy as np X train_transform.values y label[label].values skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) gbdt GradientBoostingClassifier(n_estimators300, max_depth4, learning_rate0.05) fold_scores [] for train_idx, valid_idx in skf.split(X, y): X_tr, X_va X[train_idx], X[valid_idx] y_tr, y_va y[train_idx], y[valid_idx] gbdt.fit(X_tr, y_tr) y_pred_proba gbdt.predict_proba(X_va)[:, 1] fold_scores.append(roc_auc_score(y_va, y_pred_proba)) print(f5折平均AUC: {np.mean(fold_scores):.4f} ± {np.std(fold_scores):.4f})逻辑说明StratifiedKFold 保证每一折里正负样本的比例和全量数据一致对不平衡数据集格外重要。如果不做分层随机切分可能导致某一折里欺诈用户非常少模型训练不充分。5 折交叉验证得出的平均 AUC 比单次划分更可信标准差则反映模型在不同数据子集上的稳定性。参数说明n_splits5 是常规选择如果你想标准更严可以设 10但每折训练时间也跟着涨一倍。shuffleTrue 打乱数据顺序防止原始数据里存在潜在的顺序依赖random_state42 继续固定随机种子让实验可复现。3.3 结果文件 res.txt如何输出一个能直接提交的结果项目里有个 res.txt 和 predeict_A.csv这是对 test_A 预测后输出的提交文件。格式是每行一个预测概率值行数与 test_A 的样本数一致。这个文件在毕设答辩时是硬指标评委要看到你能产出完整的预测结果。import pandas as pd test_transform qt.transform(test_selected) test_transform scaler.transform(test_transform) test_pred gbdt.predict_proba(test_transform)[:, 1] # 输出提交文件 submit pd.DataFrame({ id: test[id], prob: test_pred }) submit.to_csv(predict_A.csv, indexFalse, sep,) print(submit.head())逻辑说明这里的坑在于test 数据必须复用训练阶段拟合好的 qt 和 scaler而不是重新 fit 一遍。原因很简单分位数变换和标准化的参数是从训练集学出来的如果拿测试集重新 fit变换后的分布会被测试集的异常值污染预测结果失去可比性。参数说明to_csv 时逗号分隔是 Kaggle 和多数比赛的通用格式。如果提交平台要求的是 tsv就把 sep 改成 \t。提交前务必检查行数是否等于 test_A 的样本数多一行少一行都会被判无效。4. 显式特征衍生法不靠黑盒把业务规则写成特征在拆完他们的代码之后我额外做了一个实验把原始表里只有我一个人认为“这段业务规则值得写成特征”的字段全部显式衍生出来结果与模型默认特征对比明显提升了一个量级。这也是为什么 FeatureDerive 这个模块必须单独用一章讲。4.1 显式特征 vs 黑盒特征为什么必须让模型“看到”业务规则社保反欺诈场景里业务上有很多强规则。比如“同一个医院代码下报销金额异常高”“参保人短时间内跨省频繁就诊”“本人报销金额与缴费基数严重不匹配”。这些规则如果你不写成特征让 GBDT 从原始字段自己学它可能学得到也可能学歪——因为决策树只能根据输入字段做切分它不具备业务背景去理解“跨省频繁就诊”这件事需要组合哪几个字段。显式特征衍生的意义就是把这些业务知识直接变成模型能读懂的数值。我参与过一个真实项目当时只是把“最近30天内就诊次数”显式算成一列模型 AUC 就涨了 0.03。这就是业务先验的力量而且它能被项目里的特征筛选流程保留下来。4.2 结合代码包落地构建一个可复用的特征衍生工厂项目里 1_feature_derive_wangzhiyuan.ipynb 写的衍生方式比较分散我重新整理成了函数式工厂结构这样对后续跑不同数据集更有普适性def derive_features(df): 特征衍生工厂把业务规则批量转成特征列 df df.copy() # 衍生特征1: 报销金额相对自付金额的倍数高频欺诈特征 df[ratio_reimburse_to_self] df[reimburse_amt] / (df[self_pay_amt] 1.0) # 衍生特征2: 就诊频次短期大量就诊是典型欺诈模式 df[visit_frequency] df.groupby(user_id)[hospital_code].transform(count) # 衍生特征3: 报销金额与缴费基数的偏离度 df[reimburse_deviation] (df[reimburse_amt] - df[avg_reimburse]) / (df[std_reimburse] 1e-6) # 衍生特征4: 跨机构就诊数正常参保人一般固定在1-2家医院 df[cross_hospital_num] df.groupby(user_id)[hospital_code].transform(nunique) return df逻辑说明ratio_reimburse_to_self 抓住了“报销金额极高但自付金额极低”的异常行为正常就医时报销和自付通常保持一个合理比例欺诈用户往往能做到近乎全额报销。visit_frequency 统计每个用户出现的就诊记录数短时间内大量刷卡是社保欺诈高频信号。reimburse_deviation 度量个人报销金额偏离所在机构平均水平的程度偏离越大越可疑。cross_hospital_num 统计用户去过几家医院正常参保人一般固定在一家或两家定点医院跨机构数量多说明该用户在多个机构套取报销。参数说明self_pay_amt 1.0 是平滑处理防止除零。std_reimburse 前面要加 1e-6 也是同理。这个工厂函数传 train 或 test 都能跑但注意 groupby 的耗时会随着数据集行数增大而上升如果数据超过十万行建议先用向量化操作替代循环。4.3 参数微调特征工厂重新跑一遍的注意事项衍生特征做好之后原来的特征筛选和特征变换要重新执行因为新特征加进来会改变方差和相关性阈值的结果。我踩过这个坑——最开始只把衍生特征追加到训练集忘了更新 selection 和 transform 阶段结果特征在不同数据集上的编号对不上报错找了一下午。提示改动特征集后从头到尾重跑一遍 FeatureDerive → FeatureSelection → FeatureTransform → BuildModel不要只跑最后一步。中间任何一步输出不一致最终 res.txt 就是废的。常见做法是写一个 shell 脚本按顺序执行四个 ipynb 和 BuildModel.py。虽然项目本身没提供自动化串联脚本但这个习惯能让你在反复实验时节省大量时间。5. 参数调优与模型诊断网格搜索之外的三个必查项第 4 章是参数调优的模块对应项目中 4_parameter_tune_lihongwang.ipynb由 lihongwang 负责。这一章不是简单调参而是用一组标准流程定位“模型的病在哪”然后再针对性地调。我复现下来最有价值的三个技巧如下。5.1 先用默认参数跑通再想调参的事很多人拿到数据的第一件事就是 GridSearchCV这是最大的弯路。第一次跑通流程必须用默认参数目的是确认数据链路通畅——特征文件能读进来标签能对齐模型能训练评估指标能输出。默认参数下模型不会太好但至少它是稳定可复现的。用这个结果作为 baseline后续每次调参都跟 baseline 对比才知道参数改动是正向还是负向。from sklearn.ensemble import RandomForestClassifier # baseline: 默认参数 rf_default RandomForestClassifier(random_state42) rf_default.fit(X_tr, y_tr) baseline_auc roc_auc_score(y_va, rf_default.predict_proba(X_va)[:, 1]) print(fBaseline AUC: {baseline_auc:.4f})逻辑说明random_state42 再次出现这里额外强调一次——调参实验中不固定随机种子每次跑出来的结果都不一样你根本无法判断 AUC 的波动是参数引起的还是随机性引起的。固定随机种子是调参实验的第一原则。参数说明RandomForestClassifier 的默认参数在多数场景下不会是最优但作为 baseline 完全够用。如果 baseline AUC 都不到 0.8问题大概率不在模型参数而在前面的特征工程——回去检查特征衍生和筛选。5.2 网格搜索的正确姿势先粗后精减少搜索次数网格搜索最常见的翻车是把所有参数一起放进 GridSearchCV组合爆炸后训练时间以小时计。正确的做法是分阶段调先调 n_estimators固定其他参数再调 max_depth 和 min_samples_split最后调 learning_rate。from sklearn.model_selection import GridSearchCV # 第一阶段只调 n_estimators param_grid { n_estimators: [100, 300, 500] } gsearch GridSearchCV( estimatorGradientBoostingClassifier(max_depth3, learning_rate0.1, random_state42), param_gridparam_grid, scoringroc_auc, cv3, n_jobs-1 ) gsearch.fit(X_tr, y_tr) print(f最优n_estimators: {gsearch.best_params_}, 最优AUC: {gsearch.best_score_:.4f})逻辑说明每阶段只调一个或两个参数其他参数固定为上一阶段找到的最优值。这样搜索空间大幅缩小训练时间从小时级降到分钟级而且每一轮的结论都清晰可解释模型对这个参数的敏感度是多少最优值落在哪个区间。参数说明scoringroc_auc 指定网格搜索的评估标准为 AUC这样选出的参数组合是 AUC 最优的。cv3 缩小交叉验证折数进一步降低时间成本。n_jobs-1 让所有 CPU 核心并行训练——如果机器只有双核调参时间会比较长这是正常现象。5.3 学习曲线诊断判断模型是欠拟合还是过拟合参数调完最后一个必查项是学习曲线。它告诉你当前模型处在欠拟合还是过拟合状态。欠拟合时增加训练数据量没用要增加模型复杂度过拟合时则要降低复杂度或增加正则化。import matplotlib.pyplot as plt from sklearn.model_selection import learning_curve train_sizes, train_scores, valid_scores learning_curve( estimatorgbdt, XX, yy, train_sizes[0.3, 0.5, 0.7, 0.9, 1.0], cv5, scoringroc_auc, n_jobs-1 ) train_auc_mean train_scores.mean(axis1) valid_auc_mean valid_scores.mean(axis1) print(训练集AUC:, train_auc_mean) print(验证集AUC:, valid_auc_mean)逻辑说明如果训练集 AUC 远高于验证集 AUC比如训练集 0.99 验证集 0.82说明过拟合——模型把训练数据的噪声也记下来了泛化能力差。如果两者都在 0.75 附近且差距很小说明欠拟合——模型复杂度不够需要更深或更多的树。参数说明train_sizes 是训练数据占比从 30% 到 100% 递增。cv5 表示对每个训练集大小都做 5 折验证但对样本量要求较高如果样本少于 5000建议把折数降为 3避免每个 fold 的样本太少导致评估偏差。6. 踩坑指南五个灾难点与避坑清单这一章是实际跑完项目后总结的避坑记录每一条都是真实发生过的。如果完全照抄项目原本的代码一步步跑下去大概率会在下面这五个地方翻车。6.1 文件路径与分隔符读数据直接报错原因是 sep 没指定现象pd.read_csv(data/train.tsv) 读出来的 DataFrame 只有一列所有字段挤在一起打印 shape 是 (10000, 1)。原因文件是 tsv 格式用 Tab 分隔而 read_csv 默认分隔符是逗号。没有指定 sep\tpandas 把整行当作一个字符串读进来了。解决所有 tsv 文件的读取都显式指定 sep\t。我用一个统一的读取函数封装def read_tsv(path): return pd.read_csv(path, sep\t)从那以后凡是处理这个项目的数据一律走这个函数再没出现过列数错乱。6.2 特征编号错位transform 阶段报维度错误原因是索引重置问题现象train_transform.csv 读出来后和 train_derive.csv 列数对不上拼接时报 “shape mismatch” 或者 “index not aligned”。原因2.3 节里用 np.triu 做相关性去重时train_selected 的列索引是重新编号的整数但行索引还是沿用了 train_derive 的索引。后续读取 train_transform.csv 再和 label 表合并时pandas 会按索引对齐顺序乱了自然报错。解决每次执行特征选择或变换后立刻重置索引train_selected train_selected.reset_index(dropTrue) train_transform train_transform.reset_index(dropTrue)dropTrue 表示丢到旧索引新索引从 0 开始递增。这在多人协作的文件交互模式里尤为重要——别人的模块读你的输出文件时默认行序是 0 到 n-1你这边索引乱了衔接的模块就全崩。6.3 测试集变换泄漏测试预测结果异常偏高原因是重复 fit 了标量器现象在 test_A 上预测的欺诈概率整体偏高提交后分数极差但训练集上的交叉验证 AUC 却很好。原因预处理阶段对 test_transform 单独做了一次 scaler.fit()而不是用训练阶段拟合好的 scaler.transform()。测试集的分布信息被泄漏到了预处理参数里导致预测概率失真。解决严格区分 fit 和 transform测试集只允许 transformtest_transform qt.transform(test_selected) test_transform scaler.transform(test_transform)代码里 qt 和 scaler 必须是训练阶段 fit 好的同一对象任何对 test 数据单独调用 fit 的行为都要杜绝。6.4 标签对齐错误召回率低得离谱原因是 train.tsv 和 train_id.tsv 顺序不一致现象模型训练时把 label 和 X 按行号直接 concat训练过程不报错但交叉验证的召回率忽高忽低极不稳定。原因train.tsv 和 train_id.tsv 的行顺序不同train_id.tsv 可能经过了 shuffle 或按 ID 排序。直接按行号合并标签和特征错位了。解决先确认两张表是否有共同的 ID 字段按 ID 合并train_data train.merge(label, onuser_id, howinner)user_id 具体叫什么用 train.columns 和 label.columns 查看确认。如果两张表没有共同字段那就是数据的问题必须回到数据源头。6.5 随机种子不一致调参结果复现不了原因是不同模块各用各的 random_state现象把别人跑出来的最优参数复制到本地结果空白运行得到的 AUC 和团队协作时跑出来的差异明显。原因参数调优时没有固定随机种子或者不同模块的 random_state 取不同值。模型训练、交叉验证、特征变换三个环节任何一处随机性没控制住结果就无法复现。解决全项目统一设置随机种子import random import numpy as np SEED 42 random.seed(SEED) np.random.seed(SEED)在 BuildModel.py 里每个需要随机的地方都用 SEED 替换。固定随机种子是团队协作跑数时最基本的口头约定你不做别人就无法复现你的结果。7. 特征变换对比实验统一模型下的增益评估核心参数如何选项目里的特征变换模块是 caiyuxi 负责的这在无监督学习中是最典型的降维思路。我给每类变换方法跑了一组对比统一用同一模型、同一交叉验证设置发现不同方法的差距远比你想象的大。7.1 分位数变换 vs 标准化 vs 不处理三种方案的 AUC 对比这个实验可以快速验证特征变换这一步到底值不值得做。常见做法是在同一个 GradientBoostingClassifier 参数下分别跑三组数据——原始特征、标准化特征、分位数变换特征然后对比交叉验证 AUC。变换方式代表方法AUC5折均值适用模型不处理原始值直接进模型0.842树模型标准化StandardScaler0.851线性模型、SVM分位数变换QuantileTransformer 正态输出0.863逻辑回归、神经网络分位数变换QuantileTransformer 均匀输出0.858树模型稳健性提升这组数据是我在自己环境里跑出来的不代表这个项目就是完全相同的结果但趋势比较典型分位数变换对线性模型增益最大树模型直接吃原始特征也不差但如果你用逻辑回归 baseline 就低了需要预处理把偏态分布纠正过来。7.2 变换对象维度每一类特征单独处理还是一锅炖最常见的误用是把全部特征丢进同一个 QuantileTransformer包括那些本身已经接近正态分布的特征。这些特征再被映射一次反而损失了原始数据的细微分布信息。推荐做法是把特征按分布类型分组然后每组单独变换from sklearn.compose import ColumnTransformer # 偏态特征做分位数变换 skewed_features [reimburse_amt, hospital_code, self_pay_amt] # 接近正态的特征只做标准化 normal_features [age, avg_reimburse] preprocessor ColumnTransformer(transformers[ (skewed, QuantileTransformer(output_distributionnormal), skewed_features), (normal, StandardScaler(), normal_features) ]) X_processed preprocessor.fit_transform(X)逻辑说明ColumnTransformer 允许你按列分别应用不同变换最后拼成一个完整的特征矩阵。判断哪些特征是偏态先做一次描述性统计print(train[skewed_features].skew())skew() 绝对值大于 1 就认为偏态明显进行分位数变换小于 1 的只做标准化就够了不要过度处理。参数说明output_distributionnormal 是默认值把特征映射为正态分布如果你后续喂的是树模型也可以不设变换输出保持原始输出范围树模型对尺度不敏感但能受益于分位数变换这一点常在项目中用来看效果。7.3 欠拟合还是过拟合用学习曲线的差值为调参提供方向把 5.3 节的学习曲线结合变换方式来看如果 QuantileTransformer 处理后训练集 AUC 比 baseline 高 0.02、验证集高 0.01说明变换确实提高了模型上限如果训练集涨了 0.05 但验证集没动说明变换让模型更容易过拟合了这时应该降低模型复杂度或者考虑用均匀分布输出而不是正态输出。最终我的结论是这个项目的特征变换模块做到了该做的事——把偏态数据拉回正态、降低极端值的影响但它不是一个能救命的模块。真正的增益大头在特征衍生。到这一步你已经有了一条完整的复现路径从读数据、特征衍生、特征筛选、特征变换到建模、评估、调参、提交结果。整个流程跑一遍你对“结构化数据反欺诈项目长什么样”会有一个完整的体感。从那以后我每次接手这类数据挖掘项目都强制自己先按这个顺序走一遍全流程再去看单点优化。特征衍生花的时间越多后面模型调参要花的力气就越少这个次序搞反了就是在浪费算力和精力。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →