
测试自家编译器的时候遇到了一个问题,同 github issue:Bug in max_pool2d with ceil_mode under certain conditions · Issue #45357 · pytorch/pytorch · GitHub
问题分析 step1:理解官方定义的output shape 计算公式 1. 官方文档MaxPool2d — PyTorch 1.9.1 documentation
2. 关键信息ceil_mode – when True, will use ceil instead of floor to compute the output shape
2. 公式简化(1)K' = dilation[i] * (kernel_size[i] -1) + 1, 表示膨胀后的滑窗大小
(2)pad_H = Hin + 2 * padding[0]
上述公式简化成:out_H = ceil ( ( pad_H - K') / stride_h + 1)
此公式默认最后至少存在一个滑窗,且滑窗的start在最后一个滑窗范围内,此公式忽略了一种情况,图示如下:
3. 结论一pytorch文档描述错误,公式不可信
step2:从应用角度出发,推理pytorch实际公式可能是什么 1. ceil_mode理解 2. note部分理解字面上理解,如果滑窗的start在左边/上边padding 区域,或者input tensor区域,则允许其越界;
若滑窗的start在右边/下边padding区域,则忽略此滑窗
3. 推论一pytorch执行结果的outshape计算公式可能为:
| ceil_mode | 类似模式 | outshape计算公式 |
| False | VALID | H_out = ceil( (pad_H -(K'-1) / stride_h) = ceil( (H_in+2*padding[0] - d_h * (kernel_h -1) -1) / stride_h) ps:公式推导过程见下 |
| True | SAME | H_out = ceil( pad_H/stride_h) = ceil( (H_in+2*padding[0]) / stride_h) 如果最后一个滑窗的start(起始行、列)在right/bottom padding区域,则该滑窗忽略; if H_out%stride_h <=padding[0]: H_out = ceil( pad_H/stride_h) -1 ==》 总结 H_out = ceil( (H_in+padding[0]) / stride_h) |
import torch as t
import numpy as np
import warnings
warnings.filterwarnings("ignore")
case = ([1, 144, 4090], [13, 13], [15, 15], [2, 4], [1, 1], True)
'''
# cal shape(tf same): (10.0, 274.0)
out_w = ceil((4090+8)/15) = ceil(273+3/15),
最后一个滑窗start=3,倒数第三行,属于padding区域
# cal shape(ceil_mode=True,推论公式): (10.0, 273.0)
# cal shape(pth doc without ceil_mode): (10.0, 273.3333333333333)
# cal shape(pth doc ceil): (10.0, 274.0)
# pytorch res shape: torch.Size([1, 10, 273])
'''
# case = ([1, 131, 4095], [13, 13], [15, 15], [2, 4], [1, 1], True)
'''
# cal shape(tf same): (9.0, 274.0)
# cal shape(ceil_mode=True,推论公式): (9.0, 274.0)
# cal shape(pth doc without ceil_mode): (9.133333333333333, 273.6666666666667)
# cal shape(pth doc ceil): (10.0, 274.0)
# pytorch res shape: torch.Size([1, 9, 274])
'''
# case = ([1, 6, 7], [1, 1], [2, 2], [0, 0], [1, 1], True)
'''
# cal shape(tf same): (3.0, 4.0)
# cal shape(ceil_mode=True,推论公式): (3.0, 4.0)
# cal shape(pth doc without ceil_mode): (3.5, 4.0)
# cal shape(pth doc ceil): (4.0, 4.0)
# pytorch res shape: torch.Size([1, 3, 4])
'''
# case = ([1, 32, 32], [1, 1], [2, 2], [0, 0], [1, 1], True)
'''
# cal shape(tf same): (16.0, 16.0)
# cal shape(ceil_mode=True,推论公式): (16.0, 16.0)
# cal shape(pth doc without ceil_mode): (16.5, 16.5)
# cal shape(pth doc ceil): (17.0, 17.0)
# pytorch res shape: torch.Size([1, 16, 16])
'''
# case = ([1, 142, 13], [10, 10], [15, 15], [5, 1], [1, 1], True)
'''
# cal shape(tf same): (11.0, 1.0)
out_h = ceil((142+10)/15+1) = ceil( 152/15+1) = ceil(10+2/15)
最后一个滑窗start=2,倒数第2行,属于padding区域
# cal shape(ceil_mode=True,推论公式): (10.0, 1.0)
# cal shape(pth doc without ceil_mode): (10.466666666666667, 1.3333333333333333)
# cal shape(pth doc ceil): (11.0, 2.0)
# pytorch res shape: torch.Size([1, 10, 1])
'''
# case = ([1, 110, 5], [15, 8], [15, 15], [7, 2], [1, 1], True)
'''
# cal shape(tf same): (9.0, 1.0)
out_h = ceil((110+14)/15) = ceil(8+4/15),
最后一个滑窗start=4,倒数第4行,属于padding区域
# cal shape(ceil_mode=True,推论公式): (8.0, 1.0)
# cal shape(pth doc without ceil_mode): (8.266666666666666, 1.0666666666666667)
# cal shape(pth doc ceil): (9.0, 2.0)
# pytorch res shape: torch.Size([1, 8, 1])
'''
# case = ([1, 109, 13], [5, 5], [8, 8], [2, 2], [1, 1], True)
'''
# cal shape(tf same): (15.0, 3.0)
out_h = ceil((109+4)/8) = ceil( 113/8) = ceil(14+1),
最后一个滑窗start=1,倒数第1行,属于padding区域
out_w = ceil((13+4)/8+1) = = ceil(2+1),
最后一个滑窗start=1,倒数第1行,属于padding区域
# cal shape(ceil_mode=True,推论公式): (14.0, 2.0)
# cal shape(pth doc without ceil_mode): (14.5, 2.5)
# cal shape(pth doc ceil): (15.0, 3.0)
# pytorch res shape: torch.Size([1, 14, 2])
'''
tensor_shape = case[0]
ceil_mode = case[5]
x = t.randn(tensor_shape)
res = t.nn.MaxPool2d(kernel_size=case[1], stride=case[2], padding=case[3],
dilation=case[4], return_indices=False, ceil_mode=ceil_mode)(x)
# 膨胀后的kernel大小
new_fh = case[4][0] * (case[1][0] - 1) + 1
new_fw = case[4][1] * (case[1][1] - 1) + 1
# 根据pytorch文档公式计算,不考虑ceil_mode
out_h = (case[0][-2] + 2 * case[3][0] - new_fh) / case[2][0] + 1
out_w = (case[0][-1] + 2 * case[3][1] - new_fw) / case[2][1] + 1
# 根据推论计算ceil_mode=False, 同valid mode
out_h2 = np.ceil((case[0][-2] + 2 * case[3][0] - (new_fh - 1)) / case[2][0])
out_w2 = np.ceil((case[0][-1] + 2 * case[3][1] - (new_fw - 1)) / case[2][1])
# 根据same mode计算,不考虑最后一个滑窗start在padding area的情形
out_h3 = np.ceil((case[0][-2] + 2 * case[3][0]) / case[2][0])
out_w3 = np.ceil((case[0][-1] + 2 * case[3][1]) / case[2][1])
# 根据推理计算ceil_mode=True
out_h4 = np.ceil((case[0][-2] + case[3][0]) / case[2][0])
out_w4 = np.ceil((case[0][-1] + case[3][1]) / case[2][1])
if ceil_mode:
print("# cal shape(tf same): ", (out_h3, out_w3))
print("# cal shape(ceil_mode=True,推论公式): ", (out_h4, out_w4))
out_h1 = np.ceil(out_h)
out_w1 = np.ceil(out_w)
print("# cal shape(pth doc without ceil_mode): ", (out_h, out_w))
print("# cal shape(pth doc ceil): ", (out_h1, out_w1))
else:
print("# cal shape(ceil_mode=False, 推理公式): ", (out_h2, out_w2))
out_h1 = np.floor(out_h)
out_w1 = np.floor(out_w)
print("# cal shape(pth doc floor): ", (out_h1, out_w1))
print("# pytorch res shape: ", res.shape)
结论二:
以上面有限的例子,暂时可以证明:推理公式计算出的outshape与pytorch实际结果相同;
应是pytorch doc中计算公式以及ceil_mode参数说明描述有误。
ps: 此结论未经大量数据验证,不保证其准确性。