cnn_keras.py 9.96 KB
Newer Older
1 2 3 4 5 6
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
"""
    Generic classifier with multiple models
    Models -> (Xception, VGG16, VGG19, ResNet50, InceptionV3, MobileNet)
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
7
    
8 9 10 11 12 13 14
    Name: cnn_keras.py
    Author: Gabriel Kirsten Menezes (gabriel.kirsten@hotmail.com)

"""

import time
import os
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
15 16
import numpy as np
from PIL import Image
17 18 19 20 21 22 23 24
from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Model
from keras.layers import Dropout, Flatten, Dense
from keras.callbacks import ModelCheckpoint

from classifier import Classifier
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
25
from classification.classifier import Classifier
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

from collections import OrderedDict

from util.config import Config
from util.file_utils import File
from util.utils import TimeUtils

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'  # Suppress warnings
START_TIME = time.time()

# =========================================================
# Constants
# =========================================================

IMG_WIDTH, IMG_HEIGHT = 256, 256
CLASS_NAMES = ['ferrugemAsiatica', 'folhaSaudavel',
               'fundo', 'manchaAlvo', 'mildio', 'oidio']

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
44

45
class CNNKeras(Classifier):
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
46 47
    """ Class for CNN classifiers based on Keras applications """

48
    def __init__(self, architecture="VGG16", learning_rate=0.0001, momentum=0.9, batch_size=16, epochs=10, fine_tuning_rate=100, transfer_learning=False, save_weights=False):
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
49 50 51 52
        """
            Constructor of CNNKeras
        """

53 54 55 56 57 58 59 60
        self.architecture = Config("Architecture", architecture, str)
        self.learning_rate = Config("Learning rate", learning_rate, float)
        self.momentum = Config("Momentum", momentum, float)
        self.batch_size = Config("Batch size", batch_size, int)
        self.epochs = Config("Epochs", epochs, int)
        self.fine_tuning_rate = Config("Fine Tuning rate", fine_tuning_rate, int)
        self.transfer_learning = Config("Transfer Learning", transfer_learning, bool)
        self.save_weights = Config("Save weights", save_weights, bool)
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
61 62

        self.file_name = "kerasCNN"
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

        model = applications.VGG16(
            weights="imagenet", include_top=False, input_shape=(IMG_WIDTH, IMG_HEIGHT, 3))

        for layer in model.layers:
            layer.trainable = True

        # Adding custom Layers
        new_custom_layers = model.output
        new_custom_layers = Flatten()(new_custom_layers)
        new_custom_layers = Dense(1024, activation="relu")(new_custom_layers)
        new_custom_layers = Dropout(0.5)(new_custom_layers)
        new_custom_layers = Dense(1024, activation="relu")(new_custom_layers)
        predictions = Dense(6, activation="softmax")(new_custom_layers)

        # creating the final model
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
79
        self.model = Model(inputs=model.input, outputs=predictions)
80 81

        # compile the model
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
82
        self.model.compile(loss="categorical_crossentropy",
83
                           optimizer=optimizers.SGD(lr=self.learning_rate.value, momentum=self.momentum.value),
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
84
                           metrics=["accuracy"])
85 86 87

    def get_config(self):
        """Return configuration of classifier. 
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
88

89 90 91 92 93
        Returns
        -------
        config : OrderedDict
            Current configs of classifier.
        """
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
94 95
        keras_config = OrderedDict()

96 97 98 99 100 101 102 103
        keras_config["Architecture"] = self.architecture
        keras_config["Learning rate"] = self.learning_rate
        keras_config["Momentum"] = self.momentum
        keras_config["Batch size"] = self.batch_size
        keras_config["Epochs"] = self.epochs
        keras_config["Fine Tuning rate"] = self.fine_tuning_rate
        keras_config["Transfer Learning"] = self.transfer_learning
        keras_config["Save weights"] = self.save_weights
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
104 105

        return keras_config
106 107 108

    def set_config(self, configs):
        """Update configuration of classifier. 
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
109

110 111 112 113 114
        Parameters
        ----------
        configs : OrderedDict
            New configs of classifier.
        """
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
115 116
        keras_config = OrderedDict()

117 118
        keras_config["Architecture"] = Config.nvl_config(
            configs["Architecture"], self.architecture)
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
119

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
        keras_config["Learning rate"] = Config.nvl_config(
            configs["Learning rate"], self.learning_rate)
        
        keras_config["Momentum"] = Config.nvl_config(
            configs["Momentum"], self.momentum)

        keras_config["Batch size"] = Config.nvl_config(
            configs["Batch size"], self.batch_size)

        keras_config["Epochs"] = Config.nvl_config(
            configs["Epochs"], self.epochs)

        keras_config["Fine Tuning rate"] = Config.nvl_config(
            configs["Fine Tuning rate"], self.fine_tuning_rate)

        keras_config["Transfer Learning"] = Config.nvl_config(
            configs["Transfer Learning"], self.transfer_learning)

        keras_config["Save weights"] = Config.nvl_config(
            configs["Save weights"], self.save_weights)
