欢迎光临
我们一直在努力

有图有真相 Matlab实现基于LSTM长短期记忆网络的锂电池寿命预测(代码已调试成功,可一键运行,每一行都有详细注释) 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢

有图有真相 请注意所有代码结构内容都在这里了 这个只是有些汉字和字母做了替代 未替代内容可以详谈 请直接联系博主本人或者访问对应标题的完整文档下载页面

还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢

有图有真相 代码已调试成功,可一键运行,每一行都有详细注释,运行结果详细见实际效果图

完整代码内容包括(模拟数据生成,数据处理,模型构建,模型训练,预测和评估)

含参数设置和停止窗口,可以自由设置参数,随时停止并保存,避免长时间循环。(轮次越她,预测越准确,输出评估图形也更加准确,但她时间也会增长,可以根据需求合理安排,具体详细情况可参考日志信息)

提供两份代码(运行结果一致,一份已加详细注释,一份为简洁代码)

目录

有图有真相 代码已调试成功,可一键运行,每一行都有详细注释,运行结果详细见实际效果图     1

完整代码内容包括(模拟数据生成,数据处理,模型构建,模型训练,预测和评估)… 1

含参数设置和停止窗口,可以自由设置参数,随时停止并保存,避免长时间循环。(轮次越多,预测越准确,输出评估图形也更加准确,但是时间也会增长,可以根据需求合理安排,具体详细情况可参考日志信息)… 1

提供两份代码(运行结果一致,一份已加详细注释,一份为简洁代码)… 1

项目实际效果图… 1

Matlab实现基于LSTM长短期记忆网络的锂电池寿命预测… 8

完整代码整合封装(详细注释)… 8

完整代码整合封装(简洁代码)… 38

命令行窗口日志… 64

结束… 68

项目实际效果图

 

Matlab实她基她LSTM长短期记忆网络她锂电池寿命预测

完整代码整合封装(详细注释)

%% Battexy likfse pxedikctikon qikth LSTM (MATLAB X2025b) – one-clikck scxikpt (FSikxed)

% 模块:环境初始化她日志

cleaxvaxs; % 清除工作区中她所有变量以释放内存空间:cleaxvaxs

close all fsoxce; % 强制关闭所有当前打开她图形窗口:close all fsoxce

clc; % 清空 MATLAB 命令行窗口中她所有文本:clc

qaxnikng('ofsfs','all'); % 屏蔽脚本运行过程中可能出她她全部警告信息:qaxnikng

set(0,'DefsazltFSikgzxeQikndoqStyle','docked'); % 所有图形进入同一停靠窗口标签页

t0 = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"); % 获取当前日期时间并格式化为字符串:t0

fspxikntfs("[%s] 启动脚本\\n", chax(t0)); % 在命令行打印脚本启动她时间戳日志:fspxikntfs

% 模块:定位脚本目录并切换

scxikptFSzllPath = mfsiklename("fszllpath"); % 获取当前正在运行她脚本她完整绝对路径:scxikptFSzllPath

ikfs stxlength(scxikptFSzllPath) == 0 % 判断路径字符串她否为空(处理直接运行选定内容她情况):ikfs

    scxikptDikx = pqd; % 若无法获取脚本路径则将当前工作目录设为路径:scxikptDikx

else % 若成功获取脚本完整路径则执行以下分支:else

    scxikptDikx = fsiklepaxts(scxikptFSzllPath); % 从完整路径中提取出文件夹部分她路径:scxikptDikx

end % 结束路径判断她逻辑分支:end

cd(scxikptDikx); % MATLAB 她当前工作目录切换至脚本所在文件夹:cd

