如何使用 NNLS 进行非负多元线性回归?

How to use NNLS for non-negative multiple linear regression?

我正在尝试解决 Java 中的非负多元线性回归问题。 我找到了一个用 Scala 编写的求解器 class org.apache.spark.mllib.optimization.NNLS。 但是,我不知道怎么用。

让我感到困惑的是,下面的方法的界面似乎很奇怪。 我认为 A 是 MxN 矩阵,b 是 M 向量,参数 ataatb 应该分别是 NxN 矩阵和 N 向量。 然而,ata的实际类型是double[]

public static double[] solve(double[] ata, double[] atb, NNLS.Workspace ws)

我搜索了示例代码,但没有找到。 谁能给我一个示例代码? 该库是用 Scala 编写的,但如果可能,我想要 Java 代码。

免责声明我从未使用过NNLS,对非负多元线性回归一无所知。

您看看 Spark 2.1.1 的 NNLS 可以满足您的要求,但自 the latest Spark 2.2.1 marked as private[spark].

以来就不是正确的选择
private[spark] object NNLS {

更重要的是,从 Spark 2.0 开始,org.apache.spark.mllib 包(包括 NNLS 所属的 org.apache.spark.mllib.optimization)位于 maintenance mode:

The MLlib RDD-based API is now in maintenance mode.

As of Spark 2.0, the RDD-based APIs in the spark.mllib package have entered maintenance mode. The primary Machine Learning API for Spark is now the DataFrame-based API in the spark.ml package.

换句话说,你应该远离包裹,尤其是NNLS

那有什么选择呢?

您可以查看 NNLS 的测试,即 NNLSSuite,在那里您可以找到一些答案。

However, the actual type of ata is double[].

这是一个矩阵,所以元素又是双倍的。事实上,ata 直接传递给 BLAS 的 dgemv (here and here) that is described in the LAPACK docs:

DGEMV performs one of the matrix-vector operations

y := alpha*A*x + beta*y,   or   y := alpha*A**T*x + beta*y,

where alpha and beta are scalars, x and y are vectors and A is an m by n matrix.

这应该会给你足够的答案。


另一个问题是 Spark MLlib 中推荐的 NNLS 类计算方式是什么?

看起来像是 Spark MLLib 的 ALS 算法 uses NNLS 在幕后(这对于机器学习从业者来说可能并不奇怪)。

当 ALS 配置为训练模型并打开 nonnegative 参数时使用该部分代码,即 true(默认情况下禁用)。

nonnegative Param for whether to apply nonnegativity constraints.

Default: false

whether to use nonnegative constraint for least squares

我建议复习 Spark MLlib 的那一部分,以更深入地了解 NNLS 用于解决非负线性回归问题的用途。

我写了一个测试代码。 虽然我得到了一些像 Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS 这样的警告,它对简单的情况很有效,但是当 m 非常大(大约 3000)时 beta 经常变成 0。

package test;

import org.apache.spark.mllib.optimization.NNLS;

public class NNLSTest {
    public static void main(String[] args) {
        int n = 6, m = 300;
        ExampleInMatLabDoc();
        AllPositiveBetaNoiseInY(n, m);
        SomeNegativesInBeta(n, m);
        NoCorrelation(n, m);
    }

    private static void test(double[][] X, double[] y, double[] b) {        
        int m = X.length; int n = X[0].length;

        double[] Xty = new double[n];
        for (int i = 0; i < n; i++) {
            Xty[i] = 0.0;
            for (int j = 0; j < m; j++) Xty[i] += X[j][i] * y[j];
        }
        double[] XtX = new double[n * n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                XtX[n * i + j] = 0.0;
                for (int k = 0; k < m; k++) XtX[n * i + j] += X[k][i] * X[k][j];
            }
        }

        double[] beta = NNLS.solve(XtX, Xty, NNLS.createWorkspace(n));
        System.out.println("\ntrue beta\tbeta");
        for (int i = 0; i < beta.length; i++) System.out.println(b[i] + "\t" + beta[i]);

    }

    private static void ExampleInMatLabDoc() {
        // https://jp.mathworks.com/help/matlab/ref/lsqnonneg.html
        double[] y = new double[] { 0.8587, 0.1781, 0.0747, 0.8405 };
        double[][] x = new double[4][];
        x[0] = new double[] { 0.0372, 0.2869 };
        x[1] = new double[] { 0.6861, 0.7071 };
        x[2] = new double[] { 0.6233, 0.6245 };
        x[3] = new double[] { 0.6344, 0.6170 };
        double[] b = new double[] { 0.0, 0.6929 };
        test(x, y, b);
    }

    private static void AllPositiveBetaNoiseInY(int n, int m) {
        double[] b = new double[n];
        for (int i = 0; i < n; i++) b[i] = Math.random() * 100.0;       // random value in [0:100]
        double[] y = new double[m];
        double[][] x = new double[m][];
        for (int i = 0; i < m; i++) {
            x[i] = new double[n];
            x[i][0] = 1.0;
            y[i] = b[0];
            for (int j = 1; j < n; j++) {
                x[i][j] = (2.0 * Math.random() - 1.0) * 100.0; // random value in [-100:100]
                y[i] += x[i][j] * b[j];
            }
            y[i] *= 1.0 + (2.0 * Math.random() - 1.0) * 0.1; // add noise
        }
        test(x, y, b);
    }

    private static void SomeNegativesInBeta(int n, int m) {
        double[] b = new double[n];
        for (int i = 0; i < n; i++) b[i] = (2.0 * Math.random() - 1.0) * 100.0; // random value in [-100:100]
        double[] y = new double[m];
        double[][] x = new double[m][];
        for (int i = 0; i < m; i++) {
            x[i] = new double[n];
            x[i][0] = 1.0;
            y[i] = b[0];
            for (int j = 1; j < n; j++) {
                x[i][j] = (2.0 * Math.random() - 1.0) * 100.0; // random value in [-100:100]
                y[i] += x[i][j] * b[j];
            }
        }
        test(x, y, b);
    }

    private static void NoCorrelation(int n, int m) {
        double[] y = new double[m];
        double[][] x = new double[m][];
        for (int i = 0; i < m; i++) {
            x[i] = new double[n];
            x[i][0] = 1.0;
            for (int j = 1; j < n; j++) 
                x[i][j] = (2.0 * Math.random() - 1.0) * 100.0; // random value in [-100:100]
            y[i] = (2.0 * Math.random() - 1.0) * 100.0;
        }
        double[] b = new double[n];
        for (int i = 0; i < n; i++) b[i] = 0;
        test(x, y, b);
    }
}