☰
新能源汽车电池数据集实战:SOH与RUL预测从数据到模型
2026/10/14 8:02:33 网站建设 项目流程

简介:这份资源面向新能源汽车电池研究与机器学习、深度学习方向的开发者,提供真实运营场景下的三元锂离子电池数据集及配套项目代码,可用于电池健康状态估计、剩余寿命预测等课题。数据源自300辆运营车辆,覆盖0至50万公里里程与0.5至4年运行周期,包含充电、放电、静置等全工况,采样频率10秒/帧,总量约8.5亿帧,含SOC、SOH、电压、电流等13个关键字段,由智能汽车安全技术全国重点实验室发布,采用CC BY-NC-SA 4.0协议共享。资源包共3个文件,以inscode项目配置、html页面和gitignore为主,压缩包约6KB,便于快速导入与预览项目结构。目前已有185人学习。配套提供100MB样例与72GB全量CSV下载及标准引用格式,读者可据此搭建数据驱动模型,分析电池老化特征,优化BMS算法,开展状态监控与寿命预测研究。

1. 新能源汽车电池数据集:从"能跑通"到"敢写进报告"的那道坎

做电池健康状态(SOH)和剩余寿命(RUL)预测的同行大概都有过这种体验:模型在实验室小样本上跑出 0.98 的 R²,一换到真实车队的充电片段就崩到 0.6,回头查半天发现是数据集的工况分布根本不一样。新能源汽车电池数据集这类资源,解决的就是这个"数据从哪来、能不能信"的问题。它面向的是做锂离子电池建模、机器学习与深度学习预测的从业者和研究生,核心价值不在算法多花哨,而在于给你一份带真实充放电曲线、温度、电流电压采样、循环老化标签的结构化数据,让你能把特征工程、模型训练、误差分析这条链路完整走一遍。适合谁?适合已经会调 sklearn 或 PyTorch、但卡在"没有像样数据"这一步的人。下面我按自己拆包复现的顺序,把这份资源怎么用、参数怎么设、坑在哪讲透。

2. 拆开数据集:字段、采样率与标签到底怎么读

拿到一份电池数据集,第一件事不是急着喂模型,而是搞清楚它的物理含义。锂电池的充放电数据本质上是时间序列,每个采样点记录电压、电流、温度,而 SOH 通常定义为当前容量与额定容量的比值,RUL 则是从当前循环到容量衰减到阈值(常见是额定容量的 70% 或 80%)还剩多少循环。这些定义如果一开始没对齐,后面所有指标都是空中楼阁。

2.1 先看目录结构和字段语义

常见的新能源汽车电池数据集一般按电池编号或工况分文件夹,每个文件对应一次充放电循环或一段行车片段。典型字段包括:时间戳、总电压、总电流、单体电压(如果是电池包)、温度探针读数、SOC、以及循环序号。我一般会先写个脚本把字段名和量纲打印出来,别小看这一步,血泪经验是很多数据集电流方向约定不统一——有的充电为正,有的放电为正,符号搞反了 SOH 曲线直接镜像。

import pandas as pd import glob # 读取单个循环文件,先摸清字段和量纲 files = glob.glob("battery_data/*.csv") df = pd.read_csv(files[0]) print("字段列表:", df.columns.tolist()) print("形状:", df.shape) print("前5行:\n", df.head()) print("统计描述:\n", df.describe()) # 检查电流符号约定:充电时电流方向 print("电流最大值:", df["current"].max(), "最小值:", df["current"].min())

这段代码的逻辑是先建立对数据的"体感"。df.describe()能帮你快速发现异常值,比如温度出现 -40 或者电压出现 0,那多半是传感器掉线或填充值。电流的极值方向能反推符号约定,如果充电段电流为负,那你在算安时积分时就得注意符号。参数上,glob的路径要按你实际解压后的目录改,别照抄。

2.2 采样率与重采样:别让时间戳骗了你

电池数据的采样率往往不统一,实验室设备可能 1Hz 甚至 10Hz,而车载 BMS 记录可能是 0.1Hz 或事件触发。做特征提取时,如果直接按行数滑窗,不同采样率的数据窗长对应的物理时间完全不同,模型学到的就是"采样率"而不是"老化规律"。常见做法是先统一重采样到固定时间间隔,比如 1 秒或 10 秒。

# 将时间戳转为 datetime 并设为索引 df["timestamp"] = pd.to_datetime(df["timestamp"]) df = df.set_index("timestamp").sort_index() # 重采样到 1 秒,数值列取均值,避免丢信息 df_resampled = df.resample("1S").mean().interpolate() # 检查重采样前后长度变化 print("原始点数:", len(df), "重采样后:", len(df_resampled))

resample("1S")里的1S表示 1 秒,如果你数据本身是分钟级,改成1T。.mean()是聚合方式,对电压电流温度都合理,但如果你有累积量(如安时吞吐),应该用.sum()或取首尾差值。.interpolate()处理重采样产生的空值,线性插值对平滑的电压曲线够用,但温度突变场景要谨慎。这一步做完,你才有资格谈"窗口长度"这个超参数。