140 141 142

    def get_summary_config(self):
        """Return fomatted summary of configuration. 
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
143

144 145 146 147 148
        Returns
        -------
        summary : string
            Formatted string with summary of configuration.
        """
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
149
        keras_config = OrderedDict()
150

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
151
        keras_config[self.architecture.label] = self.architecture.value
152 153 154 155
        keras_config[self.learning_rate.label] = self.learning_rate.value
        keras_config[self.momentum.label] = self.momentum.value
        keras_config[self.batch_size.label] = self.batch_size.value
        keras_config[self.epochs.label] = self.epochs.value
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
156
        keras_config[self.fine_tuning_rate.label] = self.fine_tuning_rate.value
157 158
        keras_config[self.transfer_learning.label] = self.transfer_learning.value
        keras_config[self.save_weights.label] = self.save_weights.value
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
159 160 161 162

        summary = ''
        for config in keras_config:
            summary += "%s: %s\n" % (config, str(keras_config[config]))
163

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
164
        return summary
165 166

    def classify(self, dataset, test_dir, test_data):
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
167
        """"Perform the classification. 
168

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
169 170 171 172 173 174 175 176
        Parameters
        ----------
        dataset : string
            Path to image dataset.
        test_dir : string
            Not used.
        test_data : string
            Name of test data file.
177

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
        Returns
        -------
        summary : list of string
            List of predicted classes for each instance in test data in ordered way.
        """

        predict_directory = File.make_path(dataset, test_dir)

        for root, dirs, files in os.walk(predict_directory, topdown=False):
            for name in files:
                print(os.path.join(root, name))
                if os.path.splitext(os.path.join(root, name))[1].lower() == ".tif":
                    if os.path.isfile(os.path.splitext(os.path.join(root, name))[0] + ".png"):
                        print "A jpeg file already exists for %s" % name
                    # If a jpeg is *NOT* present, create one from the tiff.
                    else:
                        outfile = os.path.splitext(
                            os.path.join(root, name))[0] + ".png"
                        try:
                            im = Image.open(os.path.join(root, name))
                            print "Generating jpeg for %s" % name
                            im.thumbnail(im.size)
                            im.save(outfile, "PNG", quality=100)
                        except Exception, e:
                            print e

        classify_datagen = ImageDataGenerator()

        classify_generator = classify_datagen.flow_from_directory(
            predict_directory,
208
            target_size=(IMG_HEIGHT, IMG_WIDTH),
209
            batch_size=self.batch_size,
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
            shuffle=False,
            class_mode=None)

        output_classification = self.model.predict_generator(
            classify_generator, classify_generator.samples, verbose=1)

        one_hot_output = np.argmax(output_classification, axis=1)

        for index in range(0, len(one_hot_output)):
            one_hot_output[index] = CLASS_NAMES[one_hot_output[index]]

        return one_hot_output

    def train(self, dataset, training_data, force=False):
        """Perform the training of classifier.

        Parameters
        ----------
        dataset : string
            Path to image dataset.
        training_data : string
            Name of ARFF training file.
        force : boolean, optional, default = False
            If False don't perform new training if there is trained data.
        """
235

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
236 237 238 239
        train_datagen = ImageDataGenerator()

        train_generator = train_datagen.flow_from_directory(
            dataset,
240
            target_size=(IMG_HEIGHT, IMG_WIDTH),
241
            batch_size=self.batch_size,
242 243 244
            shuffle=True,
            class_mode="categorical")

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
        # test_datagen = ImageDataGenerator(
        #     rescale=1. / 255,
        #     horizontal_flip=True,
        #     fill_mode="nearest",
        #     zoom_range=0.3,
        #     width_shift_range=0.3,
        #     height_shift_range=0.3,
        #     rotation_range=30)

        # validation_generator = test_datagen.flow_from_directory(
        #     VALIDATION_DATA_DIR,
        #     target_size=(IMG_HEIGHT, IMG_WIDTH),
        #     batch_size=BATCH_SIZE,
        #     shuffle=True,
        #     class_mode="categorical")

261
        # Save the model according to the conditions
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
262 263 264
        checkpoint = ModelCheckpoint("../models_checkpoints/" + self.file_name + ".h5", monitor='val_acc',
                                     verbose=1, save_best_only=True, save_weights_only=False,
                                     mode='auto', period=1)
265 266

        # Train the model
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
267
        self.model.fit_generator(
268
            train_generator,
269 270
            steps_per_epoch=train_generator.samples // self.batch_size,
            epochs=self.epochs,
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
271 272 273 274 275 276 277 278 279 280 281 282 283
            callbacks=[checkpoint])

    def must_train(self):
        """Return if classifier must be trained. 

        Returns
        -------
        True
        """
        return True

    def must_extract_features(self):
        """Return if classifier must be extracted features. 
284

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
285 286 287 288 289
        Returns
        -------
        False
        """
        return False