图片分类及Paddle派部署实例

项目简介

这是一个demo 项目,用于演示如何在 AI Studio 上训练一个“小”模型,而后把它转化成一个能够部署到Paddle派硬件上的模型。html

为了简单起见,在此只训练一个猫猫和狗狗的二分类模型。linux

进入项目时,已经引用了 AI Studio 的公开数据集"猫狗大战数据集"做为训练数据。数据存储在 data/data62/ 目录下,以压缩包的形式存在。执行下面的代码,进入目录,将训练数据解压网络

In[1]dom

!cd /home/aistudio/data/data62 && unzip -q train.zip 
!cd /home/aistudio/data/data62 && unzip -q test.zip

数据预处理

训练集中的数据按照ide

cat.123.jpg函数

dog.456.jpg工具

的命名方式。因为数据中存在一些破损的图片,因此须要先清洗一下数据。同时,为了方便训练,将数据的一行准备成学习

文件名\t类别fetch

的格式,并输出到和图片同级目录下的label.txt文件中。猫猫的类别是1,狗狗的类别是0。执行如下代码,进行数据的简单清洗优化

In[2]

#数据清洗
import codecs
import os
from PIL import Image

train_file_list = os.listdir('data/data62/train')
with codecs.open("data/data62/train/label.txt", 'w') as out_file:
    for file in train_file_list:
        try:
            img = Image.open(os.path.join('data/data62/train', file))
            if file.find('cat') != -1:
                out_file.write("{0}\t{1}\n".format(file, 1))
            else:
                out_file.write("{0}\t{1}\n".format(file, 0))
        except Exception as e:
            pass
            # 存在一些文件打不开,此处须要稍做清洗

参数设置

设置基础训练参数,例如

  • 图片尺寸,注意是 chw 格式
  • 训练数据路径
  • 保存模型的输出路径
  • 训练轮数、训练批次大小
  • 是否使用GPU
  • 学习率变化等

其中类别数量会在读取数据时提早计算,初始为-1,仅用做占位

In[3]

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import numpy as np
import uuid
import random
import time
import six
import sys
import functools
import math
import paddle
import paddle.fluid as fluid
import paddle.dataset.flowers as flowers
import argparse
import functools
import subprocess
import codecs
import distutils.util
from paddle.fluid import core
from paddle.fluid.initializer import MSRA
from paddle.fluid.param_attr import ParamAttr
from PIL import Image, ImageEnhance
import logging


train_parameters = {
    "input_size": [3, 224, 224],
    "class_dim": -1,
    "data_dir": "data/data62/train",
    "save_model_dir": "./classify-model",
    "mode": "train",
    "num_epochs": 120,
    "image_count": -1,
    "train_batch_size": 50,
    "mean_rgb": [127.5, 127.5, 127.5],
    "use_gpu": True,            # 根据本身的环境,选择适当的设备进行训练
    "image_distort_strategy": {
        "need_distort": True,
        "expand_prob": 0.5,
        "expand_max_ratio": 4,
        "hue_prob": 0.5,
        "hue_delta": 18,
        "contrast_prob": 0.5,
        "contrast_delta": 0.5,
        "saturation_prob": 0.5,
        "saturation_delta": 0.5,
        "brightness_prob": 0.5,
        "brightness_delta": 0.125
    },
    "rsm_strategy": {
        "learning_rate": 0.02,
        "lr_epochs": [20, 40, 60, 80, 100],
        "lr_decay": [1, 0.5, 0.25, 0.1, 0.01, 0.002]
    },
    "momentum_strategy": {
        "learning_rate": 0.005,
        "lr_epochs": [20, 40, 60, 80, 100],
        "lr_decay": [1, 0.5, 0.25, 0.1, 0.01, 0.002]
    }
}

定义网络结构

设定网络结构,此处定义了三个经常使用的网络结构

  • resnet
  • mobile-net
  • vgg-net

为了训练一个小模型,此处使用mobile-net。若是是其余项目或者其余用途,使用其余网络结构亦可

In[4]

