論文Pyramid Attention Network for Semantic Segmentation筆記

該論文發表於2017CVPR,由北理工+曠視科技+北大共同完成
論文傳送門
本人主要借鑑其中的思想在目標檢測中的可行性,故不考慮語義分割相關的內容
初次實現,如有不足之處,還請指出,謝謝!

名詞解釋

  • Global contextual information: 譯爲中文即爲全局語義信息,即圖像中的物體並不是孤立的,像素之間都是有聯繫的,這種聯繫就是語義信息,而全局語義信息是從圖像全局的像素之間的聯繫,以下面的圖爲例[1],“盲人摸象”的典故能夠很形象地說明全局語義信息的重要性。

在這裏插入圖片描述
圖 1.a 從這一小塊區域,無法判斷出其所在的整張圖片的類別
在這裏插入圖片描述
圖 1.b 從另一小塊區域,同樣無法判斷其所在的整張圖片的類別
在這裏插入圖片描述
圖 1.c 稍微擴大一下小塊區域的範圍,同樣無法判斷出這張圖片所屬的類別
在這裏插入圖片描述
圖 1.d 當看到了全局的信息,我們可以很肯定的給這張圖片一個類別

  • Global Average pooling: 全局平均池化,將張量[C, H, W]變爲[C, 1, 1],用來獲取全局語義信息。如下圖:[2]

在這裏插入圖片描述

圖2 全局平均池化的示意圖
  • Dilated convolution: 空洞卷積,廣泛應用於圖像分割和目標檢測任務中,可以看作是標準卷積的一種特殊形式,可以通過在卷積核中插入0或者對輸入等間隔採樣來實現,具體過程以圖爲例:

在這裏插入圖片描述

圖3 空洞卷積示意圖
空洞卷積的作用爲:
  1. 擴大感受野:在不丟失特徵分辨率的情況下擴大感受野,擴大感受野對於檢測大物體有好處。
  2. 捕獲多尺度上下文信息:通過調整擴張率(dilation rate)獲得多尺度信息。

其中不丟失特徵分辨率的情況下擴大感受野的意思是:在非空洞卷積中,要想擴大感受野,必須通過下采樣的方式進行(可以通過pooling或者標準卷積),這樣一來必定會造成空間分辨率的降低,以下圖爲例[3]:

在這裏插入圖片描述
左圖0-9爲10個像素,左側是對這10個像素進行標準卷積的過程,標準卷積核的大小爲3×33\times3,步長爲1,padding爲1,stride爲1得到綠色特徵,綠色特徵的分辨率沒有減小,此時綠色特徵對應的感受野爲3×33\times3,然後綠色特徵再次經過標準卷積3×33\times3得到黃色特徵,黃色特徵的分辨率減小了,此時黃色特徵對應的感受野爲5×55\times5;右圖對應的是空洞卷積,卷積核爲3×33\times3,步長爲1,dilated rate爲2,得到綠色特徵,綠色特徵分辨率沒有減小,此時綠色特徵對應的感受野爲5×55\times5,由此可以看到,空洞卷積的分辨率和作圖的綠色特徵分辨率相同的情況下,其感受野要比左圖的綠色特徵的感受野有所擴大,而作圖要想擁有和右圖相同大小的感受野,則需要損失分辨率(黃色特徵)

其中能夠捕獲多尺度上下文信息的意思是:通過調整擴張率可以改變感受野的大小,不同大小的感受野可以感受不同尺度信息的物體。

摘要

  這篇論文提出了Pyramid Attention Network(PAN),用於探究全局上下文信息在語義分割中的重要性,結合注意力機制和空間金字塔提取用於像素分類的特徵。主要的貢獻就是提出了兩個重要結構模塊。(1)Feature Pyramid Attention模塊:在高層語義特徵的基礎上連接空間金字塔注意力結構,增大感受野,獲取不同尺度的上下文信息,並結合全局池化特徵,從高層語義信息中學習更加有用的表示。(2)Global Attention Upsample模塊:應用於每個解碼層,將全局信息作爲指導,指導低水平特徵選擇更加有利於定位的細節信息,逐步恢復細節信息。

