10.4.13. GaNet车道线检测模型训练¶
这篇教程主要是告诉大家如何利用HAT在车道线数据集 CuLane
上从头开始训练一个 GaNet
模型,包括浮点、量化和定点模型。
CuLane
是车道线检测中用的比较多的数据集,很多先进的车道线检测研究都会优先基于这个数据集做好验证。
开始训练模型之前,第一步是准备好数据集,这里我们下载官方的数据集以及相应的标签数据 CuLaneDataset ,
需要注意的是, annotations_new.tar.gz
这个文件必须要最后解压。
解压缩之后数据目录结构如下所示:
tmp_data
|-- driver_100_30frame
|-- xxx.MP4
|-- xxx.lines.txt
|-- xxx.jpg
|-- driver_161_90frame
|-- driver_182_30frame
|-- driver_193_90frame
|-- driver_23_30frame
|-- driver_37_30frame
|-- list
|-- test_split
|-- test0_normal.txt
|-- test1_crowd.txt
|-- test2_hlight.txt
|-- test3_shadow.txt
|-- test4_noline.txt
|-- test5_arrow.txt
|-- test6_curve.txt
|-- test7_cross.txt
|-- test8_night.txt
|-- test.txt
|-- train_gt.txt
|-- train.txt
|-- val_gt.txt
|-- val.txt
其中 list/train.txt
里面是训练数据的路径, list/test.txt
里面是测试数据的路径。
10.4.13.1. 训练流程¶
如果你只是想简单的把 GaNet
的模型训练起来,那么可以首先阅读一下这一章的内容。
和其他任务一样,对于所有的训练,评测任务,HAT统一采用 tools + config
的形式来完成。在准备好原始数据集之后,可以通过下面的流程,
方便地完成整个训练的流程。
10.4.13.1.1. 数据集准备¶
为了提升训练速度,我们对原始的数据集做了一个打包,将其转换为 LMDB
格式的数据集。只需要运行下面的脚本,
就可以成功实现转换:
python3 tools/datasets/culane_packer.py --src-data-dir ${data-dir} --split-name train --pack-type lmdb --num-workers 10 --target-data-dir ${target-data-dir}
python3 tools/datasets/culane_packer.py --src-data-dir ${data-dir} --split-name test --pack-type lmdb --num-workers 10 --target-data-dir ${target-data-dir}
上面这两条命令分别对应转换训练数据集和验证数据集,打包完成之后, ${target-data-dir}
目录下的文件结构应该如下所示:
${target-data-dir}
|-- train_lmdb
|-- test_lmdb
train_lmdb
和 test_lmdb
就是打包之后的训练数据集和验证数据集,接下来就可以开始训练模型。
10.4.13.1.2. 模型训练¶
在网络开始训练之前,你可以使用以下命令先计算一下网络的计算量和参数数量:
python3 tools/calops.py --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
下一步就可以开始训练。训练也可以通过下面的脚本来完成, 在训练之前需要确认配置中数据集路径是否已经切换到已经打包好的数据集路径。
python3 tools/train.py --stage "float" --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
python3 tools/train.py --stage "calibration" --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
python3 tools/train.py --stage "int_infer" --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
由于HAT算法包使用了注册机制,使得每一个训练任务都可以按照这种 train.py
加上 config
配置文件的形式启动。
train.py
是统一的训练脚本,与任务无关,我们需要训练什么样的任务、使用什么样的数据集以及训练相关的超参数设置都在指定的 config
配置文件里面。
上面的命令中 --stage
后面的参数可以是 "float"
、 "calibration"
、 "int_infer"
,
分别可以完成浮点模型、量化模型的训练以及量化模型到定点模型的转化,
其中量化模型的训练依赖于上一步浮点训练产出的浮点模型,定点模型的转化依赖于量化训练产生的量化模型。
10.4.13.1.3. 模型验证¶
在完成训练之后,可以得到训练完成的浮点、量化或定点模型。和训练方法类似,
我们可以用相同方法来对训好的模型做指标验证,得到为 Float
、 Calibration
和 Quantized
的指标,分别为浮点、量化和完全定点的指标。
python3 tools/predict.py --stage "float" --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
python3 tools/predict.py --stage "calibration" --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
python3 tools/predict.py --stage "int_infer" --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
和训练模型时类似, --stage
后面的参数为 "float"
、 "calibration"
、 "int_infer"
时,分别可以完成对训练好的浮点模型、量化模型、定点模型的验证。
10.4.13.1.4. 模型推理¶
HAT
提供了 infer.py
脚本提供了对定点模型的推理结果进行可视化展示:
python3 tools/infer.py --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py --model-inputs img:${img-path} --save-path ${save_path}
10.4.13.1.5. 仿真上板精度验证¶
除了上述模型验证之外,我们还提供和上板完全一致的精度验证方法,可以通过下面的方式完成:
python3 tools/align_bpu_validation.py --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
10.4.13.1.6. 定点模型检查和编译¶
在HAT中集成的量化训练工具链主要是为了地平线的计算平台准备的,因此,对于量化模型的检查和编译是必须的。
我们在HAT中提供了模型检查的接口,可以让用户定义好量化模型之后,先检查能否在 BPU
上正常运行:
python3 tools/model_checker.py --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
在模型训练完成后,可以通过 compile_perf
脚本将量化模型编译成可以上板运行的 hbm
文件,同时该工具也能预估在 BPU
上的运行性能:
python3 tools/compile_perf.py --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
以上就是从数据准备到生成量化可部署模型的全过程。
10.4.13.1.7. ONNX模型导出¶
如果想要导出onnx模型, 运行下面的命令即可:
python3 tools/export_onnx.py --config configs/lane_pred/ganet/ganet_mixvargenet_culane.py
10.4.13.2. 训练细节¶
在这个说明中,我们对模型训练需要注意的一些事项进行说明,主要为 config
的一些相关设置。
10.4.13.2.1. 模型构建¶
GaNet
的网络结构可以参考 论文 ,这里不做详细介绍。
我们通过在 config
配置文件中定义 model
这样的一个 dict
型变量,就可以方便的实现对模型的定义和修改。
from hat.models.backbones.mixvargenet import MixVarGENetConfig
bn_kwargs = {}
radius = 2
hid_dim = 32
attn_ratio = 4
model = dict(
type="GaNet",
backbone=dict(
type='MixVarGENet',
net_config=[
[MixVarGENetConfig(in_channels=32, out_channels=32, head_op='mixvarge_f2', stack_ops=[], stride=1, stack_factor=1, fusion_strides=[], extra_downsample_num=0)],
[MixVarGENetConfig(in_channels=32, out_channels=32, head_op='mixvarge_f4', stack_ops=['mixvarge_f4', 'mixvarge_f4'], stride=2, stack_factor=1, fusion_strides=[], extra_downsample_num=0)],
[MixVarGENetConfig(in_channels=32, out_channels=64, head_op='mixvarge_f4', stack_ops=['mixvarge_f4', 'mixvarge_f4'], stride=2, stack_factor=1, fusion_strides=[], extra_downsample_num=0)],
[MixVarGENetConfig(in_channels=64, out_channels=96, head_op='mixvarge_f2_gb16', stack_ops=['mixvarge_f2_gb16', 'mixvarge_f2_gb16', 'mixvarge_f2_gb16', 'mixvarge_f2_gb16', 'mixvarge_f2_gb16', 'mixvarge_f2_gb16'], stride=2, stack_factor=1, fusion_strides=[], extra_downsample_num=0)],
[MixVarGENetConfig(in_channels=96, out_channels=160, head_op='mixvarge_f2_gb16', stack_ops=['mixvarge_f2_gb16', 'mixvarge_f2_gb16'], stride=2, stack_factor=1, fusion_strides=[], extra_downsample_num=0)]
],
disable_quanti_input =False,
input_channels = 3,
input_sequence_length = 1,
num_classes = 1000,
bn_kwargs = bn_kwargs,
include_top = False,
bias = True,
output_list =[2, 3, 4],
),
neck = dict(
type="GaNetNeck",
fpn_module = dict(
type="FPN",
in_strides=[8, 16, 32],
in_channels=[64, 96, hid_dim],
out_strides=[8, 16, 32],
out_channels=[hid_dim, hid_dim, hid_dim],
),
attn_in_channels=[160],
attn_out_channels=[hid_dim],
attn_ratios=[attn_ratio],
pos_shape=(1, 10, 25)
),
head=dict(
type="GaNetHead",
in_channel=hid_dim,
),
targets=dict(
type="GaNetTarget",
hm_down_scale=8,
radius=radius,
),
post_process=dict(
type="GaNetDecoder",
root_thr=1,
kpt_thr=0.4,
cluster_thr=5,
downscale=8,
),
losses=dict(
type="GaNetLoss",
loss_kpts_cls=dict(
type="LaneFastFocalLoss",
loss_weight=1.0,
),
loss_pts_offset_reg=dict(
type="L1Loss",
loss_weight=0.5,
),
loss_int_offset_reg=dict(
type="L1Loss",
loss_weight=1.0,
),
),
)
模型除了 backbone
之外,还有 neck
、 head
、 targets
、 post_process
和 losses
模块,
在 GaNet
中, backbone
主要是提取图像的特征, neck
主要是特征增强, head
主要是由特征来得到预测的车道线关键点的分数和偏移。
targets
是训练时从 gt
中得到训练的target, post_process
主要是后处理部分,推理的时候使用。
losses
部分采用 论文中的 LaneFastFocalLoss
和 L1Loss
来作为训练的 loss
, loss_weight
是对应的 loss
的权重。
10.4.13.2.2. 数据增强¶
跟 model
的定义一样,数据增强的流程是通过在 config
配置文件中定义 train_data_loader
和 val_data_loader
这两个 dict
来实现的,
分别对应着训练集和验证集的处理流程。以 train_data_loader
为例,
数据增强使用了 FixedCrop
、 RandomFlip
、 Resize
、 RandomSelectOne
、 RGBShift
、 HueSaturationValue
、 JPEGCompress
、 MeanBlur
、 MedianBlur
、 RandomBrightnessContrast
、 ShiftScaleRotate
和 RandomResizedCrop
来增加训练数据的多样性,增强模型的泛化能力。
train_data_loader = dict(
type=torch.utils.data.DataLoader,
dataset=dict(
type="CuLaneDataset",
data_path=train_data_path,
to_rgb=True,
transforms=[
dict(
type="FixedCrop",
size=(0, 270, 1640, 320),
),
dict(
type="RandomFlip",
px=0.5,
py=0.0,
),
dict(
type="Resize",
img_scale=(320, 800),
multiscale_mode='value',
keep_ratio=False,
),
dict(
type='RandomSelectOne',
transforms=[
dict(
type="RGBShift",
r_shift_limit=(-10, 10),
g_shift_limit=(-10, 10),
b_shift_limit=(-10, 10),
p=1.0,
),
dict(
type="HueSaturationValue",
hue_range=(-10, 10),
sat_range=(-15, 15),
val_range=(-10, 10),
p=1.0,
),
],
p=0.7,
),
dict(
type="JPEGCompress",
p = 0.2,
max_quality = 85,
min_quality = 95,
),
dict(
type='RandomSelectOne',
transforms=[
dict(
type="MeanBlur",
ksize=3,
p=1.0,
),
dict(
type="MedianBlur",
ksize=3,
p=1.0,
),
],
p=0.2,
),
dict(
type="RandomBrightnessContrast",
brightness_limit=(-0.2, 0.2),
contrast_limit=(-0.0, 0.0),
p=0.5,
),
dict(
type="ShiftScaleRotate",
shift_limit=(-0.1, 0.1),
scale_limit=(0.8, 1.2),
rotate_limit=(-10, 10),
interpolation=1,
border_mode=0,
p=0.6,
),
dict(
type='RandomResizedCrop',
height=320,
width=800,
scale=(0.8, 1.2),
ratio=(1.7, 2.7),
p=0.6,
),
dict(
type="ToTensor",
to_yuv=False,
),
],
),
sampler=dict(type=torch.utils.data.DistributedSampler),
batch_size=batch_size_per_gpu,
pin_memory=True,
shuffle=True,
num_workers=data_num_workers,
collate_fn=collate_2d,
)
因为最终跑在 BPU
上的模型使用的是 YUV444
的图像输入,而一般的训练图像输入都采用 RGB
的形式,
所以HAT提供 BgrToYuv444
的数据增强来将 RGB
转到 YUV444
的格式。
为了优化训练过程,HAT使用了 batch_processor
,可将一些增强处理放在 batch_processor
中优化训练:
def loss_collector(outputs: dict):
losses = []
for _, loss in outputs.items():
losses.append(loss)
return losses
train_batch_processor = dict(
type="BasicBatchProcessor",
need_grad_update=True,
loss_collector=loss_collector,
batch_transforms=[
dict(type="BgrToYuv444", rgb_input=True),
dict(
type="TorchVisionAdapter",
interface="Normalize",
mean=128.0,
std=128.0,
),
],
)
其中 loss_collector
是一个获取当前批量数据的 loss
的函数。
验证集的数据转换相对简单很多,如下所示:
val_data_loader = dict(
type=torch.utils.data.DataLoader,
dataset=dict(
type="CuLaneDataset",
data_path=val_data_path,
to_rgb=True,
transforms=[
dict(
type="FixedCrop",
size=(0, 270, 1640, 320),
),
dict(
type="Resize",
img_scale=(320, 800),
multiscale_mode='value',
keep_ratio=False,
),
dict(
type="ToTensor",
to_yuv=False,
),
],
),
sampler=dict(type=torch.utils.data.DistributedSampler),
batch_size=batch_size_per_gpu,
pin_memory=True,
shuffle=False,
num_workers=data_num_workers,
collate_fn=collate_2d,
)
val_batch_processor = dict(
type="BasicBatchProcessor",
need_grad_update=False,
loss_collector=None,
batch_transforms=[
dict(type="BgrToYuv444", rgb_input=True),
dict(
type="TorchVisionAdapter",
interface="Normalize",
mean=128.0,
std=128.0,
),
],
)
10.4.13.2.3. 训练策略¶
在 CuLane
数据集上训练浮点模型使用 Cosine
的学习策略配合 Warmup
,以及对 weight
的参数施加L2 norm。
configs/lane_pred/ganet/ganet_mixvargenet_culane.py
文件中的 float_trainer
、 calibration_trainer
、 int_trainer
分别对应浮点、量化、定点模型的训练策略。
下面为 float_trainer
训练策略示例:
import torch
base_lr = 0.01
num_epochs = 240
float_trainer = dict(
type="distributed_data_parallel_trainer",
model=model,
data_loader=train_data_loader,
optimizer=dict(
type=torch.optim.Adam,
params={"weight": dict(weight_decay=4e-5)},
lr=base_lr,
),
batch_processor=train_batch_processor,
stop_by="epoch",
num_epochs=num_epochs,
device=None,
sync_bn=True,
callbacks=[
stat_callback,
loss_show_update,
dict(
type="CosLrUpdater",
warmup_len=1,
warmup_by="epoch",
step_log_interval=10,
),
val_callback,
ckpt_callback,
],
train_metrics=[
dict(type="LossShow"),
],
val_metrics=[
dict(type="CulaneF1Score"),
],
)
10.4.13.2.4. 量化训练¶
关于量化训练中的关键步骤,比如准备浮点模型、算子替换、插入量化和反量化节点、设置量化参数以及算子的融合等,请阅读 量化感知训练 章节的内容。
这里主要讲一下 HAT
的车道线检测中如何定义和使用量化模型。
在模型准备的好情况下,包括量化已有的一些模块完成之后,HAT在训练脚本中统一使用下面的脚本将浮点模型映射到定点模型上来。
model.fuse_model()
model.set_qconfig()
horizon.quantization.prepare_qat(model, inplace=True)
量化训练的整体策略可以直接沿用浮点训练的策略,但学习率和训练长度需要适当调整。
因为有浮点预训练模型,所以量化训练的学习率 Lr
可以很小,
一般可以从 0.001 或 0.0001 开始,并可以搭配 StepLrUpdater
做1-2次 scale=0.1
的 Lr
调整;
同时训练的长度不用很长。此外 weight decay
也会对训练结果有一定影响。
GaNet
示例模型的量化训练策略可见 configs/lane_pred/ganet/ganet_mixvargenet_culane.py
文件。
10.4.13.2.5. 模型检查编译和仿真上板精度验证¶
对于HAT来说,量化模型的意义在于可以在 BPU
上直接运行。
因此,对于量化模型的检查和编译是必须的。前文提到的 compile_perf
脚本也可以让用户定义好量化模型之后,先检查能否在 BPU
上正常运行,
并可通过 align_bpu_validation
脚本获取模型上板精度。用法同前文。