Matlab 2019 中的 3 维 IRR

3-dimensional IRR in Matlab 2019

我正在尝试在 Matlab 2019a 中计算具有多个维度的 IRR。我的公式在理论上有效(暂时忽略 "multiple rates of return" 警告),但问题是对于更大的矩阵,即 noScenarios > 5 左右,代码变得非常慢。什么是编程替代方案?我也试过 fsolve 但我认为它也不快。

请注意,由于我不是数学高手,像 "Brent's method" 这样的简单关键字对我来说是不够的(例如 What is the Most Efficient Way to Calculate the Internal Rate of Return IRR?)。我必须知道 a) 如何在 Matlab 中实现它,以及 b) 如果它是非常白痴的证明,那么什么都不会出错?谢谢!

clc
clear
close all

noScenarios = 50;

CF = ones(300,noScenarios,noScenarios,noScenarios);
CF = [repmat(-300, 1,noScenarios,noScenarios,noScenarios); CF];

for scenarios1 = 1:noScenarios
    for scenarios2 = 1:noScenarios
        for scenarios3 = 1:noScenarios
            IRR3dimensional(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
        end
    end
end

要计算 IRR,您需要求解一个多项式方程。这必须分别为每个现金流向量完成。因此,将 irr 应用于多维矩阵不会缩短执行时间。我怀疑Matlab内部还在使用循环

您可能可以通过使用 fsolve 的优化选项来提高速度,但不太可能有很大的提高。据推测,Matlab 开发人员已经选择了足够好的方法。

因此,您唯一的另一种选择是并行化。如果您可以访问服务器或您的 laptop/desktop 有多个 CPU,您可以通过并行 运行 宁 irr 函数来减少 运行 时间。 (您可能还需要一个并行计算工具箱。)

我稍微修改了您的示例以使用 运行dom 现金流量值以使其更易于检查。不过我减少了场景和时间点的数量,让timeit函数可以在合理的时间内运行多次模拟。 (另外,请记住,执行时间似乎是时间点数量的指数。)

t = 150;
noScenarios = 10;
noThreads = 4;

CF = rand(t,noScenarios,noScenarios,noScenarios);
CF = [-rand(1,noScenarios,noScenarios,noScenarios); CF];

h1 = @() f1(CF, noScenarios);
fprintf("%0.4f : single thread, loop\n", timeit(h1))

h2 = @() f2(CF, noScenarios);
fprintf("%0.4f : single thread, vectorized\n", timeit(h2))

poolObj = parpool('local', noThreads);
h3 = @() f3(CF, noScenarios);
fprintf("%0.4f : parallelized outer loop\n", timeit(h3))
delete(poolObj);

poolObj = parpool('local', noThreads);
h4 = @() f4(CF, noScenarios);
fprintf("%0.4f : parallelized inner loop\n", timeit(h4))
delete(poolObj);

function res = f1(CF, noScenarios)
    res = zeros(noScenarios, noScenarios, noScenarios);
    for scenarios1 = 1:noScenarios
        for scenarios2 = 1:noScenarios
            for scenarios3 = 1:noScenarios
                res(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
            end
        end
    end
end

function res = f2(CF, noScenarios)
    res = reshape(irr(CF), noScenarios, noScenarios, noScenarios);
end

function res = f3(CF, noScenarios)
    res = zeros(noScenarios, noScenarios, noScenarios);
    parfor scenarios1 = 1:noScenarios
        for scenarios2 = 1:noScenarios
            for scenarios3 = 1:noScenarios
                res(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
            end
        end
    end
end

function res = f4(CF, noScenarios)
    res = zeros(noScenarios, noScenarios, noScenarios);
    for scenarios1 = 1:noScenarios
        for scenarios2 = 1:noScenarios
            parfor scenarios3 = 1:noScenarios
                res(scenarios1,scenarios2,scenarios3) = irr(CF(:,scenarios1,scenarios2,scenarios3));
            end
        end
    end
end

当我 运行 在具有 4 CPU 和 16 Gb 内存的服务器上执行此代码时,我得到了以下结果。

19.9357 : single thread, loop
20.4318 : single thread, vectorized
...
5.6346 : parallelized outer loop
...
12.4640 : parallelized inner loop

如您所见,irr 的矢量化版本对循环没有任何好处。在这种情况下,它会稍微慢一些。在我的其他测试中,它偶尔会快一点。

但是,您可以通过使用 parfor 函数并行化外循环来显着减少 运行 时间。它比并行化 inner-most 循环更好,因为每个批处理都有一定的执行开销。因此,与大量较小的批次相比,少量较大的批次的开销较低。

并行化的工作原理如下。首先,您使用以下命令创建一个本地工作线程池。确保您没有超过您拥有的 CPU 的数量。 parpool 可以无限期地等待,直到创建所有本地工作人员,并且只有在 CPU 可用时才能创建本地工作人员。

poolObj = parpool('local', noThreads);

创建池可能需要几秒钟。这就是为什么我将它移到我计时的功能之外。对于较大的作业,池创建时间与总执行时间相比微不足道。

在这里,我将池对象保存在一个变量中,然后将其删除。但是,它是可选的。默认情况下,池在 30 分钟不活动后或 Matlab 终止时被销毁。

之后,将要并行化的 for 循环替换为 parfor 调用,即 for scenarios1 = 1:noScenarios 变为 parfor scenarios1 = 1:noScenarios。默认情况下,parfor 将使用所有可用的工作人员,但您也可以指定允许使用 parfor (scenarios1 = 1:noScenarios, maxWorkers) 的最大工作人员数量。但是请注意,执行顺序不是固定的,即第五次迭代可能在第三次迭代之前执行。