From c9d7d262a0b036530113ae2300b5465893ecbc70 Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Fri, 9 Oct 2026 00:28:09 -0500 Subject: [PATCH 1/4] Add multiple-source transfer learning and separate space penalties Integrate the learnerv2 0.3.0 extensions into the R and C++ package interfaces, including weighted source operators and cross-validation for independent penalties. Preserve the common-penalty interface and add documentation, examples, reference regression tests, and stopping-rule tests. Prepare version 1.1.0 and add Jodie Li as a package author. Validation: R CMD check --no-manual passed with 151 test assertions; additional original-package and learnerv2 comparisons passed. --- .Rbuildignore | 3 + .gitignore | 6 + DESCRIPTION | 13 +- NEWS.md | 26 ++ R/RcppExports.R | 8 +- R/dlearner.R | 103 +++--- R/input-validation.R | 76 ++++ R/learner.R | 369 +++++++++++++------ inst/examples/multiple-sources.R | 43 +++ man/cv.learner.Rd | 101 ++++- man/dlearner.Rd | 59 ++- man/learner.Rd | 145 ++++++-- src/RcppExports.cpp | 31 +- src/kernels.cpp | 213 ++++++----- tests/testthat/fixtures/README.md | 54 +++ tests/testthat/fixtures/generate-reference.R | 87 +++++ tests/testthat/fixtures/multisource-v030.rds | Bin 0 -> 6238 bytes tests/testthat/test-cv-multisource.R | 135 +++++++ tests/testthat/test-main.R | 4 +- tests/testthat/test-reference-regression.R | 51 +++ tests/testthat/test-separate-penalties.R | 144 ++++++++ tests/testthat/test-stopping-behavior.R | 51 +++ 22 files changed, 1393 insertions(+), 329 deletions(-) create mode 100644 R/input-validation.R create mode 100644 inst/examples/multiple-sources.R create mode 100644 tests/testthat/fixtures/README.md create mode 100644 tests/testthat/fixtures/generate-reference.R create mode 100644 tests/testthat/fixtures/multisource-v030.rds create mode 100644 tests/testthat/test-cv-multisource.R create mode 100644 tests/testthat/test-reference-regression.R create mode 100644 tests/testthat/test-separate-penalties.R create mode 100644 tests/testthat/test-stopping-behavior.R diff --git a/.Rbuildignore b/.Rbuildignore index d96d2f7..5cbe826 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -4,3 +4,6 @@ ^\.github$ ^data-raw$ ^README_files/ +^\.Rprofile$ +^build_sean_meeting_.*\.cjs$ +^test_temp_file\.txt$ diff --git a/.gitignore b/.gitignore index 5b6a065..e4ae119 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,9 @@ .Rhistory .RData .Ruserdata + +# Local R startup and meeting preparation files +.Rprofile +.DS_Store +build_sean_meeting_*.cjs +test_temp_file.txt diff --git a/DESCRIPTION b/DESCRIPTION index ec868aa..de98bdc 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,10 +1,12 @@ Package: learner Type: Package Title: Latent Space-Based Transfer Learning -Version: 1.0.1 +Version: 1.1.0 Authors@R: c(person("Sean", "McGrath", role = c("aut", "cre"), email = "sean.mcgrath514@gmail.com", comment = c(ORCID = "0000-0002-7281-3516")), + person("Jodie", "Li", role = "aut", + email = "ljuanshuo@gmail.com"), person("Ryan", "ODea", role = c("aut"), email = "ryanodea@hsph.harvard.edu"), person("Cenhao", "Zhu", role = c("aut"), @@ -14,14 +16,16 @@ Authors@R: c(person("Sean", "McGrath", role = c("aut", "cre"), Maintainer: Sean McGrath Description: Implements transfer learning methods for low-rank matrix estimation. These methods leverage similarity in the latent row and column - spaces between the source and target populations to improve estimation in - the target population. The methods include the LatEnt spAce-based tRaNsfer + spaces between one or more source populations and a target population to + improve estimation in the target population. The methods include the + LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method and the direct projection LEARNER (D-LEARNER) method described by McGrath et al. (2024) . + Supports weighted combinations of source spaces, separate row- and + column-space penalties, and cross-validation for penalty selection. License: GPL (>=3) Encoding: UTF-8 LazyData: true -RoxygenNote: 7.3.2 URL: https://github.com/stmcg/learner BugReports: https://github.com/stmcg/learner/issues Imports: ScreeNOT, @@ -33,3 +37,4 @@ Suggests: Config/testthat/edition: 3 Depends: R (>= 2.10) +Config/roxygen2/version: 8.1.0 diff --git a/NEWS.md b/NEWS.md index a538a8b..56127db 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,29 @@ +# learner 1.1.0 + +* Integrate multiple-source support and separate space penalties from the + preliminary `learnerv2` 0.3.0 implementation into the package's R and C++ + implementation. +* `learner()`, `cv.learner()`, and `dlearner()` accept a list of aligned source + matrices and optional `source_weights`. Nonnegative weights are normalized + to sum to one and combine source projection operators. The common rank + interface is retained: automatic rank selection uses the first source, + and LEARNER initialization uses the first source even when rank is supplied. +* `learner()` accepts `lambda_1_row` and `lambda_1_col` to penalize the U and V + source-space residuals separately. The original `lambda_1` interface, + including positional calls, is retained. Equal separate coefficients reproduce + the common-penalty calculation under the same settings. +* `cv.learner()` accepts separate row and column penalty grids and returns a + three-dimensional validation-score array. Common-penalty calls retain their + previous output structure and score scale (summed held-out squared errors). +* Cache source decompositions during cross-validation and use C++ data in + parallel workers. Validate sources, weights, penalties, ranks, and fold counts. +* Expand argument documentation and examples for multiple sources, separate + penalties, cross-validation, missing data, and the current stopping rules. +* Expand automated tests for compatibility, gradients, multiple-source fitting, + cross-validation, and stopping behavior. Add fixed reference outputs from + `learnerv2` 0.3.0 to detect unintended numerical changes. +* Add Jodie Li as a package author. + ### learner version 1.0.0 (2025-03-02) * Re-implemented most of `learner` and `cv.learner` in Rcpp, resulting in significant speed-ups diff --git a/R/RcppExports.R b/R/RcppExports.R index ec831f9..d1b4b70 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -1,12 +1,12 @@ # Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 -learner_cpp <- function(Y_source, Y_target, r, lambda1, lambda2, step_size, max_iter, threshold, max_value) { - .Call(`_learner_learner_cpp`, Y_source, Y_target, r, lambda1, lambda2, step_size, max_iter, threshold, max_value) +learner_cpp <- function(Y_source, Y_target, source_weights, r, lambda1_row, lambda1_col, lambda2, step_size, max_iter, threshold, max_value) { + .Call(`_learner_learner_cpp`, Y_source, Y_target, source_weights, r, lambda1_row, lambda1_col, lambda2, step_size, max_iter, threshold, max_value) } -cv_learner_cpp <- function(Y_source, Y_target, lambda1_all, lambda2_all, step_size, n_folds, max_iter, threshold, n_cores, r, max_value, index_set) { - .Call(`_learner_cv_learner_cpp`, Y_source, Y_target, lambda1_all, lambda2_all, step_size, n_folds, max_iter, threshold, n_cores, r, max_value, index_set) +cv_learner_cpp <- function(Y_source, Y_target, source_weights, row_grid, col_grid, balance_grid, step_size, max_iter, threshold, n_cores, r, max_value, index_set) { + .Call(`_learner_cv_learner_cpp`, Y_source, Y_target, source_weights, row_grid, col_grid, balance_grid, step_size, max_iter, threshold, n_cores, r, max_value, index_set) } omp_max_threads <- function() { diff --git a/R/dlearner.R b/R/dlearner.R index 0454de2..7ce14aa 100644 --- a/R/dlearner.R +++ b/R/dlearner.R @@ -1,11 +1,13 @@ -#' Latent space-based transfer learning +#' Direct projection transfer learning #' -#' This function applies the Direct project LatEnt spAce-based tRaNsfer lEaRning (D-LEARNER) method (McGrath et al. 2024) to leverage data from a source population to improve -#' estimation of a low rank matrix in an underrepresented target population. +#' Estimate a target signal matrix by applying source-space operators directly +#' to the target data, using the D-LEARNER method (McGrath et al. 2024). +#' One source matrix or a list of source matrices can be supplied. #' -#' @param Y_target matrix containing the target population data -#' @param Y_source matrix containing the source population data -#' @param r (optional) integer specifying the rank of the knowledge graphs. By default, ScreeNOT (Donoho et al. 2023) is applied to the source population knowledge graph to select the rank. +#' @param Y_target nonempty numeric target matrix with only finite values. +#' Unlike \code{learner()}, this function does not allow missing target entries. +#' @param r optional common rank of the retained source spaces. If omitted, ScreeNOT selects it from the first source. +#' @inheritParams learner #' #' @return A list with the following components: #' \item{dlearner_estimate}{matrix containing the D-LEARNER estimate of the target population knowledge graph.} @@ -13,16 +15,24 @@ #' #' @details #' -#' \strong{Data and notation:} +#' Each source has the same dimensions as the target matrix \eqn{Y_0}. +#' Let \eqn{B_{U,k}} and \eqn{B_{V,k}} contain the first \eqn{r} left and +#' right singular vectors of source \eqn{k}. The estimate is +#' \deqn{\widehat\Theta_0 = \bar P_U Y_0 \bar P_V, \qquad +#' \bar P_U=\sum_k w_k B_{U,k}B_{U,k}^\top, \qquad +#' \bar P_V=\sum_k w_k B_{V,k}B_{V,k}^\top.} +#' The nonnegative weights are normalized to sum to one and default to equal +#' values. With one source this reduces to projection onto its retained left +#' and right singular spaces. With multiple sources, the averaged operators +#' need not be idempotent, and the estimate can have rank larger than \code{r}. +#' This is not an average of the raw source matrices or of separately computed +#' single-source D-LEARNER estimates. #' -#' The data consists of a matrix in the target population \eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population \eqn{Y_1 \in \mathbb{R}^{p \times q}}. -#' Let \eqn{\hat{U}_{k} \hat{\Lambda}_{k} \hat{V}_{k}^{\top}} denote the truncated singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. -#' -#' For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference is the target population knowledge graph, \eqn{\Theta_0}. -#' -#' \strong{Estimation:} -#' -#' This method estimates \eqn{\Theta_0} by \eqn{\hat{U}_{1}^{\top}\hat{U}_{1} Y_0 \hat{V}_{1}^{\top}\hat{V}_{1}}. +#' D-LEARNER does not iteratively fit target factors and has no space or balance +#' penalty parameters. All inputs must have aligned rows and columns and no +#' missing values. Names are not matched. If \code{r} is omitted, it is selected +#' from the first source, including when that source has zero weight. +#' Output dimension names are taken from the first source. #' #' @references #' Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. @@ -31,36 +41,41 @@ #' res <- dlearner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target) #' +#' # A second simulated source, made by perturbing the first. +#' set.seed(803) +#' source_A <- dat_highsim$Y_source[1:20, 1:10] +#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), +#' nrow(source_A), ncol(source_A)) +#' res_multi <- dlearner( +#' Y_source = list(A = source_A, B = source_B), +#' Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, +#' source_weights = c(0.7, 0.3)) +#' dim(res_multi$dlearner_estimate) +#' #' @export -dlearner <- function(Y_source, Y_target, r){ - # Error catching - if (!identical(dim(Y_source), dim(Y_target))){ - stop('Y_source and Y_target must have the same dimensions') +dlearner <- function(Y_source, Y_target, r, source_weights = NULL) { + sources <- prepare_sources(Y_source, Y_target, source_weights, + allow_missing_target = FALSE) + r <- resolve_rank(if (missing(r)) NULL else r, sources$matrices[[1]]) + bases <- lapply(sources$matrices, svd, nu = r, nv = r) + if (length(bases) == 1L) { + # Preserve the original single-source multiplication order. + s <- bases[[1]] + estimate <- s$u %*% (t(s$u) %*% Y_target %*% s$v) %*% t(s$v) + } else { + # Average source projectors, not raw source matrices. + middle <- matrix(0, nrow(Y_target), ncol(Y_target)) + for (k in seq_along(bases)) { + u <- bases[[k]]$u + middle <- middle + sources$weights[k] * (u %*% crossprod(u, Y_target)) + } + estimate <- matrix(0, nrow(Y_target), ncol(Y_target)) + for (k in seq_along(bases)) { + v <- bases[[k]]$v + estimate <- estimate + sources$weights[k] * ((middle %*% v) %*% t(v)) + } } - if (any(is.na(Y_source))){ - stop('Y_source cannot have NA values.') - } - if (any(is.na(Y_target))){ - stop('Y_target cannot have NA values') - } - - p <- nrow(Y_source) - q <- ncol(Y_source) - - if (missing(r)){ - max_rank <- min(p, q) / 3 - r <- max(ScreeNOT::adaptiveHardThresholding(Y = Y_source, k = max_rank)$r, 1) - } - - svd_source <- svd(Y_source, nu = r, nv = r) - dlearner_estimate <- svd_source$u %*% - (t(svd_source$u) %*% Y_target %*% svd_source$v) %*% - t(svd_source$v) - - colnames(dlearner_estimate) <- colnames(Y_source) - rownames(dlearner_estimate) <- rownames(Y_source) - - return(list(dlearner_estimate = dlearner_estimate, r = r)) + dimnames(estimate) <- dimnames(sources$matrices[[1]]) + list(dlearner_estimate = estimate, r = r) } - diff --git a/R/input-validation.R b/R/input-validation.R new file mode 100644 index 0000000..d15f347 --- /dev/null +++ b/R/input-validation.R @@ -0,0 +1,76 @@ +# Normalize one source or a source list without changing the user's ordering. +# The first source determines the default rank and LEARNER initialization. +prepare_sources <- function(Y_source, Y_target, source_weights = NULL, + allow_missing_target = TRUE) { + if (!is.matrix(Y_target) || !is.numeric(Y_target) || + any(dim(Y_target) == 0L) || any(is.infinite(Y_target))) { + stop('Y_target must be a nonempty numeric matrix without infinite values.') + } + if ((!allow_missing_target && anyNA(Y_target)) || all(is.na(Y_target))) { + stop('Y_target must contain observed values; dlearner does not allow NA values.') + } + sources <- if (is.matrix(Y_source)) list(Y_source) else Y_source + if (!is.list(sources) || length(sources) == 0L) { + stop('Y_source must be a matrix or a nonempty list of matrices.') + } + for (source in sources) { + if (!is.matrix(source) || !is.numeric(source) || any(!is.finite(source))) { + stop('Every source must be a numeric matrix with finite values and no NA values.') + } + if (!identical(dim(source), dim(Y_target))) { + stop('All source matrices and Y_target must have the same dimensions.') + } + } + if (is.null(source_weights)) source_weights <- rep(1, length(sources)) + if (!is.numeric(source_weights) || length(source_weights) != length(sources) || + any(!is.finite(source_weights)) || any(source_weights < 0) || + !any(source_weights > 0)) { + stop('source_weights must have one finite nonnegative value per source and a positive total.') + } + weights <- source_weights / max(source_weights) + list(matrices = sources, weights = weights / sum(weights)) +} + +resolve_rank <- function(r, source) { + if (is.null(r)) { + r <- max(ScreeNOT::adaptiveHardThresholding( + Y = source, k = min(dim(source)) / 3)$r, 1) + } + if (!is.numeric(r) || length(r) != 1L || !is.finite(r) || + r < 1 || r != floor(r) || r > min(dim(source))) { + stop('r must be an integer between 1 and the smaller matrix dimension.') + } + as.integer(r) +} + +validate_penalty_grid <- function(values, name) { + if (!is.numeric(values) || length(values) == 0L || + any(!is.finite(values)) || any(values < 0)) { + stop(name, ' must be a nonempty vector of finite nonnegative numbers.', call. = FALSE) + } +} + +validate_count <- function(value, name, lower = 1, upper = .Machine$integer.max) { + if (!is.numeric(value) || length(value) != 1L || !is.finite(value) || + value != floor(value) || value < lower || value > upper) { + stop(name, ' must be an integer between ', lower, ' and ', upper, '.', call. = FALSE) + } +} + +prepare_control <- function(control, step_size) { + if (!is.list(control)) stop('control must be a list.') + if (!is.numeric(step_size) || length(step_size) != 1L || + !is.finite(step_size) || step_size <= 0) { + stop('step_size must be a finite positive number.') + } + if (is.null(control$max_iter)) control$max_iter <- 100L + if (is.null(control$threshold)) control$threshold <- 0.001 + if (is.null(control$max_value)) control$max_value <- 10 + validate_count(control$max_iter, 'control$max_iter') + validate_space_penalty(control$threshold, 'control$threshold') + if (!is.numeric(control$max_value) || length(control$max_value) != 1L || + !is.finite(control$max_value) || control$max_value <= 0) { + stop('control$max_value must be a finite positive number.') + } + control +} diff --git a/R/learner.R b/R/learner.R index 5116111..c22cc60 100644 --- a/R/learner.R +++ b/R/learner.R @@ -1,25 +1,53 @@ #' Cross-validation for LEARNER #' -#' This function performs k-fold cross-validation to select the nuisance parameters \eqn{(\lambda_1, \lambda_2)} for \code{\link{learner}}. -#' -#' @param Y_target matrix containing the target population data, as in \code{\link{learner}} -#' @param Y_source matrix containing the source population data, as in \code{\link{learner}} -#' @param r (optional) integer specifying the rank of the knowledge graphs, as in \code{\link{learner}} -#' @param lambda_1_all vector of numerics specifying the candidate values of \eqn{\lambda_1} (see Details) -#' @param lambda_2_all vector of numerics specifying the candidate values of \eqn{\lambda_2} (see Details) -#' @param step_size numeric scalar specifying the step size for the Newton steps in the numerical optimization algorithm, as in \code{\link{learner}} -#' @param n_folds an integer specify the number of cross-validation folds. The default is \code{4}. -#' @param n_cores an integer specifying the number of CPU cores in OpenMP parallelization. Parallelization is performed across the different candidate \eqn{(\lambda_1, \lambda_2)} pairs. The default is \code{1}, i.e., no parallelization. +#' Select common or separate source-space penalties and a balance penalty for +#' \code{\link{learner}} by k-fold cross-validation on observed target entries. +#' One source matrix or a list of source matrices can be supplied. +#' +#' @param lambda_1_all nonempty vector of finite nonnegative candidate common penalties. Use this or both separate grids, not both modes. +#' @param lambda_2_all nonempty vector of finite nonnegative candidate balance penalties. +#' @param lambda_1_row_all nonempty vector of finite nonnegative candidate penalties on U; requires \code{lambda_1_col_all}. +#' @param lambda_1_col_all nonempty vector of finite nonnegative candidate penalties on V; requires \code{lambda_1_row_all}. +#' @inheritParams learner +#' @param step_size numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm, as in \code{\link{learner}} +#' @param n_folds integer from 2 to the number of observed target entries. The default is \code{4}. +#' @param n_cores positive integer specifying the requested number of OpenMP +#' threads. Candidate penalty combinations are evaluated in parallel when the +#' package is built with OpenMP support; otherwise they run sequentially. +#' The default is \code{1}. #' @param control a list of parameters for controlling the stopping criteria for the numerical optimization algorithm, as in \code{\link{learner}}. #' #' @return A list with the following elements: -#' \item{lambda_1_min}{value of \eqn{\lambda_1} with the smallest MSE} -#' \item{lambda_2_min}{value of \eqn{\lambda_2} with the smallest MSE} -#' \item{mse_all}{matrix containing MSE value for each \eqn{(\lambda_1, \lambda_2)} pair. The rows correspond to the \eqn{\lambda_1} values, and the columns correspond to the \eqn{\lambda_2} values.} +#' \item{lambda_1_min}{selected common penalty; \code{NA} in separate mode.} +#' \item{lambda_1_row_min, lambda_1_col_min}{selected separate penalties, returned only in separate mode.} +#' \item{lambda_2_min}{balance penalty in the combination with the smallest held-out squared-error sum.} +#' \item{mse_all}{held-out squared-error sums, retaining the historical scale despite the name. Common mode returns a matrix indexed by \code{lambda_1_all} and \code{lambda_2_all}. Separate mode returns a three-dimensional array indexed by \code{lambda_1_row_all}, \code{lambda_1_col_all}, and \code{lambda_2_all}, in that order.} #' \item{r}{rank value used.} #' #' @details -#' Given sets of candidate values of \eqn{\lambda_1} and \eqn{\lambda_2}, this function performs k-fold cross-validation to select the pair \eqn{(\lambda_1, \lambda_2)} with the smallest held out error. This function randomly partitions the entries of \code{Y_target} into \eqn{k} (approximately) equally sized subsamples. The training data sets are obtained by removing one of the \eqn{k} subsamples and the corresponding test data sets are based on the held out subsamples. The \code{\link{learner}} function is applied to each training data set. The held out error is computed by the mean squared error comparing the entries in the test data sets with those imputed from the LEARNER estimates. See McGrath et al. (2024) for further details. +#' Each observed target entry is held out once. The same folds are used for all +#' candidates. Dividing \code{mse_all} by the number of observed target entries +#' gives the mean squared error without changing the selected candidate. Ties +#' select the first minimum in R array order. Existing missing entries are +#' excluded from validation. Rank, source spaces, source weights, and the +#' first-source initialization are fixed across folds. The function tunes +#' penalties, not rank or source weights. Call \code{set.seed()} before +#' \code{cv.learner()} to reproduce the fold allocation. +#' For independent penalties, supply both separate grids and \code{lambda_2_all}. +#' Candidates with non-finite errors are excluded with a warning; if all fail, +#' the function stops. Finite fits that reach the iteration limit are retained, +#' so selecting a candidate does not certify convergence of its fold fits. +#' Assess the optimization settings as well as the final refit. +#' +#' The return value contains scores and selected parameters, not a final fitted +#' target matrix. Refit with \code{learner()} using all observed target entries +#' and the selected penalties, while retaining the same sources, weights, and rank. +#' +#' Observed target entries are randomly partitioned into approximately equal +#' folds. Each fold is set to missing in the training matrix, and the fitted +#' matrix is used to predict the held-out entries. Errors are summed across +#' folds for each candidate pair (common penalties) or triple (separate +#' penalties). Source entries are not held out. See McGrath et al. (2024). #' #' @references #' McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. @@ -31,93 +59,196 @@ #' lambda_2_all = c(1, 10, 100), #' step_size = 0.003) #' +#' # A small simulated example: source B is a perturbation of source A, +#' # not an independent population. Weights are fixed demonstration values. +#' set.seed(803) +#' source_A <- dat_highsim$Y_source[1:20, 1:10] +#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), +#' nrow(source_A), ncol(source_A)) +#' sources <- list(A = source_A, B = source_B) +#' target <- dat_highsim$Y_target[1:20, 1:10] +#' weights <- c(0.7, 0.3) +#' +#' set.seed(815) +#' res_separate <- cv.learner( +#' Y_source = sources, Y_target = target, r = 2, +#' source_weights = weights, +#' lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), +#' lambda_2_all = c(0.1, 1), step_size = 0.0003, +#' n_folds = 2, control = list(max_iter = 100)) +#' dim(res_separate$mse_all) # row grid x column grid x balance grid #' +#' fit <- learner( +#' Y_source = sources, Y_target = target, r = res_separate$r, +#' source_weights = weights, +#' lambda_1_row = res_separate$lambda_1_row_min, +#' lambda_1_col = res_separate$lambda_1_col_min, +#' lambda_2 = res_separate$lambda_2_min, step_size = 0.0003, +#' control = list(max_iter = 100)) +#' fit$convergence_criterion +#' # This short example demonstrates the workflow; check convergence before +#' # interpreting results or comparing statistical performance. #' #' @export -cv.learner <- function(Y_source, Y_target, r, lambda_1_all, lambda_2_all, - step_size, n_folds = 4, n_cores = 1, control = list()) { - ## Error catching - if (!identical(dim(Y_source), dim(Y_target))){ - stop('Y_source and Y_target must have the same dimensions') - } - if (any(is.na(Y_source))){ - stop('Y_source cannot have NA values.') - } +cv.learner <- function(Y_source, Y_target, r, lambda_1_all = NULL, lambda_2_all, + step_size, n_folds = 4, n_cores = 1, control = list(), + lambda_1_row_all = NULL, lambda_1_col_all = NULL, + source_weights = NULL) { + sources <- prepare_sources(Y_source, Y_target, source_weights) + r <- resolve_rank(if (missing(r)) NULL else r, sources$matrices[[1]]) + control <- prepare_control(control, step_size) + validate_penalty_grid(lambda_2_all, 'lambda_2_all') - if (missing(r)){ - p <- nrow(Y_source); q <- ncol(Y_source) - max_rank <- min(p, q) / 3 - r <- max(ScreeNOT::adaptiveHardThresholding(Y = Y_source, k = max_rank)$r, 1) - } - if (!('max_iter' %in% names(control))){ - control$max_iter <- 100 - } - if (!('threshold' %in% names(control))){ - control$threshold <- 0.001 - } - if (!('max_value' %in% names(control))){ - control$max_value <- 10 - } - - if (n_folds < 2){ - stop('n_folds must be 2 or greater.') + # A common penalty uses a 2D grid; independent penalties use a 3D grid. + separate <- !is.null(lambda_1_row_all) || !is.null(lambda_1_col_all) + if (separate) { + if (!is.null(lambda_1_all)) { + stop('Provide either lambda_1_all or both separate grids, not both modes.') + } + if (is.null(lambda_1_row_all) || is.null(lambda_1_col_all)) { + stop('Both lambda_1_row_all and lambda_1_col_all must be provided together.') + } + validate_penalty_grid(lambda_1_row_all, 'lambda_1_row_all') + validate_penalty_grid(lambda_1_col_all, 'lambda_1_col_all') + grid <- expand.grid(row = lambda_1_row_all, col = lambda_1_col_all, + balance = lambda_2_all) + grid_dim <- c(length(lambda_1_row_all), length(lambda_1_col_all), length(lambda_2_all)) + } else { + validate_penalty_grid(lambda_1_all, 'lambda_1_all') + grid <- expand.grid(row = lambda_1_all, balance = lambda_2_all) + grid$col <- grid$row + grid_dim <- c(length(lambda_1_all), length(lambda_2_all)) } - ## Creating training and testing data sets + # Only observed target entries can be validation entries. available_indices <- which(!is.na(Y_target)) n_indices <- length(available_indices) - indices <- sample(available_indices, size = n_indices, replace = FALSE) - 1 - - index_set <- vector(mode = "list", length = n_folds) - for (fold in 1:n_folds){ - index_set[[fold]] <- indices[floor((fold - 1) * n_indices / n_folds + 1):floor(fold * n_indices / n_folds)] + validate_count(n_folds, 'n_folds', lower = 2, upper = n_indices) + validate_count(n_cores, 'n_cores') + indices <- sample(available_indices, size = n_indices, replace = FALSE) - 1L + index_set <- vector('list', n_folds) + for (fold in seq_len(n_folds)) { + start <- floor((fold - 1) * n_indices / n_folds) + 1L + end <- floor(fold * n_indices / n_folds) + index_set[[fold]] <- indices[seq.int(start, end)] } - result <- cv_learner_cpp(Y_source, Y_target, lambda_1_all, lambda_2_all, step_size, - n_folds, control$max_iter, control$threshold, n_cores, r, control$max_value, - index_set) - return(result) + scores <- cv_learner_cpp(sources$matrices, Y_target, sources$weights, + grid$row, grid$col, grid$balance, step_size, + control$max_iter, control$threshold, n_cores, r, + control$max_value, index_set) + if (!any(is.finite(scores))) stop('No candidate produced finite validation errors.') + if (any(!is.finite(scores))) warning('Some candidates produced non-finite validation errors and were excluded.') + scores[!is.finite(scores)] <- Inf + best <- which.min(scores) + # Keep the historical mse_all scale: summed held-out squared errors. + mse_all <- array(scores, dim = grid_dim) + if (!separate) { + return(list(lambda_1_min = grid$row[best], lambda_2_min = grid$balance[best], + mse_all = mse_all, r = r)) + } + list(lambda_1_min = NA_real_, lambda_2_min = grid$balance[best], + lambda_1_row_min = grid$row[best], lambda_1_col_min = grid$col[best], + mse_all = mse_all, r = r) } #' Latent space-based transfer learning #' -#' This function applies the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method (McGrath et al. 2024) to leverage data from a source population to improve -#' estimation of a low rank matrix in an underrepresented target population. -#' -#' @param Y_target matrix containing the target population data -#' @param Y_source matrix containing the source population data -#' @param r (optional) integer specifying the rank of the knowledge graphs. By default, ScreeNOT (Donoho et al. 2023) is applied to the source population knowledge graph to select the rank. -#' @param lambda_1 numeric scalar specifying the value of \eqn{\lambda_1} (see Details) -#' @param lambda_2 numeric scalar specifying the value of \eqn{\lambda_2} (see Details) -#' @param step_size numeric scalar specifying the step size for the Newton steps in the numerical optimization algorithm -#' @param control a list of parameters for controlling the stopping criteria for the numerical optimization algorithm. The list may include the following components: -#' \tabular{ll}{ -#' \code{max_iter} \tab integer specifying the maximum number of iterations \cr -#' \code{threshold} \tab numeric scalar specifying a convergence threshold. The algorithm converges when \eqn{|\epsilon_t - \epsilon_{t-1}| < }\code{threshold}, where \eqn{\epsilon_t} denotes the value of the objective function at iteration \eqn{t}. \cr -#' \code{max_value} \tab numeric scalar used to specify the maximum value of the objective function allowed before terminating the algorithm. Specifically, the algorithm will terminate if the value of the objective function exceeds \code{max_value}\eqn{\times \epsilon_0}, where \eqn{\epsilon_0} denotes the value of the objective function at the initial point. This is used to prevent unnecessary computation time after the optimization algorithm diverges. \cr} +#' Estimate a low-rank target matrix using information from one or more source +#' populations with the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method +#' (McGrath et al. 2024). The two source-space penalties can share a coefficient +#' or use separate coefficients. +#' +#' @param Y_target nonempty numeric matrix containing target population data. +#' Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry +#' must be observed. Infinite values are not allowed. +#' @param Y_source numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values. +#' @param r optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source. +#' @param source_weights optional finite nonnegative numeric vector with one +#' weight per source and at least one positive value. Weights correspond to the +#' order of matrices in \code{Y_source}; names are not used for matching. They +#' are normalized to sum to one. \code{NULL} gives equal weights. For a single +#' source, a supplied weight must be a positive scalar and is normalized to one. +#' @param lambda_1 finite nonnegative numeric scalar specifying the common space penalty. Supply either this parameter or both \code{lambda_1_row} and \code{lambda_1_col}, not both modes. +#' @param lambda_2 finite nonnegative numeric scalar for the balance penalty (see Details) +#' @param lambda_1_row optional finite nonnegative numeric scalar for the source-space penalty on \eqn{U} (the SNP side when SNPs are rows). Must be supplied together with \code{lambda_1_col}. +#' @param lambda_1_col optional finite nonnegative numeric scalar for the source-space penalty on \eqn{V} (the phenotype side when phenotypes are columns). Must be supplied together with \code{lambda_1_row}. +#' @param step_size numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm +#' @param control list controlling the numerical optimization: +#' \describe{ +#' \item{\code{max_iter}}{Positive integer giving the maximum number of updates; +#' default \code{100}.} +#' \item{\code{threshold}}{Finite nonnegative absolute objective-change threshold; +#' default \code{0.001}. Starting with the second update, the algorithm stops +#' when \eqn{|L_t-L_{t-1}|} is strictly less than this value. This rule alone does +#' not establish factor stability or a global optimum.} +#' \item{\code{max_value}}{Finite positive multiplier; default \code{10}. +#' Starting with the second update, the algorithm stops if the current objective +#' exceeds this multiplier times the preceding objective (unless the +#' objective-change stopping rule has already been met).}} #' #' @return A list with the following elements: -#' \item{learner_estimate}{matrix containing the LEARNER estimate of the target population knowledge graph} -#' \item{objective_values}{numeric vector containing the values of the objective function at each iteration} -#' \item{convergence_criterion}{integer specifying the criterion that was satisfied for terminating the numerical optimization algorithm. A value of 1 indicates the convergence threshold was satisfied; A value of 2 indicates that the maximum number of iterations was satisfied; A value of 3 indicates that the maximum value of the objective function was satisfied.} +#' \item{learner_estimate}{target signal estimate with the same dimensions as \code{Y_target}, formed from the factors with the lowest objective encountered, including the initialization.} +#' \item{objective_values}{objective values after each update, excluding the initialization. The last value need not correspond to the returned estimate.} +#' \item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} #' \item{r}{rank value used.} #' #' @details #' -#' \strong{Data and notation:} +#' \strong{Data and source spaces:} #' -#' The data consists of a matrix in the target population \eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population \eqn{Y_1 \in \mathbb{R}^{p \times q}}. -#' Let \eqn{\hat{U}_{k} \hat{\Lambda}_{k} \hat{V}_{k}^{\top}} denote the truncated singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. +#' Let \eqn{Y_0} be the \eqn{p \times q} target matrix, and let +#' \eqn{Y_1,\ldots,Y_K} be aligned source matrices of the same dimensions. +#' The target signal is estimated by \eqn{UV^\top}, with +#' \eqn{U \in \mathbb{R}^{p \times r}} and +#' \eqn{V \in \mathbb{R}^{q \times r}}. For each source, the first \eqn{r} +#' left and right singular vectors form bases \eqn{B_{U,k}} and \eqn{B_{V,k}}. +#' The source operators are +#' \deqn{\bar P_U = \sum_{k=1}^K w_k B_{U,k}B_{U,k}^\top, \qquad +#' \bar P_V = \sum_{k=1}^K w_k B_{V,k}B_{V,k}^\top,} +#' where \eqn{w_k \geq 0} and \eqn{\sum_k w_k=1}. +#' These combine source spaces, not the raw source matrices. The averaged +#' operators are generally not orthogonal projectors themselves. +#' Rows and columns must already be aligned by the caller; names are not matched. #' -#' For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference is the target population knowledge graph, \eqn{\Theta_0}. +#' \strong{Objective and penalties:} #' -#' \strong{Estimation:} +#' The numerical algorithm seeks to minimize +#' \deqn{L(U,V) = \rho^{-1}\|\mathcal{P}_{\Omega}(UV^\top-Y_0)\|_F^2 +#' + \lambda_{1,\mathrm{row}}\|(I-\bar P_U)U\|_F^2 +#' + \lambda_{1,\mathrm{col}}\|(I-\bar P_V)V\|_F^2 +#' + \lambda_2\|U^\top U-V^\top V\|_F^2.} +#' Here \eqn{\Omega} contains the observed target entries, +#' \eqn{\mathcal{P}_{\Omega}} keeps residuals at those entries and sets the +#' others to zero, and \eqn{\rho=|\Omega|/(pq)} is the observed fraction. +#' With no missing entries, \eqn{\rho=1}. Source matrices must be complete. #' -#' This method estimates \eqn{\Theta_0} by \eqn{\tilde{U}\tilde{V}^{\top}}, where \eqn{(\tilde{U}, \tilde{V})} is the solution to the following optimization problem -#' \deqn{\mathrm{arg\,min}_{U \in \mathbb{R}^{p \times r}, V \in \mathbb{R}^{q \times r}} \big\{ \| U V^{\top} - Y_0 \|_F^2 + \lambda_1\| \mathcal{P}_{\perp}(\hat{U}_{1})U \|_F^2 + \lambda_1\| \mathcal{P}_{\perp}(\hat{V}_{1})V \|_F^2 + \lambda_2 \| U^{\top} U - V^{\top} V \|_F^2 \big\}} -#' where \eqn{\mathcal{P}_{\perp}(\hat{U}_{1}) = I - \hat{U}_{1}^{\top}\hat{U}_{1}} and \eqn{\mathcal{P}_{\perp}(\hat{V}_{1}) = I - \hat{V}_{1}^{\top}\hat{V}_{1}}. +#' Supply \code{lambda_1} for a common space penalty, or supply both +#' \code{lambda_1_row} and \code{lambda_1_col} and leave \code{lambda_1=NULL}. +#' Mixing the modes or providing only one separate coefficient is an error. +#' Equal separate coefficients reproduce the common-penalty calculation under +#' the same inputs and optimization settings. The balance penalty +#' \code{lambda_2} is required in either mode. +#' The space penalties square the residual from the weighted source operator; +#' they are not weighted sums of separately squared per-source residuals. +#' Use \code{\link{cv.learner}} to select common or separate penalty values. #' -#' This function uses an alternating minimization strategy to solve the optimization problem. That is, this approach updates \eqn{U} by minimizing the objective function (via a gradient descent step) treating \eqn{V} as fixed. Then, \eqn{V} is updated treating \eqn{U} as fixed. These updates of \eqn{U} and \eqn{V} are repeated until convergence. +#' \strong{Initialization and stopping:} +#' +#' If \code{r} is omitted, ScreeNOT selects it from the first source, with a +#' minimum of one. A supplied \code{r} is used directly. The first source is +#' always used to initialize the target factors, including when its weight is +#' zero. Reordering sources with their weights therefore preserves the weighted +#' operators for fixed \code{r}, but can change initialization and fitted results. +#' The initial factors are the first source's retained left and right singular +#' vectors, each multiplied by the square roots of its retained singular values. +#' +#' This function uses scaled gradient descent steps. Both gradients are +#' evaluated at the current \eqn{U} and \eqn{V}, then both matrices are updated. +#' Updates are repeated until a stopping criterion is satisfied. The returned +#' estimate uses the best objective encountered, not necessarily the final +#' iterate. Inspect \code{objective_values} and \code{convergence_criterion}; +#' code 1 only indicates that the stated objective-change rule was met. #' #' @references #' McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. @@ -130,40 +261,64 @@ cv.learner <- function(Y_source, Y_target, r, lambda_1_all, lambda_2_all, #' lambda_1 = 1, lambda_2 = 1, #' step_size = 0.003) #' +#' res_separate <- learner(Y_source = dat_highsim$Y_source, +#' Y_target = dat_highsim$Y_target, +#' lambda_1_row = 10, lambda_1_col = 1, +#' lambda_2 = 1, step_size = 0.003) +#' +#' # Multiple sources with fixed demonstration weights. +#' # Source B is a perturbed copy, not an independent population. +#' set.seed(803) +#' source_A <- dat_highsim$Y_source[1:20, 1:10] +#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), +#' nrow(source_A), ncol(source_A)) +#' res_multi <- learner( +#' Y_source = list(A = source_A, B = source_B), +#' Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, +#' source_weights = c(0.7, 0.3), +#' lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, +#' step_size = 0.0003, control = list(max_iter = 100)) +#' dim(res_multi$learner_estimate) +#' res_multi$convergence_criterion +#' # Short runs demonstrate usage; they need not meet the stopping threshold. +#' #' @export -learner <- function(Y_source, Y_target, r, lambda_1, lambda_2, step_size, - control = list()) { - ## Error catching - if (!identical(dim(Y_source), dim(Y_target))){ - stop('Y_source and Y_target must have the same dimensions') - } - if (any(is.na(Y_source))){ - stop('Y_source cannot have NA values.') - } - if (length(lambda_1) > 1){ - stop('lambda_1 must be a scalar. See the function cv.learner for selecting a suitable lambda_1 value.') - } - if (length(lambda_2) > 1){ - stop('lambda_2 must be a scalar. See the function cv.learner for selecting a suitable lambda_2 value.') +learner <- function(Y_source, Y_target, r, lambda_1 = NULL, lambda_2, step_size, + control = list(), lambda_1_row = NULL, lambda_1_col = NULL, + source_weights = NULL) { + sources <- prepare_sources(Y_source, Y_target, source_weights) + r <- resolve_rank(if (missing(r)) NULL else r, sources$matrices[[1]]) + control <- prepare_control(control, step_size) + # Resolve the original common penalty or the two independent penalties. + separate <- !is.null(lambda_1_row) || !is.null(lambda_1_col) + if (separate){ + if (!is.null(lambda_1)){ + stop('Provide either lambda_1 or both lambda_1_row and lambda_1_col, not both modes.') + } + if (is.null(lambda_1_row) || is.null(lambda_1_col)){ + stop('Both lambda_1_row and lambda_1_col must be provided together.') + } + validate_space_penalty(lambda_1_row, 'lambda_1_row') + validate_space_penalty(lambda_1_col, 'lambda_1_col') + } else { + if (is.null(lambda_1)){ + stop('Provide lambda_1 or both lambda_1_row and lambda_1_col.') + } + validate_space_penalty(lambda_1, 'lambda_1') + lambda_1_row <- lambda_1 + lambda_1_col <- lambda_1 } + validate_space_penalty(lambda_2, 'lambda_2') - if (missing(r)){ - p <- nrow(Y_source); q <- ncol(Y_source) - max_rank <- min(p, q) / 3 - r <- max(ScreeNOT::adaptiveHardThresholding(Y = Y_source, k = max_rank)$r, 1) - } - if (!('max_iter' %in% names(control))){ - control$max_iter <- 100 - } - if (!('threshold' %in% names(control))){ - control$threshold <- 0.001 - } - if (!('max_value' %in% names(control))){ - control$max_value <- 10 - } - - result <- learner_cpp(Y_source, Y_target, r, lambda_1, lambda_2, step_size, control$max_iter, control$threshold, control$max_value) + result <- learner_cpp(sources$matrices, Y_target, sources$weights, r, + lambda_1_row, lambda_1_col, lambda_2, step_size, + control$max_iter, control$threshold, control$max_value) return(result) } - +validate_space_penalty <- function(value, name) { + if (!is.numeric(value) || length(value) != 1L || + !is.finite(value) || value < 0){ + stop(name, ' must be a finite nonnegative numeric scalar.', call. = FALSE) + } +} diff --git a/inst/examples/multiple-sources.R b/inst/examples/multiple-sources.R new file mode 100644 index 0000000..a3a39c7 --- /dev/null +++ b/inst/examples/multiple-sources.R @@ -0,0 +1,43 @@ +# Demonstration only: these are simulated matrices, not actual population data. +# The second source is constructed here to illustrate the multiple-source API. +set.seed(803) +source_A <- learner::dat_highsim$Y_source +source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), + nrow(source_A), ncol(source_A)) +target <- learner::dat_highsim$Y_target +sources <- list(source_A, source_B) +weights <- c(0.7, 0.3) + +# Tune the U-space, V-space, and balance penalties separately. +set.seed(815) +cv_result <- learner::cv.learner( + Y_source = sources, Y_target = target, r = 3, + source_weights = weights, + lambda_1_row_all = c(1, 10), + lambda_1_col_all = c(1, 3), + lambda_2_all = c(0.1, 1), + step_size = 0.003, n_folds = 3, + control = list(max_iter = 100) +) +print(c(row = cv_result$lambda_1_row_min, + col = cv_result$lambda_1_col_min, + balance = cv_result$lambda_2_min)) +print(dim(cv_result$mse_all)) # row grid x column grid x balance grid + +# Refit with the selected penalties using all observed target entries. +fit <- learner::learner( + Y_source = sources, Y_target = target, r = cv_result$r, + source_weights = weights, + lambda_1_row = cv_result$lambda_1_row_min, + lambda_1_col = cv_result$lambda_1_col_min, + lambda_2 = cv_result$lambda_2_min, + step_size = 0.003, control = list(max_iter = 100) +) +print(fit$learner_estimate[1:5, 1:5]) +print(fit$convergence_criterion) +# 1: objective-change threshold; 2: iteration limit; 3: excessive growth/breakdown. +# This short example tests the workflow, not scientific performance or convergence. + +# D-LEARNER also accepts multiple sources with the same weights. +direct_fit <- learner::dlearner(sources, target, r = 3, source_weights = weights) +print(dim(direct_fit$dlearner_estimate)) diff --git a/man/cv.learner.Rd b/man/cv.learner.Rd index 8c0bd0c..8cea0ab 100644 --- a/man/cv.learner.Rd +++ b/man/cv.learner.Rd @@ -8,45 +8,88 @@ cv.learner( Y_source, Y_target, r, - lambda_1_all, + lambda_1_all = NULL, lambda_2_all, step_size, n_folds = 4, n_cores = 1, - control = list() + control = list(), + lambda_1_row_all = NULL, + lambda_1_col_all = NULL, + source_weights = NULL ) } \arguments{ -\item{Y_source}{matrix containing the source population data, as in \code{\link{learner}}} +\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} -\item{Y_target}{matrix containing the target population data, as in \code{\link{learner}}} +\item{Y_target}{nonempty numeric matrix containing target population data. +Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry +must be observed. Infinite values are not allowed.} -\item{r}{(optional) integer specifying the rank of the knowledge graphs, as in \code{\link{learner}}} +\item{r}{optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source.} -\item{lambda_1_all}{vector of numerics specifying the candidate values of \eqn{\lambda_1} (see Details)} +\item{lambda_1_all}{nonempty vector of finite nonnegative candidate common penalties. Use this or both separate grids, not both modes.} -\item{lambda_2_all}{vector of numerics specifying the candidate values of \eqn{\lambda_2} (see Details)} +\item{lambda_2_all}{nonempty vector of finite nonnegative candidate balance penalties.} -\item{step_size}{numeric scalar specifying the step size for the Newton steps in the numerical optimization algorithm, as in \code{\link{learner}}} +\item{step_size}{numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm, as in \code{\link{learner}}} -\item{n_folds}{an integer specify the number of cross-validation folds. The default is \code{4}.} +\item{n_folds}{integer from 2 to the number of observed target entries. The default is \code{4}.} -\item{n_cores}{an integer specifying the number of CPU cores in OpenMP parallelization. Parallelization is performed across the different candidate \eqn{(\lambda_1, \lambda_2)} pairs. The default is \code{1}, i.e., no parallelization.} +\item{n_cores}{positive integer specifying the requested number of OpenMP +threads. Candidate penalty combinations are evaluated in parallel when the +package is built with OpenMP support; otherwise they run sequentially. +The default is \code{1}.} \item{control}{a list of parameters for controlling the stopping criteria for the numerical optimization algorithm, as in \code{\link{learner}}.} + +\item{lambda_1_row_all}{nonempty vector of finite nonnegative candidate penalties on U; requires \code{lambda_1_col_all}.} + +\item{lambda_1_col_all}{nonempty vector of finite nonnegative candidate penalties on V; requires \code{lambda_1_row_all}.} + +\item{source_weights}{optional finite nonnegative numeric vector with one +weight per source and at least one positive value. Weights correspond to the +order of matrices in \code{Y_source}; names are not used for matching. They +are normalized to sum to one. \code{NULL} gives equal weights. For a single +source, a supplied weight must be a positive scalar and is normalized to one.} } \value{ A list with the following elements: -\item{lambda_1_min}{value of \eqn{\lambda_1} with the smallest MSE} -\item{lambda_2_min}{value of \eqn{\lambda_2} with the smallest MSE} -\item{mse_all}{matrix containing MSE value for each \eqn{(\lambda_1, \lambda_2)} pair. The rows correspond to the \eqn{\lambda_1} values, and the columns correspond to the \eqn{\lambda_2} values.} +\item{lambda_1_min}{selected common penalty; \code{NA} in separate mode.} +\item{lambda_1_row_min, lambda_1_col_min}{selected separate penalties, returned only in separate mode.} +\item{lambda_2_min}{balance penalty in the combination with the smallest held-out squared-error sum.} +\item{mse_all}{held-out squared-error sums, retaining the historical scale despite the name. Common mode returns a matrix indexed by \code{lambda_1_all} and \code{lambda_2_all}. Separate mode returns a three-dimensional array indexed by \code{lambda_1_row_all}, \code{lambda_1_col_all}, and \code{lambda_2_all}, in that order.} \item{r}{rank value used.} } \description{ -This function performs k-fold cross-validation to select the nuisance parameters \eqn{(\lambda_1, \lambda_2)} for \code{\link{learner}}. +Select common or separate source-space penalties and a balance penalty for +\code{\link{learner}} by k-fold cross-validation on observed target entries. +One source matrix or a list of source matrices can be supplied. } \details{ -Given sets of candidate values of \eqn{\lambda_1} and \eqn{\lambda_2}, this function performs k-fold cross-validation to select the pair \eqn{(\lambda_1, \lambda_2)} with the smallest held out error. This function randomly partitions the entries of \code{Y_target} into \eqn{k} (approximately) equally sized subsamples. The training data sets are obtained by removing one of the \eqn{k} subsamples and the corresponding test data sets are based on the held out subsamples. The \code{\link{learner}} function is applied to each training data set. The held out error is computed by the mean squared error comparing the entries in the test data sets with those imputed from the LEARNER estimates. See McGrath et al. (2024) for further details. +Each observed target entry is held out once. The same folds are used for all +candidates. Dividing \code{mse_all} by the number of observed target entries +gives the mean squared error without changing the selected candidate. Ties +select the first minimum in R array order. Existing missing entries are +excluded from validation. Rank, source spaces, source weights, and the +first-source initialization are fixed across folds. The function tunes +penalties, not rank or source weights. Call \code{set.seed()} before +\code{cv.learner()} to reproduce the fold allocation. +For independent penalties, supply both separate grids and \code{lambda_2_all}. +Candidates with non-finite errors are excluded with a warning; if all fail, +the function stops. Finite fits that reach the iteration limit are retained, +so selecting a candidate does not certify convergence of its fold fits. +Assess the optimization settings as well as the final refit. + +The return value contains scores and selected parameters, not a final fitted +target matrix. Refit with \code{learner()} using all observed target entries +and the selected penalties, while retaining the same sources, weights, and rank. + +Observed target entries are randomly partitioned into approximately equal +folds. Each fold is set to missing in the training matrix, and the fitted +matrix is used to predict the held-out entries. Errors are summed across +folds for each candidate pair (common penalties) or triple (separate +penalties). Source entries are not held out. See McGrath et al. (2024). } \examples{ res <- cv.learner(Y_source = dat_highsim$Y_source, @@ -55,7 +98,35 @@ res <- cv.learner(Y_source = dat_highsim$Y_source, lambda_2_all = c(1, 10, 100), step_size = 0.003) +# A small simulated example: source B is a perturbation of source A, +# not an independent population. Weights are fixed demonstration values. +set.seed(803) +source_A <- dat_highsim$Y_source[1:20, 1:10] +source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), + nrow(source_A), ncol(source_A)) +sources <- list(A = source_A, B = source_B) +target <- dat_highsim$Y_target[1:20, 1:10] +weights <- c(0.7, 0.3) + +set.seed(815) +res_separate <- cv.learner( + Y_source = sources, Y_target = target, r = 2, + source_weights = weights, + lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), + lambda_2_all = c(0.1, 1), step_size = 0.0003, + n_folds = 2, control = list(max_iter = 100)) +dim(res_separate$mse_all) # row grid x column grid x balance grid +fit <- learner( + Y_source = sources, Y_target = target, r = res_separate$r, + source_weights = weights, + lambda_1_row = res_separate$lambda_1_row_min, + lambda_1_col = res_separate$lambda_1_col_min, + lambda_2 = res_separate$lambda_2_min, step_size = 0.0003, + control = list(max_iter = 100)) +fit$convergence_criterion +# This short example demonstrates the workflow; check convergence before +# interpreting results or comparing statistical performance. } \references{ diff --git a/man/dlearner.Rd b/man/dlearner.Rd index f1c4563..a0be094 100644 --- a/man/dlearner.Rd +++ b/man/dlearner.Rd @@ -2,16 +2,23 @@ % Please edit documentation in R/dlearner.R \name{dlearner} \alias{dlearner} -\title{Latent space-based transfer learning} +\title{Direct projection transfer learning} \usage{ -dlearner(Y_source, Y_target, r) +dlearner(Y_source, Y_target, r, source_weights = NULL) } \arguments{ -\item{Y_source}{matrix containing the source population data} +\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} -\item{Y_target}{matrix containing the target population data} +\item{Y_target}{nonempty numeric target matrix with only finite values. +Unlike \code{learner()}, this function does not allow missing target entries.} -\item{r}{(optional) integer specifying the rank of the knowledge graphs. By default, ScreeNOT (Donoho et al. 2023) is applied to the source population knowledge graph to select the rank.} +\item{r}{optional common rank of the retained source spaces. If omitted, ScreeNOT selects it from the first source.} + +\item{source_weights}{optional finite nonnegative numeric vector with one +weight per source and at least one positive value. Weights correspond to the +order of matrices in \code{Y_source}; names are not used for matching. They +are normalized to sum to one. \code{NULL} gives equal weights. For a single +source, a supplied weight must be a positive scalar and is normalized to one.} } \value{ A list with the following components: @@ -19,25 +26,45 @@ A list with the following components: \item{r}{rank value used.} } \description{ -This function applies the Direct project LatEnt spAce-based tRaNsfer lEaRning (D-LEARNER) method (McGrath et al. 2024) to leverage data from a source population to improve -estimation of a low rank matrix in an underrepresented target population. +Estimate a target signal matrix by applying source-space operators directly +to the target data, using the D-LEARNER method (McGrath et al. 2024). +One source matrix or a list of source matrices can be supplied. } \details{ -\strong{Data and notation:} - -The data consists of a matrix in the target population \eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population \eqn{Y_1 \in \mathbb{R}^{p \times q}}. -Let \eqn{\hat{U}_{k} \hat{\Lambda}_{k} \hat{V}_{k}^{\top}} denote the truncated singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. - -For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference is the target population knowledge graph, \eqn{\Theta_0}. +Each source has the same dimensions as the target matrix \eqn{Y_0}. +Let \eqn{B_{U,k}} and \eqn{B_{V,k}} contain the first \eqn{r} left and +right singular vectors of source \eqn{k}. The estimate is +\deqn{\widehat\Theta_0 = \bar P_U Y_0 \bar P_V, \qquad +\bar P_U=\sum_k w_k B_{U,k}B_{U,k}^\top, \qquad +\bar P_V=\sum_k w_k B_{V,k}B_{V,k}^\top.} +The nonnegative weights are normalized to sum to one and default to equal +values. With one source this reduces to projection onto its retained left +and right singular spaces. With multiple sources, the averaged operators +need not be idempotent, and the estimate can have rank larger than \code{r}. +This is not an average of the raw source matrices or of separately computed +single-source D-LEARNER estimates. -\strong{Estimation:} - -This method estimates \eqn{\Theta_0} by \eqn{\hat{U}_{1}^{\top}\hat{U}_{1} Y_0 \hat{V}_{1}^{\top}\hat{V}_{1}}. +D-LEARNER does not iteratively fit target factors and has no space or balance +penalty parameters. All inputs must have aligned rows and columns and no +missing values. Names are not matched. If \code{r} is omitted, it is selected +from the first source, including when that source has zero weight. +Output dimension names are taken from the first source. } \examples{ res <- dlearner(Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target) +# A second simulated source, made by perturbing the first. +set.seed(803) +source_A <- dat_highsim$Y_source[1:20, 1:10] +source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), + nrow(source_A), ncol(source_A)) +res_multi <- dlearner( + Y_source = list(A = source_A, B = source_B), + Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, + source_weights = c(0.7, 0.3)) +dim(res_multi$dlearner_estimate) + } \references{ Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. diff --git a/man/learner.Rd b/man/learner.Rd index e1bdce4..fc8e9be 100644 --- a/man/learner.Rd +++ b/man/learner.Rd @@ -4,53 +4,125 @@ \alias{learner} \title{Latent space-based transfer learning} \usage{ -learner(Y_source, Y_target, r, lambda_1, lambda_2, step_size, control = list()) +learner( + Y_source, + Y_target, + r, + lambda_1 = NULL, + lambda_2, + step_size, + control = list(), + lambda_1_row = NULL, + lambda_1_col = NULL, + source_weights = NULL +) } \arguments{ -\item{Y_source}{matrix containing the source population data} +\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} -\item{Y_target}{matrix containing the target population data} +\item{Y_target}{nonempty numeric matrix containing target population data. +Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry +must be observed. Infinite values are not allowed.} -\item{r}{(optional) integer specifying the rank of the knowledge graphs. By default, ScreeNOT (Donoho et al. 2023) is applied to the source population knowledge graph to select the rank.} +\item{r}{optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source.} -\item{lambda_1}{numeric scalar specifying the value of \eqn{\lambda_1} (see Details)} +\item{lambda_1}{finite nonnegative numeric scalar specifying the common space penalty. Supply either this parameter or both \code{lambda_1_row} and \code{lambda_1_col}, not both modes.} -\item{lambda_2}{numeric scalar specifying the value of \eqn{\lambda_2} (see Details)} +\item{lambda_2}{finite nonnegative numeric scalar for the balance penalty (see Details)} -\item{step_size}{numeric scalar specifying the step size for the Newton steps in the numerical optimization algorithm} +\item{step_size}{numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm} -\item{control}{a list of parameters for controlling the stopping criteria for the numerical optimization algorithm. The list may include the following components: -\tabular{ll}{ -\code{max_iter} \tab integer specifying the maximum number of iterations \cr -\code{threshold} \tab numeric scalar specifying a convergence threshold. The algorithm converges when \eqn{|\epsilon_t - \epsilon_{t-1}| < }\code{threshold}, where \eqn{\epsilon_t} denotes the value of the objective function at iteration \eqn{t}. \cr -\code{max_value} \tab numeric scalar used to specify the maximum value of the objective function allowed before terminating the algorithm. Specifically, the algorithm will terminate if the value of the objective function exceeds \code{max_value}\eqn{\times \epsilon_0}, where \eqn{\epsilon_0} denotes the value of the objective function at the initial point. This is used to prevent unnecessary computation time after the optimization algorithm diverges. \cr}} +\item{control}{list controlling the numerical optimization: +\describe{ +\item{\code{max_iter}}{Positive integer giving the maximum number of updates; +default \code{100}.} +\item{\code{threshold}}{Finite nonnegative absolute objective-change threshold; +default \code{0.001}. Starting with the second update, the algorithm stops +when \eqn{|L_t-L_{t-1}|} is strictly less than this value. This rule alone does +not establish factor stability or a global optimum.} +\item{\code{max_value}}{Finite positive multiplier; default \code{10}. +Starting with the second update, the algorithm stops if the current objective +exceeds this multiplier times the preceding objective (unless the +objective-change stopping rule has already been met).}}} + +\item{lambda_1_row}{optional finite nonnegative numeric scalar for the source-space penalty on \eqn{U} (the SNP side when SNPs are rows). Must be supplied together with \code{lambda_1_col}.} + +\item{lambda_1_col}{optional finite nonnegative numeric scalar for the source-space penalty on \eqn{V} (the phenotype side when phenotypes are columns). Must be supplied together with \code{lambda_1_row}.} + +\item{source_weights}{optional finite nonnegative numeric vector with one +weight per source and at least one positive value. Weights correspond to the +order of matrices in \code{Y_source}; names are not used for matching. They +are normalized to sum to one. \code{NULL} gives equal weights. For a single +source, a supplied weight must be a positive scalar and is normalized to one.} } \value{ A list with the following elements: -\item{learner_estimate}{matrix containing the LEARNER estimate of the target population knowledge graph} -\item{objective_values}{numeric vector containing the values of the objective function at each iteration} -\item{convergence_criterion}{integer specifying the criterion that was satisfied for terminating the numerical optimization algorithm. A value of 1 indicates the convergence threshold was satisfied; A value of 2 indicates that the maximum number of iterations was satisfied; A value of 3 indicates that the maximum value of the objective function was satisfied.} +\item{learner_estimate}{target signal estimate with the same dimensions as \code{Y_target}, formed from the factors with the lowest objective encountered, including the initialization.} +\item{objective_values}{objective values after each update, excluding the initialization. The last value need not correspond to the returned estimate.} +\item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} \item{r}{rank value used.} } \description{ -This function applies the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method (McGrath et al. 2024) to leverage data from a source population to improve -estimation of a low rank matrix in an underrepresented target population. +Estimate a low-rank target matrix using information from one or more source +populations with the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method +(McGrath et al. 2024). The two source-space penalties can share a coefficient +or use separate coefficients. } \details{ -\strong{Data and notation:} +\strong{Data and source spaces:} + +Let \eqn{Y_0} be the \eqn{p \times q} target matrix, and let +\eqn{Y_1,\ldots,Y_K} be aligned source matrices of the same dimensions. +The target signal is estimated by \eqn{UV^\top}, with +\eqn{U \in \mathbb{R}^{p \times r}} and +\eqn{V \in \mathbb{R}^{q \times r}}. For each source, the first \eqn{r} +left and right singular vectors form bases \eqn{B_{U,k}} and \eqn{B_{V,k}}. +The source operators are +\deqn{\bar P_U = \sum_{k=1}^K w_k B_{U,k}B_{U,k}^\top, \qquad + \bar P_V = \sum_{k=1}^K w_k B_{V,k}B_{V,k}^\top,} +where \eqn{w_k \geq 0} and \eqn{\sum_k w_k=1}. +These combine source spaces, not the raw source matrices. The averaged +operators are generally not orthogonal projectors themselves. +Rows and columns must already be aligned by the caller; names are not matched. -The data consists of a matrix in the target population \eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population \eqn{Y_1 \in \mathbb{R}^{p \times q}}. -Let \eqn{\hat{U}_{k} \hat{\Lambda}_{k} \hat{V}_{k}^{\top}} denote the truncated singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. +\strong{Objective and penalties:} -For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference is the target population knowledge graph, \eqn{\Theta_0}. +The numerical algorithm seeks to minimize +\deqn{L(U,V) = \rho^{-1}\|\mathcal{P}_{\Omega}(UV^\top-Y_0)\|_F^2 + + \lambda_{1,\mathrm{row}}\|(I-\bar P_U)U\|_F^2 + + \lambda_{1,\mathrm{col}}\|(I-\bar P_V)V\|_F^2 + + \lambda_2\|U^\top U-V^\top V\|_F^2.} +Here \eqn{\Omega} contains the observed target entries, +\eqn{\mathcal{P}_{\Omega}} keeps residuals at those entries and sets the +others to zero, and \eqn{\rho=|\Omega|/(pq)} is the observed fraction. +With no missing entries, \eqn{\rho=1}. Source matrices must be complete. -\strong{Estimation:} +Supply \code{lambda_1} for a common space penalty, or supply both +\code{lambda_1_row} and \code{lambda_1_col} and leave \code{lambda_1=NULL}. +Mixing the modes or providing only one separate coefficient is an error. +Equal separate coefficients reproduce the common-penalty calculation under +the same inputs and optimization settings. The balance penalty +\code{lambda_2} is required in either mode. +The space penalties square the residual from the weighted source operator; +they are not weighted sums of separately squared per-source residuals. +Use \code{\link{cv.learner}} to select common or separate penalty values. -This method estimates \eqn{\Theta_0} by \eqn{\tilde{U}\tilde{V}^{\top}}, where \eqn{(\tilde{U}, \tilde{V})} is the solution to the following optimization problem -\deqn{\mathrm{arg\,min}_{U \in \mathbb{R}^{p \times r}, V \in \mathbb{R}^{q \times r}} \big\{ \| U V^{\top} - Y_0 \|_F^2 + \lambda_1\| \mathcal{P}_{\perp}(\hat{U}_{1})U \|_F^2 + \lambda_1\| \mathcal{P}_{\perp}(\hat{V}_{1})V \|_F^2 + \lambda_2 \| U^{\top} U - V^{\top} V \|_F^2 \big\}} -where \eqn{\mathcal{P}_{\perp}(\hat{U}_{1}) = I - \hat{U}_{1}^{\top}\hat{U}_{1}} and \eqn{\mathcal{P}_{\perp}(\hat{V}_{1}) = I - \hat{V}_{1}^{\top}\hat{V}_{1}}. +\strong{Initialization and stopping:} -This function uses an alternating minimization strategy to solve the optimization problem. That is, this approach updates \eqn{U} by minimizing the objective function (via a gradient descent step) treating \eqn{V} as fixed. Then, \eqn{V} is updated treating \eqn{U} as fixed. These updates of \eqn{U} and \eqn{V} are repeated until convergence. +If \code{r} is omitted, ScreeNOT selects it from the first source, with a +minimum of one. A supplied \code{r} is used directly. The first source is +always used to initialize the target factors, including when its weight is +zero. Reordering sources with their weights therefore preserves the weighted +operators for fixed \code{r}, but can change initialization and fitted results. +The initial factors are the first source's retained left and right singular +vectors, each multiplied by the square roots of its retained singular values. + +This function uses scaled gradient descent steps. Both gradients are +evaluated at the current \eqn{U} and \eqn{V}, then both matrices are updated. +Updates are repeated until a stopping criterion is satisfied. The returned +estimate uses the best objective encountered, not necessarily the final +iterate. Inspect \code{objective_values} and \code{convergence_criterion}; +code 1 only indicates that the stated objective-change rule was met. } \examples{ res <- learner(Y_source = dat_highsim$Y_source, @@ -58,6 +130,27 @@ res <- learner(Y_source = dat_highsim$Y_source, lambda_1 = 1, lambda_2 = 1, step_size = 0.003) +res_separate <- learner(Y_source = dat_highsim$Y_source, + Y_target = dat_highsim$Y_target, + lambda_1_row = 10, lambda_1_col = 1, + lambda_2 = 1, step_size = 0.003) + +# Multiple sources with fixed demonstration weights. +# Source B is a perturbed copy, not an independent population. +set.seed(803) +source_A <- dat_highsim$Y_source[1:20, 1:10] +source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), + nrow(source_A), ncol(source_A)) +res_multi <- learner( + Y_source = list(A = source_A, B = source_B), + Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, + source_weights = c(0.7, 0.3), + lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, + step_size = 0.0003, control = list(max_iter = 100)) +dim(res_multi$learner_estimate) +res_multi$convergence_criterion +# Short runs demonstrate usage; they need not meet the stopping threshold. + } \references{ McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index f39d9a3..0574cb1 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -12,43 +12,46 @@ Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif // learner_cpp -List learner_cpp(const Eigen::MatrixXd& Y_source, const Eigen::MatrixXd& Y_target, int r, double lambda1, double lambda2, double step_size, int max_iter, double threshold, double max_value); -RcppExport SEXP _learner_learner_cpp(SEXP Y_sourceSEXP, SEXP Y_targetSEXP, SEXP rSEXP, SEXP lambda1SEXP, SEXP lambda2SEXP, SEXP step_sizeSEXP, SEXP max_iterSEXP, SEXP thresholdSEXP, SEXP max_valueSEXP) { +List learner_cpp(const Rcpp::List& Y_source, const Eigen::MatrixXd& Y_target, const std::vector& source_weights, int r, double lambda1_row, double lambda1_col, double lambda2, double step_size, int max_iter, double threshold, double max_value); +RcppExport SEXP _learner_learner_cpp(SEXP Y_sourceSEXP, SEXP Y_targetSEXP, SEXP source_weightsSEXP, SEXP rSEXP, SEXP lambda1_rowSEXP, SEXP lambda1_colSEXP, SEXP lambda2SEXP, SEXP step_sizeSEXP, SEXP max_iterSEXP, SEXP thresholdSEXP, SEXP max_valueSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const Eigen::MatrixXd& >::type Y_source(Y_sourceSEXP); + Rcpp::traits::input_parameter< const Rcpp::List& >::type Y_source(Y_sourceSEXP); Rcpp::traits::input_parameter< const Eigen::MatrixXd& >::type Y_target(Y_targetSEXP); + Rcpp::traits::input_parameter< const std::vector& >::type source_weights(source_weightsSEXP); Rcpp::traits::input_parameter< int >::type r(rSEXP); - Rcpp::traits::input_parameter< double >::type lambda1(lambda1SEXP); + Rcpp::traits::input_parameter< double >::type lambda1_row(lambda1_rowSEXP); + Rcpp::traits::input_parameter< double >::type lambda1_col(lambda1_colSEXP); Rcpp::traits::input_parameter< double >::type lambda2(lambda2SEXP); Rcpp::traits::input_parameter< double >::type step_size(step_sizeSEXP); Rcpp::traits::input_parameter< int >::type max_iter(max_iterSEXP); Rcpp::traits::input_parameter< double >::type threshold(thresholdSEXP); Rcpp::traits::input_parameter< double >::type max_value(max_valueSEXP); - rcpp_result_gen = Rcpp::wrap(learner_cpp(Y_source, Y_target, r, lambda1, lambda2, step_size, max_iter, threshold, max_value)); + rcpp_result_gen = Rcpp::wrap(learner_cpp(Y_source, Y_target, source_weights, r, lambda1_row, lambda1_col, lambda2, step_size, max_iter, threshold, max_value)); return rcpp_result_gen; END_RCPP } // cv_learner_cpp -List cv_learner_cpp(const Eigen::MatrixXd& Y_source, const Eigen::MatrixXd& Y_target, const std::vector& lambda1_all, const std::vector& lambda2_all, double step_size, int n_folds, int max_iter, double threshold, int n_cores, int r, double max_value, const std::vector>& index_set); -RcppExport SEXP _learner_cv_learner_cpp(SEXP Y_sourceSEXP, SEXP Y_targetSEXP, SEXP lambda1_allSEXP, SEXP lambda2_allSEXP, SEXP step_sizeSEXP, SEXP n_foldsSEXP, SEXP max_iterSEXP, SEXP thresholdSEXP, SEXP n_coresSEXP, SEXP rSEXP, SEXP max_valueSEXP, SEXP index_setSEXP) { +std::vector cv_learner_cpp(const Rcpp::List& Y_source, const Eigen::MatrixXd& Y_target, const std::vector& source_weights, const std::vector& row_grid, const std::vector& col_grid, const std::vector& balance_grid, double step_size, int max_iter, double threshold, int n_cores, int r, double max_value, const std::vector>& index_set); +RcppExport SEXP _learner_cv_learner_cpp(SEXP Y_sourceSEXP, SEXP Y_targetSEXP, SEXP source_weightsSEXP, SEXP row_gridSEXP, SEXP col_gridSEXP, SEXP balance_gridSEXP, SEXP step_sizeSEXP, SEXP max_iterSEXP, SEXP thresholdSEXP, SEXP n_coresSEXP, SEXP rSEXP, SEXP max_valueSEXP, SEXP index_setSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; - Rcpp::traits::input_parameter< const Eigen::MatrixXd& >::type Y_source(Y_sourceSEXP); + Rcpp::traits::input_parameter< const Rcpp::List& >::type Y_source(Y_sourceSEXP); Rcpp::traits::input_parameter< const Eigen::MatrixXd& >::type Y_target(Y_targetSEXP); - Rcpp::traits::input_parameter< const std::vector& >::type lambda1_all(lambda1_allSEXP); - Rcpp::traits::input_parameter< const std::vector& >::type lambda2_all(lambda2_allSEXP); + Rcpp::traits::input_parameter< const std::vector& >::type source_weights(source_weightsSEXP); + Rcpp::traits::input_parameter< const std::vector& >::type row_grid(row_gridSEXP); + Rcpp::traits::input_parameter< const std::vector& >::type col_grid(col_gridSEXP); + Rcpp::traits::input_parameter< const std::vector& >::type balance_grid(balance_gridSEXP); Rcpp::traits::input_parameter< double >::type step_size(step_sizeSEXP); - Rcpp::traits::input_parameter< int >::type n_folds(n_foldsSEXP); Rcpp::traits::input_parameter< int >::type max_iter(max_iterSEXP); Rcpp::traits::input_parameter< double >::type threshold(thresholdSEXP); Rcpp::traits::input_parameter< int >::type n_cores(n_coresSEXP); Rcpp::traits::input_parameter< int >::type r(rSEXP); Rcpp::traits::input_parameter< double >::type max_value(max_valueSEXP); Rcpp::traits::input_parameter< const std::vector>& >::type index_set(index_setSEXP); - rcpp_result_gen = Rcpp::wrap(cv_learner_cpp(Y_source, Y_target, lambda1_all, lambda2_all, step_size, n_folds, max_iter, threshold, n_cores, r, max_value, index_set)); + rcpp_result_gen = Rcpp::wrap(cv_learner_cpp(Y_source, Y_target, source_weights, row_grid, col_grid, balance_grid, step_size, max_iter, threshold, n_cores, r, max_value, index_set)); return rcpp_result_gen; END_RCPP } @@ -64,8 +67,8 @@ END_RCPP } static const R_CallMethodDef CallEntries[] = { - {"_learner_learner_cpp", (DL_FUNC) &_learner_learner_cpp, 9}, - {"_learner_cv_learner_cpp", (DL_FUNC) &_learner_cv_learner_cpp, 12}, + {"_learner_learner_cpp", (DL_FUNC) &_learner_learner_cpp, 11}, + {"_learner_cv_learner_cpp", (DL_FUNC) &_learner_cv_learner_cpp, 13}, {"_learner_omp_max_threads", (DL_FUNC) &_learner_omp_max_threads, 0}, {NULL, NULL, 0} }; diff --git a/src/kernels.cpp b/src/kernels.cpp index dbf2ed3..f6d09b0 100644 --- a/src/kernels.cpp +++ b/src/kernels.cpp @@ -12,31 +12,56 @@ using namespace Rcpp; using namespace Eigen; -//-------------------------------------------------------------- -// Internal worker function: computes the learner estimate - thread safe for OMP -List learner_worker(const Eigen::MatrixXd &Y_source, - const Eigen::MatrixXd &Y_target, - int r, double lambda1, double lambda2, - double step_size, int max_iter, double threshold, - double max_value) { - - Eigen::BDCSVD svd(Y_source, Eigen::ComputeThinU | Eigen::ComputeThinV); - Eigen::MatrixXd U_full = svd.matrixU(); - Eigen::MatrixXd V_full = svd.matrixV(); - int r_use = std::min(r, static_cast(U_full.cols())); - Eigen::MatrixXd U_trunc = U_full.leftCols(r_use); - Eigen::MatrixXd V_trunc = V_full.leftCols(r_use); - Eigen::VectorXd singular_vals = svd.singularValues().head(r_use); - - // Initialize as in R: U_init = U_trunc * sqrt(D), V_init = V_trunc * sqrt(D) - Eigen::MatrixXd sqrtD = singular_vals.array().sqrt().matrix().asDiagonal(); - Eigen::MatrixXd U = U_trunc * sqrtD; - Eigen::MatrixXd V = V_trunc * sqrtD; - - // Precompute reusable matrices - Eigen::MatrixXd U_trunc_T = U_trunc.transpose(); - Eigen::MatrixXd V_trunc_T = V_trunc.transpose(); +// Source decompositions are prepared once on the R thread and shared read-only. +struct SourceSpaces { + std::vector u, v; + std::vector weights; + Eigen::MatrixXd initial_u, initial_v; +}; + +SourceSpaces prepare_spaces(const Rcpp::List &sources, + const std::vector &weights, int r) { + SourceSpaces spaces; + spaces.weights = weights; + for (R_xlen_t k = 0; k < sources.size(); ++k) { + Eigen::MatrixXd source = Rcpp::as(sources[k]); + Eigen::BDCSVD svd(source, Eigen::ComputeThinU | Eigen::ComputeThinV); + spaces.u.push_back(svd.matrixU().leftCols(r)); + spaces.v.push_back(svd.matrixV().leftCols(r)); + if (k == 0) { + Eigen::MatrixXd root = svd.singularValues().head(r).array().sqrt().matrix().asDiagonal(); + spaces.initial_u = spaces.u[0] * root; + spaces.initial_v = spaces.v[0] * root; + } + } + return spaces; +} + +// Apply I - sum(w_k B_k B_k') without allocating a p-by-p projector. +Eigen::MatrixXd space_residual(const std::vector &bases, + const std::vector &weights, + const Eigen::MatrixXd &value) { + Eigen::MatrixXd projected = Eigen::MatrixXd::Zero(value.rows(), value.cols()); + for (size_t k = 0; k < bases.size(); ++k) { + projected.noalias() += weights[k] * (bases[k] * (bases[k].transpose() * value)); + } + return value - projected; +} +// Workers use no R objects or allocations, including inside the OpenMP loop. +struct FitResult { + Eigen::MatrixXd estimate; + std::vector objective; + int criterion; +}; + +FitResult learner_worker(const SourceSpaces &spaces, + const Eigen::MatrixXd &Y_target, + double lambda1_row, double lambda1_col, double lambda2, + double step_size, int max_iter, double threshold, + double max_value) { + Eigen::MatrixXd U = spaces.initial_u; + Eigen::MatrixXd V = spaces.initial_v; double perc_nonmissing = 1.0 - (static_cast((Y_target.array().isNaN()).count()) / Y_target.size()); bool missing = Y_target.hasNaN(); @@ -47,8 +72,8 @@ List learner_worker(const Eigen::MatrixXd &Y_source, diff = diff.array().isNaN().select(Eigen::MatrixXd::Zero(diff.rows(), diff.cols()), diff); } obj_init = diff.squaredNorm() / perc_nonmissing + - lambda1 * (U - U_trunc * (U_trunc_T * U)).squaredNorm() + - lambda1 * (V - V_trunc * (V_trunc_T * V)).squaredNorm() + + lambda1_row * space_residual(spaces.u, spaces.weights, U).squaredNorm() + + lambda1_col * space_residual(spaces.v, spaces.weights, V).squaredNorm() + lambda2 * (U.transpose() * U - V.transpose() * V).squaredNorm(); } @@ -71,12 +96,20 @@ List learner_worker(const Eigen::MatrixXd &Y_source, adjusted_theta = adjusted_theta.array().isNaN().select(temp, adjusted_theta); } + Eigen::MatrixXd residual_u = space_residual(spaces.u, spaces.weights, U); + Eigen::MatrixXd residual_v = space_residual(spaces.v, spaces.weights, V); + // A weighted average of projectors is generally not idempotent. + // The derivative of ||A U||^2 is 2 A' A U; here A is symmetric. + Eigen::MatrixXd penalty_u = spaces.u.size() == 1 ? residual_u : + space_residual(spaces.u, spaces.weights, residual_u); + Eigen::MatrixXd penalty_v = spaces.v.size() == 1 ? residual_v : + space_residual(spaces.v, spaces.weights, residual_v); Eigen::MatrixXd grad_U = (2.0 / perc_nonmissing) * (U * V_tilde - adjusted_theta * V) - + lambda1 * 2 * (U - U_trunc * (U_trunc_T * U)) - + lambda2 * 4 * U * (U_tilde - V_tilde); + + lambda1_row * 2 * penalty_u + + lambda2 * 4 * U * (U_tilde - V_tilde); Eigen::MatrixXd grad_V = (2.0 / perc_nonmissing) * (V * U_tilde - adjusted_theta.transpose() * U) - + lambda1 * 2 * (V - V_trunc * (V_trunc_T * V)) - + lambda2 * 4 * V * (V_tilde - U_tilde); + + lambda1_col * 2 * penalty_v + + lambda2 * 4 * V * (V_tilde - U_tilde); double grad_U_norm = grad_U.norm(); double grad_V_norm = grad_V.norm(); @@ -93,11 +126,14 @@ List learner_worker(const Eigen::MatrixXd &Y_source, diff = diff.array().isNaN().select(Eigen::MatrixXd::Zero(diff.rows(), diff.cols()), diff); } obj = diff.squaredNorm() / perc_nonmissing + - lambda1 * (U - U_trunc * (U_trunc_T * U)).squaredNorm() + - lambda1 * (V - V_trunc * (V_trunc_T * V)).squaredNorm() + + lambda1_row * space_residual(spaces.u, spaces.weights, U).squaredNorm() + + lambda1_col * space_residual(spaces.v, spaces.weights, V).squaredNorm() + lambda2 * (U.transpose() * U - V.transpose() * V).squaredNorm(); } obj_values.push_back(obj); + if (!std::isfinite(obj) || !U.allFinite() || !V.allFinite()) { + return FitResult{U_best * V_best.transpose(), obj_values, 3}; + } if (obj < obj_best) { obj_best = obj; @@ -117,79 +153,63 @@ List learner_worker(const Eigen::MatrixXd &Y_source, obj_init = obj; } - return List::create( - Named("learner_estimate") = U_best * V_best.transpose(), - Named("objective_values") = obj_values, - Named("convergence_criterion") = convergence_criterion - ); + return FitResult{U_best * V_best.transpose(), obj_values, convergence_criterion}; } -//-------------------------------------------------------------- -// Exported learner function. // [[Rcpp::export]] -List learner_cpp(const Eigen::MatrixXd &Y_source, const Eigen::MatrixXd &Y_target, - int r, double lambda1, double lambda2, double step_size, int max_iter, double threshold, double max_value) { - List out = learner_worker(Y_source, Y_target, r, lambda1, lambda2, step_size, max_iter, threshold, max_value); - return List::create( - Named("learner_estimate") = out["learner_estimate"], - Named("objective_values") = out["objective_values"], - Named("convergence_criterion") = out["convergence_criterion"], - Named("r") = r - ); +List learner_cpp(const Rcpp::List &Y_source, const Eigen::MatrixXd &Y_target, + const std::vector &source_weights, int r, + double lambda1_row, double lambda1_col, double lambda2, + double step_size, int max_iter, double threshold, double max_value) { + SourceSpaces spaces = prepare_spaces(Y_source, source_weights, r); + FitResult out = learner_worker(spaces, Y_target, lambda1_row, lambda1_col, + lambda2, step_size, max_iter, threshold, max_value); + return List::create(Named("learner_estimate") = out.estimate, + Named("objective_values") = out.objective, + Named("convergence_criterion") = out.criterion, + Named("r") = r); } -//-------------------------------------------------------------- +// All three vectors contain one entry per candidate combination, in R array order. // [[Rcpp::export]] -List cv_learner_cpp(const Eigen::MatrixXd &Y_source, const Eigen::MatrixXd &Y_target, - const std::vector &lambda1_all, const std::vector &lambda2_all, - double step_size, int n_folds, int max_iter, double threshold, - int n_cores, int r, double max_value, - const std::vector> &index_set) { - int p = Y_source.rows(); - int q = Y_source.cols(); - int n_lambda1 = lambda1_all.size(); - int n_lambda2 = lambda2_all.size(); - - Eigen::MatrixXd mse_all = Eigen::MatrixXd::Zero(n_lambda1, n_lambda2); - -#pragma omp parallel for collapse(2) schedule(dynamic) num_threads(n_cores) - for (int i = 0; i < n_lambda1; ++i) { - for (int j = 0; j < n_lambda2; ++j) { - double mse = 0.0; - - for (int fold = 0; fold < n_folds; ++fold) { - Eigen::MatrixXd Y_train = Y_target; - for (int idx : index_set[fold]) { - int row = idx % p; - int col = idx / p; - Y_train(row, col) = std::numeric_limits::quiet_NaN(); - } - List temp = learner_worker(Y_source, Y_train, r, - lambda1_all[i], lambda2_all[j], - step_size, max_iter, threshold, max_value); - Eigen::MatrixXd learner_estimate = temp["learner_estimate"]; - double fold_mse = 0.0; - for (int idx : index_set[fold]) { - int row = idx % p; - int col = idx / p; - double diff = learner_estimate(row, col) - Y_target(row, col); - fold_mse += diff * diff; - } - mse += fold_mse; +std::vector cv_learner_cpp(const Rcpp::List &Y_source, + const Eigen::MatrixXd &Y_target, + const std::vector &source_weights, + const std::vector &row_grid, + const std::vector &col_grid, + const std::vector &balance_grid, + double step_size, int max_iter, double threshold, + int n_cores, int r, double max_value, + const std::vector> &index_set) { + SourceSpaces spaces = prepare_spaces(Y_source, source_weights, r); + int p = Y_target.rows(); + int n_grid = row_grid.size(); + std::vector scores(n_grid, 0.0); + +#pragma omp parallel for schedule(dynamic) num_threads(n_cores) + for (int g = 0; g < n_grid; ++g) { + double score = 0.0; + for (const auto &fold : index_set) { + Eigen::MatrixXd training = Y_target; + for (int idx : fold) training(idx % p, idx / p) = std::numeric_limits::quiet_NaN(); + FitResult fit = learner_worker(spaces, training, row_grid[g], col_grid[g], + balance_grid[g], step_size, max_iter, threshold, max_value); + if (!fit.estimate.allFinite() || + !std::all_of(fit.objective.begin(), fit.objective.end(), + [](double x) { return std::isfinite(x); })) { + score = std::numeric_limits::infinity(); + break; + } + double fold_score = 0.0; + for (int idx : fold) { + double difference = fit.estimate(idx % p, idx / p) - Y_target(idx % p, idx / p); + fold_score += difference * difference; } - mse_all(i, j) = mse; + score += fold_score; } + scores[g] = score; } - - Eigen::Index minRow, minCol; - mse_all.minCoeff(&minRow, &minCol); - - return List::create( - Named("lambda_1_min") = lambda1_all[minRow], - Named("lambda_2_min") = lambda2_all[minCol], - Named("mse_all") = mse_all, - Named("r") = r - ); + return scores; } // -------------------------------------------------------------- @@ -201,4 +221,3 @@ int omp_max_threads() { return 1; #endif } - diff --git a/tests/testthat/fixtures/README.md b/tests/testthat/fixtures/README.md new file mode 100644 index 0000000..ea3a456 --- /dev/null +++ b/tests/testthat/fixtures/README.md @@ -0,0 +1,54 @@ +# Fixed reference results for the extensions + +`multisource-v030.rds` contains inputs, fitting arguments, and expected outputs +computed by Sean McGrath's base-R **learnerv2 0.3.0** implementation from the +`learner-paper` repository's simulations materials. The tests do not create +expected values by calling the `learner` implementation under test. + +## Coverage + +- Three simulated 9-by-7 source matrices with weights 2, 3, 5 and fixed rank 2. +- Common penalties and unequal U/V penalties, for complete and missing targets. +- Complete fitted matrices and all 15 objective values, rank, and stopping code. +- Separate-penalty CV with 2-by-2-by-2 candidates, three folds, and fixed RNG + settings, for complete and missing targets. All scores and selected values + are checked; each saved CV case has a unique minimum. +- The complete multiple-source D-LEARNER estimate, also checked at generation + time against explicit dense weighted source operators. + +The saved inputs are synthetic. They are generated with seed 405 and then stored +in full, rather than regenerated during tests. CV uses seed 514 with RNG kinds +`Mersenne-Twister`, `Inversion`, and `Rejection`; its test restores the incoming +RNG state. The fixture records the reference version, source-file MD5 hashes, +R version, and RNG kinds so the source of the expected values can be audited. + +`expect_equal()` uses tolerance `1e-8` to allow ordinary floating-point and SVD +library differences. Matrix estimates and objective histories are checked, not +raw U/V factors, whose SVD signs are arbitrary. Rank is fixed to avoid depending +on future changes in automatic rank selection. + +The 15-update examples use `threshold = 0` and a large growth limit to compare a +fixed optimization path. They do not demonstrate convergence or statistical +accuracy. They do not assert that the two implementations have identical stopping +behavior for all settings. The analytic tests in `test-stopping-behavior.R` +separately check the current package's documented stopping rules. + +Normal tests only read this fixture and require neither `learnerv2` nor `foreach`. + +## Deliberate regeneration + +Run the generator manually from the package root, using an extracted, unmodified +copy of learnerv2 0.3.0. The generator requires `foreach`; it verifies the package +name and version and sources the reference R files without loading `learner`. + +```sh +Rscript tests/testthat/fixtures/generate-reference.R \ + /path/to/learnerv2 \ + tests/testthat/fixtures/multisource-v030.rds +``` + +The generator refuses to overwrite an existing baseline unless `--overwrite` is +added. Do not regenerate the fixture merely to make a failing test pass. First +understand whether a change is an intended method/interface change, an optimization +difference, or a bug; review any new baseline independently and update this note. +Neither testthat nor R CMD check runs the generator automatically. diff --git a/tests/testthat/fixtures/generate-reference.R b/tests/testthat/fixtures/generate-reference.R new file mode 100644 index 0000000..fa1b5c0 --- /dev/null +++ b/tests/testthat/fixtures/generate-reference.R @@ -0,0 +1,87 @@ +# Developer-only script. It is never run by testthat or R CMD check. +# Usage: Rscript generate-reference.R /path/to/learnerv2-0.3.0 /path/to/output.rds +# Add --overwrite only after reviewing an intentional change to the baseline. +args <- commandArgs(trailingOnly = TRUE) +if (length(args) < 2L) stop('Supply the extracted reference package directory and output .rds path.') +reference_dir <- normalizePath(args[1], mustWork = TRUE) +output <- args[2] +if (file.exists(output) && !('--overwrite' %in% args)) { + stop('Reference already exists; review the change before using --overwrite.') +} +description <- read.dcf(file.path(reference_dir, 'DESCRIPTION')) +stopifnot(description[1, 'Package'] == 'learnerv2', description[1, 'Version'] == '0.3.0') +# The reference CV implementation uses foreach even for sequential execution. +if (!requireNamespace('foreach', quietly = TRUE)) stop('Install foreach to generate this fixture.') +reference <- new.env(parent = asNamespace('foreach')) +reference_files <- file.path('R', c('learner.R', 'cv_learner.R', 'dlearner.R')) +for (f in reference_files) sys.source(file.path(reference_dir, f), envir = reference) + +# Inputs are saved in the fixture, so tests do not regenerate them from the RNG. +RNGkind('Mersenne-Twister', 'Inversion', 'Rejection') +set.seed(405) +sources <- list(matrix(rnorm(63), 9, 7), matrix(rnorm(63), 9, 7), + matrix(rnorm(63), 9, 7)) +target <- matrix(rnorm(63), 9, 7) +masked <- target +masked[c(3, 8, 19)] <- NA_real_ +targets <- list(complete = target, missing = masked) +control <- list(max_iter = 15L, threshold = 0, max_value = 1e6) +rank <- 2L +weights <- c(2, 3, 5) +step <- 0.01 +penalties <- list(common = list(lambda_1 = 3), + separate = list(lambda_1_row = 3, lambda_1_col = 0.25)) +grids <- list(lambda_1_row_all = c(0.25, 3), lambda_1_col_all = c(0.5, 2), + lambda_2_all = c(0, 1)) +fit_cases <- list() +cv_cases <- list() +for (target_name in names(targets)) { + for (penalty_name in names(penalties)) { + fit_args <- c(list(Y_source = sources, Y_target = targets[[target_name]], + r = rank, source_weights = weights, lambda_2 = 1, + step_size = step, control = control), penalties[[penalty_name]]) + reference_args <- fit_args + reference_args$r <- NULL + reference_args$r_source <- rank + reference_args$r_target <- rank + expected <- do.call(reference$learner, reference_args) + stopifnot(expected$convergence_criterion == 2L, + length(expected$objective_values) == control$max_iter, + all(is.finite(expected$objective_values))) + fit_cases[[paste(target_name, penalty_name, sep = '_')]] <- + list(args = fit_args, expected = expected) + } + cv_args <- c(list(Y_source = sources, Y_target = targets[[target_name]], + r = rank, source_weights = weights, step_size = step, + control = control, n_folds = 3L, n_cores = 1L), grids) + reference_args <- cv_args + reference_args$r <- NULL + reference_args$r_source <- rank + reference_args$r_target <- rank + set.seed(514) + expected <- do.call(reference$cv.learner, reference_args) + stopifnot(all(is.finite(expected$mse_all))) + sorted_scores <- sort(as.vector(expected$mse_all)) + stopifnot(sorted_scores[2] - sorted_scores[1] > 1e-5) + cv_cases[[target_name]] <- list(args = cv_args, seed = 514L, expected = expected) +} +direct_args <- list(Y_source = sources, Y_target = target, r = rank, + source_weights = weights) +direct_expected <- do.call(reference$dlearner, direct_args) +# Independently verify D-LEARNER by forming the dense weighted operators. +bases <- lapply(sources, svd, nu = rank, nv = rank) +w <- weights / sum(weights) +pu <- Reduce(`+`, Map(function(b, wk) wk * tcrossprod(b$u), bases, w)) +pv <- Reduce(`+`, Map(function(b, wk) wk * tcrossprod(b$v), bases, w)) +stopifnot(isTRUE(all.equal(direct_expected$dlearner_estimate, + pu %*% target %*% pv, tolerance = 1e-12))) +fixture <- list( + provenance = list(reference_package = 'learnerv2', reference_version = '0.3.0', + reference_md5 = setNames(as.character(tools::md5sum( + file.path(reference_dir, reference_files))), reference_files), + r_version = as.character(getRversion()), + rng_kind = RNGkind(), tolerance = 1e-8), + fit = fit_cases, cv = cv_cases, + direct = list(args = direct_args, expected = direct_expected)) +saveRDS(fixture, output, version = 2) +cat('Wrote independently generated reference fixture:', output, '\n') diff --git a/tests/testthat/fixtures/multisource-v030.rds b/tests/testthat/fixtures/multisource-v030.rds new file mode 100644 index 0000000000000000000000000000000000000000..7377dc5f0459e0e4305fe54b224ad1446c22ef7a GIT binary patch literal 6238 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 Date: Fri, 9 Oct 2026 02:09:55 -0500 Subject: [PATCH 2/4] Clean repository metadata and align documentation tooling --- .Rbuildignore | 2 -- .gitignore | 3 --- DESCRIPTION | 2 +- NEWS.md | 2 +- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.Rbuildignore b/.Rbuildignore index 5cbe826..42bec2f 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -5,5 +5,3 @@ ^data-raw$ ^README_files/ ^\.Rprofile$ -^build_sean_meeting_.*\.cjs$ -^test_temp_file\.txt$ diff --git a/.gitignore b/.gitignore index e4ae119..7234786 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,5 @@ .RData .Ruserdata -# Local R startup and meeting preparation files .Rprofile .DS_Store -build_sean_meeting_*.cjs -test_temp_file.txt diff --git a/DESCRIPTION b/DESCRIPTION index de98bdc..d7b4663 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -37,4 +37,4 @@ Suggests: Config/testthat/edition: 3 Depends: R (>= 2.10) -Config/roxygen2/version: 8.1.0 +RoxygenNote: 7.3.2 diff --git a/NEWS.md b/NEWS.md index 56127db..1bff73e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,4 @@ -# learner 1.1.0 +### learner version 1.1.0 (unreleased) * Integrate multiple-source support and separate space penalties from the preliminary `learnerv2` 0.3.0 implementation into the package's R and C++ From a1c22952b2b389560a780b224885486d38d85062 Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Thu, 15 Oct 2026 20:37:09 -0500 Subject: [PATCH 3/4] Add independent multiple-source example data and clarify method documentation --- NEWS.md | 29 +-- R/data-multisource.R | 43 ++++ R/dlearner.R | 97 +++++--- R/learner.R | 325 +++++++++++++------------ data-raw/gen-data.R | 35 +++ data/dat_multisource.rda | Bin 0 -> 212592 bytes inst/examples/multiple-sources.R | 21 +- man/cv.learner.Rd | 129 +++++----- man/dat_multisource.Rd | 54 ++++ man/dlearner.Rd | 91 ++++--- man/learner.Rd | 194 +++++++-------- tests/testthat/test-multisource-data.R | 31 +++ 12 files changed, 630 insertions(+), 419 deletions(-) create mode 100644 R/data-multisource.R create mode 100644 data/dat_multisource.rda create mode 100644 man/dat_multisource.Rd create mode 100644 tests/testthat/test-multisource-data.R diff --git a/NEWS.md b/NEWS.md index 1bff73e..e9ac59c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,28 +1,11 @@ ### learner version 1.1.0 (unreleased) -* Integrate multiple-source support and separate space penalties from the - preliminary `learnerv2` 0.3.0 implementation into the package's R and C++ - implementation. -* `learner()`, `cv.learner()`, and `dlearner()` accept a list of aligned source - matrices and optional `source_weights`. Nonnegative weights are normalized - to sum to one and combine source projection operators. The common rank - interface is retained: automatic rank selection uses the first source, - and LEARNER initialization uses the first source even when rank is supplied. -* `learner()` accepts `lambda_1_row` and `lambda_1_col` to penalize the U and V - source-space residuals separately. The original `lambda_1` interface, - including positional calls, is retained. Equal separate coefficients reproduce - the common-penalty calculation under the same settings. -* `cv.learner()` accepts separate row and column penalty grids and returns a - three-dimensional validation-score array. Common-penalty calls retain their - previous output structure and score scale (summed held-out squared errors). -* Cache source decompositions during cross-validation and use C++ data in - parallel workers. Validate sources, weights, penalties, ranks, and fold counts. -* Expand argument documentation and examples for multiple sources, separate - penalties, cross-validation, missing data, and the current stopping rules. -* Expand automated tests for compatibility, gradients, multiple-source fitting, - cross-validation, and stopping behavior. Add fixed reference outputs from - `learnerv2` 0.3.0 to detect unintended numerical changes. -* Add Jodie Li as a package author. +* Add support for multiple source populations and optional source weights in + `learner()`, `dlearner()`, and `cv.learner()`. +* Add `dat_multisource`, an example dataset with one target and two source + populations with independently generated noise. +* Add separate row- and column-space penalties in `learner()` and + cross-validation over these penalties in `cv.learner()`. ### learner version 1.0.0 (2025-03-02) diff --git a/R/data-multisource.R b/R/data-multisource.R new file mode 100644 index 0000000..d98d4d9 --- /dev/null +++ b/R/data-multisource.R @@ -0,0 +1,43 @@ +#' Simulated data set: Multiple sources with different noise levels +#' +#' Simulated matrices for one target population and two source populations. +#' The sources share the target's latent row and column spaces, but source A +#' is observed more precisely than source B. This data set illustrates fitting +#' LEARNER and D-LEARNER with multiple sources. +#' +#' @docType data +#' @format A list with four components. All matrices have 100 rows and 50 columns, +#' with corresponding rows and columns aligned across populations. +#' \describe{ +#' \item{\code{Y_source}}{A named list of observed source matrices, \code{A} +#' and \code{B}, suitable for the \code{Y_source} argument.} +#' \item{\code{Y_target}}{The observed target matrix.} +#' \item{\code{Theta_source}}{A named list of the true rank-3 source matrices, +#' \code{A} and \code{B}. The two true matrices are identical.} +#' \item{\code{Theta_target}}{The true rank-3 target matrix.} +#' } +#' @details +#' The source signal is obtained by reversing the three nonzero singular values +#' of the target signal while keeping its singular vectors. Each observed matrix +#' is generated by adding independent, mean-zero normal noise to its own signal. +#' Noise is independent across entries and populations, with variances 0.10 for +#' the target, 0.01 for source A, and 0.50 for source B. Thus, source A has less +#' noise than the target and source B has more. Source B is not generated from +#' the observed source A matrix. +#' +#' The true signal matrices are included for illustration and estimation-error +#' calculations; they are not inputs to the fitting functions. These simulated +#' data are a usage example, not a reproduction of the paper's simulation study. +#' +#' @examples +#' data(dat_multisource) +#' fit <- dlearner( +#' Y_source = dat_multisource$Y_source, +#' Y_target = dat_multisource$Y_target, +#' r = 3, source_weights = c(0.7, 0.3) +#' ) +#' dim(fit$dlearner_estimate) +#' @seealso \code{\link{dat_highsim}}, \code{\link{dat_modsim}}, +#' \code{\link{learner}}, \code{\link{dlearner}} +#' @keywords datasets +"dat_multisource" diff --git a/R/dlearner.R b/R/dlearner.R index 7ce14aa..014d9ad 100644 --- a/R/dlearner.R +++ b/R/dlearner.R @@ -1,54 +1,79 @@ #' Direct projection transfer learning #' -#' Estimate a target signal matrix by applying source-space operators directly -#' to the target data, using the D-LEARNER method (McGrath et al. 2024). -#' One source matrix or a list of source matrices can be supplied. +#' @description +#' This function applies the Direct projection LatEnt spAce-based tRaNsfer +#' lEaRning (D-LEARNER) method (McGrath et al. 2024) to leverage data from a source +#' population to improve estimation of a low-rank matrix in an underrepresented +#' target population. #' -#' @param Y_target nonempty numeric target matrix with only finite values. -#' Unlike \code{learner()}, this function does not allow missing target entries. -#' @param r optional common rank of the retained source spaces. If omitted, ScreeNOT selects it from the first source. -#' @inheritParams learner -#' -#' @return A list with the following components: -#' \item{dlearner_estimate}{matrix containing the D-LEARNER estimate of the target population knowledge graph.} -#' \item{r}{rank value used.} +#' @param Y_source Matrix containing the source population data, with the same rows +#' and columns as \code{Y_target}. For multiple sources, supply a list of +#' matrices. +#' @param Y_target Matrix containing the target population data. Missing entries +#' are not allowed. +#' @param r Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +#' is applied to the source population data to select the rank. +#' @param source_weights Relative weights of the sources, in the order of +#' \code{Y_source}. Default is equal weights. #' #' @details +#' \strong{Data and notation:} +#' +#' The data consists of a matrix in the target population +#' \eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population +#' \eqn{Y_1 \in \mathbb{R}^{p \times q}}. Let +#' \eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +#' singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. +#' +#' For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +#' \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +#' is the target population knowledge graph, \eqn{\Theta_0}. +#' +#' \strong{Estimation:} +#' +#' This method estimates \eqn{\Theta_0} by +#' +#' \deqn{\hat U_1\hat U_1^\top Y_0 \hat V_1\hat V_1^\top.} #' -#' Each source has the same dimensions as the target matrix \eqn{Y_0}. -#' Let \eqn{B_{U,k}} and \eqn{B_{V,k}} contain the first \eqn{r} left and -#' right singular vectors of source \eqn{k}. The estimate is -#' \deqn{\widehat\Theta_0 = \bar P_U Y_0 \bar P_V, \qquad -#' \bar P_U=\sum_k w_k B_{U,k}B_{U,k}^\top, \qquad -#' \bar P_V=\sum_k w_k B_{V,k}B_{V,k}^\top.} -#' The nonnegative weights are normalized to sum to one and default to equal -#' values. With one source this reduces to projection onto its retained left -#' and right singular spaces. With multiple sources, the averaged operators -#' need not be idempotent, and the estimate can have rank larger than \code{r}. -#' This is not an average of the raw source matrices or of separately computed -#' single-source D-LEARNER estimates. -#' -#' D-LEARNER does not iteratively fit target factors and has no space or balance -#' penalty parameters. All inputs must have aligned rows and columns and no -#' missing values. Names are not matched. If \code{r} is omitted, it is selected -#' from the first source, including when that source has zero weight. -#' Output dimension names are taken from the first source. +#' That is, the target population data are projected onto the latent row and +#' column spaces of the source population, without iterative fitting or penalty +#' selection. +#' +#' \strong{Multiple sources:} +#' +#' Supply a list of source matrices to combine information from several +#' populations. Their projection operators are averaged using +#' \code{source_weights}, and the estimate becomes +#' +#' \deqn{\widehat\Theta_0=\bar P_U Y_0\bar P_V,\qquad +#' \bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad +#' \bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} +#' +#' where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +#' the \eqn{k}th source and the weights \eqn{w_k} are given by +#' \code{source_weights}. If \code{r} is not specified, the rank is selected +#' using the first source. +#' +#' @return +#' A list with the following components: +#' \item{dlearner_estimate}{matrix containing the D-LEARNER estimate of the target population knowledge graph.} +#' \item{r}{rank value used.} #' #' @references #' Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. #' +#' @seealso +#' \code{\link{learner}} for a target fit that can adapt the source +#' spaces through penalized estimation. +#' #' @examples #' res <- dlearner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target) #' -#' # A second simulated source, made by perturbing the first. -#' set.seed(803) -#' source_A <- dat_highsim$Y_source[1:20, 1:10] -#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), -#' nrow(source_A), ncol(source_A)) +#' # Multiple sources with fixed demonstration weights. #' res_multi <- dlearner( -#' Y_source = list(A = source_A, B = source_B), -#' Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, +#' Y_source = dat_multisource$Y_source, +#' Y_target = dat_multisource$Y_target, r = 3, #' source_weights = c(0.7, 0.3)) #' dim(res_multi$dlearner_estimate) #' diff --git a/R/learner.R b/R/learner.R index c22cc60..c668e7d 100644 --- a/R/learner.R +++ b/R/learner.R @@ -1,57 +1,71 @@ #' Cross-validation for LEARNER #' -#' Select common or separate source-space penalties and a balance penalty for -#' \code{\link{learner}} by k-fold cross-validation on observed target entries. -#' One source matrix or a list of source matrices can be supplied. -#' -#' @param lambda_1_all nonempty vector of finite nonnegative candidate common penalties. Use this or both separate grids, not both modes. -#' @param lambda_2_all nonempty vector of finite nonnegative candidate balance penalties. -#' @param lambda_1_row_all nonempty vector of finite nonnegative candidate penalties on U; requires \code{lambda_1_col_all}. -#' @param lambda_1_col_all nonempty vector of finite nonnegative candidate penalties on V; requires \code{lambda_1_row_all}. -#' @inheritParams learner -#' @param step_size numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm, as in \code{\link{learner}} -#' @param n_folds integer from 2 to the number of observed target entries. The default is \code{4}. -#' @param n_cores positive integer specifying the requested number of OpenMP -#' threads. Candidate penalty combinations are evaluated in parallel when the -#' package is built with OpenMP support; otherwise they run sequentially. -#' The default is \code{1}. -#' @param control a list of parameters for controlling the stopping criteria for the numerical optimization algorithm, as in \code{\link{learner}}. -#' -#' @return A list with the following elements: -#' \item{lambda_1_min}{selected common penalty; \code{NA} in separate mode.} -#' \item{lambda_1_row_min, lambda_1_col_min}{selected separate penalties, returned only in separate mode.} -#' \item{lambda_2_min}{balance penalty in the combination with the smallest held-out squared-error sum.} -#' \item{mse_all}{held-out squared-error sums, retaining the historical scale despite the name. Common mode returns a matrix indexed by \code{lambda_1_all} and \code{lambda_2_all}. Separate mode returns a three-dimensional array indexed by \code{lambda_1_row_all}, \code{lambda_1_col_all}, and \code{lambda_2_all}, in that order.} -#' \item{r}{rank value used.} +#' @description +#' Performs k-fold cross-validation to select the source-space and balance +#' penalties for \code{\link{learner}}. Returns prediction errors and the +#' penalty combination with the smallest error. +#' +#' @param Y_source Source population data, as in \code{\link{learner}}. +#' Supply a matrix for one source or a list of matrices for multiple sources. +#' @param Y_target Target population data matrix, as in \code{\link{learner}}. +#' Only observed entries are used to evaluate predictions. +#' @param r Rank used in every fold, as in \code{\link{learner}}. It is not +#' selected by cross-validation. +#' @param lambda_1_all Candidate values for the common source-space penalty +#' \eqn{\lambda_1}. All combinations with \code{lambda_2_all} are evaluated. +#' @param lambda_2_all Candidate values for the balance penalty \eqn{\lambda_2}. +#' @param step_size Step size for each fit, as in \code{\link{learner}}. +#' @param n_folds Number of cross-validation folds. Default is \code{4}. +#' @param n_cores Number of CPU cores used to evaluate the candidate penalty +#' combinations in parallel. Default is \code{1}, i.e., no parallelization. +#' @param control Optimization settings for each fit; see \code{\link{learner}}. +#' @param lambda_1_row_all,lambda_1_col_all Candidate values for separate +#' penalties on the row and column factors. Supply both instead of +#' \code{lambda_1_all}; all combinations with \code{lambda_2_all} are evaluated. +#' @param source_weights Relative source weights, as in \code{\link{learner}}. The weights +#' are not selected by cross-validation. Default is equal weights. #' #' @details -#' Each observed target entry is held out once. The same folds are used for all -#' candidates. Dividing \code{mse_all} by the number of observed target entries -#' gives the mean squared error without changing the selected candidate. Ties -#' select the first minimum in R array order. Existing missing entries are -#' excluded from validation. Rank, source spaces, source weights, and the -#' first-source initialization are fixed across folds. The function tunes -#' penalties, not rank or source weights. Call \code{set.seed()} before -#' \code{cv.learner()} to reproduce the fold allocation. -#' For independent penalties, supply both separate grids and \code{lambda_2_all}. -#' Candidates with non-finite errors are excluded with a warning; if all fail, -#' the function stops. Finite fits that reach the iteration limit are retained, -#' so selecting a candidate does not certify convergence of its fold fits. -#' Assess the optimization settings as well as the final refit. -#' -#' The return value contains scores and selected parameters, not a final fitted -#' target matrix. Refit with \code{learner()} using all observed target entries -#' and the selected penalties, while retaining the same sources, weights, and rank. -#' -#' Observed target entries are randomly partitioned into approximately equal -#' folds. Each fold is set to missing in the training matrix, and the fitted -#' matrix is used to predict the held-out entries. Errors are summed across -#' folds for each candidate pair (common penalties) or triple (separate -#' penalties). Source entries are not held out. See McGrath et al. (2024). +#' Given sets of candidate values of \eqn{\lambda_1} and \eqn{\lambda_2}, this +#' function performs k-fold cross-validation to select the pair +#' \eqn{(\lambda_1, \lambda_2)} with the smallest held out error. This function +#' randomly partitions the entries of \code{Y_target} into \eqn{k} +#' (approximately) equally sized subsamples. The training data sets are obtained +#' by removing one of the \eqn{k} subsamples and the corresponding test data +#' sets are based on the held out subsamples. The \code{\link{learner}} function is +#' applied to each training data set. The held out error is computed by comparing +#' the entries in the test data sets with those imputed from the LEARNER +#' estimates. See McGrath et al. (2024) for further details. +#' +#' With separate penalties, the procedure selects the row, column, and balance +#' penalties together. Both modes can be used with multiple sources. Call +#' \code{set.seed()} first to reproduce the random partition. +#' +#' To obtain the final estimate, apply \code{\link{learner}} with the selected +#' penalties to the full data (see Examples). Fits that reach the iteration +#' limit still contribute to the held out error, so check the settings in +#' \code{control} before interpreting the selection. +#' +#' @return +#' A list containing: +#' \item{lambda_1_min}{Common source-space penalty in the selected combination. +#' \code{NA} when separate penalties are used.} +#' \item{lambda_2_min}{Balance penalty in the selected combination.} +#' \item{lambda_1_row_min, lambda_1_col_min}{Row and column penalties in the +#' selected combination. Returned only when separate penalties are used.} +#' \item{mse_all}{Sum of held-out squared errors for each candidate combination. +#' For common penalties, rows correspond to \code{lambda_1_all} and columns to +#' \code{lambda_2_all}. For separate penalties, the three dimensions correspond +#' to the row, column, and balance grids, in that order. Divide by the number +#' of observed target entries to obtain mean squared errors.} +#' \item{r}{Rank used in the fits.} #' #' @references #' McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. #' +#' @seealso +#' \code{\link{learner}} for fitting with the selected penalties. +#' #' @examples #' res <- cv.learner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target, @@ -59,19 +73,20 @@ #' lambda_2_all = c(1, 10, 100), #' step_size = 0.003) #' -#' # A small simulated example: source B is a perturbation of source A, -#' # not an independent population. Weights are fixed demonstration values. -#' set.seed(803) -#' source_A <- dat_highsim$Y_source[1:20, 1:10] -#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), -#' nrow(source_A), ncol(source_A)) -#' sources <- list(A = source_A, B = source_B) -#' target <- dat_highsim$Y_target[1:20, 1:10] +#' # Refit using the selected penalties and all observed target entries. +#' fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, r = res$r, +#' lambda_1 = res$lambda_1_min, lambda_2 = res$lambda_2_min, +#' step_size = 0.003) +#' fit$convergence_criterion +#' +#' # Multiple sources with fixed demonstration weights. +#' sources <- dat_multisource$Y_source +#' target <- dat_multisource$Y_target #' weights <- c(0.7, 0.3) #' #' set.seed(815) #' res_separate <- cv.learner( -#' Y_source = sources, Y_target = target, r = 2, +#' Y_source = sources, Y_target = target, r = 3, #' source_weights = weights, #' lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), #' lambda_2_all = c(0.1, 1), step_size = 0.0003, @@ -154,127 +169,131 @@ cv.learner <- function(Y_source, Y_target, r, lambda_1_all = NULL, lambda_2_all, #' Latent space-based transfer learning #' -#' Estimate a low-rank target matrix using information from one or more source -#' populations with the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method -#' (McGrath et al. 2024). The two source-space penalties can share a coefficient -#' or use separate coefficients. -#' -#' @param Y_target nonempty numeric matrix containing target population data. -#' Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry -#' must be observed. Infinite values are not allowed. -#' @param Y_source numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values. -#' @param r optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source. -#' @param source_weights optional finite nonnegative numeric vector with one -#' weight per source and at least one positive value. Weights correspond to the -#' order of matrices in \code{Y_source}; names are not used for matching. They -#' are normalized to sum to one. \code{NULL} gives equal weights. For a single -#' source, a supplied weight must be a positive scalar and is normalized to one. -#' @param lambda_1 finite nonnegative numeric scalar specifying the common space penalty. Supply either this parameter or both \code{lambda_1_row} and \code{lambda_1_col}, not both modes. -#' @param lambda_2 finite nonnegative numeric scalar for the balance penalty (see Details) -#' @param lambda_1_row optional finite nonnegative numeric scalar for the source-space penalty on \eqn{U} (the SNP side when SNPs are rows). Must be supplied together with \code{lambda_1_col}. -#' @param lambda_1_col optional finite nonnegative numeric scalar for the source-space penalty on \eqn{V} (the phenotype side when phenotypes are columns). Must be supplied together with \code{lambda_1_row}. -#' @param step_size numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm -#' @param control list controlling the numerical optimization: +#' @description +#' This function applies the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) +#' method (McGrath et al. 2024) to leverage data from a source population to improve +#' estimation of a low-rank matrix in an underrepresented target population. +#' +#' @param Y_source Matrix containing the source population data, with the same rows +#' and columns as \code{Y_target}. For multiple sources, supply a list of +#' matrices. +#' @param Y_target Matrix containing the target population data. Missing entries +#' are allowed. +#' @param r Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +#' is applied to the source population data to select the rank. +#' @param lambda_1 Common source-space penalty. Larger values borrow more +#' information from the source population; zero does not use the source spaces. +#' Use \code{\link{cv.learner}} to select penalty values. +#' @param lambda_2 Balance penalty. Larger values more strongly encourage +#' \eqn{U} and \eqn{V} to have comparable scale. +#' @param step_size Step size in the optimization algorithm. Smaller values may +#' reduce oscillation but require more iterations. +#' @param control List of settings controlling when optimization stops: #' \describe{ -#' \item{\code{max_iter}}{Positive integer giving the maximum number of updates; -#' default \code{100}.} -#' \item{\code{threshold}}{Finite nonnegative absolute objective-change threshold; -#' default \code{0.001}. Starting with the second update, the algorithm stops -#' when \eqn{|L_t-L_{t-1}|} is strictly less than this value. This rule alone does -#' not establish factor stability or a global optimum.} -#' \item{\code{max_value}}{Finite positive multiplier; default \code{10}. -#' Starting with the second update, the algorithm stops if the current objective -#' exceeds this multiplier times the preceding objective (unless the -#' objective-change stopping rule has already been met).}} -#' -#' @return A list with the following elements: -#' \item{learner_estimate}{target signal estimate with the same dimensions as \code{Y_target}, formed from the factors with the lowest objective encountered, including the initialization.} -#' \item{objective_values}{objective values after each update, excluding the initialization. The last value need not correspond to the returned estimate.} -#' \item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} -#' \item{r}{rank value used.} +#' \item{\code{max_iter}}{Maximum number of updates; default \code{100}.} +#' \item{\code{threshold}}{Stop when the absolute change between consecutive +#' objective values is smaller than this value; default \code{0.001}.} +#' \item{\code{max_value}}{Stop if the objective grows beyond this multiple of +#' its preceding value; default \code{10}.}} +#' @param lambda_1_row,lambda_1_col Separate source-space penalties for the row +#' factor \eqn{U} and column factor \eqn{V}, respectively. Use these when the two +#' sides should borrow different amounts of source information. Supply both +#' instead of \code{lambda_1}; equal values reproduce the common penalty. +#' @param source_weights Relative weights of the sources, in the order of +#' \code{Y_source}. Default is equal weights. #' #' @details +#' \strong{Data and notation:} +#' +#' The data consists of a matrix in the target population +#' \eqn{Y_0\in\mathbb{R}^{p\times q}} and the source population +#' \eqn{Y_1\in\mathbb{R}^{p\times q}}. Let +#' \eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +#' singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. +#' +#' For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +#' \eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +#' is the target population knowledge graph, \eqn{\Theta_0}. #' -#' \strong{Data and source spaces:} -#' -#' Let \eqn{Y_0} be the \eqn{p \times q} target matrix, and let -#' \eqn{Y_1,\ldots,Y_K} be aligned source matrices of the same dimensions. -#' The target signal is estimated by \eqn{UV^\top}, with -#' \eqn{U \in \mathbb{R}^{p \times r}} and -#' \eqn{V \in \mathbb{R}^{q \times r}}. For each source, the first \eqn{r} -#' left and right singular vectors form bases \eqn{B_{U,k}} and \eqn{B_{V,k}}. -#' The source operators are -#' \deqn{\bar P_U = \sum_{k=1}^K w_k B_{U,k}B_{U,k}^\top, \qquad -#' \bar P_V = \sum_{k=1}^K w_k B_{V,k}B_{V,k}^\top,} -#' where \eqn{w_k \geq 0} and \eqn{\sum_k w_k=1}. -#' These combine source spaces, not the raw source matrices. The averaged -#' operators are generally not orthogonal projectors themselves. -#' Rows and columns must already be aligned by the caller; names are not matched. -#' -#' \strong{Objective and penalties:} -#' -#' The numerical algorithm seeks to minimize -#' \deqn{L(U,V) = \rho^{-1}\|\mathcal{P}_{\Omega}(UV^\top-Y_0)\|_F^2 -#' + \lambda_{1,\mathrm{row}}\|(I-\bar P_U)U\|_F^2 -#' + \lambda_{1,\mathrm{col}}\|(I-\bar P_V)V\|_F^2 -#' + \lambda_2\|U^\top U-V^\top V\|_F^2.} -#' Here \eqn{\Omega} contains the observed target entries, -#' \eqn{\mathcal{P}_{\Omega}} keeps residuals at those entries and sets the -#' others to zero, and \eqn{\rho=|\Omega|/(pq)} is the observed fraction. -#' With no missing entries, \eqn{\rho=1}. Source matrices must be complete. -#' -#' Supply \code{lambda_1} for a common space penalty, or supply both -#' \code{lambda_1_row} and \code{lambda_1_col} and leave \code{lambda_1=NULL}. -#' Mixing the modes or providing only one separate coefficient is an error. -#' Equal separate coefficients reproduce the common-penalty calculation under -#' the same inputs and optimization settings. The balance penalty -#' \code{lambda_2} is required in either mode. -#' The space penalties square the residual from the weighted source operator; -#' they are not weighted sums of separately squared per-source residuals. -#' Use \code{\link{cv.learner}} to select common or separate penalty values. -#' -#' \strong{Initialization and stopping:} -#' -#' If \code{r} is omitted, ScreeNOT selects it from the first source, with a -#' minimum of one. A supplied \code{r} is used directly. The first source is -#' always used to initialize the target factors, including when its weight is -#' zero. Reordering sources with their weights therefore preserves the weighted -#' operators for fixed \code{r}, but can change initialization and fitted results. -#' The initial factors are the first source's retained left and right singular -#' vectors, each multiplied by the square roots of its retained singular values. -#' -#' This function uses scaled gradient descent steps. Both gradients are -#' evaluated at the current \eqn{U} and \eqn{V}, then both matrices are updated. -#' Updates are repeated until a stopping criterion is satisfied. The returned -#' estimate uses the best objective encountered, not necessarily the final -#' iterate. Inspect \code{objective_values} and \code{convergence_criterion}; -#' code 1 only indicates that the stated objective-change rule was met. +#' \strong{Estimation:} +#' +#' This method estimates \eqn{\Theta_0} by \eqn{\tilde U\tilde V^\top}, where +#' \eqn{(\tilde U, \tilde V)} is the solution to the following optimization problem +#' +#' \deqn{\mathrm{arg\,min}_{U\in\mathbb{R}^{p\times r},\,V\in\mathbb{R}^{q\times r}} +#' \big\{\|UV^\top-Y_0\|_F^2 +#' +\lambda_1\|\mathcal{P}_\perp(\hat U_1)U\|_F^2 +#' +\lambda_1\|\mathcal{P}_\perp(\hat V_1)V\|_F^2 +#' +\lambda_2\|U^\top U-V^\top V\|_F^2\big\}} +#' +#' where \eqn{\mathcal{P}_\perp(\hat U_1) = I - \hat U_1\hat U_1^\top} and +#' \eqn{\mathcal{P}_\perp(\hat V_1) = I - \hat V_1\hat V_1^\top}. The four terms measure +#' target fit, departures from the two source spaces, and imbalance between the +#' target factors, respectively. If \code{Y_target} has missing entries, the +#' first term is computed over the observed entries. +#' +#' At each iteration, \eqn{U} and \eqn{V} are updated by a gradient +#' descent step, starting from the truncated SVD of the source population data. +#' These updates are repeated until convergence. +#' +#' \strong{Extensions:} +#' +#' Separate penalties \code{lambda_1_row} and \code{lambda_1_col} replace +#' \eqn{\lambda_1} in the second and third terms. For multiple sources, the +#' source projectors are replaced by weighted combinations: +#' +#' \deqn{\bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad +#' \bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} +#' +#' where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +#' the \eqn{k}th source and the weights \eqn{w_k} are given by +#' \code{source_weights}. The full objective is +#' +#' \deqn{\begin{aligned} +#' L(U,V) ={}& \|UV^\top-Y_0\|_F^2 \\ +#' &+\lambda_{1,\mathrm{row}}\|(I_p-\bar P_U)U\|_F^2 \\ +#' &+\lambda_{1,\mathrm{col}}\|(I_q-\bar P_V)V\|_F^2 \\ +#' &+\lambda_2\|U^\top U-V^\top V\|_F^2. +#' \end{aligned}} +#' +#' where \eqn{I_p} and \eqn{I_q} are identity matrices. Equal space penalties +#' give the common-penalty version. With multiple sources, the first source is +#' used to initialize the algorithm and, if \code{r} is not specified, to +#' select the rank. +#' +#' @return +#' A list with the following elements: +#' \item{learner_estimate}{matrix containing the LEARNER estimate of the target population knowledge graph.} +#' \item{objective_values}{numeric vector containing the values of the objective function at each iteration.} +#' \item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} +#' \item{r}{rank value used.} #' #' @references #' McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. #' #' Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. #' +#' @seealso +#' \code{\link{cv.learner}} for penalty selection; +#' \code{\link{dlearner}} for direct projection onto source spaces. +#' #' @examples +#' # Single source with a common source-space penalty. #' res <- learner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target, #' lambda_1 = 1, lambda_2 = 1, #' step_size = 0.003) #' +#' # Same source, with separate penalties for the two factors. #' res_separate <- learner(Y_source = dat_highsim$Y_source, #' Y_target = dat_highsim$Y_target, #' lambda_1_row = 10, lambda_1_col = 1, #' lambda_2 = 1, step_size = 0.003) #' #' # Multiple sources with fixed demonstration weights. -#' # Source B is a perturbed copy, not an independent population. -#' set.seed(803) -#' source_A <- dat_highsim$Y_source[1:20, 1:10] -#' source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), -#' nrow(source_A), ncol(source_A)) #' res_multi <- learner( -#' Y_source = list(A = source_A, B = source_B), -#' Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, +#' Y_source = dat_multisource$Y_source, +#' Y_target = dat_multisource$Y_target, r = 3, #' source_weights = c(0.7, 0.3), #' lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, #' step_size = 0.0003, control = list(max_iter = 100)) diff --git a/data-raw/gen-data.R b/data-raw/gen-data.R index 32d302a..53a7863 100644 --- a/data-raw/gen-data.R +++ b/data-raw/gen-data.R @@ -55,3 +55,38 @@ dat_modsim <- list(Y_source = Y_source, Theta_target = Theta_target) usethis::use_data(dat_modsim, overwrite = TRUE) + +################################################################################ +## Multiple sources with different noise levels +################################################################################ + +# This block can be run on its own without regenerating the existing datasets. +local({ + set.seed(20261014) + p <- 100; q <- 50; r <- 3 + + Theta_target <- matrix(rnorm(p * r), p, r) %*% + matrix(rnorm(r * q), r, q) + target_svd <- svd(Theta_target, nu = r, nv = r) + # Both sources share the target's latent spaces, with reversed signal strengths. + Theta_source <- target_svd$u %*% + diag(rev(target_svd$d[seq_len(r)]), nrow = r, ncol = r) %*% + t(target_svd$v) + + # Variances, not standard deviations. Each call draws independent noise. + noise_variance <- c(target = 0.10, A = 0.01, B = 0.50) + Y_target <- Theta_target + matrix( + rnorm(p * q, sd = sqrt(noise_variance['target'])), p, q) + Y_A <- Theta_source + matrix( + rnorm(p * q, sd = sqrt(noise_variance['A'])), p, q) + Y_B <- Theta_source + matrix( + rnorm(p * q, sd = sqrt(noise_variance['B'])), p, q) + + dat_multisource <- list( + Y_source = list(A = Y_A, B = Y_B), + Y_target = Y_target, + Theta_source = list(A = Theta_source, B = Theta_source), + Theta_target = Theta_target + ) + usethis::use_data(dat_multisource, overwrite = TRUE, version = 2) +}) diff --git a/data/dat_multisource.rda b/data/dat_multisource.rda new file mode 100644 index 0000000000000000000000000000000000000000..21395b444e2ef32a711d2e88a3337194eb58c8db GIT binary patch literal 212592 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%0DY@G&#{g9$tJ5*I_<6GC6^m-nQmn- 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≻bnd_p`B4 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
XA3Ke5f!fTl`lCb8PU4RHr;mwaWT_-j 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¥1m|B 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 UKXKQv5C8FZBvXY61A&#^VEk8;4*&oF literal 0 HcmV?d00001 diff --git a/inst/examples/multiple-sources.R b/inst/examples/multiple-sources.R index a3a39c7..7881e4e 100644 --- a/inst/examples/multiple-sources.R +++ b/inst/examples/multiple-sources.R @@ -1,11 +1,8 @@ -# Demonstration only: these are simulated matrices, not actual population data. -# The second source is constructed here to illustrate the multiple-source API. -set.seed(803) -source_A <- learner::dat_highsim$Y_source -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) -target <- learner::dat_highsim$Y_target -sources <- list(source_A, source_B) +# Demonstration using the built-in simulated multiple-source data. +# Each population has independently generated noise; source A is more precise. +sources <- learner::dat_multisource$Y_source +target <- learner::dat_multisource$Y_target +# Illustrative weights, not weights estimated or tuned from these data. weights <- c(0.7, 0.3) # Tune the U-space, V-space, and balance penalties separately. @@ -16,8 +13,8 @@ cv_result <- learner::cv.learner( lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), lambda_2_all = c(0.1, 1), - step_size = 0.003, n_folds = 3, - control = list(max_iter = 100) + step_size = 0.0003, n_folds = 3, + control = list(max_iter = 2000) ) print(c(row = cv_result$lambda_1_row_min, col = cv_result$lambda_1_col_min, @@ -31,12 +28,12 @@ fit <- learner::learner( lambda_1_row = cv_result$lambda_1_row_min, lambda_1_col = cv_result$lambda_1_col_min, lambda_2 = cv_result$lambda_2_min, - step_size = 0.003, control = list(max_iter = 100) + step_size = 0.0003, control = list(max_iter = 5000) ) print(fit$learner_estimate[1:5, 1:5]) print(fit$convergence_criterion) # 1: objective-change threshold; 2: iteration limit; 3: excessive growth/breakdown. -# This short example tests the workflow, not scientific performance or convergence. +# This example demonstrates the workflow, not scientific performance or convergence. # D-LEARNER also accepts multiple sources with the same weights. direct_fit <- learner::dlearner(sources, target, r = 3, source_weights = weights) diff --git a/man/cv.learner.Rd b/man/cv.learner.Rd index 8cea0ab..609ca27 100644 --- a/man/cv.learner.Rd +++ b/man/cv.learner.Rd @@ -20,76 +20,75 @@ cv.learner( ) } \arguments{ -\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} +\item{Y_source}{Source population data, as in \code{\link{learner}}. +Supply a matrix for one source or a list of matrices for multiple sources.} -\item{Y_target}{nonempty numeric matrix containing target population data. -Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry -must be observed. Infinite values are not allowed.} +\item{Y_target}{Target population data matrix, as in \code{\link{learner}}. +Only observed entries are used to evaluate predictions.} -\item{r}{optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source.} +\item{r}{Rank used in every fold, as in \code{\link{learner}}. It is not +selected by cross-validation.} -\item{lambda_1_all}{nonempty vector of finite nonnegative candidate common penalties. Use this or both separate grids, not both modes.} +\item{lambda_1_all}{Candidate values for the common source-space penalty +\eqn{\lambda_1}. All combinations with \code{lambda_2_all} are evaluated.} -\item{lambda_2_all}{nonempty vector of finite nonnegative candidate balance penalties.} +\item{lambda_2_all}{Candidate values for the balance penalty \eqn{\lambda_2}.} -\item{step_size}{numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm, as in \code{\link{learner}}} +\item{step_size}{Step size for each fit, as in \code{\link{learner}}.} -\item{n_folds}{integer from 2 to the number of observed target entries. The default is \code{4}.} +\item{n_folds}{Number of cross-validation folds. Default is \code{4}.} -\item{n_cores}{positive integer specifying the requested number of OpenMP -threads. Candidate penalty combinations are evaluated in parallel when the -package is built with OpenMP support; otherwise they run sequentially. -The default is \code{1}.} +\item{n_cores}{Number of CPU cores used to evaluate the candidate penalty +combinations in parallel. Default is \code{1}, i.e., no parallelization.} -\item{control}{a list of parameters for controlling the stopping criteria for the numerical optimization algorithm, as in \code{\link{learner}}.} +\item{control}{Optimization settings for each fit; see \code{\link{learner}}.} -\item{lambda_1_row_all}{nonempty vector of finite nonnegative candidate penalties on U; requires \code{lambda_1_col_all}.} +\item{lambda_1_row_all, lambda_1_col_all}{Candidate values for separate +penalties on the row and column factors. Supply both instead of +\code{lambda_1_all}; all combinations with \code{lambda_2_all} are evaluated.} -\item{lambda_1_col_all}{nonempty vector of finite nonnegative candidate penalties on V; requires \code{lambda_1_row_all}.} - -\item{source_weights}{optional finite nonnegative numeric vector with one -weight per source and at least one positive value. Weights correspond to the -order of matrices in \code{Y_source}; names are not used for matching. They -are normalized to sum to one. \code{NULL} gives equal weights. For a single -source, a supplied weight must be a positive scalar and is normalized to one.} +\item{source_weights}{Relative source weights, as in \code{\link{learner}}. The weights +are not selected by cross-validation. Default is equal weights.} } \value{ -A list with the following elements: -\item{lambda_1_min}{selected common penalty; \code{NA} in separate mode.} -\item{lambda_1_row_min, lambda_1_col_min}{selected separate penalties, returned only in separate mode.} -\item{lambda_2_min}{balance penalty in the combination with the smallest held-out squared-error sum.} -\item{mse_all}{held-out squared-error sums, retaining the historical scale despite the name. Common mode returns a matrix indexed by \code{lambda_1_all} and \code{lambda_2_all}. Separate mode returns a three-dimensional array indexed by \code{lambda_1_row_all}, \code{lambda_1_col_all}, and \code{lambda_2_all}, in that order.} -\item{r}{rank value used.} +A list containing: +\item{lambda_1_min}{Common source-space penalty in the selected combination. +\code{NA} when separate penalties are used.} +\item{lambda_2_min}{Balance penalty in the selected combination.} +\item{lambda_1_row_min, lambda_1_col_min}{Row and column penalties in the +selected combination. Returned only when separate penalties are used.} +\item{mse_all}{Sum of held-out squared errors for each candidate combination. +For common penalties, rows correspond to \code{lambda_1_all} and columns to +\code{lambda_2_all}. For separate penalties, the three dimensions correspond +to the row, column, and balance grids, in that order. Divide by the number +of observed target entries to obtain mean squared errors.} +\item{r}{Rank used in the fits.} } \description{ -Select common or separate source-space penalties and a balance penalty for -\code{\link{learner}} by k-fold cross-validation on observed target entries. -One source matrix or a list of source matrices can be supplied. +Performs k-fold cross-validation to select the source-space and balance +penalties for \code{\link{learner}}. Returns prediction errors and the +penalty combination with the smallest error. } \details{ -Each observed target entry is held out once. The same folds are used for all -candidates. Dividing \code{mse_all} by the number of observed target entries -gives the mean squared error without changing the selected candidate. Ties -select the first minimum in R array order. Existing missing entries are -excluded from validation. Rank, source spaces, source weights, and the -first-source initialization are fixed across folds. The function tunes -penalties, not rank or source weights. Call \code{set.seed()} before -\code{cv.learner()} to reproduce the fold allocation. -For independent penalties, supply both separate grids and \code{lambda_2_all}. -Candidates with non-finite errors are excluded with a warning; if all fail, -the function stops. Finite fits that reach the iteration limit are retained, -so selecting a candidate does not certify convergence of its fold fits. -Assess the optimization settings as well as the final refit. - -The return value contains scores and selected parameters, not a final fitted -target matrix. Refit with \code{learner()} using all observed target entries -and the selected penalties, while retaining the same sources, weights, and rank. - -Observed target entries are randomly partitioned into approximately equal -folds. Each fold is set to missing in the training matrix, and the fitted -matrix is used to predict the held-out entries. Errors are summed across -folds for each candidate pair (common penalties) or triple (separate -penalties). Source entries are not held out. See McGrath et al. (2024). +Given sets of candidate values of \eqn{\lambda_1} and \eqn{\lambda_2}, this +function performs k-fold cross-validation to select the pair +\eqn{(\lambda_1, \lambda_2)} with the smallest held out error. This function +randomly partitions the entries of \code{Y_target} into \eqn{k} +(approximately) equally sized subsamples. The training data sets are obtained +by removing one of the \eqn{k} subsamples and the corresponding test data +sets are based on the held out subsamples. The \code{\link{learner}} function is +applied to each training data set. The held out error is computed by comparing +the entries in the test data sets with those imputed from the LEARNER +estimates. See McGrath et al. (2024) for further details. + +With separate penalties, the procedure selects the row, column, and balance +penalties together. Both modes can be used with multiple sources. Call +\code{set.seed()} first to reproduce the random partition. + +To obtain the final estimate, apply \code{\link{learner}} with the selected +penalties to the full data (see Examples). Fits that reach the iteration +limit still contribute to the held out error, so check the settings in +\code{control} before interpreting the selection. } \examples{ res <- cv.learner(Y_source = dat_highsim$Y_source, @@ -98,19 +97,20 @@ res <- cv.learner(Y_source = dat_highsim$Y_source, lambda_2_all = c(1, 10, 100), step_size = 0.003) -# A small simulated example: source B is a perturbation of source A, -# not an independent population. Weights are fixed demonstration values. -set.seed(803) -source_A <- dat_highsim$Y_source[1:20, 1:10] -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) -sources <- list(A = source_A, B = source_B) -target <- dat_highsim$Y_target[1:20, 1:10] +# Refit using the selected penalties and all observed target entries. +fit <- learner(dat_highsim$Y_source, dat_highsim$Y_target, r = res$r, + lambda_1 = res$lambda_1_min, lambda_2 = res$lambda_2_min, + step_size = 0.003) +fit$convergence_criterion + +# Multiple sources with fixed demonstration weights. +sources <- dat_multisource$Y_source +target <- dat_multisource$Y_target weights <- c(0.7, 0.3) set.seed(815) res_separate <- cv.learner( - Y_source = sources, Y_target = target, r = 2, + Y_source = sources, Y_target = target, r = 3, source_weights = weights, lambda_1_row_all = c(1, 10), lambda_1_col_all = c(1, 3), lambda_2_all = c(0.1, 1), step_size = 0.0003, @@ -132,3 +132,6 @@ fit$convergence_criterion \references{ McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer learning method for low-rank matrix estimation}. arXiv preprint arXiv:2412.20605. } +\seealso{ +\code{\link{learner}} for fitting with the selected penalties. +} diff --git a/man/dat_multisource.Rd b/man/dat_multisource.Rd new file mode 100644 index 0000000..32bc0f1 --- /dev/null +++ b/man/dat_multisource.Rd @@ -0,0 +1,54 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/data-multisource.R +\docType{data} +\name{dat_multisource} +\alias{dat_multisource} +\title{Simulated data set: Multiple sources with different noise levels} +\format{ +A list with four components. All matrices have 100 rows and 50 columns, + with corresponding rows and columns aligned across populations. +\describe{ + \item{\code{Y_source}}{A named list of observed source matrices, \code{A} + and \code{B}, suitable for the \code{Y_source} argument.} + \item{\code{Y_target}}{The observed target matrix.} + \item{\code{Theta_source}}{A named list of the true rank-3 source matrices, + \code{A} and \code{B}. The two true matrices are identical.} + \item{\code{Theta_target}}{The true rank-3 target matrix.} +} +} +\usage{ +dat_multisource +} +\description{ +Simulated matrices for one target population and two source populations. +The sources share the target's latent row and column spaces, but source A +is observed more precisely than source B. This data set illustrates fitting +LEARNER and D-LEARNER with multiple sources. +} +\details{ +The source signal is obtained by reversing the three nonzero singular values +of the target signal while keeping its singular vectors. Each observed matrix +is generated by adding independent, mean-zero normal noise to its own signal. +Noise is independent across entries and populations, with variances 0.10 for +the target, 0.01 for source A, and 0.50 for source B. Thus, source A has less +noise than the target and source B has more. Source B is not generated from +the observed source A matrix. + +The true signal matrices are included for illustration and estimation-error +calculations; they are not inputs to the fitting functions. These simulated +data are a usage example, not a reproduction of the paper's simulation study. +} +\examples{ +data(dat_multisource) +fit <- dlearner( + Y_source = dat_multisource$Y_source, + Y_target = dat_multisource$Y_target, + r = 3, source_weights = c(0.7, 0.3) +) +dim(fit$dlearner_estimate) +} +\seealso{ +\code{\link{dat_highsim}}, \code{\link{dat_modsim}}, + \code{\link{learner}}, \code{\link{dlearner}} +} +\keyword{datasets} diff --git a/man/dlearner.Rd b/man/dlearner.Rd index a0be094..4d9bbbb 100644 --- a/man/dlearner.Rd +++ b/man/dlearner.Rd @@ -7,18 +7,18 @@ dlearner(Y_source, Y_target, r, source_weights = NULL) } \arguments{ -\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} +\item{Y_source}{Matrix containing the source population data, with the same rows +and columns as \code{Y_target}. For multiple sources, supply a list of +matrices.} -\item{Y_target}{nonempty numeric target matrix with only finite values. -Unlike \code{learner()}, this function does not allow missing target entries.} +\item{Y_target}{Matrix containing the target population data. Missing entries +are not allowed.} -\item{r}{optional common rank of the retained source spaces. If omitted, ScreeNOT selects it from the first source.} +\item{r}{Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +is applied to the source population data to select the rank.} -\item{source_weights}{optional finite nonnegative numeric vector with one -weight per source and at least one positive value. Weights correspond to the -order of matrices in \code{Y_source}; names are not used for matching. They -are normalized to sum to one. \code{NULL} gives equal weights. For a single -source, a supplied weight must be a positive scalar and is normalized to one.} +\item{source_weights}{Relative weights of the sources, in the order of +\code{Y_source}. Default is equal weights.} } \value{ A list with the following components: @@ -26,42 +26,57 @@ A list with the following components: \item{r}{rank value used.} } \description{ -Estimate a target signal matrix by applying source-space operators directly -to the target data, using the D-LEARNER method (McGrath et al. 2024). -One source matrix or a list of source matrices can be supplied. +This function applies the Direct projection LatEnt spAce-based tRaNsfer +lEaRning (D-LEARNER) method (McGrath et al. 2024) to leverage data from a source +population to improve estimation of a low-rank matrix in an underrepresented +target population. } \details{ -Each source has the same dimensions as the target matrix \eqn{Y_0}. -Let \eqn{B_{U,k}} and \eqn{B_{V,k}} contain the first \eqn{r} left and -right singular vectors of source \eqn{k}. The estimate is -\deqn{\widehat\Theta_0 = \bar P_U Y_0 \bar P_V, \qquad -\bar P_U=\sum_k w_k B_{U,k}B_{U,k}^\top, \qquad -\bar P_V=\sum_k w_k B_{V,k}B_{V,k}^\top.} -The nonnegative weights are normalized to sum to one and default to equal -values. With one source this reduces to projection onto its retained left -and right singular spaces. With multiple sources, the averaged operators -need not be idempotent, and the estimate can have rank larger than \code{r}. -This is not an average of the raw source matrices or of separately computed -single-source D-LEARNER estimates. +\strong{Data and notation:} -D-LEARNER does not iteratively fit target factors and has no space or balance -penalty parameters. All inputs must have aligned rows and columns and no -missing values. Names are not matched. If \code{r} is omitted, it is selected -from the first source, including when that source has zero weight. -Output dimension names are taken from the first source. +The data consists of a matrix in the target population +\eqn{Y_0 \in \mathbb{R}^{p \times q}} and the source population +\eqn{Y_1 \in \mathbb{R}^{p \times q}}. Let +\eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. + +For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +\eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +is the target population knowledge graph, \eqn{\Theta_0}. + +\strong{Estimation:} + +This method estimates \eqn{\Theta_0} by + +\deqn{\hat U_1\hat U_1^\top Y_0 \hat V_1\hat V_1^\top.} + +That is, the target population data are projected onto the latent row and +column spaces of the source population, without iterative fitting or penalty +selection. + +\strong{Multiple sources:} + +Supply a list of source matrices to combine information from several +populations. Their projection operators are averaged using +\code{source_weights}, and the estimate becomes + +\deqn{\widehat\Theta_0=\bar P_U Y_0\bar P_V,\qquad +\bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad +\bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} + +where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +the \eqn{k}th source and the weights \eqn{w_k} are given by +\code{source_weights}. If \code{r} is not specified, the rank is selected +using the first source. } \examples{ res <- dlearner(Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target) -# A second simulated source, made by perturbing the first. -set.seed(803) -source_A <- dat_highsim$Y_source[1:20, 1:10] -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) +# Multiple sources with fixed demonstration weights. res_multi <- dlearner( - Y_source = list(A = source_A, B = source_B), - Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, + Y_source = dat_multisource$Y_source, + Y_target = dat_multisource$Y_target, r = 3, source_weights = c(0.7, 0.3)) dim(res_multi$dlearner_estimate) @@ -69,3 +84,7 @@ dim(res_multi$dlearner_estimate) \references{ Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. } +\seealso{ +\code{\link{learner}} for a target fit that can adapt the source +spaces through penalized estimation. +} diff --git a/man/learner.Rd b/man/learner.Rd index fc8e9be..96de9bb 100644 --- a/man/learner.Rd +++ b/man/learner.Rd @@ -18,132 +18,130 @@ learner( ) } \arguments{ -\item{Y_source}{numeric source matrix or nonempty list of numeric source matrices. Every source must have the same dimensions and corresponding row/column order as \code{Y_target}, and contain no missing or infinite values.} +\item{Y_source}{Matrix containing the source population data, with the same rows +and columns as \code{Y_target}. For multiple sources, supply a list of +matrices.} -\item{Y_target}{nonempty numeric matrix containing target population data. -Missing entries (\code{NA} or \code{NaN}) are allowed, but at least one entry -must be observed. Infinite values are not allowed.} +\item{Y_target}{Matrix containing the target population data. Missing entries +are allowed.} -\item{r}{optional common rank for the source spaces and target factors, between 1 and the smaller matrix dimension. If omitted, ScreeNOT selects it from the first source.} +\item{r}{Rank of the knowledge graphs. If omitted, ScreeNOT (Donoho et al. 2023) +is applied to the source population data to select the rank.} -\item{lambda_1}{finite nonnegative numeric scalar specifying the common space penalty. Supply either this parameter or both \code{lambda_1_row} and \code{lambda_1_col}, not both modes.} +\item{lambda_1}{Common source-space penalty. Larger values borrow more +information from the source population; zero does not use the source spaces. +Use \code{\link{cv.learner}} to select penalty values.} -\item{lambda_2}{finite nonnegative numeric scalar for the balance penalty (see Details)} +\item{lambda_2}{Balance penalty. Larger values more strongly encourage +\eqn{U} and \eqn{V} to have comparable scale.} -\item{step_size}{numeric scalar specifying the step size for the scaled gradient steps in the numerical optimization algorithm} +\item{step_size}{Step size in the optimization algorithm. Smaller values may +reduce oscillation but require more iterations.} -\item{control}{list controlling the numerical optimization: +\item{control}{List of settings controlling when optimization stops: \describe{ -\item{\code{max_iter}}{Positive integer giving the maximum number of updates; -default \code{100}.} -\item{\code{threshold}}{Finite nonnegative absolute objective-change threshold; -default \code{0.001}. Starting with the second update, the algorithm stops -when \eqn{|L_t-L_{t-1}|} is strictly less than this value. This rule alone does -not establish factor stability or a global optimum.} -\item{\code{max_value}}{Finite positive multiplier; default \code{10}. -Starting with the second update, the algorithm stops if the current objective -exceeds this multiplier times the preceding objective (unless the -objective-change stopping rule has already been met).}}} - -\item{lambda_1_row}{optional finite nonnegative numeric scalar for the source-space penalty on \eqn{U} (the SNP side when SNPs are rows). Must be supplied together with \code{lambda_1_col}.} - -\item{lambda_1_col}{optional finite nonnegative numeric scalar for the source-space penalty on \eqn{V} (the phenotype side when phenotypes are columns). Must be supplied together with \code{lambda_1_row}.} - -\item{source_weights}{optional finite nonnegative numeric vector with one -weight per source and at least one positive value. Weights correspond to the -order of matrices in \code{Y_source}; names are not used for matching. They -are normalized to sum to one. \code{NULL} gives equal weights. For a single -source, a supplied weight must be a positive scalar and is normalized to one.} +\item{\code{max_iter}}{Maximum number of updates; default \code{100}.} +\item{\code{threshold}}{Stop when the absolute change between consecutive +objective values is smaller than this value; default \code{0.001}.} +\item{\code{max_value}}{Stop if the objective grows beyond this multiple of +its preceding value; default \code{10}.}}} + +\item{lambda_1_row, lambda_1_col}{Separate source-space penalties for the row +factor \eqn{U} and column factor \eqn{V}, respectively. Use these when the two +sides should borrow different amounts of source information. Supply both +instead of \code{lambda_1}; equal values reproduce the common penalty.} + +\item{source_weights}{Relative weights of the sources, in the order of +\code{Y_source}. Default is equal weights.} } \value{ A list with the following elements: -\item{learner_estimate}{target signal estimate with the same dimensions as \code{Y_target}, formed from the factors with the lowest objective encountered, including the initialization.} -\item{objective_values}{objective values after each update, excluding the initialization. The last value need not correspond to the returned estimate.} +\item{learner_estimate}{matrix containing the LEARNER estimate of the target population knowledge graph.} +\item{objective_values}{numeric vector containing the values of the objective function at each iteration.} \item{convergence_criterion}{stopping code: \code{1}, the absolute objective-change threshold was met; \code{2}, the iteration limit was reached; \code{3}, excessive objective growth or non-finite objective/factors were detected.} \item{r}{rank value used.} } \description{ -Estimate a low-rank target matrix using information from one or more source -populations with the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) method -(McGrath et al. 2024). The two source-space penalties can share a coefficient -or use separate coefficients. +This function applies the LatEnt spAce-based tRaNsfer lEaRning (LEARNER) +method (McGrath et al. 2024) to leverage data from a source population to improve +estimation of a low-rank matrix in an underrepresented target population. } \details{ -\strong{Data and source spaces:} - -Let \eqn{Y_0} be the \eqn{p \times q} target matrix, and let -\eqn{Y_1,\ldots,Y_K} be aligned source matrices of the same dimensions. -The target signal is estimated by \eqn{UV^\top}, with -\eqn{U \in \mathbb{R}^{p \times r}} and -\eqn{V \in \mathbb{R}^{q \times r}}. For each source, the first \eqn{r} -left and right singular vectors form bases \eqn{B_{U,k}} and \eqn{B_{V,k}}. -The source operators are -\deqn{\bar P_U = \sum_{k=1}^K w_k B_{U,k}B_{U,k}^\top, \qquad - \bar P_V = \sum_{k=1}^K w_k B_{V,k}B_{V,k}^\top,} -where \eqn{w_k \geq 0} and \eqn{\sum_k w_k=1}. -These combine source spaces, not the raw source matrices. The averaged -operators are generally not orthogonal projectors themselves. -Rows and columns must already be aligned by the caller; names are not matched. - -\strong{Objective and penalties:} - -The numerical algorithm seeks to minimize -\deqn{L(U,V) = \rho^{-1}\|\mathcal{P}_{\Omega}(UV^\top-Y_0)\|_F^2 - + \lambda_{1,\mathrm{row}}\|(I-\bar P_U)U\|_F^2 - + \lambda_{1,\mathrm{col}}\|(I-\bar P_V)V\|_F^2 - + \lambda_2\|U^\top U-V^\top V\|_F^2.} -Here \eqn{\Omega} contains the observed target entries, -\eqn{\mathcal{P}_{\Omega}} keeps residuals at those entries and sets the -others to zero, and \eqn{\rho=|\Omega|/(pq)} is the observed fraction. -With no missing entries, \eqn{\rho=1}. Source matrices must be complete. - -Supply \code{lambda_1} for a common space penalty, or supply both -\code{lambda_1_row} and \code{lambda_1_col} and leave \code{lambda_1=NULL}. -Mixing the modes or providing only one separate coefficient is an error. -Equal separate coefficients reproduce the common-penalty calculation under -the same inputs and optimization settings. The balance penalty -\code{lambda_2} is required in either mode. -The space penalties square the residual from the weighted source operator; -they are not weighted sums of separately squared per-source residuals. -Use \code{\link{cv.learner}} to select common or separate penalty values. - -\strong{Initialization and stopping:} - -If \code{r} is omitted, ScreeNOT selects it from the first source, with a -minimum of one. A supplied \code{r} is used directly. The first source is -always used to initialize the target factors, including when its weight is -zero. Reordering sources with their weights therefore preserves the weighted -operators for fixed \code{r}, but can change initialization and fitted results. -The initial factors are the first source's retained left and right singular -vectors, each multiplied by the square roots of its retained singular values. - -This function uses scaled gradient descent steps. Both gradients are -evaluated at the current \eqn{U} and \eqn{V}, then both matrices are updated. -Updates are repeated until a stopping criterion is satisfied. The returned -estimate uses the best objective encountered, not necessarily the final -iterate. Inspect \code{objective_values} and \code{convergence_criterion}; -code 1 only indicates that the stated objective-change rule was met. +\strong{Data and notation:} + +The data consists of a matrix in the target population +\eqn{Y_0\in\mathbb{R}^{p\times q}} and the source population +\eqn{Y_1\in\mathbb{R}^{p\times q}}. Let +\eqn{\hat U_k\hat\Lambda_k\hat V_k^\top} denote the rank-\eqn{r} truncated +singular value decomposition (SVD) of \eqn{Y_k}, \eqn{k = 0, 1}. + +For \eqn{k = 0, 1}, one can view \eqn{Y_k} as a noisy version of +\eqn{\Theta_k}, referred to as the knowledge graph. The target of inference +is the target population knowledge graph, \eqn{\Theta_0}. + +\strong{Estimation:} + +This method estimates \eqn{\Theta_0} by \eqn{\tilde U\tilde V^\top}, where +\eqn{(\tilde U, \tilde V)} is the solution to the following optimization problem + +\deqn{\mathrm{arg\,min}_{U\in\mathbb{R}^{p\times r},\,V\in\mathbb{R}^{q\times r}} + \big\{\|UV^\top-Y_0\|_F^2 + +\lambda_1\|\mathcal{P}_\perp(\hat U_1)U\|_F^2 + +\lambda_1\|\mathcal{P}_\perp(\hat V_1)V\|_F^2 + +\lambda_2\|U^\top U-V^\top V\|_F^2\big\}} + +where \eqn{\mathcal{P}_\perp(\hat U_1) = I - \hat U_1\hat U_1^\top} and +\eqn{\mathcal{P}_\perp(\hat V_1) = I - \hat V_1\hat V_1^\top}. The four terms measure +target fit, departures from the two source spaces, and imbalance between the +target factors, respectively. If \code{Y_target} has missing entries, the +first term is computed over the observed entries. + +At each iteration, \eqn{U} and \eqn{V} are updated by a gradient +descent step, starting from the truncated SVD of the source population data. +These updates are repeated until convergence. + +\strong{Extensions:} + +Separate penalties \code{lambda_1_row} and \code{lambda_1_col} replace +\eqn{\lambda_1} in the second and third terms. For multiple sources, the +source projectors are replaced by weighted combinations: + +\deqn{\bar P_U=\sum_{k=1}^K w_k\hat U_k\hat U_k^\top,\qquad + \bar P_V=\sum_{k=1}^K w_k\hat V_k\hat V_k^\top.} + +where \eqn{\hat U_k} and \eqn{\hat V_k} come from the truncated SVD of +the \eqn{k}th source and the weights \eqn{w_k} are given by +\code{source_weights}. The full objective is + +\deqn{\begin{aligned} +L(U,V) ={}& \|UV^\top-Y_0\|_F^2 \\ +&+\lambda_{1,\mathrm{row}}\|(I_p-\bar P_U)U\|_F^2 \\ +&+\lambda_{1,\mathrm{col}}\|(I_q-\bar P_V)V\|_F^2 \\ +&+\lambda_2\|U^\top U-V^\top V\|_F^2. +\end{aligned}} + +where \eqn{I_p} and \eqn{I_q} are identity matrices. Equal space penalties +give the common-penalty version. With multiple sources, the first source is +used to initialize the algorithm and, if \code{r} is not specified, to +select the rank. } \examples{ +# Single source with a common source-space penalty. res <- learner(Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target, lambda_1 = 1, lambda_2 = 1, step_size = 0.003) +# Same source, with separate penalties for the two factors. res_separate <- learner(Y_source = dat_highsim$Y_source, Y_target = dat_highsim$Y_target, lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, step_size = 0.003) # Multiple sources with fixed demonstration weights. -# Source B is a perturbed copy, not an independent population. -set.seed(803) -source_A <- dat_highsim$Y_source[1:20, 1:10] -source_B <- source_A + matrix(rnorm(length(source_A), sd = 0.3), - nrow(source_A), ncol(source_A)) res_multi <- learner( - Y_source = list(A = source_A, B = source_B), - Y_target = dat_highsim$Y_target[1:20, 1:10], r = 2, + Y_source = dat_multisource$Y_source, + Y_target = dat_multisource$Y_target, r = 3, source_weights = c(0.7, 0.3), lambda_1_row = 10, lambda_1_col = 1, lambda_2 = 1, step_size = 0.0003, control = list(max_iter = 100)) @@ -157,3 +155,7 @@ McGrath, S., Zhu, C,. Guo, M. and Duan, R. (2024). \emph{LEARNER: A transfer lea Donoho, D., Gavish, M. and Romanov, E. (2023). \emph{ScreeNOT: Exact MSE-optimal singular value thresholding in correlated noise}. The Annals of Statistics, 51(1), pp.122-148. } +\seealso{ +\code{\link{cv.learner}} for penalty selection; +\code{\link{dlearner}} for direct projection onto source spaces. +} diff --git a/tests/testthat/test-multisource-data.R b/tests/testthat/test-multisource-data.R new file mode 100644 index 0000000..0cecc33 --- /dev/null +++ b/tests/testthat/test-multisource-data.R @@ -0,0 +1,31 @@ +test_that('multiple-source example contains aligned observations and rank-three signals', { + d <- dat_multisource + expect_named(d, c('Y_source', 'Y_target', 'Theta_source', 'Theta_target')) + expect_named(d$Y_source, c('A', 'B')) + expect_named(d$Theta_source, c('A', 'B')) + matrices <- c(d$Y_source, list(d$Y_target), d$Theta_source, list(d$Theta_target)) + for (m in matrices) { + expect_identical(dim(m), c(100L, 50L)) + expect_true(all(is.finite(m))) + } + for (m in c(d$Theta_source, list(d$Theta_target))) { + expect_equal(sum(svd(m, nu = 0, nv = 0)$d > 1e-8), 3) + } + expect_equal(d$Theta_source$A, d$Theta_source$B) + target <- svd(d$Theta_target, nu = 3, nv = 3) + source <- svd(d$Theta_source$A, nu = 3, nv = 3) + expect_equal(tcrossprod(target$u), tcrossprod(source$u), tolerance = 1e-10) + expect_equal(tcrossprod(target$v), tcrossprod(source$v), tolerance = 1e-10) +}) + +test_that('saved example has the intended noise scales without shared source noise', { + d <- dat_multisource + noise <- cbind(target = as.vector(d$Y_target - d$Theta_target), + A = as.vector(d$Y_source$A - d$Theta_source$A), + B = as.vector(d$Y_source$B - d$Theta_source$B)) + # Fixed saved data: broad checks catch variance/SD mistakes and reused noise. + ratios <- apply(noise, 2, var) / c(target = 0.10, A = 0.01, B = 0.50) + expect_true(all(ratios > 0.8 & ratios < 1.2)) + correlations <- cor(noise) + expect_true(all(abs(correlations[upper.tri(correlations)]) < 0.05)) +}) From d439afb39d9fe489e3caa5f66bdd904b7ec3b562 Mon Sep 17 00:00:00 2001 From: Jodie Li Date: Thu, 15 Oct 2026 22:28:04 -0500 Subject: [PATCH 4/4] Clarify missing-data scaling and compare source choices --- R/learner.R | 4 +++- inst/examples/multiple-sources.R | 20 ++++++++++++++++++++ man/learner.Rd | 4 +++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/R/learner.R b/R/learner.R index c668e7d..00fc67d 100644 --- a/R/learner.R +++ b/R/learner.R @@ -230,7 +230,9 @@ cv.learner <- function(Y_source, Y_target, r, lambda_1_all = NULL, lambda_2_all, #' \eqn{\mathcal{P}_\perp(\hat V_1) = I - \hat V_1\hat V_1^\top}. The four terms measure #' target fit, departures from the two source spaces, and imbalance between the #' target factors, respectively. If \code{Y_target} has missing entries, the -#' first term is computed over the observed entries. +#' first term is computed over the observed entries and divided by the observed +#' fraction (the number of observed entries divided by \eqn{pq}). The penalty +#' terms are unchanged. The same adjustment applies to the extensions below. #' #' At each iteration, \eqn{U} and \eqn{V} are updated by a gradient #' descent step, starting from the truncated SVD of the source population data. diff --git a/inst/examples/multiple-sources.R b/inst/examples/multiple-sources.R index 7881e4e..61ea953 100644 --- a/inst/examples/multiple-sources.R +++ b/inst/examples/multiple-sources.R @@ -38,3 +38,23 @@ print(fit$convergence_criterion) # D-LEARNER also accepts multiple sources with the same weights. direct_fit <- learner::dlearner(sources, target, r = 3, source_weights = weights) print(dim(direct_fit$dlearner_estimate)) + +# Compare source choices using D-LEARNER at the same fixed rank. +# D-LEARNER avoids optimization and penalty tuning as additional differences. +# Truth is available only because this is a simulated dataset. +source_choices <- list( + A_only = sources$A, + B_only = sources$B, + Weighted_A_B = sources +) +comparison <- do.call(rbind, lapply(names(source_choices), function(label) { + estimate <- learner::dlearner( + source_choices[[label]], target, r = 3, + source_weights = if (label == 'Weighted_A_B') weights else NULL + )$dlearner_estimate + data.frame(source = label, + frobenius_error = norm(estimate - learner::dat_multisource$Theta_target, 'F')) +})) +print(comparison, row.names = FALSE) +# Lower Frobenius error is better. These are single-dataset results, not CV MSE. +# The weights 0.7/0.3 are illustrative; combining sources need not beat source A. diff --git a/man/learner.Rd b/man/learner.Rd index 96de9bb..a56809b 100644 --- a/man/learner.Rd +++ b/man/learner.Rd @@ -94,7 +94,9 @@ where \eqn{\mathcal{P}_\perp(\hat U_1) = I - \hat U_1\hat U_1^\top} and \eqn{\mathcal{P}_\perp(\hat V_1) = I - \hat V_1\hat V_1^\top}. The four terms measure target fit, departures from the two source spaces, and imbalance between the target factors, respectively. If \code{Y_target} has missing entries, the -first term is computed over the observed entries. +first term is computed over the observed entries and divided by the observed +fraction (the number of observed entries divided by \eqn{pq}). The penalty +terms are unchanged. The same adjustment applies to the extensions below. At each iteration, \eqn{U} and \eqn{V} are updated by a gradient descent step, starting from the truncated SVD of the source population data.