PyTorch学习之torch.nn.functional.pad函数

一、简介

torch.nn.functional.pad 是 PyTorch 中用于对张量进行填充操作的函数。填充操作在处理图像、序列数据等任务时非常常见,它可以在张量的指定维度两端添加一定数量的元素,填充方式多样,包括零填充、常数填充、反射填充和边界填充等。

二、基本语法

torch.nn.functional.pad(input, pad, mode='xxx', value=x)

三、参数说明

  • input (Tensor): 输入的张量。
  • pad (tuple): 指定每个维度填充的数目。格式为 (left, right, top, bottom, …)。
  • mode (str, 可选): 填充模式,包括 constant(常数填充,默认)、reflect(反射填充)、replicate(边界填充)和 circular(循环填充)。
  • value (float, 可选): 常数填充模式下填充值,仅当 mode 为 ‘constant’ 时有效,其他模式时即使指定了值也会被忽略掉。

四、返回值

返回一个新的张量,对输入张量进行了指定方式的填充。

五、填充模式详解

在详细讲解和演示每一种模式的效果前,我想对填充的一些规则进行提前说明:

  • 我们会填充【 d i m = l e n ( p a d ) 2 \mathrm{dim=\frac{len(pad)}{2}} dim=2len(pad)】个维度

    举个例子:有一个(4, 4)形状的矩阵,我给的pad=(1,1),那么根据公式 d i m = 2 2 = 1 \mathrm{dim=\frac22=1} dim=22=1因此我们只填充1个维度。有人想,既然只填充1个维度,那为什么给两个值?

    矩阵是4x4的,有横向有纵向对吧,最后一个维度肯定是横向的(我们常说矩阵存储是行优先的),此时矩阵是不是有左右两边?那么给出的2个填充值分别对应于在左边(left)要填充1个像素,在右边(right)要填充一个像素。

    同样的随着维度扩展,pad可以提供更长的参数,如pad=(1, 1, 2, 2, 3, 3),根据公式填充3个维度,哪3个?(left, right, top, bottom, front, back)

  • 填充是从内向外的:例如pad=(1, 1, 2, 2, 3, 3),(1, 1)对应填充(left, right),(2, 2)对应填充(top, bottom),(3, 3)对应填充(front, back)

5.1 constant模式

在该模式下,我们必须指定参数value,value的值将填充到我们扩充的区域。

例如有如下代码:

import torch
import torch.nn as nn

data = torch.tensor([[
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
    [13, 14, 15, 16]
]])

print(data.size())
print(data)
paddata1 = nn.functional.pad(data, (1, 1, 1, 1), mode="constant", value=1)
print(paddata1.size())
print(paddata1)

输出:

torch.Size([1, 4, 4])
tensor([[[ 1,  2,  3,  4],
         [ 5,  6,  7,  8],
         [ 9, 10, 11, 12],
         [13, 14, 15, 16]]])
torch.Size([1, 6, 6])
tensor([[[ 1,  1,  1,  1,  1,  1],
         [ 1,  1,  2,  3,  4,  1],
         [ 1,  5,  6,  7,  8,  1],
         [ 1,  9, 10, 11, 12,  1],
         [ 1, 13, 14, 15, 16,  1],
         [ 1,  1,  1,  1,  1,  1]]])

可以看到,我们在(left,right,top,bottom)四个方向都填充了一个像素,像素值=value

5.2 reflect模式

该模式分我们既可以说是反射模式,也可以说是镜像模式,当然我个人认为镜像模式更形象:

我们看代码:

import torch
import torch.nn as nn

data = torch.tensor([[
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
    [13, 14, 15, 16]
]])

print(data.size())
print(data)
paddata2 = nn.functional.pad(data, (1, 1, 1, 1), mode="reflect", value=0) # value指定值将无效
print(paddata2.size())
print(paddata2)

输出:

torch.Size([1, 4, 4])
tensor([[[ 1,  2,  3,  4],
         [ 5,  6,  7,  8],
         [ 9, 10, 11, 12],
         [13, 14, 15, 16]]])
torch.Size([1, 6, 6])
tensor([[[ 6,  5,  6,  7,  8,  7],
         [ 2,  1,  2,  3,  4,  3],
         [ 6,  5,  6,  7,  8,  7],
         [10,  9, 10, 11, 12, 11],
         [14, 13, 14, 15, 16, 15],
         [10,  9, 10, 11, 12, 11]]])

此刻,我们来看,为什么说用镜像一词更贴切:

  • 第1列是以第2列为镜子的第3列的镜像
  • 最后1列是以倒数第2列为镜子的倒数第3列的镜像
  • 左上角的6是以1为镜子的6的镜像,右上角的7是以4为镜子的7的镜像
  • 以此类推,可以看出符合我们的说法

再看一组测试,此时我们将填充语句变更为

paddata2 = nn.functional.pad(data, (2, 1, 1, 1), mode="reflect", value=0)

输出:

torch.Size([1, 4, 4])
tensor([[[ 1,  2,  3,  4],
         [ 5,  6,  7,  8],
         [ 9, 10, 11, 12],
         [13, 14, 15, 16]]])
