Signature Transform: Signature Transform for Time Series

Signature Transform : Guide Complet — Transformée de Signature pour Séries Temporelles

Signature Transform: Complete Guide — Signature Transform for Time Series

Summary

The Signature Transform is a powerful mathematical method rooted in Terry Lyons’ rough path theory. Unlike classical approaches that treat time series as sequences of discrete numbers, the Signature Transform considers a time series as a continuous path in a multidimensional space. The signature of this path — a collection of Chen’s iterated integrals — encodes the entirety of the geometric and dynamic information of the path, up to reparametrization equivalence.

This approach offers an extremely rich representation of sequential data, particularly well-suited to irregularly sampled time series, missing data, and problems where the order and interactions between variables are paramount. Since its introduction in the machine learning domain around 2015-2016 through the work of Terry Lyons, Harald Oberhauser, and their collaborators, the Signature Transform has seen growing adoption, notably in quantitative finance, gesture recognition, and medical analysis of physiological signals.

In this complete guide, we explore the underlying mathematical theory, the intuition behind the method, its practical implementation in Python, and its concrete applications across various domains.

Mathematical Principle

The signature of a path: fundamental definition

Formally, let a continuous path $X : [0, T] \to \mathbb{R}^d$, where $d$ represents the number of dimensions (or observed variables). The signature of this path, denoted $S(X)$, is defined as the collection of all its Chen iterated integrals:

$$S(X) = \left(1,\; S^1(X),\; S^2(X),\; S^3(X),\; \ldots\right)$$

where each term of order $k$ is defined by:

$$S^k(X) = \int_{0 < t_1 < t_2 < \cdots < t_k < T} dX_{t_1} \otimes dX_{t_2} \otimes \cdots \otimes dX_{t_k}$$

Each term of order $k$ captures the interactions of order $k$ between the path’s dimensions. More precisely:

  • Order 0: the scalar 1 (constant term).
  • Order 1: $\int_0^T dX_t^i = X_T^i – X_0^i$, that is, the net change of each dimension. For a time series, this corresponds to the total displacement of each variable between the start and the end.
  • Order 2: $\int_0^T \int_0^{t_2} dX_{t_1}^i \, dX_{t_2}^j$, which captures the quadratic interactions between pairs of dimensions, including the signed area swept between trajectories. This term encodes not only the correlation between variables but also their temporal order: which variable evolves before the other?
  • Order 3 and higher: higher-order interactions capture increasingly subtle temporal dependencies, integrating triple, quadruple, and higher effects between the path’s dimensions. Each term of order $k$ is a rank-$k$ tensor of dimension $d^k$.

The Hambly-Lyons theorem

A fundamental result, proved by Hambly and Lyons in 2010, establishes that the signature characterizes a path up to reparametrization equivalence. In other words, two paths have the same signature if and only if one is a reparametrization of the other (i.e., they traverse the same geometric trajectory, potentially at different speeds). This property is remarkable: it means the signature captures the geometric essence of the path, independently of the speed at which it is traversed.

This theorem fully justifies the use of the signature for time series analysis: whether observations are spaced regularly or irregularly, the signature encodes the same fundamental geometric information.

Truncation and dimensionality

In practice, one cannot compute an infinite-order signature. The signature is truncated to a maximum depth $m$, yielding a truncated signature:

$$S^{(m)}(X) = \left(1,\; S^1(X),\; S^2(X),\; \ldots,\; S^m(X)\right)$$

The space of truncated signatures is finite-dimensional:

$$\text{dim} = d + d^2 + d^3 + \cdots + d^m = \sum_{k=1}^{m} d^k$$

For example, for $d = 3$ dimensions and a depth $m = 3$:
– Order 1: $3$ coefficients
– Order 2: $3^2 = 9$ coefficients
– Order 3: $3^3 = 27$ coefficients
Total: $3 + 9 + 27 = 39$ coefficients (plus the constant term 1)