2.3 标签构造:SOH 和 RUL 不是现成的

很多数据集只给原始曲线,SOH 和 RUL 要自己算。SOH 的容量法需要你对每个充电段做安时积分,找出恒流充电阶段的容量;RUL 则要先定寿命终点阈值。我一般会先画一条容量随循环的衰减曲线,肉眼确认拐点和异常循环,再决定阈值。

# 按循环分组,对充电段做安时积分估算容量 def calc_capacity(cycle_df): # 只取充电段,假设电流为负表示充电 charge = cycle_df[cycle_df["current"] < 0].copy() # 安时积分:电流绝对值对时间积分,单位换算成 Ah dt = charge.index.to_series().diff().dt.total_seconds().fillna(0) capacity = (charge["current"].abs() * dt).sum() / 3600 return capacity capacity_list = df.groupby("cycle").apply(calc_capacity) soh = capacity_list / capacity_list.iloc[0] # 以首循环为额定容量 print(soh.head(10))

这里groupby("cycle")要求你的数据有循环编号列,如果没有就得自己按充电事件切分。dt用时间戳差分算秒数,/3600转成小时得到 Ah。soh用首循环容量做基准是一种简化,严谨做法是用额定容量,但很多公开数据集不给额定值,用首循环是行业里常见的妥协。算完一定要画图,如果 SOH 不是单调下降而是上下跳,多半是积分区间切错了。

3. 特征工程与模型训练:把时序数据喂给机器学习

数据读懂了,接下来才是建模。锂电池老化预测的难点在于:容量这种直接标签在实车上很难在线获取,所以真正有落地价值的是用电压、温度、增量容量(ICA/DVA)等可测量构造特征,去预测 SOH。这一章讲怎么从原始曲线里榨出特征,以及机器学习模型怎么设参数。

3.1 从充放电曲线提取健康特征

最常用的特征分三类:统计特征(均值、方差、极值)、形态特征(恒流充电时间、恒压充电时间)、以及增量容量分析(ICA)峰。ICA 是把容量对电压求导,峰值的位置和高度与电池老化强相关,是学术界公认的好特征。下面这段代码演示从单个循环提取几个关键特征。

import numpy as np def extract_features(cycle_df): feats = {} charge = cycle_df[cycle_df["current"] < 0] # 恒流充电时间:电压从下限升到上限的耗时 feats["cc_time"] = (charge.index[-1] - charge.index[0]).total_seconds() # 电压均值与标准差 feats["voltage_mean"] = charge["voltage"].mean() feats["voltage_std"] = charge["voltage"].std() # 温度最大值,反映热效应 feats["temp_max"] = cycle_df["temperature"].max() # 增量容量峰值:对电压分箱后求 dQ/dV v_bins = np.linspace(charge["voltage"].min(), charge["voltage"].max(), 50) q, _ = np.histogram(charge["voltage"], bins=v_bins, weights=charge["current"].abs()) dv = np.diff(v_bins) ic = q / dv feats["ica_peak"] = ic.max() if len(ic) > 0 else 0 return pd.Series(feats) feature_df = df.groupby("cycle").apply(extract_features) print(feature_df.head())

cc_time捕捉充电极化,老化后恒流段会变短。ica_peak用直方图近似 dQ/dV,np.histogram的weights传电流绝对值相当于把电流当容量增量,这是简化处理,严谨做法要先对电流积分成容量再求导。bins=50是分箱数,太少峰不明显,太多噪声大,我一般从 30 到 100 试几轮看稳定性。这些特征拼成一张表,每行一个循环,就可以接监督学习了。

3.2 机器学习与深度学习模型的选型差异

特征表有了,选模型要看数据量。几百个循环的小样本,随机森林或梯度提升树(XGBoost、LightGBM)往往比深度学习稳,因为树模型对特征缩放不敏感、抗过拟合。上到几千上万循环,LSTM 或 Transformer 这类时序模型才能发挥优势,但代价是调参成本高。我一般先用树模型跑个 baseline,确认特征有效,再上深度模型对比。

from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, r2_score X = feature_df.drop(columns=["soh"]) # 假设 soh 是标签列 y = feature_df["soh"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) rf = RandomForestRegressor(n_estimators=200, max_depth=8, random_state=42) rf.fit(X_train, y_train) pred = rf.predict(X_test) print("MAE:", mean_absolute_error(y_test, pred)) print("R2:", r2_score(y_test, pred))

n_estimators=200是树的数量,小数据集 100 到 300 够用,再多收益递减。max_depth=8控制复杂度,电池特征维度不高,深度太深必过拟合。test_size=0.2是留出法,但时序数据更严谨的做法是按循环顺序切分,别随机打乱,否则未来信息泄漏到训练集,指标虚高。这一点是审稿人最爱挑的毛病,也是很多论文翻车的地方。

3.3 深度学习时序模型的输入组织

如果决定上 LSTM,输入就不是特征表而是序列张量。常见做法是把每个循环的电压、电流、温度序列截断或填充到固定长度,做成(样本数, 时间步, 通道数)的三维数组。下面用 PyTorch 搭一个最小可用的 LSTM 回归器。

