Reconnaissance faciale à l'aide de réseaux siamois



Le réseau neuronal siamois est l'un des algorithmes d'apprentissage simples les plus simples et les plus populaires. Méthodes dans lesquelles pour chaque classe est prise une seule étude de cas. Ainsi, le réseau siamois est généralement utilisé dans des applications où il n'y a pas beaucoup d'unités de données dans chaque classe.

Supposons que nous devions créer un modèle de reconnaissance faciale pour une organisation qui emploie environ 500 personnes. Si vous créez un tel modèle à partir de zéro à partir du réseau neuronal convolutif (CNN), puis pour former le modèle et obtenir une bonne précision de reconnaissance, nous aurons besoin de nombreuses images de chacune de ces 500 personnes. Mais il est évident que nous ne pouvons pas collecter un tel ensemble de données, vous ne devez donc pas créer un modèle basé sur CNN ou tout autre algorithme d' apprentissage en profondeur si nous ne disposons pas de suffisamment de données. Dans de tels cas, vous pouvez utiliser l'algorithme d'apprentissage unique complexe, comme le réseau siamois, qui peut être formé sur moins de données.

En fait, les réseaux siamois se composent de deux réseaux de neurones symétriques, avec les mêmes poids et la même architecture, qui à la fin combinent et utilisent la fonction d'énergie - E.
Regardons le réseau siamois, créant un modèle de reconnaissance faciale basé sur lui. Nous lui apprendrons à déterminer quand deux visages sont identiques ou non. Et pour commencer, nous utiliserons l'ensemble de données AT&T Database of Faces, qui peut être téléchargé sur le site Web du laboratoire informatique de l'Université de Cambridge .

Téléchargez, décompressez et voyez les dossiers de s1 à s40:



Chaque dossier contient 10 photographies différentes d'une même personne prises sous différents angles. Voici le contenu du dossier s1:



Et voici ce qui se trouve dans le dossier s13:



Les réseaux siamois doivent entrer des valeurs appariées avec des marquages, alors créons de tels ensembles. Prenez deux photos au hasard dans le même dossier et marquez-les comme une paire «authentique». Ensuite, nous prenons deux photos de différents dossiers et les marquons comme une «fausse» paire (imposite):



Après avoir distribué toutes les photos en paires marquées, nous étudierons le réseau. De chaque paire, nous transférerons une photo vers le réseau A et la seconde vers le réseau B. Les deux réseaux extraient uniquement des vecteurs de propriété. Pour ce faire, nous utilisons deux couches convolutives avec activation de l'unité linéaire rectifiée (ReLU). Après avoir étudié les propriétés, nous transférons les vecteurs générés par les deux réseaux dans une fonction énergétique qui estime la similitude. Nous utilisons la distance euclidienne en fonction.

Examinez maintenant toutes ces étapes plus en détail.


Tout d'abord, importez les bibliothèques nécessaires:

import re import numpy as np from PIL import Image from sklearn.model_selection import train_test_split from keras import backend as K from keras.layers import Activation from keras.layers import Input, Lambda, Dense, Dropout, Convolution2D, MaxPooling2D, Flatten from keras.models import Sequential, Model from keras.optimizers import RMSprop 

Nous définissons maintenant une fonction pour lire les images d'entrée. La fonction read_image prend une photo et retourne un tableau NumPy:

 def read_image(filename, byteorder='>'): #first we read the image, as a raw file to the buffer with open(filename, 'rb') as f: buffer = f.read() #using regex, we extract the header, width, height and maxval of the image header, width, height, maxval = re.search( b"(^P5\s(?:\s*#.*[\r\n])*" b"(\d+)\s(?:\s*#.*[\r\n])*" b"(\d+)\s(?:\s*#.*[\r\n])*" b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", buffer).groups() #then we convert the image to numpy array using np.frombuffer which interprets buffer as one dimensional array return np.frombuffer(buffer, dtype='u1' if int(maxval) 

Par exemple, ouvrez cette photo:

 Image.open("data/orl_faces/s1/1.pgm") 



Nous le passons à la fonction read_image et obtenons un tableau NumPy:

 img = read_image('data/orl_faces/s1/1.pgm') img.shape (112, 92) 

Nous définissons get_data fonction get_data qui générera les données. Permettez-moi de vous rappeler que les réseaux siamois doivent soumettre des paires de données (authentiques et imposantes) avec marquage binaire.