The exponential growth of dimensionality with depth is a major challenge: choosing $m$ too large quickly leads to the curse of dimensionality. For this reason, typical depths range between 2 and 5 in practical applications.

Invariance to temporal reparametrization

The most remarkable property of the Signature Transform is its invariance to temporal reparametrization. If two paths traverse the same points in the same order, but at different speeds, their signatures will be identical. This property makes the signature ideally suited for irregularly sampled time series, an extremely common scenario in practice:

  • Financial data (transactions at irregular intervals)
  • Medical sensors (measurements taken at non-uniform times)
  • GPS tracking data (points recorded at varying frequencies depending on movement)
  • Application logs (events occurring asynchronously)

Unlike classical methods such as RNNs or LSTMs which assume uniform temporal spacing, the Signature Transform works naturally with irregular data, without any prior imputation or interpolation.

Intuition: The Signature as a Complete Statistical Summary

To understand the intuition behind the Signature Transform, consider a narrative analogy.

If a time series is a story, the signature is its complete statistical summary. Imagine reading a novel and having to extract its essence:

  • Order 1 statistics (first-order terms of the signature) give you the broad strokes: where the story begins, where it ends, how much each character has changed. This is the equivalent of a Wikipedia summary — the raw facts.
  • Order 2 statistics reveal the interactions between characters and events. Does the hero act before or after the villain? Do the variables evolve in the same direction or in opposite directions? What is the “area” swept by their joint evolution? This is the equivalent of an analysis of causal and temporal relationships.
  • Order 3 and higher statistics capture even more sophisticated dependencies: complex patterns involving three or more variables, feedback loops, emerging behaviors not visible in pairwise interactions.

It’s like the difference between reading a book summary and reading the entire book. The order-1 summary tells you that “the hero goes on a quest and returns victorious.” Order 2 reveals that “during his journey, he met his ally before discovering the treasure, and this sequence of events was crucial.” Order 3 adds that “it was precisely because he met his ally first, then discovered the treasure, and then faced the dragon, that victory was possible — a different order would have changed everything.”

This informational richness explains why the Signature Transform is so powerful: it encodes not only classical statistical values (mean, variance, derivatives) but also how variables interact over time, in what order, and with what relative intensity.

Comparison with classical approaches

Unlike RNNs and LSTMs which learn representations implicitly through weights adjusted by backpropagation, the Signature Transform provides a deterministic and non-parametric transformation of the original path. There is nothing to “learn” when computing the signature itself — it is a fixed, explicit mathematical transformation.

This deterministic nature confers several advantages:
Guaranteed reproducibility: the same input always produces the same signature.
Interpretability: each coefficient of the signature has a precise mathematical meaning.
No training data needed for the transformation itself: the signature is computed directly from the path, without a learning phase.

In practice, the signature is often used as a preprocessing layer (feature extraction), followed by a lightweight supervised model (linear regression, SVM, linear network) for the final task. This approach combines the richness of the signature with the predictive power of supervised models.

Python Implementation

Prerequisites

The reference library for computing signatures in Python is iisignature, an extremely optimized C++ library. It is simply installed with:

pip install iisignature numpy scikit-learn torch

Example 1: Computing the signature of a simple path

Let’s start with a fundamental example — computing the signature of a path in two dimensions:

import numpy as np
import iisignature

# Define a 2D path
# Each row represents a point (x, y) along the path
path = np.array([
    [0.0, 0.0],
    [1.0, 0.5],
    [1.5, 1.5],
    [2.0, 1.0],
    [2.5, 2.0]
])

d = path.shape[1]  # number of dimensions = 2
m = 3  # truncation depth = 3

# Compute the signature
signature = iisignature.sig(path, m)

print(f"Path dimensions: {d}")
print(f"Truncation depth: {m}")
print(f"Signature (dim = {len(signature)}): {signature}")

