Exemple #1
0
def setup(obj):
    torch.set_num_threads(1)

    obj.path = torch.rand(obj.size, dtype=torch.float, requires_grad=True)
    shape = obj.size[-3], signatory.logsignature_channels(obj.size[-1], obj.depth)
    obj.grad = torch.rand(shape)
    obj.logsignature = signatory.LogSignature(obj.depth)(obj.path)
def setup(obj):
    obj.path = torch.rand(obj.size,
                          dtype=torch.float,
                          requires_grad=True,
                          device='cuda')
    shape = obj.size[-3], signatory.logsignature_channels(
        obj.size[-1], obj.depth)
    obj.grad = torch.rand(shape, device='cuda')
    obj.logsignature = signatory.LogSignature(obj.depth)(obj.path)
    def __init__(self, input_dim, depth, logsig, basepoint=True):
        super().__init__()

        self.basepoint = basepoint
        self.depth = depth
        self.logsig = logsig
        self.sig_func = signatory.logsignature if self.logsig else signatory.signature
        self.terms = signatory.logsignature_channels(
            input_dim,
            self.depth) if self.logsig else signatory.signature_channels(
                input_dim, self.depth)
        if self.terms >= TERMS_LIMIT:
            raise ImportError(
                "Number of signature terms {} is greater than {}..".format(
                    self.terms, TERMS_LIMIT))
Exemple #4
0
    def transform(self, sequence) -> torch.Tensor:
        embedded = sequence['embedded']

        sig_func = signatory.logsignature if self.logsig else signatory.signature

        terms = signatory.logsignature_channels(
            embedded.shape[-1],
            self.depth) if self.logsig else signatory.signature_channels(
                embedded.shape[-1], self.depth)
        if terms >= TERMS_LIMIT:
            raise ImportError(
                "Number of signature terms is greater than {}..".format(
                    TERMS_LIMIT))

        signature_terms = sig_func(path=embedded,
                                   depth=self.depth,
                                   basepoint=self.basepoint)
        return signature_terms
    def __init__(self, in_channels, depth, p=2, include_time=True, pseudometric=True, metric_type='general'):
        """
        Called with some path `f` and some shapelet `g` such that
        ```
        f, g \colon [0, T] \to R^in_channels,
        ```
        which are described by as being the unique continuous piecewise linear functions such that
        ```
        f(times[i]) == path1[i] for all i,
        g(times[i]) == path2[i] for all i,
        ```
        where `times` is a strictly increasing 1D tensor of shape (length,) for some  value `length`, and `path1` is a
        tensor of shape (..., length, in_channels) and `path2` is a tensor of shape (*, length, in_channels), where
        '...' and '*' represent potentially different batch dimensions.

        Then let `q = logsig(f, depth) - logsig(g, depth)` be the difference in their logsignatures to depth `depth`,
        which will be a vector of size `l = logsignature_channels(in_channels, depth)`.

        Then this discrepancy calculates
        ```
        ||Aq||_p
        ```
        where ||.||_p denotes the L^p vector norm, and A is a matrix of shape (l, l), which:
            - will be the identity matrix if pseudometric==False
            - will be learnt and diagonal if pseudometric=True and metric_type == 'diagonal'
            - will be learnt and square if pseudometric==True and metric_type == 'general'.

        The return value will be a tensor of shape (..., *).

        Arguments:
            in_channels: The number of input channels of the path.
            depth: An integer describing the depth of the logsignature transform to take.
            p: A number in [1, \infty] specifying the parameter p of the distance. Defaults to 2.
            include_time: Boolean. Whether to take the logsignature discrepancy of the time-augmented path or not.
                Defaults to True. (Setting this to False produces a pseudometric rather similar in spirit to dynamic
                time warping, in that it's reparameterisation invariant.)
            pseudometric: Whether to take a learnt linear transformation beforehand. Defaults to True.
            metric_type: Either 'general' or 'diagonal'. Whether to take a general learnt linear transformation or just
                a diagonal one. Defaults to 'general'.
        """
        super(LogsignatureDiscrepancy, self).__init__()

        assert metric_type in ('general', 'diagonal'), "Valid values for 'metric_type' are 'general' and 'diagonal'."
        if signatory is None:
            raise ImportError("Signatory must be installed to compute logsignature discrepancies. It can be found at "
                              "`https://github.com/patrick-kidger/signatory`. See also the installation instructions "
                              "for `torchshapelets` at "
                              "`https://github.com/jambo6/generalised_shapelets/tree/master/torchshapelets`. ")

        self.in_channels = in_channels
        self.depth = depth
        self.p = p
        self.include_time = include_time
        self.pseudometric = pseudometric
        self.metric_type = metric_type

        if pseudometric:
            channels = in_channels
            if include_time:
                channels += 1
            logsignature_channels = signatory.logsignature_channels(channels, depth)
            if metric_type == 'general':
                linear = torch.randn(logsignature_channels, logsignature_channels, requires_grad=True)
                torch.nn.init.kaiming_uniform_(linear, a=math.sqrt(5))
            else:
                linear = torch.randn(logsignature_channels, requires_grad=True)
                torch.nn.init.uniform_(linear, 0.9, 1.1)
            self.linear = torch.nn.Parameter(linear)
        else:
            self.register_parameter('linear', None)

        self.logsignature = signatory.Logsignature(depth=depth)
