正如 hkchengrex 的回答所指出的,PyTorch 文档没有解释自适应池化层使用什么规则来确定池化内核的大小和位置。 (其实有fixme in the PyTorch code表示文档需要改进。)
然而,内核大小和位置is implemented by this cpp function的计算以及关键逻辑实际上在对函数start_index和end_index的调用中,它们定义了内核的位置和偏移量。
我相信这段 Python 代码重新实现了该代码并显示了如何计算内核:
from typing import List
import math
def kernels(ind,outd) -> List:
"""Returns a List [(kernel_offset_start,kernel_length)] defining all the pooling kernels for a 1-D adaptive pooling layer that takes an input of dimension `ind` and yields an output of dimension `outd`"""
def start_index(a,b,c):
return math.floor((float(a) * float(c)) / b)
def end_index(a,b,c):
return math.ceil((float(a + 1) * float(c)) / b)
results = []
for ow in range(outd):
start = start_index(ow,outd,ind)
end = end_index(ow,outd,ind)
sz = end - start
results.append((start,sz))
return results
def kernel_indexes(ind,out) -> List:
"""Returns a List [[*ind]] containing the indexes of the pooling kernels"""
startsLengths = kernels(ind,out)
return [list(range(start,start+length)) for (start,length) in startsLengths]
以下是需要注意的关键点。
首先,输入维度 (ind) 是否是输出维度 (outd) 的整数倍非常重要。
其次,在这种情况下,自适应层的内核大小相等且不重叠,并且正是根据以下规则定义内核和步幅所产生的结果:
stride = ind // outd
kernel_size = ind - (outd-1)*stride
padding = 0
换句话说,在这种情况下,可以通过使用定义了合适的步幅、kernel_size 和填充的非自适应池化层来重现自适应池化层的效果。 (下面的示例。)
最后,当输入大小不是输出大小的整数倍时,PyTorch 的自适应池化规则会生成重叠的内核,并且具有可变大小。
由于非自适应池 API 不允许可变大小的内核,在这种情况下,在我看来 没有办法通过将合适的值输入到非自适应池中来重现自适应池的效果层。
这是一个显示这两种情况的示例。这个辅助函数让我们可以比较自适应平均池化层和使用固定步幅和内核的普通平均池化层的情况:
import torch
import torch.nn as nn
def compare1DAdaptivity(ind,outd,inputpattern):
c = 1
padding = 0
input = torch.Tensor(inputpattern).view(1,c,ind)
stride = ind // outd
kernel_size = (ind - (outd-1)*stride)
avg_pool = nn.AvgPool1d(stride=stride,kernel_size=kernel_size,padding=padding)
avg_out = avg_pool(input)
adap_avg_pool = torch.nn.AdaptiveAvgPool1d(outd)
adap_avg_out = adap_avg_pool(input)
try:
equal_output = torch.allclose(avg_out,adap_avg_out)
except:
equal_output = False
print("input.shape: {}".format(input.shape))
print("in_dims: {}".format(ind))
print("out_dims: {}".format(outd))
print("")
print("AAL strides: {}".format(stride))
print("AAL kernel_sizes: {}".format(kernel_size))
print("AAL pad: {}".format(padding))
print("")
print("outputs equal: {}".format(equal_output))
print("")
print("AAL input -> output: {} -> {}".format(input,avg_out))
print("adap input -> output: {} -> {}".format(input,adap_avg_out))
return equal_output
所以,举第一种情况的例子,输入维度是输出维度的倍数,我们可以从 6 到 3。我们可以看到近似自适应层和真实自适应层给出相同的输出:
compare1DAdaptivity(6,3,[1,0,0,0,0]) # => Tue
AAL input -> output: tensor([[[1., 0., 0., 0., 0., 0.]]]) -> tensor([[[0.5000, 0.0000, 0.0000]]])
adap input -> output: tensor([[[1., 0., 0., 0., 0., 0.]]]) -> tensor([[[0.5000, 0.0000, 0.0000]]])
但是,如果我们从 5 变为 3,这将不再有效。
compare1DAdaptivity(5,3,[1,0,0,0,0]) # => False
AAL input -> output: tensor([[[1., 0., 0., 0., 0.]]]) -> tensor([[[0.3333, 0.0000, 0.0000]]])
adap input -> output: tensor([[[1., 0., 0., 0., 0.]]]) -> tensor([[[0.5000, 0.0000, 0.0000]]])
但是我们可以通过手动计算索引来重现自适应层的结果:
t = [1,0,0,0,0]; [sum( [t[x] for x in xs] ) / len(xs) for xs in kernel_indexes(5,3)]
# => [0.5,0.0,0.0]