惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
Stack Overflow Blog
Stack Overflow Blog
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
Engineering at Meta
Engineering at Meta
L
LangChain Blog
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
Y
Y Combinator Blog
月光博客
月光博客
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Fortinet All Blogs
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Proofpoint News Feed
小众软件
小众软件
H
Help Net Security
Last Week in AI
Last Week in AI
B
Blog RSS Feed
宝玉的分享
宝玉的分享
N
Netflix TechBlog - Medium
博客园 - 叶小钗
The GitHub Blog
The GitHub Blog

又见苍岚

COLMAP PatchMatch Stereo 算法详解 事件驱动的状态机框架:从理论到工程实践 Git 在国内网络环境下无法 Push 的排查与修复 —— 配置 Clash 代理 分段五次多项式插值原理详解 路径插值方法深度对比研究 Claude Code 使用指南 OpenClaw 记忆管理与技能创建指南 CBS(Conflict-Based Search)算法详解 A* 算法及其变种详解 OpenClaw 配置多 Agents Windows Powershell 无法加载文件,因为在此系统上禁止运行脚本问题的解决方案 MaxClaw 安装流程 大模型 AI 名词介绍 AList 网盘聚合工具简介 Protobuf 简介与测试 Claude Code 简介以及 GLM 4.7 模型接入 Github 歌词下载工具 163MusicLyrics Python __getattr__ 懒加载 Python TypedDict 机器人仿真平台 Gazebo 安装记录 机器人仿真平台 Gazebo 简介 多机器人路径规划问题(Multi-Agent Path Finding, MAPF)简介 Python exifread 读取修改过的 jpeg 信息错误问题修复 3D 坐标系变换的理解 3D 旋转矩阵基本概念 MongoDB Compass 介绍 Python 环境管理工具 uv Flutter 开发指南 Snipaste 安装下载与黑屏问题解决方案 全局路径规划算法记录
ONNX 不支持 adaptive_avg_pool 算子的解决方案
Yiwei Zhang · 2024-02-04 · via 又见苍岚

ONNX 部署pytorch 模型时,可能会遇到 adaptive_avg_pool 算子不支持而报错的情况,本文记录解决方案。

简介

自适应平均池算子是自适应平均池的简称,是深度学习和神经网络体系结构中常用的一种数学运算。可将张量池化到任意的尺寸上。

问题复现

有时在模型转换到 ONNX 时报错:

1
Unsupported: ONNX export of operator adaptive_avg_pool1d, output size that are not factor of input size. Please feel free to request support or submit a pull request on PyTorch GitHub.

pytorch 仓库也有这个问题(2D算子):

https://github.com/pytorch/pytorch/issues/42653

解决方案

用朴实的 torch 语法重写这个算子

方案一

上述 issue 中有大神提到了解决方案(2D):

1
2
3
4
5
6
7
8
9
10
11
class AdaptiveAvgPool2dCustom(nn.Module):
def __init__(self, output_size):
super(AdaptiveAvgPool2dCustom, self).__init__()
self.output_size = np.array(output_size)

def forward(self, x: torch.Tensor):
stride_size = np.floor(np.array(x.shape[-2:]) / self.output_size).astype(np.int32)
kernel_size = np.array(x.shape[-2:]) - (self.output_size - 1) * stride_size
avg = nn.AvgPool2d(kernel_size=list(kernel_size), stride=list(stride_size))
x = avg(x)
return x

思路是将原始数据维度降维到新的目标维度,通过动态自适应调整池化的步长和窗口实现自适应池化。

我对照这份代码修改出了 1D 的算子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AdaptiveAvgPool1dCustom(nn.Module):
def __init__(self, output_size):
super(AdaptiveAvgPool1dCustom, self).__init__()
self.output_size = np.array(output_size)

def forward(self, x: torch.Tensor):
cur_shape = np.array(x.shape[-1])
if cur_shape < self.output_size:
raise RuntimeError(f"AdaptiveAvgPool1dCustom is converting {cur_shape} feature to {self.output_size} by avgpool which is not supported, suggestion is to change outputsize to input shape {cur_shape}.")
stride_size = np.floor(np.array(x.shape[-1]) / self.output_size).astype(np.int32)
kernel_size = np.array(x.shape[-1]) - (self.output_size - 1) * stride_size
avg = nn.AvgPool1d(kernel_size=kernel_size, stride=stride_size)
x = avg(x)
return x

但是该方法导出的 onnx 模型有时会在 onnx 运行时报错,可能是因为输出维度更大时 stride_size 为 0 导致的

方案二

上述代码在数据降维的时候可以正常运行,但是当数据维度升高时无法正常工作,而且输出结果与原始自适应池化算子不一致。

这是由于原始自适应池化算子的计算原理与上述方案不同:
$$
lstart=floor(i*L_{in}/L_{out})
$$

$$
lend=ceil((i+1)*L_{in}/L_{out})
$$

$$
Output(i)=\frac{sum(Input[lstart:lend])}{(lstart-lend)}
$$

上述 issue 中也有 大神 提到了这种原理的计算方式,这篇博客 也提到了类似计算方法:

1
2
3
4
5
6
7
8
def torch_pool(inputs, target_size):
start_points = (torch.arange(target_size, dtype=torch.float32) * (inputs.size(-1) / target_size)).long()
end_points = ((torch.arange(target_size, dtype=torch.float32)+1) * (inputs.size(-1) / target_size)).ceil().long()
pooled = []
for idx in range(target_size):
pooled.append(torch.mean(inputs[:, :, start_points[idx]:end_points[idx]], dim=-1, keepdim=False))
pooled = torch.cat(pooled, -1)
return pooled

原理应该没有问题,不过这份代码我没有运行过

但是我考虑这些代码都执行了 for 循环,我觉得不够优雅,写了如下版本,可以正常运行,也可以保存 onnx 模型,供大家参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

class AdaptiveAvgPool1dCustomPlus(nn.Module):
def __init__(self, output_size):
super(AdaptiveAvgPool1dCustomPlus, self).__init__()
self.output_size = int(output_size)
assert self.output_size > 0

def forward(self, x: torch.Tensor):
L = x.shape[-1]
cum_res = torch.cumsum(x, dim=-1)
cum_res = torch.cat((torch.zeros(*x.shape[:-1], 1).to(x.device), cum_res), dim=-1)
indexs = torch.arange(0, self.output_size) * L /self.output_size
indexs_larger = indexs + L /self.output_size
lstart_t = torch.floor(indexs).to(torch.long).to(x.device)
lend_t = torch.ceil(indexs_larger).to(torch.long).to(x.device)

output = (cum_res[...,lend_t] - cum_res[...,lstart_t]) / (lend_t - lstart_t)
return output

实现原理是一致的,只是通过累加和 来计算起始和结束的下标,摒弃了 for 循环。

方案三

方案二的版本可以成功转换 onnx 模型并且可以正常运行,但是转为 Tensorrt 后速度很慢,可以尝试直接使用 F.interpolate 算子

1
2
3
4
5
6
7
8
class AdaptiveAvgPool1dVersion3_d3(nn.Module):
def __init__(self, output_size):
super(AdaptiveAvgPool1dVersion3_d3, self).__init__()
self.output_size = int(output_size)
assert self.output_size > 0
def forward(self, x):
x = F.interpolate(x, self.output_size, mode='linear')
return x

可以正常运行,速度快了一些,只是实现原理和原始函数稍有不同,采用的是差值方式。

参考资料

文章链接:
https://www.zywvvd.com/notes/study/deep-learning/deploy/onnx-adaavgpool-bug/onnx-adaavgpool-bug/