【问题标题】:Cholesky decomposition generating NaNs in JavaCholesky 分解在 Java 中生成 NaN
【发布时间】:2018-03-11 23:16:35
【问题描述】:

我不确定这是 maths.se 还是 SO 问题,但我会选择 SO,因为我认为它与我的 Java 有关。

我正在关注一本关于高斯过程 (R&W) 的教科书,并在 Java 中实现了一些示例。几个示例的一个常见步骤是生成协方差矩阵的 Cholesky 分解。在我的尝试中,我可以获得最大尺寸有限(33x33)的矩阵的成功结果。然而,对于任何更大的 NaN 出现在对角线(在 32,32 处),因此矩阵中的所有后续值同样是 NaN。

代码如下所示,在cholesky方法中注明了NaN的来源。本质上,协方差元素 a[32][32] 为 1.0,但 sum 的值略高于此 (1.0000001423291431),因此平方根是虚数。所以我的问题是:

  1. 这是线性代数的预期结果,或者,例如, 我的实现的人工制品?
  2. 在实践中如何最好地避免这个问题?

请注意,我不是在寻找要使用的库的建议。这只是为了我自己的理解。

抱歉,篇幅较长,但我已尝试提供完整的 MWE:

import static org.junit.Assert.assertFalse;

import org.junit.Test;

public class CholeskyTest {

    @Test
    public void testCovCholesky() {
        final int n = 34; // Test passes for n<34
        final double[] xData = getSpread(-5, 5, n);
        double[][] cov = covarianceSE(xData);
        double[][] lower = cholesky(cov);
        for(int i=0; i<n; ++i) {
            for(int j=0; j<n; ++j) {
                assertFalse("NaN at " + i + "," + j, Double.isNaN(lower[i][j]));
            }
        }
    }

    /**
     * Generate n evenly space values from min to max inclusive
     */
    private static double[] getSpread(final double min, final double max, final int n) {
        final double[] values = new double[n];
        final double delta = (max - min)/(n - 1);
        for(int i=0; i<n; ++i) {
            values[i] = min + i*delta;
        }
        return values;
    }

    /**
     * Calculate the covariance matrix for the given observations using
     * the squared exponential (SE) covariance function.
     */
    private static double[][] covarianceSE (double[] v) {
        final int m = v.length;
        double[][] k = new double[m][];
        for(int i=0; i<m; ++i) {
            double vi = v[i];
            double row[] = new double[m];
            for(int j=0; j<m; ++j) {
                double dist = vi - v[j];
                row[j] = Math.exp(-0.5*dist*dist);
            }
            k[i] = row;
        }
        return k;
    }

    /**
     * Calculate lower triangular matrix L such that LL^T = A
     * Using Cholesky decomposition from
     * https://rosettacode.org/wiki/Cholesky_decomposition#Java
     */
    private static double[][] cholesky(double[][] a) {
        final int m = a.length;
        double[][] l = new double[m][m];
        for(int i = 0; i< m;i++){
            for(int k = 0; k < (i+1); k++){
                double sum = 0;
                for(int j = 0; j < k; j++){
                    sum += l[i][j] * l[k][j];
                }
                l[i][k] = (i == k) ? Math.sqrt(a[i][i] - sum) : // Source of NaN at 32,32
                    (1.0 / l[k][k] * (a[i][k] - sum));
            }
        }
        return l;
    }
}

【问题讨论】:

    标签: java linear-algebra matrix-factorization


    【解决方案1】:

    嗯,我想我已经从我所遵循的同一本教科书中找到了我自己问题的答案。来自R&W第201页:

    在实践中,可能需要添加一个小的倍数 单位矩阵 $\epsilon I$ 到数值的协方差矩阵 原因。这是因为矩阵 K 的特征值可以衰减 非常迅速 [...] 并且没有这种稳定,Cholesky 分解失败。对生成的样本的影响是添加 额外的独立方差噪声$epsilon$。

    所以下面的改动似乎就足够了:

    private static double[][] cholesky(double[][] a) {
        final int m = a.length;
        double epsilon = 0.000001; // Small extra noise value
        double[][] l = new double[m][m];
        for(int i = 0; i< m;i++){
            for(int k = 0; k < (i+1); k++){
                double sum = 0;
                for(int j = 0; j < k; j++){
                    sum += l[i][j] * l[k][j];
                }
                l[i][k] = (i == k) ? Math.sqrt(a[i][i]+epsilon - sum) : // Add noise to diagonal values
                    (1.0 / l[k][k] * (a[i][k] - sum));
            }
        }
        return l;
    }
    

    【讨论】:

    • 这可能比我使用BigDecimal 解决这个问题的简短调查更好。
    • @AJNeufeld 啊,是的,这是我最初的想法(使用更高精度的浮点数)。伟大的思想都一样;-)
    • 还是“傻瓜很少不同”? :-p
    • @AJNeufeld 后者 ;-P 但说真的:每当精度问题导致建议使用 BigDecimal 时,我都会有点不寒而栗。想象一下上面的代码使用BigDecimal 会是什么样子。 (除此之外,它们基本上会导致问题转移到使用正确的舍入模式、比例等 - 这也不是微不足道的)
    • 是的,很丑。注意,我只说简短的调查——从未推荐过。缺少BigDecimal.sqrt 只是其中的一部分。遗憾的是,Java 缺乏long double 和运算符重载,这使得数学繁重的编程变得困难。但是有 Scala... :-)
    【解决方案2】:

    我刚刚用 C++ 和 JavaScript 编写了自己的 Cholesky 分解例程版本。它不是计算 L,而是计算 U,但我很想用导致 NaN 错误的矩阵对其进行测试。您能在此处发布矩阵吗,或与我联系(个人资料中的信息。)

    【讨论】:

    • 代码都在测试中:矩阵值是由covarianceSE(getSpread(-5, 5, 34)); 生成的。矩阵是对称的,所以如果计算 U 与 L 有任何不同,我会感到惊讶。
    • 这不是问题的真正答案,可能很快就会被删除。
    • @Marco13:也许是这样。但是我没有其他方法可以与 OP 进行交互。我试图评论他的原帖,但弹出一条错误消息,“你必须有 50 声望才能评论。”而且他的个人资料中没有联系信息。所以我只回复了我能做的。
    • @beldaz 是的,结果应该是彼此的转置。但是,我对教科书中描述的朴素算法进行了一些调整,并且很想知道它如何处理潜在的困难问题。
    猜你喜欢
    • 2013-02-12
    • 2020-05-29
    • 2015-06-20
    • 1970-01-01
    • 1970-01-01
    • 2014-03-03
    • 2013-11-01
    • 2017-11-04
    • 2020-11-05
    相关资源
    最近更新 更多