飞桨框架初体验——人脸识别

前言

笔者最近在尝试接触飞桨框架
感觉百度开发的这个框架性能,架构都还是很不错的
相比其他的框架简洁很多
并且官网还有一些项目可供学习
今天写这篇博客是基于https://aistudio.baidu.com/aistudio/projectdetail/44338人脸识别项目
并对总体进行一个总结

图像数据处理

图像数据有很多种处理方法
该项目选择以字典形式进行存储

import os
import json

data_path = './images/face'
class_detail = []
class_dirs = os.listdir(data_path)
# print(class_dirs)
class_label = 0
father_paths = data_path.split('/')    #['', 'home', 'aistudio', 'images', 'face']
while True:
    if father_paths[father_paths.__len__() - 1] == '':
        del father_paths[father_paths.__len__() - 1]
    else:
        break
father_path = father_paths[father_paths.__len__() - 1]
# print(father_path)
data_list_path = './%s/'%father_path
isexist = os.path.exists(data_list_path)
if not isexist:
    os.makedirs(data_list_path)

with open(data_list_path + "test.list", 'w') as f:
    pass

with open(data_list_path + "trainer.list", 'w') as f:
    pass
all_class_images = 0
for class_dir in class_dirs:
    class_detail_list = {}
    test_sum = 0
    trainer_sum = 0
    class_sum = 0
    path = data_path + '/' + class_dir
    img_paths = os.listdir(path)

    for img_path in img_paths:
        name_path = path + '/' + img_path
        if class_sum % 10 == 0:
            test_sum += 1
            with open(data_list_path + "test.list", 'a') as f:
                f.write(name_path + "\t%d"%class_label +'\n')
        else:
            trainer_sum +=1
            with open(data_list_path + "trainer.list", 'a') as f:
                f.write(name_path + "\t%d"%class_label + '\n')

        class_sum += 1
        all_class_images += 1

    class_detail_list['class_name'] = class_dir
    class_detail_list['class_label'] = class_label
    class_detail_list['class_test_images'] = test_sum
    class_detail_list['class_trainer_images'] = trainer_sum
    class_detail.append(class_detail_list)
    class_label += 1
all_class_sum = class_dirs.__len__()

readjson = {}
readjson['all_class_name'] = father_path                  #文件父目录
readjson['all_class_sum'] = all_class_sum                #
readjson['all_class_images'] = all_class_images
readjson['class_detail'] = class_detail
jsons = json.dumps(readjson, sort_keys=True, indent=4, separators=(',', ': '))
with open(data_list_path + "readme.json",'w') as f:
    f.write(jsons)
print ('生成数据列表完成!')

它数据集有三类人脸,并以9:1的比例划分训练集和测试集
然后以字典形式存储
face目录下会多出三个文件
分别是readme.json test.list trainer.list

总体框架搭建

框架简介

飞桨框架提供了很多工具库来进行模型搭建
主要流程如下:
1.对数据进行处理
2.编写一个文件reader进行批量读入,从而避免内存爆炸
3.搭建网络结构
4.定义占位符,优化器,损失值
5.编写训练流程函数

它也是基于图机制的框架,所以大体操作跟tensorflow,mxnet类似、

图片修剪

数据集图片大小不一,我们进行统一的修剪

import paddle
import paddle.fluid as fluid
import numpy as np
import sys
import os
from multiprocessing import cpu_count
import matplotlib.pyplot as plt

def train_mapper(sample):
    """
    :param sample:是一个元组,包含图片路径以及标签
    :return:
    """
    img, label = sample
    img = paddle.dataset.image.load_image(img)
    # 对图片进行统一修剪
    img = paddle.dataset.image.simple_transform(im=img,
                                                resize_size=100,
                                                crop_size=100,
                                                is_color=True,
                                                is_train=True)

    img = img.flatten().astype('float32')/255.0
    return img, label

我们通过调用simple_transform进行图片修剪, is_color为true代表的是彩色图片

定义reader