import torch import torch.nn as nn class BatteryLSTM(nn.Module): def __init__(self, input_dim=3, hidden_dim=64, num_layers=2): super().__init__() self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True) self.fc = nn.Linear(hidden_dim, 1) # 输出 SOH def forward(self, x): # x: (batch, seq_len, input_dim) out, _ = self.lstm(x) out = out[:, -1, :] # 取最后时间步 return self.fc(out) model = BatteryLSTM(input_dim=3, hidden_dim=64, num_layers=2) print(model)

input_dim=3对应电压、电流、温度三个通道,如果你加了 SOC 就改成 4。hidden_dim=64是隐藏层维度,序列不长的话 32 到 128 都常见。num_layers=2堆两层,再深容易梯度问题。out[:, -1, :]取最后时间步的隐状态做回归,也可以对所有时间步做注意力池化,但那是进阶玩法。训练时记得对输入做标准化,LSTM 对量纲敏感,电压几百伏和电流几安培放一起不归一化,梯度直接爆炸。

4. 避坑与排查:那些让指标虚高或直接报错的坑

这一章是我踩过的坑合集,每条按现象、原因、解决写,你对着排查能省不少时间。

4.1 数据泄漏导致 R² 虚高

现象:随机划分训练测试集后 R² 到 0.99,换一批电池数据就掉到 0.5。原因:同一电池的相邻循环高度相似,随机划分让训练集和测试集共享了几乎相同的样本,模型在"背答案"。解决:按电池编号或按时间顺序划分,确保测试集的电池或循环段在训练时完全没见过。我一般用GroupKFold按电池 ID 分组交叉验证。

4.2 电流符号约定不一致

现象:算出来的容量是负值,或者 SOH 曲线先升后降。原因:不同数据集对充电电流正负定义不同,有的用正表示充电,有的用负。解决:先画电流随时间的变化图,结合 SOC 上升段判断充电方向,统一符号后再做积分。这个坑不排查,后面所有特征都是错的。

4.3 温度传感器缺失值被当真实值

现象:模型对温度特征重要性极高,但实际部署时温度一换就崩。原因:原始数据里温度掉线被填了 -40 或 0,模型学到了这个伪模式。解决:把明显不合理的温度值(如低于 -20 或高于 80)设为 NaN,再用前向填充或插值,并在特征里加一个"是否缺失"的指示列。

4.4 重采样后时间戳重复或乱序

现象:resample报错或结果长度对不上。原因:原始时间戳有重复或未排序,pandas 的 resample 要求单调索引。解决:先sort_index()再去重df = df[~df.index.duplicated(keep="first")],然后再重采样。别跳过这步,否则后面滑窗全乱。

4.5 深度学习输入未归一化

现象:LSTM 训练 loss 不下降或变 NaN。原因:电压几百、电流几安、温度几十,量纲差异大,梯度更新被大数值主导。解决:对每个通道做 z-score 标准化,用训练集的均值和方差,测试集复用同一组参数,别各自标准化。

5. 进阶技巧:用增量容量和迁移验证把结论坐实

走到这里,baseline 应该能跑了。但要让结果经得起推敲,还得做两件事:一是把 ICA 特征做细,二是做跨电池或跨工况的迁移验证。ICA 的精细做法不是直方图近似,而是先对充电段做容量-电压曲线,再用 Savitzky-Golay 滤波求导,峰的位置偏移量是老化的重要指标。

from scipy.signal import savgol_filter def ica_curve(cycle_df, v_res=0.01): charge = cycle_df[cycle_df["current"] < 0].copy() # 对电压等间隔重采样,保证求导均匀 v_grid = np.arange(charge["voltage"].min(), charge["voltage"].max(), v_res) dt = charge.index.to_series().diff().dt.total_seconds().fillna(0).values q = np.cumsum(charge["current"].abs().values * dt) / 3600 # 累积容量 q_interp = np.interp(v_grid, charge["voltage"].values, q) dqdv = savgol_filter(q_interp, window_length=21, polyorder=3, deriv=1, delta=v_res) return v_grid, dqdv

v_res=0.01是电压网格间隔,越小曲线越细但噪声越大。savgol_filter的window_length=21必须为奇数,polyorder=3是多项式阶数,这两个参数决定平滑程度,我一般会画几条对比着调。deriv=1表示求一阶导,delta是自变量间隔。算出来的dqdv峰值位置随老化左移或右移,取决于电池体系,这个规律要结合你的数据自己确认,别照搬文献。

迁移验证则是把在一个电池上训练的模型直接用到另一个电池,看误差涨多少。如果涨得离谱,说明特征对电池个体差异不鲁棒,得加归一化或换特征。我习惯在报告里同时给同电池随机划分和跨电池划分两组指标,前者看拟合能力,后者看泛化,只报前者基本等于自欺欺人。

从那以后我每次拿到新的电池数据集,都强制先跑一遍字段检查、符号确认、缺失值扫描这三步,再谈建模。这套流程帮我省下了无数次返工。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询