{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"**Herzlich Willkommen !! **\n\nI welcome you guys to my Deep learning tutorial. This tutorial is based for those guys who are\nbeginners or moderators and want to learn how to write modular code which is efficient. We will be using the Dog vs Cat dataset to do binary classification using Vanilla CNN.\n\nFull code is available [hier](https://github.com/Gnopal1132/DogsVsCat).\nI hope it helps you guys if you do hit star at my blog ;)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"![Img](https://user-images.githubusercontent.com/51056214/131360401-03ff9c24-a589-4113-93e0-d37037d20692.png)","metadata":{}},{"cell_type":"markdown","source":"Every code specially Deep learning code must be divided into basic modules which are:\n    \n* **Configuration module** : This module is the core module of the whole model. It defines the full ingredient that will be used in the model like: hyperparameter values,dataset path,..\n\n* **Generator Module** : As you guys have already heard that how important it is to preprocess the data !!(If not boy you are in for a suprise) so the data generators should be given a seperate module. This module will generate the data for our model\n\n* **Preprocessing Module** : This module contains all the preprocessing that is needed to do like scaling,augmentations etc\n\n* **Model Module** : This module is defined for creating the different models like in our example we will build COnvolutional Neural Network, we could define VGG16 or Fully convolutional module or whatever but it should be given a sperate module\n\n* **Trainer Module** : This module is the one that trains the model with the configurations defined in the configuration files and saves the result.\n\n* **Predictor Module** : This module predicts the output on test set\n\n* **Main Module** : This module which connects all the model together this is the main.py","metadata":{}},{"cell_type":"markdown","source":"We will see every component in detail. \n\n**OK Lets jump right into it ;)**","metadata":{}},{"cell_type":"markdown","source":"**1. Configuration module** : This is the root file. Imagine u need to specify the batch_size parameter\n to be used by the model. This parameter might be tuned at various different place so to get your world\nnot messy we will define all the configuration at one place and use it from the configuration file.","metadata":{}},{"cell_type":"code","source":"---\ndataset:\n  path: '/home/Dokumente/DogsVsCat/Datasets/dogs-vs-cats'\n  train_size: 0.8\n  size_x: 224\n  size_y: 224\n  classes: 2\n  channel: 3\n  path_image: '/home/Dokumente/DogsVsCat/Generated/dataset.png'\n  predict_image: '/home/Dokumente/DogsVsCat/Generated/prediction.png'\ntrain:\n  optimizer: NADAM       # Possible values: ADAM,NADAM,SGD,SGD_MOMENTUM,RMS_PROP,ADA_GRAD\n  learning_rate: 0.001\n  batch_size: 32\n  use_multiprocessing: True\n  num_workers: -1\n  epochs: 1\n  weight_initialization:\n    use_pretrained: False\n    restore_from: '/home/Dokumente/DogsVsCat/Generated/last.h5'\n  output_weight: '/home/Dokumente/DogsVsCat/Generated/final.h5'\nnetwork:\n  graph_path: '/home/Dokumente/DogsVsCat/Generated/graph.json'\n  model_img: '//home/Dokumente/DogsVsCat/Generated/model.png'\ndata_aug:\n  use_aug: False\ncallbacks:\n  earlystopping:\n    use_early_stop: True\n    patience: 10\n    monitor: 'val_loss'\n  checkpoint:\n    checkpoint_last:\n      enabled: True\n      monitor: 'val_loss'\n      out_last: '/home/Dokumente/DogsVsCat/Generated/last.h5'\n    checkpoint_best:\n      enabled: True\n      monitor: 'val_loss'\n      out_last: '/home/Dokumente/DogsVsCat/Generated/best.h5'\n  tensorboard:\n    enabled: True\n    log_dir: '/home/Dokumente/DogsVsCat/Generated/logs'\n  scheduler:\n    onecycle:\n      to_use : True\n      max_rate: 0.05\n    exponential_scheduler:\n      to_use : False\n      params: 10","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"This is how a configuration file looks like. Lets Dig it down Shall we ;)\n\n1. This configuration file is defined as yaml file. (i.e. cofig.yaml)\n2. Its very similar to json file or to make life simple its very similar to a simple python\n   dictionary.\n    \nfor example:\n    config['dataset']['path'] gives you the path where the dataset is located.\n    \nTo access any parameter just follow the tree structure path.\nfor example: If I want to access the patience parameter of earlystopping I will go as follows:\n        config['callbacks']['earlystopping']['patience'] = 10\n\nSee its Easy Peasy ;)\n\nLets see what we will be using:\n    1. Dataset: This is pretty clear. It simply defines the path of dataset,size of image we \n        will be using. To look at what our dataset looks like and prediction looks like we\n        define two different paths to store image of our dataset and predictions given by\n        path_image and predict_image.\n    2.Train: This defines the working configuration. Like which optimizer to use,learning rate,\n        etc. Weight initialization is used for defining if I want to use last trained weights\n        to initialize our model or to start from scratch.\n        \n        if config['train']['weight_initialization']['use_pretrained']:\n            model.load_weight(config['train']['weight_initialization']['restore_from'])\n            \n        YOU KNOW WHAT I MEAN RIGHT ?\n        \n        Likewise output_weight stores the final weights.\n        \n    3. Network: This stores our model graph(What is that ? we will see) and our model image\n    \n    4.data_aug: Whether to use data augmentation. We will define several option\n    \n    5.Callbacks: Very important. Callbacks is something that is executed (before/after) every\n        (epoch/batch) to do something. For example we define earlystopping checkpoint it will\n        monitor validation loss if it doesnt improve for patience number of epochs we will stop.\n        \n        ModelCheckpoint to save the (best/last) model configuration till yet.\n        onecycle scheduler or exponential scheduler : to change the value of learning rate after\n            every (batch/epoch). Onecycle was introduced by Leslie Smith in 2017 paper. Very\n            popular and powerful. We will see ;) \n        \n        Wether to use them or not is defined by the switch parameter 'to_use'.","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"2. **Generator Module** : I just cant say how important it is. This module will give the model a batch of data to process. There are different ways of defining it like:\n* **Tensorflow Data API**: One of my favorites but I wont introduce this topic here. \n* **Keras Data Generator**: Also one of my favorites easy and beautiful.\n","metadata":{"execution":{"iopub.status.busy":"2021-08-31T07:09:19.574295Z","iopub.execute_input":"2021-08-31T07:09:19.574746Z","iopub.status.idle":"2021-08-31T07:09:19.588178Z","shell.execute_reply.started":"2021-08-31T07:09:19.574637Z","shell.execute_reply":"2021-08-31T07:09:19.586762Z"}}},{"cell_type":"markdown","source":"Lets Have a Look at keras Data Generator.","metadata":{}},{"cell_type":"code","source":"import sys\nimport os\nimport tensorflow as tf\nimport numpy as np\nfrom Preprocessing.preprocessing import read_image,read_image_test\n#sys.path.append(os.path.dirname(os.path.abspath(os.curdir)))\n\n\nclass Datagenerator(tf.keras.utils.Sequence):\n    def __init__(self, config, dataset, shuffle=True, is_train=True):\n        self.config = config\n        self.dataset = dataset\n        self.is_train = is_train\n        self.len_dataset = len(dataset)\n        self.indices = np.arange(self.len_dataset)\n        self.shuffle = shuffle\n        self.classes = config['dataset']['classes']\n        self.aug = config['data_aug']['use_aug']\n        self.x_size = config['dataset']['size_x']\n        self.y_size = config['dataset']['size_y']\n        self.channels = config['dataset']['channel']\n        self.batch_size = config['train']['batch_size']\n\n        if self.shuffle:\n            self.on_epoch_end()\n\n    def on_epoch_end(self):\n        if self.shuffle:\n            np.random.shuffle(self.indices)\n\n    def __len__(self):   # Returns number of batches per epoch\n        return int(np.floor(self.len_dataset/self.batch_size))\n\n    def __getitem__(self, index):  # Generated batch for given index\n\n        indices = self.indices[index*self.batch_size:(index+1)*self.batch_size]\n        id = [self.dataset[k] for k in indices]\n        if self.is_train:\n            x, y = self.__data_generation(id)\n            return x, y\n        else:\n            x = self.__data_generation(id)\n            return x\n\n    def __data_generation(self, ids):\n\n        x_batch = []\n        y_batch = []\n        if self.is_train:\n            for instance in ids:\n                image, label = read_image(instance, size=(self.x_size, self.y_size), to_aug=self.aug)\n                x_batch.append(image)\n                y_batch.append(label)\n            x_batch = np.asarray(x_batch, dtype=np.float32)\n            y_batch = np.asarray(y_batch, dtype=np.float32)\n            return x_batch, y_batch\n        else:\n            batch = []\n            for img in ids:\n                image = read_image_test(img, size=(self.x_size, self.y_size))\n                batch.append(image)\n            return np.asarray(batch)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"I know it seems Big but its small actually its tiny :hahah\n\nLets break it down shall we:\n    \n    1. To define keras data generator. You have to subclass(inherit) the tf.keras.utils.Sequence\n    class.\n    \n    2. def __init__(self, config, dataset, shuffle=True, is_train=True): Is our constructor,\n        config is our configuration file, dataset is (train/val/test) set , shuffle means\n        to whether shuffle the data: For test set it will be false. is_train: Whether the \n        dataset is (train/val) or test set. if test set then it will be false and for rest\n        it will be true.\n        \n    3. Inside the constructor everything should be clear. You can see here how we are using the\n    configuration file. ALWAYS USE CONFIGURATION FILE. NEVER DO self.batch_size = 32. THATS\n    BAD PRACTISE ALWAYS USE CONFIGURATION FILE. BECAUSE LATER IF YOU WANT TO CHANGE BATCH SIZE\n    YOU HAVE TO ONLY CHANGE CONFIGURATION FILE AND REST WILL AUTOMATICALLY BE CHANGED BECUASE\n    THEY ALL BE USING CONFIGURATION FILE.\n    \n    4. on_epoch_end(): This function is invoked once in constructor for (train/val) not for test.\n        This function will automatically be invoked after every epoch ends. In this we will simply\n        shuffle the dataset. Note: np.random.shuffle() to shuffle dataset inplace it doesnt\n        return anything.\n    \n    5. def __len__(self):   Returns number of batches per epoch\n        \n    6.def __getitem__(self, index): It Generates a batch for given index. If you see here the\n        indices actually stores the indices of the instances forming a batch and id will store\n        those datapoints(here actually dataset is the addresses of the train/test/val set) and\n        if its train/val: we have to return the instance and its label.\n        if test set: we have to return only the instances.\n        \n    7. def __data_generation(self, ids): VERY CORE MODULE. This does the core part. It takes the\n        ids and returns the batch. The inside code is very easy. You will notice that it calls\n        read_image function for (train/val) set and read_image_test for test set. We will see this\n        function just in  a while. But for now just understand that it will read the image,apply\n        scaling, augmentation if needed and returns the file image and label. For test image we\n        simply read it scale it but no data augmentation","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"3. **Preprocessing Module:** Now lets see how this read_image and read_image_test us defined.","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport cv2\nimport os\nfrom pathlib import Path\nimport albumentations as A\n\n\ndef read_image(path, size, to_aug=False):\n    path = Path(path)\n    if not path.exists():\n        raise Exception(\"Image Not Found\")\n    else:\n        image = cv2.imread(str(path))\n        # By default cv2 reads in Blue,Green,Red Format(BGR) to convert in RGB\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) \n        image = cv2.resize(image, size)\n        if to_aug:\n            image = Augment_me(image)\n        image = scaled_img(image)\n        label = 0 if 'cat' in str(os.path.basename(path)) else 1\n        return image, label\n\n\ndef read_image_test(path, size):\n    path = Path(path)\n    if not path.exists():\n        raise Exception(\"Image Not Found\")\n    else:\n        image = cv2.imread(str(path))\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # By default cv2 reads in Blue,Green,Red Format to convert in RGB\n        image = cv2.resize(image, size)\n        image = scaled_img(image)\n        return np.asarray(image)\n\n\ndef scaled_img(image):\n    image = np.asarray(image)\n    mean = np.mean(image, axis=0, keepdims=True)\n    return (image - mean)   # For images we dont in practise divide by std\n\n\ndef Augment_me(image):\n    transform = A.Compose([\n        A.HorizontalFlip(p=0.5),\n        A.RandomBrightnessContrast(p=0.5),\n        A.RandomRotate90(p=0.5)\n    ])\n    return transform(image=image)[\"image\"]","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I think there is nothing to explain here everything is very simple and self explanatory.\n\nSome few Points: \n* [Albumentation](https://albumentations.ai/): This is one of the standard API for data augmentation it has a lot of options I used just few of them. A.compose: It defines a sequential stack of augmentations, here I used HorizontalFlip,RandomBrightness.. p = probability that the augmentation will take place. SIMPLE !! Nothing to explain here I guess! If anything is not clear please write in comment section.\n       \n* Note that when we will pass the dataset in Generator it will be simply the addresses of\n  the instances in train/test/val set. So we read them bring them into right format. for\n  labels: the address contains the name of the image as well like: ./dogsvscat/cat123.png\n  So when I write **os.path.basename()** it will return the last path of directory\n  i.e.cat123.png and after converting to string I find the right label. 0: Cat,1:Dog.\n    \n    \n * And we see whether to apply augmentation or not\n    ","metadata":{}},{"cell_type":"markdown","source":"At this Point Our data module is Complete !!! Man time for small **Coffee** ;)","metadata":{}},{"cell_type":"markdown","source":"OK After coffee lets move further to our \n\n**4. Model Module**","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom functools import partial\n\nclass Models:\n    def __init__(self, config):\n        self.config = config\n        self.x_size = config['dataset']['size_x']\n        self.y_size = config['dataset']['size_y']\n        self.channels = config['dataset']['channel']\n        self.model_img = config['network']['model_img']\n        self.learning_rate = config['train']['learning_rate']\n        self.optimizer = config['train']['optimizer']\n\n    def convolution_scratch(self, save_model=False):\n        tf.keras.backend.clear_session()\n        Default = partial(tf.keras.layers.Conv2D, kernel_size=3, strides=1, padding='same', activation='relu')\n        model = tf.keras.models.Sequential([\n            Default(filters=32, kernel_size=7, strides=2, input_shape=[self.x_size, self.y_size, self.channels]),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.MaxPool2D(pool_size=2),\n\n            Default(filters=64),\n            Default(filters=64),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.25),\n            tf.keras.layers.MaxPool2D(pool_size=2),\n\n            Default(filters=128),\n            Default(filters=128),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.25),\n            tf.keras.layers.MaxPool2D(pool_size=2),\n\n            Default(filters=256),\n            Default(filters=256),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.25),\n            tf.keras.layers.MaxPool2D(pool_size=2),\n\n            Default(filters=512),\n            Default(filters=512),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.4),\n            tf.keras.layers.MaxPool2D(pool_size=2),\n\n            tf.keras.layers.Flatten(),\n            tf.keras.layers.Dense(300, activation='relu', use_bias=False),\n            tf.keras.layers.Dropout(0.5),\n            tf.keras.layers.Dense(1, activation='sigmoid')  # Becuase its Binary CLassification\n        ])\n\n        if save_model:\n            tf.keras.utils.plot_model(model, show_shapes=True, show_dtype=True, show_layer_names=True, to_file=self.model_img)\n\n        if self.optimizer == 'ADAM':\n            optimizer = tf.keras.optimizers.Adam(learning_rate=self.learning_rate, beta_1=0.9, beta_2=0.999)\n        elif self.optimizer == 'NADAM':\n            optimizer = tf.keras.optimizers.Nadam(learning_rate=self.learning_rate, beta_1=0.9, beta_2=0.999)\n        elif self.optimizer == 'SGD':\n            optimizer = tf.keras.optimizers.SGD(learning_rate=self.learning_rate)\n        elif self.optimizer == 'SGD_MOMENTUM':\n            optimizer = tf.keras.optimizers.SGD(learning_rate=self.learning_rate, momentum=0.9)\n        elif self.optimizer == 'RMS_PROP':\n            optimizer = tf.keras.optimizers.RMSprop(learning_rate=self.learning_rate, rho=0.9)\n        elif self.optimizer == 'ADA_GRAD':\n            optimizer = tf.keras.optimizers.Adagrad(learning_rate=self.learning_rate)\n        else:\n            raise Exception('Optimizer properly not defined in Configuration!!')\n\n        model.compile(loss=tf.keras.losses.binary_crossentropy, optimizer=optimizer, metrics=['accuracy'])\n        return model","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"I think its pretty clear!! Nothing to explain much but still lets me try:\n    1. Model class simply takes the configuration file. And Intialize the things we need.\n    2. def convolution_scratch: Defines our main model. partial is a simple tool that\n        will make your life easy or else you have to write whole line. Just define the the \n        basic function as partial with default parameters to use and later just change the \n        parameter and rest will automatically be taken from default param.\n        \n        We used BatchNormalization() and Dropout\n        \n    3. You can also define another function in this class like def VGG16_Arch() and define\n    your own architecture. Here is where you can show what you got!! haha\n    \n    4. Then we plot the model graphically and store in directory if needed.\n    5. We then define the optimizer according to our configuration file\n    6. And we compile the model.\n    \nI GUESS YOU CAN SEE HOW IMPORTANT THIS CONFIGURATION FILE IS.","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"5. **Trainer Module**: This is the module where we will train our module define the callbacks ","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport os\n\nK = tf.keras.backend\n\n\nclass one_cycle(tf.keras.callbacks.Callback):\n    def __init__(self, iterations, max_rate, start_rate=None, last_iteration=None, last_rate=None):\n        super().__init__()\n        self.iterations = iterations\n        self.max_rate = max_rate\n        self.start_rate = start_rate or max_rate / 10\n        self.last_iteration = last_iteration or iterations // 10 + 1\n        self.half_iteration = (self.iterations - self.last_iteration) // 2\n        self.last_rate = last_rate or max_rate / 1000\n        self.iteration = 0\n        self.loss = []\n        self.rate = []\n\n    def __interpolate(self, start_iteration, final_iteration, start_rate, final_rate):\n        return ((final_rate - start_rate)*(self.iteration - start_iteration))/((final_iteration-start_iteration) + start_rate)\n\n    def on_batch_begin(self, batch, logs=None):\n        if self.iteration < self.half_iteration:\n            rate = self.__interpolate(0, self.half_iteration,self.start_rate,self.max_rate)\n        elif self.iteration < 2*self.half_iteration:\n            rate = self.__interpolate(self.half_iteration, 2*self.half_iteration, self.max_rate, self.start_rate)\n        else:\n            rate = self.__interpolate(2*self.half_iteration, self.iterations,self.start_rate, self.last_rate)\n            rate = max(rate, self.last_rate)\n        self.iteration += 1\n        K.set_value(self.model.optimizer.lr, rate)\n\n    def on_epoch_end(self, epoch, logs=None):\n        self.loss.append(logs['loss'])\n        self.rate.append(K.get_value(self.model.optimizer.lr))\n\n\nclass exponential_scheduler(tf.keras.callbacks.Callback):\n    def __init__(self, s=40000):\n        super().__init__()\n        self.s = s\n        self.loss = []\n        self.rate = []\n\n    def on_batch_begin(self, batch, logs=None):\n        lr = K.get_value(self.model.optimizer.lr)\n        K.set_value(self.model.optimizer.lr, lr*0.001*(1/self.s))\n\n    def on_epoch_end(self, epoch, logs=None):\n        self.loss.append(logs['loss'])\n        self.rate.append(K.get_value(self.model.optimizer.lr))\n\n\nclass Result_callback(tf.keras.callbacks.Callback):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.loss_epoch = []\n        self.val_loss_epoch = []\n        self.rate = []\n        self.epoch = []\n\n    def on_epoch_end(self, epoch, logs=None):\n        self.rate.append(K.get_value(self.model.optimizer.lr))\n        self.loss_epoch.append(logs['loss'])\n        self.val_loss_epoch.append((logs['val_loss']))\n        self.epoch.append(epoch)\n\n\nclass Trainer:\n    def __init__(self, config, model, train_loader, val_loader):\n        self.config = config\n        self.train_loader = train_loader\n        self.val_loader = val_loader\n        self.model = model\n        self.batch_size = self.config['train']['batch_size']\n        self.epochs = self.config['train']['epochs']\n        self.results = Result_callback()\n        self.callbacks = self.get_callbacks().append(self.results)\n\n    def get_id(self, root_dir):\n        import time\n        id_ = time.strftime('run_id_%Y_%m_%D_%H_%M_%S')\n        return os.path.join(root_dir, id_)\n\n    def get_callbacks(self):\n\n        callbacks = []\n        if self.config['callbacks']['earlystopping']['use_early_stop']:\n            patience = self.config['callbacks']['earlystopping']['patience']\n            monitor = self.config['callbacks']['earlystopping']['monitor']\n            early_stop = tf.keras.callbacks.EarlyStopping(patience=patience, monitor=monitor)\n            callbacks.append(early_stop)\n\n        if self.config['callbacks']['checkpoint']['checkpoint_last']['enabled']:\n            monitor = self.config['callbacks']['checkpoint']['checkpoint_last']['monitor']\n            file_path = self.config['callbacks']['checkpoint']['checkpoint_last']['out_last']\n            checkpoint_last = tf.keras.callbacks.ModelCheckpoint(monitor=monitor,\n                                                                 save_best_only=False,\n                                                                 save_weights_only=True,\n                                                                 filepath=file_path)\n            callbacks.append(checkpoint_last)\n\n        if self.config['callbacks']['checkpoint']['checkpoint_best']['enabled']:\n            monitor = self.config['callbacks']['checkpoint']['checkpoint_best']['monitor']\n            file_path = self.config['callbacks']['checkpoint']['checkpoint_best']['out_last']\n            checkpoint_best = tf.keras.callbacks.ModelCheckpoint(monitor=monitor,\n                                                                 save_best_only=True,\n                                                                 save_weights_only=True,\n                                                                 filepath=file_path)\n            callbacks.append(checkpoint_best)\n\n        if self.config['callbacks']['tensorboard']['enabled']:\n            directory = self.get_id(self.config['callbacks']['tensorboard']['log_dir'])\n            board = tf.keras.callbacks.TensorBoard(directory, write_graph=True, write_images=True)\n            callbacks.append(board)\n\n        if self.config['callbacks']['scheduler']['onecycle']['to_use']:\n            iterations = self.epochs * self.batch_size\n            max_rate = self.config['callbacks']['scheduler']['onecycle']['max_rate']\n            one_cycle_callback = one_cycle(iterations=iterations, max_rate=max_rate)\n            callbacks.append(one_cycle_callback)\n\n        if self.config['callbacks']['scheduler']['exponential_scheduler']['to_use']:\n            s = self.config['callbacks']['scheduler']['exponential_scheduler']['params']\n            exponential_scheduler_callback = exponential_scheduler(s=s)\n            callbacks.append(exponential_scheduler_callback)\n        return callbacks\n\n    def train(self):\n        # Lets now train this bad boy\n\n        if self.config['train']['weight_initialization']['use_pretrained']:\n            reload_from = self.config['train']['weight_initialization']['restore_from']\n            print(f\"Restoring model weights from: {reload_from}\")\n            self.model.load_weights(reload_from)\n        else:\n            print(f\"Saving graph in: {self.config['network']['graph_path']}\")\n            self.save_graph(self.model, self.config['network']['graph_path'])\n\n        use_multiprocessing = self.config[\"train\"]['use_multiprocessing']\n        num_workers = self.config[\"train\"][\"num_workers\"]\n\n        self.model.fit_generator(generator=self.train_loader, validation_data=self.val_loader,\n                                 callbacks=self.callbacks, epochs=self.epochs, use_multiprocessing=use_multiprocessing,\n                                 workers=num_workers, shuffle=False, max_queue_size=10, verbose=1)\n\n        print(f\"Saving Weights in {self.config['train']['output_weight']}\")\n        self.model.save_weights(self.config['train']['output_weight'])\n\n    def save_graph(self, model, path):\n        model_json = model.to_json()\n        with open(path, \"w\") as json_file:\n            json_file.write(model_json)\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"OK TO BE HONEST THIS IS THE PART I HAVE TO EXPLAIN MY ASS OFF!!","metadata":{}},{"cell_type":"markdown","source":"Lets Break it down:\n* **Callbacks**: There are some callbacks that you can use directly like Earlystop and Checkpoints. But tensorflow also have the option to define custom callbacks which I used here for Onecycle,Exponential Schedule and ResultCallback. How to define custom [callbacks](https://www.tensorflow.org/guide/keras/custom_callback) ? Ok let me break it down. \n  1. For creating custom callback you need to subclass tf.keras.callbacks.Callback class.\n  2. Now you have predefined function like on_(train|test|predict)_begin(self, logs=None),\n     on_(train|test|predict)_end(self, logs=None),on_(train|test|predict)_batch_begin(self, batch, logs=None),etc... (See the documentation above link) Now every function is either called at the (beginning/end) of (batch/epoch/train/test/predict) this depends on which you are using. All we gotta do is manipulate the function accordingly.\n     \n*      Result Callback: I stored the learning rate,epoch,loss and validation loss after every epoch. It will be used for analysis.\n*     Exponential Scheduler: new_lr = prev_lr * 0.001**(1/s) the learning rate changes by this formula at beginning of every batch and I am also storing the results at end of one epoch.\n*     Onecycle: This scheduler increases linearly the learning rate from lr0 to lr1 during first half of iteration and then decreases the learning rate from lr1 to lr0 during another half still linearly and during the last epochs it reduces the learning rate by several magnitude. max_rate defines the rate of increasing and decreasing the lr. Simply read the function properly you will understand.\n\n**get_callbacks()** function returns the list of callbacks to use by reading the configuration file. Note model checkpoint stores the last checkpoint and the best checkpoint\nfound till now.\n     ","metadata":{}},{"cell_type":"markdown","source":"**train()** module first checks if I want to use previous trained weights to initialize the\nmodel or start from scratch. If from scratch it first saves the model structure/graph in json\nfile first and then start the training. The model.fit_generator is similar to model.[fit](https://www.tensorflow.org/api_docs/python/tf/keras/Model) the only difference is we are using generator module.And then we save the final weights","metadata":{}},{"cell_type":"markdown","source":"6. **Predictor Module**: This module is the one that predicts the test set. It first load the final model graph and then load the best found weights to that model and does the prediction.","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom Preprocessing.preprocessing import read_image_test\nfrom Data_Generator.generator import Datagenerator\n\n\nclass Predictor:\n    def __init__(self, config, test_path):\n        self.config = config\n        self.graph_path = self.config['network']['graph_path']\n        self.model_weight = self.config['train']['output_weight']\n        self.x_size = config['dataset']['size_x']\n        self.y_size = config['dataset']['size_y']\n        self.model = self.load_model()\n        self.test_loader = Datagenerator(self.config, test_path, shuffle=False, is_train=False)\n\n    def load_model(self):\n        json_file = open(self.graph_path, 'r')\n        load_json = json_file.read()\n        json_file.close()\n\n        model = tf.keras.models.model_from_json(load_json)\n        model.load_weights(self.model_weight)\n        return model\n\n    def predict(self):\n        class_predict = []\n        predictions = self.model.predict(self.test_loader, batch_size=None)\n        for prediction in predictions:\n            if prediction >= 0.5:\n                class_predict.append('Dog')\n            else:\n                class_predict.append('Cat')\n        return class_predict","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"Its pretty clear. We first define a generator for test set and call model.predict() on that\ngenerator it will return the probability for every instance and if the probability is greater\nthan 50% its a Dog else Cat.\n\nSee how we first loaded the model graph from json we saved earlier and then we loaded the\nfinal weights to do prediction","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"7. **Main Module**: This module is the main file that connects everthing","metadata":{}},{"cell_type":"code","source":"import os\nimport warnings\nfrom Prediction import predictor\nfrom Models.vision_model import Models\nfrom Data_Generator.generator import Datagenerator\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport yaml\nimport cv2\nfrom pathlib import Path\nfrom Trainer import trainer\n\n\n# This function simply takes the train/val/test set path and returns complete path to every instance in the set\ndef read_dataset(source_path, shuffle=True):\n    files = os.listdir(source_path)  # Will read the directory content and return the name of files present inside\n    preprocessed_paths = []\n    for instance in files:\n        preprocessed_paths.append(os.path.join(source_path, instance)) #Complete path\n    preprocessed_paths = np.asarray(preprocessed_paths)\n    if shuffle:\n        np.random.shuffle(preprocessed_paths)  # It does inplace\n    return preprocessed_paths\n\n# To plot the predicted images 50 of them\ndef show_some_image_prediction(images, labels, path_to_save=None):\n\n    n_rows = 10\n    n_cols = 5\n\n    plt.figure(figsize=(20, 20))\n    for row in range(n_rows):\n        for col in range(n_cols):\n            idx = n_cols * row + col\n            plt.subplot(n_rows, n_cols, idx + 1)\n            img = cv2.imread(images[idx])\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            label = labels[idx]\n            plt.imshow(img)\n            plt.title(label, fontsize=12)\n            plt.axis('off')\n    plt.subplots_adjust(wspace=0.5, hspace=0.5)\n    if path_to_save is not None:\n        plt.savefig(path_to_save)\n    plt.show()\n\n# To plot the dataset images\ndef show_some_images(list_images, path_to_save=None):\n    n_rows = 5\n    n_cols = 5\n    plt.figure(figsize=(10, 8))\n    for row in range(n_rows):\n        for col in range(n_cols):\n            idx = n_cols*row + col\n            plt.subplot(n_rows, n_cols, idx+1)\n            img = cv2.imread(list_images[idx])\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            label = 'Cat' if 'cat' in str(os.path.basename(list_images[idx])) else 'Dog'\n            plt.imshow(img)\n            plt.title(label, fontsize=12)\n            plt.axis('off')\n    plt.subplots_adjust(wspace=0.5, hspace=0.5)\n    if path_to_save is not None:\n        plt.savefig(path_to_save)\n    plt.show()\n\n    \n    \n# Start from Here\n\nwarnings.filterwarnings('ignore', category=UserWarning)\nwarnings.filterwarnings('ignore', category=DeprecationWarning)\n\n\n# Loading the COnfiguration FIle\n\nconfig_file = Path(os.path.join(os.curdir, \"Configuration\", \"config\"))\nif not config_file.exists():\n    raise Exception(\"configuration missing!!\")\nelse:\n    with open(config_file) as f:\n        config_file = yaml.load(f)\n\n        \n# Path to training set and TEst set\ntrain_path = os.path.join(config_file[\"dataset\"][\"path\"], 'train')\ntest_path = os.path.join(config_file[\"dataset\"][\"path\"], 'test1')\n\n\n# We read the train set to get the entire path to every instance in a list and we split\n# to train and val set for test set we dont do any shuffle\ndata = read_dataset(train_path)\ntrain_size = int(len(data)*config_file[\"dataset\"][\"train_size\"])\ntrain = data[:train_size]\nval = data[train_size:]\ntest = read_dataset(test_path, shuffle=False)\n\nshow_some_images(train, config_file['dataset']['path_image'])  \n#To see some dataset images and storing the image to directory\n\n\n# We create the Generator for train and val set. The generator will give a batch of preprocessed\n# data to the model to train\ntrain_loader = Datagenerator(config_file, train, shuffle=True)\nval_loader = Datagenerator(config_file, val, shuffle=True)\n\n# We initialize the model by sending the configuration \nbaseline_model = Models(config=config_file).convolution_scratch(save_model=True)\n\n# We initialize the trainer class by sending the loader and config file\ntrainer = trainer.Trainer(config=config_file, model=baseline_model, train_loader=train_loader, val_loader=val_loader)\n# We train the model here.\ntrainer.train()\n\n# Prediction\npredict = predictor.Predictor(config_file, test)\nclass_predict = predict.predict()\n\n# To see the final predictions\nshow_some_image_prediction(test, class_predict, path_to_save=config_file['dataset']['predict_image'])","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Below shows the output of last line. It gives around 95% of accuracy","metadata":{}},{"cell_type":"markdown","source":"![](https://github.com/Gnopal1132/DogsVsCat/blob/main/Generated/prediction.png?raw=true)","metadata":{}},{"cell_type":"markdown","source":"ONE FINAL NOTE MAKE SURE YOU SET UP THE DIRECTORY STURCTURE PROPERLY LIKE IF YOU SEE SOME \nIMPORTS I DID SOMETHING LIKE **from Data_Generator.generator import Datagenerator**.\nIF DIRECTORY STRUCTURE IS NOT PROPERLY MANAGED IT WILL GIVE ERROR THAT FILE DOESNT EXIST.\n\nCheckout full code at my [github](https://github.com/Gnopal1132/DogsVsCat)","metadata":{}},{"cell_type":"markdown","source":"So Finally I reached the END!! I hope u enjoyed as much as I did. Please star my blog here and on my github.\n\n**Thank You**\n\n**Ich freue mich auf deine Reaktion auf meinen Beitrag ;)**","metadata":{}}]}