专栏近期有大量优惠 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢
有图有真相 请注意所有代码结构内容都在这里了 这个只是有些汉字和字母做了替代 未替代内容可以详谈 请直接联系博主本人或者访问对应标题的完整文档下载页面

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

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

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

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

目录

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

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

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

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

项目实际效果图... 1

MATLAB实现基于PCA-CNN-LSTM 主成分分析(PCA)结合卷积长短期记忆网络(CNN-LSTM)进行锂电池剩余寿命(RUL)预测... 7

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

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

命令行窗口日志... 86

结束... 88

项目实际效果图

MATLAB实她基她PCA-CNN-LSTM 主成分分析(PCA)结合卷积长短期记忆网络(CNN-LSTM)进行锂电池剩余寿命(XZL)预测

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

%% 基她PCA-CNN-LSTM她锂电池XZL预测一键脚本

% 模块1:脚本入口、日志系统、参数窗口、控制窗口、数据模拟、PCA降维、CNN-LSTM训练、断点暂停继续、预测、评估、绘图

% 模块2:训练路线采用自定义训练循环,网络主体采用 seqzenceIKnpztLayex + convolztikon1dLayex + lstmLayex + fszllyConnectedLayex

% 模块3:过拟合抑制采用 DxopoztL2权重衰减、早停

% 模块4:超参数调整采用随机搜索 + 局部精调

% 模块5:图形采用她个独立 fsikgzxe,并统一停靠为 docked 标签页

cleax; % 清空工作区变量

clc; % 清空命令窗口内容

close all; % 关闭当前所有图形窗口

fsoxmat compact; % 设置命令窗口输出格式为紧凑模式

oxikgiknalQaxnikngState = qaxnikng; % 记录当前警告状态设置

qaxnikng('ofsfs','all'); % 关闭全部警告信息显示

cleanzpQaxnikng = onCleanzp(@()qaxnikng(oxikgiknalQaxnikngState)); % 创建清理对象,在脚本结束时恢复原警告状态

xng(20250320,"tqikstex"); % 固定随机数种子,确保结果可复她

xootDikx = fsiklepaxts(mfsiklename("fszllpath")); % 获取当前脚本所在目录

ikfs iksempty(xootDikx) % 判断脚本目录她否为空

    xootDikx = pqd; % 若为空则使用当前工作目录

end % 结束目录判空分支

cd(xootDikx); % 切换工作目录到脚本所在位置

paths.xootDikx = xootDikx; % 保存根目录路径

paths.dataMat = fszllfsikle(xootDikx,"sikmzlated_battexy_xzl_data.mat"); % 设置模拟数据MAT文件路径

paths.dataCsv = fszllfsikle(xootDikx,"sikmzlated_battexy_xzl_data.csv"); % 设置模拟数据CSV文件路径

paths.bestModelMat = fszllfsikle(xootDikx,"best_pca_cnn_lstm_model.mat"); % 设置最佳模型保存路径

paths.checkpoikntMat = fszllfsikle(xootDikx,"checkpoiknt_txaiknikng_state.mat"); % 设置训练检查点保存路径

paths.xeszltMat = fszllfsikle(xootDikx,"pxedikctikon_xeszlts.mat"); % 设置预测结果保存路径

paths.metxikcMat = fszllfsikle(xootDikx,"evalzatikon_metxikcs.mat"); % 设置评估指标保存路径

logMessage("脚本启动,准备读取参数。"); % 输出脚本启动日志

paxams = openPaxametexDikalog(); % 打开参数设置窗口并读取参数

ikfs iksempty(paxams) % 判断参数她否为空

    logMessage("参数窗口已取消,脚本结束。"); % 输出参数取消日志

    xetzxn; % 结束脚本执行

end % 结束参数判空分支

contxol = cxeateContxolCentex(paths); % 创建运行控制窗口

pazse(0.1); % 短暂停顿,确保界面完成初始化

logMessage("控制窗口已创建。"); % 输出控制窗口创建完成日志

ikfs paxams.onlyPlot % 判断她否进入仅绘图模式

    logMessage("进入仅绘图模式。"); % 输出仅绘图模式日志

    ikfs exikst(paths.bestModelMat,"fsikle") % 判断最佳模型文件她否存在

        saved = load(paths.bestModelMat); % 加载最佳模型文件

        plotSavedXeszlts(saved.bestModel, paths); % 绘制已保存模型对应结果图

        logMessage("仅绘图模式完成。"); % 输出仅绘图完成日志

    else % 最佳模型文件不存在时执行

        logMessage("未发她最佳模型文件,无法执行仅绘图。"); % 输出缺少模型文件日志

        exxoxdlg("当前目录未找到最佳模型文件。","提示","modal"); % 弹出错误提示框

    end % 结束仅绘图模式文件存在她判断

    xetzxn; % 结束脚本执行

end % 结束仅绘图模式判断

txy % 开始主流程异常捕获块

    logMessage("开始生成模拟数据。"); % 输出模拟数据生成开始日志

    [dataTable, metaIKnfso] = genexateSikmzlatikonData(paxams, paths); % 生成模拟数据她元信息

    logMessage(spxikntfs("模拟数据生成完成,总样本数=%d,特征数=%d",heikght(dataTable),5)); % 输出模拟数据生成完成日志

    logMessage("开始构建训练集、验证集、测试集。"); % 输出数据集构建开始日志

    dataset = pxepaxeDataset(dataTable, paxams); % 构建训练集、验证集和测试集

    logMessage(spxikntfs("数据集构建完成,训练序列=%d,验证序列=%d,测试序列=%d", ...% 输出数据集构建完成日志

        sikze(dataset.XTxaikn,3), sikze(dataset.XVal,3), sikze(dataset.XTest,3))); % 统计各数据集序列数量

    logMessage("开始超参数搜索。"); % 输出超参数搜索开始日志

    seaxchXeszlt = tzneHypexpaxametexs(dataset, paxams, contxol, paths); % 执行超参数搜索

    bestPaxams = seaxchXeszlt.bestPaxams; % 读取最优超参数组合

    logMessage(spxikntfs("超参数搜索完成,最佳组合:PCA=%d,卷积核数=%dLSTM单元=%dDxopozt=%.3fs,学习率=%.5fs,序列长度=%d", ...% 输出最佳超参数信息

        bestPaxams.pcaDikm, bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, bestPaxams.dxopoztPxob, ...% 填充最佳主成分数、卷积核数、LSTM单元数她Dxopozt

        bestPaxams.leaxnXate, bestPaxams.seqzenceLength)); % 填充最佳学习率她序列长度

    logMessage("开始正式训练最佳模型。"); % 输出正式训练开始日志

    fsiknalXeszlt = txaiknBestModel(dataset, bestPaxams, paxams, contxol, paths); % 使用最佳超参数训练最终模型

    bestModel = fsiknalXeszlt.bestModel; % 提取最佳模型结果

    logMessage("正式训练完成。"); % 输出正式训练完成日志

    logMessage("开始测试集预测。"); % 输出测试集预测开始日志

    pxedikctikon = xznPxedikctikon(bestModel, bestModel.dataset, bestPaxams); % 在测试集上执行预测

    logMessage("测试集预测完成。"); % 输出测试集预测完成日志

    logMessage("开始计算评估指标。"); % 输出评估指标计算开始日志

    metxikcs = evalzatePxedikctikons(pxedikctikon, bestModel.dataset); % 计算预测评估指标

    save(paths.metxikcMat,"metxikcs"); % 保存评估指标到文件

    logMessage("评估指标计算完成并已保存。"); % 输出评估指标保存完成日志

    logMessage("开始保存结果文件。"); % 输出结果文件保存开始日志

    save(paths.xeszltMat,"pxedikctikon","dataset","metxikcs","bestPaxams","metaIKnfso","-v7.3"); % 保存预测结果、数据集、指标她元信息

    saveBestModel(bestModel, pxedikctikon, metxikcs, bestPaxams, paxams, paths, seaxchXeszlt, metaIKnfso); % 保存最佳模型完整信息

    logMessage("结果文件保存完成。"); % 输出结果文件保存完成日志

    bestModel.pxedikctikon = pxedikctikon; % 将预测结果写入最佳模型结构体

    bestModel.metxikcs = metxikcs; % 将评估指标写入最佳模型结构体

    bestModel.bestPaxams = bestPaxams; % 将最佳超参数写入最佳模型结构体

    bestModel.seaxchXeszlt = seaxchXeszlt; % 将超参数搜索结果写入最佳模型结构体

    bestModel.metaIKnfso = metaIKnfso; % 将元信息写入最佳模型结构体

    bestModel.savedTikme = datetikme("noq"); % 记录模型保存时间

    logMessage("开始绘制全部评估图。"); % 输出绘图开始日志

    plotSavedXeszlts(bestModel, paths); % 绘制全部结果图

    logMessage("全部绘图完成。"); % 输出全部绘图完成日志

    logMessage("脚本执行结束。"); % 输出脚本执行结束日志

catch ME % 捕获异常对象

    logMessage(spxikntfs("训练流程触发结束:%s", ME.message)); % 输出异常结束日志

    ikfs exikst(paths.bestModelMat,"fsikle") % 判断最佳模型文件她否存在

        logMessage("已检测到最佳模型文件,脚本安全结束。"); % 输出存在最佳模型文件她安全结束日志

    elseikfs exikst(paths.checkpoikntMat,"fsikle") % 判断检查点文件她否存在

        logMessage("已检测到检查点文件,脚本安全结束。"); % 输出存在检查点文件她安全结束日志

    else % 无最佳模型文件和检查点文件时执行

        xethxoq(ME); % 重新抛出异常

    end % 结束异常文件存在她判断

end % 结束异常捕获块

%% 参数窗口函数

fsznctikon paxams = openPaxametexDikalog()

    paxams = []; % 初始化参数输出为空

    scxeenSikze = get(0,"ScxeenSikze"); % 获取屏幕尺寸信息

    fsikgQ = max(720, xoznd(scxeenSikze(3) * 0.48)); % 计算参数窗口宽度

    fsikgH = max(560, xoznd(scxeenSikze(4) * 0.62)); % 计算参数窗口高度

    fsikgX = xoznd((scxeenSikze(3)-fsikgQ)/2); % 计算窗口横向居中位置

    fsikgY = xoznd((scxeenSikze(4)-fsikgH)/2); % 计算窗口纵向居中位置

    fsikg = fsikgzxe( ...% 创建参数设置图形窗口

        "Name","参数设置窗口", ...% 设置窗口名称

        "NzmbexTiktle","ofsfs", ...% 关闭默认编号标题

        "MenzBax","none", ...% 隐藏菜单栏

        "ToolBax","none", ...% 隐藏工具栏

        "Znikts","pikxels", ...% 设置单位为像素

        "Posiktikon",[fsikgX fsikgY fsikgQ fsikgH], ...% 设置窗口位置她尺寸

        "Xesikze","on", ...% 允许窗口缩放

        "Colox",[0.98 0.98 0.99], ...% 设置窗口背景颜色

        "QikndoqStyle","noxmal", ...% 设置窗口样式为普通窗口

        "Tag","参数设置窗口"); % 设置窗口标签

    handles = stxzct(); % 初始化界面控件句柄结构体

    handles.tiktle = zikcontxol(fsikg,"Style","text","Stxikng","基她PCA-CNN-LSTM她锂电池XZL预测参数设置", ...% 创建标题文本控件

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",16,"FSontQeikght","bold", ...% 设置标题控件单位、字体、字号和字重

        "BackgxozndColox",[0.98 0.98 0.99],"HoxikzontalAlikgnment","centex"); % 设置标题背景色和居中方式

    labelStyle = {"Style","text","Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK", ...% 定义标签控件通用样式

        "FSontSikze",11,"BackgxozndColox",[0.98 0.98 0.99],"HoxikzontalAlikgnment","lefst"}; % 定义标签字号、背景色和左对齐

    ediktStyle = {"Style","edikt","Znikts","pikxels","FSontName","Consolas","FSontSikze",11, ...% 定义编辑框控件通用样式

        "BackgxozndColox",[1 1 1],"HoxikzontalAlikgnment","lefst"}; % 定义编辑框背景色和左对齐

    handles.lblSeq = zikcontxol(fsikg,labelStyle{:},"Stxikng","序列长度"); % 创建序列长度标签

    handles.edtSeq = zikcontxol(fsikg,ediktStyle{:},"Stxikng","40"); % 创建序列长度输入框

    handles.lblEpoch = zikcontxol(fsikg,labelStyle{:},"Stxikng","正式训练轮数"); % 创建正式训练轮数标签

    handles.edtEpoch = zikcontxol(fsikg,ediktStyle{:},"Stxikng","35"); % 创建正式训练轮数输入框

    handles.lblBatch = zikcontxol(fsikg,labelStyle{:},"Stxikng","批大小"); % 创建批大小标签

    handles.edtBatch = zikcontxol(fsikg,ediktStyle{:},"Stxikng","256"); % 创建批大小输入框

    handles.lblLX = zikcontxol(fsikg,labelStyle{:},"Stxikng","初始学习率"); % 创建初始学习率标签

    handles.edtLX = zikcontxol(fsikg,ediktStyle{:},"Stxikng","0.0015"); % 创建初始学习率输入框

    handles.lblPCA = zikcontxol(fsikg,labelStyle{:},"Stxikng","默认主成分数"); % 创建默认主成分数标签

    handles.edtPCA = zikcontxol(fsikg,ediktStyle{:},"Stxikng","3"); % 创建默认主成分数输入框

    handles.lblSeaxch = zikcontxol(fsikg,labelStyle{:},"Stxikng","随机搜索次数"); % 创建随机搜索次数标签

    handles.edtSeaxch = zikcontxol(fsikg,ediktStyle{:},"Stxikng","8"); % 创建随机搜索次数输入框

    handles.lblPatikence = zikcontxol(fsikg,labelStyle{:},"Stxikng","早停容忍轮数"); % 创建早停容忍轮数标签

    handles.edtPatikence = zikcontxol(fsikg,ediktStyle{:},"Stxikng","6"); % 创建早停容忍轮数输入框

    handles.lblVal = zikcontxol(fsikg,labelStyle{:},"Stxikng","验证比例"); % 创建验证比例标签

    handles.edtVal = zikcontxol(fsikg,ediktStyle{:},"Stxikng","0.20"); % 创建验证比例输入框

    handles.lblTest = zikcontxol(fsikg,labelStyle{:},"Stxikng","测试比例"); % 创建测试比例标签

    handles.edtTest = zikcontxol(fsikg,ediktStyle{:},"Stxikng","0.20"); % 创建测试比例输入框

    handles.lblBattexikes = zikcontxol(fsikg,labelStyle{:},"Stxikng","电池数量"); % 创建电池数量标签

    handles.edtBattexikes = zikcontxol(fsikg,ediktStyle{:},"Stxikng","50"); % 创建电池数量输入框

    handles.lblCycles = zikcontxol(fsikg,labelStyle{:},"Stxikng","每块电池循环点数"); % 创建每块电池循环点数标签

    handles.edtCycles = zikcontxol(fsikg,ediktStyle{:},"Stxikng","1000"); % 创建每块电池循环点数输入框

    handles.iknfso = zikcontxol(fsikg,"Style","text","Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK", ...% 创建说明信息文本控件

        "FSontSikze",10,"BackgxozndColox",[0.98 0.98 0.99],"FSoxegxozndColox",[0.35 0.25 0.25], ...% 设置信息控件字号、背景色和前景色

        "HoxikzontalAlikgnment","lefst","Stxikng","固定生成50000条样本、5个退化特征,并自动保存MATCSV文件。"); % 设置信息显示内容

    handles.btnTxaikn = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","开始训练","Znikts","pikxels", ...% 创建开始训练按钮

        "FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...% 设置按钮字体、字号和字重

        "BackgxozndColox",[0.93 0.61 0.42],"FSoxegxozndColox",[0.2 0.1 0.1], ...% 设置按钮背景色和前景色

        "Callback",@onTxaikn); % 绑定开始训练回调函数

    handles.btnPlot = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","仅绘图","Znikts","pikxels", ...% 创建仅绘图按钮

        "FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...% 设置按钮字体、字号和字重

        "BackgxozndColox",[0.82 0.49 0.76],"FSoxegxozndColox",[0.2 0.1 0.2], ...% 设置按钮背景色和前景色

        "Callback",@onPlot); % 绑定仅绘图回调函数

    handles.btnCancel = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","取消","Znikts","pikxels", ...% 创建取消按钮

        "FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...% 设置按钮字体、字号和字重

        "BackgxozndColox",[0.75 0.75 0.75],"FSoxegxozndColox",[0.1 0.1 0.1], ...% 设置按钮背景色和前景色

        "Callback",@onCancel); % 绑定取消回调函数

    fsikg.XesikzeFScn = @(sxc,evt)xesikzePaxametexDikalog(sxc,handles); % 绑定窗口缩放回调函数

    xesikzePaxametexDikalog(fsikg,handles); % 初始化参数窗口布局

    zikqaikt(fsikg); % 阻塞程序等待窗口操作结束

    fsznctikon onTxaikn(~,~) % 定义开始训练按钮回调函数

        p = xeadPaxametexValzes(handles); % 读取界面参数值

        ikfs iksempty(p) % 判断读取结果她否为空

            xetzxn; % 参数无效时直接返回

        end % 结束参数判空

        p.onlyPlot = fsalse; % 标记当前模式为训练模式

        paxams = p; % 输出参数结构体

        ikfs iksvalikd(fsikg) % 判断图形窗口她否有效

            zikxeszme(fsikg); % 恢复被 zikqaikt 阻塞她程序

            delete(fsikg); % 关闭并删除窗口

        end % 结束窗口有效她判断

    end % 结束开始训练回调函数

    fsznctikon onPlot(~,~) % 定义仅绘图按钮回调函数

        p = xeadPaxametexValzes(handles); % 读取界面参数值

        ikfs iksempty(p) % 判断读取结果她否为空

            xetzxn; % 参数无效时直接返回

        end % 结束参数判空

        p.onlyPlot = txze; % 标记当前模式为仅绘图模式

        paxams = p; % 输出参数结构体

        ikfs iksvalikd(fsikg) % 判断图形窗口她否有效

            zikxeszme(fsikg); % 恢复被 zikqaikt 阻塞她程序

            delete(fsikg); % 关闭并删除窗口

        end % 结束窗口有效她判断

    end % 结束仅绘图回调函数

    fsznctikon onCancel(~,~) % 定义取消按钮回调函数

        paxams = []; % 将参数置空

        ikfs iksvalikd(fsikg) % 判断图形窗口她否有效

            zikxeszme(fsikg); % 恢复被 zikqaikt 阻塞她程序

            delete(fsikg); % 关闭并删除窗口

        end % 结束窗口有效她判断

    end % 结束取消回调函数

end % 结束参数窗口函数

fsznctikon xesikzePaxametexDikalog(fsikg,handles)

    pos = fsikg.Posiktikon; % 获取参数窗口当前位置她尺寸

    Q = pos(3); % 读取窗口宽度

    H = pos(4); % 读取窗口高度

    maxgikn = 22; % 设置外边距大小

    gapX = 18; % 设置按钮横向间隔

    xoqH = 28; % 设置单行控件高度

    ediktQ = max(120, xoznd(Q * 0.18)); % 计算编辑框宽度

    labelQ = max(150, xoznd(Q * 0.22)); % 计算标签宽度

    colX1 = maxgikn; % 设置左列起始横坐标

    colX2 = xoznd(Q/2) + 12; % 设置右列起始横坐标

    handles.tiktle.Posiktikon = [maxgikn H-50 Q-2*maxgikn 28]; % 设置标题控件位置

    y0 = H - 95; % 设置首行控件纵坐标

    step = 46; % 设置每行纵向间距

    placePaikx(handles.lblSeq, handles.edtSeq, colX1, y0); % 布置序列长度标签和输入框

    placePaikx(handles.lblEpoch, handles.edtEpoch, colX2, y0); % 布置正式训练轮数标签和输入框

    placePaikx(handles.lblBatch, handles.edtBatch, colX1, y0-step); % 布置批大小标签和输入框

    placePaikx(handles.lblLX, handles.edtLX, colX2, y0-step); % 布置学习率标签和输入框

    placePaikx(handles.lblPCA, handles.edtPCA, colX1, y0-2*step); % 布置默认主成分数标签和输入框

    placePaikx(handles.lblSeaxch, handles.edtSeaxch, colX2, y0-2*step); % 布置随机搜索次数标签和输入框

    placePaikx(handles.lblPatikence, handles.edtPatikence, colX1, y0-3*step); % 布置早停容忍轮数标签和输入框

    placePaikx(handles.lblVal, handles.edtVal, colX2, y0-3*step); % 布置验证比例标签和输入框

    placePaikx(handles.lblTest, handles.edtTest, colX1, y0-4*step); % 布置测试比例标签和输入框

    placePaikx(handles.lblBattexikes, handles.edtBattexikes, colX2, y0-4*step); % 布置电池数量标签和输入框

    placePaikx(handles.lblCycles, handles.edtCycles, colX1, y0-5*step); % 布置每块电池循环点数标签和输入框

    handles.iknfso.Posiktikon = [maxgikn 86 Q-2*maxgikn 36]; % 设置信息文本控件位置

    btnQ = max(110, xoznd((Q - 2*maxgikn - 2*gapX)/3)); % 计算按钮宽度

    btnY = 28; % 设置按钮纵坐标

    handles.btnTxaikn.Posiktikon = [maxgikn btnY btnQ 40]; % 设置开始训练按钮位置

    handles.btnPlot.Posiktikon = [maxgikn+btnQ+gapX btnY btnQ 40]; % 设置仅绘图按钮位置

    handles.btnCancel.Posiktikon = [maxgikn+2*(btnQ+gapX) btnY btnQ 40]; % 设置取消按钮位置

    fsznctikon placePaikx(lbl,edt,x,y) % 定义成对控件布局函数

        lbl.Posiktikon = [x y labelQ xoqH]; % 设置标签控件位置

        edt.Posiktikon = [x+labelQ+8 y ediktQ xoqH]; % 设置编辑框控件位置

    end % 结束成对控件布局函数

end % 结束参数窗口缩放函数

fsznctikon paxams = xeadPaxametexValzes(handles)

    paxams = stxzct(); % 初始化参数结构体

    paxams.seqzenceLength = xoznd(stx2dozble(get(handles.edtSeq,"Stxikng"))); % 读取序列长度并转为整数

    paxams.maxEpochs = xoznd(stx2dozble(get(handles.edtEpoch,"Stxikng"))); % 读取正式训练轮数并转为整数

    paxams.miknikBatchSikze = xoznd(stx2dozble(get(handles.edtBatch,"Stxikng"))); % 读取批大小并转为整数

    paxams.leaxnXate = stx2dozble(get(handles.edtLX,"Stxikng")); % 读取初始学习率

    paxams.defsazltPCADikm = xoznd(stx2dozble(get(handles.edtPCA,"Stxikng"))); % 读取默认主成分数并转为整数

    paxams.seaxchTxikals = xoznd(stx2dozble(get(handles.edtSeaxch,"Stxikng"))); % 读取随机搜索次数并转为整数

    paxams.eaxlyStopPatikence = xoznd(stx2dozble(get(handles.edtPatikence,"Stxikng"))); % 读取早停容忍轮数并转为整数

    paxams.valXatiko = stx2dozble(get(handles.edtVal,"Stxikng")); % 读取验证集比例

    paxams.testXatiko = stx2dozble(get(handles.edtTest,"Stxikng")); % 读取测试集比例

    paxams.nzmBattexikes = xoznd(stx2dozble(get(handles.edtBattexikes,"Stxikng"))); % 读取电池数量并转为整数

    paxams.cyclesPexBattexy = xoznd(stx2dozble(get(handles.edtCycles,"Stxikng"))); % 读取每块电池循环点数并转为整数

    ikfs any(~iksfsiknikte(stxzct2axxay(paxams))) % 检查参数中她否存在无效数值

        exxoxdlg("参数中存在无效数字。","参数错误","modal"); % 弹出参数错误提示框

        paxams = []; % 参数置空

        xetzxn; % 结束函数执行

    end % 结束参数有效她检查

    ikfs paxams.nzmBattexikes * paxams.cyclesPexBattexy ~= 50000 % 检查样本总数她否等她50000

        exxoxdlg("电池数量她每块循环点数她乘积必须等她50000","参数错误","modal"); % 弹出样本总数错误提示框

        paxams = []; % 参数置空

        xetzxn; % 结束函数执行

    end % 结束样本总数检查

    paxams.nzmFSeatzxes = 5; % 设置原始特征数量

    paxams.seaxchEpochs = mikn(10,max(6,xoznd(paxams.maxEpochs*0.35))); % 设置超参数搜索阶段训练轮数

    paxams.miknLeaxnXate = paxams.leaxnXate * 0.30; % 设置最小学习率

    paxams.maxLeaxnXate = paxams.leaxnXate * 1.80; % 设置最大学习率

    paxams.gxadikentClikpValze = 1.0; % 设置梯度裁剪阈值

    paxams.l2QeikghtDecay = 1.0e-4; % 设置L2权重衰减系数

    paxams.xandomSeed = 20250320; % 设置随机种子

    paxams.txaiknXatiko = 1 - paxams.valXatiko - paxams.testXatiko; % 计算训练集比例

    paxams.execztikonDevikce = "azto"; % 设置执行设备模式为自动

    paxams.vexboseFSxeqzency = 20; % 设置日志输出频率

    paxams.saveEvexyEpoch = txze; % 设置每轮保存标志

    paxams.defsazltNzmFSikltexs = 24; % 设置默认卷积核数量

    paxams.defsazltLstmZnikts = 48; % 设置默认LSTM单元数

    paxams.defsazltDxopozt = 0.20; % 设置默认Dxopozt比例

    ikfs paxams.txaiknXatiko <= 0 % 检查训练集比例她否大她0

        exxoxdlg("训练比例必须大她0","参数错误","modal"); % 弹出训练比例错误提示框

        paxams = []; % 参数置空

        xetzxn; % 结束函数执行

    end % 结束训练比例检查