# Verify the theoretical dimension
# dim = d + d² + d³ = 2 + 4 + 8 = 14
theoretical_dim = sum(d**k for k in range(1, m+1))
print(f"Theoretical dimension: {theoretical_dim}")
assert len(signature) == theoretical_dim, "Incorrect dimension!"

This code produces a signature of dimension 14 for a 2D path truncated at order 3. The signature always starts with the constant term 1, followed by the $d$ order-1 terms, then the $d^2$ order-2 terms, and so on.

Example 2: Preparing time series for the signature

A crucial step is data preparation. The Signature Transform operates on paths, which are sequences of points in $\mathbb{R}^d$. For a traditional univariate time series (a single number per time step), it is often beneficial to enrich the path before computation:

import numpy as np
import iisignature

def prepare_path(series, include_time=True, include_cumulate=True):
    """
    Prepare a path from a univariate time series.

    Arguments:
        series: np.array of shape (T, ) — time series
        include_time: bool — add time as a dimension
        include_cumulate: bool — add cumulative integral (lead-lag)

    Returns:
        path: np.array of shape (T, d) — prepared path
    """
    T = len(series)

    # Start with the series itself (possibly reshaped)
    dims = [series.reshape(-1, 1)]

    if include_time:
        # Add normalized time as a dimension
        t = np.linspace(0, 1, T).reshape(-1, 1)
        dims.append(t)

    if include_cumulate:
        # Add cumulative integral (lead-lag variant)
        cumulate = np.cumsum(series).reshape(-1, 1)
        # Normalize for numerical stability
        cumulate = cumulate / (np.abs(cumulate).max() + 1e-8)
        dims.append(cumulate)

    path = np.concatenate(dims, axis=1)
    return path

# Usage example
series = np.sin(np.linspace(0, 4*np.pi, 100)) + 0.1 * np.random.randn(100)
path = prepare_path(series, include_time=True, include_cumulate=True)
print(f"Path shape: {path.shape}")  # (100, 3) — value, time, cumulative integral

Adding time as a dimension is particularly important: it allows the signature to distinguish paths that traverse the same spatial points but at different speeds (although the signature is invariant to reparametrization, adding time as a coordinate creates a path in an augmented space that implicitly encodes the temporal dynamics).

Adding the cumulative integral (the lead-lag technique) is a classic trick that doubles the available information: instead of having just $(X_t, Y_t)$ at each time step, we have $(X_t, Y_t, \int_0^t X_s ds, \int_0^t Y_s ds)$. This augmentation of the path enriches the computed signature considerably.

Example 3: Time series classification with signature

Here is a complete example of time series classification using the Signature Transform coupled with a linear network (PyTorch):

import numpy as np
import iisignature
import torch
import torch.nn as nn
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, classification_report

# ------------------------------------------------
# 1. Synthetic data generation
# ------------------------------------------------

def generate_data(n_samples, length, n_classes):
    """Generate synthetic time series data."""
    X, y = [], []

    if n_classes == 2:
        for i in range(n_samples):
            if i < n_samples // 2:
                # Class 0: sine wave with low noise
                series = np.sin(np.linspace(0, 3*np.pi, length)) + 0.1 * np.random.randn(length)
            else:
                # Class 1: cosine wave with offset
                series = np.cos(np.linspace(0, 3*np.pi, length)) + 0.1 * np.random.randn(length)
            X.append(series)
            y.append(i < n_samples // 2)

    return np.array(X), np.array(y, dtype=int)

# Create data
n_samples = 500
length = 50
X_data, y_data = generate_data(n_samples, length, 2)

# ------------------------------------------------
# 2. Path preparation and signature computation
# ------------------------------------------------

def compute_signatures(X, depth=3):
    """Compute signatures for all series."""
    signatures = []
    for series in X:
        path = prepare_path(series, include_time=True, include_cumulate=True)
        sig = iisignature.sig(path, depth)
        signatures.append(sig)
    return np.array(signatures)

depth = 3
X_signatures = compute_signatures(X_data, depth=depth)

# Normalize signatures (as with any features)
scaler = StandardScaler()
X_signatures = scaler.fit_transform(X_signatures)

print(f"Signatures shape: {X_signatures.shape}")
print(f"Number of features after signature: {X_signatures.shape[1]}")

# ------------------------------------------------
# 3. Train/test split
# ------------------------------------------------

X_train, X_test, y_train, y_test = train_test_split(
    X_signatures, y_data, test_size=0.2, random_state=42, stratify=y_data
)

# ------------------------------------------------
# 4. Model: simple linear network on signatures
# ------------------------------------------------

class SignatureClassifier(nn.Module):
    """Linear classifier on signature features."""

    def __init__(self, n_features, n_classes):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(n_features, 64),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(64, 32),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(32, n_classes)
        )

    def forward(self, x):
        return self.network(x)

