1. Sub-Series Patching & Channel Independence (PatchTST)

Classical time series forecasting applies pointwise recurrent or attention layers across raw timesteps $t$, incurring $\mathcal{O}(L^2)$ computational complexity and losing local semantic context.

Nie et al. (2023) introduce PatchTST, segmenting continuous univariate series $\mathbf{x}^{(i)} \in \mathbb{R}^{1 \times L}$ into $N$ overlapping sub-series patches of length $P$ with stride $S$:

$$\mathbf{P}^{(i)} \in \mathbb{R}^{N \times P}, \quad N = \left\lfloor \frac{L - P}{S} \right\rfloor + 2$$

Channel Independence (CI) Theorem

In multivariate series $\mathbf{X} \in \mathbb{R}^{C \times L}$ with $C$ channels, joint cross-channel attention often overfits spurious correlations. Channel Independence (CI) shares a single Transformer backbone across all $C$ channels independently, treating each channel as an independent batch dimension during forward passes while preserving temporal attention patterns.


2. Reversible Instance Normalization (RevIN) for Covariate Shift

Real-world financial and sensor time series suffer from non-stationary distribution shifts (changing mean and variance across horizons). Standard layer normalization fails because it removes trend and scale information permanently.

RevIN (Kim et al., 2021) normalizes inputs symmetrically before the neural backbone and denormalizes outputs afterward using exact input statistics:

$$\text{Forward Normalization:} \quad \tilde{\mathbf{x}} = \frac{\mathbf{x} - \boldsymbol{\mu}_x}{\boldsymbol{\sigma}_x + \epsilon}$$$$\text{Neural Prediction:} \quad \tilde{\mathbf{y}} = \text{Transformer}(\tilde{\mathbf{x}})$$$$\text{Inverse Denormalization:} \quad \hat{\mathbf{y}} = \tilde{\mathbf{y}} \odot \boldsymbol{\sigma}_x + \boldsymbol{\mu}_x$$

3. Chronos: Quantized Tokenization of Continuous Time Series

Ansari et al. (2024) demonstrate that unmodified Language Models (T5, LLaMA) can perform zero-shot time series forecasting if continuous values are tokenized into discrete vocabulary bins.

   Continuous x(t) ──► [RevIN Scaling] ──► [Uniform Quantization Bins] ──► Text Tokens ──► [LLM]

Given scaling factors $s = \text{MAD}(\mathbf{x}) = \text{median}(|\mathbf{x} - \text{median}(\mathbf{x})|)$, continuous values are mapped to $K$ quantization centers $c_k$:

$$b_t = \arg\min_{k \in \{1, \dots, K\}} \left| \frac{x_t - \text{median}(\mathbf{x})}{s} - c_k \right|$$

4. Probabilistic Forecasting: Lag-Llama Student-$t$ Distribution

To quantify predictive epistemic uncertainty, Lag-Llama (Rasul et al., 2024) outputs parametric Student-$t$ distribution heads $(\mu_t, \sigma_t, \nu_t)$ at horizon $t$:

$$p_{\text{Student-T}}(y_t \mid \mu_t, \sigma_t, \nu_t) = \frac{\Gamma\left(\frac{\nu_t + 1}{2}\right)}{\Gamma\left(\frac{\nu_t}{2}\right) \sqrt{\pi \nu_t} \sigma_t} \left( 1 + \frac{1}{\nu_t} \left( \frac{y_t - \mu_t}{\sigma_t} \right)^2 \right)^{-\frac{\nu_t + 1}{2}}$$

optimized via Negative Log-Likelihood ($\mathcal{L}_{\text{NLL}}$).


5. Python Verification: Reversible Instance Normalization & Patching

import numpy as np

class ReversibleInstanceNorm:
    """Rigorous numpy implementation of RevIN non-stationarity normalization/denormalization."""
    def __init__(self, eps: float = 1e-5):
        self.eps = eps
        self.mean = None
        self.std = None

    def normalize(self, x: np.ndarray) -> np.ndarray:
        """x shape: (batch_size, seq_len, num_channels)"""
        self.mean = np.mean(x, axis=1, keepdims=True)
        self.std = np.std(x, axis=1, keepdims=True) + self.eps
        return (x - self.mean) / self.std

    def denormalize(self, y_hat_norm: np.ndarray) -> np.ndarray:
        """Restores original scale to forecasted normalized horizon."""
        return y_hat_norm * self.std + self.mean

def extract_patches(x_norm: np.ndarray, patch_len: int = 4, stride: int = 2) -> np.ndarray:
    """Segments sequence into non-overlapping/overlapping patches."""
    batch_size, seq_len, channels = x_norm.shape
    num_patches = (seq_len - patch_len) // stride + 1
    patches = []
    for p in range(num_patches):
        start = p * stride
        patches.append(x_norm[:, start:start+patch_len, :])
    return np.stack(patches, axis=1)  # Shape: (batch_size, num_patches, patch_len, channels)

if __name__ == "__main__":
    np.random.seed(42)
    # Synthetic time series with upward linear trend
    t = np.linspace(0, 10, 16)
    raw_series = (2.5 * t + np.random.normal(0, 0.5, size=16)).reshape(1, 16, 1)
    
    revin = ReversibleInstanceNorm()
    normalized = revin.normalize(raw_series)
    patches = extract_patches(normalized, patch_len=4, stride=2)
    
    # Simulate identity forecast model
    recovered = revin.denormalize(normalized)
    
    print("Raw Time Series First 4 points:       ", np.round(raw_series[0, :4, 0], 3))
    print("RevIN Normalized First 4 points:      ", np.round(normalized[0, :4, 0], 3))
    print(f"Extracted Patches Shape (L=16, P=4, S=2): {patches.shape}")
    print("Exact RevIN Reconstruction Error Max: ", np.max(np.abs(raw_series - recovered)))