news 2026/9/18 7:20:34

Transformer-LSTM混合模型:锂电池剩余寿命预测实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Transformer-LSTM混合模型:锂电池剩余寿命预测实战

简介:面向新能源与电池管理领域的数据科学从业者及研究生,这份Python项目实例基于Transformer-LSTM混合模型实现锂电池剩余寿命(RUL)预测,覆盖数据预处理、滑动窗口采样、特征工程、模型训练优化全流程,并集成GUI交互界面,支持数据加载、模型推理与预测结果动态展示。文档通过MSE、MAE、R²、RMSE、MAPE等指标及残差分析评估精度,提供注意力权重与LSTM隐状态可视化,增强模型可解释性。代码详解从Transformer自注意力机制与LSTM时序建模的融合入手,兼顾工程化部署,可直接复现或扩展至多模态输入与轻量化场景。资源为单份docx文档,约72KB,内容紧凑完整。目前已有262人学习,适合具备PyTorch基础、希望深入时序预测与GUI落地的中高级读者。

1. 从容量衰减到 Transformer-LSTM:RUL 预测为什么需要混合模型

锂电池剩余寿命(RUL)预测的难点,不是拟合一条平均衰减曲线,而是捕捉同型号电池在不同工况下的分散性。同样是 20% 容量衰减,有的电池还能跑 300 次循环,有的只剩不到 100 次。LSTM 擅长记住最近的退化趋势,但面对长序列中的全局模式容易遗忘;Transformer 的自注意力恰好擅长跨时间步的长程特征提取。把两者串成混合结构:先由 Transformer 建模全局依赖,再由 LSTM 提取局部时序特征,最后融合回归,是当前比较稳定的方案。这篇文章记录一个用 Python + PyTorch 实现的 Transformer-LSTM 锂电池 RUL 预测项目,覆盖数据构造、模型构建、训练评估与 GUI 热插拔推理,可直接作为 BMS 电池健康管理或课程设计的复现底稿。

2. 数据准备与特征工程:滑动窗口、归一化与 DataLoader

RUL 预测本质是时序回归。输入是电池前若干次循环的观测序列,输出是剩余可用循环数。如果拿整条退化曲线训练,模型学到的只是“这条曲线对应某个寿命”,换一条曲线就失效;滑动窗口采样把长序列切成固定长度的子序列,让模型在每个时间步都能看到最近 N 次循环的特征。这是项目能在小样本下保持泛化能力的第一步。

2.1 生成合成电池退化数据

真实电池数据集如 NASA 或 CALCE 公开数据量小、工况不完全一致,做课程设计和算法验证时,通常先用合成数据跑通流程。项目里生成 40 条电池容量衰减曲线,每条曲线从 1.0 开始按指数 + 线性趋势下降,叠加高斯噪声和随机波动,模拟不同电池的初始差异。

import numpy as np import pandas as pd def generate_battery_curves(n_batteries=40, cycles=600, noise_std=0.006): data = [] for i in range(n_batteries): # 不同电池的衰减速率在 0.00035~0.00055 之间浮动 rate = np.random.uniform(0.00035, 0.00055) start_cap = np.random.uniform(0.98, 1.02) # 初始容量轻微偏移 idx = np.arange(cycles) # 指数+线性组合,模拟早期慢、后期快的退化过程 capacity = start_cap - rate * idx - 0.08 * np.exp(idx / 300) capacity += np.random.normal(0, noise_std, cycles) # 温度、电流、电压模拟统一放另一组特征列,这里先只保留容量 df = pd.DataFrame({ 'battery_id': i, 'cycle': idx, 'capacity': capacity }) data.append(df) return pd.concat(data, ignore_index=True) df = generate_battery_curves() print(df.shape)

说明:n_batteries=40控制样本数量,cycles=600是每条曲线的最大循环数,noise_std是观测噪声。这里用“指数 + 线性”组合是为了模拟锂电池先缓慢衰减、后期加速老化的非线性趋势。实际使用公开数据集时,把df替换成读取文件即可,后续特征工程代码不用改动。

