Combining signaturesΒΆ

Suppose we have two paths, and want to combine their signatures. That is, we know the signatures of the two paths, and would like to know the signature of the two paths concatenated together. This can be done with the signatory.signature_combine() function.

import torch
import signatory

depth = 3
input_channels = 5
path1 = torch.rand(1, 10, input_channels)
path2 = torch.rand(1, 5, input_channels)
sig_path1 = signatory.signature(path1, depth)
sig_path2 = signatory.signature(path2, depth,
                                basepoint=path1[:, -1, :])

### OPTION 1: efficient, using signature_combine
sig_combined = signatory.signature_combine(sig_path1, sig_path2,
                                           input_channels, depth)

### OPTION 2: inefficient, without using signature_combine
path_combined = torch.cat([path1, path2], dim=1)
sig_combined = signatory.signature(path_combined, depth)

### Both options will produce the same value for sig_combined

Danger

Note in particular that the end of path1 is used as the basepoint when calculating sig_path2 in Option 1. It is important that path2 starts from the same place that path1 finishes. Otherwise there will be a jump between the end of path1 and the start of path2 which the signature will not see.

If it is known that path1[:, -1, :] == path2[:, 0, :], so that in fact path1 does finish where path2 starts, then only in this case can the use of basepoint safely be skipped. (And if basepoint is set to this value then it will not change the result.)

With Option 2 it is clearest what is being computed. However this is also going to be slower: the signature of path1 is already known, but Option 2 does not use this information at all, and instead performs a lot of unnecessary computation. Furthermore its calculation requires holding all of path1 in memory, instead of just path1[:, -1, :].

Note how with Option 1, once sig_path1 has been computed, then the only thing that must now be held in memory is sig_path1 and path1[:, -1, :]. This means that the amount of memory required is independent of the length of path1. Thus if path is very long, or can grow to arbitrary length as time goes by, then the use of this option (over Option 2) is crucial.

Tip

Combining signatures in this way is the most sensible way to do things if the signature of path2 is actually desirable information on its own.

However if only the signature of the combined path is of interest, then this can be computed even more efficiently by

sig_path1 = signatory.signature(path1, depth)
sig_combined = signatory.signature(path2, depth,
                                   basepoint=path1[:, -1, :],
                                   initial=sig_path1)

For further examples of this nature, see Computing the signature of an incoming stream of data.