Reconocimiento facial con redes siamesas



La red neuronal siamesa es uno de los algoritmos de aprendizaje único más simples y populares. Métodos en los que para cada clase se toma solo un estudio de caso. Por lo tanto, la red siamesa se usa generalmente en aplicaciones donde no hay muchas unidades de datos en cada clase.

Supongamos que necesitamos hacer un modelo de reconocimiento facial para una organización que emplea a unas 500 personas. Si hacemos un modelo desde cero basado en la red neuronal convolucional (CNN), para entrenar el modelo y lograr una buena precisión de reconocimiento, necesitaremos muchas imágenes de cada una de estas 500 personas. Pero es obvio que no podemos compilar dicho conjunto de datos, por lo que no debe hacer un modelo basado en CNN o cualquier otro algoritmo de aprendizaje profundo si no tenemos suficientes datos. En tales casos, puede utilizar el complejo algoritmo de aprendizaje de una sola vez, como la red siamesa, que puede entrenarse con menos datos.

De hecho, las redes siamesas consisten en dos redes neuronales simétricas, con los mismos pesos y arquitectura, que al final combinan y usan la función de energía - E.
Veamos la red siamesa, creando un modelo de reconocimiento facial basado en ella. Le enseñaremos a determinar cuándo dos caras son iguales y cuándo no. Y para empezar, utilizaremos el conjunto de datos AT&T Database of Faces, que se puede descargar del sitio web del laboratorio de computación de la Universidad de Cambridge .

Descargue, descomprima y vea carpetas de s1 a s40:



Cada carpeta contiene 10 fotografías diferentes de una sola persona tomadas desde diferentes ángulos. Aquí está el contenido de la carpeta s1:



Y esto es lo que hay en la carpeta s13:



Las redes siamesas necesitan ingresar valores emparejados con marcas, así que creemos tales conjuntos. Tome dos fotos al azar de la misma carpeta y márquelas como un par "genuino". Luego tomamos dos fotos de diferentes carpetas y las marcamos como un par "falso" (falso):



Habiendo distribuido todas las fotos en pares marcados, estudiaremos la red. De cada par, transferiremos una foto a la red A, y la segunda a la red B. Ambas redes solo extraen vectores de propiedad. Para hacer esto, utilizaremos dos capas convolucionales con activación de unidad lineal rectificada (ReLU). Después de estudiar las propiedades, transferimos los vectores generados por ambas redes a una función de energía que estima la similitud. Usamos la distancia euclidiana como una función.

Ahora considere todos estos pasos con más detalle.


Primero, importe las bibliotecas necesarias:

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 

Ahora definimos una función para leer imágenes de entrada. La función read_image toma una imagen y devuelve una matriz 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) 

Por ejemplo, abra esta foto:

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



Lo pasamos a la función read_image y obtenemos una matriz NumPy:

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

Ahora definimos la función get_data que generará los datos. Permítame recordarle que las redes siamesas deben enviar pares de datos (genuinos e imposibles) con marcado binario.

Primero, lea las imágenes ( img1 , img2 ) de un directorio, x_genuine_pair, en la matriz x_genuine_pair, establezca y_genuine en 1 . Luego leemos las imágenes ( img1 , img2 ) de diferentes directorios, las x_imposite, en el par x_imposite, y establecemos y_imposite en 0 .

Concatenar x_genuine_pair y x_imposite en X , y y_genuine e y_imposite en 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 

Ahora generaremos los datos y verificaremos su tamaño. Tenemos 20,000 fotos, de las cuales se recolectaron 10,000 pares genuinos y 10,000 falsos:

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

Compartiremos toda la gama de información: el 75% de las parejas irán a entrenamiento y el 25% a pruebas:
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=.25)

Ahora crea una red siamesa. Primero definimos la red central: será una red neuronal convolucional para extraer propiedades. Cree dos capas convolucionales usando activaciones ReLU y una capa con la agrupación máxima después de una capa plana:

 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 


Luego transferiremos un par de imágenes de la red central, que devolverán representaciones vectoriales, es decir, vectores de propiedad:

 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 y feat_vecs_b son vectores de propiedad de un par de imágenes. Pasemos sus funciones de energía para calcular la distancia entre ellos. Y en función de la energía, usamos la distancia euclidiana:

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

Establecemos el número de épocas en 13, aplicamos la propiedad RMS para la optimización y declaramos el modelo:

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

Ahora definimos la función de pérdida contrastive_loss y compilamos el modelo:

 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) 

Estudiemos el modelo:

 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) 

Usted ve cómo las pérdidas disminuyen a medida que pasan las eras:

 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 

Y ahora probemos el modelo en los datos de prueba:

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

Defina una función para calcular la precisión:

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

Calculamos la precisión:

 compute_accuracy(pred, y_test) 0.9779092702169625 

Conclusiones


En esta guía, aprendimos cómo crear modelos de reconocimiento facial basados ​​en redes siamesas. La arquitectura de tales redes consiste en dos redes neuronales idénticas que tienen el mismo peso y estructura, y los resultados de su trabajo se transfieren a una función de energía: esto determina la identidad de los datos de entrada. Para obtener más información sobre el metaaprendizaje con Python, consulte Meta-Learning práctico con Python.

Mi comentario


Actualmente se requiere conocimiento de las redes siamesas cuando se trabaja con imágenes. Existen muchos enfoques para capacitar redes en muestras pequeñas, nueva generación de datos, métodos de aumento. Este método permite obtener resultados relativamente “baratos”, aquí hay un ejemplo más clásico de la red siamesa en “Hello world” para redes neuronales: conjunto de datos MNIST keras.io/examples/mnist_siamese

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


All Articles