PaddleClas/docs/zh_CN/tutorials/quick_start_community.md

20 KiB
Raw Blame History

PaddleClas代码解析与社区贡献指南

1. 整体代码结构解析

1.1 前言

有用户对PaddleClas的代码做了非常详细的解读可以参考下面的三篇文档。本部分内容大部分也是来自该系列文档在此感谢FutureSI的贡献与解读。

1.2 代码解析

1.2.1 整体代码和目录概览

  • PaddleClas主要代码和目录结构如下
  • configs 文件夹下存放训练脚本和验证脚本的yaml配置文件文件按模型类别存放。
  • dataset 文件夹下存放数据集和用于处理数据集的脚本。脚本负责将数据集处理为适合Dataloader处理的格式。
  • docs 文件夹下存放中英文文档。
  • deploy 文件夹存放的是部署工具,支持 Cpp inference、Hub Serveing、Paddle Lite、Slim量化等多种部署方式。
  • ppcls 文件夹下存放PaddleClas框架主体。模型结构脚本、数据增强脚本、优化脚本等DL程序的标准流程代码都在这里。
  • tools 文件夹下存放用于模型下载、训练、预测的脚本。
  • requirements.txt 文件用于安装 PaddleClas 的依赖项。使用pip进行升级安装使用。

1.2.2 训练模块定义

深度学习模型训练流程框图如下。

具体地,深度学习模型训练过程中,主要包含以下几个核心模块。

  • 数据对于有监督任务来说训练数据一般包含原始数据及其标注。在基于单标签的图像分类任务中原始数据指的是图像数据而标注则是该图像数据所属的类比。PaddleClas中训练时需要提供标签文件形式如下每一行包含一条训练样本分别表示图片路径和类别标签用分隔符隔开默认为空格
train/n01440764/n01440764_10026.JPEG 0
train/n01440764/n01440764_10027.JPEG 0

在代码ppcls/data/reader.py中,包含CommonDataset类,继承自paddle.io.Dataset,该数据集类可以通过一个键值进行索引并获取指定样本。

对于读入的数据,需要通过数据转换,将原始的图像数据进行转换。训练时,标准的数据预处理包含:DecodeImage, RandCropImage, RandFlipImage, NormalizeImage, ToCHWImage。在配置文件中体现如下,数据预处理主要包含在transforms字段中,以列表形式呈现,会按照顺序对数据依次做这些转换。

TRAIN:
    batch_size: 256 # 所有训练设备上的总batch size
    num_workers: 4 # 训练时每块设备上的进程数
    file_list: "./dataset/ILSVRC2012/train_list.txt" # 训练标签文件
    data_dir: "./dataset/ILSVRC2012/" # 训练图片文件夹
    shuffle_seed: 0 # 随机打散的种子数
    transforms:
        - DecodeImage: # 对图像文件进行解码转成numpy矩阵
            to_rgb: True
            channel_first: False
        - RandCropImage: # 对图像做随机裁剪
            size: 224
        - RandFlipImage: # 对图像做随机翻转
            flip_code: 1
        - NormalizeImage: # 对图像做归一化
            scale: 1./255.
            mean: [0.485, 0.456, 0.406]
            std: [0.229, 0.224, 0.225]
            order: ''
        - ToCHWImage: # 将图像从HWC格式转成CHW格式
    mix:
        - MixupOperator: # mixup数据增广在全局配置use_mix=True时生效
            alpha: 0.2

PaddleClas中也包含了AutoAugment, RandAugment等数据增广方法,也可以通过在配置文件中配置,从而添加到训练过程的数据预处理中。每个数据转换的方法均以类实现,方便迁移和复用,更多的数据处理具体实现过程可以参考:ppcls/data/imaug/operators.py

对于组成一个batch的数据也可以使用mixup或者cutmix等方法进行数据增广。PaddleClas中集成了MixupOperator, CutmixOperator, FmixOperator等基于batch的数据增广方法可以在配置文件中配置mix参数进行配置更加具体的实现可以参考ppcls/data/imaug/batch_operators.py

图像分类中,数据后处理主要为argmax操作,在此不再赘述。

  • 模型结构

在配置文件中,模型结构定义如下

ARCHITECTURE:
    name: "EfficientNetB0"
    params: # 模型需要传入的额外参数,如果没有可不填
        padding_type : "SAME"
        override_params:
            drop_connect_rate: 0.1

ARCHITECTURE.name表示模型名称,ARCHITECTURE.params表示需要额外传入的参数,默认为空。所有的模型名称均在/ppcls/modeling/architectures/__init__.py中定义。