输入特征工程中除了容量,还常加入循环次数、恒流充电时间、温度变化率等。项目示例为了突出模型结构,先用容量序列做单特征输入,但代码里保留了扩展多特征的接口。最终每条样本是一个形状为(batch, window_size, n_features)的三维张量。

2.2 滑动窗口采样与归一化

滑动窗口有两个关键参数:window_sizehorizonwindow_size是输入序列长度,horizon是预测目标距离当前时刻的步数。常见做法是window_size=30, horizon=1,即用最近 30 次循环预测下一次循环后的剩余寿命;也可以把horizon设为 5,输出未来第 5 次循环时对应的 RUL。窗口长度的选择要结合电池循环数据长度,太小会丢失退化趋势,太大会让 Transformer 注意力矩阵变稀疏、训练变慢。

归一化使用 MinMaxScaler。注意:必须只对特征列拟合,cyclebattery_id不做归一化;预测目标 RUL 单独定义,不参与特征归一化。下面的代码先构造滑动窗口样本,再切分训练集,最后在训练集上拟合 scaler。

def create_sequences(data, window_size=30, horizon=1): X, y = [], [] values = data['capacity'].values for i in range(len(values) - window_size - horizon + 1): # 输入:连续 window_size 个容量观测值 X.append(values[i:i+window_size]) # 目标:当前窗口结束位置到寿命结束的剩余循环数 rul = len(values) - (i + window_size + horizon - 1) y.append(rul) return np.array(X, dtype=np.float32), np.array(y, dtype=np.float32) X, y = create_sequences(df[df.battery_id == 0], window_size=30, horizon=1) print(X.shape, y[:3])

逻辑说明:循环用i遍历起始位置,X[i][i, i+window_size)的容量值,y[i]是窗口末端到曲线末尾的剩余循环次数。这里RUL定义为“还能循环多少次”,和容量阈值的映射关系由外部设置。如果按 80% 容量作为报废线,需要先截断到失效点再生成序列,否则模型会学到“寿命固定在 600”的假规律。多电池组合时,按battery_id分组后分别调用该函数,再合并。

归一化代码:

from sklearn.preprocessing import MinMaxScaler def normalize_features(X_train, X_val, X_test): # shape: (n_samples, window_size) -> (n_samples, window_size, 1) scaler = MinMaxScaler() n_samples_train, window = X_train.shape X_train_2d = X_train.reshape(-1, window) scaler.fit(X_train_2d) X_train_norm = scaler.transform(X_train_2d).reshape(n_samples_train, window, 1) X_val_norm = scaler.transform(X_val.reshape(-1, window)).reshape(-1, window, 1) X_test_norm = scaler.transform(X_test.reshape(-1, window)).reshape(-1, window, 1) return X_train_norm, X_val_norm, X_test_norm, scaler

参数说明:reshape(-1, window)是把三维样本先压成二维,再喂给 scaler,目的是让每个时间步的容量分布统一映射到 0~1;reshape(..., 1)是为了给 PyTorch 增加特征维度。验证集和测试集只用训练集拟合好的 scaler 变换,不能在验证集上重新 fit,否则会引入未来信息。

参数建议值作用调参方向
window_size30输入序列长度,决定模型看多长的历史数据长度短时取 10~20,数据密集取 30~50
horizon1预测目标与当前时刻的步数差做长期预测时增大到 5~10,但误差会明显上升
训练/验证/测试比例60%/20%/20%按电池曲线划分,不能按样本随机划分数据量大时可增加验证集比例
noise_std0.006观测噪声幅度,模拟传感器误差数据噪声大时可以到 0.01

按电池曲线划分而非按样本划分,是因为时序样本本身存在时间重叠。随机划分会让同一条曲线的相邻窗口同时出现在训练集和验证集,评估出的指标会明显高于真实部署表现。

