torch.nn.functional 基本函数
pytorch
本文字数:3k 字 | 阅读时长 ≈ 14 min

torch.nn.functional 基本函数

pytorch
本文字数:3k 字 | 阅读时长 ≈ 14 min

1. normalize

normalize在指定的维度计算$p$范数,默认的计算是 2 范数

torch.nn.functional.normalize(input, p=2.0, dim=1, eps=1e-12)

$$
v = \frac{v}{max(||v||_{p}, \epsilon)}
$$

二维例子

>>> input = torch.randn((3, 4))
tensor([[-0.9216,  0.2382,  0.0036,  0.1124],
        [ 0.6481,  0.0569,  2.6192,  0.6064],
        [-0.5110, -0.4260,  1.5873, -0.3685]])
>>> output = nn.functional.normalize(input)
tensor([[-0.9615,  0.2485,  0.0037,  0.1173],
        [ 0.2343,  0.0206,  0.9469,  0.2192],
        [-0.2903, -0.2420,  0.9018, -0.2094]])

normalize默认是计算dim=1的,输入矩阵是(3,4),计算第1维就是4个数字的那一维。如下图所示,就是红色/蓝色/绿色的那四个元素计算归一化.这里以-0.9615为例
$$
-0.9615 = \frac{-0.9216}{\sqrt{(-0.9216){2}+(0.2382){2}+(0.0036){2}+(0.1124){2}}}
$$

三维例子

>>> input = torch.randn((2, 3, 4))
tensor([[[ 0.0125,  0.0806,  1.0750, -0.9401],
         [ 0.1409,  2.8251, -0.2162,  0.9788],
         [-1.1588,  0.7537, -0.0691, -0.6371]],

        [[-0.2402, -0.8545,  1.2948, -1.4992],
         [-0.1676, -1.0821,  1.7947, -1.5175],
         [-0.1969,  0.3079, -1.2304, -0.7987]]])
>>> output = nn.functional.normalize(input)
tensor([[[ 0.0107,  0.0276,  0.9784, -0.6270],
         [ 0.1207,  0.9658, -0.1968,  0.6529],
         [-0.9926,  0.2577, -0.0629, -0.4250]],

        [[-0.6805, -0.6048,  0.5114, -0.6582],
         [-0.4749, -0.7659,  0.7088, -0.6662],
         [-0.5580,  0.2180, -0.4859, -0.3507]]])

这里dim=1也就是3那一维,所以对3个数进行归一化,这里以0.0107为例

$$
0.0107 = \frac{0.0125}{\sqrt{(0.0125){2}+(0.1409){2}+(-1.1588)^{2}}}
$$

2. sigmoid

sigmoid已被抛弃,请参考torch.nn.Sigmoid,Sigmoid的函数图像如下所示

$$
Sigmoid(x) = \frac{1}{1+exp^{-x}}
$$

torch.nn.functional.sigmoid(input)

>>> x = torch.tensor([1., 2., 3.])
tensor([1., 2., 3.])
>>> y = nn.functional.sigmoid(x)
tensor([0.7311, 0.8808, 0.9526])

3. interpolate

interpolate给定一个feature,将其插值为给定的size大小,或者根据scale factor参数进行缩放

torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False)

1. nearest插值

>>> a = torch.tensor([[1.,2.],[3.,4.]])
>>> a = torch.unsqueeze(a, dim=0)
>>> a = torch.unsqueeze(a, dim=0)
tensor([[[[1., 2.],
          [3., 4.]]]])
torch.Size([1, 1, 2, 2])
>>> b = nn.functional.interpolate(a, size=(4, 4))
tensor([[[[1., 1., 2., 2.],
          [1., 1., 2., 2.],
          [3., 3., 4., 4.],
          [3., 3., 4., 4.]]]])
>>> b = nn.functional.interpolate(a, scale_factor=2)  # 将`scale_factor`设为2,也可以实现上述效果
tensor([[[[1., 1., 2., 2.],
          [1., 1., 2., 2.],
          [3., 3., 4., 4.],
          [3., 3., 4., 4.]]]])

2. bilinear插值