# Initialization
n_features = X_signatures.shape[1]
n_classes = 2
model = SignatureClassifier(n_features, n_classes)

# Convert to tensors
X_train_t = torch.tensor(X_train, dtype=torch.float32)
y_train_t = torch.tensor(y_train, dtype=torch.long)
X_test_t = torch.tensor(X_test, dtype=torch.float32)
y_test_t = torch.tensor(y_test, dtype=torch.long)

# ------------------------------------------------
# 5. Training
# ------------------------------------------------

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

n_epochs = 100
batch_size = 32

for epoch in range(n_epochs):
    model.train()
    # Mini-batch
    permutation = torch.randperm(X_train_t.size(0))
    epoch_losses = []

    for i in range(0, X_train_t.size(0), batch_size):
        indices = permutation[i:i+batch_size]
        batch_X = X_train_t[indices]
        batch_y = y_train_t[indices]

        optimizer.zero_grad()
        predictions = model(batch_X)
        loss = criterion(predictions, batch_y)
        loss.backward()
        optimizer.step()
        epoch_losses.append(loss.item())

    if (epoch + 1) % 20 == 0:
        model.eval()
        with torch.no_grad():
            test_preds = model(X_test_t).argmax(dim=1).numpy()
            acc = accuracy_score(y_test, test_preds)
            print(f"Epoch {epoch+1}/{n_epochs} — "
                  f"Average loss: {np.mean(epoch_losses):.4f} — "
                  f"Test accuracy: {acc:.4f}")

# Final evaluation
model.eval()
with torch.no_grad():
    test_preds = model(X_test_t).argmax(dim=1).numpy()
    print(f"\nFinal accuracy: {accuracy_score(y_test, test_preds):.4f}")
    print(classification_report(y_test, test_preds))

Example 4: Application to irregular financial data

The Signature Transform excels particularly in the financial domain, where data is naturally irregularly spaced:

import numpy as np
import iisignature
from sklearn.ensemble import RandomForestClassifier

def prepare_financial_path(prices, volumes=None, intervals=None):
    """
    Prepare a path for irregular financial data.

    Arguments:
        prices: np.array — price at each transaction
        volumes: np.array or None — transaction volumes
        intervals: np.array or None — time between transactions

    Returns:
        path: np.array — multidimensional path ready for signature
    """
    T = len(prices)
    dims = []

    # Log return (more stable than raw prices)
    log_ret = np.diff(np.log(prices + 1e-8))
    log_ret = np.concatenate([[0], log_ret])
    dims.append(log_ret.reshape(-1, 1))

    # Normalized price
    norm_price = (prices - prices[0]) / (prices[0] + 1e-8)
    dims.append(norm_price.reshape(-1, 1))

    if volumes is not None:
        vol_norm = volumes / (volumes.max() + 1e-8)
        dims.append(vol_norm.reshape(-1, 1))

    if intervals is not None:
        # Inter-transaction delays (crucial for irregularity)
        dt_norm = intervals / (intervals.max() + 1e-8)
        dims.append(dt_norm.reshape(-1, 1))

    # Absolute time
    t = np.linspace(0, 1, T).reshape(-1, 1)
    dims.append(t)

    return np.concatenate(dims, axis=1)