class ResNet():
    def __init__(self, layers=50):
        self.layers = layers
        
    def name(self):
        return 'resnet'

    def net(self, input, class_dim=1000):
        layers = self.layers
        supported_layers = [50, 101, 152]
        assert layers in supported_layers, \
            "supported layers are {} but input layer is {}".format(supported_layers, layers)

        if layers == 50:
            depth = [3, 4, 6, 3]
        elif layers == 101:
            depth = [3, 4, 23, 3]
        elif layers == 152:
            depth = [3, 8, 36, 3]
        num_filters = [64, 128, 256, 512]

        conv = self.conv_bn_layer(
            input=input,
            num_filters=64,
            filter_size=7,
            stride=2,
            act='relu',
            name="conv1")
        conv = fluid.layers.pool2d(
            input=conv,
            pool_size=3,
            pool_stride=2,
            pool_padding=1,
            pool_type='max')

        for block in range(len(depth)):
            for i in range(depth[block]):
                if layers in [101, 152] and block == 2:
                    if i == 0:
                        conv_name = "res" + str(block + 2) + "a"
                    else:
                        conv_name = "res" + str(block + 2) + "b" + str(i)
                else:
                    conv_name = "res" + str(block + 2) + chr(97 + i)
                conv = self.bottleneck_block(
                    input=conv,
                    num_filters=num_filters[block],
                    stride=2 if i == 0 and block != 0 else 1,
                    name=conv_name)

        pool = fluid.layers.pool2d(
            input=conv, pool_size=7, pool_type='avg', global_pooling=True)
        stdv = 1.0 / math.sqrt(pool.shape[1] * 1.0)
        out = fluid.layers.fc(input=pool,
                              size=class_dim,
                              act='softmax', 
                              param_attr=fluid.param_attr.ParamAttr(
                                  initializer=fluid.initializer.Uniform(-stdv,
                                                                        stdv)))
        return out

    def conv_bn_layer(self,
                      input,
                      num_filters,
                      filter_size,
                      stride=1,
                      groups=1,
                      act=None,
                      name=None):
        conv = fluid.layers.conv2d(
            input=input,
            num_filters=num_filters,
            filter_size=filter_size,
            stride=stride,
            padding=(filter_size - 1) // 2,
            groups=groups,
            act=None,
            param_attr=ParamAttr(name=name + "_weights"),
            bias_attr=False,
            name=name + '.conv2d.output.1')
        if name == "conv1":
            bn_name = "bn_" + name
        else:
            bn_name = "bn" + name[3:]
        return fluid.layers.batch_norm(
            input=conv,
            act=act,
            name=bn_name + '.output.1',
            param_attr=ParamAttr(name=bn_name + '_scale'),
            bias_attr=ParamAttr(bn_name + '_offset'),
            moving_mean_name=bn_name + '_mean',
            moving_variance_name=bn_name + '_variance', )

    def shortcut(self, input, ch_out, stride, name):
        ch_in = input.shape[1]
        if ch_in != ch_out or stride != 1:
            return self.conv_bn_layer(input, ch_out, 1, stride, name=name)
        else:
            return input

    def bottleneck_block(self, input, num_filters, stride, name):
        conv0 = self.conv_bn_layer(
            input=input,
            num_filters=num_filters,
            filter_size=1,
            act='relu',
            name=name + "_branch2a")
        conv1 = self.conv_bn_layer(
            input=conv0,
            num_filters=num_filters,
            filter_size=3,
            stride=stride,
            act='relu',
            name=name + "_branch2b")
        conv2 = self.conv_bn_layer(
            input=conv1,
            num_filters=num_filters * 4,
            filter_size=1,
            act=None,
            name=name + "_branch2c")

        short = self.shortcut(
            input, num_filters * 4, stride, name=name + "_branch1")

        return fluid.layers.elementwise_add(
            x=short, y=conv2, act='relu', name=name + ".add.output.5")


