Reconhecimento de faces usando redes siamesas



A rede neural siamesa é um dos algoritmos de aprendizado único mais simples e mais populares. Métodos em que para cada aula é realizado apenas um estudo de caso. Assim, a rede siamesa é geralmente usada em aplicativos onde não há muitas unidades de dados em cada classe.

Suponha que precisamos criar um modelo de reconhecimento facial para uma organização que emprega cerca de 500 pessoas. Se você criar um modelo desse tipo com base na rede neural convolucional (CNN), para treiná-lo e obter uma boa precisão de reconhecimento, precisaremos de muitas imagens de cada uma dessas 500 pessoas. Mas é óbvio que não podemos compilar esse conjunto de dados, portanto, você não deve criar um modelo baseado na CNN ou em qualquer outro algoritmo de aprendizado profundo se não tivermos dados suficientes. Nesses casos, você pode usar o algoritmo complexo de aprendizado único, como a rede siamesa, que pode ser treinada com menos dados.

De fato, as redes siamesas consistem em duas redes neurais simétricas, com os mesmos pesos e arquitetura, que no final combinam e usam a função de energia - E.
Vejamos a rede siamesa, criando um modelo de reconhecimento de rosto baseado nela. Vamos ensiná-la a determinar quando duas faces são iguais e quando não são. E, para começar, usaremos o conjunto de dados AT&T Database of Faces, que pode ser baixado do site do laboratório de computação da Universidade de Cambridge .

Baixe, descompacte e veja as pastas de s1 a s40:



Cada pasta contém 10 fotografias diferentes de uma única pessoa, tiradas de diferentes ângulos. Aqui está o conteúdo da pasta s1:



E aqui está o que está na pasta s13:



As redes siamesas precisam inserir valores emparelhados com marcações, então vamos criar esses conjuntos. Tire duas fotos aleatórias da mesma pasta e marque-as como um par “genuíno”. Depois, tiramos duas fotos de pastas diferentes e as marcamos como um par "falso" (imposição):



Depois de distribuir todas as fotos em pares marcados, estudaremos a rede. De cada par, transferiremos uma foto para a rede A e a segunda para a rede B. Ambas as redes extraem apenas vetores de propriedades. Para isso, usamos duas camadas convolucionais com a ativação da unidade linear retificada (ReLU). Tendo estudado as propriedades, transferimos os vetores gerados pelas duas redes para uma função energética que estima a similaridade. Usamos a distância euclidiana como uma função.

Agora considere todas essas etapas com mais detalhes.


Primeiro, importe as bibliotecas necessárias:

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 

Agora, definimos uma função para ler imagens de entrada. A função read_image tira uma foto e retorna uma 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 exemplo, abra esta foto:

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



Passamos para a função read_image e obtemos uma matriz NumPy:

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

Agora, definimos a função get_data que irá gerar os dados. Deixe-me lembrá-lo de que as redes siamesas precisam enviar pares de dados (genuínos e imponentes) com marcação binária.

Primeiro, leia as imagens ( img1 , img2 ) em um diretório, salve-as na matriz x_genuine_pair, defina y_genuine como 1 . Em seguida, lemos as imagens ( img1 , img2 ) de diretórios diferentes, salve-as no par x_imposite, e defina y_imposite como 0 .

Concatene x_genuine_pair e x_imposite em X e y_genuine e y_imposite em 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 

Agora vamos gerar os dados e verificar seu tamanho. Temos 20.000 fotos, das quais 10.000 pares genuínos e 10.000 falsos foram coletados:

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

Compartilharemos toda a gama de informações: 75% dos pares serão treinados e 25% - para testes:
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=.25)

Agora crie uma rede siamesa. Primeiro, definimos a rede principal - será uma rede neural convolucional para extrair propriedades. Crie duas camadas convolucionais usando ativações ReLU e uma camada com pool máximo após uma camada 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 


Em seguida, transferiremos um par de imagens da rede principal, que retornará representações vetoriais, ou seja, vetores de propriedades:

 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 e feat_vecs_b são vetores de propriedades de um par de imagens. Vamos passar suas funções de energia para calcular a distância entre eles. E em função da energia, usamos a distância 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]) 

Definimos o número de épocas como 13, aplicamos a propriedade RMS para otimização e declaramos o modelo:

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

Agora, definimos a função de perda contrastive_loss e compilamos o 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) 

Vamos estudar o 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) 

Você vê como as perdas diminuem à medida que as eras passam:

 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 

E agora vamos testar o modelo nos dados de teste:

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

Defina uma função para calcular a precisão:

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

Calculamos a precisão:

 compute_accuracy(pred, y_test) 0.9779092702169625 

Conclusões


Neste guia, aprendemos como criar modelos de reconhecimento de rosto baseados em redes siamesas. A arquitetura de tais redes consiste em duas redes neurais idênticas com o mesmo peso e estrutura, e os resultados de seu trabalho são transferidos para uma função de energia - isso determina a identidade dos dados de entrada. Para obter mais informações sobre meta-aprendizado usando Python, consulte Meta-Learning prático com Python.

Meu comentário


Atualmente, o conhecimento de redes siamesas é necessário ao trabalhar com imagens. Existem muitas abordagens para o treinamento de redes em pequenas amostras, nova geração de dados, métodos de aumento. Este método permite que relativamente "barato" alcance bons resultados, eis um exemplo mais clássico da rede siamesa no "Hello world" para redes neurais - conjunto de dados MNIST keras.io/examples/mnist_siamese

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


All Articles