Skip to content

bug: graph penalty does not affect gp loss #82

Description

@travis-leith

min rep (generated by AI)

# Minimal reproducible example: graph-Laplacian penalty (graph_W / graph_lambda) is a no-op
# for IRLS losses (loss = "gp"), but works for the Gaussian solver (loss = "mse").
#
# RcppML build: GitHub zdebruine/RcppML, version 1.0.0, RemoteSha df69dddbe37962cd0179b6b6c6ace94e400b4f60
#
# Expectation: a nonzero graph_lambda over a nonempty feature graph should change the fitted
# factor W. It does under loss = "mse", but leaves W BIT-FOR-BIT IDENTICAL under loss = "gp",
# even at an extreme graph_lambda. The `angular` penalty, by contrast, DOES take effect under
# loss = "gp", so it is the graph penalty specifically that is not wired into the IRLS path.

library(RcppML)

cat("RcppML version:", as.character(packageVersion("RcppML")), "\n\n")

# Built-in dense count matrix (824 features x 135 samples); no external data needed.
data(aml)
A <- as.matrix(aml)

K <- 4L
SEED <- 1L
LAMBDA <- 50 # deliberately large so any effect is unmissable

# A chain graph over ADJACENT FEATURES (rows of A) -- a valid nonzero feature adjacency.
n_feat <- nrow(A)
feat_chain <- (abs(row(diag(n_feat)) - col(diag(n_feat))) == 1) * 1
cat("feature graph:", nrow(feat_chain), "x", ncol(feat_chain),
    "with", sum(feat_chain), "nonzeros\n\n")

fit_nmf <- function(loss, ...) {
    RcppML::nmf(A, k = K, loss = loss, seed = SEED, verbose = FALSE, ...)
}

# --- Graph penalty under the Gaussian (MSE) solver: EXPECTED to change W --------------------
mse_plain <- fit_nmf("mse")
mse_graph <- fit_nmf("mse", graph_W = feat_chain, graph_lambda = LAMBDA)
cat(sprintf("mse loss: max|W_plain - W_graph(%.0f)| = %.6g   <- graph penalty active\n",
            LAMBDA, max(abs(mse_plain@w - mse_graph@w))))

# --- Graph penalty under the IRLS (gp) solver: NO EFFECT (the bug) --------------------------
gp_plain <- fit_nmf("gp")
gp_graph <- fit_nmf("gp", graph_W = feat_chain, graph_lambda = LAMBDA)
cat(sprintf("gp  loss: max|W_plain - W_graph(%.0f)| = %.6g   <- graph penalty IGNORED\n",
            LAMBDA, max(abs(gp_plain@w - gp_graph@w))))

# --- Control: `angular` DOES take effect under gp, so the IRLS path is reached ---------------
gp_angular <- fit_nmf("gp", angular = c(0.5, 0))
cat(sprintf("gp  loss: max|W_plain - W_angular(0.5)| = %.6g   <- angular penalty active\n",
            max(abs(gp_plain@w - gp_angular@w))))

Either this should be supported, or an explicit error should be raised indicating that it is not supported.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions