在Matlab

时间:2017-01-11 12:59:48

标签: matlab optimization mathematical-optimization

我在MATLAB 2016b中使用fsolve()在一个十亿个体素的数据集中为每个体素求解一对非线性方程。

equation pair

我已经完成了所有' easy'我所知道的优化。记忆定位是可以的,我使用parfor,方程式在数字上非常简单。积分的所有不连续性都被馈送到integral()。我使用具有良好起始值和合适的起始阻尼常数的Levenberg-Marquardt算法,它平均收敛6次迭代。

我现在每个体素大约6毫秒,这很好,但还不够好。我需要减少一个数量级才能使该技术可行。在开始提高准确性之前,我只能想到一些改进的事情:

等式中的样条用于快速采样复杂方程。每个方程有两个,一个在复杂的非线性方程中。它们代表两个方程,一个方程具有大量项但是平滑且没有不连续性,一个方程近似于从光谱中提取的直方图。我正如编辑建议的那样使用griddedInterpolant()

是否有更快的方法从预先计算的分布中采样点?

parfor i=1:numel(I1)
    sols = fsolve(@(x) equationPair(x, input1, input2, ... 
         6 static inputs, fsolve options)
    output1(i) = sols(1); output2(i) = sols(2)
end

致电fsolve时,我正在使用'参数化' Mathworks建议输入变量。我有一种唠叨的感觉,为每个体素定义一个匿名函数在这一点上占用了很大一部分时间。这是真的吗,一次又一次地定义匿名函数会有相对较大的开销吗?我有没有办法将调用矢量化为fsolve

有两个输入变量不断变化,所有其他输入变量保持静态。我需要为每个输入对解决一个方程对,这样我才能使它成为一个庞大的系统并立即解决它。除了fsolve之外,我还有其他选项来解决非线性方程对吗?

如果没有,一些静态输入相当大。有没有办法使用MATLAB' persistent将输入保持为持久变量,这会提高性能吗?我只看到了如何加载持久变量的例子,我怎样才能使它们只输入一次,未来的函数调用将从大量输入的假定大量开销中幸免?

编辑:

完整形式的原始方程式如下:

I_high

I_low

其中:

f_KN

Alpha

其他一切都是已知的,解决了x_1和x_2。 f_KN由样条逼近。 S_low(E)和S_high(E)是样条曲线,它们的直方图如下所示:

Spectra

2 个答案:

答案 0 :(得分:2)

所以,我想到了一些事情:

查找表

因为函数中的积分不依赖于x以外的任何参数,所以您可以从中创建一个简单的2D查找表:

% assuming simple (square) range here, adjust as needed
[x1,x2]  = meshgrid( linspace(0, xmax, N) );

LUT_high = zeros(size(x1));
LUT_low  = zeros(size(x1));

for ii = 1:N        

    LUT_high(:,ii) = integral(@(E) Fhi(E, x1(1,ii), x2(:,ii)), ...
                              0, E_high, ...
                              'ArrayValued', true);

    LUT_low(:,ii) = integral(@(E) Flo(E, x1(1,ii), x2(:,ii)), ...
                             0, E_low, ...
                             'ArrayValued', true);

end 

其中FhiFlo是帮助函数来计算这些积分,在此示例中使用标量x1和向量x2进行矢量化。将N设置为内存允许的最高值。

然后将那些查找表作为参数传递给equationPair()(允许parfor分发数据)。然后在interp2中使用equationPair()

F(1) = I_high - interp2(x1,x2,LUT_high, x(1), x(2));
F(2) = I_low  - interp2(x1,x2,LUT_low , x(1), x(2));

因此,不是每次重新计算整个 ,而是对x的预期范围进行一次评估,并重复使用结果。

您可以指定使用的插值方法,默认为linear。如果您真的关心准确性,请指定cubic

粗/细

如果查找表方法由于某种原因不可能(内存限制,如果x的可能范围太大),那么你可以做另外一件事:将整个过程分成两部分,我称之为罚款

粗略方法的目的是快速改善您的初始估算 ,但可能不那么准确。近似到目前为止的最快方法是通过rectangle method

  • 使用S 近似spline,只需使用原始列表数据(S_high/low = [S_high/low@E0, S_high/low@E1, ..., S_high/low@E_high/low]
  • E数据(SE0,...)使用的E1相同的值,评估指数{{1} 1}}:

    x

    然后使用矩形方法:

    Elo = linspace(0, E_low, numel(S_low)).';
    integrand_exp_low = exp(x(1)./Elo.^3 + x(2)*fKN(Elo));
    
    Ehi = linspace(0, E_high, numel(S_high)).';
    integrand_exp_high = exp(x(1)./Ehi.^3 + x(2)*fKN(Ehi));
    

对所有F(1) = I_low - (S_low * Elo) * (Elo(2) - Elo(1)); F(2) = I_high - (S_high * Ehi) * (Ehi(2) - Ehi(1)); fsolve这样运行I_low会使您的初始估算值I_high提高到接近“实际”收敛点。

或者,您使用x0trapezoidal method)代替矩形方法。有点慢,但可能更准确。