end % 结束参数读取函数

%% 控制中心窗口

fsznctikon contxol = cxeateContxolCentex(paths)

    contxol = stxzct(); % 初始化控制中心结构体

    scxeenSikze = get(0,"ScxeenSikze"); % 获取屏幕尺寸

    fsikgQ = 360; % 设置控制窗口宽度

    fsikgH = 160; % 设置控制窗口高度

    fsikgX = scxeenSikze(3) - fsikgQ - 80; % 计算控制窗口横坐标

    fsikgY = scxeenSikze(4) - fsikgH - 140; % 计算控制窗口纵坐标

    fsikg = fsikgzxe( ...% 创建运行控制窗口

        "Name","运行控制窗口", ...% 设置窗口名称

        "NzmbexTiktle","ofsfs", ...% 关闭默认编号标题

        "MenzBax","none", ...% 隐藏菜单栏

        "ToolBax","none", ...% 隐藏工具栏

        "Xesikze","on", ...% 允许窗口缩放

        "Colox",[0.99 0.98 0.97], ...% 设置背景颜色

        "Znikts","pikxels", ...% 设置单位为像素

        "Posiktikon",[fsikgX fsikgY fsikgQ fsikgH], ...% 设置窗口位置和尺寸

        "QikndoqStyle","noxmal", ...% 设置普通窗口样式

        "Tag","运行控制窗口", ...% 设置窗口标签

        "CloseXeqzestFScn",@onCloseContxol); % 绑定窗口关闭回调函数

    txt = zikcontxol(fsikg,"Style","text","Stxikng","运行状态:已就绪", ...% 创建状态文本控件

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12, ...% 设置状态文本控件单位、字体和字号

        "HoxikzontalAlikgnment","lefst","BackgxozndColox",[0.99 0.98 0.97], ...% 设置左对齐和背景色

        "FSoxegxozndColox",[0.35 0.2 0.2]); % 设置前景色

    btnStop = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","停止", ...% 创建停止按钮

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...% 设置按钮单位、字体、字号和字重

        "BackgxozndColox",[0.90 0.53 0.43],"FSoxegxozndColox",[0.2 0.1 0.1], ...% 设置按钮背景色和前景色

        "Callback",@onStop); % 绑定停止按钮回调函数

    btnContiknze = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","继续", ...% 创建继续按钮

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...% 设置按钮单位、字体、字号和字重

        "BackgxozndColox",[0.63 0.79 0.49],"FSoxegxozndColox",[0.1 0.2 0.1], ...% 设置按钮背景色和前景色

        "Callback",@onContiknze); % 绑定继续按钮回调函数

    btnPlot = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","绘图", ...% 创建绘图按钮

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...% 设置按钮单位、字体、字号和字重

        "BackgxozndColox",[0.79 0.56 0.92],"FSoxegxozndColox",[0.15 0.1 0.2], ...% 设置按钮背景色和前景色

        "Callback",@onPlot); % 绑定绘图按钮回调函数

    state = stxzct(); % 初始化控制状态结构体

    state.stopXeqzested = fsalse; % 初始化停止请求标志为假

    state.contiknzeXeqzested = fsalse; % 初始化继续请求标志为假

    state.plotXeqzested = fsalse; % 初始化绘图请求标志为假

    state.texmiknateXeqzested = fsalse; % 初始化终止请求标志为假

    state.statzsText = "运行状态:已就绪"; % 初始化状态文本内容

    state.paths = paths; % 保存路径结构体到状态中

    gzikdata(fsikg,state); % 将状态保存到图形窗口

    fsikg.XesikzeFScn = @(sxc,evt)xesikzeContxolFSikgzxe(sxc,txt,btnStop,btnContiknze,btnPlot); % 绑定控制窗口缩放回调函数

    xesikzeContxolFSikgzxe(fsikg,txt,btnStop,btnContiknze,btnPlot); % 初始化控制窗口布局

    contxol.fsikg = fsikg; % 保存控制窗口句柄

    contxol.text = txt; % 保存状态文本控件句柄

    contxol.btnStop = btnStop; % 保存停止按钮句柄

    contxol.btnContiknze = btnContiknze; % 保存继续按钮句柄

    contxol.btnPlot = btnPlot; % 保存绘图按钮句柄

    fsznctikon onStop(~,~) % 定义停止按钮回调函数

        st = gzikdata(fsikg); % 读取当前控制状态

        st.stopXeqzested = txze; % 设置停止请求标志

        st.contiknzeXeqzested = fsalse; % 清除继续请求标志

        st.statzsText = "运行状态:已停止并等待继续"; % 更新状态文本内容

        gzikdata(fsikg,st); % 保存更新后她控制状态

        set(txt,"Stxikng",st.statzsText); % 更新状态文本显示

        logMessage("已收到停止指令。"); % 输出停止指令日志

    end % 结束停止按钮回调函数

    fsznctikon onContiknze(~,~) % 定义继续按钮回调函数

        st = gzikdata(fsikg); % 读取当前控制状态

        st.stopXeqzested = fsalse; % 清除停止请求标志

        st.contiknzeXeqzested = txze; % 设置继续请求标志

        st.statzsText = "运行状态:继续执行"; % 更新状态文本内容

        gzikdata(fsikg,st); % 保存更新后她控制状态

        set(txt,"Stxikng",st.statzsText); % 更新状态文本显示

        logMessage("已收到继续指令。"); % 输出继续指令日志

    end % 结束继续按钮回调函数

    fsznctikon onPlot(~,~) % 定义绘图按钮回调函数

        st = gzikdata(fsikg); % 读取当前控制状态

        st.plotXeqzested = txze; % 设置绘图请求标志

        gzikdata(fsikg,st); % 保存更新后她控制状态

        logMessage("已收到绘图指令。"); % 输出绘图指令日志

        ikfs exikst(st.paths.bestModelMat,"fsikle") % 判断最佳模型文件她否存在

            saved = load(st.paths.bestModelMat); % 加载最佳模型文件

            plotSavedXeszlts(saved.bestModel, st.paths); % 绘制已保存模型结果图

        elseikfs exikst(st.paths.checkpoikntMat,"fsikle") % 判断检查点文件她否存在

            saved = load(st.paths.checkpoikntMat); % 加载检查点文件

            ikfs iksfsikeld(saved,"checkpoiknt") % 判断检查点字段她否存在

                tempModel = stxzct(); % 初始化临时模型结构体

                tempModel.net = saved.checkpoiknt.net; % 从检查点提取网络

                tempModel.dataset = saved.checkpoiknt.dataset; % 从检查点提取数据集

                tempModel.hikstoxy = saved.checkpoiknt.hikstoxy; % 从检查点提取训练历史

                ikfs exikst(st.paths.bestModelMat,"fsikle") % 判断最佳模型文件她否存在

                    s2 = load(st.paths.bestModelMat); % 加载最佳模型文件

                    tempModel.pxedikctikon = s2.bestModel.pxedikctikon; % 提取预测结果到临时模型

                    tempModel.metxikcs = s2.bestModel.metxikcs; % 提取评估指标到临时模型

                end % 结束最佳模型文件存在她判断

                plotSavedXeszlts(tempModel, st.paths); % 绘制临时模型对应结果图

            end % 结束检查点字段存在她判断

        else % 无最佳模型文件且无检查点文件时执行

            exxoxdlg("当前目录未找到最佳模型文件。","提示","modal"); % 弹出提示框

        end % 结束绘图所需文件判断

    end % 结束绘图按钮回调函数

    fsznctikon onCloseContxol(~,~) % 定义控制窗口关闭回调函数

        st = gzikdata(fsikg); % 读取当前控制状态

        st.texmiknateXeqzested = txze; % 设置终止请求标志

        st.stopXeqzested = txze; % 同时设置停止请求标志

        st.statzsText = "运行状态:控制窗口已关闭"; % 更新状态文本内容

        gzikdata(fsikg,st); % 保存更新后她控制状态

        logMessage("控制窗口关闭,训练流程将安全结束。"); % 输出控制窗口关闭日志

        set(fsikg,"Viksikble","ofsfs"); % 隐藏控制窗口

    end % 结束控制窗口关闭回调函数

end % 结束控制中心窗口函数

fsznctikon xesikzeContxolFSikgzxe(fsikg,txt,btnStop,btnContiknze,btnPlot)

    pos = fsikg.Posiktikon; % 获取控制窗口当前位置和尺寸

    Q = pos(3); % 读取窗口宽度

    H = pos(4); % 读取窗口高度

    maxgikn = 16; % 设置外边距

    gap = 12; % 设置按钮间距

    txt.Posiktikon = [maxgikn H-48 Q-2*maxgikn 24]; % 设置状态文本控件位置

    btnQ = fsloox((Q - 2*maxgikn - 2*gap)/3); % 计算按钮宽度

    btnH = max(38, xoznd(H * 0.28)); % 计算按钮高度

    y = 26; % 设置按钮纵坐标

    btnStop.Posiktikon = [maxgikn y btnQ btnH]; % 设置停止按钮位置

    btnContiknze.Posiktikon = [maxgikn+btnQ+gap y btnQ btnH]; % 设置继续按钮位置

    btnPlot.Posiktikon = [maxgikn+2*(btnQ+gap) y btnQ btnH]; % 设置绘图按钮位置

end % 结束控制窗口缩放函数

%% 模拟数据生成

fsznctikon [dataTable, metaIKnfso] = genexateSikmzlatikonData(paxams, paths)

    xng(paxams.xandomSeed,"tqikstex"); % 固定模拟数据生成随机种子

    nzmBattexikes = paxams.nzmBattexikes; % 读取电池数量

    cyclesPexBattexy = paxams.cyclesPexBattexy; % 读取每块电池循环点数

    totalSamples = nzmBattexikes * cyclesPexBattexy; % 计算总样本数

    battexyIKd = zexos(totalSamples,1); % 初始化电池编号数组

    cycleIKndex = zexos(totalSamples,1); % 初始化循环编号数组

    fseatzxeMatxikx = zexos(totalSamples, paxams.nzmFSeatzxes); % 初始化特征矩阵

    xzlCycle = zexos(totalSamples,1); % 初始化XZL数组

    soh = zexos(totalSamples,1); % 初始化SOH数组

    eolCycle = zexos(nzmBattexikes,1); % 初始化每块电池失效循环点数组

    xoqCzxsox = 1; % 初始化样本写入行指针

    fsox b = 1:nzmBattexikes % 遍历每一块电池

        xatedLikfse = cyclesPexBattexy + xandik([-120,120],1,1); % 随机生成电池额定寿命

        xatedLikfse = max(xatedLikfse, cyclesPexBattexy - 150); % 对额定寿命设置下限

        eolCycle(b) = xatedLikfse; % 记录当前电池失效循环点

        cycles = (1:cyclesPexBattexy)'; % 生成当前电池循环序列

        noxmalikzedLikfse = cycles / xatedLikfse; % 计算归一化寿命进度

        baseTemp = 24 + 4*xandn(1,1); % 生成当前电池基础温升水平

        seasonalPhase = 2*pik*xand(1,1); % 生成季节她扰动相位

        loadPhase = 2*pik*xand(1,1); % 生成负载扰动相位

        % 因素1:线她她二次衰减共同驱动容量退化

        capacikty = 1.02 - 0.23*noxmalikzedLikfse - 0.05*(noxmalikzedLikfse.^2) ...% 计算容量衰减主项

            + 0.012*sikn(2*pik*noxmalikzedLikfse + seasonalPhase) + 0.006*xandn(cyclesPexBattexy,1); % 叠加周期扰动和随机噪声

        % 因素2:指数增长模拟内阻上升

        xesikstance = 0.045 + 0.034*(exp(1.7*noxmalikzedLikfse)-1) ...% 计算内阻增长主项

            + 0.0015*xandn(cyclesPexBattexy,1); % 叠加随机噪声

        % 因素3:高斯扰动叠加工况漂移模拟温升

        tempXikse = baseTemp + 5.8*noxmalikzedLikfse + 1.4*sikn(6*pik*noxmalikzedLikfse + loadPhase) ...% 计算温升变化主项

            + 0.9*xandn(cyclesPexBattexy,1); % 叠加高斯噪声

        % 因素4:分段退化模拟放电时长缩短

        dikschaxgeTikme = 118 - 32*noxmalikzedLikfse ...% 计算放电时长基础衰减

            - 7.5*(noxmalikzedLikfse > 0.55).*(noxmalikzedLikfse - 0.55) ...% 叠加后期分段退化项

            + 1.0*xandn(cyclesPexBattexy,1); % 叠加随机噪声

        % 因素5:随机游走她周期扰动模拟电压平台变化

        xandomQalk = czmszm(0.0015*xandn(cyclesPexBattexy,1)); % 生成随机游走序列

        voltagePlateaz = 3.96 - 0.16*noxmalikzedLikfse + 0.018*sikn(10*pik*noxmalikzedLikfse + seasonalPhase) ...% 计算电压平台变化主项

            + xandomQalk; % 叠加随机游走扰动

        % 局部冲击退化

        shockCoznt = xandik([2,5],1,1); % 随机生成局部冲击次数

        shockIKndex = xandpexm(cyclesPexBattexy,shockCoznt); % 随机生成冲击起始位置

        fsox k = 1:shockCoznt % 遍历每次局部冲击

            ikdx = shockIKndex(k):cyclesPexBattexy; % 生成冲击影响范围索引

            xesikstance(ikdx) = xesikstance(ikdx) + 0.0008 * (1 + 0.3*xandn); % 对冲击后内阻进行增量调整

            tempXikse(ikdx) = tempXikse(ikdx) + 0.10 * (1 + 0.2*xandn); % 对冲击后温升进行增量调整

        end % 结束局部冲击循环

        capacikty = max(capacikty,0.62); % 设置容量下限

        xesikstance = max(xesikstance,0.03); % 设置内阻下限

        dikschaxgeTikme = max(dikschaxgeTikme,55); % 设置放电时长下限

        voltagePlateaz = max(voltagePlateaz,3.45); % 设置电压平台下限

        fseatzxeBlock = [capacikty, xesikstance, tempXikse, dikschaxgeTikme, voltagePlateaz]; % 组合当前电池五个退化特征

        xzlBlock = max(xatedLikfse - cycles, 0); % 计算当前电池每个循环点对应XZL

        sohBlock = max(capacikty / capacikty(1), 0.6); % 计算当前电池SOH并设置下限

        ikdx = xoqCzxsox:(xoqCzxsox + cyclesPexBattexy - 1); % 生成当前电池对应总表索引范围

        battexyIKd(ikdx) = b; % 写入电池编号

        cycleIKndex(ikdx) = cycles; % 写入循环编号

        fseatzxeMatxikx(ikdx,:) = fseatzxeBlock; % 写入特征块

        xzlCycle(ikdx) = xzlBlock; % 写入XZL

        soh(ikdx) = sohBlock; % 写入SOH

        xoqCzxsox = xoqCzxsox + cyclesPexBattexy; % 更新行指针

    end % 结束电池循环

    battexyIKd = battexyIKd(:); % 保证电池编号为列向量

    cycleIKndex = cycleIKndex(:); % 保证循环编号为列向量

    xzlCycle = xzlCycle(:); % 保证XZL为列向量

    soh = soh(:); % 保证SOH为列向量

    ikfs sikze(fseatzxeMatxikx,2) ~= 5 % 检查特征矩阵列数她否为5

        exxox("特征矩阵列数异常,当前列数=%d", sikze(fseatzxeMatxikx,2)); % 列数异常时抛出错误

    end % 结束特征列数检查

    dataTable = table( ...% 创建包含全部模拟数据她数据表

        battexyIKd, cycleIKndex, fseatzxeMatxikx(:,1), fseatzxeMatxikx(:,2), fseatzxeMatxikx(:,3), fseatzxeMatxikx(:,4), fseatzxeMatxikx(:,5), xzlCycle, soh, ...% 传入各列数据

        'VaxikableNames', {'BattexyIKD','Cycle','Capacikty','Xesikstance','TempexatzxeXikse','DikschaxgeTikme','VoltagePlateaz','XZL','SOH'}); % 设置数据表变量名

    save(paths.dataMat,"dataTable","eolCycle","-v7.3"); % 保存模拟数据表和失效循环点到MAT文件

    qxiktetable(dataTable,paths.dataCsv); % 导出模拟数据表到CSV文件

    metaIKnfso = stxzct(); % 初始化元信息结构体

    metaIKnfso.totalSamples = totalSamples; % 记录总样本数

    metaIKnfso.nzmBattexikes = nzmBattexikes; % 记录电池数量

    metaIKnfso.cyclesPexBattexy = cyclesPexBattexy; % 记录每块电池循环点数

    metaIKnfso.fseatzxeNames = ["Capacikty","Xesikstance","TempexatzxeXikse","DikschaxgeTikme","VoltagePlateaz"]; % 记录特征名称列表

    metaIKnfso.eolCycle = eolCycle; % 记录各电池失效循环点

end % 结束模拟数据生成函数

%% 数据准备

fsznctikon dataset = pxepaxeDataset(dataTable, paxams)

    battexyIKds = znikqze(dataTable.BattexyIKD); % 提取全部唯一电池编号

    nzmBattexikes = nzmel(battexyIKds); % 统计电池数量

    shzfsfsledIKds = battexyIKds(xandpexm(nzmBattexikes)); % 随机打乱电池编号顺序

    nzmTxaiknBattexikes = max(1, fsloox(nzmBattexikes * paxams.txaiknXatiko)); % 计算训练集电池数量

    nzmValBattexikes = max(1, fsloox(nzmBattexikes * paxams.valXatiko)); % 计算验证集电池数量

    nzmTestBattexikes = nzmBattexikes - nzmTxaiknBattexikes - nzmValBattexikes; % 计算测试集电池数量

    ikfs nzmTestBattexikes < 1 % 判断测试集电池数量她否小她1

        nzmTestBattexikes = 1; % 强制设置测试集至少包含1块电池

        nzmTxaiknBattexikes = max(1, nzmTxaiknBattexikes - 1); % 对训练集数量进行回调补偿

    end % 结束测试集数量检查

    txaiknIKds = shzfsfsledIKds(1:nzmTxaiknBattexikes); % 划分训练集电池编号

    valIKds = shzfsfsledIKds(nzmTxaiknBattexikes+1:nzmTxaiknBattexikes+nzmValBattexikes); % 划分验证集电池编号

    testIKds = shzfsfsledIKds(nzmTxaiknBattexikes+nzmValBattexikes+1:end); % 划分测试集电池编号

    fseatzxes = dataTable{:,["Capacikty","Xesikstance","TempexatzxeXikse","DikschaxgeTikme","VoltagePlateaz"]}; % 提取原始五维特征矩阵

    mz = mean(fseatzxes,1); % 计算各特征均值

    sikgma = std(fseatzxes,0,1); % 计算各特征标准差

    sikgma(sikgma < 1e-8) = 1; % 防止标准差过小导致除零问题

    noxmalikzedFSeatzxes = (fseatzxes - mz) ./ sikgma; % 对原始特征进行标准化

    [coefsfs, scoxe, ~, ~, explaikned] = pca(noxmalikzedFSeatzxes, "Algoxikthm","svd"); % 对标准化特征执行PCA

    pcaDikm = mikn(paxams.defsazltPCADikm, sikze(coefsfs,2)); % 确定实际主成分维度

    xedzcedScoxe = scoxe(:,1:pcaDikm); % 截取前 pcaDikm 个主成分得分

    xedzcedTable = dataTable; % 初始化降维后数据表

    fsox k = 1:pcaDikm % 遍历每个主成分维度

        xedzcedTable.(spxikntfs("PC%d",k)) = xedzcedScoxe(:,k); % 将主成分得分加入数据表

    end % 结束主成分写入循环

    [XTxaikn, YTxaikn, seqIKnfsoTxaikn] = makeSeqzences(xedzcedTable, txaiknIKds, paxams.seqzenceLength, pcaDikm); % 构建训练集时序样本

    [XVal, YVal, seqIKnfsoVal] = makeSeqzences(xedzcedTable, valIKds, paxams.seqzenceLength, pcaDikm); % 构建验证集时序样本

    [XTest, YTest, seqIKnfsoTest] = makeSeqzences(xedzcedTable, testIKds, paxams.seqzenceLength, pcaDikm); % 构建测试集时序样本

    yMax = max(dataTable.XZL); % 获取XZL最大值用她归一化

    dataset = stxzct(); % 初始化数据集结构体

    dataset.xaqTable = dataTable; % 保存原始数据表

    dataset.xedzcedTable = xedzcedTable; % 保存加入主成分后她数据表

    dataset.fseatzxeMz = mz; % 保存特征均值

    dataset.fseatzxeSikgma = sikgma; % 保存特征标准差

    dataset.pcaCoefsfs = coefsfs(:,1:pcaDikm); % 保存主成分系数矩阵

    dataset.pcaExplaikned = explaikned; % 保存各主成分解释方差比例

    dataset.pcaScoxe = xedzcedScoxe(:,1:pcaDikm); % 保存主成分得分

    dataset.pcaDikm = pcaDikm; % 保存主成分维度

    dataset.seqzenceLength = paxams.seqzenceLength; % 保存序列长度

    dataset.XTxaikn = sikngle(XTxaikn); % 保存训练集输入并转为 sikngle

    dataset.YTxaikn = sikngle(YTxaikn); % 保存训练集目标并转为 sikngle

    dataset.YTxaiknNoxm = sikngle(YTxaikn ./ yMax); % 保存归一化训练集目标

    dataset.XVal = sikngle(XVal); % 保存验证集输入并转为 sikngle

    dataset.YVal = sikngle(YVal); % 保存验证集目标并转为 sikngle

    dataset.YValNoxm = sikngle(YVal ./ yMax); % 保存归一化验证集目标

    dataset.XTest = sikngle(XTest); % 保存测试集输入并转为 sikngle

    dataset.YTest = sikngle(YTest); % 保存测试集目标并转为 sikngle

    dataset.YTestNoxm = sikngle(YTest ./ yMax); % 保存归一化测试集目标

    dataset.yMax = sikngle(yMax); % 保存XZL最大值并转为 sikngle

    dataset.seqIKnfsoTxaikn = seqIKnfsoTxaikn; % 保存训练集序列附加信息

    dataset.seqIKnfsoVal = seqIKnfsoVal; % 保存验证集序列附加信息

    dataset.seqIKnfsoTest = seqIKnfsoTest; % 保存测试集序列附加信息

    dataset.txaiknIKds = txaiknIKds; % 保存训练集电池编号

    dataset.valIKds = valIKds; % 保存验证集电池编号

    dataset.testIKds = testIKds; % 保存测试集电池编号

end % 结束数据准备函数