class MobileNet():
    def __init__(self):
        pass
    
    def name(self):
        return 'mobile-net'

    def net(self, input, class_dim=1000, scale=1.0):
        # conv1: 112x112
        input = self.conv_bn_layer(
            input,
            filter_size=3,
            num_filters=int(32 * scale),
            stride=2,
            padding=1)

        # 56x56
        input = self.depthwise_separable(
            input,
            num_filters1=32,
            num_filters2=64,
            num_groups=32,
            stride=1,
            scale=scale)

        input = self.depthwise_separable(
            input,
            num_filters1=64,
            num_filters2=128,
            num_groups=64,
            stride=2,
            scale=scale)

        # 28x28
        input = self.depthwise_separable(
            input,
            num_filters1=128,
            num_filters2=128,
            num_groups=128,
            stride=1,
            scale=scale)

        input = self.depthwise_separable(
            input,
            num_filters1=128,
            num_filters2=256,
            num_groups=128,
            stride=2,
            scale=scale)

        # 14x14
        input = self.depthwise_separable(
            input,
            num_filters1=256,
            num_filters2=256,
            num_groups=256,
            stride=1,
            scale=scale)

        input = self.depthwise_separable(
            input,
            num_filters1=256,
            num_filters2=512,
            num_groups=256,
            stride=2,
            scale=scale)

        # 14x14
        for i in range(5):
            input = self.depthwise_separable(
                input,
                num_filters1=512,
                num_filters2=512,
                num_groups=512,
                stride=1,
                scale=scale)
        module1 = input
        # 7x7
        input = self.depthwise_separable(
            input,
            num_filters1=512,
            num_filters2=1024,
            num_groups=512,
            stride=2,
            scale=scale)

        input = self.depthwise_separable(
            input,
            num_filters1=1024,
            num_filters2=1024,
            num_groups=1024,
            stride=1,
            scale=scale)

        # class_dim x 1
        input = paddle.fluid.layers.conv2d(
            input,
            num_filters=class_dim,
            filter_size=1,
            stride=1)

        pool = fluid.layers.pool2d(
            input=input,
            pool_size=0,
            pool_stride=1,
            pool_type='avg',
            global_pooling=True)

        output = fluid.layers.fc(input=pool,
                              size=class_dim,
                              act='softmax', 
                              param_attr=ParamAttr(initializer=MSRA()))
        
        return output

    def conv_bn_layer(self,
                      input,
                      filter_size,
                      num_filters,
                      stride,
                      padding,
                      num_groups=1,
                      act='relu',
                      use_cudnn=True):
        conv = fluid.layers.conv2d(
            input=input,
            num_filters=num_filters,
            filter_size=filter_size,
            stride=stride,
            padding=padding,
            groups=num_groups,
            act=None,
            use_cudnn=use_cudnn,
            param_attr=ParamAttr(initializer=MSRA()),
            bias_attr=False)
        return fluid.layers.batch_norm(input=conv, act=act)

    def depthwise_separable(self, input, num_filters1, num_filters2, num_groups,
                            stride, scale):
        depthwise_conv = self.conv_bn_layer(
            input=input,
            filter_size=3,
            num_filters=int(num_filters1 * scale),
            stride=stride,
            padding=1,
            num_groups=int(num_groups * scale),
            use_cudnn=True)

        pointwise_conv = self.conv_bn_layer(
            input=depthwise_conv,
            filter_size=1,
            num_filters=int(num_filters2 * scale),
            stride=1,
            padding=0)
        return pointwise_conv


class VGGNet():
    def __init__(self, layers=16):
        self.layers = layers
        
    def name(self):
        return 'vgg-net'

    def net(self, input, class_dim=1000):
        layers = self.layers
        vgg_spec = {
            11: ([1, 1, 2, 2, 2]),
            13: ([2, 2, 2, 2, 2]),
            16: ([2, 2, 3, 3, 3]),
            19: ([2, 2, 4, 4, 4])
        }
        assert layers in vgg_spec.keys(), \
            "supported layers are {} but input layer is {}".format(vgg_spec.keys(), layers)

        nums = vgg_spec[layers]
        conv1 = self.conv_block(input, 64, nums[0])
        conv2 = self.conv_block(conv1, 128, nums[1])
        conv3 = self.conv_block(conv2, 256, nums[2])
        conv4 = self.conv_block(conv3, 512, nums[3])
        conv5 = self.conv_block(conv4, 512, nums[4])

        fc_dim = 4096
        fc1 = fluid.layers.fc(
            input=conv5,
            size=fc_dim,
            act='relu',
            param_attr=fluid.param_attr.ParamAttr(
                initializer=fluid.initializer.Normal(scale=0.005)),
            bias_attr=fluid.param_attr.ParamAttr(
                initializer=fluid.initializer.Constant(value=0.1)))
        fc1 = fluid.layers.dropout(x=fc1, dropout_prob=0.5)
        fc2 = fluid.layers.fc(
            input=fc1,
            size=fc_dim,
            act='relu',
            param_attr=fluid.param_attr.ParamAttr(
                initializer=fluid.initializer.Normal(scale=0.005)),
            bias_attr=fluid.param_attr.ParamAttr(
                initializer=fluid.initializer.Constant(value=0.1)))
        fc2 = fluid.layers.dropout(x=fc2, dropout_prob=0.5)
        out = fluid.layers.fc(
            input=fc2,
            size=class_dim,
            act='softmax',
            param_attr=fluid.param_attr.ParamAttr(
                initializer=fluid.initializer.Normal(scale=0.005)),
            bias_attr=fluid.param_attr.ParamAttr(
                initializer=fluid.initializer.Constant(value=0.1)))

        return out

    def conv_block(self, input, num_filter, groups):
        conv = input
        for i in range(groups):
            if i == groups - 1:
                act = None
            else:
                act = 'relu'
            conv = fluid.layers.conv2d(
                input=conv,
                num_filters=num_filter,
                filter_size=3,
                stride=1,
                padding=1,
                act=act,
                param_attr=fluid.param_attr.ParamAttr(
                    initializer=fluid.initializer.Normal(scale=0.01)),
                bias_attr=fluid.param_attr.ParamAttr(
                    initializer=fluid.initializer.Constant(value=0.0)))
        conv = fluid.layers.batch_norm(input=conv, act='relu')
        return fluid.layers.pool2d(input=conv, pool_size=2, pool_type='max', pool_stride=2)

