Refactoring

This commit is contained in:
Si11ium
2020-04-08 12:04:04 +02:00
parent c7971c063f
commit 25c0e8e358
17 changed files with 0 additions and 21 deletions
-21
View File
@@ -1,21 +0,0 @@
from PIL import ImageDraw
from PIL import Image
import numpy as np
def are_homotopic(map_array, trajectory, other_trajectory):
polyline = trajectory.vertices.copy()
polyline.extend(reversed(other_trajectory.vertices))
height, width = map_array.shape
img = Image.new('L', (height, width), 0)
ImageDraw.Draw(img).polygon(polyline, outline=1, fill=1)
a = (np.array(img) * map_array).sum()
if a >= 1:
return False
else:
return True
View File
-244
View File
@@ -1,244 +0,0 @@
from functools import reduce
from operator import mul
from random import choice
import torch
from torch import nn
from torch.optim import Adam
from datasets.mnist import MyMNIST
from datasets.trajectory_dataset import TrajData
from lib.modules.blocks import ConvModule, DeConvModule
from lib.modules.utils import LightningBaseModule, Flatten
import matplotlib.pyplot as plt
import lib.variables as V
from lib.visualization.generator_eval import GeneratorVisualizer
class CNNRouteGeneratorModel(LightningBaseModule):
torch.autograd.set_detect_anomaly(True)
name = 'CNNRouteGenerator'
def configure_optimizers(self):
return Adam(self.parameters(), lr=self.hparams.train_param.lr)
def training_step(self, batch_xy, batch_nb, *args, **kwargs):
batch_x, _ = batch_xy
reconstruction, z, mu, logvar = self(batch_x)
recon_loss = self.criterion(reconstruction, batch_x)
kldivergence = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
loss = recon_loss + kldivergence
return dict(loss=loss, log=dict(reconstruction_loss=recon_loss, loss=loss, kld_loss=kldivergence))
def _test_val_step(self, batch_xy, batch_nb, *args):
batch_x, _ = batch_xy
mu, logvar = self.encoder(batch_x)
z = self.reparameterize(mu, logvar)
reconstruction = self.decoder(mu)
return_dict = dict(input=batch_x, batch_nb=batch_nb, output=reconstruction, z=z, mu=mu, logvar=logvar)
labels = torch.full((batch_x.shape[0], 1), V.ANY)
return_dict.update(labels=self._move_to_model_device(labels))
return return_dict
def _test_val_epoch_end(self, outputs, test=False):
plt.close('all')
g = GeneratorVisualizer(choice(outputs))
fig = g.draw_io_bundle()
self.logger.log_image(f'{self.name}_Output', fig, step=self.global_step)
plt.clf()
fig = g.draw_latent()
self.logger.log_image(f'{self.name}_Latent', fig, step=self.global_step)
plt.clf()
return dict(epoch=self.current_epoch)
def validation_step(self, *args):
return self._test_val_step(*args)
def validation_epoch_end(self, outputs: list):
return self._test_val_epoch_end(outputs)
def test_step(self, *args):
return self._test_val_step(*args)
def test_epoch_end(self, outputs):
return self._test_val_epoch_end(outputs, test=True)
def __init__(self, *params, issubclassed=False):
super(CNNRouteGeneratorModel, self).__init__(*params)
# Dataset
self.dataset = TrajData(self.hparams.data_param.map_root,
mode=self.hparams.data_param.mode,
preprocessed=self.hparams.data_param.use_preprocessed,
length=self.hparams.data_param.dataset_length)
self.criterion = nn.BCELoss(reduction='sum')
# Additional Attributes
###################################################
self.in_shape = self.dataset.map_shapes_max
self.use_res_net = self.hparams.model_param.use_res_net
self.lat_dim = self.hparams.model_param.lat_dim
self.feature_dim = self.lat_dim
self.out_channels = 1 if 'generator' in self.hparams.data_param.mode else self.in_shape[0]
# NN Nodes
###################################################
self.encoder = Encoder(self.in_shape, self.hparams)
self.decoder = Decoder(self.out_channels, self.encoder.last_conv_shape, self.hparams)
def forward(self, batch_x):
# Encode
mu, logvar = self.encoder(batch_x)
# Bottleneck
z = self.reparameterize(mu, logvar)
# Decode
reconstruction = self.decoder(z)
return reconstruction, z, mu, logvar
@staticmethod
def reparameterize(mu, logvar):
std = 0.5 * torch.exp(logvar)
eps = torch.randn_like(mu)
z = mu + std * eps
return z
class Encoder(nn.Module):
def __init__(self, in_shape, hparams):
super(Encoder, self).__init__()
# Params
###################################################
self.hparams = hparams
# Additional Attributes
###################################################
self.in_shape = in_shape
self.use_res_net = self.hparams.model_param.use_res_net
self.lat_dim = self.hparams.model_param.lat_dim
self.feature_dim = self.lat_dim * 10
# NN Nodes
###################################################
#
# Utils
self.activation = self.hparams.activation()
#
# Encoder
self.conv_0 = ConvModule(self.in_shape, conv_kernel=3, conv_stride=1, conv_padding=1,
conv_filters=self.hparams.model_param.filters[0],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.conv_1 = ConvModule(self.conv_0.shape, conv_kernel=3, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[0],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.conv_2 = ConvModule(self.conv_1.shape, conv_kernel=5, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[1],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.conv_3 = ConvModule(self.conv_2.shape, conv_kernel=7, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[2],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.last_conv_shape = self.conv_3.shape
self.flat = Flatten(in_shape=self.last_conv_shape)
self.lin = nn.Linear(self.flat.shape, self.feature_dim)
#
# Variational Bottleneck
self.mu = nn.Linear(self.feature_dim, self.lat_dim)
self.logvar = nn.Linear(self.feature_dim, self.lat_dim)
def forward(self, batch_x):
tensor = self.conv_0(batch_x)
tensor = self.conv_1(tensor)
tensor = self.conv_2(tensor)
tensor = self.conv_3(tensor)
tensor = self.flat(tensor)
tensor = self.lin(tensor)
tensor = self.activation(tensor)
#
# Variational
# Parameter for Sampling
mu = self.mu(tensor)
logvar = self.logvar(tensor)
return mu, logvar
class Decoder(nn.Module):
def __init__(self, out_channels, last_conv_shape, hparams):
super(Decoder, self).__init__()
# Params
###################################################
self.hparams = hparams
# Additional Attributes
###################################################
self.use_res_net = self.hparams.model_param.use_res_net
self.lat_dim = self.hparams.model_param.lat_dim
self.feature_dim = self.lat_dim
self.out_channels = out_channels
# NN Nodes
###################################################
#
# Utils
self.activation = self.hparams.activation()
#
# Alternative Generator
self.lin = nn.Linear(self.lat_dim, reduce(mul, last_conv_shape))
self.reshape = Flatten(in_shape=reduce(mul, last_conv_shape), to=last_conv_shape)
self.deconv_1 = DeConvModule(last_conv_shape, self.hparams.model_param.filters[2],
conv_padding=0, conv_kernel=7, conv_stride=1,
use_norm=self.hparams.model_param.use_norm)
self.deconv_2 = DeConvModule(self.deconv_1.shape, self.hparams.model_param.filters[1],
conv_padding=1, conv_kernel=5, conv_stride=1,
use_norm=self.hparams.model_param.use_norm)
self.deconv_3 = DeConvModule(self.deconv_2.shape, self.hparams.model_param.filters[0],
conv_padding=0, conv_kernel=3, conv_stride=1,
use_norm=self.hparams.model_param.use_norm)
self.deconv_out = DeConvModule(self.deconv_3.shape, self.out_channels, activation=nn.Sigmoid,
conv_padding=0, conv_kernel=3, conv_stride=1,
use_norm=self.hparams.model_param.use_norm)
def forward(self, z):
tensor = self.lin(z)
tensor = self.activation(tensor)
tensor = self.reshape(tensor)
tensor = self.deconv_1(tensor)
tensor = self.deconv_2(tensor)
tensor = self.deconv_3(tensor)
reconstruction = self.deconv_out(tensor)
return reconstruction
-116
View File
@@ -1,116 +0,0 @@
from random import choices, seed
import numpy as np
import torch
from functools import reduce
from operator import mul
from torch import nn
from torch.optim import Adam
from datasets.trajectory_dataset import TrajData
from lib.evaluation.classification import ROCEvaluation
from lib.models.generators.cnn import CNNRouteGeneratorModel
from lib.modules.blocks import ConvModule, ResidualModule, DeConvModule
from lib.modules.utils import LightningBaseModule, Flatten
import matplotlib.pyplot as plt
class CNNRouteGeneratorDiscriminated(CNNRouteGeneratorModel):
name = 'CNNRouteGeneratorDiscriminated'
def training_step(self, batch_xy, batch_nb, *args, **kwargs):
batch_x, label = batch_xy
generated_alternative, z, mu, logvar = self(batch_x)
map_array, trajectory = batch_x
map_stack = torch.cat((map_array, trajectory, generated_alternative), dim=1)
pred_label = self.discriminator(map_stack)
discriminated_bce_loss = self.criterion(pred_label, label.float().unsqueeze(-1))
# see Appendix B from VAE paper:
# Kingma and Welling. Auto-Encoding Variational Bayes. ICLR, 2014
# https://arxiv.org/abs/1312.6114
# 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
kld_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
# Dimensional Resizing
kld_loss /= reduce(mul, self.in_shape)
loss = (kld_loss + discriminated_bce_loss) / 2
return dict(loss=loss, log=dict(loss=loss,
discriminated_bce_loss=discriminated_bce_loss,
kld_loss=kld_loss)
)
def _test_val_step(self, batch_xy, batch_nb, *args):
batch_x, label = batch_xy
generated_alternative, z, mu, logvar = self(batch_x)
map_array, trajectory = batch_x
map_stack = torch.cat((map_array, trajectory, generated_alternative), dim=1)
pred_label = self.discriminator(map_stack)
discriminated_bce_loss = self.criterion(pred_label, label.float().unsqueeze(-1))
return dict(discriminated_bce_loss=discriminated_bce_loss, batch_nb=batch_nb,
pred_label=pred_label, label=label, generated_alternative=generated_alternative)
def validation_step(self, *args):
return self._test_val_step(*args)
def validation_epoch_end(self, outputs: list):
return self._test_val_epoch_end(outputs)
def _test_val_epoch_end(self, outputs, test=False):
evaluation = ROCEvaluation(plot_roc=True)
pred_label = torch.cat([x['pred_label'] for x in outputs])
labels = torch.cat([x['label'] for x in outputs]).unsqueeze(1)
mean_losses = torch.stack([x['discriminated_bce_loss'] for x in outputs]).mean()
# Sci-py call ROC eval call is eval(true_label, prediction)
roc_auc, tpr, fpr = evaluation(labels.cpu().numpy(), pred_label.cpu().numpy(), )
if test:
# self.logger.log_metrics(score_dict)
self.logger.log_image(f'{self.name}_ROC-Curve', plt.gcf(), step=self.global_step)
plt.clf()
maps, trajectories, labels, val_restul_dict = self.generate_random()
from lib.visualization.generator_eval import GeneratorVisualizer
g = GeneratorVisualizer(maps, trajectories, labels, val_restul_dict)
fig = g.draw()
self.logger.log_image(f'{self.name}_Output', fig, step=self.global_step)
plt.clf()
return dict(mean_losses=mean_losses, roc_auc=roc_auc, epoch=self.current_epoch)
def test_step(self, *args):
return self._test_val_step(*args)
def test_epoch_end(self, outputs):
return self._test_val_epoch_end(outputs, test=True)
@property
def discriminator(self):
if self._disc is None:
raise RuntimeError('Set the Discriminator first; "set_discriminator(disc_model)')
return self._disc
def set_discriminator(self, disc_model):
if self._disc is not None:
raise RuntimeError('Discriminator has already been set... What are trying to do?')
self._disc = disc_model
def __init__(self, *params):
raise NotImplementedError
super(CNNRouteGeneratorDiscriminated, self).__init__(*params, issubclassed=True)
self._disc = None
self.criterion = nn.BCELoss()
self.dataset = TrajData(self.hparams.data_param.map_root, mode='just_route', preprocessed=True,
length=self.hparams.data_param.dataset_length, normalized=True)
-55
View File
@@ -1,55 +0,0 @@
from lib.modules.losses import BinaryHomotopicLoss
from lib.modules.utils import LightningBaseModule
from lib.objects.map import Map
from lib.objects.trajectory import Trajectory
import torch.nn as nn
class LinearRouteGeneratorModel(LightningBaseModule):
def test_epoch_end(self, outputs):
pass
name = 'LinearRouteGenerator'
def configure_optimizers(self):
pass
def validation_step(self, *args, **kwargs):
pass
def validation_end(self, outputs):
pass
def training_step(self, batch, batch_nb, *args, **kwargs):
# Type Annotation
traj_x: Trajectory
traj_o: Trajectory
label_x: int
map_name: str
map_x: Map
# Batch unpacking
traj_x, traj_o, label_x, map_name = batch
map_x = self.map_storage[map_name]
pred_y = self(map_x, traj_x, label_x)
loss = self.loss(traj_x, pred_y)
def training_step(self, batch_xy, batch_nb, *args, **kwargs):
batch_x, batch_y = batch_xy
pred_y = self(batch_x)
loss = self.criterion(pred_y, batch_y.unsqueeze(-1).float())
return dict(loss=loss, log=dict(loss=loss))
def test_step(self, *args, **kwargs):
pass
def __init__(self, *params):
super(LinearRouteGeneratorModel, self).__init__(*params)
self.criterion = BinaryHomotopicLoss(self.map_storage)
def forward(self, map_x, traj_x, label_x):
pass
-348
View File
@@ -1,348 +0,0 @@
from random import choice
import torch
from functools import reduce
from operator import mul
from torch import nn
from torch.optim import Adam
from datasets.trajectory_dataset import TrajData
from lib.evaluation.classification import ROCEvaluation
from lib.modules.blocks import ConvModule, ResidualModule, DeConvModule
from lib.modules.utils import LightningBaseModule, Flatten
import matplotlib.pyplot as plt
class CNNRouteGeneratorModel(LightningBaseModule):
name = 'CNNRouteGenerator'
def configure_optimizers(self):
return Adam(self.parameters(), lr=self.hparams.train_param.lr)
def training_step(self, batch_xy, batch_nb, *args, **kwargs):
batch_x, alternative = batch_xy
generated_alternative, z, mu, logvar = self(batch_x)
element_wise_loss = self.criterion(generated_alternative, alternative)
# see Appendix B from VAE paper:
# Kingma and Welling. Auto-Encoding Variational Bayes. ICLR, 2014
# https://arxiv.org/abs/1312.6114
# 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
kld_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
# Dimensional Resizing TODO: Does This make sense? Sanity Check it!
# kld_loss /= reduce(mul, self.in_shape)
# kld_loss *= self.hparams.data_param.dataset_length / self.hparams.train_param.batch_size * 100
loss = (kld_loss + element_wise_loss) / 2
return dict(loss=loss, log=dict(element_wise_loss=element_wise_loss, loss=loss, kld_loss=kld_loss))
def _test_val_step(self, batch_xy, batch_nb, *args):
batch_x, alternative = batch_xy
map_array = batch_x[0]
trajectory = batch_x[1]
label = batch_x[2].max()
z, _, _ = self.encode(batch_x)
generated_alternative = self.generate(z)
return dict(map_array=map_array, trajectory=trajectory, batch_nb=batch_nb, label=label,
generated_alternative=generated_alternative, pred_label=-1, alternative=alternative
)
def _test_val_epoch_end(self, outputs, test=False):
maps, trajectories, labels, val_restul_dict = self.generate_random()
from lib.visualization.generator_eval import GeneratorVisualizer
g = GeneratorVisualizer(maps, trajectories, labels, val_restul_dict)
fig = g.draw()
self.logger.log_image(f'{self.name}_Output', fig, step=self.global_step)
plt.clf()
return dict(epoch=self.current_epoch)
def validation_step(self, *args):
return self._test_val_step(*args)
def validation_epoch_end(self, outputs: list):
return self._test_val_epoch_end(outputs)
def test_step(self, *args):
return self._test_val_step(*args)
def test_epoch_end(self, outputs):
return self._test_val_epoch_end(outputs, test=True)
def __init__(self, *params, issubclassed=False):
super(CNNRouteGeneratorModel, self).__init__(*params)
if not issubclassed:
# Dataset
self.dataset = TrajData(self.hparams.data_param.map_root, mode='generator_all_in_map',
length=self.hparams.data_param.dataset_length, normalized=True)
self.criterion = nn.MSELoss()
# Additional Attributes #
#######################################################
self.map_shape = self.dataset.map_shapes_max
self.trajectory_features = 4
self.res_net = self.hparams.model_param.use_res_net
self.lat_dim = self.hparams.model_param.lat_dim
self.feature_dim = self.lat_dim * 10
########################################################
# NN Nodes
###################################################
#
# Utils
self.activation = nn.ReLU()
self.sigmoid = nn.Sigmoid()
#
# Map Encoder
self.enc_conv_0 = ConvModule(self.map_shape, conv_kernel=3, conv_stride=1, conv_padding=1,
conv_filters=self.hparams.model_param.filters[0],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_res_1 = ResidualModule(self.enc_conv_0.shape, ConvModule, 2, conv_kernel=5, conv_stride=1,
conv_padding=2, conv_filters=self.hparams.model_param.filters[0],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_conv_1a = ConvModule(self.enc_res_1.shape, conv_kernel=3, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[1],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_conv_1b = ConvModule(self.enc_conv_1a.shape, conv_kernel=3, conv_stride=2, conv_padding=0,
conv_filters=self.hparams.model_param.filters[1],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_res_2 = ResidualModule(self.enc_conv_1b.shape, ConvModule, 2, conv_kernel=5, conv_stride=1,
conv_padding=2, conv_filters=self.hparams.model_param.filters[1],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_conv_2a = ConvModule(self.enc_res_2.shape, conv_kernel=5, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[2],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_conv_2b = ConvModule(self.enc_conv_2a.shape, conv_kernel=5, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[2],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_res_3 = ResidualModule(self.enc_conv_2b.shape, ConvModule, 2, conv_kernel=7, conv_stride=1,
conv_padding=3, conv_filters=self.hparams.model_param.filters[2],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_conv_3a = ConvModule(self.enc_res_3.shape, conv_kernel=7, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[2],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
self.enc_conv_3b = ConvModule(self.enc_conv_3a.shape, conv_kernel=7, conv_stride=1, conv_padding=0,
conv_filters=self.hparams.model_param.filters[2],
use_norm=self.hparams.model_param.use_norm,
use_bias=self.hparams.model_param.use_bias)
# Trajectory Encoder
self.env_gru_1 = nn.GRU(input_size=self.trajectory_features, hidden_size=self.feature_dim,
num_layers=3, batch_first=True)
self.enc_flat = Flatten(self.enc_conv_3b.shape)
self.enc_lin_1 = nn.Linear(self.enc_flat.shape, self.feature_dim)
#
# Mixed Encoder
self.enc_lin_2 = nn.Linear(self.feature_dim, self.feature_dim)
self.enc_norm = nn.BatchNorm1d(self.feature_dim) if self.hparams.model_param.use_norm else lambda x: x
#
# Variational Bottleneck
self.mu = nn.Linear(self.feature_dim, self.lat_dim)
self.logvar = nn.Linear(self.feature_dim, self.lat_dim)
#
# Alternative Generator
self.gen_lin_1 = nn.Linear(self.hparams.model_param.lat_dim, self.feature_dim)
self.gen_lin_2 = nn.Linear(self.feature_dim, self.enc_flat.shape)
self.gen_gru_x = nn.GRU(None, None, batch_first=True)
def forward(self, batch_x):
#
# Encode
z, mu, logvar = self.encode(batch_x)
#
# Generate
alt_tensor = self.generate(z)
return alt_tensor, z, mu, logvar
@staticmethod
def reparameterize(mu, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + eps * std
def encode(self, batch_x):
combined_tensor = self.enc_conv_0(batch_x)
combined_tensor = self.enc_res_1(combined_tensor) if self.use_res_net else combined_tensor
combined_tensor = self.enc_conv_1a(combined_tensor)
combined_tensor = self.enc_conv_1b(combined_tensor)
combined_tensor = self.enc_res_2(combined_tensor) if self.use_res_net else combined_tensor
combined_tensor = self.enc_conv_2a(combined_tensor)
combined_tensor = self.enc_conv_2b(combined_tensor)
combined_tensor = self.enc_res_3(combined_tensor) if self.use_res_net else combined_tensor
combined_tensor = self.enc_conv_3a(combined_tensor)
combined_tensor = self.enc_conv_3b(combined_tensor)
combined_tensor = self.enc_flat(combined_tensor)
combined_tensor = self.enc_lin_1(combined_tensor)
combined_tensor = self.enc_lin_2(combined_tensor)
combined_tensor = self.enc_norm(combined_tensor)
combined_tensor = self.activation(combined_tensor)
combined_tensor = self.enc_lin_2(combined_tensor)
combined_tensor = self.enc_norm(combined_tensor)
combined_tensor = self.activation(combined_tensor)
#
# Parameter and Sampling
mu = self.mu(combined_tensor)
logvar = self.logvar(combined_tensor)
z = self.reparameterize(mu, logvar)
return z, mu, logvar
def generate(self, z):
alt_tensor = self.gen_lin_1(z)
alt_tensor = self.activation(alt_tensor)
alt_tensor = self.gen_lin_2(alt_tensor)
alt_tensor = self.activation(alt_tensor)
alt_tensor = self.reshape_to_last_conv(alt_tensor)
alt_tensor = self.gen_deconv_1a(alt_tensor)
alt_tensor = self.gen_deconv_1b(alt_tensor)
alt_tensor = self.gen_deconv_2a(alt_tensor)
alt_tensor = self.gen_deconv_2b(alt_tensor)
alt_tensor = self.gen_deconv_3a(alt_tensor)
alt_tensor = self.gen_deconv_3b(alt_tensor)
alt_tensor = self.gen_deconv_out(alt_tensor)
# alt_tensor = self.activation(alt_tensor)
alt_tensor = self.sigmoid(alt_tensor)
return alt_tensor
def generate_random(self, n=6):
maps = [self.map_storage[choice(self.map_storage.keys_list)] for _ in range(n)]
trajectories = [x.get_random_trajectory() for x in maps]
trajectories = [x.draw_in_array(self.map_storage.max_map_size) for x in trajectories]
trajectories = [torch.as_tensor(x, dtype=torch.float32) for x in trajectories] * 2
trajectories = self._move_to_model_device(torch.stack(trajectories))
maps = [torch.as_tensor(x.as_array, dtype=torch.float32) for x in maps] * 2
maps = self._move_to_model_device(torch.stack(maps))
labels = self._move_to_model_device(torch.as_tensor([0] * n + [1] * n))
return maps, trajectories, labels, self._test_val_step(((maps, trajectories, labels), None), -9999)
class CNNRouteGeneratorDiscriminated(CNNRouteGeneratorModel):
name = 'CNNRouteGeneratorDiscriminated'
def training_step(self, batch_xy, batch_nb, *args, **kwargs):
batch_x, label = batch_xy
generated_alternative, z, mu, logvar = self(batch_x)
map_array, trajectory = batch_x
map_stack = torch.cat((map_array, trajectory, generated_alternative), dim=1)
pred_label = self.discriminator(map_stack)
discriminated_bce_loss = self.criterion(pred_label, label.float().unsqueeze(-1))
# see Appendix B from VAE paper:
# Kingma and Welling. Auto-Encoding Variational Bayes. ICLR, 2014
# https://arxiv.org/abs/1312.6114
# 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
kld_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
# Dimensional Resizing
kld_loss /= reduce(mul, self.in_shape)
loss = (kld_loss + discriminated_bce_loss) / 2
return dict(loss=loss, log=dict(loss=loss,
discriminated_bce_loss=discriminated_bce_loss,
kld_loss=kld_loss)
)
def _test_val_step(self, batch_xy, batch_nb, *args):
batch_x, label = batch_xy
generated_alternative, z, mu, logvar = self(batch_x)
map_array, trajectory = batch_x
map_stack = torch.cat((map_array, trajectory, generated_alternative), dim=1)
pred_label = self.discriminator(map_stack)
discriminated_bce_loss = self.criterion(pred_label, label.float().unsqueeze(-1))
return dict(discriminated_bce_loss=discriminated_bce_loss, batch_nb=batch_nb,
pred_label=pred_label, label=label, generated_alternative=generated_alternative)
def validation_step(self, *args):
return self._test_val_step(*args)
def validation_epoch_end(self, outputs: list):
return self._test_val_epoch_end(outputs)
def _test_val_epoch_end(self, outputs, test=False):
evaluation = ROCEvaluation(plot_roc=True)
pred_label = torch.cat([x['pred_label'] for x in outputs])
labels = torch.cat([x['label'] for x in outputs]).unsqueeze(1)
mean_losses = torch.stack([x['discriminated_bce_loss'] for x in outputs]).mean()
# Sci-py call ROC eval call is eval(true_label, prediction)
roc_auc, tpr, fpr = evaluation(labels.cpu().numpy(), pred_label.cpu().numpy(), )
if test:
# self.logger.log_metrics(score_dict)
self.logger.log_image(f'{self.name}_ROC-Curve', plt.gcf(), step=self.global_step)
plt.clf()
maps, trajectories, labels, val_restul_dict = self.generate_random()
from lib.visualization.generator_eval import GeneratorVisualizer
g = GeneratorVisualizer(maps, trajectories, labels, val_restul_dict)
fig = g.draw()
self.logger.log_image(f'{self.name}_Output', fig, step=self.global_step)
plt.clf()
return dict(mean_losses=mean_losses, roc_auc=roc_auc, epoch=self.current_epoch)
def test_step(self, *args):
return self._test_val_step(*args)
def test_epoch_end(self, outputs):
return self._test_val_epoch_end(outputs, test=True)
@property
def discriminator(self):
if self._disc is None:
raise RuntimeError('Set the Discriminator first; "set_discriminator(disc_model)')
return self._disc
def set_discriminator(self, disc_model):
if self._disc is not None:
raise RuntimeError('Discriminator has already been set... What are trying to do?')
self._disc = disc_model
def __init__(self, *params):
super(CNNRouteGeneratorDiscriminated, self).__init__(*params, issubclassed=True)
self._disc = None
self.criterion = nn.BCELoss()
self.dataset = TrajData(self.hparams.data_param.map_root, mode='just_route',
length=self.hparams.data_param.dataset_length, normalized=True)
@@ -1,118 +0,0 @@
from functools import reduce
from operator import mul
import torch
from torch import nn
import torch.nn.functional as F
from torch.optim import Adam
from torch.utils.data import DataLoader
from datasets.trajectory_dataset import TrajData
from lib.evaluation.classification import ROCEvaluation
from lib.modules.utils import LightningBaseModule, Flatten
from lib.modules.blocks import ConvModule, ResidualModule
import matplotlib.pyplot as plt
class ConvHomDetector(LightningBaseModule):
name = 'CNNHomotopyClassifier'
def configure_optimizers(self):
return Adam(self.parameters(), lr=self.hparams.lr)
def training_step(self, batch_xy, batch_nb, *args, **kwargs):
batch_x, batch_y = batch_xy
pred_y = self(batch_x)
loss = self.criterion(pred_y, batch_y.unsqueeze(-1).float())
return {'loss': loss, 'log': dict(loss=loss)}
def test_step(self, batch_xy, batch_nb, **kwargs):
batch_x, batch_y = batch_xy
pred_y = self(batch_x)
return dict(prediction=pred_y, label=batch_y, batch_nb=batch_nb)
def validation_step(self, batch_xy, batch_nb, **kwargs):
batch_x, batch_y = batch_xy
pred_y = self(batch_x)
return dict(prediction=pred_y, label=batch_y, batch_nb=batch_nb)
def test_epoch_end(self, outputs):
return self._val_test_end(outputs)
def validation_epoch_end(self, outputs: list):
return self._val_test_end(outputs)
def _val_test_end(self, outputs, test=True):
evaluation = ROCEvaluation(plot_roc=True if test else False)
predictions = torch.cat([x['prediction'] for x in outputs])
labels = torch.cat([x['label'] for x in outputs]).unsqueeze(1)
# Sci-py call ROC eval call is eval(true_label, prediction)
roc_auc, tpr, fpr = evaluation(labels.cpu().numpy(), predictions.cpu().numpy())
# self.logger.log_metrics(score_dict)
if test:
self.logger.log_image(f'{self.name}', plt.gcf())
return dict(score=roc_auc, log=dict(roc_auc=roc_auc))
def __init__(self, hparams):
super(ConvHomDetector, self).__init__(hparams)
# Dataset
self.dataset = TrajData(self.hparams.data_param.map_root, mode='classifier_all_in_map', )
# Additional Attributes
self.map_shape = self.dataset.map_shapes_max
# Model Parameters
self.in_shape = self.dataset.map_shapes_max
assert len(self.in_shape) == 3, f'Image or map shape has to have 3 dims, but had: {len(self.in_shape)}'
self.criterion = nn.BCELoss()
self.sigmoid = nn.Sigmoid()
self.relu = nn.ReLU()
# NN Nodes
# ============================
# Convolutional Map Processing
self.map_conv_0 = ConvModule(self.in_shape, conv_kernel=3, conv_stride=1,
conv_padding=0, conv_filters=self.hparams.model_param.filters[0])
self.map_res_1 = ResidualModule(self.map_conv_0.shape, ConvModule, 3,
**dict(conv_kernel=3, conv_stride=1,
conv_padding=1, conv_filters=self.hparams.model_param.filters[0]))
self.map_conv_1 = ConvModule(self.map_res_1.shape, conv_kernel=5, conv_stride=1,
conv_padding=0, conv_filters=self.hparams.model_param.filters[0])
self.map_res_2 = ResidualModule(self.map_conv_1.shape, ConvModule, 3,
**dict(conv_kernel=3, conv_stride=1,
conv_padding=1, conv_filters=self.hparams.model_param.filters[0]))
self.map_conv_2 = ConvModule(self.map_res_2.shape, conv_kernel=5, conv_stride=1,
conv_padding=0, conv_filters=self.hparams.model_param.filters[0])
self.map_res_3 = ResidualModule(self.map_conv_2.shape, ConvModule, 3,
**dict(conv_kernel=3, conv_stride=1,
conv_padding=1, conv_filters=self.hparams.model_param.filters[0]))
self.map_conv_3 = ConvModule(self.map_res_3.shape, conv_kernel=5, conv_stride=1,
conv_padding=0, conv_filters=self.hparams.model_param.filters[0])
self.flatten = Flatten(self.map_conv_3.shape)
# ============================
# Classifier
#
self.linear = nn.Linear(reduce(mul, self.flatten.shape), self.hparams.model_param.classes * 10)
# Comments on Multi Class labels
self.classifier = nn.Linear(self.hparams.model_param.classes * 10, 1) # self.hparams.model_param.classes)
def forward(self, x):
tensor = self.map_conv_0(x)
tensor = self.map_res_1(tensor)
tensor = self.map_conv_1(tensor)
tensor = self.map_res_2(tensor)
tensor = self.map_conv_2(tensor)
tensor = self.map_conv_3(tensor)
tensor = self.flatten(tensor)
tensor = self.linear(tensor)
tensor = self.relu(tensor)
tensor = self.classifier(tensor)
tensor = self.sigmoid(tensor)
return tensor
View File
-193
View File
@@ -1,193 +0,0 @@
from collections import UserDict
from pathlib import Path
import copy
from math import sqrt
from random import Random
import numpy as np
from PIL import Image, ImageDraw
import networkx as nx
from matplotlib import pyplot as plt
from lib.objects.trajectory import Trajectory
import lib.variables as V
class Map(object):
def __copy__(self):
return copy.deepcopy(self)
@property
def shape(self):
return self.map_array.shape
@property
def width(self):
return self.shape[-2]
@property
def height(self):
return self.shape[-1]
@property
def as_graph(self):
return self._G
@property
def as_array(self):
return self.map_array
@property
def as_2d_array(self):
return self.map_array.squeeze()
def __init__(self, name='', array_like_map_representation=None):
if array_like_map_representation is not None:
array_like_map_representation = array_like_map_representation.astype(np.float32)
if array_like_map_representation.ndim == 2:
array_like_map_representation = np.expand_dims(array_like_map_representation, axis=0)
assert array_like_map_representation.ndim == 3
self.map_array: np.ndarray = array_like_map_representation
self.name = name
self.prng = Random()
pass
def seed(self, seed):
self.prng.seed(seed)
def __setattr__(self, key, value):
super(Map, self).__setattr__(key, value)
if key == 'map_array' and self.map_array is not None:
self._G = self._build_graph()
def _build_graph(self, full_neighbors=True):
graph = nx.Graph()
# Do checks in order: up - left - upperLeft - lowerLeft
neighbors = [(0, -1, 1), (-1, 0, 1), (-1, -1, sqrt(2)), (-1, 1, sqrt(2))]
# Check pixels for their color (determine if walkable)
for idx, value in np.ndenumerate(self.map_array):
if value != V.BLACK:
# IF walkable, add node
graph.add_node(idx, count=0)
# Fully connect to all surrounding neighbors
for n, (xdif, ydif, weight) in enumerate(neighbors):
# Differentiate between 8 and 4 neighbors
if not full_neighbors and n >= 2:
break
# ToDO: make this explicite and less ugly
query_node = idx[:1] + (idx[1] + ydif,) + (idx[2] + xdif,)
if graph.has_node(query_node):
graph.add_edge(idx, query_node, weight=weight)
return graph
@classmethod
def from_image(cls, imagepath: Path, embedding_size=None):
with Image.open(imagepath) as image:
# Turn the image to single Channel Greyscale
if image.mode != 'L':
image = image.convert('L')
map_array = np.expand_dims(np.array(image), axis=0)
if embedding_size:
assert isinstance(embedding_size, tuple), f'embedding_size was of type: {type(embedding_size)}'
embedding = np.full(embedding_size, V.BLACK)
embedding[:map_array.shape[0], :map_array.shape[1], :map_array.shape[2]] = map_array
map_array = embedding
return cls(name=imagepath.name, array_like_map_representation=map_array)
def simple_trajectory_between(self, start, dest):
vertices = list(nx.shortest_path(self._G, start, dest))
trajectory = Trajectory(vertices)
return trajectory
def get_valid_position(self):
valid_position = self.prng.choice(list(self._G.nodes))
return valid_position
def get_trajectory_from_vertices(self, *args):
coords = list()
for start, dest in zip(args[:-1], args[1:]):
coords.extend(nx.shortest_path(self._G, start, dest))
return Trajectory(coords)
def get_random_trajectory(self):
simple_trajectory = None
while simple_trajectory is None:
try:
start = self.get_valid_position()
dest = self.get_valid_position()
simple_trajectory = self.simple_trajectory_between(start, dest)
except nx.exception.NetworkXNoPath:
pass
return simple_trajectory
def generate_alternative(self, trajectory, mode='one_patching'):
start, dest = trajectory.endpoints
alternative = None
while alternative is None:
try:
if mode == 'one_patching':
patch = self.get_valid_position()
alternative = self.get_trajectory_from_vertices(start, patch, dest)
else:
raise RuntimeError(f'mode checking went wrong...')
except nx.exception.NetworkXNoPath:
pass
return alternative
def are_homotopic(self, trajectory, other_trajectory):
if not all(isinstance(x, Trajectory) for x in [trajectory, other_trajectory]):
raise TypeError
polyline = trajectory.xy_vertices
polyline.extend(reversed(other_trajectory.xy_vertices))
img = Image.new('L', (self.height, self.width), color=V.WHITE)
draw = ImageDraw.Draw(img)
draw.polygon(polyline, outline=V.BLACK, fill=V.BLACK)
binary_img = np.where(np.asarray(img).squeeze() == V.BLACK, 1, 0)
binary_map = np.where(self.as_2d_array == V.BLACK, 1, 0)
a = (binary_img * binary_map).sum()
if a:
return V.ALTERNATIVE # Non-Homotoph
else:
return V.HOMOTOPIC # Homotoph
def draw(self):
fig, ax = plt.gcf(), plt.gca()
# The standard colormaps also all have reversed versions.
# They have the same names with _r tacked on to the end.
# https: // matplotlib.org / api / pyplot_summary.html?highlight = colormaps
img = ax.imshow(self.as_2d_array, cmap='Greys_r')
return dict(img=img, fig=fig, ax=ax)
class MapStorage(UserDict):
@property
def keys_list(self):
return list(super(MapStorage, self).keys())
def __init__(self, map_root, *args, **kwargs):
super(MapStorage, self).__init__(*args, **kwargs)
self.map_root = Path(map_root)
map_files = list(self.map_root.glob('*.bmp'))
self.max_map_size = (1, ) + tuple(
reversed(
tuple(
map(
max, *[Image.open(map_file).size for map_file in map_files])
)
)
)
for map_file in map_files:
current_map = Map.from_image(map_file, embedding_size=self.max_map_size)
self.__setitem__(map_file.name, current_map)
-86
View File
@@ -1,86 +0,0 @@
from math import atan2
from typing import List, Tuple, Union
from matplotlib import pyplot as plt
from lib import variables as V
import numpy as np
class Trajectory(object):
@property
def vertices(self):
return self._vertices
@property
def xy_vertices(self):
return [(x, y) for _, y, x in self._vertices]
@property
def endpoints(self):
return self.start, self.dest
@property
def start(self):
return self._vertices[0]
@property
def dest(self):
return self._vertices[-1]
@property
def xs(self):
return [x[2] for x in self._vertices]
@property
def ys(self):
return [x[1] for x in self._vertices]
@property
def as_paired_list(self):
return list(zip(self._vertices[:-1], self._vertices[1:]))
def draw_in_array(self, shape):
trajectory_space = np.zeros(shape).astype(np.float32)
for index in self.vertices:
trajectory_space[index] = V.WHITE
return trajectory_space
@property
def np_vertices(self):
return [np.array(vertice) for vertice in self._vertices]
def __init__(self, vertices: Union[List[Tuple[int]], None] = None):
assert any((isinstance(vertices, list), vertices is None))
if vertices is not None:
self._vertices = vertices
pass
def is_equal_to(self, other_trajectory):
# ToDo: do further equality Checks here
return self._vertices == other_trajectory.vertices
def draw(self, highlights=True, label=None, **kwargs):
if label is not None:
kwargs.update(color='red' if label == V.HOMOTOPIC else 'green',
label='Homotopic' if label == V.HOMOTOPIC else 'Alternative',
lw=1)
if highlights:
kwargs.update(marker='o')
fig, ax = plt.gcf(), plt.gca()
img = plt.plot(self.xs, self.ys, **kwargs)
return dict(img=img, fig=fig, ax=ax)
def min_vertices(self, vertices):
vertices, last_angle = [self.start], 0
for (x1, y1), (x2, y2) in self.as_paired_list:
current_angle = atan2(x1-x2, y1-y2)
if current_angle != last_angle:
vertices.append((x2, y2))
last_angle = current_angle
else:
continue
if vertices[-1] != self.dest:
vertices.append(self.dest)
return self.__class__(vertices=vertices)
View File
-117
View File
@@ -1,117 +0,0 @@
import multiprocessing as mp
import pickle
import shelve
from collections import defaultdict
from pathlib import Path
from tqdm import tqdm
from lib.objects.map import Map
class Generator:
possible_modes = ['one_patching']
def __init__(self, data_root, map_obj, binary=True):
self.binary: bool = binary
self.map: Map = map_obj
self.data_root = Path(data_root)
def generate_n_trajectories_m_alternatives(self, n, m, datafile_name, processes=0, **kwargs):
datafile_name = datafile_name if datafile_name.endswith('.pik') else f'{str(datafile_name)}.pik'
kwargs.update(n=m)
processes = processes if processes else mp.cpu_count() - 1
mutex = mp.Lock()
with mp.Pool(processes) as pool:
async_results = [pool.apply_async(self.generate_n_alternatives, kwds=kwargs) for _ in range(n)]
for result_obj in tqdm(async_results, total=n, desc='Producing trajectories with Alternatives'):
trajectory, alternatives, labels = result_obj.get()
mutex.acquire()
self.write_to_disk(datafile_name, trajectory, alternatives, labels)
mutex.release()
with shelve.open(str(self.data_root / datafile_name)) as f:
for datafile in self.data_root.glob(f'datafile_name*'):
with shelve.open(str(datafile)) as sub_f:
for key in sub_f.keys():
f[len(f)] = sub_f[key]
datafile.unlink()
pass
def generate_n_alternatives(self, n=None, datafile_name='', trajectory=None, is_sub_process=False,
mode='one_patching', equal_samples=True, binary_check=True):
assert n is not None, f'n is not allowed to be None but was: {n}'
assert mode in self.possible_modes, f'Parameter "mode" must be either {self.possible_modes}, but was {mode}.'
trajectory = trajectory if trajectory is not None else self.map.get_random_trajectory()
results = [self.map.generate_alternative(trajectory=trajectory, mode=mode) for _ in range(n)]
# label per homotopic class
homotopy_classes = defaultdict(list)
homotopy_classes[0].append(trajectory)
for i in range(len(results)):
alternative = results[i]
class_not_found = True
# check for homotopy class
for label in homotopy_classes.keys():
if self.map.are_homotopic(homotopy_classes[label][0], alternative):
homotopy_classes[label].append(alternative)
class_not_found = False
break
if class_not_found:
label = 1 if binary_check else len(homotopy_classes)
homotopy_classes[label].append(alternative)
# There should be as much homotopic samples as non-homotopic samples
if equal_samples:
homotopy_classes = self._remove_unequal(homotopy_classes)
if not homotopy_classes:
return None, None, None
# Compose lists of alternatives with labels
alternatives, labels = list(), list()
for key in homotopy_classes.keys():
alternatives.extend(homotopy_classes[key])
labels.extend([key] * len(homotopy_classes[key]))
if datafile_name:
if is_sub_process:
datafile_name = f'{str(datafile_name)}_{mp.current_process().pid}'
# Write to disk
self.write_to_disk(datafile_name, trajectory, alternatives, labels)
return trajectory, alternatives, labels
def write_to_disk(self, datafile_name, trajectory, alternatives, labels):
self.data_root.mkdir(exist_ok=True, parents=True)
with shelve.open(str(self.data_root / datafile_name), protocol=pickle.HIGHEST_PROTOCOL) as f:
new_key = len(f)
f[f'trajectory_{new_key}'] = dict(alternatives=alternatives,
trajectory=trajectory,
labels=labels)
if 'map' not in f:
f['map'] = dict(map=self.map, name=self.map.name)
@staticmethod
def _remove_unequal(hom_dict):
# We argue, that there will always be more non-homotopic routes than homotopic alternatives.
# TODO: Otherwise introduce a second condition / loop
hom_dict = hom_dict.copy()
if len(hom_dict[0]) <= 1:
return None
counter = len(hom_dict)
while sum([len(hom_dict[class_id]) for class_id in range(1, len(hom_dict))]) > len(hom_dict[0]):
if counter == 0:
counter = len(hom_dict)
if counter in hom_dict:
if len(hom_dict[counter]) == 0:
del hom_dict[counter]
else:
del hom_dict[counter][-1]
counter -= 1
return hom_dict
View File
-117
View File
@@ -1,117 +0,0 @@
from collections import defaultdict
import matplotlib.pyplot as plt
import matplotlib.cm as cmaps
from mpl_toolkits.axisartist.axes_grid import ImageGrid
from sklearn.cluster import Birch, DBSCAN, KMeans
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
import lib.variables as V
import numpy as np
class GeneratorVisualizer(object):
def __init__(self, outputs, k=8):
d = defaultdict(list)
for key in outputs.keys():
try:
d[key] = outputs[key][:k].cpu().numpy()
except AttributeError:
d[key] = outputs[key][:k]
except TypeError:
self.batch_nb = outputs[key]
for key in d.keys():
self.__setattr__(key, d[key])
# val_results = dict(discriminated_bce_loss, batch_nb, pred_label, label, generated_alternative)
self._map_width, self._map_height = self.input.shape[1], self.input.shape[2]
self.column_dict_list = self._build_column_dict_list()
self._cols = len(self.column_dict_list)
self._rows = len(self.column_dict_list[0])
self.colormap = cmaps.tab20
def _build_column_dict_list(self):
trajectories = []
alternatives = []
for idx in range(self.output.shape[0]):
image = (self.output[idx]).squeeze()
label = 'Homotopic' if self.labels[idx].item() == V.HOMOTOPIC else 'Alternative'
alternatives.append(dict(image=image, label=label))
for idx in range(len(alternatives)):
image = (self.input[idx]).squeeze()
label = 'original'
trajectories.append(dict(image=image, label=label))
return trajectories, alternatives
@staticmethod
def cluster_data(data):
cluster = Birch()
labels = cluster.fit_predict(data)
return labels
def draw_latent(self):
plt.close('all')
clusterer = KMeans(10)
try:
labels = clusterer.fit_predict(self.logvar)
except ValueError:
fig = plt.figure()
return fig
if self.z.shape[-1] > 2:
fig, axs = plt.subplots(ncols=2, nrows=1)
transformers = [TSNE(2), PCA(2)]
for idx, transformer in enumerate(transformers):
transformed = transformer.fit_transform(self.z)
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 self.z.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(self.z[:, 0]), np.max(self.z[:, 0]))
axs.set_ylim(np.min(self.z[:, 1]), np.max(self.z[:, 1]))
# ToDo: Insert Normalization
colored = self.colormap(labels)
plt.scatter(self.z[:, 0], self.z[:, 1], c=colored)
else:
raise NotImplementedError("Latent Dimensions can not be one-dimensional (yet).")
return fig
def draw_io_bundle(self):
width, height = self._cols * 5, self._rows * 5
additional_size = self._cols * V.PADDING + 3 * V.PADDING
# width = (self._map_width * self._cols) / V.DPI + additional_size
# height = (self._map_height * self._rows) / V.DPI + additional_size
fig = plt.figure(figsize=(width, height), dpi=V.DPI)
grid = ImageGrid(fig, 111, # similar to subplot(111)
nrows_ncols=(self._rows, self._cols),
axes_pad=V.PADDING, # pad between axes in inch.
)
for idx in range(len(grid.axes_all)):
row, col = divmod(idx, len(self.column_dict_list))
if self.column_dict_list[col][row] is not None:
current_image = self.column_dict_list[col][row]['image']
current_label = self.column_dict_list[col][row]['label']
grid[idx].imshow(current_image)
grid[idx].title.set_text(current_label)
else:
continue
fig.cbar_mode = 'single'
fig.tight_layout()
return fig