引言

  隨着卷積神經網絡的發展,我們可以利用卷積神經網絡的層級結構以及端到端的訓練方式,提取出豐富的層級特徵,這種方式推動了語義分割的發展進步。然而,在對高維特徵進行編碼的時候,特徵分辨率會發生損失,我們主要考慮兩個問題:

  1. 多尺度物體的存在會對分類造成困難。爲了解決這個問題,PSPNet進行不同尺度的空間金字塔池化,而空間金字塔池化的過程中會損失定位的細節信息;DeepLab使用空洞卷積,而空洞卷積會導致棋盤格效應。本研究受SENet和Parsenet的啓發,提出FPA模塊,FPA模塊通過增加感受野,捕獲不同尺度的語義信息
  2. 高水平特徵利於分類,但在重建原始分辨率方面存在缺陷。爲了解決這個問題,一些U形結構的網絡被提出,比如SegNet, Refinenet以及提拉米蘇結構提出複雜的解碼模塊,使用低水平特徵來幫助高水平特徵恢復細節信息。然而,結構複雜必然導致計算耗時。於是本研究提出更加有效的GAU模塊,該模塊利用高層語義信息作爲指導,指導低水平特徵選取細節信息,並進行融合,逐步恢復細節信息,這也是一種有效的解碼模塊。

相關研究

  目前的研究主要集中於探索能夠更好地利用上下文信息的網絡結構。本文將這些研究主要分爲三大類:

  • Encoder-decoder結構:SOTA的分割方法主要基於這種結構,然而大部分方法都是試圖直接融合相鄰層的特徵來加強低水平特徵的語義信息,卻沒有考慮不同層特徵的多樣性以及全局上下文信息。具體體現爲不應該直接融合,而應該考慮重要性;還應該考慮全局上下文信息,僅各層特徵可能會“一葉障目”。
  • Global Context Attention:受ParseNet的啓發,許多方法都使用global branch來利用全局上下文信息。本研究也同樣在FPA模塊中使用global branch以從多尺度特徵表示中對特徵進行選取。
  • Spatial Pyramid: 用於獲取多尺度上下文信息,但是計算量大。

方法

Feature Pyramid Attention模塊

  動機 受注意力機制的啓發,考慮爲高層特徵提供像素級注意力。目前的研究缺乏全局語義信息作爲指導;並且類似SENet模塊的結構在通道注意力方面並不能對多尺度信息進行選擇。鑑於此,提出FPN模塊,對高層特徵執行不同大小卷積核的卷積運算,提取不同尺度的信息;然後逐步對不同尺度的信息進行融合,這樣可以更加準確地對相鄰的上下文信息進行整合;接下來將經過1×11\times1卷積的高層特徵和融合後的注意力特徵進行相乘;最後再和全局語義信息進行相加。

在這裏插入圖片描述
代碼實現:

class ConvGnRelu(nn.Module):
	"""
	作用:
		FPA模塊中的Conv層,論文使用的是BN,我自己用的是GN
	參數:
		in_channel: 輸入通道數
		out_channel: 輸出通道數
		kernel_size
		stride
		padding
	返回:
		輸出的特徵張量
	"""

	def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, padding=0):
		super(ConvGnRelu, self).__init__()
		self.conv = nn.Sequential(
							nn.Conv2d(in_channel, out_channel,
									  kernel_size=kernel_size,
									  stride=stride,
									  padding=padding),
							nn.GroupNorm(32, out_channel),
							nn.ReLU(inplace=True)
							)
	
	def forward(self, x):
		out = self.conv(x)
		return out


