基于PalldeClas实现表情分析

一、项目简介

  大家是否曾经有过这样的疑惑,在和别人交流时,不知道和你谈话的人心里是怎么想的,他的表情究竟代表的是什么意思,即使是心理学家也无法完全看透人的表情,不过我们在今天可以训练一个AI小助手,它会帮你捕捉他人的表情并进行分析。本项目经过数据准备、数据加载、模型训练、模型评估、模型预测的完成流程,相信小伙伴们看到最后,一定对图像识别有着很好的理解,那么话不多说,我们开始。

二、PaddleClas介绍

  众所周知,PaddleClas是飞桨研发专门针对实现图像分类的套件,PaddleClas中包括了多达23个系列的模型结构、ImageNet1k验证集精度82.4%的ResNet50_vd模型、10W类的图像分类预训练模型,同时提供了知识蒸馏以及丰富的数据增广方案,帮助图像分类模型进一步获得性能提升。

图像分类任务简介

  图像分类是根据图像的语义信息将不同类别图像区分开来,是计算机视觉中重要的基础任务。
  图像分类在很多领域有广泛应用,包括零售商品分类、农作物品质分级、医学领域的图像识别、交通标志分类等。作为计算机视觉任务的基础,还可辅助目标检测、图像分割、物体跟踪、行为分析等其他高层视觉任务组网和效果提升,比如经典的目标检测模型FasterRCNN的结构,骨干网络即使用分类任务中的网络结构,如ResNet。作为骨干网络,分类任务的预训练模型也可以帮助其他视觉任务在训练时更快地收敛。

如果有想更详细了解的小伙伴可以戳下方链接慢慢了解:

PaddleClas github地址:

https://github.com/PaddlePaddle/PaddleClas/

PaddleClas教程文档地址:

https://paddleclas.readthedocs.io/zh_CN/latest/index.html

解压PaddleClas

PaddleClas需要大家从github上clone下载,速度有时会比较慢,这里已经为大家准备好了,放在data目录下,大家自己解压使用就好,如果有想克隆的小伙伴可以自己克隆,代码如下:git clone https://github.com/PaddlePaddle/PaddleClas.git
!unzip -oq data/data114890/PaddleClas.zip -d /home/aistudio/

三、数据集介绍

本项目用的是fer2013表情数据集,一共有六类,分别是:happy , Fear ,normal,surprised , Sad , Angry,共有30000+张图片,图片数量比较多,加载时间比较长,请小伙伴们耐心等待
  • 需要注意的是:
    • 数据集存放路径要与配置文件中一直,不要忘记修改
    • 数据列表文件中路径与标签之间的空格要划分准确
    • 数据列表文件中不要包含其他文件

解压数据集

数据集已经给大家放在PaddleClas/dataset目录下,因为之后在训练过程中放在dataset下面会比较方便训练,所以这边直接给大家放好了
!unzip -oq /home/aistudio/PaddleClas/dataset/fer62013.zip
!mv fer62013 PaddleClas/dataset
from PIL import Image

# 读取图片
png_img = Image.open('PaddleClas/dataset/fer62013/Angry/Angry-2039.jpg')
png_img  # 展示图片

四、 数据处理

大家不难发现,数据集的格式是每类图片集的文件名就是图片的lable,而且为了模拟真实的情况,所有的文件都没有标注,需要我们进行数据集和验证集的划分
  • 需要注意的是:
    • 数据集存放的位置与生成的数据列表文件中的数据路径需要与配置文件对应,这也是初学者时常出现问题的地方。
    • 数据列表文件中路径与标签之间的分割符号,行与行之间的换行符号
    • 有些特定字符转义之后出现问题
#划分数据集
import codecs
import os
import random
import shutil
from PIL import Image

