我目前正在代码中运行相当大的蒙特卡洛模拟,而性能则有待改善。
我想知道是否有一种方法可以直接在数据库上运行,我认为性能会更好。我可以生成随机数,但没有看到统计分布函数。
已经对我有很大帮助的第一步是:
我有一张参数表,其中每一行都是一个beta分布及其所有参数。我想使用这些分布参数生成随机值,并将其存储在单独的表中(蒙特卡罗模拟表,每次模拟运行一行)。
我该怎么办?
答案 0 :(得分:2)
您已经指出,PostgreSQL可以使用random()
函数来生成Uniform发行版。
对此问题的一般回答是Inverse Transform Sampling。 这种方法的局限性是:
显式构造的能力Quantile Function(又称PPF),可以将其定义为Inverse Function的Improper Integral:{{1 }};
构造分位数函数所需的PostgreSQL mathematical functions的存在。
也就是说,只要分位数函数是显式的,并且我们能够使用PostgreSQL数学函数来构造它,那么我们就可以使用PPF(u) = CDF^(-1)(u) | u = CDF(x) = int(PDF(x), x=(-infinty,x))
作为统一PRG为特定分布创建一个Pseudo Random Generator。
Exponential Distribution的逆变换采样效果很好:
random()
此函数从参数CREATE OR REPLACE FUNCTION expon(N INTEGER, l FLOAT = 1)
RETURNS SETOF FLOAT AS
$BODY$
SELECT
-(1/l)*ln(1 - random())
FROM
generate_series(1, N) AS i;
$BODY$
LANGUAGE SQL;
的指数分布中生成N
个样本。
对于Lognormal distribution,分位数功能依赖于Error Function,而PostgreSQL中未实现。因此,我们需要实现缺少的功能(这是一个整体,使用WINDOWING functions并非不可能,但可能不是最好的主意)或寻找另一种方法。
幸运的是,我们可以使用Normal Distribution生成Box-Muller Transform个样本:
l
以下通话:
CREATE OR REPLACE FUNCTION norm(N INTEGER, mu FLOAT = 0, sigma FLOAT = 1)
RETURNS SETOF FLOAT AS
$BODY$
SELECT
sigma*sqrt(-2.*ln(random()))*cos(2*pi()*random()) + mu
FROM
generate_series(1, N) AS i;
$BODY$
LANGUAGE SQL;
礼物:
并且MLE返回的SELECT norm(10000);
还不错,我们可能步入正轨。
然后我们可以对该函数取指数:
(mu=0.021131501222537274, sigma=1.0042820700537662)
我们有一个PRG用于对数正态分布。
以下通话:
CREATE OR REPLACE FUNCTION lognorm(N INTEGER, mu FLOAT = 0, sigma FLOAT = 1)
RETURNS SETOF FLOAT AS
$BODY$
SELECT
exp(x)
FROM
norm(N, mu, sigma) AS x;
$BODY$
LANGUAGE SQL;
也给出可接受的结果:
MLE返回SELECT lognorm(10000);
。
尽管它可能没有性能,但是使用Trapezoid Rule在PostgreSQL中估计错误函数非常容易。认为这是一个幼稚的实现:
(sigma=0.9996878296400589, loc=0.0, exp(mu)=1.0002728392916154)
如果我们将结果与精确形式(Python,scipy)进行比较,那么看起来我们至少可以得到6位有效数字:
CREATE OR REPLACE FUNCTION erf(x FLOAT, dx NUMERIC = 1e-3)
RETURNS FLOAT AS
$BODY$
WITH
D AS (
SELECT
y::FLOAT,
exp(-((y::FLOAT)^2)) AS fx0,
LEAD(exp(-((y::FLOAT)^2))) OVER(ORDER BY y) AS fx1
FROM
generate_series(0, x::NUMERIC, dx) AS y
)
SELECT
COALESCE((2/sqrt(pi()))*SUM((D.fx1 + D.fx0)*dx::FLOAT/2), 0.)
FROM D;
$BODY$
LANGUAGE SQL IMMUTABLE;
因此我们可以像对指数一样使用 x psql scipy errabs errrel
0 0.0 0.000000 0.000000 0.000000e+00 NaN
5 0.5 0.520500 0.520500 -7.323189e-08 -1.406953e-07
10 1.0 0.842701 0.842701 -6.918458e-08 -8.209863e-08
15 1.5 0.966105 0.966105 -2.973257e-08 -3.077571e-08
20 2.0 0.995322 0.995322 -6.888995e-09 -6.921371e-09
25 2.5 0.999593 0.999593 -9.076190e-10 -9.079885e-10
30 3.0 0.999978 0.999978 -6.962642e-11 -6.962795e-11
35 3.5 0.999999 0.999999 -3.149592e-12 -3.149594e-12
40 4.0 1.000000 1.000000 -8.404388e-14 -8.404388e-14
45 4.5 1.000000 1.000000 1.110223e-16 1.110223e-16
50 5.0 1.000000 1.000000 2.442491e-15 2.442491e-15
函数对Normal和Lognormal进行逆变换采样,但是我可能不是一个好主意。由于算法复杂性和集成不正确性,它的性能应会很差。
不幸的是,逆变换采样似乎不适用于Beta Distribution,因为不能将分位数函数表示为简单函数:它需要获得Regularized Incomplete Beta Function的逆。我不知道是否有可能:Wikipedia没有引用Beta分发的分位数功能。
在这种情况下,您可能需要使用某种编程语言(例如C / C ++)编译该函数并将其绑定到PostgreSQL函数,如@Nick Barnes在其注释中建议的那样。
正如@Nick Barnes在其评论中指出的那样:
erf
的函数不是random()
(默认为IMMUTABLE
,因为它们会更改PostgreSQL PRG的种子值; VOLATILE
;这样的极端情况。ln(0.)
中的函数通常运行良好(尽管我们必须考虑它们的复杂性和收敛性); LANGUAGE SQL
比使用SETOF FLOAT
更好,并且避免了使用FLOAT[]
的需要; unnest()
之类的演员表; ::FLOAT
,无需使用2.*acos(0.)
进行评估。