DeepVAC提供了基于PyTorch的AI项目的工程化规范。为了达到这一目标,DeepVAC包含了:
诸多PyTorch AI项目的内在逻辑都大同小异,因此DeepVAC致力于把更通用的逻辑剥离出来,从而使得工程代码的准确性、易读性、可维护性上更具优势。
如果想使得AI项目符合DeepVAC规范,需要仔细阅读DeepVAC标准。
1. 阅读DeepVAC标准
可以粗略阅读,建立起第一印象。
DeepVAC的依赖有:
- Python3。不支持Python2,其已被废弃;
- 依赖包:torch, torchvision, tensorboard, scipy, numpy, cv2, Pillow;
- 字体文件(可选):如果使用text synthesis,请安装字体文件:https://github.com/CivilNet/SYSZUXfont;
这些依赖使用pip命令(或者git clone)自行安装,不再赘述。
在DeepVAC内部,我们尽量使用最新版的PyTorch版本,并且使用Docker容器(实际上是基于Docker的更复杂的MLab2.0系统)进行训练和发布。我们为用户提供了构建好的Docker镜像,帮助用户省掉不必要的环境配置:
#只使用cpu
docker run -it gemfield/pytorch:1.8.0-11.0.3-cudnn8-devel-ubuntu20.04 bash
#使用GPU的话
docker run --gpus all -it gemfield/pytorch:1.8.0-11.0.3-cudnn8-devel-ubuntu20.04 bash
该Docker镜像的Dockerfile参考:Dockerfile
可以使用pip来进行安装:
pip3 install deepvac
或者
python3 -m pip install deepvac
如果你需要使用deepvac在github上的最新代码,就需要使用如下的开发者模式:
- 克隆该项目到本地:
git clone https://github.com/DeepVAC/deepvac
- 在你的入口文件中添加:
import sys
#replace with your local deepvac directory
sys.path.append('/home/gemfield/github/deepvac')
- 初始化自己项目的git仓库;
- 在仓库中创建第一个研究分支,比如分支名为 LTS_b1_aug9_movie_video_plate_130w;
- 切换到上述的LTS_b1分支中,开始coding;
配置文件的文件名均为 config.py,在代码开始处添加from deepvac.syszux_config import *
;
所有用户的配置都存放在这个文件里。 有些配置是全局唯一的,则直接配置如下:
config.device = "cuda"
config.output_dir = "output"
config.log_dir = "log"
config.log_every = 10
......
有些配置在train、val、test上下文中有不同的值,比如batch_size,则配置在对应的上下文中:
#in train context
config.train.batch_size = 128
#in val context
config.val.batch_size = 32
......
一个完整的config.py例子可以参考 config.py例子
然后用下面的方式来使用 config.py文件:
from config import config as conf
vac = Deepvac(conf)
之后,代码中一般通过如下方式来读写配置项
#通过conf模块来访问
print(conf.log_dir)
#在类中可以通过self.conf成员访问配置
print(self.conf.train.batch_size)
编写该文件,用于产生数据集和data/train.txt,data/val.txt (待完善)
编写该文件,用于实现数据增强策略; 继承syszux_executor模块中的Executor类体系,比如:
class MyAugExecutor(Executor):
def __init__(self, deepvac_config):
super(MyAugExecutor, self).__init__(deepvac_config)
ac1 = AugChain('RandomColorJitterAug@0.5 => MosaicAug@0.5',deepvac_config)
ac2 = AugChain('MotionAug || GaussianAug',deepvac_config)
self.addAugChain('ac1', ac1, 1)
self.addAugChain('ac2', ac2, 0.5)
(待完善)
代码编写在train.py文件中。 继承syszux_loader模块中的Dataset类体系,比如FileLineDataset类提供了对如下train.txt对装载封装:
#train.txt,第一列为图片路径,第二列为label
img0/1.jpg 0
img0/2.jpg 0
...
img1/0.jpg 1
...
img2/0.jpg 2
...
有时第二列是字符串,并且想把FileLineDataset中使用Image读取图片对方式替换为cv2,那么可以通过如下的继承方式来重新实现:
class FileLineCvStrDataset(FileLineDataset):
def _buildLabelFromLine(self, line):
line = line.strip().split(" ")
return [line[0], line[1]]
def _buildSampleFromPath(self, abs_path):
#we just set default loader with Pillow Image
sample = cv2.imread(abs_path)
if self.transform is not None:
sample = self.transform(sample)
return sample
哦,FileLineCvStrDataset也已经是syszux_loader模块中提供的类了。
再比如,在例子a_resnet_project 中,NSFWTrainDataset就继承了deepvac库中的ImageFolderWithTransformDataset类:
class NSFWTrainDataset(ImageFolderWithTransformDataset):
def __init__(self, nsfw_config):
super(NSFWTrainDataset, self).__init__(nsfw_config)
代码写在train.py文件中,继承syszux_deepvac模块中的DeepvacTrain类,或者DeepvacDDP类(用于分布式训练)。继承DeepvacTrain类的子类必须(重新)实现以下方法才能够开始训练:
类的方法(*号表示必需重新实现) | 功能 | 备注 |
---|---|---|
* initNetWithCode | 初始化self.net成员 | 用于初始化网络,在此方法中手动将网络加载到device设备上 |
* initCriterion | 初始化self.criterion成员 | 用于初始化损失/评价函数 |
initOptimizer | 初始化self.optimizer成员 | 用于初始化优化器,默认初始化为SGD |
initScheduler | 初始化self.scheduler成员 | 默认初始化为torch.optim.lr_scheduler |
* initTrainLoader | 初始化self.train_loader成员 | 初始化用于训练的DataLoader |
* initValLoader | 初始化self.val_loader成员 | 初始化用于验证的DataLoader |
preEpoch | 每轮Epoch之前的操作 | 默认啥也不做 |
preIter | 每个batch迭代之前的操作 | 默认会将数据加载到device上,并初始化self.sample、self.target,并对上一个迭代计算得到的梯度进行zero_grad操作 |
postIter | 每个batch迭代之后的操作 | 默认啥也不做 |
postEpoch | 每轮Epoch之后的操作 | 默认会调用self.scheduler.step() |
doForward | 网络前向推理过程 | 默认会将推理得到的值赋值给self.output成员 |
doLoss | 计算loss的过程 | 默认会使用self.output和self.target进行计算得到此次迭代的loss |
doBackward | 网络反向传播过程 | 默认调用self.loss.backward() |
doOptimize | 网络权重更新的过程 | 默认调用self.optimizer.step() |
一个train.py的例子 train.py。
如果使用了DDP,那么除了继承自DeepvacDDP类外,还需要在initTrainLoader中初始化self.train_sampler:
def initTrainLoader(self):
self.train_dataset = FaceDataset(self.conf)
self.train_sampler = torch.utils.data.distributed.DistributedSampler(self.train_dataset)
self.train_loader = DataLoader(self.train_dataset, batch_size=self.conf.train_batch_size, shuffle=(self.train_sampler is None), pin_memory=True, num_workers=self.conf.num_workers,sampler=self.train_sampler)
代码写在test.py文件中,继承syszux_deepvac模块中的Deepvac类。和train.py中的train/val的本质不同在于:
- 舍弃train/val上下文;
- 不再使用DataLoader装载数据,开始使用OpenCV等三方库来直接读取图片样本;
- 网络不再使用autograd上下文;
- 不再计算loss、acc等;取而代之的是使用Deepvac的*Report模块来进行准确度、速度方面的衡量;
- 代码更便于生产环境的部署;
继承Deepvac类的子类必须(重新)实现以下方法才能够开始测试:
类的方法(*号表示必需重新实现) | 功能 | 备注 |
---|---|---|
* initNetWithCode | 初始化self.net成员 | 用于初始化网络,在此方法中手动将网络转移到device设备中 |
* process | 网络的推理计算过程 | 在该过程中,通过report.add(gt, pred)添加测试结果,生成报告 |
一个test.py的小例子 test.py。开始测试前,必须在config.py中配置config.model_path
。
基于deepvac的PyTorch项目,可以通过在config.py中添加一些配置项来自动实现特定的功能。
#单卡训练和测试所使用的device,多卡请使用DeepvacDDP
config.device = "cuda"
#是否禁用git branch约束
config.disable_git = False
#模型输出和加载所使用的路径,非必要不要改动
config.output_dir = "output"
#日志输出的目录,非必要不要改动
config.log_dir = "log"
#每多少次迭代打印一次训练日志
config.log_every = 10
#Dataloader的线程数
config.num_workers = 3
#学习率
config.lr = 0.01
#学习率下降比
config.lr_factor = 0.2703
#SGD相关
config.momentum = 0.9
config.nesterov = False
config.weight_decay = None
#使用MultiStepLR时的学习率下降Epoch idx
config.milestones = [2,4,6,8,10]
#训练的batch size
config.train.batch_size = 128
#训练多少个Epoch
config.epoch_num = 30
#一个Epoch中保存多少次模型和Checkpoint文件
config.save_num = 5
#checkpoint_suffix一旦配置,则启动train.py的时候将加载output/<git_branch>/checkpoint:<checkpoint_suffix>
#训练将会从Epoch10重新开始
#不配置或者配置为空字符串,表明从头开始训练
config.checkpoint_suffix = '2020-09-01-17-37_acc:0.9682857142857143_epoch:10_step:6146_lr:0.00011543040395151496.pth'
#验证时所用的batch size
config.val.batch_size = None
#测试和验证不同之处有很多,其中一点就是要显式的从文件系统上加载训练过程中保存的模型:
#model_path指定要加载模型的路径
config.model_path = '/root/.cache/torch/hub/checkpoints/resnet50-19c8e357.pth'
要启用分布式训练,你的类需要继承DeepvacDDP,并且进行如下配置:
#dist_url,单机多卡无需改动,多机训练一定要修改
config.dist_url = "tcp://localhost:27030"
#rank的数量,一定要修改
config.world_size = 3
以下两个配置为命令行参数,不是config.py中的配置:
#从0开始
--rank <rank_idx>
#从0开始
--gpu <gpu_idx>
上述的配置表明我们将使用3个进程在3个CUDA设备上进行训练。配置完成后,我们在命令行手工启动3个进程:
python train.py --rank 0 --gpu 0
python train.py --rank 1 --gpu 1
python train.py --rank 2 --gpu 2
Deepvac会自动在log/<git_branch>/下写入tensorboard数据,如果需要在线可视化,则还需要如下配置:
# 如果不配置,则不启用tensorboard服务
config.tensorboard_port = "6007"
# 不配置的话为0.0.0.0,如非必要则无需改变
config.tensorboard_ip = None
如果要转换PyTorch模型到TorchScript,你需要设置如下的配置:
#通过script的方式将pytorch训练的模型编译为TorchScript模型
config.script_model_dir = <your_script_model_dir_only4smoketest>
#通过trace的方式将pytorch训练的模型转换为TorchScript模型
config.trace_model_dir = <your_trace_model_dir_only4smoketest>
注意:
- 一旦配置上面的参数后,Deepvac会在第一次迭代的时候,进行冒烟测试,也就是测试网络是否能够成功转换为TorchScript。之后,在每次保存PyTorch模型的时候,会同时保存TorchScript;
- <your_trace_model_dir_only4smoketest> 仅用于冒烟测试,真正的存储目录为PyTorch模型所在的目录,无需用户额外指定。
如果要转换PyTorch模型到ONNX,你需要设置如下的配置:
#输出config.onnx_model_dir
config.onnx_model_dir = <your_onnx_model_dir_only4smoketest>
注意:
- 一旦配置上面的参数后,Deepvac会在第一次迭代的时候,进行冒烟测试,也就是测试网络是否能够成功转换为ONNX。之后,在每次保存PyTorch模型的时候,会同时保存ONNX。
- <your_onnx_model_dir_only4smoketest> 仅用于冒烟测试,真正的存储目录为PyTorch模型所在的目录,无需用户额外指定。
如果要转换PyTorch模型到NCNN,你需要设置如下的配置:
# NCNN的文件路径, ncnn.arch ncnn.bin
config.ncnn_model_dir = <your_ncnn_model_dir_only4smoketest>
# onnx2ncnn可执行文件的路径,https://github.com/Tencent/ncnn/wiki/how-to-build#build-for-linux-x86
config.onnx2ncnn = <your_onnx2ncnn_executable_file>
注意:
- 一旦配置上面的参数后,Deepvac会在第一次迭代的时候,进行冒烟测试,也就是测试网络是否能够成功转换为NCNN。之后,在每次保存PyTorch模型的时候,会同时保存NCNN。
- <your_ncnn_model_dir_only4smoketest> 仅用于冒烟测试,真正的存储目录为PyTorch模型所在的目录,无需用户额外指定。
如果要转换PyTorch模型到CoreML,你需要设置如下的配置:
config.coreml_model_dir = <your_coreml_model_dir_only4smoketest>
config.coreml_preprocessing_args = dict(is_bgr=False, image_scale = 1.0 / 255.0, red_bias = 0, green_bias = 0, blue_bias = 0,image_format='NCHW')
#config.coreml_preprocessing_args = dict(is_bgr=False, image_scale = 1.0 / (0.226 * 255.0), red_bias = -0.485 / 0.226, green_bias = -0.456 / 0.226, blue_bias = -0.406 / 0.226,image_format='NCHW')
config.minimum_ios_deployment_target = '13'
#如果类别多,使用代码初始化这个值
config.coreml_class_labels = ["cls1","cls2","cls3","cls4","cls5","cls6"]
config.coreml_mode = 'classifier'
注意:
- 一旦配置上面的参数后,Deepvac会在第一次迭代的时候,进行冒烟测试,也就是测试网络是否能够成功转换为CoreML。之后,在每次保存PyTorch模型的时候,会同时保存CoreML。
- <your_coreml_model_dir_only4smoketest> 仅用于冒烟测试,真正的存储目录为PyTorch模型所在的目录,无需用户额外指定。
- 参考转换PyTorch模型到CoreML 获取更多参数的用法。
如果要开启自动混合精度训练(AMP),你只需要设置如下配置即可:
config.amp = True
详情参考PyTorch的自动混合精度。
目前PyTorch有三种量化方式,详情参考PyTorch的量化:
- 动态量化
- 静态量化
- 量化感知训练
一次训练任务中只能开启一种。
要开启动态量化,你需要设置如下的配置:
config.dynamic_quantize_dir = <your_quantize_model_output_dir_only4smoketest>
要开启静态量化,你需要设置如下配置:
config.static_quantize_dir = <your_quantize_model_output_dir_only4smoketest>
# backend 为可选,默认为fbgemm
config.quantize_backend = <'fbgemm' | 'qnnpack'>
开启QAT后,整个训练任务的Net就会转变为量化模型,也即所有trace、script、onnx、ncnn、coreml、amp等作用的对象已经变为量化感知模型。要开启QAT,你需要设置如下配置:
config.qat_dir = <your_quantize_model_output_dir_only4smoketest>
# backend 为可选,默认为fbgemm
config.quantize_backend = <'fbgemm' | 'qnnpack'>
注意:
- 一旦配置上面的参数后,Deepvac会在第一次迭代的时候,进行冒烟测试,也就是测试网络是否能够量化成功。之后,在每次保存PyTorch模型的时候,会同时保存量化模型(QAT有点特殊,直接替换了之前的模型)。
- <your_quantize_model_output_dir_only4smoketest> 仅用于冒烟测试,真正的存储目录为PyTorch模型所在的目录,无需用户额外指定。