train_ratio = 4.0 / 5
all_file_dir = 'PaddleClas/dataset/fer62013'
class_list = [c for c in os.listdir(all_file_dir) if os.path.isdir(os.path.join(all_file_dir, c)) and not c.endswith('Set') and not c.startswith('.')]
class_list.sort()
print(class_list)
train_image_dir = os.path.join(all_file_dir, "trainImageSet")
if not os.path.exists(train_image_dir):
    os.makedirs(train_image_dir)
    
eval_image_dir = os.path.join(all_file_dir, "evalImageSet")
if not os.path.exists(eval_image_dir):
    os.makedirs(eval_image_dir)

train_file = codecs.open(os.path.join(all_file_dir, "train.txt"), 'w')
eval_file = codecs.open(os.path.join(all_file_dir, "eval.txt"), 'w')

with codecs.open(os.path.join(all_file_dir, "label_list.txt"), "w") as label_list:
    label_id = 0
    for class_dir in class_list:
        label_list.write("{0}\t{1}\n".format(label_id, class_dir))
        image_path_pre = os.path.join(all_file_dir, class_dir)
        for file in os.listdir(image_path_pre):
            try:
                img = Image.open(os.path.join(image_path_pre, file))
                if random.uniform(0, 1) <= train_ratio:
                    shutil.copyfile(os.path.join(image_path_pre, file), os.path.join(train_image_dir, file))
                    train_file.write("{0} {1}\n".format(os.path.join("trainImageSet", file), label_id))
                else:
                    shutil.copyfile(os.path.join(image_path_pre, file), os.path.join(eval_image_dir, file))
                    eval_file.write("{0} {1}\n".format(os.path.join("evalImageSet", file), label_id))
            except Exception as e:
                pass
                # 存在一些文件打不开,此处需要稍作清洗
        label_id += 1
            
train_file.close()
eval_file.close()

查看划分的情况

这里看到我们已经划分完整
!tree -d PaddleClas/dataset
! head PaddleClas/dataset/fer62013/train.txt

五、模型选择

