Python吴恩达深度学习作业16 -- 人脸识别

人脸识别 - the Happy House

在此次作业中,你将学习构建人脸识别系统。

人脸识别问题通常分为两类:

  • 人脸验证:比如在某些机场,系统通过扫描你的护照,然后确认你(携带护照的人)是本人,从而通过海关。也比如使用脸部解锁的手机。通常这一类是1:1匹配的问题。
  • 人脸识别:例如讲座显示了一个百度员工进入办公室的人脸识别视频。此类则是1:K匹配问题。

FaceNet网络将人脸图像编码为128个数字向量并学习,通过比较两个这样的向量,以确定两个图片是否是同一个人。

在此作业中,你将

  • 实现triplet loss 损失函数
  • 使用预先训练的模型将人脸图像映射为128维编码
  • 使用这些编码实现人脸验证和人脸识别

在本练习中,我们将使用预训练的模型,该模型使用"channels first"来表示ConvNet激活,而不是像先前的编程作业一样使用"channels last"。换句话说,一批图像将具有 ( m , n C , n H , n W ) (m,n_C,n_H,n_W) (m,nC,nH,nW)的维度,而非 ( m , n H , n W , n C ) (m,n_H,n_W,n_C) (m,nH,nW,nC)。这两种方式在开源实现中都有相当大的吸引力。深度学习中也没有统一的标准。

首先让我们加载所需的软件包。

from keras.models import Sequential
from keras.layers import Conv2D, ZeroPadding2D, Activation, Input, concatenate
from keras.models import Model
from keras.layers.normalization import BatchNormalization
from keras.layers.pooling import MaxPooling2D, AveragePooling2D
from keras.layers.merge import Concatenate
from keras.layers.core import Lambda, Flatten, Dense
from keras.initializers import glorot_uniform
from keras.engine.topology import Layer
from keras import backend as K
K.set_image_data_format('channels_first')
import cv2
import os
import numpy as np
from numpy import genfromtxt
import pandas as pd
import tensorflow as tf
from fr_utils import *
from inception_blocks_v2 import *

%matplotlib inline
%load_ext autoreload
%autoreload 2

# np.set_printoptions(threshold=np.nan)
import sys
np.set_printoptions(threshold=sys.maxsize)
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload

0 人脸验证

在人脸验证中,你将获得两张图像,并且必须确定它们是否属于同一个人。最简单的方法是逐像素比较两个图像。如果原始图像之间的距离小于选定的阈值,则可能是同一个人!
在这里插入图片描述

当然,此算法的性能确实很差,因为像素值会由于光照,人脸方向,甚至头部位置的微小变化等因素而急剧变化。

你会发现,可以编码 f ( i m g ) f(img) f(img)而不是使用原始图像,这样对该编码进行逐元素比较就可以更准确地判断两张图片是否属于同一个人。

1 将人脸图像编码为128维向量

1.1 使用ConvNet计算编码

FaceNet模型需要大量训练数据并需要很长时间去训练。因此,按照深度学习中的常规应用做法,我们加载已经训练好的权重。网络结构遵循Szegedy et al.中的Inception模型。我们提供了初始网络实现。你可以查看文件inception_blocks.py以了解其实现方式。

你需要知道的关键事项是:

  • 该网络使用 96 × 96 96 \times 96 96×96尺寸的RGB图像作为输入。具体来说,输入一张人脸图像(或一批 m m m人脸图像)作为维度为 ( m , n C , n H , n W ) = ( m , 3 , 96 , 96 ) (m,n_C,n_H,n_W)=(m,3,96,96) (m,nC,nH,nW)=(m,3,96,96)的张量。
  • 输出维度为 ( m , 128 ) (m,128) (m,128)的矩阵,该矩阵将每个输入的脸部图像编码为128维向量。

运行下面的单元格以创建人脸图像模型。

FRmodel = faceRecoModel(input_shape=(3, 96, 96))
print("Total Params:", FRmodel.count_params())
Total Params: 3743280

通过使用128个神经元组成的全连接层作为最后一层,该模型可确保输出是大小为128的编码向量。然后,使用该编码比较两个人脸图像,如下所示:
在这里插入图片描述

通过计算两种编码和阈值之间的距离,以确定两张图片是否代表同一个人

