"""
# Intel image multi label classification using transfer learning

multi label CNN Classification for: 

mountain, street, glacier, buildings, sea,forest

I'll try different type of networks while using transfer learning

https://towardsdatascience.com/illustrated-10-cnn-architectures-95d78ace614d
"""
"""
![image.png](attachment:image.png)
"""
"""
# Import Packages
"""
import numpy as np
import os
import pandas as pd
from sklearn.metrics import confusion_matrix
import seaborn as sn; sn.set(font_scale=1.4)
from sklearn.utils import shuffle           
import matplotlib.pyplot as plt             
import cv2                                 
import tensorflow as tf                
from tqdm import tqdm
from keras.preprocessing import image
from keras import applications
#import efficientnet
from keras import callbacks
from keras.models import Sequential

!pip install -U efficientnet
class_names = ['mountain', 'street', 'glacier', 'buildings', 'sea', 'forest']
class_names_label = {class_name:i for i, class_name in enumerate(class_names)}

nb_classes = len(class_names)

IMAGE_SIZE = (150, 150)
class_names_label
"""
# Loading the Data
We have to write a load_data function that load the images and the labels from the folder.
"""
def load_data():
    """
        Load the data:
            - 14,034 images to train the network.
            - 3,000 images to evaluate how accurately the network learned to classify images.
    """
    
    datasets = ['../input/intel-image-classification/seg_train/seg_train', '../input/intel-image-classification/seg_test/seg_test']
    output = []
    
    # Iterate through training and test sets
    for dataset in datasets:
        
        images = []
        labels = []
        
        print("Loading {}".format(dataset))
        
        # Iterate through each folder corresponding to a category
        for folder in os.listdir(dataset):
            label = class_names_label[folder]
            
            # Iterate through each image in our folder
            for file in tqdm(os.listdir(os.path.join(dataset, folder))):
                
                # Get the path name of the image
                img_path = os.path.join(os.path.join(dataset, folder), file)
                
                # Open and resize the img
                image = cv2.imread(img_path)
                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
                image = cv2.resize(image, IMAGE_SIZE) 
                
                # Append the image and its corresponding label to the output
                images.append(image)
                labels.append(label)
                
        images = np.array(images, dtype = 'float32')
        labels = np.array(labels, dtype = 'int32')   
        
        output.append((images, labels))

    return output
(train_images, train_labels), (test_images, test_labels) = load_data()
train_images[0].shape
train_images, train_labels = shuffle(train_images, train_labels, random_state=7)
train_labels_unique, train_counts = np.unique(train_labels, return_counts=True)
test_labels_unique, test_counts = np.unique(test_labels, return_counts=True)
pd.DataFrame({'train': train_counts,
                    'test': test_counts}, 
             index=class_names
            ).plot.bar()
plt.show()
"""
# explore the dataset

"""
n_train = train_labels.shape[0]
n_test = test_labels.shape[0]

print ("Number of training examples: {}".format(n_train))
print ("Number of testing examples: {}".format(n_test))
print ("Each image -RGB  shape is: {}".format(IMAGE_SIZE))
plt.pie(train_counts,
        explode=(0, 0, 0, 0, 0, 0) , 
        labels=class_names,
        autopct='%1.1f%%')
plt.axis('equal')
plt.title('Proportion of each observed category')
plt.show()
"""
## Scal image dataset
"""
train_images = train_images / 255.0 
test_images = test_images / 255.0
"""
## Visualize the data
We can display a random image from the training set.
"""
def display_random_image(class_names, images, labels):
    """
        Display a random image from the images array and its correspond label from the labels array.
    """
    
    index = np.random.randint(images.shape[0])
    plt.figure()
    plt.imshow(images[index])
    plt.xticks([])
    plt.yticks([])
    #plt.grid(False)
    plt.title('Image #{} : '.format(index) + class_names[labels[index]])
    plt.show()
display_random_image(class_names, train_images, train_labels)
"""
We can also display the first 25 images from the training set directly with a loop to get a better view
"""
def display_examples(class_names, images, labels):
    """
        Display 25 images from the images array with its corresponding labels
    """
    
    fig = plt.figure(figsize=(10,10))
    fig.suptitle("Some examples of images of the dataset", fontsize=16)
    for i in range(25):
        plt.subplot(5,5,i+1)
        plt.xticks([])
        plt.yticks([])
        plt.grid(False)
        plt.imshow(images[i], cmap=plt.cm.binary)
        plt.xlabel(class_names[labels[i]])
    plt.show()
display_examples(class_names, train_images, train_labels)
#model.summary()
"""
We fit the model to the data from the training set. The neural network will learn by itself the pattern in order to distinguish each category.
"""
def plot_accuracy_loss(history,model_name):
    """
        Plot the accuracy and the loss during the training of the nn.
    """
    fig = plt.figure(figsize=(10,5))

    # Plot accuracy
    plt.subplot(221)
    plt.plot(history.history['acc'],'bo--', label = "acc")
    plt.plot(history.history['val_acc'], 'ro--', label = "val_acc")
    plt.title("train_acc vs val_acc" + model_name)
    plt.ylabel("accuracy")
    plt.xlabel("epochs")
    plt.legend()

    # Plot loss function
    plt.subplot(222)
    plt.plot(history.history['loss'],'bo--', label = "loss")
    plt.plot(history.history['val_loss'], 'ro--', label = "val_loss")
    plt.title("train_loss vs val_loss"  + model_name)
    plt.ylabel("loss")
    plt.xlabel("epochs")

    plt.legend()
    plt.show()