2.3 划分训练集、验证集、测试集与 DataLoader

时序数据的划分有一个最容易踩的坑:直接train_test_split(X, y, random_state=42)会随机打乱样本,导致验证集里出现与训练集时间重叠的样本,评估结果偏高。正确做法是把电池按 ID 分组,先切分电池,再在每组内生成滑动窗口样本。battery_id为 0-29 的进训练集,30-35 进验证集,36-39 进测试集。

import torch from torch.utils.data import TensorDataset, DataLoader def build_dataloader(battery_ids, window_size=30, horizon=1, batch_size=64, shuffle=True): X_all, y_all = [], [] for bid in battery_ids: sub = df[df.battery_id == bid] X, y = create_sequences(sub, window_size, horizon) X_all.append(X) y_all.append(y) X_cat = np.concatenate(X_all, axis=0) y_cat = np.concatenate(y_all, axis=0) # 演示简化:这里用 X_cat 统一归一化,实际应先用训练集 fit 再 transform X_norm, _, _, _ = normalize_features(X_cat, X_cat, X_cat) dataset = TensorDataset(torch.tensor(X_norm), torch.tensor(y_cat).float()) return DataLoader(dataset, batch_size=batch_size, shuffle=shuffle) train_loader = build_dataloader(range(0, 30)) val_loader = build_dataloader(range(30, 36), shuffle=False) test_loader = build_dataloader(range(36, 40), shuffle=False)

这段演示代码为了简洁,在build_dataloader里重新拟合了 scaler,实际项目里应该把训练集 scaler 作为参数传入,否则验证集和测试集的归一化基准不一致。shuffle=False用于验证集和测试集,保留时间顺序,方便后面对应样本序号画误差曲线。到这里,数据部分已经准备好了符合 PyTorch 输入规范的三维张量,下一步开始构造 Transformer-LSTM 混合模型。

3. Transformer-LSTM 混合模型构建:自注意力、时序记忆与特征融合

模型结构分三层:先用 Transformer 编码器提取全局依赖,再用 LSTM 对局部退化趋势做时序建模,最后把两条路径的特征拼接并通过全连接层回归出 RUL。选择这种结构的原因是,Transformer 的 self-attention 能直接看到窗口内任意两个时刻的关系,比如早期一次容量骤降与后期加速衰减之间可能强相关;LSTM 则保持了对连续波动的敏感性。混合结构会带来额外的参数量,但在本项目的 600 循环样本量级下不会过拟合,前提是做好 dropout 和早停。

3.1 Transformer 编码器模块

PyTorch 自带nn.TransformerEncoderLayer,不需要从零实现多头注意力。这里用d_model=64nhead=4dim_feedforward=128batch_first=True让输入形状直接是(batch, seq_len, features),避免频繁 permute。

import torch.nn as nn class TransformerEncoderBlock(nn.Module): def __init__(self, d_model=64, nhead=4, dim_feedforward=128, dropout=0.1): super().__init__() self.encoder_layer = nn.TransformerEncoderLayer( d_model=d_model, nhead=nhead, dim_feedforward=dim_feedforward, dropout=dropout, batch_first=True, activation='relu' ) self.norm = nn.LayerNorm(d_model) def forward(self, x): # x: (batch, seq_len, d_model) x = self.encoder_layer(x) return self.norm(x)

参数说明:d_model=64表示每个时间步被映射成 64 维向量,太小会丢失特征表达,太大在电池数据这种小样本场景容易过拟合;nhead=4表示多头注意力切成 4 个子空间,注意力输出维度仍然是 64。这里在 Transformer 输出后又接了一个LayerNorm,因为编码器内部可能已经有 norm,但多一次 norm 能稳定后期与 LSTM 特征拼接后的梯度。

3.2 LSTM 时序编码模块

