コラムディレクトリ: pytorch (画像分割UNet) クイック入門と実戦 - ゼロ、まえがき
pytorch クイック入門と実戦 - 1、知識の準備(要素の紹介)
pytorch クイック入門と実戦 - 2、深層学習の古典的なネットワーク開発
pytorch クイック導入と実際の戦闘 - 3、Unet による
pytorch の迅速な導入と実際の戦闘 - 4、ネットワークのトレーニングとテスト
上記の続き: pytorch クイックスタートと実戦 - 3、Unet 実装
ネットワークが実装された後、データを読み取り、パラメーターを返します。
セマンティック セグメンテーションの実装プロセス
セマンティック セグメンテーション実装プロセスの
トレーニング :
バッチ サイズに従って、データセット内のトレーニング サンプルとラベルが畳み込みニューラル ネットワークに読み込まれます。実際のニーズに応じて、最初にトレーニング画像とラベルをトリミングやデータ拡張などの前処理する必要があります。これは、深いネットワークのトレーニングに役立ち、収束プロセスを高速化し、過剰適合の問題を回避し、モデルの汎化能力を強化します。
確認:
エポックのトレーニング後、データセット内の検証サンプルとラベルを畳み込みニューラル ネットワークに読み取り、トレーニングの重みを読み込みます。書かれたセマンティック セグメンテーション インデックスに従って検証し、現在のトレーニング プロセスでインデックス スコアを取得し、対応する重みを保存します。一度トレーニングして検証する方法は、モデルのパフォーマンスをより適切に監視するためによく使用されます。
テスト:
すべてのトレーニングが終了したら、データセット内のテスト サンプルとラベルを畳み込みニューラル ネットワークに読み取り、保存されている最適な重み値をテスト用のモデルに読み込みます。テスト結果は、共通の指標スコアに基づいてネットワークのパフォーマンスを測定するものと、ネットワークの予測結果を画像として保存し、セグメンテーションの精度を直感的に感じるものの2種類に分かれています。
1. データの読み取り
セグメンテーションではなく分類の問題を行っている場合は、ここを参照するか、他の記事を参照してください。
ImageFolder
には、PyTorch に既製のデータ読み取りメソッドがあり、それは torchvision.datasets.ImageFolder です。この API は、主に分類問題のために keras の後に書かれており、各タイプのデータを同じフォルダーに置きます。たとえば、10 個のカテゴリがあります次に、大きなフォルダーの下に 10 個のサブフォルダーを作成します。各サブフォルダーには同じ種類のデータが含まれます。
画像のセグメンテーションは以下を参照してください。
1.1 Dataset クラスの継承
PyTorch は、主に torch.utils.data.Dataset クラスを通じて画像を読み取ります。パッケージ ガイド メソッドは次のとおりです。
from torch.utils.data import Dataset
次に、このクラスを継承して独自のデータ読み取りクラスを実装します。非常に簡潔で、
主に 2 つのメソッド: クラス実装メソッド __init__ と要素取得メソッド __getitem__
class myImageDataset(Dataset):
def __init__(self, inputs_root, labels_root):
self.files = sorted(glob.glob(f"{
inputs_root}\\*.png"))
self.files_using = sorted(glob.glob(f"{
labels_root}\\*.png"))
def __getitem__(self, index):
inputs = plt.imread(self.files[index % len(self.files)])
labels = plt.imread(self.files_using[index % len(self.files_using)])
return inputs, labels
def __len__(self):
return len(self.files)
簡単に読んでください:
- 初期化
inputs_root は入力データのルート ディレクトリ パス、labels_root はラベルのパスです。
glob は Python に付属のメソッドです。glob.glob は現在のディレクトリ内のファイルを走査できます。上記のステートメントはディレクトリ内のすべての png ファイルを走査し、sorted は入力とラベルを無秩序に 1 対 1 に対応させるためのものです。
Init の self は重要ではありません。パラメータを渡すときにそれをスキップしてください。
- アイテムを入手
Index は自己インクリメントするインデックスであり、メソッドの内部操作です。
インデックスを使用してパスを取得し、画像を読み取ってそこに戻れば完了です。
1.2 ローダー DataLoader メソッド
Dataset が写真を読み取った後、人工知能はどのようにしてそれらをバッチ処理できるのでしょうか?
Pytorch は、torch.utils.data クラスの DataLoader メソッドというメソッドも提供します。
from torch.utils.data import DataLoader
train_loader = DataLoader(
dataset=myImageDataset(inputs_root=in_folder + r"\train\inputs",
labels_root=in_folder + r"\train\labels"),
batch_size=16, # 一批有几个,一般为2的指数
shuffle=True,
num_workers=8 # 使用的CPU核心数量
)
データセットへのパスを詰め込み、データセットを取得し、データローダーにバッチで処理させることにより、バッチの数はbatch_sizeによって決定され、スレッドの数はパラメータnum_workerによって決定され、その他のパラメータを参照して検討することができます。自分で。
テストセットは同じです:
test_loader = DataLoader(
dataset=myImageDataset(inputs_root=in_folder + r"\test\inputs",
labels_root=in_folder + r"\test\labels"),
batch_size=1,
shuffle=True,
num_workers=opt.n_cpu
)
2. その他の初期化
2.1 グローバル変数の設定
トレーニング バッチ、バッチあたりのデータ量を定義し、学習率とその他のパラメーターを定義します (ニーズに応じて自分で設定します。兄弟からコピーしました)。
parser = argparse.ArgumentParser()
parser.add_argument("--epoch", type=int, default=0, help="epoch to start training from")
parser.add_argument("--n_epochs", type=int, default=100, help="number of epochs of training")
# parser.add_argument("--dataset_name", type=str, default="img_align_celeba", help="name of the dataset")
parser.add_argument("--batch_size", type=int, default=16, help="size of the batches")
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--decay_epoch", type=int, default=100, help="epoch from which to start lr decay")
parser.add_argument("--n_cpu", type=int, default=4, help="number of cpu threads to use during batch generation")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=100, help="interval between saving image samples")
parser.add_argument("--checkpoint_interval", type=int, default=-1, help="interval between model checkpoints")
opt = parser.parse_args()
2.2 ネットワーク設定、損失関数、オプティマイザオプティマイザ、テンソル変換テンソル
わざわざ Python でこのオプティマイザについて説明する必要はありません。ここでは adam が使用されています。
テンソルに関しては、テンソルは多線形関数であり、行列は特定の基底ベクトルのセットの下でのテンソルの表現です。
自分で他の人を検索してください。また、次の記事も読むことができます:テンソル tensor とは何かについての話
# Initialize net
net = AdUNet()
# Losses
loss = torch.nn.L1Loss()
# 数据传给显卡?
cuda = torch.cuda.is_available()
if cuda:
net = net.cuda()
gloss = loss.cuda()
if opt.epoch != 0:
# Load pretrained models
net.load_state_dict(torch.load("../saved_models/AdUnet_%d.pkl"))
# Optimizers
optimizer = torch.optim.Adam(net.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
Tensor = torch.cuda.FloatTensor if cuda else torch.Tensor
2.3 トレーニング
トレーニングの一般的なプロセス: 勾配のクリア、バックプロパゲーション、学習率の更新
optimizer.zero_grad() # 梯度归零:step之前要进行梯度归零
loss.backward() # 进行反向传播求出每个参数的梯度
optimizer.step() # 更新学习率
具体的な処理:
注釈を挿入: emmm 以下のコードでは、train_loader から読み込んだデータセットのデータに、入力 inputs とラベルに加えて、パス inputs_labels もありますよね? これは、後で結果マップを生成したいためです。結果マップに名前付き設定のパラメータを与えるために、以前に私のそのデータセットの __getitem__ も少し異なります、これは私のものです (実際、パスを選択して渡しただけです):
def __getitem__(self, index):
inputs_path = self.files[index % len(self.files)]
inputs = plt.imread(inputs_path)
labels = plt.imread(self.files_using[index % len(self.files_using)])
inputs_name = inputs_path.split("\\")[-1]
return inputs, labels, inputs_name
2.3.1 訓練データの読み込み、訓練データの変換
for i, data in enumerate(train_loader):
# 一个batch
inputs, labels, inputs_path = data
inputs = inputs.unsqueeze(1)
labels = labels.unsqueeze(1)
# 将这些数据转换成Variable类型
inputs, labels = Variable(inputs), Variable(labels)
device = torch.device("cuda" if cuda else "cpu")
inputs = inputs.to(device)
labels = labels.to(device)
読み込まれたグレースケール画像は 2 次元、つまり (120,240) であり、さらに、batch_size は 3 次元のみで、バッチサイズは 16 なので、読み取った入力は (16,120,240) ですが、実際にはネットワークが実行されているように見えます。次元、どうやってやるの?
実際、グレースケール画像は単一チャネル、つまり (120,240,1) なので、このチャネル 1 を手動で追加する必要があります。デバッグ プロセス中に、ネットワーク内のチャネルが通常 2 番目に配置されていることが判明したため、それを (16,1,120,240) に変更する必要があります。、アンスクイーズ解凍によって直接実現することも、2 番目の場所に直接追加することもできますinputs.unsqueeze(1)
。同様に、最初のプラスはinputs.unsqueeze(0)
入力とラベルが変換されたら、バックプロパゲーションを実行するために変数型に変換する必要があります (理由はわかりません。コピーしました)。その後、グラフィックス カードを使用してデータをグラフィックス カードに転送します。次に、それをグラフィックス カードなしで CPU に渡します。
2.3.2 トレーニングネットワーク
- 勾配はゼロに戻ります (次のサイクルでは最初は 0 です)
- ネットワークトレーニング
- ネットアウトの結果を取得する
- 損失関数損失を計算する
- 誤差逆伝播法
- 学習率を更新するには、
それを理解する必要はなく、ただコピーするだけです。
optimizer.zero_grad() # 梯度归零:step之前要进行梯度归零
net.train()
netout = net(inputs)
# Total loss
gloss = loss(netout, labels)
gloss.backward() # 进行反向传播求出每个参数的梯度
optimizer.step() # 更新学习率
2.4 テスト
評価基準 ssim、psnr、rmse を初期化します。
total_s = 0 # ssim
total_p = 0 # psnr
total_r = 0 # rmse
skimage には次のライブラリが付属しています。
from skimage.measure import compare_ssim as ssim
from skimage.measure import compare_psnr as psnr
from skimage.measure import compare_mse as mse
- テストデータを読み取る
- 統一されたデータ形式
- 変数変換
- グラフィックスカードに送信
- これはテストとしてマークされています (バックプロパゲーションには参加しません。最初の行と同じ効果があり、直接追加するだけです)。
- ネットワークに送信して結果 netout を取得します
- 2 次元まで (グレースケール画像は 2 次元)
- ラベルと比較して画像を評価する
- 以上
with torch.no_grad():
for inputs, labels, inputs_path in test_loader:
# 一个batch
inputs = inputs.unsqueeze(1)
# labels = labels.unsqueeze(0)
# 将这些数据转换成Variable类型
inputs, labels = Variable(inputs), Variable(labels)
device = torch.device("cuda" if cuda else "cpu")
inputs = inputs.to(device)
labels = labels.squeeze(0).cpu().numpy()
# optimizer.zero_grad()
net.eval()
netout = net(inputs)
img_out = netout.squeeze(1)
img_out = img_out.squeeze(0)
img_out = img_out.cpu().numpy()
# Total loss
# gloss = loss(netout, labels)
# print(netout.shape)
s = ssim(labels, img_out)
p = psnr(labels, img_out)
r = sqrt(mse(labels, img_out))
total_s += float(s.item())
total_p += float(p.item())
total_r += float(r)
3 全体のコード:
import argparse
import os
import sys
import torch
from torch.autograd import Variable
from torch.utils.data import DataLoader
from main.AdUNet import AdUNet
from main.datasets import *
from skimage.measure import compare_ssim as ssim
from skimage.measure import compare_psnr as psnr
from skimage.measure import compare_mse as mse
from math import sqrt
from other.mkdir import mkdir
mkdir("../saved_models")
parser = argparse.ArgumentParser()
parser.add_argument("--epoch", type=int, default=0, help="epoch to start training from")
parser.add_argument("--n_epochs", type=int, default=100, help="number of epochs of training")
# parser.add_argument("--dataset_name", type=str, default="img_align_celeba", help="name of the dataset")
parser.add_argument("--batch_size", type=int, default=16, help="size of the batches")
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--decay_epoch", type=int, default=100, help="epoch from which to start lr decay")
parser.add_argument("--n_cpu", type=int, default=4, help="number of cpu threads to use during batch generation")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=100, help="interval between saving image samples")
parser.add_argument("--checkpoint_interval", type=int, default=-1, help="interval between model checkpoints")
opt = parser.parse_args()
in_folder = r"..\data\pigs"
train_folder = in_folder + "\\train"
# Initialize net
net = AdUNet()
# Losses
loss = torch.nn.L1Loss()
# 数据传给显卡?
cuda = torch.cuda.is_available()
if cuda:
net = net.cuda()
gloss = loss.cuda()
if opt.epoch != 0:
# Load pretrained models
net.load_state_dict(torch.load("../saved_models/AdUnet_%d.pkl"))
# Optimizers
optimizer = torch.optim.Adam(net.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
Tensor = torch.cuda.FloatTensor if cuda else torch.Tensor
train_loader = DataLoader(
dataset=ImageDataset(inputs_root=in_folder + r"\train\inputs",
labels_root=in_folder + r"\train\labels"),
batch_size=opt.batch_size,
shuffle=True,
num_workers=opt.n_cpu
)
test_loader = DataLoader(
dataset=ImageDataset(inputs_root=in_folder + r"\test\inputs",
labels_root=in_folder + r"\test\labels"),
batch_size=1,
shuffle=True,
num_workers=opt.n_cpu
)
# ----------
# Training
# ----------
def train(epoch):
for i, data in enumerate(train_loader):
# 一个batch
inputs, labels, inputs_path = data
inputs = inputs.unsqueeze(1)
labels = labels.unsqueeze(1)
# 将这些数据转换成Variable类型
inputs, labels = Variable(inputs), Variable(labels)
device = torch.device("cuda" if cuda else "cpu")
inputs = inputs.to(device)
labels = labels.to(device)
# ------------------
# Train net
# ------------------
optimizer.zero_grad() # 梯度归零:step之前要进行梯度归零
net.train()
netout = net(inputs)
# Total loss
gloss = loss(netout, labels)
gloss.backward() # 进行反向传播求出每个参数的梯度
optimizer.step() # 更新学习率
##
# --------------
# Log Progress
# --------------
sys.stdout.write(
"\n[Epoch %d/%d] [Batch %d/%d] [D loss: %f]"
% (epoch, opt.n_epochs, i, len(train_loader), gloss.item())
)
with open(r"../data/pigs/loss.txt", "a") as f1:
f1.write("\n[Epoch %d/%d] [Batch %d/%d] [D loss: %f]"
% (epoch, opt.n_epochs, i, len(train_loader), gloss.item()))
f1.close()
def test():
total_s = 0 # ssim
total_p = 0 # psnr
total_r = 0 # rmse
with torch.no_grad():
for inputs, labels, inputs_path in test_loader:
# 一个batch
inputs = inputs.unsqueeze(1)
# labels = labels.unsqueeze(0)
# 将这些数据转换成Variable类型
inputs, labels = Variable(inputs), Variable(labels)
device = torch.device("cuda" if cuda else "cpu")
inputs = inputs.to(device)
labels = labels.squeeze(0).cpu().numpy()
# optimizer.zero_grad()
net.eval()
netout = net(inputs)
img_out = netout.squeeze(1)
img_out = img_out.squeeze(0)
img_out = img_out.cpu().numpy()
# Total loss
# gloss = loss(netout, labels)
# print(netout.shape)
s = ssim(labels, img_out)
p = psnr(labels, img_out)
r = sqrt(mse(labels, img_out))
total_s += float(s.item())
total_p += float(p.item())
total_r += float(r)
print('\n|Epoch %d/%d| |Average SSIM: %f | |Average PSNR: %f| |Average RMSE: %f| '
% ((epoch + 1), opt.n_epochs, total_s / len(test_loader), total_p / len(test_loader),
total_r / len(test_loader)))
with open(r"../data/pigs/test_parameters.txt", "a") as f:
f.write("|Epoch %d/%d| |Average SSIM: %f | |Average PSNR: %f| |Average RMSE: %f| \r\n"
% ((epoch + 1), opt.n_epochs, total_s / len(test_loader), total_p / len(test_loader),
total_r / len(test_loader)))
f.close()
return total_s / len(test_loader), total_p / len(test_loader), total_r / len(test_loader)
if __name__ == '__main__':
for epoch in range(opt.epoch, opt.n_epochs):
# 一个epoch
train(epoch)
# Save model checkpoints
# torch.save(net.state_dict(), "saved_models/generator_%d.pkl" % epoch)
torch.save(net, "../saved_models/AdUnet_%d.pkl" % epoch)
epoch_s, epoch_p, epoch_r = test()
先ほどの net クラスと dataset クラスに加えて、合計 3 つのファイルがあります。このファイルを実行するだけで完了です。
「へへ、そういうことだよ」
4. Q&A
①ヘッダー関数のimporのmkdir
説明を忘れていましたが、別のフォルダーの下に書いた mkdir.py で、内容は次のとおりです。
import os
def mkdir(path):
if os.path.exists(path):
return
else:
os.mkdir(path)
主な理由は、os.mkdir がファイルをチェックしないためで、通常使用する場合は、最初に os.path.exists でファイルが存在するかどうかを確認し、存在しない場合は作成する必要があります
。その他、自分でコンパイルしたツールキットに入れてください。独自のホイールを作成したい場合は、独自のツールキットを作成するのが良いです。また、独自のツールキットに myUtils などの名前を付けることもできます。