- 计算机视觉
- 深度学习
- 预训练
【免费下载链接】gluon-cv
Gluon CV Toolkit
gluoncv.data.batchify是 GluonCV 为计算机视觉任务量身定制的"样本到 mini-batch"聚合工具集。计算机视觉数据天然存在形状不齐的问题——图片尺寸各异、每张图的目标数量不同、视频片段长度不一——无法直接送入神经网络。本文以 docs/api/data.batchify.rst 为骨架,结合 gluoncv/data/batchify.py 的源码实现、训练脚本与单元测试,系统讲解四大核心批处理函数(Stack、Pad、Append、Tuple)的原理、参数、源码细节与实战组合方式,帮助读者在 SSD、YOLO、CenterNet、Faster R-CNN 等检测/实例分割训练管线中正确选用并自定义 batchify 函数。
为什么视觉任务需要专门的 batchify 函数
在 Gluon 数据管线中,mxnet.gluon.data.DataLoader每次从Dataset中取出若干样本后,会调用batchify_fn将这些样本"折叠"成一个 mini-batch。默认的 batchify 函数假设所有样本形状一致,直接做nd.stack;但在计算机视觉任务中,这一前提几乎不成立:
- 图像尺寸不同:Faster R-CNN 等检测模型直接处理原始长宽比图片,同一 batch 内图片大小各异;
- 标签长度不同:每张图中目标(object)数量不同,标签数组形状为
(num_object, 5),num_object 逐图变化; - 需要保留原始长度:Pad 后的标签必须能还原真实长度,否则无法计算匹配与损失。
GluonCV 因此在gluoncv.data.batchify中提供了一套"即插即用"的批处理函数。模块的公开导出在源码中通过__all__ = ['Stack', 'Pad', 'Append', 'Tuple', 'FasterRCNNTrainBatchify']声明,而 gluoncv/data/init.py 通过from . import batchify将其挂载为gluoncv.data.batchify。
Stack:同形状样本的快速堆叠
Stack将 N 个形状/长度完全一致的输入样本沿新 batch 轴堆叠成单个 NDArray。官方文档给出了三类输入的示例:
from gluoncv.data import batchify # 堆叠多个列表 a = [1, 2, 3, 4] b = [4, 5, 6, 8] c = [8, 9, 1, 2] batchify.Stack()([a, b, c]) # [[1. 2. 3. 4.] # [4. 5. 6. 8.] # [8. 9. 1. 2.]] # <NDArray 3x4 @cpu(0)> # 堆叠多个 numpy.ndarray 或 mxnet.nd.NDArray 亦可 import numpy as np a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) b = np.array([[5, 6, 7, 8], [1, 2, 3, 4]]) batchify.Stack()([a, b]) # <NDArray 2x2x4 @cpu(0)>从 gluoncv/data/batchify.py 的_stack_arrs实现可以看到关键细节:输入为 NDArray 时调用mx.nd.stack(*arrs);输入为列表/numpy 数组时先转成 numpy 再整体np.asarray转 NDArray。值得注意的是,Stack.__call__固定使用use_shared_mem=True,将结果写入cpu_shared上下文,这是为了配合多进程 DataLoader 在 worker 进程间高效传递数据(详见下文"多进程与共享内存"小节)。
Pad:将不等长样本补齐后堆叠
Pad解决"形状不齐但必须堆叠"的问题:所有输入先被填充(pad)到指定维度上的最大长度,再堆叠成 batch。官方文档参数表:
| 参数 | 类型/默认值 | 含义 |
|---|---|---|
axis | int 或 tuple,默认0 | 沿哪个/哪些轴做 padding,数组会填充到该轴的最大维度 |
pad_val | float 或 int,默认0 | 填充值 |
num_shards | int,默认1 | 分片数量,每个 shard 独立填充(对应一块设备) |
ret_length | bool,默认False | 是否同时返回各样本在填充轴上的原始有效长度 |
官方示例演示了三种输入形态:
from gluoncv.data import batchify # 输入为多个不等长列表,默认 axis=0、pad_val=0 a = [1, 2, 3, 4] b = [4, 5, 6] c = [8, 2] batchify.Pad()([a, b, c]) # [[ 1 2 3 4] # [ 4 5 6 0] # [ 8 2 0 0]] # <NDArray 3x4 @cpu(0)> # ret_length=True 时额外返回原始长度 batchify.Pad(ret_length=True)([a, b, c]) # ( # [[1 2 3 4] # [4 5 6 0] # [8 2 0 0]] # <NDArray 3x4 @cpu(0)>, # [4 3 2] # <NDArray 3 @cpu(0)>) # 指定 axis 与填充值 import numpy as np a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) b = np.array([[5, 8], [1, 2]]) batchify.Pad(axis=1, pad_val=-1)([a, b]) # [[[ 1 2 3 4] # [ 5 6 7 8]] # [[ 5 8 -1 -1] # [ 1 2 -1 -1]]] # <NDArray 2x2x4 @cpu(0)>源码级实现细节
Pad.__call__会先检查首元素类型,仅接受mx.nd.NDArray、np.ndarray或list,否则抛出NotImplementedError(见 gluoncv/data/batchify.py)。其底层_pad_arrs_to_max_length(gluoncv/data/batchify.py)值得关注几个设计点:
- 多轴填充:当
axis为 tuple 时(如 Faster R-CNN 训练用的axis=(1, 2)),会逐轴记录原始长度并堆叠成(N, len(axis))的original_length矩阵; - 分片(sharding):
original_length按num_shards用np.array_split切分,每个 shard 独立计算该轴的最大长度——这样每张 GPU 卡拿到的是"按自己 batch 内最大尺寸填充"的数据,避免全局最大尺寸造成浪费;当 batch 无法被 shards 整除时会输出 warning; - 共享内存:
use_shared_mem=True时用mx.nd.full(..., ctx=mx.Context('cpu_shared', 0))分配填充结果,同时original_length以int32存于共享内存; - 回填:形状与目标一致的数组直接整块赋值,不一致时按
slices切片回填到左上角,实现"补齐"效果。
Append:保留各自形状的松散列表
Append不做任何堆叠或填充,仅把样本逐个转为 NDArray 后以列表形式返回,因此对样本形状没有任何约束。代价是后续只能做单样本级操作(如逐图前向),无法享受 batch 运算。
from gluoncv.data import batchify a = [1, 2, 3, 4] b = [4, 5, 6] c = [8, 2] batchify.Append()([a, b, c]) # [ # [[1. 2. 3. 4.]] # <NDArray 1x4 @cpu_shared(0)>, # [[4. 5. 6.]] # <NDArray 1x3 @cpu_shared(0)>, # [[8. 2.]] # <NDArray 1x2 @cpu_shared(0)> # ]Append接受两个构造参数:expand=True(默认)表示在batch_axis(默认 0)上增加一维 batch 轴;expand=False时直接返回原始形状的 NDArray 列表。内部实现_append_arrs(gluoncv/data/batchify.py)同样把结果放到cpu_shared上下文中,方便多进程传输。
在 Faster R-CNN 的验证阶段,图片与标签形状各不相同且无需堆叠,脚本 scripts/detection/faster_rcnn/eval_faster_rcnn.py 使用batchify.Tuple(*[batchify.Append() for _ in range(3)])原样保留三个输出字段。
Tuple:按字段组合多个 batchify 函数
Tuple是组合器:每个样本应是一个 list/tuple,包含多个属性字段;Tuple中第 i 个 batchify 函数作用于第 i 个字段。典型场景是检测任务中每个样本形如(image, label),其中 image 可堆叠、label 需填充。
from gluoncv.data import batchify a = ([1, 2, 3, 4], 0) b = ([5, 7], 1) c = ([1, 2, 3, 4, 5, 6, 7], 0) # 三种等价写法:Tuple(A, B)、Tuple([A, B])、Tuple((A, B)) batchify.Tuple(batchify.Pad(), batchify.Stack())([a, b]) # ( # [[1 2 3 4] # [5 7 0 0]] # <NDArray 2x4 @cpu(0)>, # [0. 1.] # <NDArray 2 @cpu(0)>) # 三个字段:两个 Pad、一个 Stack a = ([1, 2, 3, 4], [5, 6], 1) b = ([1, 2], [3, 4, 5, 6], 0) c = ([1], [2, 3, 4, 5, 6], 0) batchify.Tuple(batchify.Pad(), batchify.Pad(), batchify.Stack())([a, b, c]) # ( # [[1 2 3 4] # [1 2 0 0] # [1 0 0 0]] # <NDArray 3x4 @cpu(0)>, # [[5 6 0 0 0] # [3 4 5 6 0] # [2 3 4 5 6]] # <NDArray 3x5 @cpu(0)>, # [1. 0. 0.] # <NDArray 3 @cpu(0)>)Tuple.__init__(gluoncv/data/batchify.py)支持Tuple(fn1, fn2, ...)与Tuple([...])两种传参方式,并断言每个成员都是可调用对象;__call__中还会校验样本字段数与函数个数一致,随后按列拆分数据ele_fn([ele[i] for ele in data])分别处理。
实战组合:SSD / YOLO / CenterNet 训练脚本中的标准用法
SSD:图片 Stack + 标签 Pad
在 scripts/detection/ssd/train_ssd.py 中,训练集每个样本包含 image、cls_targets、box_targets 三个字段,三者形状一致(anchor 个数固定),因此全部 Stack;而验证集每张图目标数不定,标签用Pad(pad_val=-1)补齐:
from gluoncv.data.batchify import Tuple, Stack, Pad batchify_fn = Tuple(Stack(), Stack(), Stack()) # 训练:堆叠 image、cls_targets、box_targets train_loader = mx.gluon.data.DataLoader( train_dataset.transform(train_transform), batch_size, True, batchify_fn=batchify_fn, last_batch='rollover', num_workers=num_workers) val_batchify_fn = Tuple(Stack(), Pad(pad_val=-1)) # 验证:图片堆叠、标签填充 val_loader = mx.gluon.data.DataLoader( val_dataset.transform(val_transform), batch_size, False, batchify_fn=val_batchify_fn, last_batch='keep', num_workers=num_workers)这段"训练 Stack、验证 Stack+Pad"的模式与官方教程 docs/tutorials/detection/train_ssd_voc.py 完全一致:Pad负责把变长标签补齐,Stack负责堆叠形状一致的张量,Tuple负责为不同输出字段指定不同行为。
YOLO:六路 Stack 加一路 Pad
scripts/detection/yolo/train_yolo.py 展示了更复杂的组合——YOLO 训练样本包含 6 个目标张量(多尺度输出)与 1 个变长标签:
batchify_fn = Tuple(*([Stack() for _ in range(6)] + [Pad(axis=0, pad_val=-1) for _ in range(1)]))即前 6 个字段用Stack()堆叠,最后 1 个字段用Pad(axis=0, pad_val=-1)沿第 0 轴填充到该 batch 最大目标数。
CenterNet:列表式 Tuple
scripts/detection/center_net/train_center_net.py 演示了Tuple([...])列表写法与验证阶段Tuple(Stack(), Pad(pad_val=-1))的标准组合,与 SSD 的训练/验证差异策略相同。
FasterRCNNTrainBatchify:检测专用的端到端批处理
FasterRCNNTrainBatchify是面向 Faster R-CNN 训练流程的专用批处理类(gluoncv/data/batchify.py),每个样本固定包含 5 个元素:img, bbox, rpn_cls_targets, rpn_box_targets, rpn_box_masks。其构造参数为:
| 参数 | 类型/默认值 | 含义 |
|---|---|---|
net | mxnet.gluon.HybridBlock | Faster R-CNN 网络,用于推断特征图输出形状 |
num_shards | int,默认1 | 分片数,每个 shard 对应一块设备 |
其内部逻辑分为三步:
- 图片与标签填充:内部组合了
Pad(axis=(1, 2), num_shards=num_shards)(沿高、宽两轴填充图像)与Pad(pad_val=-1, num_shards=num_shards)(bbox 标签填充为 -1,作为忽略标记); - RPN 目标按特征图形状对齐:对每张输入图,通过
net.features(mx.sym.var(name='data'))的符号图(feat_sym)调用infer_shape得到各层特征图的高宽w, h,然后把每个样本的 cls/box/mask 目标填充到对应特征图尺寸,并reshape为(1, -1)与(1, -1, 4)后沿特征层维度concat; - 分片打包:按
num_shards切分后使用mx.nd.concat(..., dim=0)聚合,并将所有结果转移到cpu_shared上下文返回。
训练脚本 scripts/detection/faster_rcnn/train_faster_rcnn.py 中,FasterRCNNTrainBatchify(net, num_shards)被直接传入DataLoader(batchify_fn=...),与SplitSortedBucketSampler(按图像长宽比分桶排序、保证同桶形状相近)配合使用;验证阶段则退化为Tuple(*[Append() for _ in range(3)])保留原始形状。教程 docs/tutorials/detection/train_faster_rcnn_voc.py 对此有同样说明:Faster R-CNN 处理各种长宽比、各种形状的原始图像,Append既不堆叠也不填充,使每个样本保持自身形状。
在此基础上,MaskRCNNTrainBatchify(gluoncv/data/batchify.py)将样本扩展为 6 个元素,内部复用FasterRCNNTrainBatchify处理前 5 个字段,并用Pad(axis=(0, 1, 2), pad_val=0)单独处理 mask 字段,被 scripts/instance/mask_rcnn/train_mask_rcnn.py 使用。
多进程 DataLoader 与共享内存机制
细心的读者会发现,上述四个类在内部实现中无一例外地把结果写入了mx.Context('cpu_shared', 0)。这是 GluonCV 针对num_workers > 0多进程数据加载的刻意设计:worker 进程在各自内存中生成 batch 后,需要通过 IPC 传回主进程,普通内存拷贝代价高昂;而共享内存(cpu_shared)可被多个进程直接读写,显著降低传输开销。
在 gluoncv/data/dataloader.py 中可以看到配套的默认实现:default_mp_batchify_fn与default_pad_batchify_fn是 GluonCV 自己维护的"默认 + 共享内存"版本,前者用于无填充的普通堆叠,后者在标签不等长时按pad = max([l.shape[0] ...])用-1填充为统一形状;tsn_mp_batchify_fn则针对时序段网络(TSN),将默认的nd.stack改为nd.concat(dim=0),因为其 batch 维度已经存在。此外,DetectionDataLoader与RandomTransformDataLoader在num_workers > 0时也分别默认采用default_mp_pad_batchify_fn/default_mp_batchify_fn(gluoncv/data/dataloader.py)。
其他形态的 batchify:分割与深度估计
gluoncv.data.batchify之外,仓库还提供了两类面向特定任务的批处理函数,可作为自定义 batchify 的参考:
ms_batchify_fn(gluoncv/data/segbase.py):多尺寸(multi-size)分割任务专用。它对字符串与 NDArray 直接返回列表、对 tuple 递归展开,保证推理时多尺度输入不被强制堆叠,被 scripts/segmentation/test.py 用于多尺度测试;dict_batchify_fn(gluoncv/data/kitti/kitti_utils.py):按 dict 键递归批处理的函数,NDArray 走mx.nd.stack、dict 按键分别递归,被 scripts/depth/trainer.py 等单目深度估计脚本作为batchify_fn使用。
单元测试验证
仓库在 tests/unittests/test_data_dataloader.py 中对 batchify 的组合行为做了系统性验证:test_detection_dataloader构造了一个目标数随机的DummyDetectionDataset(每个样本返回(300, 300, 3)图像与(num_object, 5)标签),遍历num_workers ∈ {0,1,2,4}、shuffle开闭与last_batch ∈ {keep, discard, rollover}的全部组合,分别用Tuple(Stack(), Pad(pad_val=-1))与Tuple(Append(), Append())作为 batchify_fn 迭代 DataLoader(tests/unittests/test_data_dataloader.py)。此外 tests/unittests/test_data_transforms.py 在 SSD、YOLO、CenterNet、Faster R-CNN 的训练/验证 loader 构造中复用了Tuple、Stack、Pad、Append的全部典型组合,验证了各检测任务的批处理约定。
小结
gluoncv.data.batchify以四个基础构件覆盖了视觉批处理的全部形态:
| 函数 | 适用场景 | 输出形态 |
|---|---|---|
Stack | 形状一致的张量 | 单个 NDArray |
Pad | 形状不一致但需堆叠(如变长标签、变尺寸图片) | 单个 NDArray(可选返回有效长度) |
Append | 形状各异、无需统一(如 Faster R-CNN 验证) | NDArray 列表 |
Tuple | 按字段组合不同策略 | 各字段独立结果的 tuple |
配合FasterRCNNTrainBatchify/MaskRCNNTrainBatchify等专用类、cpu_shared共享内存机制以及 scripts/detection 下的各类训练脚本范例,开发者可以快速为自己的数据集与模型定制正确、高效的批处理管线。需要进一步研究时,可直接阅读 gluoncv/data/batchify.py 源码与其在 tests/unittests 中的测试用例。
- 计算机视觉
- 深度学习
- 预训练
【免费下载链接】gluon-cv
Gluon CV Toolkit
相关推荐
PaddleNLP paddlenlp.data.collate 批处理函数详解:Stack、Pad、Tuple 与 Dict 的组合实战
PaddleNLP paddlenlp.data.collate 批处理函数详解:Stack、Pad、Tuple 与 Dict 的组合实战 本文围绕 Paddl
人工智能大模型预训练微调LoRARLHF强化学习分布式训练模型推理服务推理引擎模型量化模型压缩本地部署NLP5分钟读懂palera1n越狱:从DFU模式到成功上手的完整指南
5分钟读懂palera1n越狱:从DFU模式到成功上手的完整指南 如果你的抽屉里还躺着一台iPhone 6s、iPhone 7、iPhone 8或者iPhone
CLI固件如何使用RedwoodJS导入器实现高效数据导入与批量处理:完整指南
如何使用RedwoodJS导入器实现高效数据导入与批量处理:完整指南 RedwoodJS是一个全栈JavaScript框架,专为现代web应用开发设计。其内置的
后端前端Web框架开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考