0
点赞
收藏
分享

微信扫一扫

人脸识别在Serverless架构下的应用

人脸识别技术介绍

早在1965年就有学者对人脸识别技术进行研究,并发表了相关的文章。但是由于计算机计算能力欠缺、人脸数据稀少等,人脸识别技术的研究没有很大的突破,也很少应用到现实生活中。1998年之后,计算机计算能力增强、数据来源广且规模越来越大,往年的研究成果逐渐得到实践。一个公认的标准往往会成为研究兴起的源头,如包含6000个人脸的LFW数据集的公开成为人脸识别算法发展的开端,各大企业如Facebook、face++、商汤科技等纷纷推出自己的方案,同时著名的算法如DeepID、FaceNet、Deepface等也流行开来。

在起初应用LFW数据集竞赛时期,Deepface、DeepID等都是基于十几层卷积网络的模型。如今计算能力越来越强,网络结构越来越深。然而在逐渐叠加网络层结构时发现,训练集上的精度反而下降了,效果比浅层网络还差。

ResNet在2015年图像分类竞赛得到了第一的成绩。常用的深层ResNet已经达到152层,它采用的抄近道的卷积网络连接方式缓解了神经网络叠加层数越多得到的结果越差的问题,并将计算机视觉算法带到了一个新的高度。从图像分类到物体检测,卷积模型几乎都使用了残差网络结构。

自ResNet提出后,Deepface等的浅层人脸识别模型渐渐被取代,深层网络结构的人脸识别模型渐渐成为主流。如今常用的人脸识别模型的卷积层都是基于ResNet结构,例如ResNet-101,即包含了101层卷积神经网络的残差网络结构。卷积神经网络从图像中提取部分到整体的特征,经过嵌入层(隐藏层数量较少的全连接神经网络)降维,再到分类层(隐藏层数量等于分类数量的全连接神经网络)得到分类结果。ResNet-101整个人脸识别模型框架如下所示。

人脸识别在Serverless架构下的应用_人脸识别

ResNet-101人脸识别模型框架 

显然与普通图像分类模型不同的是,人脸识别模型中加入了嵌入层。针对一批大小为batch_size的数据,假设嵌入层神经元数量为E,嵌入层会产生(batch_size,E)形状的输出(可以看作每个样本对应E长度的特征),相当于对卷积特征进行了降维,而这个特征长度E越大,包含的信息就越多,分类层可以得到的用于分类计算的信息也就越多。

人脸识别模型训练

1.数据准备

本节介绍人脸识别模型的训练。训练使用的数据集是“野外标记人脸”(见图5-20),是目前人脸识别的常用测试集,提供的人脸图像均来源于生活中的自然场景,因此识别难度大,尤其由于姿态、光照、表情、年龄、遮挡等的影响,即使同一人,照片差别也很大。有些照片中可能不止一个人脸,对这些多人脸图像处理方式是仅选择中心坐标的人脸作为目标,其他区域视为背景干扰。LFW数据集共有13233张人脸图像,每张图像均给出对应的人名,共有5749人,且绝大部分人仅有一张图片。每张图像的尺寸均为250×250,绝大部分图像为彩色,但也存在少许黑白色。本案例只选择同一个人的人脸数大于10的图像作为训练和验证。

人脸识别在Serverless架构下的应用_数据集_02

LFW数据集示例预览 

数据集的下载地址为​​http://vis-www.cs.umass.edu/lfw/lfw-funneled.tgz​​,下载后解压,可以看到它包含5700多个类别,先对它们进行过滤:

import os 
import shutil
import random
num_classes = 0
num_samples = 0
data_path = "./lfw_funneled"
output_path = "./lfw_funneled_filtered"
if not os.path.exists(output_path):
os.makedirs(output_path)
for _label in os.listdir(data_path):
label_path = os.path.join(data_path, _label)
if os.path.isfile(label_path):
continue
label_data_number = len(os.listdir(label_path))
# 过滤小于10张图的类
if label_data_number < 10:
continue
num_classes += 1
# 随机分配训练集和测试集
all_file_list = os.listdir(label_path)
num_samples += len(all_file_list)
train_file_list = random.sample(all_file_list, int(0.8 * len(all_file_list)))
test_file_list = [x for x in all_file_list if x not in train_file_list]
# 复制图像

def _copy(mode, _list):
for _file in _list:
# 随机选择图像来配置训练集和测试集
new_label_path = os.path.join(output_path, mode, _label)
if not os.path.exists(new_label_path):
os.makedirs(new_label_path)
shutil.copy(os.path.join(label_path, _file), os.path.join(new_label_path, _file))
_copy("train", train_file_list)
_copy("test", test_file_list)
print("总的数据量为:", num_samples)
print("类别数为:", num_classes)

