From a70c9b7fefb4e01b6940ea157b7b69648ef2778e Mon Sep 17 00:00:00 2001 From: Si11ium <steffen.illium@ifi.lmu.de> Date: Sun, 29 Sep 2019 09:37:30 +0200 Subject: [PATCH] Visualization approach n --- dataset.py | 58 ++- eval/metrices.py | 6 + networks/adverserial_auto_encoder.py | 8 +- networks/attention_based_auto_enoder.py | 8 +- networks/auto_encoder.py | 4 +- networks/modules.py | 75 +++- .../seperating_adversarial_auto_encoder.py | 19 +- networks/variational_auto_encoder.py | 14 +- run_models.py | 77 ++-- viz/output.png | Bin 0 -> 55875 bytes viz/print_movement_in_map.py | 50 +++ viz/utils.py | 341 +++++++++++++++++- viz/viz_latent.py | 84 ++--- viz/viz_map.py | 50 --- viz/viz_prediction_in_map.py | 55 +++ 15 files changed, 652 insertions(+), 197 deletions(-) create mode 100644 eval/metrices.py create mode 100644 viz/output.png create mode 100644 viz/print_movement_in_map.py delete mode 100644 viz/viz_map.py create mode 100644 viz/viz_prediction_in_map.py diff --git a/dataset.py b/dataset.py index 19c4499..7987c99 100644 --- a/dataset.py +++ b/dataset.py @@ -1,9 +1,11 @@ import argparse +import bisect from collections import defaultdict from distutils.util import strtobool import os import ast from abc import ABC, abstractmethod +from torch.nn.modules import BatchNorm1d from tqdm import tqdm import numpy as np @@ -61,10 +63,9 @@ class AbstractDataset(ConcatDataset, ABC): def processed_paths(self): return [os.path.join(self.path, 'processed', x) for x in self.processed_filenames] - def __init__(self, path, refresh=False, transforms=None, **kwargs): + def __init__(self, path, refresh=False, **kwargs): self.path = path self.refresh = refresh - self.transforms = transforms or None self.maps = list(set([x.name.split('_')[0] for x in os.scandir(os.path.join(self.path, 'raw'))])) super(AbstractDataset, self).__init__(datasets=self._load_datasets()) @@ -139,7 +140,19 @@ class DataContainer(AbstractDataset): for attr, x in zip(headers, line.rstrip().split(delimiter)[None:None]): if attr not in ['inDoor']: dataDict[attr].append(ast.literal_eval(x)) - return Trajectories(self.size, self.step, headers, transforms=self.transforms, **dataDict) + return Trajectories(self.size, self.step, headers, **dataDict, normalize=True) + + def get_both_by_key(self, item): + if item < 0: + if -item > len(self): + raise ValueError("absolute value of index should not exceed dataset length") + item = len(self) + item + dataset_idx = bisect.bisect_right(self.cumulative_sizes, item) + if dataset_idx == 0: + sample_idx = item + else: + sample_idx = item - self.cumulative_sizes[dataset_idx - 1] + return self.datasets[dataset_idx].get_both_by_key(sample_idx) class Trajectories(Dataset): @@ -153,12 +166,12 @@ class Trajectories(Dataset): def features(self): return len(self.isovistMeasures) - def __init__(self, size, step, headers, transforms=None, **kwargs): + def __init__(self, size, step, headers, normalize=True, **kwargs): super(Trajectories, self).__init__() self.size: int = size self.step: int = step self.headers: list = headers - self.transforms: list = transforms or list() + self.normalize: bool = normalize self.data = self.__init_data_(**kwargs) pass @@ -170,9 +183,10 @@ class Trajectories(Dataset): # Check if all keys are of same length assert len(set(x.size()[0] for x in dataDict.values() if torch.is_tensor(x))) <= 1 data = torch.stack([dataDict[key] for key in self.isovistMeasures], dim=-1) - for transformation in self.transforms: + if self.normalize: # All but x,y - data[:, 2:] = transformation(data[:, 2:]) + std, mean = torch.std_mean(data[:, 2:], dim=0) + data[:, 2:] = (data[:, 2:] - mean) / std return data def __iter__(self): @@ -180,15 +194,18 @@ class Trajectories(Dataset): for i in range(len(self)): yield self[i] - def __getitem__(self, item, coords=False): - """ - Return a trajectory sample from the dataset by a specific key. - :param item: The index number of the trajectory to return. - :return: - """ - subList = self.data[item:item + self.size * self.step or None:self.step] - xy, tensor = subList[:, :2], subList[:, 2:] - return (xy, tensor) if coords else tensor + def __getitem__(self, item): + return self.data[item:item + self.size * self.step or None:self.step][:, 2:] + + def get_isovist_measures_by_key(self, item): + return self[item] + + def get_coordinates_by_key(self, item): + return self.data[item:item + self.size * self.step or None:self.step][:, :2] + + def get_both_by_key(self, item): + data = self.data[item:item + self.size * self.step or None:self.step] + return data def __len__(self): total_len = self.data.size()[0] @@ -224,18 +241,21 @@ class MapContainer(AbstractDataset): for attr, x in zip(headers, line.rstrip().split(delimiter)[None:None]): dataDict[attr].append(ast.literal_eval(x)) - return Map(np.asarray([dataDict[head] for head in headers])) + return Map(np.asarray([dataDict[head] for head in headers]), + name=os.path.splitext(os.path.basename(filepath))[0] + ) class Map(object): - def __init__(self, mapData: np.ndarray): + def __init__(self, mapData: np.ndarray, name='MapName'): """ This is a Container Class for triangulated basemaps in csv format. :param mapData: The map as np.ndarray, already read from disk. """ - self.map: np.ndarray = mapData + self.map: np.ndarray = np.transpose(mapData) + self.name = name self.minx, self.maxx = np.min(self.map[[0, 2, 4]]), np.max(self.map[[0, 2, 4]]) self.miny, self.maxy = np.min(self.map[[1, 3, 5]]), np.max(self.map[[1, 3, 5]]) diff --git a/eval/metrices.py b/eval/metrices.py new file mode 100644 index 0000000..7f9e1b8 --- /dev/null +++ b/eval/metrices.py @@ -0,0 +1,6 @@ +#ToDo: We need a metric that analysis sequences of coordinates of arbitrary length and clusters them based +# on their embedded type of mevement + +# ToDo: we ne a function, that compares the clustering outcome of our movement analysis with the AE output. + +# Do the variants of AE really adjust their latent space regarding the embedded moveement type? diff --git a/networks/adverserial_auto_encoder.py b/networks/adverserial_auto_encoder.py index d4b10d1..3da06c9 100644 --- a/networks/adverserial_auto_encoder.py +++ b/networks/adverserial_auto_encoder.py @@ -9,10 +9,10 @@ import torch device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') -class AdversarialAutoEncoder(AutoEncoder): +class AdversarialAE(AutoEncoder): def __init__(self, *args, **kwargs): - super(AdversarialAutoEncoder, self).__init__(*args, **kwargs) + super(AdversarialAE, self).__init__(*args, **kwargs) self.discriminator = Discriminator(self.latent_dim, self.features) def forward(self, batch): @@ -26,10 +26,10 @@ class AdversarialAutoEncoder(AutoEncoder): return z, x_hat -class AdversarialAELightningOverrides(LightningModuleOverrides): +class AdversarialAE_LO(LightningModuleOverrides): def __init__(self): - super(AdversarialAELightningOverrides, self).__init__() + super(AdversarialAE_LO, self).__init__() def training_step(self, batch, _, optimizer_i): if optimizer_i == 0: diff --git a/networks/attention_based_auto_enoder.py b/networks/attention_based_auto_enoder.py index 3ef456e..1cb12b1 100644 --- a/networks/attention_based_auto_enoder.py +++ b/networks/attention_based_auto_enoder.py @@ -7,11 +7,11 @@ from torch import Tensor ####################### # Basic AE-Implementation -class AutoEncoder(AbstractNeuralNetwork, ABC): +class AE_WithAttention(AbstractNeuralNetwork, ABC): def __init__(self, latent_dim: int=0, features: int = 0, **kwargs): assert latent_dim and features - super(AutoEncoder, self).__init__() + super(AE_WithAttention, self).__init__() self.latent_dim = latent_dim self.features = features self.encoder = Encoder(self.latent_dim) @@ -28,10 +28,10 @@ class AutoEncoder(AbstractNeuralNetwork, ABC): return z, x_hat -class AutoEncoderLightningOverrides(LightningModuleOverrides): +class AE_WithAttention_LO(LightningModuleOverrides): def __init__(self): - super(AutoEncoderLightningOverrides, self).__init__() + super(AE_WithAttention_LO, self).__init__() def training_step(self, x, batch_nb): # ToDo: We need a new loss function, fullfilling all attention needs diff --git a/networks/auto_encoder.py b/networks/auto_encoder.py index a834d1d..74f351a 100644 --- a/networks/auto_encoder.py +++ b/networks/auto_encoder.py @@ -28,10 +28,10 @@ class AutoEncoder(AbstractNeuralNetwork, ABC): return z, x_hat -class AutoEncoderLightningOverrides(LightningModuleOverrides): +class AutoEncoder_LO(LightningModuleOverrides): def __init__(self): - super(AutoEncoderLightningOverrides, self).__init__() + super(AutoEncoder_LO, self).__init__() def training_step(self, x, batch_nb): # z, x_hat diff --git a/networks/modules.py b/networks/modules.py index f3be740..cf95c55 100644 --- a/networks/modules.py +++ b/networks/modules.py @@ -1,9 +1,13 @@ import os +from operator import mul +from functools import reduce import torch +from torch import randn import pytorch_lightning as pl from pytorch_lightning import data_loader -from torch.nn import Module, Linear, ReLU, Tanh, Sigmoid, Dropout, GRU +from torch.nn import Module, Linear, ReLU, Sigmoid, Dropout, GRU +from torchvision.transforms import Normalize from abc import ABC, abstractmethod @@ -29,8 +33,16 @@ class LightningModuleOverrides: @data_loader def tng_dataloader(self): num_workers = 0 # os.cpu_count() // 2 - return DataLoader(DataContainer(os.path.join('data', 'training'), self.size, self.step), + return DataLoader(DataContainer(os.path.join('data', 'training'), + self.size, self.step, transforms=[Normalize]), shuffle=True, batch_size=10000, num_workers=num_workers) + """ + @data_loader + def val_dataloader(self): + num_workers = 0 # os.cpu_count() // 2 + return DataLoader(DataContainer(os.path.join('data', 'validation'), self.size, self.step), + shuffle=True, batch_size=100, num_workers=num_workers) + """ class AbstractNeuralNetwork(Module): @@ -82,6 +94,7 @@ class LightningModule(pl.LightningModule, ABC): # return DataLoader(MNIST(os.getcwd(), train=True, download=True, # transform=transforms.ToTensor()), batch_size=32) + """ @pl.data_loader def val_dataloader(self): # OPTIONAL @@ -91,7 +104,7 @@ class LightningModule(pl.LightningModule, ABC): def test_dataloader(self): # OPTIONAL pass - + """ ####################### # Utility Modules @@ -185,7 +198,7 @@ class DecoderLinearStack(Module): self.l1 = Linear(10, 100, bias=True) self.l2 = Linear(100, out_shape, bias=True) self.activation = ReLU() - self.activation_out = Tanh() + self.activation_out = Sigmoid() def forward(self, x): tensor = self.l1(x) @@ -197,30 +210,53 @@ class DecoderLinearStack(Module): class EncoderLinearStack(Module): - def __init__(self): + @property + def shape(self): + x = randn(self.features).unsqueeze(0) + output = self(x) + return output.shape[1:] + + def __init__(self, features=6, separated=False, use_bias=True): super(EncoderLinearStack, self).__init__() # FixMe: Get Hardcoded shit out of here - self.l1 = Linear(6, 100, bias=True) - self.l2 = Linear(100, 10, bias=True) + self.separated = separated + self.features = features + if self.separated: + self.l1s = [Linear(1, 10, bias=use_bias) for _ in range(self.features)] + self.l2s = [Linear(10, 5, bias=use_bias) for _ in range(self.features)] + else: + self.l1 = Linear(self.features, self.features * 10, bias=use_bias) + self.l2 = Linear(self.features * 10, self.features * 5, bias=use_bias) + self.l3 = Linear(self.features * 5, 10, use_bias) self.activation = ReLU() def forward(self, x): - tensor = self.l1(x) - tensor = self.activation(tensor) - tensor = self.l2(tensor) + if self.separated: + x = x.unsqueeze(-1) + tensors = [self.l1s[idx](x[:, idx, :]) for idx in range(len(self.l1s))] + tensors = [self.activation(tensor) for tensor in tensors] + tensors = [self.l2s[idx](tensors[idx]) for idx in range(len(self.l2s))] + tensors = [self.activation(tensor) for tensor in tensors] + tensor = torch.cat(tensors, dim=-1) + else: + tensor = self.l1(x) + tensor = self.activation(tensor) + tensor = self.l2(tensor) + tensor = self.l3(tensor) tensor = self.activation(tensor) return tensor class Encoder(Module): - def __init__(self, lat_dim, variational=False): + def __init__(self, lat_dim, variational=False, separate_features=False, with_dense=True, features=6): self.lat_dim = lat_dim + self.features = features self.variational = variational - super(Encoder, self).__init__() - self.l_stack = TimeDistributed(EncoderLinearStack()) - self.gru = GRU(10, 10, batch_first=True) + self.l_stack = TimeDistributed(EncoderLinearStack(separated=separate_features, + features=features)) if with_dense else False + self.gru = GRU(10 if with_dense else self.features, 10, batch_first=True) self.filter = RNNOutputFilter(only_last=True) if variational: self.mu = Linear(10, self.lat_dim) @@ -229,8 +265,9 @@ class Encoder(Module): self.lat_dim_layer = Linear(10, self.lat_dim) def forward(self, x): - tensor = self.l_stack(x) - tensor = self.gru(tensor) + if self.l_stack: + x = self.l_stack(x) + tensor = self.gru(x) tensor = self.filter(tensor) if self.variational: tensor = self.mu(tensor), self.logvar(tensor) @@ -262,10 +299,10 @@ class PoolingEncoder(Module): self.p = AvgDimPool() self.l = EncoderLinearStack() if variational: - self.mu = Linear(10, self.lat_dim) - self.logvar = Linear(10, self.lat_dim) + self.mu = Linear(self.l.shape, self.lat_dim) + self.logvar = Linear(self.l.shape, self.lat_dim) else: - self.lat_dim_layer = Linear(10, self.lat_dim) + self.lat_dim_layer = Linear(reduce(mul, self.l.shape), self.lat_dim) def forward(self, x): tensor = self.p(x) diff --git a/networks/seperating_adversarial_auto_encoder.py b/networks/seperating_adversarial_auto_encoder.py index ad914d9..daae05b 100644 --- a/networks/seperating_adversarial_auto_encoder.py +++ b/networks/seperating_adversarial_auto_encoder.py @@ -4,15 +4,15 @@ from networks.modules import * import torch -class SeperatingAdversarialAutoEncoder(Module): +class SeperatingAAE(Module): def __init__(self, latent_dim, features): - super(SeperatingAdversarialAutoEncoder, self).__init__() + super(SeperatingAAE, self).__init__() self.latent_dim = latent_dim self.features = features self.spatial_encoder = PoolingEncoder(self.latent_dim) - self.temporal_encoder = Encoder(self.latent_dim) + self.temporal_encoder = Encoder(self.latent_dim, with_dense=False) self.decoder = Decoder(self.latent_dim * 2, self.features) self.spatial_discriminator = Discriminator(self.latent_dim, self.features) self.temporal_discriminator = Discriminator(self.latent_dim, self.features) @@ -29,10 +29,19 @@ class SeperatingAdversarialAutoEncoder(Module): return z_spatial, z_temporal, x_hat -class SeparatingAdversarialAELightningOverrides(LightningModuleOverrides): +class SuperSeperatingAAE(SeperatingAAE): + def __init__(self, *args): + super(SuperSeperatingAAE, self).__init__(*args) + self.temporal_encoder = Encoder(self.latent_dim, separate_features=True) + + def forward(self, batch): + return batch + + +class SeparatingAAE_LO(LightningModuleOverrides): def __init__(self): - super(SeparatingAdversarialAELightningOverrides, self).__init__() + super(SeparatingAAE_LO, self).__init__() def training_step(self, batch, _, optimizer_i): spatial_latent_fake, temporal_latent_fake, batch_hat = self.network.forward(batch) diff --git a/networks/variational_auto_encoder.py b/networks/variational_auto_encoder.py index aad4a54..31c61ba 100644 --- a/networks/variational_auto_encoder.py +++ b/networks/variational_auto_encoder.py @@ -6,7 +6,7 @@ from torch.nn.functional import mse_loss ####################### # Basic AE-Implementation -class VariationalAutoEncoder(AbstractNeuralNetwork, ABC): +class VariationalAE(AbstractNeuralNetwork, ABC): @property def name(self): @@ -14,7 +14,7 @@ class VariationalAutoEncoder(AbstractNeuralNetwork, ABC): def __init__(self, latent_dim=0, features=0, **kwargs): assert latent_dim and features - super(VariationalAutoEncoder, self).__init__() + super(VariationalAE, self).__init__() self.features = features self.latent_dim = latent_dim self.encoder = Encoder(self.latent_dim, variational=True) @@ -32,16 +32,16 @@ class VariationalAutoEncoder(AbstractNeuralNetwork, ABC): z = self.reparameterize(mu, logvar) repeat = Repeater((batch.shape[0], batch.shape[1], -1)) x_hat = self.decoder(repeat(z)) - return x_hat, mu, logvar + return mu, logvar, x_hat -class VariationalAutoEncoderLightningOverrides(LightningModuleOverrides): +class VAE_LO(LightningModuleOverrides): def __init__(self): - super(VariationalAutoEncoderLightningOverrides, self).__init__() + super(VAE_LO, self).__init__() def training_step(self, x, _): - x_hat, logvar, mu = self.forward(x) + mu, logvar, x_hat = self.forward(x) BCE = mse_loss(x_hat, x, reduction='mean') # see Appendix B from VAE paper: @@ -52,7 +52,7 @@ class VariationalAutoEncoderLightningOverrides(LightningModuleOverrides): return {'loss': BCE + KLD} def configure_optimizers(self): - return [Adam(self.parameters(), lr=0.02)] + return [Adam(self.parameters(), lr=0.004)] if __name__ == '__main__': diff --git a/run_models.py b/run_models.py index 4405a5b..294728a 100644 --- a/run_models.py +++ b/run_models.py @@ -1,30 +1,32 @@ from torch.distributions import Normal -from networks.auto_encoder import * import time -from networks.variational_auto_encoder import * -from networks.adverserial_auto_encoder import * -from networks.seperating_adversarial_auto_encoder import * -from networks.modules import LightningModule -from pytorch_lightning import Trainer -from test_tube import Experiment +import os from argparse import Namespace from argparse import ArgumentParser from distutils.util import strtobool +from networks.auto_encoder import AutoEncoder, AutoEncoder_LO +from networks.variational_auto_encoder import VariationalAE, VAE_LO +from networks.adverserial_auto_encoder import AdversarialAE_LO, AdversarialAE +from networks.seperating_adversarial_auto_encoder import SeperatingAAE, SeparatingAAE_LO, SuperSeperatingAAE +from networks.modules import LightningModule + +from pytorch_lightning import Trainer +from test_tube import Experiment + + args = ArgumentParser() -args.add_argument('--step', default=6) +args.add_argument('--step', default=5) args.add_argument('--features', default=6) args.add_argument('--size', default=9) -args.add_argument('--latent_dim', default=4) -args.add_argument('--model', default='Model') +args.add_argument('--latent_dim', default=2) +args.add_argument('--model', default='VAE_Model') args.add_argument('--refresh', type=strtobool, default=False) -# ToDo: How to implement this better? -# other_classes = [AutoEncoder, AutoEncoderLightningOverrides] -class Model(AutoEncoderLightningOverrides, LightningModule): +class AE_Model(AutoEncoder_LO, LightningModule): def __init__(self, parameters): assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']]) @@ -32,11 +34,23 @@ class Model(AutoEncoderLightningOverrides, LightningModule): self.latent_dim = parameters.latent_dim self.features = parameters.features self.step = parameters.step - super(Model, self).__init__() + super(AE_Model, self).__init__() self.network = AutoEncoder(self.latent_dim, self.features) -class AdversarialModel(AdversarialAELightningOverrides, LightningModule): +class VAE_Model(VAE_LO, LightningModule): + + def __init__(self, parameters): + assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']]) + self.size = parameters.size + self.latent_dim = parameters.latent_dim + self.features = parameters.features + self.step = parameters.step + super(VAE_Model, self).__init__() + self.network = VariationalAE(self.latent_dim, self.features) + + +class AAE_Model(AdversarialAE_LO, LightningModule): def __init__(self, parameters: Namespace): assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']]) @@ -44,13 +58,13 @@ class AdversarialModel(AdversarialAELightningOverrides, LightningModule): self.latent_dim = parameters.latent_dim self.features = parameters.features self.step = parameters.step - super(AdversarialModel, self).__init__() + super(AAE_Model, self).__init__() self.normal = Normal(0, 1) - self.network = AdversarialAutoEncoder(self.latent_dim, self.features) + self.network = AdversarialAE(self.latent_dim, self.features) pass -class SeparatingAdversarialModel(SeparatingAdversarialAELightningOverrides, LightningModule): +class SAAE_Model(SeparatingAAE_LO, LightningModule): def __init__(self, parameters: Namespace): assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']]) @@ -58,9 +72,23 @@ class SeparatingAdversarialModel(SeparatingAdversarialAELightningOverrides, Ligh self.latent_dim = parameters.latent_dim self.features = parameters.features self.step = parameters.step - super(SeparatingAdversarialModel, self).__init__() + super(SAAE_Model, self).__init__() self.normal = Normal(0, 1) - self.network = SeperatingAdversarialAutoEncoder(self.latent_dim, self.features) + self.network = SeperatingAAE(self.latent_dim, self.features) + pass + + +class SSAAE_Model(SeparatingAAE_LO, LightningModule): + + def __init__(self, parameters: Namespace): + assert all([x in parameters for x in ['step', 'size', 'latent_dim', 'features']]) + self.size = parameters.size + self.latent_dim = parameters.latent_dim + self.features = parameters.features + self.step = parameters.step + super(SSAAE_Model, self).__init__() + self.normal = Normal(0, 1) + self.network = SuperSeperatingAAE(self.latent_dim, self.features) pass @@ -84,8 +112,13 @@ if __name__ == '__main__': period=4 ) - trainer = Trainer(experiment=exp, max_nb_epochs=250, gpus=[0], - add_log_row_interval=1000, checkpoint_callback=checkpoint_callback) + trainer = Trainer(experiment=exp, + max_nb_epochs=250, + gpus=[0], + add_log_row_interval=1000, + # checkpoint_callback=checkpoint_callback + ) + trainer.fit(model) trainer.save_checkpoint(os.path.join(outpath, 'weights.ckpt')) diff --git a/viz/output.png b/viz/output.png new file mode 100644 index 0000000000000000000000000000000000000000..2e3f1ad32caeaf6b35460d463b0870a2141e04bd GIT binary patch literal 55875 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�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 literal 0 HcmV?d00001 diff --git a/viz/print_movement_in_map.py b/viz/print_movement_in_map.py new file mode 100644 index 0000000..5d002a1 --- /dev/null +++ b/viz/print_movement_in_map.py @@ -0,0 +1,50 @@ +from argparse import ArgumentParser +import os + +from dataset import DataContainer +from viz.utils import MotionAnalyser, Printer, MapContainer, search_for_weights +import torch +from run_models import SAAE_Model, AAE_Model, VAE_Model, AE_Model + +arguments = ArgumentParser() +arguments.add_argument('--data', default='output') + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + +def load_and_viz(path_like_element): + # Define Loop to search for models and folder with visualizations + splitpath = path_like_element.split(os.sep) + base_dir = os.path.join(*splitpath[:4]) + model = globals()[splitpath[2]] + print(f'... loading model named: "{model.name}" from timestamp: {splitpath[3]}') + pretrained_model = model.load_from_metrics( + weights_path=path_like_element, + tags_csv=os.path.join(base_dir, 'default', 'version_0', 'meta_tags.csv'), + on_gpu=True if torch.cuda.is_available() else False, + # map_location=None + ) + + # Init model and freeze its weights ( for faster inference) + pretrained_model = pretrained_model.to(device) + pretrained_model.eval() + pretrained_model.freeze() + + dataIndex = 0 + + datasets = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device) + dataset = datasets.datasets[dataIndex] + # ToDO: use dataloader for iteration instead! - dataloader = DataLoader(dataset, ) + + maps = MapContainer(os.path.join(os.pardir, 'data', 'validation')) + base_map = maps.datasets[dataIndex] + + p = Printer(pretrained_model) + p.print_trajec_on_basemap(dataset, base_map, save=os.path.join(base_dir, f'{base_map.name}_movement.png'), + color_by_movement=True) + return True + + +if __name__ == '__main__': + args = arguments.parse_args() + search_for_weights(load_and_viz, args.data, file_type='movement') diff --git a/viz/utils.py b/viz/utils.py index 0248b79..0407ed8 100644 --- a/viz/utils.py +++ b/viz/utils.py @@ -1,13 +1,30 @@ -import os +from typing import Union +from functools import reduce + +from statistics import stdev + +from sklearn.cluster import Birch, KMeans, DBSCAN +from sklearn.manifold import TSNE +from sklearn.decomposition import PCA + +from dataset import * +from networks.modules import AbstractNeuralNetwork + +from matplotlib import pyplot as plt +from matplotlib.patches import Polygon +from matplotlib.collections import LineCollection, PatchCollection +import matplotlib.colors as mcolors +import matplotlib.cm as cmaps + +from math import pi -def search_for_weights(func, folder): +def search_for_weights(func, folder, file_type='latent_space'): while not os.path.exists(folder): if len(os.path.split(folder)) >= 50: raise FileNotFoundError(f'The folder "{folder}" could not be found') folder = os.path.join(os.pardir, folder) - - if any([x.name.endswith('.png') for x in os.scandir(folder)]): + if any([file_type in x.name for x in os.scandir(folder)]): return if any(['.ckpt' in element.name and element.is_dir() for element in os.scandir(folder)]): @@ -19,12 +36,324 @@ def search_for_weights(func, folder): for element in os.scandir(folder): if os.path.exists(element): if element.is_dir(): - search_for_weights(func, element.path) + search_for_weights(func, element.path, file_type=file_type) elif element.is_file() and element.name.endswith('.ckpt'): - func(element) + func(element.path) else: continue +class Printer(object): + + def __init__(self, model: AbstractNeuralNetwork, ax=None): + self.norm = mcolors.Normalize(vmin=0, vmax=1) + self.colormap = cmaps.gist_rainbow + self.network = model + self.fig = plt.figure(dpi=300) + self.ax = ax if ax else plt.subplot(1, 1, 1) + pass + + def colorize(self, x, min_val: Union[float, None] = None, max_val: Union[float, None] = None, + colormap=cmaps.rainbow, **kwargs): + norm = mcolors.Normalize(vmin=min_val, vmax=max_val) + colored = colormap(norm(x)) + return colored + + @staticmethod + def project_to_2d(data: np.ndarray, method: str = 'tsne') -> np.ndarray: + projector = TSNE() if method.lower() == 'tsne' else PCA() + print('Starting TSNE Transformation') + projected_data = projector.fit_transform(data) + assert projected_data.shape[-1] == 2 + print('TSNE Projection Successfull') + return projected_data + + @staticmethod + def cluster_data(data: np.ndarray, cluster_center_file: str = None) -> np.ndarray: + print('Start Clustering with Birch') + if cluster_center_file: + with open(cluster_center_file, 'r') as f: + cluster_center_string = f.readlines()[0] + centers = ast.literal_eval(cluster_center_string) + clusterer = Birch(n_clusters=len(centers)) + clusterer.init = np.asarray(centers) + else: + # clusterer = Birch(n_clusters=None) + clusterer = Birch() + + labels = clusterer.fit_predict(data) + print('Birch Clustering Sucessfull') + return labels + + def print_possible_latent_spaces(self, data: Trajectories, n: Union[int, str] = 1000, **kwargs): + predictions, _ = self._gather_predictions(data, n) + if len(predictions) >= 2: + predictions += (torch.cat(predictions, dim=-1), ) + + labels = self.cluster_data(predictions[-1]) + for idx, prediction in enumerate(predictions): + self.print_latent_space(prediction, labels, running_index=idx, **kwargs) + + def print_latent_space(self, prediction, labels, running_index=0, save=None): + + self.colormap = cmaps.tab20 + + if isinstance(prediction, torch.Tensor): + prediction = prediction.numpy() + elif isinstance(prediction, np.ndarray): + pass + elif isinstance(prediction, list): + prediction = np.asarray(prediction) + else: + raise RuntimeError + + if prediction.shape[-1] > 2: + fig, axs = plt.subplots(ncols=2, nrows=1) + transformers = [TSNE(2), PCA(2)] + print('Starting Dimensional Reduction') + for idx, transformer in enumerate(transformers): + transformed = transformer.fit_transform(prediction) + print(f'{transformer.__class__.__name__} Projection Sucessfull') + colored = self.colormap(labels) + ax = axs[idx] + ax.scatter(x=transformed[:, 0], y=transformed[:, 1], c=colored) + ax.set_title(transformer.__class__.__name__) + ax.set_xlim(np.min(transformed[:, 0])*1.1, np.max(transformed[:, 0]*1.1)) + ax.set_ylim(np.min(transformed[:, 1]*1.1), np.max(transformed[:, 1]*1.1)) + elif prediction.shape[-1] == 2: + fig, axs = plt.subplots() + + # TODO: Build transformation for lat_dim_size >= 3 + print('All Predictions sucesfully Gathered and Shaped ') + axs.set_xlim(np.min(prediction[:, 0]), np.max(prediction[:, 0])) + axs.set_ylim(np.min(prediction[:, 1]), np.max(prediction[:, 1])) + # ToDo: Insert Normalization + colored = self.colormap(labels) + plt.scatter(prediction[:, 0], prediction[:, 1], c=colored) + else: + raise NotImplementedError("Latent Dimensions can not be one-dimensional (yet).") + + if save: + plt.savefig(f'{save}_{running_index}.png') + + def print_latent_density(self): # , data: DataContainer): + raise NotImplementedError("My Future Self has to come up with smth") + + # fig, ax = plt.subplots() + + # preds = [] + # for i in range(data.len - data.width * data.stepsize): + # for i in range(5000): + # + # seq = data.sub_trajectory_by_key(i, stepsize=data.stepsize) + # + # preds.append(self.nn.encoder([seq[None, ...]])[0]) + # + # TODO: Build transformation for lat_dim_size >= 3 + # pred_array = np.asarray(preds).reshape((-1, nn.latDim)) + # k = KernelDensity() + # k.fit(pred_array) + # z = np.exp(k.score_samples(pred_array)) + # + # levels = np.linspace(0, z.max(), 25) + # xgrid, ygrid = np.meshgrid(pred_array[::5, 0], pred_array[::5, 1]) + # xy = np.vstack([xgrid.ravel(), ygrid.ravel()]).T + # z = np.exp(k.score_samples(xy)).reshape(xgrid.shape) + # + # plt.contourf(xgrid, ygrid, z, levels=levels, cmap=plt.cm.Reds) + # plt.show() + + def _gather_predictions(self, data: Trajectories, n: int = 1000, + color_by_movement=False, **kwargs): + """ + Check if any value for n is given and gather some random datapoints from the dataset. In accordance with the + maximal possible trajectory amount that is given by stepsize * width. + Also retunr the keys for all possible predictions. + :param data: + :type data: Dataset + :param n: + :param tsne: + :param kwargs: + :return: + """ + print("Gathering Predictions") + + n = n if isinstance(n, int) and n else len(data) - (data.size * data.step) + idxs = np.random.choice(np.arange(len(data) - data.step * data.size), n, replace=False) + complete_data = torch.stack([data.get_both_by_key(idx) for idx in idxs], dim=0) + segment_coords, trajectories = complete_data[:, :, :2], complete_data[:, :, 2:] + if color_by_movement: + motion_analyser = MotionAnalyser() + predictions = (motion_analyser.cluster_motion(segment_coords), ) + + else: + with torch.no_grad(): + predictions = self.network(trajectories)[:-1] + + return predictions, segment_coords + + @staticmethod + def colorize_as_hsv(self, x, min_val: Union[float, None] = None, max_val: Union[float, None] = None, + colormap=cmaps.rainbow, **kwargs): + norm = mcolors.Normalize(vmin=min_val, vmax=max_val) + colored = colormap(norm(x)) + return colored + + def _build_trajectory_shapes(self, predictions: np.ndarray, segment_coordinates, + axis=None, transformation=TSNE, **kwargs): + if not isinstance(predictions, np.ndarray): + predictions = tuple((x if torch.is_tensor(x) else torch.from_numpy(x) for x in predictions)) + predictions = torch.cat(predictions, dim=-1) + + if axis is not None: + predictions = predictions[:, axis][..., None] + + if predictions.shape[-1] >= 4: + if True: + predictions = Birch(n_clusters=3).fit_predict(predictions).reshape(-1, 1) + else: + transformer = transformation(n_components=3, random_state=42) + predictions = transformer.fit_transform(predictions) + + if predictions.shape[-1] == 1: + colored = self.colorize(predictions.reshape(-1), **kwargs) + + elif predictions.shape[-1] == 2: + colored = self.colorize(predictions[:, 0], **kwargs) + + if kwargs.get('min_val', None): + lightning = mcolors.Normalize(vmin=kwargs.get('min_val', None), vmax=kwargs.get('max_val', None)) + else: + lightning = mcolors.Normalize() + alpha = lightning(predictions[:, 1]) + colored[:, -1] = alpha + + elif predictions.shape[-1] == 3: + norm = mcolors.Normalize() + colored = [(r, g, b) for r,g,b in norm(predictions)] + + else: + raise NotImplementedError('Full Prediction Shape was: {}'.format(predictions.shape)) + # TODO Build a isomap or tsne transformation here to get a two dimensional space + + segment_coordinates = segment_coordinates.cpu() if torch.is_tensor(segment_coordinates) else segment_coordinates + + return LineCollection(segment_coordinates, linewidths=(1, 1, 1, 1), + colors=colored, linestyle='solid') + + @staticmethod + def _build_map_shapes(base_map: Map): + # Base Map Plotting + # filled Triangle + patches = [Polygon(base_map[i], True, color='black') for i in range(len(base_map))] + return PatchCollection(patches, color='black') + + def print_trajec_on_basemap(self, data, base_map: Map, save=False, color_by_movement=False, **kwargs): + """ + + :rtype: object + """ + prediction_segments = self._gather_predictions(data, color_by_movement=color_by_movement, **kwargs) + trajectory_shapes = self._build_trajectory_shapes(*prediction_segments, **kwargs) + map_shapes = self._build_map_shapes(base_map) + self.ax.add_collection(trajectory_shapes) + self.ax.axis('auto') + self.ax.add_collection(map_shapes) + + self.ax.set_title('Trajectories on BaseMap') + if save: + if isinstance(save, str): + self.save(save) + else: + self.save(base_map.name) + pass + + @staticmethod + def show(): + plt.show() + return True + + @staticmethod + def save(filename): + plt.savefig(filename) + + +class MotionAnalyser(object): + + def __init__(self): + pass + + def _sequential_pairwise_map(self, func, xy_sequence, on_deltas=False): + zipped_list = [x for x in zip(xy_sequence[:-1], xy_sequence[1:])] + + if on_deltas: + zipped_list = [self.delta(*movement) for movement in zipped_list] + else: + pass + + return [func(*xy) for xy in zipped_list] + + @staticmethod + def delta(x1y1, x2y2): + x1, y1 = x1y1 + x2, y2 = x2y2 + return x2-x1, y2-y1 + + @staticmethod + def get_r(deltax, deltay): + # https://mathinsight.org/polar_coordinates + r = torch.sqrt(deltax**2 + deltay**2) + return r + + @staticmethod + def get_theta(deltax, deltay, rad=False): + # https://mathinsight.org/polar_coordinates + theta = torch.atan2(deltay, deltax) + return theta if rad else theta * 180 / pi + + def get_theta_for_sequence(self, xy_sequence): + ts = self._sequential_pairwise_map(self.get_theta, xy_sequence, on_deltas=True) + return ts + + def get_r_for_sequence(self, xy_sequence): + rs = self._sequential_pairwise_map(self.get_r, xy_sequence, on_deltas=True) + return rs + + def get_unique_seq_identifier(self, xy_sequence): + + # Globals + global_delta = self.delta(xy_sequence[0], xy_sequence[-1]) + global_theta = self.get_theta(*global_delta) + global_r = self.get_r(*global_delta) + + # For Each + theta_seq = self.get_theta_for_sequence(xy_sequence) + mean_theta = sum(theta_seq) / len(theta_seq) + theta_sum = sum([abs(theta) for theta in theta_seq]) + std_theta = stdev(map(float, theta_seq)) + + return torch.stack((global_r, torch.as_tensor(std_theta), mean_theta, global_theta)) + + def cluster_motion(self, trajectory_samples, cluster_class=KMeans): + cluster_class = cluster_class(3) + + std, mean = torch.std_mean(trajectory_samples, dim=0) + trajectory_samples = (trajectory_samples - mean) / std + + unique_seq_identifiers = torch.stack([self.get_unique_seq_identifier(trajectory) + for trajectory in trajectory_samples]) + + clustered_movement = cluster_class.fit_predict(unique_seq_identifiers) + if False: + from sklearn.decomposition import PCA + p = PCA(2) + t = p.fit_transform(unique_seq_identifiers) + f = plt.figure() + plt.scatter(t[:, 0], t[:,1]) + plt.show() + + return clustered_movement.reshape(-1, 1) + + if __name__ == '__main__': raise PermissionError('This file should not be called.') diff --git a/viz/viz_latent.py b/viz/viz_latent.py index ab16f3f..ceaa132 100644 --- a/viz/viz_latent.py +++ b/viz/viz_latent.py @@ -1,12 +1,12 @@ -from sklearn.manifold import TSNE -from sklearn.decomposition import PCA - -import seaborn as sns -import matplotlib.pyplot as plt - +import warnings +warnings.filterwarnings('ignore', category=FutureWarning) +import torch +from dataset import DataContainer +from viz.utils import search_for_weights, Printer from run_models import * -sns.set() + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') def load_and_predict(path_like_element): @@ -14,73 +14,39 @@ def load_and_predict(path_like_element): splitpath = path_like_element.split(os.sep) base_dir = os.path.join(*splitpath[:4]) model = globals()[splitpath[2]] - print(f'... loading model named: "{Model.name}" from timestamp: {splitpath[3]}') pretrained_model = model.load_from_metrics( weights_path=path_like_element, tags_csv=os.path.join(base_dir, 'default', 'version_0', 'meta_tags.csv'), on_gpu=True if torch.cuda.is_available() else False, - map_location=None + # map_location=None ) + print(f'... loading model named: "{model.name}" from timestamp: {splitpath[3]}') # Init model and freeze its weights ( for faster inference) pretrained_model = pretrained_model.to(device) pretrained_model.eval() pretrained_model.freeze() - with torch.no_grad(): + # Load the data for prediction - # Load the data for prediction + # TODO!!!!!!!!!: + # Hier müssen natürlich auch die date parameter geladen werden! + # Muss ich die val-sets automatisch neu setzen, also immer auf refresh haben, wenn ich validieren möchte? + # Was ist denn eigentlich mein Val Dataset? + # Hab ich irgendwo eine ganze karte? + # Wie sorge ich dafür, dass gewisse karten, also größenverhältnisse usw nicht überrepräsentiert sind? + dataset = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device) - # TODO!!!!!!!!!: - # Hier müssen natürlich auch die date parameter geladen werden! - # Muss ich die val-sets automatisch neu setzen, also immer auf refresh haben, wenn ich validieren möchte? - # Was ist denn eigentlich mein Val Dataset? - # Hab ich irgendwo eine ganze karte? - # Wie sorge ich dafür, dass gewisse karten, also größenverhältnisse usw nicht überrepräsentiert sind? - dataset = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device) - dataloader = DataLoader(dataset, shuffle=True, batch_size=len(dataset)) + # Do the inference + # test_pred = [pretrained_model(test_sample)[:-1] for test_sample in dataloader][0] - # Do the inference - test_pred = [pretrained_model(test_sample)[:-1] for test_sample in dataloader][0] - - for idx, prediction in enumerate(test_pred): - plot, _ = viz_latent(prediction) - plot.savefig(os.path.join(base_dir, f'latent_space_{idx}.png')) - - -def viz_latent(prediction): - try: - prediction = prediction.cpu() - prediction = prediction.numpy() - except AttributeError: - pass - - if prediction.shape[-1] <= 1: - raise ValueError('How did this happen?') - elif prediction.shape[-1] == 2: - ax = sns.scatterplot(x=prediction[:, 0], y=prediction[:, 1]) - try: - plt.show() - except: - pass - return ax.figure, (ax) - else: - fig, axs = plt.subplots(ncols=2) - plots = [] - for idx, dim_reducer in enumerate([PCA, TSNE]): - predictions_reduced = dim_reducer(n_components=2).fit_transform(prediction) - plot = sns.scatterplot(x=predictions_reduced[:, 0], y=predictions_reduced[:, 1], - ax=axs[idx]) - plot.set_title(dim_reducer.__name__) - plots.append(plot) - - try: - plt.show() - except: - pass - return fig, (*plots, ) + p = Printer(pretrained_model) + # Important: + # Use all given valdiation samples, even if they relate to differnt maps. This is important since we want to have a + # view on the complete latent space, not just in relation to a single basemap, which would be a major bias. + p.print_possible_latent_spaces(dataset, save=os.path.join(base_dir, f'latent_space')) if __name__ == '__main__': path = 'output' - search_for_weights(search_for_weights, path) \ No newline at end of file + search_for_weights(load_and_predict, path, file_type='latent') \ No newline at end of file diff --git a/viz/viz_map.py b/viz/viz_map.py deleted file mode 100644 index 4b5a6eb..0000000 --- a/viz/viz_map.py +++ /dev/null @@ -1,50 +0,0 @@ - -from dataset import * -# Plotting -# import matplotlib as mlp -from matplotlib import pyplot as plt -from matplotlib.patches import Polygon -from matplotlib.collections import LineCollection, PatchCollection -import matplotlib.colors as mcolors -import matplotlib.cm as cmaps - -from sklearn.manifold import TSNE -from sklearn.decomposition import PCA - -import seaborn as sns -from argparse import ArgumentParser - -from viz.utils import search_for_weights - -from run_models import * - -sns.set() - - -arguments = ArgumentParser() -arguments.add_argument('--data', default=os.path.join('data', 'validation')) - -dataset = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device) -dataloader = DataLoader(dataset, shuffle=True, batch_size=len(dataset)) - - - -def viz_map(self, base_map: MapContainer): - # Base Map Plotting - # filled Triangle - patches = [Polygon(base_map.get_triangle_by_key(i), True, color='k') for i in range(len(base_map))] - patch_collection = PatchCollection(patches, color='k') - - self.ax.add_collection(patch_collection) - print('Basemap Plotted') - - patches = [Polygon(base_map.get_triangle_by_key(i), True, color='k') for i in range(len(base_map))] - return PatchCollection(patches, color='k') - -def load_and_predict(folder): - pass - - -if __name__ == '__main__': - search_for_weights(load_and_predict, arguments.data) - # ToDo: THIS diff --git a/viz/viz_prediction_in_map.py b/viz/viz_prediction_in_map.py new file mode 100644 index 0000000..72e2975 --- /dev/null +++ b/viz/viz_prediction_in_map.py @@ -0,0 +1,55 @@ +from argparse import ArgumentParser +import os + +from dataset import DataContainer +from viz.utils import MotionAnalyser, Printer, MapContainer, search_for_weights +import torch + +arguments = ArgumentParser() +arguments.add_argument('--data', default='output') + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + +from viz.utils import * +from run_models import * + +arguments = ArgumentParser() +arguments.add_argument('--data', default='output') + +device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') + + +def load_and_viz(path_like_element): + # Define Loop to search for models and folder with visualizations + splitpath = path_like_element.split(os.sep) + base_dir = os.path.join(*splitpath[:4]) + model = globals()[splitpath[2]] + print(f'... loading model named: "{model.name}" from timestamp: {splitpath[3]}') + pretrained_model = model.load_from_metrics( + weights_path=path_like_element, + tags_csv=os.path.join(base_dir, 'default', 'version_0', 'meta_tags.csv'), + on_gpu=True if torch.cuda.is_available() else False, + # map_location=None + ) + + # Init model and freeze its weights ( for faster inference) + pretrained_model = pretrained_model.to(device) + pretrained_model.eval() + pretrained_model.freeze() + + dataIndex = 0 + + datasets = DataContainer(os.path.join(os.pardir, 'data', 'validation'), 9, 6).to(device) + dataset = datasets.datasets[dataIndex] + # ToDO: use dataloader for iteration instead! - dataloader = DataLoader(dataset, ) + + maps = MapContainer(os.path.join(os.pardir, 'data', 'validation')) + base_map = maps.datasets[dataIndex] + + p = Printer(pretrained_model) + p.print_trajec_on_basemap(dataset, base_map, save=os.path.join(base_dir, f'{base_map.name}_map.png')) + + +if __name__ == '__main__': + args = arguments.parse_args() + search_for_weights(load_and_viz, args.data, file_type='map')