>>> b = nn.functional.interpolate(a, size=(4, 4), mode="bilinear")
tensor([[[[1.0000, 1.2500, 1.7500, 2.0000],
          [1.5000, 1.7500, 2.2500, 2.5000],
          [2.5000, 2.7500, 3.2500, 3.5000],
          [3.0000, 3.2500, 3.7500, 4.0000]]]])
/Users/harry/miniconda3/envs/torch/lib/python3.7/site-packages/torch/nn/functional.
py:3455: UserWarning: Default upsampling behavior when mode=bilinear is changed to
align_corners=False since 0.4.0. Please specify align_corners=True if the old behavior
is desired. See the documentation of nn.Upsample for details.
  "See the documentation of nn.Upsample for details.".format(mode)

看到双线性bilinear插值的结果和nearest结果不同,这里还报了错,大体意思是说在双线性插值的情况下,如果设置align_corners=True,输出可能取决于输入大小,并且不会按照比例将输出和输入像素进行对齐,因此默认的将align_corners=False,可以参考这两个文档: upsample, interpolate

下面给出align_corners=True的例子

>>> b = nn.functional.interpolate(a, size=(4, 4), mode="bilinear", align_corners=True)
tensor([[[[1.0000, 1.3333, 1.6667, 2.0000],
          [1.6667, 2.0000, 2.3333, 2.6667],
          [2.3333, 2.6667, 3.0000, 3.3333],
          [3.0000, 3.3333, 3.6667, 4.0000]]]])

观察上面两个例子,如果设置align_corners=False,也就是默认设置,1,2,3,4最后会到四个角落,还是比较规则的比例,但是如果设置align_corners=True,1,2,3,4不会按照比例,位置会发生变化变得不规则,但是此时的1,2,3,4四周均被元素环绕,并且算上环绕部分,他们是对齐的,即(1, 1.3333, 1.6667, 2.000), (1.6667, 2.0000, 2.3333, 2.6667), (2.3333, 2.6667, 3.0000, 3.3333), (3.0000, 3.3333, 3.6667, 4.0000)这四部分分别看做一个整体,他们是对齐的,下面介绍下align_corners参数的运作

图来自pytorch论坛,此参数主要解释为原来的输入是否会被插值元素环绕

4. fold/unfold

foldunfold的作用恰好相反,unfold是用一个滑窗来提取图像中的像素值,类似于卷积操作,但是只提取不计算,fold恰好相反将滑窗提取的值返回为一个图像

nn.functional.unfold(input, kernel_size, dilation=1, padding=0, stride=1)

>>> x = torch.Tensor([[[[  1,  2,  3,  4],
   		    [  5,  6,  7,  8],
   		    [  9, 10, 11, 12],
   	            [ 13, 14, 15, 16]]]])
>>> x = F.unfold(x, kernel_size=(2, 2), padding=0, stride=2)
tensor([[[ 1.,  3.,  9., 11.],
         [ 2.,  4., 10., 12.],
         [ 5.,  7., 13., 15.],
         [ 6.,  8., 14., 16.]]])
>>> x.size()
torch.Size([1, 4, 4])

执行过程很简单,用一个$2\times 2$的窗在图上滑动,步长为2,第一次覆盖的内容为1256,第二次为3478,以此类推,每次滑窗的结果用一个列向量表示,列数就是滑窗提取的次数。如果我们要得到每次滑窗的结果,例如第一次提取的结果,用表达式x[:,:,0]即可

nn.functional.fold(input, output_size, kernel_size, dilation=1, padding=0, stride=1)

folder是unfold的逆过程,我们依然用几个例子来对其进行详细的解释

1. 第一个例子

>>> x = torch.Tensor([[[[  1,  2,  3,  4],
                        [  5,  6,  7,  8],
                        [  9, 10, 11, 12],
                        [ 13, 14, 15, 16]]]])
>>> x = F.unfold(x, kernel_size=(2, 2), padding=0, stride=2)
tensor([[[ 1.,  3.,  9., 11.],
         [ 2.,  4., 10., 12.],
         [ 5.,  7., 13., 15.],
         [ 6.,  8., 14., 16.]]])
torch.Size([1, 4, 4])
>>> x = F.fold(x, output_size=(4,4), kernel_size=(2,2), padding=0, stride=2)
tensor([[[[ 1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.],
          [ 9., 10., 11., 12.],
          [13., 14., 15., 16.]]]])
torch.Size([1, 1, 4, 4])