上述代码运行完成后,可以看到如下输出:

总的数据量为:4324 类别数为:158

可见,本次训练的数据集包含4324张图像,共158个类别。准备好数据集之后,可以开始创建数据加载器。在第3章介绍过,PyTorch自带数据加载器,方便开发者进行数据遍历和数据处理:

import os 
import torch
import torchvision
import torch.nn as nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from torchvision.datasets import ImageFolder
from torch.utils.tensorboard import SummaryWriter
data_path = "./lfw_funneled_filtered"
tfs = transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225])])
batch_size = 16
train_data = ImageFolder(os.path.join(data_path, "train"), transform=tfs)
test_data = ImageFolder(os.path.join(data_path, "test"), transform=tfs)
train_loader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle= True, num_workers=4)
test_loader = DataLoader(dataset=test_data, batch_size=batch_size, shuffle=False, num_workers=4)

这里定义的数据预处理方法是:先调整图像大小到256×256,再以中心裁剪到224×224,之后做归一化处理,最终生成训练和测试的数据加载器。其中每个数据加载器的batch_size等于6,只有训练集做随机洗牌。

2.模型定义

本节使用的人脸识别模型是基于上一节介绍的常用人脸识别模型实现的,包含有ResNet卷积模型、嵌入层、全连接层等,并且使用了PyTorch框架。模型骨干上使用了resnet50,但由于PyTorch的上层算法库torchvision中已经包含resnet50模型,我们只需在此基础上进行改造。

从torchvision里加载resnet50作为人脸识别模型的骨干网络,该resnet50已经包含一个全连接层,且它的全连接是用于分类的,所以本案例中可以先忽略这一部分。根据前面介绍的常用人脸识别模型,定义一个嵌入层和分类层,其中嵌入层的输入为原本模型中分类层的输入,输出固定为512;分类层的输入为512,输出为人脸类别数:

class FaceNet(nn.Module):    
def __init__(self, num_classes=10):
super(FaceNet, self).__init__() s
elf.resnet = torchvision.models.resnet50(pretrained=True)
self.embedding = nn.Linear(self.resnet.fc.in_features, 512)
self.fc = nn.Linear(512, num_classes)

def forward(self, x):
x = self.resnet.conv1(x)
x = self.resnet.bn1(x)
x = self.resnet.relu(x)
x = self.resnet.maxpool(x)
x = self.resnet.layer1(x)
x = self.resnet.layer2(x)
x = self.resnet.layer3(x)
x = self.resnet.layer4(x)
x = self.resnet.avgpool(x)
x = torch.flatten(x, 1)
x = self.embedding(x)
x = self.fc(x)
return x

之后定义FaceNet对象,其中的num_classes来自上节得到的类别数,device指定使用什么设备进行训练。这里指定CPU也可以,因为数据量很小,且只需要2个Epoch就可以达到很高的精度:

# 定义TensorBoard 
writer = SummaryWriter("run/face-cls")
# 定义模型对象
face_model = FaceNet(num_classes)
# 绑定模型训练、推理的设备
device = torch.device('cpu')
model = face_model.to(device)
# 将模型加入TensorBoard
writer.add_graph(model, input_to_model=torch.ones(size=(1, 3, 224, 224)))

3.模型训练

在训练之前,需要先定义优化器和损失函数。对于优化器,可以选择常用的随机梯度下降法(SGD),学习率为0.001(因为是在预训练模型的基础上进行微调,所以不需要很大的学习率);对于损失函数,选择常规的交叉熵损失函数:

# 定义训练优化器为SGD,学习率为0.01

optimizer = optim.SGD(face_model.parameters(), lr=0.001, momentum=0.9, nesterov=True)

# 交叉熵损失函数

celoss = nn.CrossEntropyLoss().to(device)

现在开始构建训练的代码,在PyTorch中常规的训练模型流程如下。

·遍历地从训练数据加载器中拿到训练样本和对应的标签。

·将训练样本输入模型,得到前向传播的输出。

·将模型输出和样本标签输入损失函数计算损失。

·损失函数调用backward方法进行反向传播。

·更新优化器参数。

依照上述流程进行训练代码的构造:

# 定义模型训练次数 
num_epochs = 5
steps = 0
for epoch in range(num_epochs):
model.train()
# 获取训练样本和对应的标签
for x, label in train_loader:
steps += 1
x, label = x.to(device), label.to(device)
# 前向输出
logits = model(x)
# 计算损失
loss = celoss(logits, label)
# 反向传播
optimizer.zero_grad()
loss.backward()
# 更新优化器参数
writer.add_scalar("train_loss", loss.item(), global_step=steps)
optimizer.step()

此时,可以启动训练。值得一提的是,在训练过程中可以根据损失值判断模型是否训练完成,但是怎么判断模型的好坏呢?我们可在每个Epoch之后,进行模型的验证。同样,模型验证也有相应的流程。

·调用eval()方法固定模型参数,同时用torch.no_grad()方法关闭梯度计算。

·遍历地从测试数据加载器中拿到测试样本和对应的标签。

·将测试样本输入模型,得到前向传播的输出。

·处理模型输入结果,计算评价指标(这里以准确度为例,选择输出置信度最高的类,与标签进行对比,如果是一样则为正确,不一样则为错误,统计正确结果再除以总的样本数,可得到最终的准确度)。

·保存模型到本地。

依照上面流程进行模型验证代码的构造:

# 在每个Epoch之后进行模型验证 
model.eval() with torch.no_grad():
total_correct = 0
total_num = 0
for x, label in test_loader:
x, label = x.to(device), label.to(device)
# 前向传播
logits = model(x)
# 处理前向输出结果
pred = logits.argmax(dim=1)
correct = torch.eq(pred, label).float().sum().item()
total_correct += correct
total_num += x.size(0)
acc = total_correct / total_num
print("Epoch: {}, Loss: {}, Acc: {}".format(epoch, loss.item(), acc))
torch.save(model, "./lfw_funneled_{}.pth".format(epoch))

启动训练,可以看到如下训练日志,最终模型在测试集上的准确度为99.6%:

总的数据量为:1288 类别数为:7

开始训练...

Epoch: 0, Loss: 1.7181710004806519, Acc: 0.6398467432950191

Epoch: 1, Loss: 0.1985243707895279, Acc: 0.9770114942528736

Epoch: 2, Loss: 0.1355658620595932, Acc: 0.9770114942528736

Epoch: 3, Loss: 0.14260172843933105, Acc: 0.9846743295019157

Epoch: 4, Loss: 0.03598347306251526, Acc: 0.9961685823754789

通过TensorBoard可以看到训练过程中的Loss信息。如下所示,它是在逐渐收敛的,整体符合预期。

人脸识别在Serverless架构下的应用_人脸识别_03

训练过程与Loss变化曲线 

下图是整个人脸识别模型结构。

人脸识别在Serverless架构下的应用_数据集_04

人脸识别模型结构 

人脸识别模型的应用

在介绍完人脸识别模型训练后,我们可以对模型进行测试,先构建一个模型推理模块,以便对现有模型做前向推理。推理流程如下。

·推理模块接收模型路径,并初始化模型。

·推理模块接收推理数据,进行数据预处理。

·将处理完的数据送入模型,进行前向推理。

·获取推理结果进行后处理,如类别处理等,并返回最终结果。

根据上述流程编写推理代码,代码中的tfs为训练时使用的数据预处理方法(推理的数据预处理方法必须和模型验证时的方法一致):

train_loader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle= True) 
class_list = train_data.classes
# 保存标签
class_index = {"classes": class_list}
with open("./model/classes_index.json", "w") as f:
f.write(json.dumps(class_index))

class FaceModelPredictor(object):
def __init__(self, model_math=None):
self.model = torch.load(model_math)
self.model.eval()
self.device = torch.device('cpu')
self.model.to(self.device)
self.class_list = json.load(open("./model/classes_index.json", "r"))["clas-ses"]

def predict(self, image_path):
img = pimg.open(image_path)
img = torch.unsqueeze(tfs(img).to(self.device), 0)
result = self.class_list[self.model(img).argmax(dim=1).numpy()[0]]
return result

构造一个推理脚本进行测试:

def predict(model_path, data):    
model = FaceModelPredictor(model_path)
print(model.predict(data))

if __name__ == "__main__":
predict("./lfw_funneled_4.pth","./lfw_funneled_filtered/test/Donald_Rumsfeld/Donald_Rumsfeld_0005.jpg")

最终得到的结果为Donald_Rumsfeld,和图片的类别一致。

为了更加清晰地进行测试和可视化操作,整合上述推理代码,通过Python Web框架(Bottle框架)将模型包装为服务,并通过网页上传图片,单击“人脸预测”按钮即可实现人脸识别功能,如下所示:

人脸识别在Serverless架构下的应用_数据集_05

项目测试

测试结果 

项目Serverless化

1.部署前准备

由于目前阿里云Serverless产品已经支持容器镜像部署,因此通过容器镜像的方法将业务功能部署到Serverless架构,相对来说是比较容易的。首先编写相关的Dockerfile文件:

FROM python:3.7-slim

RUN pip install-r requirements.txt

# Create app directory

WORKDIR /usr/src/app

# Bundle app source

COPY . .

编写符合Serverless Devs规范的Yaml文件。对比前面的案例,在该项目的资源描述文档中新增了GPU的相关描述:

gpuMemorySize: 8192 instanceType: g1

这两个字段分别表示选择GPU实例、GPU实例的内存大小。通过GPU实例的引入,我们将人工智能模型训练与推理的工作下沉到GPU硬件,大大提高了效率。完整的资源描述如下:

# s.yaml 
edition: 1.0.0
name: face-recognition
access: default
services: face-recognition
component: fc
props:
region: cn-shanghai
service:
name: face-recognition
description: face-recognition service
function:
name: face-recognition-function
runtime: custom-container
caPort: 8080
codeUri: ./
timeout: 60
gpuMemorySize: 8192
instanceType: g1
customContainerConfig:
image: 'registry.cn-shanghai.aliyuncs.com/custom-container/face-recognition:0.0.1'
command: '["python"]'
args: '["index.py"]'
triggers:
- name: httpTrigger
type: http
config:
authType: anonymous
methods:
- GET
- POST
customDomains:
- domainName: auto
protocol: HTTP
routeConfigs:
- path: /*

2.项目部署

首先构建镜像,此处可以通过Serverless Devs进行构建:

s build --use-docker

构建完成之后,可以通过Serverless Devs工具直接进行部署:

s deploy --push-registry acr-internet --use-local -y

部署完成后还可以进一步进行预留相关操作,以最小化冷启动影响:

# 配置预留实例

$ s provision put --target 1 --qualifier LATEST

# 查询预留实例是否就绪

$ s provision get --qualifier LATEST

完成上述操作后,可以通过invoke命令进行函数的调用与测试,也可以通过返回的地址进行函数的可视化测试。

项目总结

图像处理技术被广泛地应用在各行各业,从传统的摄像、印刷、医学图像处理到新近的遥感卫星、汽车障碍标识,从简单的几何变换、图像融合到复杂的边缘检测、图像压缩。在海量数据的计算需求背景下,传统CPU渐渐不能胜任,衍生出异构计算需求。2007年,英伟达推出第一个可编程通用计算平台CUDA,不计其数的科研人员和开发者对大量算法进行改写,从而实现几十甚至几千倍的加速效果。传统的图形图像处理软件开发者更是充分利用GPU硬件进行加速,从而获得数倍的加速收益。本项目与之前部署的项目不同的是,在进行资源描述时,首次引入了GPU实例的概念,即部署到Serverless架构的GPU实例,以更快获得结果。在2021年云栖大会上,函数计算正式推出基于Turning架构的GPU实例。基于该架构的GPU实例,Serverless开发者可以将图形图像处理的工作下沉到GPU硬件,从而大大加快图形图像处理速度。基于GPU与CPU图形图像处理速度对比如下所示(数据源引自OpenCV官网)。

人脸识别在Serverless架构下的应用_数据_06

基于GPU与CPU图形图像处理速度对比简图 

基于阿里云预留实例与Serverless GPU模式的共同作用,图形图像处理类项目的性能提升速度高达10x~50x。该项目引入了很多冷启动优化方法,以及模型性能提升方法。但是,该项目仍然存在诸多值得优化的部分。

·静态资源与推理业务分离。尽管项目使用了Bottle框架作为测试,但是依旧对外暴露了相关页面,而这些页面又依赖某些静态资源,这就导致通过页面进行一次完整的测试需要多次触发实例。这种模式不仅更容易造成比较多的实例并发,进而加重冷启动影响,而且因实例每次触发都会涉及比较大的资源损耗,但是某些请求(例如请求某些JS文件、CSS文件)又不需要过多的资源支持,在一定程度上增加了成本。所以,合理地将静态资源与推理业务进行分离可以有效降低成本,降低冷启动带来的影响。

·容器镜像进一步精简。单纯就该项目而言,容器镜像的体积还是比较大的。镜像体积越大,冷启动时镜像拉取时间损耗越多,所以合理地对容器镜像进行精简有助于降低冷启动带来的影响。

 

举报

相关推荐

0 条评论