cnn_keras.py 8 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
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
BATCH_SIZE = 16
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
41
EPOCHS = 1
42 43 44
CLASS_NAMES = ['ferrugemAsiatica', 'folhaSaudavel',
               'fundo', 'manchaAlvo', 'mildio', 'oidio']

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
45

46
class CNNKeras(Classifier):
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
47 48 49 50 51 52 53 54 55 56 57
    """ Class for CNN classifiers based on Keras applications """

    def __init__(self, architecture = "VGG16", fine_tuning_rate = 100):
        """
            Constructor of CNNKeras
        """

        self.architecture = Config("architecture", architecture, str)
        self.fine_tuning_rate = Config("fineTuningRate", fine_tuning_rate, int)

        self.file_name = "kerasCNN"
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

        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
74
        self.model = Model(inputs=model.input, outputs=predictions)
75 76

        # compile the model
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
77 78 79
        self.model.compile(loss="categorical_crossentropy",
                           optimizer=optimizers.SGD(lr=0.0001, momentum=0.9),
                           metrics=["accuracy"])
80 81 82

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

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

        keras_config["architecture"] = self.architecture
        keras_config["fine_tuning_rate"] = self.fine_tuning_rate

        return keras_config
95 96 97

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

99 100 101 102 103
        Parameters
        ----------
        configs : OrderedDict
            New configs of classifier.
        """
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
104 105 106 107 108 109 110
        keras_config = OrderedDict()

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

        keras_config["fine_tuning_rate"] = Config.nvl_config(
            configs["fine_tuning_rate"], self.fine_tuning_rate)
111 112 113

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

115 116 117 118 119
        Returns
        -------
        summary : string
            Formatted string with summary of configuration.
        """
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
120 121 122 123 124 125 126 127 128 129
        keras_config = OrderedDict()
        
        keras_config[self.architecture.label] = self.architecture.value
        keras_config[self.fine_tuning_rate.label] = self.fine_tuning_rate.value

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

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

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
134 135 136 137 138 139 140 141
        Parameters
        ----------
        dataset : string
            Path to image dataset.
        test_dir : string
            Not used.
        test_data : string
            Name of test data file.
142

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
        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,
173 174
            target_size=(IMG_HEIGHT, IMG_WIDTH),
            batch_size=BATCH_SIZE,
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
            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.
        """
200

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
201 202 203 204
        train_datagen = ImageDataGenerator()

        train_generator = train_datagen.flow_from_directory(
            dataset,
205 206 207 208 209
            target_size=(IMG_HEIGHT, IMG_WIDTH),
            batch_size=BATCH_SIZE,
            shuffle=True,
            class_mode="categorical")

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
        # 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")

226
        # Save the model according to the conditions
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
227 228 229
        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)
230 231

        # Train the model
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
232
        self.model.fit_generator(
233 234 235
            train_generator,
            steps_per_epoch=train_generator.samples // BATCH_SIZE,
            epochs=EPOCHS,
Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
236 237 238 239 240 241 242 243 244 245 246 247 248
            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. 
249

Gabriel Kirsten's avatar
 
Gabriel Kirsten committed
250 251 252 253 254
        Returns
        -------
        False
        """
        return False