PyTorch自定义transform需区分场景:需参与训练/编译的必须继承nn.Module并用torch随机函数;纯数据增强可用callable,但须注意设备一致性、dtype统一及参数/缓冲区的设备迁移。

PyTorch 的 torchvision.transforms 本身不支持直接在 Compose 中插入任意可训练或状态依赖的逻辑,但你可以通过继承 torch.nn.Module 或实现 __call__ 方法来安全扩展——关键在于区分「纯函数式增强」和「需参与梯度/状态管理」的场景。
自定义 Transform 必须继承 torch.nn.Module 才能进模型训练流程
如果你写的增强要和模型一起被 torch.compile 编译、参与反向传播(比如可学习的色彩抖动),它必须是 nn.Module 子类。否则只在数据加载时用,用普通 callable 就够了。
-
nn.Module子类能自动注册参数(如self.gamma = nn.Parameter(torch.tensor(1.0))),可在forward中使用 - 普通函数或带
__call__的类无法被model.parameters()捕获,也不能用torch.compile加速 - 注意:
transforms.Compose接收任意 callable,但内部不会调用其train()/eval(),状态切换需手动处理
写一个带随机性但可复现的增强类,别只靠 random.random()
PyTorch 数据加载器多进程下,random 模块的全局种子不生效,会导致各 worker 增强结果不一致甚至训练崩溃。
- 用
torch.randint或torch.rand,它们尊重torch.manual_seed()和 worker 的worker_init_fn - 在
__call__或forward中显式传入generator参数(如torch.rand((), generator=gen))更可控 - 避免在
__init__里预生成随机数;每次调用都应基于当前 batch 的随机状态
示例(裁剪概率为 0.5 的随机中心裁剪):
立即学习“Python免费学习笔记(深入)”;
class RandomCenterCrop(nn.Module):
def __init__(self, size, p=0.5):
super().__init__()
self.size = size
self.p = p
<pre class="brush:php;toolbar:false;">def forward(self, img):
if torch.rand((), device=img.device) < self.p:
h, w = img.shape[-2:]
th, tw = self.size
i = (h - th) // 2
j = (w - tw) // 2
return img[..., i:i+th, j:j+tw]
return img
组合自定义 Transform 时,顺序和设备一致性容易出错
图像张量如果已在 GPU 上,而你的自定义类内部用了 CPU-only 的 NumPy 操作(如 cv2.resize),会直接报 RuntimeError: expected scalar type Byte but found Float 或设备不匹配错误。
- 所有 tensor 操作统一用 PyTorch 原生函数:
F.resize、F.rotate、F.gaussian_blur(来自torchvision.transforms.functional) - 若必须用 OpenCV,先
.cpu().numpy(),处理完再torch.from_numpy().to(img.device) -
Compose中多个 transform 的输入输出 dtype 应保持一致;例如输入是float32 [C,H,W],就别在中间插一个输出uint8的函数
最常被忽略的是:自定义类若含缓冲区(register_buffer)或参数(nn.Parameter),必须确保在模型 to(device) 时一并迁移——否则前向时张量设备不匹配会静默失败或报错位置诡异。


















