
简介这份资源面向新能源汽车电池研究与机器学习、深度学习方向的开发者与科研人员提供真实运营场景下的三元锂离子电池数据集及配套项目代码可用于电池健康状态估计、剩余寿命预测等课题。数据源自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. 新能源汽车电池数据集从一份“项目代码”里挖出可复现的建模链路新能源汽车电池数据集配上项目代码这个组合在工程圈里越来越常见但真正能跑通的人不多。多数人拿到手的是一堆 CSV 或 MAT 文件字段名是英文缩写时间戳对不齐充放电片段混在一起SOH、SOC 标签要么没有要么口径不明。我见过不少团队把数据喂进模型指标看着漂亮一上车就翻车——因为训练集里混进了未来信息或者把不同电芯的曲线当成了同一分布。这份笔记要解决的就是这件事把一份典型的新能源汽车电池数据集从字段理解、清洗对齐、特征构造一路做到能复现的容量衰减与健康状态估计基线并给出项目代码该有的目录结构和关键参数。适合做 BMS 算法、储能数据分析、电池寿命预测的从业者也适合刚拿到数据集不知道从哪下手的新手。下面所有步骤都按“能抄作业”的标准写参数给了默认值和调整边界坑也标出来了。2. 电池数据集里到底有什么字段、口径与选型理由2.1 常见字段拆解与物理含义一份典型的新能源汽车电池数据集通常来自实车采集或台架测试核心字段逃不出这几类时间列、电流、电压、温度、SOC、累计里程、充放电状态标志。实车数据还会带车速、电机功率、环境温度台架数据则更干净但工况单一。项目代码里如果直接pd.read_csv然后df.describe()大概率会看到电流有正有负电压在 3.0 到 4.2 之间跳温度从 15 到 45 不等。这里第一个要确认的是电流符号约定充电为负还是放电为负不同厂商相反。我一般先画一段 10 分钟的电流曲线看车辆行驶时电流是正还是负行驶时放电所以行驶段为正就是放电正。这个符号直接决定后面安时积分的方向搞反了 SOH 会算出大于 1 的荒谬值。电压字段要注意是单体电压还是总压。实车数据集常给总压和单体极值台架数据可能给每个单体的电压矩阵。如果只有总压做单体一致性分析就没戏只能做包级 SOH。温度同理有电芯温度、模组温度、冷却液温度建模时选哪个取决于你要预测什么。SOC 字段如果是厂商给的要警惕它是基于安时积分还是基于开路电压修正过的前者会累积误差后者在静置后跳变。项目代码里最好保留原始 SOC自己再算一个安时积分 SOC 做对比差异大的片段直接标记为低置信度。2.2 容量衰减与 SOH 标签的构造口径SOH 没有统一标准常见定义是当前最大可用容量除以额定容量。但数据集里往往不给最大可用容量只给充放电片段。这时候需要自己从充电片段里提取找一次完整的恒流恒压充电取恒流段安时积分作为容量估计。项目代码里我会写一个extract_capacity函数按以下逻辑先按时间排序标记充电段电流为负且持续超过 5 分钟对每个充电段计算∫|I|dt单位从安秒转安时再除以额定容量得到 SOH。注意要过滤掉不完整充电比如起始 SOC 高于 30% 或结束 SOC 低于 90% 的片段否则容量被低估。这个口径下SOH 曲线应该是单调下降带噪声如果出现阶跃上升多半是换电或数据拼接错误。提示不同数据集对“额定容量”的定义可能不同有的用安时有的用瓦时。项目代码里最好把额定容量做成配置项不要硬编码。2.3 为什么选安时积分而不是直接回归有人拿到数据第一反应是拿电压、温度、电流直接回归 SOH。这样做不是不行但可解释性差而且跨电芯泛化容易崩。安时积分是电池领域的“物理先验”它把电流和时间积分成容量物理意义明确误差来源也清楚电流传感器零漂、采样丢失、积分初值。项目代码里把安时积分作为基线再叠加一个轻量回归模型修正比端到端黑箱更稳。我一般先用安时积分算一版 SOH画出来看趋势再用随机森林或梯度提升树去拟合残差特征用温度均值、放电深度、平均电流倍率。这样即使模型翻车基线还在不至于交不了差。3. 用项目代码跑通数据清洗与片段提取3.1 项目代码目录结构与依赖一份能复现的项目代码目录不用花哨但职责要清楚。我常用的结构是data/raw放原始文件data/processed放清洗后 parquetsrc/下分io.py、clean.py、features.py、model.pyconfigs/放 YAML 参数notebooks/只做探索不参与生产。依赖就几个pandas、numpy、scikit-learn、pyarrow、matplotlib。不要一上来就上深度学习框架电池数据量通常不大树模型足够。下面这段是clean.py的核心清洗逻辑直接可抄。import pandas as pd import numpy as np def load_and_clean(path, current_signdischarge_positive): df pd.read_csv(path, parse_dates[timestamp]) df df.sort_values(timestamp).reset_index(dropTrue) # 统一电流符号放电为正 if current_sign charge_positive: df[current] -df[current] # 去掉电压越界和电流缺失的采样点 df df[(df[voltage] 2.0) (df[voltage] 4.5)] df df.dropna(subset[current, voltage, temperature]) # 标记充放电状态电流绝对值小于 0.5A 视为静置 df[state] np.where(df[current] 0.5, discharge, np.where(df[current] -0.5, charge, rest)) # 按时间间隔重采样到 1 秒避免原始采样抖动 df df.set_index(timestamp).resample(1S).mean().interpolate(limit5) return df.reset_index()这段代码的逻辑说明current_sign参数解决符号约定问题默认放电为正电压过滤去掉传感器毛刺state列用 0.5A 阈值区分充放电和静置阈值可调但不要低于传感器噪声重采样到 1 秒是为了后续安时积分步长一致interpolate(limit5)只补 5 秒以内的缺失长缺失保留 NaN 让下游决定丢弃还是插值。参数方面电压上下限按三元锂设的磷酸铁锂要改成 2.0 到 3.8。重采样频率如果原始数据是 10 秒一次就别强行升到 1 秒保持 10 秒即可。3.2 充电片段提取与容量计算清洗完数据下一步是把连续的充电段切出来。项目代码里我会用状态列做游程编码找到连续charge的区间再对每个区间做质量检查。下面这个函数返回一个 DataFrame每行是一个充电片段带容量和 SOH。def extract_charge_segments(df, rated_capacity_ah, min_duration_s300): segments [] df df.copy() df[block] (df[state] ! df[state].shift()).cumsum() for _, seg in df[df[state] charge].groupby(block): if len(seg) min_duration_s: continue dt seg[timestamp].diff().dt.total_seconds().fillna(0) ah np.sum(np.abs(seg[current].values) * dt.values) / 3600.0 soc_start, soc_end seg[soc].iloc[0], seg[soc].iloc[-1] # 只保留 SOC 区间合理的完整充电 if soc_start 30 or soc_end 90: continue segments.append({ start: seg[timestamp].iloc[0], end: seg[timestamp].iloc[-1], capacity_ah: ah, soh: ah / rated_capacity_ah, soc_start: soc_start, soc_end: soc_end, temp_mean: seg[temperature].mean() }) return pd.DataFrame(segments)逻辑说明block列用状态变化累积和做游程标记这是 pandas 里切连续段的常用技巧。min_duration_s过滤太短的充电避免碎片噪声。安时积分用|current| * dt因为充电电流在清洗后是负的取绝对值。SOC 区间过滤是血泪经验起始 SOC 太高充进去的安时少算出的容量偏低结束 SOC 太低没充满同样偏低。rated_capacity_ah必须从配置读不要猜。如果数据集没有 SOC 字段这个过滤就得换成电压区间比如起始单体电压低于 3.5V、结束高于 4.1V。3.3 特征构造与训练集划分拿到充电片段表后特征不要只放容量。我一般加这几类温度统计均值、最大值、标准差、电流倍率平均电流除以额定容量、SOC 区间宽度、充电时长、以及历史片段的 SOH 滑动平均。标签就是当前片段的 SOH。训练集划分要按时间切不能随机打乱否则未来信息泄漏指标虚高。项目代码里用TimeSeriesSplit或者简单按前 70% 时间训练、后 30% 测试。下面这段构造特征并训练一个梯度提升树基线。from sklearn.ensemble import GradientBoostingRegressor from sklearn.metrics import mean_absolute_error def build_features(seg_df): seg_df seg_df.sort_values(start).reset_index(dropTrue) seg_df[soh_roll3] seg_df[soh].rolling(3, min_periods1).mean() seg_df[soc_width] seg_df[soc_end] - seg_df[soc_start] seg_df[duration_h] (seg_df[end] - seg_df[start]).dt.total_seconds() / 3600 return seg_df.dropna() feat build_features(segments) split int(len(feat) * 0.7) train, test feat.iloc[:split], feat.iloc[split:] cols [temp_mean, soc_width, duration_h, soh_roll3] model GradientBoostingRegressor(n_estimators200, max_depth3, learning_rate0.05) model.fit(train[cols], train[soh]) pred model.predict(test[cols]) print(MAE:, mean_absolute_error(test[soh], pred))参数说明n_estimators200对几百个片段足够片段多可以加到 500max_depth3防止过拟合电池特征非线性但不算复杂learning_rate0.05配合 200 棵树收敛平稳。soh_roll3是滑动平均用到了历史信息注意在测试集上也要用训练集末尾的 SOH 来算不能直接用测试集自身的未来值。MAE 如果在 0.02 以内算不错超过 0.05 要检查片段提取口径是否一致。4. 避坑与排查电池数据建模最常见的五个翻车点4.1 电流符号搞反导致 SOH 大于 1现象算出来的 SOH 有 1.2、1.5 这种值。原因充电电流符号约定和安时积分方向不一致把放电段当充电段积分了。解决先画一段已知行驶工况的电流曲线确认放电时电流正负然后在load_and_clean里统一符号并在extract_charge_segments里打印几个片段的容量做人工核对。4.2 时间戳重复或乱序导致积分错误现象安时积分结果忽大忽小同一片段重复计算。原因原始数据有重复时间戳或者跨天采集时时间戳回绕。解决清洗时drop_duplicates(subset[timestamp])排序后检查diff是否有负值有负值就说明乱序重新排序。如果时间戳是字符串先pd.to_datetime并统一时区。4.3 SOC 字段被厂商修正过不能直接当标签现象SOC 曲线在静置后跳变或者充电末端 SOC 不收敛到 100%。原因厂商 BMS 的 SOC 是安时积分加开路电压修正的混合值不同工况下修正策略不同。解决不要用 SOC 做回归标签只用它做片段筛选。自己算安时积分 SOC 做对比差异大的片段标记低置信度训练时降权或剔除。4.4 温度传感器位置不同导致特征漂移现象同一车型不同车辆温度均值差 5 度以上模型跨车泛化差。原因有的数据给电芯温度有的给模组温度有的给冷却液温度。解决项目代码里把温度字段名做成配置训练前画温度分布对比图如果分布差异大做分位数归一化或者直接只用温度变化率而不是绝对值。4.5 片段太少导致模型过拟合现象训练集 MAE 很低测试集 MAE 翻倍。原因完整充电片段可能只有几十个特征维度一高就过拟合。解决减少特征只用温度均值、SOC 宽度、历史 SOH 三个或者用线性回归加正则再不行就退回到纯安时积分基线不强行上模型。我一般要求至少 100 个片段才考虑树模型否则就用滑动平均。5. 进阶技巧用残差修正和在线更新把 SOH 估计做稳基线跑通后真正决定落地效果的是残差修正和在线更新。安时积分算出的 SOH 有系统性偏差比如电流传感器零漂导致容量缓慢高估温度低时内阻增大导致可用容量低估。这些偏差不是随机的可以用一个轻量模型去学。我的做法是先用安时积分得到soh_ah再用梯度提升树预测soh_true - soh_ah的残差特征用温度、电流倍率、SOC 区间、以及历史残差的滑动平均。这样模型只学修正量不用从零学 SOH样本效率高很多。下面这段是残差修正的代码骨架。def residual_correction(seg_df, base_colsoh_ah, target_colsoh): seg_df seg_df.sort_values(start).reset_index(dropTrue) seg_df[residual] seg_df[target_col] - seg_df[base_col] seg_df[res_roll3] seg_df[residual].rolling(3, min_periods1).mean() seg_df[temp_bin] pd.cut(seg_df[temp_mean], bins[0, 15, 25, 35, 50]) feat_cols [temp_mean, soc_width, duration_h, res_roll3] # 按时间切分前 70% 训练残差模型 split int(len(seg_df) * 0.7) train, test seg_df.iloc[:split], seg_df.iloc[split:] from sklearn.ensemble import GradientBoostingRegressor res_model GradientBoostingRegressor(n_estimators150, max_depth2, learning_rate0.05) res_model.fit(train[feat_cols], train[residual]) test test.copy() test[soh_corrected] test[base_col] res_model.predict(test[feat_cols]) return test[[start, soh, soh_ah, soh_corrected]]参数上残差模型要比主模型更保守max_depth2、n_estimators150足够因为残差本身幅度小。res_roll3用历史残差滑动平均在线更新时每来一个新片段就更新这个值不用重新训练。在线更新的关键是维护一个滑动窗口窗口大小 10 到 20 个片段每次新片段进来用窗口内数据微调残差模型的最后一棵树或者干脆只更新res_roll3。我一般用后者简单且不容易翻车。验证方法上不要只看 MAE。把soh_corrected按时间画出来看它是否比soh_ah更贴近真实 SOH 曲线尤其关注温度突变和 SOC 区间变化大的片段。如果修正后反而更差检查残差模型是否学到了时间泄漏比如用了未来片段的res_roll3。另一个技巧是留一电芯交叉验证如果有多个电芯的数据拿一个电芯做测试其余训练看跨电芯泛化。跨电芯 MAE 通常比同电芯高 30% 到 50%这是正常的但如果高出一倍以上说明特征里有过拟合电芯身份的信息比如温度绝对值。最后说个习惯我每次拿到新的电池数据集先不急着建模花半小时把电流、电压、温度、SOC 四条曲线画出来标出充放电段肉眼过一遍。这一步能发现 80% 的数据质量问题比后面调参省事得多。项目代码里我也会保留一个notebooks/explore.ipynb只做可视化不参与训练方便交接时别人能快速看懂数据长什么样。希望帮到你。本文还有配套的精品资源点击获取