对应的,在tools/program.py中,通过create_model方法创建模型对象。

def create_model(architecture, classes_num):
    name = architecture["name"]
    params = architecture.get("params", {})
    return architectures.__dict__[name](class_dim=classes_num, **params)
  • 损失函数

PaddleClas中包含了CELoss, MixCELoss, GoogLeNetLoss, JSDivLoss, MultiLabelLoss等损失函数,均定义在ppcls/modeling/loss.py中。

tools/program.py文件中,使用create_loss构建模型的损失函数不同训练策略中所需要的损失函数与计算方法不同PaddleClas在构建损失函数过程中主要考虑了以下几个因素。

  1. 是否使用label smooth
  2. 是否使用mixup或者cutmix
  3. 是否使用蒸馏方法进行训练
  4. 是否进行多标签训练
def create_loss(feeds,
                out,
                architecture,
                classes_num=1000,
                epsilon=None,
                use_mix=False,
                use_distillation=False,
                multilabel=False):
    if architecture["name"] == "GoogLeNet":
        assert len(out) == 3, "GoogLeNet should have 3 outputs"
        loss = GoogLeNetLoss(class_dim=classes_num, epsilon=epsilon)
        return loss(out[0], out[1], out[2], feeds["label"])

    if use_distillation:
        assert len(out) == 2, ("distillation output length must be 2, "
                               "but got {}".format(len(out)))
        loss = JSDivLoss(class_dim=classes_num, epsilon=epsilon)
        return loss(out[1], out[0])

    if use_mix:
        loss = MixCELoss(class_dim=classes_num, epsilon=epsilon)
        feed_y_a = feeds['y_a']
        feed_y_b = feeds['y_b']
        feed_lam = feeds['lam']
        return loss(out, feed_y_a, feed_y_b, feed_lam)
    else:
        if not multilabel:
            loss = CELoss(class_dim=classes_num, epsilon=epsilon)
        else:
            loss = MultiLabelLoss(class_dim=classes_num, epsilon=epsilon)
        return loss(out, feeds["label"])
  • 优化器和学习率衰减、权重衰减策略

图像分类任务中,Momentum是一种比较常用的优化器PaddleClas中提供了MomentumRMSProp两种优化器策略。

权重衰减策略是一种比较常用的正则化方法主要用于防止模型过拟合。PaddleClas中提供了L1DecayL2Decay两种权重衰减策略。

学习率衰减是图像分类任务中必不可少的精度提升训练方法PaddleClas目前支持Cosine, Piecewise, CosineWarmup, ExponentialWarmup等学习率衰减策略。

在配置文件中,优化器和权重衰减策略可以通过以下的字段进行配置。

OPTIMIZER:
    function: 'Momentum' # Momentum优化器
    params:
        momentum: 0.9
    regularizer:
        function: 'L2' # L1 means L1Decay, L2 means L2Decay
        factor: 0.00010

学习率衰减策略可以通过以下的字段进行配置。

LEARNING_RATE:
    function: 'Piecewise' # Piecewise学习率衰减策略
    params:
        lr: 0.1 # 初始学习率
        decay_epochs: [30, 60, 90] # 学习率下降时对应的epoch数量
        gamma: 0.1 # 学习率衰减倍数

tools/program.py中使用create_optimizer创建优化器和学习率对象。

def create_optimizer(config, parameter_list=None):
    # create learning_rate instance
    lr_config = config['LEARNING_RATE']
    lr_config['params'].update({
        'epochs': config['epochs'],
        'step_each_epoch':
        config['total_images'] // config['TRAIN']['batch_size'],
    })
    lr = LearningRateBuilder(**lr_config)()

    # create optimizer instance
    opt_config = config['OPTIMIZER']
    opt = OptimizerBuilder(**opt_config)
    return opt(lr, parameter_list), lr

不同优化器和权重衰减策略均以类的形式实现,具体实现可以参考文件ppcls/optimizer/optimizer.py;不同的学习率衰减策略可以参考文件ppcls/optimizer/learning_rate.py

  • 训练时评估与模型存储

模型在训练的时候可以设置模型保存的间隔也可以选择每隔若干个epoch对验证集进行评估从而可以保存在验证集上精度最佳的模型。配置文件中可以通过下面的字段进行配置。

save_interval: 1 # 模型保存的epoch间隔
validate: True # 是否进行训练时评估
valid_interval: 1 # 评估的epoch间隔

模型存储是通过 Paddle 框架的 paddle.save() 函数实现的,存储的是模型的 persistable 版本,便于继续训练。具体实现如下