LSTM 部分只取最后一个时间步的隐状态还是取所有时间步的平均池化?常见做法是双向 LSTM 取最后时刻的隐状态拼接,但项目里更推荐取最后一个前向隐状态和最后一个反向隐状态拼接,因为电池退化方向性明确,反向信息对“当前状态距离寿命终点还有多远”有直接提示。下面代码用nn.LSTMbatch_first=True,输入是 Transformer 输出的同维度特征。

class LSTMTemporalEncoder(nn.Module): def __init__(self, input_size=64, hidden_size=128, num_layers=1, dropout=0.2): super().__init__() # 双向 LSTM,输出维度为 hidden_size * 2 self.lstm = nn.LSTM( input_size=input_size, hidden_size=hidden_size, num_layers=num_layers, batch_first=True, bidirectional=True, dropout=dropout if num_layers > 1 else 0.0 ) def forward(self, x): out, (h_n, _) = self.lstm(x) # h_n shape: (num_layers * num_directions, batch, hidden_size) last_forward = h_n[-2] # 前向最后层 last_backward = h_n[-1] # 反向最后层 combined = torch.cat([last_forward, last_backward], dim=-1) return combined

h_n[-2]h_n[-1]的取法在双向 LSTM 里固定:最后一层前向隐状态在前,反向在后。hidden_size=128定义单方向维度,双向拼接后变成 256 维。这个维度会直接决定后面的全连接层输入大小。这里num_layers=1,避免多层 LSTM 在短序列上出现梯度退化。

3.3 特征融合与输出层

把 Transformer 的输出序列再做一次全局平均池化,和 LSTM 得到的 256 维向量拼接,形成 64+256=320 维特征。平均池化的动机是自注意力已经编码了位置关系,取平均可以保留全局模式;而 LSTM 部分提供方向性状态。融合层采用三层全连接:320 -> 128 -> 32 -> 1,激活函数用 ReLU,最后一层不接激活函数。

class TransformerLSTM(nn.Module): def __init__(self, n_features=1, d_model=64, nhead=4, hidden_size=128, dropout=0.2): super().__init__() self.input_proj = nn.Linear(n_features, d_model) self.transformer = TransformerEncoderBlock(d_model=d_model, nhead=nhead) self.lstm = LSTMTemporalEncoder(input_size=d_model, hidden_size=hidden_size, dropout=dropout) fusion_dim = d_model + hidden_size * 2 self.fc = nn.Sequential( nn.Linear(fusion_dim, 128), nn.ReLU(), nn.Dropout(dropout), nn.Linear(128, 32), nn.ReLU(), nn.Linear(32, 1) ) def forward(self, x): # x: (batch, seq_len, n_features) x = self.input_proj(x) # 映射到 d_model 维 x = self.transformer(x) # 全局特征 avg_pool = x.mean(dim=1) # (batch, d_model) lstm_feat = self.lstm(x) # (batch, hidden_size*2) feat = torch.cat([avg_pool, lstm_feat], dim=-1) out = self.fc(feat) return out.squeeze(-1)

逻辑说明:input_proj把原始单特征从 1 维扩展到 64 维,这样 Transformer 的 positional encoding 才有足够的维数做位置区分。x.mean(dim=1)对序列长度维求平均,与 LSTM 输出拼接。在真实项目中,如果同时输入电压、电流、温度等多个特征,只需修改n_featuresinput_proj的输入维数,后续结构不需要改动。

模块输入维度输出维度作用
input_proj(batch, seq_len, 1)(batch, seq_len, 64)升维并构造特征表示
TransformerEncoderBlock(batch, seq_len, 64)(batch, seq_len, 64)提取全局长程依赖
LSTM Temporal Encoder(batch, seq_len, 64)(batch, 256)提取局部退化模式
全局平均池化(batch, seq_len, 64)(batch, 64)压缩时序信息
全连接回归层(batch, 320)(batch, 1)输出 RUL 预测值

3.4 位置编码与前向形状验证

