"""
<h3><center>Introduction</center></h3>

![image.png](attachment:image.png)

<div style="font-family:verdana; word-spacing:1.7px;">
FaceNet is a face recognition system developed in 2015 by researchers at Google that achieved then state-of-the-art results on a range of face recognition benchmark datasets.<br><br>    
The FaceNet system can be used to extract high-quality features from faces(128 element vector representation), called face embeddings, that can then be used to train a face identification system. In this kernel, we will  develop a face detection system using FaceNet and an SVM classifier to identify people from photographs.
</div>
"""
"""
<h3><center>1. Importing Libraries</center></h3>
"""
!pip install -q mtcnn
import warnings
warnings.filterwarnings('ignore')

import numpy as np
import pandas as pd
import os
import random

from tqdm import tqdm_notebook as tqdm
tqdm().pandas()

from PIL import Image
import matplotlib.pyplot as plt

from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import Normalizer
from sklearn.svm import SVC

from mtcnn.mtcnn import MTCNN
PATH = '/kaggle/input/5-celebrity-faces-dataset/train/ben_afflek/'

ROOT_PATH = '/kaggle/input/5-celebrity-faces-dataset/'

TRAIN_PATH = '/kaggle/input/5-celebrity-faces-dataset/train/'

VAL_PATH = '/kaggle/input/5-celebrity-faces-dataset/val/'
plt.figure(figsize=(10,5))
for i, filename in enumerate(os.listdir(PATH)):
    path = PATH + filename
    image = plt.imread(path)
    
    plt.subplot(2, 7, i+1)
    
    plt.axis('off')
    plt.imshow(image)
    
plt.show()
"""
<h3><center>2. Detect Faces using MTCNN</center></h3>
"""
# extract a single face from a given photograph
def extract_face(filename, required_size=(160, 160)):
    image = Image.open(filename)
    image = image.convert('RGB')
    pixels = np.asarray(image)
    
    detector = MTCNN()
    results = detector.detect_faces(pixels)
    
    # extract the bounding box from the first face
    x1, y1, width, height = results[0]['box']
    # bug fix
    x1, y1 = abs(x1), abs(y1)
    
    x2, y2 = x1 + width, y1 + height
    
    face = pixels[y1:y2, x1:x2]
    
    image = Image.fromarray(face)
    
    image = image.resize(required_size)
    face_array = np.asarray(image)
    
    return face_array

plt.figure(figsize=(10,5))
for i, filename in tqdm(enumerate(os.listdir(PATH))):
    path = PATH + filename
    
    face = extract_face(path)
    
    #print(i+1, face.shape)
    
    plt.subplot(2, 7, i+1)
    
    plt.axis('off')
    plt.imshow(face)
    
plt.show()
"""
<h3><center>3. Load Faces</center></h3>
"""
def load_faces(directory):
    faces = list()
    for file_name in tqdm(os.listdir(directory)):
        path = directory + file_name
        face = extract_face(path)
        
        faces.append(face)
    return faces

def load_dataset(directory):
    images, labels = list(), list()
    for folder in tqdm(os.listdir(directory)):
        path = directory + folder + '/'
        
        if not os.path.isdir(path):
            continue
        
        faces = load_faces(path)

        print(f'Celebrity : {folder}, Faces : {len(faces)}')

        label = [folder for _ in range(len(faces))]

        images.extend(faces)
        labels.extend(label)
        
    return np.asarray(images), np.asarray(labels)
X_train, y_train = load_dataset(TRAIN_PATH)

X_test, y_test = load_dataset(VAL_PATH)

"""
<h3><center>4. Create Face Embeddings</center></h3>

<div style="font-family:verdana; word-spacing:1.7px;">
    A face embedding is a vector that represents the features extracted from the face.<br>
    For example, another vector that is close (by some measure) may be the same person, whereas another vector that is far (by some measure) may be a different person. The classifier model that we want to develop will take a face embedding as input and predict the identity of the face. The FaceNet model will generate this embedding for a given image of a face.</div>
"""
face_pixels = X_train[0]

print(face_pixels.shape)

face_pixels = np.expand_dims(face_pixels, axis=0)
print(face_pixels.shape)
from keras.models import load_model

def get_embedding(model, face_pixels):
    face_pixels = face_pixels.astype('float32')
    
    # standardize pixel values across channels (global) 
    mean, std = face_pixels.mean(), face_pixels.std() 
    face_pixels = (face_pixels - mean) / std
    
    samples = np.expand_dims(face_pixels, axis=0)
    
    yhat = model.predict(samples)
    
    return yhat[0]

model = load_model('/kaggle/input/keras-facenet/facenet_keras.h5')


trainX = list()

for pixels in tqdm(X_train):
    embedding = get_embedding(model, pixels)
    trainX.append(embedding)
trainX = np.asarray(trainX)

print('Train X :', trainX.shape)


testX = list()

for pixels in tqdm(X_test):
    embedding = get_embedding(model, pixels)
    testX.append(embedding)
testX = np.asarray(testX)

print('test X :', testX.shape)
"""
<h3><center>5. Perform Face Classification</center></h3>
"""
norm = Normalizer(norm='l2')

trainX = norm.transform(trainX)
testX = norm.transform(testX)

label = LabelEncoder()

trainy = label.fit_transform(y_train)
testy = label.fit_transform(y_test)

model = SVC(kernel='linear', probability=True)
model.fit(trainX, trainy)

yhat_train = model.predict(trainX)
yhat_test = model.predict(testX)

score_train = accuracy_score(trainy, yhat_train)
score_test = accuracy_score(testy, yhat_test)

print('Accuracy: train=%.3f, test=%.3f' % (score_train*100, score_test*100))
# Choose a random index
random.seed(100)
selection = random.choice([i for i in range(testX.shape[0])])

face = X_test[selection]

yhat_prob = model.predict_proba(testX[selection].reshape(1,-1))
yhat = model.predict(testX[selection].reshape(1,-1))

celeb = label.inverse_transform(yhat)

plt.imshow(face)
plt.axis('off')

print(f'Probability : {np.max(yhat_prob)*100}, \nCelebrity - Predicted : {celeb}, Acutal : {y_test[selection]}')
"""
<h3><center>6. T-SNE Plot </center></h3>
"""
from sklearn.manifold import TSNE
import seaborn as sns

tsne = TSNE(learning_rate=100)

tsne_features = tsne.fit_transform(trainX)

X = tsne_features[:,0]
y = tsne_features[:,1]

dataset = pd.DataFrame(data=y_train, columns=['label'])
dataset['X'] = X
dataset['y'] = y

plt.figure(figsize=(13,8))
sns.scatterplot(data=dataset, x='X', y='y', hue='label', s=120)