GGCNN2 論文導讀:用空洞卷積擴大感受野的即時抓取網路
Morrison, Corke & Leitner · 2020原文連結
論文資訊卡
- 作者:Douglas Morrison、Peter Corke、Jürgen Leitner
- 關係:GG-CNN 的架構改良版,輸入輸出定義完全相同,差異在中間的特徵提取架構
- 原始碼:dougsm/ggcnn(同一個倉庫同時包含 GG-CNN 與 GGCNN2 的模型定義)
問題背景
GG-CNN 證明了用一個 62K 參數的輕量全卷積網路做像素級抓取地圖生成,可以達到 50Hz 的即時閉迴路抓取。但這個架構有兩個具體的、可以歸因到設計細節的問題:其一,三層卷積、完全不含 Pooling 的 encoder,能「看到」的空間範圍天花板較低——夾爪需要橫跨的物體寬度,往往超過小卷積核單獨能覆蓋的範圍;其二,decoder 用 Transposed Convolution 做上採樣,這類反卷積在 kernel 大小與 stride 不整除時,容易在輸出上形成規律的棋盤格偽影(checkerboard artifacts),干擾抓取品質圖與角度圖的精確定位。
GGCNN2 不是重新設計一個模型,而是針對這兩個具體問題做架構改良,目標是在幾乎不犧牲 GG-CNN 引以為傲的即時推論速度的前提下,提升抓取精度。
方法核心:三個架構改動
GGCNN2 的輸入輸出與訓練方式跟 GG-CNN 完全一樣——同樣是從深度影像生成 Quality、cos(2Φ)、sin(2Φ)、Width 四張像素級輸出圖。改動全部集中在中間的特徵提取架構,可以歸納成三點:
1. 加入 MaxPooling
GG-CNN 的三層卷積全部靠 stride 縮小特徵圖尺寸,沒有 Pooling 層。GGCNN2 用兩次 MaxPooling(每次 stride=2)把特徵圖尺寸各縮小一半:
nn.Conv2d(input_channels, filter_sizes[0], kernel_size=11, stride=1, padding=5, bias=True)
nn.ReLU(inplace=True)
nn.Conv2d(filter_sizes[0], filter_sizes[0], kernel_size=5, stride=1, padding=2, bias=True)
nn.ReLU(inplace=True)
nn.MaxPool2d(kernel_size=2, stride=2)
nn.Conv2d(filter_sizes[0], filter_sizes[1], kernel_size=5, stride=1, padding=2, bias=True)
nn.ReLU(inplace=True)
nn.Conv2d(filter_sizes[1], filter_sizes[1], kernel_size=5, stride=1, padding=2, bias=True)
nn.ReLU(inplace=True)
nn.MaxPool2d(kernel_size=2, stride=2)
這帶來三個好處:感受野隨著空間解析度下降而自然放大、特徵更抽象、後續層的運算量因為特徵圖變小而降低——這是 GGCNN2 能在層數比 GG-CNN 多的情況下,仍維持接近的推論速度的原因之一。
2. 加入 Dilated Convolution(空洞卷積)
這是全篇最核心的改動。在兩次 MaxPooling 之後,GGCNN2 接了兩層 dilation rate 分別為 2 與 4 的卷積:
nn.Conv2d(filter_sizes[1], filter_sizes[2], kernel_size=l3_k_size,
dilation=dilations[0], stride=1,
padding=(l3_k_size // 2 * dilations[0]), bias=True)
nn.ReLU(inplace=True)
nn.Conv2d(filter_sizes[2], filter_sizes[2], kernel_size=l3_k_size,
dilation=dilations[1], stride=1,
padding=(l3_k_size // 2 * dilations[1]), bias=True)
nn.ReLU(inplace=True)
一般 3×3 卷積核涵蓋的是連續九個像素:
X X X
X X X
X X X
dilation=2 的卷積核,權重數量還是九個,但涵蓋的實際輸入範圍變成間隔取樣的更大區域:
X . X . X
. . . . .
X . X . X
. . . . .
X . X . X
也就是說,不增加任何參數與計算量,卻能讓每個輸出像素「看到」更大範圍的輸入。這正好對應前面提到的問題:模型如果看不到物體的完整邊界,估計出來的抓取寬度與角度就容易失準。疊加兩層不同 dilation rate 的卷積,讓有效感受野在只多兩層的代價下大幅擴張。
3. 用 Bilinear Upsampling 取代 Transposed Convolution
decoder 段不再用 ConvTranspose2d,改成先用 UpsamplingBilinear2d 做純插值放大(不引入權重),再用一般 Conv2d 做特徵融合:
nn.UpsamplingBilinear2d(scale_factor=2)
nn.Conv2d(filter_sizes[2], filter_sizes[3], 3, padding=1)
nn.ReLU(inplace=True)
nn.UpsamplingBilinear2d(scale_factor=2)
nn.Conv2d(filter_sizes[3], filter_sizes[3], 3, padding=1)
nn.ReLU(inplace=True)
插值本身平滑、不會產生棋盤格效應,卷積層再負責在放大後的特徵圖上重新學習細節。這是圖像分割、影像生成任務裡常見的替代方案。
完整架構對照
| 比較項目 | GG-CNN | GGCNN2 |
|---|---|---|
| Encoder | 3 層 Conv,無 Pooling | 4 層 Conv + 2 次 MaxPool |
| 感受野(Context) | 較小 | 較大(Dilated Conv, d=2、4) |
| Decoder | ConvTranspose | Bilinear Upsampling + Conv |
| 棋盤格偽影 | 容易出現 | 明顯減少 |
| Output Head | kernel_size=2 Conv | kernel_size=1 Conv(純 channel 映射) |
| Filter 配置 | [32, 16, 8] 系列 | [16, 16, 32, 16] |
| 推論速度 | 即時等級 | 與 GG-CNN 相近,仍為即時等級 |
輸出頭部分,GG-CNN 用 kernel_size=2 的卷積做最終映射,GGCNN2 簡化成 kernel_size=1,純粹做 channel 之間的線性組合、不再混合空間資訊——因為空間層面的特徵提取在前面的 Dilated Conv 已經做得比較充分。
完整原始碼
import torch
import torch.nn as nn
import torch.nn.functional as F
class GGCNN2(nn.Module):
def __init__(self, input_channels=1, filter_sizes=None, l3_k_size=5, dilations=None):
super().__init__()
if filter_sizes is None:
filter_sizes = [16, # First set of convs
16, # Second set of convs
32, # Dilated convs
16] # Transpose Convs
if dilations is None:
dilations = [2, 4]
self.features = nn.Sequential(
# 4 conv layers.
nn.Conv2d(input_channels, filter_sizes[0], kernel_size=11, stride=1, padding=5, bias=True),
nn.ReLU(inplace=True),
nn.Conv2d(filter_sizes[0], filter_sizes[0], kernel_size=5, stride=1, padding=2, bias=True),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(filter_sizes[0], filter_sizes[1], kernel_size=5, stride=1, padding=2, bias=True),
nn.ReLU(inplace=True),
nn.Conv2d(filter_sizes[1], filter_sizes[1], kernel_size=5, stride=1, padding=2, bias=True),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2, stride=2),
# Dilated convolutions.
nn.Conv2d(filter_sizes[1], filter_sizes[2], kernel_size=l3_k_size, dilation=dilations[0], stride=1,
padding=(l3_k_size // 2 * dilations[0]), bias=True),
nn.ReLU(inplace=True),
nn.Conv2d(filter_sizes[2], filter_sizes[2], kernel_size=l3_k_size, dilation=dilations[1], stride=1,
padding=(l3_k_size // 2 * dilations[1]), bias=True),
nn.ReLU(inplace=True),
# Output layers
nn.UpsamplingBilinear2d(scale_factor=2),
nn.Conv2d(filter_sizes[2], filter_sizes[3], 3, padding=1),
nn.ReLU(inplace=True),
nn.UpsamplingBilinear2d(scale_factor=2),
nn.Conv2d(filter_sizes[3], filter_sizes[3], 3, padding=1),
nn.ReLU(inplace=True),
)
self.pos_output = nn.Conv2d(filter_sizes[3], 1, kernel_size=1)
self.cos_output = nn.Conv2d(filter_sizes[3], 1, kernel_size=1)
self.sin_output = nn.Conv2d(filter_sizes[3], 1, kernel_size=1)
self.width_output = nn.Conv2d(filter_sizes[3], 1, kernel_size=1)
for m in self.modules():
if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
nn.init.xavier_uniform_(m.weight, gain=1)
def forward(self, x):
x = self.features(x)
pos_output = self.pos_output(x)
cos_output = self.cos_output(x)
sin_output = self.sin_output(x)
width_output = self.width_output(x)
return pos_output, cos_output, sin_output, width_output
def compute_loss(self, xc, yc):
y_pos, y_cos, y_sin, y_width = yc
pos_pred, cos_pred, sin_pred, width_pred = self(xc)
p_loss = F.mse_loss(pos_pred, y_pos)
cos_loss = F.mse_loss(cos_pred, y_cos)
sin_loss = F.mse_loss(sin_pred, y_sin)
width_loss = F.mse_loss(width_pred, y_width)
return {
'loss': p_loss + cos_loss + sin_loss + width_loss,
'losses': {
'p_loss': p_loss,
'cos_loss': cos_loss,
'sin_loss': sin_loss,
'width_loss': width_loss
},
'pred': {
'pos': pos_pred,
'cos': cos_pred,
'sin': sin_pred,
'width': width_pred
}
}
作為對照,GG-CNN 原始的 encoder-decoder 定義(ConvTranspose2d 解碼、無 Pooling、無 Dilated Conv)可參考 GG-CNN 論文導讀 一文中的架構表。
影響與定位
GGCNN2 的三個改動——MaxPooling、Dilated Convolution、Bilinear Upsampling——沒有一個是深度學習裡的新技術,論文的價值在於證明這幾個運算成本低的元件組合起來,就能在不引入 ResNet、U-Net、Transformer 這類重量級骨幹的前提下,同時解決 GG-CNN 感受野不足與反卷積偽影兩個具體問題,維持住即時抓取偵測需要的低延遲。這也是理解「輕量模型架構改良」的一個很好的案例:不是靠堆更多參數換精度,而是靠更適合任務特性的算子組合。
相關論文推薦
- GG-CNN 論文導讀:用像素級生成取代候選抓取評分——GGCNN2 改良的原始架構,建議先讀這篇再看本文