这是一个比较关键的一部分,刚开始学习该框架我也不是很懂得reader的编写方法
其实他就是返回一个有数据迭代器的函数
再通过调用xmap_reader,传入你自定义的函数,得到的就是一个reader

# 定义图片读取的reader
def train_r(train_list, buffered_size=1024):
    def reader():
        with open(train_list, 'r') as f:
            lines = [line.strip() for line in f]
            for line in lines:
                img_path, lab = line.strip().split('\t')
                yield img_path, lab

    return paddle.reader.xmap_readers(train_mapper, reader, cpu_count(), buffered_size)

该操作是打开我们之前写好的list文件
然后进行分行,我们是用制表符\t来隔开label和path的
所以使用split方法分别得到path和label
最后再使用yield 方法迭代img_path, lab

再总方法最后
调用paddle.reader.xmap_readers, 第一个参数传递的是我们的预处理函数mapper,也就是刚刚我们定义的图像修剪函数,第二个传递的是我们自定义的reader函数,第三个传递的是处理线程数目,这里我们调用multiprocessing的cpu_count()方法获得线程数量,注:本项目训练量并不是很大,完全可以通过cpu进行训练。第四个传入的是缓冲大小

同样我们对测试集进行相同的处理,编写mapper和reader函数
注意这里在transform这里把is_train置为False

def test_mapper(sample):
    img, label = sample
    img = paddle.dataset.image.load_image(img)
    img = paddle.dataset.image.simple_transform(im=img, resize_size=100,crop_size=100, is_color=True,
                                                is_train=False)
    img = img.flatten().astype('float32')/255.0
    return img, label

def test_r(test_list, buffered_size=1024):
    def reader():
        with open(test_list, 'r') as f:
            lines = [line.strip() for line in f]
            for line in lines:
                img_path, lab = line.strip().split('\t')
                yield img_path, lab

    return paddle.reader.xmap_readers(train_mapper, reader, cpu_count(), buffered_size)

然后我们进行调用这几个函数生成相应对象,并利用paddle.batch类让其能以批数量的形式进行数据的读取

BATCH_SIZE = 32
trainer_reader = train_r(train_list='./face/trainer.list')
trainer_reader = paddle.batch(paddle.reader.shuffle(
    reader=trainer_reader, buf_size=300),
    batch_size=BATCH_SIZE
)

tester_reader = test_r(test_list='./face/test.list')
test_reader = paddle.batch(
    tester_reader, batch_size=BATCH_SIZE
)

搭建CNN网络

这个模型用的也是很传统的卷积,池化,激活,批量归一化的形式
整体结构并没有什么特别点,这里就不再赘述

def convolutional_nerual_network(image, type_size):
    conv_pool_1 = fluid.nets.simple_img_conv_pool(input=image,
                                                  filter_size=3,
                                                  num_filters=32,
                                                  pool_size=2,
                                                  pool_stride=2,
                                                  act='relu')
    bn1 = fluid.layers.batch_norm(input=conv_pool_1, act='relu')
    drop = fluid.layers.dropout(x=bn1, dropout_prob=0.3)

    conv_pool_2 = fluid.nets.simple_img_conv_pool(input=drop,
                                                  filter_size=3,
                                                  num_filters=64,
                                                  pool_size=2,
                                                  pool_stride=2,
                                                  act='relu')
    bn2 = fluid.layers.batch_norm(input=conv_pool_2, act='relu')
    drop = fluid.layers.dropout(x=bn2, dropout_prob=0.5)

    fc = fluid.layers.fc(input=drop, size=512, act='relu')
    bn3 = fluid.layers.batch_norm(input=fc, act='relu')
    drop = fluid.layers.dropout(x=bn3, dropout_prob=0.5)
    predict = fluid.layers.fc(input=drop, size=type_size, act='softmax')

    return predict

创建数据层

在这里我们要开始定义我们数据的输入维度
在tensorflow这种操作是叫定义占位符
而在paddle框架里面它专门有个层叫数据层
说白了就是定义好一个有维度的张量,流入计算图里