Tout d'abord, lisez les images ( img1 , img2 ) à partir d'un répertoire, enregistrez-les dans le tableau x_genuine_pair, définissez y_genuine sur 1 . Ensuite, nous lisons les images ( img1 , img2 ) à partir de différents répertoires, les enregistrons dans la paire x_imposite, et définissons y_imposite à 0 .

Concaténer x_genuine_pair et x_imposite dans X , et y_genuine et y_imposite dans Y :

 size = 2 total_sample_size = 10000 def get_data(size, total_sample_size): #read the image image = read_image('data/orl_faces/s' + str(1) + '/' + str(1) + '.pgm', 'rw+') #reduce the size image = image[::size, ::size] #get the new size dim1 = image.shape[0] dim2 = image.shape[1] count = 0 #initialize the numpy array with the shape of [total_sample, no_of_pairs, dim1, dim2] x_geuine_pair = np.zeros([total_sample_size, 2, 1, dim1, dim2]) # 2 is for pairs y_genuine = np.zeros([total_sample_size, 1]) for i in range(40): for j in range(int(total_sample_size/40)): ind1 = 0 ind2 = 0 #read images from same directory (genuine pair) while ind1 == ind2: ind1 = np.random.randint(10) ind2 = np.random.randint(10) # read the two images img1 = read_image('data/orl_faces/s' + str(i+1) + '/' + str(ind1 + 1) + '.pgm', 'rw+') img2 = read_image('data/orl_faces/s' + str(i+1) + '/' + str(ind2 + 1) + '.pgm', 'rw+') #reduce the size img1 = img1[::size, ::size] img2 = img2[::size, ::size] #store the images to the initialized numpy array x_geuine_pair[count, 0, 0, :, :] = img1 x_geuine_pair[count, 1, 0, :, :] = img2 #as we are drawing images from the same directory we assign label as 1. (genuine pair) y_genuine[count] = 1 count += 1 count = 0 x_imposite_pair = np.zeros([total_sample_size, 2, 1, dim1, dim2]) y_imposite = np.zeros([total_sample_size, 1]) for i in range(int(total_sample_size/10)): for j in range(10): #read images from different directory (imposite pair) while True: ind1 = np.random.randint(40) ind2 = np.random.randint(40) if ind1 != ind2: break img1 = read_image('data/orl_faces/s' + str(ind1+1) + '/' + str(j + 1) + '.pgm', 'rw+') img2 = read_image('data/orl_faces/s' + str(ind2+1) + '/' + str(j + 1) + '.pgm', 'rw+') img1 = img1[::size, ::size] img2 = img2[::size, ::size] x_imposite_pair[count, 0, 0, :, :] = img1 x_imposite_pair[count, 1, 0, :, :] = img2 #as we are drawing images from the different directory we assign label as 0. (imposite pair) y_imposite[count] = 0 count += 1 #now, concatenate, genuine pairs and imposite pair to get the whole data X = np.concatenate([x_geuine_pair, x_imposite_pair], axis=0)/255 Y = np.concatenate([y_genuine, y_imposite], axis=0) return X, Y 

Nous allons maintenant générer les données et vérifier leur taille. Nous avons 20 000 photos, dont 10 000 authentiques et 10 000 fausses paires ont été collectées:

 X, Y = get_data(size, total_sample_size) X.shape (20000, 2, 1, 56, 46) Y.shape (20000, 1) 

Nous partagerons l'ensemble des informations: 75% des paires iront à la formation et 25% aux tests:
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=.25)

Créez maintenant un réseau siamois. Nous définissons d'abord le réseau central - ce sera un réseau neuronal convolutif pour extraire les propriétés. Créez deux couches convolutives à l'aide des activations ReLU et une couche avec un regroupement maximal après une couche plate:

 def build_base_network(input_shape): seq = Sequential() nb_filter = [6, 12] kernel_size = 3 #convolutional layer 1 seq.add(Convolution2D(nb_filter[0], kernel_size, kernel_size, input_shape=input_shape, border_mode='valid', dim_ordering='th')) seq.add(Activation('relu')) seq.add(MaxPooling2D(pool_size=(2, 2))) seq.add(Dropout(.25)) #convolutional layer 2 seq.add(Convolution2D(nb_filter[1], kernel_size, kernel_size, border_mode='valid', dim_ordering='th')) seq.add(Activation('relu')) seq.add(MaxPooling2D(pool_size=(2, 2), dim_ordering='th')) seq.add(Dropout(.25)) #flatten seq.add(Flatten()) seq.add(Dense(128, activation='relu')) seq.add(Dropout(0.1)) seq.add(Dense(50, activation='relu')) return seq 