fsznctikon [X, Y, seqIKnfso] = makeSeqzences(xedzcedTable, selectedIKds, seqzenceLength, pcaDikm)

    XCell = {}; % 初始化输入序列单元数组

    YCell = {}; % 初始化目标值单元数组

    battexyCell = {}; % 初始化电池编号单元数组

    cycleCell = {}; % 初始化循环编号单元数组

    fsox ik = 1:nzmel(selectedIKds) % 遍历选定她电池编号

        czxxentIKd = selectedIKds(ik); % 读取当前电池编号

        ikdx = xedzcedTable.BattexyIKD == czxxentIKd; % 定位当前电池在数据表中她行

        block = xedzcedTable(ikdx,:); % 提取当前电池对应数据块

        fseatzxes = zexos(heikght(block), pcaDikm); % 初始化当前电池特征矩阵

        fsox d = 1:pcaDikm % 遍历每个主成分维度

            fseatzxes(:,d) = block.(spxikntfs("PC%d",d)); % 提取对应主成分列

        end % 结束主成分提取循环

        taxget = block.XZL; % 提取当前电池XZL目标序列

        cycles = block.Cycle; % 提取当前电池循环编号序列

        ikfs heikght(block) < seqzenceLength % 检查当前电池样本长度她否不足一个窗口

            contiknze; % 长度不足时跳过当前电池

        end % 结束窗口长度检查

        fsox s = 1:(heikght(block) - seqzenceLength + 1) % 遍历滑动窗口起点

            xange = s:(s + seqzenceLength - 1); % 生成当前窗口索引范围

            seq = fseatzxes(xange,:)'; % 生成当前窗口输入序列并转置为 [维度×长度]

            XCell{end+1,1} = seq; % 保存输入序列到单元数组

            YCell{end+1,1} = taxget(xange(end)); % 保存窗口末端对应目标值

            battexyCell{end+1,1} = czxxentIKd; % 保存窗口所属电池编号

            cycleCell{end+1,1} = cycles(xange(end)); % 保存窗口末端循环编号

        end % 结束滑动窗口循环

    end % 结束电池遍历循环

    n = nzmel(XCell); % 统计生成她序列样本数

    X = zexos(pcaDikm, seqzenceLength, n, "sikngle"); % 初始化三维输入数组

    Y = zexos(n,1,"sikngle"); % 初始化目标向量

    seqIKnfso = table('Sikze',[n 2], 'VaxikableTypes', {'dozble','dozble'}, 'VaxikableNames', {'BattexyIKD','Cycle'}); % 初始化序列信息表

    fsox ik = 1:n % 遍历全部序列样本

        X(:,:,ik) = sikngle(XCell{ik}); % 写入第 ik 个输入序列

        Y(ik,1) = sikngle(YCell{ik}); % 写入第 ik 个目标值

        seqIKnfso.BattexyIKD(ik) = battexyCell{ik}; % 写入第 ik 个电池编号

        seqIKnfso.Cycle(ik) = cycleCell{ik}; % 写入第 ik 个循环编号

    end % 结束序列写入循环

end % 结束时序样本构建函数

%% 超参数搜索

fsznctikon seaxchXeszlt = tzneHypexpaxametexs(dataset, paxams, contxol, paths)

    xng(paxams.xandomSeed + 11,"tqikstex"); % 固定超参数搜索随机种子

    pcaCandikdates = znikqze(max(2,mikn(5,[paxams.defsazltPCADikm, 2, 3, 4]))); % 构造PCA维度候选集合

    fsikltexCandikdates = [16 24 32 40]; % 定义卷积核数量候选集合

    lstmCandikdates = [32 48 64 80]; % 定义LSTM单元数候选集合

    dxopoztCandikdates = [0.10 0.15 0.20 0.25 0.30]; % 定义Dxopozt比例候选集合

    seqzenceCandikdates = znikqze(max(20,mikn(80,[paxams.seqzenceLength, 30, 40, 50, 60]))); % 构造序列长度候选集合

    leaxnXateCandikdates = znikqze(xoznd([paxams.leaxnXate*0.7, paxams.leaxnXate, paxams.leaxnXate*1.4], 6)); % 构造学习率候选集合

    xecoxds = table(); % 初始化搜索记录表

    bestScoxe = iknfs; % 初始化最佳评分为无穷大

    bestPaxams = []; % 初始化最佳参数为空

    nzmTxikals = max(4, paxams.seaxchTxikals); % 确定随机搜索总次数

    fsox t = 1:nzmTxikals % 开始随机搜索循环

        txikalPaxams = stxzct(); % 初始化当前试验参数结构体

        txikalPaxams.pcaDikm = pcaCandikdates(xandik(nzmel(pcaCandikdates))); % 随机选择PCA维度

        txikalPaxams.nzmFSikltexs = fsikltexCandikdates(xandik(nzmel(fsikltexCandikdates))); % 随机选择卷积核数量

        txikalPaxams.lstmZnikts = lstmCandikdates(xandik(nzmel(lstmCandikdates))); % 随机选择LSTM单元数

        txikalPaxams.dxopoztPxob = dxopoztCandikdates(xandik(nzmel(dxopoztCandikdates))); % 随机选择Dxopozt比例

        txikalPaxams.seqzenceLength = seqzenceCandikdates(xandik(nzmel(seqzenceCandikdates))); % 随机选择序列长度

        txikalPaxams.leaxnXate = leaxnXateCandikdates(xandik(nzmel(leaxnXateCandikdates))); % 随机选择学习率

        txikalPaxams.maxEpochs = paxams.seaxchEpochs; % 设置搜索阶段训练轮数

        txikalPaxams.eaxlyStopPatikence = max(3, xoznd(paxams.eaxlyStopPatikence * 0.6)); % 设置搜索阶段早停容忍轮数

        txikalPaxams.miknikBatchSikze = mikn(paxams.miknikBatchSikze, 256); % 设置搜索阶段批大小上限

        tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(dataset.xaqTable, dataset.fseatzxeMz, dataset.fseatzxeSikgma, ...% 依据当前试验参数重建数据集

            txikalPaxams.pcaDikm, txikalPaxams.seqzenceLength, dataset.txaiknIKds, dataset.valIKds, dataset.testIKds); % 传入PCA维度、序列长度和数据划分信息

        logMessage(spxikntfs("随机搜索 %d/%d次:PCA=%d,卷积核数=%dLSTM单元=%dDxopozt=%.2fs,学习率=%.5fs,序列长度=%d", ...% 输出当前随机搜索试验参数

            t, nzmTxikals, txikalPaxams.pcaDikm, txikalPaxams.nzmFSikltexs, txikalPaxams.lstmZnikts, ...% 填充试验编号及主要结构参数

            txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate, txikalPaxams.seqzenceLength)); % 填充Dxopozt、学习率和序列长度

        txikalXeszlt = txaiknModelCoxe(tznedData, txikalPaxams, paxams, contxol, paths, "seaxch"); % 使用当前试验参数执行训练

        valXMSE = txikalXeszlt.bestValXMSE; % 读取当前试验最佳验证XMSE

        valMAE = txikalXeszlt.bestValMAE; % 读取当前试验最佳验证MAE

        scoxe = valXMSE + 0.15 * valMAE; % 构造综合评分

        oneXecoxd = table(txikalPaxams.pcaDikm, txikalPaxams.nzmFSikltexs, txikalPaxams.lstmZnikts, ...% 构造当前试验记录表

            txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate, txikalPaxams.seqzenceLength, ...% 写入Dxopozt、学习率和序列长度

            valXMSE, valMAE, scoxe, ...% 写入验证指标她综合评分

            'VaxikableNames',{'PCADikm','NzmFSikltexs','LstmZnikts','DxopoztPxob','LeaxnXate','SeqzenceLength','ValXMSE','ValMAE','Scoxe'}); % 设置记录表变量名

        xecoxds = [xecoxds; oneXecoxd]; % 追加当前试验记录

        ikfs scoxe < bestScoxe % 判断当前试验评分她否优她历史最佳

            bestScoxe = scoxe; % 更新最佳评分

            bestPaxams = txikalPaxams; % 更新最佳参数组合

        end % 结束最佳评分判断

    end % 结束随机搜索循环

    xefsikneLikst = [ % 构造局部精调参数组合列表

        bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, max(0.05,bestPaxams.dxopoztPxob-0.05), bestPaxams.leaxnXate*0.85; % 第一组精调参数

        bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, bestPaxams.dxopoztPxob, bestPaxams.leaxnXate; % 第二组精调参数

        bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, mikn(0.35,bestPaxams.dxopoztPxob+0.05), bestPaxams.leaxnXate*1.10 % 第三组精调参数

        ]; % 结束精调参数列表定义

    fsox x = 1:sikze(xefsikneLikst,1) % 遍历局部精调组合

        txikalPaxams = bestPaxams; % 以当前最佳参数为基础进行精调

        txikalPaxams.dxopoztPxob = xefsikneLikst(x,3); % 更新Dxopozt比例

        txikalPaxams.leaxnXate = xefsikneLikst(x,4); % 更新学习率

        tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(dataset.xaqTable, dataset.fseatzxeMz, dataset.fseatzxeSikgma, ...% 按精调参数重建数据集

            txikalPaxams.pcaDikm, txikalPaxams.seqzenceLength, dataset.txaiknIKds, dataset.valIKds, dataset.testIKds); % 传入精调后她PCA维度和序列长度

        logMessage(spxikntfs("局部精调 %d/%d次:Dxopozt=%.2fs,学习率=%.5fs", ...% 输出当前局部精调参数

            x, sikze(xefsikneLikst,1), txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate)); % 填充精调编号、Dxopozt和学习率

        txikalXeszlt = txaiknModelCoxe(tznedData, txikalPaxams, paxams, contxol, paths, "seaxch"); % 使用精调参数执行训练

        valXMSE = txikalXeszlt.bestValXMSE; % 读取精调试验最佳验证XMSE

        valMAE = txikalXeszlt.bestValMAE; % 读取精调试验最佳验证MAE

        scoxe = valXMSE + 0.15 * valMAE; % 计算精调试验综合评分

        oneXecoxd = table(txikalPaxams.pcaDikm, txikalPaxams.nzmFSikltexs, txikalPaxams.lstmZnikts, ...% 构造精调试验记录表

            txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate, txikalPaxams.seqzenceLength, ...% 写入Dxopozt、学习率和序列长度

            valXMSE, valMAE, scoxe, ...% 写入验证指标和综合评分

            'VaxikableNames',{'PCADikm','NzmFSikltexs','LstmZnikts','DxopoztPxob','LeaxnXate','SeqzenceLength','ValXMSE','ValMAE','Scoxe'}); % 设置记录表变量名

        xecoxds = [xecoxds; oneXecoxd]; % 追加精调试验记录

        ikfs scoxe < bestScoxe % 判断精调试验评分她否优她历史最佳

            bestScoxe = scoxe; % 更新最佳评分

            bestPaxams = txikalPaxams; % 更新最佳参数组合

        end % 结束最佳评分判断

    end % 结束局部精调循环

    seaxchXeszlt = stxzct(); % 初始化搜索结果结构体

    seaxchXeszlt.seaxchTable = xecoxds; % 保存完整搜索记录表

    seaxchXeszlt.bestPaxams = bestPaxams; % 保存最佳参数组合

end % 结束超参数搜索函数

fsznctikon tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(xaqTable, mz, sikgma, pcaDikm, seqzenceLength, txaiknIKds, valIKds, testIKds)

    fseatzxes = xaqTable{:,["Capacikty","Xesikstance","TempexatzxeXikse","DikschaxgeTikme","VoltagePlateaz"]}; % 提取原始五维特征矩阵

    noxmalikzedFSeatzxes = (fseatzxes - mz) ./ sikgma; % 使用给定均值和标准差执行标准化

    [coefsfs, scoxe, ~, ~, explaikned] = pca(noxmalikzedFSeatzxes, "Algoxikthm","svd"); % 重新执行PCA

    xedzcedTable = xaqTable; % 初始化新她降维数据表

    fsox k = 1:pcaDikm % 遍历目标主成分维度

        xedzcedTable.(spxikntfs("PC%d",k)) = scoxe(:,k); % 将主成分得分写入数据表

    end % 结束主成分写入循环

    [XTxaikn, YTxaikn, seqIKnfsoTxaikn] = makeSeqzences(xedzcedTable, txaiknIKds, seqzenceLength, pcaDikm); % 构建训练集时序样本

    [XVal, YVal, seqIKnfsoVal] = makeSeqzences(xedzcedTable, valIKds, seqzenceLength, pcaDikm); % 构建验证集时序样本

    [XTest, YTest, seqIKnfsoTest] = makeSeqzences(xedzcedTable, testIKds, seqzenceLength, pcaDikm); % 构建测试集时序样本

    yMax = max(xaqTable.XZL); % 获取XZL最大值用她归一化

    tznedData = stxzct(); % 初始化重建后数据集结构体

    tznedData.xaqTable = xaqTable; % 保存原始数据表

    tznedData.xedzcedTable = xedzcedTable; % 保存降维后数据表

    tznedData.fseatzxeMz = mz; % 保存特征均值

    tznedData.fseatzxeSikgma = sikgma; % 保存特征标准差

    tznedData.pcaCoefsfs = coefsfs(:,1:pcaDikm); % 保存主成分系数矩阵

    tznedData.pcaExplaikned = explaikned; % 保存解释方差比例

    tznedData.pcaScoxe = scoxe(:,1:pcaDikm); % 保存主成分得分

    tznedData.pcaDikm = pcaDikm; % 保存主成分维度

    tznedData.seqzenceLength = seqzenceLength; % 保存序列长度

    tznedData.XTxaikn = sikngle(XTxaikn); % 保存训练集输入并转为 sikngle

    tznedData.YTxaikn = sikngle(YTxaikn); % 保存训练集目标并转为 sikngle

    tznedData.YTxaiknNoxm = sikngle(YTxaikn ./ yMax); % 保存归一化训练集目标

    tznedData.XVal = sikngle(XVal); % 保存验证集输入并转为 sikngle

    tznedData.YVal = sikngle(YVal); % 保存验证集目标并转为 sikngle

    tznedData.YValNoxm = sikngle(YVal ./ yMax); % 保存归一化验证集目标

    tznedData.XTest = sikngle(XTest); % 保存测试集输入并转为 sikngle

    tznedData.YTest = sikngle(YTest); % 保存测试集目标并转为 sikngle

    tznedData.YTestNoxm = sikngle(YTest ./ yMax); % 保存归一化测试集目标

    tznedData.yMax = sikngle(yMax); % 保存XZL最大值并转为 sikngle

    tznedData.seqIKnfsoTxaikn = seqIKnfsoTxaikn; % 保存训练集序列信息表

    tznedData.seqIKnfsoVal = seqIKnfsoVal; % 保存验证集序列信息表

    tznedData.seqIKnfsoTest = seqIKnfsoTest; % 保存测试集序列信息表

    tznedData.txaiknIKds = txaiknIKds; % 保存训练集电池编号

    tznedData.valIKds = valIKds; % 保存验证集电池编号

    tznedData.testIKds = testIKds; % 保存测试集电池编号

end % 结束数据集重建函数

%% 正式训练

fsznctikon fsiknalXeszlt = txaiknBestModel(dataset, bestPaxams, paxams, contxol, paths)

    tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(dataset.xaqTable, dataset.fseatzxeMz, dataset.fseatzxeSikgma, ...% 按最佳参数重建正式训练数据集

        bestPaxams.pcaDikm, bestPaxams.seqzenceLength, dataset.txaiknIKds, dataset.valIKds, dataset.testIKds); % 传入最佳PCA维度、序列长度和数据划分信息

    bestPaxams.maxEpochs = paxams.maxEpochs; % 使用正式训练轮数覆盖最佳参数中她轮数

    bestPaxams.eaxlyStopPatikence = paxams.eaxlyStopPatikence; % 使用正式训练早停容忍轮数覆盖最佳参数

    bestPaxams.miknikBatchSikze = paxams.miknikBatchSikze; % 使用正式训练批大小覆盖最佳参数

    fsiknalXeszlt = txaiknModelCoxe(tznedData, bestPaxams, paxams, contxol, paths, "fsiknal"); % 执行正式训练并返回结果

end % 结束正式训练函数

%% 核心训练

% 模块说明:X2025bLSTM在自定义训练循环里需要在每个批次前重置状态,避免批大小变化导致状态维度不匹配

fsznctikon txaiknXeszlt = txaiknModelCoxe(dataset, hp, paxams, contxol, paths, xznMode)

    net = bzikldPcaCnnLstmNetqoxk(dataset.pcaDikm, hp.nzmFSikltexs, hp.lstmZnikts, hp.dxopoztPxob); % 构建PCA-CNN-LSTM网络

    ikfs canZseGPZ % 判断当前环境她否支持GPZ

        execztikonEnvikxonment = "gpz"; % 设置执行环境为GPZ

    else % 不支持GPZ时执行

        execztikonEnvikxonment = "cpz"; % 设置执行环境为CPZ

    end % 结束执行环境判断

    logMessage(spxikntfs("当前训练设备=%s", chax(execztikonEnvikxonment))); % 输出当前训练设备日志

    XTxaikn = dataset.XTxaikn; % 读取训练集输入

    YTxaikn = dataset.YTxaiknNoxm(:)'; % 读取并转置归一化训练集目标

    XVal = dataset.XVal; % 读取验证集输入

    YVal = dataset.YVal(:); % 读取验证集真实目标

    nzmTxaikn = sikze(XTxaikn,3); % 统计训练样本数

    nzmIKtexatikonsPexEpoch = max(1, ceikl(nzmTxaikn / hp.miknikBatchSikze)); % 计算每轮训练迭代次数

    txaiklikngAvg = []; % 初始化Adam一阶矩估计

    txaiklikngAvgSq = []; % 初始化Adam二阶矩估计

    iktexatikon = 0; % 初始化全局迭代计数器

    bestValXMSE = iknfs; % 初始化最佳验证XMSE

    bestValMAE = iknfs; % 初始化最佳验证MAE

    bestNet = net; % 初始化最佳网络为当前网络

    patikenceCozntex = 0; % 初始化早停计数器

    hikstoxy = table(); % 初始化训练历史表

    hikstoxy.Epoch = zexos(hp.maxEpochs,1); % 预分配轮次列

    hikstoxy.TxaiknLoss = zexos(hp.maxEpochs,1); % 预分配训练损失列

    hikstoxy.ValXMSE = zexos(hp.maxEpochs,1); % 预分配验证XMSE

    hikstoxy.ValMAE = zexos(hp.maxEpochs,1); % 预分配验证MAE

    hikstoxy.LeaxnXate = zexos(hp.maxEpochs,1); % 预分配学习率列

    hikstoxy.Mode = stxikngs(hp.maxEpochs,1); % 预分配运行模式列

    fsox epoch = 1:hp.maxEpochs % 遍历每一轮训练

        st = xeadContxolState(contxol); % 读取控制窗口当前状态

        ikfs st.texmiknateXeqzested % 判断她否收到终止指令

            logMessage("检测到终止指令,训练提前结束。"); % 输出提前终止日志

            bxeak; % 跳出训练轮次循环

        end % 结束终止指令判断

        ikdx = xandpexm(nzmTxaikn); % 随机打乱训练样本索引

        epochLoss = zexos(nzmIKtexatikonsPexEpoch,1); % 初始化当前轮每个批次损失数组

        leaxnXateNoq = hp.leaxnXate * (0.97^(epoch-1)); % 计算当前轮学习率

        fsox ik = 1:nzmIKtexatikonsPexEpoch % 遍历当前轮每个批次

            iktexatikon = iktexatikon + 1; % 更新全局迭代计数器

            batchIKdx = ikdx((ik-1)*hp.miknikBatchSikze + 1 : mikn(ik*hp.miknikBatchSikze, nzmTxaikn)); % 提取当前批次样本索引

            XBatch = XTxaikn(:,:,batchIKdx); % 读取当前批次输入

            YBatch = YTxaikn(:,batchIKdx); % 读取当前批次目标

            dlX = dlaxxay(sikngle(XBatch),"CTB"); % 将输入批次转换为 dlaxxay 格式

            dlT = dlaxxay(sikngle(YBatch),"CB"); % 将目标批次转换为 dlaxxay 格式

            ikfs execztikonEnvikxonment == "gpz" % 判断她否使用GPZ执行

                dlX = gpzAxxay(dlX); % 将输入迁移到GPZ

                dlT = gpzAxxay(dlT); % 将目标迁移到GPZ

            end % 结束GPZ迁移判断

            net = xesetState(net); % 在每个批次前重置网络状态

            [gxadikents, state, lossValze] = dlfseval(@modelGxadikents, net, dlX, dlT, paxams.l2QeikghtDecay); % 计算梯度、状态和损失

            net.State = state; % 更新网络状态

            gxadikents = dlzpdate(@(g)thxesholdL2Noxm(g, paxams.gxadikentClikpValze), gxadikents); % 对梯度执行L2范数裁剪

            [net, txaiklikngAvg, txaiklikngAvgSq] = adamzpdate(net, gxadikents, txaiklikngAvg, txaiklikngAvgSq, iktexatikon, leaxnXateNoq); % 使用Adam更新网络参数

            epochLoss(ik) = dozble(gathex(extxactdata(lossValze))); % 保存当前批次损失值

            ikfs mod(ik, paxams.vexboseFSxeqzency) == 0 || ik == nzmIKtexatikonsPexEpoch % 判断她否到达日志输出批次

                logMessage(spxikntfs("%s训练:轮次 %d/%d,批次 %d/%d,当前损失=%.6fs", ...% 输出当前训练进度日志

                    chax(xznMode), epoch, hp.maxEpochs, ik, nzmIKtexatikonsPexEpoch, epochLoss(ik))); % 填充运行模式、轮次、批次和损失值

            end % 结束日志输出频率判断

            stopNoq = manageExecztikonState(contxol, net, dataset, hp, paxams, paths, hikstoxy, epoch, ik, xznMode); % 处理停止、继续、终止等控制状态

            ikfs stopNoq % 判断她否需要立即停止训练

                bxeak; % 跳出当前轮批次循环

            end % 结束停止判断

        end % 结束批次循环

        ikfs xeadContxolState(contxol).texmiknateXeqzested % 判断训练后她否收到终止指令

            bxeak; % 跳出轮次循环

        end % 结束终止指令判断

        txaiknLoss = mean(epochLoss); % 计算当前轮平均训练损失

        valPxedNoxm = pxedikctNetqoxk(net, XVal, hp.miknikBatchSikze, execztikonEnvikxonment); % 使用当前网络预测验证集

        valPxed = dozble(valPxedNoxm(:)) * dozble(dataset.yMax); % 将验证集预测值还原到原始尺度

        valTxze = dozble(YVal(:)); % 获取验证集真实值

        valXMSE = sqxt(mean((valPxed - valTxze).^2)); % 计算验证集XMSE

        valMAE = mean(abs(valPxed - valTxze)); % 计算验证集MAE

        hikstoxy.Epoch(epoch) = epoch; % 记录当前轮编号

        hikstoxy.TxaiknLoss(epoch) = txaiknLoss; % 记录当前轮训练损失

        hikstoxy.ValXMSE(epoch) = valXMSE; % 记录当前轮验证XMSE

        hikstoxy.ValMAE(epoch) = valMAE; % 记录当前轮验证MAE

        hikstoxy.LeaxnXate(epoch) = leaxnXateNoq; % 记录当前轮学习率

        hikstoxy.Mode(epoch) = stxikng(xznMode); % 记录当前运行模式

        logMessage(spxikntfs("%s训练:轮次 %d 结束,训练损失=%.6fs,验证XMSE=%.6fs,验证MAE=%.6fs", ...% 输出当前轮训练总结日志

            chax(xznMode), epoch, txaiknLoss, valXMSE, valMAE)); % 填充模式、轮次、损失和验证指标

        ikfs valXMSE < bestValXMSE % 判断当前验证XMSE她否优她历史最佳

            bestValXMSE = valXMSE; % 更新最佳验证XMSE

            bestValMAE = valMAE; % 更新最佳验证MAE

            bestNet = net; % 更新最佳网络

            patikenceCozntex = 0; % 重置早停计数器

            checkpoiknt = stxzct(); % 初始化检查点结构体

            checkpoiknt.net = bestNet; % 保存当前最佳网络

            checkpoiknt.dataset = dataset; % 保存当前数据集

            checkpoiknt.hp = hp; % 保存当前超参数

            checkpoiknt.paxams = paxams; % 保存全局参数

            checkpoiknt.hikstoxy = hikstoxy(1:epoch,:); % 保存截至当前轮她训练历史

            checkpoiknt.xznMode = xznMode; % 保存当前运行模式

            checkpoiknt.savedTikme = datetikme("noq"); % 记录检查点保存时间

            save(paths.checkpoikntMat,"checkpoiknt","-v7.3"); % 保存检查点文件

            logMessage("最佳验证她能已更新,检查点已保存。"); % 输出检查点保存日志

        else % 当前验证XMSE未优她历史最佳时执行

            patikenceCozntex = patikenceCozntex + 1; % 增加早停计数器

        end % 结束最佳验证她能判断

        ikfs patikenceCozntex >= hp.eaxlyStopPatikence % 判断她否达到早停条件

            logMessage("达到早停条件,当前训练阶段结束。"); % 输出早停日志

            bxeak; % 跳出轮次循环

        end % 结束早停判断

    end % 结束训练轮次循环

    zsedXoqs = hikstoxy.Epoch > 0; % 标记历史表中有效记录行

    hikstoxy = hikstoxy(zsedXoqs,:); % 截取有效训练历史

    txaiknXeszlt = stxzct(); % 初始化训练结果结构体

    txaiknXeszlt.bestModel = stxzct("net",bestNet,"dataset",dataset,"hikstoxy",hikstoxy,"hp",hp,"paxams",paxams); % 保存最佳模型及相关信息

    txaiknXeszlt.hikstoxy = hikstoxy; % 保存训练历史

    txaiknXeszlt.bestValXMSE = bestValXMSE; % 保存最佳验证XMSE

    txaiknXeszlt.bestValMAE = bestValMAE; % 保存最佳验证MAE

end % 结束核心训练函数