Exemple #6
0
def _logsignature_windows(x, depth, window_length, t, _version):
    t = misc.validate_input_path(x, t)

    # slightly roundabout way of doing things (rather than using arange) so that it's constructed differentiably
    timespan = t[-1] - t[0]
    num_pieces = (timespan / window_length).ceil().to(int).item()
    end_t = t[0] + num_pieces * window_length
    new_t = torch.linspace(t[0],
                           end_t,
                           num_pieces + 1,
                           dtype=t.dtype,
                           device=t.device)
    new_t = torch.min(new_t, t.max())

    t_index = 0
    new_t_unique = []
    new_t_indices = []
    for new_t_elem in new_t:
        while True:
            lequal = (new_t_elem <= t[t_index])
            close = new_t_elem.allclose(t[t_index])
            if lequal or close:
                break
            t_index += 1
        new_t_indices.append(t_index + len(new_t_unique))
        if close:
            continue
        new_t_unique.append(new_t_elem.unsqueeze(0))

    batch_dimensions = x.shape[:-2]

    missing_X = torch.full((1, ), float('nan'), dtype=x.dtype,
                           device=x.device).expand(*batch_dimensions, 1,
                                                   x.size(-1))
    if len(new_t_unique) > 0:  # no-op if len == 0, so skip for efficiency
        t, indices = torch.cat([t, *new_t_unique]).sort()
        x = torch.cat([x, missing_X], dim=-2)[...,
                                              indices.clamp(0, x.size(-2)), :]

    # Fill in any missing data linearly (linearly because that's what signatures do in between observations anyway)
    # and conveniently that's what this already does. Here 'missing data' includes the NaNs we've just added.
    x = interpolation_linear.linear_interpolation_coeffs(x, t)

    # Flatten batch dimensions for compatibility with Signatory
    flatten_X = x.view(-1, x.size(-2), x.size(-1))
    first_increment = torch.zeros(*batch_dimensions,
                                  signatory.logsignature_channels(
                                      x.size(-1), depth),
                                  dtype=x.dtype,
                                  device=x.device)
    first_increment[..., :x.size(-1)] = x[..., 0, :]
    logsignatures = [first_increment]
    compute_logsignature = signatory.Logsignature(depth=depth)
    for index, next_index, time, next_time in zip(new_t_indices[:-1],
                                                  new_t_indices[1:],
                                                  new_t[:-1], new_t[1:]):
        logsignature = compute_logsignature(flatten_X[...,
                                                      index:next_index + 1, :])
        logsignature = logsignature.view(*batch_dimensions, -1)
        if _version == 0:
            logsignature = logsignature * (next_time - time)
        elif _version == 1:
            pass
        else:
            raise RuntimeError
        logsignatures.append(logsignature)

    logsignatures = torch.stack(logsignatures, dim=-2)
    logsignatures = logsignatures.cumsum(dim=-2)

    if _version == 0:
        return logsignatures, new_t
    elif _version == 1:
        return logsignatures
    else:
        raise RuntimeError