处理工具函数

如下是一些工具函数, 例如日志处理, 图片加强处理等等.

  1. 初始化日志部分,会在 logs 文件夹下保存当次训练的日志,并清空之前的日志
  2. 图像处理的工具函数,用于训练时图像加强。例如常见的包核对,对比度,亮度的调整
  3. 自定义训练数据读取器,在获取读取器以前,初始化类别数量
  4. 优化器配置,此处准备了三种优化器,SGD、Adam和RMS,使用任意一个都可
  5. 模型保存方法,先保存训练参数,可用于再训练;后存用于预测模型

In[5]

# 初始化日志
def init_log_config():
    global logger
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    log_path = os.path.join(os.getcwd(), 'logs')
    if not os.path.exists(log_path):
        os.makedirs(log_path)
    log_name = os.path.join(log_path, 'train.log')
    fh = logging.FileHandler(log_name, mode='w')
    fh.setLevel(logging.DEBUG)
    formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
    fh.setFormatter(formatter)
    logger.addHandler(fh)


# 简单的图像加强函数
def resize_img(img, target_size):
    percent_h = float(target_size[1]) / img.size[1]
    percent_w = float(target_size[2]) / img.size[0]
    percent = min(percent_h, percent_w)
    resized_width = int(round(img.size[0] * percent))
    resized_height = int(round(img.size[1] * percent))
    w_off = (target_size[1] - resized_width) / 2
    h_off = (target_size[2] - resized_height) / 2
    img = img.resize((resized_width, resized_height), Image.LANCZOS)
    array = np.ndarray((target_size[1], target_size[2], target_size[0]), np.uint8)
    array[:, :, 0] = 127.5
    array[:, :, 1] = 127.5
    array[:, :, 2] = 127.5
    ret = Image.fromarray(array)
    ret.paste(img, (int(w_off), int(h_off)))
    return ret


def random_brightness(img):
    prob = np.random.uniform(0, 1)
    if prob < train_parameters['image_distort_strategy']['brightness_prob']:
        brightness_delta = train_parameters['image_distort_strategy']['brightness_delta']
        delta = np.random.uniform(-brightness_delta, brightness_delta) + 1
        img = ImageEnhance.Brightness(img).enhance(delta)
    return img


def random_contrast(img):
    prob = np.random.uniform(0, 1)
    if prob < train_parameters['image_distort_strategy']['contrast_prob']:
        contrast_delta = train_parameters['image_distort_strategy']['contrast_delta']
        delta = np.random.uniform(-contrast_delta, contrast_delta) + 1
        img = ImageEnhance.Contrast(img).enhance(delta)
    return img


def random_saturation(img):
    prob = np.random.uniform(0, 1)
    if prob < train_parameters['image_distort_strategy']['saturation_prob']:
        saturation_delta = train_parameters['image_distort_strategy']['saturation_delta']
        delta = np.random.uniform(-saturation_delta, saturation_delta) + 1
        img = ImageEnhance.Color(img).enhance(delta)
    return img


