|
3 | 3 | import numpy as np |
4 | 4 | from numpy import linalg as la |
5 | 5 | from numpy.typing import NDArray |
| 6 | +from sklearn.utils.extmath import randomized_range_finder |
6 | 7 |
|
7 | 8 |
|
8 | 9 | class Sketcher(ABC): |
@@ -46,7 +47,7 @@ def __init__( |
46 | 47 | frobenius: NDArray[np.float64], |
47 | 48 | rng: np.random.RandomState, |
48 | 49 | ) -> None: |
49 | | - """Init QILinearEstimator. |
| 50 | + """Init FKV. |
50 | 51 |
|
51 | 52 | Note: LS stands for length-square. |
52 | 53 |
|
@@ -130,3 +131,89 @@ def sample_row_idx(self, rng: np.random.RandomState) -> int: |
130 | 131 | sample_i = rng.choice(self._n_rows, 1, p=self._C_ls_prob_rows[:, sample_j])[0] |
131 | 132 |
|
132 | 133 | return sample_i |
| 134 | + |
| 135 | + |
| 136 | +class Halko(Sketcher): |
| 137 | + """Halko sketching.""" |
| 138 | + |
| 139 | + def __init__( |
| 140 | + self, |
| 141 | + A: NDArray[np.float64], |
| 142 | + r: int, |
| 143 | + c: int, |
| 144 | + ls_prob_rows: NDArray[np.float64], |
| 145 | + ls_prob_columns: NDArray[np.float64], |
| 146 | + rng: np.random.RandomState, |
| 147 | + ) -> None: |
| 148 | + """Init Halko. |
| 149 | +
|
| 150 | + Note: LS stands for length-square. |
| 151 | +
|
| 152 | + Args: |
| 153 | + A: coefficient matrix. |
| 154 | + r: number of rows for left projection matrix. |
| 155 | + c: number of columns for right projection matrix. |
| 156 | + ls_prob_rows: row LS probability distribution of `A`. |
| 157 | + ls_prob_columns: column LS probability distribution of `A`. |
| 158 | + rng: random state. |
| 159 | + """ |
| 160 | + self._Q_left = Halko._get_low_dimensional_projector(A, axis=0, n_components=r, random_state=rng) |
| 161 | + self._Q_right = Halko._get_low_dimensional_projector(A, axis=1, n_components=c, random_state=rng) |
| 162 | + self._ls_prob_rows = ls_prob_rows |
| 163 | + self._ls_prob_columns = ls_prob_columns |
| 164 | + |
| 165 | + @classmethod |
| 166 | + def _get_low_dimensional_projector( |
| 167 | + cls, |
| 168 | + M: NDArray[np.float64], |
| 169 | + axis: int, |
| 170 | + n_components: int, |
| 171 | + random_state: np.random.RandomState, |
| 172 | + ) -> NDArray[np.float128]: |
| 173 | + """Find random matrix to reduce dimensionality of axis of `M`.""" |
| 174 | + n_oversamples = 10 |
| 175 | + n_random = n_components + n_oversamples |
| 176 | + n_iter = 7 if n_components < 0.1 * min(M.shape) else 4 |
| 177 | + if axis == 1: |
| 178 | + M = M.T |
| 179 | + Q = np.asarray( |
| 180 | + randomized_range_finder( |
| 181 | + M, |
| 182 | + size=n_random, |
| 183 | + n_iter=n_iter, |
| 184 | + power_iteration_normalizer="auto", |
| 185 | + random_state=random_state, |
| 186 | + ) |
| 187 | + ) |
| 188 | + if axis == 0: |
| 189 | + Q = Q.T |
| 190 | + |
| 191 | + return np.asarray(Q) |
| 192 | + |
| 193 | + def left_project(self, M: NDArray[np.float64]) -> NDArray[np.float64]: |
| 194 | + """Define left projector.""" |
| 195 | + return self._Q_left @ M |
| 196 | + |
| 197 | + def right_project(self, M: NDArray[np.float64]) -> NDArray[np.float64]: |
| 198 | + """Define right projector.""" |
| 199 | + return M @ self._Q_right |
| 200 | + |
| 201 | + def set_up_column_sampler(self, A: NDArray[np.float64]) -> None: |
| 202 | + """No setup required.""" |
| 203 | + |
| 204 | + def set_up_row_sampler(self, A: NDArray[np.float64]) -> None: |
| 205 | + """No setup required.""" |
| 206 | + |
| 207 | + def sample_column_idx(self, rng: np.random.RandomState) -> int: |
| 208 | + """Sample a column index.""" |
| 209 | + n_cols = self._ls_prob_columns.size |
| 210 | + sample_j = rng.choice(n_cols, 1, p=self._ls_prob_columns)[0] |
| 211 | + |
| 212 | + return sample_j |
| 213 | + |
| 214 | + def sample_row_idx(self, rng: np.random.RandomState) -> int: |
| 215 | + """Sample a row index.""" |
| 216 | + n_rows = self._ls_prob_rows.size |
| 217 | + sample_i = rng.choice(n_rows, 1, p=self._ls_prob_rows)[0] |
| 218 | + |
| 219 | + return sample_i |
0 commit comments