def get_data(order, params, freq, ll, scale): Batchsize = params['M'] train_windows, train_path, time = _load_rough_bergomi(params, freq, ll) train_logsig = signatory.logsignature(torch.tensor(train_windows), order).numpy() train_sig = signatory.signature(torch.tensor(train_windows), order).numpy() train_sig_exp = np.concatenate([np.ones([Batchsize, 1]), train_sig], axis=1) # train_sig_normalized = normalize.normalize_sig(2, order, train_sig_exp) test_windows, test_path, time = _load_rough_bergomi(params, freq, ll) test_logsig = signatory.logsignature(torch.tensor(test_windows), order).numpy() test_sig = signatory.signature(torch.tensor(test_windows), order).numpy() test_sig_exp = np.concatenate([np.ones([Batchsize, 1]), test_sig], axis=1) if scale: scaler_logsig = MinMaxScaler(feature_range=(0.00001, 0.99999)) logsig_transformed = scaler_logsig.fit_transform(train_logsig) data = logsig_transformed[1:] # 1 week forecasting 1 week data_cond = logsig_transformed[:-1] data_cond = np.zeros_like( data_cond) ######################################### for VAE scaler = scaler_logsig else: logsig_transformed = None scaler = None data = train_logsig[1:] # 1 week forecasting 1 week data_cond = train_logsig[:-1] data_cond = np.zeros_like( data_cond) ######################################### for VAE return train_windows, train_path, train_logsig, train_sig, train_sig_exp, test_windows, test_path,\ test_logsig, test_sig, test_sig_exp, logsig_transformed, data, data_cond, scaler
def test_insertion_matrix_structure(): """Tests that the insertion matrix has identical singular values, all equal to the norm of the signature of order depth""" batch_size = 1 for input_stream in (2, 3, 10): for input_channels in (1, 2, 6): path = torch.rand((batch_size, input_stream, input_channels)) for depth in (1, 2, 4, 6): signature = signatory.signature(path, depth) norm_sign = torch.norm( signatory.extract_signature_term(signature, input_channels, depth)[0])**2 for insertion_position in range(1, depth + 1): insertion_matrix = get_insertion_matrix( signature, insertion_position, depth, input_channels)[0] diagonal_matrix = torch.matmul(insertion_matrix, insertion_matrix.T) assert diagonal_matrix.shape == (input_channels, input_channels) assert torch.allclose(diagonal_matrix[0, 0], norm_sign, atol=1e-05) if input_channels > 1: assert diagonal_matrix[0, input_channels - 1] == 0. assert torch.allclose( diagonal_matrix[input_channels - 1, input_channels - 1], diagonal_matrix[0, 0])
def get_stream_signatures(self, ts: torch.Tensor, x: torch.Tensor, lag: int): """ Given a path, get the stream of signatures Parameters ---------- ts: torch.Tensor Time discretisation. x: torch.Tensor Tensor of size (batch_size, n_steps, d) """ device = x.device batch_size = x.shape[0] path_signature = torch.zeros(batch_size, len(range(0, len(ts), lag)), self.sig_channels, device=device) basepoint = torch.zeros(batch_size, 1, self.d, device=device) for idx, id_t in enumerate(range(0, len(ts), lag)): if idx == 0: portion_path = torch.cat([basepoint, x[:, 0, :].unsqueeze(1)], 1) else: portion_path = x[:, id_t - lag:id_t + 1, :] augmented_path = apply_augmentations(portion_path, self.augmentations) path_signature[:, idx, :] = signatory.signature( augmented_path, self.depth) return path_signature
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.signature_channels(obj.size[-1], obj.depth) obj.grad = torch.rand(shape) obj.signature = signatory.signature(obj.path, obj.depth)
def get_initial(batch_size, input_channels, device, depth, initial, scalar_term): """Gets a value for the 'initial' argument of signatory.signature.""" if initial in (without_grad, with_grad): initial_path = torch.rand(batch_size, 2, input_channels, device=device, dtype=torch.double) initial_signature = signatory.signature(initial_path, depth, scalar_term=scalar_term) if initial == with_grad: initial_signature.requires_grad_() return initial_signature else: return initial
def prepare_data(self, ts: torch.Tensor, x0: torch.Tensor, lag: int): """ Prepare the data: 1. Solve the sde using some sde solver on a fine time discretisation 2. calculate path signature between consecutive timesteps of a coarser time discretisation 3. Calculate increments of brownian motion on the coarser time discretisation Parameters ---------- ts: torch.Tensor Time discrstisation. Tensor of size (n_steps + 1) x0: torch.Tensor initial value of paths. Tensor of size (batch_size, d) lag: int lag used to create the coarse time discretisation in terms of the fine time discretisation. Returns ------- x: torch.Tensor Solution of the SDE on the fine time discretisation. Tensor of shape (batch_size, n_steps+1, d) path_signature: torch.Tensor Stream of signatures. Tensor of shape (batch_size, n_steps/lag + 1, sig_channels) sum_increments: torch.Tensor Increments of the Brownian motion on the coarse time discretisation. Tensor of shape (batch_size, n_steps/lag+1, d) """ x, brownian_increments = self.sdeint(ts, x0) device = x.device batch_size = x.shape[0] path_signature = torch.zeros(batch_size, len(range(0, len(ts), lag)), self.sig_channels, device=device) sum_increments = torch.zeros(batch_size, len(range(0, len(ts), lag)), self.d, device=device) basepoint = torch.zeros(batch_size, 1, self.d, device=device) for idx, id_t in enumerate(range(0, len(ts), lag)): if idx == 0: portion_path = torch.cat([basepoint, x[:, 0, :].unsqueeze(1)], 1) else: portion_path = x[:, id_t - lag:id_t + 1, :] augmented_path = apply_augmentations(portion_path, self.augmentations) path_signature[:, idx, :] = signatory.signature( augmented_path, self.depth) try: sum_increments[:, idx, :] = torch.sum( brownian_increments[:, id_t:id_t + lag], 1) except: pass # it is the last point and we don't have anymore increments, but that's okay, because at the last step of the bsde, we compare thes olution of the bsde against the payoff of the option return x, path_signature, sum_increments
def test_inverted_path_shape(): """Tests that the inverted path is of the right shape""" for batch_size in (1, 2, 5): for input_stream in (2, 3, 10): for input_channels in (1, 2, 6): path = torch.rand((batch_size, input_stream, input_channels)) for depth in (2, 4, 6): signature = signatory.signature(path, depth) inverted_path = invert_signature( signature, depth, input_channels, initial_position=path[:, 0, :]) assert inverted_path.shape == (batch_size, depth + 1, input_channels)
def test_insertion_matrix_shape(): """ Tests that the insertion matrix obtained is of the right shape""" for batch_size in (1, 2, 5): for input_stream in (2, 3, 10): for input_channels in (1, 2, 6): path = torch.rand((batch_size, input_stream, input_channels)) for depth in (1, 2, 4, 6): signature = signatory.signature(path, depth) correct_shape = (batch_size, input_channels, input_channels**(depth + 1)) for insertion_position in range(1, depth + 1): insertion_matrix = get_insertion_matrix( signature, insertion_position, depth, input_channels) assert insertion_matrix.shape == correct_shape
def get_initial(batch_size, input_channels, device, depth, initial): """Gets a value for the 'initial' argument of signatory.signature, and also returns the path it used to generate that value.""" if initial in (without_grad, with_grad): initial_path = torch.rand(batch_size, 2, input_channels, device=device, dtype=torch.double) initial_signature = signatory.signature(initial_path, depth) if initial == with_grad: initial_signature.requires_grad_() return initial_signature else: return initial
def test_initial_position_zero(): """Tests that the inverted path initial position is the right one.""" batch_size = 10 input_stream = 10 input_channels = 3 path = torch.rand((batch_size, input_stream, input_channels)) for depth in (2, 4, 6): signature = signatory.signature(path, depth) inverted_path = invert_signature(signature, depth, input_channels) assert torch.equal(inverted_path[:, 0, :], torch.zeros(batch_size, input_channels)) initial_position = torch.rand((batch_size, input_channels)) inverted_path = invert_signature(signature, depth, input_channels, initial_position=initial_position) assert torch.equal(inverted_path[:, 0, :], initial_position)
def forward(self, x, lengths): # `x` should be a three dimensional tensor (batch, stream, channel) # `lengths` should be a one dimensional tensor (batch,) giving the true length of each batch element along the # stream dimension batch, stream, channel = x.shape x = become_constant_trick(x, lengths) x = stack([augment(x) for augment in self.augments], dim=1) # channel_group is essentially an extra batch dimension, but unfortunately signatory.signature doesn't support # multiple batch dimensions. So the trick is just to combine all the batch dimensions and then peel them apart # again afterwards. x = x.view(batch * self.channel_groups, stream, x.size(-1)) x = signatory.signature(x, self.sig_depth) x = x.view(batch, -1) x = self.neural(x) return x
def sig_stream1(path, depth): # Unified depth path = torch.Tensor(path[None, :, :]) batch, length, channels = path.shape length = length - 1 index2word = index_to_word(channels, depth) dim_sig = len(index2word) + 1 sig_path_split = [ signatory.signature(path[:, i:i + 2, :], depth) for i in range(length) ] add = sig_path_split[0] sig_path_stream = [add[:, None, :]] for i in range(len(sig_path_split) - 1): add = signatory.signature_combine(add, sig_path_split[i + 1], channels, depth) sig_path_stream.append(add[:, None, :]) sig_path_stream = torch.cat(sig_path_stream, axis=1) sig_path_stream = torch.cat( [torch.zeros([batch, 1, dim_sig - 1]), sig_path_stream], axis=1) sig_path_stream = torch.cat( [torch.ones([batch, length + 1, 1]), sig_path_stream], axis=2) return sig_path_stream
def make_signature(path="/home/azureuser/ifs/data/", date_str="2019/12/30", freq='5T'): df, symbol_list = read_from_db(path, date_str) df = df[df.filter(regex='^time$|price|size|no|orders|^sym$').columns] # Columns after dropping # print("Num cols after filtering ", len(df.columns)) df_g = df.groupby([ pd.Grouper(key='time', freq=freq), 'sym' ]).mean().apply(lambda x: (x - min(x)) / (max(x) - min(x))).unstack( fill_value=0).stack().fillna(0) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") data_tensor = torch.from_numpy(df_g.values).float().to(device) rows = data_tensor.shape[0] cols = data_tensor.shape[1] batch = rows // len(symbol_list) path = data_tensor.reshape([batch, len(symbol_list), cols]) sig = signatory.signature(path, 3) print("Signature ", sig) return sig
def sig(step, stride, level, data): size = data.shape[0] frame = data.shape[1] data = data.reshape(size, 39, 19, 3) data = np.swapaxes(data, 1, 2) data = data.reshape(size * 19, 39, 3) sig = list() for i in range(0, frame - step + 1, stride): temp_data = data[:, i:i + step, :] temp_data = torch.from_numpy(temp_data).float() temp_sig = (signatory.logsignature(temp_data, level)) temp_data = temp_data.cpu().numpy() temp_sig = temp_sig.cpu().numpy() temp_data = np.swapaxes(temp_data, 0, 1) temp_data = temp_data[0] att = np.concatenate((temp_data, temp_sig), axis=1) sig.append(att) if frame % stride > 1: temp_data1 = data[:, frame - step + 1:frame, :] temp_data1 = torch.from_numpy(temp_data1).float() temp_sig1 = (signatory.signature(temp_data1, level)) temp_data1 = temp_data1.cpu().numpy() temp_sig1 = temp_sig1.cpu().numpy() temp_data1 = np.swapaxes(temp_data1, 0, 1) temp_data1 = temp_data1[0] att1 = np.concatenate((temp_data1, temp_sig1), axis=1) sig.append(att1) sig = np.swapaxes(sig, 0, 1) t1 = sig.shape[1] s1 = sig.shape[2] sig = sig.reshape(size, 19, t1, s1) sig = np.swapaxes(sig, 1, 3) sig = np.expand_dims(sig, axis=-1) return s1, sig
def augment_path_and_compute_signatures( x: torch.Tensor, config: SignatureConfig) -> torch.Tensor: y = apply_augmentations(x, config.augmentations) return signatory.signature(y, config.depth, basepoint=config.basepoint)
def run(obj): result = signatory.signature(obj.path, obj.depth) torch.cuda.synchronize() return result
batch = signature.shape[0] path_derivatives = torch.zeros((batch, depth, channels)) path = torch.zeros((batch, depth + 1, channels)) if initial_position is not None: path[:, 0, :] = initial_position if depth == 1: path[:, 1, :] = path[:, 0, :] + signature else: for insertion_position in torch.arange(1, depth + 1): x_optimal = solve_optimization_problem(signature, insertion_position, depth, channels) path_derivatives[:, insertion_position - 1, :] = x_optimal path[:, insertion_position, :] = ( path[:, insertion_position - 1, :] + path_derivatives[:, insertion_position - 1, :] * (1 / depth)) return path if __name__ == '__main__': depth_test = 1 channels_test = 2 batch_test = 10 test_path = torch.rand((batch_test, 100, channels_test)) signature_test = signatory.signature(test_path, depth_test) invert_signature(signature_test, depth_test, channels_test)
import matplotlib import matplotlib.pyplot as plt import math import signatory import torch matplotlib.rc('text', usetex=True) matplotlib.rc('font', size=10) def save(name): plt.tight_layout() plt.savefig(name) plt.close() time = torch.linspace(0, 1, 10) path = torch.stack([torch.cos(math.pi * time), torch.sin(math.pi * time)]).T.unsqueeze(0) depth = 11 signature = signatory.signature(path, depth) reconstructed_path = signatory.invert_signature(signature, depth, path.shape[2], initial_position=path[:, 0, :]) plt.plot(path[0, :, 0], path[0, :, 1], marker='o', label='original') plt.plot(reconstructed_path[0, :, 0], reconstructed_path[0, :, 1], marker='o', label='reconstruction') plt.legend() save('Half_circle_inversion.png')
out = torch.cat(outs, dim=-1) if self.affine: if self.affine_graded == self.affine_channel: out = self._affine_layer(out) else: end = 0 term_length = 1 outs = [] for affine_layer in self._affine_layers: start = end term_length *= self.channels end = start + term_length outs.append(affine_layer(out[:, start:end])) # TODO: not super efficient if we've just cat'd them together above, to split them apart and cat them # again now. out = torch.cat(outs, dim=-1) return out if __name__ == '__main__': import torch import signatory a = torch.randn(2, 100, 10) signature = [[signatory.signature(a, 2)]] normalisation = GradedNormalization(shape=signature[0][0].shape, channels=10, depth=2) normalisation.forward(signature)
def run(obj): return signatory.signature(obj.path, obj.depth)
def transform(self, X): if self.append_zero: X = AppendZero().transform(X) return torch.cumsum(X, 1) class AppendZero(): """ This will append a zero starting vector to every path. """ def __init__(self): pass def fit(self, X, y=None): return self def transform(self, X): zero_vec = torch.zeros(size=(X.size(0), 1, X.size(2))) return torch.cat((zero_vec, X), dim=1) if __name__ == '__main__': a = torch.randn((10, 50, 10)) a = torch.rand((1, 10, 2)) penoff = PenOff().transform(a) true_sig = signatory.signature(penoff, 3) c = torch.cat((torch.ones(1, a.shape[1], 1), a), dim=2) basic_sig = signatory.signature(c, 3) d = penoff[:, -2:, :] new_sig = signatory.signature(d, 3, basepoint=c[:, -1, :]) signatory.signature_combine(basic_sig, new_sig, input_channels=3, depth=3)
def compute_signature(x, scalar, sig_depth, with_time, with_lift, with_concat, m): y = postprocess(x, scalar, with_time, with_lift, with_concat, m) return signatory.signature(y, sig_depth)