深度学习语义分割理论与实战指南
本文來自微信公眾號【機器學習實驗室】
深度學習語義分割理論與實戰指南
- 1 語義分割概述
- 2 關鍵技術組件
- 2.1 編碼器與分類網絡
- 2.2 解碼器與上采樣
- 2.2.1 雙線性插值(Bilinear Interpolation)
- 2.2.2 轉置卷積(Transposed Convolution)
- 2.2.3 反池化
- 2.3 Skip Connection
- 2.4 空洞卷積與多尺度
- 2.5 后處理技術
- 2.6 深監督
- 2.7 通用技術
- 2.7.1 損失函數
- 2.7.2 精度描述
- 3 數據Pipeline
- 3.1 PyTorch讀取數據模板
- 3.2 transform與數據增強
- 4 模型與算法
- 4.1 FCN
- 4.2 UNet
- 4.3 SegNet
- 4.4 Deeplab系列
- 4.5 PSPNet
- 4.6 UNet++
- 5 語義分割訓練Tips
- 5.1 PyTorch代碼搭建方式
- 5.2 可視化方法
- Visdom
- TensorBoard
圖像分類、目標檢測和圖像分割是基于深度學習的計算機視覺三大核心任務。三大任務之間明顯存在一種遞進的層級關系,圖像分類聚焦于整張圖像,目標檢測定位于圖像具體區域,而圖像分割則是細化到每一個像素。基于深度學習的圖像分割具體包括語義分割、實例分割和全景分割。語義分割的目的是要給每一個像素賦予一個語義標簽。語義分割在自動駕駛、場景理解、衛星遙感圖像和醫學影像等領域都有著廣泛的應用場景。
參考代碼:https://github.com/meetps/pytorch-semseg
1 語義分割概述
圖像分割主要包括語義分割(Semantic Segmentation)和實例分割(Instance Segmentation)。
- 語義分割是對圖像中的每個像素都劃分出對應的類別,即實現像素級別的分類;而類的具體對象,即為實例。
- 實例分割不但要進行像素級別的分類,還需要在具體的類別基礎上區別開不同的個體。例如,圖像中有多個人甲、乙、丙,那么他們的語義分割結果都是人,而實例分割結果卻是不同的對象。
- 為了同時實現實例分割與不可數類別的語義分割,相關研究又提出了全景分割(Panoptic Segmentation)。
語義分割的輸入和輸出:一張原始的RGB圖像或者單通道圖像,但是輸出不再是簡單的分類類別或者目標定位,而是帶有各個像素標簽的、與輸入同分辨率的分割圖像。
語義分割的任務是輸入圖像經過深度學習算法處理得到帶有語義標簽的同樣尺寸的輸出圖像。
2 關鍵技術組件
在予以分割發展早期,為了能夠讓深度學習進行像素級的分類任務,在分類任務的基礎上對CNN做了一些修改,將分類網絡中濃縮語義表征的全連接層去掉,提出全卷積網絡(FCN)來處理語義分割問題。U-Net奠定了編解碼結構的U型深度學習語義分割統治地位。
編碼器、解碼器和跳躍連接(Skip Connection)屬于分割網絡的核心結構組件,空洞卷積(Dilate Conv)是獨立于U型結構的第二大核心設計。條件隨機場(CRF)和馬爾可夫隨機場(MRF)則是用于優化神經網絡分割后的細節處理。
2.1 編碼器與分類網絡
編碼器對于分割網絡來說,就是進行特征提取和語義信息濃縮的過程,編碼器通過卷積核池化的組合不斷對圖像進行下采樣,得到的特征圖空間尺寸也越來越小,但會更加具備語義分辨性。
比如,以VGG16作為SegNet編碼器的預訓練模型:
from torchvision import models import torch.nnclass SegNet(torch.nn.Module):def __init__(self, classes):super(SegNet, self).__init__()vgg16 = models.vgg16(pretrained=True)features = vgg16.featuresself.enc1 = features[0:4]self.enc2 = features[5:9]self.enc3 = features[10:16]self.enc4 = features[17:23]self.enc5 = features[24:-1]在上述代碼中,可以將vgg16的31個層作為5個編碼模塊,每個編碼模塊的基本結構如下所示:
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU(inplace=True) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)2.2 解碼器與上采樣
編碼器將輸入不斷進行下采樣達到信息濃縮,而解碼器則負責上采樣來恢復輸入尺寸,上采樣方法主要包括:雙線性插值、轉置卷積和反池化。
2.2.1 雙線性插值(Bilinear Interpolation)
插值法是一種經典的數值分析方法,比如線性插值、三次樣條插值和拉格朗日插值法等。線性插值法是指使用連接兩個已知量的直線來確定在這兩個已知量之間的一個未知量的方法。
已知直線上兩點坐標分別為(x1,y1)(x_1,y_1)(x1?,y1?)和(x2,y2)(x_2,y_2)(x2?,y2?),現在通過線性插值法得到某一點(x,y)
線性插值用到兩個點來確定插值,雙線性插值則需要四個點。在圖像上采樣中,雙線性插值利用四個點的像素值來確定要插值的一個像素值,本質上還是分別在x和y方向上分別進行兩次線性插值。
雙線性插值的優點是速度非常快,計算量小,但缺點是效果不是特別理想。
2.2.2 轉置卷積(Transposed Convolution)
轉置卷積也叫解卷積或者反卷積(Deconvolution),在常規卷積中,每次得到的卷積特征圖尺寸是越來越小的,但是在圖像分割領域需要逐步恢復輸入時的尺寸。如果把常規卷積時的特征圖不斷變小叫做下采樣,那么通過轉置卷積來恢復分辨率的操作可以稱作上采樣。
本質上說,轉置卷積跟常規卷積并無區別。不同之處在于先按照一定的比例進行padding來擴大輸入尺寸,然后把常規卷積中的卷積核進行轉置,再按照常規卷積方法進行卷積就是轉置卷積。假設輸入圖像矩陣是X,卷積核矩陣為C,常規卷積的輸出為Y,則有:
Y=CXY=CXY=CX
兩邊同時乘以卷積核的轉置CTC^TCT,轉置卷積的輸入輸出計算公式如下:
X=CTYX=C^TYX=CTY
假設輸入大小為4x4,濾波器大小為3x3,常規卷積下輸出為2x2,為了演示轉置卷積,將濾波器矩陣進行稀疏化處理為4x16,將輸入矩陣進行拉平為16x1,相應輸出結果也會拉平為4x1,圖示如下:
然后按照轉置卷積,把卷積核矩陣進行轉置,按照X=CTYX=C^TYX=CTY進行驗證:
2.2.3 反池化
反池化(Unpooling)可以理解為池化的逆操作,相較于前兩種上采樣方法,反池化用的并不是特別多。原理如下:在池化時記錄下對應Kernel中的坐標,在反池化時將一個元素根據kernel進行放大,根據之前的坐標將元素寫進行去,其他位置補0即可。
2.3 Skip Connection
跳躍連接本身是在ResNet中率先提出,用于學習一個恒等式和殘差結構,后面在DenseNet、FCN和U-Net等網絡中廣泛使用。最典型的就是U-Net的跳躍連接,在每個編碼和解碼層之間各添加一個跳躍連接,每一次下采樣都會有一個跳躍連接與對應的上采樣進行級聯,這種不同尺度的特征融合對上采樣恢復像素有很大幫助。
2.4 空洞卷積與多尺度
空洞卷積(Dilated/Atrous Convolution)也叫擴張卷積或膨脹卷積,就是在卷積核中插入空洞,起到擴大感受野的作用。空洞卷積的直接做法是在常規卷積核中填充0,用來擴大感受野,且進行計算時,空洞卷積中實際只有非零元素起作用。假設以一個變量a來衡量空洞卷積的擴張系數,則加入空洞之后的實際卷積核尺寸與原始卷積核尺寸之間的關系:
Kernel=k+(k?1)?(a?1)Kernel=k+(k-1)*(a-1)Kernel=k+(k?1)?(a?1)
其中k為原始卷積核大小,a為卷積擴張率(dilation rate),Kernel為經過擴展后實際核大小。當a=1時,空洞卷積就退化為常規卷積。當a=1,2,4時,空洞卷積示意圖如下:
- 當a=1時,原始卷積核為3x3,就是常規卷積。
- 當a=2時,加入空洞之后的卷積核為3+(3-1)*(2-1)=5,對應的感受野為2(a+1)?1=72^{(a+1)}-1=72(a+1)?1=7。
- 當a=3時,加入空洞之后的卷積核為3+(3-1)*(4-1)=9,對應的感受野為2(a+1)?1=152^{(a+1)}-1=152(a+1)?1=15。
對比不加空洞卷積的情況,在Stride為1的情況下,3層3x3卷積的疊加,第三層輸出特征圖對應的感受野只有1+(3-1)*3=7。所以,空洞卷積的一個重要作用就是增大感受野。
在語義分割的發展歷程中,增大感受野是一個非常重要的設計。早期FCN提出以全卷積的方式來處理像素級別的分割任務,包括后來奠定語義分割baseline地位的U-Net,網絡結構中存在大量的池化層來進行下采樣,大量使用池化層的結果就是損失掉了一些信息,在解碼上采樣重建分辨率的時候會有一定的影響。特別是對于多目標、小物體的語義分割問題,以U-Net為代表的分割模型一直存在著精度瓶頸的問題。而基于增大感受野的動機背景下,就提出了以空洞卷積為重大創新的deeplab系列分割網絡。
對于語義分割而言,空洞卷積主要有三個作用:
- 擴大感受野。池化也可以擴大感受野,但是空間分辨率降低了,相比之下,空洞卷積可以在擴大感受野的同時不丟失分辨率,且保持像素的相對空間位置不變。空洞卷積可以同時控制感受野和分辨率。
- 獲取多尺度上下文信息。當多個帶有不同dilation rate的空洞卷積核疊加時,不同的感受野會帶來多尺度信息。
- 可以降低計算量。不需要引入額外的參數,實際卷積時只有帶紅點的元素真正進行計算。
2.5 后處理技術
早期語義分割模型效果較為粗糙,在沒有更好地特征提取模型的情況下,研究者們在神經網絡模型的粗糙結果上進行后處理(Post-Processing),主要方法就是一些常用的概率圖模型,比如說條件隨機場(Conditional Random Field,CRF)和馬爾可夫隨機場(Markov Random Field,MDF)。CRF是一種經典的概率圖模型,簡單而言就是給定一組輸入序列的條件下,求另一組輸出序列的條件概率分布模型,CRF在自然語言處理領域有著廣泛的應用。
CRF在語義分割后處理中用法的基本思路如下:對于FCN或者其他分割網絡的粗粒度分割結果而言,每個像素點i具有對應的類別標簽xix_ixi?和觀測值yiy_iyi?,以每個像素為節點,以像素與像素之間的關系作為邊即可構建一個CRF模型(DenseCRF)。
CRF論文:Efficient Inference in Fully Connected CRFs with Gaussian Edge Potentials
MRF在Deep Parsing Network(DPN)中有詳細描述,MRF論文:Semantic Image Segmentation via Deep Parsing Network.
語義分割發展前期,在分割網絡模型的結果后加上CRF或者MRF等后處理技術形成了早期的語義分割技術框架。從DeepLabv3開始,主流的語義分割網絡已經不再熱衷于后處理技術。主要原因有:
- CRF本身不容易訓練。
- 語義分割任務的端到端趨勢。
2.6 深監督
深監督(Deep supervision)就是在深度神經網絡的某些中間隱藏層加了一些輔助的分類器,作為一個網絡分支來對主干網絡進行監督的技巧,用來解決深度神經網絡訓練梯度消失和收斂速度過慢等問題。
帶有深監督的一個8層深度卷積網絡結構如下圖所示。
可以看到,圖中在第四個卷積塊之后添加了一個監督分類器作為分支。 Conv4 輸出的特征圖除了隨著主網絡進入 Conv5 之外,也作為輸入進入了分支分類器。如圖所示,該分支分類器包括一個卷積塊、兩個帶有 Dropout 和 ReLu 的全連接塊和一個純全連接塊。帶有深監督的卷積模塊例子如下。
2.7 通用技術
通用技術主要是深度學習中會用到的基本模塊,比如說損失函數的選取,選擇哪種精度衡量指標,優化器的選取,以及學習率的控制等。
2.7.1 損失函數
常用的分類損失函數都可用于語義分割。如交叉熵損失函數、二分類的交叉熵損失函數等,對于慕白哦較小的情況下可以使用Dice損失,對于目標類別不均衡的情況下可以使用加權的交叉熵損失。
2.7.2 精度描述
語義分割常見的評價指標包括像素準確率(Pixel Accuracy)、平均像素準確率(Mean Pixel Accuracy)、平均交并比(Mean IoU)、頻權交并比(FWIoU)和Dice系數(Dice Confident)。
DIce系數是一種度量兩個集合相似性的函數,定義為兩倍的交集除以像素和,跟IoU有點類似。
dice=2∣x∩y∣∣x∣+∣y∣dice={{2|x\cap y|}\over{|x|+|y|}}dice=∣x∣+∣y∣2∣x∩y∣?
import torchdef dice_coef(pred, target):"""Dice=(2*|x&Y|/(|x|+|Y|))=2*sum(|A*B|)/(sum(A^2)+sum(B^2)):param pred::param target::return:"""smooth = 1m1 = pred.view(-1).float()m2 = target.view(-1).float()intersection = (m1 * m2).sum().float()dice = (2. * intersection + smooth) / (torch.sum(m1 * m2) + torch.sum(m1 * m2) + smooth)return dice3 數據Pipeline
這里主要說一下PyTorch的自定義數據讀取pipeline模板和相關trciks以及如何優化數據讀取的pipeline等。我們從PyTorch的數據對象類 Dataset 開始, Dataset 在PyTorch中的模塊位于 utils.data 下。
3.1 PyTorch讀取數據模板
PyTorch官方為我們提供了自定義數據讀取的標準化代碼代碼模塊,作為一個讀取框架,我們這里稱之為原始模板。其代碼結構如下:
from torch.utils.data import Datasetclass CustomDataset(Dataset):def __init__(self):super(CustomDataset, self).__init__()def __getitem__(self, item):return itemdef __len__(self):return cout3.2 transform與數據增強
PyTorch數據增強功能可以放在 transform 模塊下,添加 transform 后的數據讀取結構如下所示:
from torch.utils.data import Dataset from torchvision import transforms as Tclass CustomDataset(Dataset):def __init__(self):# compose the transforms methodsself.transform=T.Compose([T.CenterCrop(100),T.RandomResizedCrop(256),T.RandomRotation(45),T.ToTensor()])def __getitem__(self,index):data = # Some data read from a file or image label = # Some data read from a file or image# execute the transform data = self.transform(data) label = self.transform(label) return (img, label)def __len__(self):# return examples sizereturn countPyTorch transform 模塊所做的數據增強并不是我們所理解的廣義上的數據增強。 transform 所做的增強,僅僅是在數據讀取過程中隨機地對某張圖像做轉化操作,實際數據量上并 沒有增多,可以將其視為是一種在線增強的策略。如果想要實現實際訓練數據成倍數的增加,可以使用離線增強策略。
與圖像分類僅需要對輸入圖像做增強不同的是,對于語義分割的數據增強而言,需要同時對輸入圖像和 輸入的mask同步進行數據增強工作。實際寫代碼時,要記得使用隨機種子,在不失隨機性的同時,保證輸入圖像和輸出mask具備同樣的轉換。一個完整的語義分割在線數據增強代碼實例如下:
import os import random import torch from torch.utils.data import Dataset from PIL import Imageclass SegementationDataset(Dataset):# read the input images@staticmethoddef _load_input_iamge(path):with open(path,'rb') as f:img=Image.open(f)return img.convert('RGB')# read the mask images@staticmethoddef _load_target_image(path):with open(path,'rb') as f:img=Image.open(f)return img.convert('L')def __init__(self,input_root,target_root,transform_input=None,transform_target=None,seed_fn=None):self.input_root=input_rootself.target_root=target_rootself.transform_input=transform_inputself.transform_target=transform_targetself.seed_fn=seed_fn# soft the idsself.input_ids=sorted(img for img in os.listdir(self.input_root))self.target_ids=sorted(img for img in os.listdir(self.target_root))assert(len(self.input_ids)==len(self.target_ids))# set random number seeddef _set_seed(self,seed):random.seed(seed)torch.manual_seed(seed)if self.seed_fn:self.seed_fn(seed)def __getitem__(self,idx):input_img=self._load_input_image(os.path.join(self.input_root,self.input_ids[idx]))target_img=self._load_target_image(os.path.join(self.target_root,self.target_ids[idx]))if self.transform_input:# ensure that the input and output have the same randomnessseed=random.randint(0,2**32)self._set_seed(seed)input_img=self.transform_input(input_img)self._set_seed(seed)target_img=self.transform_target(target_img)return input_img,target_img,self.input_ids[idx]def __len__(self):return len(self.input_ids)其中 transform_input 和 transform_target 均可由 transform 模塊下的函數封裝而成。一個皮膚病灶分割的在線數據增強實例效果如下圖所示。
4 模型與算法
早期基于深度學習的圖像分割以FCN為核心,旨在重點解決如何更好從卷積下采樣中恢復丟掉的信息損失。后來逐漸形成了以U-Net為核心的這樣一種編解碼對稱的U形結構。
語義分割迄今為止最重要的兩個設計,一個是以U-Net為代表的U形結構,目前基于U-Net結構的創新層出不窮,比如說應用3D圖像的V-Net,嵌套U-Net結構的U-Net++等,除此之外,還有SegNet、RefineNet、HRNet和FastFCN。另一個則是以DeepLab系列為代表的Dilation設計,主要包括DeepLab系列和PSPNet。
隨著模型的Baseline效果不斷提升,語義分割任務的主要矛盾也逐漸從downsample損失恢復像素逐漸演變為如何有效地利用context上下文信息。
4.1 FCN
FCN(Fully Convilutional Networks)是語義分割領域的開山之作。FCN的提出是在2016年,相較于此 前提出的AlexNet和VGG等卷積全連接的網絡結構,FCN提出用卷積層代替全連接層來處理語義分割問題,這也是FCN的由來,即全卷積網絡。
FCN關鍵點有三個:
- 全卷積進行特征提取和下采樣
- 雙線性插值進行上采樣
- 跳躍連接進行特征融合
利用PyTorch實現一個FCN-8網絡:
從代碼中可以看到,我們使用了vgg16作為FCN-8的編碼部分,這使得FCN-8具備較強的特征提取能力。
4.2 UNet
早期基于深度學習的圖像分割以FCN為核心,旨在重點解決如何更好從卷積下采樣中恢復丟掉的信息損失。后來逐漸形成了以UNet為核心的這樣一種編解碼對稱的U形結構。
UNet結構能夠在分割界具有一統之勢,最根本的還是其效果好,尤其是在醫學圖像領域。所以,做醫學 影像相關的深度學習應用時,一定都用過UNet,而且最原始的UNet一般都會有一個不錯的baseline表 現。2015年發表UNet的MICCAI,是目前醫學圖像分析領域最頂級的國際會議。
U-Net結構如下圖所示:
我們來把UNet簡化一下,如下圖所示:
從圖中可以看到,簡化之后的UNet的關鍵點只有三條線:
- 下采樣編碼
- 上采樣編碼
- 跳躍連接
下采樣進行信息濃縮和上采樣進行像素恢復,這是其他分割網絡都會有的部分。UNet進行了4次最大池化下采樣,每一次采樣后都使用卷積進行信息提取得到特征圖,然后再經過4次上采樣恢復輸入像素尺寸。
但UNet最關鍵的、也是最有特色的部分在于圖中紅色虛線的Skip Connection。每次下采樣都會有一個跳躍連接與對應的上采樣進行級聯,這種不同尺度的特征融合對上采樣恢復像素大有幫助,具體來說就是高層(淺層)下采樣倍數小,特征圖具備更加細致的圖特征,底層(深層)下采樣倍數大,信息經過大量濃縮,空間損失大,但有助于目標區域(分類)判斷,當high level和low level的特征進行融合時,分割效果往往非常好,從某種程度上講,這種跳躍連接也可以視為一種Deep Supervision。
U-Net的簡單實現如下:
import torch import torch.nn as nn import torch.nn.functional as F # 編碼塊 class UNetEnc(nn.Module):def __init__(self,in_channels,out_channels,dropout=False):super().__init__()layers=[nn.Conv2d(in_channels,out_channels,3,dilation=2),nn.ReLU(inplace=True),nn.Conv2d(in_channels,out_channels,3,dilation=2),nn.ReLU(inplace=True),]if dropout:layers+=[nn.Dropout(.5)]layers+=[nn.MaxPool2d(2,stride=2,ceil_mode=True)]self.down=nn.Sequential(*layers)def forward(self,x):return self.down(x) # 解碼塊 class UNetDec(nn.Module):def __init__(self,in_channels,features,out_channels):super().__init__()self.up=nn.Sequential(nn.Conv2d(in_channels,features,3),nn.ReLU(inplace=True),nn.Conv2d(features,features,3),nn.ReLU(inplace=True),nn.ConvTranspose2d(features,out_channels,2,stride=2),nn.ReLU(inplace=True),)def forward(self,x):return self.up(x) # U-Net class UNet(nn.Module):def __init__(self,num_classes):super().__init__()self.enc1=UNetEnc(3,64)self.enc2=UNetEnc(64,128)self.enc3=UNetEnc(128,256)self.enc4=UNetEnc(256,512,dropout=True)self.center=nn.Sequential(nn.Conv2d(512,1024,3),nn.ReLU(inplace=True),nn.Conv2d(1024,1024,3),nn.ReLU(inplace=True),nn.Dropout(),nn.ConvTranspose2d(1024,512,2,stride=2),nn.ReLU(inplace=True),)self.dec4=UNetDec(1024,512,256)self.dec3=UNetDec(512,256,128)self.dec2=UNetDec(256,128,64)self.dec1=nn.Sequential(nn.Conv2d(128,64,3),nn.ReLU(inplace=True),nn.Conv2d(64,64,3),nn.ReLU(inplace=True),)self.final=nn.Conv2d(64,num_classes,1)# 前向傳播過程def forward(self,x):enc1=self.enc1(x)enc2=self.enc2(enc1)enc3=self.enc3(enc2)enc4=self.enc4(enc3)center=self.center(enc4)# 包含了同層分辨率級聯的解碼塊dec4=self.dec4(torch.cat([center,F.upsample_bilinear(enc4,center.size()[2:])], 1))dec3=self.dec3(torch.cat([dec4,F.upsample_bilinear(enc3,dec4.size()[2:])], 1))dec2=self.dec2(torch.cat([dec3,F.upsample_bilinear(enc2,dec3.size()[2:])], 1))dec1=self.dec1(torch.cat([dec2,F.upsample_bilinear(enc1,dec2.size()[2:])], 1))return F.upsample_bilinear(self.final(dec1),x.size()[2:])4.3 SegNet
SegNet網絡是典型的編碼-解碼結構。SegNet編碼器網絡由VGG16的前13個卷積層構成,所以通常使用VGG16的預訓練權重來進行初始化,每個編碼器層都有一個對應的解碼器層,因此解碼器層也有13層。解碼器最后的輸出進入到softmax分類器中,輸出每個像素的類別概率。
SegNet如下圖所示:
4.4 Deeplab系列
Deeplab系列可以算是深度學習語義分割的另一個主要架構,其代表方法就是基于Dilation的多尺度設計。Deeplab系列主要包括:
- Deeplab v1
- Deeplab v2
- Deeplab v3
- Deeplab v3+
Deeplab v1主要是率先使用了空洞卷積,是Deeplab系列最原始的版本。Deeplab v2在Deeplab v1的基礎上最大的改進在于提出了ASPP(Atrous Spatial Pyramid Pooling),即帶有空洞卷積的金字塔池化,該設計的主要目的就是提取圖像的多尺度特征。另外Deeplab v2也將Deeplab v1的Backbone網絡更換為ResNet。Deeplab v1和v2還有一個比較大的特點就是使用了CRF作為后處理技術。
多尺度問題就是圖像中的目標對象存在不同大小時,分割效果不佳的現象。比如同樣的物體,在近處拍攝時物體顯得大,遠處拍攝時顯得小。解決多尺度問題的目標就是不論目標對象是大還是小,網絡都能將其分割地很好,Deeplab v2使用ASPP處理多尺度問題,ASPP設計結構如下圖所示。
從Deeplab v3開始,Deeplab系列舍棄了CRF后處理模塊,提出了更加通用的、適用任何網絡的分割框架,對ResNet最后的Block做了復制和級聯(Cascade),對ASPP模塊做了升級,在其中添加了BN層,改進后的ASPP如下圖所示。
Deeplabv3+在Deeplabv3的基礎上做了擴展和改進,其主要改進就是在編解碼結構上使用了ASPP。Deeplabv3+可以視作是融合了語義分割兩大流派的一項工作,即編解碼+ASPP結構。另外Deeplabv3+的Backbone換成了Xception,其深度可分離卷積的設計使得分割網絡更加高效,Deeplabv3結構如下圖所示。
關于Deeplab系列各個版本的技術點構成總結如下表所示。
4.5 PSPNet
PSPNet是針對多尺度問題提出的另一種代表性分割網絡。PSPNet認為此前的分割網絡沒有引入足夠的上下文信息及不同感受野下的全局信息,而存在分割出現錯誤的情況,因此引入Global-Scence-Level的信息解決該問題,其Backbone網絡也是ResNet。
PSPNet就是將Deeplab的ASPP模塊之前的特征圖Pooling了四種尺度,然后將原始特征圖和四種Pooling之后的特征圖合并到一起,再經過一系列卷積之后進行預測的過程,PSPNet結構如下圖所示。
# Pyramid Pooling Module import torch.nn as nnclass PPMDeepsup(nn.Module):def __init__(self, num_class=150, fc_dim=4096, use_softmax=False, pool_scales=(1, 2, 3, 6)):super(PPMDeepsup, self).__init__()self.use_softmax = use_softmax# PPMself.ppm = []for scale in pool_scales:self.ppm.append(nn.Sequential(nn.AdaptiveAvgPool2d(scale),nn.Conv2d(fc_dim, 512, kernel_size=1, bias=False),nn.BatchNorm2d(512),nn.ReLU(inplace=True),))self.ppm = nn.ModuleList(self.ppm)# Deep Supervisionself.cbr_deepsup = conv3x3_bn_relu(fc_dim // 2, fc_dim // 4, 1)self.conv_last = nn.Sequential(nn.Conv2d(fc_dim + len(pool_scales) * 512, 512, kernel_size=3, padding=1, bias=False),nn.BatchNorm2d(512),nn.ReLU(inplace=True),nn.Dropout2d(0.1),nn.Conv2d(512, num_class, kernel_size=1))self.conv_last_deepsup = nn.Conv2d(fc_dim // 4, num_class, 1, 1, 0)self.dropout_deepsup = nn.Dropout2d(0.1)4.6 UNet++
自從2015年UNet網絡提出后,這么多年大家沒少在這個U形結構上折騰。大部分做語義分割的朋友都沒 少在UNet結構上做各種魔改,如果把UNet++算作是UNet的一種魔改的話,那它一定是最成功的魔改 機器學習實驗室者。
UNet++是一種嵌套的U-Net結構,即內置了不同深度的UNet網絡,并且利用全尺度的跳躍連接(skip connections)和深監督(deep supervisions)。另外UNet++還設計了一種剪枝方案,加快了UNet++的推理速度。UNet++的結構示意圖如下所示。
單純從結構設計的角度來看,UNet++效果好要歸功于其嵌套結構和重新設計的跳躍連接,旨在解決
UNet的兩個關鍵挑戰:
1)優化整體結構的未知深度
2)跳躍連接的不必要的限制性設計
UNet++的一個簡單的實現代碼如下所示。https://github.com/4uiiurz1/pytorch-nested-unet/
以上僅對幾個主要的語義分割網絡模型進行介紹,從當年的FCN到如今的各種模型層出不窮,想要對所 有的SOTA模型全部進行介紹已經不太可能。其他諸如ENet、DeconvNet、RefineNet、HRNet、PixelNet、BiSeNet、UpperNet等網絡模型,均各有千秋。深度學習和計算機視覺發展日新月異,一個新的SOTA模型出來,肯定很快就會被更新的結構設計所代替,重點是我們要了解語義分割的發展脈絡,對主流的前沿研究能夠保持一定的關注。
5 語義分割訓練Tips
PyTorch是一款極為便利的深度學習框架。在日常實驗過程中,我們要多積累和總結,假以時日,人人 都能總結出一套自己的高效模型搭建和訓練套路。這一節我們給出一些慣用的PyTorch代碼搭建方式,
以及語義分割訓練過程中的可視化方法,方便大家在訓練過程中能夠直觀的看到訓練效果。
5.1 PyTorch代碼搭建方式
無論是分類、檢測還是分割抑或是其他非視覺的深度學習任務,其代碼套路相對來說較為固定,不會跳
出基本的代碼框架。一個深度學習的實現代碼框架無非就是以下五個主要構成部分:
- 數據:Data
- 模型:Model
- 判斷:Criterion
- 優化:Optimizer
- 日志:Logger
所以一個基本的順序實現范式如下:
# data dataset = VOC()||COCO()||ADE20K() data_loader = data.DataLoader(dataSet)# model model = ... model_parallel = torch.nn.DataParallel(model)# Criterion loss = criterion(...)# Optimizer optimer = optim.SGD(...)# Logger and Visulization visdom = ... tensorboard = ... textlog = ...# Model Parameters data_size, batch_size, epoch_size, iterations = ..., ...不論是哪種深度學習任務,一般都免不了以上五項基本模塊。所以一個簡單的、相對完整的PyTorch模
型項目代碼應該是如下結構的:
上面的示例代碼結構中,我們把訓練和驗證相關代碼都放到 main.py 文件中,但在實際實驗中,這塊的 靈活性極大。一般來說,模型訓練策略有三種,一種是邊訓練邊驗證最后再測試、另一種則是在訓練中 驗證,將驗證過程糅合到訓練過程中,還有一種最簡單,就是訓練完了再單獨驗證和測試。所以,我們 這里也可以單獨定義對應的函數,訓練 train() 、驗證 val() 以及測試 test() 除此之外,還有一些輔助 機器學習實驗室功能需要設計,包括打印訓練信息 print() 、繪制損失函數 plot() 、保存最優模型 save() ,調整訓練參數 update() 。
所以訓練代碼控制流程可以歸納為TVT+PPSU的模式。
5.2 可視化方法
PyTorch原生的可視化支持模塊是Visdom,當然鑒于TensorFlow的應用廣泛性,PyTorch同時也支持 TensorBoard的可視化方法。語義分割需要能夠直觀的看到訓練效果,所以在訓練過程中輔以一定的可
視化方法是十分必要的。
Visdom
visdom是一款用于創建、組織和共享實時大量訓練數據可視化的靈活工具。深度學習模型訓練通常放在 遠程的服務器上,服務器上訓練的一個問題就在于不能方便地對訓練進行可視化,相較于TensorFlow的 可視化工具TensorBoard,visdom則是對應于PyTorch的可視化工具。直接通過 pip install visdom 即
可完成安裝,之后在終端輸入如下命令即可啟動visdom服務。
啟動服務后輸入本地或者遠程地址,端口號8097,即可打開visdom主頁。具體到深度學習訓練時,我
們可以在torch訓練代碼下插入visdom的可視化模塊.
visdom效果展示如下:
TensorBoard
很多TensorFlow用戶更習慣于使用TensorBoard來進行訓練的可視化展示。為了能讓PyTorch用戶也能 用 上 TensorBoard , 有 開 發 者 提 供 了 PyTorch 版 本 的 TensorBoard , 也 就 是 tensorboardX 。 熟 悉 TensorBoard的用戶可以無縫對接到tensorboardX,安裝方式為:
pip install tensorboardX除了要安裝PyTorch之外,還需要安裝TensorFlow。跟TensorBoard一樣,tensorboardX也支持scalar,
image, figure, histogram, audio, text, graph, onnx_graph, embedding, pr_curve,video等不同類型對象
的可視化展示方式。tensorboardX和TensorBoard的啟動方式一樣,直接在終端下運行:
總結
以上是生活随笔為你收集整理的深度学习语义分割理论与实战指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java随记 2月16
- 下一篇: JS ~ Promise.reject(