# Simulated irregular transaction data
signals = np.random.choice([-1, 1], size=300)

X_fin, y_fin = [], []
for i in range(300):
    n = np.random.randint(50, 200)
    # Simulated prices with trend
    rets = 0.001 * np.random.randn(n)
    if signals[i] == 1:
        rets += 0.0005  # Subtle upward trend
    price = 100 * np.exp(np.cumsum(rets))

    # Irregular intervals (modeling pauses in trading)
    intervals = np.abs(np.random.exponential(scale=2.0, size=n))
    volumes = np.random.lognormal(mean=3, sigma=1, size=n)

    path = prepare_financial_path(price, volumes, intervals)
    sig = iisignature.sig(path, m=3)
    X_fin.append(sig)
    y_fin.append((signals[i] + 1) // 2)

X_fin = np.array(X_fin)
y_fin = np.array(y_fin)

# Classification with Random Forest (non-linear model on signatures)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X_fin, y_fin, test_size=0.2, random_state=42
)

rf = RandomForestClassifier(n_estimators=100, max_depth=8, random_state=42)
rf.fit(X_train, y_train)
acc = rf.score(X_test, y_test)
print(f"Random Forest accuracy on financial signatures: {acc:.4f}")

This last example illustrates the power of the Signature Transform in a realistic context: transactions with irregular intervals, varying volumes, and a weak signal to detect. The signature captures subtle geometric price patterns that approaches based on fixed time windows would miss.

Hyperparameters

Signature depth (signature_depth / m)

This is the most critical hyperparameter of the Signature Transform. The depth determines the maximum order of iterated integrals included in the representation.

Depth m Dim (d=2) Dim (d=3) Dim (d=5) Typical use
2 6 12 30 Simple problems, noisy data
3 14 39 155 Standard use, good trade-off
4 30 120 780 Complex problems, clean data
5 62 363 3905 Rare cases, risk of overfitting

Practical recommendations:

  • Always start with m = 3 — this is the standard reference point in the literature and works well in the majority of cases.
  • If your data is very noisy, reduce to m = 2 to avoid overfitting.
  • If you have a lot of data and a complex problem, try m = 4, but rigorously monitor performance on the validation set.
  • Avoid m > 5 except in exceptional cases: the dimension explodes and the risk of overfitting becomes very high.

Number of features after signature (num_features_after_signature)

This number is deterministic and depends only on $d$ (number of path dimensions) and $m$ (depth):

$$\text{num_features} = \sum_{k=1}^{m} d^k$$

It is crucial to know this number in advance to correctly size the downstream layers of your machine learning pipeline. In Python with iisignature, it can be computed as follows:

import iisignature
d = 4  # path dimensions
m = 3  # depth
n_features = iisignature.siglength(d, m)
print(f"Features after signature: {n_features}")  # 4 + 16 + 64 = 84

Path preparation

How you prepare your path (choice of dimensions to include) is just as important as the depth itself:

  • Including time as a dimension is almost always recommended.
  • Lead-lag augmentation (adding cumulative integrals) generally improves performance, especially for univariate data.
  • Normalizing each dimension before computation is essential for numerical stability.

Advantages and Limitations

Advantages

  1. Invariance to temporal reparametrization: Works naturally with irregularly sampled data, without interpolation or imputation.
  2. Deterministic transformation: No learning phase for the signature itself. Computable on any path.
  3. Informational richness: Encodes higher-order interactions between variables, well beyond classical descriptive statistics.
  4. Computational efficiency: Signature computation is very fast (linear algorithm in path length), thanks to optimized implementations like iisignature.
  5. Universal compatibility: The signature is simply a vector of real numbers. It can be used with any supervised model: linear regression, SVM, random forests, neural networks, etc.
  6. Solid theoretical foundation: Rooted in Terry Lyons’ rough path theory, the signature benefits from rigorously proved mathematical properties.
  7. Robustness to missing data: Unlike RNNs/LSTMs, the Signature Transform is not disrupted by missing observations, since it operates on the global geometric path rather than a discrete sequence.

