Skip to content

Commit 44761f0

Browse files
committed
enh: add gle-mm, gle-admm, and cgl implementations
1 parent 1e4ae7b commit 44761f0

File tree

13 files changed

+584
-716
lines changed

13 files changed

+584
-716
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export(A)
44
export(L)
55
export(block_diag)
66
export(cluster_k_component_graph)
7+
export(fscore)
78
export(learn_bipartite_graph)
89
export(learn_bipartite_k_component_graph)
910
export(learn_combinatorial_graph_laplacian)

R/combinatorialGraphLaplacian.R

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
#' Selected Topics in Signal Processing, vol. 11, no. 6, pp. 825-841, Sept. 2017.
2323
#' Original MATLAB source code is available at: https://github.com/STAC-USC/Graph_Learning
2424
#' @export
25-
learn_combinatorial_graph_laplacian <- function(S, A_mask, alpha, prob_tol = 1e-4,
26-
max_cycle = 1000, regtype = 1, verbose = TRUE) {
25+
learn_combinatorial_graph_laplacian <- function(S, A_mask = NULL, alpha = 0, prob_tol = 1e-5,
26+
max_cycle = 10000, regtype = 1,
27+
record_objective = FALSE, verbose = TRUE) {
2728
n <- nrow(S)
29+
if (is.null(A_mask))
30+
A_mask <- matrix(1, n, n) - diag(n)
2831
e_v <- rep(1, n) / sqrt(n)
2932
dc_var <- t(e_v) %*% S %*% e_v
3033
isshifting <- c(abs(dc_var) < prob_tol)
@@ -48,6 +51,8 @@ learn_combinatorial_graph_laplacian <- function(S, A_mask, alpha, prob_tol = 1e-
4851
pb <- progress::progress_bar$new(format = "<:bar> :current/:total eta: :eta",
4952
total = max_cycle, clear = FALSE, width = 80)
5053
time_seq <- c(0)
54+
if (record_objective)
55+
fun <- vanilla.objective(O_best - (1/n), K)
5156
start_time <- proc.time()[3]
5257
for (i in c(1:max_cycle)) {
5358
O_old <- O
@@ -99,6 +104,8 @@ learn_combinatorial_graph_laplacian <- function(S, A_mask, alpha, prob_tol = 1e-
99104
}
100105
O_best <- O
101106
C_best <- C
107+
if (record_objective)
108+
fun <- c(fun, vanilla.objective(O_best - (1/n), K))
102109
if (verbose)
103110
pb$tick()
104111
time_seq <- c(time_seq, proc.time()[3] - start_time)
@@ -113,7 +120,10 @@ learn_combinatorial_graph_laplacian <- function(S, A_mask, alpha, prob_tol = 1e-
113120
}
114121
O <- O_best - (1 / n)
115122
C <- C_best - (1 / n)
116-
return(list(Laplacian = O, frob_norm = frob_norm, elapsed_time = time_seq))
123+
results <- list(Laplacian = O, frob_norm = frob_norm, elapsed_time = time_seq)
124+
if (record_objective)
125+
results$obj_fun <- fun
126+
return(results)
117127
}
118128

119129
update_sherman_morrison_diag <- function(O, C, shift, idx) {

R/graphLaplacianEstimation.R

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,28 @@ get_incidence_from_adjacency <- function(A) {
4040
#' IEEE Trans. on Signal Processing, vol. 67, no. 16, pp. 4231-4244, Aug. 2019
4141

4242
#' @export
43-
learn_laplacian_gle_mm <- function(S, A, alpha = 0, maxiter = 10000, reltol = 1e-5,
44-
abstol = 1e-5, record_objective = FALSE,
45-
verbose = TRUE) {
43+
learn_laplacian_gle_mm <- function(S, A_mask = NULL, alpha = 0, maxiter = 10000, reltol = 1e-5,
44+
record_objective = FALSE, verbose = TRUE) {
45+
# number of nodes
46+
p <- nrow(S)
4647
Sinv <- MASS::ginv(S)
47-
mask <- Ainv(A) > 0
48+
if (is.null(A_mask))
49+
A_mask <- matrix(1, p, p) - diag(p)
50+
mask <- Ainv(A_mask) > 0
4851
w <- w_init("naive", Sinv)[mask]
4952
wk <- w
50-
# number of nodes
51-
p <- nrow(S)
5253
# number of nonzero edges
53-
m <- .5 * sum(A > 0)
54+
m <- sum(mask)#.5 * sum(A_mask > 0)
5455
# l1-norm penalty factor
5556
J <- matrix(1, p, p) / p
5657
H <- 2 * diag(p) - p * J
5758
K <- S + alpha * H
58-
E <- get_incidence_from_adjacency(A)
59+
E <- get_incidence_from_adjacency(A_mask)
5960
R <- t(E) %*% K %*% E
6061
r <- nrow(R)
6162
G <- cbind(E, rep(1, p))
6263
if (record_objective)
63-
fun <- obj_func(E, K, wk, J)
64+
fun <- vanilla.objective(L(wk), K)
6465
if (verbose)
6566
pb <- progress::progress_bar$new(format = "<:bar> :current/:total eta: :eta",
6667
total = maxiter, clear = FALSE, width = 80)
@@ -72,11 +73,10 @@ learn_laplacian_gle_mm <- function(S, A, alpha = 0, maxiter = 10000, reltol = 1e
7273
Q <- Q[1:m, 1:m]
7374
wk <- sqrt(diag(Q) / diag(R))
7475
if (record_objective)
75-
fun <- c(fun, obj_func(E, K, wk, J))
76+
fun <- c(fun, vanilla.objective(L(wk), K))
7677
if (verbose)
7778
pb$tick()
78-
werr <- abs(w - wk)
79-
has_converged <- all(werr <= .5 * reltol * (w + wk)) || all(werr <= abstol)
79+
has_converged <- norm(w - wk, "2") / norm(w, "2") < reltol
8080
if (has_converged && k > 1) break
8181
w <- wk
8282
}
@@ -89,6 +89,7 @@ learn_laplacian_gle_mm <- function(S, A, alpha = 0, maxiter = 10000, reltol = 1e
8989
return(results)
9090
}
9191

92+
9293
obj_func <- function(E, K, w, J) {
9394
p <- ncol(J)
9495
EWEt <- E %*% diag(w) %*% t(E)
@@ -117,17 +118,20 @@ obj_func <- function(E, K, w, J) {
117118
#' Optimization Algorithms for Graph Laplacian Estimation via ADMM and MM.
118119
#' IEEE Trans. on Signal Processing, vol. 67, no. 16, pp. 4231-4244, Aug. 2019
119120
#' @export
120-
learn_laplacian_gle_admm <- function(S, A, alpha = 0, rho = 1, maxiter = 10000,
121-
reltol = 1e-5, record_objective = FALSE,
122-
verbose = TRUE) {
121+
learn_laplacian_gle_admm <- function(S, A_mask = NULL, alpha = 0, rho = 1, maxiter = 10000,
122+
reltol = 1e-5, record_objective = FALSE, verbose = TRUE) {
123123
p <- nrow(S)
124+
if (is.null(A_mask))
125+
A_mask <- matrix(1, p, p) - diag(p)
124126
Sinv <- MASS::ginv(S)
125127
w <- w_init("naive", Sinv)
126128
Yk <- L(w)
127129
Theta <- Yk
128-
P <- eigvec_sym(Yk)
129-
P <- P[, 2:p]
130130
Ck <- Yk
131+
C <- Ck
132+
# ADMM constants
133+
mu <- 2
134+
tau <- 2
131135
# l1-norm penalty factor
132136
J <- matrix(1, p, p) / p
133137
H <- 2 * diag(p) - p * J
@@ -136,23 +140,35 @@ learn_laplacian_gle_admm <- function(S, A, alpha = 0, rho = 1, maxiter = 10000,
136140
pb <- progress::progress_bar$new(format = "<:bar> :current/:total eta: :eta",
137141
total = maxiter, clear = FALSE, width = 80)
138142
if (record_objective)
139-
fun <- c()
143+
fun <- c(vanilla.objective(Theta, K))
140144
# ADMM loop
145+
# P <- qr.Q(qr(rep(1, p)), complete=TRUE)[, 2:p]
146+
P <- eigvec_sym(Yk)
147+
P <- P[, 2:p]
141148
for (k in c(1:maxiter)) {
142149
Gamma <- t(P) %*% (K + Yk - rho * Ck) %*% P
143150
U <- eigvec_sym(Gamma)
144151
lambda <- eigval_sym(Gamma)
145152
d <- .5 * c(sqrt(lambda ^ 2 + 4 / rho) - lambda)
146153
Xik <- crossprod(sqrt(d) * t(U))
147154
Thetak <- P %*% Xik %*% t(P)
148-
Ck <- (diag(pmax(0, diag(Yk / rho + Thetak))) +
149-
A * pmin(0, Yk / rho + Thetak))
150-
Yk <- Yk + rho * (Thetak - Ck)
155+
Ck_tmp <- Yk / rho + Thetak
156+
Ck <- (diag(pmax(0, diag(Ck_tmp))) +
157+
A_mask * pmin(0, Ck_tmp))
158+
Rk <- Thetak - Ck
159+
Yk <- Yk + rho * Rk
151160
if (record_objective)
152-
fun <- c(fun, aug_lag(K, P, Xik, Yk, Ck, d, rho))
153-
has_converged <- norm(Theta - Thetak, "F") / norm(Thetak, "F") < reltol
161+
fun <- c(fun, vanilla.objective(Thetak, K))
162+
normF_Rk <- norm(Rk, "F")
163+
has_converged <- norm(Theta - Thetak) / norm(Theta, "F") < reltol
154164
if (has_converged && k > 1) break
165+
#s <- rho * norm(C - Ck, "F")
166+
#if (normF_Rk > mu * s)
167+
# rho <- rho * tau
168+
#else if (s > mu * normF_Rk)
169+
# rho <- rho / tau
155170
Theta <- Thetak
171+
C <- Ck
156172
if (verbose)
157173
pb$tick()
158174
}

R/learnGraphTopology.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ learn_bipartite_graph <- function(S, is_data_matrix = FALSE, z = 0, nu = 1e4, al
560560
#' vertex.color = c("red","black")[V(estimated_bipartite)$type + 1],
561561
#' vertex.shape = c("square", "circle")[V(estimated_bipartite)$type + 1],
562562
#' vertex.label = NA, vertex.size = 5)
563+
563564
#' @export
564565
learn_bipartite_k_component_graph <- function(S, is_data_matrix = FALSE, z = 0, k = 1,
565566
w0 = "naive", m = 7, alpha = 0., beta = 1e4,

R/objectiveFunction.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,8 @@ joint.prior <- function(beta, nu, Lw, Aw, U, V, lambda, psi) {
4646
return(laplacian.prior(beta = beta, Lw = Lw, lambda = lambda, U = U) +
4747
bipartite.prior(nu = nu, Aw = Aw, psi = psi, V = V))
4848
}
49+
50+
vanilla.objective <- function(Theta, K) {
51+
p <- nrow(Theta)
52+
return(sum(diag(Theta %*% K)) - sum(log(eigval_sym(Theta)[2:p])))
53+
}

R/utils.R

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ relative_error <- function(A, B) {
2828
}
2929

3030

31+
#' Computes the fscore between two matrices
32+
#'
33+
#' @param A first matrix
34+
#' @param B second matrix
35+
#' @param eps real number such that edges whose values are smaller than eps are
36+
#' not considered in the computation of the fscore
37+
#' @examples
38+
#' library(spectralGraphTopology)
39+
#' X <- L(c(1, 0, 1))
40+
#' fscore(X, X)
41+
#' @export
42+
fscore <- function(A, B, eps = 1e-4) {
43+
return(metrics(A, B, eps)[1])
44+
}
45+
46+
3147
# Compute the prial value between two matrices
3248
# @param Ltrue true Laplacian matrix
3349
# @param Lest estimated Laplacian matrix

benchmarks/cospectral/example.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ set.seed(123)
88
p <- 15
99
f <- .05
1010
w <- runif(p * (p - 1) / 2)
11-
Laplacian <- L(w)
11+
Laplacian <- block_diag(L(w), L(w), L(w))
1212
lambda <- spectralGraphTopology:::eigval_sym(Laplacian)
1313
Adjacency <- diag(diag(Laplacian)) - Laplacian
1414
# construct the network

papers/1909.11594.pdf

974 KB
Binary file not shown.
7.74 KB
Binary file not shown.

tests/testthat/test-learnGraphTopology.R

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ test_that("learn_k_component_graph with single component random graph", {
1717

1818

1919
with_parameters_test_that("we can recover a simple connected graph with the GLE-MM and GLE-ADMM methods", {
20-
w <- sample(1:10, 6)
20+
w <- sample(0:10, 6)
2121
Laplacian <- L(w)
2222
n <- ncol(Laplacian)
23-
Y <- MASS::mvrnorm(n * 500, rep(0, n), MASS::ginv(Laplacian))
24-
res <- func(cov(Y), A = A(rep(1, length(w))), record_objective = TRUE)
23+
A_mask <- 1 * (Laplacian < 0)
24+
Y <- MASS::mvrnorm(n * 250, rep(0, n), MASS::ginv(Laplacian))
25+
res <- func(cov(Y), A_mask = A_mask, record_objective = TRUE, reltol = 1e-7)
2526
expect_true(res$convergence)
2627
expect_true(relative_error(Laplacian, res$Laplacian) < 1e-1)
2728
expect_true(metrics(Laplacian, res$Laplacian, 1e-1)[1] > .9)

0 commit comments

Comments
 (0)