dimc-toolkit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dimc/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from dimc.fit import fit, run_dimc, sweep_alpha, train_dimc
2
+ from dimc.config import DEFAULT_ALPHA_CANDIDATES, DEFAULT_SETTINGS
3
+
4
+ __all__ = [
5
+ "fit",
6
+ "run_dimc",
7
+ "sweep_alpha",
8
+ "train_dimc",
9
+ "DEFAULT_SETTINGS",
10
+ "DEFAULT_ALPHA_CANDIDATES",
11
+ ]
12
+
13
+
@@ -0,0 +1,23 @@
1
+ """
2
+ Multiplicative update rules for the DIMC alternating optimization
3
+ (Algorithm 1, lines 4-9 / Eqs. 13, 14, 16, and the graph-regularization
4
+ loss of Eq. 3/6).
5
+
6
+ Re-exports the individual update functions so callers can do either::
7
+
8
+ from dimc.algorithms import update_PC, update_PI, update_U
9
+ from dimc.algorithms.update_graph import update_PC
10
+ """
11
+
12
+ from dimc.algorithms.update_graph import update_PC, graph_regularization_loss
13
+ from dimc.algorithms.update_weights import update_PI
14
+ from dimc.algorithms.update_W import update_U
15
+ from dimc.algorithms.update_H import finetune_view_network
16
+
17
+ __all__ = [
18
+ "update_PC",
19
+ "graph_regularization_loss",
20
+ "update_PI",
21
+ "update_U",
22
+ "finetune_view_network",
23
+ ]
@@ -0,0 +1,50 @@
1
+ """
2
+ View-specific DNN fine-tuning step, producing the updated H(v) (Eqs. 17-23).
3
+
4
+ Eq. (17): min_{W_v,b_v} || H(v) - U(v) P(v) ||_F^2
5
+ This is exactly reconstruction of the NMF target U(v)P(v) by the DNN
6
+ output H(v), so a plain MSE gradient step implements Eqs. (18)-(23)
7
+ (backprop derives the same gradient as Eq. 20-23 automatically).
8
+ """
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+
13
+
14
+ def finetune_view_network(
15
+ encoder: nn.Sequential,
16
+ X: torch.Tensor,
17
+ target: torch.Tensor,
18
+ learning_rate: float = 1e-2,
19
+ ) -> float:
20
+ """
21
+ One gradient-descent step of Eq. (17) via backpropagation (Eqs. 18-23).
22
+
23
+ Parameters
24
+ ----------
25
+ encoder : torch.nn.Sequential
26
+ The view-specific encoder network, modified in place.
27
+ X : torch.Tensor
28
+ Input data for the samples this view observes, shape
29
+ ``(n_obs, input_dim)``.
30
+ target : torch.Tensor
31
+ Reconstruction target ``U(v) P(v)``, shape ``(n_obs, latent_dim)``.
32
+ learning_rate : float, default=1e-2
33
+ Plain gradient-descent step size.
34
+
35
+ Returns
36
+ -------
37
+ float
38
+ The MSE loss value before the gradient step is applied.
39
+ """
40
+ loss_fn = nn.MSELoss()
41
+ H = encoder(X)
42
+ loss = loss_fn(H, target)
43
+
44
+ encoder.zero_grad()
45
+ loss.backward()
46
+ with torch.no_grad():
47
+ for p in encoder.parameters():
48
+ p -= learning_rate * p.grad
49
+
50
+ return loss.item()
@@ -0,0 +1,42 @@
1
+ """
2
+ Multiplicative update rule for U(v), the per-view NMF basis matrix (Eq. 16).
3
+
4
+ Named ``update_W`` to match the requested package layout; note that in the
5
+ paper's own notation this updates U(v) (the basis matrix), not the graph
6
+ affinity matrix W(v) -- that construction lives in ``dimc.graph``.
7
+ """
8
+
9
+ import numpy as np
10
+
11
+ from dimc.config import EPS
12
+
13
+
14
+ def update_U(
15
+ U: np.ndarray,
16
+ H_full: np.ndarray,
17
+ P_full: np.ndarray,
18
+ ) -> np.ndarray:
19
+ """
20
+ Eq. (16):
21
+ U(v)_ij <- U(v)_ij * (H(v) P(v)^T)_ij / (U(v) P(v) P(v)^T)_ij
22
+ where H(v) = [H(v)_C, H(v)_I] and P(v) = [PC, P(v)_I] (Eq. 15).
23
+
24
+ Parameters
25
+ ----------
26
+ U : numpy.ndarray
27
+ Current basis matrix for this view, shape ``(latent_dim, latent_k)``.
28
+ H_full : numpy.ndarray
29
+ Full DNN latent representation for all samples this view observes,
30
+ shape ``(latent_dim, n_obs)``.
31
+ P_full : numpy.ndarray
32
+ Concatenated code matrix ``[PC, P(v)_I]``, shape
33
+ ``(latent_k, n_obs)``.
34
+
35
+ Returns
36
+ -------
37
+ numpy.ndarray
38
+ Updated basis matrix ``U(v)``, same shape as the input ``U``.
39
+ """
40
+ numerator = H_full @ P_full.T
41
+ denominator = U @ (P_full @ P_full.T) + EPS
42
+ return U * (numerator / denominator)
@@ -0,0 +1,87 @@
1
+ """
2
+ Multiplicative update rule for PC (the shared/graph-regularized code
3
+ matrix, Eq. 13) and the graph-regularization loss term (Eq. 3/6).
4
+
5
+ Model, per view v: H(v) = [H(v)_C ; H(v)_I] ~= U(v) [PC ; P(v)_I]
6
+ PC is shared across all views and is graph-regularized per view (Eq. 6);
7
+ P(v)_I is private to view v and carries no graph term.
8
+ """
9
+
10
+ from typing import List
11
+
12
+ import numpy as np
13
+
14
+ from dimc.config import EPS
15
+
16
+
17
+ def update_PC(
18
+ PC: np.ndarray,
19
+ U_list: List[np.ndarray],
20
+ H_complete_list: List[np.ndarray],
21
+ W_list: List[np.ndarray],
22
+ D_list: List[np.ndarray],
23
+ alpha_list: List[float],
24
+ ) -> np.ndarray:
25
+ """
26
+ Eq. (13):
27
+ PC_ij <- PC_ij * [ sum_v ( U(v)^T H(v)_C + alpha(v) PC W(v) )_ij ]
28
+ --------------------------------------------
29
+ [ sum_v ( U(v)^T U(v) PC + alpha(v) PC D(v) )_ij ]
30
+
31
+ Parameters
32
+ ----------
33
+ PC : numpy.ndarray
34
+ Current shared code matrix, shape ``(latent_k, n_complete)``.
35
+ U_list : List[numpy.ndarray]
36
+ Per-view basis matrices ``U(v)``, each shape
37
+ ``(latent_dim, latent_k)``.
38
+ H_complete_list : List[numpy.ndarray]
39
+ Per-view DNN latent representations restricted to complete
40
+ samples, each shape ``(latent_dim, n_complete)``.
41
+ W_list : List[numpy.ndarray]
42
+ Per-view affinity matrices, each shape
43
+ ``(n_complete, n_complete)``.
44
+ D_list : List[numpy.ndarray]
45
+ Per-view degree matrices, each shape
46
+ ``(n_complete, n_complete)``.
47
+ alpha_list : List[float]
48
+ Per-view graph-regularization strengths.
49
+
50
+ Returns
51
+ -------
52
+ numpy.ndarray
53
+ Updated shared code matrix ``PC``, same shape as the input.
54
+ """
55
+ numerator = np.zeros_like(PC)
56
+ denominator = np.zeros_like(PC) + EPS
57
+
58
+ for U, H_C, W, D, alpha in zip(U_list, H_complete_list, W_list, D_list, alpha_list):
59
+ numerator += U.T @ H_C + alpha * (PC @ W)
60
+ denominator += (U.T @ U) @ PC + alpha * (PC @ D)
61
+
62
+ return PC * (numerator / denominator)
63
+
64
+
65
+ def graph_regularization_loss(
66
+ PC: np.ndarray,
67
+ L: np.ndarray,
68
+ alpha: float,
69
+ ) -> float:
70
+ """
71
+ Eq. (3)/(6): alpha * trace(PC L PC^T), the local-invariance penalty.
72
+
73
+ Parameters
74
+ ----------
75
+ PC : numpy.ndarray
76
+ Shared code matrix, shape ``(latent_k, n_complete)``.
77
+ L : numpy.ndarray
78
+ Graph Laplacian for this view, shape ``(n_complete, n_complete)``.
79
+ alpha : float
80
+ Graph-regularization strength for this view.
81
+
82
+ Returns
83
+ -------
84
+ float
85
+ The scalar regularization loss value.
86
+ """
87
+ return alpha * np.trace(PC @ L @ PC.T)
@@ -0,0 +1,39 @@
1
+ """
2
+ Multiplicative update rule for P(v)_I, the private (per-view,
3
+ incomplete-sample) code matrix (Eq. 14).
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ from dimc.config import EPS
9
+
10
+
11
+ def update_PI(
12
+ PI: np.ndarray,
13
+ U: np.ndarray,
14
+ H_private: np.ndarray,
15
+ ) -> np.ndarray:
16
+ """
17
+ Eq. (14):
18
+ P(v)_I_ij <- P(v)_I_ij * (U(v)^T H(v)_I)_ij / (U(v)^T U(v) P(v)_I)_ij
19
+ (no graph term for the private/incomplete-view code)
20
+
21
+ Parameters
22
+ ----------
23
+ PI : numpy.ndarray
24
+ Current private code matrix for this view, shape
25
+ ``(latent_k, n_private)``.
26
+ U : numpy.ndarray
27
+ Basis matrix for this view, shape ``(latent_dim, latent_k)``.
28
+ H_private : numpy.ndarray
29
+ DNN latent representation restricted to this view's private
30
+ (view-only-observed) samples, shape ``(latent_dim, n_private)``.
31
+
32
+ Returns
33
+ -------
34
+ numpy.ndarray
35
+ Updated private code matrix ``P(v)_I``, same shape as the input.
36
+ """
37
+ numerator = U.T @ H_private
38
+ denominator = (U.T @ U) @ PI + EPS
39
+ return PI * (numerator / denominator)
dimc/config.py ADDED
@@ -0,0 +1,84 @@
1
+ """
2
+ Configuration constants and default settings for the DIMC package.
3
+
4
+ This module centralizes every numeric/string constant that appeared as a
5
+ literal in the original notebook (the "Multiple Features" experiment from
6
+ Section 4.1(ii) of the paper), plus the small numerical-stability constant
7
+ used throughout the multiplicative-update rules.
8
+
9
+ Nothing here changes the meaning of any constant -- values are copied
10
+ verbatim from the notebook's ``settings`` dict and the ``_EPS`` constant
11
+ used in the update rules.
12
+ """
13
+
14
+ from typing import Any, Dict
15
+
16
+ # ============================================================
17
+ # Numerical stability
18
+ # ============================================================
19
+ # Used in every multiplicative-update denominator (Eqs. 13, 14, 16) to
20
+ # avoid division by zero when a denominator term is exactly 0.
21
+ EPS: float = 1e-10
22
+
23
+ # ============================================================
24
+ # Default experiment settings
25
+ # ============================================================
26
+ # Defaults below reproduce the paper's "Multiple Features" experiment
27
+ # (Section 4.1(ii) and Fig. 4(b)): 10 classes x 200 samples, 76-D Fourier
28
+ # coefficients + 240-D pixel-average views, missing ratio (ISR) 0.2.
29
+ #
30
+ # IMPORTANT about alpha_1 / alpha_2: the paper does NOT claim alpha=60 works
31
+ # for any fou/pix file -- it is the value THEY found by two-fold cross
32
+ # validation on THEIR exact preprocessing (Section 4.3, Fig. 4b). The graph
33
+ # term grows with alpha and with how similar/dense your own data's k-NN
34
+ # graph turns out to be; too high overpowers reconstruction and over-smooths
35
+ # PC, which can make clustering much worse, not better (confirmed by
36
+ # sweep_alpha below). Run the sweep once on your own data and use its best
37
+ # alpha instead of trusting 60 blindly.
38
+ DEFAULT_SETTINGS: Dict[str, Any] = {
39
+ # data (Section 4.1(ii): Multiple Features dataset)
40
+ "fou_path": "/Users/suchibrata/Desktop/ISI_PLP/multiple_features/mfeat-fou",
41
+ "pix_path": "/Users/suchibrata/Desktop/ISI_PLP/multiple_features/mfeat-pix",
42
+ "n_classes": 10,
43
+ "samples_per_class": 200,
44
+
45
+ # missing-view simulation: incomplete sample ratio (ISR), Section 4.3
46
+ "missing_ratio": 0.20,
47
+ "mask_seed": 1,
48
+
49
+ # affinity graph (Eq. 2): p nearest neighbors per view
50
+ "p_neighbors": 10,
51
+
52
+ # architecture (Section 3.3): latent_dim = DNN output size, latent_k = NMF rank
53
+ "latent_dim": 64,
54
+ "latent_k": 10,
55
+ "n_clusters": 10,
56
+
57
+ # auto-encoder pretraining (Algorithm 1, line 1)
58
+ "pretrain_epochs": 700,
59
+ "pretrain_lr": 1e-3,
60
+
61
+ # NMF warm-up before joint training (Algorithm 1, line 2 / Eq. 7)
62
+ "nmf_pretrain_iters": 200,
63
+
64
+ # alternating optimization (Algorithm 1, lines 4-9)
65
+ # starting point only -- see the alpha sweep below before trusting this
66
+ "alpha_1": 1,
67
+ "alpha_2": 1,
68
+ "learning_rate": 1e-5,
69
+ "n_epochs": 500,
70
+ "log_every": 50,
71
+
72
+ # reproducibility
73
+ "seed": 42,
74
+ }
75
+
76
+ # ============================================================
77
+ # Alpha-sensitivity sweep defaults
78
+ # ============================================================
79
+ # The candidate alpha values used in the notebook's Step 1 sweep.
80
+ DEFAULT_ALPHA_CANDIDATES = [1, 10, 100, 1000, 10000]
81
+
82
+ # Sweep runs use a reduced epoch budget so multiple alpha values can be
83
+ # tried quickly (Section 4.4 / Fig. 4 does exactly this per-dataset).
84
+ SWEEP_MAX_EPOCHS: int = 100
@@ -0,0 +1,5 @@
1
+ """Dataset loading utilities for the DIMC package."""
2
+
3
+ from dimc.datasets.load_dataset import load_views
4
+
5
+ __all__ = ["load_views"]
@@ -0,0 +1,13 @@
1
+ """
2
+ Dataset loading utilities.
3
+
4
+ The single implementation of view-loading lives in
5
+ ``dimc.preprocessing.load_views`` (Section 1 of the notebook, "Data
6
+ loading"). This module re-exports it under the requested
7
+ ``dimc/datasets/load_dataset.py`` path so there is exactly one
8
+ implementation rather than a duplicate.
9
+ """
10
+
11
+ from dimc.preprocessing import load_views
12
+
13
+ __all__ = ["load_views"]