摘要:以高分辨率子網(wǎng)開(kāi)始作為第一階段,逐個(gè)添加高到低分辨率子網(wǎng)以形成更多階段,并且并行連接多分辨率子網(wǎng)。優(yōu)點(diǎn)并行連接高低分辨率子網(wǎng),而不是像大多數(shù)現(xiàn)有解決方案那樣串聯(lián)連接。我們認(rèn)為原因是從低分辨率子網(wǎng)上的早期階段提取的低級(jí)功能不太有用。
摘要:
大多數(shù)現(xiàn)有方法從由高到低分辨率網(wǎng)絡(luò)產(chǎn)生的低分辨率表示中恢復(fù)高分辨率表示。相反,本文在整個(gè)過(guò)程中保持高分辨率的表示。我們將高分辨率子網(wǎng)開(kāi)始作為第一階段,逐步添加高到低分辨率子網(wǎng)以形成更多階段,并行連接多個(gè)子網(wǎng),每個(gè)子網(wǎng)具有不同的分辨率。我們進(jìn)行重復(fù)的多尺度融合,使得高到低分辨率表示可以重復(fù)從其他分辨率的表示獲取信息,從而導(dǎo)致豐富的高分辨率表示。因此,預(yù)測(cè)的關(guān)鍵點(diǎn)熱圖可能更準(zhǔn)確,空間更精確。1. 簡(jiǎn)介 1.1 現(xiàn)有方法
1.2 HRNet(a) 對(duì)稱(chēng)結(jié)構(gòu),先下采樣,再上采樣,同時(shí)使用跳層連接恢復(fù)下采樣丟失的信息;
(b) 級(jí)聯(lián)金字塔;
(c) 先下采樣,轉(zhuǎn)置卷積上采樣,不使用跳層連接進(jìn)行數(shù)據(jù)融合;
(d) 擴(kuò)張卷積,減少下采樣次數(shù),不使用跳層連接進(jìn)行數(shù)據(jù)融合;
2. 方法描述 2.1 并行高分辨率子網(wǎng) 2.2 重復(fù)多尺度融合 3. 實(shí)驗(yàn)部分 3.1 消融研究 3.1.1 重復(fù)多尺度融合簡(jiǎn)要描述:
HighResolution Net(HRNet),它能夠在整個(gè)過(guò)程中保持高分辨率表示。以高分辨率子網(wǎng)開(kāi)始作為第一階段,逐個(gè)添加高到低分辨率子網(wǎng)以形成更多階段,并且并行連接多分辨率子網(wǎng)。在整個(gè)過(guò)程中反復(fù)交換并行多分辨率子網(wǎng)絡(luò)中的信息來(lái)進(jìn)行重復(fù)的多尺度融合。優(yōu)點(diǎn):
(a)并行連接高低分辨率子網(wǎng),而不是像大多數(shù)現(xiàn)有解決方案那樣串聯(lián)連接。因此,我們的方法能夠保持高分辨率而不是通過(guò)從低到高的過(guò)程恢復(fù)分辨率,因此預(yù)測(cè)的熱圖可能在空間上更精確
(b)大多數(shù)現(xiàn)有的融合方案匯總了低級(jí)別和高級(jí)別的表示。相反,我們?cè)谙嗤疃群拖嗨扑降牡头直媛时硎镜膸椭聢?zhí)行重復(fù)的多尺度融合以提升高分辨率表示,反之亦然,導(dǎo)致高分辨率表示對(duì)于姿勢(shì)估計(jì)也是豐富的。因此,我們預(yù)測(cè)的熱圖可能更準(zhǔn)確。個(gè)人感覺(jué)增加多尺度信息之間的融合是正確的,例如原圖像和模糊圖像進(jìn)行聯(lián)合雙邊濾波可以得到介于兩者之間的模糊程度的圖像,而RGF濾波就是重復(fù)將聯(lián)合雙邊濾波的結(jié)果作為那張模糊的引導(dǎo)圖,這樣得到的結(jié)果會(huì)越來(lái)越趨近于原圖。此處同樣的道理,不同分辨率的圖像采樣到相同的尺度反復(fù)的融合,加之網(wǎng)絡(luò)的學(xué)習(xí)能力,會(huì)使得多次融合后的結(jié)果更加趨近于正確的表示。
3.1.2 分辨率保持(a) W / o中間交換單元(1個(gè)融合):除最后一個(gè)交換單元外,多分辨率子網(wǎng)之間沒(méi)有交換;
(b) 僅W /跨階段交換單元(3個(gè)融合):每個(gè)階段內(nèi)并行子網(wǎng)之間沒(méi)有交換;
(c) W /跨階段和階段內(nèi)交換單元(共8個(gè)融合):這是我們提出的方法;
所有四個(gè)高到低分辨率子網(wǎng)都在開(kāi)頭添加,深度相同,融合方案與我們的相同。該變體實(shí)現(xiàn)了72.5的AP,低于我們的小型網(wǎng)HRNet-W32的73.4 AP。我們認(rèn)為原因是從低分辨率子網(wǎng)上的早期階段提取的低級(jí)功能不太有用。此外,沒(méi)有低分辨率并行子網(wǎng)的類(lèi)似參數(shù)和計(jì)算復(fù)雜度的簡(jiǎn)單高分辨率網(wǎng)絡(luò)表現(xiàn)出低得多的性能。3.1.3 分辨率表示質(zhì)量
檢查從每個(gè)分辨率的特征圖估計(jì)的熱圖的質(zhì)量。4. 代碼學(xué)習(xí)(源碼地址) 4.1 ResNet模塊
雖然很熟悉了,但是還是介紹一下resnet網(wǎng)絡(luò)的基本模塊。如下的左圖對(duì)應(yīng)于resnet-18/34使用的基本塊,右圖是50/101/152所使用的,由于他們都比較深,所以有圖相比于左圖使用了1x1卷積來(lái)降維。
(a) conv3x3: 沒(méi)啥好解釋的,將原有的pytorch函數(shù)固定卷積和尺寸為3重新封裝了一次;
(b) BasicBlock: 搭建上圖左邊的模塊。
(1) 每個(gè)卷積塊后面連接BN層進(jìn)行歸一化;(2) 殘差連接前的3x3卷積之后只接入BN,不使用ReLU,避免加和之后的特征皆為正,保持特征的多樣;
(3) 跳層連接:兩種情況,當(dāng)模塊輸入和殘差支路(3x3->3x3)的通道數(shù)一致時(shí),直接相加;當(dāng)兩者通道不一致時(shí)(一般發(fā)生在分辨率降低之后,同分辨率一般通道數(shù)一致),需要對(duì)模塊輸入特征使用1x1卷積進(jìn)行升/降維(步長(zhǎng)為2,上面說(shuō)了分辨率會(huì)降低),之后同樣接BN,不用ReLU。
(c) Bottleneck: 搭建上圖右邊的模塊。
(1) 使用1x1卷積先降維,再使用3x3卷積進(jìn)行特征提取,最后再使用1x1卷積把維度升回去;(2) 每個(gè)卷積塊后面連接BN層進(jìn)行歸一化;
(2) 殘差連接前的1x1卷積之后只接入BN,不使用ReLU,避免加和之后的特征皆為正,保持特征的多樣性。
(3) 跳層連接:兩種情況,當(dāng)模塊輸入和殘差支路(1x1->3x3->1x1)的通道數(shù)一致時(shí),直接相加;當(dāng)兩者通道不一致時(shí)(一般發(fā)生在分辨率降低之后,同分辨率一般通道數(shù)一致),需要對(duì)模塊輸入特征使用1x1卷積進(jìn)行升/降維(步長(zhǎng)為2,上面說(shuō)了分辨率會(huì)降低),之后同樣接BN,不用ReLU。
def conv3x3(in_planes, out_planes, stride=1): """3x3 convolution with padding""" return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False) class BasicBlock(nn.Module): expansion = 1 def __init__(self, inplanes, planes, stride=1, downsample=None): super(BasicBlock, self).__init__() self.conv1 = conv3x3(inplanes, planes, stride) self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) self.relu = nn.ReLU(inplace=True) self.conv2 = conv3x3(planes, planes) self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) self.downsample = downsample self.stride = stride def forward(self, x): residual = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) if self.downsample is not None: residual = self.downsample(x) out += residual out = self.relu(out) return out class Bottleneck(nn.Module): expansion = 4 def __init__(self, inplanes, planes, stride=1, downsample=None): super(Bottleneck, self).__init__() self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes, momentum=BN_MOMENTUM) self.conv3 = nn.Conv2d(planes, planes * self.expansion, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(planes * self.expansion, momentum=BN_MOMENTUM) self.relu = nn.ReLU(inplace=True) self.downsample = downsample self.stride = stride def forward(self, x): residual = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out = self.relu(out) out = self.conv3(out) out = self.bn3(out) if self.downsample is not None: residual = self.downsample(x) out += residual out = self.relu(out) return out4.2 HighResolutionModule (高分辨率模塊)
當(dāng)僅包含一個(gè)分支時(shí),生成該分支,沒(méi)有融合模塊,直接返回;當(dāng)包含不僅一個(gè)分支時(shí),先將對(duì)應(yīng)分支的輸入特征輸入到對(duì)應(yīng)分支,得到對(duì)應(yīng)分支的輸出特征;緊接著執(zhí)行融合模塊。
(a) _check_branches: 判斷num_branches (int) 和 num_blocks, num_inchannels, num_channels (list) 三者的長(zhǎng)度是否一致,否則報(bào)錯(cuò);
(b) _make_one_branch: 搭建一個(gè)分支,單個(gè)分支內(nèi)部分辨率相等,一個(gè)分支由num_blocks[branch_index]個(gè)block組成,block可以是兩種ResNet模塊中的一種;
(1) 首先判斷是否降維或者輸入輸出的通道(num_inchannels[branch_index]和 num_channels[branch_index] * block.expansion(通道擴(kuò)張率))是否一致,不一致使用1z1卷積進(jìn)行維度升/降,后接BN,不使用ReLU;
(2) 順序搭建num_blocks[branch_index]個(gè)block,第一個(gè)block需要考慮是否降維的情況,所以多帶帶拿出來(lái),后面1 到 num_blocks[branch_index]個(gè)block完全一致,使用循環(huán)搭建就行。此時(shí)注意在執(zhí)行完第一個(gè)block后將num_inchannels[branch_index重新賦值為 num_channels[branch_index] * block.expansion。
(c) _make_branches: 循環(huán)調(diào)用_make_one_branch函數(shù)創(chuàng)建多個(gè)分支;
(d) _make_fuse_layers:
(1) 如果分支數(shù)等于1,返回None,說(shuō)明此事不需要使用融合模塊;
(2) 雙層循環(huán):for i in range(num_branches if self.multi_scale_output else 1):的作用是,如果需要產(chǎn)生多分辨率的結(jié)果,就雙層循環(huán)num_branches 次,如果只需要產(chǎn)生最高分辨率的表示,就將i確定為0。
(2.1) 如果j > i,此時(shí)的目標(biāo)是將所有分支上采樣到和i分支相同的分辨率并融合,也就是說(shuō)j所代表的分支分辨率比i分支低,2**(j-i)表示j分支上采樣這么多倍才能和i分支分辨率相同。先使用1x1卷積將j分支的通道數(shù)變得和i分支一致,進(jìn)而跟著B(niǎo)N,然后依據(jù)上采樣因子將j分支分辨率上采樣到和i分支分辨率相同,此處使用最近鄰插值;
(2.2) 如果j = i,也就是說(shuō)自身與自身之間不需要融合,nothing to do;
(2.3) 如果j < i,轉(zhuǎn)換角色,此時(shí)最終目標(biāo)是將所有分支采樣到和i分支相同的分辨率并融合,注意,此時(shí)j所代表的分支分辨率比i分支高,正好和(2.1)相反。此時(shí)再次內(nèi)嵌了一個(gè)循環(huán),這層循環(huán)的作用是當(dāng)i-j > 1時(shí),也就是說(shuō)兩個(gè)分支的分辨率差了不止二倍,此時(shí)還是兩倍兩倍往上采樣,例如i-j = 2時(shí),j分支的分辨率比i分支大4倍,就需要上采樣兩次,循環(huán)次數(shù)就是2;
(2.3.1) 當(dāng)k == i - j - 1時(shí),舉個(gè)例子,i = 2,j = 1, 此時(shí)僅循環(huán)一次,并采用當(dāng)前模塊,此時(shí)直接將j分支使用3x3的步長(zhǎng)為2的卷積下采樣(不使用bias),后接BN,不使用ReLU;
(2.3.2) 當(dāng)k != i - j - 1時(shí),舉個(gè)例子,i = 3,j = 1, 此時(shí)循環(huán)兩次,先采用當(dāng)前模塊,將j分支使用3x3的步長(zhǎng)為2的卷積下采樣(不使用bias)兩倍,后接BN和ReLU,緊跟著再使用(2.3.1)中的模塊,這是為了保證最后一次二倍下采樣的卷積操作不使用ReLU,猜測(cè)也是為了保證融合后特征的多樣性;
(e) forward: 前向傳播函數(shù),利用以上函數(shù)的功能搭建一個(gè)HighResolutionModule;
(1) 當(dāng)僅包含一個(gè)分支時(shí),生成該分支,沒(méi)有融合模塊,直接返回;
(2) 當(dāng)包含不僅一個(gè)分支時(shí),先將對(duì)應(yīng)分支的輸入特征輸入到對(duì)應(yīng)分支,得到對(duì)應(yīng)分支的輸出特征;緊接著執(zhí)行融合模塊;
(2.1) 循環(huán)將對(duì)應(yīng)分支的輸入特征輸入到對(duì)應(yīng)分支模型中,得到對(duì)應(yīng)分支的輸出特征;
(2.2) 融合模塊:對(duì)著這張圖看,很容易看懂。每次多尺度之間的加法運(yùn)算都是從最上面的尺度開(kāi)始往下加,所以y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]);加到他自己的時(shí)候,不需要經(jīng)過(guò)融合函數(shù)的處理,直接加,所以if i == j: y = y + x[j];遇到不是最上面的尺度那個(gè)特征圖或者它本身相同分辨率的那個(gè)特征圖時(shí),需要經(jīng)過(guò)融合函數(shù)處理再加,所以y = y + self.fuse_layers[i][j](x[j])。最后將ReLU激活后的融合(加法)特征append到x_fuse,x_fuse的長(zhǎng)度等于1(單尺度輸出)或者num_branches(多尺度輸出)。
class HighResolutionModule(nn.Module): def __init__(self, num_branches, blocks, num_blocks, num_inchannels, num_channels, fuse_method, multi_scale_output=True): super(HighResolutionModule, self).__init__() self._check_branches( num_branches, blocks, num_blocks, num_inchannels, num_channels) self.num_inchannels = num_inchannels self.fuse_method = fuse_method self.num_branches = num_branches self.multi_scale_output = multi_scale_output self.branches = self._make_branches( num_branches, blocks, num_blocks, num_channels) self.fuse_layers = self._make_fuse_layers() self.relu = nn.ReLU(True) def _check_branches(self, num_branches, blocks, num_blocks, num_inchannels, num_channels): if num_branches != len(num_blocks): error_msg = "NUM_BRANCHES({}) <> NUM_BLOCKS({})".format( num_branches, len(num_blocks)) logger.error(error_msg) raise ValueError(error_msg) if num_branches != len(num_channels): error_msg = "NUM_BRANCHES({}) <> NUM_CHANNELS({})".format( num_branches, len(num_channels)) logger.error(error_msg) raise ValueError(error_msg) if num_branches != len(num_inchannels): error_msg = "NUM_BRANCHES({}) <> NUM_INCHANNELS({})".format( num_branches, len(num_inchannels)) logger.error(error_msg) raise ValueError(error_msg) def _make_one_branch(self, branch_index, block, num_blocks, num_channels, stride=1): # ---------------------------(1) begin---------------------------- # downsample = None if stride != 1 or self.num_inchannels[branch_index] != num_channels[branch_index] * block.expansion: downsample = nn.Sequential( nn.Conv2d( self.num_inchannels[branch_index], num_channels[branch_index] * block.expansion, kernel_size=1, stride=stride, bias=False ), nn.BatchNorm2d( num_channels[branch_index] * block.expansion, momentum=BN_MOMENTUM ), ) # ---------------------------(1) end---------------------------- # # ---------------------------(2) begin---------------------------- # layers = [] layers.append( block( self.num_inchannels[branch_index], num_channels[branch_index], stride, downsample ) ) # ---------------------------(2) middle---------------------------- # self.num_inchannels[branch_index] = num_channels[branch_index] * block.expansion for i in range(1, num_blocks[branch_index]): layers.append( block( self.num_inchannels[branch_index], num_channels[branch_index] ) ) # ---------------------------(2) end---------------------------- # return nn.Sequential(*layers) def _make_branches(self, num_branches, block, num_blocks, num_channels): branches = [] for i in range(num_branches): branches.append( self._make_one_branch(i, block, num_blocks, num_channels) ) return nn.ModuleList(branches) def _make_fuse_layers(self): # ---------------------------(1) begin---------------------------- # if self.num_branches == 1: return None # ---------------------------(1) end---------------------------- # num_branches = self.num_branches num_inchannels = self.num_inchannels # ---------------------------(2) begin---------------------------- # fuse_layers = [] for i in range(num_branches if self.multi_scale_output else 1): fuse_layer = [] for j in range(num_branches): # ---------------------------(2.1) begin---------------------------- # if j > i: fuse_layer.append( nn.Sequential( nn.Conv2d( num_inchannels[j], num_inchannels[i], 1, 1, 0, bias=False ), nn.BatchNorm2d(num_inchannels[i]), nn.Upsample(scale_factor=2**(j-i), mode="nearest") ) ) # ---------------------------(2.1) end---------------------------- # # ---------------------------(2.2) begin---------------------------- # elif j == i: fuse_layer.append(None) # ---------------------------(2.2) end---------------------------- # # ---------------------------(2.3) begin---------------------------- # else: conv3x3s = [] for k in range(i-j): # ---------------------------(2.3.1) begin---------------------------- # if k == i - j - 1: num_outchannels_conv3x3 = num_inchannels[i] conv3x3s.append( nn.Sequential( nn.Conv2d( num_inchannels[j], num_outchannels_conv3x3, 3, 2, 1, bias=False ), nn.BatchNorm2d(num_outchannels_conv3x3) ) ) # ---------------------------(2.3.1) end---------------------------- # # ---------------------------(2.3.1) begin---------------------------- # else: num_outchannels_conv3x3 = num_inchannels[j] conv3x3s.append( nn.Sequential( nn.Conv2d( num_inchannels[j], num_outchannels_conv3x3, 3, 2, 1, bias=False ), nn.BatchNorm2d(num_outchannels_conv3x3), nn.ReLU(True) ) ) # ---------------------------(2.3.1) end---------------------------- # # ---------------------------(2.3) end---------------------------- # fuse_layer.append(nn.Sequential(*conv3x3s)) fuse_layers.append(nn.ModuleList(fuse_layer)) # ---------------------------(2) end---------------------------- # return nn.ModuleList(fuse_layers) def get_num_inchannels(self): return self.num_inchannels def forward(self, x): # ---------------------------(1) begin---------------------------- # if self.num_branches == 1: return [self.branches[0](x[0])] # ---------------------------(1) end---------------------------- # # ---------------------------(2) begin---------------------------- # # ---------------------------(2.1) begin---------------------------- # for i in range(self.num_branches): x[i] = self.branches[i](x[i]) # ---------------------------(2.1) end---------------------------- # # ---------------------------(2.2) begin---------------------------- # x_fuse = [] for i in range(len(self.fuse_layers)): y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) for j in range(1, self.num_branches): if i == j: y = y + x[j] else: y = y + self.fuse_layers[i][j](x[j]) x_fuse.append(self.relu(y)) # ---------------------------(2.2) end---------------------------- # # ---------------------------(2) end---------------------------- # return x_fuse
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/43794.html
摘要:以高分辨率子網(wǎng)開(kāi)始作為第一階段,逐個(gè)添加高到低分辨率子網(wǎng)以形成更多階段,并且并行連接多分辨率子網(wǎng)。優(yōu)點(diǎn)并行連接高低分辨率子網(wǎng),而不是像大多數(shù)現(xiàn)有解決方案那樣串聯(lián)連接。我們認(rèn)為原因是從低分辨率子網(wǎng)上的早期階段提取的低級(jí)功能不太有用。 摘要: 大多數(shù)現(xiàn)有方法從由高到低分辨率網(wǎng)絡(luò)產(chǎn)生的低分辨率表示中恢復(fù)高分辨率表示。相反,本文在整個(gè)過(guò)程中保持高分辨率的表示。我們將高分辨率子網(wǎng)開(kāi)始作為第一階段...
摘要:注此讀書(shū)筆記只記錄本人原先不太理解的內(nèi)容經(jīng)過(guò)閱讀你不知道的后的理解。作用域及閉包基礎(chǔ),代碼運(yùn)行的幕后工作者引擎及編譯器。 注:此讀書(shū)筆記只記錄本人原先不太理解的內(nèi)容經(jīng)過(guò)閱讀《你不知道的JS》后的理解。 作用域及閉包基礎(chǔ),JS代碼運(yùn)行的幕后工作者:引擎及編譯器。引擎負(fù)責(zé)JS程序的編譯及執(zhí)行,編譯器負(fù)責(zé)詞法分析和代碼生成。那么作用域就像一個(gè)容器,引擎及編譯器都從這里提取東西。 ...
摘要:從現(xiàn)在開(kāi)始,養(yǎng)成寫(xiě)技術(shù)博客的習(xí)慣,或許可以在你的職業(yè)生涯發(fā)揮著不可忽略的作用。如果想了解更多優(yōu)秀的前端資料,建議收藏下前端英文網(wǎng)站匯總這個(gè)網(wǎng)站,收錄了國(guó)外一些優(yōu)質(zhì)的博客及其視頻資料。 前言 寫(xiě)文章是一個(gè)短期收益少,長(zhǎng)期收益很大的一件事情,人們總是高估短期收益,低估長(zhǎng)期收益。往往是很多人堅(jiān)持不下來(lái),特別是寫(xiě)文章的初期,剛寫(xiě)完文章沒(méi)有人閱讀會(huì)有一種挫敗感,影響了后期創(chuàng)作。 從某種意義上說(shuō),...
摘要:從現(xiàn)在開(kāi)始,養(yǎng)成寫(xiě)技術(shù)博客的習(xí)慣,或許可以在你的職業(yè)生涯發(fā)揮著不可忽略的作用。如果想了解更多優(yōu)秀的前端資料,建議收藏下前端英文網(wǎng)站匯總這個(gè)網(wǎng)站,收錄了國(guó)外一些優(yōu)質(zhì)的博客及其視頻資料。 前言 寫(xiě)文章是一個(gè)短期收益少,長(zhǎng)期收益很大的一件事情,人們總是高估短期收益,低估長(zhǎng)期收益。往往是很多人堅(jiān)持不下來(lái),特別是寫(xiě)文章的初期,剛寫(xiě)完文章沒(méi)有人閱讀會(huì)有一種挫敗感,影響了后期創(chuàng)作。 從某種意義上說(shuō),...
閱讀 3317·2021-09-30 09:54
閱讀 3811·2021-09-22 15:01
閱讀 3119·2021-08-27 16:19
閱讀 2584·2019-08-29 18:39
閱讀 2170·2019-08-29 14:09
閱讀 638·2019-08-26 10:23
閱讀 1348·2019-08-23 12:01
閱讀 1880·2019-08-22 13:57