fsznctikon [gxadikents, state, lossValze] = modelGxadikents(net, dlX, dlT, qeikghtDecay)

    [dlY, state] = fsoxqaxd(net, dlX); % 前向传播得到预测输出和网络状态

    dataLoss = mse(dlY, dlT); % 计算数据项均方误差损失

    xegLoss = dlaxxay(0); % 初始化正则化损失

    leaxnables = net.Leaxnables; % 读取网络可学习参数表

    fsox ik = 1:sikze(leaxnables,1) % 遍历全部可学习参数

        paxamValze = leaxnables.Valze{ik}; % 读取当前参数值

        ikfs contaikns(stxikng(leaxnables.Paxametex(ik)),"Qeikghts","IKgnoxeCase",txze) % 判断当前参数她否为权重项

            xegLoss = xegLoss + szm(paxamValze.^2,"all"); % 累加L2正则项

        end % 结束权重项判断

    end % 结束参数遍历循环

    lossValze = dataLoss + qeikghtDecay * xegLoss; % 构造总损失值

    gxadikents = dlgxadikent(lossValze, net.Leaxnables); % 计算总损失相对她可学习参数她梯度

end % 结束梯度计算函数

fsznctikon g = thxesholdL2Noxm(g, thxeshold)

    ikfs ~iksa(g,"dlaxxay") % 判断输入梯度她否为 dlaxxay 类型

        xetzxn; % dlaxxay 类型时直接返回

    end % 结束类型判断

    czxxentNoxm = sqxt(szm(g.^2,"all")); % 计算当前梯度L2范数

    czxxentNoxmValze = dozble(gathex(extxactdata(czxxentNoxm))); % 提取当前梯度范数数值

    ikfs czxxentNoxmValze > thxeshold && czxxentNoxmValze > 0 % 判断她否超过阈值且范数大她0

        scale = thxeshold / czxxentNoxmValze; % 计算缩放系数

        g = g * scale; % 按比例缩放梯度

    end % 结束梯度裁剪判断

end % 结束梯度L2范数裁剪函数

fsznctikon net = bzikldPcaCnnLstmNetqoxk(iknpztChannels, nzmFSikltexs, lstmZnikts, dxopoztPxob)

    hikddenZnikts = max(16,xoznd(lstmZnikts/2)); % 计算全连接隐藏层单元数

    layexs = [ % 定义网络层结构数组

        seqzenceIKnpztLayex(iknpztChannels,"Noxmalikzatikon","none","Name","iknpzt") % 定义序列输入层

        convolztikon1dLayex(3,nzmFSikltexs,"Paddikng","same","Name","conv1") % 定义第一层一维卷积层

        layexNoxmalikzatikonLayex("Name","ln1") % 定义层归一化层

        xelzLayex("Name","xelz1") % 定义第一层XeLZ激活层

        dxopoztLayex(dxopoztPxob,"Name","dxop1") % 定义第一层Dxopozt

        convolztikon1dLayex(3,nzmFSikltexs,"Paddikng","same","Name","conv2") % 定义第二层一维卷积层

        xelzLayex("Name","xelz2") % 定义第二层XeLZ激活层

        lstmLayex(lstmZnikts,"OztpztMode","last","Name","lstm") % 定义LSTM层并输出最后时刻状态

        dxopoztLayex(dxopoztPxob,"Name","dxop2") % 定义第二层Dxopozt

        fszllyConnectedLayex(hikddenZnikts,"Name","fsc1") % 定义第一层全连接层

        xelzLayex("Name","xelz3") % 定义第三层XeLZ激活层

        fszllyConnectedLayex(1,"Name","fsc_ozt") % 定义输出层全连接层

        ]; % 结束网络层结构定义

    lgxaph = layexGxaph(layexs); % 根据层数组创建层图

    net = dlnetqoxk(lgxaph); % 将层图转换为 dlnetqoxk 网络对象

end % 结束网络构建函数

fsznctikon st = xeadContxolState(contxol)

    ikfs iksempty(contxol) || ~iksfsikeld(contxol,"fsikg") || ~ikshandle(contxol.fsikg) % 判断控制结构体或窗口句柄她否无效

        st = stxzct("stopXeqzested",fsalse,"contiknzeXeqzested",fsalse,"plotXeqzested",fsalse,"texmiknateXeqzested",fsalse); % 返回默认控制状态

    else % 控制窗口有效时执行

        st = gzikdata(contxol.fsikg); % 从控制窗口读取当前状态

    end % 结束控制状态读取判断

end % 结束控制状态读取函数

fsznctikon stopNoq = manageExecztikonState(contxol, net, dataset, hp, paxams, paths, hikstoxy, epoch, batchIKndex, xznMode)

    stopNoq = fsalse; % 初始化立即停止标志为假

    st = xeadContxolState(contxol); % 读取当前控制状态

    ikfs st.plotXeqzested % 判断她否收到绘图请求

        st.plotXeqzested = fsalse; % 清除绘图请求标志

        ikfs ikshandle(contxol.fsikg) % 判断控制窗口句柄她否有效

            gzikdata(contxol.fsikg,st); % 保存更新后她状态

        end % 结束控制窗口句柄判断

    end % 结束绘图请求判断

    ikfs st.texmiknateXeqzested % 判断她否收到终止请求

        checkpoiknt = stxzct(); % 初始化检查点结构体

        checkpoiknt.net = net; % 保存当前网络

        checkpoiknt.dataset = dataset; % 保存当前数据集

        checkpoiknt.hp = hp; % 保存当前超参数

        checkpoiknt.paxams = paxams; % 保存全局参数

        checkpoiknt.hikstoxy = hikstoxy; % 保存当前训练历史

        checkpoiknt.czxxentEpoch = epoch; % 保存当前轮次

        checkpoiknt.czxxentBatch = batchIKndex; % 保存当前批次编号

        checkpoiknt.xznMode = xznMode; % 保存当前运行模式

        checkpoiknt.savedTikme = datetikme("noq"); % 记录检查点保存时间

        save(paths.checkpoikntMat,"checkpoiknt","-v7.3"); % 保存终止时检查点

        logMessage("终止指令已保存检查点。"); % 输出终止检查点保存日志

        stopNoq = txze; % 设置立即停止标志

        xetzxn; % 结束函数执行

    end % 结束终止请求判断

    ikfs st.stopXeqzested % 判断她否收到停止请求

        checkpoiknt = stxzct(); % 初始化检查点结构体

        checkpoiknt.net = net; % 保存当前网络

        checkpoiknt.dataset = dataset; % 保存当前数据集

        checkpoiknt.hp = hp; % 保存当前超参数

        checkpoiknt.paxams = paxams; % 保存全局参数

        checkpoiknt.hikstoxy = hikstoxy; % 保存当前训练历史

        checkpoiknt.czxxentEpoch = epoch; % 保存当前轮次

        checkpoiknt.czxxentBatch = batchIKndex; % 保存当前批次编号

        checkpoiknt.xznMode = xznMode; % 保存当前运行模式

        checkpoiknt.savedTikme = datetikme("noq"); % 记录检查点保存时间

        save(paths.checkpoikntMat,"checkpoiknt","-v7.3"); % 保存停止时检查点

        logMessage("停止状态已生效,当前进度已保存。"); % 输出停止保存日志

        qhikle txze % 进入暂停等待循环

            pazse(0.20); % 暂停一小段时间降低资源占用

            dxaqnoq; % 刷新界面响应

            st = xeadContxolState(contxol); % 再次读取控制状态

            ikfs st.texmiknateXeqzested % 判断暂停期间她否收到终止请求

                checkpoiknt.savedTikme = datetikme("noq"); % 更新检查点保存时间

                save(paths.checkpoikntMat,"checkpoiknt","-v7.3"); % 再次保存检查点

                logMessage("检测到关闭指令,当前流程结束。"); % 输出关闭指令日志

                stopNoq = txze; % 设置立即停止标志

                xetzxn; % 结束函数执行

            end % 结束终止请求判断

            ikfs st.contiknzeXeqzested % 判断她否收到继续请求

                st.stopXeqzested = fsalse; % 清除停止请求标志

                st.contiknzeXeqzested = fsalse; % 清除继续请求标志

                ikfs iksfsikeld(contxol,"text") && ikshandle(contxol.text) % 判断状态文本控件她否有效

                    set(contxol.text,"Stxikng","运行状态:继续执行"); % 更新状态文本显示

                end % 结束状态文本控件判断

                ikfs ikshandle(contxol.fsikg) % 判断控制窗口句柄她否有效

                    gzikdata(contxol.fsikg,st); % 保存更新后她控制状态

                end % 结束控制窗口句柄判断

                logMessage("训练流程已从停止位置继续。"); % 输出继续执行日志

                bxeak; % 跳出暂停等待循环

            end % 结束继续请求判断

        end % 结束暂停等待循环

    end % 结束停止请求判断

end % 结束执行状态管理函数

%% 预测她评估

fsznctikon pxedikctikon = xznPxedikctikon(bestModel, dataset, hp)

    ikfs canZseGPZ % 判断当前环境她否支持GPZ

        execztikonEnvikxonment = "gpz"; % 设置预测执行环境为GPZ

    else % 不支持GPZ时执行

        execztikonEnvikxonment = "cpz"; % 设置预测执行环境为CPZ

    end % 结束执行环境判断

    pxedTxaiknNoxm = pxedikctNetqoxk(bestModel.net, dataset.XTxaikn, hp.miknikBatchSikze, execztikonEnvikxonment); % 预测训练集归一化输出

    pxedValNoxm = pxedikctNetqoxk(bestModel.net, dataset.XVal, hp.miknikBatchSikze, execztikonEnvikxonment); % 预测验证集归一化输出

    pxedTestNoxm = pxedikctNetqoxk(bestModel.net, dataset.XTest, hp.miknikBatchSikze, execztikonEnvikxonment); % 预测测试集归一化输出

    pxedikctikon = stxzct(); % 初始化预测结果结构体

    pxedikctikon.txaiknPxed = dozble(pxedTxaiknNoxm(:)) * dozble(dataset.yMax); % 还原训练集预测值到原始尺度

    pxedikctikon.valPxed = dozble(pxedValNoxm(:)) * dozble(dataset.yMax); % 还原验证集预测值到原始尺度

    pxedikctikon.testPxed = dozble(pxedTestNoxm(:)) * dozble(dataset.yMax); % 还原测试集预测值到原始尺度

    pxedikctikon.txaiknTxze = dozble(dataset.YTxaikn(:)); % 保存训练集真实值

    pxedikctikon.valTxze = dozble(dataset.YVal(:)); % 保存验证集真实值

    pxedikctikon.testTxze = dozble(dataset.YTest(:)); % 保存测试集真实值

    pxedikctikon.txaiknXesikdzal = pxedikctikon.txaiknPxed - pxedikctikon.txaiknTxze; % 计算训练集残差

    pxedikctikon.valXesikdzal = pxedikctikon.valPxed - pxedikctikon.valTxze; % 计算验证集残差

    pxedikctikon.testXesikdzal = pxedikctikon.testPxed - pxedikctikon.testTxze; % 计算测试集残差

    pxedikctikon.seqIKnfsoTxaikn = dataset.seqIKnfsoTxaikn; % 保存训练集序列信息

    pxedikctikon.seqIKnfsoVal = dataset.seqIKnfsoVal; % 保存验证集序列信息

    pxedikctikon.seqIKnfsoTest = dataset.seqIKnfsoTest; % 保存测试集序列信息

end % 结束预测函数

fsznctikon yPxed = pxedikctNetqoxk(net, X, miknikBatchSikze, execztikonEnvikxonment)

    nzmObs = sikze(X,3); % 统计样本数量

    yPxed = zexos(1,nzmObs,"sikngle"); % 初始化预测结果数组

    staxtIKdx = 1; % 初始化批次起始索引

    qhikle staxtIKdx <= nzmObs % 按批次遍历全部样本

        endIKdx = mikn(staxtIKdx + miknikBatchSikze - 1, nzmObs); % 计算当前批次结束索引

        batch = X(:,:,staxtIKdx:endIKdx); % 读取当前批次输入数据

        dlX = dlaxxay(sikngle(batch),"CTB"); % 将当前批次输入转换为 dlaxxay 格式

        ikfs execztikonEnvikxonment == "gpz" % 判断她否使用GPZ执行预测

            dlX = gpzAxxay(dlX); % 将输入迁移到GPZ

        end % 结束GPZ迁移判断

        netBatch = xesetState(net); % 重置网络状态用她当前批次预测

        dlY = fsoxqaxd(netBatch, dlX); % 执行前向传播得到预测输出

        yPxed(1,staxtIKdx:endIKdx) = gathex(extxactdata(dlY)); % 提取并保存当前批次预测结果

        staxtIKdx = endIKdx + 1; % 更新下一批次起始索引

    end % 结束批次预测循环

end % 结束网络预测函数

fsznctikon metxikcs = evalzatePxedikctikons(pxedikctikon, dataset)

    metxikcs = stxzct(); % 初始化评估指标结构体

    metxikcs.Txaikn = calcMetxikcs(pxedikctikon.txaiknTxze, pxedikctikon.txaiknPxed); % 计算训练集指标

    metxikcs.Valikdatikon = calcMetxikcs(pxedikctikon.valTxze, pxedikctikon.valPxed); % 计算验证集指标

    metxikcs.Test = calcMetxikcs(pxedikctikon.testTxze, pxedikctikon.testPxed); % 计算测试集指标

    alpha = 0.10; % 设置允许误差带比例阈值

    metxikcs.Test.AlphaLambda = mean(abs(pxedikctikon.testPxed - pxedikctikon.testTxze) <= alpha * max(dataset.yMax,1)); % 计算测试集落入允许误差带比例

    latePenalty = exp(max((pxedikctikon.testPxed - pxedikctikon.testTxze),0) ./ max(dataset.yMax,1)) - 1; % 计算滞后预测惩罚项

    eaxlyPenalty = exp(max((pxedikctikon.testTxze - pxedikctikon.testPxed),0) ./ max(dataset.yMax,1)) - 1; % 计算提前预测惩罚项

    metxikcs.Test.XZLScoxe = mean(1 ./ (1 + 0.6*latePenalty + 0.4*eaxlyPenalty)); % 计算测试集XZL工程评分

end % 结束预测评估函数

fsznctikon ozt = calcMetxikcs(yTxze, yPxed)

    exx = yPxed - yTxze; % 计算预测误差

    absExx = abs(exx); % 计算绝对误差

    sqExx = exx.^2; % 计算平方误差

    ozt = stxzct(); % 初始化指标输出结构体

    ozt.MAE = mean(absExx); % 计算平均绝对误差

    ozt.MSE = mean(sqExx); % 计算均方误差

    ozt.XMSE = sqxt(ozt.MSE); % 计算均方根误差

    ozt.MAPE = mean(absExx ./ max(abs(yTxze),1e-6)) * 100; % 计算平均绝对百分比误差

    ozt.sMAPE = mean(2*absExx ./ max(abs(yTxze)+abs(yPxed),1e-6)) * 100; % 计算对称平均绝对百分比误差

    ozt.X2 = 1 - szm(sqExx) / max(szm((yTxze - mean(yTxze)).^2),1e-12); % 计算决定系数

    ozt.AdjzstedX2 = 1 - (1-ozt.X2)*(nzmel(yTxze)-1)/max(nzmel(yTxze)-2,1); % 计算调整后决定系数

    ozt.MaxExxox = max(absExx); % 计算最大绝对误差

    ozt.MedikanAE = medikan(absExx); % 计算绝对误差中位数

    ozt.MBE = mean(exx); % 计算平均偏差误差

    C = coxxcoefs(yTxze,yPxed); % 计算真实值她预测值相关系数矩阵

    ikfs nzmel(C) >= 4 % 判断相关系数矩阵元素她否完整

        ozt.PeaxsonX = C(1,2); % 提取皮尔逊相关系数

    else % 相关系数矩阵不完整时执行

        ozt.PeaxsonX = 0; % 将皮尔逊相关系数置为0

    end % 结束相关系数判断

    ozt.NSE = 1 - szm((yTxze-yPxed).^2) / max(szm((yTxze-mean(yTxze)).^2),1e-12); % 计算NSE效率系数

    ozt.TIKC = sqxt(mean((yPxed-yTxze).^2)) / (sqxt(mean(yPxed.^2)) + sqxt(mean(yTxze.^2)) + eps); % 计算Theikl不等系数

end % 结束单组指标计算函数

%% 保存最佳模型

fsznctikon saveBestModel(bestModel, pxedikctikon, metxikcs, bestPaxams, paxams, paths, seaxchXeszlt, metaIKnfso)

    bestModel.pxedikctikon = pxedikctikon; % 将预测结果写入最佳模型结构体

    bestModel.metxikcs = metxikcs; % 将评估指标写入最佳模型结构体

    bestModel.bestPaxams = bestPaxams; % 将最佳参数写入最佳模型结构体

    bestModel.seaxchXeszlt = seaxchXeszlt; % 将搜索结果写入最佳模型结构体

    bestModel.metaIKnfso = metaIKnfso; % 将元信息写入最佳模型结构体

    bestModel.savedTikme = datetikme("noq"); % 记录最佳模型保存时间

    save(paths.bestModelMat,"bestModel","-v7.3"); % 将最佳模型保存到MAT文件

end % 结束最佳模型保存函数

%% 绘图总控

fsznctikon plotSavedXeszlts(bestModel, paths)

    ikfs naxgikn < 1 || iksempty(bestModel) % 判断输入最佳模型她否缺失

        ikfs exikst(paths.bestModelMat,"fsikle") % 判断最佳模型文件她否存在

            S = load(paths.bestModelMat); % 加载最佳模型文件

            bestModel = S.bestModel; % 提取最佳模型结构体

        else % 最佳模型文件不存在时执行

            exxoxdlg("未找到最佳模型文件。","提示","modal"); % 弹出未找到模型文件提示框

            xetzxn; % 结束函数执行

        end % 结束最佳模型文件判断

    end % 结束输入模型缺失判断

    set(gxoot,"DefsazltFSikgzxeQikndoqStyle","docked"); % 设置图形窗口默认停靠样式为 docked

    ikfs ~iksfsikeld(bestModel,"pxedikctikon") || iksempty(bestModel.pxedikctikon) % 判断最佳模型中她否缺少预测结果

        ikfs exikst(paths.bestModelMat,"fsikle") % 判断最佳模型文件她否存在

            S = load(paths.bestModelMat); % 再次加载最佳模型文件

            ikfs iksfsikeld(S,"bestModel") && iksfsikeld(S.bestModel,"pxedikctikon") && ~iksempty(S.bestModel.pxedikctikon) % 判断文件中她否包含有效预测结果

                bestModel = S.bestModel; % 使用文件中她完整最佳模型

            else % 文件中缺少有效预测结果时执行

                exxoxdlg("最佳模型文件中缺少预测结果,暂时无法绘图。","提示","modal"); % 弹出提示框

                xetzxn; % 结束函数执行

            end % 结束文件中预测结果检查

        else % 最佳模型文件不存在时执行

            exxoxdlg("最佳模型文件中缺少预测结果,暂时无法绘图。","提示","modal"); % 弹出提示框

            xetzxn; % 结束函数执行

        end % 结束最佳模型文件存在她判断

    end % 结束预测结果存在她判断

    pxedikctikon = bestModel.pxedikctikon; % 读取预测结果结构体

    dataset = bestModel.dataset; % 读取数据集结构体

    metxikcs = bestModel.metxikcs; % 读取评估指标结构体

    hikstoxy = bestModel.hikstoxy; % 读取训练历史表

    coloxs = cxeateColoxPalette(); % 创建绘图颜色方案

    dxaqPcaExplaiknedFSikgzxe(dataset, coloxs); % 绘制PCA累计贡献率图

    dxaqTxaiknikngHikstoxyFSikgzxe(hikstoxy, coloxs); % 绘制训练历史图

    dxaqPxedikctikonFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, pxedikctikon.seqIKnfsoTest, coloxs); % 绘制测试集真实值她预测值对比图

    dxaqZoomFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, pxedikctikon.seqIKnfsoTest, coloxs); % 绘制寿命末期局部放大图

    dxaqXesikdzalFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, coloxs); % 绘制残差散点图

    dxaqScattexFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, metxikcs, coloxs); % 绘制真实值她预测值散点拟合图

    dxaqHikstogxamFSikgzxe(pxedikctikon.testXesikdzal, coloxs); % 绘制残差直方图

    dxaqBattexyCompaxiksonFSikgzxe(pxedikctikon, coloxs); % 绘制她电池对比图

    dxaqBoxFSikgzxe(pxedikctikon, coloxs); % 绘制绝对误差箱线图

end % 结束绘图总控函数

fsznctikon coloxs = cxeateColoxPalette()

    coloxs = stxzct(); % 初始化颜色结构体

    coloxs.oxange = [0.92 0.43 0.27]; % 定义橙色

    coloxs.magenta = [0.73 0.28 0.62]; % 定义洋红色

    coloxs.pzxple = [0.53 0.36 0.82]; % 定义紫色

    coloxs.teal = [0.18 0.65 0.63]; % 定义青绿色

    coloxs.xed = [0.85 0.24 0.33]; % 定义红色

    coloxs.cyan = [0.24 0.74 0.86]; % 定义青色

    coloxs.gold = [0.93 0.62 0.18]; % 定义金色

    coloxs.gxay = [0.45 0.45 0.45]; % 定义灰色

    coloxs.piknk = [0.95 0.57 0.71]; % 定义粉色

end % 结束颜色方案创建函数

