Gesichtserkennung mit siamesischen Netzwerken



Das siamesische neuronale Netzwerk ist einer der einfachsten und beliebtesten Algorithmen für das einzelne Lernen. Methoden, bei denen für jede Klasse nur eine Fallstudie durchgeführt wird. Daher wird das siamesische Netzwerk normalerweise in Anwendungen verwendet, in denen nicht viele Dateneinheiten in jeder Klasse vorhanden sind.

Angenommen, wir müssen ein Gesichtserkennungsmodell für eine Organisation erstellen, die etwa 500 Mitarbeiter beschäftigt. Wenn Sie ein solches Modell basierend auf dem Convolutional Neural Network (CNN) von Grund auf neu erstellen, benötigen wir viele Bilder von jeder dieser 500 Personen, um das Modell zu trainieren und eine gute Erkennungsgenauigkeit zu erzielen. Es ist jedoch offensichtlich, dass wir einen solchen Datensatz nicht erfassen können. Sie sollten daher kein Modell erstellen, das auf CNN oder einem anderen Deep-Learning- Algorithmus basiert, wenn wir nicht über genügend Daten verfügen. In solchen Fällen können Sie den komplexen einmaligen Lernalgorithmus wie das siamesische Netzwerk verwenden, der mit weniger Daten trainiert werden kann.

Tatsächlich bestehen siamesische Netze aus zwei symmetrischen neuronalen Netzen mit den gleichen Gewichten und der gleichen Architektur, die am Ende die Energiefunktion kombinieren und nutzen - E.
Schauen wir uns das siamesische Netzwerk an und erstellen ein darauf basierendes Gesichtserkennungsmodell. Wir werden sie lehren zu bestimmen, wann zwei Gesichter gleich sind und wann nicht. Für den Anfang verwenden wir den Datensatz AT & T Database of Faces, der von der Computerlabor- Website der Universität Cambridge heruntergeladen werden kann.

Laden Sie Ordner von s1 bis s40 herunter, entpacken Sie sie und sehen Sie sie sich an:



Jeder Ordner enthält 10 verschiedene Fotos einer einzelnen Person aus verschiedenen Blickwinkeln. Hier ist der Inhalt des s1-Ordners:



Und hier ist, was sich im s13-Ordner befindet:



Siamesische Netzwerke müssen gepaarte Werte mit Markierungen eingeben, also erstellen wir solche Mengen. Nehmen Sie zwei zufällige Fotos aus demselben Ordner auf und markieren Sie sie als „echtes“ Paar. Dann machen wir zwei Fotos aus verschiedenen Ordnern und markieren sie als "falsches" Paar (imposit):



Nachdem wir alle Fotos in markierten Paaren verteilt haben, werden wir das Netzwerk untersuchen. Von jedem Paar übertragen wir ein Foto an Netzwerk A und das zweite an Netzwerk B. Beide Netzwerke extrahieren nur Eigenschaftsvektoren. Dazu verwenden wir zwei Faltungsschichten mit Aktivierung der gleichgerichteten Lineareinheit (ReLU). Nachdem wir die Eigenschaften untersucht haben, übertragen wir die von beiden Netzwerken erzeugten Vektoren in eine Energiefunktion, die die Ähnlichkeit abschätzt. Wir verwenden den euklidischen Abstand als Funktion.

Betrachten Sie nun alle diese Schritte genauer.


Importieren Sie zunächst die erforderlichen Bibliotheken:

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 

Nun definieren wir eine Funktion zum Lesen von Eingabebildern. Die Funktion read_image macht ein Bild und gibt ein NumPy-Array zurück:

 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) 

Öffnen Sie zum Beispiel dieses Foto:

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



Wir übergeben es an die Funktion read_image und erhalten ein NumPy-Array:

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

Jetzt definieren wir die Funktion get_data , die die Daten generiert. Ich möchte Sie daran erinnern, dass siamesische Netzwerke Datenpaare (echt und imposant) mit binärer Markierung einreichen müssen.

Lesen Sie zuerst die Bilder ( img1 , img2 ) aus einem Verzeichnis, speichern Sie sie im Array x_genuine_pair, setzen Sie y_genuine auf 1 . Dann lesen wir die Bilder ( img1 , img2 ) aus verschiedenen Verzeichnissen, speichern sie im x_imposite, Paar und setzen y_imposite auf 0 .

x_genuine_pair x_imposite x_genuine_pair und x_imposite in X und y_genuine und y_imposite in 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 

Jetzt werden wir die Daten generieren und ihre Größe überprüfen. Wir haben 20.000 Fotos, von denen 10.000 echte und 10.000 falsche Paare gesammelt wurden:

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

Wir werden die gesamte Reihe von Informationen teilen: 75% der Paare werden zum Training gehen und 25% - zum Testen:
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=.25)

Erstellen Sie jetzt ein siamesisches Netzwerk. Zuerst definieren wir das Kernnetzwerk - es wird ein neuronales Faltungsnetzwerk sein, um Eigenschaften zu extrahieren. Erstellen Sie zwei Faltungsebenen mit ReLU-Aktivierungen und eine Ebene mit maximalem Pooling nach einer flachen Ebene:

 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 


Dann werden wir ein Paar von Bildern des Kernnetzwerks übertragen, die Vektordarstellungen, dh Eigenschaftsvektoren, zurückgeben:

 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 und feat_vecs_b sind Eigenschaftsvektoren eines Bildpaares. Lassen Sie uns ihre Energiefunktionen übergeben, um den Abstand zwischen ihnen zu berechnen. Und als Funktion der Energie verwenden wir die euklidische Distanz:

 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]) 

Wir setzen die Anzahl der Epochen auf 13, wenden die RMS-Eigenschaft zur Optimierung an und deklarieren das Modell:

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

Nun definieren wir die Verlustfunktion contrastive_loss und kompilieren das Modell:

 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) 

Lassen Sie uns das Modell studieren:

 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) 

Sie sehen, wie die Verluste im Laufe der Zeit abnehmen:

 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 

Und jetzt testen wir das Modell anhand von Testdaten:

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

Definieren Sie eine Funktion zur Berechnung der Genauigkeit:

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

Wir berechnen die Genauigkeit:

 compute_accuracy(pred, y_test) 0.9779092702169625 

Schlussfolgerungen


In diesem Handbuch haben wir gelernt, wie Gesichtserkennungsmodelle basierend auf siamesischen Netzwerken erstellt werden. Die Architektur solcher Netze besteht aus zwei identischen neuronalen Netzen mit gleichem Gewicht und gleicher Struktur, und die Ergebnisse ihrer Arbeit werden auf eine Energiefunktion übertragen - dies bestimmt die Identität der Eingabedaten. Weitere Informationen zum Meta-Lernen mit Python finden Sie unter Praktisches Meta-Lernen mit Python.

Mein Kommentar


Für die Arbeit mit Bildern sind derzeit Kenntnisse über siamesische Netzwerke erforderlich. Es gibt viele Ansätze, um Netzwerke in kleinen Stichproben, neue Datengenerierung und Erweiterungsmethoden zu trainieren. Diese Methode ermöglicht es relativ "billig", gute Ergebnisse zu erzielen. Hier ist ein klassischeres Beispiel für das siamesische Netzwerk in "Hallo Welt" für neuronale Netze - Datensatz MNIST keras.io/examples/mnist_siamese

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


All Articles