Exemple #7
0
def logsignature_windows(x, depth, window_length, t=None):
    """Calculates logsignatures over multiple windows, for the batch of controls given, as in the log-ODE method.
    
    This corresponds to a transform of the time series, and should be used prior to applying one of the interpolation
    schemes.

    Arguments:
        x: tensor of values, of shape (..., length, input_channels), where ... is some number of batch dimensions. This
            is interpreted as a (batch of) paths taking values in an input_channels-dimensional real vector space, with
            length-many observations. Missing values are supported, and should be represented as NaNs.
        depth: What depth to compute the logsignatures to.
        window_length: How long a time interval to compute logsignatures over.
        t: Optional one dimensional tensor of times. Must be monotonically increasing. If not passed will default to
            tensor([0., 1., ..., length - 1]).

    Warning:
        If there are missing values then calling this function can be pretty slow. Make sure to cache the result, and
        don't reinstantiate it on every forward pass, if at all possible.

    Returns:
        A tuple of two tensors, which are the values and times of the transformed path.
    """
    t = misc.validate_input_path(x, t)

    # slightly roundabout way of doing things (rather than using arange) so that it's constructed differentiably
    timespan = t[-1] - t[0]
    num_pieces = (timespan / window_length).ceil().to(int).item()
    end_t = t[0] + num_pieces * window_length
    new_t = torch.linspace(t[0],
                           end_t,
                           num_pieces + 1,
                           dtype=t.dtype,
                           device=t.device)
    new_t = torch.min(new_t, t.max())

    t_index = 0
    new_t_unique = []
    new_t_indices = []
    for new_t_elem in new_t:
        while True:
            lequal = (new_t_elem <= t[t_index])
            close = new_t_elem.allclose(t[t_index])
            if lequal or close:
                break
            t_index += 1
        new_t_indices.append(t_index + len(new_t_unique))
        if close:
            continue
        new_t_unique.append(new_t_elem.unsqueeze(0))

    batch_dimensions = x.shape[:-2]

    missing_X = torch.full((1, ), float('nan'), dtype=x.dtype,
                           device=x.device).expand(*batch_dimensions, 1,
                                                   x.size(-1))
    if len(new_t_unique) > 0:  # no-op if len == 0, so skip for efficiency
        t, indices = torch.cat([t, *new_t_unique]).sort()
        x = torch.cat([x, missing_X], dim=-2)[...,
                                              indices.clamp(0, x.size(-2)), :]

    # Fill in any missing data linearly (linearly because that's what signatures do in between observations anyway)
    # and conveniently that's what this already does. Here 'missing data' includes the NaNs we've just added.
    x = interpolation_linear.linear_interpolation_coeffs(x, t)

    # Flatten batch dimensions for compatibility with Signatory
    flatten_X = x.view(-1, x.size(-2), x.size(-1))
    first_increment = torch.zeros(*batch_dimensions,
                                  signatory.logsignature_channels(
                                      x.size(-1), depth),
                                  dtype=x.dtype,
                                  device=x.device)
    first_increment[..., :x.size(-1)] = x[..., 0, :]
    logsignatures = [first_increment]
    compute_logsignature = signatory.Logsignature(depth=depth)
    for index, next_index, time, next_time in zip(new_t_indices[:-1],
                                                  new_t_indices[1:],
                                                  new_t[:-1], new_t[1:]):
        logsignature = compute_logsignature(flatten_X[...,
                                                      index:next_index + 1, :])
        logsignature = logsignature.view(*batch_dimensions,
                                         -1) * (next_time - time)
        logsignatures.append(logsignature)

    logsignatures = torch.stack(logsignatures, dim=-2)
    logsignatures = logsignatures.cumsum(dim=-2)

    return logsignatures, new_t