【华为云-上云之路】【2020华为云AI实战营】【每天进步一点点】基于ModelArts 实现人脸识别(MXNET)
什么是ModelArts? 来看看介绍

这里我们用到的就是ModelArts中的NoteBook,也用到了OBS桶。正好在ModelArts上做了一个人脸识别的云端实验,在这里分享一下执行过程,下面开始吧。
1. 创建NoteBook
选择左侧栏,点击“开发环境”-> “Notebook”->“创建”,如下图所示:

进入Notebook创建页面。参数:
① 计费方式:按需计费
② 名称:任意,如face_recognition
③ 自动停止:关闭
④ 工作环境:Python3
⑤ 资源池:公共资源池
⑥ 类型:GPU
⑦ 规格:GPU: 1*v100NV32 CPU: 8 核 64GiB
⑧ 存储配置:云硬盘⑧ 磁盘规格:默认

参数填写完毕后,点击“下一步”,查看Notebook实例预览信息,确认无误后点击“提交”。创建任务提交成功,点击页面的“返回Notebook列表”,返回Notebook列表页,等待Notebook创建成功,创建成功后状态会变成“运行中”,如下图所示:

2. 创建Notebook Python开发环境
点击“打开”按钮进入Notebook。点击“New” ->”MXNet 1.2.1”创建 Notebook Python开发环境。如下图所示:

重命名刚刚创建的Notebook Python开发环境。点击“Untitled”,我们可以填写一个跟本实验相关的名称,然后点击“Rename”按钮,如下图所示:

我们打印一行字符串,按Shift+Enter(该组合键是Notebook中执行代码的快捷键)或者点击下图“Run”。
print("hello notebook!")
查看代码执行结果。如下图所示:

3. 准备源代码和数据
相关资源已经保存在OBS中,通过ModelArts SDK将资源下载,并解压到当前目录下。解压后,当前目录包含src和data两个目录,分别存有源代码和数据集(可进入Notebook查看)。代码如下:
from modelarts.session import Session session = Session() if session.region_name=='cn-north-4': session.download_data(bucket_path='ai-course-common-20-bj4/face_recognition/face_recognition.tar', path='./face_recognition.tar') else: session.download_data(bucket_path='ai-course-common-20/face_recognition/face_recognition.tar', path='./face_recognition.tar') # 使用tar命令解压资源包 !tar xf ./face_recognition.tar
日志如下:
Successfully download file ai-course-common-20-bj4/face_recognition/face_recognition.tar from OBS to local ./face_recognition.tar
如下图所示:

执行成功,回到ModelArts“Notebook”列表即可看到资源,后面步骤生成的文件及文件夹同样可以这样查看,如下图所示:

4. 创建OBS桶
鼠标移动到云桌面浏览器页面中左侧菜单栏,点击“服务列表”-> 选择“存储”的“对象存储服务”,进入后右上角点击“创建桶”。
参数:
① 区域:华北-北京四
② 桶名称:自定义即可(需要记住此名称以备后续步骤使用)
③ 存储类别:标准存储
④ 桶策略:私有
⑤ 归档数据直读:关闭
⑥ 多AZ:关闭
点击右下角“立即创建”,如下图所示:

