
简介这份资源面向计算机、人工智能、自动化等专业学生与开发者提供基于大航杯“智造扬中”电力AI大赛的数据挖掘管道搭建完整示例适合作为毕业设计、课程设计、竞赛练手或项目初期立项参考。压缩包共32个文件约3.72MB以22个ipynb笔记本和4个py脚本为主辅以3个csv数据文件、2个txt说明及1个md文档覆盖数据清洗、特征提取、模型训练与结果预测等环节并附有天气与节假日爬虫脚本。已有44人学习关注。读者可从中获得一套可运行的赛题方案理解从原始数据到预测输出的完整流程包括特征组合、样本划分与规则整理等关键步骤代码经过测试便于在此基础上修改扩展实现其他功能或直接用于毕设、课设与作业。1. 电力AI大赛的数据挖掘管道从原始表到可提交结果要过几道手“大航杯·智造扬中”电力AI大赛给的是典型工业时序数据多张表、多设备、多测点、时间戳不规整还夹着大量缺失和异常。很多人第一次拿到这种数据第一反应是直接上模型结果线下AUC看着还行线上提交直接翻车。问题不在模型在管道——数据挖掘管道搭建才是这类比赛真正的分水岭。这篇笔记面向两类人一是刚接触电力AI、想用Python把整条链路跑通的工程师二是做过Kaggle但没碰过工业时序、想知道电力场景下管道该怎么搭的熟手。我会按“数据接入→清洗对齐→特征构造→模型训练→结果输出”的顺序把每一步的代码、参数和踩坑点讲清楚让你能照着复现一套可提交的基线再往上叠自己的优化。2. 数据接入与表结构理解先把黑匣子拆开看2.1 电力AI大赛常见的数据组织方式这类比赛的数据通常以CSV或Excel形式给出表与表之间靠设备ID和时间戳关联。常见的有三类表测点定义表描述每个传感器测什么、单位是什么、运行数据表按时间戳记录各测点数值、标签表训练集才有标记某段时间是否发生目标事件。很多人拿到数据直接pd.read_csv一把梭结果发现时间列是字符串、测点列名是中文、缺失值用-9999表示——这些都是管道要处理的第一道关。我一般会先做三件事看表头、看时间范围、看缺失分布。不要急着合并先把每张表的粒度搞清楚。运行数据表可能是秒级、分钟级或15分钟级标签表可能是事件级只有起止时间两者粒度不一致时合并逻辑完全不同。import pandas as pd import numpy as np # 读取原始表注意编码和缺失值标记 raw pd.read_csv(train_data.csv, encodinggbk, na_values[-9999, , NULL]) label pd.read_csv(train_label.csv, encodinggbk) # 看时间列格式 print(raw[TIME].head(10)) print(raw[TIME].dtype) # 看测点列有哪些 sensor_cols [c for c in raw.columns if c not in [DEVICE_ID, TIME]] print(f测点数量: {len(sensor_cols)}) print(sensor_cols[:20]) # 看缺失比例 missing_ratio raw[sensor_cols].isnull().mean().sort_values(ascendingFalse) print(missing_ratio.head(15))这段代码的逻辑是先统一缺失值标记避免-9999被当成真实数值参与统计然后确认时间列格式决定后续用pd.to_datetime还是手动解析最后按缺失比例排序缺失超过70%的测点基本可以放弃强行填充只会引入噪声。参数上encodinggbk是电力行业CSV的常见编码如果报错就换utf-8或gb18030na_values要把所有可能的缺失标记都列进去否则后面填充时会把异常值当正常值处理。2.2 时间戳对齐与重采样策略电力数据的采集频率往往不统一有的测点1秒一个点有的1分钟一个点标签可能是事件触发才记录。管道里必须有一个统一的时间基准。我的做法是先确定标签的时间粒度然后把所有运行数据重采样到同一粒度。如果标签是事件级比如“某设备在10:00-10:15异常”就把运行数据聚合成15分钟窗口的统计量均值、最大值、最小值、标准差。# 统一时间格式 raw[TIME] pd.to_datetime(raw[TIME], format%Y/%m/%d %H:%M:%S) label[START_TIME] pd.to_datetime(label[START_TIME]) label[END_TIME] pd.to_datetime(label[END_TIME]) # 按设备时间排序 raw raw.sort_values([DEVICE_ID, TIME]).reset_index(dropTrue) # 重采样到15分钟取统计量 def resample_device(df): df df.set_index(TIME) agg df[sensor_cols].resample(15T).agg([mean, max, min, std]) agg.columns [_.join(col) for col in agg.columns] return agg.reset_index() resampled raw.groupby(DEVICE_ID, group_keysFalse).apply(resample_device) print(resampled.shape)这里的关键参数是resample(15T)T代表分钟15T就是15分钟。如果你的标签粒度是1小时就改成1H。agg里我选了均值、最大、最小、标准差四个统计量这是工业时序最常用的组合均值反映趋势最大最小反映波动范围标准差反映稳定性。注意groupby后apply会丢失设备ID列需要在函数里保留或后续合并。重采样后会出现新的缺失值某些窗口内没有原始数据下一步要处理。提示重采样前一定要确认时间列没有重复值否则resample会报错。用raw.duplicated(subset[DEVICE_ID,TIME]).sum()检查。3. 清洗与特征构造把原始表变成模型能吃的矩阵3.1 缺失值填充的三种策略与选择依据重采样后的缺失值分两种一种是短时缺失连续几个窗口没有数据一种是长时缺失设备离线或测点故障。短时缺失用前向填充或线性插值就够了长时缺失如果超过一定长度填充反而会误导模型更好的做法是加一个“缺失标记”列让模型自己学。# 短时缺失线性插值限制连续填充长度 resampled[sensor_cols_mean] resampled.groupby(DEVICE_ID)[sensor_cols_mean].apply( lambda g: g.interpolate(methodlinear, limit4) ) # 长时缺失加标记列 for col in sensor_cols_mean: resampled[f{col}_missing] resampled[col].isnull().astype(int) # 剩余缺失用中位数填充按设备分组 resampled[sensor_cols_mean] resampled.groupby(DEVICE_ID)[sensor_cols_mean].transform( lambda g: g.fillna(g.median()) )limit4表示连续最多填4个窗口即1小时超过就留空后面用中位数兜底。加_missing标记列是工业场景的常用技巧设备离线本身就是一种信号模型能学到“缺失即异常”的模式。中位数填充按设备分组是因为不同设备的量纲和正常范围差异很大用全局中位数会引入偏差。3.2 滑动窗口统计特征让模型看到趋势电力AI大赛的标签往往和“一段时间内的变化趋势”有关单点值的信息量不够。滑动窗口统计是必做的对每个测点计算过去N个窗口的均值、斜率、峰度等。N的选择要看标签的持续时间一般取3到12个窗口。def rolling_features(df, cols, windows[3, 6, 12]): for col in cols: for w in windows: df[f{col}_roll_mean_{w}] df.groupby(DEVICE_ID)[col].transform( lambda x: x.rolling(w, min_periods1).mean() ) df[f{col}_roll_std_{w}] df.groupby(DEVICE_ID)[col].transform( lambda x: x.rolling(w, min_periods1).std() ) df[f{col}_diff_{w}] df.groupby(DEVICE_ID)[col].transform( lambda x: x.diff(w) ) return df resampled rolling_features(resampled, sensor_cols_mean[:10], windows[3, 6, 12]) print(f特征数量: {resampled.shape[1]})min_periods1保证窗口开头也有值不会因为前几个窗口数据不足而丢样本。diff(w)是差分反映变化量对突变型异常特别敏感。注意这里只选了前10个测点做示例实际比赛中要对所有测点做但特征数量会爆炸需要后续做特征筛选。我一般会先算全部再用LightGBM的feature_importance筛掉重要性低于阈值的。3.3 标签对齐与样本构造标签表通常是事件级的需要把它转换成每个时间窗口的0/1标签。做法是对每个设备遍历标签中的事件区间把落在区间内的窗口标为1其余为0。注意正负样本比例电力异常通常极少可能1:1000需要做负采样或调scale_pos_weight。# 构造标签列 resampled[label] 0 for _, row in label.iterrows(): mask ( (resampled[DEVICE_ID] row[DEVICE_ID]) (resampled[TIME] row[START_TIME]) (resampled[TIME] row[END_TIME]) ) resampled.loc[mask, label] 1 print(resampled[label].value_counts()) print(f正样本比例: {resampled[label].mean():.4f})这段代码的逻辑很直白但有个坑如果标签区间跨设备或跨天iterrows会慢。数据量大时改用pd.merge_asof或区间索引。另外标签区间边界是否包含端点要看比赛说明有的比赛定义“事件开始时间”是异常已经发生有的定义是异常即将发生差一个窗口结果差很多。注意构造完标签后一定要检查每个设备的正样本数量如果某个设备全是负样本说明标签表里没有该设备的事件训练时可以考虑剔除或单独处理。4. 模型训练与验证别在时序数据上乱用交叉验证4.1 时序交叉验证的正确做法很多人习惯用StratifiedKFold但在时序数据上这是错的——它会用未来的数据预测过去线下分数虚高线上直接崩。正确做法是TimeSeriesSplit按时间顺序切分训练集在前验证集在后。from sklearn.model_selection import TimeSeriesSplit import lightgbm as lgb # 按时间排序 resampled resampled.sort_values(TIME).reset_index(dropTrue) feature_cols [c for c in resampled.columns if c not in [DEVICE_ID, TIME, label]] X resampled[feature_cols] y resampled[label] tscv TimeSeriesSplit(n_splits5) scores [] for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] model lgb.LGBMClassifier( n_estimators500, learning_rate0.05, num_leaves31, scale_pos_weight(y_train 0).sum() / (y_train 1).sum(), random_state42 ) model.fit(X_train, y_train) pred model.predict_proba(X_val)[:, 1] # 用AUC评估 from sklearn.metrics import roc_auc_score scores.append(roc_auc_score(y_val, pred)) print(fCV AUC: {np.mean(scores):.4f} ± {np.std(scores):.4f})TimeSeriesSplit的n_splits5表示切5折每折训练集逐渐增大。scale_pos_weight是LightGBM处理不平衡数据的参数设成负正样本比能让模型更关注正样本。注意TimeSeriesSplit不保证每折都有正样本如果某折验证集全负AUC无法计算需要手动跳过或调整切分点。4.2 特征重要性与筛选训练完一轮后用feature_importance看哪些特征有用。电力场景下通常roll_mean和diff类特征最重要原始单点值反而靠后。# 用全量数据训练一个模型看重要性 model_full lgb.LGBMClassifier(n_estimators500, learning_rate0.05, num_leaves31, random_state42) model_full.fit(X, y) importance pd.DataFrame({ feature: feature_cols, importance: model_full.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance.head(20)) # 筛掉重要性为0的特征 selected_features importance[importance[importance] 0][feature].tolist() print(f筛选后特征数: {len(selected_features)})重要性为0的特征直接删掉能减少过拟合风险。但注意重要性低不代表没用可能是被其他相关特征替代了。我一般会保留重要性前100的特征再手动加几个业务上认为重要的测点原始值。4.3 模型融合与阈值选择单模型AUC到0.85左右后提升空间有限可以试模型融合。LightGBM XGBoost CatBoost各训一个取概率平均。阈值选择不要用默认0.5按验证集上的F1或业务指标调。from sklearn.metrics import f1_score # 假设已有三个模型的验证集预测 pred_lgb model_lgb.predict_proba(X_val)[:, 1] pred_xgb model_xgb.predict_proba(X_val)[:, 1] pred_cat model_cat.predict_proba(X_val)[:, 1] pred_ensemble (pred_lgb pred_xgb pred_cat) / 3 # 找最佳阈值 best_thresh, best_f1 0.5, 0 for thresh in np.arange(0.1, 0.9, 0.02): f1 f1_score(y_val, (pred_ensemble thresh).astype(int)) if f1 best_f1: best_f1 f1 best_thresh thresh print(f最佳阈值: {best_thresh:.2f}, F1: {best_f1:.4f})融合时注意各模型的输出概率要校准过否则直接平均会被某个过自信的模型带偏。阈值搜索范围0.1到0.9步长0.02够用了。如果比赛指标是AUC阈值不影响AUC但影响提交后的排名如果按F1排。5. 避坑与排查电力AI管道里最容易翻车的五个地方5.1 时间戳时区不一致导致标签错位现象线下验证AUC很高线上提交分数极低。原因原始数据的时间戳可能是UTC标签表是本地时间差8小时导致标签和特征完全错位。解决统一转成同一时区或者直接看数据分布——如果标签区间集中在凌晨而特征峰值在白天大概率是时区问题。5.2 重采样后正样本被稀释现象正样本比例从1%降到0.1%模型学不到正样本。原因标签是事件级重采样到15分钟窗口后一个事件可能只覆盖1-2个窗口而负样本窗口数量巨大。解决对负样本做下采样或者用scale_pos_weight加大正样本权重或者把标签窗口前后各扩一个窗口如果业务允许。5.3 设备ID编码错误导致特征串设备现象某个设备的特征值异常大模型在该设备上表现极差。原因groupby(DEVICE_ID)后没有reset_index或者设备ID在合并时被当成数值排序导致特征错位。解决设备ID一律当字符串处理合并前检查dtypes用merge时指定on[DEVICE_ID,TIME]。5.4 滑动窗口用了未来数据现象线下AUC 0.95线上0.6。原因rolling默认是向后看但如果数据没按时间排序或者用了centerTrue就会引入未来信息。解决确保sort_values(TIME)在rolling之前永远不用centerTruediff的方向要确认是当前减过去。5.5 提交格式与样例不一致现象提交后系统报格式错误或分数为0。原因提交文件的列名、索引、概率值范围与样例不符。解决严格按样例的列名和顺序输出概率值保留4-6位小数不要有索引列不要有NaN。提交前用pd.read_csv读回来检查一遍。提示每次改完管道先在小样本上跑通全流程再上全量数据。全量跑一次可能几十分钟小样本几分钟就能暴露格式和逻辑错误。6. 把管道跑成可复用的脚本我的参数习惯和验证套路走到这一步你已经有一套能提交的基线了。但比赛结束后这套管道能不能复用到下一个电力AI项目我的做法是把整个流程拆成三个脚本preprocess.py负责读取、清洗、重采样、特征构造输出一个feature_matrix.parquettrain.py负责读特征矩阵、交叉验证、训练、输出模型和阈值predict.py负责读测试集、走同样的预处理、加载模型、输出提交文件。三个脚本之间只靠文件传递不共享内存这样任何一步出问题都能单独重跑。参数上我习惯把重采样粒度、滑动窗口大小、模型超参数写在一个config.yaml里而不是硬编码。电力AI大赛的数据集每次都不一样硬编码意味着每次都要改代码容易漏改。用配置文件后换数据集只需要改配置代码不动。验证套路方面除了TimeSeriesSplit我还会做一个“时间外推”测试用前80%时间训练后20%验证模拟线上“用历史预测未来”的场景。如果这个测试的AUC比TimeSeriesSplit低很多说明模型过拟合了近期模式需要加正则或减少特征。# 时间外推验证 split_point int(len(X) * 0.8) X_train, X_val X.iloc[:split_point], X.iloc[split_point:] y_train, y_val y.iloc[:split_point], y.iloc[split_point:] model lgb.LGBMClassifier(n_estimators500, learning_rate0.05, num_leaves31, random_state42) model.fit(X_train, y_train) pred model.predict_proba(X_val)[:, 1] print(f时间外推 AUC: {roc_auc_score(y_val, pred):.4f})这个测试比交叉验证更接近线上真实场景因为线上就是用过去的数据预测未来的事件。如果时间外推AUC远低于交叉验证别急着提交先检查特征里有没有“未来函数”。最后说一个血泪教训我曾在一次电力AI比赛中线下AUC 0.92提交后只有0.65。排查了两天才发现测试集的设备ID和训练集完全不重叠模型学到的设备相关特征全部失效。后来我把所有设备ID相关的特征删掉只用测点统计量线上分数反而涨到0.78。电力场景下设备差异很大如果比赛不保证设备重叠就别让模型依赖设备ID。这个习惯我一直保留到现在任何工业时序项目先问一句“测试集的设备/工况和训练集一样吗”答案不确定就把设备相关特征全部拿掉。希望帮到你。本文还有配套的精品资源点击获取