Transformer 本身不像 LSTM 那样天然带顺序信息,必须给每个时间步添加位置编码。常见做法是使用可学习的位置嵌入,维度与d_model一致。下面定义PositionalEncoding模块并在模型前向中叠加。

class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=200): super().__init__() self.pos_embedding = nn.Parameter(torch.randn(1, max_len, d_model) * 0.02) def forward(self, x): return x + self.pos_embedding[:, :x.size(1), :]

完整使用时,在TransformerLSTM.forward里把x = self.transformer(x)之前插入一行x = self.pos_enc(x)即可。位置编码初始化为随机值,训练后会学到窗口内不同时序位置的相对关系。前向验证代码:

model = TransformerLSTM(n_features=1) x = torch.randn(8, 30, 1) y = model(x) print(y.shape) # (8,)

输出(8,)表示 8 个样本各自获得一个标量 RUL 预测值。如果输出维度是(8, 1),说明squeeze(-1)没有被正确调用,不影响评估逻辑,但后续写 GUI 时需要自行处理维度。

4. 训练与评估:损失函数、早停、多指标对比与可视化

Transformer-LSTM 混合模型的训练策略和纯 LSTM 有两点不同:一是学习率要更低,通常在 1e-4 以下,因为 Transformer 对学习率敏感;二是 Batch 内样本的 RUL 分布差异很大,训练前期丢给模型一批早期电池样本和一批接近报废的样本,会让 loss 震荡。项目里采用 AdamW + 余弦退火学习率,并在验证集 loss 连续 20 个 epoch 不下降时提前停止。

4.1 损失函数与优化器选择

回归任务首选 MSE,因为它对大偏差惩罚更重,能让模型更关注寿命中后段误差大的样本。也可以使用 Huber Loss,在残差大于阈值时退化为 MAE,对离群点更稳健。下面的训练配置是项目里效果较好的组合:lr=1e-4weight_decay=1e-5batch_size=64max_epochs=200

import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR model = TransformerLSTM(n_features=1) criterion = nn.MSELoss() optimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5) scheduler = CosineAnnealingLR(optimizer, T_max=200, eta_min=1e-6)

参数说明:weight_decay是 L2 正则,防止全连接层过拟合;T_max=200是余弦周期长度,这里等于最大 epoch 数,让学习率从 1e-4 逐渐降到 1e-6;eta_min是最低学习率。如果训练集很小,可以把weight_decay提高到 1e-4。

4.2 训练主循环与早停机制

早停的指标是验证集 MSE。每个 epoch 结束后计算验证 loss,如果比历史最优值低就保存模型参数,否则连续 20 次不更新就停止训练。注意保存的是model.state_dict(),不是整个模型对象,方便之后在 GUI 里热插拔加载。

best_loss = float('inf') patience = 20 trigger_times = 0 for epoch in range(200): model.train() train_loss = 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred = model(xb) loss = criterion(pred, yb) loss.backward() optimizer.step() train_loss += loss.item() * xb.size(0) scheduler.step() model.eval() val_loss = 0.0 with torch.no_grad(): for xb, yb in val_loader: pred = model(xb) val_loss += criterion(pred, yb).item() * xb.size(0) avg_val_loss = val_loss / len(val_loader.dataset) if avg_val_loss < best_loss: best_loss = avg_val_loss trigger_times = 0 torch.save(model.state_dict(), 'best_model.pth') else: trigger_times += 1 if trigger_times >= patience: print(f'Early stop at epoch {epoch+1}') break

逻辑说明:xb进入模型前不需要额外归一化,因为数据加载环节已经完成;xb.size(0)是当前 batch 的真实样本数,避免最后一个 batch 不足导致 loss 平均偏差。早停保存的best_model.pth会直接作为后续 GUI 默认权重。

4.3 评估指标:MSE、MAE、R²、RMSE、MAPE 与残差形态

