能耗预测模型开发:回归与集成学习全流程实战
简介这是一份面向高校计算机相关专业学生及数据挖掘初学者的钢铁行业能耗预测与分析系统源码包聚焦机器学习的回归与集成学习建模流程。项目基于Python实现涵盖线性回归、岭回归、Lasso、弹性网络等算法利用无功功率、二氧化碳排放、功率因数等特征预测能源消耗并集成Streamlit交互界面便于完成课程设计或毕业设计演示。压缩包共11个文件以pkl模型文件、py源码、ipynb分析笔记本及xlsx原始数据为主另含说明文档与依赖清单整体仅2.21MB结构精简易上手。目前已有86人学习使用适合入门者复现完整建模过程也可在此基础上扩展新特征或尝试更高级算法。通过源码可掌握数据预处理、模型训练与评价、结果可视化的整套实现思路。1. 钢铁行业能耗预测回归与集成学习的完整落地方案拿到一份带历史电力负载、无功功率、功率因数和二氧化碳排放记录的钢铁厂数据多数人第一反应是“这不就是套个线性回归”。真去跑一轮你会发现特征量纲乱、时间信息没利用、模型在凌晨和交接班时段的预测值肉眼可见地偏。这个资源并不是一个只丢给你算法的空壳而是把数据集Steel_industry_data.xlsx、四个回归模型线性、岭、Lasso、弹性网络、两个可视化与分析入口app.py / streamlit_app.py、一个 Jupyter Notebook 和一份模型说明文档打包成了完整闭环。你可以直接把它当课程设计交差也可以拿它当毕设的初版基线更可以把它当成“机器学习应用流程”的一次真实复现——适合刚学完 sklearn、想把手上的回归模型落成一个能演示系统的在校学生也适合企业里需要快速验证能耗预测可行性的工程师。2. 能耗数据清洗与特征工程从 xlsx 到训练集的三个关键动作2.1 数据形态与字段基线先打开Steel_industry_data.xlsx看结构。这类能耗数据集的字段形态大体一致时间列、当周第几天、目标值用电量或能耗、滞后无功功率、超前无功功率、功率因数滞后/超前、相位角、二氧化碳排放等。下表是常见字段基线训练前你要确保列名一一对上否则后续全部代码都会在 KeyError 上翻车。典型字段含义角色date / datetime时间戳特征衍生usage_kwh 或 target能耗或用电量目标变量lagging_current_reactive_power_kvarh滞后无功功率输入特征leading_current_reactive_power_kvarh超前无功功率输入特征cos_phi / phase相位角、功率因数输入特征NSMV电网状态相关的统计量输入特征初次加载建议用 pandas 的read_excel文件是 xlsx 格式需要带上 openpyxl 引擎。import pandas as pd df pd.read_excel(Steel_industry_data.xlsx, engineopenpyxl) print(df.head()) print(df.dtypes)这段代码先解决“能不能读出来”的问题。很多新手在这一步会卡住因为没有指定engineopenpyxl在部分环境里 pandas 默认的 xlrd 不支持新格式。读到数据后别急着建模先看字段类型和缺失值分布。2.2 时间特征衍生与归一化的落地写法钢铁能耗天然和时间强相关白天换班、夜间保温、周末检修能耗特征都会变。原始数据里的时间列如果只当作字符串模型学不到任何周期性。我一般会把时间戳拆成小时、星期几两个维度如果想做得更细还可以加“是否工作日”标志。下面是完整的数据准备函数。import pandas as pd from sklearn.preprocessing import StandardScaler def prepare_features(df: pd.DataFrame) - pd.DataFrame: df df.copy() dt pd.to_datetime(df[datetime]) # 时间列按实际情况改名 df[hour] dt.dt.hour df[day_of_week] dt.dt.dayofweek df[is_weekend] (df[day_of_week] 5).astype(int) return df.drop(columns[datetime], errorsignore)这里把时间信息拆成了三个可解释特征。hour直接进模型会导致“0点和23点距离很远”但能耗在夜间是连续的。如果想更强一点可以把 hour 转成sin(2π*hour/24)和cos(2π*hour/24)两个周期特征。初始阶段用普通数值特征够用后面调精度再上周期编码。特征工程完成后归一化是很多人忽略的关键。能耗数据里无功功率可能是几千的数值功率因数可能只有零点几如果不做标准化正则化回归会自动认为数值大的变量更重要。用StandardScaler做 Z-score 标准化即可。from sklearn.preprocessing import StandardScaler feature_cols [lagging_current_reactive_power_kvarh, leading_current_reactive_power_kvarh, cos_phi, NSMV, hour, day_of_week, is_weekend] X prepare_features(df)[feature_cols] y df[usage_kwh]这个代码片段暴露了一个常见坑直接在整个数据集上 fitStandardScaler再拆分训练集测试集属于典型的数据泄漏会让测试指标虚高。正确顺序永远是先切分再只用训练集 fit scaler。2.3 训练/验证拆分能耗数据为什么要按时间切交叉验证在普通表格数据里很好用但能耗数据是时间序列随机打乱会引入严重的信息泄漏。比如用第 500 条样本去预测第 100 条模型已经“看过未来”了测试分数自然漂亮部署后立刻现原形。这里必须按时间顺序切把最后 20% 的数据当作验证集。def temporal_split(df, test_ratio0.2): df df.sort_values(datetime).reset_index(dropTrue) split_idx int(len(df) * (1 - test_ratio)) train_df df.iloc[:split_idx].copy() test_df df.iloc[split_idx:].copy() return train_df, test_df train_df, test_df temporal_split(df) X_train prepare_features(train_df)[feature_cols] y_train train_df[usage_kwh] X_test prepare_features(test_df)[feature_cols] y_test test_df[usage_kwh]注意temporal_split里我做了两件事先按时间排序再按行数比例切分。如果原始数据本身是按时间升序排列的排序不会改变数据如果原始顺序被打乱过这一步就是后悔药。之后再用一个 scaler 实例单独拟合训练集测试集只 transformscaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)这里刻意让fit_transform和transform分开含义很明确scaler 只能从训练集学均值与方差。如果你手滑对全量数据调用过一次fit_transform后面所有验证数字都别当真重跑一遍再谈模型效果。3. 回归模型训练与保存线性、岭、Lasso、弹性网络的十八般姿势3.1 为什么是这四个回归器项目里出现的四个模型——线性回归、岭回归、Lasso 回归、弹性网络回归几乎是能耗预测项目里最标准的起步组合。线性回归是基线它能告诉你特征和目标大致呈现什么关系。岭回归在损失函数里加了 L2 惩罚专门处理特征之间存在多重共线性的情况能耗特征里无功功率和功率因数往往相关性很高岭回归在线性回归发散时不至于崩掉。Lasso 用 L1 惩罚会把不重要的特征系数直接压成 0本质上做了一次特征选择。弹性网络把 L1 和 L2 组合起来特征高度相关时比单独用 Lasso 更稳。我的习惯是先用线性回归跑一遍拿指标再依次上岭、Lasso、弹性网络做对比看正则化强度对测试集 R² 和 MAE 的影响而不是一上来就调到最优。这样交付时写“为什么选这个模型”可以拿出前后对比数据。3.2 用 Pipeline 串起标准化与训练标准化和模型训练应当放在一个 Pipeline 里。好处是预测时不需要自己手动保存 scaler整体存一个文件就行。下面以岭回归和 Lasso 为例中间换模型只需要改类名。from sklearn.pipeline import Pipeline from sklearn.linear_model import Ridge, Lasso, ElasticNet, LinearRegression models { linear: LinearRegression(), ridge: Ridge(alpha1.0), lasso: Lasso(alpha0.01), elasticnet: ElasticNet(alpha0.01, l1_ratio0.5), } for name, reg in models.items(): pipe Pipeline([ (scaler, StandardScaler()), (reg, reg) ]) pipe.fit(X_train, y_train) train_score pipe.score(X_train, y_train) test_score pipe.score(X_test, y_test) print(f{name:12s} train R2{train_score:.4f} test R2{test_score:.4f})这段代码会把四个模型的结果一次性打出来。注意 Pipeline 里第一个环节是 StandardScaler这样每次 fit 时都会重新计算训练集的均值和方差测试集预测时只调用已经学好的 scale 参数。如果你把 X 在外部提前标准化好再在 Pipeline 里塞 StandardScaler那就是白做反而多绕一圈。alpha 和 l1_ratio 是先手工给的初值。不知道最优值的时候别猜用网格搜索扫一遍。能耗数据量级一般在几千条网格搜索毫秒级就能跑完。from sklearn.model_selection import GridSearchCV grid { reg__alpha: [0.001, 0.01, 0.1, 1, 10, 100], } ridge_pipe Pipeline([ (scaler, StandardScaler()), (reg, Ridge()) ]) gs GridSearchCV(ridge_pipe, grid, cv5, scoringr2, n_jobs-1) gs.fit(X_train, y_train) print(best alpha:, gs.best_params_) print(best cv R2:, gs.best_score_)reg__alpha的双下划线写法是 sklearn Pipeline 里惯用的参数寻址方式reg对应管道步名alpha是 Ridge 自身的参数。cv5在这里其实存在前面提到的时序问题但网格搜索内部只是粗筛不需要强求严谨。得到候选 alpha 后再用自定义的时间序列切分做一次最终验证。3.3 模型序列化pkl 落盘与路径焦虑项目里见到regression_model.pkl、ridge_model.pkl、lasso_model.pkl这类文件对应的就是训练完保存下来的模型。保存模型最常见的方式是 joblib它比 pickle 更适合存 sklearn 对象内部对大数组做了优化。扩展名即使写成 .pkl也完全可以用 joblib 读写。import joblib # 保存整个 Pipeline 一起存scaler 不会丢 joblib.dump(ridge_pipe, ridge_model.pkl) # 加载 loaded_model joblib.load(ridge_model.pkl) y_pred loaded_model.predict(X_test)这里我坚持整个 Pipeline 一起存。如果你只存了模型对象而没有存 scaler预测新数据前就要自己重新写一遍 StandardScaler 的流程一旦均值方差对不上预测值就是“半瓶水晃荡”。加载模型后第一件事不是立即部署而是拿测试集把predict结果和当时训练完的记录对一遍。我见过不少同事加载 pkl 后输出异常最后发现是路径指错加载到了旧版本模型文件。如果要看特征对模型的影响回归系数比 feature_importance 更直观coef_df pd.DataFrame({ feature: feature_cols, coef: loaded_model.named_steps[reg].coef_ }) print(coef_df.sort_values(coef, keyabs, ascendingFalse))Lasso 模型系数中有不少会被压缩成 0这正是它做特征选择的体现。发现时间特征被压成 0 也别慌那只说明在当前数据规模下它贡献有限换一个生产周期再采数据结论可能就变了。4. 集成学习扩展回归树、随机森林与 LightGBM 的正确打开方式4.1 为什么单回归之后要补集成模型这四个回归模型都属于狭义线性模型预测值是特征的加权求和。钢铁能耗不是纯粹的线性过程设备启停、生产班次切换、环境温度突变都会让能耗曲线出现明显拐点。线性模型在这些拐点附近只能给一个平均值意义上的预测整体 R² 到 0.9 之后很难再往上走。这时把随机森林回归拉进来对比是成本最低的升级路线。随机森林由多棵回归树构成每棵树在样本和特征两个维度上做随机采样。它对异常值不敏感也不需要归一化还能自动捕捉非线性交互关系。在几千条样本的能耗数据集上随机森林通常不会过拟合太严重而且训练时间可以按秒计非常适合作为“进阶对比”。不过它也有局限外推能力差。训练集里没有覆盖的深夜高负载工况树模型预测会趋向训练集中相近叶子节点的均值而不是趋势性外推。所以集成模型更适合做“同工况下的精细预测”不适合做“未来新工况的趋势外推”。4.2 随机森林调参与对比代码新建一个随机森林回归器只需要替换掉前面的 Pipeline 里的回归器。树模型不要求标准化甚至不建议标准化所以这里要单独起一条流程。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error rf RandomForestRegressor( n_estimators300, max_depth12, min_samples_leaf3, random_state42, n_jobs-1 ) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) rf_mae mean_absolute_error(y_test, y_pred_rf) print(fRF test MAE: {rf_mae:.4f})max_depth12和min_samples_leaf3是两个控制过拟合的核心旋钮。能耗数据特征维度不多树太深很容易把每个训练样本都记住测试分数下降。min_samples_leaf强制叶子节点至少包含 3 个样本平滑掉个例噪声。random_state42保证每次运行结果可复现这是给课程设计/论文交付的参数兜底。做完这步把 MAE 和四个回归模型对比基本就能写“集成学习在XX工况下误差下降了 X%”这类结论了。4.3 想冲精度再补 LightGBM如果随机森林提升不够明显下一步我会尝试 LightGBM。它是梯度提升树框架在结构化数据里通常比随机森林更快、上限更高。但小样本场景下 LightGBM 非常容易把验证集吃干抹净必须开早停或者严格控制树深度。import lightgbm as lgb lgb_model lgb.LGBMRegressor( n_estimators500, learning_rate0.05, max_depth5, num_leaves31, subsample0.8, colsample_bytree0.8, random_state42 ) lgb_model.fit( X_train, y_train, eval_set[(X_test, y_test)], eval_metricmae, callbacks[lgb.early_stopping(50)] )learning_rate0.05配n_estimators500是防止训练提前收敛导致的欠拟合。subsample和colsample_bytree分别控制行采样与列采样本质上是给每棵树制造随机性也降低过拟合风险。能耗数据特征少num_leaves不要开太大31 到 63 够用。early_stopping(50)的意思是验证集指标连续 50 轮没有提升就停止。别等到训练结束才看曲线那样 n_estimators 设多少都会过拟合。这个属于项目里的“锦上添花”环节。资源本身交付的回归模型已经足够跑通全流程集成模型代码建议你自己加在 notebook 末尾做个对比实验——课程设计答辩时这块是加分项能讲清楚“为什么集成模型在某些时段不如 Lasso”甚至比单纯报一个高分数更有说服力。5. 能耗预测项目常见踩坑记录四条血泪经验5.1 验证集指标虚高部署后完全对不上典型现象训练时测试集 R² 有 0.95模型上线后预测误差翻倍尤其在凌晨时段误差集中在同一方向。原因数据拆分时直接用了train_test_split默认的随机 shuffle。能耗数据存在强时间自相关随机拆分让训练集和测试集互相“剧透”模型实际上已经把未来信息学走了线上预测面对的才是真正的未知未来。解决严格按时间排序后做尾部切分。我在第 2.3 节给出的temporal_split函数就是为此而写的。另外注意 GridSearchCV 内部的交叉验证也默认随机拆分最终参数确定后一定要再用按时间的训练集/测试集复验一次指标。5.2 加载 pkl 报错或者预测结果全是常量典型现象joblib.load加载成功后predict输出每个样本值几乎相同换个机器运行报ModuleNotFoundError或AttributeError。原因模型文件里包含了类定义的引用路径。如果你用 pickle 保存了 sklearn 对象而加载环境里的 sklearn 版本比保存时新或旧对象结构可能对不上另一种情况是保存时模型没有真正 fit只是从别的进程拿了个空模型对象。解决保存前用joblib.dump而不是 pickle加载前先import sklearn确认版本差异。每次训练完保存时顺手把测试集 MAE 和 R² 附在文件名里比如lasso_model_mae0.13.pkl。下次加载后对测试集跑一遍预测如果分数和文件后缀对不上那说明你加载到了错的 pkl。5.3 预测值出现负数能耗怎么可能小于 0典型现象模型预测出 -0.8、-2.3 这样的负能耗值汇报时被质疑。原因线性回归和 Lasso 对特征超出训练分布范围时预测值可以任意延伸本质上没有做边界约束。能耗数据分布本身是右偏的正值均值附近的线性拟合对最低值区域不够敏感。解决预测后做一次下限截断。最粗暴的方式是np.clip(y_pred, 0, None)更优雅的方式是先用对数变换把目标变量做平滑模型学log1p(usage_kwh)预测后expm1还原。这样能保证预测值恒为正。在使用弹性网络这类对异常值敏感的模型时目标变量对数化这件事几乎成了我固定的前置步骤。5.4 换台电脑跑 Streamlit提示接口找不到模型文件典型现象在自己机器上streamlit run一切正常拷贝项目到另一台电脑后启动报错“文件不存在”或者页面直接 500。原因应用代码里用了绝对路径或者默认相对路径。自己机器上路径存在别人机器上目录结构稍微变化就找不到 pkl。Streamlit 启动时的工作目录不一定是项目根目录尤其用 IDE 运行时最容易触发。解决在入口文件顶部统一做路径解析。用Path(__file__).parent定位项目根目录再把所有模型文件相对这个目录引用。这样无论在哪里启动、从哪个目录执行命令模型路径都能正确解析。如果项目里有requirements.txt换环境后先运行pip install -r requirements.txt再启动别让缺依赖和缺模型文件两个问题混在一起排查。6. 把模型搬上 Streamlit双入口启动、参数滑杆与端到端验证6.1 看清资源里的两个应用入口压缩包里同时提供了app.py和streamlit_app.py这是很常见的双入口交付方式。streamlit_app.py通常是交互式演示主入口用浏览器操作app.py可能是一个更小的可视化脚本或备用入口。你不需要两个都跑通先打开streamlit_app.py看前 50 行确认它导入的模型文件与训练时保存的文件名一致。如果入口里写的模型名和你第 3 章训练输出的 pkl 名不一致直接改代码里的字符串别去重训模型。我一般只用 Streamlit 入口做演示因为参数滑杆天然适合能耗预测的“输入特征 → 输出预测值”交互模式。把 notch、小时后、无功功率这类输入做成人机交互控件比命令行传参直观得多。6.2 页面逻辑与预测核心代码Streamlit 页面只需要三块标题区、输入控件区、预测结果显示区。下面是一个适配本项目字段的精简版骨架。import streamlit as st import joblib import pandas as pd st.cache_resource def load_model(): return joblib.load(lasso_model.pkl) model load_model() st.title(钢铁行业能耗预测) lagging_reactive st.number_input( 滞后无功功率(kvarh), min_value0.0, max_value1000.0, value330.0 ) leading_reactive st.number_input( 超前无功功率(kvarh), min_value0.0, max_value1000.0, value88.0 ) cos_phi st.number_input( 功率因数角, min_value-1.0, max_value1.0, value0.98 ) hour st.slider(小时, 0, 23, 12) day_of_week st.slider(星期几, 0, 6, 2) if st.button(开始预测): X_input pd.DataFrame([[ lagging_reactive, leading_reactive, cos_phi, hour, day_of_week, int(day_of_week 5) ]], columns[ lagging_current_reactive_power_kvarh, leading_current_reactive_power_kvarh, cos_phi, hour, day_of_week, is_weekend ]) pred model.predict(X_input)[0] st.success(f预测能耗{pred:.2f} kWh)st.cache_resource的作用是让模型只加载一次避免每次点击按钮都重新读 pkl。st.number_input的value参数给出的是初始化默认值新能源场景下可以直接把训练集特征的均值填进去。注意X_input特征列的顺序必须与训练时完全一致否则 pandas DataFrame 传给模型时列顺序错位预测结果必然错。最稳妥的办法是在 Streamlit 代码里和训练代码共用同一个feature_cols列表不手动重复写列名。6.3 启动命令与端到端验证跑 Streamlit 的启动命令很简单但有两个细节值得注意。pip install -r requirements.txt streamlit run streamlit_app.py --server.port 8501第一行安装依赖时如果机器上已有旧版 pandas建议用pip install -r requirements.txt --upgrade。第二行的--server.port用于固定端口8501 是默认值端口被占用时换成 8502 即可。启动后浏览器会自动打开但服务器部署时不会自动弹浏览器看到终端输出Network URL: http://localhost:8501就说明服务起来了。启动成功后我强烈建议你走一遍端到端验证从训练阶段拿一条测试样本的真实能耗值手动把它的特征输入 Streamlit 页面看预测值和 notebook 里跑出的结果是否一致。误差在浮点精度范围内正常差很多的话就要怀疑模型文件加载错了。从那以后我每次给这类项目做交付检查都强制走一遍“训练 → 保存 → 加载 → 页面输入 → 对比测试集输出”的冒烟测试。这个流程看似多花三分钟实际上能把路径问题、版本问题、特征顺序问题一次性全揪出来。希望帮到你。本文还有配套的精品资源点击获取