def save_model(net, optimizer, model_path, epoch_id, prefix='ppcls'):
    # just save model in trainer_id=0
    if paddle.distributed.get_rank() != 0:
        return
    model_path = os.path.join(model_path, str(epoch_id))
    _mkdir_if_not_exist(model_path)
    model_prefix = os.path.join(model_path, prefix)
    # save student model during distillation
    _save_student_model(net, model_prefix)

    paddle.save(net.state_dict(), model_prefix + ".pdparams")
    paddle.save(optimizer.state_dict(), model_prefix + ".pdopt")
    logger.info("Already save model in {}".format(model_path))

在保存的时候有两点需要注意:

  1. 只在0号节点上保存模型。否则多卡训练的时候如果所有节点都保存模型到相同的路径则多个节点写文件时可能会发生写文件冲突导致最终保存的模型无法被正确加载。
  2. 优化器参数也需要存储,方便后续的加载断点进行训练。

1.2.3 预测部署代码和方式。

2. 如何贡献代码

2.1 PaddleClas分支说明

PaddleClas未来将维护2种分支分别为

  • release/x.x系列分支为稳定的发行版本分支会适时打tag发布版本适配Paddle的release版本。当前最新的分支为release/2.0分支是当前默认分支适配Paddle v2.0.0。随着版本迭代release/x.x系列分支会越来越多默认维护最新版本的release分支前1个版本分支会修复bug其他的分支不再维护。
  • develop分支为开发分支适配Paddle的develop版本主要用于开发新功能。如果有同学需要进行二次开发请选择develop分支。为了保证develop分支能在需要的时候拉出release/x.x分支develop分支的代码只能使用Paddle最新release分支中有效的api。也就是说如果Paddle develop分支中开发了新的api但尚未出现在release分支代码中那么请不要在PaddleClas中使用。除此之外对于不涉及api的性能优化、参数调整、策略更新等都可以正常进行开发。

PaddleClas的历史分支未来将不再维护。考虑到一些同学可能仍在使用这些分支还会继续保留

  • release/static分支这个分支曾用于静态图的开发与测试目前兼容>=1.7版本的Paddle。如果有特殊需求要适配旧版本的Paddle那还可以使用这个分支但除了修复bug外不再更新代码。
  • dygraph-dev分支这个分支将不再维护也不再接受新的代码请使用的同学尽快迁移到develop分支。

PaddleClas欢迎大家向repo中积极贡献代码下面给出一些贡献代码的基本流程。

2.2 PaddleClas代码提交流程与规范

2.2.1 fork和clone代码

  • 跳转到PaddleClas GitHub首页,然后单击 Fork 按钮,生成自己目录下的仓库,比如 https://github.com/USERNAME/PaddleClas
  • 将远程仓库clone到本地
# 拉取develop分支的代码
git clone https://github.com/USERNAME/PaddleClas.git -b develop
cd PaddleClas

clone的地址可以从下面获取

2.2.2 和远程仓库建立连接

首先通过git remote -v查看当前远程仓库的信息。

origin    https://github.com/USERNAME/PaddleClas.git (fetch)
origin    https://github.com/USERNAME/PaddleClas.git (push)

只有clone的远程仓库的信息也就是自己用户名下的 PaddleClas接下来我们创建一个原始 PaddleClas 仓库的远程主机,命名为 upstream。

git remote add upstream https://github.com/PaddlePaddle/PaddleClas.git

使用git remote -v查看当前远程仓库的信息输出如下发现包括了origin和upstream 2个远程仓库。

origin    https://github.com/USERNAME/PaddleClas.git (fetch)
origin    https://github.com/USERNAME/PaddleClas.git (push)
upstream    https://github.com/PaddlePaddle/PaddleClas.git (fetch)
upstream    https://github.com/PaddlePaddle/PaddleClas.git (push)

这主要是为了后续在提交pull request(PR)时,始终保持本地仓库最新。

2.2.3 创建本地分支

可以基于当前分支创建新的本地分支,命令如下。

git checkout -b new_branch

也可以基于远程或者上游的分支创建新的分支,命令如下。

# 基于用户远程仓库(origin)的develop创建new_branch分支
git checkout -b new_branch origin/develop
# 基于上游远程仓库(upstream)的develop创建new_branch分支
# 如果需要从upstream创建新的分支需要首先使用git fetch upstream获取上游代码
git checkout -b new_branch upstream/develop

最终会显示切换到新的分支,输出信息如下

Branch new_branch set up to track remote branch develop from upstream.
Switched to a new branch 'new_branch'