5. 配置信息
将运行结果上传至OBS中,设置相关的参数(使用自己真实的桶名替换掉*号):
• BUCKET_NAME:自己的OBS桶名。
切换至前面创建的Notebook Python开发环境界面,将下方代码拷贝粘贴并替换桶名称后,执行“run”,此段代码无输出。
BUCKET_NAME = '*' UNIQUE_ID = 'face-data' OBS_BASE_PATH = BUCKET_NAME + '/' + UNIQUE_ID
6. 导入基本信息
执行以下代码,导入使用的Python开发基本工具库,代码无输出。
MXNet框架是我们使用的AI编程框架;
argparse库用于解析外部输入参数;
src.face_resnet包含了人脸识别的卷积模型骨干。
import os
import mxnet as mx
import argparse
from src.face_resnet import get_symbol
import warnings
warnings.filterwarnings('ignore')
import logginglogger = logging.getLogger()
logger.setLevel(logging.INFO)
训练数据量和训练的关系说明:
per_batch_size:指在每个GPU上一次训练一批数据的大小,调整这个值越大,表示一次训练的数据量也就越大,这个公式代表了梯度更新的方法大小,
m为batch_size大小,
Var是指方差,显然当batch_size越大,方差也就越大,而方差越大,也就表示数据越稳定,在深度学习中也就表示训练得越稳定。
结合上面讲的学习率大小的调整,在显存可承受范围内,越大的batch_size使得学习更稳定情况下,学习率也可以随着调整得更大。
def parse_args():
    parser = argparse.ArgumentParser(description='Train face network')
    parser.add_argument('--data_dir', default='data',
                        help='training set directory')# 训练数据路径
    parser.add_argument('--train_url', type=str, default='ckpt',
                        help='train path')# 训练模型输出路径
    parser.add_argument('--num_epoch', type=int, default=20,
                        help='training epoch size.')# 训练epoch数量
    parser.add_argument('--num_layers', type=int, default=18,
                        help='specify network layer, 18 50 100')# 使用的ResNet卷积层层数
    parser.add_argument('--image_size', default='112,112',
                        help='specify input image height and width')# 输入图像的大小,高、宽
    parser.add_argument('--lr', type=float, default=0.005,
                        help='start learning rate')# 训练开始的学习率大小
    parser.add_argument('--wd', type=float, default=0.0005,
                        help='weight decay')# 权值衰减系数
    parser.add_argument('--mom', type=float, default=0.9,
                        help='momentum')# 动量
    parser.add_argument('--per_batch_size', type=int, default=8,
                        help='batch size in each context')# 每一个GPU的batch size大小
    parser.add_argument('--epoch_image_num', type=int, default=200,
                        help='image size in one epoch')# 训练样本大小
    parser.add_argument('--train_file', type=str, default='celeb_train.rec',
                        help='train')# 训练数据文件
    parser.add_argument('--val_file', type=str, default='celeb_val.rec',
                        help='val')# 验证集文件
    parser.add_argument('--num_classes', type=int, default=5,
                        help='classes number')# 分类数
    parser.add_argument('--num_embed', type=int, default=8,
                        help='embedding number')# 嵌入层神经元数量
    parser.add_argument('--num_gpus', type=int, default=1,
                        help='GPUs number')# GPU数量
    parser.add_argument('--save_frequency', type=int, default=1,
                        help='save frequency')# 保存模型的频率
    parser.add_argument('--export_model', type=bool, default=False,
                        help='change train url to model,metric.json')#是否改变train url结构
    
    args, _ = parser.parse_known_args()
    return args
args = parse_args()
# 模型所在的环境,指cpu或者哪块gpu ctx = [mx.gpu(x) for x in range(args.num_gpus)] if args.num_gpus>0 else [mx.cpu()] print(ctx)
设置超参,就是在训练前需要人工指定的参数。设置训练需要的超参:
- 设置模型训练的设备环境; 
- 设置训练时的batch size,固定图像大小的信息,和梯度归一化等参数; 
- 设置优化器参数; 
- 设置训练时的回调函数; 
- 设置模型参数初始化方法 
# 一次训练输入的batch size大小的数据
batch_size = int(args.per_batch_size * (args.num_gpus if args.num_gpus>0 else 1))
print('batch size: ', batch_size)
# 输入图像的高和宽
image_h, image_w = [int(x) for x in args.image_size.split(',')]
print('image height: ', image_h, ', image width: ', image_w)
# 梯度归一化参数,大小为1.0 / batch_size
_rescale = 1.0 / batch_size
print('rescale grad: ', _rescale)
# 优化器参数,包含学习率调度器、权值衰减系数等
optimizer_params = {'learning_rate': args.lr,
                    'wd' : args.wd,
                    'lr_scheduler': None,
                    'clip_gradient': 5,
                    'momentum' : args.mom,
                    'multi_precision' : True,
                    'rescale_grad': _rescale,
                    }