fold函数是如何执行的呢,他会提取unfold函数的每一列,首先提取1256这一列,然后根据kernel_size的大小将1256重新resize并填到output的第一个位置,如下

[[ 1.,  2.,  0.,  0.],
 [ 5.,  6.,  0.,  0.],
 [ 0.,  0.,  0.,  0.],
 [ 0.,  0.,  0.,  0.]]

随后提取第二列2.6.10.14,resize为$2\times 2$的形状,根据步长为2添加到output的下一个位置,并以此类推

[[ 1.,  2.,  3.,  4.],
 [ 5.,  6.,  7.,  8.],
 [ 0.,  0.,  0.,  0.],
 [ 0.,  0.,  0.,  0.]]

注意:output,kernel以及stride必须满足一定的关系(参考文档)

知道原理以后我们可以自由操作上述tensor,但是注意,如果步长等设置不合适的话,最后的结果是有overlap的,下面我们展示两个例子

2. 第二个例子

自由操作tensor

>>> x = torch.Tensor([[[[  1,  2,  3,  4],
                        [  5,  6,  7,  8],
                        [  9, 10, 11, 12],
                        [ 13, 14, 15, 16]]]])
>>> x = F.unfold(x, kernel_size=(2, 2), padding=0, stride=2)
tensor([[[ 1.,  3.,  9., 11.],
         [ 2.,  4., 10., 12.],
         [ 5.,  7., 13., 15.],
         [ 6.,  8., 14., 16.]]])
torch.Size([1, 4, 4])
>>> x = F.fold(x, output_size=(4,4), kernel_size=(4,1), padding=0, stride=1)
# tensor又变回了原来的样子
tensor([[[[ 1.,  3.,  9., 11.],
          [ 2.,  4., 10., 12.],
          [ 5.,  7., 13., 15.],
          [ 6.,  8., 14., 16.]]]])
torch.Size([1, 1, 4, 4])

overlap的情况

根据上述讲的可以自己推一下

>>> x = torch.Tensor([[[[  1,  2,  3,  4],
                        [  5,  6,  7,  8],
                        [  9, 10, 11, 12],
                        [ 13, 14, 15, 16]]]])
>>> x = F.unfold(x, kernel_size=(2, 2), padding=0, stride=2)
tensor([[[ 1.,  3.,  9., 11.],
         [ 2.,  4., 10., 12.],
         [ 5.,  7., 13., 15.],
         [ 6.,  8., 14., 16.]]])
torch.Size([1, 4, 4])
>>> x = F.fold(x, output_size=(3,3), kernel_size=(2,2), padding=0, stride=1)
tensor([[[[ 1.,  5.,  4.],
          [14., 34., 20.],
          [13., 29., 16.]]]])
torch.Size([1, 1, 3, 3])

3. kernel size小于列向量的情况

上面讲了,fold每次都会对列向量进行提取,之前的例子都是kernel size等于列向量,如果我们的kernel size小于列向量就会出现以下情况

>>> x = torch.Tensor([[[[  1,  2,  3,  4], 
                        [  5,  6,  7,  8],
                        [  9, 10, 11, 12],
                        [ 13, 14, 15, 16]]]])
>>> x = F.unfold(x, kernel_size=(2, 2), padding=0, stride=2)
tensor([[[ 1.,  3.,  9., 11.],
         [ 2.,  4., 10., 12.],
         [ 5.,  7., 13., 15.],
         [ 6.,  8., 14., 16.]]])
torch.Size([1, 4, 4])
>>> x = F.fold(x, output_size=(2,2), kernel_size=(1,1), padding=0, stride=1)
tensor([[[[ 1.,  3.],
          [ 9., 11.]],

         [[ 2.,  4.],
          [10., 12.]],

         [[ 5.,  7.],
          [13., 15.]],

         [[ 6.,  8.],
          [14., 16.]]]])
torch.Size([1, 4, 2, 2])

解释一下,我们第一次提取的应该是1256,但是由于我们的kernel太小了,$1\times 1=1$,只能提取一个元素,因此就是1,我们的output size是$2\times 2$,步长为 1,所以第一次提取的结果如下

[[  1,  0],
 [  0,  0]]

第二次提取时,就需要移动了,提取的不是列向量中的 2,而是横向移动的 3,接着放到刚才那个元素后面

