主要内容

本页采用了机器翻译。点击此处可查看英文原文。

使用 parfeval 进行并行优化时的监控

本示例演示了如何使用 parfeval 并行执行多项优化,并在客户端监控优化的进度。

在并行池中进行计算时,您可以使用 DataQueue 对象将工作单元的进度数据发送给客户端。如果您的优化函数支持 OutputFcn 选项,则可以使用 DataQueue 指定一个输出函数,该函数会在每次迭代时向客户端发送进度数据。您还可以将 DataQueue 与并行语言功能(例如 parforparfevalspmd)一起使用。

该示例演示了如何:

  • 从不同的起始点出发,并行对罗森布罗克函数 [1] 进行多次优化。

  • 使用自定义绘图函数,监控客户端优化进程的进展。

设置并行环境

在本地计算机上启动一个并行进程工作单元池。

parpool("Processes");
Starting parallel pool (parpool) using the 'Processes' profile ...
Connected to parallel pool with 6 workers.

定义问题

问题是在约束条件 x12+x221 下,使二变量罗森布罗克函数 f(x)=100(x2-x12)2+(1-x1)2 取最小值。该约束定义了一个以原点为中心的单位圆盘。例如,在此示例中,您将从单位圆盘内的多个起始点出发来求解该问题,并监控每次优化的进展情况。

定义目标函数和非线性约束函数。

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";

Progress plots for each optimization.

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 函数用于创建图、坐标轴和动画线对象,以可视化优化进程。具体来说,该函数:

  • 构建一个平铺布局,并返回坐标轴的句柄

  • 预先计算并在每个图块的网格上显示罗森布罗克目标函数等值线

  • 绘制单位圆盘边界 x12+x22-1=0 作为参考

  • 为每个图块创建一条动画线,并在优化过程中返回用于增量轨迹更新的句柄

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

另请参阅

函数

主题