名为 'area' 的上采样方法有什么用?
What is the upsampling method called 'area' used for?
PyTorch函数torch.nn.functional.interpolate
包含了几种上采样模式,例如:nearest
、linear
、bilinear
、bicubic
、trilinear
, area
.
area
上采样模式的用途是什么?
查看源代码,area
插值相当于通过 adaptive average pooling. You can refer to this question 调整张量的大小,以解释自适应平均池化。因此area
插值比上采样更适用于下采样。
正如 jodag 所说,它正在使用自适应平均池调整大小。虽然 link 的答案旨在解释自适应平均池是什么,但我觉得解释有点含糊。
TL;DR torch.nn.functional.interpolate
的 area
模式可能是想对图像进行下采样时最直观的思考方式之一。
您可以将其视为对原始图像应用 平均 Low-Pass 滤波器 (LPF),然后进行采样。在采样之前应用 LPF 是为了防止下采样图像中潜在的 混叠 。 混叠 会导致缩小后的图像出现摩尔纹。
它可能被称为“面积”,因为它(大致)在平均输入像素时保留输入和输出形状之间的面积比。更具体地说,输出图像中的每个像素将是输入图像中相应区域的平均值,其中该区域的 1/area
大致是输出图像面积与输入图像面积之间的比率。
此外,具有 mode = 'area'
的 interpolate
函数调用源函数 adaptie_avg_pool2d
(在 C++ 中实现),该函数将输出张量中的每个像素分配给输出张量中所有像素强度的平均值输入的计算区域。该区域按像素计算,不同像素的大小可能不同。它的计算方式是将输出像素的高度和宽度乘以输入和输出(按此顺序)高度和宽度(分别)之间的比率,然后取一次 floor
(对于区域的起始索引)并且一旦结果值的ceil
(对于区域的结束索引)。
这是对 nn.AdaptiveAvgPool2d
中发生的事情的 in-depth 分析:
首先,如那里所述,您可以在此处找到自适应平均池化(C++ 语言)的源代码:source
看一下神奇发生的函数(或者至少是单帧 CPU 上的神奇),static void adaptive_avg_pool2d_single_out_frame
,我们有 5 个嵌套循环,运行 在通道维度上,然后是宽度,然后是高度,在第三个循环的主体内,魔法发生了:
首先计算输入图像中用于计算当前像素值的区域(回想一下,我们在输出中的所有像素上将宽度和高度循环到 运行)。
这是怎么做到的?
使用高度和宽度的开始和结束索引的简单计算如下:floor((input_height/output_height) * current_output_pixel_height)
开始和 ceil((input_height/output_height) * (current_output_pixel_height+1))
以及类似的宽度。
然后,所有要做的就是简单地对该区域和当前通道中所有像素的强度进行平均,并将结果放入当前输出像素。
我写了一个简单的 Python 片段,它以相同的方式(循环,天真)做同样的事情并产生相同的结果。它采用张量 a
并使用自适应平均池以两种方式调整 a
的大小以塑造 output_shape
- 一次使用 built-in nn.AdaptiveAvgPool2d
一次使用我的翻译C++ 中源函数的 Python:static void adaptive_avg_pool2d_single_out_frame
。 Built-in 函数的结果保存到 b
,我的翻译保存到 b_hat
。您可以看到结果是等效的(您可以进一步使用空间形状并验证这一点):
import torch
from math import floor, ceil
from torch import nn
a = torch.randn(1, 3, 15, 17)
out_shape = (10, 11)
b = nn.AdaptiveAvgPool2d(out_shape)(a)
b_hat = torch.zeros(b.shape)
for d in range(a.shape[1]):
for w in range(b_hat.shape[3]):
for h in range(b_hat.shape[2]):
startW = floor(w * a.shape[3] / out_shape[1])
endW = ceil((w + 1) * a.shape[3] / out_shape[1])
startH = floor(h * a.shape[2] / out_shape[0])
endH = ceil((h + 1) * a.shape[2] / out_shape[0])
b_hat[0, d, h, w] = torch.mean(a[0, d, startH: endH, startW: endW])
'''
Prints Mean Squared Error = 0 (or a very small number, due to precision error)
as both outputs are the same, proof of output equivalence:
'''
print(nn.MSELoss()(b_hat, b))
PyTorch函数torch.nn.functional.interpolate
包含了几种上采样模式,例如:nearest
、linear
、bilinear
、bicubic
、trilinear
, area
.
area
上采样模式的用途是什么?
查看源代码,area
插值相当于通过 adaptive average pooling. You can refer to this question 调整张量的大小,以解释自适应平均池化。因此area
插值比上采样更适用于下采样。
正如 jodag 所说,它正在使用自适应平均池调整大小。虽然 link 的答案旨在解释自适应平均池是什么,但我觉得解释有点含糊。
TL;DR torch.nn.functional.interpolate
的 area
模式可能是想对图像进行下采样时最直观的思考方式之一。
您可以将其视为对原始图像应用 平均 Low-Pass 滤波器 (LPF),然后进行采样。在采样之前应用 LPF 是为了防止下采样图像中潜在的 混叠 。 混叠 会导致缩小后的图像出现摩尔纹。
它可能被称为“面积”,因为它(大致)在平均输入像素时保留输入和输出形状之间的面积比。更具体地说,输出图像中的每个像素将是输入图像中相应区域的平均值,其中该区域的 1/area
大致是输出图像面积与输入图像面积之间的比率。
此外,具有 mode = 'area'
的 interpolate
函数调用源函数 adaptie_avg_pool2d
(在 C++ 中实现),该函数将输出张量中的每个像素分配给输出张量中所有像素强度的平均值输入的计算区域。该区域按像素计算,不同像素的大小可能不同。它的计算方式是将输出像素的高度和宽度乘以输入和输出(按此顺序)高度和宽度(分别)之间的比率,然后取一次 floor
(对于区域的起始索引)并且一旦结果值的ceil
(对于区域的结束索引)。
这是对 nn.AdaptiveAvgPool2d
中发生的事情的 in-depth 分析:
首先,如那里所述,您可以在此处找到自适应平均池化(C++ 语言)的源代码:source
看一下神奇发生的函数(或者至少是单帧 CPU 上的神奇),static void adaptive_avg_pool2d_single_out_frame
,我们有 5 个嵌套循环,运行 在通道维度上,然后是宽度,然后是高度,在第三个循环的主体内,魔法发生了:
首先计算输入图像中用于计算当前像素值的区域(回想一下,我们在输出中的所有像素上将宽度和高度循环到 运行)。 这是怎么做到的?
使用高度和宽度的开始和结束索引的简单计算如下:floor((input_height/output_height) * current_output_pixel_height)
开始和 ceil((input_height/output_height) * (current_output_pixel_height+1))
以及类似的宽度。
然后,所有要做的就是简单地对该区域和当前通道中所有像素的强度进行平均,并将结果放入当前输出像素。
我写了一个简单的 Python 片段,它以相同的方式(循环,天真)做同样的事情并产生相同的结果。它采用张量 a
并使用自适应平均池以两种方式调整 a
的大小以塑造 output_shape
- 一次使用 built-in nn.AdaptiveAvgPool2d
一次使用我的翻译C++ 中源函数的 Python:static void adaptive_avg_pool2d_single_out_frame
。 Built-in 函数的结果保存到 b
,我的翻译保存到 b_hat
。您可以看到结果是等效的(您可以进一步使用空间形状并验证这一点):
import torch
from math import floor, ceil
from torch import nn
a = torch.randn(1, 3, 15, 17)
out_shape = (10, 11)
b = nn.AdaptiveAvgPool2d(out_shape)(a)
b_hat = torch.zeros(b.shape)
for d in range(a.shape[1]):
for w in range(b_hat.shape[3]):
for h in range(b_hat.shape[2]):
startW = floor(w * a.shape[3] / out_shape[1])
endW = ceil((w + 1) * a.shape[3] / out_shape[1])
startH = floor(h * a.shape[2] / out_shape[0])
endH = ceil((h + 1) * a.shape[2] / out_shape[0])
b_hat[0, d, h, w] = torch.mean(a[0, d, startH: endH, startW: endW])
'''
Prints Mean Squared Error = 0 (or a very small number, due to precision error)
as both outputs are the same, proof of output equivalence:
'''
print(nn.MSELoss()(b_hat, b))