Source code for inet.models.architectures.classifier

from typing import Optional

import keras_tuner
import sklearn.metrics
from tensorflow import keras

from inet.data.constants import ModelType
from inet.data.visualization import plot_confusion_matrix
from inet.models.architectures.base_model import Backbone, SingleTaskModel
from inet.models.data_structures import ModelArchitecture
from inet.models.hyper_parameter_optimization import FrozenBlockConf
from scripts.constants import CLASS_MAP


[docs]class Classifier(SingleTaskModel): """ Class label prediction model Example: >>> from tensorflow.keras.applications.vgg16 import VGG16 >>> backbone = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3)) >>> clf = Classifier(backbone, 128, 5, True, 'My-Classifier', 0.125, 0.5, 64, FrozenBlockConf.TRAIN_ALL.value) >>> clf.load_weights('my_weights.h5') >>> clf.predict(some_input) """ ## Fixed type of model model_type = ModelType.CLASSIFICATION.value def __init__(self, backbone: Backbone, dense_neurons: int, num_classes: int = 5, include_pooling: bool = False, name: Optional[str] = None, regularization_factor: float = 1e-3, dropout_factor: float = 0.2, batch_size: int = 32, frozen_backbone_blocks: FrozenBlockConf = FrozenBlockConf.TRAIN_NONE.value): """ :param backbone: backbone model :param num_classes: number of output neurons :param dense_neurons: number dense neurons for FC layer :param include_pooling: use pooling prior to FC layer :param name: model name :param regularization_factor: factor for L2 regularization in output layer :param dropout_factor: factor of dropout that's applied in front of the FC layer :param batch_size: batch size of the dataset :param frozen_backbone_blocks: allows to freeze specific layers of a model, see `FrozenBlockConf` """ FrozenBlockConf(frozen_backbone_blocks).process(backbone) super().__init__(backbone, num_classes=num_classes, output_activation='softmax', dense_neurons=dense_neurons, include_pooling=include_pooling, name=name, regularization_factor=regularization_factor, dropout_factor=dropout_factor) ## Number neurons in FC layer self.dense_neurons = dense_neurons ## Batch size used to train the model self.batch_size = batch_size
[docs] def compile(self, learning_rate: float = 1e-6, loss='categorical_crossentropy', metrics=None, *args, **kwargs): """ Extended `keras.Model.compile`. Adds default `Adam` optimizer and Accuracy metric :param learning_rate: the learning rate to train with :param loss: the loss function to optimize :param metrics: additional metrics to calculate during training :param args: will be passed as args to parent implementation :param kwargs: will be passed as kwargs to parent implementation :return: """ if metrics is None: metrics = [] ## arguments used while calling `compile` self.compile_args = dict(loss=loss, learning_rate=learning_rate, metrics=metrics, args=args, kwargs=kwargs) super().compile( *args, loss=loss, optimizer=keras.optimizers.Adam(learning_rate=learning_rate), metrics=['accuracy', *metrics], **kwargs )
[docs] @staticmethod def evaluate_predictions(predictions, labels, features, render_samples=False) -> None: """ Evaluates predictions done by a classification model. Computes: - Accuracy - F1-Score :param predictions: the predictions performed by the model to evaluate :param labels: ground truth labels for the predictions :param features: input features used to perform predictions :param render_samples: if `True` renders confusion matrix for predictions :return: """ if sum(predictions.shape) > len(predictions): pred = predictions.argmax(axis=1) else: pred = predictions if sum(labels.shape) > len(labels): lab = labels.argmax(axis=1) else: lab = labels print( '=' * 35, '\n\tAccuracy:\t', sklearn.metrics.accuracy_score(lab, pred, normalize=True), '\n\tf1 score:\t', sklearn.metrics.f1_score(lab, pred, average='macro'), ) if render_samples: plot_confusion_matrix(pred, lab, list(CLASS_MAP.keys()), normalize=True)
[docs]class ClassifierHyperModel(keras_tuner.HyperModel): """ HPO wrapper for Classifier model. Used Hyper parameters (HPs): - Dropout factor `alpha`: [1e-4, 5e-4, 1e-3, 5e-3, 1e-2] - Learning rate `learning_rate`: [1e-4, 5e-4, 1e-3, 5e-3, 1e-2] - Number frozen layers `frozen_layers`: [TRAIN_ALL, TRAIN_HALF, TRAIN_NONE] Example: >>> import keras_tuner as kt >>> hpo_model = ClassifierHyperModel() >>> kt.BayesianOptimization( ... hpo_model, ... objective=kt.Objective('val_accuracy', 'max'), ... max_trials=36, ... directory=f'./model-selection/my-model/', ... project_name='proj_name', ... seed=42, ... overwrite=False, ... num_initial_points=12 ... ) >>> tuner.search( ... train_set=train_set.unbatch(), ... validation_set=validation_set.unbatch(), ... monitoring_val='val_accuracy', ... epochs=50, ... ) """ ## model configuration to use when creating a new model for HPO model_data: Optional[ModelArchitecture] = None ## model weights used weights: str = None
[docs] def build(self, hp): """ Builds new classification model for HPO :param hp: current state of HPs :return: model for next iteration in HPO """ hp_alpha = hp.Choice('alpha', [1e-4, 5e-4, 1e-3, 5e-3, 1e-2]) hp_lr = hp.Choice('learning_rate', [1e-4, 5e-4, 1e-3, 5e-3, 1e-2]) hp_frozen_blocks = hp.Choice( 'frozen_blocks', FrozenBlockConf.choices() ) backbone_clone = keras.models.clone_model(self.model_data.backbone) backbone_clone.set_weights(self.model_data.backbone.get_weights()) model = Classifier( backbone_clone, dense_neurons=128, include_pooling=True, name=self.model_data.name, regularization_factor=hp_alpha, dropout_factor=0.5, batch_size=32, frozen_backbone_blocks=hp_frozen_blocks ) if self.weights: model.load_weights(self.weights, by_name=True) model.compile(learning_rate=hp_lr) return model