Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 3 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,44 +19,12 @@ Then, you can import the module `my_pytorch_kit`.
## Usage

This package revolves around the [`BaseModel`](my_pytorch_kit/model/models.py), [`Trainer`](my_pytorch_kit/train/train.py) and [`Evaluator`](my_pytorch_kit/evaluation/evaluation.py) classes, which are extended to model, train and evaluate a model respectively.
```mermaid
graph TD
subgraph "Usage Workflow"
A["<b>Define Model</b><br/>(extends BaseModel)"]
B["<b>Define Data</b><br/>(Dataset / DataLoader)"]
G["<b>Extras</b><br/>(Optimizer, Tensorboard)"]
C["<b>Initialize Trainer</b><br/>(extends Trainer)"]
D["<b>Initialize Evaluator</b><br/>(extends Evaluator)"]
E["trainer.train()"]
F["evaluator.evaluate()"]
G["<b>Intitialize Tuner</b><br/>(Hyperparameter Tuning)"]
H["tuner.tune()"]
I["<b>Provided Architectures</b><br/>(Classifier, AE, VAE, ...)"]

end

%% Define node relationships
A --> C
B --> C
I --> A
A --> D
B --> D
C --> E
D --> F
C --> G
G --> H
<p align="middle">
<img src="docs/assets/flowchart.svg" width="600" />
</p>

%% Style the nodes
style A fill:#fbe,stroke:#333,stroke-width:2px
style B fill:#fbe,stroke:#333,stroke-width:2px
style G fill:#ffc,stroke:#333,stroke-width:2px
style C fill:#cde,stroke:#333,stroke-width:2px
style D fill:#cde,stroke:#333,stroke-width:2px
style E fill:#cfc,stroke:#333,stroke-width:2px
style F fill:#cfc,stroke:#333,stroke-width:2px
style G fill:#cde,stroke:#333,stroke-width:2px
style H fill:#cfc,stroke:#333,stroke-width:2px
```

Furthermore, this package provides architecture implementations and modelling utilities.
Currently implemented architectures include:
Expand Down
Binary file modified docs/assets/vae_4_generation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion examples/mnist/classifier/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def evaluate_batch(self, model, batch):

trainer.train(optimizer, **hparams)

model.save_model("models/classifier.pt")
model.save_model("models/classifier.pt", hparams)

else:
model.load_model("models/classifier.pt")
Expand Down
2 changes: 1 addition & 1 deletion examples/mnist/classifier/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def accumulate_result(self, result):
def get_result(self):
return self.result

def on_eval(self):
def on_eval(self, model):
self.batch_count = 0

#++++++++++++++ Here comes the main code ++++++++++++++++#
Expand Down
3 changes: 2 additions & 1 deletion examples/mnist/classifier/tune.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@
num_search=num_search,
ranks_considered=ranks_considered,
check_multiplicant=check_multiplier,
mode='dynamic')
mode='dynamic',
model_path='models/mnist_random_dynamic_')

result = evaluator.evaluate(model, test_loader)
print(f"Model accuracy on test set doing random dynamic search: {result*100:.2f}%")
Expand Down
14 changes: 9 additions & 5 deletions examples/mnist/vae/vae.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,26 @@ def kl_annealing(epoch, epochs, model, **kwargs):
train = input("Train new model? (y/[n]) ").lower() == "y"

hparams = {
"learning_rate": 1e-3,
"learning_rate": 3e-3,
"batch_size": 64,
"epochs": 15,
"optimizer_method": "Adam",
# "optimizer_kwargs": {"weight_decay": 1e-4},
"loss_func": nn.BCELoss(reduction="sum"),
"alpha": 1e-1,
"beta": 1.5e-3,
"classifier_loss_weight": 1.2,
"beta": 1.5e-1,
"classifier_loss_weight": 12,
"start_beta": 1e-3,
"final_beta": 4e-2,
"feature_space": (64, 7, 7),
"latent_dim": 4,
"latent_dim": 2,
"sample_input_shape": (1, 1, 28, 28),
"classifier_num_layers": 3,
# "epoch_function": kl_annealing
}

