高级AI:使用Siamese网络进行人脸识别

2019-07-01 14:52:49 浏览数 (1)

悲剧了,上一篇文章被删了,看来以后还是只能发技术的文章。 通常在图像识别中我们会采用深度卷积神经网络,但这篇文章所谈及的Siamese网络并没有采用,它是如何做的呢?这是一篇翻译的文章,原文链接:https://medium.com/swlh/advance-ai-face-recognition-using-siamese-networks-219ee1a85cd5

题图:Photo by 浮萍 闪电 on Unsplash

什么是Siamese网络?

Siamese网络是一种特殊类型的神经网络,也是最简单和最常用的一次性学习算法之一。

一次性学习是一种只从每个类别的一个训练样例中学习的技术。

因此,Siamese网络主要用于在每个类别中没有很多数据点的应用中。

为什么要使用Siamese网络?

比如,假设我们想为公司建立一个人脸识别模型,大约有500人。如果从零开始使用 卷积神经网络(CNN) 构建人脸识别模型,那么我们需要所有这500人的许多图像来训练网络,以获得良好的准确性。 但显然,我们没有这500人的很多图像照片,因此使用CNN或任何深度学习算法构建模型是不可行的,除非我们有足够的数据。因此,在这种场景中,我们可以采用复杂的一次性学习算法,例如Siamese网络,它可以从较少的数据点中学习。

Siamese网络如何工作?

Siamese网络基本上由两个对称的神经网络组成,它们共享相同的权重和体系结构,并且最后使用能量函数 E 连接在一起。Siamese网络的目标是了解两个输入值是相似还是不相似。假设我们有两个图像,X1X2,我们想知道这两个图像是相似还是不相似。

Siamese网络不仅用于人脸识别,还广泛用于没有很多数据点,以及需要学习两个输入之间的相似性的任务中。Siamese网络的应用包括签名验证、类似问题检索,对象跟踪等。在接下来的部分我们将详细研究Siamese网络。

Siamese网络的体系结构

正如您在上图中所看到的,Siamese网络由两个相同的网络组成,它们共享相同的权重和体系结构。假设我们有两个输入:X1X2。我们将输入 X1 馈送到网络A,即fw(X1),将输入 X2 馈送到网络B,即 fw(X2) 。正如您看到的,这两个网络具有相同的权重w,它们将为我们的输入X1X2生成嵌入。 然后,将这些嵌入提供给能量函数E,这将使我们在两个输入之间具有相似性。

它可以表示如下:

Siamese网络的输入应该是成对的 (X1,X2),以及对应的二分类标签 Y ∈(0,1),表明输入对是真正的一对(相同)还是一对无效对(不同)。 如下表所见,我们将句子成对使用,标签表明句子对是语义相同的(1)还是不同(0):

一个Siamese网络通过使用相同的架构找到两个输入值之间的相似性来学习。它是涉及计算两个实体之间相似性的任务中最常用的少数几种学习算法之一。它功能强大且鲁棒,可作为少数据量问题的解决方案。

使用Siamese网络进行人脸识别

我们将通过构建人脸识别模型来创建Siamese网络。网络的目标是了解两张面孔是相似还是不同。 我们使用AT&T的面部数据库,可以从这里下载:https://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html。

下载并解压包后,您可以看到文件夹s1、s2、… 、s40,如下所示:

每一个文件夹中都有从不同角度拍摄的单个人的10个不同图像。 例如,打开文件夹s1,可以看到一个人的10个不同的照片:

我们打开并检查文件夹s13:

然后,我们将从同一个文件夹中随机抽取两张图像,并将它们标记为真的一对,从两个不同的文件夹中抽取单张图像,并将它们标记为假的一对。

首先,我们导入所需的包:

代码语言:javascript复制
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

接下来,定义一个用于读取输入图像的函数。read_image函数将图像作为输入并返回NumPy数组:

代码语言:javascript复制
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"(^P5s(?:s*#.*[rn])*"
        b"(d )s(?:s*#.*[rn])*"
        b"(d )s(?:s*#.*[rn])*"
        b"(d )s(?:s*#.*[rn]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) < 256 else byteorder 'u2',
                            count=int(width)*int(height),
                            offset=len(header)
                            ).reshape((int(height), int(width)))

最后,我们将x_genuine_pair和x_imposite串接到X,将y_genuine 和 y_imposite串接到Y:

代码语言:javascript复制
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

现在,我们生成数据并检查数据大小。正如您所看到的,我们有20,000个数据点,其中10,000个是真的对,10,000个是假的对:

代码语言:javascript复制
X, Y = get_data(size, total_sample_size)

X.shape
(20000, 2, 1, 56, 46)

Y.shape
(20000, 1)

接下来,我们将数据分成训练数据和测试数据,比例为75%的训练数据,25%的测试数据:

代码语言:javascript复制
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=.25)

现在我们已经成功生成了数据,接下来构建Siamese网络。首先,定义基本网络,它是用于特征提取的卷积网络。我们构建了两个卷积层,其中包含ReLU激活和最大池化,然后是Flat层:

代码语言:javascript复制
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

接下来,我们将图像对提供给基础网络,基础网络将返回嵌入,即特征向量:

代码语言:javascript复制
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和feat_vecs_b是我们图像对的特征向量。接下来,我们将这些特征向量提供给能量函数以计算它们之间的距离,这里我们使用欧氏距离作为能量函数:

代码语言:javascript复制
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])

现在,我们将epoch长度设置为13,使用RMS prop进行优化并定义我们的模型:

代码语言:javascript复制
epochs = 13
rms = RMSprop()

model = Model(input=[input_a, input_b], output=distance)

接下来,我们将损失函数定义为contrastive_loss函数并编译模型:

代码语言:javascript复制
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)

紧接着,训练模型:

代码语言:javascript复制
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)

然后,使用测试数据进行预测:

代码语言:javascript复制
pred = model.predict([x_test[:, 0], x_test[:, 1]])

接下来,定义一个计算精度的函数:

代码语言:javascript复制
def compute_accuracy(predictions, labels):
    return labels[predictions.ravel() < 0.5].mean()

最后,模型的准确性:

代码语言:javascript复制
compute_accuracy(pred, y_test)

0.9779092702169625

0 人点赞