From 69e5ff324323e5c476838bf11fedfbf540d0c14b Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:08:08 +0200 Subject: [PATCH 01/17] Import from the newly cleaned-up prototorch namespace --- prototorch/models/__init__.py | 15 +++++++++++-- prototorch/models/abstract.py | 9 +++++--- prototorch/models/callbacks.py | 2 +- prototorch/models/cbc.py | 12 ++++++++--- prototorch/models/extras.py | 34 ++++++++++++++++++++++++++++-- prototorch/models/glvq.py | 17 ++++++--------- prototorch/models/knn.py | 7 +++--- prototorch/models/lvq.py | 3 +-- prototorch/models/probabilistic.py | 10 ++++----- prototorch/models/unsupervised.py | 8 +++---- 10 files changed, 80 insertions(+), 37 deletions(-) diff --git a/prototorch/models/__init__.py b/prototorch/models/__init__.py index ea057da..6b3330f 100644 --- a/prototorch/models/__init__.py +++ b/prototorch/models/__init__.py @@ -4,8 +4,19 @@ from importlib.metadata import PackageNotFoundError, version from .callbacks import PrototypeConvergence, PruneLoserPrototypes from .cbc import CBC, ImageCBC -from .glvq import (GLVQ, GLVQ1, GLVQ21, GMLVQ, GRLVQ, LGMLVQ, LVQMLN, - ImageGLVQ, ImageGMLVQ, SiameseGLVQ, SiameseGMLVQ) +from .glvq import ( + GLVQ, + GLVQ1, + GLVQ21, + GMLVQ, + GRLVQ, + LGMLVQ, + LVQMLN, + ImageGLVQ, + ImageGMLVQ, + SiameseGLVQ, + SiameseGMLVQ, +) from .knn import KNN from .lvq import LVQ1, LVQ21, MedianLVQ from .probabilistic import CELVQ, PLVQ, RSLVQ, SLVQ diff --git a/prototorch/models/abstract.py b/prototorch/models/abstract.py index 89d322b..0b2f9ed 100644 --- a/prototorch/models/abstract.py +++ b/prototorch/models/abstract.py @@ -5,9 +5,12 @@ from typing import Final, final import pytorch_lightning as pl import torch import torchmetrics -from prototorch.components import Components, LabeledComponents -from prototorch.functions.distances import euclidean_distance -from prototorch.modules import WTAC, LambdaLayer + +from ..core.competitions import WTAC +from ..core.components import Components, LabeledComponents +from ..core.distances import euclidean_distance +from ..core.pooling import stratified_min_pooling +from ..nn.wrappers import LambdaLayer class ProtoTorchMixin(object): diff --git a/prototorch/models/callbacks.py b/prototorch/models/callbacks.py index d088c7d..32798e6 100644 --- a/prototorch/models/callbacks.py +++ b/prototorch/models/callbacks.py @@ -4,8 +4,8 @@ import logging import pytorch_lightning as pl import torch -from prototorch.components import Components +from ..core.components import Components from .extras import ConnectionTopology diff --git a/prototorch/models/cbc.py b/prototorch/models/cbc.py index 2a12b12..6f0f40c 100644 --- a/prototorch/models/cbc.py +++ b/prototorch/models/cbc.py @@ -1,10 +1,16 @@ import torch import torchmetrics +from ..core.components import ReasoningComponents from .abstract import ImagePrototypesMixin -from .extras import (CosineSimilarity, MarginLoss, ReasoningLayer, - euclidean_similarity, rescaled_cosine_similarity, - shift_activation) +from .extras import ( + CosineSimilarity, + MarginLoss, + ReasoningLayer, + euclidean_similarity, + rescaled_cosine_similarity, + shift_activation, +) from .glvq import SiameseGLVQ diff --git a/prototorch/models/extras.py b/prototorch/models/extras.py index 60bd158..1b87ca8 100644 --- a/prototorch/models/extras.py +++ b/prototorch/models/extras.py @@ -5,8 +5,9 @@ Modules not yet available in prototorch go here temporarily. """ import torch -from prototorch.functions.distances import euclidean_distance -from prototorch.functions.similarities import cosine_similarity + +from ..core.distances import euclidean_distance +from ..core.similarities import cosine_similarity def rescaled_cosine_similarity(x, y): @@ -24,6 +25,35 @@ def euclidean_similarity(x, y, variance=1.0): return torch.exp(-(d * d) / (2 * variance)) +def gaussian(distances, variance): + return torch.exp(-(distances * distances) / (2 * variance)) + + +def rank_scaled_gaussian(distances, lambd): + order = torch.argsort(distances, dim=1) + ranks = torch.argsort(order, dim=1) + + return torch.exp(-torch.exp(-ranks / lambd) * distances) + + +class GaussianPrior(torch.nn.Module): + def __init__(self, variance): + super().__init__() + self.variance = variance + + def forward(self, distances): + return gaussian(distances, self.variance) + + +class RankScaledGaussianPrior(torch.nn.Module): + def __init__(self, lambd): + super().__init__() + self.lambd = lambd + + def forward(self, distances): + return rank_scaled_gaussian(distances, self.lambd) + + class ConnectionTopology(torch.nn.Module): def __init__(self, agelimit, num_prototypes): super().__init__() diff --git a/prototorch/models/glvq.py b/prototorch/models/glvq.py index f130dba..15a5ca3 100644 --- a/prototorch/models/glvq.py +++ b/prototorch/models/glvq.py @@ -1,18 +1,13 @@ """Models based on the GLVQ framework.""" import torch -from prototorch.functions.activations import get_activation -from prototorch.functions.competitions import wtac -from prototorch.functions.distances import ( - lomega_distance, - omega_distance, - squared_euclidean_distance, -) -from prototorch.functions.helper import get_flat -from prototorch.functions.losses import glvq_loss, lvq1_loss, lvq21_loss -from prototorch.modules import LambdaLayer, LossLayer from torch.nn.parameter import Parameter +from ..core.competitions import wtac +from ..core.distances import lomega_distance, omega_distance, squared_euclidean_distance +from ..core.losses import glvq_loss, lvq1_loss, lvq21_loss +from ..nn.activations import get_activation +from ..nn.wrappers import LambdaLayer, LossLayer from .abstract import ImagePrototypesMixin, SupervisedPrototypeModel @@ -137,7 +132,7 @@ class SiameseGLVQ(GLVQ): def compute_distances(self, x): protos, _ = self.proto_layer() - x, protos = get_flat(x, protos) + x, protos = [arr.view(arr.size(0), -1) for arr in (x, protos)] latent_x = self.backbone(x) self.backbone.requires_grad_(self.both_path_gradients) latent_protos = self.backbone(protos) diff --git a/prototorch/models/knn.py b/prototorch/models/knn.py index c23a276..d30ee7a 100644 --- a/prototorch/models/knn.py +++ b/prototorch/models/knn.py @@ -2,9 +2,10 @@ import warnings -from prototorch.components import LabeledComponents -from prototorch.modules import KNNC - +from ..core.competitions import KNNC +from ..core.components import LabeledComponents +from ..core.initializers import LiteralCompInitializer, LiteralLabelsInitializer +from ..utils.utils import parse_data_arg from .abstract import SupervisedPrototypeModel diff --git a/prototorch/models/lvq.py b/prototorch/models/lvq.py index d3f60d4..4e84be3 100644 --- a/prototorch/models/lvq.py +++ b/prototorch/models/lvq.py @@ -1,7 +1,6 @@ """LVQ models that are optimized using non-gradient methods.""" -from prototorch.functions.losses import _get_dp_dm - +from ..core.losses import _get_dp_dm from .abstract import NonGradientMixin from .glvq import GLVQ diff --git a/prototorch/models/probabilistic.py b/prototorch/models/probabilistic.py index b1771c5..ac161e2 100644 --- a/prototorch/models/probabilistic.py +++ b/prototorch/models/probabilistic.py @@ -1,13 +1,11 @@ """Probabilistic GLVQ methods""" import torch -from prototorch.functions.losses import nllr_loss, rslvq_loss -from prototorch.functions.pooling import (stratified_min_pooling, - stratified_sum_pooling) -from prototorch.functions.transforms import (GaussianPrior, - RankScaledGaussianPrior) -from prototorch.modules import LambdaLayer, LossLayer +from ..core.losses import nllr_loss, rslvq_loss +from ..core.pooling import stratified_min_pooling, stratified_sum_pooling +from ..nn.wrappers import LambdaLayer, LossLayer +from .extras import GaussianPrior, RankScaledGaussianPrior from .glvq import GLVQ, SiameseGMLVQ diff --git a/prototorch/models/unsupervised.py b/prototorch/models/unsupervised.py index d171115..ba2c80d 100644 --- a/prototorch/models/unsupervised.py +++ b/prototorch/models/unsupervised.py @@ -2,11 +2,11 @@ import numpy as np import torch -from prototorch.functions.competitions import wtac -from prototorch.functions.distances import squared_euclidean_distance -from prototorch.modules import LambdaLayer -from prototorch.modules.losses import NeuralGasEnergy +from ..core.competitions import wtac +from ..core.distances import squared_euclidean_distance +from ..core.losses import NeuralGasEnergy +from ..nn.wrappers import LambdaLayer from .abstract import NonGradientMixin, UnsupervisedPrototypeModel from .callbacks import GNGCallback from .extras import ConnectionTopology From 97ec15b76a51c9e87070906fe83e20128e5fb307 Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:09:41 +0200 Subject: [PATCH 02/17] [BUGFIX] KNN works again --- prototorch/models/abstract.py | 27 +++++++++++---------------- prototorch/models/knn.py | 6 +++++- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/prototorch/models/abstract.py b/prototorch/models/abstract.py index 0b2f9ed..eacf586 100644 --- a/prototorch/models/abstract.py +++ b/prototorch/models/abstract.py @@ -88,13 +88,11 @@ class UnsupervisedPrototypeModel(PrototypeModel): super().__init__(hparams, **kwargs) # Layers - prototype_initializer = kwargs.get("prototype_initializer", None) - initialized_prototypes = kwargs.get("initialized_prototypes", None) - if prototype_initializer is not None or initialized_prototypes is not None: + prototypes_initializer = kwargs.get("prototypes_initializer", None) + if prototypes_initializer is not None: self.proto_layer = Components( self.hparams.num_prototypes, - initializer=prototype_initializer, - initialized_components=initialized_prototypes, + initializer=prototypes_initializer, ) def compute_distances(self, x): @@ -112,19 +110,17 @@ class SupervisedPrototypeModel(PrototypeModel): super().__init__(hparams, **kwargs) # Layers - prototype_initializer = kwargs.get("prototype_initializer", None) - initialized_prototypes = kwargs.get("initialized_prototypes", None) - if prototype_initializer is not None or initialized_prototypes is not None: + prototypes_initializer = kwargs.get("prototypes_initializer", None) + if prototypes_initializer is not None: self.proto_layer = LabeledComponents( distribution=self.hparams.distribution, - initializer=prototype_initializer, - initialized_components=initialized_prototypes, + components_initializer=prototypes_initializer, ) self.competition_layer = WTAC() @property def prototype_labels(self): - return self.proto_layer.component_labels.detach().cpu() + return self.proto_layer.labels.detach().cpu() @property def num_classes(self): @@ -137,15 +133,14 @@ class SupervisedPrototypeModel(PrototypeModel): def forward(self, x): distances = self.compute_distances(x) - y_pred = self.predict_from_distances(distances) - # TODO - y_pred = torch.eye(self.num_classes, device=self.device)[ - y_pred.long()] # depends on labels {0,...,num_classes} + plabels = self.proto_layer.labels + winning = stratified_min_pooling(distances, plabels) + y_pred = torch.nn.functional.softmin(winning) return y_pred def predict_from_distances(self, distances): with torch.no_grad(): - plabels = self.proto_layer.component_labels + plabels = self.proto_layer.labels y_pred = self.competition_layer(distances, plabels) return y_pred diff --git a/prototorch/models/knn.py b/prototorch/models/knn.py index d30ee7a..0886550 100644 --- a/prototorch/models/knn.py +++ b/prototorch/models/knn.py @@ -20,9 +20,13 @@ class KNN(SupervisedPrototypeModel): data = kwargs.get("data", None) if data is None: raise ValueError("KNN requires data, but was not provided!") + data, targets = parse_data_arg(data) # Layers - self.proto_layer = LabeledComponents(initialized_components=data) + self.proto_layer = LabeledComponents( + distribution=[], + components_initializer=LiteralCompInitializer(data), + labels_initializer=LiteralLabelsInitializer(targets)) self.competition_layer = KNNC(k=self.hparams.k) def training_step(self, train_batch, batch_idx, optimizer_idx=None): From 68034d56f6f57c3b8c1758a7347c3cf0f99167e3 Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:13:25 +0200 Subject: [PATCH 03/17] [BUGFIX] `examples/glvq_iris.py` works again --- examples/glvq_iris.py | 4 ++-- prototorch/models/glvq.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/glvq_iris.py b/examples/glvq_iris.py index 486ef5e..f9556ae 100644 --- a/examples/glvq_iris.py +++ b/examples/glvq_iris.py @@ -23,7 +23,7 @@ if __name__ == "__main__": hparams = dict( distribution={ "num_classes": 3, - "prototypes_per_class": 4 + "per_class": 4 }, lr=0.01, ) @@ -32,7 +32,7 @@ if __name__ == "__main__": model = pt.models.GLVQ( hparams, optimizer=torch.optim.Adam, - prototype_initializer=pt.components.SMI(train_ds), + prototypes_initializer=pt.initializers.SMCI(train_ds), lr_scheduler=ExponentialLR, lr_scheduler_kwargs=dict(gamma=0.99, verbose=False), ) diff --git a/prototorch/models/glvq.py b/prototorch/models/glvq.py index 15a5ca3..953a1c6 100644 --- a/prototorch/models/glvq.py +++ b/prototorch/models/glvq.py @@ -56,7 +56,7 @@ class GLVQ(SupervisedPrototypeModel): def shared_step(self, batch, batch_idx, optimizer_idx=None): x, y = batch out = self.compute_distances(x) - plabels = self.proto_layer.component_labels + plabels = self.proto_layer.labels mu = self.loss(out, y, prototype_labels=plabels) batch_loss = self.transfer_layer(mu, beta=self.hparams.transfer_beta) loss = batch_loss.sum(dim=0) From 3afced8662a6ae44d28114ddc6d8100006cb54d7 Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:19:08 +0200 Subject: [PATCH 04/17] [BUGFIX] `examples/glvq_spiral.py` works again --- examples/glvq_spiral.py | 11 +++++------ prototorch/models/callbacks.py | 9 +++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/glvq_spiral.py b/examples/glvq_spiral.py index e8e28a8..386c01a 100644 --- a/examples/glvq_spiral.py +++ b/examples/glvq_spiral.py @@ -25,7 +25,6 @@ if __name__ == "__main__": distribution=(num_classes, prototypes_per_class), transfer_function="swish_beta", transfer_beta=10.0, - # lr=0.1, proto_lr=0.1, bb_lr=0.1, input_dim=2, @@ -36,7 +35,7 @@ if __name__ == "__main__": model = pt.models.GMLVQ( hparams, optimizer=torch.optim.Adam, - prototype_initializer=pt.components.SSI(train_ds, noise=1e-2), + prototypes_initializer=pt.initializers.SSCI(train_ds, noise=1e-2), ) # Callbacks @@ -46,12 +45,12 @@ if __name__ == "__main__": block=False, ) pruning = pt.models.PruneLoserPrototypes( - threshold=0.02, + threshold=0.01, idle_epochs=10, prune_quota_per_epoch=5, - frequency=2, + frequency=5, replace=True, - initializer=pt.components.SSI(train_ds, noise=1e-2), + prototypes_initializer=pt.initializers.SSCI(train_ds, noise=1e-1), verbose=True, ) es = pl.callbacks.EarlyStopping( @@ -67,7 +66,7 @@ if __name__ == "__main__": args, callbacks=[ vis, - # es, + # es, # FIXME pruning, ], terminate_on_nan=True, diff --git a/prototorch/models/callbacks.py b/prototorch/models/callbacks.py index 32798e6..1f07f6a 100644 --- a/prototorch/models/callbacks.py +++ b/prototorch/models/callbacks.py @@ -16,7 +16,7 @@ class PruneLoserPrototypes(pl.Callback): prune_quota_per_epoch=-1, frequency=1, replace=False, - initializer=None, + prototypes_initializer=None, verbose=False): self.threshold = threshold # minimum win ratio self.idle_epochs = idle_epochs # epochs to wait before pruning @@ -24,7 +24,7 @@ class PruneLoserPrototypes(pl.Callback): self.frequency = frequency self.replace = replace self.verbose = verbose - self.initializer = initializer + self.prototypes_initializer = prototypes_initializer def on_epoch_end(self, trainer, pl_module): if (trainer.current_epoch + 1) < self.idle_epochs: @@ -55,8 +55,9 @@ class PruneLoserPrototypes(pl.Callback): if self.verbose: print(f"Re-adding pruned prototypes...") print(f"{distribution=}") - pl_module.add_prototypes(distribution=distribution, - initializer=self.initializer) + pl_module.add_prototypes( + distribution=distribution, + components_initializer=self.prototypes_initializer) new_num_protos = pl_module.num_prototypes if self.verbose: print(f"`num_prototypes` changed from {cur_num_protos} " From 4eafe88dc488de24131fa966723ede348d24d3de Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:23:07 +0200 Subject: [PATCH 05/17] [BUGFIX] `examples/ksom_colors.py` works again --- examples/ksom_colors.py | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/examples/ksom_colors.py b/examples/ksom_colors.py index aaca382..eee4a04 100644 --- a/examples/ksom_colors.py +++ b/examples/ksom_colors.py @@ -6,20 +6,7 @@ import prototorch as pt import pytorch_lightning as pl import torch from matplotlib import pyplot as plt - - -def hex_to_rgb(hex_values): - for v in hex_values: - v = v.lstrip('#') - lv = len(v) - c = [int(v[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)] - yield c - - -def rgb_to_hex(rgb_values): - for v in rgb_values: - c = "%02x%02x%02x" % tuple(v) - yield c +from prototorch.utils.colors import hex_to_rgb class Vis2DColorSOM(pl.Callback): @@ -92,7 +79,7 @@ if __name__ == "__main__": # Initialize the model model = pt.models.KohonenSOM( hparams, - prototype_initializer=pt.components.Random(3), + prototypes_initializer=pt.initializers.RNCI(3), ) # Compute intermediate input and output sizes From d2856383e27f37cfd422d7ff4df5566764b5ccfc Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:29:31 +0200 Subject: [PATCH 06/17] [BUGFIX] `examples/gng_iris.py` works again --- examples/gng_iris.py | 2 +- prototorch/models/callbacks.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/gng_iris.py b/examples/gng_iris.py index dd25b16..7f1275d 100644 --- a/examples/gng_iris.py +++ b/examples/gng_iris.py @@ -29,7 +29,7 @@ if __name__ == "__main__": # Initialize the model model = pt.models.GrowingNeuralGas( hparams, - prototype_initializer=pt.components.Zeros(2), + prototypes_initializer=pt.initializers.ZCI(2), ) # Compute intermediate input and output sizes diff --git a/prototorch/models/callbacks.py b/prototorch/models/callbacks.py index 1f07f6a..0267270 100644 --- a/prototorch/models/callbacks.py +++ b/prototorch/models/callbacks.py @@ -6,6 +6,7 @@ import pytorch_lightning as pl import torch from ..core.components import Components +from ..core.initializers import LiteralCompInitializer from .extras import ConnectionTopology @@ -117,7 +118,7 @@ class GNGCallback(pl.Callback): # Add component pl_module.proto_layer.add_components( - initialized_components=new_component.unsqueeze(0)) + initializer=LiteralCompInitializer(new_component.unsqueeze(0))) # Adjust Topology topology.add_prototype() From 6197d7d5d638f4cf5e1614357ede8b1d8e574cfb Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:31:39 +0200 Subject: [PATCH 07/17] [BUGFIX] `examples/dynamic_pruning.py` works again --- examples/dynamic_pruning.py | 2 +- prototorch/models/probabilistic.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/dynamic_pruning.py b/examples/dynamic_pruning.py index bb01bd1..454b66e 100644 --- a/examples/dynamic_pruning.py +++ b/examples/dynamic_pruning.py @@ -36,7 +36,7 @@ if __name__ == "__main__": # Initialize the model model = pt.models.CELVQ( hparams, - prototype_initializer=pt.components.Ones(2, scale=3), + prototypes_initializer=pt.initializers.FVCI(2, 3.0), ) # Compute intermediate input and output sizes diff --git a/prototorch/models/probabilistic.py b/prototorch/models/probabilistic.py index ac161e2..6129bcf 100644 --- a/prototorch/models/probabilistic.py +++ b/prototorch/models/probabilistic.py @@ -20,7 +20,7 @@ class CELVQ(GLVQ): def shared_step(self, batch, batch_idx, optimizer_idx=None): x, y = batch out = self.compute_distances(x) # [None, num_protos] - plabels = self.proto_layer.component_labels + plabels = self.proto_layer.labels winning = stratified_min_pooling(out, plabels) # [None, num_classes] probs = -1.0 * winning batch_loss = self.loss(probs, y.long()) From 1911d4b33e794451b530a9531ea13b74a6d59c85 Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:34:46 +0200 Subject: [PATCH 08/17] [BUGFIX] `examples/lgmlvq_moons.py` works again --- examples/lgmlvq_moons.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/lgmlvq_moons.py b/examples/lgmlvq_moons.py index 5c053ad..cbdc9b4 100644 --- a/examples/lgmlvq_moons.py +++ b/examples/lgmlvq_moons.py @@ -12,12 +12,12 @@ if __name__ == "__main__": parser = pl.Trainer.add_argparse_args(parser) args = parser.parse_args() - # Dataset - train_ds = pt.datasets.Moons(num_samples=300, noise=0.2, seed=42) - # Reproducibility pl.utilities.seed.seed_everything(seed=2) + # Dataset + train_ds = pt.datasets.Moons(num_samples=300, noise=0.2, seed=42) + # Dataloaders train_loader = torch.utils.data.DataLoader(train_ds, batch_size=256, @@ -31,8 +31,10 @@ if __name__ == "__main__": ) # Initialize the model - model = pt.models.LGMLVQ(hparams, - prototype_initializer=pt.components.SMI(train_ds)) + model = pt.models.LGMLVQ( + hparams, + prototypes_initializer=pt.initializers.SMCI(train_ds), + ) # Compute intermediate input and output sizes model.example_input_array = torch.zeros(4, 2) From 1c658cdc1b38685767490d5c3e732076cb699732 Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:42:57 +0200 Subject: [PATCH 09/17] [FEATURE] Add warm-starting example --- examples/warm_starting.py | 84 ++++++++++++++++++++++++++++++++++ prototorch/models/abstract.py | 4 ++ prototorch/models/callbacks.py | 1 + 3 files changed, 89 insertions(+) create mode 100644 examples/warm_starting.py diff --git a/examples/warm_starting.py b/examples/warm_starting.py new file mode 100644 index 0000000..1a966f3 --- /dev/null +++ b/examples/warm_starting.py @@ -0,0 +1,84 @@ +"""Warm-starting GLVQ with prototypes from Growing Neural Gas.""" + +import argparse + +import prototorch as pt +import pytorch_lightning as pl +import torch +from torch.optim.lr_scheduler import ExponentialLR + +if __name__ == "__main__": + # Command-line arguments + parser = argparse.ArgumentParser() + parser = pl.Trainer.add_argparse_args(parser) + args = parser.parse_args() + + # Prepare the data + train_ds = pt.datasets.Iris(dims=[0, 2]) + train_loader = torch.utils.data.DataLoader(train_ds, batch_size=64) + + # Initialize the gng + gng = pt.models.GrowingNeuralGas( + hparams=dict(num_prototypes=5, insert_freq=2, lr=0.1), + prototypes_initializer=pt.initializers.ZCI(2), + lr_scheduler=ExponentialLR, + lr_scheduler_kwargs=dict(gamma=0.99, verbose=False), + ) + + # Callbacks + es = pl.callbacks.EarlyStopping( + monitor="loss", + min_delta=0.001, + patience=20, + mode="min", + verbose=False, + check_on_train_epoch_end=True, + ) + + # Setup trainer for GNG + trainer = pl.Trainer( + max_epochs=200, + callbacks=[es], + weights_summary=None, + ) + + # Training loop + trainer.fit(gng, train_loader) + + # Hyperparameters + hparams = dict( + distribution=[], + lr=0.01, + ) + + # Warm-start prototypes + knn = pt.models.KNN(dict(k=1), data=train_ds) + prototypes = gng.prototypes + plabels = knn.predict(prototypes) + + # Initialize the model + model = pt.models.GLVQ( + hparams, + optimizer=torch.optim.Adam, + prototypes_initializer=pt.initializers.LCI(prototypes), + labels_initializer=pt.initializers.LLI(plabels), + lr_scheduler=ExponentialLR, + lr_scheduler_kwargs=dict(gamma=0.99, verbose=False), + ) + + # Compute intermediate input and output sizes + model.example_input_array = torch.zeros(4, 2) + + # Callbacks + vis = pt.models.VisGLVQ2D(data=train_ds) + + # Setup trainer + trainer = pl.Trainer.from_argparse_args( + args, + callbacks=[vis], + weights_summary="full", + accelerator="ddp", + ) + + # Training loop + trainer.fit(model, train_loader) diff --git a/prototorch/models/abstract.py b/prototorch/models/abstract.py index eacf586..4e79d4a 100644 --- a/prototorch/models/abstract.py +++ b/prototorch/models/abstract.py @@ -9,6 +9,7 @@ import torchmetrics from ..core.competitions import WTAC from ..core.components import Components, LabeledComponents from ..core.distances import euclidean_distance +from ..core.initializers import LabelsInitializer from ..core.pooling import stratified_min_pooling from ..nn.wrappers import LambdaLayer @@ -111,10 +112,13 @@ class SupervisedPrototypeModel(PrototypeModel): # Layers prototypes_initializer = kwargs.get("prototypes_initializer", None) + labels_initializer = kwargs.get("labels_initializer", + LabelsInitializer()) if prototypes_initializer is not None: self.proto_layer = LabeledComponents( distribution=self.hparams.distribution, components_initializer=prototypes_initializer, + labels_initializer=labels_initializer, ) self.competition_layer = WTAC() diff --git a/prototorch/models/callbacks.py b/prototorch/models/callbacks.py index 0267270..62b628a 100644 --- a/prototorch/models/callbacks.py +++ b/prototorch/models/callbacks.py @@ -118,6 +118,7 @@ class GNGCallback(pl.Callback): # Add component pl_module.proto_layer.add_components( + None, initializer=LiteralCompInitializer(new_component.unsqueeze(0))) # Adjust Topology From 24ebfdc667903687dcb94dcdada62a39f810187e Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:44:36 +0200 Subject: [PATCH 10/17] [BUGFIX] `examples/siamese_glvq_iris.py` works again --- examples/siamese_glvq_iris.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/siamese_glvq_iris.py b/examples/siamese_glvq_iris.py index cdd279d..9ca9d07 100644 --- a/examples/siamese_glvq_iris.py +++ b/examples/siamese_glvq_iris.py @@ -2,11 +2,10 @@ import argparse +import prototorch as pt import pytorch_lightning as pl import torch -import prototorch as pt - class Backbone(torch.nn.Module): def __init__(self, input_size=4, hidden_size=10, latent_size=2): @@ -52,7 +51,7 @@ if __name__ == "__main__": # Initialize the model model = pt.models.SiameseGLVQ( hparams, - prototype_initializer=pt.components.SMI(train_ds), + prototypes_initializer=pt.initializers.SMCI(train_ds), backbone=backbone, both_path_gradients=False, ) From a44219ee474f7cf03b2bc94cb82f82af0087421e Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 20:56:38 +0200 Subject: [PATCH 11/17] [BUG] PLVQ seems broken --- examples/rslvq_iris.py | 11 ++++------- prototorch/models/probabilistic.py | 17 ++++++++--------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/examples/rslvq_iris.py b/examples/rslvq_iris.py index d70a763..c7d3961 100644 --- a/examples/rslvq_iris.py +++ b/examples/rslvq_iris.py @@ -5,7 +5,6 @@ import argparse import prototorch as pt import pytorch_lightning as pl import torch -from torchvision.transforms import Lambda if __name__ == "__main__": # Command-line arguments @@ -27,19 +26,17 @@ if __name__ == "__main__": distribution=[2, 2, 3], proto_lr=0.05, lambd=0.1, + variance=1.0, input_dim=2, latent_dim=2, bb_lr=0.01, ) # Initialize the model - model = pt.models.probabilistic.PLVQ( + model = pt.models.RSLVQ( hparams, optimizer=torch.optim.Adam, - # prototype_initializer=pt.components.SMI(train_ds), - prototype_initializer=pt.components.SSI(train_ds, noise=0.2), - # prototype_initializer=pt.components.Zeros(2), - # prototype_initializer=pt.components.Ones(2, scale=2.0), + prototypes_initializer=pt.initializers.SSCI(train_ds, noise=0.2), ) # Compute intermediate input and output sizes @@ -49,7 +46,7 @@ if __name__ == "__main__": print(model) # Callbacks - vis = pt.models.VisSiameseGLVQ2D(data=train_ds) + vis = pt.models.VisGLVQ2D(data=train_ds) # Setup trainer trainer = pl.Trainer.from_argparse_args( diff --git a/prototorch/models/probabilistic.py b/prototorch/models/probabilistic.py index 6129bcf..d7276f3 100644 --- a/prototorch/models/probabilistic.py +++ b/prototorch/models/probabilistic.py @@ -54,7 +54,7 @@ class ProbabilisticLVQ(GLVQ): def training_step(self, batch, batch_idx, optimizer_idx=None): x, y = batch out = self.forward(x) - plabels = self.proto_layer.component_labels + plabels = self.proto_layer.labels batch_loss = self.loss(out, y, plabels) loss = batch_loss.sum(dim=0) return loss @@ -87,11 +87,10 @@ class PLVQ(ProbabilisticLVQ, SiameseGMLVQ): self.hparams.lambd) self.loss = torch.nn.KLDivLoss() - def training_step(self, batch, batch_idx, optimizer_idx=None): - x, y = batch - out = self.forward(x) - y_dist = torch.nn.functional.one_hot( - y.long(), num_classes=self.num_classes).float() - batch_loss = self.loss(out, y_dist) - loss = batch_loss.sum(dim=0) - return loss + # FIXME + # def training_step(self, batch, batch_idx, optimizer_idx=None): + # x, y = batch + # y_pred = self(x) + # batch_loss = self.loss(y_pred, y) + # loss = batch_loss.sum(dim=0) + # return loss From 7ec5528adea5803cbc773e7ea075e30c8efee1ee Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 21:00:26 +0200 Subject: [PATCH 12/17] [BUGFIX] `examples/lvqmln_iris.py` works again --- examples/lvqmln_iris.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/lvqmln_iris.py b/examples/lvqmln_iris.py index c688788..79df874 100644 --- a/examples/lvqmln_iris.py +++ b/examples/lvqmln_iris.py @@ -2,11 +2,10 @@ import argparse +import prototorch as pt import pytorch_lightning as pl import torch -import prototorch as pt - class Backbone(torch.nn.Module): def __init__(self, input_size=4, hidden_size=10, latent_size=2): @@ -41,7 +40,7 @@ if __name__ == "__main__": # Hyperparameters hparams = dict( - distribution=[1, 2, 2], + distribution=[3, 4, 5], proto_lr=0.001, bb_lr=0.001, ) @@ -52,7 +51,10 @@ if __name__ == "__main__": # Initialize the model model = pt.models.LVQMLN( hparams, - prototype_initializer=pt.components.SSI(train_ds, transform=backbone), + prototypes_initializer=pt.initializers.SSCI( + train_ds, + transform=backbone, + ), backbone=backbone, ) @@ -67,11 +69,21 @@ if __name__ == "__main__": resolution=500, axis_off=True, ) + pruning = pt.models.PruneLoserPrototypes( + threshold=0.01, + idle_epochs=20, + prune_quota_per_epoch=2, + frequency=10, + verbose=True, + ) # Setup trainer trainer = pl.Trainer.from_argparse_args( args, - callbacks=[vis], + callbacks=[ + vis, + pruning, + ], ) # Training loop From 1b420c1f6b9390ce4e252b2daaa982cbd334acbb Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Mon, 14 Jun 2021 21:08:05 +0200 Subject: [PATCH 13/17] [BUG] LVQ1 is broken --- examples/lvq1_iris.py | 74 ++++++++++++++++++++++++++++++++++++++++ prototorch/models/lvq.py | 6 ++-- 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 examples/lvq1_iris.py diff --git a/examples/lvq1_iris.py b/examples/lvq1_iris.py new file mode 100644 index 0000000..f5b61bf --- /dev/null +++ b/examples/lvq1_iris.py @@ -0,0 +1,74 @@ +"""LVQ1 example using the Iris dataset.""" + +import prototorch as pt +import pytorch_lightning as pl +import torch + +if __name__ == "__main__": + # Acquire data + from sklearn.datasets import load_iris + x_train, y_train = load_iris(return_X_y=True) + x_train = x_train[:, [0, 2]] + + # Relabel classes + # y_train[y_train == 0] = 3 + # y_train[y_train == 1] = 4 + # y_train[y_train == 2] = 6 + + # Dataset + train_ds = pt.datasets.NumpyDataset(x_train, y_train) + + # Dataloaders + train_loader = torch.utils.data.DataLoader(train_ds, shuffle=True) + + # Hyperparameters + num_classes = 3 + prototypes_per_class = 10 + hparams = dict( + distribution={ + # class_label: num_prototypes + # 3: 1, + # 4: 2, + # 6: 3, + 0: 1, + 2: 2, + 3: 3, + }, + lr=0.001, + ) + + # Initialize the model + model = pt.models.LVQ1( + hparams, + prototypes_initializer=pt.initializers.SMCI(train_ds), + ) + + # Check if `num_classes` is correct + print(f"{model.num_classes=}") + assert model.num_classes == 3 + + # Compute intermediate input and output sizes + model.example_input_array = torch.zeros(4, 2) + + # Model summary + print(model) + + # Callbacks + vis = pt.models.VisGLVQ2D(data=(x_train, y_train), + cmap="viridis", + resolution=200, + block=False) + + # Setup trainer + trainer = pl.Trainer( + gpus=0, + max_epochs=50, + callbacks=[vis], + # fast_dev_run=1, + ) + + # Get prototype labels + print(f"Protoype Labels are: ", model.prototype_labels.tolist()) + + # Training loop + trainer.fit(model, train_loader) diff --git a/prototorch/models/lvq.py b/prototorch/models/lvq.py index 4e84be3..61c946f 100644 --- a/prototorch/models/lvq.py +++ b/prototorch/models/lvq.py @@ -9,7 +9,7 @@ class LVQ1(NonGradientMixin, GLVQ): """Learning Vector Quantization 1.""" def training_step(self, train_batch, batch_idx, optimizer_idx=None): protos = self.proto_layer.components - plabels = self.proto_layer.component_labels + plabels = self.proto_layer.labels x, y = train_batch dis = self.compute_distances(x) @@ -28,6 +28,8 @@ class LVQ1(NonGradientMixin, GLVQ): self.proto_layer.load_state_dict({"_components": updated_protos}, strict=False) + print(f"{dis=}") + print(f"{y=}") # Logging self.log_acc(dis, y, tag="train_acc") @@ -38,7 +40,7 @@ class LVQ21(NonGradientMixin, GLVQ): """Learning Vector Quantization 2.1.""" def training_step(self, train_batch, batch_idx, optimizer_idx=None): protos = self.proto_layer.components - plabels = self.proto_layer.component_labels + plabels = self.proto_layer.labels x, y = train_batch dis = self.compute_distances(x) From a37095409b15696035ac6ab91cbfafc5fbdb814e Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Tue, 15 Jun 2021 15:59:47 +0200 Subject: [PATCH 14/17] [BUGFIX] `examples/cbc_iris.py` works again --- examples/cbc_iris.py | 13 +++--- prototorch/models/abstract.py | 2 +- prototorch/models/cbc.py | 57 +++++++++++------------- prototorch/models/extras.py | 84 +---------------------------------- prototorch/models/glvq.py | 6 +-- prototorch/models/vis.py | 2 +- 6 files changed, 39 insertions(+), 125 deletions(-) diff --git a/examples/cbc_iris.py b/examples/cbc_iris.py index e204e81..f0561af 100644 --- a/examples/cbc_iris.py +++ b/examples/cbc_iris.py @@ -2,11 +2,10 @@ import argparse +import prototorch as pt import pytorch_lightning as pl import torch -import prototorch as pt - if __name__ == "__main__": # Command-line arguments parser = argparse.ArgumentParser() @@ -24,14 +23,18 @@ if __name__ == "__main__": # Hyperparameters hparams = dict( - distribution=[2, 2, 2], - proto_lr=0.1, + distribution=[1, 0, 3], + margin=0.1, + proto_lr=0.01, + bb_lr=0.01, ) # Initialize the model model = pt.models.CBC( hparams, - prototype_initializer=pt.components.SSI(train_ds, noise=0.01), + components_initializer=pt.initializers.SSCI(train_ds, noise=0.01), + reasonings_iniitializer=pt.initializers. + PurePositiveReasoningsInitializer(), ) # Callbacks diff --git a/prototorch/models/abstract.py b/prototorch/models/abstract.py index 4e79d4a..d55bd08 100644 --- a/prototorch/models/abstract.py +++ b/prototorch/models/abstract.py @@ -128,7 +128,7 @@ class SupervisedPrototypeModel(PrototypeModel): @property def num_classes(self): - return len(self.proto_layer.distribution) + return self.proto_layer.num_classes def compute_distances(self, x): protos, _ = self.proto_layer() diff --git a/prototorch/models/cbc.py b/prototorch/models/cbc.py index 6f0f40c..430ae6c 100644 --- a/prototorch/models/cbc.py +++ b/prototorch/models/cbc.py @@ -1,55 +1,54 @@ import torch import torchmetrics +from ..core.competitions import CBCC from ..core.components import ReasoningComponents +from ..core.initializers import RandomReasoningsInitializer +from ..core.losses import MarginLoss +from ..core.similarities import euclidean_similarity +from ..nn.wrappers import LambdaLayer from .abstract import ImagePrototypesMixin -from .extras import ( - CosineSimilarity, - MarginLoss, - ReasoningLayer, - euclidean_similarity, - rescaled_cosine_similarity, - shift_activation, -) from .glvq import SiameseGLVQ class CBC(SiameseGLVQ): """Classification-By-Components.""" - def __init__(self, hparams, margin=0.1, **kwargs): + def __init__(self, hparams, **kwargs): super().__init__(hparams, **kwargs) - self.margin = margin - self.similarity_fn = kwargs.get("similarity_fn", euclidean_similarity) - num_components = self.components.shape[0] - self.reasoning_layer = ReasoningLayer(num_components=num_components, - num_classes=self.num_classes) - self.component_layer = self.proto_layer - @property - def components(self): - return self.prototypes + similarity_fn = kwargs.get("similarity_fn", euclidean_similarity) + components_initializer = kwargs.get("components_initializer", None) + reasonings_initializer = kwargs.get("reasonings_initializer", + RandomReasoningsInitializer()) + self.components_layer = ReasoningComponents( + self.hparams.distribution, + components_initializer=components_initializer, + reasonings_initializer=reasonings_initializer, + ) + self.similarity_layer = LambdaLayer(similarity_fn) + self.competition_layer = CBCC() - @property - def reasonings(self): - return self.reasoning_layer.reasonings.cpu() + # Namespace hook + self.proto_layer = self.components_layer + + self.loss = MarginLoss(self.hparams.margin) def forward(self, x): - components, _ = self.component_layer() + components, reasonings = self.components_layer() latent_x = self.backbone(x) self.backbone.requires_grad_(self.both_path_gradients) latent_components = self.backbone(components) self.backbone.requires_grad_(True) - detections = self.similarity_fn(latent_x, latent_components) - probs = self.reasoning_layer(detections) + detections = self.similarity_layer(latent_x, latent_components) + probs = self.competition_layer(detections, reasonings) return probs def shared_step(self, batch, batch_idx, optimizer_idx=None): x, y = batch - # x = x.view(x.size(0), -1) y_pred = self(x) - num_classes = self.reasoning_layer.num_classes + num_classes = self.num_classes y_true = torch.nn.functional.one_hot(y.long(), num_classes=num_classes) - loss = MarginLoss(self.margin)(y_pred, y_true).mean(dim=0) + loss = self.loss(y_pred, y_true).mean(dim=0) return y_pred, loss def training_step(self, batch, batch_idx, optimizer_idx=None): @@ -76,7 +75,3 @@ class ImageCBC(ImagePrototypesMixin, CBC): """CBC model that constrains the components to the range [0, 1] by clamping after updates. """ - def __init__(self, hparams, **kwargs): - super().__init__(hparams, **kwargs) - # Namespace hook - self.proto_layer = self.component_layer diff --git a/prototorch/models/extras.py b/prototorch/models/extras.py index 1b87ca8..644d0f1 100644 --- a/prototorch/models/extras.py +++ b/prototorch/models/extras.py @@ -6,33 +6,12 @@ Modules not yet available in prototorch go here temporarily. import torch -from ..core.distances import euclidean_distance -from ..core.similarities import cosine_similarity - - -def rescaled_cosine_similarity(x, y): - """Cosine Similarity rescaled to [0, 1].""" - similarities = cosine_similarity(x, y) - return (similarities + 1.0) / 2.0 - - -def shift_activation(x): - return (x + 1.0) / 2.0 - - -def euclidean_similarity(x, y, variance=1.0): - d = euclidean_distance(x, y) - return torch.exp(-(d * d) / (2 * variance)) - - -def gaussian(distances, variance): - return torch.exp(-(distances * distances) / (2 * variance)) +from ..core.similarities import gaussian def rank_scaled_gaussian(distances, lambd): order = torch.argsort(distances, dim=1) ranks = torch.argsort(order, dim=1) - return torch.exp(-torch.exp(-ranks / lambd) * distances) @@ -109,64 +88,3 @@ class ConnectionTopology(torch.nn.Module): def extra_repr(self): return f"(agelimit): ({self.agelimit})" - - -class CosineSimilarity(torch.nn.Module): - def __init__(self, activation=shift_activation): - super().__init__() - self.activation = activation - - def forward(self, x, y): - epsilon = torch.finfo(x.dtype).eps - normed_x = (x / x.pow(2).sum(dim=tuple(range( - 1, x.ndim)), keepdim=True).clamp(min=epsilon).sqrt()).flatten( - start_dim=1) - normed_y = (y / y.pow(2).sum(dim=tuple(range( - 1, y.ndim)), keepdim=True).clamp(min=epsilon).sqrt()).flatten( - start_dim=1) - # normed_x = (x / torch.linalg.norm(x, dim=1)) - diss = torch.inner(normed_x, normed_y) - return self.activation(diss) - - -class MarginLoss(torch.nn.modules.loss._Loss): - def __init__(self, - margin=0.3, - size_average=None, - reduce=None, - reduction="mean"): - super().__init__(size_average, reduce, reduction) - self.margin = margin - - def forward(self, input_, target): - dp = torch.sum(target * input_, dim=-1) - dm = torch.max(input_ - target, dim=-1).values - return torch.nn.functional.relu(dm - dp + self.margin) - - -class ReasoningLayer(torch.nn.Module): - def __init__(self, num_components, num_classes, num_replicas=1): - super().__init__() - self.num_replicas = num_replicas - self.num_classes = num_classes - probabilities_init = torch.zeros(2, 1, num_components, - self.num_classes) - probabilities_init.uniform_(0.4, 0.6) - # TODO Use `self.register_parameter("param", Paramater(param))` instead - self.reasoning_probabilities = torch.nn.Parameter(probabilities_init) - - @property - def reasonings(self): - pk = self.reasoning_probabilities[0] - nk = (1 - pk) * self.reasoning_probabilities[1] - ik = 1 - pk - nk - img = torch.cat([pk, nk, ik], dim=0).permute(1, 0, 2) - return img.unsqueeze(1) - - def forward(self, detections): - pk = self.reasoning_probabilities[0].clamp(0, 1) - nk = (1 - pk) * self.reasoning_probabilities[1].clamp(0, 1) - numerator = (detections @ (pk - nk)) + nk.sum(1) - probs = numerator / (pk + nk).sum(1) - probs = probs.squeeze(0) - return probs diff --git a/prototorch/models/glvq.py b/prototorch/models/glvq.py index 953a1c6..7aeea03 100644 --- a/prototorch/models/glvq.py +++ b/prototorch/models/glvq.py @@ -4,7 +4,8 @@ import torch from torch.nn.parameter import Parameter from ..core.competitions import wtac -from ..core.distances import lomega_distance, omega_distance, squared_euclidean_distance +from ..core.distances import (lomega_distance, omega_distance, + squared_euclidean_distance) from ..core.losses import glvq_loss, lvq1_loss, lvq21_loss from ..nn.activations import get_activation from ..nn.wrappers import LambdaLayer, LossLayer @@ -27,9 +28,6 @@ class GLVQ(SupervisedPrototypeModel): # Loss self.loss = LossLayer(glvq_loss) - # Prototype metrics - self.initialize_prototype_win_ratios() - def initialize_prototype_win_ratios(self): self.register_buffer( "prototype_win_ratios", diff --git a/prototorch/models/vis.py b/prototorch/models/vis.py index b4e17a0..dfedfe5 100644 --- a/prototorch/models/vis.py +++ b/prototorch/models/vis.py @@ -199,7 +199,7 @@ class VisCBC2D(Vis2DAbstract): self.plot_protos(ax, protos, "w") x = np.vstack((x_train, protos)) mesh_input, xx, yy = self.get_mesh_input(x) - _components = pl_module.component_layer._components + _components = pl_module.components_layer._components y_pred = pl_module.predict( torch.Tensor(mesh_input).type_as(_components)) y_pred = y_pred.cpu().reshape(xx.shape) From 29063dcec44cbe45a46360829b19c531c6ef2777 Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Wed, 16 Jun 2021 12:39:39 +0200 Subject: [PATCH 15/17] Update gitignore --- .gitignore | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index b70ebbe..360bbb8 100644 --- a/.gitignore +++ b/.gitignore @@ -128,14 +128,19 @@ dmypy.json # Pyre type checker .pyre/ -# Datasets -datasets/ - -# PyTorch-Lightning -lightning_logs/ - .vscode/ +# Vim +*~ +*.swp +*.swo + # Pytorch Models or Weights # If necessary make exceptions for single pretrained models -*.pt \ No newline at end of file +*.pt + +# Artifacts created by ProtoTorch Models +datasets/ +lightning_logs/ +examples/_*.py +examples/_*.ipynb \ No newline at end of file From 4ab0a5a414e58e49a51d6639ca2247dc1951365f Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Thu, 17 Jun 2021 14:51:09 +0200 Subject: [PATCH 16/17] Add setup config --- setup.cfg | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..24eeb0b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[isort] +profile = hug +src_paths = isort, test + +[yapf] +based_on_style = pep8 +spaces_before_comment = 2 +split_before_logical_operator = true From 7eb496110f072d033186ff61a23f2f75b6d53918 Mon Sep 17 00:00:00 2001 From: Jensun Ravichandran Date: Fri, 18 Jun 2021 13:43:44 +0200 Subject: [PATCH 17/17] Use `mesh2d` from `prototorch.utils` --- prototorch/models/vis.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/prototorch/models/vis.py b/prototorch/models/vis.py index dfedfe5..4f6b696 100644 --- a/prototorch/models/vis.py +++ b/prototorch/models/vis.py @@ -7,6 +7,8 @@ import torchvision from matplotlib import pyplot as plt from torch.utils.data import DataLoader, Dataset +from ..utils.utils import mesh2d + class Vis2DAbstract(pl.Callback): def __init__(self, @@ -73,16 +75,6 @@ class Vis2DAbstract(pl.Callback): ax.axis("off") return ax - def get_mesh_input(self, x): - x_shift = self.border * np.ptp(x[:, 0]) - y_shift = self.border * np.ptp(x[:, 1]) - x_min, x_max = x[:, 0].min() - x_shift, x[:, 0].max() + x_shift - y_min, y_max = x[:, 1].min() - y_shift, x[:, 1].max() + y_shift - xx, yy = np.meshgrid(np.linspace(x_min, x_max, self.resolution), - np.linspace(y_min, y_max, self.resolution)) - mesh_input = np.c_[xx.ravel(), yy.ravel()] - return mesh_input, xx, yy - def plot_data(self, ax, x, y): ax.scatter( x[:, 0], @@ -138,7 +130,7 @@ class VisGLVQ2D(Vis2DAbstract): self.plot_data(ax, x_train, y_train) self.plot_protos(ax, protos, plabels) x = np.vstack((x_train, protos)) - mesh_input, xx, yy = self.get_mesh_input(x) + mesh_input, xx, yy = mesh2d(x, self.border, self.resolution) _components = pl_module.proto_layer._components mesh_input = torch.from_numpy(mesh_input).type_as(_components) y_pred = pl_module.predict(mesh_input) @@ -173,9 +165,9 @@ class VisSiameseGLVQ2D(Vis2DAbstract): if self.show_protos: self.plot_protos(ax, protos, plabels) x = np.vstack((x_train, protos)) - mesh_input, xx, yy = self.get_mesh_input(x) + mesh_input, xx, yy = mesh2d(x, self.border, self.resolution) else: - mesh_input, xx, yy = self.get_mesh_input(x_train) + mesh_input, xx, yy = mesh2d(x_train, self.border, self.resolution) _components = pl_module.proto_layer._components mesh_input = torch.Tensor(mesh_input).type_as(_components) y_pred = pl_module.predict_latent(mesh_input, @@ -198,7 +190,7 @@ class VisCBC2D(Vis2DAbstract): self.plot_data(ax, x_train, y_train) self.plot_protos(ax, protos, "w") x = np.vstack((x_train, protos)) - mesh_input, xx, yy = self.get_mesh_input(x) + mesh_input, xx, yy = mesh2d(x, self.border, self.resolution) _components = pl_module.components_layer._components y_pred = pl_module.predict( torch.Tensor(mesh_input).type_as(_components))