如果满足以下条件,编码将是一种不错的选择:

  • 同一个人的两张图像的编码彼此非常相似
  • 不同人的两幅图像的编码差距明显

triplet loss损失函数促进此实现,它尝试将“同一个人(锚点和正向)”的两个图像的编码“推”得更近,同时将另外一个人(锚点,负向)的两个图像的编码“拉”得更远。

在这里插入图片描述

在下一部分中,我们将从左到右调用图片:锚点(A),正向(P),负向(N)

1.2 三元组损失

对于图像 x x x,其编码表示为 f ( x ) f(x) f(x),其中 f f f是神经网络的计算函数。
在这里插入图片描述

我们在模型的末尾添加一个标准化步骤,以使 ∣ ∣ f ( x ) ∣ ∣ 2 = 1 {||f(x)||}_2=1 ∣∣f(x)∣∣2=1(意味着编码向量应为范数1)。

训练将使用三组图像 ( A , P , N ) (A,P,N) (A,P,N):

  • A是“锚示例”图像:人的照片。
  • P是“正示例”图像:与锚示例图像相同的人的照片。
  • N是“负示例”图像:与锚示例图像不同的人的照片。

这些图像是从我们的训练集中选取的。我们使用 ( A ( i ) , P ( i ) , N ( i ) ) (A^{(i)},P^{(i)},N^{(i)}) (A(i),P(i),N(i))来表示第 i i i个训练示例。

如果你想确定一个人的图像 A ( i ) A^{(i)} A(i)比负例图像 N ( i ) N^{(i)} N(i)更接近正例图像 P ( i ) P^{(i)} P(i)至少要保证 α \alpha α:
∣ ∣ f ( A ( i ) ) − f ( P ( i ) ) ∣ ∣ 2 2 + α < ∣ ∣ f ( A ( i ) ) − f ( N ( i ) ) ∣ ∣ 2 2 \mid \mid f(A^{(i)}) - f(P^{(i)}) \mid \mid_2^2 + \alpha < \mid \mid f(A^{(i)}) - f(N^{(i)}) \mid \mid_2^2 ∣∣f(A(i))f(P(i))22+α<∣∣f(A(i))f(N(i))22
因此,你需要最小化以下"triplet cost":
J = ∑ i = 1 m [ ∣ ∣ f ( A ( i ) ) − f ( P ( i ) ) ∣ ∣ 2 2 ⏟ (1) − ∣ ∣ f ( A ( i ) ) − f ( N ( i ) ) ∣ ∣ 2 2 ⏟ (2) + α ] + (3) \mathcal{J} = \sum^{m}_{i=1} \large[ \small \underbrace{\mid \mid f(A^{(i)}) - f(P^{(i)}) \mid \mid_2^2}_\text{(1)} - \underbrace{\mid \mid f(A^{(i)}) - f(N^{(i)}) \mid \mid_2^2}_\text{(2)} + \alpha \large ] \small_+ \tag{3} J=i=1m[(1) ∣∣f(A(i))f(P(i))22(2) ∣∣f(A(i))f(N(i))22+α]+(3)
在这里,我们使用符号" [ z ] + [z]_+ [z]+"表示 m a x ( z , 0 ) max(z,0) max(z,0)

注意:

  • 项(1)是给定三元组的锚示例“A”与正示例“P”之间的平方距离;期望最小化的值。
  • 项(2)是给定三元组的锚示例“A”和负示例“N”之间的平方距离,期望该值相对较大,因此在它前面有一个负号是有意义的。
  • α \alpha α称为边距。它是一个超参数可以手动调节。我们将使用 α = 0.2 \alpha = 0.2 α=0.2

