分组卷积
class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
group参数的作用是控制分组卷积。
View的作用
# view的作用
# 当view()参数不止一个,且其中一个为-1时,则-1的作用相当于占位符,其大小由torch根据矩阵元素个数和其他维大小来自动计算
# x = torch.Tensor([1,10,100,1000,10000,100000]).view(1,-1,1,1) #torch.Size([1, 6, 1, 1])
# 注意:torch中的矩阵顺序为[batch_size, channels, x, y]
# a = torch.arange(0,20) #此时a的shape是(1,20)
# a.view(4,5).shape #输出为(4,5)
# a.view(4,5,1,1).shape #输出为torch.Size([4, 5, 1, 1])
# # a.view(-1,5).shape #输出为(4,5)
# # a.view(4,-1).shape #输出为(4,5)
分组卷积实例
import torch
import torch.nn as nn
# https://blog.csdn.net/ECNU_LZJ/article/details/105265843 Pytorch-nn.Conv2d中groups参数的理解
# y = torch.Tensor([1,10,100,1000,10000,100000]) #torch.Size([6])
x = torch.Tensor([1,10,100,1000,10000,100000]).view(1,-1,1,1) #torch.Size([1, 6, 1, 1])
print("x:",x.int())
# x: tensor([[[[ 1]],
# [[ 10]],
# [[ 100]],
# [[ 1000]],
# [[ 10000]],
# [[100000]]]], dtype=torch.int32)
# group参数的作用是控制分组卷积。
# 输入in_channels被划分为groups组, 每组通道数为in_channels/groups 6/3=2。每组需要重复计算out_channels/(in_channels/groups)次 3/1=3。
# 分组卷积
conv = nn.Conv2d(
in_channels=6,out_channels=9,kernel_size=1,stride=1,padding=0,groups=3,bias=False
)
# 普通卷积
conv1 = nn.Conv2d(
in_channels=6,out_channels=9,kernel_size=1,stride=1,padding=0,groups=1,bias=False
)
# 分别查看两种卷积的参数
print("Group Conv weight size: ", conv.weight.data.size()) #Group Conv weight size: torch.Size([9, 2, 1, 1])
print("Conv weight size: ", conv1.weight.data.size()) #Conv weight size: torch.Size([9, 6, 1, 1])
# 给分组卷积的参数赋值 1-18
conv.weight.data = torch.arange(1,19).float().view(9,2,1,1)
print("conv weight data: ",conv.weight.data.int())
# conv weight data: tensor([[[[ 1]],
# [[ 2]]],
# [[[ 3]],
# [[ 4]]],
# [[[ 5]],
# [[ 6]]],
# [[[ 7]],
# [[ 8]]],
# [[[ 9]],
# [[10]]],
# [[[11]],
# [[12]]],
# [[[13]],
# [[14]]],
# [[[15]],
# [[16]]],
# [[[17]],
# [[18]]]], dtype=torch.int32)
# 分组卷积运算
output = conv(x).int()
#print("output shape: ",output.shape) #output shape: torch.Size([1, 9, 1, 1])
print("output: ",output)
# output: tensor([[[[ 21]],
# [[ 43]],
# [[ 65]],
# [[ 8700]],
# [[ 10900]],
# [[ 13100]],
# [[1530000]],
# [[1750000]],
# [[1970000]]]], dtype=torch.int32)
如果是正常的卷积,参数大小应该为: [9(输出通道), 6(输入通道), 1(核h), 1(核w)]。
这是因为输出是9个通道,每个通道都需要一个[6, 1, 1]大小的卷积(输入的每个通道都参与到了运算)。
但是我们可以从代码的运行结果中看到Conv层的参数大小为: [9, 2, 1, 1]。这就说明对于每个输出的通道,只有两个输入的通道参与了运算。 这个2就是 input/group = 6/3 = 2
事实就是这样,分组卷积的过程中只有部分输入的通道才参与了运算。我们就以上面的代码为例进行讲解。
首先将输入的6个通道分为3组: [1, 10], [100, 1000], [10000, 100000],每一组都用来生成输出的一个通道。
3个组只能生成3个输出通道,但是要求输出是9个通道,所以每个组需要重复计算三次。
输出的第1个通道: 1 * 1 + 2 * 10 = 21,需要用到输入的第1组。
输出的第2个通道: 3 * 1 + 4 * 10 = 43,需要用到输入的第1组。
输出的第3个通道: 5 * 1 + 6 * 10 = 65,需要用到输入的第1组。
输出的第4个通道: 7 * 100 + 8 * 1000 = 8700,需要用到输入的第2组。
输出的第5个通道: 9 * 100 + 10 * 1000 = 10900,需要用到输入的第2组。
输出的第6个通道: 11 * 100 + 12 * 1000 = 13100,需要用到输入的第2组。
输出的第7个通道: 13 * 10000 + 14 * 100000 = 1530000,需要用到输入的第3组。
输出的第8个通道: 15 * 10000 + 16 * 100000 = 1750000,需要用到输入的第3组。
输出的第9个通道: 17 * 10000 + 18 * 100000 = 1970000,需要用到输入的第3组。
普通卷积 分组卷积 深度可分离卷积对比
#可分离卷积及深度可分离卷积详解 https://blog.csdn.net/qq_40406731/article/details/107398593
# 普通卷积:总参数量是 4x8x3x3=288。
# 分组卷积:假设输入层为一个大小为64×64像素的彩色图片、in_channels=4,out_channels=8,经过2组卷积层,最终输出8个Feature Map,我们可以计算出卷积层的参数数量是 2x8x3x3=144。
# 深度可分离卷积:逐深度卷积的卷积数量是 4x3x3=36, 逐点卷积卷积数量是 1x1x4x8=32,总参数量为68。
# 普通卷积 : Conv2d( 4,8,3,stride=1,padding=1,groups=1,bias=False) #torch.Size([8, 4, 3, 3]) Total params: 288
# 分组卷积 : Conv2d(4,8,3,stride=1,padding=1,groups=2,bias=False) #torch.Size([8, 2, 3, 3]) Total params: 144
# 深度可分离卷积: Conv2d(4,4,3,stride=1,padding=1,groups=4,bias=False) (降通)+ #torch.Size([4, 1, 3, 3]) Total params: 36 +
# Conv2d(4,8,1,stride=1,padding=0,groups=1,bias=False) (升维) #torch.Size([8, 4, 1, 1]) Total params: 32 = 68
import torch.nn as nn
import torch
from torchsummary import summary
class Conv_test(nn.Module):
def __init__(self,in_ch,out_ch,kernel_size,padding,groups):
super(Conv_test, self).__init__()
self.conv = nn.Conv2d(
in_channels=in_ch,
out_channels=out_ch,
kernel_size=kernel_size,
stride=1,
padding=padding,
groups=groups,
bias=False
)
def forward(self, input):
out = self.conv(input)
return out
#标准的卷积层,输入的是3*64*64,目标输出4个feature map
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
ordinary_conv = Conv_test(4, 8, 3, padding=1, groups=1).to(device)
ordinary_conv1 = nn.Conv2d( in_channels=4,out_channels=8,kernel_size=3,stride=1,padding=1,groups=1,bias=False)
print("ordinary_conv1 weight size:",ordinary_conv1.weight.data.size()) #torch.Size([8, 4, 3, 3])
# print(summary(ordinary_conv,input_size=(4,64,64)))
# ----------------------------------------------------------------
# Layer (type) Output Shape Param #
# ================================================================
# Conv2d-1 [-1, 8, 64, 64] 288
# ================================================================
# Total params: 288
# Trainable params: 288
# Non-trainable params: 0
# ----------------------------------------------------------------
# Input size (MB): 0.06
# Forward/backward pass size (MB): 0.25
# Params size (MB): 0.00
# Estimated Total Size (MB): 0.31
# ----------------------------------------------------------------
#分组卷积层,输入的是4*64*64,目标输出8个feature map
group_conv = Conv_test(4,8,3,padding=1,groups=2).to(device)
group_conv1 = nn.Conv2d(4,8,3,padding=1,groups=2)
print("group_conv1 weight size:",group_conv1.weight.data.size())#torch.Size([8, 2, 3, 3])
# print(summary(group_conv,input_size=(4,64,64)))
# ----------------------------------------------------------------
# Layer (type) Output Shape Param #
# ================================================================
# Conv2d-1 [-1, 8, 64, 64] 144
# ================================================================
# Total params: 144
# Trainable params: 144
# Non-trainable params: 0
# ----------------------------------------------------------------
# Input size (MB): 0.06
# Forward/backward pass size (MB): 0.25
# Params size (MB): 0.00
# Estimated Total Size (MB): 0.31
# ----------------------------------------------------------------
# 逐深度卷积层,输入同上
# group参数的作用是控制分组卷积。
# 输入in_channels被划分为groups组, 每组通道数为in_channels/groups 4/4=1。每组需要重复计算out_channels/(in_channels/groups)次 4/1=3。
# 总共是 3*3*1*3=27
depth_conv = Conv_test(4, 4, 3, padding=1, groups=4).to(device)
depth_conv1 = nn.Conv2d(4, 4, 3, padding=1, groups=4)
print("depth_conv1 weight size:",depth_conv1.weight.data.size()) #torch.Size([4, 1, 3, 3])
# print(summary(depth_conv,input_size=(4,64,64)))
# ----------------------------------------------------------------
# Layer (type) Output Shape Param #
# ================================================================
# Conv2d-1 [-1, 4, 64, 64] 36
# ================================================================
# Total params: 36
# Trainable params: 36
# Non-trainable params: 0
# ----------------------------------------------------------------
# Input size (MB): 0.06
# Forward/backward pass size (MB): 0.12
# Params size (MB): 0.00
# Estimated Total Size (MB): 0.19
# ----------------------------------------------------------------
# 逐点卷积层,输入即逐深度卷积的输出大小,目标输出也是8个feature map channel=input/groups
point_conv = Conv_test(4, 8, kernel_size=1, padding=0, groups=1).to(device)
point_conv1 = nn.Conv2d(4, 8,1)
print("point_conv1 weight size:",point_conv1.weight.data.size()) #torch.Size([8, 4, 1, 1])
print(summary(point_conv,input_size=(4,64,64)))
# ----------------------------------------------------------------
# Layer (type) Output Shape Param #
# ================================================================
# Conv2d-1 [-1, 8, 64, 64] 32
# ================================================================
# Total params: 32
# Trainable params: 32
# Non-trainable params: 0
# ----------------------------------------------------------------
# Input size (MB): 0.06
# Forward/backward pass size (MB): 0.25
# Params size (MB): 0.00
# Estimated Total Size (MB): 0.31
# ----------------------------------------------------------------