【工程】mmcls中EfficientNet网络转onnx格式问题记录
转换问题
针对mmcls中EfficientNet网络转onnx文件,出现如下错误:
1)首先,使用mmcls项目中提供tools/deployment/pytorch2onnx.py
脚本进行转换,命令如下:
python tools/deployment/pytorch2onnx.py your/config/path --checkpoint your/model/path --verify --show --output-file save/onnx/path --shape 224 224
2)虽然转换为onnx文件成功,但加载并检查该onnx文件时如下报错:
onnxruntime.capi.onnxruntime_pybind11_state.Fail: [ONNXRuntimeError] : 1 : FAIL : Node (Concat_42) Op (Concat) [ShapeInferenceError] All inputs to Concat must have same rank. Input 4 has rank 1 != 2
错误信息大概意思是:对于concat算子输入的多个张量元素的维度不一致。
Tip: 所使用的mmcls是1.0版本之前,目前官方项目名更改为mmpretrain
定位问题
对于onnx转换不适配问题,需要定位清楚是哪个算子导致的错误。
1.首先使用Netron
软件打开onnx文件模型,可以查看网络中各节点连接和节点信息;
2.根据错误提示中说的Node (Concat_42) Op
节点信息,找到对应的节点,并对应到深度学习框架中的具体算子。
3.根据分析,是EfficientNet中的Conv2dAdaptivePadding
算子转换产生的转换错误,具体分析如下:
Conv2dAdaptivePadding
算子定义
Conv2dAdaptivePadding
算子含义:针对输入特征图的维度进行自适应padding,然后再进行卷积操作;如果卷积步长为1,需要输入特征图的维度与输入特征图维度一致;如果卷积步长为2,输出特征图的size是输入特征图的一半。
对应的转换具体问题是:ONNX 导出不直接支持 Python 中的 math.ceil 操作;ONNX 中支持 torch.nn.functional.pad 和 torch.nn.functional.conv2d
。
自定义算子转onnx模型
对于这种算子不适配问题,解决思路是替换模型中相应的算子层。具体的思路:
1、重写该算子层,使其能够适配;
2、遍历模型中各个子模块,找到待替换的算子层进行替换;
具体地:
1、重写Conv2dAdaptivePadding算子的forward逻辑,自定义算子:CustomConv2dAdaptivePadding,并修改forward逻辑。
1)正确处理 math.ceil
ONNX 导出不直接支持 Python 中的 math.ceil 操作。在 ONNX 中,你需要确保张量操作能够被支持。通常,可以使用张量上的 torch.ceil() 作为替代,因为 ONNX 支持张量操作,而不支持 Python 的内建函数如 math.ceil。这将使 output_h 和 output_w 的计算与 ONNX 导出兼容。
2)pad_h/pad_w的类型处理
在Conv2dAdaptivePadding 实现中,在 F.pad 函数调用时将计算结果传递给 pad 参数,导致产生浮点数。
原始的转换还存在错误:typeError: constant_pad_nd(): argument 'pad' must be tuple of ints, but found element of type float at pos 1
该错误通常表示在调用 constant_pad_nd 时,传递给 pad 参数的值不是预期的整数元组,而是包含浮点数的元组。因此需要确保将所有填充值转换为整数类型。
解决:
通过强制转换所有计算结果为整数来解决。以确保 pad_h 和 pad_w 变量是整数:
由于F.pad中处理的是int,所以需要将pad_h,pad_w转换为int.
2、替换带权重的Conv2dAdaptivePadding算子
将带权重的Conv2dAdaptivePadding算子替换为修改的CustomConv2dAdaptivePadding
具体地,遍历模型中的所有模块层,将带权重的Conv2dAdaptivePadding替换为可转的CustomConv2dAdaptivePadding.
代码如下:
for k, m in model.named_modules():if isinstance(m, Conv2dAdaptivePadding):# 获取层的属性in_channels = m.in_channelsout_channels = m.out_channelskernel_size = m.kernel_sizestride = m.stridepadding = m.paddingdilation = m.dilationgroups = m.groupsbias = m.bias is not None# 用自定义卷积层替换new_conv = CustomConv2dAdaptivePadding(in_channels, out_channels, kernel_size,stride, padding, dilation, groups, bias)new_conv.weight.data.copy_(m.weight.data) # 复制权重if m.bias is not None:new_conv.bias.data.copy_(m.bias.data) # 复制偏置# 将新层替换到模型中parent_module = modelfor part in k.split('.')[:-1]:parent_module = getattr(parent_module, part)setattr(parent_module, k.split('.')[-1], new_conv)
总结
在已训练的模型进行onnx文件格式转换时,如果遇到不支持的算子,可以采取的思路:首先定位转换出错的算子;然后对相应算子进行重写,确保其中的操作支持onnx转换;最后,将重写后的算子替换原模型中对应的算子。当然,也可以重写onnx算子,需根据个人对onnx掌握情况进行选择。