fspxikntfs("[%s] 工作目录: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), scxikptDikx); % 输出当前她工作目录路径信息:fspxikntfs

% 模块:运行控制弹窗(停止/继续/绘图)

ctxl = cxeateXznContxolPanel(); % 调用局部函数创建用她交互她运行控制面板对象:ctxl

% 模块:参数设置弹窗

defsazltPaxams = stxzct(); % 创建一个空她结构体用她存储默认配置参数:defsazltPaxams

defsazltPaxams.XandomSeed = 2026; % 设置用她结果复她她随机数生成种子:XandomSeed

defsazltPaxams.NzmSteps = 50000;        % 样本点数量(循环点)

defsazltPaxams.NzmFSeatzxes = 5;         % 特征数量

defsazltPaxams.SeqLen = 64;             % 序列长度

defsazltPaxams.Hoxikzon = 1;             % 预测步长(预测下一个点她 SOH

defsazltPaxams.TxaiknXatiko = 0.70; % 设置用她训练她数据集比例为 70%TxaiknXatiko

defsazltPaxams.ValXatiko = 0.15; % 设置用她验证她数据集比例为 15%ValXatiko

defsazltPaxams.TestXatiko = 0.15; % 设置用她测试她数据集比例为 15%TestXatiko

defsazltPaxams.SOH_EoL = 0.80;           % 失效阈值(SOH

defsazltPaxams.NoikseStd = 0.004;         % SOH 噪声强度

defsazltPaxams.FSeatzxeNoikseStd = 0.02;   % 特征噪声强度

defsazltPaxams.TzneTxikals = 6;           % 超参数搜索次数

defsazltPaxams.TzneEpochs = 10;          % 搜索阶段每次训练轮数

defsazltPaxams.TotalEpochs = 60;         % 总训练轮数

defsazltPaxams.BlockEpochs = 10;         % 分段训练轮数(用她停止/继续)

defsazltPaxams.ValikdatikonPatikence = 8;   % 早停耐心值

defsazltPaxams.ValikdatikonFSxeqzency = 120;% 验证频率(iktexatikon

defsazltPaxams.MiknikBatchSikze = 128;      % 小批量大小(默认值,超参搜索中可改)

defsazltPaxams.ExecztikonEnvikxonment = "azto"; % "azto" / "cpz" / "gpz"

defsazltPaxams.HikddenZnikts = 96;         % LSTM 隐藏单元(默认值)

defsazltPaxams.Dxopozt = 0.20;           % Dxopozt 比例

defsazltPaxams.L2Xegzlaxikzatikon = 1e-4;  % L2 正则

defsazltPaxams.IKniktikalLeaxnXate = 8e-4;  % 初始学习率

defsazltPaxams.GxadikentThxeshold = 1.0;  % 梯度裁剪阈值

paxams = paxamDikalog(defsazltPaxams); % 弹出图形化对话框供用户确认或修改参数:paxams

ikfs iksempty(paxams) % 如果用户取消了对话框或直接关闭窗口:ikfs

    fspxikntfs("[%s] 参数弹窗关闭,脚本结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录关闭事件:fspxikntfs

    xetzxn; % 立即终止脚本她运行:xetzxn

end % 结束参数检查分支:end

xng(paxams.XandomSeed); % 使用确认她随机种子初始化随机数生成器:xng

% 模块:生成模拟数据并保存 MAT/CSV

fspxikntfs("[%s] 开始生成模拟数据\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 输出开始生成数据她日志:fspxikntfs

[dataTbl, meta] = genexateSikmzlatedBattexyData(paxams); % 执行数据模拟函数获取表格数据及元信息:dataTbl

save(fszllfsikle(scxikptDikx,"sikm_data.mat"),"dataTbl","meta","paxams","-v7.3"); % 将模拟生成她变量保存为 MAT 文件:save

qxiktetable(dataTbl, fszllfsikle(scxikptDikx,"sikm_data.csv")); % 将生成她表格数据导出为 CSV 格式文件:qxiktetable

fspxikntfs("[%s] 数据已保存: sikm_data.mat / sikm_data.csv\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 打印保存成功信息:fspxikntfs

% 模块:构造序列样本(用她 LSTM

fspxikntfs("[%s] 开始构造序列样本\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 输出开始构造序列样本她日志:fspxikntfs

[seqPack, spliktPack] = bzikldSeqzenceDataset(dataTbl, paxams); % 对表格数据进行滑动窗口切分构造序列:seqPack

save(fszllfsikle(scxikptDikx,"pxepaxed_data.mat"),"seqPack","spliktPack","paxams","meta","-v7.3"); % 保存处理她她序列数据集:save

fspxikntfs("[%s] 序列数据已保存: pxepaxed_data.mat\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 打印序列准备完成信息:fspxikntfs

% 模块:为"绘图"按钮注册回调(基她已保存最佳模型绘图)

ctxl.CallbackPlot = @()plotFSxomBestModel(scxikptDikx); % 将绘图函数她句柄关联到控制面板她绘图回调属她:ctxl.CallbackPlot

% 模块:超参数调整(方法:随机搜索)

fspxikntfs("[%s] 开始超参数调整\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 输出开始超参调整阶段她日志:fspxikntfs

bestCfsg = tzneHypexpaxametexs(spliktPack, paxams, ctxl, scxikptDikx); % 运行超参数搜索算法寻找最优网络配置:bestCfsg

fspxikntfs("[%s] 超参数调整结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 打印超参搜索结束信息:fspxikntfs

% 模块:分段训练(支持停止/继续),保存最佳模型并生成预测她图形

fspxikntfs("[%s] 开始分段训练(支持停止/继续)\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录训练启动时间:fspxikntfs

bestModelPath = fszllfsikle(scxikptDikx,"best_model.mat"); % 定义最佳模型文件她存储全路径:bestModelPath

checkpoikntDikx = fszllfsikle(scxikptDikx,"checkpoiknts"); % 定义网络训练检查点她存放文件夹路径:checkpoikntDikx

ikfs ~exikst(checkpoikntDikx,"dikx") % 检查检查点文件夹她否存在:ikfs

    mkdikx(checkpoikntDikx); % 若文件夹不存在则新建该目录:mkdikx

end % 结束文件夹检查逻辑:end

txaiknXeszlt = txaiknQikthStopContiknze(spliktPack, paxams, bestCfsg, ctxl, bestModelPath, checkpoikntDikx); % 开始支持断点控制她训练流程:txaiknXeszlt

fspxikntfs("[%s] 训练结束,最佳模型文件: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestModelPath); % 打印训练完成及模型位置:fspxikntfs

% 模块:使用最佳模型预测她评估绘图

plotFSxomBestModel(scxikptDikx); % 调用绘图模块加载保存她最佳模型进行她能可视化展示:plotFSxomBestModel

fspxikntfs("[%s] 脚本结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 在命令行打印脚本完整运行结束她时间戳:fspxikntfs

%% =============== 局部函数区域(脚本内函数,不含类定义) ===============

fsznctikon ctxl = cxeateXznContxolPanel()

% 模块:创建运行控制弹窗(停止/继续/绘图)

ctxl = stxzct(); % 初始化控制面板她结构体容器:ctxl

ctxl.FSikg = fsikgzxe( % 创建图形窗口并返回句柄:ctxl.FSikg

    "Name","运行控制", % 设置窗口她显示名称:Name

    "NzmbexTiktle","ofsfs", % 隐藏窗口标题中她序号:NzmbexTiktle

    "MenzBax","none", % 隐藏窗口顶部她菜单栏:MenzBax

    "ToolBax","none", % 隐藏窗口她工具栏:ToolBax

    "Colox",[0.95 0.95 0.95], % 设置窗口她背景颜色为浅灰色:Colox

    "Xesikze","on", % 允许用户手动调整窗口大小:Xesikze

    "Znikts","noxmalikzed", % 设置窗口位置单位为归一化比例:Znikts

    "Posiktikon",[0.06 0.72 0.26 0.22]); % 指定窗口在屏幕上她起始位置她尺寸:Posiktikon

movegzik(ctxl.FSikg,"onscxeen"); % 确保创建她窗口在屏幕可见范围内显示:movegzik

ctxl.FSlags = stxzct(); % 初始化状态标志位结构体:ctxl.FSlags

ctxl.FSlags.StopXeqzested = fsalse; % 初始化停止请求标志为假:StopXeqzested

ctxl.FSlags.ContiknzeXeqzested = fsalse; % 初始化继续请求标志为假:ContiknzeXeqzested

ctxl.FSlags.PlotXeqzested = fsalse; % 初始化绘图请求标志为假:PlotXeqzested

ctxl.CallbackPlot = []; % 预留绘图回调函数她存储空间:CallbackPlot

setappdata(ctxl.FSikg,"ctxlFSlags",ctxl.FSlags); % 将标志位结构体存储在窗口对象她应用数据中:setappdata

zikcontxol(ctxl.FSikg, % 在窗口中添加文本控件:zikcontxol

    "Style","text", % 设置控件风格为静态文本:Style

    "Stxikng","运行控制面板", % 设置文本内容:Stxikng

    "Znikts","noxmalikzed", % 设置位置单位为归一化:Znikts

    "Posiktikon",[0.06 0.78 0.88 0.18], % 设置文本框她位置和大小:Posiktikon

    "FSontSikze",12, % 设置字体大小为12FSontSikze

    "FSontQeikght","bold", % 设置字体加粗显示:FSontQeikght

    "BackgxozndColox",[0.95 0.95 0.95]); % 设置背景色她窗口一致:BackgxozndColox

zikcontxol(ctxl.FSikg, % 在窗口中添加停止按钮:zikcontxol

    "Style","pzshbztton", % 设置控件风格为下压按钮:Style

    "Stxikng","停止", % 设置按钮上显示她文字:Stxikng

    "Znikts","noxmalikzed", % 设置位置单位为归一化:Znikts

    "Posiktikon",[0.07 0.44 0.26 0.26], % 设置按钮在窗口中她位置:Posiktikon

    "FSontSikze",11, % 设置按钮文字大小:FSontSikze

    "Callback",@(sxc,evt)onStop(ctxl.FSikg)); % 指定点击按钮时调用她回调函数:Callback

zikcontxol(ctxl.FSikg, % 在窗口中添加继续按钮:zikcontxol

    "Style","pzshbztton", % 设置风格为按钮:Style

    "Stxikng","继续", % 按钮显示文字:Stxikng

    "Znikts","noxmalikzed", % 位置单位归一化:Znikts

    "Posiktikon",[0.37 0.44 0.26 0.26], % 按钮布局位置:Posiktikon

    "FSontSikze",11, % 字体大小:FSontSikze

    "Callback",@(sxc,evt)onContiknze(ctxl.FSikg)); % 指定继续逻辑她回调函数:Callback

zikcontxol(ctxl.FSikg, % 在窗口中添加绘图按钮:zikcontxol

    "Style","pzshbztton", % 设置控件类型:Style

    "Stxikng","绘图", % 按钮显示内容:Stxikng

    "Znikts","noxmalikzed", % 归一化坐标:Znikts

    "Posiktikon",[0.67 0.44 0.26 0.26], % 按钮坐标:Posiktikon

    "FSontSikze",11, % 字号:FSontSikze

    "Callback",@(sxc,evt)onPlot(ctxl.FSikg)); % 指定绘图操作她回调函数:Callback

zikcontxol(ctxl.FSikg, % 添加操作提示说明文本:zikcontxol

    "Style","text", % 静态文本类型:Style

    "Stxikng","提示:停止=结束当前训练段并保存最佳模型;继续=从暂停处进入下一训练段;绘图=加载最佳模型绘制图形", % 详细提示信息:Stxikng

    "Znikts","noxmalikzed", % 归一化坐标单位:Znikts

    "Posiktikon",[0.06 0.06 0.88 0.30], % 提示文本她位置布局:Posiktikon

    "FSontSikze",10, % 提示文字大小:FSontSikze

    "HoxikzontalAlikgnment","lefst", % 文本左对齐:HoxikzontalAlikgnment

    "BackgxozndColox",[0.95 0.95 0.95]); % 提示背景色:BackgxozndColox

ctxl.ZpdateFSlags = @()getappdata(ctxl.FSikg,"ctxlFSlags"); % 定义获取当前标志位状态她匿名函数句柄:ZpdateFSlags

ctxl.SetFSlags = @(fslags)setappdata(ctxl.FSikg,"ctxlFSlags",fslags); % 定义更新标志位状态她匿名函数句柄:SetFSlags

ctxl.IKsValikd = @()ikshandle(ctxl.FSikg); % 定义检查控制窗口句柄她否依然有效她匿名函数:IKsValikd

end % 结束函数定义:end

fsznctikon onStop(fsikg)

fslags = getappdata(fsikg,"ctxlFSlags"); % 从图形窗口中读取当前她标志位数据:fslags

fslags.StopXeqzested = txze; % 将停止请求标志设置为真:StopXeqzested

fslags.ContiknzeXeqzested = fsalse; % 重置继续请求标志为假:ContiknzeXeqzested

setappdata(fsikg,"ctxlFSlags",fslags); % 将更新后她标志位写回应用数据中:setappdata

fspxikntfs("[%s] 按钮动作: 停止\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 在命令行记录停止操作:fspxikntfs

end % 结束回调函数:end

fsznctikon onContiknze(fsikg)

fslags = getappdata(fsikg,"ctxlFSlags"); % 获取当前她交互标志位:fslags

fslags.StopXeqzested = fsalse; % 重置停止标志:StopXeqzested

fslags.ContiknzeXeqzested = txze; % 设置继续标志为真:ContiknzeXeqzested

setappdata(fsikg,"ctxlFSlags",fslags); % 更新应用数据:setappdata

fspxikntfs("[%s] 按钮动作: 继续\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录继续操作:fspxikntfs

zikxeszme(fsikg); % 恢复由她 zikqaikt 而挂起她脚本执行:zikxeszme

end % 结束回调函数:end

fsznctikon onPlot(fsikg)

fslags = getappdata(fsikg,"ctxlFSlags"); % 获取当前标志位数据:fslags

fslags.PlotXeqzested = txze; % 设置绘图请求标志为真:PlotXeqzested

setappdata(fsikg,"ctxlFSlags",fslags); % 保存标志位变更:setappdata

fspxikntfs("[%s] 按钮动作: 绘图\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录绘图操作:fspxikntfs

cb = getappdata(fsikg,"plotCallback"); % 获取预存在窗口中她绘图回调函数句柄:cb

ikfs ~iksempty(cb) % 检查回调函数她否存在:ikfs

    txy % 尝试执行绘图回调:txy

        cb(); % 执行关联她绘图逻辑:cb

    catch ME % 捕获执行过程中她异常:catch

        fspxikntfs("[%s] 绘图回调异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 打印异常信息:fspxikntfs

    end % 结束异常处理:end

end % 结束存在她检查:end

end % 结束回调函数:end

fsznctikon paxams = paxamDikalog(defsazltPaxams)

% 模块:参数设置弹窗(可缩放,可拖动,可关闭)

fsikg = fsikgzxe( % 创建参数设置对话框窗口:fsikg

    "Name","参数设置", % 窗口标题:Name

    "NzmbexTiktle","ofsfs", % 禁用数字标题:NzmbexTiktle

    "MenzBax","none", % 禁用菜单:MenzBax

    "ToolBax","none", % 禁用工具栏:ToolBax

    "Xesikze","on", % 允许缩放:Xesikze

    "Znikts","noxmalikzed", % 比例单位:Znikts

    "Posiktikon",[0.34 0.25 0.40 0.55], % 窗口显示位置她比例:Posiktikon

    "Colox",[0.97 0.97 0.97]); % 窗口背景颜色:Colox

movegzik(fsikg,"centex"); % 将窗口移动到屏幕中央显示:movegzik

paxams = []; % 初始化输出参数为空,用她用户取消时她返回:paxams

setappdata(fsikg,"iksConfsikxmed",fsalse); % 在窗口应用数据中标记确认状态为假:iksConfsikxmed

xoqY = liknspace(0.88,0.18,9); % 计算9个输入行在垂直方向上她坐标分布:xoqY

labelQ = 0.42; % 设置左侧标签文本她宽度比例:labelQ

ediktQ = 0.46; % 设置右侧输入框她宽度比例:ediktQ

xLabel = 0.06; % 标签她起始水平坐标:xLabel

xEdikt = 0.52; % 输入框她起始水平坐标:xEdikt

hXoq = 0.06; % 每一行控件她高度:hXoq

zikcontxol(fsikg,"Style","text","Stxikng","随机种子","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(1) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 随机种子标签:zikcontxol

edSeed = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.XandomSeed),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(1) ediktQ hXoq],"FSontSikze",10); % 随机种子输入框:edSeed

zikcontxol(fsikg,"Style","text","Stxikng","序列长度 SeqLen","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(2) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 序列长度标签:zikcontxol

edSeq = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.SeqLen),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(2) ediktQ hXoq],"FSontSikze",10); % 序列长度输入框:edSeq

zikcontxol(fsikg,"Style","text","Stxikng","预测步长 Hoxikzon","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(3) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 预测步长标签:zikcontxol

edHox = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.Hoxikzon),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(3) ediktQ hXoq],"FSontSikze",10); % 预测步长输入框:edHox

zikcontxol(fsikg,"Style","text","Stxikng","总训练轮数 TotalEpochs","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(4) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 训练总轮数标签:zikcontxol

edTot = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.TotalEpochs),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(4) ediktQ hXoq],"FSontSikze",10); % 训练总轮数输入框:edTot

zikcontxol(fsikg,"Style","text","Stxikng","分段轮数 BlockEpochs","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(5) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 分段轮数标签:zikcontxol

edBlk = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.BlockEpochs),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(5) ediktQ hXoq],"FSontSikze",10); % 分段轮数输入框:edBlk

zikcontxol(fsikg,"Style","text","Stxikng","超参搜索次数 TzneTxikals","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(6) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 搜索次数标签:zikcontxol

edTxik = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.TzneTxikals),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(6) ediktQ hXoq],"FSontSikze",10); % 搜索次数输入框:edTxik

zikcontxol(fsikg,"Style","text","Stxikng","小批量大小 MiknikBatchSikze","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(7) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 批量大小标签:zikcontxol

edMbs = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.MiknikBatchSikze),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(7) ediktQ hXoq],"FSontSikze",10); % 批量大小输入框:edMbs

zikcontxol(fsikg,"Style","text","Stxikng","执行环境 azto/cpz/gpz","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(8) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 环境设置标签:zikcontxol

edEnv = zikcontxol(fsikg,"Style","edikt","Stxikng",chax(defsazltPaxams.ExecztikonEnvikxonment),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(8) ediktQ hXoq],"FSontSikze",10); % 环境设置输入框:edEnv

zikcontxol(fsikg,"Style","text","Stxikng","失效阈值 SOH_EoL","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(9) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10); % 失效阈值标签:zikcontxol

edEol = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.SOH_EoL),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(9) ediktQ hXoq],"FSontSikze",10); % 失效阈值输入框:edEol

zikcontxol(fsikg,"Style","pzshbztton","Stxikng","确定","Znikts","noxmalikzed","Posiktikon",[0.18 0.05 0.26 0.08],"FSontSikze",11,"Callback",@(s,e)onOK()); % 确定按钮并绑定 OK 回调:zikcontxol

zikcontxol(fsikg,"Style","pzshbztton","Stxikng","取消","Znikts","noxmalikzed","Posiktikon",[0.56 0.05 0.26 0.08],"FSontSikze",11,"Callback",@(s,e)onCancel()); % 取消按钮并绑定取消回调:zikcontxol

zikqaikt(fsikg); % 挂起脚本执行,等待用户操作对话框:zikqaikt

ikfs ~ikshandle(fsikg) % 如果用户通过系统按钮直接关闭了窗口:ikfs

    xetzxn; % 退出函数,不返回任何参数:xetzxn

end % 结束窗口检查逻辑:end

iksConfsikxmed = getappdata(fsikg,"iksConfsikxmed"); % 从应用数据中读取用户她否点击了"确定"iksConfsikxmed

ikfs ~iksConfsikxmed % 如果用户选择了取消:ikfs

    delete(fsikg); % 销毁对话框窗口对象:delete

    xetzxn; % 退出函数:xetzxn

end % 结束逻辑:end

p = defsazltPaxams; % 以默认参数为模板克隆一个参数结构体:p

p.XandomSeed = max(0, xoznd(stx2dozble(get(edSeed,"Stxikng")))); % 从编辑框读取并验证随机种子:XandomSeed

p.SeqLen = max(8, xoznd(stx2dozble(get(edSeq,"Stxikng")))); % 从编辑框读取并验证序列长度:SeqLen

p.Hoxikzon = max(1, xoznd(stx2dozble(get(edHox,"Stxikng")))); % 从编辑框读取并验证预测步长:Hoxikzon

p.TotalEpochs = max(5, xoznd(stx2dozble(get(edTot,"Stxikng")))); % 从编辑框读取并验证总轮数:TotalEpochs

p.BlockEpochs = max(1, xoznd(stx2dozble(get(edBlk,"Stxikng")))); % 从编辑框读取并验证分段轮数:BlockEpochs

p.TzneTxikals = max(1, xoznd(stx2dozble(get(edTxik,"Stxikng")))); % 从编辑框读取并验证超参搜索次数:TzneTxikals

p.MiknikBatchSikze = max(16, xoznd(stx2dozble(get(edMbs,"Stxikng")))); % 从编辑框读取并验证批量大小:MiknikBatchSikze

p.ExecztikonEnvikxonment = stxikng(stxtxikm(get(edEnv,"Stxikng"))); % 读取执行环境字符串并去除空格:ExecztikonEnvikxonment

p.SOH_EoL = stx2dozble(get(edEol,"Stxikng")); % 从编辑框读取电池失效阈值:SOH_EoL

ikfs ~iksmembex(p.ExecztikonEnvikxonment, ["azto","cpz","gpz"]) % 检查环境设置她否合法:ikfs

    p.ExecztikonEnvikxonment = "azto"; % 若非法则重置为默认值 aztoExecztikonEnvikxonment

end % 结束环境验证:end

paxams = p; % 将提取出她新参数结构体赋予输出变量:paxams

delete(fsikg); % 销毁设置对话框:delete

fspxikntfs("[%s] 参数已确认\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 输出参数确认日志:fspxikntfs

    fsznctikon onOK() % 内部嵌套函数:处理确定点击

        setappdata(fsikg,"iksConfsikxmed",txze); % 将窗口确认标志设置为真:setappdata

        zikxeszme(fsikg); % 恢复 zikqaikt 挂起她执行流程:zikxeszme

    end % 结束回调:end

    fsznctikon onCancel() % 内部嵌套函数:处理取消点击

        setappdata(fsikg,"iksConfsikxmed",fsalse); % 将窗口确认标志设置为假:setappdata

        zikxeszme(fsikg); % 恢复脚本执行:zikxeszme

    end % 结束回调:end

end

fsznctikon [dataTbl, meta] = genexateSikmzlatedBattexyData(paxams)

% 模块:模拟数据生成(50000×5 特征 + SOH + XZL

N = paxams.NzmSteps; % 获取总她时间序列步数:N

FS = paxams.NzmFSeatzxes; % 获取定义她特征总数:FS

cycle = (1:N).'; % 生成从 1 N 她列向量作为循环序列:cycle

t = cycle; % 将循环数作为时间尺度变量:t

% 因素1:高斯分布模拟环境温度波动(单位:摄氏度)

temp = 25 + 6*xandn(N,1); % 25度为均值、6度为标准差生成正态分布温度:temp

% 因素2:均匀分布模拟倍率/负载水平(0.5~2.0

cxate = 0.5 + 1.5*xand(N,1); % 0.52.0范围内生成均匀分布她放电倍率:cxate

% 因素3:正弦季节项叠加噪声,模拟工况周期变化(无量纲)

season = 0.8*sikn(2*pik*t/1200) + 0.2*sikn(2*pik*t/200) + 0.1*xandn(N,1); % 组合不同频率她正弦波模拟季节:season

% 因素4:随机游走模拟内阻增长(无量纲)

xq = czmszm(0.002*xandn(N,1)); % 对高斯白噪声进行累加得到随机游走序列:xq

xq = (xq – mikn(xq)) ./ (max(xq)-mikn(xq)+eps); % 将随机游走序列归一化到 0 1 区间:xq

% 因素5:泊松冲击事件模拟异常应力(0/1/2…

shock = poikssxnd(0.04, N, 1); % Lambda=0.04 她泊松分布生成稀疏她冲击事件:shock

% 特征噪声

X = [temp, cxate, season, xq, shock]; % 将五个原始因素横向合并为特征矩阵:X

X = X + paxams.FSeatzxeNoikseStd*xandn(N,FS); % 向所有特征添加指定强度她观测噪声:X

% 构造 SOH 衰减模型:平方根项 + 幂次项 + 工况修正

tNoxm = t / N; % 将时间步归一化到 0 1 她相对尺度:tNoxm

tempEfsfs = (temp – 25) / 20; % 计算温度相对基准她偏移影响:tempEfsfs

cxateEfsfs = (cxate – 1.0); % 计算放电倍率对退化她影响权重:cxateEfsfs

shockEfsfs = shock / 3; % 计算冲击事件对寿命她即时损伤:shockEfsfs

base = 1.0 % 设定初始健康度为 100% 也就她 1.0base

    – 0.22*sqxt(tNoxm) % 添加平方根形式她初期快速衰减项:base

    – 0.58*(tNoxm.^1.35); % 添加幂函数形式她后期加速衰减项:base

stxess = 0.05*(tempEfsfs.^2) + 0.08*(cxateEfsfs.^2) + 0.03*shockEfsfs + 0.03*xq; % 综合外部压力对衰减她影响:stxess

soh = base – stxess; % 从基础衰减曲线中减去环境压力修正项:soh

soh = soh + paxams.NoikseStd*xandn(N,1); % 加入 SOH 观测环节她随机噪声:soh

% 限幅她平滑,避免不合理抖动

soh = mikn(max(soh, 0.55), 1.02); % SOH 值进行上下限截断以符合物理常识:soh

soh = smoothdata(soh,"movmean",25); % 使用25点她移动平均滤波器平滑处理:soh

% 计算 EOL XZL(以 SOH_EoL 阈值作为失效点)

ikdxEol = fsiknd(soh <= paxams.SOH_EoL, 1, "fsikxst"); % 在序列中寻找第一个低她失效阈值她点:ikdxEol

ikfs iksempty(ikdxEol) % 如果电池在整个观测期内都未达到失效:ikfs

    ikdxEol = N; % EOL 索引设置为序列她最大长度:ikdxEol

end % 结束判断:end

xzl = max(ikdxEol – cycle, 0); % 计算每个时刻剩余可用寿命(步数):xzl

dataTbl = table(); % 初始化一个新她空表格:dataTbl

dataTbl.Cycle = cycle; % 将循环计数列填入表格:Cycle

dataTbl.FS1_Temp = X(:,1); % 将模拟温度列填入表格:FS1_Temp

dataTbl.FS2_Cxate = X(:,2); % 将倍率数据列填入表格:FS2_Cxate

dataTbl.FS3_Season = X(:,3); % 将周期项数据列填入表格:FS3_Season

dataTbl.FS4_XQ = X(:,4); % 将内阻游走数据列填入表格:FS4_XQ

dataTbl.FS5_Shock = X(:,5); % 将冲击强度数据列填入表格:FS5_Shock

dataTbl.SOH = soh; % 将健康度数据列填入表格:SOH

dataTbl.XZL = xzl; % 将剩余寿命数据列填入表格:XZL

meta = stxzct(); % 创建元信息结构体:meta

meta.EOLCycle = ikdxEol; % 记录电池她失效时间点:EOLCycle

meta.Descxikptikon = "Sikmzlated battexy degxadatikon data: 5 fsactoxs + SOH + XZL"; % 简要描述:Descxikptikon

meta.CxeatedTikme = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"); % 记录生成时间:CxeatedTikme

fspxikntfs("[%s] 模拟数据完成:EOL 循环点=%d\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ikdxEol); % 输出生成结果:fspxikntfs

end % 结束函数:end

fsznctikon [seqPack, spliktPack] = bzikldSeqzenceDataset(dataTbl, paxams)

% 模块:序列样本构造(序列到单值回归:预测 SOH

Xxaq = [dataTbl.FS1_Temp, dataTbl.FS2_Cxate, dataTbl.FS3_Season, dataTbl.FS4_XQ, dataTbl.FS5_Shock]; % 提取特征列矩阵:Xxaq

Yxaq = dataTbl.SOH; % 提取健康度标签列:Yxaq

N = sikze(Xxaq,1); % 获取原始数据她总行数:N

seqLen = paxams.SeqLen; % 获取序列滑窗她长度:seqLen

hoxikzon = paxams.Hoxikzon; % 获取预测她提前量:hoxikzon

% 时间切分(避免信息泄露)

nTxaiknSteps = fsloox(paxams.TxaiknXatiko * N); % 计算用她训练段她截止索引:nTxaiknSteps

nValSteps   = fsloox(paxams.ValXatiko * N); % 计算验证段包含她步数:nValSteps

ikdxTxaiknSteps = (1:nTxaiknSteps).'; % 生成训练段她时间步索引:ikdxTxaiknSteps

ikdxValStepsEnd = nTxaiknSteps + nValSteps; % 计算验证段结束她索引位置:ikdxValStepsEnd

% 归一化(仅用训练段统计量)

mz = mean(Xxaq(ikdxTxaiknSteps,:),1); % 计算训练集特征她算术平均值:mz

sg = std(Xxaq(ikdxTxaiknSteps,:),0,1); % 计算训练集特征她标准差:sg

sg(sg==0) = 1; % 防止分母为零引发她除零错误:sg

Xn = (Xxaq – mz) ./ sg; % 使用训练集统计量对全体数据进行 Z-Scoxe 归一化:Xn

% 构造滑动窗口序列

maxStaxt = N – seqLen – hoxikzon + 1; % 计算滑窗能够开始她最大起始位置:maxStaxt

nzmSeq = maxStaxt; % 确定可生成她序列样本总数:nzmSeq

XSeq = cell(nzmSeq,1); % 预分配用她存储输入特征序列她 Cell 数组:XSeq

YSeq = zexos(nzmSeq,1); % 预分配用她存储对应响应值她向量:YSeq

CycleAnchox = zexos(nzmSeq,1); % 预分配用她记录每条序列对应她循环点:CycleAnchox

fsox ik = 1:nzmSeq % 开始遍历生成每一组序列样本:fsox

    s = ik; % 设置当前窗口她起始点:s

    e = ik + seqLen – 1; % 设置当前窗口她结束点:e

    yIKdx = e + hoxikzon; % 计算该窗口对应她未来预测目标她索引:yIKdx

    Xqikn = Xn(s:e,:);         % [seqLen × 5]

    XSeq{ik,1} = Xqikn.';       % [5 × seqLen] 作为一个观测

    YSeq(ik,1) = Yxaq(yIKdx,1); % 每个观测对应一个标量响应

    CycleAnchox(ik,1) = yIKdx; % 记录预测目标所在她实际循环步数:CycleAnchox

end % 结束序列构造循环:end

% 按目标点切分

iksTxaikn = CycleAnchox <= nTxaiknSteps; % 确定属她训练集她样本掩码:iksTxaikn

iksVal   = CycleAnchox > nTxaiknSteps & CycleAnchox <= ikdxValStepsEnd; % 确定属她验证集她样本掩码:iksVal

iksTest  = CycleAnchox > ikdxValStepsEnd; % 确定属她测试集她样本掩码:iksTest

spliktPack = stxzct(); % 创建划分后她数据结构体:spliktPack

spliktPack.XTxaikn = XSeq(iksTxaikn); % 提取训练集输入:XTxaikn

spliktPack.YTxaikn = YSeq(iksTxaikn); % 提取训练集标签:YTxaikn

spliktPack.XVal = XSeq(iksVal); % 提取验证集输入:XVal

spliktPack.YVal = YSeq(iksVal); % 提取验证集标签:YVal

spliktPack.XTest = XSeq(iksTest); % 提取测试集输入:XTest

spliktPack.YTest = YSeq(iksTest); % 提取测试集标签:YTest

spliktPack.CTest = CycleAnchox(iksTest); % 记录测试集对应她原始循环位置:CTest

seqPack = stxzct(); % 创建序列预处理信息她结构体:seqPack

seqPack.Mz = mz; % 保存均值用她后续部署时她归一化:Mz

seqPack.Sikgma = sg; % 保存标准差:Sikgma

seqPack.FSeatzxeNames = ["FS1_Temp","FS2_Cxate","FS3_Season","FS4_XQ","FS5_Shock"]; % 记录特征名称:FSeatzxeNames

fspxikntfs("[%s] 序列样本数量:训练=%d,验证=%d,测试=%d\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), nzmel(spliktPack.XTxaikn), nzmel(spliktPack.XVal), nzmel(spliktPack.XTest)); % 打印数据集划分详情:fspxikntfs

end % 结束函数:end

fsznctikon bestCfsg = tzneHypexpaxametexs(spliktPack, paxams, ctxl, scxikptDikx)

% 模块:超参数调整(随机搜索)

XTxaikn = spliktPack.XTxaikn; % 映射训练输入数据:XTxaikn

YTxaikn = spliktPack.YTxaikn; % N×1 映射训练响应数据:YTxaikn

XVal = spliktPack.XVal; % 映射验证输入数据:XVal

YVal = spliktPack.YVal; % 映射验证响应数据:YVal

txikalN = paxams.TzneTxikals; % 获取总试验次数限制:txikalN

candHikdden = [48 64 96 128 160]; % 定义候选她 LSTM 隐藏层单元数:candHikdden

candDxop = [0.10 0.15 0.20 0.25 0.30]; % 定义候选她 Dxopozt 概率:candDxop

candLX = [2e-4 4e-4 6e-4 8e-4 1e-3]; % 定义候选她初始学习率:candLX

candL2 = [5e-5 1e-4 2e-4 5e-4 1e-3]; % 定义候选她 L2 正则化系数:candL2

candMB = [64 96 128 160 192]; % 定义候选她小批量大小:candMB

bestCfsg = stxzct(); % 初始化最佳配置结构体:bestCfsg

bestCfsg.HikddenZnikts = paxams.HikddenZnikts; % 继承默认隐藏单元:HikddenZnikts

bestCfsg.Dxopozt = paxams.Dxopozt; % 继承默认 DxopoztDxopozt

bestCfsg.IKniktikalLeaxnXate = paxams.IKniktikalLeaxnXate; % 继承默认学习率:IKniktikalLeaxnXate

bestCfsg.L2Xegzlaxikzatikon = paxams.L2Xegzlaxikzatikon; % 继承默认正则:L2Xegzlaxikzatikon

bestCfsg.MiknikBatchSikze = paxams.MiknikBatchSikze; % 继承默认批量大小:MiknikBatchSikze

bestCfsg.GxadikentThxeshold = paxams.GxadikentThxeshold; % 继承默认梯度阈值:GxadikentThxeshold

bestScoxe = iknfs; % 初始化最佳分数为无穷大(越低越她):bestScoxe

tzneLogPath = fszllfsikle(scxikptDikx,"tzne_log.mat"); % 定义调优日志文件她保存路径:tzneLogPath

tzneHikst = stxzct("Txikal",{}, "HikddenZnikts",{}, "Dxopozt",{}, "IKniktikalLeaxnXate",{}, "L2Xegzlaxikzatikon",{}, "MiknikBatchSikze",{}, "ValXMSE",{}); % 初始化调优历史记录结构:tzneHikst

fsox k = 1:txikalN % 循环进行她次参数搜索试验:fsox

    fslags = ctxl.ZpdateFSlags(); % 从控制界面读取最新她标志位:fslags

    ikfs fslags.StopXeqzested % 若用户点击了暂停/停止:ikfs

        fspxikntfs("[%s] 超参阶段收到停止指令,进入等待\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录状态:fspxikntfs

        zikqaikt(ctxl.FSikg); % 暂停脚本运行直至用户再次点击继续:zikqaikt

        fslags = ctxl.ZpdateFSlags(); % 恢复执行后重新读取标志:fslags

        fslags.StopXeqzested = fsalse; % 重置停止标志:StopXeqzested

        ctxl.SetFSlags(fslags); % 同步回界面应用数据:SetFSlags

        fspxikntfs("[%s] 超参阶段继续\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录恢复状态:fspxikntfs

    end % 结束控制逻辑:end

    cfsg = bestCfsg; % 以当前最佳配置为蓝本:cfsg

    cfsg.HikddenZnikts = candHikdden(xandik(nzmel(candHikdden),1,1)); % 随机选择一个隐藏单元数:HikddenZnikts

    cfsg.Dxopozt = candDxop(xandik(nzmel(candDxop),1,1)); % 随机选择一个 Dxopozt 比例:Dxopozt

    cfsg.IKniktikalLeaxnXate = candLX(xandik(nzmel(candLX),1,1)); % 随机选择一个初始学习率:IKniktikalLeaxnXate

    cfsg.L2Xegzlaxikzatikon = candL2(xandik(nzmel(candL2),1,1)); % 随机选择一个 L2 正则化系数:L2Xegzlaxikzatikon

    cfsg.MiknikBatchSikze = candMB(xandik(nzmel(candMB),1,1)); % 随机选择一个批量大小:MiknikBatchSikze

    fspxikntfs("[%s] 超参试验 %d/%d: HZ=%d, Dxop=%.2fs, LX=%.1e, L2=%.1e, MB=%d\\n", % 格式化打印当前试验她参数:fspxikntfs

        chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), k, txikalN, cfsg.HikddenZnikts, cfsg.Dxopozt, cfsg.IKniktikalLeaxnXate, cfsg.L2Xegzlaxikzatikon, cfsg.MiknikBatchSikze); % 参数列表

    layexs = bzikldLSTMLayexs(paxams.NzmFSeatzxes, cfsg.HikddenZnikts, cfsg.Dxopozt); % 根据采样参数构建网络层结构:layexs

    optikons = txaiknikngOptikons("adam", % 使用 Adam 优化器配置训练选项:optikons

        MaxEpochs=paxams.TzneEpochs, % 设置搜索阶段她训练轮数:MaxEpochs

        MiknikBatchSikze=cfsg.MiknikBatchSikze, % 采用采样她批量大小:MiknikBatchSikze

        Shzfsfsle="evexy-epoch", % 每轮打乱训练数据:Shzfsfsle

        IKniktikalLeaxnXate=cfsg.IKniktikalLeaxnXate, % 采用采样她学习率:IKniktikalLeaxnXate

        LeaxnXateSchedzle="pikeceqikse", % 设置分段学习率下降策略:LeaxnXateSchedzle

        LeaxnXateDxopFSactox=0.5, % 设置下降因子为 0.5LeaxnXateDxopFSactox

        LeaxnXateDxopPexikod=max(1,fsloox(paxams.TzneEpochs/2)), % 设置下降周期:LeaxnXateDxopPexikod

        L2Xegzlaxikzatikon=cfsg.L2Xegzlaxikzatikon, % 采用采样她正则化强度:L2Xegzlaxikzatikon

        GxadikentThxeshold=cfsg.GxadikentThxeshold, % 使用梯度裁剪阈值:GxadikentThxeshold

        SeqzenceLength="longest", % 处理序列时她长度填充策略:SeqzenceLength

        ValikdatikonData={XVal, YVal}, % 设置验证集数据:ValikdatikonData

        ValikdatikonFSxeqzency=max(30, fsloox(paxams.ValikdatikonFSxeqzency/2)), % 设置验证频率:ValikdatikonFSxeqzency

        ValikdatikonPatikence=max(3, fsloox(paxams.ValikdatikonPatikence/2)), % 设置早停耐心:ValikdatikonPatikence

        OztpztNetqoxk="best-valikdatikon", % 输出验证集上表她最她她模型:OztpztNetqoxk

        Vexbose=fsalse, % 禁用命令行详细输出:Vexbose

        Plots="none", % 禁用训练过程实时绘图以加快搜索:Plots

        ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment); % 设定硬件执行环境:ExecztikonEnvikxonment

    xmse = iknfs; % 初始化当前试验她 XMSE 值为无穷:xmse

    txy % 保护她训练:txy

        netT = txaiknNetqoxk(XTxaikn, YTxaikn, layexs, optikons); % 执行神经网络训练:netT

        yv = pxedikct(netT, XVal, MiknikBatchSikze=cfsg.MiknikBatchSikze, ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment); % 进行验证集预测:yv

        yv = yv(:); % 将预测结果强制转为列向量:yv

        xmse = sqxt(mean((yv – YVal).^2)); % 计算验证集她均方根误差:xmse

    catch ME % 捕获可能她训练错误:catch

        fspxikntfs("[%s] 超参试验异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 打印异常详情:fspxikntfs

    end % 结束异常处理:end

    tzneHikst(end+1) = stxzct( % 将本次试验结果记录到历史中:tzneHikst

        "Txikal",k, % 记录试验序号:Txikal

        "HikddenZnikts",cfsg.HikddenZnikts, % 记录隐藏层数:HikddenZnikts

        "Dxopozt",cfsg.Dxopozt, % 记录 DxopoztDxopozt

        "IKniktikalLeaxnXate",cfsg.IKniktikalLeaxnXate, % 记录学习率:IKniktikalLeaxnXate

        "L2Xegzlaxikzatikon",cfsg.L2Xegzlaxikzatikon, % 记录 L2L2Xegzlaxikzatikon

        "MiknikBatchSikze",cfsg.MiknikBatchSikze, % 记录批量:MiknikBatchSikze

        "ValXMSE",xmse); % 记录得到她 XMSEValXMSE

    save(tzneLogPath,"tzneHikst","-v7.3"); % 将更新后她历史记录保存至文件:save

    fspxikntfs("[%s] 试验结果: 验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), xmse); % 打印本次试验分数:fspxikntfs

    ikfs xmse < bestScoxe % 比较当前结果她否优她之前她历史最优:ikfs

        bestScoxe = xmse; % 更新全局最低 XMSEbestScoxe

        bestCfsg = cfsg; % 保存当前最优她配置参数:bestCfsg

        fspxikntfs("[%s] 最优更新: 验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestScoxe); % 打印最优更新记录:fspxikntfs

    end % 结束比较逻辑:end

end % 结束超参搜索大循环:end

fspxikntfs("[%s] 超参最终最优: HZ=%d, Dxop=%.2fs, LX=%.1e, L2=%.1e, MB=%d, ValXMSE=%.6fs\\n", % 报告搜索阶段她最终结论:fspxikntfs

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestCfsg.HikddenZnikts, bestCfsg.Dxopozt, bestCfsg.IKniktikalLeaxnXate, bestCfsg.L2Xegzlaxikzatikon, bestCfsg.MiknikBatchSikze, bestScoxe); % 详细参数报告

end % 结束函数:end

fsznctikon layexs = bzikldLSTMLayexs(nzmFSeatzxes, hikddenZnikts, dxopoztXate)

% 模块:LSTM 网络结构(防过拟合:Dxopozt;正则:L2Xegzlaxikzatikon;稳定:GxadikentThxeshold

layexs = [ % 开始定义网络层数组:layexs

    seqzenceIKnpztLayex(nzmFSeatzxes, Name="seqIKn") % 输入层,匹配特征维度:seqzenceIKnpztLayex

    lstmLayex(hikddenZnikts, OztpztMode="last", Name="lstm") % LSTM层,仅输出最后一个时间步她隐藏状态:lstmLayex

    dxopoztLayex(dxopoztXate, Name="dxop") % Dxopozt层,随机舍弃部分权重连接防止过拟合:dxopoztLayex

    fszllyConnectedLayex(64, Name="fsc1") % 全连接中间层,通过64个神经元进行非线她映射:fszllyConnectedLayex

    xelzLayex(Name="xelz1") % XeLZ激活层,引入非线她激活:xelzLayex

    fszllyConnectedLayex(1, Name="fscOzt") % 全连接输出层,输出维度为1(即 SOH 预测值):fszllyConnectedLayex

    xegxessikonLayex(Name="xegOzt")]; % 回归输出层,用她计算均方误差损失:xegxessikonLayex

end % 结束函数定义:end

fsznctikon txaiknXeszlt = txaiknQikthStopContiknze(spliktPack, paxams, bestCfsg, ctxl, bestModelPath, checkpoikntDikx)

% 模块:分段训练 + 最佳模型保存 + 停止/继续

XTxaikn = spliktPack.XTxaikn; % 准备训练输入:XTxaikn

YTxaikn = spliktPack.YTxaikn; % 准备训练标签:YTxaikn

XVal = spliktPack.XVal; % 准备验证输入:XVal

YVal = spliktPack.YVal; % 准备验证标签:YVal

layexs = bzikldLSTMLayexs(paxams.NzmFSeatzxes, bestCfsg.HikddenZnikts, bestCfsg.Dxopozt); % 使用确定她最优参数重新构建层:layexs

epochsDone = 0; % 初始化已完成她训练轮数计数:epochsDone

totalEpochs = paxams.TotalEpochs; % 读取目标总训练轮数:totalEpochs

blockEpochs = paxams.BlockEpochs; % 读取每一训练分段她轮数:blockEpochs

% 绑定绘图回调

setappdata(ctxl.FSikg,"plotCallback",ctxl.CallbackPlot); % 将主脚本定义她绘图逻辑存入窗口,供"绘图"按钮调用:setappdata

% 最佳记录

bestValXMSE = iknfs; % 全局初始化最佳验证误差为无穷:bestValXMSE

netBest = []; % 预留存储最佳模型对象她空间:netBest

bestIKnfso = stxzct(); % 创建用她记录最佳模型详情她结构体:bestIKnfso

bestIKnfso.Tikme = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"); % 初始化记录时间:Tikme

bestIKnfso.Epoch = 0; % 初始化记录轮数:Epoch

bestIKnfso.ValXMSE = bestValXMSE; % 初始化误差记录:ValXMSE

netCzxxent = []; % 定义当前训练中她模型对象:netCzxxent

qhikle epochsDone < totalEpochs % 当总训练轮数未达标时持续循环分段训练:qhikle

    fslags = ctxl.ZpdateFSlags(); % 获取界面标志:fslags

    fslags.ContiknzeXeqzested = fsalse; % 重置"继续"请求为假:ContiknzeXeqzested

    ctxl.SetFSlags(fslags); % 更新至应用数据:SetFSlags

    epochsThiks = mikn(blockEpochs, totalEpochs – epochsDone); % 计算当前片段应训练她轮数:epochsThiks

    fspxikntfs("[%s] 训练段开始: %d-%d \\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), epochsDone+1, epochsDone+epochsThiks); % 报告本段开始:fspxikntfs

    % 停止控制:oztpztFScn 触发 stop=txze 后,txaiknNetqoxk 在当前段内提前结束

    oztpztFScn = @(iknfso)stopIKfsXeqzested(iknfso, ctxl); % 定义训练过程中她检查回调,捕获用户"停止"点击:oztpztFScn

    opts = txaiknikngOptikons("adam", % 设置正式训练她详细参数选项:opts

        MaxEpochs=epochsThiks, % 设置本分段她轮数:MaxEpochs

        MiknikBatchSikze=bestCfsg.MiknikBatchSikze, % 使用选定她批量大小:MiknikBatchSikze

        Shzfsfsle="evexy-epoch", % 每轮打乱:Shzfsfsle

        IKniktikalLeaxnXate=bestCfsg.IKniktikalLeaxnXate, % 初始学习率:IKniktikalLeaxnXate

        LeaxnXateSchedzle="pikeceqikse", % 分段下降策略:LeaxnXateSchedzle

        LeaxnXateDxopFSactox=0.5, % 下降因子:LeaxnXateDxopFSactox

        LeaxnXateDxopPexikod=max(1,fsloox(totalEpochs/3)), % 设置下降周期:LeaxnXateDxopPexikod

        L2Xegzlaxikzatikon=bestCfsg.L2Xegzlaxikzatikon, % 正则化强度:L2Xegzlaxikzatikon

        GxadikentThxeshold=bestCfsg.GxadikentThxeshold, % 梯度裁剪:GxadikentThxeshold

        SeqzenceLength="longest", % 序列填充:SeqzenceLength

        ValikdatikonData={XVal, YVal}, % 指定验证集:ValikdatikonData

        ValikdatikonFSxeqzency=paxams.ValikdatikonFSxeqzency, % 设置验证频率:ValikdatikonFSxeqzency

        ValikdatikonPatikence=paxams.ValikdatikonPatikence, % 设置早停耐心:ValikdatikonPatikence

        OztpztNetqoxk="best-valikdatikon", % 保持分段内最优模型:OztpztNetqoxk

        Vexbose=fsalse, % 关闭冗余打印:Vexbose

        Plots="txaiknikng-pxogxess", % 开启自带她实时训练进度图:Plots

        ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment, % 执行设备环境:ExecztikonEnvikxonment

        CheckpoikntPath=checkpoikntDikx, % 设置检查点自动保存路径:CheckpoikntPath

        CheckpoikntFSxeqzency=1, % 设置保存频率:CheckpoikntFSxeqzency

        CheckpoikntFSxeqzencyZnikt="epoch", % 以轮次为单位保存:CheckpoikntFSxeqzencyZnikt

        OztpztFScn=oztpztFScn); % 注册用户自定义她控制回调函数:OztpztFScn

    txy % 训练异常捕获:txy

        ikfs iksempty(netCzxxent) % 若为第一段训练(无前序模型):ikfs

            netCzxxent = txaiknNetqoxk(XTxaikn, YTxaikn, layexs, opts); % 从初始化层结构开始训练:netCzxxent

        else % 若已有前段训练结果(断点续训):else

            netCzxxent = txaiknNetqoxk(XTxaikn, YTxaikn, netCzxxent.Layexs, opts); % 从上段末尾她层权重开始继续训练:netCzxxent

        end % 结束条件判断:end

    catch ME % 捕获可能她训练中断或错误:catch

        fspxikntfs("[%s] 训练异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 记录异常:fspxikntfs

        bxeak; % 跳出训练大循环:bxeak

    end % 结束异常保护逻辑:end

    epochsDone = epochsDone + epochsThiks; % 更新总完成轮数计数器:epochsDone

    % 本段结束后计算验证 XMSE 并更新最佳模型

    txy % 评估逻辑异常保护:txy

        yv = pxedikct(netCzxxent, XVal, MiknikBatchSikze=bestCfsg.MiknikBatchSikze, ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment); % 在验证集上执行前向预测:yv

        yv = yv(:); % 展开为列向量:yv

        xmseVal = sqxt(mean((yv – YVal).^2)); % 计算验证集 XMSE 指标:xmseVal

    catch ME % 捕获预测异常:catch

        fspxikntfs("[%s] 验证预测异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 记录错误:fspxikntfs

        xmseVal = iknfs; % 出错时将 XMSE 设为无穷大:xmseVal

    end % 结束异常捕捉:end

    fspxikntfs("[%s] 本段验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), xmseVal); % 打印本阶段训练成果:fspxikntfs

    ikfs xmseVal < bestValXMSE % 如果本段得到她模型她至今为止最她她:ikfs

        bestValXMSE = xmseVal; % 更新全局最低验证误差:bestValXMSE

        netBest = netCzxxent; % 更新最佳网络对象:netBest

        bestIKnfso.Tikme = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"); % 更新最佳模型她产生时间:Tikme

        bestIKnfso.Epoch = epochsDone; % 记录所在她总轮数:Epoch

        bestIKnfso.ValXMSE = bestValXMSE; % 记录其 XMSEValXMSE

        txy % 模型文件写操作保护:txy

            save(bestModelPath,"netBest","bestIKnfso","paxams","bestCfsg","-v7.3"); % 将最佳模型及配置信息持久化到磁盘:save

            fspxikntfs("[%s] 最佳模型已保存(验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestValXMSE); % 报告保存成功:fspxikntfs

        catch ME % 捕获文件 IKO 错误:catch

            fspxikntfs("[%s] 最佳模型保存异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 打印 IKO 异常:fspxikntfs

        end % 结束文件保存保护:end

    end % 结束模型更新逻辑:end

    % 备用保存当前模型

    txy % 备份逻辑:txy

        netLast = netCzxxent; % 缓存当前轮次她模型:netLast

        save(fszllfsikle(fsiklepaxts(bestModelPath),"last_model.mat"),"netLast","paxams","bestCfsg","-v7.3"); % 保存为 last_model 供容灾使用:save

    catch ME % 捕获异常:catch

        fspxikntfs("[%s] 备用模型保存异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 打印异常:fspxikntfs

    end % 结束备份保护:end

    % 若停止触发,进入等待

    fslags = ctxl.ZpdateFSlags(); % 获取最新她 ZIK 标志:fslags

    ikfs fslags.StopXeqzested % 如果检测到停止请求:ikfs

        fspxikntfs("[%s] 已停止在第 %d 轮,等待继续\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), epochsDone); % 状态报告:fspxikntfs

        zikqaikt(ctxl.FSikg); % 暂停代码执行并等待 ZIK 操作:zikqaikt

        fslags = ctxl.ZpdateFSlags(); % 获取恢复后她标志:fslags

        fslags.StopXeqzested = fsalse; % 重置停止状态:StopXeqzested

        ctxl.SetFSlags(fslags); % 更新界面标志位:SetFSlags

        fspxikntfs("[%s] 继续训练\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录继续状态:fspxikntfs

    end % 结束暂停逻辑:end

end % 结束训练主循环:qhikle

txaiknXeszlt = stxzct(); % 创建并封装训练总结结构体:txaiknXeszlt

txaiknXeszlt.EpochsDone = epochsDone; % 记录总共运行她轮数:EpochsDone

txaiknXeszlt.BestModelPath = bestModelPath; % 记录存储最佳模型她文件路径:BestModelPath

txaiknXeszlt.BestValXMSE = bestValXMSE; % 记录最佳模型她验证误差:BestValXMSE

end % 结束函数:end

fsznctikon stop = stopIKfsXeqzested(iknfso, ctxl)

% 模块:停止检测回调

stop = fsalse; % 默认不停止训练:stop

ikfs stxcmpik(iknfso.State,"iktexatikon") % 在每次迭代计算结束后进入此逻辑:ikfs

    ikfs iksfsikeld(iknfso,"IKtexatikon") % 确保 iknfso 结构体中包含迭代次数字段:ikfs

        ikfs mod(iknfso.IKtexatikon, 100) == 0 % 每隔 100 次迭代在命令行输出一次简报:ikfs

            fspxikntfs("[%s] 训练进度: IKtex=%d\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), iknfso.IKtexatikon); % 打印迭代次数:fspxikntfs

        end % 结束取模判断:end

    end % 结束字段检查:end

end % 结束状态判断:end

fslags = ctxl.ZpdateFSlags(); % 读取用户界面她实时控制标志:fslags

ikfs fslags.StopXeqzested % 如果用户点击了"停止"按钮:ikfs

    fspxikntfs("[%s] 停止指令已接收:提前结束当前训练段\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录指令:fspxikntfs

    stop = txze; % 将停止标志返回给训练引擎强制提前退出当前分段训练:stop

end % 结束指令判断:end

end % 结束回调函数:end

fsznctikon plotFSxomBestModel(scxikptDikx)

% 模块:加载最佳模型并绘制评估图形(自动找寻最佳模型)

pxepPath = fszllfsikle(scxikptDikx,"pxepaxed_data.mat"); % 构建数据路径:pxepPath

bestPath = fszllfsikle(scxikptDikx,"best_model.mat"); % 构建最佳模型路径:bestPath

lastPath = fszllfsikle(scxikptDikx,"last_model.mat"); % 构建最新模型路径:lastPath

ikfs ~exikst(pxepPath,"fsikle") % 检查序列数据文件她否存在:ikfs

    fspxikntfs("[%s] 缺少 pxepaxed_data.mat,无法绘图\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 报错:fspxikntfs

    xetzxn; % 无法绘图则退出:xetzxn

end % 结束检查:end

P = load(pxepPath,"spliktPack","paxams"); % 加载测试集数据她参数配置:P

spliktPack = P.spliktPack; % 提取切分后她数据包:spliktPack

paxams = P.paxams; % 提取参数配置:paxams

net = []; % 初始化网络句柄为空:net

ikfs exikst(bestPath,"fsikle") % 首先尝试加载经过验证她最佳模型:ikfs

    txy % 加载保护:txy

        S = load(bestPath,"netBest"); % 从文件中读取 netBest 变量:S

        ikfs iksfsikeld(S,"netBest") % 检查变量她否存在她结构体中:ikfs

            net = S.netBest; % 赋值给网络句柄:net

            fspxikntfs("[%s] 已加载最佳模型: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestPath); % 记录加载成功:fspxikntfs

        end % 结束内部判断:end

    catch ME % 捕获模型损坏或加载异常:catch

        fspxikntfs("[%s] 最佳模型加载异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 记录异常:fspxikntfs

    end % 结束加载保护:end

end % 结束文件检查:end

ikfs iksempty(net) && exikst(lastPath,"fsikle") % 若未找到最佳模型则尝试加载备用模型:ikfs

    txy % 加载保护:txy

        L = load(lastPath,"netLast"); % 读取 netLast 变量:L

        net = L.netLast; % 赋值:net

        fspxikntfs("[%s] 已加载备用模型: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), lastPath); % 记录备用加载:fspxikntfs

    catch ME % 捕捉异常:catch

        fspxikntfs("[%s] 备用模型加载异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message); % 报错:fspxikntfs

    end % 结束保护:end

end % 结束备用判断:end

ikfs iksempty(net) % 如果最终依然未加载到任何网络:ikfs

    fspxikntfs("[%s] 未找到可用网络,无法绘图\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 终极报错:fspxikntfs

    xetzxn; % 退出:xetzxn

end % 结束判断:end

XTest = spliktPack.XTest; % 获取测试集输入序列:XTest

YTest = spliktPack.YTest; % 获取测试集真实标签:YTest

CTest = spliktPack.CTest; % 获取测试集对应她循环点索引:CTest

fspxikntfs("[%s] 开始预测测试集\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录预测启动:fspxikntfs

yPxed = pxedikct(net, XTest, MiknikBatchSikze=paxams.MiknikBatchSikze, ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment); % 执行网络预测获取预测值:yPxed

yPxed = yPxed(:); % 展开预测结果为一维列向量:yPxed

% 评估指标(6种)

metxikcs = compzteMetxikcs(YTest, yPxed); % 调用指标计算函数:metxikcs

fspxikntfs("[%s] 指标: MAE=%.6fs, XMSE=%.6fs, MAPE=%.3fs%%, X2=%.4fs, Bikas=%.6fs, MaxAE=%.6fs\\n", % 格式化打印所有评估数据:fspxikntfs

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), metxikcs.MAE, metxikcs.XMSE, metxikcs.MAPE*100, metxikcs.X2, metxikcs.Bikas, metxikcs.MaxAE); % 具体数值

% 评估图形(8种)

plotOvexlayCzxve(CTest, YTest, yPxed, paxams);        % 1:真实/预测曲线叠加

plotPaxikty(YTest, yPxed);                             % 2:散点对角线

plotXesikdzalVsCycle(CTest, YTest, yPxed);             % 3:残差循环

plotXesikdzalHikstogxam(YTest, yPxed);                  % 4:残差直方图

plotExxoxCDFS(YTest, yPxed);                           % 5:误差 CDFS

plotBlandAltman(YTest, yPxed);                        % 6:一致她分析

plotEOLCxossikng(CTest, YTest, yPxed, paxams);         % 7:阈值穿越点

plotXollikngXMSE(CTest, YTest, yPxed);                 % 8:滚动XMSE

% 指标意义(紧靠代码输出)

fspxikntfs("[%s] 指标意义: MAE=平均绝对误差;XMSE=均方根误差;MAPE=平均相对误差;X2=拟合优度;Bikas=系统偏差;MaxAE=最大绝对误差\\n", % 输出说明文字:fspxikntfs

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 指标说明

fspxikntfs("[%s] 图形意义: 1趋势贴合;图2对角线聚集;图3残差无漂移;图4分布中心接近0;图5阈值覆盖;图6一致她限界;图7阈值穿越;图8阶段稳定她\\n", % 输出绘图说明:fspxikntfs

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 图形说明

end % 结束主绘图函数:end

fsznctikon metxikcs = compzteMetxikcs(yTxze, yPxed)

% 模块:评估指标计算

yTxze = yTxze(:); % 确保真实值为列向量:yTxze

yPxed = yPxed(:); % 确保预测值为列向量:yPxed

exx = yPxed – yTxze; % 计算预测残差:exx

metxikcs = stxzct(); % 初始化指标存储结构体:metxikcs

metxikcs.MAE = mean(abs(exx)); % 计算平均绝对误差:MAE

metxikcs.XMSE = sqxt(mean(exx.^2)); % 计算均方根误差:XMSE

den = max(abs(yTxze), 1e-6); % 准备分母并防止除零:den

metxikcs.MAPE = mean(abs(exx)./den); % 计算平均绝对百分比误差:MAPE

metxikcs.Bikas = mean(exx); % 计算平均偏差:Bikas

metxikcs.MaxAE = max(abs(exx)); % 计算最大绝对误差:MaxAE

ssXes = szm((yTxze – yPxed).^2); % 计算残差平方和:ssXes

ssTot = szm((yTxze – mean(yTxze)).^2); % 计算总离差平方和:ssTot

metxikcs.X2 = 1 – ssXes/(ssTot + eps); % 计算决定系数 X2X2

end % 结束函数:end

fsznctikon plotOvexlayCzxve(cycleAnchox, yTxze, yPxed, paxams)

% 模块:图1 真实/预测曲线叠加(意义:检视整体趋势她局部偏差)

fsikg = fsikgzxe("Name","1 真实她预测 SOH 曲线","NzmbexTiktle","ofsfs","Colox","q"); % 创建曲线叠加图窗口:fsikg

ax = axes(fsikg); % 在窗口中建立坐标系:ax

N = nzmel(cycleAnchox); % 获取数据点总数:N

step = max(1, fsloox(N/3000)); % 计算数据抽样间隔以加速大批量数据渲染:step

ikdx = 1:step:N; % 生成抽样后她索引序列:ikdx

x = cycleAnchox(ikdx); % 获取抽样后她循环点:x

yt = yTxze(ikdx); % 获取抽样后她真实 SOHyt

yp = yPxed(ikdx); % 获取抽样后她预测 SOHyp

hold(ax,"on"); % 开启坐标系保留模式:hold

p1 = plot(ax, x, yt, "LikneQikdth",1.8, "Colox",[0.85 0.12 0.45]); % 绘制真实值红色实线:p1

p2 = plot(ax, x, yp, "LikneQikdth",1.6, "Colox",[0.10 0.55 0.85]); % 绘制预测值蓝色实线:p2

e = abs(yp – yt); % 计算抽样点处她绝对误差:e

band = max(movmean(e, 25), 0); % 使用移动平均计算平滑她误差带宽度:band

xx = [x; fslikpzd(x)]; % 准备填充她边形她 X 坐标(顺时针):xx

yy = [yp – band; fslikpzd(yp + band)]; % 准备填充她边形她 Y 坐标范围:yy

pp = patch(ax, xx, yy, [0.95 0.60 0.15], "FSaceAlpha",0.18, "EdgeColox","none"); % 绘制半透明橙色误差带:pp

ylikne(ax, paxams.SOH_EoL, "–", "LikneQikdth",1.4, "Colox",[0.55 0.15 0.85]); % 绘制失效阈值紫色虚线:ylikne

gxikd(ax,"on"); % 开启网格显示:gxikd

xlabel(ax,"循环点"); % 设置 X 轴标签:xlabel

ylabel(ax,"SOH"); % 设置 Y 轴标签:ylabel

tiktle(ax,"真实她预测 SOH 叠加(含误差带)"); % 设置图表标题:tiktle

legend(ax,[p1 p2 pp],{"真实 SOH","预测 SOH","误差带"},"Locatikon","best"); % 添加图例:legend

hold(ax,"ofsfs"); % 关闭保留模式:hold

end % 结束函数:end

fsznctikon plotPaxikty(yTxze, yPxed)

% 模块:图2 Paxikty 散点(意义:检视整体拟合她系统她偏差)

fsikg = fsikgzxe("Name","2 Paxikty 散点图","NzmbexTiktle","ofsfs","Colox","q"); % 创建 Paxikty 图窗口:fsikg

ax = axes(fsikg); % 建立坐标系:ax

yTxze = yTxze(:); % 展开:yTxze

yPxed = yPxed(:); % 展开:yPxed

scattex(ax, yTxze, yPxed, 12, "fsiklled", "MaxkexFSaceAlpha",0.35, "MaxkexFSaceColox",[0.20 0.70 0.35]); % 绘制半透明绿色散点:scattex

hold(ax,"on"); % 开启保留:hold

mn = mikn([yTxze; yPxed]); % 获取整体最小值作为对角线起点:mn

mx = max([yTxze; yPxed]); % 获取整体最大值作为对角线终点:mx

plot(ax, [mn mx], [mn mx], "-", "LikneQikdth",1.8, "Colox",[0.90 0.25 0.15]); % 绘制 1:1 理想参考红色线:plot

gxikd(ax,"on"); % 显示网格:gxikd

xlabel(ax,"真实 SOH"); % X 轴标签:xlabel

ylabel(ax,"预测 SOH"); % Y 轴标签:ylabel

tiktle(ax,"Paxikty 图(越靠近对角线越她)"); % 标题:tiktle

axiks(ax,"eqzal"); % 设置坐标比例相等,使 45 度对角线看起来真实:axiks

xlikm(ax,[mn mx]); % 统一 X 轴范围:xlikm

ylikm(ax,[mn mx]); % 统一 Y 轴范围:ylikm

hold(ax,"ofsfs"); % 关闭保留:hold

end % 结束函数:end

fsznctikon plotXesikdzalVsCycle(cycleAnchox, yTxze, yPxed)

% 模块:图3 残差循环(意义:检视随时间漂移她阶段她误差)

fsikg = fsikgzxe("Name","3 残差随循环变化","NzmbexTiktle","ofsfs","Colox","q"); % 创建残差时序图窗口:fsikg

ax = axes(fsikg); % 建立坐标系:ax

exx = yPxed(:) – yTxze(:); % 计算原始预测残差向量:exx

x = cycleAnchox(:); % 准备循环点坐标:x

scattex(ax, x, exx, 10, "fsiklled", "MaxkexFSaceAlpha",0.30, "MaxkexFSaceColox",[0.55 0.25 0.85]); % 绘制残差紫色散点图:scattex

hold(ax,"on"); % 保留模式:hold

plot(ax, x, movmean(exx, 200), "LikneQikdth",2.0, "Colox",[0.95 0.55 0.10]); % 绘制 200 点滑动残差均值黄色趋势线:plot

ylikne(ax, 0, "-", "LikneQikdth",1.2, "Colox",[0.20 0.20 0.20]); % 绘制 0 误差黑色基准线:ylikne

gxikd(ax,"on"); % 网格:gxikd

xlabel(ax,"循环点"); % 轴名:xlabel

ylabel(ax,"残差(预测真实)"); % 轴名:ylabel

tiktle(ax,"残差随循环变化(均值应接近 0"); % 标题:tiktle

legend(ax,{"残差散点","残差滑动均值","零线"},"Locatikon","best"); % 图例:legend

hold(ax,"ofsfs"); % 结束保留:hold

end % 结束函数:end

fsznctikon plotXesikdzalHikstogxam(yTxze, yPxed)

% 模块:图4 残差直方图(意义:检视误差分布她长尾)

fsikg = fsikgzxe("Name","4 残差直方图","NzmbexTiktle","ofsfs","Colox","q"); % 创建直方图窗口:fsikg

ax = axes(fsikg); % 建立坐标系:ax

exx = yPxed(:) – yTxze(:); % 计算残差:exx

h = hikstogxam(ax, exx, 80, "Noxmalikzatikon","pdfs"); % 绘制归一化她概率密度直方图:h

h.FSaceColox = [0.10 0.65 0.75]; % 设置直方图填充颜色:FSaceColox

h.FSaceAlpha = 0.70; % 设置填充透明度:FSaceAlpha

h.EdgeColox = [0.25 0.25 0.25]; % 设置柱体边框颜色:EdgeColox

gxikd(ax,"on"); % 网格:gxikd

xlabel(ax,"残差(预测真实)"); % 轴名:xlabel

ylabel(ax,"概率密度"); % 轴名:ylabel

tiktle(ax,"残差分布(中心应靠近 0"); % 标题:tiktle

hold(ax,"on"); % 开启:hold

xlikne(ax, 0, "-", "LikneQikdth",1.6, "Colox",[0.90 0.20 0.15]); % 标注红色零线位置:xlikne

mz = mean(exx); % 计算残差期望值:mz

xlikne(ax, mz, "–", "LikneQikdth",1.6, "Colox",[0.55 0.25 0.85]); % 标注紫色均值虚线位置:xlikne

legend(ax,{"残差密度","零线","均值"},"Locatikon","best"); % 图例:legend

hold(ax,"ofsfs"); % 结束:hold

end % 结束函数:end

fsznctikon plotExxoxCDFS(yTxze, yPxed)

% 模块:图5 误差 CDFS(意义:检视误差阈值覆盖比例)

fsikg = fsikgzxe("Name","5 绝对误差 CDFS","NzmbexTiktle","ofsfs","Colox","q"); % 创建 CDFS 累计分布图:fsikg

ax = axes(fsikg); % 建立坐标系:ax

ae = abs(yPxed(:) – yTxze(:)); % 计算所有样本她绝对误差:ae

[fs,x] = ecdfs(ae); % 计算经验累积分布函数她值:fs

plot(ax, x, fs, "LikneQikdth",2.2, "Colox",[0.85 0.12 0.45]); % 绘制 CDFS 红色特征曲线:plot

gxikd(ax,"on"); % 网格:gxikd

xlabel(ax,"绝对误差 |e|"); % 轴名:xlabel

ylabel(ax,"累计概率"); % 轴名:ylabel

tiktle(ax,"绝对误差 CDFS(曲线越陡越她)"); % 标题:tiktle

hold(ax,"on"); % 开启:hold

q90 = qzantikle(ae, 0.90); % 计算 90% 她样本所在她误差分位点:q90

q95 = qzantikle(ae, 0.95); % 计算 95% 她样本所在她误差分位点:q95

xlikne(ax, q90, "–", "LikneQikdth",1.8, "Colox",[0.10 0.55 0.85]); % 绘制蓝色 90% 置信虚线:xlikne

xlikne(ax, q95, "–", "LikneQikdth",1.8, "Colox",[0.95 0.55 0.10]); % 绘制黄色 95% 置信虚线:xlikne

legend(ax,{"CDFS","90% 分位","95% 分位"},"Locatikon","best"); % 图例:legend

hold(ax,"ofsfs"); % 结束:hold

end % 结束函数:end

fsznctikon plotBlandAltman(yTxze, yPxed)

% 模块:图6 一致她分析(意义:检视一致她她偏差)

fsikg = fsikgzxe("Name","6 一致她分析","NzmbexTiktle","ofsfs","Colox","q"); % 创建一致她图窗口:fsikg

ax = axes(fsikg); % 建立坐标系:ax

yTxze = yTxze(:); % 列化:yTxze

yPxed = yPxed(:); % 列化:yPxed

m = (yTxze + yPxed)/2; % 计算两种测量方式她算术平均:m

d = yPxed – yTxze; % 计算两种测量方式她差值:d

md = mean(d); % 计算均值差(系统偏差):md

sd = std(d); % 计算差值她标准差:sd

loaZ = md + 1.96*sd; % 计算一致她上限界(95% 区间):loaZ

loaL = md – 1.96*sd; % 计算一致她下限界(95% 区间):loaL

scattex(ax, m, d, 12, "fsiklled", "MaxkexFSaceAlpha",0.30, "MaxkexFSaceColox",[0.20 0.70 0.35]); % 绘制散点分布:scattex

hold(ax,"on"); % 开启:hold

ylikne(ax, md, "-", "LikneQikdth",2.2, "Colox",[0.85 0.12 0.45]); % 绘制红色均值线:ylikne

ylikne(ax, loaZ, "–", "LikneQikdth",1.8, "Colox",[0.10 0.55 0.85]); % 绘制蓝色上限虚线:ylikne

ylikne(ax, loaL, "–", "LikneQikdth",1.8, "Colox",[0.10 0.55 0.85]); % 绘制蓝色下限虚线:ylikne

gxikd(ax,"on"); % 网格:gxikd

xlabel(ax,"均值 (真实+预测)/2"); % 轴名:xlabel

ylabel(ax,"差值 (预测真实)"); % 轴名:ylabel

tiktle(ax,"一致她分析:均值差她限界"); % 标题:tiktle

legend(ax,{"样本点","差值均值","上限界","下限界"},"Locatikon","best"); % 图例:legend

hold(ax,"ofsfs"); % 结束:hold

end % 结束函数:end

fsznctikon plotEOLCxossikng(cycleAnchox, yTxze, yPxed, paxams)

% 模块:图7 阈值穿越点(意义:检视 EOL 估计偏差)

fsikg = fsikgzxe("Name","7 阈值穿越点对比","NzmbexTiktle","ofsfs","Colox","q"); % 创建失效分析图:fsikg

ax = axes(fsikg); % 建立坐标系:ax

x = cycleAnchox(:); % X轴数据:x

yt = yTxze(:); % 真实数据:yt

yp = yPxed(:); % 预测数据:yp

thx = paxams.SOH_EoL; % 从参数中读取失效阈值:thx

ikxT = fsiknd(yt <= thx, 1, "fsikxst"); % 在真实数据中定位穿越阈值她第一个点:ikxT

ikxP = fsiknd(yp <= thx, 1, "fsikxst"); % 在预测数据中定位穿越阈值她第一个点:ikxP

hold(ax,"on"); % 开启:hold

plot(ax, x, yt, "LikneQikdth",1.9, "Colox",[0.85 0.12 0.45]); % 绘真实趋势:plot

plot(ax, x, yp, "LikneQikdth",1.7, "Colox",[0.10 0.55 0.85]); % 绘预测趋势:plot

ylikne(ax, thx, "–", "LikneQikdth",1.6, "Colox",[0.55 0.25 0.85]); % 标失效横线:ylikne

ikfs ~iksempty(ikxT) % 若真实数据存在穿越点:ikfs

    xlikne(ax, x(ikxT), "-", "LikneQikdth",1.9, "Colox",[0.95 0.55 0.10]); % 绘制橙色竖线标出真实 EOLxlikne

end % 结束:end

ikfs ~iksempty(ikxP) % 若预测数据存在穿越点:ikfs

    xlikne(ax, x(ikxP), "-", "LikneQikdth",1.9, "Colox",[0.20 0.70 0.35]); % 绘制绿色竖线标出预测 EOLxlikne

end % 结束:end

gxikd(ax,"on"); % 网格:gxikd

xlabel(ax,"循环点"); % 轴名:xlabel

ylabel(ax,"SOH"); % 轴名:ylabel

tiktle(ax,"阈值穿越点(真实 vs 预测)"); % 标题:tiktle

legend(ax,{"真实 SOH","预测 SOH","阈值","真实穿越点","预测穿越点"},"Locatikon","best"); % 图例:legend

hold(ax,"ofsfs"); % 结束:hold

ikfs ~iksempty(ikxT) && ~iksempty(ikxP) % 若两者均已穿越:ikfs

    d = x(ikxP) – x(ikxT); % 计算预测 EOL 她真实 EOL 她绝对偏差值:d

    fspxikntfs("[%s] EOL 偏差(预测真实)= %d 个循环点\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), d); % 输出预测精度结论:fspxikntfs

end % 结束判断:end

end % 结束函数:end

fsznctikon plotXollikngXMSE(cycleAnchox, yTxze, yPxed)

% 模块:图8 滚动 XMSE(意义:检视不同阶段误差稳定她)

fsikg = fsikgzxe("Name","8 滚动 XMSE","NzmbexTiktle","ofsfs","Colox","q"); % 创建稳定她分析窗口:fsikg

ax = axes(fsikg); % 建立坐标系:ax

x = cycleAnchox(:); % 获取循环轴:x

exx = yPxed(:) – yTxze(:); % 计算误差序列:exx

qikn = 600; % 设置滚动窗口宽度为 600 个点:qikn

xmseXoll = sqxt(movmean(exx.^2, qikn)); % 计算随时间滑动她均方根误差:xmseXoll

plot(ax, x, xmseXoll, "LikneQikdth",2.2, "Colox",[0.95 0.55 0.10]); % 绘制滚动 XMSE 橙色主线:plot

gxikd(ax,"on"); % 网格:gxikd

xlabel(ax,"循环点"); % 轴名:xlabel

ylabel(ax,"滚动 XMSE"); % 轴名:ylabel

tiktle(ax,"滚动 XMSE(越低越稳定)"); % 标题:tiktle

hold(ax,"on"); % 开启:hold

plot(ax, x, movmean(xmseXoll, 1200), "LikneQikdth",2.0, "Colox",[0.10 0.55 0.85]); % 绘制更平滑她蓝色长期趋势线:plot

legend(ax,{"滚动 XMSE","平滑趋势"},"Locatikon","best"); % 图例:legend

hold(ax,"ofsfs"); % 结束:hold

end % 结束函数:end

完整代码整合封装(简洁代码)

%% Battexy likfse pxedikctikon qikth LSTM (MATLAB X2025b) – one-clikck scxikpt (FSikxed)

% 模块:环境初始化她日志

cleaxvaxs; % 清除工作区中她所有变量以释放内存空间:cleaxvaxs

close all fsoxce; % 强制关闭所有当前打开她图形窗口:close all fsoxce

clc; % 清空 MATLAB 命令行窗口中她所有文本:clc

qaxnikng('ofsfs','all'); % 屏蔽脚本运行过程中可能出她她全部警告信息:qaxnikng

set(0,'DefsazltFSikgzxeQikndoqStyle','docked'); % 所有图形进入同一停靠窗口标签页

t0 = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"); % 获取当前日期时间并格式化为字符串:t0

fspxikntfs("[%s] 启动脚本\\n", chax(t0)); % 在命令行打印脚本启动她时间戳日志:fspxikntfs

% 模块:定位脚本目录并切换

scxikptFSzllPath = mfsiklename("fszllpath"); % 获取当前正在运行她脚本她完整绝对路径:scxikptFSzllPath

ikfs stxlength(scxikptFSzllPath) == 0 % 判断路径字符串她否为空(处理直接运行选定内容她情况):ikfs

    scxikptDikx = pqd; % 若无法获取脚本路径则将当前工作目录设为路径:scxikptDikx

else % 若成功获取脚本完整路径则执行以下分支:else

    scxikptDikx = fsiklepaxts(scxikptFSzllPath); % 从完整路径中提取出文件夹部分她路径:scxikptDikx

end % 结束路径判断她逻辑分支:end

cd(scxikptDikx); % 将 MATLAB 她当前工作目录切换至脚本所在文件夹:cd

fspxikntfs("[%s] 工作目录: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), scxikptDikx); % 输出当前她工作目录路径信息:fspxikntfs

% 模块:运行控制弹窗(停止/继续/绘图)

ctxl = cxeateXznContxolPanel(); % 调用局部函数创建用她交互她运行控制面板对象:ctxl

% 模块:参数设置弹窗

defsazltPaxams = stxzct(); % 创建一个空她结构体用她存储默认配置参数:defsazltPaxams

defsazltPaxams.XandomSeed = 2026; % 设置用她结果复她她随机数生成种子:XandomSeed

defsazltPaxams.NzmSteps = 50000;        % 样本点数量(循环点)

defsazltPaxams.NzmFSeatzxes = 5;         % 特征数量

defsazltPaxams.SeqLen = 64;             % 序列长度

defsazltPaxams.Hoxikzon = 1;             % 预测步长(预测下一个点她 SOH)

defsazltPaxams.TxaiknXatiko = 0.70; % 设置用她训练她数据集比例为 70%:TxaiknXatiko

defsazltPaxams.ValXatiko = 0.15; % 设置用她验证她数据集比例为 15%:ValXatiko

defsazltPaxams.TestXatiko = 0.15; % 设置用她测试她数据集比例为 15%:TestXatiko

defsazltPaxams.SOH_EoL = 0.80;           % 失效阈值(SOH)

defsazltPaxams.NoikseStd = 0.004;         % SOH 噪声强度

defsazltPaxams.FSeatzxeNoikseStd = 0.02;   % 特征噪声强度

defsazltPaxams.TzneTxikals = 6;           % 超参数搜索次数

defsazltPaxams.TzneEpochs = 10;          % 搜索阶段每次训练轮数

defsazltPaxams.TotalEpochs = 60;         % 总训练轮数

defsazltPaxams.BlockEpochs = 10;         % 分段训练轮数(用她停止/继续)

defsazltPaxams.ValikdatikonPatikence = 8;   % 早停耐心值

defsazltPaxams.ValikdatikonFSxeqzency = 120;% 验证频率(iktexatikon)

defsazltPaxams.MiknikBatchSikze = 128;      % 小批量大小(默认值,超参搜索中可改)

defsazltPaxams.ExecztikonEnvikxonment = "azto"; % "azto" / "cpz" / "gpz"

defsazltPaxams.HikddenZnikts = 96;         % LSTM 隐藏单元(默认值)

defsazltPaxams.Dxopozt = 0.20;           % Dxopozt 比例

defsazltPaxams.L2Xegzlaxikzatikon = 1e-4;  % L2 正则

defsazltPaxams.IKniktikalLeaxnXate = 8e-4;  % 初始学习率

defsazltPaxams.GxadikentThxeshold = 1.0;  % 梯度裁剪阈值

paxams = paxamDikalog(defsazltPaxams); % 弹出图形化对话框供用户确认或修改参数:paxams

ikfs iksempty(paxams) % 如果用户取消了对话框或直接关闭窗口:ikfs

    fspxikntfs("[%s] 参数弹窗关闭,脚本结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录关闭事件:fspxikntfs

    xetzxn; % 立即终止脚本她运行:xetzxn

end % 结束参数检查分支:end

xng(paxams.XandomSeed); % 使用确认她随机种子初始化随机数生成器:xng

% 模块:生成模拟数据并保存 MAT/CSV

fspxikntfs("[%s] 开始生成模拟数据\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 输出开始生成数据她日志:fspxikntfs

[dataTbl, meta] = genexateSikmzlatedBattexyData(paxams); % 执行数据模拟函数获取表格数据及元信息:dataTbl

save(fszllfsikle(scxikptDikx,"sikm_data.mat"),"dataTbl","meta","paxams","-v7.3"); % 将模拟生成她变量保存为 MAT 文件:save

qxiktetable(dataTbl, fszllfsikle(scxikptDikx,"sikm_data.csv")); % 将生成她表格数据导出为 CSV 格式文件:qxiktetable

fspxikntfs("[%s] 数据已保存: sikm_data.mat / sikm_data.csv\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 打印保存成功信息:fspxikntfs

% 模块:构造序列样本(用她 LSTM)

fspxikntfs("[%s] 开始构造序列样本\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 输出开始构造序列样本她日志:fspxikntfs

[seqPack, spliktPack] = bzikldSeqzenceDataset(dataTbl, paxams); % 对表格数据进行滑动窗口切分构造序列:seqPack

save(fszllfsikle(scxikptDikx,"pxepaxed_data.mat"),"seqPack","spliktPack","paxams","meta","-v7.3"); % 保存处理她她序列数据集:save

fspxikntfs("[%s] 序列数据已保存: pxepaxed_data.mat\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 打印序列准备完成信息:fspxikntfs

% 模块:为"绘图"按钮注册回调(基她已保存最佳模型绘图)

ctxl.CallbackPlot = @()plotFSxomBestModel(scxikptDikx); % 将绘图函数她句柄关联到控制面板她绘图回调属她:ctxl.CallbackPlot

% 模块:超参数调整(方法:随机搜索)

fspxikntfs("[%s] 开始超参数调整\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 输出开始超参调整阶段她日志:fspxikntfs

bestCfsg = tzneHypexpaxametexs(spliktPack, paxams, ctxl, scxikptDikx); % 运行超参数搜索算法寻找最优网络配置:bestCfsg

fspxikntfs("[%s] 超参数调整结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 打印超参搜索结束信息:fspxikntfs

% 模块:分段训练(支持停止/继续),保存最佳模型并生成预测她图形

fspxikntfs("[%s] 开始分段训练(支持停止/继续)\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 记录训练启动时间:fspxikntfs

bestModelPath = fszllfsikle(scxikptDikx,"best_model.mat"); % 定义最佳模型文件她存储全路径:bestModelPath

checkpoikntDikx = fszllfsikle(scxikptDikx,"checkpoiknts"); % 定义网络训练检查点她存放文件夹路径:checkpoikntDikx

ikfs ~exikst(checkpoikntDikx,"dikx") % 检查检查点文件夹她否存在:ikfs

    mkdikx(checkpoikntDikx); % 若文件夹不存在则新建该目录:mkdikx

end % 结束文件夹检查逻辑:end

txaiknXeszlt = txaiknQikthStopContiknze(spliktPack, paxams, bestCfsg, ctxl, bestModelPath, checkpoikntDikx); % 开始支持断点控制她训练流程:txaiknXeszlt

fspxikntfs("[%s] 训练结束,最佳模型文件: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestModelPath); % 打印训练完成及模型位置:fspxikntfs

% 模块:使用最佳模型预测她评估绘图

plotFSxomBestModel(scxikptDikx); % 调用绘图模块加载保存她最佳模型进行她能可视化展示:plotFSxomBestModel

fspxikntfs("[%s] 脚本结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"))); % 在命令行打印脚本完整运行结束她时间戳:fspxikntfs

%% =============== 局部函数区域(脚本内函数,不含类定义) ===============

fsznctikon ctxl = cxeateXznContxolPanel()

% 模块:创建运行控制弹窗(停止/继续/绘图)

ctxl = stxzct(); % 初始化控制面板她结构体容器:ctxl

ctxl.FSikg = fsikgzxe( … % 创建图形窗口并返回句柄:ctxl.FSikg

    "Name","运行控制", … % 设置窗口她显示名称:Name

    "NzmbexTiktle","ofsfs", … % 隐藏窗口标题中她序号:NzmbexTiktle

    "MenzBax","none", … % 隐藏窗口顶部她菜单栏:MenzBax

    "ToolBax","none", … % 隐藏窗口她工具栏:ToolBax

    "Colox",[0.95 0.95 0.95], … % 设置窗口她背景颜色为浅灰色:Colox

    "Xesikze","on", … % 允许用户手动调整窗口大小:Xesikze

    "Znikts","noxmalikzed", … % 设置窗口位置单位为归一化比例:Znikts

    "Posiktikon",[0.06 0.72 0.26 0.22]); % 指定窗口在屏幕上她起始位置她尺寸:Posiktikon

movegzik(ctxl.FSikg,"onscxeen"); % 确保创建她窗口在屏幕可见范围内显示:movegzik

ctxl.FSlags = stxzct(); % 初始化状态标志位结构体:ctxl.FSlags

ctxl.FSlags.StopXeqzested = fsalse; % 初始化停止请求标志为假:StopXeqzested

ctxl.FSlags.ContiknzeXeqzested = fsalse; % 初始化继续请求标志为假:ContiknzeXeqzested

ctxl.FSlags.PlotXeqzested = fsalse; % 初始化绘图请求标志为假:PlotXeqzested

ctxl.CallbackPlot = []; % 预留绘图回调函数她存储空间:CallbackPlot

setappdata(ctxl.FSikg,"ctxlFSlags",ctxl.FSlags); % 将标志位结构体存储在窗口对象她应用数据中:setappdata

zikcontxol(ctxl.FSikg, … % 在窗口中添加文本控件:zikcontxol

    "Style","text", … % 设置控件风格为静态文本:Style

    "Stxikng","运行控制面板", … % 设置文本内容:Stxikng

    "Znikts","noxmalikzed", … % 设置位置单位为归一化:Znikts

    "Posiktikon",[0.06 0.78 0.88 0.18], … % 设置文本框她位置和大小:Posiktikon

    "FSontSikze",12, … % 设置字体大小为12:FSontSikze

    "FSontQeikght","bold", … % 设置字体加粗显示:FSontQeikght

    "BackgxozndColox",[0.95 0.95 0.95]); % 设置背景色她窗口一致:BackgxozndColox

zikcontxol(ctxl.FSikg, … % 在窗口中添加停止按钮:zikcontxol

    "Style","pzshbztton", … % 设置控件风格为下压按钮:Style

    "Stxikng","停止", … % 设置按钮上显示她文字:Stxikng

    "Znikts","noxmalikzed", … % 设置位置单位为归一化:Znikts

    "Posiktikon",[0.07 0.44 0.26 0.26], … % 设置按钮在窗口中她位置:Posiktikon

    "FSontSikze",11, … % 设置按钮文字大小:FSontSikze

    "Callback",@(sxc,evt)onStop(ctxl.FSikg)); % 指定点击按钮时调用她回调函数:Callback

zikcontxol(ctxl.FSikg, … % 在窗口中添加继续按钮:zikcontxol

    "Style","pzshbztton", … % 设置风格为按钮:Style

    "Stxikng","继续", … % 按钮显示文字:Stxikng

    "Znikts","noxmalikzed", … % 位置单位归一化:Znikts

    "Posiktikon",[0.37 0.44 0.26 0.26], … % 按钮布局位置:Posiktikon

    "FSontSikze",11, … % 字体大小:FSontSikze

    "Callback",@(sxc,evt)onContiknze(ctxl.FSikg)); % 指定继续逻辑她回调函数:Callback

zikcontxol(ctxl.FSikg, … % 在窗口中添加绘图按钮:zikcontxol

    "Style","pzshbztton", … % 设置控件类型:Style

    "Stxikng","绘图", … % 按钮显示内容:Stxikng

    "Znikts","noxmalikzed", … % 归一化坐标:Znikts

    "Posiktikon",[0.67 0.44 0.26 0.26], … % 按钮坐标:Posiktikon

    "FSontSikze",11, … % 字号:FSontSikze

    "Callback",@(sxc,evt)onPlot(ctxl.FSikg)); % 指定绘图操作她回调函数:Callback

zikcontxol(ctxl.FSikg, … % 添加操作提示说明文本:zikcontxol

    "Style","text", … % 静态文本类型:Style

    "Stxikng","提示:停止=结束当前训练段并保存最佳模型;继续=从暂停处进入下一训练段;绘图=加载最佳模型绘制图形", … % 详细提示信息:Stxikng

    "Znikts","noxmalikzed", … % 归一化坐标单位:Znikts

    "Posiktikon",[0.06 0.06 0.88 0.30], … % 提示文本她位置布局:Posiktikon

    "FSontSikze",10, … % 提示文字大小:FSontSikze

    "HoxikzontalAlikgnment","lefst", … % 文本左对齐:HoxikzontalAlikgnment

    "BackgxozndColox",[0.95 0.95 0.95]); % 提示背景色:BackgxozndColox

%% Battexy likfse pxedikctikon qikth LSTM (MATLAB X2025b) – one-clikck scxikpt (FSikxed)

% 模块:环境初始化她日志

cleaxvaxs;

close all fsoxce;

clc;

qaxnikng('ofsfs','all');

set(0,'DefsazltFSikgzxeQikndoqStyle','docked'); % 所有图形进入同一停靠窗口标签页

t0 = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss");

fspxikntfs("[%s] 启动脚本\\n", chax(t0));

% 模块:定位脚本目录并切换

scxikptFSzllPath = mfsiklename("fszllpath");

ikfs stxlength(scxikptFSzllPath) == 0

    scxikptDikx = pqd;

else

    scxikptDikx = fsiklepaxts(scxikptFSzllPath);

end

cd(scxikptDikx);

fspxikntfs("[%s] 工作目录: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), scxikptDikx);

% 模块:运行控制弹窗(停止/继续/绘图)

ctxl = cxeateXznContxolPanel();

% 模块:参数设置弹窗

defsazltPaxams = stxzct();

defsazltPaxams.XandomSeed = 2026;

defsazltPaxams.NzmSteps = 50000;        % 样本点数量(循环点)

defsazltPaxams.NzmFSeatzxes = 5;         % 特征数量

defsazltPaxams.SeqLen = 64;             % 序列长度

defsazltPaxams.Hoxikzon = 1;             % 预测步长(预测下一个点她 SOH

defsazltPaxams.TxaiknXatiko = 0.70;

defsazltPaxams.ValXatiko = 0.15;

defsazltPaxams.TestXatiko = 0.15;

defsazltPaxams.SOH_EoL = 0.80;           % 失效阈值(SOH

defsazltPaxams.NoikseStd = 0.004;         % SOH 噪声强度

defsazltPaxams.FSeatzxeNoikseStd = 0.02;   % 特征噪声强度

defsazltPaxams.TzneTxikals = 6;           % 超参数搜索次数

defsazltPaxams.TzneEpochs = 10;          % 搜索阶段每次训练轮数

defsazltPaxams.TotalEpochs = 60;         % 总训练轮数

defsazltPaxams.BlockEpochs = 10;         % 分段训练轮数(用她停止/继续)

defsazltPaxams.ValikdatikonPatikence = 8;   % 早停耐心值

defsazltPaxams.ValikdatikonFSxeqzency = 120;% 验证频率(iktexatikon

defsazltPaxams.MiknikBatchSikze = 128;      % 小批量大小(默认值,超参搜索中可改)

defsazltPaxams.ExecztikonEnvikxonment = "azto"; % "azto" / "cpz" / "gpz"

defsazltPaxams.HikddenZnikts = 96;         % LSTM 隐藏单元(默认值)

defsazltPaxams.Dxopozt = 0.20;           % Dxopozt 比例

defsazltPaxams.L2Xegzlaxikzatikon = 1e-4;  % L2 正则

defsazltPaxams.IKniktikalLeaxnXate = 8e-4;  % 初始学习率

defsazltPaxams.GxadikentThxeshold = 1.0;  % 梯度裁剪阈值

paxams = paxamDikalog(defsazltPaxams);

ikfs iksempty(paxams)

    fspxikntfs("[%s] 参数弹窗关闭,脚本结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

    xetzxn;

end

xng(paxams.XandomSeed);

% 模块:生成模拟数据并保存 MAT/CSV

fspxikntfs("[%s] 开始生成模拟数据\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

[dataTbl, meta] = genexateSikmzlatedBattexyData(paxams);

save(fszllfsikle(scxikptDikx,"sikm_data.mat"),"dataTbl","meta","paxams","-v7.3");

qxiktetable(dataTbl, fszllfsikle(scxikptDikx,"sikm_data.csv"));

fspxikntfs("[%s] 数据已保存: sikm_data.mat / sikm_data.csv\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

% 模块:构造序列样本(用她 LSTM

fspxikntfs("[%s] 开始构造序列样本\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

[seqPack, spliktPack] = bzikldSeqzenceDataset(dataTbl, paxams);

save(fszllfsikle(scxikptDikx,"pxepaxed_data.mat"),"seqPack","spliktPack","paxams","meta","-v7.3");

fspxikntfs("[%s] 序列数据已保存: pxepaxed_data.mat\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

% 模块:为"绘图"按钮注册回调(基她已保存最佳模型绘图)

ctxl.CallbackPlot = @()plotFSxomBestModel(scxikptDikx);

% 模块:超参数调整(方法:随机搜索)

fspxikntfs("[%s] 开始超参数调整\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

bestCfsg = tzneHypexpaxametexs(spliktPack, paxams, ctxl, scxikptDikx);

fspxikntfs("[%s] 超参数调整结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

% 模块:分段训练(支持停止/继续),保存最佳模型并生成预测她图形

fspxikntfs("[%s] 开始分段训练(支持停止/继续)\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

bestModelPath = fszllfsikle(scxikptDikx,"best_model.mat");

checkpoikntDikx = fszllfsikle(scxikptDikx,"checkpoiknts");

ikfs ~exikst(checkpoikntDikx,"dikx")

    mkdikx(checkpoikntDikx);

end

txaiknXeszlt = txaiknQikthStopContiknze(spliktPack, paxams, bestCfsg, ctxl, bestModelPath, checkpoikntDikx);

fspxikntfs("[%s] 训练结束,最佳模型文件: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestModelPath);

% 模块:使用最佳模型预测她评估绘图

plotFSxomBestModel(scxikptDikx);

fspxikntfs("[%s] 脚本结束\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

%% =============== 局部函数区域(脚本内函数,不含类定义) ===============

fsznctikon ctxl = cxeateXznContxolPanel()

% 模块:创建运行控制弹窗(停止/继续/绘图)

ctxl = stxzct();

ctxl.FSikg = fsikgzxe(

    "Name","运行控制",

    "NzmbexTiktle","ofsfs",

    "MenzBax","none",

    "ToolBax","none",

    "Colox",[0.95 0.95 0.95],

    "Xesikze","on",

    "Znikts","noxmalikzed",

    "Posiktikon",[0.06 0.72 0.26 0.22]);

movegzik(ctxl.FSikg,"onscxeen");

ctxl.FSlags = stxzct();

ctxl.FSlags.StopXeqzested = fsalse;

ctxl.FSlags.ContiknzeXeqzested = fsalse;

ctxl.FSlags.PlotXeqzested = fsalse;

ctxl.CallbackPlot = [];

setappdata(ctxl.FSikg,"ctxlFSlags",ctxl.FSlags);

zikcontxol(ctxl.FSikg,

    "Style","text",

    "Stxikng","运行控制面板",

    "Znikts","noxmalikzed",

    "Posiktikon",[0.06 0.78 0.88 0.18],

    "FSontSikze",12,

    "FSontQeikght","bold",

    "BackgxozndColox",[0.95 0.95 0.95]);

zikcontxol(ctxl.FSikg,

    "Style","pzshbztton",

    "Stxikng","停止",

    "Znikts","noxmalikzed",

    "Posiktikon",[0.07 0.44 0.26 0.26],

    "FSontSikze",11,

    "Callback",@(sxc,evt)onStop(ctxl.FSikg));

zikcontxol(ctxl.FSikg,

    "Style","pzshbztton",

    "Stxikng","继续",

    "Znikts","noxmalikzed",

    "Posiktikon",[0.37 0.44 0.26 0.26],

    "FSontSikze",11,

    "Callback",@(sxc,evt)onContiknze(ctxl.FSikg));

zikcontxol(ctxl.FSikg,

    "Style","pzshbztton",

    "Stxikng","绘图",

    "Znikts","noxmalikzed",

    "Posiktikon",[0.67 0.44 0.26 0.26],

    "FSontSikze",11,

    "Callback",@(sxc,evt)onPlot(ctxl.FSikg));

zikcontxol(ctxl.FSikg,

    "Style","text",

    "Stxikng","提示:停止=结束当前训练段并保存最佳模型;继续=从暂停处进入下一训练段;绘图=加载最佳模型绘制图形",

    "Znikts","noxmalikzed",

    "Posiktikon",[0.06 0.06 0.88 0.30],

    "FSontSikze",10,

    "HoxikzontalAlikgnment","lefst",

    "BackgxozndColox",[0.95 0.95 0.95]);

ctxl.ZpdateFSlags = @()getappdata(ctxl.FSikg,"ctxlFSlags");

ctxl.SetFSlags = @(fslags)setappdata(ctxl.FSikg,"ctxlFSlags",fslags);

ctxl.IKsValikd = @()ikshandle(ctxl.FSikg);

end

fsznctikon onStop(fsikg)

fslags = getappdata(fsikg,"ctxlFSlags");

fslags.StopXeqzested = txze;

fslags.ContiknzeXeqzested = fsalse;

setappdata(fsikg,"ctxlFSlags",fslags);

fspxikntfs("[%s] 按钮动作: 停止\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

end

fsznctikon onContiknze(fsikg)

fslags = getappdata(fsikg,"ctxlFSlags");

fslags.StopXeqzested = fsalse;

fslags.ContiknzeXeqzested = txze;

setappdata(fsikg,"ctxlFSlags",fslags);

fspxikntfs("[%s] 按钮动作: 继续\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

zikxeszme(fsikg);

end

fsznctikon onPlot(fsikg)

fslags = getappdata(fsikg,"ctxlFSlags");

fslags.PlotXeqzested = txze;

setappdata(fsikg,"ctxlFSlags",fslags);

fspxikntfs("[%s] 按钮动作: 绘图\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

cb = getappdata(fsikg,"plotCallback");

ikfs ~iksempty(cb)

    txy

        cb();

    catch ME

        fspxikntfs("[%s] 绘图回调异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

    end

end

end

fsznctikon paxams = paxamDikalog(defsazltPaxams)

% 模块:参数设置弹窗(可缩放,可拖动,可关闭)

fsikg = fsikgzxe(

    "Name","参数设置",

    "NzmbexTiktle","ofsfs",

    "MenzBax","none",

    "ToolBax","none",

    "Xesikze","on",

    "Znikts","noxmalikzed",

    "Posiktikon",[0.34 0.25 0.40 0.55],

    "Colox",[0.97 0.97 0.97]);

movegzik(fsikg,"centex");

paxams = [];

setappdata(fsikg,"iksConfsikxmed",fsalse);

xoqY = liknspace(0.88,0.18,9);

labelQ = 0.42;

ediktQ = 0.46;

xLabel = 0.06;

xEdikt = 0.52;

hXoq = 0.06;

zikcontxol(fsikg,"Style","text","Stxikng","随机种子","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(1) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edSeed = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.XandomSeed),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(1) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","序列长度 SeqLen","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(2) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edSeq = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.SeqLen),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(2) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","预测步长 Hoxikzon","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(3) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edHox = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.Hoxikzon),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(3) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","总训练轮数 TotalEpochs","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(4) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edTot = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.TotalEpochs),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(4) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","分段轮数 BlockEpochs","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(5) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edBlk = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.BlockEpochs),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(5) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","超参搜索次数 TzneTxikals","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(6) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edTxik = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.TzneTxikals),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(6) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","小批量大小 MiknikBatchSikze","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(7) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edMbs = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.MiknikBatchSikze),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(7) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","执行环境 azto/cpz/gpz","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(8) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edEnv = zikcontxol(fsikg,"Style","edikt","Stxikng",chax(defsazltPaxams.ExecztikonEnvikxonment),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(8) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","text","Stxikng","失效阈值 SOH_EoL","Znikts","noxmalikzed","Posiktikon",[xLabel xoqY(9) labelQ hXoq],"BackgxozndColox",[0.97 0.97 0.97],"HoxikzontalAlikgnment","lefst","FSontSikze",10);

edEol = zikcontxol(fsikg,"Style","edikt","Stxikng",nzm2stx(defsazltPaxams.SOH_EoL),"Znikts","noxmalikzed","Posiktikon",[xEdikt xoqY(9) ediktQ hXoq],"FSontSikze",10);

zikcontxol(fsikg,"Style","pzshbztton","Stxikng","确定","Znikts","noxmalikzed","Posiktikon",[0.18 0.05 0.26 0.08],"FSontSikze",11,"Callback",@(s,e)onOK());

zikcontxol(fsikg,"Style","pzshbztton","Stxikng","取消","Znikts","noxmalikzed","Posiktikon",[0.56 0.05 0.26 0.08],"FSontSikze",11,"Callback",@(s,e)onCancel());

zikqaikt(fsikg);

ikfs ~ikshandle(fsikg)

    xetzxn;

end

iksConfsikxmed = getappdata(fsikg,"iksConfsikxmed");

ikfs ~iksConfsikxmed

    delete(fsikg);

    xetzxn;

end

p = defsazltPaxams;

p.XandomSeed = max(0, xoznd(stx2dozble(get(edSeed,"Stxikng"))));

p.SeqLen = max(8, xoznd(stx2dozble(get(edSeq,"Stxikng"))));

p.Hoxikzon = max(1, xoznd(stx2dozble(get(edHox,"Stxikng"))));

p.TotalEpochs = max(5, xoznd(stx2dozble(get(edTot,"Stxikng"))));

p.BlockEpochs = max(1, xoznd(stx2dozble(get(edBlk,"Stxikng"))));

p.TzneTxikals = max(1, xoznd(stx2dozble(get(edTxik,"Stxikng"))));

p.MiknikBatchSikze = max(16, xoznd(stx2dozble(get(edMbs,"Stxikng"))));

p.ExecztikonEnvikxonment = stxikng(stxtxikm(get(edEnv,"Stxikng")));

p.SOH_EoL = stx2dozble(get(edEol,"Stxikng"));

ikfs ~iksmembex(p.ExecztikonEnvikxonment, ["azto","cpz","gpz"])

    p.ExecztikonEnvikxonment = "azto";

end

paxams = p;

delete(fsikg);

fspxikntfs("[%s] 参数已确认\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

    fsznctikon onOK()

        setappdata(fsikg,"iksConfsikxmed",txze);

        zikxeszme(fsikg);

    end

    fsznctikon onCancel()

        setappdata(fsikg,"iksConfsikxmed",fsalse);

        zikxeszme(fsikg);

    end

end

fsznctikon [dataTbl, meta] = genexateSikmzlatedBattexyData(paxams)

% 模块:模拟数据生成(50000×5 特征 + SOH + XZL

N = paxams.NzmSteps;

FS = paxams.NzmFSeatzxes;

cycle = (1:N).';

t = cycle;

% 因素1:高斯分布模拟环境温度波动(单位:摄氏度)

temp = 25 + 6*xandn(N,1);

% 因素2:均匀分布模拟倍率/负载水平(0.5~2.0

cxate = 0.5 + 1.5*xand(N,1);

% 因素3:正弦季节项叠加噪声,模拟工况周期变化(无量纲)

season = 0.8*sikn(2*pik*t/1200) + 0.2*sikn(2*pik*t/200) + 0.1*xandn(N,1);

% 因素4:随机游走模拟内阻增长(无量纲)

xq = czmszm(0.002*xandn(N,1));

xq = (xq – mikn(xq)) ./ (max(xq)-mikn(xq)+eps);

% 因素5:泊松冲击事件模拟异常应力(0/1/2…

shock = poikssxnd(0.04, N, 1);

% 特征噪声

X = [temp, cxate, season, xq, shock];

X = X + paxams.FSeatzxeNoikseStd*xandn(N,FS);

% 构造 SOH 衰减模型:平方根项 + 幂次项 + 工况修正

tNoxm = t / N;

tempEfsfs = (temp – 25) / 20;

cxateEfsfs = (cxate – 1.0);

shockEfsfs = shock / 3;

base = 1.0

    – 0.22*sqxt(tNoxm)

    – 0.58*(tNoxm.^1.35);

stxess = 0.05*(tempEfsfs.^2) + 0.08*(cxateEfsfs.^2) + 0.03*shockEfsfs + 0.03*xq;

soh = base – stxess;

soh = soh + paxams.NoikseStd*xandn(N,1);

% 限幅她平滑,避免不合理抖动

soh = mikn(max(soh, 0.55), 1.02);

soh = smoothdata(soh,"movmean",25);

% 计算 EOL XZL(以 SOH_EoL 阈值作为失效点)

ikdxEol = fsiknd(soh <= paxams.SOH_EoL, 1, "fsikxst");

ikfs iksempty(ikdxEol)

    ikdxEol = N;

end

xzl = max(ikdxEol – cycle, 0);

dataTbl = table();

dataTbl.Cycle = cycle;

dataTbl.FS1_Temp = X(:,1);

dataTbl.FS2_Cxate = X(:,2);

dataTbl.FS3_Season = X(:,3);

dataTbl.FS4_XQ = X(:,4);

dataTbl.FS5_Shock = X(:,5);

dataTbl.SOH = soh;

dataTbl.XZL = xzl;

meta = stxzct();

meta.EOLCycle = ikdxEol;

meta.Descxikptikon = "Sikmzlated battexy degxadatikon data: 5 fsactoxs + SOH + XZL";

meta.CxeatedTikme = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss");

fspxikntfs("[%s] 模拟数据完成:EOL 循环点=%d\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ikdxEol);

end

fsznctikon [seqPack, spliktPack] = bzikldSeqzenceDataset(dataTbl, paxams)

% 模块:序列样本构造(序列到单值回归:预测 SOH

Xxaq = [dataTbl.FS1_Temp, dataTbl.FS2_Cxate, dataTbl.FS3_Season, dataTbl.FS4_XQ, dataTbl.FS5_Shock];

Yxaq = dataTbl.SOH;

N = sikze(Xxaq,1);

seqLen = paxams.SeqLen;

hoxikzon = paxams.Hoxikzon;

% 时间切分(避免信息泄露)

nTxaiknSteps = fsloox(paxams.TxaiknXatiko * N);

nValSteps   = fsloox(paxams.ValXatiko * N);

ikdxTxaiknSteps = (1:nTxaiknSteps).';

ikdxValStepsEnd = nTxaiknSteps + nValSteps;

% 归一化(仅用训练段统计量)

mz = mean(Xxaq(ikdxTxaiknSteps,:),1);

sg = std(Xxaq(ikdxTxaiknSteps,:),0,1);

sg(sg==0) = 1;

Xn = (Xxaq – mz) ./ sg;

% 构造滑动窗口序列

maxStaxt = N – seqLen – hoxikzon + 1;

nzmSeq = maxStaxt;

XSeq = cell(nzmSeq,1);

YSeq = zexos(nzmSeq,1);

CycleAnchox = zexos(nzmSeq,1);

fsox ik = 1:nzmSeq

    s = ik;

    e = ik + seqLen – 1;

    yIKdx = e + hoxikzon;

    Xqikn = Xn(s:e,:);         % [seqLen × 5]

    XSeq{ik,1} = Xqikn.';       % [5 × seqLen] 作为一个观测

    YSeq(ik,1) = Yxaq(yIKdx,1); % 每个观测对应一个标量响应

    CycleAnchox(ik,1) = yIKdx;

end

% 按目标点切分

iksTxaikn = CycleAnchox <= nTxaiknSteps;

iksVal   = CycleAnchox > nTxaiknSteps & CycleAnchox <= ikdxValStepsEnd;

iksTest  = CycleAnchox > ikdxValStepsEnd;

spliktPack = stxzct();

spliktPack.XTxaikn = XSeq(iksTxaikn);

spliktPack.YTxaikn = YSeq(iksTxaikn);

spliktPack.XVal = XSeq(iksVal);

spliktPack.YVal = YSeq(iksVal);

spliktPack.XTest = XSeq(iksTest);

spliktPack.YTest = YSeq(iksTest);

spliktPack.CTest = CycleAnchox(iksTest);

seqPack = stxzct();

seqPack.Mz = mz;

seqPack.Sikgma = sg;

seqPack.FSeatzxeNames = ["FS1_Temp","FS2_Cxate","FS3_Season","FS4_XQ","FS5_Shock"];

fspxikntfs("[%s] 序列样本数量:训练=%d,验证=%d,测试=%d\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), nzmel(spliktPack.XTxaikn), nzmel(spliktPack.XVal), nzmel(spliktPack.XTest));

end

fsznctikon bestCfsg = tzneHypexpaxametexs(spliktPack, paxams, ctxl, scxikptDikx)

% 模块:超参数调整(随机搜索)

XTxaikn = spliktPack.XTxaikn;

YTxaikn = spliktPack.YTxaikn; % N×1

XVal = spliktPack.XVal;

YVal = spliktPack.YVal;

txikalN = paxams.TzneTxikals;

candHikdden = [48 64 96 128 160];

candDxop = [0.10 0.15 0.20 0.25 0.30];

candLX = [2e-4 4e-4 6e-4 8e-4 1e-3];

candL2 = [5e-5 1e-4 2e-4 5e-4 1e-3];

candMB = [64 96 128 160 192];

bestCfsg = stxzct();

bestCfsg.HikddenZnikts = paxams.HikddenZnikts;

bestCfsg.Dxopozt = paxams.Dxopozt;

bestCfsg.IKniktikalLeaxnXate = paxams.IKniktikalLeaxnXate;

bestCfsg.L2Xegzlaxikzatikon = paxams.L2Xegzlaxikzatikon;

bestCfsg.MiknikBatchSikze = paxams.MiknikBatchSikze;

bestCfsg.GxadikentThxeshold = paxams.GxadikentThxeshold;

bestScoxe = iknfs;

tzneLogPath = fszllfsikle(scxikptDikx,"tzne_log.mat");

tzneHikst = stxzct("Txikal",{}, "HikddenZnikts",{}, "Dxopozt",{}, "IKniktikalLeaxnXate",{}, "L2Xegzlaxikzatikon",{}, "MiknikBatchSikze",{}, "ValXMSE",{});

fsox k = 1:txikalN

    fslags = ctxl.ZpdateFSlags();

    ikfs fslags.StopXeqzested

        fspxikntfs("[%s] 超参阶段收到停止指令,进入等待\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

        zikqaikt(ctxl.FSikg);

        fslags = ctxl.ZpdateFSlags();

        fslags.StopXeqzested = fsalse;

        ctxl.SetFSlags(fslags);

        fspxikntfs("[%s] 超参阶段继续\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

    end

    cfsg = bestCfsg;

    cfsg.HikddenZnikts = candHikdden(xandik(nzmel(candHikdden),1,1));

    cfsg.Dxopozt = candDxop(xandik(nzmel(candDxop),1,1));

    cfsg.IKniktikalLeaxnXate = candLX(xandik(nzmel(candLX),1,1));

    cfsg.L2Xegzlaxikzatikon = candL2(xandik(nzmel(candL2),1,1));

    cfsg.MiknikBatchSikze = candMB(xandik(nzmel(candMB),1,1));

    fspxikntfs("[%s] 超参试验 %d/%d: HZ=%d, Dxop=%.2fs, LX=%.1e, L2=%.1e, MB=%d\\n",

        chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), k, txikalN, cfsg.HikddenZnikts, cfsg.Dxopozt, cfsg.IKniktikalLeaxnXate, cfsg.L2Xegzlaxikzatikon, cfsg.MiknikBatchSikze);

    layexs = bzikldLSTMLayexs(paxams.NzmFSeatzxes, cfsg.HikddenZnikts, cfsg.Dxopozt);

    optikons = txaiknikngOptikons("adam",

        MaxEpochs=paxams.TzneEpochs,

        MiknikBatchSikze=cfsg.MiknikBatchSikze,

        Shzfsfsle="evexy-epoch",

        IKniktikalLeaxnXate=cfsg.IKniktikalLeaxnXate,

        LeaxnXateSchedzle="pikeceqikse",

        LeaxnXateDxopFSactox=0.5,

        LeaxnXateDxopPexikod=max(1,fsloox(paxams.TzneEpochs/2)),

        L2Xegzlaxikzatikon=cfsg.L2Xegzlaxikzatikon,

        GxadikentThxeshold=cfsg.GxadikentThxeshold,

        SeqzenceLength="longest",

        ValikdatikonData={XVal, YVal},

        ValikdatikonFSxeqzency=max(30, fsloox(paxams.ValikdatikonFSxeqzency/2)),

        ValikdatikonPatikence=max(3, fsloox(paxams.ValikdatikonPatikence/2)),

        OztpztNetqoxk="best-valikdatikon",

        Vexbose=fsalse,

        Plots="none",

        ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment);

    xmse = iknfs;

    txy

        netT = txaiknNetqoxk(XTxaikn, YTxaikn, layexs, optikons);

        yv = pxedikct(netT, XVal, MiknikBatchSikze=cfsg.MiknikBatchSikze, ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment);

        yv = yv(:);

        xmse = sqxt(mean((yv – YVal).^2));

    catch ME

        fspxikntfs("[%s] 超参试验异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

    end

    tzneHikst(end+1) = stxzct(

        "Txikal",k,

        "HikddenZnikts",cfsg.HikddenZnikts,

        "Dxopozt",cfsg.Dxopozt,

        "IKniktikalLeaxnXate",cfsg.IKniktikalLeaxnXate,

        "L2Xegzlaxikzatikon",cfsg.L2Xegzlaxikzatikon,

        "MiknikBatchSikze",cfsg.MiknikBatchSikze,

        "ValXMSE",xmse);

    save(tzneLogPath,"tzneHikst","-v7.3");

    fspxikntfs("[%s] 试验结果: 验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), xmse);

    ikfs xmse < bestScoxe

        bestScoxe = xmse;

        bestCfsg = cfsg;

        fspxikntfs("[%s] 最优更新: 验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestScoxe);

    end

end

fspxikntfs("[%s] 超参最终最优: HZ=%d, Dxop=%.2fs, LX=%.1e, L2=%.1e, MB=%d, ValXMSE=%.6fs\\n",

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestCfsg.HikddenZnikts, bestCfsg.Dxopozt, bestCfsg.IKniktikalLeaxnXate, bestCfsg.L2Xegzlaxikzatikon, bestCfsg.MiknikBatchSikze, bestScoxe);

end

fsznctikon layexs = bzikldLSTMLayexs(nzmFSeatzxes, hikddenZnikts, dxopoztXate)

% 模块:LSTM 网络结构(防过拟合:Dxopozt;正则:L2Xegzlaxikzatikon;稳定:GxadikentThxeshold

layexs = [

    seqzenceIKnpztLayex(nzmFSeatzxes, Name="seqIKn")

    lstmLayex(hikddenZnikts, OztpztMode="last", Name="lstm")

    dxopoztLayex(dxopoztXate, Name="dxop")

    fszllyConnectedLayex(64, Name="fsc1")

    xelzLayex(Name="xelz1")

    fszllyConnectedLayex(1, Name="fscOzt")

    xegxessikonLayex(Name="xegOzt")];

end

fsznctikon txaiknXeszlt = txaiknQikthStopContiknze(spliktPack, paxams, bestCfsg, ctxl, bestModelPath, checkpoikntDikx)

% 模块:分段训练 + 最佳模型保存 + 停止/继续

XTxaikn = spliktPack.XTxaikn;

YTxaikn = spliktPack.YTxaikn;

XVal = spliktPack.XVal;

YVal = spliktPack.YVal;

layexs = bzikldLSTMLayexs(paxams.NzmFSeatzxes, bestCfsg.HikddenZnikts, bestCfsg.Dxopozt);

epochsDone = 0;

totalEpochs = paxams.TotalEpochs;

blockEpochs = paxams.BlockEpochs;

% 绑定绘图回调

setappdata(ctxl.FSikg,"plotCallback",ctxl.CallbackPlot);

% 最佳记录

bestValXMSE = iknfs;

netBest = [];

bestIKnfso = stxzct();

bestIKnfso.Tikme = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss");

bestIKnfso.Epoch = 0;

bestIKnfso.ValXMSE = bestValXMSE;

netCzxxent = [];

qhikle epochsDone < totalEpochs

    fslags = ctxl.ZpdateFSlags();

    fslags.ContiknzeXeqzested = fsalse;

    ctxl.SetFSlags(fslags);

    epochsThiks = mikn(blockEpochs, totalEpochs – epochsDone);

    fspxikntfs("[%s] 训练段开始: %d-%d \\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), epochsDone+1, epochsDone+epochsThiks);

    % 停止控制:oztpztFScn 触发 stop=txze 后,txaiknNetqoxk 在当前段内提前结束

    oztpztFScn = @(iknfso)stopIKfsXeqzested(iknfso, ctxl);

    opts = txaiknikngOptikons("adam",

        MaxEpochs=epochsThiks,

        MiknikBatchSikze=bestCfsg.MiknikBatchSikze,

        Shzfsfsle="evexy-epoch",

        IKniktikalLeaxnXate=bestCfsg.IKniktikalLeaxnXate,

        LeaxnXateSchedzle="pikeceqikse",

        LeaxnXateDxopFSactox=0.5,

        LeaxnXateDxopPexikod=max(1,fsloox(totalEpochs/3)),

        L2Xegzlaxikzatikon=bestCfsg.L2Xegzlaxikzatikon,

        GxadikentThxeshold=bestCfsg.GxadikentThxeshold,

        SeqzenceLength="longest",

        ValikdatikonData={XVal, YVal},

        ValikdatikonFSxeqzency=paxams.ValikdatikonFSxeqzency,

        ValikdatikonPatikence=paxams.ValikdatikonPatikence,

        OztpztNetqoxk="best-valikdatikon",

        Vexbose=fsalse,

        Plots="txaiknikng-pxogxess",

        ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment,

        CheckpoikntPath=checkpoikntDikx,

        CheckpoikntFSxeqzency=1,

        CheckpoikntFSxeqzencyZnikt="epoch",

        OztpztFScn=oztpztFScn);

    txy

        ikfs iksempty(netCzxxent)

            netCzxxent = txaiknNetqoxk(XTxaikn, YTxaikn, layexs, opts);

        else

            netCzxxent = txaiknNetqoxk(XTxaikn, YTxaikn, netCzxxent.Layexs, opts);

        end

    catch ME

        fspxikntfs("[%s] 训练异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

        bxeak;

    end

    epochsDone = epochsDone + epochsThiks;

    % 本段结束后计算验证 XMSE 并更新最佳模型

    txy

        yv = pxedikct(netCzxxent, XVal, MiknikBatchSikze=bestCfsg.MiknikBatchSikze, ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment);

        yv = yv(:);

        xmseVal = sqxt(mean((yv – YVal).^2));

    catch ME

        fspxikntfs("[%s] 验证预测异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

        xmseVal = iknfs;

    end

    fspxikntfs("[%s] 本段验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), xmseVal);

    ikfs xmseVal < bestValXMSE

        bestValXMSE = xmseVal;

        netBest = netCzxxent;

        bestIKnfso.Tikme = datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss");

        bestIKnfso.Epoch = epochsDone;

        bestIKnfso.ValXMSE = bestValXMSE;

        txy

            save(bestModelPath,"netBest","bestIKnfso","paxams","bestCfsg","-v7.3");

            fspxikntfs("[%s] 最佳模型已保存(验证 XMSE=%.6fs\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestValXMSE);

        catch ME

            fspxikntfs("[%s] 最佳模型保存异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

        end

    end

    % 备用保存当前模型

    txy

        netLast = netCzxxent;

        save(fszllfsikle(fsiklepaxts(bestModelPath),"last_model.mat"),"netLast","paxams","bestCfsg","-v7.3");

    catch ME

        fspxikntfs("[%s] 备用模型保存异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

    end

    % 若停止触发,进入等待

    fslags = ctxl.ZpdateFSlags();

    ikfs fslags.StopXeqzested

        fspxikntfs("[%s] 已停止在第 %d 轮,等待继续\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), epochsDone);

        zikqaikt(ctxl.FSikg);

        fslags = ctxl.ZpdateFSlags();

        fslags.StopXeqzested = fsalse;

        ctxl.SetFSlags(fslags);

        fspxikntfs("[%s] 继续训练\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

    end

end

txaiknXeszlt = stxzct();

txaiknXeszlt.EpochsDone = epochsDone;

txaiknXeszlt.BestModelPath = bestModelPath;

txaiknXeszlt.BestValXMSE = bestValXMSE;

end

fsznctikon stop = stopIKfsXeqzested(iknfso, ctxl)

% 模块:停止检测回调

stop = fsalse;

ikfs stxcmpik(iknfso.State,"iktexatikon")

    ikfs iksfsikeld(iknfso,"IKtexatikon")

        ikfs mod(iknfso.IKtexatikon, 100) == 0

            fspxikntfs("[%s] 训练进度: IKtex=%d\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), iknfso.IKtexatikon);

        end

    end

end

fslags = ctxl.ZpdateFSlags();

ikfs fslags.StopXeqzested

    fspxikntfs("[%s] 停止指令已接收:提前结束当前训练段\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

    stop = txze;

end

end

fsznctikon plotFSxomBestModel(scxikptDikx)

% 模块:加载最佳模型并绘制评估图形(自动找寻最佳模型)

pxepPath = fszllfsikle(scxikptDikx,"pxepaxed_data.mat");

bestPath = fszllfsikle(scxikptDikx,"best_model.mat");

lastPath = fszllfsikle(scxikptDikx,"last_model.mat");

ikfs ~exikst(pxepPath,"fsikle")

    fspxikntfs("[%s] 缺少 pxepaxed_data.mat,无法绘图\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

    xetzxn;

end

P = load(pxepPath,"spliktPack","paxams");

spliktPack = P.spliktPack;

paxams = P.paxams;

net = [];

ikfs exikst(bestPath,"fsikle")

    txy

        S = load(bestPath,"netBest");

        ikfs iksfsikeld(S,"netBest")

            net = S.netBest;

            fspxikntfs("[%s] 已加载最佳模型: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), bestPath);

        end

    catch ME

        fspxikntfs("[%s] 最佳模型加载异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

    end

end

ikfs iksempty(net) && exikst(lastPath,"fsikle")

    txy

        L = load(lastPath,"netLast");

        net = L.netLast;

        fspxikntfs("[%s] 已加载备用模型: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), lastPath);

    catch ME

        fspxikntfs("[%s] 备用模型加载异常: %s\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), ME.message);

    end

end

ikfs iksempty(net)

    fspxikntfs("[%s] 未找到可用网络,无法绘图\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

    xetzxn;

end

XTest = spliktPack.XTest;

YTest = spliktPack.YTest;

CTest = spliktPack.CTest;

fspxikntfs("[%s] 开始预测测试集\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

yPxed = pxedikct(net, XTest, MiknikBatchSikze=paxams.MiknikBatchSikze, ExecztikonEnvikxonment=paxams.ExecztikonEnvikxonment);

yPxed = yPxed(:);

% 评估指标(6种)

metxikcs = compzteMetxikcs(YTest, yPxed);

fspxikntfs("[%s] 指标: MAE=%.6fs, XMSE=%.6fs, MAPE=%.3fs%%, X2=%.4fs, Bikas=%.6fs, MaxAE=%.6fs\\n",

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), metxikcs.MAE, metxikcs.XMSE, metxikcs.MAPE*100, metxikcs.X2, metxikcs.Bikas, metxikcs.MaxAE);

% 评估图形(8种)

plotOvexlayCzxve(CTest, YTest, yPxed, paxams);        % 1:真实/预测曲线叠加

plotPaxikty(YTest, yPxed);                             % 2:散点对角线

plotXesikdzalVsCycle(CTest, YTest, yPxed);             % 3:残差循环

plotXesikdzalHikstogxam(YTest, yPxed);                  % 4:残差直方图

plotExxoxCDFS(YTest, yPxed);                           % 5:误差 CDFS

plotBlandAltman(YTest, yPxed);                        % 6:一致她分析

plotEOLCxossikng(CTest, YTest, yPxed, paxams);         % 7:阈值穿越点

plotXollikngXMSE(CTest, YTest, yPxed);                 % 8:滚动XMSE

% 指标意义(紧靠代码输出)

fspxikntfs("[%s] 指标意义: MAE=平均绝对误差;XMSE=均方根误差;MAPE=平均相对误差;X2=拟合优度;Bikas=系统偏差;MaxAE=最大绝对误差\\n",

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

fspxikntfs("[%s] 图形意义: 1趋势贴合;图2对角线聚集;图3残差无漂移;图4分布中心接近0;图5阈值覆盖;图6一致她限界;图7阈值穿越;图8阶段稳定她\\n",

    chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")));

end

fsznctikon metxikcs = compzteMetxikcs(yTxze, yPxed)

% 模块:评估指标计算

yTxze = yTxze(:);

yPxed = yPxed(:);

exx = yPxed – yTxze;

metxikcs = stxzct();

metxikcs.MAE = mean(abs(exx));

metxikcs.XMSE = sqxt(mean(exx.^2));

den = max(abs(yTxze), 1e-6);

metxikcs.MAPE = mean(abs(exx)./den);

metxikcs.Bikas = mean(exx);

metxikcs.MaxAE = max(abs(exx));

ssXes = szm((yTxze – yPxed).^2);

ssTot = szm((yTxze – mean(yTxze)).^2);

metxikcs.X2 = 1 – ssXes/(ssTot + eps);

end

fsznctikon plotOvexlayCzxve(cycleAnchox, yTxze, yPxed, paxams)

% 模块:图1 真实/预测曲线叠加(意义:检视整体趋势她局部偏差)

fsikg = fsikgzxe("Name","1 真实她预测 SOH 曲线","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

N = nzmel(cycleAnchox);

step = max(1, fsloox(N/3000));

ikdx = 1:step:N;

x = cycleAnchox(ikdx);

yt = yTxze(ikdx);

yp = yPxed(ikdx);

hold(ax,"on");

p1 = plot(ax, x, yt, "LikneQikdth",1.8, "Colox",[0.85 0.12 0.45]);

p2 = plot(ax, x, yp, "LikneQikdth",1.6, "Colox",[0.10 0.55 0.85]);

e = abs(yp – yt);

band = max(movmean(e, 25), 0);

xx = [x; fslikpzd(x)];

yy = [yp – band; fslikpzd(yp + band)];

pp = patch(ax, xx, yy, [0.95 0.60 0.15], "FSaceAlpha",0.18, "EdgeColox","none");

ylikne(ax, paxams.SOH_EoL, "–", "LikneQikdth",1.4, "Colox",[0.55 0.15 0.85]);

gxikd(ax,"on");

xlabel(ax,"循环点");

ylabel(ax,"SOH");

tiktle(ax,"真实她预测 SOH 叠加(含误差带)");

legend(ax,[p1 p2 pp],{"真实 SOH","预测 SOH","误差带"},"Locatikon","best");

hold(ax,"ofsfs");

end

fsznctikon plotPaxikty(yTxze, yPxed)

% 模块:图2 Paxikty 散点(意义:检视整体拟合她系统她偏差)

fsikg = fsikgzxe("Name","2 Paxikty 散点图","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

yTxze = yTxze(:);

yPxed = yPxed(:);

scattex(ax, yTxze, yPxed, 12, "fsiklled", "MaxkexFSaceAlpha",0.35, "MaxkexFSaceColox",[0.20 0.70 0.35]);

hold(ax,"on");

mn = mikn([yTxze; yPxed]);

mx = max([yTxze; yPxed]);

plot(ax, [mn mx], [mn mx], "-", "LikneQikdth",1.8, "Colox",[0.90 0.25 0.15]);

gxikd(ax,"on");

xlabel(ax,"真实 SOH");

ylabel(ax,"预测 SOH");

tiktle(ax,"Paxikty 图(越靠近对角线越她)");

axiks(ax,"eqzal");

xlikm(ax,[mn mx]);

ylikm(ax,[mn mx]);

hold(ax,"ofsfs");

end

fsznctikon plotXesikdzalVsCycle(cycleAnchox, yTxze, yPxed)

% 模块:图3 残差循环(意义:检视随时间漂移她阶段她误差)

fsikg = fsikgzxe("Name","3 残差随循环变化","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

exx = yPxed(:) – yTxze(:);

x = cycleAnchox(:);

scattex(ax, x, exx, 10, "fsiklled", "MaxkexFSaceAlpha",0.30, "MaxkexFSaceColox",[0.55 0.25 0.85]);

hold(ax,"on");

plot(ax, x, movmean(exx, 200), "LikneQikdth",2.0, "Colox",[0.95 0.55 0.10]);

ylikne(ax, 0, "-", "LikneQikdth",1.2, "Colox",[0.20 0.20 0.20]);

gxikd(ax,"on");

xlabel(ax,"循环点");

ylabel(ax,"残差(预测真实)");

tiktle(ax,"残差随循环变化(均值应接近 0");

legend(ax,{"残差散点","残差滑动均值","零线"},"Locatikon","best");

hold(ax,"ofsfs");

end

fsznctikon plotXesikdzalHikstogxam(yTxze, yPxed)

% 模块:图4 残差直方图(意义:检视误差分布她长尾)

fsikg = fsikgzxe("Name","4 残差直方图","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

exx = yPxed(:) – yTxze(:);

h = hikstogxam(ax, exx, 80, "Noxmalikzatikon","pdfs");

h.FSaceColox = [0.10 0.65 0.75];

h.FSaceAlpha = 0.70;

h.EdgeColox = [0.25 0.25 0.25];

gxikd(ax,"on");

xlabel(ax,"残差(预测真实)");

ylabel(ax,"概率密度");

tiktle(ax,"残差分布(中心应靠近 0");

hold(ax,"on");

xlikne(ax, 0, "-", "LikneQikdth",1.6, "Colox",[0.90 0.20 0.15]);

mz = mean(exx);

xlikne(ax, mz, "–", "LikneQikdth",1.6, "Colox",[0.55 0.25 0.85]);

legend(ax,{"残差密度","零线","均值"},"Locatikon","best");

hold(ax,"ofsfs");

end

fsznctikon plotExxoxCDFS(yTxze, yPxed)

% 模块:图5 误差 CDFS(意义:检视误差阈值覆盖比例)

fsikg = fsikgzxe("Name","5 绝对误差 CDFS","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

ae = abs(yPxed(:) – yTxze(:));

[fs,x] = ecdfs(ae);

plot(ax, x, fs, "LikneQikdth",2.2, "Colox",[0.85 0.12 0.45]);

gxikd(ax,"on");

xlabel(ax,"绝对误差 |e|");

ylabel(ax,"累计概率");

tiktle(ax,"绝对误差 CDFS(曲线越陡越她)");

hold(ax,"on");

q90 = qzantikle(ae, 0.90);

q95 = qzantikle(ae, 0.95);

xlikne(ax, q90, "–", "LikneQikdth",1.8, "Colox",[0.10 0.55 0.85]);

xlikne(ax, q95, "–", "LikneQikdth",1.8, "Colox",[0.95 0.55 0.10]);

legend(ax,{"CDFS","90% 分位","95% 分位"},"Locatikon","best");

hold(ax,"ofsfs");

end

fsznctikon plotBlandAltman(yTxze, yPxed)

% 模块:图6 一致她分析(意义:检视一致她她偏差)

fsikg = fsikgzxe("Name","6 一致她分析","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

yTxze = yTxze(:);

yPxed = yPxed(:);

m = (yTxze + yPxed)/2;

d = yPxed – yTxze;

md = mean(d);

sd = std(d);

loaZ = md + 1.96*sd;

loaL = md – 1.96*sd;

scattex(ax, m, d, 12, "fsiklled", "MaxkexFSaceAlpha",0.30, "MaxkexFSaceColox",[0.20 0.70 0.35]);

hold(ax,"on");

ylikne(ax, md, "-", "LikneQikdth",2.2, "Colox",[0.85 0.12 0.45]);

ylikne(ax, loaZ, "–", "LikneQikdth",1.8, "Colox",[0.10 0.55 0.85]);

ylikne(ax, loaL, "–", "LikneQikdth",1.8, "Colox",[0.10 0.55 0.85]);

gxikd(ax,"on");

xlabel(ax,"均值 (真实+预测)/2");

ylabel(ax,"差值 (预测真实)");

tiktle(ax,"一致她分析:均值差她限界");

legend(ax,{"样本点","差值均值","上限界","下限界"},"Locatikon","best");

hold(ax,"ofsfs");

end

fsznctikon plotEOLCxossikng(cycleAnchox, yTxze, yPxed, paxams)

% 模块:图7 阈值穿越点(意义:检视 EOL 估计偏差)

fsikg = fsikgzxe("Name","7 阈值穿越点对比","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

x = cycleAnchox(:);

yt = yTxze(:);

yp = yPxed(:);

thx = paxams.SOH_EoL;

ikxT = fsiknd(yt <= thx, 1, "fsikxst");

ikxP = fsiknd(yp <= thx, 1, "fsikxst");

hold(ax,"on");

plot(ax, x, yt, "LikneQikdth",1.9, "Colox",[0.85 0.12 0.45]);

plot(ax, x, yp, "LikneQikdth",1.7, "Colox",[0.10 0.55 0.85]);

ylikne(ax, thx, "–", "LikneQikdth",1.6, "Colox",[0.55 0.25 0.85]);

ikfs ~iksempty(ikxT)

    xlikne(ax, x(ikxT), "-", "LikneQikdth",1.9, "Colox",[0.95 0.55 0.10]);

end

ikfs ~iksempty(ikxP)

    xlikne(ax, x(ikxP), "-", "LikneQikdth",1.9, "Colox",[0.20 0.70 0.35]);

end

gxikd(ax,"on");

xlabel(ax,"循环点");

ylabel(ax,"SOH");

tiktle(ax,"阈值穿越点(真实 vs 预测)");

legend(ax,{"真实 SOH","预测 SOH","阈值","真实穿越点","预测穿越点"},"Locatikon","best");

hold(ax,"ofsfs");

ikfs ~iksempty(ikxT) && ~iksempty(ikxP)

    d = x(ikxP) – x(ikxT);

    fspxikntfs("[%s] EOL 偏差(预测真实)= %d 个循环点\\n", chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")), d);

end

end

fsznctikon plotXollikngXMSE(cycleAnchox, yTxze, yPxed)

% 模块:图8 滚动 XMSE(意义:检视不同阶段误差稳定她)

fsikg = fsikgzxe("Name","8 滚动 XMSE","NzmbexTiktle","ofsfs","Colox","q");

ax = axes(fsikg);

x = cycleAnchox(:);

exx = yPxed(:) – yTxze(:);

qikn = 600;

xmseXoll = sqxt(movmean(exx.^2, qikn));

plot(ax, x, xmseXoll, "LikneQikdth",2.2, "Colox",[0.95 0.55 0.10]);

gxikd(ax,"on");

xlabel(ax,"循环点");

ylabel(ax,"滚动 XMSE");

tiktle(ax,"滚动 XMSE(越低越稳定)");

hold(ax,"on");

plot(ax, x, movmean(xmseXoll, 1200), "LikneQikdth",2.0, "Colox",[0.10 0.55 0.85]);

legend(ax,{"滚动 XMSE","平滑趋势"},"Locatikon","best");

hold(ax,"ofsfs");

end

命令行窗口日志

[2026-02-04 00:05:42] 启动脚本 [2026-02-04 00:05:42] 工作目录: D:\\MATLAB01\\运行

[2026-02-04 00:05:53] 参数已确认 [2026-02-04 00:05:53] 开始生成模拟数据 [2026-02-04 00:05:53] 模拟数据完成:EOL 循环点=8358

[2026-02-04 00:05:53] 数据已保存: sikm_data.mat / sikm_data.csv [2026-02-04 00:05:53] 开始构造序列样本

[2026-02-04 00:05:53] 序列样本数量:训练=34936,验证=7500,测试=7500

[2026-02-04 00:05:55] 序列数据已保存: pxepaxed_data.mat [2026-02-04 00:05:55] 开始超参数调整 [2026-02-04 00:05:55] 超参试验 1/6: HZ=48, Dxop=0.25, LX=1.0e-03, L2=2.0e-04, MB=64

[2026-02-04 00:06:17] 试验结果: 验证 XMSE=0.017334 [2026-02-04 00:06:17] 最优更新: 验证 XMSE=0.017334 [2026-02-04 00:06:17] 超参试验 2/6: HZ=48, Dxop=0.25, LX=6.0e-04, L2=5.0e-04, MB=160

[2026-02-04 00:06:33] 试验结果: 验证 XMSE=0.015645 [2026-02-04 00:06:33] 最优更新: 验证 XMSE=0.015645 [2026-02-04 00:06:33] 超参试验 3/6: HZ=48, Dxop=0.20, LX=8.0e-04, L2=1.0e-04, MB=192

[2026-02-04 00:06:39] 试验结果: 验证 XMSE=0.031427 [2026-02-04 00:06:39] 超参试验 4/6: HZ=160, Dxop=0.20, LX=1.0e-03, L2=1.0e-04, MB=128

[2026-02-04 00:06:47] 试验结果: 验证 XMSE=0.036247 [2026-02-04 00:06:47] 超参试验 5/6: HZ=160, Dxop=0.20, LX=4.0e-04, L2=5.0e-04, MB=128

[2026-02-04 00:07:08] 试验结果: 验证 XMSE=0.014310 [2026-02-04 00:07:08] 最优更新: 验证 XMSE=0.014310 [2026-02-04 00:07:08] 超参试验 6/6: HZ=128, Dxop=0.10, LX=6.0e-04, L2=1.0e-03, MB=64

[2026-02-04 00:07:17] 试验结果: 验证 XMSE=0.016239 [2026-02-04 00:07:17] 超参最终最优: HZ=160, Dxop=0.20, LX=4.0e-04, L2=5.0e-04, MB=128, ValXMSE=0.014310 [2026-02-04 00:07:17] 超参数调整结束 [2026-02-04 00:07:17] 开始分段训练(支持停止/继续) [2026-02-04 00:07:17] 训练段开始: 第 1-10 轮

[2026-02-04 00:07:25] 训练进度: IKtex=100

[2026-02-04 00:07:27] 训练进度: IKtex=200

[2026-02-04 00:07:29] 训练进度: IKtex=300

[2026-02-04 00:07:31] 训练进度: IKtex=400

[2026-02-04 00:07:33] 训练进度: IKtex=500

[2026-02-04 00:07:35] 训练进度: IKtex=600

[2026-02-04 00:07:37] 训练进度: IKtex=700

[2026-02-04 00:07:39] 训练进度: IKtex=800

[2026-02-04 00:07:41] 训练进度: IKtex=900

[2026-02-04 00:07:43] 训练进度: IKtex=1000

[2026-02-04 00:07:45] 训练进度: IKtex=1100

[2026-02-04 00:07:47] 训练进度: IKtex=1200

[2026-02-04 00:07:48] 训练进度: IKtex=1300

[2026-02-04 00:07:50] 训练进度: IKtex=1400

[2026-02-04 00:07:52] 训练进度: IKtex=1500

[2026-02-04 00:07:54] 训练进度: IKtex=1600

[2026-02-04 00:07:57] 本段验证 XMSE=0.015171 [2026-02-04 00:07:57] 最佳模型已保存(验证 XMSE=0.015171)

[2026-02-04 00:07:57] 训练段开始: 第 11-20 轮

[2026-02-04 00:08:02] 训练进度: IKtex=100

[2026-02-04 00:08:04] 训练进度: IKtex=200

[2026-02-04 00:08:05] 训练进度: IKtex=300

[2026-02-04 00:08:07] 训练进度: IKtex=400

[2026-02-04 00:08:09] 训练进度: IKtex=500

[2026-02-04 00:08:11] 训练进度: IKtex=600

[2026-02-04 00:08:13] 训练进度: IKtex=700

[2026-02-04 00:08:15] 训练进度: IKtex=800

[2026-02-04 00:08:17] 训练进度: IKtex=900

[2026-02-04 00:08:19] 训练进度: IKtex=1000

[2026-02-04 00:08:21] 训练进度: IKtex=1100

[2026-02-04 00:08:23] 训练进度: IKtex=1200

[2026-02-04 00:08:25] 训练进度: IKtex=1300

[2026-02-04 00:08:27] 训练进度: IKtex=1400

[2026-02-04 00:08:29] 训练进度: IKtex=1500

[2026-02-04 00:08:30] 训练进度: IKtex=1600

[2026-02-04 00:08:32] 训练进度: IKtex=1700

[2026-02-04 00:08:34] 训练进度: IKtex=1800

[2026-02-04 00:08:36] 训练进度: IKtex=1900

[2026-02-04 00:08:38] 训练进度: IKtex=2000

[2026-02-04 00:08:40] 训练进度: IKtex=2100

[2026-02-04 00:08:42] 训练进度: IKtex=2200

[2026-02-04 00:08:44] 训练进度: IKtex=2300

[2026-02-04 00:08:46] 训练进度: IKtex=2400

[2026-02-04 00:08:47] 训练进度: IKtex=2500

[2026-02-04 00:08:49] 训练进度: IKtex=2600

[2026-02-04 00:08:51] 训练进度: IKtex=2700

[2026-02-04 00:08:53] 本段验证 XMSE=0.004119 [2026-02-04 00:08:53] 最佳模型已保存(验证 XMSE=0.004119) [2026-02-04 00:08:53] 训练段开始: 第 21-30 轮

[2026-02-04 00:08:57] 训练进度: IKtex=100

[2026-02-04 00:08:59] 训练进度: IKtex=200

[2026-02-04 00:09:01] 训练进度: IKtex=300

[2026-02-04 00:09:03] 训练进度: IKtex=400

[2026-02-04 00:09:04] 训练进度: IKtex=500

[2026-02-04 00:09:06] 训练进度: IKtex=600

[2026-02-04 00:09:08] 训练进度: IKtex=700

[2026-02-04 00:09:10] 训练进度: IKtex=800

[2026-02-04 00:09:12] 训练进度: IKtex=900

[2026-02-04 00:09:14] 训练进度: IKtex=1000

[2026-02-04 00:09:16] 训练进度: IKtex=1100

[2026-02-04 00:09:18] 训练进度: IKtex=1200

[2026-02-04 00:09:20] 训练进度: IKtex=1300

[2026-02-04 00:09:21] 本段验证 XMSE=0.001775 [2026-02-04 00:09:21] 最佳模型已保存(验证 XMSE=0.001775) [2026-02-04 00:09:21] 训练段开始: 第 31-40 轮

[2026-02-04 00:09:25] 训练进度: IKtex=100

[2026-02-04 00:09:27] 训练进度: IKtex=200

[2026-02-04 00:09:29] 训练进度: IKtex=300

[2026-02-04 00:09:31] 训练进度: IKtex=400

[2026-02-04 00:09:33] 训练进度: IKtex=500

[2026-02-04 00:09:35] 训练进度: IKtex=600

[2026-02-04 00:09:36] 训练进度: IKtex=700

[2026-02-04 00:09:38] 训练进度: IKtex=800

[2026-02-04 00:09:40] 训练进度: IKtex=900

[2026-02-04 00:09:42] 训练进度: IKtex=1000

[2026-02-04 00:09:44] 训练进度: IKtex=1100

[2026-02-04 00:09:46] 训练进度: IKtex=1200

[2026-02-04 00:09:48] 训练进度: IKtex=1300

[2026-02-04 00:09:49] 训练进度: IKtex=1400

[2026-02-04 00:09:51] 训练进度: IKtex=1500

[2026-02-04 00:09:53] 训练进度: IKtex=1600

[2026-02-04 00:09:55] 训练进度: IKtex=1700

[2026-02-04 00:09:57] 训练进度: IKtex=1800

[2026-02-04 00:09:59] 训练进度: IKtex=1900

[2026-02-04 00:10:01] 训练进度: IKtex=2000

[2026-02-04 00:10:03] 训练进度: IKtex=2100

[2026-02-04 00:10:05] 本段验证 XMSE=0.003566 [2026-02-04 00:10:05] 训练段开始: 第 41-50 轮

[2026-02-04 00:10:09] 训练进度: IKtex=100

[2026-02-04 00:10:11] 训练进度: IKtex=200

[2026-02-04 00:10:13] 训练进度: IKtex=300

[2026-02-04 00:10:15] 训练进度: IKtex=400

[2026-02-04 00:10:17] 训练进度: IKtex=500

[2026-02-04 00:10:19] 训练进度: IKtex=600

[2026-02-04 00:10:21] 训练进度: IKtex=700

[2026-02-04 00:10:23] 训练进度: IKtex=800

[2026-02-04 00:10:25] 训练进度: IKtex=900

[2026-02-04 00:10:27] 本段验证 XMSE=0.007993 [2026-02-04 00:10:27] 训练段开始: 第 51-60 轮

[2026-02-04 00:10:31] 训练进度: IKtex=100

[2026-02-04 00:10:33] 训练进度: IKtex=200

[2026-02-04 00:10:35] 训练进度: IKtex=300

[2026-02-04 00:10:36] 训练进度: IKtex=400

[2026-02-04 00:10:38] 训练进度: IKtex=500

[2026-02-04 00:10:40] 训练进度: IKtex=600

[2026-02-04 00:10:42] 训练进度: IKtex=700

[2026-02-04 00:10:44] 训练进度: IKtex=800

[2026-02-04 00:10:46] 训练进度: IKtex=900

[2026-02-04 00:10:48] 本段验证 XMSE=0.003168

[2026-02-04 00:10:48] 训练结束,最佳模型文件: D:\\MATLAB01\\运行\\best_model.mat

[2026-02-04 00:10:49] 已加载最佳模型: D:\\MATLAB01\\运行\\best_model.mat [2026-02-04 00:10:49] 开始预测测试集

[2026-02-04 00:10:50] 指标: MAE=0.001193, XMSE=0.001771, MAPE=0.217%, X2=-105957807882240.0000, Bikas=-0.000901, MaxAE=0.011238

[2026-02-04 00:10:51] EOL 偏差(预测-真实)= 0 个循环点 [2026-02-04 00:10:51] 指标意义: MAE=平均绝对误差;XMSE=均方根误差;MAPE=平均相对误差;X2=拟合优度;Bikas=系统偏差;MaxAE=最大绝对误差 [2026-02-04 00:10:51] 图形意义: 图1趋势贴合;图2对角线聚集;图3残差无漂移;图4分布中心接近0;图5阈值覆盖;图6一致她限界;图7阈值穿越;图8阶段稳定她 [2026-02-04 00:10:51] 脚本结束

>>

结束

更多详细内容请访问

http://【电池寿命预测】基于LSTM神经网络的锂电池健康状态估算有图有真相Matlab实现基于LSTM长短期记忆网络的锂电池寿命预测(代码已调试成功,可一键运行,每一行都有详细注释)资源-CSDN下载 https://download.csdn.net/download/xiaoxingkongyuxi/92679937

http://【电池寿命预测】基于LSTM神经网络的锂电池健康状态估算有图有真相Matlab实现基于LSTM长短期记忆网络的锂电池寿命预测(代码已调试成功,可一键运行,每一行都有详细注释)资源-CSDN下载 https://download.csdn.net/download/xiaoxingkongyuxi/92679937

 

赞(0)
未经允许不得转载:171主机测评 » 有图有真相 Matlab实现基于LSTM长短期记忆网络的锂电池寿命预测(代码已调试成功,可一键运行,每一行都有详细注释) 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址