Contrib 代码提交规范 - PaddlePaddle/Contrib GitHub Wiki

Contrib 代码提交规范

1、目录结构

需要在 Contrib 目录下新建项目,建议的目录结构如下:

./ModelName
|-- config               # 参数配置文件夹
|-- data                 # 数据文件夹
|-- images               # 图片文件夹
|-- model                # 模型实现文件夹
|-- utils                # 工具类API文件夹
|-- README_cn.md         # 中文用户手册
|-- README.md            # 英文用户手册
|-- eval.py              # 执行评估功能的代码
|-- main.py 			 # 主函数,负责调用所有功能
|-- predict.py           # 执行预测功能的代码
|-- run.sh               # 运行脚本,需要提供不同环境下运行的示例
|- train.py              # 执行训练功能的代码
  • config: 存储模型配置相关文件的文件夹,保存模型的配置信息,如 configs.py、configs.yml
  • data: 存储数据相关文件的文件夹,包含数据下载、数据处理等,如 dataset_download.py、dataset_process.py
  • images: 存储项目相关的图片
  • model: 存储模型相关文件的文件夹,保存模型的实现,如 resnet.py、cyclegan.py
  • utils: 存储工具类相关文件的文件夹
  • README_cn.md: 中文版当前模型的使用说明,规范参考 README内容要求
  • README.md: 英文版当前模型的使用说明,规范参考 README内容要求
  • eval.py: 执行评估的脚本
  • main.py: 主程序入口,通过命令行,执行不同的逻辑
  • predict.py: 执行预测的脚本,需要能单独运行
  • run.sh: 运行脚本,需要给出不同环境下(GPU单卡多卡,CPU多核),如何进行训练,预测,评估等功能。
  • train.py: 执行训练的脚本,需要能单独运行

2、功能实现

模型需要提供的功能包含:

  • 训练:可以在GPU单卡/多卡,CPU多核的环境下执行训练
  • 预测:可以在GPU单卡和CPU单核下执行预测
  • 评估:可以在GPU单卡和CPU单核下执行评估
  • 使用自定义数据:要求模型可以灵活支持/适配自定义数据,可以通过在readme中加入数据格式描部分和如何使用自定义数据章节解决。

3、命名规范和使用规范

  • 文件和文件夹命名中,尽量使用下划线'_ '代表空格,不要使用'-'
  • 模型定义过程中,需要有一个统一的变量(parameter)命名管理手段,如尽量手动声明每个变量的名字并支持名称可变,禁止将名称定义为一个常数(如"embedding"),避免在复用代码阶段出现各种诡异的问题。
  • 重要文件,变量的名称定义过程中需要能够通过名字表明含义,禁止使用含混不清的名称,如net.py, aaa.py等。
  • 在代码中定义path时,需要使用os.path.join完成,禁止使用string加的方式,导致模型对windows环境缺乏支持。

4、注释和Licenses

对于代码中重要的部分,需要加入注释介绍功能,帮助用户快速熟悉代码结构,包括但不仅限于:

  • Dataset、DataLoader的定义。
  • 整个模型定义,包括input,运算过程,loss等内容。
  • init,save,load,等io部分
  • 运行中间的关键状态,如print loss,save model等。

如:

import random

from paddle.io import Dataset
from paddle.vision.transforms import transforms as T


class PetDataset(Dataset):
    """
    Pet 数据集定义
    """
    def __init__(self, mode='train'):
        """
        构造函数
        """
        self.image_size = IMAGE_SIZE
        self.mode = mode.lower()
        
        assert self.mode in ['train', 'test', 'predict'], \
            "mode should be 'train' or 'test' or 'predict', but got {}".format(self.mode)
        
        self.train_images = []
        self.label_images = []

        with open('./{}.txt'.format(self.mode), 'r') as f:
            for line in f.readlines():
                image, label = line.strip().split('\t')
                self.train_images.append(image)
                self.label_images.append(label)
        
    def _load_img(self, path, color_mode='rgb', transforms=[]):
        """
        统一的图像处理接口封装,用于规整图像大小和通道
        """
        with open(path, 'rb') as f:
            img = PilImage.open(io.BytesIO(f.read()))
            if color_mode == 'grayscale':
                # if image is not already an 8-bit, 16-bit or 32-bit grayscale image
                # convert it to an 8-bit grayscale image.
                if img.mode not in ('L', 'I;16', 'I'):
                    img = img.convert('L')
            elif color_mode == 'rgba':
                if img.mode != 'RGBA':
                    img = img.convert('RGBA')
            elif color_mode == 'rgb':
                if img.mode != 'RGB':
                    img = img.convert('RGB')
            else:
                raise ValueError('color_mode must be "grayscale", "rgb", or "rgba"')
            
            return T.Compose([
                T.Resize(self.image_size)
            ] + transforms)(img)

    def __getitem__(self, idx):
        """
        返回 image, label
        """
        train_image = self._load_img(self.train_images[idx], 
                                     transforms=[
                                         T.Transpose(), 
                                         T.Normalize(mean=127.5, std=127.5)
                                     ]) # 加载原始图像
        label_image = self._load_img(self.label_images[idx], 
                                     color_mode='grayscale',
                                     transforms=[T.Grayscale()]) # 加载Label图像
    
        # 返回image, label
        train_image = np.array(train_image, dtype='float32')
        label_image = np.array(label_image, dtype='int64')
        return train_image, label_image
        
    def __len__(self):
        """
        返回数据集总数
        """
        return len(self.train_images)

对于整个模型代码,都需要在文件头内加入licenses,readme中加入licenses标识。

文件头内licenses格式如下:

#encoding=utf8
# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

5、其他问题

  • 使用 paddle 2.0 API开发,不使用 paddle.fluid.* 下的API;
  • 代码封装得当,易读性好,不用一些随意的变量/类/函数命名
  • 注释清晰,不仅说明做了什么,也要说明为什么这么做
  • 如果模型依赖paddlepaddle未涵盖的依赖(如 pandas),则需要在README中显示提示用户安装对应依赖
  • 随机控制,需要尽量固定含有随机因素模块的随机种子,保证模型可以正常复现
  • 超参数:模型内部超参数禁止写死,尽量都可以通过配置文件进行配置。

6 、README 内容&格式说明

模型的readme共分为以下几个部分,示例见:models/README_cn.md

# 模型名称         
TOC       
## 一、简介
## 二、复现精度
## 三、数据集
## 四、环境依赖
## 五、快速开始
## 六、代码结构与详细说明
## 七、模型信息

具体格式&说明如下:

模型名称

模型的具体名称,如:# ResNet50

TOC 模型目录,可以使用gh-md-toc生成

一、简介

简单的介绍模型,以及模型的主要架构或主要功能,如果能给出效果图,可以在简介的下方直接贴上图片,展示模型效果,然后另起一行,按如下格式给出论文及链接。

论文:title

二、复现精度

三、数据集

给出数据集的链接,然后按格式描述数据集大小与数据集格式即可。

格式如下:

  • 数据集大小:关于数据集大小的描述,如类别,数量,图像大小等等;

  • 数据格式:关于数据集格式的说明

四、环境依赖

主要分为两部分介绍,一部分是支持的硬件,另一部分是框架等环境的要求,格式如下:

  • 硬件:

  • 框架:

    • PaddlePaddle >= 2.0.0

五、快速开始

需要给出快速训练、预测、使用预训练模型预测的使用说明;

六、代码结构与详细说明

需要用一小节描述整个项目的代码结构,用一小节描述项目的参数说明,之后各个小节详细的描述每个功能的使用说明;

七、模型信息

以表格的信息,给出模型相关的信息。