项目使用六个指标,分别从不同角度衡量预测质量:MSE 强调大误差,RMSE 与原始单位一致,MAE 反映平均绝对偏离,R² 反映模型相对均值基线的好坏,MAPE 给出百分比误差,偏度和峰度则用来检验残差是否接近正态分布。残差偏度接近 0 表示预测没有系统性高估或低估,峰度接近 3 说明大部分误差集中在中心区域、没有多根异常尾巴。

from scipy.stats import skew, kurtosis def evaluate_metrics(y_true, y_pred): y_true = np.asarray(y_true) y_pred = np.asarray(y_pred) mse = np.mean((y_true - y_pred) ** 2) mae = np.mean(np.abs(y_true - y_pred)) rmse = np.sqrt(mse) r2 = 1 - np.sum((y_true - y_pred)**2) / np.sum((y_true - np.mean(y_true))**2) mape = np.mean(np.abs((y_true - y_pred) / y_true)) * 100 residual = y_true - y_pred skewness = skew(residual) kurt = kurtosis(residual) return { 'MSE': mse, 'MAE': mae, 'R2': r2, 'RMSE': rmse, 'MAPE': mape, 'Skewness': skewness, 'Kurtosis': kurt } metrics = evaluate_metrics(y_test, y_pred) for k, v in metrics.items(): print(f'{k}: {v:.4f}')
指标公式/含义数值偏好实际提示
MSE均方误差越小越好对大误差敏感
MAE平均绝对误差越小越好反映平均偏离程度
决定系数越接近 1 越好表示模型解释的方差比例
RMSE均方根误差越小越好单位与 RUL 相同
MAPE绝对百分比误差越小越好寿命短时容易变大
Skewness残差偏度接近 0正偏说明低估较多
Kurtosis残差峰度接近 3大于 3 说明长尾异常

4.4 可视化:注意力分布、残差与三维曲线

训练完成后,除了指标,还要确认模型“看”到了什么。将TransformerEncoderLayerself_attn输出配合 forward hook 可以拿到注意力矩阵,画出每个时间步对最后预测的贡献。LSTM 隐状态也可以沿着时间维做 L2 范数可视化,观察模型在哪些循环附近发生状态切换。下面是用测试集画真实值与预测值对比和残差直方图的代码。

import matplotlib.pyplot as plt # 真实值与预测值对比 plt.figure(figsize=(8, 4)) plt.plot(y_test, label='True RUL', linewidth=1.5) plt.plot(y_pred, label='Pred RUL', linewidth=1.5) plt.xlabel('Sample Index') plt.ylabel('Remaining Useful Life') plt.legend() plt.show() # 残差分布直方图 residual = y_test - y_pred plt.figure(figsize=(6, 4)) plt.hist(residual, bins=30, edgecolor='white', alpha=0.7) plt.xlabel('Residual') plt.ylabel('Frequency') plt.show() # 真实与预测散点图(相关性分析) plt.figure(figsize=(6, 6)) plt.scatter(y_test, y_pred, alpha=0.6) plt.plot([0, max(y_test)], [0, max(y_test)], 'r--') plt.xlabel('True RUL') plt.ylabel('Predicted RUL') plt.show()

说明:散点图里点越贴近对角线说明预测越准,红色虚线是 y=x 参考线。真实值与预测值曲线上的尖峰位置,往往对应电池容量后期加速衰减的阶段,也是模型最容易低估 RUL 的位置。三维曲线可以用mpl_toolkits.mplot3d绘制,把真实 RUL、预测 RUL 和时间步放在三个轴上,观察误差是否随循环推进而放大。注意力矩阵本身可以通过nn.MultiheadAttentionattn_weights输出获得,在 GUI 里只要增加一个下拉框选择样本序号,就能把矩阵画成热力图,代码量不大但能显著提升结果的可解释性。

5. GUI 集成与热插拔推理:把模型封装成交付工具

