新能源汽车电池数据集实战:从SOC估算到SOH预测全流程

发布时间:2026/10/17 20:33:40
新能源汽车电池数据集实战:从SOC估算到SOH预测全流程 简介面向新能源汽车电池健康管理与寿命预测研究这套项目代码可快速对接智能汽车安全技术全国重点实验室发布的三元锂离子电池运行数据集。数据集采集自300辆运营车辆覆盖0-50万公里里程与0.5-4年周期包含SOC、SOH、电压、电流等13个关键字段总量约8.5亿帧适用于电池健康状态估计、剩余寿命预测与BMS算法优化等机器学习、深度学习课题。代码包共3个文件整体仅6KBHTML可视化页面为数据特征预览提供清晰入口inscode脚本可一键拉起项目环境并引导数据读取与分析流程gitignore配置则规范了工程版本管理。目前已有183人学习使用。借助该代码研究者可省去环境配置与接口适配成本快速进入100MB样例数据或72GB全量CSV数据的分析流程将主要精力放在模型构建与实验验证上尤其适合入门及进阶的电池数据分析开发人员。1. 新能源汽车电池数据集先别急着搭模型把数据底座打好再跑我接过不少电池算法需求SOC 和 SOH 估计在论文里都很漂亮真落地的瓶颈往往不是模型而是数据集和配套的项目代码。新能源汽车电池数据集本质上是一份记录了电池从新到衰的“体检档案”每一次充放电的电压、电流、温度、容量、循环次数再加上能把这些字段串起来的清洗与特征代码才构成一份可训练的资产。它的价值在于让电池的健康状态可以量化、可复现、可验证适合做电池预警、BMS 云端监控、剩余寿命预测的工程师和算法团队。我一般建议先花一周把数据和代码底座打好再碰模型否则后面每个结果都是黑匣子。2. 新能源汽车电池数据集怎么选先分清三条技术路线再决定要不要自己采选数据集的第一步不是找下载链接而是先把任务类型定下来你是要做 SOC荷电状态实时估算做 SOH健康状态衰减评估还是做热失控这种异常预警不同任务对数据的需求完全不同。我见过直接拿实验室循环老化数据去训实车 SOC 模型的结果实车一到充电桩边上就飘根本原因是数据分布对不上。所以先把技术路线列清楚再谈“数据集”三个字。2.1 实验室循环老化数据 vs 实车 BMS 工况数据选型对照我给自己常用的三类数据源做过一张对照表几乎每个项目都会先过一遍这张表再动手数据路线典型来源适合任务主要成本实验室循环老化18650 或软包电池在恒温箱里反复充放电记录每圈的电压流、电流和容量SOH 估计、剩余寿命预测、衰减机理分析采集周期长单批次跑几百个循环要数周实车 BMS 工况整车控制器里的 BMS 报文含 SOC、电流、电压、温度、充电起始时间SOC 实时估算、充电异常检测、车队级健康管理数据量大且脏工况不完整标签缺失多仿真半实物用等效电路模型ECM或电化学模型生成虚拟充放电曲线算法开发初期、无数据时验证流程模型参数不准时迁移到实车效果打折这里有一条选型经验SOH/RUL 类任务优先找带“循环号 容量”的老化数据因为标签天然递进SOC 类任务需要完整工况段实验室恒流恒压数据往往不够而云端电池预警必须有同一车辆很长时间跨度的历史数据才能建模“和过去比有什么异常”。市面上的公开数据集里像 NASA PCoE 老化数据、牛津电池老化数据都是常见选择但许可和使用方式各有差异先进去把 readme 和结构读一遍再决定。和自动驾驶数据集不同电池数据几乎没有通用标注工具字段含义每个源都不同所以读懂数据结构比会下模型重要得多。2.2 最少需要保留的 9 个字段以及一个最小读取脚本不管从哪种数据源拿到原始文件我建议你先检查这些字段是否齐备record_id、cycle、timestamp、voltage、current、capacity、soc、temperature、cell_id。其中cell_id特别容易被忽略如果一条数据里混了多颗电池而你没有这个字段后面所有按电池分组的交叉验证都做不了。下面是我常用的最小读取脚本把多种 CSV 文件统一成标准 pandas DataFrameimport pandas as pd import os def load_battery_csv(filepath: str) - pd.DataFrame: 读取原始电池数据 CSV 并统一成标准字段。 要求文件至少包含time、voltage、current 其他字段可有可无。 df pd.read_csv(filepath) # 常见列名归一化兼容不同采集设备命名 rename_map { time_s: timestamp, cycle_index: cycle, voltage_v: voltage, current_a: current, capacity_ah: capacity, temp_c: temperature, soc_percent: soc } df df.rename(columnsrename_map) # 如果文件里没有 cycle 字段按充电/放电切换点补一个 if cycle not in df.columns: df[cycle] (df[current].diff().abs() 0.5).cumsum() return df这段脚本的逻辑并不复杂核心是两件事一是把不同设备的不同列名映射成统一命名二是当原始文件没有cycle时用电流跳变来切分充放电周期。current.diff().abs() 0.5的含义是找电流突变超过 0.5A 的位置每次突变视为一个阶段的开始。参数 0.5 不是固定的如果你手里的电池是 18650约 2A 满放电0.5A 够用如果是电动大巴的大容量电池电流突变可能到几十安这个阈值要放大到 5A 甚至 10A否则会把一个循环切成几十段。如果发现切出来的 cycle 少了一半去检查是不是阈值过小把正常波动也当成切点。3. 把原始充放电曲线变成训练集滑窗切片与 IC/DV 特征的项目代码有了标准格式的数据下一步不是直接扔给模型。原始电压电流曲线是每个循环几千行数据模型直接吃会有两个问题一是维度太宽导致过拟合二是电池退化最敏感的信号不在绝对电压而在电压平台的变化上必须显式提取出来。这一章讲我常用的两条特征工程路径增量容量 ICIncremental Capacity和基于循环序号的滑窗特征。3.1 为什么要做增量容量IC和滑窗而不是直接把曲线喂进模型先讲原理。磷酸铁锂电池的充电曲线有个典型特征3.2V 到 3.4V 之间有一段很宽的平台肉眼看上去电压几乎不变但容量一直在涨。如果只看电压序列模型很难区分这节电池是新的还是已经衰减了但把横轴从时间换成电压纵轴从电流换成 dQ/dV单位电压变化对应的容量变化平台区就会出现一个明显的峰。随着电池老化这个峰会往低压方向移动、高度下降。这个“峰的位置和高度”就是 SOH 预测里最有辨识度的特征。滑窗则是为了把“趋势”纳入样本。SOH 不是只看某一个循环的容量而是看“近 N 个循环容量下降的速度”。同一节电池在第 2 圈和第 200 圈的容量绝对值相差不大但斜率完全不同。所以我一般会以循环为单位取最近 20 个循环的 IC 峰值、容量衰减斜率、平均内阻作为特征这样模型看到的不是一张瞬态快照而是一段退化历史。滑窗这个思路和社交推荐、时序预测里用的滑窗是一样的但在电池数据里要特别注意窗口不要跨过“容量回升”点否则会把短期的虚假回升当成恢复能力。3.2 一个可复用的特征工程代码块CC 段、电压分箱与容量衰减斜率下面这段代码是我多个项目里复制修改过很多次的通用模板。它把每个循环的充放电数据压缩成一组数值特征import pandas as pd import numpy as np def extract_ic_curve(df_cycle: pd.DataFrame, voltage_bin: float 0.02): 从单个循环的充电段提取 IC 曲线。 步骤筛选充电段 - 按电压分箱 - 聚合容量增量。 voltage_bin: 电压分箱宽度单位 V。 # 只保留充电段电流阈值 0.1A 用于排除静置和放电 chg df_cycle[(df_cycle[current] 0.1)].copy() if len(chg) 5: return None # 给电压分箱0.02V 是 18650 常用经验值 chg[voltage_bin] (chg[voltage] / voltage_bin).round() * voltage_bin # 每个电压箱内累计容量增量得到 dQ grouped chg.groupby(voltage_bin).agg( dQ(capacity, last) ).reset_index() # 用相邻箱差求 dQ/dV grouped[dV] grouped[voltage_bin].diff().fillna(voltage_bin) grouped[ic] grouped[dQ].diff() / grouped[dV] return grouped.dropna(subset[ic]) def build_cycle_features(df: pd.DataFrame, window: int 20): 按电池和循环序号滑窗生成 SOH 预测样本。 window: 用最近 N 个循环的特征拼成一条样本。 records [] for cell_id in df[cell_id].unique(): cell_df df[df[cell_id] cell_id].sort_values(cycle) for i in range(window, len(cell_df)): hist cell_df.iloc[i - window:i] feat { cell_id: cell_id, cycle: cell_df.iloc[i][cycle], capacity_slope: np.polyfit(hist[cycle], hist[capacity], 1)[0], ic_peak: hist[ic].max(), mean_temp: hist[temperature].mean(), label_soh: cell_df.iloc[i][capacity] / cell_df.iloc[0][capacity] } records.append(feat) return pd.DataFrame(records)两个函数各有分工extract_ic_curve负责把一个循环压成 IC 曲线build_cycle_features负责把所有循环的历史拼成训练样本。参数里voltage_bin是最需要调的那个0.02V 适合 18650如果是三元锂电池电压范围 3.0V 到 4.2V通常 0.02V 还是可用如果是磷酸铁锂平台区宽建议放大到 0.05V否则单个电压箱内采样点太少dQ/dV 会抖成一条毛刺。window我一般取 10 到 30取太大了训练样本少取太小了捕捉不到退化趋势。这段代码跑完之后label_soh是 0 到 1 之间的比值后续训练直接当回归标签。4. 用项目代码训一个 SOH 回归基线XGBoost 与电池级交叉验证特征工程做完训练部分并不复杂。我的经验是电池 SOH 预测的基线模型用梯度提升树如 XGBoost通常比深度学习更稳样本量往往只有几百到几千条树模型不容易过拟合调参也少。在这一章里我要强调两个初学者最容易栽的环节SOH 标签怎么定义以及交叉验证怎么分组。4.1 SOH 标签怎么标额定容量、循环容量与容量参考点的选择SOH 最常见的定义是当前充满容量除以额定容量但这里有个数据坑同一节电池的充电容量和放电容量往往不相等因为库仑效率和温度都在影响。用谁当分子直接决定模型学出来的物理含义。我一般坚持用“标准工况放电容”作为当前容量。实验室数据里放电段通常是恒流恒阻条件固定容量值可比性好充电段则受充电策略CC/CV 比例影响大如果你的数据里有快充和慢充混在一起充电容量根本不具备可比性。所以特征可以从充电段里提如 IC 曲线标签一定优先从同温度下的放电段取。若项目里只有充电数据另一个可行做法是取充满电压后静置阶段的容量作为近似标签但要清楚这是妥协换来的 SOH 会有 1% 到 2% 的偏差且对恒流恒压切换不敏感。还有一个参考系问题cell_df.iloc[0][capacity]作为额定容量在数据不完整时会失真。比如第一批次数据从第 5 个循环才记录第 5 圈的容量已经不是 100% 了。这种情况下我会先做一次健康度初筛取前 5 个循环的容量最大值作为额定容量而不是第一个循环的记录值。代码如下rated_capacity cell_df[capacity].iloc[:5].max() cell_df[soh] cell_df[capacity] / rated_capacity这段代码的逻辑是抵抗退化曲线的早期异常点。电池放电容量在最初几次循环内可能先升后降化成效应如果直接拿第一圈当基准SOH 会超过 100% 甚至出现“越用越健康”的假象用户体验很差模型也会掉进这个陷阱。4.2 训练脚本GroupKFold 防泄漏、参数与评估输出接下来是训练。我用一个最小可复现的 XGBoost 脚本关键是交叉验证方式import xgboost as xgb import pandas as pd from sklearn.model_selection import GroupKFold from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np def train_soh_model(features: pd.DataFrame): # 特征列排除 cell_id、cycle、label feat_cols [capacity_slope, ic_peak, mean_temp] X features[feat_cols] y features[label_soh] groups features[cell_id] # 同一电池的所有循环不能跨组泄漏 gkf GroupKFold(n_splits5) y_true_all, y_pred_all [], [] for train_idx, val_idx in gkf.split(X, y, groups): 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 xgb.XGBRegressor( n_estimators500, learning_rate0.05, max_depth4, subsample0.8, colsample_bytree0.8, early_stopping_rounds30, eval_metricmae, random_state42 ) model.fit(X_train, y_train, eval_set[(X_val, y_val)], verboseFalse) y_pred_all.extend(model.predict(X_val)) y_true_all.extend(y_val) # 整体评估输出 SOH 误差 mae mean_absolute_error(y_true_all, y_pred_all) rmse mean_squared_error(y_true_all, y_pred_all, squaredFalse) mape np.mean(np.abs(np.array(y_true_all) - np.array(y_pred_all)) / np.array(y_true_all)) * 100 print(fMAE{mae:.4f} RMSE{rmse:.4f} MAPE{mape:.2f}%) return model这段代码里有三个刻意的小设计。第一groupsfeatures[cell_id]是最关键的一行它保证同一节电池的所有循环样本只在折内或折外不会被拆散到训练和验证两侧。如果不加这一行模型会把同一颗电池的相邻循环既当训练又当验证结果会虚高我见过有人因此把 SOH 误差报到 1% 以内换车数据后直接翻车到 6%。第二early_stopping_rounds30配合eval_set用验证集选迭代轮数防止树模型在几百个特征上钻太久。第三eval_metricmae在电池场景比默认的 RMSE 更稳因为 SOH 标签里偶发的传感器毛刺会放大 RMSE使早停过早MAE 对异常值更钝。参数上learning_rate0.05和max_depth4是我试过很多电池表格数据后比较稳的组合树深超过 6 就容易把 IC 峰值的噪声吃进去建议不用动。如果特征是几百维可以把colsample_bytree降到 0.6 增加随机性如果样本量只有几百条n_estimators配合早停会自动收敛不需要手动调大。5. 电池数据集项目代码的 4 个常见坑从容量回升到标签噪声的排查方案这一章是血泪经验的汇总。我在前几个项目里每个坑都填过列出来希望你能跳过这些弯路。5.1 容量回升导致 SOH 序列锯齿化模型学到“回光返照”假规律现象训练曲线的 loss 反复震荡验证集误差忽高忽低把预测结果画出来发现模型在某些循环交界处会预测出多余的“容量上升”。原因锂离子电池静置一段时间后内部极化电压消散可恢复容量会小幅回升同一颗电池在第 120 圈的容量比第 119 圈还高 0.5%这在物理上是正常的。但滑窗特征会把这种回升当成真实趋势模型就会学到“容量会波动上升”的错误模式。解决在特征工程里对容量序列做一次指数加权平滑EWMA把短于 3 个循环的异常波动滤掉。具体做法是对capacity做ewm(span5).mean()再拿平滑后的容量算斜率。注意平滑只用于特征不用于标签标签还是得用原始放电容否则会掩盖真实健康度。5.2 随机划分交叉验证导致“信息泄漏”线上测试误差翻倍现象用train_test_split随机划分时MAE 在 2% 以内换到新电池或新的生产批次后误差直接涨到 5% 以上。原因同一次循环数据的两个相邻样本第 19 圈和第 20 圈几乎一模一样随机划分时它们一个进了训练集、一个进了验证集模型实际上记住了这条曲线的结构而不是学会了泛化的退化规律。解决用GroupKFold并且按cell_id分组严格保证一节电池只出现在一侧。更进一步做最终评估时要留出 1 到 2 节全新的电池完全不参与调参模拟“车上新装一节电池”的场景。如果你的项目是对接 BMS 的增量更新这个留出集比任何分数都有说服力。5.3 充电策略混用把 CC 段和 CV 段的容量都算进 IC 特征现象同一颗电池在慢充和快充下提取的 IC 峰值差异很大模型在训练时学到的峰值“仿佛跟 SOH 无关”特征重要性里ic_peak排在最后。原因恒流CC段电流稳定电压从低到高IC 曲线形态清晰恒压CV段电流持续下降电压不动按电压分箱后全部落在同一个箱子里dQ/dV 被拉出巨大的假峰。把两段混在一起就像把苹果和橙子放一个榨汁机。解决在extract_ic_curve里对充电段分两步过滤。先筛current 0.1A再筛掉电压接近满充上限且电流持续下降的尾部。更稳的做法是按电流绝对值把 CC/CV 段分开如果当前电流小于起始充电电流的 20%视为 CV 段并截断。这样 IC 曲线更干净峰值也稳定。5.4 标签噪声来自容量读取基准不一致同一颗电池 SOH 忽高忽低现象模型 MAPE 一直降不下去排查特征发现capacity字段本身有跳变相邻两个循环容量差 2% 以上。原因采集系统记录容量时有的按库仑积分从电池启动算起有的只算当前循环还有的会在温度变化时修正容量。混用这些口径会让标签本身带噪声模型再聪明也学不出来。解决做一个数据体检函数检查每个cell_id的capacity序列是否有超过 3% 的单次跳变打印出来人工核对。另一个常用做法是同时记录charge_capacity和discharge_capacity两个字段交叉验证若两者相差超过 1%、且不是温度剧烈变化段标记该循环为可疑样本。宁可删掉 5% 的可疑循环也别让它们污染模型。6. 电池级验证怎么才算数把项目代码收敛成能复现的基线最后一章我不聊更多新模型而是分享一个我在每个电池项目里都会做的验证闭环。这个做法保证无论谁拿到你的代码和数据集跑出来的数字都跟你报的数字一致。先建立一个规则所有评估必须按电池分组输出而不是只看总表。单看 MAE 会被多数正常电池“平均”掉几节异常电池的坏成绩被掩盖。我一般这样输出import pandas as pd def evaluate_by_cell(pred_df: pd.DataFrame): # pred_df 至少包含 cell_id, y_true, y_pred pred_df[abs_err] (pred_df[y_true] - pred_df[y_pred]).abs() summary pred_df.groupby(cell_id).agg( mae(abs_err, mean), p90_ae(abs_err, lambda x: x.quantile(0.9)), max_ae(abs_err, max) ) return summary.sort_values(p90_ae, ascendingFalse)这段代码的输出能迅速暴露问题如果所有电池的 p90 误差都比 MAE 高出一倍说明同一节电池在低频循环时误差大需要检查是不是低温段样本不足如果个别电池的 max_ae 明显高多半是标签噪声回到第 5 章去查容量跳变。我现在的习惯是每个电池项目都把这个evaluate_by_cell输出保存成 CSV跟数据集版本放在一起上面写清楚用的原始数据日期、特征脚本版本、模型参数。因为电池数据会有新批次不断补充进来没有这份记录三个月后连你自己都说不清当初的基线是拿哪一版数据跑的。最后提醒一点当模型误差压到 2% 以下时继续加大模型复杂度往往没有收益真正的收益来自数据覆盖度——有没有低温数据有没有快充慢充混用有没有不同厂家电池这些分布扩展比调参宝贵得多。这也是我这两年做电池数据项目最大的教训先跑通一版极简基线再花时间扩大数据多样性和验证闭环性价比最高。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询