print(optimizer_params)
# 每一个step训练完后的操作 batch_end_callbacks = [mx.callback.Speedometer(batch_size, 10, auto_reset=True)] # 每一个epoch训练完后的操作 # 这里的模型保存路径为模型输出文件目录下的model目录,模型文件以face开头 epoch_end_callbacks = mx.callback.do_checkpoint( os.path.join(args.train_url,'model/face'), args.save_frequency) # 模型评估方法,准确度和交叉熵 eval_metrics = [mx.metric.Accuracy(), mx.metric.CrossEntropy()]
# 模型参数的初始化方法 initializer = mx.init.Xavier(rnd_type='gaussian', factor_type="out", magnitude=2)
7. 读取数据集,准备训练
人脸识别数据集使用的是明星人脸图像集CelebFace,经过人脸3D对齐等操作制作成了适合分类模型训练的数据,每张图像的大小为112*112,样本图像如下:

数据集格式为MXNet专用的rec格式,rec格式的数据集相比原图读取速度更快。使用MXNet自带的mx.io.ImageRecordIter方法读取rec格式数据集,mx.io.ImageRecordIter可以在读取数据时就实现数据增强的方法,这里做了随机翻转、数据集重新洗牌等增强。拷贝执行如下代码,这里定义数据加载器的对象,加载成功会打印输出 “data loaded!”。
# 训练集和验证集的rec文件路径
path_imgrec = os.path.join(args.data_dir, args.train_file)
path_val_imgrec = os.path.join(args.data_dir, args.val_file)
# 使用mxnet自带的mx.io.ImageRecordIter加载数据
val_dataiter = mx.io.ImageRecordIter(
        path_imgrec=path_val_imgrec,
        data_shape=(3, image_h, image_w),
        batch_size=batch_size,
        resize=image_w,                 # 图像按照其最短边resize到定义好的宽长
        shuffle=False,
        preprocess_threads=2)
train_dataiter = mx.io.ImageRecordIter(
        path_imgrec=path_imgrec,
        data_shape=(3, image_h, image_w),
        num_parts=1,                    # 训练的节点数
        part_index=0,                   # 本机所在的节点id
        batch_size=batch_size,
        resize=image_w,
        rand_crop=False,
        rand_mirror=True,              # 图像随机翻转
        shuffle=True,                  # 数据集随机洗牌
        preprocess_threads=2)
print('data loaded!')
import matplotlib.pyplot as plt
next_data_batch = next(iter(train_dataiter))
img = next_data_batch.data[0][0].transpose(axes=(2,1,0))
img = img.asnumpy().astype('uint8')
# 画图,数据增强后的图片
%matplotlib inline
fig = plt.figure()
plt.imshow(img)
plt.show()
搭建网络,人脸识别算法使用的模型骨干(backbone)为ResNet-50,基于ResNet-50模型构建人脸识别神经网络。src目录(打开的Notebook下可以查看)下的face_resnet.py源代码文件加载模型骨干,并将其分类层作为嵌入层,分类数设置为嵌入层神经元数。在嵌入层后面接一个分类数为num_classes的分类层,就可以得到本实验的人脸识别神经网络了。拷贝执行如下代码,这里会输出一个 mx.mod.Module 对象。
# 初始化模型参数为None,这里可以加载预训练的模型参数
arg_params = None
aux_params = None
# 得到嵌入层模型的Symbol
embedding = get_symbol(num_classes=args.num_embed, num_layers=args.num_layers)
# 设置分类层的权值
_weight = mx.symbol.Variable("fc7_weight",
                             shape=(args.num_classes, args.num_embed),
                             lr_mult=1.0, wd_mult=1.0)
_bias = mx.symbol.Variable('fc7_bias', lr_mult=2.0, wd_mult=0.0)
# 全连接层
fc7 = mx.sym.FullyConnected(data=embedding, weight = _weight,
                            bias = _bias, num_hidden=args.num_classes, name='fc7')
all_label = mx.symbol.Variable('softmax_label')
softmax = mx.symbol.SoftmaxOutput(data=fc7, label = all_label, name='softmax')
# 输入模型环境和Symbol
model = mx.mod.Module(context=ctx, symbol=softmax)
print(model)
开始训练
model_output_dir = args.train_url + '/model/' if os.path.exists(model_output_dir) == False: os.makedirs(model_output_dir) model.fit(train_dataiter, begin_epoch = 0, num_epoch = args.num_epoch, eval_data = val_dataiter, eval_metric = eval_metrics, kvstore = 'local', optimizer = 'nag', optimizer_params = optimizer_params, initializer = initializer, arg_params = arg_params, aux_params = aux_params, allow_missing = True, batch_end_callback = batch_end_callbacks, epoch_end_callback = epoch_end_callbacks )
部分日志如下:

