新聞中心
神經(jīng)網(wǎng)絡(luò)是一組應(yīng)用于輸入對(duì)象的輸出的操作(層)。在計(jì)算機(jī)視覺中,輸入對(duì)象是一張圖片:一個(gè)大小為 [通道數(shù)X高度X寬度] 的張量,其中通道數(shù)通常為 3 (RGB)。中中間輸出稱為特征映射(feature map)。特征映射在某種意義上是相同的圖片,只是通道數(shù)是任意的,張量的每個(gè)單元稱為特征。為了能夠在一次傳遞中同時(shí)運(yùn)行多個(gè)圖像,所有這些張量都有一個(gè)額外的維度,大小等于批處理中的對(duì)象數(shù)量。

在鼓樓等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供做網(wǎng)站、網(wǎng)站制作 網(wǎng)站設(shè)計(jì)制作按需制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),高端網(wǎng)站設(shè)計(jì),成都營銷網(wǎng)站建設(shè),外貿(mào)網(wǎng)站制作,鼓樓網(wǎng)站建設(shè)費(fèi)用合理。
互相關(guān)操作可以表示為沿著輸入特征映射從核上滑動(dòng),就像在gif上一樣。每次我們將一個(gè)核應(yīng)用于一個(gè)輸入,我們將相應(yīng)的核權(quán)重與特征相乘并將它們相加,從而在輸出通道中獲得一個(gè)新特征。
卷積幾乎是一個(gè)互相關(guān),在神經(jīng)網(wǎng)絡(luò)中,卷積從一組通道創(chuàng)建另一組通道。也就是說:上面描述的幾個(gè)核一次存儲(chǔ)在卷積層中,每個(gè)核生成一個(gè)通道,然后它們被連接起來。這種卷積的核的最終維度為:[每輸出通道數(shù)X每輸入通道數(shù)X核高X核寬]。
在PyTorch和許多其他機(jī)器學(xué)習(xí)框架中,卷積層的下一個(gè)部分是bias。偏差是每個(gè)輸出通道的附加項(xiàng)。因此,偏差是一組參數(shù),其大小等于輸出通道的數(shù)量。
我們的任務(wù)是將兩個(gè)卷積合并為一個(gè)卷積。簡而言之,卷積和偏差加法都是線性運(yùn)算,線性運(yùn)算的組合也是一個(gè)線性運(yùn)算。
讓我們從最簡單的情況開始。一個(gè)是任意大小的卷積,第二個(gè)是1x1的卷積。他們都沒有bias。實(shí)際上,這意味著將特征映射乘以一個(gè)常數(shù)。你可以簡單地把第一次卷積的權(quán)重值乘以這個(gè)常數(shù)。Python代碼如下:
import torch
import numpy as np
conv1 = torch.nn.Conv2d(1, 1, (3, 3), bias=False) # [input channels X output channels X kernel shape]
conv2 = torch.nn.Conv2d(1, 1, (1, 1), bias=False) # 1х1 convolution. There will be only one weight in it's weights.
new_conv = torch.nn.Conv2d(1, 1, 3, bias=False) # This convolution will merge two
new_conv.weight.data = conv1.weight.data * conv2.weight.data
# Let's check
x = torch.randn([1, 1, 6, 6]) # [batch size X input channels X vertical size X horisontal size]
out = conv2(conv1(x))
new_out = new_conv(x)
assert (torch.abs(out?-?new_out) < 1e-6).min()
現(xiàn)在讓第一個(gè)卷積將任意數(shù)量的通道轉(zhuǎn)換成另一個(gè)任意數(shù)量的通道。在這種情況下,我們的1x1卷積將是中間特征映射通道的加權(quán)和。這意味著你可以對(duì)產(chǎn)生這些通道的權(quán)重進(jìn)行加權(quán)和。
conv1 = torch.nn.Conv2d(2, 3, (3, 3), bias=False) # [input channels X output channels X kernel shape]
conv2 = torch.nn.Conv2d(3, 1, (1, 1), bias=False) # 1х1 convolution. There will be only one weight in it's weights.
new_conv = torch.nn.Conv2d(2, 1, 3, bias=False) # As a result we want to get 1 channel
# The convolution weights: [output channels X input channels X kernel shape 1 X kernel shape 2]
# In order to multiply by the first dimension the weights responsible for creating each intermediate channel with their weight
# we will have to permute the dimensions of the second second convolution.
# Then we sum up the weighted weights and finish the measurement in order to successfully replace the 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()
現(xiàn)在讓我們的兩個(gè)卷積將任意數(shù)量的通道轉(zhuǎn)換為另一個(gè)任意數(shù)量的通道。在本例中,我們的1x1卷積將是一組中間特征映射通道的加權(quán)和。這里的邏輯是一樣的。需要將生成中間特征的權(quán)重與第二次卷積得到的權(quán)重相加。
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) # Curios face:
# It doesn't matter what sizes to pass during initialization.
# Replacing the weights will fix everything.
# The magic of the broadcast in action. It was possible to do this in the previous example, but something should change.
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()
現(xiàn)在,是時(shí)候放棄對(duì)第二個(gè)卷積大小的限制了。為簡化起見,讓我們看一下一維卷積。核2中的“k”操作會(huì)是什么樣子呢?
讓我們添加額外的卷積v,核大小為2:
改寫方程:
最后:
結(jié)果是與核3的卷積。這個(gè)卷積中的核是應(yīng)用于第一個(gè)卷積的填充權(quán)重與第二個(gè)卷積的權(quán)重創(chuàng)建的核的互相關(guān)的結(jié)果。
對(duì)于其他大小的核、二維情況和多通道情況,相同的邏輯也適用。Python示例代碼如下:
kernel_size_1 = np.array([3, 3])
kernel_size_2 = np.array([3, 5])
kernel_size_merged = kernel_size_1 + kernel_size_2–1
conv1 = 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)
# Calculation how many zeros we need to pad after first convolution
# Padding meand how many zeros we need to add by verical and horizontal
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), # We allready saw this.
conv2.weight.data.flip(-1, -2), # This is done to make a cross-correlation from convolution.
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()
現(xiàn)在讓我們添加偏差。我們將從第二個(gè)卷積中的偏差開始。偏差是一個(gè)與輸出通道數(shù)大小相同的向量,然后將其添加到卷積的輸出中。這意味著我們只需要將第二個(gè)卷積的偏差放入結(jié)果中。
kernel_size_1 = np.array([3, 3])
kernel_size_2 = np.array([3, 5])
kernel_size_merged = kernel_size_1 + kernel_size_2–1
conv1 = 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 # here is the new part
new_out = new_conv(x)
assert (torch.abs(out?-?new_out) < 1e-6).min(
在第一個(gè)卷積中添加偏差會(huì)稍微復(fù)雜一些。我們將分兩個(gè)階段進(jìn)行,首先,我們注意到在卷積中使用偏差等同于創(chuàng)建一個(gè)額外的特征映射,其中每個(gè)通道的特征都是常數(shù),等于偏差參數(shù)。然后將這個(gè)特征添加到卷積的輸出中。
kernel_size_1 = np.array([3, 3])
kernel_size_2 = np.array([3, 5])
kernel_size_merged = kernel_size_1 + kernel_size_2–1
conv1 = 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] # New featuremap
new_out += conv2(add_x)
assert (torch.abs(out?-?new_out) < 1e-6).min()
但是我們不想每次都創(chuàng)建這個(gè)額外的特征,我們想以某種方式改變卷積參數(shù),我們是可以做到。我們知道,在對(duì)一個(gè)常量特征圖應(yīng)用卷積之后,將獲得另一個(gè)常量特征圖。所以,我們只需對(duì)這個(gè)特征圖進(jìn)行一次卷積就足夠了。
kernel_size_1 = np.array([3, 3])
kernel_size_2 = np.array([3, 5])
kernel_size_merged = kernel_size_1 + kernel_size_2–1
conv1 = 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, None]
# This operation simultaneously transfers the bias from the first convolution and adds the bias from the second.
new_conv.bias.data = conv2(add_x).flatten()
new_out = new_conv(x)
assert (torch.abs(out?-?new_out) < 1e-6).min()
在文章的最后,我想說我們的函數(shù)并不適用于所有的卷積。在這個(gè)Pyhon實(shí)現(xiàn)中,我們沒有考慮填充、步長等參數(shù),本文僅僅是用來進(jìn)行演示。
標(biāo)題名稱:PyTorch中的卷積是如何工作的
標(biāo)題網(wǎng)址:http://www.dlmjj.cn/article/dpssios.html


咨詢
建站咨詢
