简介:这是一份面向电池管理系统设计、电池寿命预测及机器学习算法研究人员的NASA锂电池试验数据集。数据集中包含B0045至B0048等电池的充放电循环记录,涵盖电压、电流、温度等多维参数随时间的动态变化,可用于分析不同充放电策略对电池性能及衰退机制的影响,帮助研究者验证电池状态估计算法、训练故障诊断模型。压缩包共六十五个文件,以二十三个mat数据文件为主,辅以xml配置文件、c/h源文件、txt说明文档及rsp脚本等,读者可直接读取mat数据进行建模与统计分析,也可结合配套的Simulink工程复现实验流程,整体大小约二十八点五六MB。目前已有五百一十五人学习下载。该数据集源自NASA在受控条件下开展的试验,数据质量可靠,既能支撑电池老化机理研究,也为电动汽车、储能系统及便携设备中的电池智能管理提供实证基础,同时可作为高校电池科学和数据分析课程的教学案例。 如果你正在做电池寿命预测、健康管理或者储能系统数据分析,八成绕不开“NASA锂电池试验数据集”这个名字。这是美国宇航局艾姆斯研究中心公开的一套锂离子电池老化试验数据,记录了18650电芯在几十个充放电循环里的电压、电流、温度、容量变化,早期很多电池RUL(剩余寿命)论文、开源项目都拿它当基准。这个数据集能做什么?概括起来是三件事:估计电池健康状态(SOH)、预测剩余使用寿命(RUL)、分析充放电曲线里的老化特征。适合做电池管理算法、想入数据驱动预测方向的同学,也适合用来验证你自己的模型到底靠不靠谱。
我在实际项目里用这套数据验证过好几个算法,整体感受是:数据干净但文档少,坑不少,但练手价值极高。下面从数据结构讲到实操建模,把从解压zip到跑出RUL预测的完整路径捋一遍。
1. 这个数据集到底能做什么:先搞懂NASA锂电池试验数据的价值
1.1 数据集从哪来、为什么它成了行业事实标准
NASA这套数据由艾姆斯研究中心的预测卓越中心(PCoE)发布,属于开源的老化实验数据。实验对象是商用18650锂离子电芯,额定容量约2Ah。它之所以成为事实标准,不是因为实验设计有多复杂,而是因为免费、公开、结构完整:每一颗电池从全新状态一直跑循环,跑到容量下降到额定容量的70%左右才停,完整覆盖了从“健康”到“寿命终止”的全过程。
对做算法的人来说,这太重要了。电池老化数据很难自己采集,一组完整的老化实验动辄几个月,还要搭恒温箱、充放电柜、上位机采集系统,成本很高。NASA把这部分脏活累活干完了,还做成统一的.mat文件格式,让后来者可以直接把精力放在特征工程和模型上。所以很多论文里用“NASA数据集+B0005电池”做对比实验,大家拿到的数据一样,算法好坏一目了然。
1.2 三条测试档案:充电、放电、阻抗
整个数据集围绕三种测试档案展开,这个设计思路值得先弄清,因为它直接决定了你能从数据里抽出什么特征。
第一是充电档案:以1.5A恒流(CC)充电到4.2V,然后转恒压(CV)充电,直到电流下降到20mA。这个过程记录的是电压、电流、温度随时间的变化。恒流阶段持续多久、恒压阶段电流衰减多快,都能反映电池内部状态。
第二是放电档案:以2A恒流放电到不同的截止电压,不同电池的截止电压设置不一样(大约在2.2V到2.7V之间)。放电过程记录的容量,就是电池当前还能放出多少电量的直接证据,这是SOH和RUL估计最核心的标签数据。
第三是阻抗档案:用0.1Hz到5kHz的频段做电化学阻抗谱(EIS)测量,记录电池在不同频率下的阻抗响应。阻抗和电池内部化学反应、内阻增长、锂离子扩散能力密切相关,是健康特征提取的重要来源。
理解这三类数据,你才算真正拿到了数据集的钥匙。后面所有建模,其实都是在回答同一个问题:如何从这些时序曲线里,把“电池老化了多少”这个隐藏变量挖出来。
2. 下到压缩包后第一步:把.mat文件拆开看一遍
2.1 按包结构解析:B0005到B0018四块电池
解压之后你会得到一组.mat文件,最常见的是四块电池:B0005、B0006、B0007、B0018。它们看起来长得差不多,实验设置其实有差异,放电截止电压、温度环境都不完全相同。这就带来一个问题:不能想当然把这四块电池当成重复实验直接合并,它们更像是同一测试标准下的四个独立样本,老化速度和容量衰减路径都有区别。
用MATLAB的人会习惯直接双击load,但做数据科学的人大多用Python。无论用什么工具,第一步都建议先看字段名,而不是急着写处理函数:
import scipy.io as sio import numpy as np data = sio.loadmat('B0005.mat', squeeze_me=True) print(data.keys())你会发现顶层字段通常是B0005,里面是一个结构体。结构体的字段包括cycle等核心内容。每块电池的cycle数组长度不一样,代表它跑过的循环次数不同,B0005的循环数可能和B0006差很多,这是实验条件差异导致的,不是数据错误。
2.2 理解cycle结构体,别把充放电数据混在一起
cycle是整个数据集最重要的结构体,它是一个数组,每个元素代表一次“实验步骤”。每个cycle里都会有个type字段,标记这次是charge、discharge还是impedance。把三种类型的数据混在一起处理,是新手最容易犯的错。
我用Python读取时,习惯先遍历一遍type,看看每块电池各自有多少个充电、放电、阻抗循环:
bs = data['B0005'] cycles = bs['cycle'] types = [] for i, cyc in enumerate(np.atleast_1d(cycles)): t = cyc['type'].item() if hasattr(cyc['type'], 'item') else cyc['type'] types.append(t) print(np.unique(types, return_counts=True))如果是用squeeze_me读取,cycle数组可能是结构体数组,遍历的时候每个元素是一个np.void,需要用上面的方式把type字段取出来。不同.mat版本的字段结构稍有差异,有的会把voltage、current、temperature放在cyc['data']的子结构里,有的会直接放在cycle顶层,建议每拿到一个文件就先打印字段名,不要猜。
另外要注意放电循环的capacity字段,有的版本在cycle顶层,有的在data里。稳妥做法是:
field_names = cycles[0].dtype.names print(field_names)先看清楚有哪些字段,再写提取逻辑。这一步多花一分钟,后面能省很多折腾时间。
3. 实操代码:从0开始提取容量衰减曲线
3.1 用Python读取.mat并遍历循环
容量衰减曲线是整个数据集最常用的基础曲线,几乎所有SOH、RUL方法都要从它出发。提取逻辑很简单:把所有type为discharge的循环遍历一遍,取出对应的capacity字段,按循环顺序排列。
discharge_capacities = [] cycle_indices = [] for i, cyc in enumerate(np.atleast_1d(cycles)): t = cyc['type'].item() if hasattr(cyc['type'], 'item') else cyc['type'] if t == 'discharge': cap = cyc['capacity'].item() if hasattr(cyc['capacity'], 'item') else cyc['capacity'] discharge_capacities.append(cap) cycle_indices.append(i) discharge_capacities = np.array(discharge_capacities)这里有个细节,NASA数据的放电循环编号不是连续从1开始的,中间会插入一些阻抗测量步骤。所以建议同时保存cycle_indices,避免后面画图时把横轴直接当成循环次数用。阻抗循环虽然没有容量数据,但它占了一个循环编号,忽略它会导致横轴错位。
3.2 画图确认衰减趋势:容量序列才是后续所有模型的地基
拿到容量序列后,第一件事永远是画图,不要急着上模型。画完你会看到一条经典的电池老化曲线:前期容量缓慢下降,中间可能出现平台期,后期加速衰退,整体呈现明显的非线性特征。
import matplotlib.pyplot as plt plt.figure(figsize=(10, 5)) plt.plot(discharge_capacities, marker='o', markersize=3, linestyle='-') plt.xlabel('Discharge Cycle Index') plt.ylabel('Capacity (Ah)') plt.title('B0005 Capacity Fade Curve') plt.grid(True) plt.show()B0005的初始容量大概在1.8Ah到2Ah之间,随着循环次数增加会掉到1.4Ah以下,这个1.4Ah就是很多论文里用的寿命终止阈值(EOL),即额定容量的70%。如果你拿到的曲线不太平滑,别急着清洗,先想想原因。容量数据来自放电积分,积分过程中电流、温度、采样频率都会产生微小误差,几个毫安时的波动很正常。
我建议把容量序列作为所有后续工作的“标签池”,每次做特征提取时都回头对照这条曲线,判断新特征是不是和容量衰减趋势一致。如果某个特征画出来和容量曲线形态差异很大,通常说明特征提取有bug或者选错了数据段。
4. 核心应用一:用数据集做电池健康状态(SOH)估计
4.1 为什么容量曲线不光滑:噪声、电压窗口和温度
SOH估计的核心任务就是实时估算电池当前容量相对于全新状态的保持率,通俗说就是“这电池还有几成新”。NASA数据集虽然是在实验室里跑的,容量曲线依然会有很多毛刺,不是教科书里那种光滑下降。
原因主要有三个:一是测量噪声,放电积分受电流传感器精度影响;二是电压窗口,不同循环的起点电压或者截止电压可能有微小差异,导致积分区间不完全一致;三是温度波动,恒温箱温度控制虽然好,但在长期实验里依然有波动,温度高了放出来的容量就多一点。
所以做SOH估计前,先对容量序列做平滑处理。我常用的方法有两种:一是简单滑动平均,窗口取3到5个点,注意不要用太大窗口,否则会把真实衰减趋势抹平;二是用鲁棒回归,比如局部加权回归(LOWESS),对离群点更耐受。另外可以设置一个离群点剔除规则,比如超过3倍标准差的点直接标记为异常,用前后两个点插值填补。
4.2 增量容量曲线与健康特征提取
如果只盯着放电容量做SOH,很多深层老化信息会被掩盖。我推荐你在NASA数据上尝试提取增量容量(IC)曲线,也就是dQ/dV对电压的关系。IC曲线的峰值位置、峰值高度会随着电池老化发生明显移动,这些特征比单纯容量更早反映性能退化。
具体计算方式是从充电曲线中取电压和容量数据,因为充电过程有恒流阶段,容量对电压的微分相对稳定。算法不复杂:
def compute_ic_curve(voltage, capacity, v_bins=500): v_min = np.min(voltage) v_max = np.max(voltage) bins = np.linspace(v_min, v_max, v_bins) idx = np.digitize(voltage, bins) dqdv = [] v_centers = [] for b in range(1, len(bins)): mask = idx == b if np.sum(mask) < 2: continue q_bin = capacity[mask] v_bin = voltage[mask] # 每个电压bin内做线性拟合,斜率就是dQ/dV coef = np.polyfit(v_bin, q_bin, 1) dqdv.append(coef[0]) v_centers.append(np.mean(v_bin)) return np.array(v_centers), np.array(dqdv)不同循环的充电电压序列长度不一样,电压区间也可能略有偏移,所以用分bin拟合比直接差分更稳定。算完后平滑一下,找到峰值位置和峰值高度,把这些作为健康特征,和容量一起组成训练数据。
我实测下来,NASA数据集的IC曲线在主峰附近能稳定观察到峰位随循环数增加而偏移,用这个特征做SOH估计,比直接用原始容量波动小。如果你熟悉机器学习,把IC特征加上温度统计量、恒压充电时长等特征,喂给随机森林或者XGBoost,能在小样本情况下拿到不错的效果。
5. 核心应用二:用数据集做剩余寿命(RUL)预测
5.1 数据准备:训练集、验证集、测试集怎么切
RUL预测的任务定义很清晰:给定电池前N个循环的观测数据,预测它还能撑多少个循环,也就是容量什么时候会衰减到EOL阈值以下。这不是普通的回归问题,而是时间序列预测问题,数据集的划分方式必须小心。
最常踩的坑是随便用train_test_split随机打乱数据。电池容量序列具有强自相关性,同一段容量曲线里的前20个点和后20个点高度相关,随机打乱会让训练集和测试集包含同一时间段的观测,导致评估结果虚高。正确做法是严格按时间顺序划分,比如用前60%的循环做训练,后20%做验证,最后20%做测试。
另一种更严谨的做法是滚动预测验证:训练集只用前若干循环,预测后一窗口,然后逐步扩展训练集。这样能模拟真实场景中“我只有截止到当前时刻的历史数据”的约束。但注意NASA每块电池总循环数只有一百多次,太复杂的滚动验证会让训练样本很少,模型容易欠拟合。
构造监督学习样本时,建议采用滑窗方式。假设用过去20个循环的容量序列X预测未来第10个循环的容量Y,每移动一步生成一个样本。滑窗长度和数据步长需要实验确定,我一般会先试20和10,再根据验证集效果调整。
5.2 模型选型:从线性外推到LSTM的最低可行方案
RUL模型有一个非常好用的baseline:直接用经验公式拟合容量衰减曲线。电池老化曲线常用双指数模型或者其他曲线拟合:
Q = a * exp(b * c) + c * exp(d * c)
其中c是循环数,a、b、c、d是待拟合参数。因为容量衰减尾部有加速趋势,双指数在前期拟合不错,但外推到中后期误差会变大。我没有必要神化这个公式,把它当成一个参考起点挺好。
真正做机器学习时,我用得比较多的是LSTM。输入是滑窗容量序列,输出是未来若干个循环的容量值。LSTM模型结构不用太深,一层128单元加一层Dropout就够,因为数据量不大,堆层越多越容易过拟合。训练时要用早停,把验证损失升高作为停止信号。
另外我还试过XGBoost直接回归,把滑窗内容量、温度统计量、充放电时长作为特征。对比下来,在NASA这种小数据量场景下,XGBoost的稳定性甚至比LSTM更好,训练快,调参也直观。LSTM的优势是可以输出整个未来轨迹,但数据量太少时反而不如结构化模型容易控制。
跑通基线之后可以改预测目标:不是预测容量曲线,而是直接预测到达EOL还需要多少循环。这样任务从回归变成分类或顺序回归,评价指标也更贴近工程需求,比如用平均绝对误差(MAE)评估预测循环数和真实循环数的偏差。
6. 常见问题与踩坑记录
6.1 六个高频问题速查表
这里整理我实际使用中遇到的典型问题,按出现频率排序:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 用scipy读取后找不到capacity字段 | 不同版本.mat文件字段位置不同 | 先用dtype.names查看字段名,capacity可能在cycle顶层,也可能在data里 |
| 遍历cycle时type字段取出来是数组 | squeeze_me参数影响结构体维度 | 用np.atleast_1d包裹后遍历,取值时加.item() |
| 容量曲线出现明显离群点 | 噪声、温度波动、实验中断 | 用3σ规则剔除或用滑动中位数平滑 |
| 用随机划分训练测试后模型效果很好 | 时序数据泄漏 | 改用按时间顺序划分,或者滚动窗口验证 |
| 训练集里看不出老化趋势,容量基本平 | 没有按顺序取循环,混入了早期平台期数据 | 检查循环索引,确保样本包含“完整老化路径”的特征 |
| 模型在某一颗电池上准,换一颗电池就崩 | 不同电池老化模式差异大 | 用多块电池做交叉验证,不要只盯一块电池调参 |
其中第二和第四个坑最值得留意。scipy.io.loadmat的squeeze_me参数会让结构体从二维数组变成一维,遇到空字段还会给你一堆nan,打印字段名是最快的排查方式。时序数据泄漏则是很多“效果惊人”的论文没有交代清楚的细节,你在复现时一定要自己把数据划分改成严格时间顺序,得到的指标才是可信的。
6.2 我的几个实战体会
第一,别把NASA数据集当成真实工况数据的替身。它是在实验室恒温条件下跑的,没有复杂的动态工况,也没有随机充放电策略。用它验证算法没问题,但部署到真实储能系统之前,一定要用实际运营数据再做一轮验证。
第二,容量衰减曲线虽然好用,但太依赖标签质量。如果你想做更工程化的SOH估计,建议把阻抗、温度、恒压阶段时长的信息都用上,多特征融合在小样本场景下比单靠容量更可靠。
第三,做RUL预测时注意电池老化阶段的切换。很多电池在前半段容量衰减平缓,后半段出现明显拐点,模型如果只学过平缓段,预测尾部会严重滞后。你可以专门训练一个“拐点检测器”,提前识别容量跳变区域,再决定使用哪段数据做外推,这个方法在工程上比单纯提高模型复杂度更有效。
第四,数据集循环次数有限,做深度学习时要控制模型容量。我见过有人拿LSTM加注意力网络在NASA数据上做出特别漂亮的图,但验证集一换就崩,本质还是过拟合。优先保证模型简单、特征明确,比你堆一个大型网络更可持续。
这套数据集我已经反复用过很多次,每次都会因为版本差异或者对电池机理理解的变化,发现一些新细节。如果你准备开始上手,我的建议是先完整跑一遍容量衰减曲线,再往上加IC特征和RUL模型,一步步来,不要急着上大模型。等你把B0005到B0018都吃透了,再去看真实业务数据,会轻松得多。
本文还有配套的精品资源,点击获取