def random_hue(img):
    prob = np.random.uniform(0, 1)
    if prob < train_parameters['image_distort_strategy']['hue_prob']:
        hue_delta = train_parameters['image_distort_strategy']['hue_delta']
        delta = np.random.uniform(-hue_delta, hue_delta)
        img_hsv = np.array(img.convert('HSV'))
        img_hsv[:, :, 0] = img_hsv[:, :, 0] + delta
        img = Image.fromarray(img_hsv, mode='HSV').convert('RGB')
    return img


def distort_image(img):
    prob = np.random.uniform(0, 1)
    # Apply different distort order
    if prob < 0.25:
        img = random_brightness(img)
        img = random_contrast(img)
        img = random_saturation(img)
        img = random_hue(img)
    elif prob < 0.5:
        img = random_brightness(img)
        img = random_saturation(img)
        img = random_hue(img)
        img = random_contrast(img)
    return img
    

# 自定义数据读取器
def custom_image_reader(file_list, data_dir, mode):
    with codecs.open(file_list) as flist:
        lines = [line.strip() for line in flist]
        train_parameters['image_count'] = len(lines)
        np.random.shuffle(lines)
        label_set = set()
        for line in lines:
            img_path, label = line.split()
            label_set.add(label)
        train_parameters['class_dim'] = len(label_set)
        print("class dim:{0} image count:{1}".format(train_parameters['class_dim'], train_parameters['image_count']))
    
    def reader():
        for line in lines:
            if mode == 'train' or mode == 'val':
                img_path, label = line.split()
                img_path = os.path.join(data_dir, img_path)
                img = Image.open(img_path)
                try:
                    if img.mode != 'RGB':
                        img = img.convert('RGB')
                    if train_parameters['image_distort_strategy']['need_distort'] == True:
                        img = distort_image(img)
                    mirror = int(np.random.uniform(0, 2))
                    if mirror == 1:
                        img = img.transpose(Image.FLIP_LEFT_RIGHT)
                    img = resize_img(img, train_parameters['input_size'])
                    # HWC--->CHW && normalized
                    img = np.array(img).astype('float32')
                    img -= train_parameters['mean_rgb']
                    img = img.transpose((2, 0, 1))  # HWC to CHW
                    img *= 0.007843
                    yield img, int(label)
                except Exception as e:
                    pass
            elif mode == 'test':
                img_path = os.path.join(data_dir, line)
                if img.mode != 'RGB':
                    img = img.convert('RGB')
                img = resize_img(img, train_parameters['input_size'])
                yield img

    return reader


# 优化器
def optimizer_momentum_setting():
    """
    阶梯型的学习率适合比较大规模的训练数据
    """
    learning_strategy = train_parameters['momentum_strategy']
    batch_size = train_parameters["train_batch_size"]
    iters = train_parameters["image_count"] // batch_size
    lr = learning_strategy['learning_rate']

    boundaries = [i * iters for i in learning_strategy["lr_epochs"]]
    values = [i * lr for i in learning_strategy["lr_decay"]]
    learning_rate = fluid.layers.piecewise_decay(boundaries, values)
    optimizer = fluid.optimizer.MomentumOptimizer(learning_rate=learning_rate, momentum=0.9)
    return optimizer


def optimizer_rms_setting():
    """
    阶梯型的学习率适合比较大规模的训练数据
    """
    batch_size = train_parameters["train_batch_size"]
    iters = train_parameters["image_count"] // batch_size
    learning_strategy = train_parameters['rsm_strategy']
    lr = learning_strategy['learning_rate']

    boundaries = [i * iters for i in learning_strategy["lr_epochs"]]
    values = [i * lr for i in learning_strategy["lr_decay"]]

    optimizer = fluid.optimizer.RMSProp(
        learning_rate=fluid.layers.piecewise_decay(boundaries, values),
        regularization=fluid.regularizer.L2Decay(0.00005))

    return optimizer
    
    
def optimizer_sgd_setting():
    """
    loss降低相对较慢,可是最终效果不错,阶梯型的学习率适合比较大规模的训练数据
    """
    learning_strategy = train_parameters['momentum_strategy']
    batch_size = train_parameters["train_batch_size"]
    iters = train_parameters["image_count"] // batch_size
    lr = learning_strategy['learning_rate']

    boundaries = [i * iters for i in learning_strategy["lr_epochs"]]
    values = [i * lr for i in learning_strategy["lr_decay"]]
    learning_rate = fluid.layers.piecewise_decay(boundaries, values)
    optimizer = fluid.optimizer.SGD(learning_rate=learning_rate)
    return optimizer
    
    