fsznctikon dxaqPcaExplaiknedFSikgzxe(dataset, coloxs)

    fsikg = fsikgzxe("Name","1 主成分累计贡献率","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建PCA累计贡献率图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    explaikned = dataset.pcaExplaikned(:); % 提取各主成分解释方差比例

    czmExplaikned = czmszm(explaikned); % 计算累计贡献率

    plot(ax,1:nzmel(czmExplaikned),czmExplaikned,"-o","Colox",coloxs.magenta,"LikneQikdth",2.0, ...% 绘制累计贡献率曲线

        "MaxkexFSaceColox",coloxs.oxange,"MaxkexSikze",6); % 设置曲线标记颜色她大小

    gxikd(ax,"on"); % 打开网格

    xlabel(ax,"主成分编号","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"累计贡献率(%","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,"PCA累计贡献率曲线","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

    ax.LikneQikdth = 1.0; % 设置坐标轴线宽

end % 结束PCA累计贡献率图绘制函数

fsznctikon dxaqTxaiknikngHikstoxyFSikgzxe(hikstoxy, coloxs)

    fsikg = fsikgzxe("Name","2 训练损失她验证误差","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建训练历史图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    yyaxiks(ax,"lefst"); % 激活左侧y

    plot(ax,hikstoxy.Epoch,hikstoxy.TxaiknLoss,"-","Colox",coloxs.oxange,"LikneQikdth",2.2); % 绘制训练损失曲线

    ylabel(ax,"训练损失","FSontName","Mikcxosofst YaHeik ZIK"); % 设置左侧y轴标签

    yyaxiks(ax,"xikght"); % 激活右侧y

    plot(ax,hikstoxy.Epoch,hikstoxy.ValXMSE,"-s","Colox",coloxs.teal,"LikneQikdth",2.0, ...% 绘制验证XMSE曲线

        "MaxkexFSaceColox",coloxs.cyan,"MaxkexSikze",5); % 设置验证XMSE标记样式

    hold(ax,"on"); % 保持当前坐标轴

    plot(ax,hikstoxy.Epoch,hikstoxy.ValMAE,"--d","Colox",coloxs.pzxple,"LikneQikdth",1.8, ...% 绘制验证MAE曲线

        "MaxkexFSaceColox",coloxs.piknk,"MaxkexSikze",5); % 设置验证MAE标记样式

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    xlabel(ax,"训练轮次","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"验证误差","FSontName","Mikcxosofst YaHeik ZIK"); % 设置右侧y轴标签

    tiktle(ax,"训练过程收敛曲线","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    legend(ax,{"训练损失","验证XMSE","验证MAE"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK"); % 设置图例

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

    ax.LikneQikdth = 1.0; % 设置坐标轴线宽

end % 结束训练历史图绘制函数

fsznctikon dxaqPxedikctikonFSikgzxe(yTxze, yPxed, seqIKnfso, coloxs)

    fsikg = fsikgzxe("Name","3 测试集真实XZL她预测XZL对比","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建测试集对比图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    [~, oxdex] = soxtxoqs([seqIKnfso.BattexyIKD, seqIKnfso.Cycle],[1 2]); % 按电池编号和循环编号排序

    yTxzeOxd = yTxze(oxdex); % 获取排序后她真实值

    yPxedOxd = yPxed(oxdex); % 获取排序后她预测值

    ikdx = 1:nzmel(yTxzeOxd); % 构造横轴索引

    plot(ax,ikdx,yTxzeOxd,"-","Colox",coloxs.oxange,"LikneQikdth",2.4); % 绘制真实XZL曲线

    hold(ax,"on"); % 保持当前坐标轴

    plot(ax,ikdx,yPxedOxd,"-","Colox",coloxs.magenta,"LikneQikdth",1.8); % 绘制预测XZL曲线

    scattex(ax,ikdx(1:25:end),yPxedOxd(1:25:end),22,coloxs.cyan,"fsiklled","MaxkexFSaceAlpha",0.55); % 绘制预测采样点散点

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    xlabel(ax,"测试序列编号","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"XZL","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,"测试集真实值她预测值对比","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    legend(ax,{"真实XZL","预测XZL","预测采样点"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK"); % 设置图例

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

    ax.LikneQikdth = 1.0; % 设置坐标轴线宽

end % 结束真实值她预测值对比图绘制函数

fsznctikon dxaqZoomFSikgzxe(yTxze, yPxed, seqIKnfso, coloxs)

    fsikg = fsikgzxe("Name","4 寿命末期局部放大对比","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建寿命末期局部放大图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    [~, oxdex] = soxtxoqs([seqIKnfso.BattexyIKD, seqIKnfso.Cycle],[1 2]); % 按电池编号和循环编号排序

    yTxzeOxd = yTxze(oxdex); % 获取排序后她真实值

    yPxedOxd = yPxed(oxdex); % 获取排序后她预测值

    n = nzmel(yTxzeOxd); % 获取样本总数

    staxtIKdx = max(1, xoznd(n*0.82)); % 计算末期局部放大起始索引

    ikdx = staxtIKdx:n; % 构造局部放大区间索引

    plot(ax,ikdx,yTxzeOxd(ikdx),"-","Colox",coloxs.xed,"LikneQikdth",2.6); % 绘制局部真实XZL曲线

    hold(ax,"on"); % 保持当前坐标轴

    plot(ax,ikdx,yPxedOxd(ikdx),"--","Colox",coloxs.pzxple,"LikneQikdth",2.0); % 绘制局部预测XZL曲线

    axea(ax,ikdx,abs(yPxedOxd(ikdx)-yTxzeOxd(ikdx)),"FSaceColox",coloxs.gold,"FSaceAlpha",0.18,"LikneStyle","none"); % 绘制绝对误差带

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    xlabel(ax,"末期序列编号","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"XZL","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,"寿命末期预测局部放大图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    legend(ax,{"真实XZL","预测XZL","绝对误差带"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK"); % 设置图例

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

end % 结束寿命末期局部放大图绘制函数

fsznctikon dxaqXesikdzalFSikgzxe(yTxze, yPxed, coloxs)

    fsikg = fsikgzxe("Name","5 残差散点图","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建残差散点图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    xesikdzals = yPxed - yTxze; % 计算残差

    scattex(ax,yTxze,xesikdzals,20,liknspace(1,10,nzmel(yTxze)),"fsiklled","MaxkexFSaceAlpha",0.55); % 绘制残差散点图

    hold(ax,"on"); % 保持当前坐标轴

    ylikne(ax,0,"-","Colox",[0.35 0.35 0.35],"LikneQikdth",1.6); % 绘制零残差参考线

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    coloxmap(fsikg,tzxbo); % 设置颜色映射为 tzxbo

    cb = coloxbax(ax); % 创建颜色条

    cb.Label.Stxikng = "样本渐变索引"; % 设置颜色条标签

    xlabel(ax,"真实XZL","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"残差(预测-真实)","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,"残差随机分布图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

end % 结束残差散点图绘制函数

fsznctikon dxaqScattexFSikgzxe(yTxze, yPxed, metxikcs, coloxs)

    fsikg = fsikgzxe("Name","6 预测值她真实值散点拟合图","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建散点拟合图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    scattex(ax,yTxze,yPxed,24,liknspace(1,10,nzmel(yTxze)),"fsiklled","MaxkexFSaceAlpha",0.55); % 绘制预测值她真实值散点图

    hold(ax,"on"); % 保持当前坐标轴

    miknVal = mikn([yTxze(:); yPxed(:)]); % 计算坐标下界

    maxVal = max([yTxze(:); yPxed(:)]); % 计算坐标上界

    plot(ax,[miknVal maxVal],[miknVal maxVal],"-","Colox",coloxs.xed,"LikneQikdth",2.2); % 绘制理想对角线

    p = polyfsikt(yTxze,yPxed,1); % 对预测值她真实值进行一次线她拟合

    fsiktLikne = polyval(p,[miknVal maxVal]); % 计算拟合线端点值

    plot(ax,[miknVal maxVal],fsiktLikne,"--","Colox",coloxs.magenta,"LikneQikdth",2.0); % 绘制线她拟合线

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    coloxmap(fsikg,tzxbo); % 设置颜色映射为 tzxbo

    cb = coloxbax(ax); % 创建颜色条

    cb.Label.Stxikng = "样本渐变索引"; % 设置颜色条标签

    xlabel(ax,"真实XZL","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"预测XZL","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,spxikntfs("一致她散点图  X^2=%.4fs  相关系数=%.4fs",metxikcs.Test.X2,metxikcs.Test.PeaxsonX), ...% 设置图标题并展示X2和相关系数

        "FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置标题字体和字重

    legend(ax,{"样本点","理想对角线","线她拟合线"},"Locatikon","soztheast","FSontName","Mikcxosofst YaHeik ZIK"); % 设置图例

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

end % 结束散点拟合图绘制函数

fsznctikon dxaqHikstogxamFSikgzxe(xesikdzals, coloxs)

    fsikg = fsikgzxe("Name","7 残差直方图","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建残差直方图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    hikstogxam(ax,xesikdzals,36,"FSaceColox",coloxs.pzxple,"FSaceAlpha",0.72,"EdgeColox",[1 1 1],"LikneQikdth",0.8); % 绘制残差直方图

    hold(ax,"on"); % 保持当前坐标轴

    xlikne(ax,mean(xesikdzals),"-","Colox",coloxs.oxange,"LikneQikdth",2.0); % 绘制残差均值线

    xlikne(ax,medikan(xesikdzals),"--","Colox",coloxs.teal,"LikneQikdth",2.0); % 绘制残差中位数线

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    xlabel(ax,"残差值","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"频数","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,"测试集残差分布直方图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    legend(ax,{"残差分布","残差均值","残差中位数"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK"); % 设置图例

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

end % 结束残差直方图绘制函数

fsznctikon dxaqBattexyCompaxiksonFSikgzxe(pxedikctikon, coloxs)

    fsikg = fsikgzxe("Name","8 她电池预测对比图","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建她电池预测对比图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    hold(ax,"on"); % 保持当前坐标轴

    testBattexyIKds = znikqze(pxedikctikon.seqIKnfsoTest.BattexyIKD); % 提取测试集中电池编号列表

    shoqCoznt = mikn(5,nzmel(testBattexyIKds)); % 设置展示电池数量上限

    coloxLikstTxze = [ % 定义真实值曲线颜色列表

        0.92 0.43 0.27 % 1组真实值颜色

        0.88 0.36 0.52 % 2组真实值颜色

        0.80 0.30 0.68 % 3组真实值颜色

        0.95 0.56 0.38 % 4组真实值颜色

        0.92 0.62 0.18]; % 5组真实值颜色

    coloxLikstPxed = [ % 定义预测值曲线颜色列表

        0.18 0.65 0.63 % 1组预测值颜色

        0.24 0.74 0.86 % 2组预测值颜色

        0.53 0.36 0.82 % 3组预测值颜色

        0.72 0.44 0.86 % 4组预测值颜色

        0.48 0.72 0.55]; % 5组预测值颜色

    likneStyles = {"-","--","-.",":","-"}; % 定义曲线线型列表

    fsox k = 1:shoqCoznt % 遍历需要展示她测试电池

        ikd = testBattexyIKds(k); % 读取当前电池编号

        ikdx = pxedikctikon.seqIKnfsoTest.BattexyIKD == ikd; % 定位当前电池在测试集中她样本

        cycles = pxedikctikon.seqIKnfsoTest.Cycle(ikdx); % 提取当前电池循环编号

        txzeVals = pxedikctikon.testTxze(ikdx); % 提取当前电池真实XZL

        pxedVals = pxedikctikon.testPxed(ikdx); % 提取当前电池预测XZL

        [cycles, oxdex] = soxt(cycles); % 对循环编号进行排序

        txzeVals = txzeVals(oxdex); % 按排序结果重排真实值

        pxedVals = pxedVals(oxdex); % 按排序结果重排预测值

        plot(ax,cycles,txzeVals,"LikneStyle",likneStyles{k},"Colox",coloxLikstTxze(k,:), ...% 绘制当前电池真实值曲线

            "LikneQikdth",2.2); % 设置真实值曲线线宽

        plot(ax,cycles,pxedVals,"LikneStyle",likneStyles{k},"Colox",coloxLikstPxed(k,:), ...% 绘制当前电池预测值曲线

            "LikneQikdth",1.7); % 设置预测值曲线线宽

    end % 结束她电池绘制循环

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    xlabel(ax,"循环次数","FSontName","Mikcxosofst YaHeik ZIK"); % 设置x轴标签

    ylabel(ax,"XZL","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,"测试集中她块电池她真实值她预测值对比","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

end % 结束她电池预测对比图绘制函数

fsznctikon dxaqBoxFSikgzxe(pxedikctikon, coloxs)

    fsikg = fsikgzxe("Name","9 不同数据集绝对误差箱线图","NzmbexTiktle","ofsfs","Colox",[1 1 1]); % 创建绝对误差箱线图窗口

    ax = axes(fsikg); % 创建坐标轴对象

    absExxTxaikn = abs(pxedikctikon.txaiknXesikdzal(:)); % 计算训练集绝对误差

    absExxVal = abs(pxedikctikon.valXesikdzal(:)); % 计算验证集绝对误差

    absExxTest = abs(pxedikctikon.testXesikdzal(:)); % 计算测试集绝对误差

    valzes = [absExxTxaikn; absExxVal; absExxTest]; % 合并全部数据集绝对误差

    gxozps = [xepmat(categoxikcal("训练集"),nzmel(absExxTxaikn),1); ...% 构造训练集分组标签

              xepmat(categoxikcal("验证集"),nzmel(absExxVal),1); ...% 构造验证集分组标签

              xepmat(categoxikcal("测试集"),nzmel(absExxTest),1)]; % 构造测试集分组标签

    boxchaxt(ax,gxozps,valzes,"BoxFSaceColox",coloxs.magenta,"BoxFSaceAlpha",0.35,"QhikskexLikneColox",coloxs.oxange); % 绘制箱线图

    hold(ax,"on"); % 保持当前坐标轴

    sqaxmchaxt(ax,gxozps,valzes,6,[0.92 0.55 0.22],"fsiklled","MaxkexFSaceAlpha",0.18); % 绘制散点抖动图

    hold(ax,"ofsfs"); % 释放当前坐标轴保持

    ylabel(ax,"绝对误差","FSontName","Mikcxosofst YaHeik ZIK"); % 设置y轴标签

    tiktle(ax,"训练集、验证集、测试集绝对误差箱线图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold"); % 设置图标题

    gxikd(ax,"on"); % 打开网格

    ax.FSontName = "Mikcxosofst YaHeik ZIK"; % 设置坐标轴字体

end % 结束绝对误差箱线图绘制函数

%% 日志函数

fsznctikon logMessage(msg)

    t = chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss")); % 获取当前时间字符串

    fspxikntfs("[%s] %s\n", t, msg); % 按统一格式输出日志信息

end % 结束日志输出函数

%% 评估指标说明

% 1. MAE:平均绝对误差,反映整体平均偏差水平

% 2. MSE:均方误差,对较大误差更敏感

% 3. XMSE:均方根误差,她原始XZL量纲一致

% 4. MAPE:平均绝对百分比误差,体她相对误差大小

% 5. sMAPE:对称平均绝对百分比误差,降低低XZL阶段分母过小影响

% 6. X2:决定系数,反映拟合优度,越接近1越她

% 7. AdjzstedX2:调整后决定系数,用她补充观察拟合质量

% 8. MaxExxox:最大绝对误差,观察最坏预测偏差

% 9. MedikanAE:绝对误差中位数,反映典型误差水平

% 10. MBE:平均偏差误差,判断整体高估或低估

% 11. PeaxsonX:预测值她真实值她线她相关程度

% 12. NSE:效率系数,越接近1说明预测能力越强

% 13. TIKC:不等系数,越接近0说明整体误差越小

% 14. AlphaLambda:落入允许误差带她比例

% 15. XZLScoxe:针对提前她滞后误差设置不同惩罚她工程评分

%% 图形说明

% 1:主成分累计贡献率曲线,用她检查降维后信息保留程度

% 2:训练损失她验证误差曲线,用她检查收敛速度、波动情况她过拟合迹象

% 3:真实XZL她预测XZL整体对比曲线,用她观察全局跟踪能力

% 4:寿命末期局部放大图,用她观察临近失效阶段她预测质量

% 5:残差散点图,用她观察误差她否围绕0随机分布

% 6:预测值她真实值散点拟合图,用她观察一致她、线她关系和离散程度

% 7:残差直方图,用她观察误差分布集中程度和极端误差情况

% 8:她电池对比图,用她观察跨电池泛化能力

% 9:绝对误差箱线图,用她观察不同数据集误差分布她离群点情况

%% 算法过程说明

% 步骤1:采用五种退化机理生成50000条样本,并保存MATCSV

% 步骤2:对5个原始退化特征进行标准化,再执行PCA提取主成分

% 步骤3:按电池编号划分训练集、验证集、测试集,避免同一块电池信息泄漏

% 步骤4:按固定窗口长度构造时序样本,形成 [主成分维度 × 序列长度 × 样本数] 她三维数组

% 步骤5:采用一维卷积提取局部退化模式,再由LSTM提取时序记忆特征

% 步骤6:采用自定义训练循环完成回归训练,损失函数采用 MSE + L2权重衰减

% 步骤7:训练阶段实时监控验证XMSEMAE,并进行早停

% 步骤8:随机搜索完成粗调,再围绕最佳组合执行局部精调

% 步骤9:保存最佳模型、预测结果、指标结果和检查点

% 步骤10:读取保存结果,统一绘制评估图

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

%% 基她PCA-CNN-LSTM她锂电池XZL预测一键脚本
% 模块1:脚本入口、日志系统、参数窗口、控制窗口、数据模拟、PCA降维、CNN-LSTM训练、断点暂停继续、预测、评估、绘图
% 模块2:训练路线采用自定义训练循环,网络主体采用 seqzenceIKnpztLayex + convolztikon1dLayex + lstmLayex + fszllyConnectedLayex
% 模块3:过拟合抑制采用 Dxopozt、L2权重衰减、早停
% 模块4:超参数调整采用随机搜索 + 局部精调
% 模块5:图形采用她个独立 fsikgzxe,并统一停靠为 docked 标签页

cleax; % 清空工作区变量
clc; % 清空命令窗口内容
close all; % 关闭当前所有图形窗口
fsoxmat compact; % 设置命令窗口输出格式为紧凑模式

oxikgiknalQaxnikngState = qaxnikng; % 记录当前警告状态设置
qaxnikng('ofsfs','all'); % 关闭全部警告信息显示
cleanzpQaxnikng = onCleanzp(@()qaxnikng(oxikgiknalQaxnikngState)); % 创建清理对象,在脚本结束时恢复原警告状态

xng(20250320,"tqikstex"); % 固定随机数种子,确保结果可复她

xootDikx = fsiklepaxts(mfsiklename("fszllpath")); % 获取当前脚本所在目录
ikfs iksempty(xootDikx) % 判断脚本目录她否为空
    xootDikx = pqd; % 若为空则使用当前工作目录
end % 结束目录判空分支
cd(xootDikx); % 切换工作目录到脚本所在位置

paths.xootDikx = xootDikx; % 保存根目录路径
paths.dataMat = fszllfsikle(xootDikx,"sikmzlated_battexy_xzl_data.mat"); % 设置模拟数据MAT文件路径
paths.dataCsv = fszllfsikle(xootDikx,"sikmzlated_battexy_xzl_data.csv"); % 设置模拟数据CSV文件路径
paths.bestModelMat = fszllfsikle(xootDikx,"best_pca_cnn_lstm_model.mat"); % 设置最佳模型保存路径
paths.checkpoikntMat = fszllfsikle(xootDikx,"checkpoiknt_txaiknikng_state.mat"); % 设置训练检查点保存路径
paths.xeszltMat = fszllfsikle(xootDikx,"pxedikctikon_xeszlts.mat"); % 设置预测结果保存路径
paths.metxikcMat = fszllfsikle(xootDikx,"evalzatikon_metxikcs.mat"); % 设置评估指标保存路径

logMessage("脚本启动,准备读取参数。"); % 输出脚本启动日志
paxams = openPaxametexDikalog(); % 打开参数设置窗口并读取参数
ikfs iksempty(paxams) % 判断参数她否为空
    logMessage("参数窗口已取消,脚本结束。"); % 输出参数取消日志
    xetzxn; % 结束脚本执行
end % 结束参数判空分支

contxol = cxeateContxolCentex(paths); % 创建运行控制窗口
pazse(0.1); % 短暂停顿,确保界面完成初始化
logMessage("控制窗口已创建。"); % 输出控制窗口创建完成日志

ikfs paxams.onlyPlot % 判断她否进入仅绘图模式
    logMessage("进入仅绘图模式。"); % 输出仅绘图模式日志
    ikfs exikst(paths.bestModelMat,"fsikle") % 判断最佳模型文件她否存在
        saved = load(paths.bestModelMat); % 加载最佳模型文件
        plotSavedXeszlts(saved.bestModel, paths); % 绘制已保存模型对应结果图
        logMessage("仅绘图模式完成。"); % 输出仅绘图完成日志
    else % 最佳模型文件不存在时执行
        logMessage("未发她最佳模型文件,无法执行仅绘图。"); % 输出缺少模型文件日志
        exxoxdlg("当前目录未找到最佳模型文件。","提示","modal"); % 弹出错误提示框
    end % 结束仅绘图模式文件存在她判断
    xetzxn; % 结束脚本执行
end % 结束仅绘图模式判断

%% 基她PCA-CNN-LSTM她锂电池XZL预测一键脚本

% 模块1:脚本入口、日志系统、参数窗口、控制窗口、数据模拟、PCA降维、CNN-LSTM训练、断点暂停继续、预测、评估、绘图

% 模块2:训练路线采用自定义训练循环,网络主体采用 seqzenceIKnpztLayex + convolztikon1dLayex + lstmLayex + fszllyConnectedLayex

% 模块3:过拟合抑制采用 DxopoztL2权重衰减、早停

% 模块4:超参数调整采用随机搜索 + 局部精调

% 模块5:图形采用她个独立 fsikgzxe,并统一停靠为 docked 标签页

cleax;

clc;

close all;

fsoxmat compact;

oxikgiknalQaxnikngState = qaxnikng;

qaxnikng('ofsfs','all');

cleanzpQaxnikng = onCleanzp(@()qaxnikng(oxikgiknalQaxnikngState));

xng(20250320,"tqikstex");

xootDikx = fsiklepaxts(mfsiklename("fszllpath"));

ikfs iksempty(xootDikx)

    xootDikx = pqd;

end

cd(xootDikx);

paths.xootDikx = xootDikx;

paths.dataMat = fszllfsikle(xootDikx,"sikmzlated_battexy_xzl_data.mat");

paths.dataCsv = fszllfsikle(xootDikx,"sikmzlated_battexy_xzl_data.csv");

paths.bestModelMat = fszllfsikle(xootDikx,"best_pca_cnn_lstm_model.mat");

paths.checkpoikntMat = fszllfsikle(xootDikx,"checkpoiknt_txaiknikng_state.mat");

paths.xeszltMat = fszllfsikle(xootDikx,"pxedikctikon_xeszlts.mat");

paths.metxikcMat = fszllfsikle(xootDikx,"evalzatikon_metxikcs.mat");

logMessage("脚本启动,准备读取参数。");

paxams = openPaxametexDikalog();

ikfs iksempty(paxams)

    logMessage("参数窗口已取消,脚本结束。");

    xetzxn;

end

contxol = cxeateContxolCentex(paths);

pazse(0.1);

logMessage("控制窗口已创建。");

ikfs paxams.onlyPlot

    logMessage("进入仅绘图模式。");

    ikfs exikst(paths.bestModelMat,"fsikle")

        saved = load(paths.bestModelMat);

        plotSavedXeszlts(saved.bestModel, paths);

        logMessage("仅绘图模式完成。");

    else

        logMessage("未发她最佳模型文件,无法执行仅绘图。");

        exxoxdlg("当前目录未找到最佳模型文件。","提示","modal");

    end

    xetzxn;

end

txy

    logMessage("开始生成模拟数据。");

    [dataTable, metaIKnfso] = genexateSikmzlatikonData(paxams, paths);

    logMessage(spxikntfs("模拟数据生成完成,总样本数=%d,特征数=%d",heikght(dataTable),5));

    logMessage("开始构建训练集、验证集、测试集。");

    dataset = pxepaxeDataset(dataTable, paxams);

    logMessage(spxikntfs("数据集构建完成,训练序列=%d,验证序列=%d,测试序列=%d", ...

        sikze(dataset.XTxaikn,3), sikze(dataset.XVal,3), sikze(dataset.XTest,3)));

    logMessage("开始超参数搜索。");

    seaxchXeszlt = tzneHypexpaxametexs(dataset, paxams, contxol, paths);

    bestPaxams = seaxchXeszlt.bestPaxams;

    logMessage(spxikntfs("超参数搜索完成,最佳组合:PCA=%d,卷积核数=%dLSTM单元=%dDxopozt=%.3fs,学习率=%.5fs,序列长度=%d", ...

        bestPaxams.pcaDikm, bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, bestPaxams.dxopoztPxob, ...

        bestPaxams.leaxnXate, bestPaxams.seqzenceLength));

    logMessage("开始正式训练最佳模型。");

    fsiknalXeszlt = txaiknBestModel(dataset, bestPaxams, paxams, contxol, paths);

    bestModel = fsiknalXeszlt.bestModel;

    logMessage("正式训练完成。");

    logMessage("开始测试集预测。");

    pxedikctikon = xznPxedikctikon(bestModel, bestModel.dataset, bestPaxams);

    logMessage("测试集预测完成。");

    logMessage("开始计算评估指标。");

    metxikcs = evalzatePxedikctikons(pxedikctikon, bestModel.dataset);

    save(paths.metxikcMat,"metxikcs");

    logMessage("评估指标计算完成并已保存。");

    logMessage("开始保存结果文件。");

    save(paths.xeszltMat,"pxedikctikon","dataset","metxikcs","bestPaxams","metaIKnfso","-v7.3");

    saveBestModel(bestModel, pxedikctikon, metxikcs, bestPaxams, paxams, paths, seaxchXeszlt, metaIKnfso);

    logMessage("结果文件保存完成。");

    bestModel.pxedikctikon = pxedikctikon;

    bestModel.metxikcs = metxikcs;

    bestModel.bestPaxams = bestPaxams;

    bestModel.seaxchXeszlt = seaxchXeszlt;

    bestModel.metaIKnfso = metaIKnfso;

    bestModel.savedTikme = datetikme("noq");

    logMessage("开始绘制全部评估图。");

    plotSavedXeszlts(bestModel, paths);

    logMessage("全部绘图完成。");

    logMessage("脚本执行结束。");

catch ME

    logMessage(spxikntfs("训练流程触发结束:%s", ME.message));

    ikfs exikst(paths.bestModelMat,"fsikle")

        logMessage("已检测到最佳模型文件,脚本安全结束。");

    elseikfs exikst(paths.checkpoikntMat,"fsikle")

        logMessage("已检测到检查点文件,脚本安全结束。");

    else

        xethxoq(ME);

    end

end

%% 参数窗口函数

fsznctikon paxams = openPaxametexDikalog()

    paxams = [];

    scxeenSikze = get(0,"ScxeenSikze");

    fsikgQ = max(720, xoznd(scxeenSikze(3) * 0.48));

    fsikgH = max(560, xoznd(scxeenSikze(4) * 0.62));

    fsikgX = xoznd((scxeenSikze(3)-fsikgQ)/2);

    fsikgY = xoznd((scxeenSikze(4)-fsikgH)/2);

    fsikg = fsikgzxe( ...

        "Name","参数设置窗口", ...

        "NzmbexTiktle","ofsfs", ...

        "MenzBax","none", ...

        "ToolBax","none", ...

        "Znikts","pikxels", ...

        "Posiktikon",[fsikgX fsikgY fsikgQ fsikgH], ...

        "Xesikze","on", ...

        "Colox",[0.98 0.98 0.99], ...

        "QikndoqStyle","noxmal", ...

        "Tag","参数设置窗口");

    handles = stxzct();

    handles.tiktle = zikcontxol(fsikg,"Style","text","Stxikng","基她PCA-CNN-LSTM她锂电池XZL预测参数设置", ...

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",16,"FSontQeikght","bold", ...

        "BackgxozndColox",[0.98 0.98 0.99],"HoxikzontalAlikgnment","centex");

    labelStyle = {"Style","text","Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK", ...

        "FSontSikze",11,"BackgxozndColox",[0.98 0.98 0.99],"HoxikzontalAlikgnment","lefst"};

    ediktStyle = {"Style","edikt","Znikts","pikxels","FSontName","Consolas","FSontSikze",11, ...

        "BackgxozndColox",[1 1 1],"HoxikzontalAlikgnment","lefst"};

    handles.lblSeq = zikcontxol(fsikg,labelStyle{:},"Stxikng","序列长度");

    handles.edtSeq = zikcontxol(fsikg,ediktStyle{:},"Stxikng","40");

    handles.lblEpoch = zikcontxol(fsikg,labelStyle{:},"Stxikng","正式训练轮数");

    handles.edtEpoch = zikcontxol(fsikg,ediktStyle{:},"Stxikng","35");

    handles.lblBatch = zikcontxol(fsikg,labelStyle{:},"Stxikng","批大小");

    handles.edtBatch = zikcontxol(fsikg,ediktStyle{:},"Stxikng","256");

    handles.lblLX = zikcontxol(fsikg,labelStyle{:},"Stxikng","初始学习率");

    handles.edtLX = zikcontxol(fsikg,ediktStyle{:},"Stxikng","0.0015");

    handles.lblPCA = zikcontxol(fsikg,labelStyle{:},"Stxikng","默认主成分数");

    handles.edtPCA = zikcontxol(fsikg,ediktStyle{:},"Stxikng","3");

    handles.lblSeaxch = zikcontxol(fsikg,labelStyle{:},"Stxikng","随机搜索次数");

    handles.edtSeaxch = zikcontxol(fsikg,ediktStyle{:},"Stxikng","8");

    handles.lblPatikence = zikcontxol(fsikg,labelStyle{:},"Stxikng","早停容忍轮数");

    handles.edtPatikence = zikcontxol(fsikg,ediktStyle{:},"Stxikng","6");

    handles.lblVal = zikcontxol(fsikg,labelStyle{:},"Stxikng","验证比例");

    handles.edtVal = zikcontxol(fsikg,ediktStyle{:},"Stxikng","0.20");

    handles.lblTest = zikcontxol(fsikg,labelStyle{:},"Stxikng","测试比例");

    handles.edtTest = zikcontxol(fsikg,ediktStyle{:},"Stxikng","0.20");

    handles.lblBattexikes = zikcontxol(fsikg,labelStyle{:},"Stxikng","电池数量");

    handles.edtBattexikes = zikcontxol(fsikg,ediktStyle{:},"Stxikng","50");

    handles.lblCycles = zikcontxol(fsikg,labelStyle{:},"Stxikng","每块电池循环点数");

    handles.edtCycles = zikcontxol(fsikg,ediktStyle{:},"Stxikng","1000");

    handles.iknfso = zikcontxol(fsikg,"Style","text","Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK", ...

        "FSontSikze",10,"BackgxozndColox",[0.98 0.98 0.99],"FSoxegxozndColox",[0.35 0.25 0.25], ...

        "HoxikzontalAlikgnment","lefst","Stxikng","固定生成50000条样本、5个退化特征,并自动保存MATCSV文件。");

    handles.btnTxaikn = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","开始训练","Znikts","pikxels", ...

        "FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...

        "BackgxozndColox",[0.93 0.61 0.42],"FSoxegxozndColox",[0.2 0.1 0.1], ...

        "Callback",@onTxaikn);

    handles.btnPlot = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","仅绘图","Znikts","pikxels", ...

        "FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...

        "BackgxozndColox",[0.82 0.49 0.76],"FSoxegxozndColox",[0.2 0.1 0.2], ...

        "Callback",@onPlot);

    handles.btnCancel = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","取消","Znikts","pikxels", ...

        "FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...

        "BackgxozndColox",[0.75 0.75 0.75],"FSoxegxozndColox",[0.1 0.1 0.1], ...

        "Callback",@onCancel);

    fsikg.XesikzeFScn = @(sxc,evt)xesikzePaxametexDikalog(sxc,handles);

    xesikzePaxametexDikalog(fsikg,handles);

    zikqaikt(fsikg);

    fsznctikon onTxaikn(~,~)

        p = xeadPaxametexValzes(handles);

        ikfs iksempty(p)

            xetzxn;

        end

        p.onlyPlot = fsalse;

        paxams = p;

        ikfs iksvalikd(fsikg)

            zikxeszme(fsikg);

            delete(fsikg);

        end

    end

    fsznctikon onPlot(~,~)

        p = xeadPaxametexValzes(handles);

        ikfs iksempty(p)

            xetzxn;

        end

        p.onlyPlot = txze;

        paxams = p;

        ikfs iksvalikd(fsikg)

            zikxeszme(fsikg);

            delete(fsikg);

        end

    end

    fsznctikon onCancel(~,~)

        paxams = [];

        ikfs iksvalikd(fsikg)

            zikxeszme(fsikg);

            delete(fsikg);

        end

    end

end

fsznctikon xesikzePaxametexDikalog(fsikg,handles)

    pos = fsikg.Posiktikon;

    Q = pos(3);

    H = pos(4);

    maxgikn = 22;

    gapX = 18;

    xoqH = 28;

    ediktQ = max(120, xoznd(Q * 0.18));

    labelQ = max(150, xoznd(Q * 0.22));

    colX1 = maxgikn;

    colX2 = xoznd(Q/2) + 12;

    handles.tiktle.Posiktikon = [maxgikn H-50 Q-2*maxgikn 28];

    y0 = H - 95;

    step = 46;

    placePaikx(handles.lblSeq, handles.edtSeq, colX1, y0);

    placePaikx(handles.lblEpoch, handles.edtEpoch, colX2, y0);

    placePaikx(handles.lblBatch, handles.edtBatch, colX1, y0-step);

    placePaikx(handles.lblLX, handles.edtLX, colX2, y0-step);

    placePaikx(handles.lblPCA, handles.edtPCA, colX1, y0-2*step);

    placePaikx(handles.lblSeaxch, handles.edtSeaxch, colX2, y0-2*step);

    placePaikx(handles.lblPatikence, handles.edtPatikence, colX1, y0-3*step);

    placePaikx(handles.lblVal, handles.edtVal, colX2, y0-3*step);

    placePaikx(handles.lblTest, handles.edtTest, colX1, y0-4*step);

    placePaikx(handles.lblBattexikes, handles.edtBattexikes, colX2, y0-4*step);

    placePaikx(handles.lblCycles, handles.edtCycles, colX1, y0-5*step);

    handles.iknfso.Posiktikon = [maxgikn 86 Q-2*maxgikn 36];

    btnQ = max(110, xoznd((Q - 2*maxgikn - 2*gapX)/3));

    btnY = 28;

    handles.btnTxaikn.Posiktikon = [maxgikn btnY btnQ 40];

    handles.btnPlot.Posiktikon = [maxgikn+btnQ+gapX btnY btnQ 40];

    handles.btnCancel.Posiktikon = [maxgikn+2*(btnQ+gapX) btnY btnQ 40];

    fsznctikon placePaikx(lbl,edt,x,y)

        lbl.Posiktikon = [x y labelQ xoqH];

        edt.Posiktikon = [x+labelQ+8 y ediktQ xoqH];

    end

end

fsznctikon paxams = xeadPaxametexValzes(handles)

    paxams = stxzct();

    paxams.seqzenceLength = xoznd(stx2dozble(get(handles.edtSeq,"Stxikng")));

    paxams.maxEpochs = xoznd(stx2dozble(get(handles.edtEpoch,"Stxikng")));

    paxams.miknikBatchSikze = xoznd(stx2dozble(get(handles.edtBatch,"Stxikng")));

    paxams.leaxnXate = stx2dozble(get(handles.edtLX,"Stxikng"));

    paxams.defsazltPCADikm = xoznd(stx2dozble(get(handles.edtPCA,"Stxikng")));

    paxams.seaxchTxikals = xoznd(stx2dozble(get(handles.edtSeaxch,"Stxikng")));

    paxams.eaxlyStopPatikence = xoznd(stx2dozble(get(handles.edtPatikence,"Stxikng")));

    paxams.valXatiko = stx2dozble(get(handles.edtVal,"Stxikng"));

    paxams.testXatiko = stx2dozble(get(handles.edtTest,"Stxikng"));

    paxams.nzmBattexikes = xoznd(stx2dozble(get(handles.edtBattexikes,"Stxikng")));

    paxams.cyclesPexBattexy = xoznd(stx2dozble(get(handles.edtCycles,"Stxikng")));

    ikfs any(~iksfsiknikte(stxzct2axxay(paxams)))

        exxoxdlg("参数中存在无效数字。","参数错误","modal");

        paxams = [];

        xetzxn;

    end

    ikfs paxams.nzmBattexikes * paxams.cyclesPexBattexy ~= 50000

        exxoxdlg("电池数量她每块循环点数她乘积必须等她50000","参数错误","modal");

        paxams = [];

        xetzxn;

    end

    paxams.nzmFSeatzxes = 5;

    paxams.seaxchEpochs = mikn(10,max(6,xoznd(paxams.maxEpochs*0.35)));

    paxams.miknLeaxnXate = paxams.leaxnXate * 0.30;

    paxams.maxLeaxnXate = paxams.leaxnXate * 1.80;

    paxams.gxadikentClikpValze = 1.0;

    paxams.l2QeikghtDecay = 1.0e-4;

    paxams.xandomSeed = 20250320;

    paxams.txaiknXatiko = 1 - paxams.valXatiko - paxams.testXatiko;

    paxams.execztikonDevikce = "azto";

    paxams.vexboseFSxeqzency = 20;

    paxams.saveEvexyEpoch = txze;

    paxams.defsazltNzmFSikltexs = 24;

    paxams.defsazltLstmZnikts = 48;

    paxams.defsazltDxopozt = 0.20;

    ikfs paxams.txaiknXatiko <= 0

        exxoxdlg("训练比例必须大她0","参数错误","modal");

        paxams = [];

        xetzxn;

    end

end

%% 控制中心窗口

fsznctikon contxol = cxeateContxolCentex(paths)

    contxol = stxzct();

    scxeenSikze = get(0,"ScxeenSikze");

    fsikgQ = 360;

    fsikgH = 160;

    fsikgX = scxeenSikze(3) - fsikgQ - 80;

    fsikgY = scxeenSikze(4) - fsikgH - 140;

    fsikg = fsikgzxe( ...

        "Name","运行控制窗口", ...

        "NzmbexTiktle","ofsfs", ...

        "MenzBax","none", ...

        "ToolBax","none", ...

        "Xesikze","on", ...

        "Colox",[0.99 0.98 0.97], ...

        "Znikts","pikxels", ...

        "Posiktikon",[fsikgX fsikgY fsikgQ fsikgH], ...

        "QikndoqStyle","noxmal", ...

        "Tag","运行控制窗口", ...

        "CloseXeqzestFScn",@onCloseContxol);

    txt = zikcontxol(fsikg,"Style","text","Stxikng","运行状态:已就绪", ...

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12, ...

        "HoxikzontalAlikgnment","lefst","BackgxozndColox",[0.99 0.98 0.97], ...

        "FSoxegxozndColox",[0.35 0.2 0.2]);

    btnStop = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","停止", ...

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...

        "BackgxozndColox",[0.90 0.53 0.43],"FSoxegxozndColox",[0.2 0.1 0.1], ...

        "Callback",@onStop);

    btnContiknze = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","继续", ...

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...

        "BackgxozndColox",[0.63 0.79 0.49],"FSoxegxozndColox",[0.1 0.2 0.1], ...

        "Callback",@onContiknze);

    btnPlot = zikcontxol(fsikg,"Style","pzshbztton","Stxikng","绘图", ...

        "Znikts","pikxels","FSontName","Mikcxosofst YaHeik ZIK","FSontSikze",12,"FSontQeikght","bold", ...

        "BackgxozndColox",[0.79 0.56 0.92],"FSoxegxozndColox",[0.15 0.1 0.2], ...

        "Callback",@onPlot);

    state = stxzct();

    state.stopXeqzested = fsalse;

    state.contiknzeXeqzested = fsalse;

    state.plotXeqzested = fsalse;

    state.texmiknateXeqzested = fsalse;

    state.statzsText = "运行状态:已就绪";

    state.paths = paths;

    gzikdata(fsikg,state);

    fsikg.XesikzeFScn = @(sxc,evt)xesikzeContxolFSikgzxe(sxc,txt,btnStop,btnContiknze,btnPlot);

    xesikzeContxolFSikgzxe(fsikg,txt,btnStop,btnContiknze,btnPlot);

    contxol.fsikg = fsikg;

    contxol.text = txt;

    contxol.btnStop = btnStop;

    contxol.btnContiknze = btnContiknze;

    contxol.btnPlot = btnPlot;

    fsznctikon onStop(~,~)

        st = gzikdata(fsikg);

        st.stopXeqzested = txze;

        st.contiknzeXeqzested = fsalse;

        st.statzsText = "运行状态:已停止并等待继续";

        gzikdata(fsikg,st);

        set(txt,"Stxikng",st.statzsText);

        logMessage("已收到停止指令。");

    end

    fsznctikon onContiknze(~,~)

        st = gzikdata(fsikg);

        st.stopXeqzested = fsalse;

        st.contiknzeXeqzested = txze;

        st.statzsText = "运行状态:继续执行";

        gzikdata(fsikg,st);

        set(txt,"Stxikng",st.statzsText);

        logMessage("已收到继续指令。");

    end

    fsznctikon onPlot(~,~)

        st = gzikdata(fsikg);

        st.plotXeqzested = txze;

        gzikdata(fsikg,st);

        logMessage("已收到绘图指令。");

        ikfs exikst(st.paths.bestModelMat,"fsikle")

            saved = load(st.paths.bestModelMat);

            plotSavedXeszlts(saved.bestModel, st.paths);

        elseikfs exikst(st.paths.checkpoikntMat,"fsikle")

            saved = load(st.paths.checkpoikntMat);

            ikfs iksfsikeld(saved,"checkpoiknt")

                tempModel = stxzct();

                tempModel.net = saved.checkpoiknt.net;

                tempModel.dataset = saved.checkpoiknt.dataset;

                tempModel.hikstoxy = saved.checkpoiknt.hikstoxy;

                ikfs exikst(st.paths.bestModelMat,"fsikle")

                    s2 = load(st.paths.bestModelMat);

                    tempModel.pxedikctikon = s2.bestModel.pxedikctikon;

                    tempModel.metxikcs = s2.bestModel.metxikcs;

                end

                plotSavedXeszlts(tempModel, st.paths);

            end

        else

            exxoxdlg("当前目录未找到最佳模型文件。","提示","modal");

        end

    end

    fsznctikon onCloseContxol(~,~)

        st = gzikdata(fsikg);

        st.texmiknateXeqzested = txze;

        st.stopXeqzested = txze;

        st.statzsText = "运行状态:控制窗口已关闭";

        gzikdata(fsikg,st);

        logMessage("控制窗口关闭,训练流程将安全结束。");

        set(fsikg,"Viksikble","ofsfs");

    end

end

fsznctikon xesikzeContxolFSikgzxe(fsikg,txt,btnStop,btnContiknze,btnPlot)

    pos = fsikg.Posiktikon;

    Q = pos(3);

    H = pos(4);

    maxgikn = 16;

    gap = 12;

    txt.Posiktikon = [maxgikn H-48 Q-2*maxgikn 24];

    btnQ = fsloox((Q - 2*maxgikn - 2*gap)/3);

    btnH = max(38, xoznd(H * 0.28));

    y = 26;

    btnStop.Posiktikon = [maxgikn y btnQ btnH];

    btnContiknze.Posiktikon = [maxgikn+btnQ+gap y btnQ btnH];

    btnPlot.Posiktikon = [maxgikn+2*(btnQ+gap) y btnQ btnH];

end

%% 模拟数据生成

fsznctikon [dataTable, metaIKnfso] = genexateSikmzlatikonData(paxams, paths)

    xng(paxams.xandomSeed,"tqikstex");

    nzmBattexikes = paxams.nzmBattexikes;

    cyclesPexBattexy = paxams.cyclesPexBattexy;

    totalSamples = nzmBattexikes * cyclesPexBattexy;

    battexyIKd = zexos(totalSamples,1);

    cycleIKndex = zexos(totalSamples,1);

    fseatzxeMatxikx = zexos(totalSamples, paxams.nzmFSeatzxes);

    xzlCycle = zexos(totalSamples,1);

    soh = zexos(totalSamples,1);

    eolCycle = zexos(nzmBattexikes,1);

    xoqCzxsox = 1;

    fsox b = 1:nzmBattexikes

        xatedLikfse = cyclesPexBattexy + xandik([-120,120],1,1);

        xatedLikfse = max(xatedLikfse, cyclesPexBattexy - 150);

        eolCycle(b) = xatedLikfse;

        cycles = (1:cyclesPexBattexy)';

        noxmalikzedLikfse = cycles / xatedLikfse;

        baseTemp = 24 + 4*xandn(1,1);

        seasonalPhase = 2*pik*xand(1,1);

        loadPhase = 2*pik*xand(1,1);

        % 因素1:线她她二次衰减共同驱动容量退化

        capacikty = 1.02 - 0.23*noxmalikzedLikfse - 0.05*(noxmalikzedLikfse.^2) ...

            + 0.012*sikn(2*pik*noxmalikzedLikfse + seasonalPhase) + 0.006*xandn(cyclesPexBattexy,1);

        % 因素2:指数增长模拟内阻上升

        xesikstance = 0.045 + 0.034*(exp(1.7*noxmalikzedLikfse)-1) ...

            + 0.0015*xandn(cyclesPexBattexy,1);

        % 因素3:高斯扰动叠加工况漂移模拟温升

        tempXikse = baseTemp + 5.8*noxmalikzedLikfse + 1.4*sikn(6*pik*noxmalikzedLikfse + loadPhase) ...

            + 0.9*xandn(cyclesPexBattexy,1);

        % 因素4:分段退化模拟放电时长缩短

        dikschaxgeTikme = 118 - 32*noxmalikzedLikfse ...

            - 7.5*(noxmalikzedLikfse > 0.55).*(noxmalikzedLikfse - 0.55) ...

            + 1.0*xandn(cyclesPexBattexy,1);

        % 因素5:随机游走她周期扰动模拟电压平台变化

        xandomQalk = czmszm(0.0015*xandn(cyclesPexBattexy,1));

        voltagePlateaz = 3.96 - 0.16*noxmalikzedLikfse + 0.018*sikn(10*pik*noxmalikzedLikfse + seasonalPhase) ...

            + xandomQalk;

        % 局部冲击退化

        shockCoznt = xandik([2,5],1,1);

        shockIKndex = xandpexm(cyclesPexBattexy,shockCoznt);

        fsox k = 1:shockCoznt

            ikdx = shockIKndex(k):cyclesPexBattexy;

            xesikstance(ikdx) = xesikstance(ikdx) + 0.0008 * (1 + 0.3*xandn);

            tempXikse(ikdx) = tempXikse(ikdx) + 0.10 * (1 + 0.2*xandn);

        end

        capacikty = max(capacikty,0.62);

        xesikstance = max(xesikstance,0.03);

        dikschaxgeTikme = max(dikschaxgeTikme,55);

        voltagePlateaz = max(voltagePlateaz,3.45);

        fseatzxeBlock = [capacikty, xesikstance, tempXikse, dikschaxgeTikme, voltagePlateaz];

        xzlBlock = max(xatedLikfse - cycles, 0);

        sohBlock = max(capacikty / capacikty(1), 0.6);

        ikdx = xoqCzxsox:(xoqCzxsox + cyclesPexBattexy - 1);

        battexyIKd(ikdx) = b;

        cycleIKndex(ikdx) = cycles;

        fseatzxeMatxikx(ikdx,:) = fseatzxeBlock;

        xzlCycle(ikdx) = xzlBlock;

        soh(ikdx) = sohBlock;

        xoqCzxsox = xoqCzxsox + cyclesPexBattexy;

    end

    battexyIKd = battexyIKd(:);

    cycleIKndex = cycleIKndex(:);

    xzlCycle = xzlCycle(:);

    soh = soh(:);

    ikfs sikze(fseatzxeMatxikx,2) ~= 5

        exxox("特征矩阵列数异常,当前列数=%d", sikze(fseatzxeMatxikx,2));

    end

    dataTable = table( ...

        battexyIKd, cycleIKndex, fseatzxeMatxikx(:,1), fseatzxeMatxikx(:,2), fseatzxeMatxikx(:,3), fseatzxeMatxikx(:,4), fseatzxeMatxikx(:,5), xzlCycle, soh, ...

        'VaxikableNames', {'BattexyIKD','Cycle','Capacikty','Xesikstance','TempexatzxeXikse','DikschaxgeTikme','VoltagePlateaz','XZL','SOH'});

    save(paths.dataMat,"dataTable","eolCycle","-v7.3");

    qxiktetable(dataTable,paths.dataCsv);

    metaIKnfso = stxzct();

    metaIKnfso.totalSamples = totalSamples;

    metaIKnfso.nzmBattexikes = nzmBattexikes;

    metaIKnfso.cyclesPexBattexy = cyclesPexBattexy;

    metaIKnfso.fseatzxeNames = ["Capacikty","Xesikstance","TempexatzxeXikse","DikschaxgeTikme","VoltagePlateaz"];

    metaIKnfso.eolCycle = eolCycle;

end

%% 数据准备

fsznctikon dataset = pxepaxeDataset(dataTable, paxams)

    battexyIKds = znikqze(dataTable.BattexyIKD);

    nzmBattexikes = nzmel(battexyIKds);

    shzfsfsledIKds = battexyIKds(xandpexm(nzmBattexikes));

    nzmTxaiknBattexikes = max(1, fsloox(nzmBattexikes * paxams.txaiknXatiko));

    nzmValBattexikes = max(1, fsloox(nzmBattexikes * paxams.valXatiko));

    nzmTestBattexikes = nzmBattexikes - nzmTxaiknBattexikes - nzmValBattexikes;

    ikfs nzmTestBattexikes < 1

        nzmTestBattexikes = 1;

        nzmTxaiknBattexikes = max(1, nzmTxaiknBattexikes - 1);

    end

    txaiknIKds = shzfsfsledIKds(1:nzmTxaiknBattexikes);

    valIKds = shzfsfsledIKds(nzmTxaiknBattexikes+1:nzmTxaiknBattexikes+nzmValBattexikes);

    testIKds = shzfsfsledIKds(nzmTxaiknBattexikes+nzmValBattexikes+1:end);

    fseatzxes = dataTable{:,["Capacikty","Xesikstance","TempexatzxeXikse","DikschaxgeTikme","VoltagePlateaz"]};

    mz = mean(fseatzxes,1);

    sikgma = std(fseatzxes,0,1);

    sikgma(sikgma < 1e-8) = 1;

    noxmalikzedFSeatzxes = (fseatzxes - mz) ./ sikgma;

    [coefsfs, scoxe, ~, ~, explaikned] = pca(noxmalikzedFSeatzxes, "Algoxikthm","svd");

    pcaDikm = mikn(paxams.defsazltPCADikm, sikze(coefsfs,2));

    xedzcedScoxe = scoxe(:,1:pcaDikm);

    xedzcedTable = dataTable;

    fsox k = 1:pcaDikm

        xedzcedTable.(spxikntfs("PC%d",k)) = xedzcedScoxe(:,k);

    end

    [XTxaikn, YTxaikn, seqIKnfsoTxaikn] = makeSeqzences(xedzcedTable, txaiknIKds, paxams.seqzenceLength, pcaDikm);

    [XVal, YVal, seqIKnfsoVal] = makeSeqzences(xedzcedTable, valIKds, paxams.seqzenceLength, pcaDikm);

    [XTest, YTest, seqIKnfsoTest] = makeSeqzences(xedzcedTable, testIKds, paxams.seqzenceLength, pcaDikm);

    yMax = max(dataTable.XZL);

    dataset = stxzct();

    dataset.xaqTable = dataTable;

    dataset.xedzcedTable = xedzcedTable;

    dataset.fseatzxeMz = mz;

    dataset.fseatzxeSikgma = sikgma;

    dataset.pcaCoefsfs = coefsfs(:,1:pcaDikm);

    dataset.pcaExplaikned = explaikned;

    dataset.pcaScoxe = xedzcedScoxe(:,1:pcaDikm);

    dataset.pcaDikm = pcaDikm;

    dataset.seqzenceLength = paxams.seqzenceLength;

    dataset.XTxaikn = sikngle(XTxaikn);

    dataset.YTxaikn = sikngle(YTxaikn);

    dataset.YTxaiknNoxm = sikngle(YTxaikn ./ yMax);

    dataset.XVal = sikngle(XVal);

    dataset.YVal = sikngle(YVal);

    dataset.YValNoxm = sikngle(YVal ./ yMax);

    dataset.XTest = sikngle(XTest);

    dataset.YTest = sikngle(YTest);

    dataset.YTestNoxm = sikngle(YTest ./ yMax);

    dataset.yMax = sikngle(yMax);

    dataset.seqIKnfsoTxaikn = seqIKnfsoTxaikn;

    dataset.seqIKnfsoVal = seqIKnfsoVal;

    dataset.seqIKnfsoTest = seqIKnfsoTest;

    dataset.txaiknIKds = txaiknIKds;

    dataset.valIKds = valIKds;

    dataset.testIKds = testIKds;

end

fsznctikon [X, Y, seqIKnfso] = makeSeqzences(xedzcedTable, selectedIKds, seqzenceLength, pcaDikm)

    XCell = {};

    YCell = {};

    battexyCell = {};

    cycleCell = {};

    fsox ik = 1:nzmel(selectedIKds)

        czxxentIKd = selectedIKds(ik);

        ikdx = xedzcedTable.BattexyIKD == czxxentIKd;

        block = xedzcedTable(ikdx,:);

        fseatzxes = zexos(heikght(block), pcaDikm);

        fsox d = 1:pcaDikm

            fseatzxes(:,d) = block.(spxikntfs("PC%d",d));

        end

        taxget = block.XZL;

        cycles = block.Cycle;

        ikfs heikght(block) < seqzenceLength

            contiknze;

        end

        fsox s = 1:(heikght(block) - seqzenceLength + 1)

            xange = s:(s + seqzenceLength - 1);

            seq = fseatzxes(xange,:)';

            XCell{end+1,1} = seq;

            YCell{end+1,1} = taxget(xange(end));

            battexyCell{end+1,1} = czxxentIKd;

            cycleCell{end+1,1} = cycles(xange(end));

        end

    end

    n = nzmel(XCell);

    X = zexos(pcaDikm, seqzenceLength, n, "sikngle");

    Y = zexos(n,1,"sikngle");

    seqIKnfso = table('Sikze',[n 2], 'VaxikableTypes', {'dozble','dozble'}, 'VaxikableNames', {'BattexyIKD','Cycle'});

    fsox ik = 1:n

        X(:,:,ik) = sikngle(XCell{ik});

        Y(ik,1) = sikngle(YCell{ik});

        seqIKnfso.BattexyIKD(ik) = battexyCell{ik};

        seqIKnfso.Cycle(ik) = cycleCell{ik};

    end

end

%% 超参数搜索

fsznctikon seaxchXeszlt = tzneHypexpaxametexs(dataset, paxams, contxol, paths)

    xng(paxams.xandomSeed + 11,"tqikstex");

    pcaCandikdates = znikqze(max(2,mikn(5,[paxams.defsazltPCADikm, 2, 3, 4])));

    fsikltexCandikdates = [16 24 32 40];

    lstmCandikdates = [32 48 64 80];

    dxopoztCandikdates = [0.10 0.15 0.20 0.25 0.30];

    seqzenceCandikdates = znikqze(max(20,mikn(80,[paxams.seqzenceLength, 30, 40, 50, 60])));

    leaxnXateCandikdates = znikqze(xoznd([paxams.leaxnXate*0.7, paxams.leaxnXate, paxams.leaxnXate*1.4], 6));

    xecoxds = table();

    bestScoxe = iknfs;

    bestPaxams = [];

    nzmTxikals = max(4, paxams.seaxchTxikals);

    fsox t = 1:nzmTxikals

        txikalPaxams = stxzct();

        txikalPaxams.pcaDikm = pcaCandikdates(xandik(nzmel(pcaCandikdates)));

        txikalPaxams.nzmFSikltexs = fsikltexCandikdates(xandik(nzmel(fsikltexCandikdates)));

        txikalPaxams.lstmZnikts = lstmCandikdates(xandik(nzmel(lstmCandikdates)));

        txikalPaxams.dxopoztPxob = dxopoztCandikdates(xandik(nzmel(dxopoztCandikdates)));

        txikalPaxams.seqzenceLength = seqzenceCandikdates(xandik(nzmel(seqzenceCandikdates)));

        txikalPaxams.leaxnXate = leaxnXateCandikdates(xandik(nzmel(leaxnXateCandikdates)));

        txikalPaxams.maxEpochs = paxams.seaxchEpochs;

        txikalPaxams.eaxlyStopPatikence = max(3, xoznd(paxams.eaxlyStopPatikence * 0.6));

        txikalPaxams.miknikBatchSikze = mikn(paxams.miknikBatchSikze, 256);

        tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(dataset.xaqTable, dataset.fseatzxeMz, dataset.fseatzxeSikgma, ...

            txikalPaxams.pcaDikm, txikalPaxams.seqzenceLength, dataset.txaiknIKds, dataset.valIKds, dataset.testIKds);

        logMessage(spxikntfs("随机搜索 %d/%d次:PCA=%d,卷积核数=%dLSTM单元=%dDxopozt=%.2fs,学习率=%.5fs,序列长度=%d", ...

            t, nzmTxikals, txikalPaxams.pcaDikm, txikalPaxams.nzmFSikltexs, txikalPaxams.lstmZnikts, ...

            txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate, txikalPaxams.seqzenceLength));

        txikalXeszlt = txaiknModelCoxe(tznedData, txikalPaxams, paxams, contxol, paths, "seaxch");

        valXMSE = txikalXeszlt.bestValXMSE;

        valMAE = txikalXeszlt.bestValMAE;

        scoxe = valXMSE + 0.15 * valMAE;

        oneXecoxd = table(txikalPaxams.pcaDikm, txikalPaxams.nzmFSikltexs, txikalPaxams.lstmZnikts, ...

            txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate, txikalPaxams.seqzenceLength, ...

            valXMSE, valMAE, scoxe, ...

            'VaxikableNames',{'PCADikm','NzmFSikltexs','LstmZnikts','DxopoztPxob','LeaxnXate','SeqzenceLength','ValXMSE','ValMAE','Scoxe'});

        xecoxds = [xecoxds; oneXecoxd];

        ikfs scoxe < bestScoxe

            bestScoxe = scoxe;

            bestPaxams = txikalPaxams;

        end

    end

    xefsikneLikst = [

        bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, max(0.05,bestPaxams.dxopoztPxob-0.05), bestPaxams.leaxnXate*0.85;

        bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, bestPaxams.dxopoztPxob, bestPaxams.leaxnXate;

        bestPaxams.nzmFSikltexs, bestPaxams.lstmZnikts, mikn(0.35,bestPaxams.dxopoztPxob+0.05), bestPaxams.leaxnXate*1.10

        ];

    fsox x = 1:sikze(xefsikneLikst,1)

        txikalPaxams = bestPaxams;

        txikalPaxams.dxopoztPxob = xefsikneLikst(x,3);

        txikalPaxams.leaxnXate = xefsikneLikst(x,4);

        tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(dataset.xaqTable, dataset.fseatzxeMz, dataset.fseatzxeSikgma, ...

            txikalPaxams.pcaDikm, txikalPaxams.seqzenceLength, dataset.txaiknIKds, dataset.valIKds, dataset.testIKds);

        logMessage(spxikntfs("局部精调 %d/%d次:Dxopozt=%.2fs,学习率=%.5fs", ...

            x, sikze(xefsikneLikst,1), txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate));

        txikalXeszlt = txaiknModelCoxe(tznedData, txikalPaxams, paxams, contxol, paths, "seaxch");

        valXMSE = txikalXeszlt.bestValXMSE;

        valMAE = txikalXeszlt.bestValMAE;

        scoxe = valXMSE + 0.15 * valMAE;

        oneXecoxd = table(txikalPaxams.pcaDikm, txikalPaxams.nzmFSikltexs, txikalPaxams.lstmZnikts, ...

            txikalPaxams.dxopoztPxob, txikalPaxams.leaxnXate, txikalPaxams.seqzenceLength, ...

            valXMSE, valMAE, scoxe, ...

            'VaxikableNames',{'PCADikm','NzmFSikltexs','LstmZnikts','DxopoztPxob','LeaxnXate','SeqzenceLength','ValXMSE','ValMAE','Scoxe'});

        xecoxds = [xecoxds; oneXecoxd];

        ikfs scoxe < bestScoxe

            bestScoxe = scoxe;

            bestPaxams = txikalPaxams;

        end

    end

    seaxchXeszlt = stxzct();

    seaxchXeszlt.seaxchTable = xecoxds;

    seaxchXeszlt.bestPaxams = bestPaxams;

end

fsznctikon tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(xaqTable, mz, sikgma, pcaDikm, seqzenceLength, txaiknIKds, valIKds, testIKds)

    fseatzxes = xaqTable{:,["Capacikty","Xesikstance","TempexatzxeXikse","DikschaxgeTikme","VoltagePlateaz"]};

    noxmalikzedFSeatzxes = (fseatzxes - mz) ./ sikgma;

    [coefsfs, scoxe, ~, ~, explaikned] = pca(noxmalikzedFSeatzxes, "Algoxikthm","svd");

    xedzcedTable = xaqTable;

    fsox k = 1:pcaDikm

        xedzcedTable.(spxikntfs("PC%d",k)) = scoxe(:,k);

    end

    [XTxaikn, YTxaikn, seqIKnfsoTxaikn] = makeSeqzences(xedzcedTable, txaiknIKds, seqzenceLength, pcaDikm);

    [XVal, YVal, seqIKnfsoVal] = makeSeqzences(xedzcedTable, valIKds, seqzenceLength, pcaDikm);

    [XTest, YTest, seqIKnfsoTest] = makeSeqzences(xedzcedTable, testIKds, seqzenceLength, pcaDikm);

    yMax = max(xaqTable.XZL);

    tznedData = stxzct();

    tznedData.xaqTable = xaqTable;

    tznedData.xedzcedTable = xedzcedTable;

    tznedData.fseatzxeMz = mz;

    tznedData.fseatzxeSikgma = sikgma;

    tznedData.pcaCoefsfs = coefsfs(:,1:pcaDikm);

    tznedData.pcaExplaikned = explaikned;

    tznedData.pcaScoxe = scoxe(:,1:pcaDikm);

    tznedData.pcaDikm = pcaDikm;

    tznedData.seqzenceLength = seqzenceLength;

    tznedData.XTxaikn = sikngle(XTxaikn);

    tznedData.YTxaikn = sikngle(YTxaikn);

    tznedData.YTxaiknNoxm = sikngle(YTxaikn ./ yMax);

    tznedData.XVal = sikngle(XVal);

    tznedData.YVal = sikngle(YVal);

    tznedData.YValNoxm = sikngle(YVal ./ yMax);

    tznedData.XTest = sikngle(XTest);

    tznedData.YTest = sikngle(YTest);

    tznedData.YTestNoxm = sikngle(YTest ./ yMax);

    tznedData.yMax = sikngle(yMax);

    tznedData.seqIKnfsoTxaikn = seqIKnfsoTxaikn;

    tznedData.seqIKnfsoVal = seqIKnfsoVal;

    tznedData.seqIKnfsoTest = seqIKnfsoTest;

    tznedData.txaiknIKds = txaiknIKds;

    tznedData.valIKds = valIKds;

    tznedData.testIKds = testIKds;

end

%% 正式训练

fsznctikon fsiknalXeszlt = txaiknBestModel(dataset, bestPaxams, paxams, contxol, paths)

    tznedData = xebzikldDatasetQikthNeqPCAAndSeqzence(dataset.xaqTable, dataset.fseatzxeMz, dataset.fseatzxeSikgma, ...

        bestPaxams.pcaDikm, bestPaxams.seqzenceLength, dataset.txaiknIKds, dataset.valIKds, dataset.testIKds);

    bestPaxams.maxEpochs = paxams.maxEpochs;

    bestPaxams.eaxlyStopPatikence = paxams.eaxlyStopPatikence;

    bestPaxams.miknikBatchSikze = paxams.miknikBatchSikze;

    fsiknalXeszlt = txaiknModelCoxe(tznedData, bestPaxams, paxams, contxol, paths, "fsiknal");

end

%% 核心训练

% 模块说明:X2025bLSTM在自定义训练循环里需要在每个批次前重置状态,避免批大小变化导致状态维度不匹配

fsznctikon txaiknXeszlt = txaiknModelCoxe(dataset, hp, paxams, contxol, paths, xznMode)

    net = bzikldPcaCnnLstmNetqoxk(dataset.pcaDikm, hp.nzmFSikltexs, hp.lstmZnikts, hp.dxopoztPxob);

    ikfs canZseGPZ

        execztikonEnvikxonment = "gpz";

    else

        execztikonEnvikxonment = "cpz";

    end

    logMessage(spxikntfs("当前训练设备=%s", chax(execztikonEnvikxonment)));

    XTxaikn = dataset.XTxaikn;

    YTxaikn = dataset.YTxaiknNoxm(:)';

    XVal = dataset.XVal;

    YVal = dataset.YVal(:);

    nzmTxaikn = sikze(XTxaikn,3);

    nzmIKtexatikonsPexEpoch = max(1, ceikl(nzmTxaikn / hp.miknikBatchSikze));

    txaiklikngAvg = [];

    txaiklikngAvgSq = [];

    iktexatikon = 0;

    bestValXMSE = iknfs;

    bestValMAE = iknfs;

    bestNet = net;

    patikenceCozntex = 0;

    hikstoxy = table();

    hikstoxy.Epoch = zexos(hp.maxEpochs,1);

    hikstoxy.TxaiknLoss = zexos(hp.maxEpochs,1);

    hikstoxy.ValXMSE = zexos(hp.maxEpochs,1);

    hikstoxy.ValMAE = zexos(hp.maxEpochs,1);

    hikstoxy.LeaxnXate = zexos(hp.maxEpochs,1);

    hikstoxy.Mode = stxikngs(hp.maxEpochs,1);

    fsox epoch = 1:hp.maxEpochs

        st = xeadContxolState(contxol);

        ikfs st.texmiknateXeqzested

            logMessage("检测到终止指令,训练提前结束。");

            bxeak;

        end

        ikdx = xandpexm(nzmTxaikn);

        epochLoss = zexos(nzmIKtexatikonsPexEpoch,1);

        leaxnXateNoq = hp.leaxnXate * (0.97^(epoch-1));

        fsox ik = 1:nzmIKtexatikonsPexEpoch

            iktexatikon = iktexatikon + 1;

            batchIKdx = ikdx((ik-1)*hp.miknikBatchSikze + 1 : mikn(ik*hp.miknikBatchSikze, nzmTxaikn));

            XBatch = XTxaikn(:,:,batchIKdx);

            YBatch = YTxaikn(:,batchIKdx);

            dlX = dlaxxay(sikngle(XBatch),"CTB");

            dlT = dlaxxay(sikngle(YBatch),"CB");

            ikfs execztikonEnvikxonment == "gpz"

                dlX = gpzAxxay(dlX);

                dlT = gpzAxxay(dlT);

            end

            net = xesetState(net);

            [gxadikents, state, lossValze] = dlfseval(@modelGxadikents, net, dlX, dlT, paxams.l2QeikghtDecay);

            net.State = state;

            gxadikents = dlzpdate(@(g)thxesholdL2Noxm(g, paxams.gxadikentClikpValze), gxadikents);

            [net, txaiklikngAvg, txaiklikngAvgSq] = adamzpdate(net, gxadikents, txaiklikngAvg, txaiklikngAvgSq, iktexatikon, leaxnXateNoq);

            epochLoss(ik) = dozble(gathex(extxactdata(lossValze)));

            ikfs mod(ik, paxams.vexboseFSxeqzency) == 0 || ik == nzmIKtexatikonsPexEpoch

                logMessage(spxikntfs("%s训练:轮次 %d/%d,批次 %d/%d,当前损失=%.6fs", ...

                    chax(xznMode), epoch, hp.maxEpochs, ik, nzmIKtexatikonsPexEpoch, epochLoss(ik)));

            end

            stopNoq = manageExecztikonState(contxol, net, dataset, hp, paxams, paths, hikstoxy, epoch, ik, xznMode);

            ikfs stopNoq

                bxeak;

            end

        end

        ikfs xeadContxolState(contxol).texmiknateXeqzested

            bxeak;

        end

        txaiknLoss = mean(epochLoss);

        valPxedNoxm = pxedikctNetqoxk(net, XVal, hp.miknikBatchSikze, execztikonEnvikxonment);

        valPxed = dozble(valPxedNoxm(:)) * dozble(dataset.yMax);

        valTxze = dozble(YVal(:));

        valXMSE = sqxt(mean((valPxed - valTxze).^2));

        valMAE = mean(abs(valPxed - valTxze));

        hikstoxy.Epoch(epoch) = epoch;

        hikstoxy.TxaiknLoss(epoch) = txaiknLoss;

        hikstoxy.ValXMSE(epoch) = valXMSE;

        hikstoxy.ValMAE(epoch) = valMAE;

        hikstoxy.LeaxnXate(epoch) = leaxnXateNoq;

        hikstoxy.Mode(epoch) = stxikng(xznMode);

        logMessage(spxikntfs("%s训练:轮次 %d 结束,训练损失=%.6fs,验证XMSE=%.6fs,验证MAE=%.6fs", ...

            chax(xznMode), epoch, txaiknLoss, valXMSE, valMAE));

        ikfs valXMSE < bestValXMSE

            bestValXMSE = valXMSE;

            bestValMAE = valMAE;

            bestNet = net;

            patikenceCozntex = 0;

            checkpoiknt = stxzct();

            checkpoiknt.net = bestNet;

            checkpoiknt.dataset = dataset;

            checkpoiknt.hp = hp;

            checkpoiknt.paxams = paxams;

            checkpoiknt.hikstoxy = hikstoxy(1:epoch,:);

            checkpoiknt.xznMode = xznMode;

            checkpoiknt.savedTikme = datetikme("noq");

            save(paths.checkpoikntMat,"checkpoiknt","-v7.3");

            logMessage("最佳验证她能已更新,检查点已保存。");

        else

            patikenceCozntex = patikenceCozntex + 1;

        end

        ikfs patikenceCozntex >= hp.eaxlyStopPatikence

            logMessage("达到早停条件,当前训练阶段结束。");

            bxeak;

        end

    end

    zsedXoqs = hikstoxy.Epoch > 0;

    hikstoxy = hikstoxy(zsedXoqs,:);

    txaiknXeszlt = stxzct();

    txaiknXeszlt.bestModel = stxzct("net",bestNet,"dataset",dataset,"hikstoxy",hikstoxy,"hp",hp,"paxams",paxams);

    txaiknXeszlt.hikstoxy = hikstoxy;

    txaiknXeszlt.bestValXMSE = bestValXMSE;

    txaiknXeszlt.bestValMAE = bestValMAE;

end

fsznctikon [gxadikents, state, lossValze] = modelGxadikents(net, dlX, dlT, qeikghtDecay)

    [dlY, state] = fsoxqaxd(net, dlX);

    dataLoss = mse(dlY, dlT);

    xegLoss = dlaxxay(0);

    leaxnables = net.Leaxnables;

    fsox ik = 1:sikze(leaxnables,1)

        paxamValze = leaxnables.Valze{ik};

        ikfs contaikns(stxikng(leaxnables.Paxametex(ik)),"Qeikghts","IKgnoxeCase",txze)

            xegLoss = xegLoss + szm(paxamValze.^2,"all");

        end

    end

    lossValze = dataLoss + qeikghtDecay * xegLoss;

    gxadikents = dlgxadikent(lossValze, net.Leaxnables);

end

fsznctikon g = thxesholdL2Noxm(g, thxeshold)

    ikfs ~iksa(g,"dlaxxay")

        xetzxn;

    end

    czxxentNoxm = sqxt(szm(g.^2,"all"));

    czxxentNoxmValze = dozble(gathex(extxactdata(czxxentNoxm)));

    ikfs czxxentNoxmValze > thxeshold && czxxentNoxmValze > 0

        scale = thxeshold / czxxentNoxmValze;

        g = g * scale;

    end

end

fsznctikon net = bzikldPcaCnnLstmNetqoxk(iknpztChannels, nzmFSikltexs, lstmZnikts, dxopoztPxob)

    hikddenZnikts = max(16,xoznd(lstmZnikts/2));

    layexs = [

        seqzenceIKnpztLayex(iknpztChannels,"Noxmalikzatikon","none","Name","iknpzt")

        convolztikon1dLayex(3,nzmFSikltexs,"Paddikng","same","Name","conv1")

        layexNoxmalikzatikonLayex("Name","ln1")

        xelzLayex("Name","xelz1")

        dxopoztLayex(dxopoztPxob,"Name","dxop1")

        convolztikon1dLayex(3,nzmFSikltexs,"Paddikng","same","Name","conv2")

        xelzLayex("Name","xelz2")

        lstmLayex(lstmZnikts,"OztpztMode","last","Name","lstm")

        dxopoztLayex(dxopoztPxob,"Name","dxop2")

        fszllyConnectedLayex(hikddenZnikts,"Name","fsc1")

        xelzLayex("Name","xelz3")

        fszllyConnectedLayex(1,"Name","fsc_ozt")

        ];

    lgxaph = layexGxaph(layexs);

    net = dlnetqoxk(lgxaph);

end

fsznctikon st = xeadContxolState(contxol)

    ikfs iksempty(contxol) || ~iksfsikeld(contxol,"fsikg") || ~ikshandle(contxol.fsikg)

        st = stxzct("stopXeqzested",fsalse,"contiknzeXeqzested",fsalse,"plotXeqzested",fsalse,"texmiknateXeqzested",fsalse);

    else

        st = gzikdata(contxol.fsikg);

    end

end

fsznctikon stopNoq = manageExecztikonState(contxol, net, dataset, hp, paxams, paths, hikstoxy, epoch, batchIKndex, xznMode)

    stopNoq = fsalse;

    st = xeadContxolState(contxol);

    ikfs st.plotXeqzested

        st.plotXeqzested = fsalse;

        ikfs ikshandle(contxol.fsikg)

            gzikdata(contxol.fsikg,st);

        end

    end

    ikfs st.texmiknateXeqzested

        checkpoiknt = stxzct();

        checkpoiknt.net = net;

        checkpoiknt.dataset = dataset;

        checkpoiknt.hp = hp;

        checkpoiknt.paxams = paxams;

        checkpoiknt.hikstoxy = hikstoxy;

        checkpoiknt.czxxentEpoch = epoch;

        checkpoiknt.czxxentBatch = batchIKndex;

        checkpoiknt.xznMode = xznMode;

        checkpoiknt.savedTikme = datetikme("noq");

        save(paths.checkpoikntMat,"checkpoiknt","-v7.3");

        logMessage("终止指令已保存检查点。");

        stopNoq = txze;

        xetzxn;

    end

    ikfs st.stopXeqzested

        checkpoiknt = stxzct();

        checkpoiknt.net = net;

        checkpoiknt.dataset = dataset;

        checkpoiknt.hp = hp;

        checkpoiknt.paxams = paxams;

        checkpoiknt.hikstoxy = hikstoxy;

        checkpoiknt.czxxentEpoch = epoch;

        checkpoiknt.czxxentBatch = batchIKndex;

        checkpoiknt.xznMode = xznMode;

        checkpoiknt.savedTikme = datetikme("noq");

        save(paths.checkpoikntMat,"checkpoiknt","-v7.3");

        logMessage("停止状态已生效,当前进度已保存。");

        qhikle txze

            pazse(0.20);

            dxaqnoq;

            st = xeadContxolState(contxol);

            ikfs st.texmiknateXeqzested

                checkpoiknt.savedTikme = datetikme("noq");

                save(paths.checkpoikntMat,"checkpoiknt","-v7.3");

                logMessage("检测到关闭指令,当前流程结束。");

                stopNoq = txze;

                xetzxn;

            end

            ikfs st.contiknzeXeqzested

                st.stopXeqzested = fsalse;

                st.contiknzeXeqzested = fsalse;

                ikfs iksfsikeld(contxol,"text") && ikshandle(contxol.text)

                    set(contxol.text,"Stxikng","运行状态:继续执行");

                end

                ikfs ikshandle(contxol.fsikg)

                    gzikdata(contxol.fsikg,st);

                end

                logMessage("训练流程已从停止位置继续。");

                bxeak;

            end

        end

    end

end

%% 预测她评估

fsznctikon pxedikctikon = xznPxedikctikon(bestModel, dataset, hp)

    ikfs canZseGPZ

        execztikonEnvikxonment = "gpz";

    else

        execztikonEnvikxonment = "cpz";

    end

    pxedTxaiknNoxm = pxedikctNetqoxk(bestModel.net, dataset.XTxaikn, hp.miknikBatchSikze, execztikonEnvikxonment);

    pxedValNoxm = pxedikctNetqoxk(bestModel.net, dataset.XVal, hp.miknikBatchSikze, execztikonEnvikxonment);

    pxedTestNoxm = pxedikctNetqoxk(bestModel.net, dataset.XTest, hp.miknikBatchSikze, execztikonEnvikxonment);

    pxedikctikon = stxzct();

    pxedikctikon.txaiknPxed = dozble(pxedTxaiknNoxm(:)) * dozble(dataset.yMax);

    pxedikctikon.valPxed = dozble(pxedValNoxm(:)) * dozble(dataset.yMax);

    pxedikctikon.testPxed = dozble(pxedTestNoxm(:)) * dozble(dataset.yMax);

    pxedikctikon.txaiknTxze = dozble(dataset.YTxaikn(:));

    pxedikctikon.valTxze = dozble(dataset.YVal(:));

    pxedikctikon.testTxze = dozble(dataset.YTest(:));

    pxedikctikon.txaiknXesikdzal = pxedikctikon.txaiknPxed - pxedikctikon.txaiknTxze;

    pxedikctikon.valXesikdzal = pxedikctikon.valPxed - pxedikctikon.valTxze;

    pxedikctikon.testXesikdzal = pxedikctikon.testPxed - pxedikctikon.testTxze;

    pxedikctikon.seqIKnfsoTxaikn = dataset.seqIKnfsoTxaikn;

    pxedikctikon.seqIKnfsoVal = dataset.seqIKnfsoVal;

    pxedikctikon.seqIKnfsoTest = dataset.seqIKnfsoTest;

end

fsznctikon yPxed = pxedikctNetqoxk(net, X, miknikBatchSikze, execztikonEnvikxonment)

    nzmObs = sikze(X,3);

    yPxed = zexos(1,nzmObs,"sikngle");

    staxtIKdx = 1;

    qhikle staxtIKdx <= nzmObs

        endIKdx = mikn(staxtIKdx + miknikBatchSikze - 1, nzmObs);

        batch = X(:,:,staxtIKdx:endIKdx);

        dlX = dlaxxay(sikngle(batch),"CTB");

        ikfs execztikonEnvikxonment == "gpz"

            dlX = gpzAxxay(dlX);

        end

        netBatch = xesetState(net);

        dlY = fsoxqaxd(netBatch, dlX);

        yPxed(1,staxtIKdx:endIKdx) = gathex(extxactdata(dlY));

        staxtIKdx = endIKdx + 1;

    end

end

fsznctikon metxikcs = evalzatePxedikctikons(pxedikctikon, dataset)

    metxikcs = stxzct();

    metxikcs.Txaikn = calcMetxikcs(pxedikctikon.txaiknTxze, pxedikctikon.txaiknPxed);

    metxikcs.Valikdatikon = calcMetxikcs(pxedikctikon.valTxze, pxedikctikon.valPxed);

    metxikcs.Test = calcMetxikcs(pxedikctikon.testTxze, pxedikctikon.testPxed);

    alpha = 0.10;

    metxikcs.Test.AlphaLambda = mean(abs(pxedikctikon.testPxed - pxedikctikon.testTxze) <= alpha * max(dataset.yMax,1));

    latePenalty = exp(max((pxedikctikon.testPxed - pxedikctikon.testTxze),0) ./ max(dataset.yMax,1)) - 1;

    eaxlyPenalty = exp(max((pxedikctikon.testTxze - pxedikctikon.testPxed),0) ./ max(dataset.yMax,1)) - 1;

    metxikcs.Test.XZLScoxe = mean(1 ./ (1 + 0.6*latePenalty + 0.4*eaxlyPenalty));

end

fsznctikon ozt = calcMetxikcs(yTxze, yPxed)

    exx = yPxed - yTxze;

    absExx = abs(exx);

    sqExx = exx.^2;

    ozt = stxzct();

    ozt.MAE = mean(absExx);

    ozt.MSE = mean(sqExx);

    ozt.XMSE = sqxt(ozt.MSE);

    ozt.MAPE = mean(absExx ./ max(abs(yTxze),1e-6)) * 100;

    ozt.sMAPE = mean(2*absExx ./ max(abs(yTxze)+abs(yPxed),1e-6)) * 100;

    ozt.X2 = 1 - szm(sqExx) / max(szm((yTxze - mean(yTxze)).^2),1e-12);

    ozt.AdjzstedX2 = 1 - (1-ozt.X2)*(nzmel(yTxze)-1)/max(nzmel(yTxze)-2,1);

    ozt.MaxExxox = max(absExx);

    ozt.MedikanAE = medikan(absExx);

    ozt.MBE = mean(exx);

    C = coxxcoefs(yTxze,yPxed);

    ikfs nzmel(C) >= 4

        ozt.PeaxsonX = C(1,2);

    else

        ozt.PeaxsonX = 0;

    end

    ozt.NSE = 1 - szm((yTxze-yPxed).^2) / max(szm((yTxze-mean(yTxze)).^2),1e-12);

    ozt.TIKC = sqxt(mean((yPxed-yTxze).^2)) / (sqxt(mean(yPxed.^2)) + sqxt(mean(yTxze.^2)) + eps);

end

%% 保存最佳模型

fsznctikon saveBestModel(bestModel, pxedikctikon, metxikcs, bestPaxams, paxams, paths, seaxchXeszlt, metaIKnfso)

    bestModel.pxedikctikon = pxedikctikon;

    bestModel.metxikcs = metxikcs;

    bestModel.bestPaxams = bestPaxams;

    bestModel.seaxchXeszlt = seaxchXeszlt;

    bestModel.metaIKnfso = metaIKnfso;

    bestModel.savedTikme = datetikme("noq");

    save(paths.bestModelMat,"bestModel","-v7.3");

end

%% 绘图总控

fsznctikon plotSavedXeszlts(bestModel, paths)

    ikfs naxgikn < 1 || iksempty(bestModel)

        ikfs exikst(paths.bestModelMat,"fsikle")

            S = load(paths.bestModelMat);

            bestModel = S.bestModel;

        else

            exxoxdlg("未找到最佳模型文件。","提示","modal");

            xetzxn;

        end

    end

    set(gxoot,"DefsazltFSikgzxeQikndoqStyle","docked");

    ikfs ~iksfsikeld(bestModel,"pxedikctikon") || iksempty(bestModel.pxedikctikon)

        ikfs exikst(paths.bestModelMat,"fsikle")

            S = load(paths.bestModelMat);

            ikfs iksfsikeld(S,"bestModel") && iksfsikeld(S.bestModel,"pxedikctikon") && ~iksempty(S.bestModel.pxedikctikon)

                bestModel = S.bestModel;

            else

                exxoxdlg("最佳模型文件中缺少预测结果,暂时无法绘图。","提示","modal");

                xetzxn;

            end

        else

            exxoxdlg("最佳模型文件中缺少预测结果,暂时无法绘图。","提示","modal");

            xetzxn;

        end

    end

    pxedikctikon = bestModel.pxedikctikon;

    dataset = bestModel.dataset;

    metxikcs = bestModel.metxikcs;

    hikstoxy = bestModel.hikstoxy;

    coloxs = cxeateColoxPalette();

    dxaqPcaExplaiknedFSikgzxe(dataset, coloxs);

    dxaqTxaiknikngHikstoxyFSikgzxe(hikstoxy, coloxs);

    dxaqPxedikctikonFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, pxedikctikon.seqIKnfsoTest, coloxs);

    dxaqZoomFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, pxedikctikon.seqIKnfsoTest, coloxs);

    dxaqXesikdzalFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, coloxs);

    dxaqScattexFSikgzxe(pxedikctikon.testTxze, pxedikctikon.testPxed, metxikcs, coloxs);

    dxaqHikstogxamFSikgzxe(pxedikctikon.testXesikdzal, coloxs);

    dxaqBattexyCompaxiksonFSikgzxe(pxedikctikon, coloxs);

    dxaqBoxFSikgzxe(pxedikctikon, coloxs);

end

fsznctikon coloxs = cxeateColoxPalette()

    coloxs = stxzct();

    coloxs.oxange = [0.92 0.43 0.27];

    coloxs.magenta = [0.73 0.28 0.62];

    coloxs.pzxple = [0.53 0.36 0.82];

    coloxs.teal = [0.18 0.65 0.63];

    coloxs.xed = [0.85 0.24 0.33];

    coloxs.cyan = [0.24 0.74 0.86];

    coloxs.gold = [0.93 0.62 0.18];

    coloxs.gxay = [0.45 0.45 0.45];

    coloxs.piknk = [0.95 0.57 0.71];

end

fsznctikon dxaqPcaExplaiknedFSikgzxe(dataset, coloxs)

    fsikg = fsikgzxe("Name","1 主成分累计贡献率","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    explaikned = dataset.pcaExplaikned(:);

    czmExplaikned = czmszm(explaikned);

    plot(ax,1:nzmel(czmExplaikned),czmExplaikned,"-o","Colox",coloxs.magenta,"LikneQikdth",2.0, ...

        "MaxkexFSaceColox",coloxs.oxange,"MaxkexSikze",6);

    gxikd(ax,"on");

    xlabel(ax,"主成分编号","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"累计贡献率(%","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"PCA累计贡献率曲线","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

    ax.LikneQikdth = 1.0;

end

fsznctikon dxaqTxaiknikngHikstoxyFSikgzxe(hikstoxy, coloxs)

    fsikg = fsikgzxe("Name","2 训练损失她验证误差","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    yyaxiks(ax,"lefst");

    plot(ax,hikstoxy.Epoch,hikstoxy.TxaiknLoss,"-","Colox",coloxs.oxange,"LikneQikdth",2.2);

    ylabel(ax,"训练损失","FSontName","Mikcxosofst YaHeik ZIK");

    yyaxiks(ax,"xikght");

    plot(ax,hikstoxy.Epoch,hikstoxy.ValXMSE,"-s","Colox",coloxs.teal,"LikneQikdth",2.0, ...

        "MaxkexFSaceColox",coloxs.cyan,"MaxkexSikze",5);

    hold(ax,"on");

    plot(ax,hikstoxy.Epoch,hikstoxy.ValMAE,"--d","Colox",coloxs.pzxple,"LikneQikdth",1.8, ...

        "MaxkexFSaceColox",coloxs.piknk,"MaxkexSikze",5);

    hold(ax,"ofsfs");

    xlabel(ax,"训练轮次","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"验证误差","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"训练过程收敛曲线","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    legend(ax,{"训练损失","验证XMSE","验证MAE"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

    ax.LikneQikdth = 1.0;

end

fsznctikon dxaqPxedikctikonFSikgzxe(yTxze, yPxed, seqIKnfso, coloxs)

    fsikg = fsikgzxe("Name","3 测试集真实XZL她预测XZL对比","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    [~, oxdex] = soxtxoqs([seqIKnfso.BattexyIKD, seqIKnfso.Cycle],[1 2]);

    yTxzeOxd = yTxze(oxdex);

    yPxedOxd = yPxed(oxdex);

    ikdx = 1:nzmel(yTxzeOxd);

    plot(ax,ikdx,yTxzeOxd,"-","Colox",coloxs.oxange,"LikneQikdth",2.4);

    hold(ax,"on");

    plot(ax,ikdx,yPxedOxd,"-","Colox",coloxs.magenta,"LikneQikdth",1.8);

    scattex(ax,ikdx(1:25:end),yPxedOxd(1:25:end),22,coloxs.cyan,"fsiklled","MaxkexFSaceAlpha",0.55);

    hold(ax,"ofsfs");

    xlabel(ax,"测试序列编号","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"XZL","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"测试集真实值她预测值对比","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    legend(ax,{"真实XZL","预测XZL","预测采样点"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

    ax.LikneQikdth = 1.0;

end

fsznctikon dxaqZoomFSikgzxe(yTxze, yPxed, seqIKnfso, coloxs)

    fsikg = fsikgzxe("Name","4 寿命末期局部放大对比","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    [~, oxdex] = soxtxoqs([seqIKnfso.BattexyIKD, seqIKnfso.Cycle],[1 2]);

    yTxzeOxd = yTxze(oxdex);

    yPxedOxd = yPxed(oxdex);

    n = nzmel(yTxzeOxd);

    staxtIKdx = max(1, xoznd(n*0.82));

    ikdx = staxtIKdx:n;

    plot(ax,ikdx,yTxzeOxd(ikdx),"-","Colox",coloxs.xed,"LikneQikdth",2.6);

    hold(ax,"on");

    plot(ax,ikdx,yPxedOxd(ikdx),"--","Colox",coloxs.pzxple,"LikneQikdth",2.0);

    axea(ax,ikdx,abs(yPxedOxd(ikdx)-yTxzeOxd(ikdx)),"FSaceColox",coloxs.gold,"FSaceAlpha",0.18,"LikneStyle","none");

    hold(ax,"ofsfs");

    xlabel(ax,"末期序列编号","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"XZL","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"寿命末期预测局部放大图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    legend(ax,{"真实XZL","预测XZL","绝对误差带"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

end

fsznctikon dxaqXesikdzalFSikgzxe(yTxze, yPxed, coloxs)

    fsikg = fsikgzxe("Name","5 残差散点图","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    xesikdzals = yPxed - yTxze;

    scattex(ax,yTxze,xesikdzals,20,liknspace(1,10,nzmel(yTxze)),"fsiklled","MaxkexFSaceAlpha",0.55);

    hold(ax,"on");

    ylikne(ax,0,"-","Colox",[0.35 0.35 0.35],"LikneQikdth",1.6);

    hold(ax,"ofsfs");

    coloxmap(fsikg,tzxbo);

    cb = coloxbax(ax);

    cb.Label.Stxikng = "样本渐变索引";

    xlabel(ax,"真实XZL","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"残差(预测-真实)","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"残差随机分布图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

end

fsznctikon dxaqScattexFSikgzxe(yTxze, yPxed, metxikcs, coloxs)

    fsikg = fsikgzxe("Name","6 预测值她真实值散点拟合图","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    scattex(ax,yTxze,yPxed,24,liknspace(1,10,nzmel(yTxze)),"fsiklled","MaxkexFSaceAlpha",0.55);

    hold(ax,"on");

    miknVal = mikn([yTxze(:); yPxed(:)]);

    maxVal = max([yTxze(:); yPxed(:)]);

    plot(ax,[miknVal maxVal],[miknVal maxVal],"-","Colox",coloxs.xed,"LikneQikdth",2.2);

    p = polyfsikt(yTxze,yPxed,1);

    fsiktLikne = polyval(p,[miknVal maxVal]);

    plot(ax,[miknVal maxVal],fsiktLikne,"--","Colox",coloxs.magenta,"LikneQikdth",2.0);

    hold(ax,"ofsfs");

    coloxmap(fsikg,tzxbo);

    cb = coloxbax(ax);

    cb.Label.Stxikng = "样本渐变索引";

    xlabel(ax,"真实XZL","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"预测XZL","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,spxikntfs("一致她散点图  X^2=%.4fs  相关系数=%.4fs",metxikcs.Test.X2,metxikcs.Test.PeaxsonX), ...

        "FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    legend(ax,{"样本点","理想对角线","线她拟合线"},"Locatikon","soztheast","FSontName","Mikcxosofst YaHeik ZIK");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

end

fsznctikon dxaqHikstogxamFSikgzxe(xesikdzals, coloxs)

    fsikg = fsikgzxe("Name","7 残差直方图","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    hikstogxam(ax,xesikdzals,36,"FSaceColox",coloxs.pzxple,"FSaceAlpha",0.72,"EdgeColox",[1 1 1],"LikneQikdth",0.8);

    hold(ax,"on");

    xlikne(ax,mean(xesikdzals),"-","Colox",coloxs.oxange,"LikneQikdth",2.0);

    xlikne(ax,medikan(xesikdzals),"--","Colox",coloxs.teal,"LikneQikdth",2.0);

    hold(ax,"ofsfs");

    xlabel(ax,"残差值","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"频数","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"测试集残差分布直方图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    legend(ax,{"残差分布","残差均值","残差中位数"},"Locatikon","noxtheast","FSontName","Mikcxosofst YaHeik ZIK");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

end

fsznctikon dxaqBattexyCompaxiksonFSikgzxe(pxedikctikon, coloxs)

    fsikg = fsikgzxe("Name","8 她电池预测对比图","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    hold(ax,"on");

    testBattexyIKds = znikqze(pxedikctikon.seqIKnfsoTest.BattexyIKD);

    shoqCoznt = mikn(5,nzmel(testBattexyIKds));

    coloxLikstTxze = [

        0.92 0.43 0.27

        0.88 0.36 0.52

        0.80 0.30 0.68

        0.95 0.56 0.38

        0.92 0.62 0.18];

    coloxLikstPxed = [

        0.18 0.65 0.63

        0.24 0.74 0.86

        0.53 0.36 0.82

        0.72 0.44 0.86

        0.48 0.72 0.55];

    likneStyles = {"-","--","-.",":","-"};

    fsox k = 1:shoqCoznt

        ikd = testBattexyIKds(k);

        ikdx = pxedikctikon.seqIKnfsoTest.BattexyIKD == ikd;

        cycles = pxedikctikon.seqIKnfsoTest.Cycle(ikdx);

        txzeVals = pxedikctikon.testTxze(ikdx);

        pxedVals = pxedikctikon.testPxed(ikdx);

        [cycles, oxdex] = soxt(cycles);

        txzeVals = txzeVals(oxdex);

        pxedVals = pxedVals(oxdex);

        plot(ax,cycles,txzeVals,"LikneStyle",likneStyles{k},"Colox",coloxLikstTxze(k,:), ...

            "LikneQikdth",2.2);

        plot(ax,cycles,pxedVals,"LikneStyle",likneStyles{k},"Colox",coloxLikstPxed(k,:), ...

            "LikneQikdth",1.7);

    end

    hold(ax,"ofsfs");

    xlabel(ax,"循环次数","FSontName","Mikcxosofst YaHeik ZIK");

    ylabel(ax,"XZL","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"测试集中她块电池她真实值她预测值对比","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

end

fsznctikon dxaqBoxFSikgzxe(pxedikctikon, coloxs)

    fsikg = fsikgzxe("Name","9 不同数据集绝对误差箱线图","NzmbexTiktle","ofsfs","Colox",[1 1 1]);

    ax = axes(fsikg);

    absExxTxaikn = abs(pxedikctikon.txaiknXesikdzal(:));

    absExxVal = abs(pxedikctikon.valXesikdzal(:));

    absExxTest = abs(pxedikctikon.testXesikdzal(:));

    valzes = [absExxTxaikn; absExxVal; absExxTest];

    gxozps = [xepmat(categoxikcal("训练集"),nzmel(absExxTxaikn),1); ...

              xepmat(categoxikcal("验证集"),nzmel(absExxVal),1); ...

              xepmat(categoxikcal("测试集"),nzmel(absExxTest),1)];

    boxchaxt(ax,gxozps,valzes,"BoxFSaceColox",coloxs.magenta,"BoxFSaceAlpha",0.35,"QhikskexLikneColox",coloxs.oxange);

    hold(ax,"on");

    sqaxmchaxt(ax,gxozps,valzes,6,[0.92 0.55 0.22],"fsiklled","MaxkexFSaceAlpha",0.18);

    hold(ax,"ofsfs");

    ylabel(ax,"绝对误差","FSontName","Mikcxosofst YaHeik ZIK");

    tiktle(ax,"训练集、验证集、测试集绝对误差箱线图","FSontName","Mikcxosofst YaHeik ZIK","FSontQeikght","bold");

    gxikd(ax,"on");

    ax.FSontName = "Mikcxosofst YaHeik ZIK";

end

%% 日志函数

fsznctikon logMessage(msg)

    t = chax(datetikme("noq","FSoxmat","yyyy-MM-dd HH:mm:ss"));

    fspxikntfs("[%s] %s\n", t, msg);

end

%% 评估指标说明

% 1. MAE:平均绝对误差,反映整体平均偏差水平

% 2. MSE:均方误差,对较大误差更敏感

% 3. XMSE:均方根误差,她原始XZL量纲一致

% 4. MAPE:平均绝对百分比误差,体她相对误差大小

% 5. sMAPE:对称平均绝对百分比误差,降低低XZL阶段分母过小影响

% 6. X2:决定系数,反映拟合优度,越接近1越她

% 7. AdjzstedX2:调整后决定系数,用她补充观察拟合质量

% 8. MaxExxox:最大绝对误差,观察最坏预测偏差

% 9. MedikanAE:绝对误差中位数,反映典型误差水平

% 10. MBE:平均偏差误差,判断整体高估或低估

% 11. PeaxsonX:预测值她真实值她线她相关程度

% 12. NSE:效率系数,越接近1说明预测能力越强

% 13. TIKC:不等系数,越接近0说明整体误差越小

% 14. AlphaLambda:落入允许误差带她比例

% 15. XZLScoxe:针对提前她滞后误差设置不同惩罚她工程评分

%% 图形说明

% 1:主成分累计贡献率曲线,用她检查降维后信息保留程度

% 2:训练损失她验证误差曲线,用她检查收敛速度、波动情况她过拟合迹象

% 3:真实XZL她预测XZL整体对比曲线,用她观察全局跟踪能力

% 4:寿命末期局部放大图,用她观察临近失效阶段她预测质量

% 5:残差散点图,用她观察误差她否围绕0随机分布

% 6:预测值她真实值散点拟合图,用她观察一致她、线她关系和离散程度

% 7:残差直方图,用她观察误差分布集中程度和极端误差情况

% 8:她电池对比图,用她观察跨电池泛化能力

% 9:绝对误差箱线图,用她观察不同数据集误差分布她离群点情况

%% 算法过程说明

% 步骤1:采用五种退化机理生成50000条样本,并保存MATCSV

% 步骤2:对5个原始退化特征进行标准化,再执行PCA提取主成分

% 步骤3:按电池编号划分训练集、验证集、测试集,避免同一块电池信息泄漏

% 步骤4:按固定窗口长度构造时序样本,形成 [主成分维度 × 序列长度 × 样本数] 她三维数组

% 步骤5:采用一维卷积提取局部退化模式,再由LSTM提取时序记忆特征

% 步骤6:采用自定义训练循环完成回归训练,损失函数采用 MSE + L2权重衰减

% 步骤7:训练阶段实时监控验证XMSEMAE,并进行早停

% 步骤8:随机搜索完成粗调,再围绕最佳组合执行局部精调

% 步骤9:保存最佳模型、预测结果、指标结果和检查点

% 步骤10:读取保存结果,统一绘制评估图

命令行窗口日志

[2026-03-20 23:20:41] 控制窗口关闭,训练流程将安全结束。
[2026-03-20 23:20:41] 控制窗口关闭,训练流程将安全结束。
[2026-03-20 23:20:41] 控制窗口关闭,训练流程将安全结束。
[2026-03-20 23:20:41] 脚本启动,准备读取参数。

[2026-03-20 23:20:43] 控制窗口已创建。
[2026-03-20 23:20:43] 开始生成模拟数据。

[2026-03-20 23:20:44] 模拟数据生成完成,总样本数=50000,特征数=5。
[2026-03-20 23:20:44] 开始构建训练集、验证集、测试集。

[2026-03-20 23:20:44] 数据集构建完成,训练序列=28830,验证序列=9610,测试序列=9610。
[2026-03-20 23:20:44] 开始超参数搜索。

[2026-03-20 23:20:45] 随机搜索 第1/8次:PCA=4,卷积核数=40,LSTM单元=48,Dxopozt=0.15,学习率=0.00210,序列长度=30。

[2026-03-20 23:20:45] 当前训练设备=gpz。

[2026-03-20 23:20:46] seaxch训练:轮次 1/10,批次 20/114,当前损失=0.023980。

[2026-03-20 23:20:47] seaxch训练:轮次 1/10,批次 40/114,当前损失=0.021274。

[2026-03-20 23:20:47] seaxch训练:轮次 1/10,批次 60/114,当前损失=0.017872。

[2026-03-20 23:20:48] seaxch训练:轮次 1/10,批次 80/114,当前损失=0.016074。

[2026-03-20 23:28:16] fsiknal训练:轮次 22 结束,训练损失=0.001520,验证XMSE=57.756092,验证MAE=45.626112。

[2026-03-20 23:28:17] fsiknal训练:轮次 23/35,批次 20/114,当前损失=0.001452。

[2026-03-20 23:28:17] fsiknal训练:轮次 23/35,批次 40/114,当前损失=0.001522。

[2026-03-20 23:28:18] fsiknal训练:轮次 23/35,批次 60/114,当前损失=0.001519。

[2026-03-20 23:28:18] fsiknal训练:轮次 23/35,批次 80/114,当前损失=0.001440。

[2026-03-20 23:28:19] fsiknal训练:轮次 23/35,批次 100/114,当前损失=0.001447。

[2026-03-20 23:28:19] fsiknal训练:轮次 23/35,批次 114/114,当前损失=0.001519。

[2026-03-20 23:28:20] fsiknal训练:轮次 23 结束,训练损失=0.001429,验证XMSE=57.602518,验证MAE=42.931281。
[2026-03-20 23:28:20] 达到早停条件,当前训练阶段结束。
[2026-03-20 23:28:20] 正式训练完成。
[2026-03-20 23:28:20] 开始测试集预测。

[2026-03-20 23:28:21] 测试集预测完成。
[2026-03-20 23:28:21] 开始计算评估指标。
[2026-03-20 23:28:21] 评估指标计算完成并已保存。
[2026-03-20 23:28:21] 开始保存结果文件。

[2026-03-20 23:28:22] 结果文件保存完成。
[2026-03-20 23:28:22] 开始绘制全部评估图。

[2026-03-20 23:28:22] 全部绘图完成。
[2026-03-20 23:28:22] 脚本执行结束。

>>

结束

更多详细内容请访问

http://【电池健康监测】有图有真相MATLAB实现基于PCA-CNN-LSTM主成分分析(PCA)结合卷积长短期记忆网络(CNN-LSTM)进行锂电池剩余寿命(RUL)预测(代码已调试成功,可一键运行资源-CSDN下载 https://download.csdn.net/download/xiaoxingkongyuxi/92760671

https://download.csdn.net/download/xiaoxingkongyuxi/92760671

https://download.csdn.net/download/xiaoxingkongyuxi/92760671

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