# 创建数据层
image = fluid.layers.data(name='image', shape=[3, 100, 100], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
predict = convolutional_nerual_network(image, type_size=4)

定义损失函数和优化器

我们这次采取的是交叉熵损失函数和Adam优化器
我们计算损失后利用mean取损失值的平均值

cost = fluid.layers.cross_entropy(input=predict, label=label)
avg_cost = fluid.layers.mean(cost)
accuracy = fluid.layers.accuracy(input=predict, label=label)

optimizer = fluid.optimizer.Adam(learning_rate=0.002)
optimizer.minimize(avg_cost)

图的初始化

这里我们需要初始化我们的计算图
并且将数据传入给feeder
feeder能接收我们传入的数据并将数据转换成图能读入的格式

# 开始训练模型
# 使用cpu
place = fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
feeder = fluid.DataFeeder(feed_list=[image, label], place=place)

定义绘制损失和准确率的函数

我们创建几个空列表,方便后续损失值和准确率加入
最后我们根据这个列表进行损失和准确率图像的绘制

all_train_iter=0
all_train_iters=[]
all_train_costs=[]
all_train_accs=[]

def draw_train_process(title, iters, costs, accs, label_cost, label_acc):
    plt.title(title, fontsize=24)
    plt.xlabel("iter", fontsize=20)
    plt.ylabel("cost/acc", fontsize=20)
    plt.plot(iters, costs, color='red', label=label_cost)
    plt.plot(iters, accs, color='green', label=label_acc)
    plt.legend()
    plt.grid()
    plt.show()

开始训练

我们这次分类任务较为简单,所以迭代轮次设置为20

我们的大轮次为EPOCH_NUM
小轮次就是总数据以批量读入的方式进行迭代
这里我们就用python自带的enumerate函数
他能返回的是一个序号和迭代器里的元素
这个序号就是我们的一个小轮次

fetch_list里面定义了我们想要得到的参数
我们需要得到损失值和准确率
然后方便后续绘制
所以这里fetch的是avg_cost 和accuracy

EPOCH_NUM = 20
print("START TRAINING")
model_save_dir = "./model_cnn"
for pass_id in range(EPOCH_NUM):
    train_cost = 0
    for batch_id, data in enumerate(trainer_reader()):
        train_cost, train_acc = exe.run(
            program=fluid.default_main_program(),
            feed=feeder.feed(data),
            fetch_list=[avg_cost, accuracy]
        )

        all_train_iter += BATCH_SIZE
        all_train_iters.append(all_train_iter)
        all_train_costs.append(train_cost[0])
        all_train_accs.append(train_acc[0])

        if batch_id% 10 == 0:
            print("\n PASS %d, STEP %d, Cost: %f, Acc: %f"%(
                pass_id, batch_id, train_cost[0], train_acc[0]
            ))

    test_accs = []
    test_costs = []
    for batch_id, data in enumerate(test_reader()):
        test_cost, test_acc = exe.run(program=fluid.default_main_program(),
                                      feed=feeder.feed(data),
                                      fetch_list=[avg_cost, accuracy])
        test_accs.append(test_acc[0])
        test_costs.append(test_cost[0])


    test_cost = (sum(test_costs) / len(test_costs))
    test_acc = (sum(test_accs) / len(test_accs))
    print("TEST: %d, COST: %0.5f, ACC: %0.5f"%(pass_id, test_cost, test_acc))

    if not os.path.exists(model_save_dir):
        os.makedirs(model_save_dir)
        # 保存训练的模型,executor 把所有相关参数保存到 dirname 中
    fluid.io.save_inference_model(dirname=model_save_dir,
                                  feeded_var_names=["image"],
                                  target_vars=[predict],
                                  executor=exe)

draw_train_process("training", all_train_iters, all_train_costs, all_train_accs, "trainning cost", "trainning acc")

print('训练模型保存完成!')

总结

总的来说飞桨框架还是很不错的
现在还不支持在win系统上debug,以及没有可视化
但是各个函数,功能设计十分合理
期待它也能跻身进入机器学习流行框架

你可能感兴趣的:(机器学习,飞桨框架)