def optimizer_adam_setting():
    """
    可以比较快速的下降 loss,可是相对后期乏力。对于小规模的数据,比较适合
    """
    optimizer = fluid.optimizer.Adam(learning_rate=0.01)
    return optimizer


# 保存模型
def save_model(base_dir, base_name, feed_var_list, target_var_list, program, exe):
    fluid.io.save_persistables(dirname=base_dir, 
        filename=base_name + '-retrain',
        main_program=program, 
        executor=exe)
    fluid.io.save_inference_model(dirname=base_dir, 
        params_filename=base_name + '-params',
        model_filename=base_name + '-model',
        feeded_var_names=feed_var_list, 
        target_vars=target_var_list, 
        main_program=program, 
        executor=exe)

最后准备工做

设置读取训练数据,组装模型,检验训练精度。在代码中能够更换想要的模型

In[6]

init_log_config()
train_prog = fluid.Program()
train_startup = fluid.Program()
print("create prog success")
logger.info("create prog success")
logger.info("train config:%s", train_parameters)
logger.info("build input custom reader and data feeder")
file_list = os.path.join(train_parameters['data_dir'], "label.txt")
mode = train_parameters['mode']
batch_reader = paddle.batch(custom_image_reader(file_list, train_parameters['data_dir'], mode), 
    batch_size=train_parameters['train_batch_size'], 
    drop_last=True)
