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

在鼓樓等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務理念,為客戶提供做網(wǎng)站、網(wǎng)站制作 網(wǎng)站設(shè)計制作按需制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),高端網(wǎng)站設(shè)計,成都營銷網(wǎng)站建設(shè),外貿(mào)網(wǎng)站制作,鼓樓網(wǎng)站建設(shè)費用合理。
互相關(guān)操作可以表示為沿著輸入特征映射從核上滑動,就像在gif上一樣。每次我們將一個核應用于一個輸入,我們將相應的核權(quán)重與特征相乘并將它們相加,從而在輸出通道中獲得一個新特征。
卷積幾乎是一個互相關(guān),在神經(jīng)網(wǎng)絡(luò)中,卷積從一組通道創(chuàng)建另一組通道。也就是說:上面描述的幾個核一次存儲在卷積層中,每個核生成一個通道,然后它們被連接起來。這種卷積的核的最終維度為:[每輸出通道數(shù)X每輸入通道數(shù)X核高X核寬]。
在PyTorch和許多其他機器學習框架中,卷積層的下一個部分是bias。偏差是每個輸出通道的附加項。因此,偏差是一組參數(shù),其大小等于輸出通道的數(shù)量。
我們的任務是將兩個卷積合并為一個卷積。簡而言之,卷積和偏差加法都是線性運算,線性運算的組合也是一個線性運算。
讓我們從最簡單的情況開始。一個是任意大小的卷積,第二個是1x1的卷積。他們都沒有bias。實際上,這意味著將特征映射乘以一個常數(shù)。你可以簡單地把第一次卷積的權(quán)重值乘以這個常數(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)在讓第一個卷積將任意數(shù)量的通道轉(zhuǎn)換成另一個任意數(shù)量的通道。在這種情況下,我們的1x1卷積將是中間特征映射通道的加權(quán)和。這意味著你可以對產(chǎn)生這些通道的權(quá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)在讓我們的兩個卷積將任意數(shù)量的通道轉(zhuǎn)換為另一個任意數(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)在,是時候放棄對第二個卷積大小的限制了。為簡化起見,讓我們看一下一維卷積。核2中的“k”操作會是什么樣子呢?
讓我們添加額外的卷積v,核大小為2:
改寫方程:
最后:
結(jié)果是與核3的卷積。這個卷積中的核是應用于第一個卷積的填充權(quán)重與第二個卷積的權(quán)重創(chuàng)建的核的互相關(guān)的結(jié)果。
對于其他大小的核、二維情況和多通道情況,相同的邏輯也適用。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)在讓我們添加偏差。我們將從第二個卷積中的偏差開始。偏差是一個與輸出通道數(shù)大小相同的向量,然后將其添加到卷積的輸出中。這意味著我們只需要將第二個卷積的偏差放入結(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(
在第一個卷積中添加偏差會稍微復雜一些。我們將分兩個階段進行,首先,我們注意到在卷積中使用偏差等同于創(chuàng)建一個額外的特征映射,其中每個通道的特征都是常數(shù),等于偏差參數(shù)。然后將這個特征添加到卷積的輸出中。
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)建這個額外的特征,我們想以某種方式改變卷積參數(shù),我們是可以做到。我們知道,在對一個常量特征圖應用卷積之后,將獲得另一個常量特征圖。所以,我們只需對這個特征圖進行一次卷積就足夠了。
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ù)并不適用于所有的卷積。在這個Pyhon實現(xiàn)中,我們沒有考慮填充、步長等參數(shù),本文僅僅是用來進行演示。
當前標題:PyTorch中的卷積是如何工作的
分享URL:http://m.5511xx.com/article/dpssios.html


咨詢
建站咨詢
