当前位置: 首页 > 科技观察

卷积在PyTorch中的工作原理

时间:2023-03-15 23:22:27 科技观察

神经网络是一组应用于输入对象输出的操作(层)。在计算机视觉中,输入对象是图像:大小为[通道数X高度X宽度]的张量,其中通道数通常为3(RGB)。中间输出称为特征图。一张featuremap在某种意义上就是同一张图,只不过通道数是任意的,张量的每个单元称为一个特征。为了能够在一次通过中同时运行多个图像,所有这些张量都有一个额外的维度,等于批次中的对象数量。互相关操作可以表示为从内核沿着输入特征图滑动,就像在GIF上一样。每次我们将内核应用于输入时,我们将相应的内核权重与特征相乘并相加,从而在输出通道中产生一个新特征。卷积几乎是一种互相关,在神经网络中,卷积从一组通道创建另一组通道。即:将上述几个kernel一次存入卷积层,每个kernel产生一个channel,然后将它们连接起来。该卷积核的最终维度为:[每个输出的通道数X每个输入的通道数X核心高度X核心宽度]。在PyTorch和许多其他机器学习框架中,卷积层的下一部分是偏置。偏差是每个输出通道的附加项。因此,偏差是一组参数,其大小等于输出通道的数量。我们的任务是将两个卷积合并为一个。总之,卷积和偏置加法都是线性运算,线性运算的组合也是线性运算。让我们从最简单的情况开始。一种是任意大小的卷积,第二种是1x1卷积。他们都没有偏见。实际上,这意味着将特征图乘以一个常数。你可以简单地将第一个卷积的权重值乘以这个常数。Python代码如下:importtorchimportnumpyasnpconv1=torch.nn.Conv2d(1,1,(3,3),bias=False)#[inputchannelsXoutputchannelsXkernelshape]conv2=torch.nn.Conv2d(1,1,(1,1),bias=False)#1х1卷积。它的权重中只有一个权重。new_conv=torch.nn.Conv2d(1,1,3,bias=False)#这个卷积会合并twonew_conv.weight.data=conv1.weight.data*conv2.weight.data#让我们检查x=torch.randn([1,1,6,6])#[批量大小X输入通道X垂直大小X水平大小]out=conv2(conv1(x))new_out=new_conv(x)assert(torch.abs(out-new_out)<1e-6).min()现在让第一个卷积将任意数量的通道转换为另一个任意数量的通道。在这种情况下,我们的1x1卷积将是中间特征图通道的加权和。这意味着您可以对产生这些通道的权重进行加权求和。conv1=torch.nn.Conv2d(2,3,(3,3),bias=False)#[输入通道X输出通道X内核形状]conv2=torch.nn.Conv2d(3,1,(1,1),bias=False)#1х1卷积。它的权重中只有一个权重。new_conv=torch.nn.Conv2d(2,1,3,bias=False)#结果我们想要得到1个通道#卷积权重:[输出通道X输入通道Xkernelshape1Xkernelshape2]#为了将负责创建每个中间通道的权重乘以第一维#我们将不得不置换第二个卷积的维度。#然后我们将加权权重相加并完成测量,以便成功替换weights.new_conv.weight.data=(conv1.weight.data*conv2.weight.data.permute(1,0,2,3)).sum(0)[None,]x=torch.randn([1,2,6,6])out=conv2(conv1(x))new_out=new_conv(x)assert(torch.abs(out-new_out)<1e-6).min()现在让我们的两个卷积将意量的通道转换为另一个任意量的通道。在这种情况下,我们的1x1卷积将是一组中间特征图通道的加权和。这里的逻辑是一样的。生成中间特征的权重需要加到第二次卷积得到的权重上。conv1=torch.nn.Conv2d(2,3,3,bias=False)conv2=torch.nn.Conv2d(3,5,1,bias=False)new_conv=torch.nn.Conv2d(1,5,3,bias=False)#Curiosface:#在初始化期间传递什么大小并不重要。#更换权重将解决所有问题。#广播的魔力在起作用。在前面的例子中可以这样做,但是应该改变一些东西。new_conv.weight.data=(conv2.weight.data[…,None]*conv1.weight.data[None]).sum(1)x=torch.randn([1,2,6,6])out=conv2(conv1(x))new_out=new_conv(x)assert(torch.abs(out-new_out)<1e-6).min()现在,是时候放弃对第二个卷积的大小的限制了。为简单起见,让我们看一下一维卷积。核心2中的“k”操作会是什么样子?让我们添加一个内核大小为2的附加卷积v:重写等式:最后:结果是内核3的卷积。此卷积中的内核是应用于第一个卷积的填充权重互相关的结果使用由第二个卷积的权重创建的内核。相同的逻辑适用于其他大小的内核、二维情况和多通道情况。Python示例代码如下:kernel_size_1=np.array([3,3])kernel_size_2=np.array([3,5])kernel_size_merged=kernel_size_1+kernel_size_2–1conv1=torch.nn.Conv2d(2,3,kernel_size_1,bias=False)conv2=torch.nn.Conv2d(3,5,kernel_size_2,bias=False)new_conv=torch.nn.Conv2d(2,5,kernel_size_merged,bias=False)#计算我们需要多少个零padafterfirstconvolution#Padding表示我们需要通过垂直和水平添加多少个零padding=[kernel_size_2[0]-1,kernel_size_2[1]-1]new_conv.weight.data=torch.conv2d(conv1.weight.data.permute(1,0,2,3),#我们已经看到了这个。conv2.weight.data.flip(-1,-2),#这样做是为了从卷积中建立互相关。padding=padding)。permute(1,0,2,3)x=torch.randn([1,2,9,9])out=conv2(conv1(x))new_out=new_conv(x)assert(torch.abs(out-new_out)<1e-6).min()现在让我们添加偏差。我们将从第二个卷积中的偏差开始。偏置是一个与输出通道数大小相同的向量,然后将其添加到卷积的输出中。这意味着我们只需要将第二个卷积的偏差放入结果中。kernel_size_1=np.array([3,3])kernel_size_2=np.array([3,5])kernel_size_merged=kernel_size_1+kernel_size_2–1conv1=torch.nn.Conv2d(2,3,kernel_size_1,bias=False)conv2=torch.nn.Conv2d(3,5,kernel_size_2,bias=True)x=torch.randn([1,2,9,9])out=conv2(conv1(x))new_conv=torch.nn.Conv2d(2,5,kernel_size_merged,bias=True)padding=[kernel_size_2[0]-1,kernel_size_2[1]-1]new_conv.weight.data=torch.conv2d(conv1.weight.data.permute(1,0,2,3),conv2.weight.data.flip(-1,-2),padding=padding).permute(1,0,2,3)new_conv.bias.data=conv2.bias.data#这里是新的partnew_out=new_conv(x)assert(torch.abs(out-new_out)<1e-6).min(向第一个卷积添加偏差有点复杂。我们将分两个阶段进行,首先,我们注意到使用卷积中的偏差相当于创建一个额外的特征图,其中每个通道的特征是恒定的,等于偏差参数。然后将该特征添加到卷积的输出中。kernel_size_1=np.array([3,3])kernel_size_2=np.array([3,5])kernel_size_merged=kernel_size_1+kernel_size_2–1conv1=torch.nn.Conv2d(2,3,kernel_size_1,bias=True)conv2=torch.nn.Conv2d(3,5,kernel_size_2,bias=False)x=torch.randn([1,2,9,9])out=conv2(conv1(x))new_conv=torch.nn.Conv2d(2,5,kernel_size_merged,bias=False)padding=[kernel_size_2[0]-1,kernel_size_2[1]-1]new_conv.weight.data=torch.conv2d(conv1.weight.data.permute(1,0,2,3)、conv2.weight.data.flip(-1,-2),padding=padding).permute(1,0,2,3)new_out=new_conv(x)add_x=torch.ones(1,3,7),7)*conv1.bias.data[None,:,None,None]#Newfeaturemapnew_out+=conv2(add_x)assert(torch.abs(out-new_out)<1e-6).min()但是我们不twantevery每次我们创建这个额外的特征时,我们都想以某种方式改变卷积参数,这是我们可以做到的。我们知道,对一个常量特征图应用卷积后,会得??到另一个常量特征图。所以,我们只需要在这个特征图上进行一次卷积就足够了。kernel_size_1=np.array([3,3])kernel_size_2=np.array([3,5])kernel_size_merged=kernel_size_1+kernel_size_2–1conv1=torch.nn.Conv2d(2,3,kernel_size_1,bias=True)conv2=torch.nn.Conv2d(3,5,kernel_size_2,bias=True)x=torch.randn([1,2,9,9])out=conv2(conv1(x))new_conv=torch.nn.Conv2d(2,5,kernel_size_merged)padding=[kernel_size_2[0]-1,kernel_size_2[1]-1]new_conv.weight.data=torch.conv2d(conv1.weight.data.permute(1,0,2,3),conv2.weight.data.flip(-1,-2),padding=padding).permute(1,0,2,3)add_x=torch.ones(1,3,*kernel_size_2)*conv1.bias.data[无,:,None,None]#这个操作同时转移了第一个卷积的偏差并添加了第二个卷积的偏差。new_conv.bias.data=conv2(add_x).flatten()new_out=new_conv(x)assert(torch.abs(out-new_out)<1e-6).min()在文章的最后,我想说我们的函数并不适用于所有的卷积。在这个Python实现中,我们没有考虑padding、stepsize等参数,本文只是为了演示。