model = ImageVAESemiSupervised(**hparams)
model.proper_weight_init()
# model = ImageVAE(**hparams)

torchsummary.summary(model, (1, 28, 28))
Expand All @@ -64,7 +65,10 @@ def kl_annealing(epoch, epochs, model, **kwargs):
model.save_model("models/vae.pt", hparams)

else:
model.load_model("models/vae.pt")
path = input("Enter path to model (default: models/vae.pt) :")
if path == "":
path = "models/vae.pt"
model.load_model(path)

model.eval()

Expand Down
45 changes: 42 additions & 3 deletions examples/mnist/vae/vae_semi_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@
import os

from my_pytorch_kit.model.vae import ImageVAESemiSupervised
from my_pytorch_kit.model.classifier import ImageClassifier
from my_pytorch_kit.model.ae import ImageAE

def generate_samples(model, latent_dim, num_samples=20):
def generate_samples(model, latent_dim, num_samples=20, check_classifier=False, check_ae=False):
"""
Generates num_samples samples from the model,
plotting them in a grid with their labels
"""

classifier = None
if check_classifier:
classifier = ImageClassifier()
classifier.load_model("models/classifier.pt")

ae = None
if check_ae:
ae = ImageAE()
ae.load_model("models/ae.pt")

num_samples = int(num_samples)
n_rows = num_samples // 10
n_cols = 10
Expand All @@ -20,13 +32,40 @@ def generate_samples(model, latent_dim, num_samples=20):
z = torch.randn((num_samples, latent_dim))
images, labels = model.generate(z)

cl_labels = None
if classifier:
cl_labels = classifier(images)
cl_labels = torch.argmax(cl_labels, dim=1)

batch_recon_losses = None
if ae:
reconstruction = ae(images)
recon_losses = torch.nn.BCELoss(reduction="none")(reconstruction, images)
batch_recon_losses = recon_losses.mean(dim=(1, 2, 3))

plt.figure(figsize=(10, 10))
for i in range(num_samples):
plt.subplot(n_rows, n_cols, i + 1)
plt.imshow(images[i].view(28, 28).cpu().numpy(), cmap="Greys_r")
label = torch.argmax(labels[i]).item()
label = f"{torch.argmax(labels[i]).item()}"
if cl_labels is not None:
label += f" ({cl_labels[i].item()})"
if recon_losses is not None:
label += f" ({batch_recon_losses[i].item():.2f})"

plt.title(label)
plt.axis("off")

extra_text = "Generator label"
if cl_labels is not None:
extra_text += " (Classifier label)"
if recon_losses is not None:
extra_text += " (Reconstruction loss)"
plt.text(-100, 50, extra_text, ha="center", va="center")

print(labels.shape, cl_labels.shape)
print(torch.nn.CrossEntropyLoss()(labels, cl_labels).item())

plt.show()


