Skip to content

Commit 9209ccb

Browse files
committed
ENH: sparse pca evaluation
1 parent cd207f4 commit 9209ccb

File tree

4 files changed

+29
-42
lines changed

4 files changed

+29
-42
lines changed

R/multiscaleSVDxpts.R

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11730,7 +11730,6 @@ digraph NSA_Flow_FA {
1173011730
#' of the original `sparse_pca_imp()` while improving efficiency by:
1173111731
#' - precomputing X'X, using it for gradient/energy,
1173211732
#' - in-place trial updates to reduce allocations,
11733-
#' - lazy orthogonalization (every `orth_every` iterations),
1173411733
#' - retaining Armijo backtracking and adaptive LR scheduling.
1173511734
#'
1173611735
#' @param X numeric matrix (n x p), rows = observations, cols = variables
@@ -11747,7 +11746,6 @@ digraph NSA_Flow_FA {
1174711746
#' @param grad_tol numeric, gradient-norm tolerance for convergence
1174811747
#' @param nsa_flow_fn optional, nsa_flow function to use (default: nsa_flow)
1174911748
#' @param verbose logical, print iteration diagnostics
11750-
#' @param orth_every integer >=1, perform orthogonalization every this many iterations (default 5)
1175111749
#'
1175211750
#' @return list with components:
1175311751
#' \item{Y}{best p x k loading matrix found}
@@ -11768,8 +11766,7 @@ nsa_flow_pca <- function(X, k,
1176811766
w_pca = 1.0, nsa_w = 0.5,
1176911767
apply_soft_thresh_in_nns = FALSE,
1177011768
tol = 1e-6, retraction = def_ret,
11771-
grad_tol = 1e-4, nsa_flow_fn = nsa_flow_autograd, verbose = FALSE,
11772-
orth_every = 5) {
11769+
grad_tol = 1e-4, nsa_flow_fn = nsa_flow_autograd, verbose = FALSE) {
1177311770
# --- argument checks ---
1177411771
if (!is.matrix(X) || any(!is.finite(X))) stop("X must be a finite numeric matrix")
1177511772
n <- nrow(X); p <- ncol(X)
@@ -11779,8 +11776,6 @@ nsa_flow_pca <- function(X, k,
1177911776
if (w_pca <= 0) stop("w_pca must be positive")
1178011777
if (nsa_w < 0 || nsa_w > 1) stop("nsa_w must be in [0,1]")
1178111778
proximal_type <- match.arg(proximal_type)
11782-
if (!is.integer(orth_every) && orth_every != as.integer(orth_every)) orth_every <- as.integer(orth_every)
11783-
if (orth_every < 1) orth_every <- 1
1178411779

1178511780
# Preserve previous behavior: when using nsa_flow proximal with nonzero nsa_w,
1178611781
# disable the L1 lambda to avoid double regularization (as in your original impl).
@@ -11838,7 +11833,7 @@ nsa_flow_pca <- function(X, k,
1183811833

1183911834
for (iter in seq_len(max_iter)) {
1184011835
t_start <- Sys.time()
11841-
11836+
if (proximal_type != "nsa_flow") Y <- qr.Q(qr(Y))
1184211837
# Euclidean gradient: - (XtX %*% Y) / n scaled by w_pca
1184311838
grad_p <- - (XtX %*% Y) / n # p x k
1184411839
eu_grad <- w_pca * grad_p
@@ -11882,9 +11877,9 @@ nsa_flow_pca <- function(X, k,
1188211877
} else if (proximal_type == "nsa_flow") {
1188311878
# call nsa_flow; we assume it takes arguments (Y0, X0=NULL, w=..., retraction=...)
1188411879
# use X0 = NULL to indicate proximal-only processing of Y_ret
11885-
prox_res <- nsa_flow_fn( Y_ret, nsa_w )
11880+
prox_res <- nsa_flow_fn( Y_ret, w=nsa_w )
1188611881
if (!is.list(prox_res) || is.null(prox_res$Y)) stop("nsa_flow returned unexpected result")
11887-
Y_new <- prox_res$Y
11882+
Y_new <- prox_res$Y %>% apply( 2, function(x) (x - min(x)) / (max(x) - min(x)))
1188811883
} else {
1188911884
stop("unknown proximal_type")
1189011885
}
@@ -11897,16 +11892,8 @@ nsa_flow_pca <- function(X, k,
1189711892
if (k == 1) {
1189811893
Q <- Y_new / sqrt(sum(Y_new^2))
1189911894
} else {
11900-
if ((iter %% orth_every) == 0 || iter == max_iter) {
11901-
qr_decomp <- qr(Y_new)
11902-
Q <- qr.Q(qr_decomp)
11903-
# optionally replace Y_new with Q so iterate stays more orthonormal
11904-
Y_new <- Q
11905-
} else {
11906-
# use Q only for explained variance computation (do not change Y_new)
11907-
qr_decomp_tmp <- qr(Y_new)
11908-
Q <- qr.Q(qr_decomp_tmp)
11909-
}
11895+
qr_decomp <- qr(Y_new)
11896+
Q <- qr.Q(qr_decomp)
1191011897
}
1191111898

1191211899
# explained variance ratio

R/nsa_flow_torch.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,10 @@ nsa_flow_autograd <- function(
653653
# Try to call python function and handle errors clearly
654654
res_py <- tryCatch(
655655
{
656-
do.call(pynsa$nsa_flow_autograd, py_args)
656+
do.call(pynsa$nsa_flow_orth, py_args)
657657
},
658658
error = function(e) {
659-
stop("Error calling Python nsa_flow_autograd():\n", e$message)
659+
stop("Error calling Python nsa_flow_orth():\n", e$message)
660660
}
661661
)
662662

man/nsa_flow_pca.Rd

Lines changed: 1 addition & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vignettes/nsa_flow.Rmd

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ Y0_toy <- matrix(runif(12, 0, 1), 4, 3)
330330
omega_default = 0.5
331331
# if ( ! exists("ini_default") )
332332
lropts=c('armijo', 'armijo_aggressive', 'exponential', 'linear', 'random', 'adaptive', 'momentum_boost', 'entropy', 'poly_decay', 'bayes')
333-
ini_default = 'bayes' #
333+
ini_default = 'armijo' #
334334
optype='asgd' # for torch backend
335335
def_ret = "soft_polar"
336336
nsa_default <- function(Y0, w = omega_default,
@@ -344,7 +344,7 @@ nsa_default <- function(Y0, w = omega_default,
344344
verbose = verbose,
345345
seed = 42,
346346
apply_nonneg = TRUE,
347-
tol = 1e-6,
347+
tol = 1e-8,
348348
window_size=10,
349349
fidelity_type = "scale_invariant", #"symmetric",
350350
orth_type = "scale_invariant",
@@ -573,7 +573,7 @@ X0 = generate_synth_data( p, k, corrval=0.35, noise=0.05, sparse_prob=0.0, inclu
573573
###
574574
w_seq <- c( 0.005, 0.05, 0.1, 0.2, 0.5 )
575575
w_seq <- c( 0.001, 0.005, 0.01, 0.05, 0.25 )
576-
w_seq <- c( 0.001, 0.25, 0.5, 0.75, 0.9 )
576+
w_seq <- c( 0.1, 0.25, 0.5, 0.75, 0.9 )
577577
mytit = paste0("w = ", round(w_seq,3))
578578
mats <- list()
579579
convergeplots <- list()
@@ -603,10 +603,9 @@ for(i in seq_along(mats)) {
603603
}
604604
grid.arrange(grobs = lapply(swplots, function(x) x$gtable), ncol = 3)
605605
if ( length(convergeplots) >=4 ) {
606-
grid.arrange(grobs=convergeplots[c(1,2,3,5)],
607-
top='Convergence Plots for Different w Values', ncol=2 )
606+
grid.arrange(grobs=convergeplots[c(1,2,3,5)], top='Convergence Plots for Different w Values', ncol=2 )
608607
}
609-
608+
#########
610609
# darkk #
611610
####################
612611
```
@@ -1048,6 +1047,7 @@ golub_scaled <- scale(data.matrix(golub_df))
10481047

10491048
```{r golub_sparse_pca_analysis, echo=FALSE, fig.width=5, message=FALSE, warning=FALSE,cache=FALSE}
10501049
1050+
10511051
set.seed(1)
10521052
myk <- 3
10531053
mxit <- 100
@@ -1057,32 +1057,35 @@ golub_scaled_ss <- golub_scaled[, ss]
10571057
## --- PCA Variants ------------------------------------------------------------
10581058
pca_std <- prcomp(golub_scaled_ss, rank. = myk)
10591059
proj_std <- pca_std$x
1060-
1061-
res_basic <- nsa_flow_pca(golub_scaled_ss, myk,lambda = 0.1, alpha = 0.01,
1060+
myalph=0.1
1061+
res_basic <- nsa_flow_pca(golub_scaled_ss, myk, lambda = 0.1, alpha = myalph,
10621062
max_iter = mxit, proximal_type = "basic", tol = 1e-5,
1063-
nsa_w = omega_default, verbose = F)
1064-
res_nns <- nsa_flow_pca(golub_scaled_ss, myk, lambda = 0.1, alpha = 0.01,
1063+
nsa_w = omega_default, verbose = TRUE )
1064+
res_nns <- nsa_flow_pca(golub_scaled_ss, myk, lambda = 0.1, alpha = myalph,
10651065
max_iter = mxit, proximal_type = "nsa_flow", tol = 1e-5,
1066-
nsa_w = omega_default, nsa_flow_fn = nsa_default, verbose = FALSE)
1066+
nsa_w = omega_default, nsa_flow_fn = nsa_default, verbose = TRUE)
10671067
## --- Core Metrics ------------------------------------------------------------
10681068
metrics_pca_g <- compute_core_metrics(pca_std$rotation, golub_scaled_ss)
10691069
metrics_basic_g <- compute_core_metrics(res_basic$Y, golub_scaled_ss)
10701070
metrics_nns_g <- compute_core_metrics(res_nns$Y, golub_scaled_ss)
1071+
proj_basic <- golub_scaled_ss %*% res_basic$Y
1072+
proj_nns <- golub_scaled_ss %*% res_nns$Y
10711073
10721074
## --- Classification Performance ---------------------------------------------
10731075
cv_acc <- function(proj, labels) {
10741076
colnames(proj) <- paste0("V", seq_len(ncol(proj)))
10751077
ctrl <- trainControl(method = "repeatedcv", number = 5, repeats = 50)
1076-
model <- train(proj, labels, method = "knn",
1077-
trControl = ctrl, tuneGrid = data.frame(k = 3))
1078+
tune_grid <- expand.grid(mtry = seq(1, ncol(proj), length.out = 5)) # Example: tune mtry over 5 values
1079+
model <- train(proj, labels, method = "rf",
1080+
trControl = ctrl, tuneGrid = tune_grid)
10781081
tibble(Accuracy = model$results$Accuracy, AccuracySD = model$results$AccuracySD)
10791082
}
1080-
1081-
proj_basic <- golub_scaled_ss %*% res_basic$Y
1082-
proj_nns <- golub_scaled_ss %*% res_nns$Y
10831083
acc_std <- cv_acc(proj_std, labels)
10841084
acc_basic <- cv_acc(proj_basic, labels)
10851085
acc_nns <- cv_acc(proj_nns, labels)
1086+
# acc_std
1087+
# acc_basic
1088+
# acc_nns
10861089
10871090
## --- Integrated Results Table -----------------------------------------------
10881091
golub_metrics <- tibble(
@@ -1093,6 +1096,7 @@ golub_metrics <- tibble(
10931096
CV_Accuracy = c(acc_std$Accuracy, acc_basic$Accuracy, acc_nns$Accuracy),
10941097
CV_Accuracy_SD = c(acc_std$AccuracySD, acc_basic$AccuracySD, acc_nns$AccuracySD)
10951098
)
1099+
10961100
#####
10971101
```
10981102

0 commit comments

Comments
 (0)