class FPAModule(nn.Module):
	"""
	作用:
		構造FPA模塊
	參數:
		in_channel: 輸入通道數
		out_channel: 輸出通道數
	返回:
		FPA輸出特徵
	"""

	def __init__(self, in_channel, out_channel):
		super(FPAModule, self).__init__()
		mid_channel = int(in_channel / 4)
		self.global_branch = nn.Sequential(
								nn.AdaptiveAvgPool2d(1),
								ConvGnRelu(in_channel, out_channel,
										   1, 1, 0)
								)
		self.middle_branch = ConvGnRelu(in_channel, out_channel, 1, 1, 0)
		self.down1 = nn.Sequential(
							nn.MaxPool2d(kernel_size=2, stride=2),
							ConvGnRelu(in_channel, mid_channel,
									   7, 1, 3))
		self.down2 = nn.Sequential(
							nn.MaxPool2d(kernel_size=2, stride=2),
							ConvGnRelu(mid_channel, mid_channel,
									   5, 1, 2))
		self.down3 = nn.Sequential(
							nn.MaxPool2d(kernel_size=2, stride=2),
							ConvGnRelu(mid_channel, mid_channel,
									   3, 1, 1),
							ConvGnRelu(mid_channel, mid_channel, 3, 1, 1))
		self.conv1 = ConvGnRelu(mid_channel, mid_channel, 7, 1, 3)
		self.conv2 = ConvGnRelu(mid_channel, mid_channel, 5, 1, 2)
		self.conv3 = ConvGnRelu(mid_channel, out_channel, 1, 1, 0)
		self.relu = nn.ReLU(inplace=True)

	def forward(self, x):
		h, w = x.size(2), x.size(3)
		b1 = self.global_branch(x)
		b1 = F.interpolate(b1, size=(h, w), mode="bilinear", align_corners=True)
		middle = self.middle_branch(x)
		# branch1
		x1_1 = self.down1(x)
		x1_2 = self.conv1(x1_1)
		# branch2
		x2_1 = self.down2(x1_1)
		x2_2 = self.conv2(x2_1)
		# branch3
		x3_2 = self.down3(x2_1)
		# merge branch1 and branch2
		x3_up = F.interpolate(x3_2, size=(h//4, w//4), mode="bilinear", align_corners=True)
		x2_merge = self.relu(x2_2 + x3_up)
		x2_up = F.interpolate(x2_merge, size=(h//2, w//2), mode="bilinear", align_corners=True)
		x1_merge = self.relu(x1_2 + x2_up)
		x1_up = F.interpolate(x1_merge, size=(h, w), mode="bilinear", align_corners=True)
		x1_up = self.conv3(x1_up)
		x_middle = middle * x1_up
		out = self.relu(b1 + x_middle)
		return out

Global Attention Upsample模塊

目前的研究比如PSPNet和Deeplab都是直接在解碼的時候使用上採樣,而這種直接的方式缺乏多尺度信息的參與,這對於細節信息的恢復無益;雖然有些考慮了不同尺度的信息,但是又缺乏高層語義信息的指導。所以本研究提出了GAU模塊,兩大優點:

  • 有效利用多尺度信息
  • 使用高層語義信息對底層信息進行指導,選取更加準確的細節信息

在這裏插入圖片描述
細節信息 低水平特徵經過3×33\times3卷積改變通道數;高水平特徵通過GAP得到全局上下文信息,然後全局信息經過1×11\times1卷積,BN和ReLU,這一步也改變了通道數,然後和經過3×33\times3卷積的低水平特徵進行相乘,這樣就相當於利用全局信息對低水平特徵進行了加權的操作;最後,高水平特徵經過上採樣和加權後的低水平特徵進行融合。

代碼實現:

# GAU模塊中所使用的3x3卷積層
class ConvGn(nn.Module):
	
	def __init__(self, in_channel, out_channel):
		super(ConvGn, self).__init__()
		self.conv = nn.Sequential(
						nn.Conv2d(in_channel, out_channel,
								  kernel_size=3, stride=1,
								  padding=1),
						nn.GroupNorm(32, out_channel))
	
	def forward(self, x):
		out = self.conv(x)
		return out


# GAU模塊中高層特徵全局語義信息的1x1卷積層
class ConvGnRelu(nn.Module):
	
	def __init__(self, in_channel, out_channel):
		super(ConvGnRelu, self).__init__()
		self.conv = nn.Sequential(
						nn.Conv2d(in_channel, out_channel,
								  kernel_size=1, stride=1,
								  padding=0),
						nn.GroupNorm(32, out_channel),
						nn.ReLU(inplace=True))
	
	def forward(self, x):
		out = self.conv(x)
		return out


# GAU模塊
class GAUModule(nn.Module):
	
	def __init__(self, in_channel, out_channel):
		super(GAUModule, self).__init__()
		# 全局信息獲取
		self.layer1 = nn.Sequential(
						nn.AdaptiveAvgPool2d(1),
						ConvGnRelu(out_channel, out_channel))
		# 底層特徵3x3卷積改變通道數
		self.layer2 = ConvGn(in_channel, out_channel)
		
	def forward(self, x, y):
		low_feature = self.layer2(x)
		global_context = self.layer1(y)
		weighted_low_feature = low_feature * global_context
		high_feature = F.interpolate(y, x.shape[-2:], mode="bilinear", align_corners=True)
		return weighted_low_feature + high_feature

Pyramid Attention Network(PAN)

  最後將FPA模塊和GAU模塊進行組合,就得到最後的PAN網絡結構。具體的組合方式爲:利用FPA模塊從高層特徵中提取不同尺度的上下文信息;然後利用GAU模塊將全局信息作爲指導,指導低水平特徵進行更好的細節選擇。

在這裏插入圖片描述
  以上圖爲例,按照這種方式進行組合即可。而結合我的具體情況,我是要研究其在目標檢測中的使用,爲了能將其嵌入到Faster R-CNN網絡中替換掉FPN結構,我編寫代碼以適應自己的研究,核心代碼如下:

class PANModule(nn.Module):
	"""
	作用:
		接受來自C2,C3,C4,C5的特徵{C2:Tensor, C3:Tensor, C4:Tensor, C5:Tensor}
		輸出經過融合以後的各層特徵,例如爲{P2:Tensor, P3:Tensor, P4:Tensor, P5:Tensor}
		Note: 該模塊所輸出的特徵將進入Bottom_up模塊中(Bottom_up模塊是我研究中的模塊)
	參數:
		in_channels_list: 輸入特徵的通道數
		out_channel: PANModule模塊每層的輸出通道數,即P2,P3,P4,P5的通道數,一般來說我設置爲相同的256
	輸出:
		out: 是個OrderedDict,形如{P2: Tensor, P3: Tensor, P4: Tensor, P5: Tensor}
	"""

	def __init__(self, in_channels_list, out_channel):
		super(PANModule, self).__init__()
		# 爲了消除融合後特徵的混疊效應,我加入了conv3x3
		self.conv3x3 = nn.ModuleList()
		for i in range(len(in_channels_list)):
			self.conv3.append(
						nn.Sequential(
							nn.Conv2d(out_channel, out_channel, 3, 1, 1),
							nn.GroupNorm(32, out_channel)))
		self.FPA = FPAModule(in_channels_list[-1], out_channel)
		self.GAU_3 = GAUModule(in_channels_list[-2], out_channel)
		self.GAU_2 = GAUModule(in_channels_list[-3], out_channel)
		self.GAU_1 = GAUModule(in_channels_list[0], out_channel)

	def get_results_from_layer_blocks(self, x, idx):
		num_blocks = 0
		for m in self.conv3x3:
			num_blocks += 1
		if idx < 0:
			idx += num_blocks
		out = x
		i = 0
		for module in self.conv3x3:
			if i == idx:
				out = module(x)
			i += 1
		return out
		
	def forward(self, x):
		names = list(x.keys())
		x = list(x.values())
		result = []
		P5 = self.FPA(x[-1])
		P5 = self.get_results_from_layer_blocks(P5, -1)
		result.append(P5)
		gau3_out = self.GAU_3(x[-2], P5)
		P5_up = F.interpolate(P5, (x[-2].size(2), x[-2].size(3)),
							  mode="bilinear", align_corners=True)
		P4 = P5_up + gau3_out
		P4 = self.get_results_from_layer_blocks(P4, -2)
		result.insert(0, P4)
		P4_up = F.interpolate(P4, (x[-3].size(2), x[-3].size(3)),
							  mode="bilinear", align_corners=True)
		gau2_out = self.GAU_2(x[-3], P4_up)
		P3 = P4_up + gau2_out
		P3 = self.get_results_from_layer_blocks(P3, -3)
		result.insert(0, P3)
		P3_up = F.interpolate(P3, (x[0].size(2), x[0].size(3)),
							  mode="bilinear", align_corners=True)
		gau1_out = self.GAU_1(x[0], P3_up)
		P2 = P3_up + gau1_out
		P2 = self.get_results_from_layer_blocks(P2, 0)
		result.insert(0, P2)
		out = OrderedDict([(k, v) for k, v in zip(names, result)])
		return out

感想

  這篇總體來說個人感覺還不錯,創新性地提出了FPA和GAU模塊,但是個人感覺是從SENet的基礎上啓發而來;其中考慮到在頂層特徵中繼續使用不同尺度的卷積核繼續卷積,增大感受野,獲取不同尺度的信息,還應用全局信息去指導底層信息的選擇,結果也不錯。但個人感覺還能和PANet的bottom up結構進行結合,看看是不是會再有提升。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章