请注意,如果trapz(步长相等),您可以进一步减少计算次数。在这种情况下,两个被积函数的第一个(Elo(2) - Elo(1)) == (Ehi(2) - Ehi(1))元素是相同的,因此指数的值仅在N_low元素中不同。因此,只需计算N_low + 1 : N_high,并将integrand_exp_high设置为等于integrand_exp_low的第一个N_low元素。

精细方法然后使用您的原始实现(使用实际的integrand_exp_high s),然后从粗略步骤的更新初始估计开始。

这里的整个目标是尝试将所需的迭代总数从大约6减少到小于2.也许您甚至会发现integral()方法已经提供了足够的准确性,呈现整个<强大>精细步骤不必要。

矢量

上面概述的粗略步骤中的矩形方法很容易矢量化:

trapz

% (uses R2016b implicit expansion rules) Elo = linspace(0, E_low, numel(S_low)); integrand_exp_low = exp(x(:,1)./Elo.^3 + x(:,2).*fKN(Elo)); Ehi = linspace(0, E_high, numel(S_high)); integrand_exp_high = exp(x(:,1)./Ehi.^3 + x(:,2).*fKN(Ehi)); F = [I_high_vector - (S_high * integrand_exp_high) * (Ehi(2) - Ehi(1)) I_low_vector - (S_low * integrand_exp_low ) * (Elo(2) - Elo(1))]; 也适用于矩阵;它将整合到矩阵中的每一列。

然后使用trapz致电equationPair(),然后x0 = [x01; x02; ...; x0N]会聚到fsolve,其中[x1; x2; ...; xN]是体素的数量,每个N为1×2(x0),因此[x(1) x(2)]x0×2。

N应该能够轻松地将所有这些切换到池中的所有工作人员。

同样,精细方法的矢量化也应该是可能的;只需使用parfor 'ArrayValued'选项,如上所示:

integral()

雅可比

获取函数的衍生物非常简单。 Here是w.r.t的衍生物。 F = [I_high_vector - integral(@(E) S_high(E) .* exp(x(:,1)./E.^3 + x(:,2).*fKN(E)),... 0, E_high,... 'ArrayValued', true); I_low_vector - integral(@(E) S_low(E) .* exp(x(:,1)./E.^3 + x(:,2).*fKN(E)),... 0, E_low,... 'ArrayValued', true); ]; here w.r.t. x_1。你的雅可比矩阵必须是一个2×2矩阵

x_2

不要忘记前导减号(J = [dF(1)/dx(1) dF(1)/dx(2) dF(2)/dx(1) dF(2)/dx(2)]; F = I_hi/lo - g(x)

使用上述一种或两种方法,您可以实现计算Jacobian matrix的函数,并通过dF/dx = -dg/dx选项(via optimoptions)将其传递给fsolve 'SpecifyObjectiveGradient'选项在那里会派上用场。

因为'CheckGradients'通常花费大部分时间通过有限的差异来计算雅可比行列式,所以手动为其手动计算值通常可以极大地加速算法

会更快,因为

  1. fsolve不必进行额外的函数评估来进行有限差分
  2. 由于Jacobian
  3. 的精度提高,收敛率会增加

    特别是如果你使用上面的矩形方法或fsolve,你可以重复使用你已经为函数值本身完成的许多计算,这意味着更快的速度。

答案 1 :(得分:0)

Rody的回答是正确的。提供雅可比行列式是最大的因素。特别是对于矢量化版本,雅各比提供的速度差异有3个数量级,而不是。

我无法在线查找有关此主题的信息,因此我将在此处拼写以供将来参考:可以使用fsolve()对独立的并行方程进行向量化,并获得很大的收益。

我还做了一些内联fsolve()的工作。在提供雅可比行列式并且更加智能化方程式之后,我的代码的串行版本主要是每个体素大约1 * 10 ^ -3秒的开销。此时函数内部的大部分时间都花费在传递选项-struct上并创建错误消息,这些消息从未发送过+许多未使用的东西,假定为优化函数内的其他优化函数(levenberg-marquardt for me)。我成功地将函数fsolve和它调用的一些函数进行了屠宰,在我的机器上将每个体素的时间减少到~1 * 10 ^ -4s。所以,如果您遇到串行实现,例如因为必须依赖以前的结果,所以很有可能内联fsolve()并获得良好的结果。

矢量化版本在我的情况下提供了最好的结果,每个体素约为5 * 10 ^ -5 s。