目錄
- 6.2. 圖像卷積
- 1)互相關運算
- 2)卷積層
- 3)圖像中目標的邊緣檢測
- 4)學習卷積核
- 5)互相關與卷積
- 6)特征映射和感受野
- 7)小結
.
6.2. 圖像卷積
卷積神經網絡的設計是用于探索圖像數據,本節我們將以圖像為例。
.
1)互相關運算
嚴格說,卷積層是個錯誤叫法,因為它所表達的運算其實是互相關運算(cross-correlation),而不是卷積運算。根據前面描述,在卷積層中,輸入張量和核張量通過互相關運算產生輸出張量。
首先,暫時忽略通道(第三維),看看如何處理二維圖像數據和隱藏表示。在下圖中,輸入3x3的二維張量,卷積核是2x2的,而卷積核窗口(或卷積窗口)的形狀由卷積核決定,即2x2。
圖6.2.1 二維互相關運算。
圖中,陰影部分是第一個元素:0 * 0 + 1 * 1 + 3 * 2 + 4 * 3 = 19
在二維互相關運算中,卷積窗口從輸入張量的左上角開始,從左到右、從上到下滑動。當卷積窗口滑動到新一個位置時,包含在該窗口中的部分張量與卷積核張量進行按元素相乘,得到的張量再求和得到一個單一的標量值,由此我們得出了這一位置的輸出張量值。在如上例子中,輸出張量的四個元素由二維互相關運算得到,這個輸出高度為2、寬度為2,如下所示:
-
0 * 0 + 1 * 1 + 3 * 2 + 4 * 3 = 19
-
1 * 0 + 2 * 1 + 4 * 2 + 5 * 3 = 25
-
3 * 0 + 4 * 1 + 6 * 2 + 7 * 3 = 37
-
4 * 0 + 5 * 1 + 7 * 2 + 8 * 3 = 43
注意,輸出大小略小于輸入大小。
卷積后輸出變小,是因為卷積核只能在圖像內部完整地滑動,邊緣部分無法處理,所以輸出尺寸會減小。所以,輸出大小等于輸入大小 nh×nwn_h \times n_wnh?×nw? 減去卷積核大小 kh×kwk_h \times k_wkh?×kw?,即 (輸入大小 - 卷積核大小 + 1):
(nh?kh+1)×(nw?kw+1).(6.2.2)(n_h - k_h + 1) \times (n_w - k_w + 1). \tag{6.2.2} (nh??kh?+1)×(nw??kw?+1).(6.2.2)
這是因為我們需要足夠的空間在圖像上“移動”卷積核。
稍后,我們將看到如何通過在圖像邊界周圍填充零來保證有足夠的空間移動卷積核,從而保持輸出大小不變。接下來,我們在 corr2d
函數中實現如上過程,該函數接受輸入張量 xxx 和卷積核張量 kkk,并返回輸出張量 yyy。
import torch
from torch import nn
from d2l import torch as d2ldef corr2d(X, K): #@save"""計算二維互相關運算"""h, w = K.shapeY = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))for i in range(Y.shape[0]):for j in range(Y.shape[1]):Y[i, j] = (X[i:i + h, j:j + w] * K).sum()return Y
驗證上述二維互相關運算的輸出:
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)# 輸出:
tensor([[19., 25.],[37., 43.]])
卷積層對輸入和卷積核權重進行互相關運算,并在添加標量偏置之后產生輸出。 所以,卷積層中的兩個被訓練的參數是卷積核權重和標量偏置。
.
2)卷積層
基于上面定義的corr2d
函數實現二維卷積層。在__init__
構造函數中,將weight
和bias
聲明為兩個模型參數。前向傳播函數調用corr2d
函數并添加偏置。
class Conv2D(nn.Module):def __init__(self, kernel_size):super().__init__()self.weight = nn.Parameter(torch.rand(kernel_size))self.bias = nn.Parameter(torch.zeros(1))def forward(self, x):return corr2d(x, self.weight) + self.bias
高度和寬度分別為 h 和 w 的卷積核可以被稱為 h × w 卷積或 h × w 卷積核。
將帶有 h × w 卷積核的卷積層稱為 h × w 卷積層。
.
3)圖像中目標的邊緣檢測
如下,是卷積層的一個簡單應用:通過找到像素變化的位置,來檢測圖像中不同顏色的邊緣。
首先,我們構造一個 6x8 像素的黑白圖像。中間四列為黑色(0),其余像素為白色(1)。
X = torch.ones((6, 8))
X[:, 2:6] = 0
X# 輸出:
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.]])
接下來,構造一個1x2 的卷積核 K
。當進行互相關運算時,如果水平相鄰的兩元素相同,則輸出為零,否則輸出為非零。
K = torch.tensor([[1.0, -1.0]])
現在,我們對參數 X
(輸入)和 K
(卷積核)執行互相關運算。如下所示,輸出 Y
中的 1 代表從白色到黑色的邊緣,-1 代表從黑色到白色的邊緣,其他情況的輸出為 0。
Y = corr2d(X, K)
Y# 輸出:
tensor([[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.]])
現在我們將輸入的二維圖像轉置,再進行如上的互相關運算。其輸出如下,之前檢測到的垂直邊緣消失了。不出所料,這個卷積核 K
只可以檢測垂直邊緣,無法檢測水平邊緣。
corr2d(X.t(), K)# 輸出:
tensor([[0., 0., 0., 0., 0.],[0., 0., 0., 0., 0.],[0., 0., 0., 0., 0.],[0., 0., 0., 0., 0.],[0., 0., 0., 0., 0.],[0., 0., 0., 0., 0.],[0., 0., 0., 0., 0.]])
.
4)學習卷積核
通過僅查看“輸入-輸出”對,來學習由X**生成Y的卷積核:**監督學習 + 梯度下降
-
初始化卷積核為隨機值
-
前向傳播:使用卷積核對輸入 X 做卷積 → 得到預測輸出 Y_hat
-
計算損失(誤差):計算預測值和真實值之間的差距(均方誤差)
-
反向傳播求梯度:自動計算損失對卷積核權重的梯度(告訴我們:“這個卷積核該往哪個方向調才能更接近正確答案?”)
-
更新卷積核:用梯度下降法更新卷積核參數,逐步減小誤差
重復迭代以上過程。卷積核會越來越優。
為了簡單起見,我們在此使用內置的二維卷積層,并忽略偏置。
# 構造一個二維卷積層,它具有1個輸出通道和形狀為(1, 2)的卷積核
conv2d = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False)# 輸入和輸出都reshape成四維格式 (批量大小, 通道, 高度, 寬度)
X = X.reshape((1, 1, 6, 8))
Y = Y.reshape((1, 1, 6, 7))
lr = 3e-2 # 學習率for i in range(10):Y_hat = conv2d(X) # 前向傳播:得到預測輸出l = (Y_hat - Y) ** 2 # 計算平方誤差(損失)conv2d.zero_grad() # 清空之前的梯度l.sum().backward() # 反向傳播,計算梯度:自動計算損失對模型中所有可訓練參數的梯度,并將這些梯度保存在 .grad 屬性中# 更新卷積核權重conv2d.weight.data[:] -= lr * conv2d.weight.gradif (i + 1) % 2 == 0:print(f'epoch {i+1}, loss {l.sum():.3f}')
輸出:
epoch 2, loss 6.422
epoch 4, loss 1.225
epoch 6, loss 0.266
epoch 8, loss 0.070
epoch 10, loss 0.022
10次迭代后,誤差降到足夠低。現在來看看所學的卷積核的權重張量。
conv2d.weight.data.reshape((1, 2))# 輸出
tensor([[ 1.0010, -0.9739]])
學習到的卷積核權重,非常接近之前定義的卷積核K。
.
5)互相關與卷積
卷積運算可通過翻轉卷積核后執行互相關運算得到。但在實際中,由于卷積核由數據學習而來,互相關與卷積對輸出結果無實質影響。因此,深度學習中常將互相關稱為“卷積”。為保持術語一致,本書也采用此慣例,并將卷積核中的參數稱為“元素”。
問題一:為什么卷積運算可通過翻轉卷積核后執行互相關運算得到?
下面對比下卷積(Convolution)和互相關(Cross-correlation)的公式,并說明它們的區別。
概念 | 數學定義 | 深度學習中的做法 |
---|---|---|
互相關 | 不翻轉,直接滑動計算 | 被稱作“卷積” |
卷積 | 先翻轉核,再滑動計算 | 實際不翻轉,直接算 |
a.互相關(Cross-correlation):
- 互相關運算,如公式(6.1.3):
[H]i,j=u+∑a=?ΔΔ∑b=?ΔΔ[V]a,b[X]i+a,j+b.(同6.1.3)[\mathbf{H}]_{i,j} = u + \sum_{a=-\Delta}^{\Delta} \sum_{b=-\Delta}^{\Delta} [\mathbf{V}]_{a,b} [\mathbf{X}]_{i+a,j+b}. \tag{同6.1.3} [H]i,j?=u+a=?Δ∑Δ?b=?Δ∑Δ?[V]a,b?[X]i+a,j+b?.(同6.1.3)
-
操作:卷積核 V\mathbf{V}V 直接在輸入 X\mathbf{X}X 上滑動,對應位置相乘求和。
-
沒有翻轉核。
-
深度學習框架(如 PyTorch、TensorFlow)中實際實現的就是這個。
b.卷積(Convolution):
- 卷積運算,如公式(6.1.6):
(f?g)(i,j)=∑a∑bf(a,b)?g(i?a,j?b)(f * g)(i, j) = \sum_a \sum_b f(a, b) \cdot g(i - a, j - b) (f?g)(i,j)=a∑?b∑?f(a,b)?g(i?a,j?b)
- 或等價地:
(V?X)i,j=∑a∑bVa,b?Xi?a,j?b(\mathbf{V} * \mathbf{X})_{i,j} = \sum_a \sum_b \mathbf{V}_{a,b} \cdot \mathbf{X}_{i-a,j-b} (V?X)i,j?=a∑?b∑?Va,b??Xi?a,j?b?
-
關鍵區別:相當于先把卷積核翻轉 180°180^\circ180°(上下 + 左右翻),再與輸入做互相關。
-
數學意義上的“卷積”要求這種“減號”形式。
問題二:為什么互相關與卷積對輸出結果無實質影響?
- 雖然數學上卷積要翻轉核,互相關不翻轉,但在深度學習中,卷積核是通過訓練數據自動學習出來的,而不是人為設定的。
關鍵點:
-
如用互相關運算:網絡會學到一個特定的 K
-
如用嚴格卷積運算:需將核翻轉后再用,所以如果想得到同樣的輸出,應該用翻轉后的核 K′=flip(K)
-
但!由于卷積核是可學習的:如果強制執行“嚴格卷積”,那么網絡會自動學習出一個“翻轉過的核”來補償這個差異。換句話說,它會學會等效于原來互相關所用的核。
最后,輸出結果是一樣的輸出。
.
6)特征映射和感受野
圖6.2.1 中輸出的卷積層有時被稱為特征映射(feature map),因為它可以被視為一個輸入映射到下一層的空間維度的轉換器。卷積神經網絡中,對于某一層的任意元素 x,其感受野(receptive field)是指在前向傳播期間可能影響 x 計算的所有元素(來自所有先前層)。
注意,感受野可能大于輸入的實際大小。用 圖6.2.1 為例來解釋感受野:給定 2 x 2 卷積核,陰影輸出元素值19的感受野是輸入陰影部分的四個元素。假設之前輸出為 Y,其大小為 2 x 2,現在我們在其后附加一個卷積層,該卷積層以 Y 為輸入,輸出單個元素 z。在這種情況下,Y 上的 z 的感受野包括 Y 的所有四個元素,而輸入的感受野包括最初所有九個輸入元素。
隨著卷積神經網絡的層數加深,每一個特征圖上的元素“看到”的輸入區域(即感受野)會變大。因此,深層的神經元可以捕捉更大范圍的上下文信息。
.
7)小結
-
二維卷積層的核心計算是二維互相關運算。最簡單的形式是,對二維輸入數據和卷積核執行互相關操作,然后添加一個偏置。
-
我們可以設計一個卷積核來檢測圖像的邊緣。
-
我們可以從數據中學習卷積核的參數。
-
學習卷積核時,無論用嚴格卷積運算或互相關運算,卷積層的輸出不會受太大影響。
-
當需要檢測輸入特征中更廣區域時,我們可以構建一個更深的卷積網絡。
.
聲明:資源可能存在第三方來源,若有侵權請聯系刪除!