使用 parfeval 进行并行优化时的监控
本示例演示了如何使用 parfeval 并行执行多项优化,并在客户端监控优化的进度。
在并行池中进行计算时,您可以使用 DataQueue 对象将工作单元的进度数据发送给客户端。如果您的优化函数支持 OutputFcn 选项,则可以使用 DataQueue 指定一个输出函数,该函数会在每次迭代时向客户端发送进度数据。您还可以将 DataQueue 与并行语言功能(例如 parfor、parfeval 和 spmd)一起使用。
该示例演示了如何:
从不同的起始点出发,并行对罗森布罗克函数 [1] 进行多次优化。
使用自定义绘图函数,监控客户端优化进程的进展。
设置并行环境
在本地计算机上启动一个并行进程工作单元池。
parpool("Processes");Starting parallel pool (parpool) using the 'Processes' profile ... Connected to parallel pool with 6 workers.
定义问题
问题是在约束条件 下,使二变量罗森布罗克函数 取最小值。该约束定义了一个以原点为中心的单位圆盘。例如,在此示例中,您将从单位圆盘内的多个起始点出发来求解该问题,并监控每次优化的进展情况。
定义目标函数和非线性约束函数。
fun = @(x)(100*(x(2) - x(1)^2)^2 + (1 - x(1))^2); nonlcon = @(x) deal(x(1)^2 + x(2)^2 - 1,[]);
指定多个起始点以进行最小化计算。利用对称对,直观展示由于罗森布罗克函数弯曲谷的对称性,优化轨迹可能产生何种差异。
x0 = [-0.5 0.2;
0.5 -0.2;
-0.2 0.5;
0.2 -0.5];设置进度可视化
创建一个图,其中每个起始点对应一条动画折线图。要生成该图,请使用 preparePlots 辅助函数,该函数在示例结尾处进行了定义。
numStartPoints = size(x0,1); [fig,hAx,hLines] = preparePlots(numStartPoints);
创建一个 DataQueue 对象,用于将求解器的进度数据从工作单元发送至客户端。
progressQueue = parallel.pool.DataQueue;
为了可视化工作单元发送给 DataQueue 的进度更新,请在工作单元每次发送数据时,使用 afterEach 调用 plotSolverProgress 辅助函数。辅助函数 plotSolverProgress 定义在示例的末尾。
plotFcn = @(data) plotSolverProgress(hAx,hLines,data{:});
afterEach(progressQueue,plotFcn);并行运行和监控优化
预分配一个由 parallel.FevalFuture 对象组成的向量,用于存储 parfeval 计算的结果。
solverFutures(1:numStartPoints) = parallel.FevalFuture;
使用 for 循环,针对每个起始点启动一次 parfeval 计算,以并行方式运行 fmincon。对于每次计算,定义一个 OutputFcn,该函数使用 sendSolverProgress 辅助函数通过 DataQueue 将迭代进度发回给客户端。一个匿名函数将计算索引和 DataQueue 对象传递给工作单元。每次调用 parfeval 都会使用指定的参量和选项运行 fmincon,并请求四个输出。parfeval 会返回 Future 对象,这些对象允许您在每次计算完成后异步收集结果。辅助函数 sendSolverProgress 定义在示例的末尾。
for idx = 1:numStartPoints outFcn = @(x,v,s) sendSolverProgress(x,v,s,idx,progressQueue); opts = optimoptions("fmincon",Display="off",OutputFcn=outFcn); solverFutures(idx) = parfeval(@fmincon,4,fun,x0(idx,:), ... [],[],[],[],[],[],nonlcon,opts); end
将图设置为可见,即可查看各优化任务在工作单元上的运行轨迹。
fig.Visible = "on";
parfeval 不会阻塞 MATLAB ®,因此在计算进行期间,您可以继续工作。工作单元在每次迭代中并行进行计算,并通过 DataQueue 发送进度数据。
如果您想在 parfeval 完成之前阻止 MATLAB,请对 Future 对象使用 wait 函数。当后续代码依赖于 wait 的完成时,使用 parfeval 函数很有用。
wait(solverFutures);
收集并显示结果
当 parfeval 完成计算后,wait 也完成,您就可以执行更多代码。
使用 fetchOutputs 函数获取优化结果。
[sol,fval,eflag,output] = fetchOutputs(solverFutures);
请将从不同初始点出发时,求解该问题所需的迭代次数以表格形式展示出来。
numIterations = [output.iterations]'; t = table(x0,numIterations, ... VariableNames=["Starting Points","Iterations"]); disp(t)
Starting Points Iterations
_______________ __________
-0.5 0.2 35
0.5 -0.2 24
-0.2 0.5 35
0.2 -0.5 39
结果表明,收敛所需的迭代次数取决于起始点。距离罗森布罗克函数的弯曲谷更近的初始点,所需迭代次数更少即可收敛;而距离该谷更远的初始点,则需要更多的迭代次数才能达到解。
参考资料
[1] 罗森布罗克,H. H.“一种寻找函数最大值或最小值的自动方法。”《计算机杂志》 第 3 卷第 3 期(1960 年):175–84. https://doi.org/10.1093/comjnl/3.3.175.
辅助函数
sendSolverProgress 函数
sendSolverProgress 函数通过一个 DataQueue 对象将当前求解器的状态和迭代信息发送给客户端。在每次求解器迭代中,必须将 sendSolverProgress 函数作为 OutputFcn 来调用。有关输出函数结构的信息,请参阅 输出函数和绘图函数语法 (Optimization Toolbox)。
function stop = sendSolverProgress(x,optimValues,state,idx,queue) stop = false; send(queue,{idx,x,optimValues,state}); pause(0.5); % Simulate an expensive function by pausing end
plotSolverProgress 函数
plotSolverProgress 函数可在优化运行过程中绘制其进度图。该函数利用 state 输入,在优化开始时、迭代过程中以及优化完成时执行不同的操作:
关于
"init":初始化当前起点的图。标记起始点和解的位置,并以起始点为轴标。关于
"iter":将当前迭代结果追加到一条动画线中,以显示求解器的轨迹。关于
"done":添加一条总结最终要点的字幕,并解除坐标轴的锁定状态。
有关绘图函数结构的信息,请参阅 输出函数和绘图函数语法 (Optimization Toolbox)。
function stop = plotSolverProgress(hAx,hLines,idx,x,optimValues,state) % Plot solver trajectories for each starting point. stop = false; ax = hAx(idx); switch state case "init" hold(ax,"on"); x1 = x(1); x2 = x(2); z1 = optimValues.fval; title(ax,sprintf("Starting Point [%.1f,%.1f]",x1,x2)) pStart = plot3(ax,x1,x2,z1,"go",MarkerSize=12,LineWidth=2); pSol = plot3(ax,0.79,0.62,0,"ko",MarkerSize=12,LineWidth=2); if idx == 1 lgd = legend([pStart,pSol],["Start","Solution"]); lgd.Layout.Tile = "east"; end drawnow limitrate nocallbacks; case "iter" al = hLines(idx); x1 = x(1); x2 = x(2); z1 = optimValues.fval; addpoints(al,x1,x2,z1); ax.SortMethod = "childorder"; drawnow limitrate nocallbacks; case "done" subtitle(ax,sprintf("Local minimum found [%.2f,%.2f]", ... x(1),x(2))); hold(ax,"off"); end end
preparePlots 函数
preparePlots 函数用于创建图、坐标轴和动画线对象,以可视化优化进程。具体来说,该函数:
构建一个平铺布局,并返回坐标轴的句柄
预先计算并在每个图块的网格上显示罗森布罗克目标函数等值线
绘制单位圆盘边界 作为参考
为每个图块创建一条动画线,并在优化过程中返回用于增量轨迹更新的句柄
function [fig,hAx,lineHdls] = preparePlots(numPlots) fig = figure(Name="Optimization Progress",Visible="off"); t = tiledlayout(fig,"flow",TileSpacing="tight"); hAx = gobjects(1,numPlots); lineHdls = gobjects(1,numPlots); xx = -3:.2:3; yy = -1:.2:3; [xx,yy] = meshgrid(xx,yy); zz = 100*(yy-xx.^2).^2+(1-xx).^2; for idx=1:numPlots ax = nexttile(t); axis(ax,"equal"); box(ax,"on"); th = linspace(0,2*pi,400); plot(ax,cos(th),sin(th),"k:"); xlabel("x_{1}"); ylabel("x_{2}"); zlabel("Rosenbrock Value"); xlim(ax,[-1.5 1.5]) ylim(ax,[-1.5 1.5]) grid on hold(ax,"on"); [~,contHndl] = contour3(ax,xx,yy,zz,[100,500],"k"); contHndl.Color = [0.8,0.8,0.8]; hold(ax,"off"); hAx(idx) = ax; lineHdls(idx) = animatedline(ax,NaN,NaN,NaN, ... Color="none",Marker="o",MarkerFaceColor="b"); end end