这套“电池异常检测”赛题,说难听点就是“卷死人不偿命”。我最后拿到第二名的时候,其实没有用什么特别黑科技的模型,更多是把数据、特征、验证和融合这四个环节拧成了一条非常稳的流水线。这篇文章不打算讲虚的,直接把我在能源AI挑战赛里的完整方案拆开,包含我在做的特征怎么设计、模型怎么搭、训练怎么填坑,以及最后排名如何去突破的思路。
先说清楚这套方案适合谁看:如果你正要打类似的异常检测比赛,或者实际业务里遇到电池、设备传感器这类时序数据的异常识别问题,那这篇文章基本可以当一份SOP来用。不需要你有特别深厚的机器学习理论,但如果你能用Python处理数据、调过LightGBM,理解起来会非常顺畅。
1. 赛题理解与整体思路
很多队伍死在起跑线上,不是因为模型不给力,而是对“电池异常检测”这个任务本身理解得太浅。比赛给的数据通常是一批电芯在长时间充放电循环中的多条时间序列,每条序列记录电压、电流、温度、SOC等物理量。目标是要在样本维度上判断哪些电芯或哪些片段存在异常,而且往往不是简单的二分类,而是需要兼顾检测率和误报率的综合评价指标。
1.1 赛制与数据形态
先搞清楚数据长什么样,这直接决定你后续所有方案的方向。我遇到的比赛数据大概长这样:每个电芯有若干次充放电循环,每次循环内又有若干秒级的采样点。原始表字段包括时间戳、循环序号、电流、电压、温度、内阻、SOC等,但正负样本比例很悬殊,异常电芯大概只占到百分之几。
这里有个关键点:异常标签到底标在哪个粒度。有的比赛标在电芯级别,有的标在循环片段级别,还有的是纯无监督异常检测,只给正常样本。我这次是半监督形态——训练集里有一部分已知异常样本,但测试集里混杂更多数据分布偏移的异常,所以纯靠有监督硬学是不够的。
对数据形态的理解要落到几个具体问题上:时间序列的长度是否一致、采样的频率是否均匀、是否存在缺失段、异常是突发型的还是缓慢衰退型的。比赛里大量异常其实是“隐式异常”,单看某一个时间点的数值都在正常范围内,但放在一段时间窗口里看斜率、波动性、相关性就会露馅。
1.2 评价指标与提分空间
赛题的得分公式直接决定了你该优化什么。常见的是F1-score,也就是precision和recall的调和平均,但很多电池异常检测赛题用的是一种“排序期望”类的指标,比如对预测概率排序后,优先看前K个样本中能抓到多少真实异常,类似P@K或prAUC。
我当时特别做了指标反推:如果用的是F1,那阈值选择非常关键,而阈值又依赖于验证集的划分方式;如果用的是排序类指标,那目标就是让异常样本的分数尽量靠前,而不是纠结于绝对0/1输出。这一点对后续的模型设计有方向性影响。
我提交的初期版本是按F1优化的,结果榜单上的排名一直卡在中游。后来仔细读了赛题说明,发现最终排名用的是另一个排序指标,于是我把模型输出从离散的阈值判定改成纯概率排序,再在验证集上重新调了阈值,名次一下就往上走了几位。
1.3 整体方案路线
最终我的方案长这样:基于领域特征构造的LightGBM和CatBoost作为底座,加了一层基于滑动窗口的时序深度模型,再叠加无监督重构误差分支,最后用两层stacking加上后处理规则做融合。
整个流程分四步:
- 数据清洗与样本重组:把原始循环级时间序列切成长短一致的窗口样本,并构造有意义的统计特征。
- 模型分层训练:先训一堆拥有不同视角的基模型,保证模型之间差异性足够大。
- 特征与模型融合:用简单但有效的融合策略,不做过于复杂的网络,防止过拟合。
- 阈值与后处理:根据验证集分布和业务逻辑,针对性修正边界样本的判定。
每个环节的内容上面碰到的大坑都不少,下面每个部分我详细拆开讲。
2. 数据清洗与特征工程
特征工程不是锦上添花,而是决定模型上限的下限。尤其这种物理量时序数据,如果你不注入对电池工作原理的理解,模型很难凭空学出真正的异常模式。我在比赛中大概花了三分之二的时间在这里。
2.1 数据清洗:从原始时间序列到样本集合
第一步的工作优先级是解决样本割裂的问题。原始数据里一个电芯有多个循环钳段,循环次数和采样长度各不相同,如果直接当成独立样本,模型会混淆“循环阶段不同”和“真的异常”这两种变化。我对样本进行了标准化对齐:
- 以电芯ID为分组单元,把连续采集的各个循环串联成完整序列。
- 对缺失值做插值,但要对异常片段标注后才处理,否则会抹掉异常模式。
- 剔除明显非物理的样本点,例如突然跳出几百伏的电压值,或者温度直接降到绝对零度这种采集器错误。
然后是切窗的参数选择。我试过固定步长的滑动窗口和按循环切分两种方式。固定步长滑窗适用于捕捉局部突跳型异常,但会让同一个电芯产生大量窗口样本,容易导致同一电芯在不同窗口间出现重复计数;按循环切分则保留完整充放电过程,对缓慢老化型异常更友好。
最后我采用了一个混合策略:第一版模型用按循环切分的样本,第二版模型用滑动窗口样本。两种视角各有盲区,最后融合却能互补,这也符合为什么融合能提升成绩的逻辑。
2.2 特征体系:基于领域知识的特征
纯用原始电压电流序列丢给树模型,效果非常差。真正有价值的特征是把物理量之间的关系、变化过程以及周期性差异给提炼出来。
我按这几类特征来建设:
- 统计特征:每个窗口内电压/电流/温度的均值、方差、峰度、偏度、极差、分位数。这些是基础,不加没分,加了加分有限,所以只能作为底座。
- 变化率特征:相邻点电流差的绝对值、电压变化速率、温度变化斜率的陡峭程度。异常电池往往在充放电切换瞬间出现抖动或者电压迟滞。
- 循环间趋势特征:同一电芯不同循环之间容量衰减的斜率、内阻上升的速度、恒压阶段时长的变化趋势。这是我最看重的一类特征,因为它体现了时间维度上的缓慢漂移。
- 频域特征:对电压序列做FFT变换,提取主要频率的能量占比和功率谱密度变体。异常振动或者噪声往往在高频段表现出不一样的能量分布。
- 相关性特征:电压与电流之间的皮尔逊相关系数,SOC与电压之间的映射偏差。电池内部故障会导致本来强相关的物理量出现解耦。
举个例子,一个某关键电芯的异常类型是电压在恒流充电阶段突然跳变,统计特征完全看不出问题,因为平均值和方差都在正常范围;但滑动窗口的电压一阶差分最大值这一特征,能直接把这种现象拉高几个数量级。
2.3 降维与筛选
特征构造出来会疯狂膨胀,我记得我当时堆了大概七八百个特征。盲目往模型里塞带来的问题就是训练慢、过拟合严重、feature importance看起来非常分散。我还是倾向于先用一组简单粗暴的筛选方式:
- 删除缺失率高于85%的特征。
- 删除完全相同的常数特征。
- 用LightGBM自带的重要性排序,保留累积重要性85%之前的特征。
- 用特征间的相关系数矩阵,把相关性超过0.95的重复特征只保留一个。
但这里有一个容易踩的坑:筛选特征时用了全量数据的关键,在比赛里不算泄漏,但在真实的项目里这会过拟合。比赛中这属于合理操作,不过你仍然需要注意筛选过程本身要放在训练折内部去做,否则会在验证集上产生虚高的分数。
关于特征工程的整体心得,我认为不要只追求“多”,而是要让特征覆盖三类异常模式:突发型异常、周期相关型异常、趋势漂移型异常。只要这三类视角都有特征能表达,模型的基本盘就有保障了。
3. 模型架构与集成策略
很多队伍到了这一环节就开始各种堆模型,LightGBM、XGBoost、CatBoost、LSTM、Transformer,全部塞进去再随便加个平均。问题是,模型之间如果差异不够大,融合结果不会有实质提升。我这次讲究的是“多层视角互补”。
3.1 树模型基线:LightGBM与CatBoost的取舍
树模型是这类表格特征任务的首选基线。我同时训练了LightGBM和CatBoost,两个模型的优势不同:
- LightGBM速度极快,处理高维稀疏特征和大量类别特征非常顺手,在普通统计特征上表现很稳。
- CatBoost在类别特征处理上有原生优势,而且它对噪声容忍度较高,预测出来的概率分布跟LightGBM的分布差异比较明显,这正好为后续融合提供多样性。
这里给一个LightGBM的参考配置,是我在比赛里反复调过、最终验证集F1最高的一组参数:
import lightgbm as lgb params = { "objective": "binary", "metric": "auc", "boosting_type": "gbdt", "learning_rate": 0.01, "num_leaves": 64, "max_depth": 7, "min_child_samples": 20, "feature_fraction": 0.7, "bagging_fraction": 0.8, "bagging_freq": 1, "lambda_l1": 0.5, "lambda_l2": 1.0, "verbose": -1, "seed": 2024, }num_leaves我故意调得比较大,因为后面要靠融合来压制方差,太保守的树模型反而容易欠拟合。注意训练时早停的策略也很关键,验证集指标一停就拉倒,别重复训练太久,竞赛里时间比过度调参更宝贵。
3.2 深度学习分支:滑动窗口的时序视角
树模型使用特征工程出来的一整块向量,但其问题在于强行割裂了序列关系。所以我加了一条深度学习分支,直接吃原始时间序列窗口。
我尝试过两个结构:
- LSTM + Attention:将滑动窗口内的电压、电流、温度作为输入,经过两层LSTM后接attention池化,输出一个序列嵌入向量,再接分类层。
- 1D CNN + GRU:用1D卷积先提取局部波形特征,再用GRU捕捉长期依赖。效果上比纯LSTM略好一点,主要优势在于训练速度更快,且对短窗口的突跳型异常更敏感。
深度模型这里我没有追求特别复杂的网络。因为异常电池样本数量本来就少,太深的模型会直接过拟合,训练噪声一堆。整个网络大概几百万参数,训练50个epoch左右就够了。
训练时还有个特殊处理,就是使用了截断正态分布的数据增强:对正常样本的电压曲线叠加一个极小幅度的随机噪声,人为增加正常样本的多样性。这个操作在老手看来其实在异常检测任务里非常有价值,因为正常样本的变化范围本来就该被模型充分认识。
3.3 无监督重构分支:用AutoEncoder抓“没见过的异常”
竞赛里最好用的往往不是有监督模型,而是无监督。因为异常模式不止训练集里的那几种,测试集里很可能有新的未知异常。纯有监督模型面对新异常时完全无能为力,所以异常检测比赛里AutoEncoder几乎是标配。
我训练了一个结构非常简单但又很有效的AutoEncoder:
- 输入层:单个滑动窗口的归一化序列。
- 编码器:两层全连接,激活函数选ReLU,中间维度压到32维。
- 解码器:两层全连接,还原到原始输入维度。
- 重构误差:用MSE计算输入与重构输出的差异。
正常样本模式单一,AutoEncoder能在很少的训练轮次内就把它们记住并重构得很好;异常样本因为“没见过”,重构误差会明显偏大。我直接把重构误差作为一个额外特征丢给融合模型,效果提升非常明显。
这里有一个必须强调的细节:训练AutoEncoder时只用训练集中的正常样本,千万不要把异常样本放进去。否则模型连异常也能重构得不错,重构误差就失去了判别能力。
3.4 模型融合:别瞎堆,要有策略
融合不是把所有输出做加权平均那么简单。关键点在于每个模型的预测应该具备不同的“视角”,而不是同一个特征的变体。我最后融合的输入变量大致是这四类:
- LightGBM输出的概率分数。
- CatBoost输出的概率分数。
- LSTM+Attention输出的概率分数。
- AutoEncoder重构误差的归一化分数。
融合方式我建议用两层结构:第一层先把四个模型的输出都做Platt Scaling校准到同一个分布空间,第二层用一个逻辑回归或浅层LightGBM去学习如何组合这些输出。简单说,就是让元模型判断哪个分支在哪种样本上更值得信任。
这个过程能带来多少提升?以我的经验,无脑平均大概能提升两到三个千分点,而校准后带元模型的融合能提升五到八个千分点。差别非常显著。
4. 训练与优化细节
方案搭好了,接下来就是训练过程的精细控制。竞赛里那些“分不高但看起来很忙”的队伍,往往差就差在训练策略和验证策略没有对齐。
4.1 验证集划分:别让同电池的样本跨越折
这块我要特别单独讲,因为太重要了。电池数据不是独立同分布的数据,同一个电芯产生的不同循环样本之间存在强关联。如果你按普通KFold随机划分,同一个电芯的样本既出现在训练集又出现在验证集,模型就相当于“偷看”了答案,验证集分数会虚高得一塌糊涂,线上表现却掉得厉害。
正确做法是使用GroupKFold,以电芯ID或电池ID作为分组依据。这样同一个电芯的所有样本不会同时分散到训练集和验证集,验证结果才稳健。
我另外还做了一个贴近线上分布的划分:把时间靠后的电芯作为验证集,模拟“预测未来”的真实场景。这样做能更早暴露模型在分布偏移上的问题。因为电池数据的采集时间顺序很重要,异常的发生概率和传感器标定都会随时间漂移。
4.2 样本不均衡:别只知道过采样
异常电池占比通常在5%以下。几万个正常样本对几百个异常样本,直接训练会大概率退化成“全部预测正常”,指标看似挺高,其实根本没用。
我实验了三种处理方式:
- 直接调整LightGBM的is_unbalance参数,让它反向调节样本权重。这个最省事,效果也不错。
- 对异常样本做SMOTE过采样。在表格特征上是可行的,但需要特别小心,别在时序窗口上瞎插值,否则容易生成不物理的样本。
- 对异常样本分配较大的自定义样本权重,比如用正常样本数除以异常样本数得到权重比。
实际表现最稳的组合是:is_unbalance开上,同时给异常样本补一个1.5到2倍的额外权重系数。过采样在这个比赛里反而容易在验证集上虚高,因为过采样很可能让模型把某个特定的噪声模式当作常见模式。
4.3 后处理:阈值和规则的临门一脚
模型输出的概率本身不是最终答案,你还需要把它变成判定结果。我在后处理环节做了两个重要操作:
一是阈值搜索。在验证集上遍历阈值,找到F1最高或排序指标最优的阈值点。这个操作老生常谈,但关键是搜索区间要画细一点,步长设置到0.001,否则容易错过最优区间。
二是规则修正。经过对误报样本的分析,我发现有一部分被预测为异常的样本,其实它们在原始数据里对应的是“充电切换瞬间”的窗口,属于电池正常工作必然出现的瞬时波动。这些样本确实有高重构误差,但业务上不该被判定为异常。
针对这种情况,我加了一条规则:如果某个窗口的前后20%时间点电压变化率都在正常范围内,则将该样本的预测分数乘以0.7的惩罚系数。这个操作把误报率压低了差不多两个点,而代价只是漏掉极少数真正异常的案例。后处理是纯业务经验的产物,但恰恰是这类细微修正,能让你在排行榜上超越好几个队。
5. 实战中的坑与排查
这段放到后面写,是因为我在比赛过程中踩过太多坑,有些坑甚至让我绕了整整两天路。为了让后来者少走弯路,我把典型问题和排查思路整理成了速查表。
5.1 常见问题速查表
| 问题现象 | 可能原因 | 排查思路 | 解决方案 |
|---|---|---|---|
| 验证集F1很高,线上分数反而低 | 随机划分导致同电池样本泄漏 | 检查是否按电芯分组划分 | 改用GroupKFold或按时间划分 |
| 所有预测概率都在0.9以上 | 模型过拟合到噪声特征 | 查看特征重要性和树深度 | 调低num_leaves、增大正则 |
| AutoEncoder重构误差无区分度 | 异常样本参与了训练 | 确认训练集只用正常样本 | 过滤异常样本后重新训练 |
| LightGBM和CatBoost结果几乎一致 | 模型输入特征完全相同 | 对比两个模型的特征重要性 | 给两个模型分别使用不同特征子集 |
| 阈值怎么调都不能提升排名 | 评价指标理解错误 | 仔细阅读赛题的评分公式 | 按排序指标重新设计优化目标 |
| 深度模型训练波动极大 | 学习率过高或样本比例失衡 | 观察每个epoch损失曲线 | 降低学习率并增加warmup |
5.2 特征泄漏的典型案例
竞赛里最隐蔽的错误是特征泄漏。我遇到过一个很典型的泄漏:特征工程里生成了一个叫“当前循环相对上一个循环的电压差异”的特征,看起来特别合理,但在构造这个特征时,我用到了整个测试周期之后的数据,相当于在预测时偷看了未来的信息。
更常见的泄漏是标准化和数据缩放完成在全量数据上。如果先对整个训练加测试数据一起做StandardScaler,再划分训练验证,验证集的分数会被抬高。正确的做法是先只对训练集拟合缩放器参数,再应用到验证集和测试集。
竞赛圈子里有个铁律:所有数据预处理转换器的fit,只能在训练折内部完成。这条规则违反了,前面所有模型搭建都白搭。
5.3 过拟合的信号与对策
怎么判断自己过拟合了?最简单的办法就是看训练集和验证集指标差距。LightGBM如果训练集AUC接近0.999,验证集只有0.85,这个差值已经说明模型在死记硬背特征组合,而不是在学通用规律。
应对过拟合我一般按下面几步走:
- 增加leaf惩罚权重和L2正则项。
- 降低leaves数量,迫使模型学习更稀疏的模式。
- 增加特征采样率调节系数,减少每棵树的特征选择空间。
- 提前停止轮次,别让它把最后几个百分点的训练损失也硬吃了。
- 减少或剔除重要度很低的长尾特征,让模型专注核心信号。
在深度模型那边,除了正则,我还习惯用early stopping并保存验证集表现最好的checkpoint,而不是最后一个epoch的权重。同理,学习率衰减策略用余弦退火比固定学习率稳得多。
6. 实操心得与扩展建议
这段作为结尾,想说些方案之外但同样值得琢磨的东西。
6.1 让方案稳定的关键心法
我这次拿到第二名的体验是:比赛方案的上限不是由某个单独模型的性能决定的,而是由整套方案的稳定性决定的。你永远不知道测试集里有多少新异常模式,线上分数总有波动,如果模型对特征或阈值的微小变化极度敏感,那排名就变成了抽奖。
要让方案稳定,我总结三条心法:
- 多视角印证:同一个异常,最好能被至少两种不同类型的模型识别出来。这样融合后不会被单一模型的错误带偏。
- 小而稳优于大而玄:一个复杂度极低的模型,只要特征质量过硬,往往比一个精巧复杂但训练不充分的大模型更可靠。
- 快速迭代闭环:每次改动都记录验证集和线上分数对比,哪怕一个特征提升了0.001,也留档。这样可以清楚知道每一次变化是向上还是向下。
6.2 从比赛到真实业务的迁移
如果把这个方案迁移到真实场景,遇到的问题会更多。比赛里基础模型可以直接用标签训练,但在实际业务中,异常标签往往需要维修记录或人工标记来积累,数据标注成本极高。
真实场景里我更推荐把这个方案改造成“召回+研判”模式:
- 召回层:用AutoEncoder重构误差排序,把重构误差排名前5%的样本捞出来。
- 研判层:用LightGBM等有监督模型对召回样本做细粒度分类。
- 人工复核:保留阈值调整按钮,让一线的维护专家根据实际检修结果持续校准模型。
这种模式的好处是:它不指望模型一次性把判断做对,而是通过“系统找人 + 人校验系统”的循环,慢慢把规则沉淀到模型里,也把误报带来的信任成本降到最低。
6.3 如果重来,我会优化什么
最后的最后,说点实在的。如果再让我重新打一次这个比赛,我会把更多时间分配在深度模型的序列建模上。表格特征虽然让我拿了第二名,但从排行榜头部队伍的输出特征来看,真正拉开差距的很可能不是特征数量,而是对时序上下文建模的精细度。
我会尝试给每个电芯的电化学阻抗谱建模,然后把阻抗谱特征和时序特征融合成多模态输入。至于能不能有效果,坦白说没有做实验前谁也说不准,但方向上肯定值得一试。竞赛就是这样,永远有下一个可以优化的角落,这也是它之所以迷人的原因。