Residual, BottleNeck, Linear BottleNeck, MBConv解釋
今天,我們將看到現(xiàn)代CNN架構(gòu)中使用的不同模塊,如ResNet、MobileNet、EfficientNet,以及它們在PyTorch中的實現(xiàn)。
讓我們創(chuàng)建一個通用的conv-norm-act層
from functools import partial
from torch import nn
class ConvNormAct(nn.Sequential):
def __init__(
self,
in_features: int,
out_features: int,
kernel_size: int,
norm: nn.Module = nn.BatchNorm2d,
act: nn.Module = nn.ReLU,
**kwargs
):
super().__init__(
nn.Conv2d(
in_features,
out_features,
kernel_size=kernel_size,
padding=kernel_size // 2,
),
norm(out_features),
act(),
)
Conv1X1BnReLU = partial(ConvNormAct, kernel_size=1)
Conv3X3BnReLU = partial(ConvNormAct, kernel_size=3)
import torch
x = torch.randn((1, 32, 56, 56))
Conv1X1BnReLU(32, 64)(x).shape
torch.Size([1, 64, 56, 56])
殘差連接殘差連接用于ResNet中,想法是將輸入添加到輸出中,輸出=層+輸入。下圖可能會幫助你將其可視化。但是,我的意思是它只是一個+運算符。殘差操作提高了梯度傳播的能力,允許有效地訓(xùn)練具有100層以上的網(wǎng)絡(luò)。
在PyTorch中,我們可以輕松創(chuàng)建一個ResidualAdd層
from torch import nn
from torch import Tensor
class ResidualAdd(nn.Module):
def __init__(self, block: nn.Module):
super().__init__()
self.block = block
def forward(self, x: Tensor) -> Tensor:
res = x
x = self.block(x)
x += res
return x
ResidualAdd(
nn.Conv2d(32, 32, kernel_size=1)
)(x).shape
shortcut
有時你的殘差沒有相同的輸出維度,所以我們不能添加它們。我們可以使用shortcut中的卷積投射輸入,以匹配輸出特征:
from typing import Optional
class ResidualAdd(nn.Module):
def __init__(self, block: nn.Module, shortcut: Optional[nn.Module] = None):
super().__init__()
self.block = block
self.shortcut = shortcut
def forward(self, x: Tensor) -> Tensor:
res = x
x = self.block(x)
if self.shortcut:
res = self.shortcut(res)
x += res
return x
ResidualAdd(
nn.Conv2d(32, 64, kernel_size=1),
shortcut=nn.Conv2d(32, 64, kernel_size=1)
)(x).shape
BottleNeck Blocks
在圖像識別的深度殘差學(xué)習(xí)中引入了Bottlenecks。Bottlenecks塊接受大小為BxCxHxW的輸入,它首先使用1x1 卷積將其變?yōu)锽xC/rxHxW,然后應(yīng)用3x3 卷積,最后將輸出重新映射到與輸入相同的特征維度BxCxHxW,然后再次使用1x1卷積。這比使用三個3x3卷積更快。
因為首先減少了輸入,所以我們稱之為“Bottlenecks”。下圖顯示了該塊,我們在原始實現(xiàn)中使用了r=4
前兩個卷積之后是batchnorm和一個非線性激活層,而最后一個非線性層在加法后應(yīng)用。
在PyTorch中為:
from torch import nn
class BottleNeck(nn.Sequential):
def __init__(self, in_features: int, out_features: int, reduction: int = 4):
reduced_features = out_features // reduction
super().__init__(
nn.Sequential(
ResidualAdd(
nn.Sequential(
# wide -> narrow
Conv1X1BnReLU(in_features, reduced_features),
# narrow -> narrow
Conv3X3BnReLU(reduced_features, reduced_features),
# narrow -> wide
Conv1X1BnReLU(reduced_features, out_features, act=nn.Identity),
),
shortcut=Conv1X1BnReLU(in_features, out_features)
if in_features != out_features
else None,
),
nn.ReLU(),
)
)
BottleNeck(32, 64)(x).shape
請注意,僅當輸入和輸出特征不同時,我們才應(yīng)用shortcut。
在實踐中,當我們希望減小空間維數(shù)時,在卷積中使用stride=2。
Linear BottleNecks
MobileNet V2中引入了Linear Bottleneck。Linear BottleNecks是沒有激活函數(shù)的Bottlenecks塊。
在論文的第3.2節(jié)中,他們詳細討論了為什么在輸出之前存在非線性會損害性能。簡而言之,非線性函數(shù)ReLU在<0時設(shè)為0會導(dǎo)致破壞信息。因此,在Bottlenecks中刪除nn.ReLU你就可以擁有Linear BottleNecks。
倒殘差
MobileNet V2中再次引入了倒殘差。
倒殘差塊是反向的Bottlenecks層。它們通過第一次卷積擴展特征,而不是減少特征。
下圖應(yīng)該可以清楚地說明這一點
我們從BxCxHxW到->BxCxHxW->BxCxHxW->BxCxHxW,其中e是膨脹率,它被設(shè)置為4。而不是像在正常的Bottlenecks區(qū)那樣變寬->變窄->變寬,而是相反,變窄->變寬->變窄。
在PyTorch中,實現(xiàn)如下
class InvertedResidual(nn.Sequential):
def __init__(self, in_features: int, out_features: int, expansion: int = 4):
expanded_features = in_features * expansion
super().__init__(
nn.Sequential(
ResidualAdd(
nn.Sequential(
# narrow -> wide
Conv1X1BnReLU(in_features, expanded_features),
# wide -> wide
Conv3X3BnReLU(expanded_features, expanded_features),
# wide -> narrow
Conv1X1BnReLU(expanded_features, out_features, act=nn.Identity),
),
shortcut=Conv1X1BnReLU(in_features, out_features)
if in_features 。 out_features
else None,
),
nn.ReLU(),
)
)
InvertedResidual(32, 64)(x).shape
在MobileNet中,只有當輸入和輸出特征匹配時,才會應(yīng)用殘差連接
class MobileNetLikeBlock(nn.Sequential):
def __init__(self, in_features: int, out_features: int, expansion: int = 4):
# use ResidualAdd if features match, otherwise a normal Sequential
residual = ResidualAdd if in_features == out_features else nn.Sequential
expanded_features = in_features * expansion
super().__init__(
nn.Sequential(
residual(
nn.Sequential(
# narrow -> wide
Conv1X1BnReLU(in_features, expanded_features),
# wide -> wide
Conv3X3BnReLU(expanded_features, expanded_features),
# wide -> narrow
Conv1X1BnReLU(expanded_features, out_features, act=nn.Identity),
),
),
nn.ReLU(),
)
)
MobileNetLikeBlock(32, 64)(x).shape
MobileNetLikeBlock(32, 32)(x).shape
MBConv
MobileNet V2的構(gòu)建塊被稱為MBConv。MBConv是具有深度可分離卷積的倒殘差的Linear BottleNecks層。
深度可分離卷積
深度可分離卷積采用一種技巧,將一個正常的3x3卷積夾在兩個卷積中,以減少參數(shù)數(shù)量。
第一個對每個輸入的通道應(yīng)用單個3x3濾波器,另一個對所有通道應(yīng)用1x1濾波器。
這與正常的3x3卷積相同,但你節(jié)省了參數(shù)。
然而它比我們現(xiàn)有硬件上的普通3x3慢得多。
下圖顯示了這個想法
通道中的不同顏色表示每個通道應(yīng)用的單個過濾器
PyTorch中:
class DepthWiseSeparableConv(nn.Sequential):
def __init__(self, in_features: int, out_features: int):
super().__init__(
nn.Conv2d(in_features, in_features, kernel_size=3, groups=in_features),
nn.Conv2d(in_features, out_features, kernel_size=1)
)
DepthWiseSeparableConv(32, 64)(x).shape
第一次卷積通常稱為depth,而第二次卷積稱為point。讓我們統(tǒng)計參數(shù)量
sum(p.numel() for p in DepthWiseSeparableConv(32, 64).parameters() if p.requires_grad)
輸出:2432
讓我們看一個普通的Conv2d
sum(p.numel() for p in nn.Conv2d(32, 64, kernel_size=3).parameters() if p.requires_grad)
輸出:18496
有很大的區(qū)別
實現(xiàn)MBConv
那么,讓我們創(chuàng)建一個完整的MBConv。
MBConv有幾個重要的細節(jié),標準化應(yīng)用于深度和點卷積,非線性僅應(yīng)用于深度卷積(Linear Bottlenecks)。
class MBConv(nn.Sequential):
def __init__(self, in_features: int, out_features: int, expansion: int = 4):
residual = ResidualAdd if in_features == out_features else nn.Sequential
expanded_features = in_features * expansion
super().__init__(
nn.Sequential(
residual(
nn.Sequential(
# narrow -> wide
Conv1X1BnReLU(in_features,
expanded_features,
act=nn.ReLU6
),
# wide -> wide
Conv3X3BnReLU(expanded_features,
expanded_features,
groups=expanded_features,
act=nn.ReLU6
),
# here you can apply SE
# wide -> narrow
Conv1X1BnReLU(expanded_features, out_features, act=nn.Identity),
),
),
nn.ReLU(),
)
)
MBConv(32, 64)(x).shape
Fused MBConv
EfficientNetV2中引入了融合倒殘差
所以基本上,由于深度卷積比較慢,他們將第一個和第二個卷積融合在一個3x3的卷積中(第3.2節(jié))。
class FusedMBConv(nn.Sequential):
def __init__(self, in_features: int, out_features: int, expansion: int = 4):
residual = ResidualAdd if in_features == out_features else nn.Sequential
expanded_features = in_features * expansion
super().__init__(
nn.Sequential(
residual(
nn.Sequential(
Conv3X3BnReLU(in_features,
expanded_features,
act=nn.ReLU6
),
# here you can apply SE
# wide -> narrow
Conv1X1BnReLU(expanded_features, out_features, act=nn.Identity),
),
),
nn.ReLU(),
)
)
MBConv(32, 64)(x).shape
結(jié)論
現(xiàn)在你應(yīng)該知道所有這些塊之間的區(qū)別以及它們背后的原因了!
原文標題 : Residual, BottleNeck, Linear BottleNeck, MBConv解釋
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
-
即日-11.13立即報名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月28日立即報名>>> 2024工程師系列—工業(yè)電子技術(shù)在線會議
-
12月19日立即報名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報名中>> OFweek2024中國智造CIO在線峰會
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍皮書》
-
精彩回顧立即查看>> 【限時免費下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
推薦專題
-
5 夾縫中的文遠知行
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市