sigtorch 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sigtorch/__init__.py +23 -0
- sigtorch/_backends.py +89 -0
- sigtorch/_tensor_algebra.py +75 -0
- sigtorch/augment.py +69 -0
- sigtorch/deprecated.py +21 -0
- sigtorch/logsignature_module.py +145 -0
- sigtorch/path.py +277 -0
- sigtorch/signature_inversion_module.py +45 -0
- sigtorch/signature_module.py +169 -0
- sigtorch/utility.py +147 -0
- sigtorch-0.1.0.dist-info/METADATA +103 -0
- sigtorch-0.1.0.dist-info/RECORD +15 -0
- sigtorch-0.1.0.dist-info/WHEEL +4 -0
- sigtorch-0.1.0.dist-info/licenses/LICENSE +21 -0
- sigtorch-0.1.0.dist-info/licenses/LICENSE-signatory +201 -0
sigtorch/__init__.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""sigtorch: differentiable signatures and logsignatures in pure PyTorch, with the API of signatory.
|
|
2
|
+
|
|
3
|
+
``import sigtorch as signatory`` is intended to work as a drop-in replacement for code written against signatory 1.2.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .augment import Augment
|
|
7
|
+
from .deprecated import max_parallelism
|
|
8
|
+
from .logsignature_module import (LogSignature, Logsignature, SignatureToLogSignature, SignatureToLogsignature,
|
|
9
|
+
logsignature, logsignature_channels, signature_to_logsignature)
|
|
10
|
+
from .path import Path
|
|
11
|
+
from .signature_inversion_module import invert_signature
|
|
12
|
+
from .signature_module import (Signature, extract_signature_term, multi_signature_combine, signature,
|
|
13
|
+
signature_channels, signature_combine)
|
|
14
|
+
from .utility import all_words, lyndon_brackets, lyndon_words
|
|
15
|
+
|
|
16
|
+
__version__ = "0.1.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Augment", "LogSignature", "Logsignature", "Path", "Signature", "SignatureToLogSignature",
|
|
20
|
+
"SignatureToLogsignature", "all_words", "extract_signature_term", "invert_signature", "logsignature",
|
|
21
|
+
"logsignature_channels", "lyndon_brackets", "lyndon_words", "max_parallelism", "multi_signature_combine",
|
|
22
|
+
"signature", "signature_channels", "signature_combine", "signature_to_logsignature",
|
|
23
|
+
]
|
sigtorch/_backends.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Signature levels of a piecewise-linear path, given its increments.
|
|
2
|
+
|
|
3
|
+
Depths 1-3 use closed forms built from cumulative sums and batched matrix products, which never materialise the
|
|
4
|
+
per-step level-2/3 tensors of the non-stream signature. Deeper signatures combine per-segment exponentials with Chen's
|
|
5
|
+
identity in a balanced tree (non-stream) or a parallel prefix scan (stream), so the number of sequential steps grows
|
|
6
|
+
with log(length) rather than length.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
from . import _tensor_algebra as ta
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def signature_levels(dX: torch.Tensor, depth: int, stream: bool) -> ta.Levels:
|
|
15
|
+
"""Signature levels of the path with increments ``dX`` of shape (B, N, C).
|
|
16
|
+
|
|
17
|
+
Returns levels of shape (B, C**k), or (B, N, C**k) holding the signature of every prefix if ``stream``.
|
|
18
|
+
"""
|
|
19
|
+
if depth <= 3:
|
|
20
|
+
return _closed_form_stream(dX, depth) if stream else _closed_form(dX, depth)
|
|
21
|
+
return _scan(dX, depth) if stream else _tree(dX, depth)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _exclusive_cumsum(x: torch.Tensor) -> torch.Tensor:
|
|
25
|
+
"""Cumulative sum over dim 1 of everything strictly before each step."""
|
|
26
|
+
return x.cumsum(dim=1) - x
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _closed_form(dX: torch.Tensor, depth: int) -> ta.Levels:
|
|
30
|
+
batch, _, channels = dX.shape
|
|
31
|
+
levels = [dX.sum(dim=1)]
|
|
32
|
+
if depth >= 2:
|
|
33
|
+
before = _exclusive_cumsum(dX) # S1 up to the start of each segment
|
|
34
|
+
# sum_t S1_before[t] (x) dX[t] + sum_t dX[t] (x) dX[t] / 2
|
|
35
|
+
S2 = torch.bmm(before.transpose(1, 2), dX) + 0.5 * torch.bmm(dX.transpose(1, 2), dX)
|
|
36
|
+
levels.append(S2.reshape(batch, channels ** 2))
|
|
37
|
+
if depth >= 3:
|
|
38
|
+
outer = torch.einsum('bti,btj->btij', dX, dX)
|
|
39
|
+
S2_before = _exclusive_cumsum(torch.einsum('bti,btj->btij', before, dX) + 0.5 * outer)
|
|
40
|
+
# Chen: S3 += S2_before (x) dX + S1_before (x) dX^2 / 2 + dX^3 / 6, summed over segments
|
|
41
|
+
S3 = (torch.einsum('btij,btk->bijk', S2_before, dX)
|
|
42
|
+
+ 0.5 * torch.einsum('bti,btjk->bijk', before, outer)
|
|
43
|
+
+ torch.einsum('bti,btjk->bijk', dX, outer) / 6)
|
|
44
|
+
levels.append(S3.reshape(batch, channels ** 3))
|
|
45
|
+
return levels
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _closed_form_stream(dX: torch.Tensor, depth: int) -> ta.Levels:
|
|
49
|
+
batch, length, channels = dX.shape
|
|
50
|
+
S1 = dX.cumsum(dim=1)
|
|
51
|
+
levels = [S1]
|
|
52
|
+
if depth >= 2:
|
|
53
|
+
before = S1 - dX
|
|
54
|
+
outer = torch.einsum('bti,btj->btij', dX, dX)
|
|
55
|
+
S2 = (torch.einsum('bti,btj->btij', before, dX) + 0.5 * outer).cumsum(dim=1)
|
|
56
|
+
levels.append(S2.reshape(batch, length, channels ** 2))
|
|
57
|
+
if depth >= 3:
|
|
58
|
+
S2_before = torch.cat([torch.zeros_like(S2[:, :1]), S2[:, :-1]], dim=1)
|
|
59
|
+
S3 = (torch.einsum('btij,btk->btijk', S2_before, dX)
|
|
60
|
+
+ 0.5 * torch.einsum('bti,btjk->btijk', before, outer)
|
|
61
|
+
+ torch.einsum('bti,btjk->btijk', dX, outer) / 6).cumsum(dim=1)
|
|
62
|
+
levels.append(S3.reshape(batch, length, channels ** 3))
|
|
63
|
+
return levels
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _tree(dX: torch.Tensor, depth: int) -> ta.Levels:
|
|
67
|
+
"""Multiplies the segment exponentials pairwise, halving their number each round."""
|
|
68
|
+
levels = ta.restricted_exp(dX, depth)
|
|
69
|
+
while levels[0].size(1) > 1:
|
|
70
|
+
tail = None
|
|
71
|
+
if levels[0].size(1) % 2: # an odd one out joins the next round at the end
|
|
72
|
+
tail = [x[:, -1:] for x in levels]
|
|
73
|
+
levels = [x[:, :-1] for x in levels]
|
|
74
|
+
levels = ta.mult([x[:, 0::2] for x in levels], [x[:, 1::2] for x in levels])
|
|
75
|
+
if tail is not None:
|
|
76
|
+
levels = [torch.cat([x, t], dim=1) for x, t in zip(levels, tail)]
|
|
77
|
+
return [x[:, 0] for x in levels]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _scan(dX: torch.Tensor, depth: int) -> ta.Levels:
|
|
81
|
+
"""Inclusive prefix products of the segment exponentials (Hillis-Steele scan)."""
|
|
82
|
+
levels = ta.restricted_exp(dX, depth)
|
|
83
|
+
length = levels[0].size(1)
|
|
84
|
+
offset = 1
|
|
85
|
+
while offset < length:
|
|
86
|
+
combined = ta.mult([x[:, :-offset] for x in levels], [x[:, offset:] for x in levels])
|
|
87
|
+
levels = [torch.cat([x[:, :offset], c], dim=1) for x, c in zip(levels, combined)]
|
|
88
|
+
offset *= 2
|
|
89
|
+
return levels
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Operations on elements of the truncated tensor algebra.
|
|
2
|
+
|
|
3
|
+
An element is a list ``[x_1, ..., x_depth]`` where ``x_k`` has shape ``(..., C**k)``: the level-k tensor flattened in
|
|
4
|
+
row-major order, after any number of leading batch dimensions. The scalar term is implicit: 1 for signatures (group
|
|
5
|
+
elements), 0 for log-signatures (Lie elements). All operations broadcast over the leading dimensions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
import torch
|
|
11
|
+
|
|
12
|
+
Levels = List[torch.Tensor]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def split_levels(flat: torch.Tensor, channels: int, depth: int) -> Levels:
|
|
16
|
+
"""Splits a ``(..., C + C**2 + ... + C**depth)`` tensor into its levels."""
|
|
17
|
+
return list(torch.split(flat, [channels ** k for k in range(1, depth + 1)], dim=-1))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _outer(a: torch.Tensor, b: torch.Tensor) -> torch.Tensor:
|
|
21
|
+
"""(..., m) x (..., n) -> (..., m * n), the flattened tensor product."""
|
|
22
|
+
return (a.unsqueeze(-1) * b.unsqueeze(-2)).flatten(-2)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def restricted_exp(x: torch.Tensor, depth: int) -> Levels:
|
|
26
|
+
"""exp(x) for a level-1 element ``x`` of shape (..., C): the signature of a single straight segment."""
|
|
27
|
+
levels = [x]
|
|
28
|
+
for k in range(2, depth + 1):
|
|
29
|
+
levels.append(_outer(levels[-1], x) / k)
|
|
30
|
+
return levels
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _product(a: List[Optional[torch.Tensor]], b: List[Optional[torch.Tensor]]) -> List[Optional[torch.Tensor]]:
|
|
34
|
+
"""Truncated product of two elements with zero scalar term. ``None`` marks a level known to be zero."""
|
|
35
|
+
depth = len(a)
|
|
36
|
+
out: List[Optional[torch.Tensor]] = [None] * depth
|
|
37
|
+
for k in range(1, depth): # output level k + 1
|
|
38
|
+
for i in range(k): # a at level i + 1, b at level k - i
|
|
39
|
+
if a[i] is not None and b[k - 1 - i] is not None:
|
|
40
|
+
term = _outer(a[i], b[k - 1 - i])
|
|
41
|
+
out[k] = term if out[k] is None else out[k] + term
|
|
42
|
+
return out
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def mult(a: Levels, b: Levels) -> Levels:
|
|
46
|
+
"""(1 + a) * (1 + b) - 1: the product of two group elements, e.g. Chen's identity for signatures."""
|
|
47
|
+
cross = _product(a, b)
|
|
48
|
+
return [x + y if c is None else x + y + c for x, y, c in zip(a, b, cross)]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def log(a: Levels) -> Levels:
|
|
52
|
+
"""log(1 + a) = a - a^2 / 2 + a^3 / 3 - ..., truncated."""
|
|
53
|
+
result = list(a)
|
|
54
|
+
power: List[Optional[torch.Tensor]] = list(a)
|
|
55
|
+
for n in range(2, len(a) + 1):
|
|
56
|
+
power = _product(power, a)
|
|
57
|
+
coeff = (-1) ** (n + 1) / n
|
|
58
|
+
result = [r if p is None else r + coeff * p for r, p in zip(result, power)]
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def inverse(a: Levels) -> Levels:
|
|
63
|
+
"""Inverse of a group-like element such as a signature: reverse every word and multiply level k by (-1)^k.
|
|
64
|
+
|
|
65
|
+
This equals the signature of the time-reversed path.
|
|
66
|
+
"""
|
|
67
|
+
channels = a[0].size(-1)
|
|
68
|
+
out = []
|
|
69
|
+
for k, x in enumerate(a, start=1):
|
|
70
|
+
batch_shape = x.shape[:-1]
|
|
71
|
+
nb = len(batch_shape)
|
|
72
|
+
x = x.reshape(*batch_shape, *([channels] * k))
|
|
73
|
+
x = x.permute(*range(nb), *range(nb + k - 1, nb - 1, -1)).reshape(*batch_shape, channels ** k)
|
|
74
|
+
out.append(x if k % 2 == 0 else -x)
|
|
75
|
+
return out
|
sigtorch/augment.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""A torch.nn.Module for augmenting streams of data before taking signatures.
|
|
2
|
+
|
|
3
|
+
Adapted from signatory's augment.py (Copyright 2019 Patrick Kidger, Apache License 2.0, see LICENSE-signatory);
|
|
4
|
+
rewritten for sigtorch's pure-PyTorch implementation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Callable, Tuple, Union
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
from torch import nn
|
|
11
|
+
from torch.nn import functional as F
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Augment(nn.Module):
|
|
15
|
+
"""Sweeps a small feedforward network along a stream, as in Bonnier et al. 2019, "Deep Signature Transforms".
|
|
16
|
+
|
|
17
|
+
The first layer is a ``Conv1d`` with the given kernel, stride, padding and dilation; later layers are pointwise
|
|
18
|
+
``Conv1d`` layers with ``activation`` in between (so the network's output is linear). Input has shape
|
|
19
|
+
(batch, stream, in_channels); the output has the network's output channels, preceded by the original stream (if
|
|
20
|
+
``include_original``) and a time channel running from 0 to 1 (if ``include_time``). Those two are aligned with
|
|
21
|
+
the last point under each kernel window, so they assume stride 1 and no padding.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, in_channels: int, layer_sizes: Union[int, Tuple[int, ...]], kernel_size: int, stride: int = 1,
|
|
25
|
+
padding: int = 0, dilation: int = 1, bias: bool = True,
|
|
26
|
+
activation: Callable[[torch.Tensor], torch.Tensor] = F.relu, include_original: bool = True,
|
|
27
|
+
include_time: bool = True, **kwargs):
|
|
28
|
+
super().__init__(**kwargs)
|
|
29
|
+
if isinstance(layer_sizes, int):
|
|
30
|
+
layer_sizes = (layer_sizes,)
|
|
31
|
+
self.in_channels = in_channels
|
|
32
|
+
self.layer_sizes = layer_sizes
|
|
33
|
+
self.kernel_size = kernel_size
|
|
34
|
+
self.stride = stride
|
|
35
|
+
self.padding = padding
|
|
36
|
+
self.dilation = dilation
|
|
37
|
+
self.bias = bias
|
|
38
|
+
self.activation = activation
|
|
39
|
+
self.include_original = include_original
|
|
40
|
+
self.include_time = include_time
|
|
41
|
+
|
|
42
|
+
self.convs = nn.ModuleList()
|
|
43
|
+
if layer_sizes:
|
|
44
|
+
self.convs.append(nn.Conv1d(in_channels, layer_sizes[0], kernel_size=kernel_size, stride=stride,
|
|
45
|
+
padding=padding, dilation=dilation, bias=bias))
|
|
46
|
+
for in_size, out_size in zip(layer_sizes[:-1], layer_sizes[1:]):
|
|
47
|
+
self.convs.append(nn.Conv1d(in_size, out_size, kernel_size=1, bias=bias))
|
|
48
|
+
|
|
49
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
50
|
+
if x.ndim != 3:
|
|
51
|
+
raise RuntimeError(f"Argument x should have three dimensions, (batch, stream, channel). Given shape "
|
|
52
|
+
f"{tuple(x.shape)}.")
|
|
53
|
+
length = x.size(1) - self.kernel_size + 1
|
|
54
|
+
pieces = []
|
|
55
|
+
if self.include_original:
|
|
56
|
+
pieces.append(x.narrow(1, self.kernel_size - 1, length))
|
|
57
|
+
if self.include_time:
|
|
58
|
+
time = torch.linspace(0, 1, length, dtype=x.dtype, device=x.device)
|
|
59
|
+
pieces.append(time.view(1, length, 1).expand(x.size(0), length, 1))
|
|
60
|
+
if self.layer_sizes:
|
|
61
|
+
out = self.convs[0](x.transpose(1, 2))
|
|
62
|
+
for conv in self.convs[1:]:
|
|
63
|
+
out = conv(self.activation(out))
|
|
64
|
+
pieces.append(out.transpose(1, 2))
|
|
65
|
+
return torch.cat(pieces, dim=2)
|
|
66
|
+
|
|
67
|
+
def extra_repr(self) -> str:
|
|
68
|
+
return (f'activation={self.activation}, include_original={self.include_original}, '
|
|
69
|
+
f'include_time={self.include_time}')
|
sigtorch/deprecated.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Deprecated interfaces, kept so that code written for signatory keeps running.
|
|
2
|
+
|
|
3
|
+
Adapted from signatory's deprecated.py (Copyright 2019 Patrick Kidger, Apache License 2.0, see LICENSE-signatory);
|
|
4
|
+
rewritten for sigtorch's pure-PyTorch implementation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import warnings
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
_max_parallelism = [8]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def max_parallelism(value: Optional[int] = None) -> int:
|
|
14
|
+
"""Deprecated and does nothing (as in signatory). Use torch.set_num_threads / torch.get_num_threads instead."""
|
|
15
|
+
warnings.warn("max_parallelism is deprecated and now does nothing. Use torch.set_num_threads and "
|
|
16
|
+
"torch.get_num_threads to control the amount of parallelism.", DeprecationWarning)
|
|
17
|
+
if value == -1:
|
|
18
|
+
value = float('inf')
|
|
19
|
+
if value is not None:
|
|
20
|
+
_max_parallelism[0] = value
|
|
21
|
+
return _max_parallelism[0]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""The logsignature transform."""
|
|
2
|
+
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
from torch import nn
|
|
7
|
+
|
|
8
|
+
from . import _tensor_algebra as ta
|
|
9
|
+
from . import signature_module as smodule
|
|
10
|
+
from . import utility
|
|
11
|
+
|
|
12
|
+
_MODES = ("words", "brackets", "expand")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _check_mode(mode: str) -> None:
|
|
16
|
+
if mode not in _MODES:
|
|
17
|
+
raise ValueError("Invalid values for argument 'mode'. Valid values are 'expand', 'brackets', or 'words'.")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _mobius(n: int) -> int:
|
|
21
|
+
result, p = 1, 2
|
|
22
|
+
while p * p <= n:
|
|
23
|
+
if n % p == 0:
|
|
24
|
+
n //= p
|
|
25
|
+
if n % p == 0:
|
|
26
|
+
return 0
|
|
27
|
+
result = -result
|
|
28
|
+
p += 1
|
|
29
|
+
return -result if n > 1 else result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def logsignature_channels(in_channels: int, depth: int) -> int:
|
|
33
|
+
"""Number of channels of a logsignature with ``mode`` "words" or "brackets" (the dimension of the truncated free
|
|
34
|
+
Lie algebra, from Witt's formula)."""
|
|
35
|
+
if in_channels < 1:
|
|
36
|
+
raise ValueError("in_channels must be at least 1")
|
|
37
|
+
if depth < 1:
|
|
38
|
+
raise ValueError("depth must be at least 1")
|
|
39
|
+
return sum(sum(_mobius(k // d) * in_channels ** d for d in range(1, k + 1) if k % d == 0) // k
|
|
40
|
+
for k in range(1, depth + 1))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _check_signature_tensor(signature: torch.Tensor, channels: int, depth: int, stream: bool,
|
|
44
|
+
scalar_term: bool) -> None:
|
|
45
|
+
ndim, dims = (3, "(batch, stream, channel)") if stream else (2, "(batch, channel)")
|
|
46
|
+
if signature.ndim != ndim:
|
|
47
|
+
raise ValueError(f"Argument 'signature' must be a {ndim}-dimensional tensor, with dimensions corresponding "
|
|
48
|
+
f"to {dims} respectively.")
|
|
49
|
+
if 0 in signature.shape:
|
|
50
|
+
raise ValueError("Argument 'signature' cannot have dimensions of size zero.")
|
|
51
|
+
if signature.size(-1) != smodule.signature_channels(channels, depth, scalar_term):
|
|
52
|
+
raise ValueError("Argument 'signature' has the wrong number of channels for the specified channels and depth.")
|
|
53
|
+
if not signature.is_floating_point():
|
|
54
|
+
raise ValueError("Argument 'signature' must be of floating point type.")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SignatureToLogSignature(nn.Module):
|
|
58
|
+
""":class:`torch.nn.Module` wrapper around :func:`sigtorch.signature_to_logsignature`."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, channels: int, depth: int, stream: bool = False, mode: str = "words",
|
|
61
|
+
scalar_term: bool = False, **kwargs):
|
|
62
|
+
super().__init__(**kwargs)
|
|
63
|
+
_check_mode(mode)
|
|
64
|
+
utility._check_channels_depth(channels, depth)
|
|
65
|
+
self._channels = channels
|
|
66
|
+
self._depth = depth
|
|
67
|
+
self._stream = stream
|
|
68
|
+
self._mode = mode
|
|
69
|
+
self._scalar_term = scalar_term
|
|
70
|
+
|
|
71
|
+
def forward(self, signature: torch.Tensor) -> torch.Tensor:
|
|
72
|
+
_check_signature_tensor(signature, self._channels, self._depth, self._stream, self._scalar_term)
|
|
73
|
+
levels = ta.split_levels(signature[..., int(self._scalar_term):], self._channels, self._depth)
|
|
74
|
+
levels = ta.log(levels)
|
|
75
|
+
if self._mode == "expand":
|
|
76
|
+
return torch.cat(levels, dim=-1)
|
|
77
|
+
out = []
|
|
78
|
+
for length, level in enumerate(levels, start=1):
|
|
79
|
+
words = level[..., utility.lyndon_flat_indices(self._channels, length, level.device)]
|
|
80
|
+
if self._mode == "brackets":
|
|
81
|
+
words = utility.words_to_brackets(words, self._channels, length)
|
|
82
|
+
out.append(words)
|
|
83
|
+
return torch.cat(out, dim=-1)
|
|
84
|
+
|
|
85
|
+
def extra_repr(self) -> str:
|
|
86
|
+
return (f'channels={self._channels}, depth={self._depth}, stream={self._stream}, mode={self._mode!r}')
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
SignatureToLogsignature = SignatureToLogSignature
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def signature_to_logsignature(signature: torch.Tensor, channels: int, depth: int, stream: bool = False,
|
|
93
|
+
mode: str = "words", scalar_term: bool = False) -> torch.Tensor:
|
|
94
|
+
"""The logsignature corresponding to a signature computed by :func:`sigtorch.signature` with the same
|
|
95
|
+
``channels``, ``depth``, ``stream`` and ``scalar_term``. ``mode`` as :func:`sigtorch.logsignature`."""
|
|
96
|
+
return SignatureToLogSignature(channels, depth, stream, mode, scalar_term)(signature)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class LogSignature(nn.Module):
|
|
100
|
+
""":class:`torch.nn.Module` wrapper around :func:`sigtorch.logsignature`."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, depth: int, stream: bool = False, inverse: bool = False, mode: str = "words", **kwargs):
|
|
103
|
+
super().__init__(**kwargs)
|
|
104
|
+
_check_mode(mode)
|
|
105
|
+
self._depth = depth
|
|
106
|
+
self._stream = stream
|
|
107
|
+
self._inverse = inverse
|
|
108
|
+
self._mode = mode
|
|
109
|
+
|
|
110
|
+
def prepare(self, in_channels: int) -> None:
|
|
111
|
+
"""Builds the Lyndon tables for paths with ``in_channels`` channels ahead of the first call."""
|
|
112
|
+
for length in range(1, self._depth + 1):
|
|
113
|
+
utility._lyndon_flat_indices_cpu(in_channels, length)
|
|
114
|
+
if self._mode == "brackets":
|
|
115
|
+
utility._words_to_brackets_cpu(in_channels, length)
|
|
116
|
+
|
|
117
|
+
def forward(self, path: torch.Tensor, basepoint: Union[bool, torch.Tensor] = False) -> torch.Tensor:
|
|
118
|
+
signature = smodule.signature(path, self._depth, stream=self._stream, basepoint=basepoint,
|
|
119
|
+
inverse=self._inverse)
|
|
120
|
+
return signature_to_logsignature(signature, path.size(-1), self._depth, self._stream, self._mode)
|
|
121
|
+
|
|
122
|
+
def extra_repr(self) -> str:
|
|
123
|
+
return (f'depth={self._depth}, stream={self._stream}, inverse={self._inverse}, mode={self._mode!r}')
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
Logsignature = LogSignature
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def logsignature(path: torch.Tensor, depth: int, stream: bool = False, basepoint: Union[bool, torch.Tensor] = False,
|
|
130
|
+
inverse: bool = False, mode: str = "words") -> torch.Tensor:
|
|
131
|
+
"""Logsignature transform of a batch of paths, as ``signatory.logsignature``.
|
|
132
|
+
|
|
133
|
+
Arguments ``path``, ``depth``, ``stream``, ``basepoint`` and ``inverse`` are as in :func:`sigtorch.signature`.
|
|
134
|
+
``mode`` selects the representation:
|
|
135
|
+
|
|
136
|
+
- ``"words"`` (default): coefficients of the Lyndon words in the tensor-algebra logarithm, one channel per Lyndon
|
|
137
|
+
word (see :func:`sigtorch.lyndon_words`).
|
|
138
|
+
- ``"brackets"``: coefficients in the Lyndon basis of the free Lie algebra (see :func:`sigtorch.lyndon_brackets`).
|
|
139
|
+
- ``"expand"``: every coefficient of the tensor-algebra logarithm; same shape as the signature.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Tensor of shape (batch, logsignature_channels(channels, depth)) for "words"/"brackets", with a stream
|
|
143
|
+
dimension inserted as for :func:`sigtorch.signature` if ``stream``.
|
|
144
|
+
"""
|
|
145
|
+
return LogSignature(depth, stream=stream, inverse=inverse, mode=mode)(path, basepoint=basepoint)
|