Download hilbert_mi/transforms.py from kiruluta/Hilbert-Operator-Intelligence: direct link, hf CLI and curl.
- Browser
- Download file 5.44 kB
-
https://huggingface.co/kiruluta/Hilbert-Operator-Intelligence/resolve/main/hilbert_mi/transforms.py
- Command line
-
hf download hf://kiruluta/Hilbert-Operator-Intelligence/hilbert_mi/transforms.py
-
curl -L -o transforms.py https://huggingface.co/kiruluta/Hilbert-Operator-Intelligence/resolve/main/hilbert_mi/transforms.py
5.44 kB
| """Spectral feature maps for Hilbert-space learning. | |
| The paper emphasizes Fourier, wavelet, scattering, and learnable spectral | |
| representations. This module implements compact CPU-friendly versions for the | |
| 8x8 Digits benchmark. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| import torch | |
| def _as_images(X: np.ndarray) -> np.ndarray: | |
| """Return images as (n, 8, 8) float32 in [0, 1].""" | |
| X = np.asarray(X, dtype=np.float32) | |
| if X.ndim == 2 and X.shape[1] == 64: | |
| X = X.reshape(-1, 8, 8) | |
| elif X.ndim != 3: | |
| raise ValueError(f"Expected (n,64) or (n,8,8), got {X.shape}") | |
| # sklearn digits are in [0,16]. If already normalized this is harmless. | |
| if X.min() >= 0.0 and X.max() > 1.0: | |
| X = X / 16.0 | |
| return X.astype(np.float32) | |
| def pixel_features(X: np.ndarray) -> np.ndarray: | |
| """Identity Hilbert embedding over pixels.""" | |
| imgs = _as_images(X) | |
| return imgs.reshape(imgs.shape[0], -1).astype(np.float32) | |
| def fourier_features(X: np.ndarray) -> np.ndarray: | |
| """Real-valued 2D Fourier feature map. | |
| We concatenate real and imaginary FFT coefficients. This is a spectral | |
| embedding in a global orthonormal basis. | |
| """ | |
| imgs = _as_images(X) | |
| fft = np.fft.fft2(imgs, axes=(-2, -1), norm="ortho") | |
| feat = np.concatenate([fft.real.reshape(len(imgs), -1), fft.imag.reshape(len(imgs), -1)], axis=1) | |
| return feat.astype(np.float32) | |
| def haar2d_single(img: np.ndarray, levels: int = 3) -> np.ndarray: | |
| """Orthonormal 2D Haar wavelet transform for square power-of-two images. | |
| Coefficients are packed in-place using the standard quadrant layout. | |
| For 8x8 images, levels=3 yields a full multiresolution transform. | |
| """ | |
| x = np.asarray(img, dtype=np.float32).copy() | |
| h, w = x.shape | |
| if h != w or (h & (h - 1)) != 0: | |
| raise ValueError("haar2d_single expects square power-of-two images") | |
| size = h | |
| for _ in range(levels): | |
| if size < 2: | |
| break | |
| block = x[:size, :size] | |
| a = block[0::2, 0::2] | |
| b = block[0::2, 1::2] | |
| c = block[1::2, 0::2] | |
| d = block[1::2, 1::2] | |
| ll = (a + b + c + d) / 2.0 | |
| lh = (a - b + c - d) / 2.0 | |
| hl = (a + b - c - d) / 2.0 | |
| hh = (a - b - c + d) / 2.0 | |
| half = size // 2 | |
| x[:half, :half] = ll | |
| x[:half, half:size] = lh | |
| x[half:size, :half] = hl | |
| x[half:size, half:size] = hh | |
| size = half | |
| return x | |
| def inverse_haar2d_single(coeff: np.ndarray, levels: int = 3) -> np.ndarray: | |
| """Inverse of haar2d_single.""" | |
| x = np.asarray(coeff, dtype=np.float32).copy() | |
| h, w = x.shape | |
| max_levels = int(np.log2(h)) | |
| levels = min(levels, max_levels) | |
| sizes = [2 ** i for i in range(1, levels + 1)] | |
| for size in sizes: | |
| half = size // 2 | |
| ll = x[:half, :half].copy() | |
| lh = x[:half, half:size].copy() | |
| hl = x[half:size, :half].copy() | |
| hh = x[half:size, half:size].copy() | |
| a = (ll + lh + hl + hh) / 2.0 | |
| b = (ll - lh + hl - hh) / 2.0 | |
| c = (ll + lh - hl - hh) / 2.0 | |
| d = (ll - lh - hl + hh) / 2.0 | |
| block = np.zeros((size, size), dtype=np.float32) | |
| block[0::2, 0::2] = a | |
| block[0::2, 1::2] = b | |
| block[1::2, 0::2] = c | |
| block[1::2, 1::2] = d | |
| x[:size, :size] = block | |
| return x | |
| def haar2d_batch(X: np.ndarray, levels: int = 3) -> np.ndarray: | |
| """Batch Haar wavelet features.""" | |
| imgs = _as_images(X) | |
| coeffs = np.stack([haar2d_single(img, levels=levels).reshape(-1) for img in imgs], axis=0) | |
| return coeffs.astype(np.float32) | |
| def scattering_like_features(X: np.ndarray, levels: int = 3) -> np.ndarray: | |
| """A compact deterministic scattering-like feature map. | |
| True scattering networks require a bank of complex wavelets. For a small | |
| offline repo, we approximate the paper's wavelet/scattering idea by using | |
| Haar coefficients, coefficient magnitudes, and energy coefficients. This | |
| gives deformation-stable, modulus-style spectral features while remaining | |
| dependency-free. | |
| """ | |
| c = haar2d_batch(X, levels=levels) | |
| return np.concatenate([c, np.abs(c), c * c], axis=1).astype(np.float32) | |
| def torch_haar2d(x: torch.Tensor, levels: int = 3) -> torch.Tensor: | |
| """Differentiable 2D Haar transform for tensors shaped (B, 1, 8, 8). | |
| This version avoids in-place writes so autograd remains fast and stable. | |
| It returns coefficients flattened in multiresolution order rather than | |
| quadrant-packed image order. | |
| """ | |
| if x.ndim != 4 or x.shape[1] != 1: | |
| raise ValueError("Expected tensor of shape (B,1,H,W)") | |
| current = x | |
| details = [] | |
| for _ in range(levels): | |
| if current.shape[-1] < 2 or current.shape[-2] < 2: | |
| break | |
| a = current[:, :, 0::2, 0::2] | |
| b = current[:, :, 0::2, 1::2] | |
| c = current[:, :, 1::2, 0::2] | |
| d = current[:, :, 1::2, 1::2] | |
| ll = (a + b + c + d) / 2.0 | |
| lh = (a - b + c - d) / 2.0 | |
| hl = (a + b - c - d) / 2.0 | |
| hh = (a - b - c + d) / 2.0 | |
| details.extend([lh.flatten(1), hl.flatten(1), hh.flatten(1)]) | |
| current = ll | |
| details.append(current.flatten(1)) | |
| return torch.cat(details, dim=1) | |
| def torch_wavelet_features(x: torch.Tensor, levels: int = 3) -> torch.Tensor: | |
| """Flattened differentiable Haar features for learnable spectral operator.""" | |
| return torch_haar2d(x, levels=levels) | |