项目简介

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

下载安装命令

## CPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu

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

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

In[1]

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

数据预处理

训练集中的数据按照

的命名方式。由于数据中存在一些破损的图片,所以需要先清洗一下数据。同时,为了方便训练,将数据的一行准备成

的格式,并输出到和图片同级目录下的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 官网,了解更多相关内容

下载安装命令

## CPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle

## GPU版本安装命令
pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu
09-04 23:46