この記事 はGitHubhttps ://github.com/Jack-Cherish/PythonParkに含まれてい ます。一次メーカーによる技術的な乾物記事、整理された学習資料、インタビューの経験の共有があります。スターへようこそ。
I.はじめに
この記事は、Pytorchディープラーニングセマンティックセグメンテーションチュートリアルシリーズに属しています。
このシリーズの記事の内容は次のとおりです。
- Pytorchの基本的な使用法
- セマンティックセグメンテーションアルゴリズムの説明
セマンティックセグメンテーションの原理と開発環境の構築について理解していない場合は、この一連のチュートリアルの前の記事「Pytorch Deep Learning Practical Tutorial(1):Semantic Segmentation Basics andEnvironmentConstruction」を参照してください。
この記事の開発環境は、前の記事で構築したWindows環境を使用しており、環境は次のとおりです。
開発環境:Windows
開発言語:Python3.7.4
フレームワークバージョン:Pytorch1.3.0
奇跡:10.2
cuDNN:7.6.0
この記事では、主にUNetネットワーク構造と対応するコードのコーディングについて説明します。
PS:記事に表示されているすべてのコードは、私のgithubにダウンロードできます。フォロー、スターへようこそ:クリックして表示
2、UNetネットワーク構造
セマンティックセグメンテーションの分野では、ディープラーニングに基づくセマンティックセグメンテーションアルゴリズムの先駆的な仕事はFCN(セマンティックセグメンテーションのための完全畳み込みネットワーク)であり、UNetはFCNの原則に従い、それに応じて改善され、小さなサンプルの単純なセグメンテーション問題に適応します。 。
UNetの紙のアドレス:クリックして表示
深層学習アルゴリズムを研究するには、最初にネットワーク構造を見て、次にネットワーク構造を理解し、次に損失計算方法、トレーニング方法などを理解することができます。この記事では主にUNetのネットワーク構造について説明し、その他の内容については次の章で説明します。
1.ネットワーク構造の原則
UNetは2015年のMICCAIカンファレンスで最初に発表されました。4年以上で、引用された論文の数は9,700以上に達しました。
UNetは、ほとんどの医療画像セマンティックセグメンテーションタスクのベースラインになりました。また、多くの研究者がU字型ネットワーク構造を研究するように促し、UNetネットワーク構造の改善に基づいた一連の論文を発表しました。
UNetネットワーク構造の2つの主な機能は、U字型ネットワーク構造とスキップ接続です。
UNetは対称的なネットワーク構造であり、左側がダウンサンプリング、右側がアップサンプリングです。
機能に応じて、左側の一連のダウンサンプリング操作をエンコーダーと呼び、右側の一連のアップサンプリング操作をデコーダーと呼びます。
Skip Connectionの中央には、4本の灰色の平行線があります。SkipConnectionはアップサンプリングの過程にあり、機能マップの融合はダウンサンプリングの過程にあります。
Skip Connectionで使用されるフュージョン操作も非常に簡単です。つまり、一般にConcatとして知られている機能マップのチャネルを重ね合わせることができます。
コンキャットの操作もわかりやすいです。たとえば、サイズが10cm * 10cmで厚さが3cmの本Aと、サイズが10cm * 10cmで厚さが4cmの本Bです。
ブックAとブックBを端を揃えて積み重ねます。このようにして、サイズが10cm * 10cm、厚さが7cmの本のスタックが次のように取得されます。
この種の「積み重ね」操作はConcatです。
同様に、フィーチャマップの場合、サイズが256 * 256 * 64のフィーチャマップ、つまり、フィーチャマップのw(幅)は256、h(高さ)は256、c(チャネル数)は64です。256 * 256 * 32のサイズの機能マップとの連結融合により、256 * 256 * 96のサイズの機能マップが作成されます。
実際の使用では、Concatによって融合された2つの機能マップのサイズは必ずしも同じではありません。たとえば、256 * 256 * 64の機能マップと240 * 240 * 32の機能マップがConcatに使用されます。
この場合、2つの方法があります。
最初のタイプ:大きな256 * 256 * 64フィーチャーマップを上、下、左、右などの240 * 240 * 64フィーチャーマップにトリミングし、8ピクセルを破棄し、トリミング後にConcatを実行して240 * 240 * 96を取得します。フィーチャーマップ。
2番目のタイプ:小さな240 * 240 * 32フィーチャマップをパディングします。パディングは、上下左右などの256 * 256 * 32のフィーチャマップで、それぞれ8ピクセルを塗りつぶし、パディング後にConcatを実行して256 * 256 * 96を取得します。フィーチャーマップ。
UNetで採用されているConcatスキームは2番目のタイプで、小さなフィーチャマップをパディングします。パディング方法は、通常の定数パディングである0を入力することです。
2.コード
一部の友人はPytorchについてあまり知らないかもしれず、クイックスタートのための公式チュートリアルをお勧めします。1時間で、いくつかの基本的な概念とPytorchコードの記述方法を習得できます。
Pytorch公式財団:クリックして表示
説明のために、UNetネットワーク全体を複数のモジュールに分割します。
DoubleConvモジュール:
2つの連続したコンボリューション操作を見てみましょう。
UNetネットワークから、ダウンサンプリングプロセスまたはアップサンプリングプロセスに関係なく、各レイヤーが2つの連続した畳み込み操作を実行することがわかります。この操作はUNetネットワークで何度も繰り返されます。DoubleConvモジュールは個別に作成できます。
import torch.nn as nn
class DoubleConv(nn.Module):
"""(convolution => [BN] => ReLU) * 2"""
def __init__(self, in_channels, out_channels):
super().__init__()
self.double_conv = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=0),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True),
nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=0),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True)
)
def forward(self, x):
return self.double_conv(x)
上記のPytorchコード:torch.nn.Sequentialはタイミングコンテナであり、モジュールは渡された順序でコンテナに追加されます。たとえば、上記のコードの操作シーケンス:convolution-> BN-> ReLU-> convolution-> BN-> ReLU。
DoubleConvモジュールのin_channelsとout_channelsは、拡張して使用するために柔軟に設定できます。
上図に示すネットワークでは、in_channelsは1に設定され、out_channelsは64に設定されています。
入力画像サイズは572 * 572です。ステップサイズが1でパディングが0の3 * 3コンボリューションの後、570 * 570のフィーチャマップが取得され、別のコンボリューション後に568 * 568のフィーチャマップが取得されます。
計算式:O =(H-F + 2×P)/ S + 1
Hは入力フィーチャマップのサイズ、Oは出力フィーチャマップのサイズ、Fはコンボリューションカーネルのサイズ、Pはパディングサイズ、Sはステップサイズです。
ダウンモジュール:
UNetネットワークには合計4つのダウンサンプリングプロセスがあり、モジュラーコードは次のとおりです。
class Down(nn.Module):
"""Downscaling with maxpool then double conv"""
def __init__(self, in_channels, out_channels):
super().__init__()
self.maxpool_conv = nn.Sequential(
nn.MaxPool2d(2),
DoubleConv(in_channels, out_channels)
)
def forward(self, x):
return self.maxpool_conv(x)
ここでのコードは非常に単純で、maxpoolプーリングレイヤー、ダウンサンプリング、そしてDoubleConvモジュールです。
この時点で、UNetネットワークの左半分のダウンサンプリングプロセスのコードが記述されており、次は右半分のアップサンプリングプロセスです。
アップモジュール:
もちろん、最もよく使われるアップサンプリングプロセスはアップサンプリングです。従来のアップサンプリング操作に加えて、機能の融合もあります。
このコードは、実装が少し複雑です。
class Up(nn.Module):
"""Upscaling then double conv"""
def __init__(self, in_channels, out_channels, bilinear=True):
super().__init__()
# if bilinear, use the normal convolutions to reduce the number of channels
if bilinear:
self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
else:
self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
self.conv = DoubleConv(in_channels, out_channels)
def forward(self, x1, x2):
x1 = self.up(x1)
# input is CHW
diffY = torch.tensor([x2.size()[2] - x1.size()[2]])
diffX = torch.tensor([x2.size()[3] - x1.size()[3]])
x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2,
diffY // 2, diffY - diffY // 2])
# if you have padding issues, see
# https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a
# https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd
x = torch.cat([x2, x1], dim=1)
return self.conv(x)
コードはもっと複雑です。個別に見ることができます。まず、__ init__初期化関数で定義されたアップサンプリングメソッドとDoubleConvを使用した畳み込みです。アップサンプリングでは、UpsampleとConvTranspose2dの2つのメソッド、つまり、双線形補間とデコンボリューションが定義されています。
双線形補間は理解しやすい、概略図:
バイリニア補間に精通している友人は、この図に慣れていないはずです。簡単に言えば、Q11、Q12、Q21、Q22の4点の座標がわかっており、R1はQ11とQ21で計算され、R2はQ12とQ22で計算され、最後に渡されます。 R1とR2はPを見つけます。このプロセスは、双線形補間です。
フィーチャマップの場合、実際にはピクセルの中央を埋めることであり、補完されたポイントの値は隣接するピクセルの値によって決定されます。
デコンボリューションは、その名前が示すように、逆コンボリューションです。畳み込みは、フィーチャマップをどんどん小さくすることであり、デコンボリューションは、フィーチャマップをどんどん大きくすることです。図:
下の青は元の画像、周囲の白い破線の四角はパディングの結果(通常は0)、上の緑は畳み込み後の画像です。
この概略図は、2 * 2フィーチャマップ-> 4 * 4からのフィーチャマッププロセスです。
順方向伝搬関数では、x1はアップサンプリングされたデータを受信し、x2は機能融合データを受信します。特徴融合法は、前述のように、最初に小さな特徴マップをパディングし、次に連結します。
OutConvモジュール:
上記のDoubleConvモジュール、Downモジュール、およびUpモジュールを使用すると、UNetの主要なネットワーク構造を詳しく説明できます。UNetネットワークの出力は、分割数に応じて出力チャネルを統合する必要があります。結果を次の図に示します。
操作は非常に簡単です。つまり、チャネルの変換です。上の図は、2としての分類を示しています(チャネルは2です)。
この操作は非常に簡単ですが、一度呼び出されます。美しさと清潔さのために、カプセル化されています。
class OutConv(nn.Module):
def __init__(self, in_channels, out_channels):
super(OutConv, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
def forward(self, x):
return self.conv(x)
この時点で、UNetネットワークで使用されるモジュールが作成されました。上記のモジュールコードをunet_parts.pyファイルに入れてから、UNetネットワーク構造に従ってunet_model.pyを作成し、各モジュールの入力チャネルと出力チャネルを設定できます。番号と呼び出しシーケンスについては、次のコードを記述してください。
""" Full assembly of the parts to form the complete network """
"""Refer https://github.com/milesial/Pytorch-UNet/blob/master/unet/unet_model.py"""
import torch.nn.functional as F
from unet_parts import *
class UNet(nn.Module):
def __init__(self, n_channels, n_classes, bilinear=False):
super(UNet, self).__init__()
self.n_channels = n_channels
self.n_classes = n_classes
self.bilinear = bilinear
self.inc = DoubleConv(n_channels, 64)
self.down1 = Down(64, 128)
self.down2 = Down(128, 256)
self.down3 = Down(256, 512)
self.down4 = Down(512, 1024)
self.up1 = Up(1024, 512, bilinear)
self.up2 = Up(512, 256, bilinear)
self.up3 = Up(256, 128, bilinear)
self.up4 = Up(128, 64, bilinear)
self.outc = OutConv(64, n_classes)
def forward(self, x):
x1 = self.inc(x)
x2 = self.down1(x1)
x3 = self.down2(x2)
x4 = self.down3(x3)
x5 = self.down4(x4)
x = self.up1(x5, x4)
x = self.up2(x, x3)
x = self.up3(x, x2)
x = self.up4(x, x1)
logits = self.outc(x)
return logits
if __name__ == '__main__':
net = UNet(n_channels=3, n_classes=1)
print(net)
コマンドpythonunet_model.pyを使用します。エラーがない場合は、次の結果が得られます。
UNet(
(inc): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
(down1): Down(
(maxpool_conv): Sequential(
(0): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(1): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
)
(down2): Down(
(maxpool_conv): Sequential(
(0): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(1): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
)
(down3): Down(
(maxpool_conv): Sequential(
(0): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(1): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
)
(down4): Down(
(maxpool_conv): Sequential(
(0): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(1): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(1024, 1024, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
)
(up1): Up(
(up): ConvTranspose2d(1024, 512, kernel_size=(2, 2), stride=(2, 2))
(conv): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(1024, 512, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
(up2): Up(
(up): ConvTranspose2d(512, 256, kernel_size=(2, 2), stride=(2, 2))
(conv): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(512, 256, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
(up3): Up(
(up): ConvTranspose2d(256, 128, kernel_size=(2, 2), stride=(2, 2))
(conv): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(256, 128, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
(up4): Up(
(up): ConvTranspose2d(128, 64, kernel_size=(2, 2), stride=(2, 2))
(conv): DoubleConv(
(double_conv): Sequential(
(0): Conv2d(128, 64, kernel_size=(3, 3), stride=(1, 1))
(1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU(inplace=True)
(3): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1))
(4): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(5): ReLU(inplace=True)
)
)
)
(outc): OutConv(
(conv): Conv2d(64, 1, kernel_size=(1, 1), stride=(1, 1))
)
)
ネットワークを設定したら、次のステップはトレーニングにネットワークを使用することです。具体的な実装については、この一連のチュートリアルの次の記事で説明します。
3、まとめ
- この記事では、主にUNetネットワークの構造について説明し、UNetネットワークのモジュラーコーミングを実行しました。
- 次の記事では、UNetネットワークを使用してトレーニングコードを作成する方法について説明します。
気に入って読んで、癖をつけて、WeChat公式アカウントで検索【JackCui-AI】インターネットを這うストーカーをフォロー