【池化层】基础回顾:带你认识神经网络中的池化层

简介: 【池化层】基础回顾:带你认识神经网络中的池化层

前言

  池化函数是深度学习中非常重要的一种操作,它可以对输入的数据进行降维,从而减小模型的复杂度,同时还能够提高模型的鲁棒性和泛化能力。在卷积神经网络中,池化函数通常被用来对卷积层的输出进行处理,在保留重要特征的同时,剔除一些冗余信息,从而达到优化模型的目的。本文将详细介绍池化函数的原理、分类以及常用方法,并探讨其在卷积神经网络中的应用。

池化

  深度学习中常用的池化函数有以下几种:

  1. 最大池化(Max Pooling):在池化窗口内选取最大值作为输出,可以提取图像中的主要特征。
  2. 平均池化(Average Pooling):在池化窗口内取平均值作为输出,可以平滑输入数据,减少噪声的影响。
  3. L2池化(L2 Pooling):在池化窗口内计算L2范数,可以增强模型的鲁棒性,减少过拟合的风险。
  4. 反向最大池化(Max Unpooling):是最大池化的逆操作,可以将池化后的数据还原到原始大小,用于图像分割等任务。
  5. 局部响应归一化(Local Response Normalization,LRN):对卷积层输出的每个像素进行归一化,可以增强模型的泛化能力。
  6. 双线性池化(Bilinear Pooling):对两个输入特征进行点积运算,可以提取图像中的高级语义信息。 总之,不同的池化函数适用于不同的任务和场景,选择合适的池化函数可以优化模型的性能和表现。

最大池化

  最大池化是一种常用的池化函数,它的原理是在池化窗口内选取最大值作为输出。在卷积神经网络中,最大池化通常被用来对卷积层的输出进行处理,在保留重要特征的同时,剔除一些冗余信息,从而达到优化模型的目的。

最大池化的过程如下:

  1. 将输入数据分成若干个不重叠的池化窗口。
  2. 在每个池化窗口中选取最大值作为输出,即将窗口内的所有像素值取最大值。
  3. 将池化窗口内的最大值作为输出,组成新的矩阵或张量。

  最大池化的优点是可以提取图像中的主要特征,同时减小数据的维度,降低计算量,防止过拟合。最大池化的缺点是可能会造成信息的丢失,因为它只选取了窗口内的最大值,而忽略了其他像素的信息。

  最大池化的参数包括池化窗口大小和步长,池化窗口大小决定了每个池化窗口内包含的像素数量,而步长决定了池化窗口的移动距离。通常情况下,池化窗口大小为2x2,步长为2,这样可以将输入数据的尺寸减半,提高模型的效率。

普通最大池化:

在每个池化窗口内选取最大值作为输出,是最常见的最大池化方法。

ruby

复制代码

pythonimport torch.nn as nn
class MaxPool(nn.Module):
    def __init__(self, kernel_size=2, stride=2):
        super(MaxPool, self).__init__()
        self.pool = nn.MaxPool2d(kernel_size=kernel_size, stride=stride)
        
    def forward(self, x):
        x = self.pool(x)
        return x

多通道最大池化:

对于具有多个通道的输入数据,分别对每个通道进行最大池化,得到多个输出,然后将这些输出合并起来,作为最终的输出。

ruby

复制代码

import torch.nn as nn
class MaxPool(nn.Module):
    def __init__(self, kernel_size=2, stride=2):
        super(MaxPool, self).__init__()
        self.pool = nn.MaxPool2d(kernel_size=kernel_size, stride=stride)
        
    def forward(self, x):
        x = self.pool(x)
        return x

非对称最大池化:

在池化窗口内选取最大值时,可以采用非对称的方法,即在水平和垂直方向上分别选取最大值,这样可以提取更多的特征信息。

ini

复制代码

