cnn_keras.py 9.91 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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
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

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
43

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

47
    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
48 49 50 51
        """
            Constructor of CNNKeras
        """

52 53 54 55 56 57 58 59
        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
60 61

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

        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
78
        self.model = Model(inputs=model.input, outputs=predictions)
79 80

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

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

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

95 96 97 98 99 100 101 102
        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
103 104

        return keras_config
105 106 107

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

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

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

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
        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)
139 140 141

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

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

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
150
        keras_config[self.architecture.label] = self.architecture.value
151 152 153 154
        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
155
        keras_config[self.fine_tuning_rate.label] = self.fine_tuning_rate.value
156 157
        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
158 159 160 161

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

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

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

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

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
177 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
        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,
207
            target_size=(IMG_HEIGHT, IMG_WIDTH),
208
            batch_size=self.batch_size,
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
            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.
        """
234

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

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

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
        # 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")

260
        # Save the model according to the conditions
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
261 262 263
        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)
264 265

        # Train the model
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
266
        self.model.fit_generator(
267
            train_generator,
268 269
            steps_per_epoch=train_generator.samples // self.batch_size,
            epochs=self.epochs,
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
270 271 272 273 274 275 276 277 278 279 280 281 282
            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. 
283

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