[[  1,  3],
 [  0,  0]]

之后的过程以此类推,直到我们提取到 11,这时我们的行向量提取完了,但是列向量没有,所以我们从第二列开始重复刚才的过程即可,可以看到最终我们输出向量大小为 [1,4,2,2],4 就是我们提取了 4 次行向量,两个 2 就是每次提取的大小(即 output size)

最后加一个复杂的具有 padding 的例子

padding 就是在对 tensor 进行操作之前在 tensor 四周补 0 或其他的值。例子中仅对 unfold 进行 padding,如果对 fold 进行 padding 也同理

x = torch.Tensor([[[[  1,  2,  3,  4,  5,  6, 7,  8],
                    [  9, 10, 11, 12, 13, 14, 15, 16],
                    [ 17, 18, 19, 20, 21, 22, 23, 24],
                    [ 25, 26, 27, 28, 29, 30, 31, 32],
                    [ 33, 34, 35, 36, 37, 38, 39, 40],
                    [ 41, 42, 43, 44, 45, 46, 47, 48],
                    [ 49, 50, 51, 52, 53, 54, 55, 56],
                    [ 57, 58, 59, 60, 61, 62, 63, 64]]]])
x = F.unfold(x, kernel_size=(6,6), padding=1, stride=4) 
x = F.fold(x, output_size=(12,12), kernel_size=(6,6), padding=0, stride=6)
'''
tensor([[[ 0.,  0.,  0., 28.],
         [ 0.,  0., 25., 29.],
         [ 0.,  0., 26., 30.],
         [ 0.,  0., 27., 31.],
         [ 0.,  0., 28., 32.],
         [ 0.,  0., 29.,  0.],
         [ 0.,  4.,  0., 36.],
         [ 1.,  5., 33., 37.],
         [ 2.,  6., 34., 38.],
         [ 3.,  7., 35., 39.],
         [ 4.,  8., 36., 40.],
         [ 5.,  0., 37.,  0.],
         [ 0., 12.,  0., 44.],
         [ 9., 13., 41., 45.],
         [10., 14., 42., 46.],
         [11., 15., 43., 47.],
         [12., 16., 44., 48.],
         [13.,  0., 45.,  0.],
         [ 0., 20.,  0., 52.],
         [17., 21., 49., 53.],
         [18., 22., 50., 54.],
         [19., 23., 51., 55.],
         [20., 24., 52., 56.],
         [21.,  0., 53.,  0.],
         [ 0., 28.,  0., 60.],
         [25., 29., 57., 61.],
         [26., 30., 58., 62.],
         [27., 31., 59., 63.],
         [28., 32., 60., 64.],
         [29.,  0., 61.,  0.],
         [ 0., 36.,  0.,  0.],
         [33., 37.,  0.,  0.],
         [34., 38.,  0.,  0.],
         [35., 39.,  0.,  0.],
         [36., 40.,  0.,  0.],
         [37.,  0.,  0.,  0.]]])
torch.Size([1, 36, 4])
tensor([[[[ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
          [ 0.,  1.,  2.,  3.,  4.,  5.,  4.,  5.,  6.,  7.,  8.,  0.],
          [ 0.,  9., 10., 11., 12., 13., 12., 13., 14., 15., 16.,  0.],
          [ 0., 17., 18., 19., 20., 21., 20., 21., 22., 23., 24.,  0.],
          [ 0., 25., 26., 27., 28., 29., 28., 29., 30., 31., 32.,  0.],
          [ 0., 33., 34., 35., 36., 37., 36., 37., 38., 39., 40.,  0.],
          [ 0., 25., 26., 27., 28., 29., 28., 29., 30., 31., 32.,  0.],
          [ 0., 33., 34., 35., 36., 37., 36., 37., 38., 39., 40.,  0.],
          [ 0., 41., 42., 43., 44., 45., 44., 45., 46., 47., 48.,  0.],
          [ 0., 49., 50., 51., 52., 53., 52., 53., 54., 55., 56.,  0.],
          [ 0., 57., 58., 59., 60., 61., 60., 61., 62., 63., 64.,  0.],
          [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.]]]])
torch.Size([1, 1, 12, 12])
'''
4月 06, 2025
3月 10, 2025
12月 31, 2024