torch.Size([1, 6, 7])
tensor([[[ 7,  6,  5,  6,  7,  8,  7],
         [ 3,  2,  1,  2,  3,  4,  3],
         [ 7,  6,  5,  6,  7,  8,  7],
         [11, 10,  9, 10, 11, 12, 11],
         [15, 14, 13, 14, 15, 16, 15],
         [11, 10,  9, 10, 11, 12, 11]]])

读者体会一下吧,看看此时各方向的镜子是哪些列?被镜像的又是哪一列?相信读者通过这两个实例对该填充模式有比较清晰的认识。

5.3 replicate模式

该模式称为复制模式拷贝模式:其将数据的边缘(1像素)进行原封不动的拷贝。说起来抽象,直接上代码:

import torch
import torch.nn as nn

data = torch.tensor([[
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
    [13, 14, 15, 16]
]])

print(data.size())
print(data)
paddata3 = nn.functional.pad(data, (1, 1, 1, 1), mode="replicate")
print(paddata3)
print(paddata3)

输出:

torch.Size([1, 4, 4])
tensor([[[ 1,  2,  3,  4],
         [ 5,  6,  7,  8],
         [ 9, 10, 11, 12],
         [13, 14, 15, 16]]])
torch.Size([1, 6, 6])
tensor([[[ 1,  1,  2,  3,  4,  4],
         [ 1,  1,  2,  3,  4,  4],
         [ 5,  5,  6,  7,  8,  8],
         [ 9,  9, 10, 11, 12, 12],
         [13, 13, 14, 15, 16, 16],
         [13, 13, 14, 15, 16, 16]]])

从输出易得,确实是将边缘进行了复制,这样做可以使得我们对张量进行扩充填充后,张量的边缘信息不变:图像处理时,这种机制会经常用到。

如果修改pad参数,会修改拷贝的列数吗?我们对填充语句做如下修改:

paddata3 = nn.functional.pad(data, (2, 1, 4, 1), mode="replicate")

输出:

torch.Size([1, 4, 4])
tensor([[[ 1,  2,  3,  4],
         [ 5,  6,  7,  8],
         [ 9, 10, 11, 12],
         [13, 14, 15, 16]]])
torch.Size([1, 9, 7])
tensor([[[ 1,  1,  1,  2,  3,  4,  4],
         [ 1,  1,  1,  2,  3,  4,  4],
         [ 1,  1,  1,  2,  3,  4,  4],
         [ 1,  1,  1,  2,  3,  4,  4],
         [ 1,  1,  1,  2,  3,  4,  4],
         [ 5,  5,  5,  6,  7,  8,  8],
         [ 9,  9,  9, 10, 11, 12, 12],
         [13, 13, 13, 14, 15, 16, 16],
         [13, 13, 13, 14, 15, 16, 16]]])

从输出可以看出,不论你怎么修改pad参数元组,被拷贝的总是一个像素宽的边缘。而你更改的填充宽度如果大于1,至于将1像素边缘复制多次罢了。

5.4 circular模式

该模式称为循环填充模式,它是怎么干的呢?以2维矩阵上下左右均填充1个像素为例,它的填充值取自其对立面:左侧填充列的值=原矩阵最右侧1列的值,右侧填充列的值=原矩阵最左侧1列的值;上下同理,直接看代码:

import torch
import torch.nn as nn

data = torch.tensor([[
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
    [13, 14, 15, 16]
]])

print(data.size())
print(data)
paddata4 = nn.functional.pad(data, (1, 1, 1, 1), mode="circular", value=0)
print(paddata4.size())
print(paddata4)

输出:

torch.Size([1, 4, 4])
tensor([[[ 1,  2,  3,  4],
         [ 5,  6,  7,  8],
         [ 9, 10, 11, 12],
         [13, 14, 15, 16]]])
torch.Size([1, 6, 6])
tensor([[[16, 13, 14, 15, 16, 13],
         [ 4,  1,  2,  3,  4,  1],
         [ 8,  5,  6,  7,  8,  5],
         [12,  9, 10, 11, 12,  9],
         [16, 13, 14, 15, 16, 13],
         [ 4,  1,  2,  3,  4,  1]]])

从输出可得,我们的解释是正确的。

哪如果我要填充多个像素又是什么样的效果?我们将填充语句进行修改如下:

paddata4 = nn.functional.pad(data, (2, 3, 1, 1), mode="circular", value=0)

输出:

torch.Size([1, 4, 4])
tensor([[[ 1,  2,  3,  4],
         [ 5,  6,  7,  8],
         [ 9, 10, 11, 12],
         [13, 14, 15, 16]]])
torch.Size([1, 6, 9])
tensor([[[15, 16, 13, 14, 15, 16, 13, 14, 15],
         [ 3,  4,  1,  2,  3,  4,  1,  2,  3],
         [ 7,  8,  5,  6,  7,  8,  5,  6,  7],
         [11, 12,  9, 10, 11, 12,  9, 10, 11],
         [15, 16, 13, 14, 15, 16, 13, 14, 15],
         [ 3,  4,  1,  2,  3,  4,  1,  2,  3]]])

读者可以自行体会一下,该模式下,它的多像素填充有什么规律。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部