锂电池寿命预测实战:基于LSTM与npy数据集的完整实现
简介这是一套基于Python的锂离子电池寿命预测完整项目面向本科毕业设计、课程设计以及期末大作业等场景重点覆盖数据清洗、特征构造、模型选择、剩余寿命回归预测和图表可视化等环节适合具备一定编程基础的学生快速复现并做二次开发。项目已获导师指导并通过整体代码完整、目录规整下载后按照说明执行即可运行无需额外修改。压缩包共含2000个文件大小约64.68MB其中1937张png图片用于展示预测结果与误差分析24个npy和15个pkl保存了经过处理的数据集与训练模型7个py是核心脚本另有5个pth权重文件、xlsx与xls格式的原始数据表、ipynb交互式分析文档以及readme说明方便按模块查阅。目前已有335人学习浏览可直接用于完成毕业设计、课程报告或作为论文实验支撑也可基于其完整流程进一步探索不同预测算法节省从零搭建项目的时间。1. 从一份能跑的毕业设计说起锂电池寿命预测到底在预测什么把这份 zip 解压后你会发现真正让项目“能跑”的核心不是模型代码而是三个 npy 文件MIT.npy、HUST.npy、RWTH.npy。它们是三个公开电池数据集的特征化结果分别来自 MIT 的早期预测实验、武汉理工的电池测试数据和德国亚琛工大RWTH的商用电池老化数据。锂离子电池寿命预测业界一般不叫“预测电池还能用几天”而是预测两个更工程化的量SOH健康状态当前容量与额定容量的比值和 RUL剩余使用寿命还能完成多少次完整充放电循环。这个项目的完整链路是解析 npy 数据矩阵 → 构造时序滑窗样本 → 训练循环神经网络 → 输出未来若干次循环的容量衰减曲线从而外推出寿命终点。它适合两类人一类是正在做电池管理、储能系统相关的工程师想快速验证 LSTM 类模型在老化工况下的表现另一类是本科毕业设计或课程设计需要一份“数据代码模型”闭环的完整参考实现。2. 读懂 npy 数据集结构与防泄漏的特征工程2.1 三个数据集的背景差异与格式解读MIT、HUST、RWTH 这三个数据集在电池领域是高频出现的基准。MIT 数据集来自 Severson 等人 2019 年发表在 Nature Energy 上的早期预测工作内容是 124 个商用 LFP/石墨电池在不同快充策略下的老化实验每个电池循环到容量衰减到 80% 寿命终止。HUST 数据集是武汉理工团队公开的磷酸铁锂电池不同温度下的循环老化数据温度范围一般在 30°C 到 50°C 之间。RWTH 数据集来自亚琛工大 ACC 项目用的是 18650 商用电池放电策略更贴近真实工况。这份 zip 里没有原始 CSV 或 mat 文件而是统一转成了 npy 二进制数组说明作者已经完成了“原始数据 → 特征数组”的预处理。拿到手第一步不是写模型而是先把数组的 shape 和 dtype 看清。import numpy as np data np.load(MIT.npy, allow_pickleTrue) print(data.shape) # 观察维度 print(data.dtype) # 观察数值类型 print(data.item().keys()) # 如果是 dict 结构查看键名allow_pickleTrue是因为部分公开数据集转 npy 时会保留对象数组内部可能存的是 dict每个键对应一个电池编号值是该电池的循环特征矩阵。你需要先确认是“单一大数组”还是“字典套数组”。如果是后者常见做法是先看键的数量再抽查某一个电池矩阵的形状通常排列是[循环数, 特征维度]特征维度可能包含容量、电压、内阻、温度等维度。这一步虽然简单但能避免后面把字典对象直接喂给张量时出现零维数组错误。2.2 时序滑窗构造训练样本寿命预测的输入输出设计直接决定模型能不能收敛。常见做法是用前window_size次循环的容量序列必要时拼接充电时间、温度等特征预测未来pred_len次循环的容量或者直接预测到寿命终止的剩余循环数。前者是序列到序列后者是序列到标量。毕业设计场景下我一般选“滑窗预测下一段容量序列”因为评估时能画出预测退化曲线和真实曲线的叠加图答辩时直观。def create_sequences(data, window_size50, pred_len20): X, y [], [] for i in range(len(data) - window_size - pred_len 1): X.append(data[i:i window_size]) y.append(data[i window_size:i window_size pred_len]) return np.array(X), np.array(y) # 假设 cell_data 是单个电池的容量序列 [循环数] cap cell_data[:, 0] # 取容量列 X, y create_sequences(cap, window_size50, pred_len20) print(X.shape, y.shape) # (样本数, 50, 1) (样本数, 20)这里面有几个容易被忽略的点。window_size取 50 意味着用 50 次循环的历史推断未来 20 次循环窗口越大包含的退化趋势越完整但会吃掉早期样本。pred_len不是越大越好预测距离越远误差累积越快。另外构建滑窗时不要把多个电池的数据混在一起滑因为每个电池的初始容量有差异混在一起等于给模型加了噪声正确做法是每个电池单独调用一次create_sequences最后再拼接。2.3 归一化与数据泄漏的边界容量序列需要归一化但不能在全体数据上做MinMaxScaler.fit否则验证集和测试集的信息在训练前就渗进了归一化参数这叫归一化泄漏会让测试指标虚高。正确做法是先切分后归一化或者按电池编号切分。from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import train_test_split # 按电池编号切分而不是按随机行切分 cell_ids list(data.keys()) train_ids, test_ids train_test_split(cell_ids, test_size0.2, random_state42) # 每个电池独立归一化拟合时只用训练集的scaler scaler MinMaxScaler() train_norm [scaler.fit_transform(data[cid][:, 0].reshape(-1, 1)) for cid in train_ids] test_norm [scaler.transform(data[cid][:, 0].reshape(-1, 1)) for cid in test_ids]按电池切分比按行随机切分更接近真实场景实际部署时你手里的模型要面对的是从未见过的新电池。如果按行随机切同一个电池的前半段在训练集、后半段在测试集等于模型“已经见过这个电池的前半生”测试结果没有参考价值。这也是很多电池寿命预测论文被审稿人质疑的常见点。3. 基于 LSTM 的剩余寿命预测模型构建与参数设定3.1 为什么选 LSTM 而不选 TCN 或 Transformer电池容量退化本质上是一个带噪声的长程相关时序过程早期循环的微小差异会累积影响后期寿命。LSTM 通过输入门、遗忘门、输出门三个门控结构控制信息在时间步间的流动能在数十步的窗口内保留早期退化信号。TCN 的膨胀卷积对长程依赖需要叠加更多层Transformer 在序列长度只有 50 左右时优势不明显而且小数据集上更容易过拟合。在这个项目的数据规模下几百个电池、每个几十到上千次循环LSTM 是收敛稳定性和实现成本之间最平衡的选择。模型定义上我习惯用双向 LSTM 拿上下文信息再接一层 attention 对时间步加权最后通过全连接层输出预测序列。双向并不增加多少计算量但对容量曲线这种整体趋势性序列能同时看到当前步前后的退化走向。import torch import torch.nn as nn class BatteryLSTM(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, pred_len20, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, bidirectionalTrue, dropoutdropout ) self.attn nn.MultiheadAttention( embed_dimhidden_size * 2, num_heads2, batch_firstTrue ) self.reg nn.Sequential( nn.Linear(hidden_size * 2, hidden_size), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_size, pred_len) ) def forward(self, x): # x: (batch, window_size, input_size) out, _ self.lstm(x) # (batch, window_size, hidden*2) attn_out, _ self.attn(out, out, out) last attn_out[:, -1, :] # 取最后一个时间步 return self.reg(last)input_size1表示每条序列是单变量容量如果后续想把电压、温度、内阻一并作为输入改成对应维度即可。hidden_size64在这个数据量级下够用过大容易过拟合过小拟合不了退化趋势的非线性。num_layers2是因为单层 LSTM 对容量曲线的长期单调下降趋势建模能力有限两层能捕获更高层级的退化特征。batch_firstTrue对应输入维度(batch, seq_len, feature)这个设置和 PyTorch 默认的(seq_len, batch, feature)不同很多人在这里踩维度错误的坑。3.2 训练之前的数据封装与超参数基线把上一章的滑窗样本封装成 PyTorch 的 Dataset注意在__getitem__里做数据增强没有意义这里要做的是把 numpy 数组转成float32张量否则默认的float64会让训练速度慢一倍且显存翻倍。from torch.utils.data import Dataset, DataLoader, TensorDataset class BatteryDataset(Dataset): def __init__(self, X, y): self.X torch.tensor(X, dtypetorch.float32) self.y torch.tensor(y, dtypetorch.float32) def __len__(self): return len(self.X) def __getitem__(self, idx): return self.X[idx], self.y[idx]训练超参数我一般先跑一组基线batch_size32lr1e-3epochs50optimizerAdamWlossMSE。50 次循环的滑窗生成的数据量属于小样本范畴batch 太大容易让梯度方向被少数异常电池主导。验证集每个 epoch 结束后计算一次 RMSE保存验证指标最优的模型权重而不是最后一个 epoch 的权重。3.3 关键参数对训练结果的影响参数取值影响方向说明window_size30~80窗口越大看到的退化历史越长但样本数越少数据充足时用 80数据紧张用 30pred_len10~30预测越远误差越大答辩演示建议 20兼容可视化和误差hidden_size32~128过大过拟合过小欠拟合64 起步看验证集曲线微调num_layers1~3层数越多非线性和表达力越强2 层在多数电池数据上够用dropout0.1~0.3抑制过拟合dropout 在 LSTM 层间和全连接层都加lr1e-4~1e-3过大训练震荡过小收敛慢用 AdamW 余弦退火更稳4. 训练收敛策略与评估指标解读4.1 早停与学习率调度电池容量序列噪声不小最后的容量衰减阶段波动尤其明显模型容易在训练后期震荡。我推荐一套组合拳ReduceLROnPlateau 配合 EarlyStopping。监测指标用验证集的 RMSE连续 10 个 epoch 不下降就把学习率乘 0.5连续 20 个 epoch 不下降就停止训练。这能避免为了“等它收敛”而盲目加大 epoch 数导致过拟合。from torch.optim.lr_scheduler import ReduceLROnPlateau model BatteryLSTM() optimizer torch.optim.AdamW(model.parameters(), lr1e-3) scheduler ReduceLROnPlateau(optimizer, modemin, factor0.5, patience10) loss_fn nn.MSELoss() best_rmse float(inf) early_stop_patience, no_improve 20, 0 for epoch in range(100): model.train() epoch_loss 0.0 for x_batch, y_batch in train_loader: optimizer.zero_grad() pred model(x_batch) loss loss_fn(pred, y_batch) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() epoch_loss loss.item() val_rmse evaluate(model, val_loader) scheduler.step(val_rmse) if val_rmse best_rmse: best_rmse val_rmse torch.save(model.state_dict(), best_model.pt) no_improve 0 else: no_improve 1 if no_improve early_stop_patience: breakclip_grad_norm_的参数max_norm1.0是防止梯度爆炸的关键。LSTM 在长序列上很容易因为循环权重连乘出现梯度爆炸裁剪后训练稳定很多。ReduceLROnPlateau的modemin表示监测指标越低越好如果监测的是准确率则改为max。epoch_loss只是打印观察真正的早停条件始终以验证集指标为准。4.2 评估指标体系RMSE、MAE、R²电池寿命预测的评估不能只看一个指标RMSE 对异常预测敏感MAE 反映平均偏差R² 衡量模型对容量衰减方差的解释程度。三个指标配合使用才能判断模型是“整体偏了一点”还是“个别电池预测崩了”。这里给出一个直接可用的评估函数。import numpy as np from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def evaluate(model, loader): model.eval() y_trues, y_preds [], [] with torch.no_grad(): for x_batch, y_batch in loader: pred model(x_batch) y_trues.append(y_batch.numpy()) y_preds.append(pred.numpy()) y_true np.concatenate(y_trues, axis0) y_pred np.concatenate(y_preds, axis0) rmse np.sqrt(mean_squared_error(y_true.flatten(), y_pred.flatten())) mae mean_absolute_error(y_true.flatten(), y_pred.flatten()) r2 r2_score(y_true.flatten(), y_pred.flatten()) # 按预测序列长度逐点分析观察远期的误差膨胀 per_step_rmse [ np.sqrt(mean_squared_error(y_true[:, i], y_pred[:, i])) for i in range(y_true.shape[1]) ] return {rmse: rmse, mae: mae, r2: r2, per_step_rmse: per_step_rmse}per_step_rmse是容易被忽略但很有诊断价值的一个输出它反映的是预测第 k 个未来时间点时的误差。如果前 5 步 RMSE 很小、第 15 步开始陡然上升说明模型对短期退化学习得好但对长期趋势的外推能力不足这时候需要调整的不是网络结构而是滑窗窗口长度或损失函数。误差可视化上我通常画两张图一张是某个代表性电池的真实容量曲线与预测曲线的对比另一张是全部测试电池的误差分布直方图。第一张图能看到“预测是否跟得上转折点”第二张图能看到“是否有某个电池是离群点”。如果误差直方图呈双峰分布说明测试集里可能混入了不同工况的电池需要考虑按数据集单独评估。4.3 常见训练陷阱与排查思路容量序列末段波动大你会发现 loss 下降到一定程度后不再下降这是正常的因为电池寿命终止前的容量骤降是强随机过程任何模型都无法精确预测。此时应该检查的是模型是否在验证集上出现了“预测曲线平滑、但没有抓住早期拐点”的情况。如果是优先尝试把输入特征从纯容量扩展到容量充电时间如果原始 npy 里只有容量列可以在特征工程阶段用差分法构造容量衰减速率特征即np.diff(cap)把它拼接到滑窗样本里。另一个高频坑是标准化时把测试集电池的容量序列整个 fit 了一遍这在离线评估里会高估实际性能。正确的部署思维是模型在训练阶段只见过训练集和验证集的电池测试集的电池从头到尾都是“陌生人”。如果你发现测试集误差远大于验证集误差先检查是不是归一化泄漏再检查是不是随机种子导致的数据划分不均匀。5. 跨数据集迁移的微调技巧与模型落盘MIT、HUST、RWTH 三个数据集虽然都是锂电池老化数据但电池化学体系、测试温度、充放电策略各不相同。直接用 MIT 训练的模型去预测 RWTH 的电池RMSE 通常会明显变大。这里介绍一个实用技巧域自适应微调。具体做法是拿预训练好的模型冻结 LSTM 层参数只用目标数据集的一小部分样本重新训练回归头等回归头收敛后再把 LSTM 层解冻用很小的学习率全量微调。# 冻结 LSTM 层训练回归头 for name, param in model.named_parameters(): if lstm in name: param.requires_grad False optimizer torch.optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr1e-3 ) # 训练 10 个 epoch 后解冻 LSTM for name, param in model.named_parameters(): param.requires_grad True optimizer torch.optim.AdamW(model.parameters(), lr1e-4)微调的核心参数是lr。冻结阶段用1e-3让回归头快速适应目标域的容量范围解冻后降到1e-4避免大步长把预训练学到的退化规律冲掉。迁移后对比两组数字微调前的跨域 RMSE 和微调后的跨域 RMSE通常会有 15% 到 40% 的下降。模型部署时用torch.jit.script把模型和预处理管线打包成一个文件推理时不需要重新加载 torchvision 或 sklearn 的每一个组件。scripted_model torch.jit.script(model) scripted_model.save(battery_lstm_mit.pt)这块模型文件可以直接嵌入到 BMS 边缘设备或云端推理服务里。毕业设计答辩时把跨数据集误差对比表一页展示再配合三行脚本说明“从数据到模型到部署”的完整链路这个项目的完成度和工程深度就都体现出来了。最后留一个小改进方向把pred_len从固定值改成动态衰减——当模型预测输出容量低于 80% 额定容量时提前终止这样输出的不再是一段固定序列而是一个具体的寿命循环数。本文还有配套的精品资源点击获取