8. 执行推理
模型推理共分为以下几个步骤:
- 导入读取图片所需的库; 
- 输出测试的原图; 
- 测试原图转换成模型需要的数据; 
- 加载推理使用的模型结构; 
- 推理计算并输出结果。 
代码如下:
import moxing as mox
# The obs_dst_path pattern: {BUCKET_NAME}/{UNIQUE_ID}/face_recognition/results/{TRAIN_URL}
obs_dst_path = "s3://" + OBS_BASE_PATH + "/face_recognition/results/" + args.train_url + '/'
mox.file.copy_parallel(args.train_url+'/model',
                      obs_dst_path)
import mxnet.image as image import moxing as mox import mxnet as mx import matplotlib.pyplot as plt import os
# 设置类别名,由于不能知道每张人脸的真实名字,用数字来替代
class_names = range(5)
# 人脸图像路径,这里可以任意选择一张人脸图像,并上传到自己的OBS路径
# 如s3://ai-course-001/face_recognition/data/下的test.png
file_name = args.data_dir + '/test.png'
# 以二进制形式,将图片读入内存
img = mox.file.read(file_name, binary=True)
# 查看图片
orig_img = mx.img.imdecode(img, to_rgb=1).asnumpy().astype('uint8')
plt.imshow(orig_img)
plt.show()
# 图像处理 # 将图像处理成(3,112,112)大小 [h, w] = [112, 112] img_arr = mx.img.imdecode(img, to_rgb=1) # 首先转换成NDArray形式 img_arr = mx.img.resize_short(img_arr, w, 0) # 翻转,经过翻转的图像不能通过plt显示 img_arr = mx.nd.transpose(img_arr, (2, 0, 1)) img_arr = mx.nd.expand_dims(img_arr, axis=0) # 将图像绑定到cpu上 d = [img_arr.as_in_context(mx.cpu())] print(d)
# 模型文件位置,比如由上面训练得到的位置train_url+'model',
# 这个路径位置下面必须有.json和.params的模型文件
ckpt = os.path.join(args.train_url, 'model/face')
# 加载第几个epoch
load_epoch = 1
# 加载模型文件,sym为推理模型结构,arg_params, aux_params为模型参数
sym, arg_params, aux_params = mx.model.load_checkpoint(ckpt, load_epoch)
# 选择使用cpu进行推理,数据名默认为data,推理无需输入label
mx_model = mx.mod.Module(symbol=sym, context=mx.cpu(),
                                      data_names=['data'], label_names=None)
# 绑定模型计算模块到底层计算引擎,绑定的数据大小默认为(1,3,112,112)
mx_model.bind(for_training=False, data_shapes=[('data',(1,3,112,112))])
# 设置模型的参数
mx_model.set_params(arg_params, aux_params, allow_missing=True)
# 模型结果后处理方法
def _postprocess(data):
    dim = len(data[0].shape)
    if dim > 2:
        data = mx.nd.array(np.squeeze(data.asnumpy(), axis=tuple(range(dim)[2:])))
    # 将得到的结果排序,选择可能性最大的类别
    sorted_prob = mx.nd.argsort(data[0], is_ascend=False)
    top_prob = list(map(lambda x: int(x.asscalar()), sorted_prob[0:2]))
    # 输出推理得到的类别和对应可能性
    return {"predicted_label": class_names[top_prob[0]],
            "scores": [[class_names[i], float(data[0, i].asscalar())] for i in top_prob]}
# 进行前向推理
mx_model.forward(mx.io.DataBatch(d))
# 输出
print(_postprocess(mx_model.get_outputs()[0]))
最后得到输出

至此,基于ModelArts的MXNET框架的人脸识别完成了,包括训练和推理,快来试试吧。最后附上完成代码,在附件,下载解压缩后,上传face_recognition.ipynb到NoteBook中,逐步运行即可。
- 点赞
- 收藏
- 关注作者
 
             
            
                        
评论(0)