整体思路

  1. 选择使用的模型,根据自己的任务难度及数据的特点,选择适合自己数据的模型进行训练,本文选择ResNet_vd模型训练关于ResNet网络结构的代码分析可移步这里查看ResNet网络结构代码分析
  2. 根据选定的模型更改PaddleClas/configs下对应的配置文件参数。例如类别数,迭代次数,batch_size,数据集路径等等。为了查看方便本文将PaddleClas/configs/ResNet/ResNet50_vd.yaml移只根目录改名为neu.yaml,在训练时,修改配置文件的路径即可。
  3. 利用更改后的配置文件训练模型,使用训练技巧与不同的参数设置调优模型并选择最优模型进行推理预测(更多的训练技巧与参数设置,可以关注 https://paddleclas.readthedocs.io/zh_CN/latest/models/Tricks.html 其对相关参数进行了详细的解释说明)
  • 下面逐步说明各部分:

mix处理

六、 训练模型

训练模型分为一下几个步骤:

  1. 修改配置文件。查看使用模型对应的配置文件并修改配置文件中一些必要的参数,本项目已经根据数据集适配了配置文件fer.yaml
  2. 切换路径,设置环境变量
  3. 训练模型

配置文件说明

想必经过前面的介绍,大家已经能明白配置文件中各个参数的意思了,现在写一个新的配置文件为fer.yaml以及eval.yaml用作训练和评估的配置文件

基础配置

mode: "train"  运行模式 2中选择["train"," valid"]
ARCHITECTURE: 模型结构名字
    name: 'ResNet50_vd'
pretrained_model: "" 预训练模型路径
model_save_dir: "./output/" 保存模型路径
classes_num: 6 分类数
total_images: 1800 总图片数
save_interval: 10 每隔多少个epoch保存模型
validate: True 是否在训练时进行评估
valid_interval: 10 每隔多少个epoch进行模型评估
epochs: 400 训练总epoch数
topk: 5 评估指标K值大小
image_shape: [3, 224, 224] 图片大小
use_mix: True 是否启用mixup
ls_epsilon: 0.1 label_smoothing epsilon值

学习率与优化配置

LEARNING_RATE:
    function: 'Cosine' decay方法名  ["Linear", "Cosine","Piecewise", "CosineWarmup"]         
    params: 初始学习率     大部分的神经网络选择的初始学习率为0.1,batch_size是256,所以根据实际的模型大小和显存情况,可以将学习率设置为0.1*k,batch_size设置为256*k              
        lr: 0.1   
*还可设置的参数
params:
	decayepochs	 piecewisedecay中衰减学习率的milestone
params:
	gamma	    piecewisedecay中gamma值	
params:
	warmupepoch	 warmup轮数	
parmas:
	steps	    lineardecay衰减steps数	
params:
	endlr	    lineardecayendlr值	

OPTIMIZER:
    function: 'Momentum' 优化器方法名 ["Momentum", "RmsProp"]
    params:
        momentum: 0.9 momentum值
    regularizer:
        function: 'L2' 正则化方法名	
        factor: 0.000070 正则化系数

训练配置

TRAIN:
    batch_size: 32 批大小
    num_workers: 4 数据读取器worker数量
    file_list: "./dataset/NEU-CLS/train.txt" train文件列表
    data_dir: "./dataset/NEU-CLS" train文件路径
    shuffle_seed: 0 用来进行shuffle的seed值
    transforms: 数据处理
        - DecodeImage:
            to_rgb: True 数据转RGB
            to_np: False 数据转numpy
            channel_first: False 按CHW排列的图片数据
        - RandCropImage: 随机裁剪
            size: 224
        - RandFlipImage: 随机翻转
            flip_code: 1
        - NormalizeImage:
            scale: 1./255. 归一化scale值
            mean: [0.485, 0.456, 0.406] 归一化均值
            std: [0.229, 0.224, 0.225] 归一化方差
            order: '' 归一化顺序
        - ToCHWImage: 调整为CHW
    mix:                       
        - MixupOperator:    
            alpha: 0.2      
*还可设置的参数
-CropImage	
	size:	裁剪大小
-ResizeImage	
	resize_short:	按短边调整大小

测试配置

VALID:
    batch_size: 64
    num_workers: 4
    file_list: "./dataset/NEU-CLS/eval.txt"
    data_dir: "./dataset/NEU-CLS"
    shuffle_seed: 0
    transforms:
        - DecodeImage:
            to_rgb: True
            to_np: False
            channel_first: False
        - ResizeImage:
            resize_short: 256
        - CropImage:
            size: 224
        - NormalizeImage:
            scale: 1.0/255.0
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
        - ToCHWImage:
%%writefile fer.yaml
mode: "train"
ARCHITECTURE:
    name: 'ResNet50_vd'

pretrained_model: ""
model_save_dir: "./output/"
classes_num: 6
total_images: 10000
save_interval: 10
validate: True
valid_interval: 10
epochs: 201
topk: 5
image_shape: [3, 224,224]

use_mix: True
ls_epsilon: 0.1

LEARNING_RATE:
    function: 'CosineWarmup'
    params:                   
        lr: 0.1
        warmupepoch : 201
     

OPTIMIZER:
    function: 'Momentum'
    params:
        momentum: 0.9
    regularizer:
        function: 'L2'
        factor: 0.000070
TRAIN:
    batch_size: 128
    num_workers: 4
    file_list: "./dataset/fer62013/train.txt"
    data_dir: "./dataset/fer62013"
    shuffle_seed: 0
    transforms:
        - DecodeImage:
            to_rgb: True
            to_np: False
            channel_first: False
        - RandCropImage:
            size: 224
        - RandFlipImage:
            flip_code: 1
        - NormalizeImage:
            scale: 1./255.
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
        - ToCHWImage:
    mix:                       
        - MixupOperator:    
            alpha: 0.2      

VALID:
    batch_size: 512
    num_workers: 4
    file_list: "./dataset/fer62013/eval.txt"
    data_dir: "./dataset/fer62013"
    shuffle_seed: 0
    transforms:
        - DecodeImage:
            to_rgb: True
            to_np: False
            channel_first: False
        - ResizeImage:
            resize_short: 256
        - CropImage:
            size: 224
        - NormalizeImage:
            scale: 1.0/255.0
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
        - ToCHWImage:
%%writefile eval.yaml
mode: 'valid'
ARCHITECTURE:
    name: "ResNet50_vd"

pretrained_model: "./pretrained_model/ResNet50_vd_pretrained"
classes_num: 6
total_images: 369
topk: 5
image_shape: [3, 224, 224]

VALID:
    batch_size: 64
    num_workers: 4
    file_list: "./dataset/fer62013/eval.txt"
    data_dir: "./dataset/fer62013/"
    shuffle_seed: 0
    transforms:
        - DecodeImage:
            to_rgb: True
            to_np: False
            channel_first: False
        - ResizeImage:
            resize_short: 256
        - CropImage:
            size: 224
        - NormalizeImage:
            scale: 1.0/255.0
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
        - ToCHWImage:

设置环境变量

%cd PaddleClas
import os 
os.environ['PYTHONPATH']="/home/aistudio/PaddleClas"

关于预训练模型

  • 如需使用预训练模型,则执行下面的脚本,并在训练脚本后增加 -o pretrained_model=./ResNet50_vd_pretrained,不使用预训练模型则跳过下面的执行语句。
  • 预训练模型我已经保存在主目录下,大家放心使用
!python ../download_model.py ResNet50_vd_pretrained
!mv ../ResNet50_vd_pretrained ./

开始训练

  • 执行训练脚本,使用已经配置好的fer.yaml文件
  • 训练脚本指令如下:PaddleClas通过launch方式启动多卡多进程训练 通过设置FLAGS_selected_gpus 指定GPU运行卡号
python -m paddle.distributed.launch  --selected_gpus="0" tools/train.py  -c ./configs/ResNet/ResNet50_vd.yaml
#!python -m paddle.distributed.launch --selected_gpus="0"  tools/train.py -c ../fer.yaml -o pretrained_model=./ResNet50_vd_pretrained
!python -m paddle.distributed.launch --selected_gpus="0"  tools/train.py -c ../fer.yaml -o pretrained_model=./ResNet50_vd_pretrained

七、 模型评估

大家看上面的训练日志是不是特别长?找不到模型最优的准确度,PaddleClas通过launch方式也可以进行评估。

首先需要修改评估所需的配置文件,修改方式有两种,

  1. 直接修改configs/eval.yaml
  2. -o参数更新配置
  • 这里建议直接修改configs/eval.yaml,使用-o设置需要评估的模型路径 较为方便

  • 评估脚本代码如下:需要注意的是加载模型时,需要指定模型的前缀,如模型参数所在的文件夹为output/ResNet50_vd/19,模型参数的名称为output/ResNet50_vd/19/ppcls.pdparams,则pretrained_model参数需要指定为output/ResNet50_vd/19/ppcls,PaddleClas会自动补齐.pdparams的后缀。

!python -m paddle.distributed.launch --selected_gpus="0" tools/eval.py \
    -c ./configs/eval.yaml \
    -o pretrained_model=output/ResNet50_vd/best_model/ppcls

评估模型

!python -m paddle.distributed.launch --selected_gpus="0" tools/eval.py \
    -c ../eval.yaml \
    -o pretrained_model=output/ResNet50_vd/best_model/ppcls

评估结果展示

2020-08-03 23:58:36 INFO: eval step:0    loss:  0.4387 top1: 0.8906 top5: 1.0000 elapse: 0.690s
2020-08-03 23:58:37 INFO: END eval loss_avg:  0.6619 top1_avg: 0.9000 top5_avg: 0.9969 elapse_sum: 1.102ss
INFO 2020-08-03 15:58:39,735 launch.py:223] Local procs complete, POD info:rank:0 id:None addr:127.0.0.1 port:None visible_gpu:[] trainers:["gpu:['0'] endpoint:127.0.0.1:56541 rank:0"]

