UNet アルゴリズムの原理の解釈とパドルの実装
U-Net ネットワークは、医療画像のセグメンテーションを起源とする非常に古典的な画像セグメンテーション ネットワークであり、パラメーターが少なく、計算が速く、適用性が高いという特徴があり、一般的なシナリオへの適応性が高くなります。U-Net は 2015 年に初めて提案され、ISBI 2015 Cell Tracking Challenge で 1 位を獲得しました。
U-Net の構造は、図 1 に示すように、標準的なエンコーダ/デコーダ構造です。左側はエンコーダ、右側はデコーダと考えることができます。画像はまずエンコーダによってダウンサンプリングされて高レベルの意味論的な特徴マップが取得され、次にデコーダによってアップサンプリングされて特徴マップが元の画像の解像度に復元されます。ネットワークではスキップ接続も使用されます。つまり、デコーダがアップサンプリングするたびに、デコーダとエンコーダの同じ解像度に対応する特徴マップがスプライシング方式で融合され、デコーダがターゲットの詳細をより適切に復元できるようになります。 。
1) エンコーダ: エンコーダは、全体として徐々に縮小する構造を提示し、コンテキスト情報をキャプチャするために特徴マップの解像度を継続的に下げます。エンコーダは 4 つのステージに分かれており、各ステージでは、最大プーリング層がダウンサンプリングに使用され、次に 2 つの畳み込み層が特徴の抽出に使用され、最終的な特徴マップは 16 分の 1 に縮小されます。
2) デコーダ: デコーダは、エンコーダと対称的な拡張構造を提示し、セグメント化されたオブジェクトの詳細と空間次元を徐々に復元して、正確な位置決めを実現します。デコーダは 4 つのステージに分かれており、各ステージで入力特徴マップがアップサンプリングされた後、エンコーダ内の対応するスケールの特徴マップと連結され、2 つの畳み込み層を使用して特徴が抽出されます。特徴マップは 16 倍に拡大されます。
3) 分類モジュール: 3×3 のサイズの畳み込みを使用してピクセルを分類します。
例証します:
詳細: U-Net: 生物医学画像セグメンテーションのための畳み込みネットワーク
UNet の実装を図 2 に示します。ペット画像の場合、最初に畳み込みニューラル ネットワーク UNet network のエンコーダを使用して特徴 (4 つのダウンサンプリング ステージを含む) を抽出し、高レベルのセマンティック特徴マップを取得します。次に、デコーダを使用します ( 4 アップサンプリング ステージ) を使用して、特徴マップを元のサイズに復元します。トレーニング フェーズでは、モデルによって出力された予測マップとサンプルの実ラベル マップから損失関数を構築することによってモデルがトレーニングされます。推論ステージでは、モデルの予測マップが最終出力として使用されます。
図 2 ペット画像セグメンテーションの設計スキーム
全体的な U-Net ネットワーク フレームワーク コードの実装は次のとおりです。
# coding=utf-8
# 导入环境
import os
import random
import cv2
import numpy as np
from PIL import Image
from paddle.io import Dataset
import matplotlib.pyplot as plt
# 在notebook中使用matplotlib.pyplot绘图时,需要添加该命令进行显示
%matplotlib inline
import paddle
import paddle.nn.functional as F
import paddle.nn as nn
class UNet(nn.Layer):
# 继承paddle.nn.Layer定义网络结构
def __init__(self, num_classes=3):
# 初始化函数
super().__init__()
# 定义编码器
self.encode = Encoder()
# 定义解码器
self.decode = Decoder()
# 分类模块
self.cls = nn.Conv2D(in_channels=64, out_channels=num_classes, kernel_size=3, stride=1, padding=1)
def forward(self, x):
# 前向计算
logit_list = []
# 编码运算
x, short_cuts = self.encode(x)
# 解码运算
x = self.decode(x, short_cuts)
# 分类运算
logit = self.cls(x)
logit_list.append(logit)
return logit_list
エンコーダを定義する
上記では、モデルをエンコーダー、デコーダー、分類モジュールの 3 つの部分に分割しました。その中で、分類モジュールが実装されており、エンコーダー部分とデコーダー部分が個別に定義されています。
まずはエンコーダー部分です。ここでのエンコーダは、チャネル数を増やし、ピクチャのサイズを縮小し、単位構造を連続的に繰り返すことによって高レベルの意味特徴マップを取得します。
コードの実装は次のとおりです。
class ConvBNReLU(nn.Layer):
def __init__(self, in_channels, out_channels, kernel_size, padding='same'):
# 初始化函数
super().__init__()
# 定义卷积层
self._conv = nn.Conv2D(in_channels, out_channels, kernel_size, padding=padding)
# 定义批归一化层
self._batch_norm = nn.SyncBatchNorm(out_channels)
def forward(self, x):
# 前向计算
x = self._conv(x)
x = self._batch_norm(x)
x = F.relu(x)
return x
class Encoder(nn.Layer):
def __init__(self):
# 初始化函数
super().__init__()
# # 封装两个ConvBNReLU模块
self.double_conv = nn.Sequential(ConvBNReLU(3, 64, 3), ConvBNReLU(64, 64, 3))
# 定义下采样通道数
down_channels = [[64, 128], [128, 256], [256, 512], [512, 512]]
# 封装下采样模块
self.down_sample_list = nn.LayerList([self.down_sampling(channel[0], channel[1]) for channel in down_channels])
# 定义下采样模块
def down_sampling(self, in_channels, out_channels):
modules = []
# 添加最大池化层
modules.append(nn.MaxPool2D(kernel_size=2, stride=2))
# 添加两个ConvBNReLU模块
modules.append(ConvBNReLU(in_channels, out_channels, 3))
modules.append(ConvBNReLU(out_channels, out_channels, 3))
return nn.Sequential(*modules)
def forward(self, x):
# 前向计算
short_cuts = []
# 卷积运算
x = self.double_conv(x)
# 下采样运算
for down_sample in self.down_sample_list:
short_cuts.append(x)
x = down_sample(x)
return x, short_cuts
デコーダを定義する
チャネル数が最大に達し、高レベルの意味論的特徴マップが取得された後、ネットワーク構造は復号化操作を開始します。ここでのデコードとは、アップサンプリングを行ってチャンネル数を減らし、対応するピクチャサイズを徐々に大きくしていき、元の画像サイズに戻すことです。この実験では、バイリニア補間法を使用して画像のアップサンプリングを実現します。
具体的なコードは次のとおりです。
# 定义上采样模块
class UpSampling(nn.Layer):
def __init__(self, in_channels, out_channels):
# 初始化函数
super().__init__()
in_channels *= 2
# 封装两个ConvBNReLU模块
self.double_conv = nn.Sequential(ConvBNReLU(in_channels, out_channels, 3), ConvBNReLU(out_channels, out_channels, 3))
def forward(self, x, short_cut):
# 前向计算
# 定义双线性插值模块
x = F.interpolate(x, paddle.shape(short_cut)[2:], mode='bilinear')
# 特征图拼接
x = paddle.concat([x, short_cut], axis=1)
# 卷积计算
x = self.double_conv(x)
return x
# 定义解码器
class Decoder(nn.Layer):
def __init__(self):
# 初始化函数
super().__init__()
# 定义上采样通道数
up_channels = [[512, 256], [256, 128], [128, 64], [64, 64]]
# 封装上采样模块
self.up_sample_list = nn.LayerList([UpSampling(channel[0], channel[1]) for channel in up_channels])
def forward(self, x, short_cuts):
# 前向计算
for i in range(len(short_cuts)):
# 上采样计算
x = self.up_sample_list[i](x, short_cuts[-(i + 1)])
return x