圖片分類及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 官網,瞭解更多相關內容

相關文章
相關標籤/搜索