上面展示的结果是笔者训练201个epochs,在测试集上top1的准确率为0.9000,经过测试在进行400次迭代之后会达到100%的准确率。

关于PaddlePaddle模型的保存方式

  1. persistable 模型(fluid.save_persistabels保存的模型) 一般做为模型的 checkpoint,可以加载后重新训练。persistable 模型保存的是零散的权重文件,每个文件代表模型中的一个 Variable,这些零散的文件不包含结构信息,需要结合模型的结构一起使用。
  2. inference 模型(fluid.io.save_inference_model保存的模型) 一般是模型训练完成后保存的固化模型,用于预测部署。与 persistable 模型相比,inference 模型会额外保存模型的结构信息,用于配合权重文件构成完整的模型。如下所示,model 中保存的即为模型的结构信息。

八、 模型推理

由于模型的保存方式以及选择引擎的不同,PaddlePaddle衍生出三种方式进行预测推理:

  • 预测引擎 + inference 模型
  • 训练引擎 + persistable 模型
  • 训练引擎 + inference 模型

本文选择使用 预测引擎 + inference模型 的方式进行推理,执行步骤如下:

  1. 对训练好的模型进行转换固化
  2. 通过预测引擎和inference模型进行推理

保存 inference 模型

在训练过程中保存的persistable 模型中选择最优模型,本文设置在训练的同时进行模型评估并保存效果最好的persistable模型,命令如下:

python tools/export_model.py --m=模型名称 --p=persistable 模型路径 --o=model和params保存路径

预测引擎 + inference 模型推理预测:

命令如下:

python ./tools/infer/predict.py -i=./test.jpeg -m=./resnet50-vd/model -p=./resnet50-vd/params --use_gpu=1

需要注意的是对应的模型权重文件路径需要确认,其他的参数设置如下所示:

参数说明:

  • image_file(简写 i):待预测的图片文件路径,如 ./test.jpeg

  • model_file(简写 m):模型文件路径,如 ./resnet50-vd/model

  • params_file(简写 p):权重文件路径,如 ./resnet50-vd/params

  • batch_size(简写 b):批大小,如 1

  • ir_optim:是否使用 IR 优化,默认值:True

  • use_tensorrt:是否使用 TesorRT 预测引擎,默认值:True

  • gpu_mem: 初始分配GPU显存,以M单位

  • use_gpu:是否使用 GPU 预测,默认值:True

  • enable_benchmark:是否启用benchmark,默认值:False

  • model_name:模型名字

  • 注意: 当启用benchmark时,默认开启tersorrt进行预测

  • 为了模拟真实性,我们直接从手机相册里拿出来一张隔壁老王的照片试试,放在PaddleClas/test.PNG中

!python tools/export_model.py \
    --model='ResNet50_vd' \
    --pretrained_model=output/ResNet50_vd/best_model/ppcls \
    --output_path=./inference
!python tools/infer/predict.py \
    -m inference/model \
    -p inference/params \
    -i "test.PNG" \
_model/ppcls \
    --output_path=./inference
!python tools/infer/predict.py \
    -m inference/model \
    -p inference/params \
    -i "test.PNG" \
    --use_gpu=1

预测结果展示

预测结果展示:

2020-08-03 15:58:48,332-INFO: class: 2
2020-08-03 15:58:48,332-INFO: score: 0.9259640336036682
0	Angry
1	Fear
2	Happy
3	Neutral
4	Sad
5	Suprise

可以看到预测正确,图片的标签为2,与label.txt对应happy

九、 总结

  1. 本文使用了PaddleClas进行表情分析识别任务
  2. 后续会进行落地部署,请大家耐心等待

个人简介

本人来自江苏科技大学本科三年级,刚刚接触深度学习不久希望大家多多关注

感兴趣的方向:目标检测,强化学习,自然语言处理、

个人链接:
马骏骁

https://aistudio.baidu.com/aistudio/personalcenter/thirdview/824948

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