2.2.4 使用pre-commit勾子

Paddle 开发人员使用 pre-commit 工具来管理 Git 预提交钩子。 它可以帮助我们格式化源代码C++Python在提交commit前自动检查一些基本事宜如每个文件只有一个 EOLGit 中不要添加大文件等)。

pre-commit测试是 Travis-CI 中单元测试的一部分,不满足钩子的 PR 不能被提交到 PaddleClas首先安装并在当前目录运行它

pip install pre-commit
pre-commit install
  • 注意
  1. Paddle 使用 clang-format 来调整 C/C++ 源代码格式,请确保 clang-format 版本在 3.8 以上。
  2. 通过pip install pre-commit和conda install -c conda-forge pre-commit安装的yapf稍有不同的PaddleClas 开发人员使用的是pip install pre-commit

2.2.5 修改与提交代码

可以通过git status查看改动的文件。 对PaddleClas的README.md做了一些修改,希望提交上去。则可以通过以下步骤

git add README.md
pre-commit

重复上述步骤直到pre-comit格式检查不报错。如下所示。

使用下面的命令完成提交。

git commit -m "your commit info"

2.2.6 保持本地仓库最新

获取 upstream 的最新代码并更新当前分支。这里的upstream来自于2.2节的和远程仓库建立连接部分。

git fetch upstream
# 如果是希望提交到其他分支则需要从upstream的其他分支pull代码这里是develop
git pull upstream develop

2.2.7 push到远程仓库

git push origin new_branch

2.2.7 提交Pull Request

点击new pull request选择本地分支和目标分支如下图所示。在PR的描述说明中填写该PR所完成的功能。接下来等待review如果有需要修改的地方参照上述步骤更新 origin 中的对应分支即可。

2.2.8 签署CLA协议和通过单元测试

  • 签署CLA 在首次向PaddlePaddle提交Pull Request时您需要您签署一次CLA(Contributor License Agreement)协议,以保证您的代码可以被合入,具体签署方式如下:
  1. 请您查看PR中的Check部分找到license/cla并点击右侧detail进入CLA网站
  2. 点击CLA网站中的“Sign in with GitHub to agree”,点击完成后将会跳转回您的Pull Request页面

2.2.9 删除分支

  • 删除远程分支

在 PR 被 merge 进主仓库后,我们可以在 PR 的页面删除远程仓库的分支。

也可以使用 git push origin :分支名 删除远程分支,如:

git push origin :new_branch
  • 删除本地分支
# 切换到develop分支否则无法删除当前分支
git checkout develop

# 删除new_branch分支
git branch -D new_branch

2.2.10 提交代码的一些约定

为了使官方维护人员在评审代码时更好地专注于代码本身,请您每次提交代码时,遵守以下约定:

1请保证Travis-CI 中单元测试能顺利通过。如果没过,说明提交的代码存在问题,官方维护人员一般不做评审。

2提交PUll Request前

请注意commit的数量。

原因如果仅仅修改一个文件但提交了十几个commit每个commit只做了少量的修改这会给评审人带来很大困扰。评审人需要逐一查看每个commit才能知道做了哪些修改且不排除commit之间的修改存在相互覆盖的情况。

建议每次提交时保持尽量少的commit可以通过git commit --amend补充上次的commit。对已经Push到远程仓库的多个commit可以参考squash commits after push

请注意每个commit的名称应能反映当前commit的内容不能太随意。

3如果解决了某个Issue的问题请在该PUll Request的第一个评论框中加上fix #issue_number这样当该PUll Request被合并后会自动关闭对应的Issue。关键词包括close, closes, closed, fix, fixes, fixed, resolve, resolves, resolved请选择合适的词汇。详细可参考Closing issues via commit messages

此外,在回复评审人意见时,请您遵守以下约定:

1官方维护人员的每一个review意见都希望得到回复这样会更好地提升开源社区的贡献。

  • 对评审意见同意且按其修改完的给个简单的Done即可
  • 对评审意见不同意的,请给出您自己的反驳理由。

2如果评审意见比较多,

  • 请给出总体的修改情况。
  • 请采用start a review进行回复,而非直接回复的方式。原因是每个回复都会发送一封邮件,会造成邮件灾难。

3. 总结

  • 开源社区依赖于众多开发者与用户的贡献和反馈在这里感谢与期待大家向PaddleClas提出宝贵的意见与pull request希望我们可以一起打造一个领先实用全面的图像分类代码仓库

4. 参考文献

  1. PaddlePaddle本地开发指南
  2. 向开源框架提交pr的过程