import torch.nn as nn
class AsymMaxPool(nn.Module):
    def __init__(self, kernel_size=2, stride=2):
        super(AsymMaxPool, self).__init__()
        self.pool = nn.MaxPool2d(kernel_size=kernel_size, stride=stride)
        
    def forward(self, x):
        # 将输入图像沿着水平方向进行分割
        left = x[:, :, :, :x.size(3)//2]
        right = x[:, :, :, x.size(3)//2:]
        # 对左半部分进行最大池化
        left = self.pool(left)
        # 将左半部分和右半部分拼接在一起
        x = torch.cat([left, right], dim=3)
        return x

平均最大池化:

在池化窗口内既选取最大值,又取平均值,这样可以在保留主要特征的同时平滑数据,减少噪声的影响。

ruby

复制代码

import torch.nn as nn
class AvgMaxPool(nn.Module):
    def __init__(self, kernel_size=2, stride=2):
        super(AvgMaxPool, self).__init__()
        self.maxpool = nn.MaxPool2d(kernel_size=kernel_size, stride=stride)
        self.avgpool = nn.AvgPool2d(kernel_size=kernel_size, stride=stride)
        
    def forward(self, x):
        # 对输入图像进行最大池化和平均池化,并将结果相加
        x1 = self.maxpool(x)
        x2 = self.avgpool(x)
        x = x1 + x2
        return x

平均池化

  平均池化(Average Pooling)是一种常用的池化函数,它的原理是在池化窗口内取平均值作为输出。具体来说,平均池化将输入数据分割成若干个大小相同的窗口,在每个窗口内计算所有元素的平均值,将平均值作为该窗口的输出。通过这种方式,平均池化可以减少输入数据的大小,降低模型的复杂度,同时还可以平滑输入数据,减少噪声的影响。

常用的平均池化方法有以下几种:

全局平均池化(Global Average Pooling):

将整个输入数据都作为一个窗口进行平均池化,输出一个标量值。全局平均池化通常用于分类任务的最后一层,将卷积层的输出降维为一个特征向量,然后通过全连接层进行分类。

ruby

复制代码

import torch.nn as nn
class GlobalAvgPool(nn.Module):
    def __init__(self):
        super(GlobalAvgPool, self).__init__()
        
    def forward(self, x):
        # 对输入图像进行全局平均池化
        x = nn.functional.adaptive_avg_pool2d(x, (1, 1))
        return x

普通平均池化(Average Pooling):

在每个窗口内计算所有元素的平均值作为输出。普通平均池化通常用于图像分类任务中,可以在特征提取阶段减小输入数据的大小。

ruby

复制代码

import torch.nn as nn
class AvgPool(nn.Module):
    def __init__(self, kernel_size=2, stride=2):
        super(AvgPool, self).__init__()
        self.pool = nn.AvgPool2d(kernel_size=kernel_size, stride=stride)
        
    def forward(self, x):
        x = self.pool(x)
        return x

加权平均池化(Weighted Average Pooling):

在每个窗口内计算所有元素的加权平均值作为输出。加权平均池化可以根据不同的特征区域设置不同的权重,提高模型的灵活性和表现。

ruby

复制代码

import torch.nn as nn
class WeightedAvgPool(nn.Module):
    def __init__(self, kernel_size=2, stride=2):
        super(WeightedAvgPool, self).__init__()
        self.pool = nn.AvgPool2d(kernel_size=kernel_size, stride=stride)
        self.weight = nn.Parameter(torch.ones(1, x.size(1), 1, 1))
        
    def forward(self, x):
        # 对输入图像进行加权平均池化
        x = self.pool(x * self.weight)
        return x

L2池化

L2池化是一种常用的池化函数,它的原理是在池化窗口内计算L2范数,即对窗口内的元素进行平方求和后再开方,将结果作为输出。L2池化可以增强模型的鲁棒性,减少过拟合的风险。

L2池化层:

可以作为卷积神经网络的一层,对输入的数据进行处理,从而减少模型的复杂度。L2池化层通常与卷积层交替使用,可以提高模型的性能和泛化能力。

ruby

复制代码

import torch.nn as nn
class L2Pool(nn.Module):
    def __init__(self, kernel_size=2, stride=2):
        super(L2Pool, self).__init__()
        self.pool = nn.AvgPool2d(kernel_size=kernel_size, stride=stride)
        
    def forward(self, x):
        # 对输入图像的平方进行平均池化,并取平方根
        x = torch.sqrt(self.pool(x**2))
        return x

L2正则化:

可以作为损失函数的一部分,对模型的权重进行正则化,从而减少模型的复杂度。L2正则化的目标是使模型的权重尽可能小,以避免过拟合的风险。

ruby

复制代码

import torch.nn as nn
class L2Regularization(nn.Module):
    def __init__(self, weight_decay=0.01):
        super(L2Regularization, self).__init__()
        self.weight_decay = weight_decay
        
    def forward(self, model):
        # 对模型的参数进行L2正则化
        l2_reg = torch.tensor(0., device=model.device)
        for name, param in model.named_parameters():
            if 'bias' not in name:
                l2_reg += torch.norm(param, p=2)
        loss = self.weight_decay * l2_reg
        return loss

反向最大池化

  反向最大池化(Max Unpooling)是最大池化(Max Pooling)的逆操作,可以将池化后的数据还原到原始大小。在图像分割等任务中,反向最大池化被广泛应用,它可以将卷积神经网络输出的特征图还原到输入图像的大小,从而得到像素级别的预测结果。

  反向最大池化的原理是:在最大池化操作中,我们记录下了每个池化窗口内的最大值所在的位置,反向最大池化时,我们可以利用这些位置信息,将池化后的数据还原到原始大小。具体来说,反向最大池化的过程如下:

  1. 将池化后的数据扩展成与原始数据相同的大小,除了最大值所在的位置,其他位置都填充0。
  2. 在最大值所在的位置填充池化前的值。
  3. 重复步骤1和2,直到所有池化窗口都被处理完毕。

反向最大池化的常用方法有以下几种:

最近邻插值(Nearest Neighbor Interpolation):

将池化前最大值所在的位置复制到池化后的区域中。

ini

复制代码

import torch
def nearest_upsample(x, scale_factor):
    _, _, H, W = x.size()
    new_H = int(H * scale_factor)
    new_W = int(W * scale_factor)
    y = torch.zeros((x.size(0), x.size(1), new_H, new_W), device=x.device)
    for i in range(new_H):
        for j in range(new_W):
            h = int(i / scale_factor)
            w = int(j / scale_factor)
            y[:, :, i, j] = x[:, :, h, w]
    return y

双线性插值(Bilinear Interpolation):

根据池化前最大值所在的位置周围的四个点,计算池化后的区域内每个像素的值。

ini

复制代码

import torch
def bilinear_upsample(x, scale_factor):
    _, _, H, W = x.size()
    new_H = int(H * scale_factor)
    new_W = int(W * scale_factor)
    y = torch.zeros((x.size(0), x.size(1), new_H, new_W), device=x.device)
    for i in range(new_H):
        for j in range(new_W):
            h = i / scale_factor
            w = j / scale_factor
            h1 = int(h)
            h2 = min(h1 + 1, H - 1)
            w1 = int(w)
            w2 = min(w1 + 1, W - 1)
            y[:, :, i, j] = (h2 - h) * (w2 - w) * x[:, :, h1, w1] \
                             + (h - h1) * (w2 - w) * x[:, :, h2, w1] \
                             + (h2 - h) * (w - w1) * x[:, :, h1, w2] \
                             + (h - h1) * (w - w1) * x[:, :, h2, w2]
    return y

反卷积(Deconvolution):

通过反向卷积运算,将池化后的数据还原到原始大小。

ini

复制代码

import torch
def conv_transpose2d(x, weight, bias=None, stride=1, padding=0, output_padding=0, groups=1, dilation=1):
    batch_size, in_channels, in_h, in_w = x.size()
    out_channels, _, kernel_h, kernel_w = weight.size()
    # compute output shape
    out_h = (in_h - 1) * stride - 2 * padding + kernel_h + output_padding
    out_w = (in_w - 1) * stride - 2 * padding + kernel_w + output_padding
    output_shape = (batch_size, out_channels, out_h, out_w)
    # initialize output tensor and unfold input tensor
    output = torch.zeros(output_shape, device=x.device)
    x_unfold = torch.nn.functional.unfold(x, kernel_size=(kernel_h, kernel_w), stride=stride, padding=padding)
    # compute output
    weight = weight.view(out_channels, -1).t()
    x_unfold = x_unfold.view(batch_size, -1, out_h * out_w)
    output = torch.bmm(weight.unsqueeze(0).expand(batch_size, -1, -1), x_unfold)
    output = output.view(output_shape)
    # add bias if necessary
    if bias is not None:
        output += bias.view(1, out_channels, 1, 1).expand_as(output)
    return output

局部相应归一化

  局部响应归一化(Local Response Normalization,LRN)是一种在卷积神经网络中常用的池化函数,它可以对卷积层输出的每个像素进行归一化,从而增强模型的泛化能力。其原理如下:

  在卷积神经网络中,每个卷积核都会对输入图像进行卷积操作,得到一个输出特征图。这些特征图中的每个像素都可以看作是一个神经元,其输出值取决于输入值和卷积核的权重。由于不同的卷积核对应着不同的特征提取任务,因此它们之间的输出值可能存在较大的差异。为了避免某些神经元的输出值过大,影响到其他神经元的学习,LRN可以对每个神经元的输出值进行归一化,从而增强模型的泛化能力。

常用的LRN方法有以下几种:

基本LRN:

将每个神经元的输出值除以相邻神经元的平方和,然后乘以一个常数,得到归一化后的输出值。

python

复制代码

import torch
class BasicLRN(torch.nn.Module):
    def __init__(self, alpha=1e-4, beta=0.75, k=2, n=5):
        super(BasicLRN, self).__init__()
        self.alpha = alpha
        self.beta = beta
        self.k = k
        self.n = n
    def forward(self, x):
        squared = x.pow(2)
        scale = self.k
        for i in range(self.n):
            scale += self.alpha * squared[:, i:i+scale.size(1)]
        scale = scale.pow(self.beta)
        return x / scale

按通道LRN:

将每个通道内的神经元输出值进行归一化,可以提高模型的鲁棒性。

python

复制代码

import torch
class ChannelLRN(torch.nn.Module):
    def __init__(self, num_channels, alpha=1e-4, beta=0.75, k=2, n=5):
        super(ChannelLRN, self).__init__()
        self.num_channels = num_channels
        self.alpha = alpha
        self.beta = beta
        self.k = k
        self.n = n
    def forward(self, x):
        squared = x.pow(2)
        scale = self.k
        for i in range(self.n):
            scale += self.alpha * squared[:, i:i+self.num_channels, :, :]
        scale = scale.pow(self.beta)
        return x / scale

跨通道LRN:

将每个神经元的输出值除以相邻通道内神经元的平方和,可以增强模型对不同通道之间的响应差异。

python

复制代码

import torch
class CrossChannelLRN(torch.nn.Module):
    def __init__(self, num_channels, alpha=1e-4, beta=0.75, k=2, n=5):
        super(CrossChannelLRN, self).__init__()
        self.num_channels = num_channels
        self.alpha = alpha
        self.beta = beta
        self.k = k
        self.n = n
    def forward(self, x):
        squared = x.pow(2)
        scale = self.k
        for i in range(self.num_channels):
            start = max(0, i - self.n // 2)
            end = min(self.num_channels, i + self.n // 2 + 1)
            s = squared[:, start:end, :, :]
            scale += self.alpha * s
        scale = scale.pow(self.beta)
        return x / scale

双线性池化

  双线性池化(Bilinear Pooling)是一种基于点积运算的池化方法,主要用于提取图像中的高级语义信息。它可以将两个输入特征进行点积运算,从而得到一个新的特征向量,用于后续的分类和识别任务。

  双线性池化的原理比较简单,它可以将两个输入特征进行点积运算,从而得到一个新的特征向量。具体来说,假设输入的特征为xy,则双线性池化的输出为:

image.png

  其中,A是一个权重矩阵,它可以通过训练得到,用于捕捉输入特征之间的相关性。z是一个新的特征向量,它可以用于后续的分类和识别任务。

双线性池化的常用方法主要包括以下几种:

Kronecker积方法

  Kronecker积方法是最常用的双线性池化方法之一,它可以将两个输入特征分别进行展开,然后进行点积运算。具体来说,假设输入的特征为xy,则双线性池化的输出为:

image.png

  其中,vec()表示将向量或矩阵展开成列向量的操作,表示Kronecker积运算。

ini

复制代码

import torch
def bilinear_pooling_kronecker(x, y):
    # x: [batch_size, C, H, W]
    # y: [batch_size, C, H, W]
    batch_size, C, H, W = x.size()
    x = x.view(batch_size, C, H*W)  # [batch_size, C, H*W]
    y = y.view(batch_size, C, H*W)  # [batch_size, C, H*W]
    out = torch.einsum('bci,bcj->bcij', x, y)  # [batch_size, C, H*W, H*W]
    out = out.view(batch_size, C, H, W, H, W)  # [batch_size, C, H, W, H, W]
    out = out.permute(0, 1, 3, 2, 5, 4)  # [batch_size, C, W, H, W, H]
    out = out.contiguous().view(batch_size, C, W*H, W*H)  # [batch_size, C, H*W, H*W]
    return out

离散傅里叶变换方法

  离散傅里叶变换方法是另一种常用的双线性池化方法,它可以将两个输入特征进行离散傅里叶变换,然后进行点积运算。具体来说,假设输入的特征为xy,则双线性池化的输出为:

image.png

  其中,x^y^分别为xy的离散傅里叶变换系数,KL分别为xy的长度。

ini

复制代码

import torch.fft
def bilinear_pooling_dft(x, y):
    # x: [batch_size, C, H, W]
    # y: [batch_size, C, H, W]
    batch_size, C, H, W = x.size()
    x = x.view(batch_size, C, H*W)  # [batch_size, C, H*W]
    y = y.view(batch_size, C, H*W)  # [batch_size, C, H*W]
    out = torch.fft.fftn(x, dim=(-1,)) * torch.fft.fftn(y, dim=(-1,))  # [batch_size, C, H*W]
    out = torch.fft.ifftn(out, dim=(-1,))  # [batch_size, C, H*W]
    out = out.view(batch_size, C, H, W, H, W)  # [batch_size, C, H, W, H, W]
    out = out.permute(0, 1, 3, 2, 5, 4)  # [batch_size, C, W, H, W, H]
    out = out.contiguous().view(batch_size, C, W*H, W*H)  # [batch_size, C, H*W, H*W]
    return out

逐通道方法

  逐通道方法是一种简化版的双线性池化方法,它可以将两个输入特征的每个通道分别进行点积运算,然后将结果相加。具体来说,假设输入的特征为xy,则双线性池化的输出为:

image.png

  其中,C为输入特征的通道数,xi,cx分别为xy的第i个通道的第c个元素。

ini

复制代码

def bilinear_pooling_channelwise(x, y):
    # x: [batch_size, C, H, W]
    # y: [batch_size, C, H, W]
    batch_size, C, H, W = x.size()
    x = x.view(batch_size, C, H*W)  # [batch_size, C, H*W]
    y = y.view(batch_size, C, H*W)  # [batch_size, C, H*W]
    out = torch.bmm(x.transpose(1, 2), y)  # [batch_size, H*W, H*W]
    out = out.view(batch_size, C, H, W, H, W)  # [batch_size, C, H, W, H, W]
    out = out.permute(0, 1, 3, 2, 5, 4)  # [batch_size, C, W, H, W, H]
    out = out.contiguous().view(batch_size, C, W*H, W*H)  # [batch_size, C, H*W, H*W]
    return out

结尾

  在实际应用中,池化函数的选择和参数设置往往需要经验和实验的支持,需要我们不断地尝试和调整,才能得到最优的结果。


相关文章
|
1月前
|
机器学习/深度学习 计算机视觉
卷积神经网络中池化层的概念介绍
卷积神经网络中池化层的概念介绍
25 0
|
1月前
|
机器学习/深度学习
卷积神经网络的全连接层的概念
卷积神经网络的全连接层的概念
25 0
|
2月前
|
机器学习/深度学习 存储 自然语言处理
卷积神经元网络CNN基础
卷积神经元网络CNN基础
38 1
|
2月前
|
机器学习/深度学习 算法框架/工具
池化层和全连接层
池化层和全连接层
|
3月前
|
机器学习/深度学习 数据采集 自然语言处理
【Conv】万金油一样的卷积层!使用卷积层替代全连接层与池化层
【Conv】万金油一样的卷积层!使用卷积层替代全连接层与池化层
35 1
|
9月前
|
数据采集 机器学习/深度学习 PyTorch
Pytorch学习笔记(5):torch.nn---网络层介绍(卷积层、池化层、线性层、激活函数层)
Pytorch学习笔记(5):torch.nn---网络层介绍(卷积层、池化层、线性层、激活函数层)
173 0
Pytorch学习笔记(5):torch.nn---网络层介绍(卷积层、池化层、线性层、激活函数层)
|
5月前
|
机器学习/深度学习 计算机视觉
CNN全连接层是什么东东?
CNN全连接层是什么东东?
90 4
|
10月前
|
机器学习/深度学习 PyTorch 算法框架/工具
PyTorch: 池化-线性-激活函数层
PyTorch: 池化-线性-激活函数层
145 0
|
机器学习/深度学习 编解码 算法
【Pytorch神经网络理论篇】 32 PNASNet模型:深层可分离卷积+组卷积+空洞卷积
PNASNet模型是Google公司的AutoML架构自动搜索所产生的模型,它使用渐进式网络架构搜索技术,并通过迭代自学习的方式,来寻找最优网络结构。即用机器来设计机器学习算法,使得它能够更好地服务于用户提供的数据。该模型在ImageNet数据集上Top-1准确率达到82.9%,Top-5准确率达到96。2%,是目前最好的图片分类模型之一。
148 0
|
机器学习/深度学习 计算机视觉
全连接层,池化层,卷积层
全连接层,池化层,卷积层科普
281 0