Ensuite, nous transférerons une paire d'images du réseau central, qui renverra des représentations vectorielles, c'est-à-dire des vecteurs de propriété:

 input_dim = x_train.shape[2:] img_a = Input(shape=input_dim) img_b = Input(shape=input_dim) base_network = build_base_network(input_dim) feat_vecs_a = base_network(img_a) feat_vecs_b = base_network(img_b) 

feat_vecs_a et feat_vecs_b sont des vecteurs de propriété d'une paire d'images. Passons leurs fonctions énergétiques pour calculer la distance entre elles. Et en fonction de l'énergie, nous utilisons la distance euclidienne:

 def euclidean_distance(vects): x, y = vects return K.sqrt(K.sum(K.square(x - y), axis=1, keepdims=True)) def eucl_dist_output_shape(shapes): shape1, shape2 = shapes return (shape1[0], 1) distance = Lambda(euclidean_distance, output_shape=eucl_dist_output_shape)([feat_vecs_a, feat_vecs_b]) 

Nous définissons le nombre d'époques à 13, appliquons la propriété RMS pour l'optimisation et déclarons le modèle:

 epochs = 13 rms = RMSprop() model = Model(input=[input_a, input_b], output=distance) 

Maintenant, nous définissons la fonction de perte fonction contrastive_loss et compilons le modèle:

 def contrastive_loss(y_true, y_pred): margin = 1 return K.mean(y_true * K.square(y_pred) + (1 - y_true) * K.square(K.maximum(margin - y_pred, 0))) model.compile(loss=contrastive_loss, optimizer=rms) 

Étudions le modèle:

 img_1 = x_train[:, 0] img_2 = x_train[:, 1] model.fit([img_1, img_2], y_train, validation_split=.25, batch_size=128, verbose=2, nb_epoch=epochs) 

Vous voyez comment les pertes diminuent au fil des époques:

 Train on 11250 samples, validate on 3750 samples Epoch 1/13 - 60s - loss: 0.2179 - val_loss: 0.2156 Epoch 2/13 - 53s - loss: 0.1520 - val_loss: 0.2102 Epoch 3/13 - 53s - loss: 0.1190 - val_loss: 0.1545 Epoch 4/13 - 55s - loss: 0.0959 - val_loss: 0.1705 Epoch 5/13 - 52s - loss: 0.0801 - val_loss: 0.1181 Epoch 6/13 - 52s - loss: 0.0684 - val_loss: 0.0821 Epoch 7/13 - 52s - loss: 0.0591 - val_loss: 0.0762 Epoch 8/13 - 52s - loss: 0.0526 - val_loss: 0.0655 Epoch 9/13 - 52s - loss: 0.0475 - val_loss: 0.0662 Epoch 10/13 - 52s - loss: 0.0444 - val_loss: 0.0469 Epoch 11/13 - 52s - loss: 0.0408 - val_loss: 0.0478 Epoch 12/13 - 52s - loss: 0.0381 - val_loss: 0.0498 Epoch 13/13 - 54s - loss: 0.0356 - val_loss: 0.0363 

Et maintenant, testons le modèle sur des données de test:

 pred = model.predict([x_test[:, 0], x_test[:, 1]]) 

Définissez une fonction pour calculer la précision:

 def compute_accuracy(predictions, labels): return labels[predictions.ravel() 

Nous calculons la précision:

 compute_accuracy(pred, y_test) 0.9779092702169625 

Conclusions


Dans ce guide, nous avons appris à créer des modèles de reconnaissance faciale basés sur des réseaux siamois. L'architecture de ces réseaux se compose de deux réseaux de neurones identiques ayant le même poids et la même structure, et les résultats de leur travail sont transférés vers une seule fonction énergétique - cela détermine l'identité des données d'entrée. Pour plus d'informations sur le méta-apprentissage avec Python, voir Méta-apprentissage pratique avec Python.

Mon commentaire


La connaissance des réseaux siamois est actuellement requise pour travailler avec des images. Il existe de nombreuses approches pour la formation des réseaux dans de petits échantillons, la génération de nouvelles données, les méthodes d'augmentation. Cette méthode permet d'obtenir des résultats relativement «bon marché», voici un exemple plus classique du réseau siamois sur «Hello world» pour les réseaux de neurones - jeu de données MNIST keras.io/examples/mnist_siamese

Source: https://habr.com/ru/post/fr465279/


All Articles