place = fluid.CUDAPlace(0) if train_parameters['use_gpu'] else fluid.CPUPlace()
img = fluid.layers.data(name='img', shape=train_parameters['input_size'], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
feeder = fluid.DataFeeder(feed_list=[img, label], place=place)

logger.info("build newwork")
# ~~~~~~替换模型在此~~~~~~
# model = ResNet(layers=50)
# model = VGGNet(layers=16)
model = MobileNet()
out = model.net(input=img, class_dim=train_parameters['class_dim'])
cost = fluid.layers.cross_entropy(out, label)
avg_cost = fluid.layers.mean(x=cost)
acc_top1 = fluid.layers.accuracy(input=out, label=label, k=1)
# optimizer = optimizer_rms_setting()
optimizer = optimizer_momentum_setting()
# optimizer = optimizer_sgd_setting()
# optimizer = optimizer_adam_setting()
optimizer.minimize(avg_cost)
exe = fluid.Executor(place)
2019-04-24 16:09:22,866 - INFO - create prog success
2019-04-24 16:09:22,867 - INFO - train config:{'input_size': [3, 224, 224], 'class_dim': -1, 'data_dir': 'data/data62/train', 'save_model_dir': './classify-model', 'mode': 'train', 'num_epochs': 120, 'image_count': -1, 'train_batch_size': 50, 'mean_rgb': [127.5, 127.5, 127.5], 'use_gpu': True, 'image_distort_strategy': {'need_distort': True, 'expand_prob': 0.5, 'expand_max_ratio': 4, 'hue_prob': 0.5, 'hue_delta': 18, 'contrast_prob': 0.5, 'contrast_delta': 0.5, 'saturation_prob': 0.5, 'saturation_delta': 0.5, 'brightness_prob': 0.5, 'brightness_delta': 0.125}, 'rsm_strategy': {'learning_rate': 0.02, 'lr_epochs': [20, 40, 60, 80, 100], 'lr_decay': [1, 0.5, 0.25, 0.1, 0.01, 0.002]}, 'momentum_strategy': {'learning_rate': 0.005, 'lr_epochs': [20, 40, 60, 80, 100], 'lr_decay': [1, 0.5, 0.25, 0.1, 0.01, 0.002]}}
2019-04-24 16:09:22,867 - INFO - build input custom reader and data feeder
2019-04-24 16:09:22,881 - INFO - build newwork
create prog success
class dim:2 image count:23202

开始训练

模型训练主体训练,有必定提早中止策略。炼丹开始! 注意观察loss的变化,而后开始不一样任务的不一样调参吧~

In[5]

main_program = fluid.default_main_program()
exe.run(fluid.default_startup_program())
# 若是有训练过的参数,能够经过打开这句话来加载接着训练
# fluid.io.load_persistables(dirname=train_parameters['save_model_dir'], filename=model.name() + '-retrain', main_program=main_program, executor=exe)
train_fetch_list = [avg_cost.name, acc_top1.name, out.name]

successive_count = 0
stop_train = False
total_batch_count = 0
for pass_id in range(train_parameters["num_epochs"]):
    logger.info("current pass: %d, start read image", pass_id)
    batch_id = 0
    for step_id, data in enumerate(batch_reader()):
        t1 = time.time()
        loss, acc1, pred_ot = exe.run(main_program,
                        feed=feeder.feed(data),
                        fetch_list=train_fetch_list)
        t2 = time.time()
        batch_id += 1
        total_batch_count += 1
        period = t2 - t1
        loss = np.mean(np.array(loss))
        acc1 = np.mean(np.array(acc1))
        if batch_id % 10 == 0:
            print("Pass {0}, trainbatch {1}, loss {2}, acc1 {3}, time {4}".format(pass_id, batch_id, loss, acc1, "%2.2f sec" % period))
            logger.info("Pass {0}, trainbatch {1}, loss {2}, acc1 {3}, time {4}".format(pass_id, batch_id, loss, acc1, "%2.2f sec" % period))
        if acc1 >= 0.93:
            successive_count += 1
            fluid.io.save_inference_model(dirname=train_parameters['save_model_dir'], 
                                            params_filename=model.name() + '-params',
                                            model_filename=model.name() + '-model',
                                            feeded_var_names=['img'], 
                                            target_vars=[out], 
                                            main_program=main_program, 
                                            executor=exe)
            if successive_count >= 5:
                logger.info("end training")
                print("end training")
                stop_train = True
                break
        else:
            successive_count = 0
        if total_batch_count % 400 == 0:
            logger.info("temp save {0} batch train result".format(total_batch_count))
            print("temp save {0} batch train result".format(total_batch_count))
            fluid.io.save_persistables(dirname=train_parameters['save_model_dir'], 
                                        filename=model.name() + '-retrain',
                                        main_program=main_program, 
                                        executor=exe)
    if stop_train:
        break
save_model(train_parameters['save_model_dir'], model.name() + '-final', ['img'], [out], main_program, exe)
2019-04-24 16:09:26,127 - INFO - current pass: 0, start read image
2019-04-24 16:09:33,745 - INFO - Pass 0, trainbatch 10, loss 0.6780937910079956, acc1 0.6200000047683716, time 0.08 sec
Pass 0, trainbatch 10, loss 0.6780937910079956, acc1 0.6200000047683716, time 0.08 sec

将单个模型参数文件拆分红多个模型参数文件,以便后面的操做

In[20]

# -*- coding: UTF-8 -*-
"""
模型转换工具,将已经保存的多参数文件合并为单参数文件
"""
import os
import paddle
import paddle.fluid as fluid


def muti_to_single(base_name, feeeze_path, out_path):
    """
    param base_name: 模型的基本名字
    param feeeze_path: 多文件预测模型所保存的目录
    param out_path: 合并后文件的输出路径
    """
    place = fluid.CPUPlace()
    exe = fluid.Executor(place)
    [inference_program, feed_target_names, fetch_targets] = paddle.fluid.io.load_inference_model(feeeze_path, exe)

    fluid.io.save_inference_model(dirname=out_path,
                                  params_filename=base_name + '-params',
                                  model_filename=base_name + '-model',
                                  feeded_var_names=feed_target_names,
                                  target_vars=fetch_targets,
                                  main_program=inference_program,
                                  executor=exe)


def single_to_muti(base_name, feeeze_path, out_path):
    """
    param base_name: 模型的基本名字
    param feeeze_path: 多文件预测模型所保存的目录
    param out_path: 合并后文件的输出路径
    """
    place = fluid.CPUPlace()
    exe = fluid.Executor(place)
    [inference_program, feed_target_names, fetch_targets] = paddle.fluid.io.load_inference_model(feeeze_path,
                                                                                                 exe,
                                                                                                 params_filename=base_name + '-params',
                                                                                                 model_filename=base_name + '-model', )

    fluid.io.save_inference_model(dirname=out_path,
                                  feeded_var_names=feed_target_names,
                                  target_vars=fetch_targets,
                                  main_program=inference_program,
                                  executor=exe)


if __name__ == '__main__':
    # muti_to_single('yolov3', 'freeze', '.')
    single_to_muti('mobile-net', 'classify-model', 'freeze-model')

模型预测

训练完成以后,咱们可使用训练好的模型来进行预测,看看猫猫的图片是否能预测类别1,狗狗的图片是类别0

In[14]

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import scipy.io as sio
import os
import numpy as np
import random
import time
import six
import sys
import functools
import time
import math
import paddle
import paddle.fluid as fluid
import paddle.dataset.flowers as flowers
import argparse
import functools
import subprocess
import distutils.util
from paddle.fluid import core
from paddle.fluid.param_attr import ParamAttr
from PIL import Image, ImageEnhance
import logging

target_size = [3, 224, 224]
mean_rgb = [127.5, 127.5, 127.5]
place = fluid.CPUPlace()
exe = fluid.Executor(place)
path = "classify-model"
[inference_program, feed_target_names, fetch_targets] = fluid.io.load_inference_model(dirname=path, 
	params_filename ='mobile-net-params',
	model_filename='mobile-net-model',
	executor=exe)
# print(fetch_targets)


def resize_img(img, target_size):
    percent_h = float(target_size[1]) / img.size[1]
    percent_w = float(target_size[2]) / img.size[0]
    percent = min(percent_h, percent_w)
    resized_width = int(round(img.size[0] * percent))
    resized_height = int(round(img.size[1] * percent))
    img = img.resize((resized_width, resized_height), Image.ANTIALIAS)
    
    w_off = (target_size[1] - resized_width) / 2
    h_off = (target_size[2] - resized_height) / 2
    array = np.ndarray((target_size[1], target_size[2], target_size[0]), np.uint8)
    array[:, :, 0] = 127.5
    array[:, :, 1] = 127.5
    array[:, :, 2] = 127.5
    ret = Image.fromarray(array)
    ret.paste(img, (int(w_off), int(h_off)))
    return ret


def read_image(img_path):
    img = Image.open(img_path)
    if img.mode != 'RGB':
        img = img.convert('RGB')
    img = resize_img(img, target_size)
    img = np.array(img).astype('float32')
    img -= mean_rgb
    img = img.transpose((2, 0, 1))  # HWC to CHW
    img *= 0.007843
    img = img[np.newaxis,:]
    return img


def infer(image_path):
    tensor_img = read_image(image_path)
    t1 = time.time()
    label = exe.run(inference_program, feed={feed_target_names[0]: tensor_img}, fetch_list=fetch_targets)
    period = time.time() - t1
    print("predict result:{0} cost time:{1}".format(label, "%2.2f sec" % period))
    return period, np.argmax(label)


# image_path = sys.argv[1]
# 1--4是狗狗的图片,5--11是猫猫的图片
image_path = 'data/data62/test/723.jpg'  # 这是一张狗狗的照片
period, result = infer(image_path)
print(result)

模型转换

普通的模型并不能很好地运行在开发板等特定硬件上, 为了在特定硬件上部署, 须要借助一些工具.

本次演示的是Paddle派转换工具.

首先先拉取并解压模型转换工具:

In[ ]

!mkdir /home/aistudio/work/ncc
!wget "https://platform.bj.bcebos.com/sdk%2Fncc-linux-x86_64.tar.gz" -O ncc-linux-x86_64.tar.gz
!tar -zxvf ncc-linux-x86_64.tar.gz -C /home/aistudio/work/ncc

而后进行模型压缩. 咱们须要进行量化。为了保证量化后的精度, 须要使用训练图片调整模型。

In[18]

import codecs
import shutil
import os

target_dir = 'work/images/'
if not os.path.exists(target_dir):
    os.makedirs(target_dir)

for i in range(200, 300):
	source_path = os.path.join('data/data62/train/', str(i) + '.jpg')
	target_path = os.path.join(target_dir, str(i) + '.jpg')
	if os.path.exists(source_path):
	    shutil.copy(source_path,  target_path)

最后进行进行模型转换, 而后将模型进行下载, 就能够部署到本身的Paddle派开发板上了

In[ ]

!/home/aistudio/work/ncc/ncc -i paddle -o k210model --dataset work/images/ freeze-model mobilenet.kmodel

至此演示所有完成. 若是有问题, 能够邮件至 aistudio@baidu.com咨询

>> 访问 PaddlePaddle 官网,了解更多相关内容

相关文章
相关标签/搜索