ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

社保大数据竞赛实战:Python与LightGBM全流程

社保大数据竞赛实战:Python与LightGBM全流程 简介在大数据时代机器学习技术广泛用于政务数据挖掘其中基于梯度提升树的LightGBM以高效训练和类别特征支持成为表格数据建模的主流。真实业务场景中的数据往往体量大、维度杂且存在严重类别不平衡如何通过特征工程构建有效指标并设计模型融合策略是提升预测效果的关键。这类技术在社会保险、金融风控等领域具有重要应用价值例如识别违规参保、欺诈报销等行为。以全国社会保险大数据应用创新大赛为背景文章分享了一套完整的Python实现方案涵盖数据清洗、特征工程、模型训练与调优等全流程为处理类似政务表格数据提供了可复用的工程实践参考。 天池这个比赛出来的时候我正好在折腾社保相关的大数据项目看到赛题第一反应是这不就是拿真实业务场景考人嘛。全国社会保险大数据应用创新大赛数据来自社保业务系统的真实脱敏数据任务是做参保人员的行为分析、风险预测这一类事情。我把自己整套Python源码和数据处理思路整理出来写给打算参加这类大数据竞赛、或者正在做社保/政务数据挖掘的朋友。这类比赛和学校的机器学习课设最大的区别在于数据量大、字段杂、业务含义重。你光靠调参是上不了分的真正拉开差距的是对业务场景的理解、特征工程的处理以及对数据质量的把控。这套源码我赛后重新整理过去掉了所有涉及敏感信息的细节保留了一套从数据清洗到模型输出的完整链路可以直接复用到类似结构的表格型数据竞赛里。1. 赛题理解与整体设计思路1.1 赛题背景与核心任务拆解先说赛题本身。全国社会保险大数据应用创新大赛由阿里天池承办主打用大数据技术提升社保经办服务能力。社保业务数据有一个其他数据没有的特点它是真正意义上的长尾数据——绝大多数参保人员行为正常少数人存在异常模式比如违规领取待遇、挂靠参保、欺诈报销等。这类问题本质上是一个不平衡分类问题而且对可解释性有要求你不能扔出一个黑盒模型就完事还要能回答为什么这个人是高风险。我拿到的数据分为几类表参保人员基本信息表、缴费明细表、待遇发放表、就医/报销记录表。数据量大概是百万级用户、上亿条流水记录。目标变量是用户是否存在违规行为二分类标签。这个设定在政务数据竞赛里非常典型核心任务就是基于历史行为数据预测用户风险等级并尽量给出可解释的风险因子。1.2 技术选型为什么用Python LightGBM这套组合赛题发布后很多人在群里问用什么框架。我当时直接拍板用Python理由很实际pandas做特征工程太方便了而且LightGBM和XGBoost都有成熟的Python接口调参、交叉验证、特征重要性分析一条龙。不要为了炫技上深度学习表格数据上深度学习除非你有海量数据和强大的调参能力否则很难干过带正则化的树模型集成。我的baseline方案是pandas LightGBM XGBoost双模型融合。选择这两个模型的核心原因有三个原生支持类别特征省去大量One-Hot编码工作不过我还是手动做了目标编码后面细说对缺失值有内置处理策略社保数据里缺失值非常多这个特性很关键训练速度快支持并行百万级样本、上千维特征也能在合理时间内跑完注意这套方案在表格型数据强业务规则的赛题里几乎永远是最优选择。如果你的数据是图像、文本或者时序信号那再考虑深度学习但社保类比赛基本不会出现这些类型。1.3 源码整体目录结构与模块职责我的项目源码按照数据处理→特征工程→建模→预测四个阶段组织。项目结构如下social_security_competition/ ├── config.py # 全局配置路径、参数、随机种子 ├── data_process/ │ ├── 01_merge_tables.py # 多表关联合并 │ ├── 02_clean_data.py # 缺失值、异常值处理 │ ├── 03_reduce_memory.py # 数据类型降级节省内存 │ └── 04_split_dataset.py # 训练/验证集划分 ├── feature_engineering/ │ ├── 05_base_features.py # 基础统计特征 │ ├── 06_time_features.py # 时间序列特征 │ ├── 07_aggregate_features.py # 分组聚合特征 │ ├── 08_target_encoding.py # 目标编码 │ └── 09_feature_select.py # 特征筛选 ├── model/ │ ├── 10_train_lgb.py # LightGBM训练 │ ├── 11_train_xgb.py # XGBoost训练 │ ├── 12_blend.py # 模型融合 │ └── 13_threshold_tune.py # 阈值优化 └── predict/ ├── 14_generate_submit.py # 生成提交文件 └── 15_feature_importance.py # 特征重要性分析每个模块都是独立脚本通过config.py统一管理参数。这样做的好处是调参时不用翻遍所有代码只需要改config.py复现实验时也不会因为改了某段代码导致结果不一致。2. 数据剖析与预处理这一步决定了你的上限2.1 原始数据字段盘点与业务归类拿到数据的第一件事不是写代码而是打开数据字典把每个字段的业务含义搞明白。我把字段分成了几个大类类别典型字段建模用途身份属性年龄、性别、户籍地、参保状态基础风险分层缴费特征缴费基数、缴费时长、缴费连续性识别挂靠、断缴待遇特征领取金额、领取时长、发放频次识别违规领取就医行为就诊次数、费用结构、医院等级识别欺诈报销时间特征参保时间、最后缴费时间、时间间隔构建时序特征这个归类的意义在于它决定了你后续特征工程的方向。比如缴费连续性这类字段直接反映用户是否有挂靠参保的嫌疑——正常在职人员缴费是连续的但挂靠人员往往在某一时段集中补缴。2.2 数据清洗中的三个关键操作数据清洗阶段最容易翻车因为政务数据比互联网数据脏得多。我总结了三个关键操作**第一去重。**社保数据里一个用户可能有多条记录因为参保单位变更、系统迁移等因素导致重复。我用用户ID 参保地 参保类型作为去重键保留时间最近的一条。这一步不做后面所有特征都会失真。# 示例去重逻辑 df df.sort_values(record_time).drop_duplicates( subset[user_id, insure_city, insure_type], keeplast )**第二异常值截断。**缴费基数是关键特征但存在极端值。比如有人缴费基数是0可能离职停保有人缴费基数超过社平工资3倍封顶线。我采用的策略是缴费基数为0的单独标记一个is_zero_payment特征不直接删除超过上限的用上限截断。**第三缺失值分类型处理。**社保数据里缺失值不是随机缺失它本身包含信息。比如单位名称缺失可能意味着灵活就业人员待遇发放金额缺失可能意味着这个人从未领取待遇。我按照缺失比例分了三类缺失率超过60%的字段建议删除或者只保留是否缺失标记缺失率20%-60%的字段用中位数填充同时保留缺失标记缺失率低于20%的字段用模型预测或按类别众数填充这里有一个经验教训不要迷信填补算法的花活比如用KNN、MICE这些方法填补。政务数据填补后的真实信息增益很低而且极度耗时。用一个中位数加一个缺失标记效果不差跑得还快。2.3 大数据量下的pandas内存优化实战社保数据动辄几千万行直接读入内存很容易爆。我在项目启动时就写了专门的内存优化函数核心思路是让pandas自动把能降级的数据类型降下来。def reduce_mem_usage(df): start_mem df.memory_usage().sum() / 1024**2 for col in df.columns: col_type df[col].dtype if col_type ! object: c_min df[col].min() c_max df[col].max() if str(col_type)[:3] int: if c_min np.iinfo(np.int8).min and c_max np.iinfo(np.int8).max: df[col] df[col].astype(np.int8) elif c_min np.iinfo(np.int16).min and c_max np.iinfo(np.int16).max: df[col] df[col].astype(np.int16) elif c_min np.iinfo(np.int32).min and c_max np.iinfo(np.int32).max: df[col] df[col].astype(np.int32) else: if c_min np.finfo(np.float16).min and c_max np.finfo(np.float16).max: df[col] df[col].astype(np.float16) elif c_min np.finfo(np.float32).min and c_max np.finfo(np.float32).max: df[col] df[col].astype(np.float32) end_mem df.memory_usage().sum() / 1024**2 print(Memory decreased from {:.2f} MB to {:.2f} MB.format(start_mem, end_mem)) return df这个函数在我的数据上把内存占用降低了约60%。注意千万级的DataFrame一定要在清洗结束后立刻做类型降级否则后面跑特征工程时内存会频繁触顶。另外还有一个技巧如果原始数据文件是CSV读取时指定dtype参数、提前定义列类型能大幅减少读取时间和中转内存占用。我习惯先读前1000行推断类型再手动修正这样比pandas自动推断快很多。2.4 训练集与验证集的划分策略验证集划分是很多参赛者容易忽略的环节。社保数据里有很强的时间特性违规行为被标记往往有时间滞后同一时间段内的用户行为模式相似。因此我在划分训练集和验证集时按时间切分而不是随机切分。具体做法按用户最后的“参保时间”排序前80%的样本做训练集后20%做验证集。这样做更接近大赛线上评测的逻辑也能避免随机划分带来的时间穿越问题——即用未来的数据预测过去的目标。还有一个注意点划分时必须以用户ID为单位同一个用户的所有记录必须分到同一侧不能打散。否则会造成信息泄露线上分数和线下分数差距会非常大。3. 特征工程核心细节上分的关键在这里3.1 时序特征参保时长、断缴次数与缴费节奏社保数据天然的时序属性非常强。参保时长、断缴次数、缴费间隔这些特征对风险预测的区分度非常高。正常职工缴费节奏稳定违规人员的缴费记录往往呈现出集中补缴、长期断缴的异常模式。我构建的时序特征主要包括insure_duration_days从首次参保到数据截止日期的总天数last_payment_gap_days最后缴费时间到截止日期的间隔天数断缴越久异常风险越高payment_break_count连续两个缴费记录间隔超过90天记为一次断缴payment_std_days所有缴费间隔的标准差衡量缴费节奏稳定性monthly_payment_ratio实际缴费月份数除以应缴费月份数反映缴费完整度其中payment_std_days这个特征我在初赛中忽略了后来加入后线上AUC提升了0.003左右。这说明节奏稳定度确实能捕捉到异常缴费行为。3.2 分组聚合特征站在业务角度做统计聚合特征是这类竞赛最核心的上分手段。思路就是按业务维度分组对数值字段做统计运算。我常用的分组维度包括user_id、insure_city、insure_type、company_id统计量包括count、mean、std、max、min、skew等等。举个例子按company_id聚合company_agg df.groupby(company_id)[payment_base].agg( company_payment_meanmean, company_payment_stdstd, company_user_countcount ).reset_index()这些特征能刻画你所在的公司整体情况。比如一家公司大量用户缴费基数恰好贴着最低标准这种公司可能就是挂靠公司所有关联用户的风险都会上升。这类特征在业务解释上也站得住脚。做聚合特征时有个效率问题如果分组后要生成大量统计量建议用agg一次搞定不要循环多个groupby否则几千万行数据跑起来非常痛苦。另外要留意skew这类高阶统计量在数据量大时计算开销不小我一般只对关键数值字段算mean和stdskew和kurt做选择性使用。3.3 目标编码与交叉特征的正确姿势社保数据里的类别特征比如insure_city、company_id基数差异巨大。insure_city可能只有几十个取值但company_id有几万个。直接用LabelEncoder会让树模型产生误解One-Hot又会让维度爆炸。我的处理方案是低基数类别特征用One-Hot或原生类别特征高基数类别特征用目标编码。目标编码用LightGBM原生的categorical_feature也可以但目标编码更灵活可以在编码时加入平滑项。我的实现方式def target_encode(train, val, col, target, smooth20): temp train.groupby(col)[target].agg([mean, count]) temp[smooth_mean] (temp[mean] * temp[count] global_mean * smooth) / (temp[count] smooth) val[col _te] val[col].map(temp[smooth_mean]) train[col _te] train[col].map(temp[smooth_mean]) return train, val交叉特征这块我重点做了几组insure_city insure_type、company_id payment_month、age_group payment_base_band。交叉特征的思路是把两个单独特征组合成新的类别特征让模型捕捉它们的交互效应。政务数据里这类交互效应非常强比如某城市的某类参保人员往往有特定的风险模式。经验目标编码必须在训练集内计算后映射到验证集绝不能直接用全量数据计算否则就是典型的数据泄露会让线下分数虚高很多。3.4 特征筛选不要迷信特征数量我做特征工程做到后面特征维度到了1000多维。这时候不是越多越好而是要筛选。我用三个方法结合特征重要性排名LightGBM的feature_importance相关性分析去除相关系数超过0.9的冗余特征单特征AUC删除单特征AUC低于0.5的特征最终保留大概400个特征。实际上从600个特征加到1000个特征线上分数几乎没有变化但训练时间增加了近一倍。后来我意识到政务数据竞赛中特征的有效性呈现出长尾分布——真正有价值的特征就那几十个剩下的都是边际贡献极小甚至纯噪声的特征。把精力花在核心特征的理解和打磨上远比堆砌特征数量有效。4. 模型构建与调参实战如何稳定上分4.1 Baseline方案与评估指标选择大赛的评估指标用的是AUCROC曲线下面积。这个指标对正负样本比例不敏感非常适合社保这种类别不平衡的数据。我搭建baseline的时候先只用了基础统计特征少量时序特征用LightGBM默认参数训练得到AUC约0.72。这个分数作为起点后续所有工作都是在这个baseline上逐步迭代。baseline的代码框架如下import lightgbm as lgb from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( features, target, test_size0.2, random_state42 ) lgb_params { objective: binary, metric: auc, learning_rate: 0.05, num_leaves: 31, max_depth: -1, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, verbose: -1, seed: 42 } d_train lgb.Dataset(X_train, labely_train) d_val lgb.Dataset(X_val, labely_val) model lgb.train( lgb_params, d_train, num_boost_round1000, valid_sets[d_val], early_stopping_rounds100, fevalNone )4.2 LightGBM关键参数调优思路调参是这个环节的重头。我按照先粗后细的顺序来调不搞暴力网格搜索。我的调参路线分为四步**第一步调num_leaves和max_depth。**这两个参数控制模型复杂度。num_leaves是LightGBM的核心参数我从小往大试观察验证集AUC的变化8、16、31、63、127。最后发现31效果最好再大会过拟合。max_depth设成-1让模型自己决定但配合min_data_in_leaf来防止过深。第二步调采样参数。feature_fraction和bagging_fraction可以简单理解成随机丢特征和随机丢样本作用都是增强泛化能力。我初始设0.8之后微调。如果验证集AUC明显高于测试集说明过拟合就调低这两个值。第三步调正则化参数。lambda_l1和lambda_l2的值我通常在0到10之间尝试。社保数据特征多且稀疏L2正则化对抑制噪声特征作用比较明显。我最后的参数是lambda_l10.1, lambda_l25。**第四步调学习率与轮数。**这部分比较无脑学习率降到0.01num_boost_round放大到5000配合early_stopping_rounds200。低学习率需要更多轮数但精度略有提升而且不容易过拟合。我最终使用的LightGBM参数表参数初赛决赛learning_rate0.050.01num_leaves3163max_depth-1-1feature_fraction0.80.7bagging_fraction0.80.7min_data_in_leaf50100lambda_l100.1lambda_l215num_boost_round10005000初赛到决赛把参数调得更保守更低的采样率、更强的正则化是为了在更复杂的全量数据上减少过拟合。实际上决赛的数据量是初赛的3倍左右同样的模型在更多数据上训练AUC自然会有提升。4.3 XGBoost与LightGBM的融合策略用过双模型的人都知道融合的收益主要来自模型看问题的方式不同。LightGBM基于直方图算法训练更快对特征中包含大量离散值的场景很友好XGBoost基于预排序算法对缺失值处理更精细。两者融合能互补。我的融合方式很简单先分别训练两个模型得到预测概率然后做加权平均。权重我通过网格搜索确定范围0到1步长0.05以验证集AUC为目标函数。最后最优权重大约是0.7 * LightGBM 0.3 * XGBoost。还要注意两个模型尽量用同一套特征和同一套样本划分否则融合时会产生不一致的样本分布导致融合结果不稳定。4.4 阈值选择与最终提交策略大赛要求提交的是预测概率但实际业务里需要给出是否违规的判定阈值。我在验证集上计算了不同阈值下的精确率和召回率画了PR曲线。对于社保欺诈检测场景我更看重召回率——漏掉一个违规用户比误报一个正常用户代价更大所以我选择了一个让召回率达到85%左右的阈值。这个决策完全来自业务场景不是一个纯技术问题。线上提交时还有一个细节提交文件必须包含所有测试集用户的预测结果不能因为某个用户特征缺失就漏掉。我写了个兜底逻辑特征完全缺失的用户用全局平均概率填充保证提交文件全覆盖。5. 常见问题与排查技巧实录5.1 数据泄露的经典案例与排查方法数据泄露是这个比赛里最容易踩的坑。我初赛时遇到过线下AUC 0.86线上只有0.75的情况后来排查发现是目标编码泄露。具体过程是这样的我用全量数据包括测试集计算了目标编码的均值映射然后在训练集上应用导致验证集分数虚高。修复方法是只在训练集内部计算映射再映射到验证集和测试集。这个错误很隐蔽因为线下验证集表现稳定你不会意识到泄露了。排查数据泄露的方法我现在分享几个检查单特征AUC如果某个特征的单独AUC超过0.8大概率泄露了打印特征重要性Top10如果榜首特征的importance远超其他特征警惕信息泄露对比验证集和线上分数线下远高于线上优先怀疑泄露5.2 类别不平衡的处理策略社保数据里违规用户占比极低大概只有1%左右。如果用原始比例训练模型会倾向把所有样本预测为负类。我的处理策略不是用class_weight而是做下采样从负样本中随机抽取与正样本数量相同的子集保证训练集正负比例1:1。验证集保持原始比例不变这样评估分数才反映真实场景。下采样后我用早停机制训练模型并在每个epoch后对验证集做完整评估。这里有一个细节下采样会导致模型预测概率普遍偏高因为训练集正样本比例远高于真实场景。因此在实际提交前我会对预测概率做一次校准用验证集上的实际正样本比例调整Cutoff。简单来说就是训练集正样本比例是0.5真实比例是0.01那么预测分数要压低阈值要相应调低。5.3 内存溢出与运行时间的优化这个坑几乎每个参赛者都会遇到。数据量大的时候特征工程做一步炸一次内存非常崩溃。我的经验是分块处理结合gc.collect()主动释放内存。import gc # 每处理完一个大表主动释放内存 del temp_df gc.collect()另外特征工程阶段尽量以user_id为粒度做聚合和合并而不是整个大表操作。具体做法先把基础表按user_id聚合生成细粒度的用户特征表最后再和主表做一次merge。这样特征工程过程中每个步骤的数据量都不会爆炸。训练时间优化方面我在特征筛选后把训练集转成LightGBM的Dataset格式并且开启了feature_pre_filterTrue。这个方法能在建树过程中提前过滤无用特征能减少20%-30%的训练时间。5.4 代码版本管理与实验记录很多人参赛时会忽略这个但实际比赛中这是非常重要的。我每天会记录实验日志包含当天的特征列表、模型参数、验证集AUC、提交分数、做了哪些改动。这个习惯让我能清楚地知道每一步上分或掉分的原因。代码版本管理我用的是Git每个可提交版本打一个tag。特征工程文件的命名带日期例如07_aggregate_features_1108.py。这样回滚到历史版本很方便不至于改崩了不知道怎么恢复。比赛中后期特征的版本管理比代码本身更重要因为不同特征组合的上分效果千差万别。5.5 其他高频报错速查表最后整理一下我遇到的高频报错和对应解决方法适合直接收藏报错信息原因解决方案MemoryError数据量超出内存类型降级、分块读取、及时delgcValueError: Input contains NaN特征中存在缺失值检查特征管线中的merge是否导致NaN填充或删除LightGBMError: number of categorical features must be integercategorical_feature参数传入格式错误改用整数索引而不是字符串列名AUC一直等于0.5正负样本顺序错乱或标签对齐错误检查训练集和标签的索引是否对齐验证集AUC高但线上低数据泄露或验证集划分不随机按时间划分、检查目标编码/填充是否泄露写在最后一些实在话赛后复盘这次天池社保大数据比赛我觉得收获最大的不是AUC从0.72提到0.81这个结果而是对整个数据竞赛流程的掌控感。第一次跑通全流程时手忙脚乱的但到决赛阶段我已经能清楚地判断每个操作对最终分数的边际贡献知道什么时候该停手什么时候该继续挖特征。对于想参加同类竞赛的朋友我个人的建议是不要一开始就追求复杂的模型和花哨的算法先把baseline打通再逐步迭代。数据竞赛没有银弹所谓高分方案都是把数据处理、特征工程、模型调参每个环节做扎实之后自然涌现的结果。这套Python源码的逻辑相信对你有帮助无论你是准备天池的比赛还是处理类似的政务表格数据按这个流程走一遍方向基本不会错。如果你在复现这套方案时遇到问题可以对照我上面整理的报错速查表排查。祝你在数据竞赛里拿到好名次。本文还有配套的精品资源点击获取
返回列表