chore(protoy): mixin restructuring

This commit is contained in:
Alexander Engelsberger 2022-05-18 15:43:09 +02:00
parent dc4f31d700
commit 3e50d0d817
No known key found for this signature in database
GPG Key ID: 72E54A9DAE51EB96
3 changed files with 101 additions and 57 deletions

View File

@ -17,7 +17,8 @@ from typing import (
import pytorch_lightning as pl import pytorch_lightning as pl
import torch import torch
from torchmetrics import Accuracy, Metric from torchmetrics import Metric
from torchmetrics.classification.accuracy import Accuracy
class BaseYArchitecture(pl.LightningModule): class BaseYArchitecture(pl.LightningModule):
@ -29,7 +30,7 @@ class BaseYArchitecture(pl.LightningModule):
registered_metrics: Dict[Type[Metric], Metric] = {} registered_metrics: Dict[Type[Metric], Metric] = {}
registered_metric_names: Dict[Type[Metric], Set[str]] = {} registered_metric_names: Dict[Type[Metric], Set[str]] = {}
components_layer: pl.LightningModule components_layer: torch.nn.Module
def __init__(self, hparams) -> None: def __init__(self, hparams) -> None:
super().__init__() super().__init__()
@ -63,7 +64,7 @@ class BaseYArchitecture(pl.LightningModule):
self.registered_metric_names[metric].add(name) self.registered_metric_names[metric].add(name)
# external API # external API
def get_competion(self, batch, components): def get_competition(self, batch, components):
latent_batch, latent_components = self.latent(batch, components) latent_batch, latent_components = self.latent(batch, components)
# TODO: => Latent Hook # TODO: => Latent Hook
comparison_tensor = self.comparison(latent_batch, latent_components) comparison_tensor = self.comparison(latent_batch, latent_components)
@ -76,7 +77,7 @@ class BaseYArchitecture(pl.LightningModule):
# TODO: manage different datatypes? # TODO: manage different datatypes?
components = self.components_layer() components = self.components_layer()
# TODO: => Component Hook # TODO: => Component Hook
comparison_tensor = self.get_competion(batch, components) comparison_tensor = self.get_competition(batch, components)
# TODO: => Competition Hook # TODO: => Competition Hook
return self.inference(comparison_tensor, components) return self.inference(comparison_tensor, components)
@ -92,13 +93,13 @@ class BaseYArchitecture(pl.LightningModule):
# TODO: manage different datatypes? # TODO: manage different datatypes?
components = self.components_layer() components = self.components_layer()
# TODO: => Component Hook # TODO: => Component Hook
return self.get_competion(batch, components) return self.get_competition(batch, components)
def loss_forward(self, batch): def loss_forward(self, batch):
# TODO: manage different datatypes? # TODO: manage different datatypes?
components = self.components_layer() components = self.components_layer()
# TODO: => Component Hook # TODO: => Component Hook
comparison_tensor = self.get_competion(batch, components) comparison_tensor = self.get_competition(batch, components)
# TODO: => Competition Hook # TODO: => Competition Hook
return self.loss(comparison_tensor, batch, components) return self.loss(comparison_tensor, batch, components)
@ -148,14 +149,14 @@ class BaseYArchitecture(pl.LightningModule):
def comparison(self, batch, components): def comparison(self, batch, components):
""" """
Takes a batch of size N and the componentsset of size M. Takes a batch of size N and the component set of size M.
It returns an NxMxD tensor containing D (usually 1) pairwise comparison measures. It returns an NxMxD tensor containing D (usually 1) pairwise comparison measures.
""" """
raise NotImplementedError( raise NotImplementedError(
"The comparison step has no reasonable default.") "The comparison step has no reasonable default.")
def competition(self, comparisonmeasures, components): def competition(self, comparison_measures, components):
""" """
Takes the tensor of comparison measures. Takes the tensor of comparison measures.
@ -164,7 +165,7 @@ class BaseYArchitecture(pl.LightningModule):
raise NotImplementedError( raise NotImplementedError(
"The competition step has no reasonable default.") "The competition step has no reasonable default.")
def loss(self, comparisonmeasures, batch, components): def loss(self, comparison_measures, batch, components):
""" """
Takes the tensor of competition measures. Takes the tensor of competition measures.
@ -172,7 +173,7 @@ class BaseYArchitecture(pl.LightningModule):
""" """
raise NotImplementedError("The loss step has no reasonable default.") raise NotImplementedError("The loss step has no reasonable default.")
def inference(self, comparisonmeasures, components): def inference(self, comparison_measures, components):
""" """
Takes the tensor of competition measures. Takes the tensor of competition measures.

View File

@ -1,4 +1,4 @@
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import Callable, Type from typing import Callable, Type
import torch import torch
@ -14,7 +14,8 @@ from prototorch.models.proto_y_architecture.base import BaseYArchitecture
from prototorch.nn.wrappers import LambdaLayer from prototorch.nn.wrappers import LambdaLayer
class SupervisedScheme(BaseYArchitecture): class SupervisedArchitecture(BaseYArchitecture):
components_layer: LabeledComponents
@dataclass @dataclass
class HyperParameters: class HyperParameters:
@ -28,23 +29,59 @@ class SupervisedScheme(BaseYArchitecture):
labels_initializer=LabelsInitializer(), labels_initializer=LabelsInitializer(),
) )
@property
def prototypes(self):
return self.components_layer.components.detach().cpu()
# ############################################################################## @property
# GLVQ def prototype_labels(self):
# ############################################################################## return self.components_layer.labels.detach().cpu()
class GLVQ(
SupervisedScheme, ):
"""GLVQ using the new Scheme class WTACompetitionMixin(BaseYArchitecture):
"""
@dataclass @dataclass
class HyperParameters(SupervisedScheme.HyperParameters): class HyperParameters(BaseYArchitecture.HyperParameters):
distance_fn: Callable = euclidean_distance pass
lr: float = 0.01
def init_inference(self, hparams: HyperParameters):
self.competition_layer = WTAC()
def inference(self, comparison_measures, components):
comp_labels = components[1]
return self.competition_layer(comparison_measures, comp_labels)
class GLVQLossMixin(BaseYArchitecture):
@dataclass
class HyperParameters(BaseYArchitecture.HyperParameters):
margin: float = 0.0 margin: float = 0.0
# TODO: make nicer
transfer_fn: str = "identity" transfer_fn: str = "sigmoid_beta"
transfer_beta: float = 10.0 transfer_args: dict = field(default_factory=lambda: dict(beta=10.0))
def init_loss(self, hparams: HyperParameters):
self.loss_layer = GLVQLoss(
margin=hparams.margin,
transfer_fn=hparams.transfer_fn,
**hparams.transfer_args,
)
def loss(self, comparison_measures, batch, components):
target = batch[1]
comp_labels = components[1]
loss = self.loss_layer(comparison_measures, target, comp_labels)
self.log('loss', loss)
return loss
class SingleLearningRateMixin(BaseYArchitecture):
@dataclass
class HyperParameters(BaseYArchitecture.HyperParameters):
# Training Hyperparameters
lr: float = 0.01
optimizer: Type[torch.optim.Optimizer] = torch.optim.Adam optimizer: Type[torch.optim.Optimizer] = torch.optim.Adam
def __init__(self, hparams: HyperParameters) -> None: def __init__(self, hparams: HyperParameters) -> None:
@ -52,20 +89,22 @@ class GLVQ(
self.lr = hparams.lr self.lr = hparams.lr
self.optimizer = hparams.optimizer self.optimizer = hparams.optimizer
def configure_optimizers(self):
return self.optimizer(self.parameters(), lr=self.lr) # type: ignore
class SimpleComparisonMixin(BaseYArchitecture):
@dataclass
class HyperParameters(BaseYArchitecture.HyperParameters):
# Training Hyperparameters
comparison_fn: Callable = euclidean_distance
comparison_args: dict = field(default_factory=lambda: dict())
def init_comparison(self, hparams: HyperParameters): def init_comparison(self, hparams: HyperParameters):
self.comparison_layer = LambdaLayer(hparams.distance_fn) self.comparison_layer = LambdaLayer(fn=hparams.comparison_fn,
**hparams.comparison_args)
def init_inference(self, hparams: HyperParameters):
self.competition_layer = WTAC()
def init_loss(self, hparams):
self.loss_layer = GLVQLoss(
margin=hparams.margin,
transfer_fn=hparams.transfer_fn,
beta=hparams.transfer_beta,
)
# Steps
def comparison(self, batch, components): def comparison(self, batch, components):
comp_tensor, _ = components comp_tensor, _ = components
batch_tensor, _ = batch batch_tensor, _ = batch
@ -76,23 +115,26 @@ class GLVQ(
return distances return distances
def inference(self, comparisonmeasures, components):
comp_labels = components[1]
return self.competition_layer(comparisonmeasures, comp_labels)
def loss(self, comparisonmeasures, batch, components): # ##############################################################################
target = batch[1] # GLVQ
comp_labels = components[1] # ##############################################################################
return self.loss_layer(comparisonmeasures, target, comp_labels) class GLVQ(
SupervisedArchitecture,
SimpleComparisonMixin,
GLVQLossMixin,
WTACompetitionMixin,
SingleLearningRateMixin,
):
"""GLVQ using the new Scheme
"""
def configure_optimizers(self): @dataclass
return self.optimizer(self.parameters(), lr=self.lr) # type: ignore class HyperParameters(
SimpleComparisonMixin.HyperParameters,
# Properties SingleLearningRateMixin.HyperParameters,
@property GLVQLossMixin.HyperParameters,
def prototypes(self): WTACompetitionMixin.HyperParameters,
return self.components_layer.components.detach().cpu() SupervisedArchitecture.HyperParameters,
):
@property pass
def prototype_labels(self):
return self.components_layer.labels.detach().cpu()

View File

@ -25,7 +25,7 @@ if __name__ == "__main__":
# Dataloader # Dataloader
train_loader = DataLoader( train_loader = DataLoader(
train_ds, train_ds,
batch_size=64, batch_size=32,
num_workers=0, num_workers=0,
shuffle=True, shuffle=True,
) )
@ -39,7 +39,7 @@ if __name__ == "__main__":
# Define Hyperparameters # Define Hyperparameters
hyperparameters = GLVQ.HyperParameters( hyperparameters = GLVQ.HyperParameters(
lr=0.5, lr=0.1,
distribution=dict( distribution=dict(
num_classes=2, num_classes=2,
per_class=1, per_class=1,
@ -49,6 +49,7 @@ if __name__ == "__main__":
# Create Model # Create Model
model = GLVQ(hyperparameters) model = GLVQ(hyperparameters)
print(model) print(model)
# ------------------------------------------------------------ # ------------------------------------------------------------