Expand Down Expand Up @@ -76,7 +115,7 @@ def generate_dataset(model, num_samples, latent_dim, std_divider=1.5, batch_size

model.load_model("models/vae_semi_3.pt")

generate_samples(model, hparams["latent_dim"])
generate_samples(model, hparams["latent_dim"], check_classifier=True, check_ae=True)

gen_dataset = input("Generate dataset? (y/[n]): ").lower() == "y"

Expand Down
148 changes: 148 additions & 0 deletions examples/mnist/vae/vae_semi_tune.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@

import torch
import torch.nn as nn
import tqdm

from my_pytorch_kit.model.vae import ImageVAESemiSupervised
from my_pytorch_kit.train.tune import Tuner
from my_pytorch_kit.evaluation.evaluation import Evaluator
from my_pytorch_kit.model.classifier import ImageClassifier
from my_pytorch_kit.model.ae import ImageAE
from mnist.utils.mnist_utils import get_mnist_loaders
from mnist.autoencoder.ae_plots import plot_latent, plot_reconstructions, plot_label_clusters


class GenerationEvaluator(Evaluator):


def __init__(self,
latent_dim = 2,
num_samples = 1000,
batch_size = 64,
ce_weight = 1,
bce_weight = 2.5):
super().__init__()
self.batch_count = 0
self.result = 0

self.classifier = ImageClassifier()
self.classifier.load_model("models/classifier.pt")

self.ae = ImageAE()
self.ae.load_model("models/ae.pt")

self.latent_dim = latent_dim
self.num_samples = num_samples
self.batch_size = batch_size

self.bce = nn.BCELoss(reduction="mean")
self.ce = nn.CrossEntropyLoss(reduction="mean", label_smoothing=0.1)

self.ce_weight = ce_weight
self.bce_weight = bce_weight

def on_eval(self, model):

print("\nEvaluating...")
generated_count = 0

acc = 0

model.eval()
pbar = tqdm.tqdm(total=self.num_samples // self.batch_size + 1)
with torch.no_grad():
while generated_count < self.num_samples:

next_batch_size = (self.num_samples - generated_count) % self.batch_size
if next_batch_size == 0:
next_batch_size = self.batch_size


z = torch.randn((next_batch_size, self.latent_dim))
images, labels = model.generate(z)

cl_labels = self.classifier(images)
cl_labels = torch.argmax(cl_labels, dim=1)

ce_loss = self.ce(labels, cl_labels)


reconstruction = self.ae(images)
recon_losses = self.bce(reconstruction, images)

loss = self.bce_weight * recon_losses.item() + self.ce_weight * ce_loss.item()

acc += loss

generated_count += next_batch_size

pbar.update(1)
self.result = acc

def evaluate_batch(self, model, batch):
return 0

def accumulate_result(self, result):
return

def get_result(self):
return self.result

def kl_annealing(epoch, epochs, model, **kwargs):
model.beta = model.beta + (kwargs["final_beta"] - kwargs["start_beta"]) / (epochs - kwargs["beta_start_delay"])


if __name__ == '__main__':

hparams = {
"learning_rate": ((1e-4, 1e-2), "log"),
"batch_size": 64,
"epochs": 10,
"patience": 5,
"optimizer_method": "Adam",
# "optimizer_kwargs": {"weight_decay": 1e-4},
"loss_func": nn.BCELoss(reduction="sum"),
"alpha": ((0, 10), "float"),
"beta": ((0, 10), "float"),
"classifier_loss_weight": ((0, 10), "float"),
"start_beta": ((0, 1), "float"),
"final_beta": ((1, 10), "float"),
"beta_start_delay": ((4, 12), "int"),
"feature_space": ([(64, 7, 7)], "item"),
"latent_dim": 2,
"sample_input_shape": ([(1, 1, 28, 28)], "item"),
"classifier_num_layers": 3,
"use_scheduler": ((True, False, False), "item"),
"use_grad_clip": ((True, False, False), "item"),
"gamma": ((0.8, 0.95), "log"),
"epoch_function": ((kl_annealing, None, None), "item"),
}


train_loader, val_loader, test_loader = get_mnist_loaders(hparams["batch_size"])

evaluator = GenerationEvaluator()

tuner = Tuner(ImageVAESemiSupervised)

num_search = 500
ranks_considered = 15
check_multiplier = 3

# train model
model, best_config, results = tuner.tune(train_loader, val_loader, hparams,
evaluator=evaluator,
num_search=num_search,
ranks_considered=ranks_considered,
check_multiplicant=check_multiplier,
mode='dynamic', model_path='models/mnist_vae_semi_tuned_')

model.eval()

model.save_model("models/mnist_vae_semi_tuned_best.pt", best_config)

plot_reconstructions(model, test_loader)

distribution = plot_label_clusters(model.encoder, test_loader)

plot_latent(model.decoder, dist=distribution, stds=2)
Loading