"""
![image.png](attachment:image.png)
"""
"""
# VGG19
"""
model = applications.VGG19(weights='imagenet', include_top=False)

train_features = model.predict(train_images)
test_features = model.predict(test_images)
n_train, x, y, z = train_features.shape
n_test, x, y, z = test_features.shape
numFeatures = x * y * z
model2 = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape = (x, y, z)),
    tf.keras.layers.Dense(100, activation=tf.nn.relu),
    tf.keras.layers.Dense(6, activation=tf.nn.softmax)
])

model2.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics=['accuracy'])

history2 = model2.fit(train_features, train_labels, batch_size=512, epochs=25, validation_split = 0.25)
plot_accuracy_loss(history2,"VGG19")
test_loss = model2.evaluate(test_features, test_labels)
del model
del model2
"""
# InceptionV3
"""
model = applications.InceptionV3(weights='imagenet', include_top=False)
train_features = model.predict(train_images)
test_features = model.predict(test_images)
n_train, x, y, z = train_features.shape
n_test, x, y, z = test_features.shape
numFeatures = x * y * z

model2 = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape = (x, y, z)),
    tf.keras.layers.Dense(100, activation=tf.nn.relu),
    tf.keras.layers.Dense(6, activation=tf.nn.softmax)
])
model2.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics=['accuracy'])

history2 = model2.fit(train_features, train_labels, batch_size=128, epochs=15, validation_split = 0.2)
plot_accuracy_loss(history2,"InceptionV3")
test_loss = model2.evaluate(test_features, test_labels)
del model
del model2
"""
# InceptionResNetV2
"""
"""
![image.png](attachment:image.png)
"""
model = applications.InceptionResNetV2(weights='imagenet', include_top=False)
train_features = model.predict(train_images)
test_features = model.predict(test_images)
n_train, x, y, z = train_features.shape
n_test, x, y, z = test_features.shape
numFeatures = x * y * z

model2 = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape = (x, y, z)),
    tf.keras.layers.Dense(100, activation=tf.nn.relu),
    tf.keras.layers.Dense(6, activation=tf.nn.softmax)
])
model2.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics=['accuracy'])

history2 = model2.fit(train_features, train_labels, batch_size=256, epochs=25, validation_split = 0.25)
plot_accuracy_loss(history2,"InceptionResNetV2")
test_loss = model2.evaluate(test_features, test_labels)
del model
del model2
"""
# DenseNet201
"""
"""
![image.png](attachment:image.png)
"""
model_DenseNet201 = applications.DenseNet201(weights='imagenet', include_top=False)
train_features_DenseNet201 = model_DenseNet201.predict(train_images)
test_features_DenseNet201 = model_DenseNet201.predict(test_images)
n_train, x, y, z = train_features_DenseNet201.shape
n_test, x, y, z = test_features_DenseNet201.shape

model2_DenseNet201 = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape = (x, y, z)),
    tf.keras.layers.Dense(100, activation=tf.nn.relu),
    tf.keras.layers.Dense(6, activation=tf.nn.softmax)
])
model2_DenseNet201.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics=['accuracy'])

history2_DenseNet201 = model2_DenseNet201.fit(train_features_DenseNet201, train_labels, batch_size=1024, epochs=30, validation_split = 0.15)
plot_accuracy_loss(history2_DenseNet201,"DenseNet201")
test_loss = model2_DenseNet201.evaluate(test_features_DenseNet201, test_labels)
del model_DenseNet201
del model2_DenseNet201
"""
# EfficientNet
"""
from efficientnet.keras import EfficientNetB7
"""
Model is allocating too much memory - need to run standalone 
"""
if 0 :
    model = EfficientNetB7(weights='imagenet', include_top=False)
    train_features = model.predict(train_images)
    test_features = model.predict(test_images)
    n_train, x, y, z = train_features.shape
    n_test, x, y, z = test_features.shape

    model2 = tf.keras.Sequential([
        tf.keras.layers.Flatten(input_shape = (x, y, z)),
        tf.keras.layers.Dense(100, activation=tf.nn.relu),
        tf.keras.layers.Dense(6, activation=tf.nn.softmax)
    ])
if 0 :   
    model2.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics=['accuracy'])

    history2 = model2.fit(train_features, train_labels, batch_size=1024, epochs=30, validation_split = 0.15)
    plot_accuracy_loss(history2,"EfficientNetB7")
if 0 :   
test_loss = model2.evaluate(test_features, test_labels)
"""
## Huge thanks for Vincent Liu :) 
"""