AI竞赛网络赛题数据全流程指南:从清洗建模到避坑提交

📅 发布时间:2026/9/29 15:42:56
AI竞赛网络赛题数据全流程指南:从清洗建模到避坑提交
简介2021年安徽省大数据与人工智能应用竞赛人工智能网络赛本科组赛题数据为参赛学生、指导教师以及机器学习入门者提供了完整规范的官方数据集。任务涵盖人脸年龄预测与房价回归两大典型场景包含train.csv、val.csv、test.csv三份已切分好的数据训练、验证、测试样本按17000比3000比3000划分并附有任务说明与基础Python脚本可直接用于监督学习建模。房价数据中涉及电梯情况、楼层、户型、区域、装修、面积、建筑时间等字段部分信息存在缺失可借此系统练习数据清洗、缺失值处理、特征编码与回归模型调优人脸图像部分则适合开展图像批量读取、标签对齐及年龄预测模型的训练与验证。压缩包共1009个文件以1000张人脸jpg图像和4个csv表格为核心另有3个py脚本与2个txt说明文档整体仅13.47MB轻量易用。目前已有1066人学习下载资源覆盖全部赛题数据与标注可直接复现竞赛流程也可用作课程设计或论文实验的数据基线。1. 一张赛题数据才是网络赛真正的起跑线它和业务数据差在哪参加过安徽省大数据与人工智能应用竞赛的同学大概率都有过这种体验赛题公告里最不起眼的附件反而是整场网络赛的胜负手。所谓“人工智能(网络赛)-本科组赛题数据”不是一份普通的数据集下载链接而是一套经过脱敏、压缩、重新编码的竞赛专用数据它决定了你在接下来的几个小时内能做到哪一步。和你在实习公司里拿到的业务数据不同赛题数据几乎没有脏到你无法下手的程度但它在“字段含义未知、标签分布隐蔽、提交格式苛刻”这些维度上藏着一整套只有比过赛才懂的规则。这篇文章写给两类人一是打算报名省赛、国赛想在真实赛题数据上练手的学生二是带竞赛的指导老师想搞清楚一套网络赛数据到底该怎么拆给学生用。我会从赛题数据的结构讲起落到清洗、建模、调参、提交的全流程最后给出避坑清单和复盘方法。这套思路不依赖特定某年的赛题拿到任何一届网赛数据都能直接用。2. 先看结构再动手赛题数据的字段形态、编码与读取套路2.1 赛题数据常见的三种形态分类标签、回归目标、多表关联大数据与人工智能应用竞赛的人工智能赛道网络赛阶段通常给一套“压缩包里的数据”而不是实时数据流。压缩包里最常见的是训练集、测试集、样例提交文件这三件套。训练集带目标字段测试集不带最后你提交的结果会和官方测试集的目标字段做比对得出分数。本科组的赛题数据按我的经验集中在三种形态上。第一种是单表分类任务。数据是一张表几十个特征加上一个类别标签标签数量从二分类到几十分类都有可能。这种任务最接近“人工智能”一词在大众认知里的样子适合考察选手对常见分类模型和特征工程的基本功。第二种是回归预测任务。标签是连续值比如某种指标、温度、销量、流量这类数值分数通常用均方误差或平均绝对误差来评估。第三种是多表关联任务一个主表和几个附表通过主键关联需要你先做表之间的拼接才能开始建模。这种题最能模拟真实场景也是网络赛里最容易让人前期耗掉大量时间的形态。拿到压缩包后第一件该做的事不是急着建模而是先回答三个问题数据是CSV还是Excel字段是什么类型标签是什么分布。这三个问题的答案直接决定后续脚本怎么写。很多人在这一步省了十分钟后面在编码和读取上翻车多花两小时血泪经验。2.2 用pandas把赛题数据盘清楚探查代码与字段体检无论数据是CSV还是Excel我都习惯用一个标准脚本开头先把数据的“户口”查清楚。下面这段代码适用于绝大多数赛题数据直接放在Jupyter Notebook第一个单元格里跑即可。import pandas as pd import numpy as np # 读取训练集和测试集 train pd.read_csv(train.csv, encodingutf-8) test pd.read_csv(test.csv, encodingutf-8) # 如果读出来乱码大概率是GBK编码改用下面这行 # train pd.read_csv(train.csv, encodinggbk) # 字段体检维度、字段名、缺失情况、类型 print(训练集 shape:, train.shape) print(测试集 shape:, test.shape) print(训练集字段类型:\n, train.dtypes) print(缺失值统计:\n, train.isnull().sum()) print(标签分布:\n, train[label].value_counts(normalizeTrue))这段代码做了四件事。第一件是确认训练集和测试集的行列数避免后续合并时维度对不上。第二件是看字段类型object字段大概率需要编码或清洗float64和int64字段可以直接进模型。第三件是统计缺失值用来规划填充策略。第四件是看标签分布如果类别极不均衡后面的评估指标和模型选择都要跟着变。参数层面有一个常见选择encoding参数的确定。国内竞赛数据用utf-8居多但偶尔会出现GBK或GB2312尤其是Excel导出的CSV。我的习惯是这样第一次读取失败或者出现乱码就把encoding依次换成gbk、gb18030、latin1去试探。不要默认什么都用utf-8也不要为了图省事直接latin1那会造成中文文本特征的不可逆损坏。还有一个容易被忽略的步骤查看字段名本身。赛题数据的字段名往往是脱敏后的feature_1、feature_2或者干脆是英文缩写。如果字段名本身含有人类可读的语义比如province、age、income那这个数据集的“难度”相对低一些因为你可以直接做业务理解。如果全是feature_xx那就要多留个心眼字段之间可能存在强相关性甚至存在“未来信息”这一点在避坑章节里细说。2.3 数据字典与评分标准比模型更早该读的两份文件很多人拿到压缩包眼睛直接盯着CSV文件却忽略了压缩包里另外两样东西数据字典和评分标准。数据字典通常是一个Excel或PDF文件里面写明每个字段的含义、取值范围、单位以及标签字段的说明。这部分信息看起来是“辅助材料”实际上包含了建模的关键先验。举个例子。如果数据字典里写“feature_7表示用户注册到首次消费的天数缺失值表示未消费”那么你对这个字段的处理方式就不该是简单填均值而应该构造一个“是否缺失”的二值特征同时把原有的天数数值保留。这种信息只有数据字典能给光看数据本身永远猜不出来。评分标准同样关键。同一个赛题评估指标可能是Accuracy、F1-score、AUC、MSE中的某一种。指标不同模型选择和调参方向完全不同。如果评分标准是AUC那么类别不均衡时就不需要做过度的重采样直接训练概率模型即可如果评分标准是F1-score那么阈值调整就比模型选择还重要。没有评分标准的竞赛少之又少但网络赛的评分标准有时不写在赛题公告里而是放在数据包的README文件里不读就是吃亏。我一般会在拿到数据的第一时间把数据字典和评分标准单独截图或复制到一个文档里然后才开始写代码。这样做的好处是后续每个处理动作都能对照着“这个字段是什么、目标指标是什么”来做决策而不是盲目地套模板处理。特别是指导老师带学生时这一步能省掉很多来回问“这个特征能不能这样处理”的时间。3. 清洗与特征工程从原始表到能直接喂模型的数据集3.1 缺失值与异常值的处理先看分布再决定填还是删赛题数据的缺失值处理是新手和熟练工分水岭最明显的地方。新手看到缺失值就 fillna(0) 或者 fillna(train[col].mean())熟练工则会先看缺失比例、缺失字段的类型、缺失值是否和标签相关。同样是填均值有的字段填完是救命有的字段填完是污染。我处理缺失值的标准流程是先算缺失比例然后分三档处理。缺失比例低于1%的字段直接删除对应样本缺失比例在1%到30%之间的字段用填充策略处理缺失比例超过30%的字段先检查是否为强特征如果不是就丢弃如果是则保留并增加一个“是否缺失”标志位字段。# 计算缺失比例 missing_ratio train.isnull().mean().sort_values(ascendingFalse) print(missing_ratio) # 分档处理阈值按实际情况调整 low_missing_cols missing_ratio[missing_ratio 0.01].index train train.dropna(subsetlow_missing_cols) # 中高缺失字段数值型用中位数填充类别型用众数填充 for col in missing_ratio[(missing_ratio 0.01) (missing_ratio 0.3)].index: if train[col].dtype in [float64, int64]: train[col] train[col].fillna(train[col].median()) test[col] test[col].fillna(test[col].median()) else: train[col] train[col].fillna(train[col].mode()[0]) test[col] test[col].fillna(test[col].mode()[0]) # 高缺失字段保留原值额外加一列是否缺失 for col in missing_ratio[missing_ratio 0.3].index: if train[col].notna().mean() 0.2: # 即使高缺失仍有部分有效信息 train[col _missing] train[col].isnull().astype(int) test[col _missing] test[col].isnull().astype(int)这里有几个参数值得说明。第一中位数填充比均值填充更稳健因为赛题数据里经常有离群值均值会被拉偏中位数不会。第二类别字段填充用众数直接用0填充会让模型以为这是一个新类别引入误导信息。第三测试集的填充必须在训练集的填充之后用同样的统计值绝对不能把训练集和测试集拼在一起算中位数——这会造成数据泄露后面避坑章节会专门讲。填充之后的异常值处理要格外谨慎。赛题数据的“异常”很多时候是真实业务特征比如一个用户的行为次数特别高不是错误而是头部用户。我只会对明显超出物理边界的值做处理比如年龄出现负数、数值型字段出现字符串内容。最安全的做法是看describe输出里的min和max如果发现极端值先用百分位截断把超过99%分位数的值拉回到99%分位数的水平再观察效果。3.2 类别特征编码与文本特征的向量化路线赛题数据里总有那么几列是字符串。性别、城市、职业、设备类型甚至还有一条评论、一段日志摘要。这些字段不能直接塞进模型需要编码成数值。类别特征的编码方式选择主要看这个类别特征的取值数量。低基数类别特征取值少于10个的直接用pandas的factorize或者sklearn的LabelEncoder即可。但要注意一点LabelEncoder给类别赋予的是任意整数模型会把这些整数当成有序数值这在小树模型里问题不大在线性模型里就是灾难。所以对于低基数类别特征我更习惯用One-Hot编码让每个类别成为一个独立字段。高基数类别特征比如城市有几百个取值或者用户ID这种特征One-Hot会炸掉维度LabelEncoder又丢失含义。竞赛里的常规做法是用频次编码或者目标编码。频次编码就是统计每个类别在训练集中出现的次数把这个次数作为特征值目标编码则是用每个类别对应的标签均值来编码。目标编码效果往往更好但需要配合交叉验证使用否则容易过拟合。from sklearn.preprocessing import LabelEncoder # 低基数类别特征One-Hot编码 low_card_cols [device_type, os_version] train pd.get_dummies(train, columnslow_card_cols) test pd.get_dummies(test, columnslow_card_cols) # 高基数类别特征频次编码 for col in [city_id, merchant_id]: freq_map train[col].value_counts() train[col _freq] train[col].map(freq_map) test[col _freq] test[col].map(freq_map) train[col _freq] train[col _freq].fillna(0) test[col _freq] test[col _freq].fillna(0)pd.get_dummies对于训练集和测试集之间类别不一致的情况会自动补零这是它的优点。但有个坑如果测试集出现了训练集中没见过的类别get_dummies会把它编码成全零向量相当于这个样本在所有类别上的取值都是0。如果这个“未知类别”本身有业务含义需要额外加一列标志位否则模型无法感知这个差异。所以上面的代码里我会对高频类别字段先做频次编码这样新类别的频次是0模型至少能知道这个字段“没见过”比单纯的全零向量多一个维度。文本特征的处理要看文本长度。如果只是几个短标签字段比如商品标题、评论短语直接并入类别特征做编码即可。如果文本字段是长文本比如一段评论文本那就需要单独处理。网络赛的数据体量不至于大到必须用深度学习TF-IDF向量化加简单模型是最稳妥的方案。from sklearn.feature_extraction.text import TfidfVectorizer # 文本字段向量化限制特征数量防止维度爆炸 tfidf TfidfVectorizer(max_features2000, ngram_range(1, 2), stop_wordsenglish) train_text tfidf.fit_transform(train[review_text].fillna()) test_text tfidf.transform(test[review_text].fillna()) # 合并到特征矩阵 from scipy.sparse import hstack X_train hstack([train.drop(columns[review_text, label]).values, train_text]) X_test hstack([test.drop(columns[review_text]).values, test_text])max_features取2000是一个比较中庸的值既能保留主要词汇又不会让矩阵大到内存吃不消。ngram_range(1,2)表示同时考虑单词和相邻两个词组成的短语对短文本的情感判断有帮助。fit_transform和transform分开调用的原因也很关键TF-IDF的词表和学习到的idf权重只能基于训练集拟合测试集只能transform不能在测试集上重新计算词频统计。3.3 特征筛选与训练集/验证集划分避免把答案漏进模型特征工程做完之后特征数量往往会膨胀到几千甚至上万。这时候直接全部喂给模型训练慢不说还引入了大量噪声。特征筛选的目的是在保持模型效果的前提下减少特征数量。最常见的做法是先用模型自身的特征重要性来筛选或者用相关性分析删掉高度相关的冗余字段。# 相关性分析去除强相关特征 corr_matrix train.corr() high_corr_pairs [] for i in range(len(corr_matrix.columns)): for j in range(i 1, len(corr_matrix.columns)): if abs(corr_matrix.iloc[i, j]) 0.95: high_corr_pairs.append((corr_matrix.columns[i], corr_matrix.columns[j])) # 删除其中一个保留和标签相关性更高的那个 drop_cols set() for col1, col2 in high_corr_pairs: label_corr1 abs(train[label].corr(train[col1])) label_corr2 abs(train[label].corr(train[col2])) drop_cols.add(col1 if label_corr1 label_corr2 else col2) train train.drop(columnslist(drop_cols)) test test.drop(columnslist(drop_cols))这里0.95是经验阈值表示两个特征之间的线性相关程度极高保留一个即可。判断保留哪个的依据是和标签的相关性相关性高的那个保留。要注意这个方法只能发现线性相关对非线性关系的冗余特征无能为力所以后面还要用模型的特征重要性再筛一轮。训练集划分是整个流程里最不能出错的一步。赛题数据往往只有一份训练集你需要从里面切出验证集来评估模型效果。网络赛时间紧张很多人用train_test_split随便一切就开始训练这里有两个原则必须守住第一划分时设置random_state保证每次跑出来的结果一致否则你没法判断模型效果的好坏是模型变动还是数据划分的随机性导致的第二划分必须在特征工程之前完成至少也要在涉及标签统计的特征构造之前完成。如果在划分前做了目标编码那验证集的目标信息就已经混进训练过程了。from sklearn.model_selection import train_test_split X train.drop(columns[label]) y train[label] X_train, X_val, y_train, y_val train_test_split( X, y, test_size0.2, random_state42, stratifyy ) print(训练集样本数:, X_train.shape[0]) print(验证集样本数:, X_val.shape[0])stratifyy这个参数在分类任务里特别有用它保证训练集和验证集的标签分布保持一致。比如原始数据里正样本占10%stratify之后训练集正样本还是10%左右。不设置stratify万一切出来的验证集正样本占50%你在验证集上的分数会虚高模型到了真实测试集上就会露馅。回归任务不用stratify但可以考虑按标签分箱后做分层或者直接用K折交叉验证保证稳定性。4. 建模与调参让模型分数从“能跑”到“能打”4.1 基线模型选择从逻辑回归或LightGBM起步特征矩阵准备好之后第一个模型不应该是你最强的模型而应该是最简单、最快、逻辑最透明的模型。这个基线模型的价值不是拿高分而是给你一个分数基准和一套完整的执行链路。有了基准后面所有模型改进都能量化提升了多少退步了多少一目了然。对于分类任务我习惯先用逻辑回归跑一遍同时记录训练时间和验证分数。逻辑回归的优点是训练快、可解释性强、不容易过拟合缺点是对非线性特征组合和高维稀疏特征的表现一般。如果逻辑回归在验证集上的分数就已经不错说明赛题数据的线性可分性较强后面即便换复杂模型也未必能提升太多如果逻辑回归得分很低说明特征和标签之间主要是非线性关系直接上树模型。from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score # 先处理为稠密矩阵逻辑回归不支持稀疏矩阵 X_train_dense X_train.toarray() if hasattr(X_train, toarray) else X_train X_val_dense X_val.toarray() if hasattr(X_val, toarray) else X_val model_lr LogisticRegression(C1.0, max_iter500, random_state42) model_lr.fit(X_train_dense, y_train) val_pred model_lr.predict(X_val_dense) print(逻辑回归验证集准确率:, accuracy_score(y_val, val_pred))C是正则化强度的倒数C越小正则化越强泛化能力越好但低于0.01时可能欠拟合。max_iter设置到500是为了保证收敛因为逻辑回归在高维稀疏特征下需要更多迭代次数才能收敛。如果发现训练时间过长优先减少max_iter其次调低特征数量而不是一开始就调小C。跑通逻辑回归之后马上换LightGBM跑一遍。LightGBM在各类竞赛里几乎是标配级的存在它对数值型特征和类别型特征的适应性好训练速度快还自带特征重要性输出方便后续分析。import lightgbm as lgb model_lgb lgb.LGBMClassifier( n_estimators300, learning_rate0.05, max_depth6, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) model_lgb.fit(X_train, y_train, eval_set[(X_val, y_val)], eval_metriclogloss) val_pred_lgb model_lgb.predict(X_val) print(LightGBM验证集准确率:, accuracy_score(y_val, val_pred_lgb))这段代码里的参数基本是一套通用的起点配置如果赛题数据是分类任务多数情况下这套参数不会太差。n_estimators300表示最多训练300棵树配合learning_rate0.05相当于用较小的步长慢慢逼近比一次性加大树数量更能防止过拟合。max_depth6是树的深度上限限制得太浅会欠拟合太深会过拟合且训练极慢。subsample和colsample_bytree都是0.8前者表示每棵树只用80%的样本后者表示每棵树只用80%的特征这两个参数都是抗过拟合的网络赛数据量不大时调成0.8比较稳妥。4.2 三个必调的参数树的深度、学习率、子采样比例赛题数据的参数调优我不建议照着一大堆参数网格搜索从头调到尾。竞赛时间有限真正影响分数的参数就那么几个把这三个调明白你的分数就能从“能跑”提升到“能打”。第一个是num_leavesLightGBM里控制树复杂度的核心参数。它的含义是一棵树最多有多少个叶子节点默认值是31。叶子数越大模型越精细但也越容易过拟合。调参时应该观察验证集分数的变化如果训练集分数很高而验证集分数上不去基本就是num_leaves太大了。反过来如果两者都在低水平徘徊那就是欠拟合需要增大num_leaves。第二个是learning_rate。这个参数控制每棵树对最终预测的贡献权重学习率越小模型需要越多棵树才能达到相同的精度但每个单棵树造成过拟合的风险也更低。调参的正确姿势是先把学习率定在一个较低的值比如0.05然后通过早停法确定最佳树数量。不要一开始就把学习率设成0.1然后放任训练几百棵树很容易过早过拟合。第三个是subsample和colsample_bytree这对“兄弟参数”。它们的作用是给模型加随机性降低树与树之间的相关性从而提升整体泛化能力。对于数据量在几万行这个量级的赛题数据这两个参数设置在0.7到0.9之间比较合理。过低的子采样比例会让模型欠拟合因为每棵树看到的数据太少了。# 用早停法确定最佳树数量避免来回手动试 model_lgb lgb.LGBMClassifier( n_estimators1000, learning_rate0.03, max_depth5, num_leaves24, subsample0.8, colsample_bytree0.8, random_state42 ) model_lgb.fit( X_train, y_train, eval_set[(X_val, y_val)], eval_metriclogloss, callbacks[lgb.early_stopping(stopping_rounds50, verboseTrue)] ) print(最佳迭代轮数:, model_lgb.best_iteration_)early_stopping的stopping_rounds50表示如果连续50轮验证集上的logloss不再下降就停止训练。这样你不需要手动确定n_estimators模型自己会告诉你最佳树数量。这里有一个经验值learning_rate降到0.03时最佳树数量通常在300到700之间如果早停得到的树数量超过了900说明学习率太小或者数据量太大训练时间会不划算可以适当调高学习率。调完这三个参数后记得用model_lgb.feature_importances_把特征重要性打出来看一眼。不是每个高重要性的特征都该保留但总能发现几个重要性极低的字段。把这些低重要性特征删掉再跑一次模型分数一般会稳定训练时间缩短这就是特征筛选和调参的良性循环。4.3 模型对比与简单融合把多个预测结果拼成更稳的答案单一模型调参调到一定程度就会进入瓶颈期这时候再看验证集的分数已经很难涨了。竞赛里突破瓶颈的常规做法是模型融合。网络赛的时间一般只有几小时到一天做复杂的Stacking可能来不及而且容易过拟合。性价比最高的做法是“投票融合”和“概率平均”。投票融合适合分类任务。逻辑回归、LightGBM、随机森林各出一个预测类别三个模型投出不同答案取多数票作为最终结果。投票融合的好处是稳定但前提是这几个模型的错误模式不能太相似如果它们都是同一种树模型的不同参数版本投票融合几乎没有提升空间。概率平均适合所有任务。每个模型输出一个预测概率对多个模型的概率取加权平均权重按验证集分数来定分数高的权重高一些。相比硬投票概率平均保留了模型的不确定度信息效果往往更好。from sklearn.ensemble import RandomForestClassifier from sklearn.ensemble import VotingClassifier # 训练随机森林作为第三个模型 model_rf RandomForestClassifier( n_estimators200, max_depth10, min_samples_leaf5, random_state42, n_jobs-1 ) model_rf.fit(X_train, y_train) # 集成三个模型软投票模式 ensemble VotingClassifier( estimators[ (lr, model_lr), (lgb, model_lgb), (rf, model_rf) ], votingsoft, weights[1, 2, 1] ) ensemble.fit(X_train, y_train) val_pred_ensemble ensemble.predict(X_val) print(集成模型验证集准确率:, accuracy_score(y_val, val_pred_ensemble))VotingClassifier的votingsoft表示用各模型的预测概率做加权平均而不是硬投票。weights[1,2,1]表示LightGBM的权重是逻辑回归和随机森林的两倍因为之前单独验证时LightGBM的分数更好。这个权重不是拍脑袋定的是按三个模型在验证集上的相对分数比例来配置的。如果融合后验证集分数没有上升也不要意外融合在小数据量上的增益本来就是有限的它能带来的是测试集上更稳定的表现降低翻车概率。5. 网络赛避坑清单从数据泄露到提交失败的五个坑5.1 坑一数据泄露让你验证集分数虚高现象训练出来的模型在验证集上的准确率高达98%一提交测试集分数只有60%多排名直接掉到中下游。原因特征工程阶段在划分训练集和验证集之前用了全量数据去做填充、编码、归一化。比如用整个train表的均值填充缺失值验证集的缺失值其实已经被训练集的信息“喂饱”了模型在验证集上相当于开卷考试。还有一种常见泄露用全量数据做了目标编码验证集的标签统计已经混进了特征里。解决严格遵循“先划分后处理”的顺序。先把训练集切成模型训练集和验证集然后只在模型训练集上计算填充值、编码映射、归一化参数验证集和测试集只能“继承”这些参数不能参与计算。写代码的时候把划分操作放在所有特征工程的第一个单元格里后面所有处理都基于划分后的变量。5.2 坑二CSV编码问题导致中文全部乱码现象用pandas读取赛题数据后发现中文字段列名或者文本特征里全是乱码比如“ç”开头的一堆符号或者直接报UnicodeDecodeError错误。原因赛题数据的CSV文件不是utf-8编码而是GBK或者GB18030。国内有些比赛平台从Excel直接导出CSV时默认使用本地化的GBK编码没有做转换。解决读取时指定encodinggbk如果还乱码就换成encodinggb18030。gb18030是gbk的超集兼容性更强。如果读取之后再转码标记数据已经坏了基本没法恢复所以必须在read_csv阶段就处理对。另外提醒一下读Excel格式时用pd.read_excel不需要指定编码但需要安装openpyxl库否则会报ImportError。5.3 坑三测试集的特征处理和训练集错位现象本地验证集分数正常提交测试集的结果后发现分数异常低甚至接近随机水平。原因特征工程阶段对训练集和测试集分别做了编码两边生成的字段顺序不一致。比如pd.get_dummies在训练集和测试集上分别调用训练集有10个类别测试集只有8个类别生成的One-Hot列数不同模型拿测试集去预测时特征矩阵的列对不上。解决所有特征编码类操作都必须先fit训练集再transform测试集。pandas的get_dummies做不到这一点它每次调用都是独立的。正确做法是用sklearn的OneHotEncoder或者CategoryEncoder先fit在训练集上然后用同一个encoder去transform测试集。遇到测试集出现新类别的情况设置handle_unknownignore让模型把新类别当作全零向量处理就不会报错了。from sklearn.preprocessing import OneHotEncoder # 先fit训练集再transform测试集保证列对齐 encoder OneHotEncoder(handle_unknownignore, sparse_outputFalse) train_encoded encoder.fit_transform(train[[device_type]]) test_encoded encoder.transform(test[[device_type]])5.4 坑四提交格式和样例文件不匹配现象辛辛苦苦跑完模型生成提交文件后平台报错说文件格式不对或者提示“提交文件列数不符”连分数都没有。原因竞赛平台的提交格式通常是固定的要求第一列是样本ID第二列是预测标签。但不同赛题的列名不同有的要求ID列叫id有的要求叫sample_id标签列名字段可以是prediction、label、target。还有一些比赛要求提交概率而不是类别或者要求标签从1开始而不是从0开始。解决压缩包里那个sample_submission.csv不是摆设。先把样例文件读出来用pd.read_csv(sample_submission.csv)查看它的列名和格式然后把自己的预测结果输出成完全一致的格式。提交前做最后一分钟检查行数是否和测试集一致是否有多余的空行或逗号是否有NaN值。这三项是高频翻车点。# 严格按照样例文件格式输出 submission pd.read_csv(sample_submission.csv) submission[label] val_pred_ensemble submission.to_csv(submission.csv, indexFalse, encodingutf-8) # 提交前自检 assert submission.shape[0] test.shape[0], 行数不一致 assert submission[label].notna().all(), 存在NaN值 print(提交文件检查通过已输出 submission.csv)5.5 坑五本地结果不可复现现象同一份代码同一份数据昨天跑出来的分数和今天跑出来的不一样或者两次运行同一套参数结果不同。原因模型训练中存在随机性。树模型的子采样、LightGBM的特征抽样、神经网络的权重初始化都依赖随机数生成器。没有固定随机种子每次运行都会产生不同的结果。这对调参过程是致命的因为你无法判断分数变化到底是参数调整带来的还是随机波动带来的。解决在所有涉及随机的操作处设置random_state包括train_test_split、模型初始化、交叉验证划分。可以顺手在代码开头设置一次全局随机种子。固定种子后同一份代码在任何机器上跑出来的结果应当一致这样你每一版的分数变化才是真实可信的。import random import numpy as np # 设置全局随机种子保证可复现 random.seed(42) np.random.seed(42)6. 用一份复盘模板锁定分数赛后验证与可复现提交比赛结束不等于工作结束。一套赛题数据做下来最有价值的沉淀不是提交的那份结果文件而是中间产出的实验记录。我习惯在每轮实验结束时把一个简单的实验记录追加到一个Markdown文件里记录内容包括模型名称、特征版本、参数配置、验证集分数、训练耗时、备注。不用写得多花哨能让自己在几周后回看时还能秒懂就行。| 实验编号 | 模型 | 特征版本 | 关键参数 | 验证分数 | 备注 | |---------|------|----------|----------|----------|------| | 01 | LogisticRegression | v1 | C1.0 | 0.812 | 基线 | | 02 | LightGBM | v1 | lr0.05, leaves31 | 0.854 | 首次换树模型 | | 03 | LightGBM | v2 | lr0.03, leaves24, early_stop | 0.861 | 调参早停 | | 04 | Voting(lrlgbrf) | v2 | soft voting | 0.866 | 融合有效 |这张表的价值在赛后复盘时特别明显。你会发现分数提升最大的动作往往不是某个参数而是某个特征工程的改动或者一次数据泄露的修复。把这些经验整理成自己的竞赛模板下次遇到相似结构的数据时不需要重新踩一遍坑——直接按最优路径走。提交结束到分数公布之间有一件容易被忽略的事保存好最终版本的特征处理脚本和模型权重。网络赛偶尔会有申诉环节如果赛方公布分数后发现某道题判分有误你需要能完整复现自己的提交结果。我的做法是把最终提交用的notebook导出为.py脚本连同特征工程和参数配置一起压缩归档命名带上日期和版本号。这样即使几个月后赛方要求复核也能立刻还原现场。这套方法的起点和终点都是那份被很多人忽视的“赛题数据”。数据本身不会直接给你答案但数据的结构、缺失模式、字段语义已经暗示了出题人想考什么。先把数据分析清楚再让模型说话——这句话陪我打完了好几场比赛希望帮到你。本文还有配套的精品资源点击获取