大多数实现方法还需对编码向量进行标准化以使其范数等于1(即 ∣ ∣ f ( i m g ) ∣ ∣ 2 = 1 ||f(img)||_2=1 ∣∣f(img)2=1

练习:实现公式(3)定义的三元组损失。包含4个步骤:

  1. 计算“锚示例”和“正示例”编码之间的距离: ∣ ∣ f ( A ( i ) ) − f ( P ( i ) ) ∣ ∣ 2 2 \mid \mid f(A^{(i)}) - f(P^{(i)}) \mid \mid_2^2 ∣∣f(A(i))f(P(i))22
  2. 计算“锚示例”和“负示例”编码之间的距离: ∣ ∣ f ( A ( i ) ) − f ( N ( i ) ) ∣ ∣ 2 2 \mid \mid f(A^{(i)}) - f(N^{(i)}) \mid \mid_2^2 ∣∣f(A(i))f(N(i))22
  3. 根据每个训练示例计算公式:$ \mid \mid f(A^{(i)}) - f(P^{(i)}) \mid - \mid \mid f(A^{(i)}) - f(N^{(i)}) \mid \mid_2^2 + \alpha$
  4. 通过将最大值取为零并对训练示例求和来计算完整公式: J = ∑ i = 1 m [ ∣ ∣ f ( A ( i ) ) − f ( P ( i ) ) ∣ ∣ 2 2 − ∣ ∣ f ( A ( i ) ) − f ( N ( i ) ) ∣ ∣ 2 2 + α ] + (3) \mathcal{J} = \sum^{m}_{i=1} \large[ \small \mid \mid f(A^{(i)}) - f(P^{(i)}) \mid \mid_2^2 - \mid \mid f(A^{(i)}) - f(N^{(i)}) \mid \mid_2^2+ \alpha \large ] \small_+ \tag{3} J=i=1m[∣∣f(A(i))f(P(i))22∣∣f(A(i))f(N(i))22+α]+(3)

一些有用的函数:tf.reduce_sum(), tf.square(), tf.subtract(), tf.add(), tf.maximum()

对于步骤1和步骤2,你需要加上 ∣ ∣ f ( A ( i ) ) − f ( P ( i ) ) ∣ ∣ 2 2 \mid \mid f(A^{(i)}) - f(P^{(i)}) \mid \mid_2^2 ∣∣f(A(i))f(P(i))22 ∣ ∣ f ( A ( i ) ) − f ( N ( i ) ) ∣ ∣ 2 2 \mid \mid f(A^{(i)}) - f(N^{(i)}) \mid \mid_2^2 ∣∣f(A(i))f(N(i))22,而在第4步中,你需要将训练示例求总。

def triplet_loss(y_true, y_pred, alpha = 0.2):
    """
    根据公式(4)实现三元组损失函数
    
    参数:
        y_true -- true标签,当你在Keras里定义了一个损失函数的时候需要它,但是这里不需要。
        y_pred -- 列表类型,包含了如下参数:
            anchor -- 给定的"anchor"图像的编码,维度为(None, 128)
            positive -- "positive"图像的编码,维度为(None, 128)
            negative -- "negative"图像的编码,维度为(None, 128)
        alpha -- 超参数,阈值
    返回:
        loss -- 实数,损失的值
    """
    # 获取anchor、positive、negative的图像编码
    anchor, positive, negative = y_pred[0], y_pred[1], y_pred[2]
    
    # Step1:计算"anchor"与"positive"之间编码的距离,这里需要使用axis = -1
    pos_dist = tf.reduce_sum(tf.square(tf.subtract(anchor,positive)),axis=-1)
    
    # Step2:计算"anchor" 与 "negative"之间编码的距离,这里需要使用axis=-1
    neg_dist = tf.reduce_sum(tf.square(tf.subtract(anchor,negative)),axis=-1)
    
    # Step3:减去之前的两个距离,然后加上alpha
    basic_loss = tf.add(tf.subtract(pos_dist,neg_dist),alpha)
    
    # 通过取带零的最大值和对训练样本的求和来计算整个公式
    loss = tf.reduce_sum(tf.maximum(basic_loss,0))
    
    return loss
with tf.Session() as test:
    tf.set_random_seed(1)
    y_true = (None, None, None)
    y_pred = (tf.random_normal([3, 128], mean=6, stddev=0.1, seed = 1),
              tf.random_normal([3, 128], mean=1, stddev=1, seed = 1),
              tf.random_normal([3, 128], mean=3, stddev=4, seed = 1))
    loss = triplet_loss(y_true, y_pred)
    
    print("loss = " + str(loss.eval()))
loss = 528.14307

2 加载训练后的模型

通过最小化三元组损失来训练FaceNet。但是由于训练需要大量数据和计算,因此在这里我们不会从头开始进行训练。我们加载以前训练好的模型。使用以下单元格加载模型;这将花费几分钟才能运行。

FRmodel.compile(optimizer = 'adam', loss = triplet_loss, metrics = ['accuracy'])
load_weights_from_FaceNet(FRmodel)

在这里插入图片描述

三人的编码距离输出示例

现在,让我们使用此模型执行人脸验证和人脸识别!

3 模型应用

回到 the Happy House(数据集介绍可参考作业)! 自从你在较早的任务中实现了对房子的幸福感识别以来,居民就过着幸福的生活。

但是,有几个问题不断出现:快乐之家变得如此高兴,以至于附近的每个快乐的人都在你的客厅里闲逛。房屋变得很拥挤,这对里面的居民产生了负面影响。所有其他快乐的人也在吃你的食物。

因此,你决定更改门禁政策,不让随机快乐的人进入,即使他们Happy!相反,你想构建一个“人脸验证”系统,以便仅允许指定列表中的人员进入。要被录取,每个人都必须刷一张ID卡(识别卡)才能触发门上的面部识别系统,然后检查他们是否是本人。

3.1 人脸验证

让我们建立一个数据库,其中包含允许进入幸福屋的人的编码向量。我们使用img_to_encoding(image_path, model)生成编码,它基本上在指定的图像上运行模型的正向传播。

运行以下代码以构建数据库(以python字典表示)。该数据库将每个人的姓名映射为其面部的128维编码。

database = {}
database["danielle"] = img_to_encoding("images/danielle.png", FRmodel)
database["younes"] = img_to_encoding("images/younes.jpg", FRmodel)
database["tian"] = img_to_encoding("images/tian.jpg", FRmodel)
database["andrew"] = img_to_encoding("images/andrew.jpg", FRmodel)
database["kian"] = img_to_encoding("images/kian.jpg", FRmodel)
database["dan"] = img_to_encoding("images/dan.jpg", FRmodel)
database["sebastiano"] = img_to_encoding("images/sebastiano.jpg", FRmodel)
database["bertrand"] = img_to_encoding("images/bertrand.jpg", FRmodel)
database["kevin"] = img_to_encoding("images/kevin.jpg", FRmodel)
database["felix"] = img_to_encoding("images/felix.jpg", FRmodel)
database["benoit"] = img_to_encoding("images/benoit.jpg", FRmodel)
database["arnaud"] = img_to_encoding("images/arnaud.jpg", FRmodel)
WARNING:tensorflow:From d:\vr\virtual_environment\lib\site-packages\keras\backend\tensorflow_backend.py:422: The name tf.global_variables is deprecated. Please use tf.compat.v1.global_variables instead.

现在,当有人出现在你的前门并刷他们的身份证时,你可以在数据库中查找他们的编码,并用它来检查站在前门的人是否是本人。

练习:实现 verify() 函数,该函数检查前门摄像头拍摄到的图片(image_path)是否是本人。你需要执行以下步骤:

  1. 从image_path计算图像的编码
  2. 计算此编码和存储在数据库中的身份图像的编码的距离
  3. 如果距离小于0.7,打开门,否则不要打开。

如上所述,你应该使用L2距离(np.linalg.norm)。(注意:在此实现中,将L2距离而不是L2距离的平方与阈值0.7进行比较。)

def verify(image_path, identity, database, model):
    """
    对"identity"与"image_path"的编码进行验证。
    参数:
        image_path -- 摄像头的图片
        identity -- 字符类型,想要验证的人的名字。
        database -- 字典类型,包含了成员的名字信息与对应的编码。
        model -- 在Keras的模型的实例
    返回:
        dist -- 摄像头的图片与数据库中的图片的编码的差距
        is_open_door -- boolean,是否该开门。
    """
    # Step1:计算图像的编码,使用fr_utils.img_to_encoding()来计算。
    encoding = fr_utils.img_to_encoding(image_path, model)
    
    # Step2:计算与数据库中保存的编码的差距
    dist = np.linalg.norm(encoding - database[identity])
    
    # Step3:判断是否打开门
    if dist < 0.7:
        print("欢迎 " + str(identity) + "回家!")
        is_door_open = True
    else:
        print("经验证,您与" + str(identity) + "不符!")
        is_door_open = False
    
    return dist, is_door_open

尤恩斯(Younes)试图进入快乐之家,然后相机为他拍照(“images/camera_0.jpg”)。让我们在这张图片上运行你的验证算法:

verify("images/camera_0.jpg","younes",database,FRmodel)
欢迎 younes回家!





(0.6671406, True)

上周末破坏水族馆的Benoit已被禁止进入房屋,并已从数据库中删除。他偷了Kian的身份证,然后回到屋子里,试图把自己打扮成Kian。 前门摄像头拍摄了Benoit的照片(“images/camera_2.jpg”)。让我们运行验证算法来检查benoit是否可以进入。

verify("images/camera_2.jpg", "kian", database, FRmodel)
经验证,您与kian不符!





(0.85868865, False)

3.2 人脸识别

你的人脸验证系统在大部分情况下运行良好。但是自从肯恩(Kian)的身份证被盗以来,那天晚上他回到家中时,他进不了门了!

为了减少这种恶作剧,你想将人脸验证系统更改为人脸识别系统。这样,不再需要携带身份证。授权人员可以走到房屋前,前门将为他们解锁!

为此,你将实现一个人脸识别系统,该系统将图像作为输入,并确定该图像是否是授权人员之一。与以前的人脸验证系统不同,我们将不再获得一个人的名字作为其他输入。

练习:实现who_is_it(),你需要执行以下步骤:

  1. 从image_path计算图像的目标编码
  2. 从数据库中查找与目标编码距离最短的编码。
    • 将min_dist变量初始化为足够大的数字(100)。这将帮助你跟踪最接近输入编码的编码。
    • 遍历数据库字典的名称和编码。循环使用for (name, db_enc) in database.items()。
      • 计算目标“编码”与数据库中当前“编码”之间的L2距离。
      • 如果此距离小于min_dist,则将min_dist设置为dist,并将identity设置为name。
def who_is_it(image_path, database, model):
    """
    根据指定的图片来进行人脸识别
    
    参数:
        image_path -- 图像地址
        database -- 包含了名字与编码的字典
        model -- 在Keras中的模型的实例。
    返回:
        min_dist -- 在数据库中与指定图像最相交的编码。
        identity -- 字符串类型,与min_dist编码相对应的名字。
    """
    # Step1:计算指定图像的编码,使用fr_utils.img_to_encoding()来计算。
    encoding = fr_utils.img_to_encoding(image_path, model)
    
    # Step2 :找到最相近的编码
    ## 初始化min_dist变量为足够大的数字,这里设置为100
    min_dist = 100
    
    ## 遍历数据库找到最相近的编码
    for (name,db_enc) in database.items():
        ### 计算目标编码与当前数据库编码之间的L2差距。
        dist = np.linalg.norm(encoding - db_enc)
        
        ### 如果差距小于min_dist,那么就更新名字与编码到identity与min_dist中。
        if dist < min_dist:
            min_dist = dist
            identity = name
    
    # 判断是否在数据库中
    if min_dist > 0.7:
        print("抱歉,您的信息不在数据库中。")
        
    else:
        print("姓名" + str(identity) + "  差距:" + str(min_dist))
    
    return min_dist, identity

尤恩斯(Younes)在前门,相机为他拍照(“images/camera_0.jpg”)。让我们看看你的who_it_is()算法是否可以识别Younes。

who_is_it("images/camera_0.jpg", database, FRmodel)
姓名younes  差距:0.6671406





(0.6671406, 'younes')

你可以将"camera_0.jpg"(younes的图片)更改为"camera_1.jpg" (bertrand的图片),然后查看结果。

你的快乐之家运作良好。它只允许经过授权的人员进入,而人们也不再需要携带身份证!

现在你已经了解了最新的人脸识别系统是如何工作的。

尽管我们不会在这里实现它,但是这里有一些方法可以进一步改进算法:

  • 将每个人的更多图像(在不同的光照条件下,在不同的日子等拍摄的图像)放入数据库中。然后给定新图像,将新面孔与人物的多张图片进行比较以提高准确性。
  • 裁剪仅包含脸部的图像,并减少脸部周围的“边框”区域。该预处理去除了面部周围的一些无关像素,并且还使算法更加健壮。

你应该记住

  • 人脸验证解决了更简单的1:1匹配问题;人脸识别则解决了更难的1:K匹配问题。
  • 三元组损失是用于训练神经网络以学习面部图像编码的有效损失函数。
  • 相同的编码可用于验证和识别。通过测量两个图像的编码之间的距离,可以确定它们是否是同一个人的照片。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Puzzle harvester

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值