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)))