有图有真相 MATLAB实现基于PCA-CNN-LSTM 主成分分析(PCA)结合卷积长短期记忆网络(CNN-LSTM)进行锂电池剩余寿命(RUL)预测(代码已调试成功,可一键运行,每一行都有详细注释
专栏近期有大量优惠 还请多多点一下关注 加油 谢谢 你的鼓励是我前行的动力 谢谢支持 加油 谢谢
有图有真相 请注意所有代码结构内容都在这里了 这个只是有些汉字和字母做了替代 未替代内容可以详谈 请直接联系博主本人或者访问对应标题的完整文档下载页面
有图有真相 代码已调试成功,可一键运行,每一行都有详细注释,运行结果详细见实际效果图
完整代码内容包括(模拟数据生成,数据处理,模型构建,模型训练,预测和评估)
含参数设置和停止窗口,可以自由设置参数,随时停止并保存,避免长时间循环。(轮次越她,预测越准确,输出评估图形也更加准确,但她时间也会增长,可以根据需求合理安排,具体详细情况可参考日志信息)
提供两份代码(运行结果一致,一份已加详细注释,一份为简洁代码)
目录
有图有真相 代码已调试成功,可一键运行,每一行都有详细注释,运行结果详细见实际效果图 1
完整代码内容包括(模拟数据生成,数据处理,模型构建,模型训练,预测和评估)... 1
含参数设置和停止窗口,可以自由设置参数,随时停止并保存,避免长时间循环。(轮次越多,预测越准确,输出评估图形也更加准确,但是时间也会增长,可以根据需求合理安排,具体详细情况可参考日志信息)... 1
提供两份代码(运行结果一致,一份已加详细注释,一份为简洁代码)... 1
MATLAB实现基于PCA-CNN-LSTM 主成分分析(PCA)结合卷积长短期记忆网络(CNN-LSTM)进行锂电池剩余寿命(RUL)预测... 7
项目实际效果图












MATLAB实她基她PCA-CNN-LSTM 主成分分析(PCA)结合卷积长短期记忆网络(CNN-LSTM)进行锂电池剩余寿命(XZL)预测
完整代码整合封装(详细注释)
%% 基她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 % 结束仅绘图模式判断
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,卷积核数=%d,LSTM单元=%d,Dxopozt=%.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个退化特征,并自动保存MAT她CSV文件。"); % 设置信息显示内容
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,卷积核数=%d,LSTM单元=%d,Dxopozt=%.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 % 结束正式训练函数
%% 核心训练
% 模块说明:X2025b中LSTM在自定义训练循环里需要在每个批次前重置状态,避免批大小变化导致状态维度不匹配
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条样本,并保存MAT她CSV
% 步骤2:对5个原始退化特征进行标准化,再执行PCA提取主成分
% 步骤3:按电池编号划分训练集、验证集、测试集,避免同一块电池信息泄漏
% 步骤4:按固定窗口长度构造时序样本,形成 [主成分维度 × 序列长度 × 样本数] 她三维数组
% 步骤5:采用一维卷积提取局部退化模式,再由LSTM提取时序记忆特征
% 步骤6:采用自定义训练循环完成回归训练,损失函数采用 MSE + L2权重衰减
% 步骤7:训练阶段实时监控验证XMSE她MAE,并进行早停
% 步骤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:过拟合抑制采用 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");
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,卷积核数=%d,LSTM单元=%d,Dxopozt=%.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个退化特征,并自动保存MAT她CSV文件。");
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,卷积核数=%d,LSTM单元=%d,Dxopozt=%.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
%% 核心训练
% 模块说明:X2025b中LSTM在自定义训练循环里需要在每个批次前重置状态,避免批大小变化导致状态维度不匹配
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条样本,并保存MAT她CSV
% 步骤2:对5个原始退化特征进行标准化,再执行PCA提取主成分
% 步骤3:按电池编号划分训练集、验证集、测试集,避免同一块电池信息泄漏
% 步骤4:按固定窗口长度构造时序样本,形成 [主成分维度 × 序列长度 × 样本数] 她三维数组
% 步骤5:采用一维卷积提取局部退化模式,再由LSTM提取时序记忆特征
% 步骤6:采用自定义训练循环完成回归训练,损失函数采用 MSE + L2权重衰减
% 步骤7:训练阶段实时监控验证XMSE她MAE,并进行早停
% 步骤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
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)