Limitations

  1. Curse of dimensionality: Signature dimension grows exponentially with depth m. For d=5 and m=5, we get 3905 features — which requires a lot of training data.
  2. Loss of temporal scale: Reparametrization invariance is a double-edged sword: if the speed of evolution is informative (e.g., a fast vs. slow movement), the standard signature loses it. Variants like the path signature with time as a dimension mitigate this problem.
  3. Not designed for sequential prediction: The Signature Transform produces a global representation of the entire path. It is not suited for step-by-step prediction (like machine translation or pure forecasting) without specific adaptations (sliding window signatures).
  4. Partial interpretability: Although each coefficient has a mathematical meaning, interpreting a coefficient of order 3 or 4 in a concrete application context can be difficult.
  5. Sensitivity to high-frequency noise: High-order signatures can amplify measurement noise. Pre-smoothing the data is often necessary.

Use Cases

1. Irregular financial data

In quantitative finance, transactions do not occur at regular intervals. Some moments see intense activity (market opening/closing, economic announcements), while others are quiet. The Signature Transform is naturally suited to this irregularity.

Concrete application: Predicting price direction from order book data. Researchers (notably the work of Lyons, Bayer et al.) have shown that signatures of price and volume trajectories allow detecting subtle trading patterns, outperforming approaches based on fixed-frequency returns.

Why the signature excels here: Market microstructure patterns — such as sequences of limit and market orders, temporary imbalances between buyers and sellers — are fundamentally geometric patterns in the (price, volume, time) space. The signature captures them naturally.

2. Gesture Recognition

Gesture recognition from inertial sensor data (accelerometers, gyroscopes) is a classic problem where the Signature Transform shows excellent performance.

Concrete application: A connected wristband records acceleration along three axes ($a_x, a_y, a_z$) during hand gestures. Each gesture (raised fist, open hand, rotation) corresponds to a different path in $\mathbb{R}^3$. The signature of this path encodes the complete dynamics of the gesture — its shape, speed, transitions — enabling robust classification.

Why the signature excels here: Gestures of the same type can be performed at different speeds (invariance to reparametrization), but will produce the same signature. This invariance property is exactly what is needed for gesture recognition.

3. Human movement classification

Beyond discrete gesture recognition, the Signature Transform applies to the classification of continuous movements: walking, running, climbing stairs, etc.

Concrete application: A smartphone in the pocket records accelerometer data throughout the day. By segmenting the data into windows and computing the signature of each window, the type of activity (sitting, standing, walking, running, cycling) can be classified with high accuracy, even with varying sampling frequencies (the phone may reduce sensor frequency to save battery).

Specific advantage: Unlike approaches based on hand-crafted statistical features (mean, standard deviation, spectral energy), the signature automatically captures the complex interactions between the three accelerometer axes, including higher-order effects that would be difficult to design manually.

4. Medicine and physiological sensors

Physiological signal analysis — electrocardiogram (ECG), electroencephalogram (EEG), continuous glucose monitoring — often involves irregular data (poorly positioned sensors, motion artifacts, missing measurements). The Signature Transform offers a robust alternative to classical methods.

Concrete application: Classification of cardiac anomalies from ECG signals. Each heartbeat is a path in the space of ECG leads. The path signature captures the complete P-QRS-T waveform morphology, including temporal interactions between the different phases of the cardiac cycle. Recent work has shown that signatures outperform approaches based on manually extracted features for arrhythmia detection.

Another example: Prediction of hypoglycemic episodes from continuous glucose monitor (CGM) data. Signatures of glucose trajectories over the past few hours capture the complex temporal dynamics of blood sugar, enabling advance prediction of sugar drops.

See Also