我在MATLAB 2016b中使用fsolve()
在一个十亿个体素的数据集中为每个体素求解一对非线性方程。
我已经完成了所有' 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
将输入保持为持久变量,这会提高性能吗?我只看到了如何加载持久变量的例子,我怎样才能使它们只输入一次,未来的函数调用将从大量输入的假定大量开销中幸免?
编辑:
完整形式的原始方程式如下:
其中:
和
其他一切都是已知的,解决了x_1和x_2。 f_KN由样条逼近。 S_low(E)和S_high(E)是样条曲线,它们的直方图如下所示:
答案 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
其中Fhi
和Flo
是帮助函数来计算这些积分,在此示例中使用标量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
数据(S
,E0
,...)使用的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
提高到接近“实际”收敛点。
或者,您使用x0
(trapezoidal 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'
通常花费大部分时间通过有限的差异来计算雅可比行列式,所以手动为其手动计算值通常可以极大地加速算法。
会更快,因为
fsolve
不必进行额外的函数评估来进行有限差分特别是如果你使用上面的矩形方法或fsolve
,你可以重复使用你已经为函数值本身完成的许多计算,这意味着更快的速度。
答案 1 :(得分:0)
Rody的回答是正确的。提供雅可比行列式是最大的因素。特别是对于矢量化版本,雅各比提供的速度差异有3个数量级,而不是。
我无法在线查找有关此主题的信息,因此我将在此处拼写以供将来参考:可以使用fsolve()对独立的并行方程进行向量化,并获得很大的收益。
我还做了一些内联fsolve()的工作。在提供雅可比行列式并且更加智能化方程式之后,我的代码的串行版本主要是每个体素大约1 * 10 ^ -3秒的开销。此时函数内部的大部分时间都花费在传递选项-struct上并创建错误消息,这些消息从未发送过+许多未使用的东西,假定为优化函数内的其他优化函数(levenberg-marquardt for me)。我成功地将函数fsolve和它调用的一些函数进行了屠宰,在我的机器上将每个体素的时间减少到~1 * 10 ^ -4s。所以,如果您遇到串行实现,例如因为必须依赖以前的结果,所以很有可能内联fsolve()并获得良好的结果。
矢量化版本在我的情况下提供了最好的结果,每个体素约为5 * 10 ^ -5 s。