Cholesky分解在Java中生成NaN

时间:2017-09-30 03:00:46

标签: java linear-algebra matrix-factorization

我不确定这是一个maths.se还是一个SO问题,但是我认为这与我的Java有关。

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

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

  1. 这是线性代数的预期结果,还是例如a 我实施的人工制品?
  2. 在实践中如何最好地避免这个问题?
  3. 请注意,我不是在寻找使用库的建议。这只是为了我自己的理解。

    道歉,但我试图提供一个完整的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;
        }
    }
    

2 个答案:

答案 0 :(得分:2)

嗯,我想我已经从我跟随的同一本教科书中找到了我自己问题的答案。来自R&W p.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;
}

答案 1 :(得分:0)

我刚刚用C ++和JavaScript编写了我自己的Cholesky分解例程版本。而不是计算L,它计算U,但我很想用导致NaN错误的矩阵测试它。你能在这里发布矩阵,或者联系我(Profile中的信息。)