模型训练好后,最终要交付给测试或产线人员使用。项目中的 GUI 采用 PySide6 实现,主窗口包含文件加载区、数据预览区、预测按钮和结果显示区。核心设计是“模型热插拔”:界面不负责训练,只负责加载best_model.pth权重文件,读取新的电池数据后,用训练时保存的 scaler 做标准化,再交给 Transformer-LSTM 推理。这样更换模型权重或评估不同电池数据时,不需要重新启动程序。

5.1 热插拔模型加载与自动标准化

加载模型时有两个易错点:一是必须用和训练时相同的模型结构初始化,直接torch.load('best_model.pth')只能拿到权重字典;二是 scaler 必须一并保存,否则新的数据无法映射到同一尺度。项目里把 scaler 也存成.pkl文件,GUI 启动时同时加载模型和 scaler。

from PySide6.QtWidgets import QMainWindow, QFileDialog, QMessageBox from PySide6.QtCore import Qt class RULPredictorGUI(QMainWindow): def __init__(self, model_path, scaler_path): super().__init__() self.model = TransformerLSTM(n_features=1) self.model.load_state_dict(torch.load(model_path, map_location='cpu')) self.model.eval() import joblib self.scaler = joblib.load(scaler_path) def predict_capacity_sequence(self, values): # 输入为长度超过 window_size 的容量数组 last_window = values[-window_size:] last_window = self.scaler.transform(last_window.reshape(1, -1)).reshape(1, window_size, 1) with torch.no_grad(): pred = self.model(torch.tensor(last_window, dtype=torch.float32)) return pred.item()

参数说明:map_location='cpu'避免模型训练在 GPU、部署在无 GPU 环境时出现设备不匹配;scaler_path指向训练阶段保存的 MinMaxScaler。代码里的values可以直接来自 GUI 导入的 CSV,last_window取最后 30 个容量值,保证推理使用的上下文与训练时一致。

5.2 画布刷新与结果动态展示

为了不让用户点一次预测就卡界面,预测逻辑放在线程池中运行,主线程只负责把返回的样本序号和预测值追加到matplotlib画布。GUI 中每一行数据显示“当前循环数、真实 RUL、预测 RUL、绝对误差”,超过阈值会标红,配合残差曲线一起刷新。这样即使是 40 节电池的批量评估,界面也不会失去响应。

如果后续接入实时传感器流,只需要把predict_capacity_sequence的输入替换成滑动缓存,再在数据源回调里调用update_plot,这套 GUI 的推理链路不需要改动。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 7:20:26

AI专著生成工具:学术写作效率革命与技术解析

1. 学术写作的范式革命&#xff1a;AI专著生成工具深度解析在传统学术出版领域&#xff0c;撰写一部专业专著通常需要耗费学者6-18个月的时间周期。根据2023年Elsevier发布的学术生产力报告&#xff0c;85%的受访教授表示专著写作过程中存在"资料整理耗时"、"结…

作者头像 李华
网站建设 2026/9/18 7:20:24

大数据环境下GDPR合规自动化实践与架构设计

1. 项目背景与核心挑战在欧洲市场开展业务的企业&#xff0c;只要涉及欧盟公民数据处理&#xff0c;就必须面对GDPR&#xff08;《通用数据保护条例》&#xff09;的合规要求。我在为某跨国电商平台实施数据合规审计时发现&#xff0c;传统的人工检查方式面对TB级用户行为数据时…

作者头像 李华
网站建设 2026/9/18 7:18:43

满意度高却任务失败?TaoToken Key 拆开 LLM judge 与模拟用户

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 7:17:07

读服务与写服务彻底分离的微服务架构落地

读服务与写服务彻底分离的微服务架构落地在电商大促的商品中心与交易架构演进过程中&#xff0c;许多技术团队在早期都维护着一个名为 item-service&#xff08;商品服务&#xff09;的巨型微服务&#xff1a; 商家在后台编辑商品标题、上传图片、修改库存与价格&#xff08;核…

作者头像 李华