Pytorch 딥 러닝 실용 자습서 (3) : UNet 모델 교육, 심층 분석!

Pytorch 딥 러닝 실습 자습서 (3) : UNet 모델 교육

이 기사  는 GitHub  https://github.com/Jack-Cherish/PythonPark 에 포함되었습니다. 기술 건조 제품 기사, 조직화 된 학습 자료 및 1 차 제조업체의 인터뷰 경험 공유가 있습니다. Star에 오신 것을 환영하고 개선하십시오.

I. 소개

이 기사는 Pytorch 딥 러닝 시맨틱 분할 튜토리얼 시리즈에 속합니다.

이 기사 시리즈의 내용은 다음과 같습니다.

  • Pytorch의 기본 사용
  • 시맨틱 분할 알고리즘 설명

추신 : 기사에 나오는 모든 코드는 내 github에서 다운로드 할 수 있습니다. 팔로우에 오신 것을 환영합니다. 스타 : 클릭하여보기

2. 프로젝트 배경

딥 러닝 알고리즘은 우리가 문제를 해결하는 방식에 지나지 않습니다. 훈련을 위해 어떤 종류의 네트워크를 선택할 것인지, 어떤 종류의 전처리를 할 것인지, 어떤 손실과 최적화 방법을 사용할 것인지는 모두 특정 작업에 따라 결정됩니다.

자, 오늘의 과제를 먼저 살펴 보겠습니다.

예, UNet 논문의 고전적인 작업 인 의료 이미지 분할입니다.

단순성과 사용 편의성 때문에 오늘날의 작업으로 선택되었습니다.

이 작업을 간략하게 설명하십시오. 애니메이션에 표시된대로 셀 구조 다이어그램을 제공하려면 각 셀을 서로 분리해야합니다.

훈련 데이터는 512x512 해상도로 30 개에 불과합니다.이 사진은 초파리의 전자 현미경 사진입니다.

글쎄, 작업 소개가 완료되고 훈련 모델이 시작됩니다.

 

세, UNet 교육

딥 러닝 모델을 훈련하기 위해 간단히 세 단계로 나눌 수 있습니다.

  • 데이터로드 : 데이터로드 방법, 태그 정의 방법 및 사용할 데이터 향상 방법이 모두이 단계에서 수행됩니다.
  • 모델 선택 :이 시리즈의 이전 기사에서 언급 한 UNet 네트워크 인 모델을 준비했습니다.
  • 알고리즘 선택 : 알고리즘 선택은 우리가 선택하는 손실과 사용할 최적화 알고리즘입니다.

각 단계는 더 일반적이며 오늘의 의료 이미지 분할 작업을 기반으로 설명을 확장 할 것입니다.

1. 데이터 로딩

이 단계에서는 많은 작업을 수행 할 수 있습니다. 솔직히 말하면 그림을로드하는 방법과 레이블을 정의하는 방법에 불과합니다. 알고리즘의 견고성을 높이거나 데이터 세트를 늘리기 위해 일부 데이터 향상 작업을 수행 할 수 있습니다.

데이터를 처리하고 있으므로 처리 방법을 결정하기 전에 데이터가 어떻게 보이는지 먼저 살펴 보겠습니다.

데이터가 준비되었습니다. 모두 여기 (Github) : 보려면 클릭하세요.

Github 다운로드 속도가 느린 경우 문서 끝에있는 Baidu 링크를 사용 하여 데이터 세트 다운로드 할 수 있습니다 .

데이터는 학습 세트와 테스트 세트 (각각 30 매)로 나뉘고 학습 세트에는 레이블이 있고 테스트 세트에는 레이블이 없습니다.

데이터로드 처리는 작업 및 데이터 집합에 따라 결정됩니다. 세분화 작업의 경우 너무 많은 처리를 할 필요는 없지만 데이터 양이 매우 적어 30 장에 불과하므로 일부 데이터 향상 방법을 사용하여 확장 할 수 있습니다. 우리의 데이터 세트.

Pytorch는 데이터로드를 용이하게하는 방법을 제공하며이 프레임 워크를 사용하여 데이터를로드 할 수 있습니다. 의사 코드를보십시오.

# ================================================================== #
#                Input pipeline for custom dataset                 #
# ================================================================== #

# You should build your custom dataset as below.
class CustomDataset(torch.utils.data.Dataset):
    def __init__(self):
        # TODO
        # 1. Initialize file paths or a list of file names. 
        pass
    def __getitem__(self, index):
        # TODO
        # 1. Read one data from file (e.g. using numpy.fromfile, PIL.Image.open).
        # 2. Preprocess the data (e.g. torchvision.Transform).
        # 3. Return a data pair (e.g. image and label).
        pass
    def __len__(self):
        # You should change 0 to the total size of your dataset.
        return 0 

# You can then use the prebuilt data loader. 
custom_dataset = CustomDataset()
train_loader = torch.utils.data.DataLoader(dataset=custom_dataset,
                                           batch_size=64, 
                                           shuffle=True)

표준 템플릿입니다.이 템플릿을 사용하여 데이터를로드하고 태그를 정의하며 데이터 향상을 수행합니다.

dataset.py 파일을 만들고 다음과 같이 코드를 작성합니다.

import torch
import cv2
import os
import glob
from torch.utils.data import Dataset
import random

class ISBI_Loader(Dataset):
    def __init__(self, data_path):
        # 初始化函数,读取所有data_path下的图片
        self.data_path = data_path
        self.imgs_path = glob.glob(os.path.join(data_path, 'image/*.png'))

    def augment(self, image, flipCode):
        # 使用cv2.flip进行数据增强,filpCode为1水平翻转,0垂直翻转,-1水平+垂直翻转
        flip = cv2.flip(image, flipCode)
        return flip
        
    def __getitem__(self, index):
        # 根据index读取图片
        image_path = self.imgs_path[index]
        # 根据image_path生成label_path
        label_path = image_path.replace('image', 'label')
        # 读取训练图片和标签图片
        image = cv2.imread(image_path)
        label = cv2.imread(label_path)
        # 将数据转为单通道的图片
        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        label = cv2.cvtColor(label, cv2.COLOR_BGR2GRAY)
        image = image.reshape(1, image.shape[0], image.shape[1])
        label = label.reshape(1, label.shape[0], label.shape[1])
        # 处理标签,将像素值为255的改为1
        if label.max() > 1:
            label = label / 255
        # 随机进行数据增强,为2时不做处理
        flipCode = random.choice([-1, 0, 1, 2])
        if flipCode != 2:
            image = self.augment(image, flipCode)
            label = self.augment(label, flipCode)
        return image, label

    def __len__(self):
        # 返回训练集大小
        return len(self.imgs_path)

    
if __name__ == "__main__":
    isbi_dataset = ISBI_Loader("data/train/")
    print("数据个数:", len(isbi_dataset))
    train_loader = torch.utils.data.DataLoader(dataset=isbi_dataset,
                                               batch_size=2, 
                                               shuffle=True)
    for image, label in train_loader:
        print(image.shape)

코드를 실행하면 다음 결과를 볼 수 있습니다.

코드를 설명하십시오.

__init__ 함수는이 클래스의 초기화 함수로 지정된 이미지 경로에 따라 모든 이미지 데이터를 읽고 self.imgs_path 목록에 저장합니다.

__len__ 함수는이 클래스가 인스턴스화 된 후 len () 함수를 통해 호출되는 데이터 양을 반환 할 수 있습니다.

__getitem__ 함수는 데이터 수집 함수입니다.이 함수에서는 데이터를 읽는 방법, 처리 방법을 작성할 수 있으며 여기에서 일부 데이터 전처리 및 데이터 향상을 수행 할 수 있습니다. 여기서 내 처리는 매우 간단합니다. 사진을 읽고 단일 채널 사진으로 처리하면됩니다. 동시에 라벨의 이미지 픽셀은 0과 255이므로 255로 나누어 0과 1이되어야합니다. 동시에 데이터 향상은 무작위로 수행되었습니다.

증가 기능은 정의 된 데이터 증가 기능입니다. 사용자가 무엇을하든 상관 없습니다. 여기서 간단한 회전 작업을 수행했습니다.

이 클래스에서는 데이터 세트를 섞기 위해 몇 가지 작업을 수행 할 필요가 없으며 배치 크기에 따라 데이터를 읽는 방법에 대해 걱정할 필요가 없습니다. 이 클래스를 인스턴스화 한 후 torch.utils.data.DataLoader 메서드를 사용하여 배치 크기의 크기를 지정하여 데이터 중단 여부를 결정할 수 있기 때문입니다.

Pytorch에서 제공하는 DataLoader는 매우 강력합니다. 데이터가 CUDA 메모리에로드되는지 여부에 관계없이 데이터를로드하는 데 사용되는 프로세스 수를 지정할 수도 있습니다.이 문서에서는 이에 대해 다루지 않으므로 다시 설명하지 않겠습니다.

2. 모델 선택

우리는 이미 모델을 선택했으며 이전 기사 " Pytorch Deep Learning Practical Tutorial (2) : UNet Semantic Segmentation Network "에서 설명한 UNet 네트워크 구조를 사용합니다 .

그러나 네트워크를 미세 조정해야합니다. 종이의 구조에 따라 모델 출력의 크기가 이미지 입력의 크기보다 약간 작아집니다. 종이의 네트워크 구조를 사용하는 경우 결과가 출력 된 후 크기 조정 작업을 수행해야합니다. 이 단계를 저장하기 위해 네트워크의 출력 크기가 그림의 입력 크기와 정확히 같도록 네트워크를 수정할 수 있습니다.

unet_parts.py 파일을 만들고 다음 코드를 작성합니다.

""" Parts of the U-Net model """
"""https://github.com/milesial/Pytorch-UNet/blob/master/unet/unet_parts.py"""

import torch
import torch.nn as nn
import torch.nn.functional as F

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=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)

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)

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 // 2, 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])

        x = torch.cat([x2, x1], dim=1)
        return self.conv(x)


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_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=True):
        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, 512)
        self.up1 = Up(1024, 256, bilinear)
        self.up2 = Up(512, 128, bilinear)
        self.up3 = Up(256, 64, 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)

이 조정 후 네트워크의 출력 크기는 그림의 입력 크기와 동일합니다.

3. 알고리즘 선택

Loss의 선택은 매우 중요하며 Loss의 선택은 데이터 피팅에서 알고리즘의 효과에 영향을 미칩니다.

손실의 선택은 또한 작업에 의해 결정됩니다. 오늘날 우리의 작업은 매우 간단한 이진 분류 작업 인 셀의 가장자리 만 분할하면되므로 BCEWithLogitsLoss를 사용할 수 있습니다.

BCEWithLogitsLoss 란 무엇입니까? BCEWithLogitsLoss는 두 범주의 교차 엔트로피를 계산하기 위해 Pytorch에서 제공하는 함수입니다.

공식은 다음과 같습니다.

내 머신 러닝 시리즈의 튜토리얼을 본 친구들은 로지스틱 회귀의 손실 함수 인이 공식에 익숙해야합니다. 분류를 위해 Sigmoid 함수의 임계 값이 [0,1] 인 기능을 사용합니다.

특정 수식 파생에 대해서는 여기에서 반복하지 않을 " Machine Learning Practical Tutorial (6) : Gradient Rise Algorithm in the Basics of Logistic Regression " 자습서의 기계 학습 시리즈를 볼 수 있습니다.

목표 함수, 즉 손실이 결정됩니다.이 목표를 최적화하는 방법은 무엇입니까?

가장 쉬운 방법은 익숙한 경사 하강 법 알고리즘을 사용하여 점진적으로 국소 극값에 접근하는 것입니다.

그러나 이런 종류의 간단한 최적화 알고리즘은 해결하기가 느리므로 최적의 솔루션을 찾는 데 노력이 필요합니다.

다양한 최적화 알고리즘은 실제로 경사 하강 법입니다. 예를 들어 가장 일반적인 SGD는 경사 하강 법을 기반으로 한 개선 된 확률 적 경사 하강 법 알고리즘입니다. Momentum은 기하 급수적 감쇠 형태로 과거 경사를 축적하기 위해 운동량 SGD를 도입합니다.

이러한 가장 기본적인 최적화 알고리즘 외에도 적응 ​​매개 변수에 대한 최적화 알고리즘이 있습니다. 이 유형의 알고리즘의 가장 큰 특징은 각 매개 변수의 학습률이 다르며 이러한 학습률은 전체 학습 과정에서 자동으로 조정되어 더 나은 수렴 효과를 얻을 수 있다는 것입니다.

이 기사에서는 적응 형 최적화 알고리즘 RMSProp를 선택합니다.

제한된 공간으로 인해 여기서는 확장하지 않을 것입니다.이 최적화 알고리즘 만 설명하는 것만으로는 충분하지 않습니다. RMSProp는 AdaGrad를 기반으로 개선 된 것이므로 RMSProp을 이해하려면 먼저 AdaGrad가 무엇인지 알아야합니다.

수정 된 Momentum + RMSProp 알고리즘으로 볼 수있는 유명한 Adam과 같은 RMSProp보다 고급 최적화 알고리즘도 있습니다.

간단히 말해서, 초보자의 경우 RMSProp이보다 고급화 된 적응 형 최적화 알고리즘이라는 사실 만 알면됩니다.

다음으로, UNet 학습을위한 코드 작성을 시작하고 train.py를 만들고 다음 코드를 작성할 수 있습니다.

from model.unet_model import UNet
from utils.dataset import ISBI_Loader
from torch import optim
import torch.nn as nn
import torch

def train_net(net, device, data_path, epochs=40, batch_size=1, lr=0.00001):
    # 加载训练集
    isbi_dataset = ISBI_Loader(data_path)
    train_loader = torch.utils.data.DataLoader(dataset=isbi_dataset,
                                               batch_size=batch_size, 
                                               shuffle=True)
    # 定义RMSprop算法
    optimizer = optim.RMSprop(net.parameters(), lr=lr, weight_decay=1e-8, momentum=0.9)
    # 定义Loss算法
    criterion = nn.BCEWithLogitsLoss()
    # best_loss统计,初始化为正无穷
    best_loss = float('inf')
    # 训练epochs次
    for epoch in range(epochs):
        # 训练模式
        net.train()
        # 按照batch_size开始训练
        for image, label in train_loader:
            optimizer.zero_grad()
            # 将数据拷贝到device中
            image = image.to(device=device, dtype=torch.float32)
            label = label.to(device=device, dtype=torch.float32)
            # 使用网络参数,输出预测结果
            pred = net(image)
            # 计算loss
            loss = criterion(pred, label)
            print('Loss/train', loss.item())
            # 保存loss值最小的网络参数
            if loss < best_loss:
                best_loss = loss
                torch.save(net.state_dict(), 'best_model.pth')
            # 更新参数
            loss.backward()
            optimizer.step()

if __name__ == "__main__":
    # 选择设备,有cuda用cuda,没有就用cpu
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    # 加载网络,图片单通道1,分类为1。
    net = UNet(n_channels=1, n_classes=1)
    # 将网络拷贝到deivce中
    net.to(device=device)
    # 指定训练集地址,开始训练
    data_path = "data/train/"
    train_net(net, device, data_path)

프로젝트를보다 명확하고 간결하게 만들기 위해 네트워크 구조 코드 인 unet_parts.py 및 unet_model.py 인 모델 관련 코드가 포함 된 모델 폴더를 만듭니다.

utils 폴더를 만들고 데이터로드 도구 dataset.py와 같은 도구 관련 코드를 배치합니다.

이 모듈 식 관리는 코드의 유지 관리 성을 크게 향상시킵니다.

Train.py는 프로젝트의 루트 디렉토리에 배치 할 수 있으며 코드에 대해 간략하게 설명합니다.

데이터가 30 개에 불과하므로 학습 세트와 검증 세트를 나누지 않고 학습 세트의 손실 값이 가장 낮은 네트워크 매개 변수를 최상의 모델 매개 변수로 저장합니다.

문제가 없으면 손실이 점차 수렴되고 있음을 알 수 있습니다.

네, 예측

모델을 학습 한 후이를 사용하여 테스트 세트에 미치는 영향을 확인할 수 있습니다.

프로젝트 루트 디렉터리에 predict.py 파일을 만들고 다음 코드를 작성합니다.

import glob
import numpy as np
import torch
import os
import cv2
from model.unet_model import UNet

if __name__ == "__main__":
    # 选择设备,有cuda用cuda,没有就用cpu
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    # 加载网络,图片单通道,分类为1。
    net = UNet(n_channels=1, n_classes=1)
    # 将网络拷贝到deivce中
    net.to(device=device)
    # 加载模型参数
    net.load_state_dict(torch.load('best_model.pth', map_location=device))
    # 测试模式
    net.eval()
    # 读取所有图片路径
    tests_path = glob.glob('data/test/*.png')
    # 遍历所有图片
    for test_path in tests_path:
        # 保存结果地址
        save_res_path = test_path.split('.')[0] + '_res.png'
        # 读取图片
        img = cv2.imread(test_path)
        # 转为灰度图
        img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        # 转为batch为1,通道为1,大小为512*512的数组
        img = img.reshape(1, 1, img.shape[0], img.shape[1])
        # 转为tensor
        img_tensor = torch.from_numpy(img)
        # 将tensor拷贝到device中,只用cpu就是拷贝到cpu中,用cuda就是拷贝到cuda中。
        img_tensor = img_tensor.to(device=device, dtype=torch.float32)
        # 预测
        pred = net(img_tensor)
        # 提取结果
        pred = np.array(pred.data.cpu()[0])[0]
        # 处理结果
        pred[pred >= 0.5] = 255
        pred[pred < 0.5] = 0
        # 保存图片
        cv2.imwrite(save_res_path, pred)

실행 후 data / test 디렉토리에서 예측 결과를 볼 수 있습니다.

Pytorch 딥 러닝 실습 자습서 (3) : UNet 모델 교육

그게 다야!

다섯, 드디어

  • 이 문서에서는 주로 데이터로드, 모델 선택 및 알고리즘 선택의 세 가지 모델 학습 단계를 설명합니다.
  • 이것은 간단한 예이며 일반적인 시력 작업을 훈련하는 것은 훨씬 더 복잡합니다. 예를 들어, 모델을 훈련 할 때 검증 세트에서 모델의 정확도에 따라 저장할 모델을 선택해야합니다. 손실 수렴 등을 쉽게 관찰 할 수 있도록 텐서 보드를 지원해야합니다.

좋아하고 읽고, 습관을 기르고, WeChat 공식 계정 에서 검색 【JackCui-AI】 인터넷에서 기어 다니는 스토커를 따라 가세요

 

추천

출처blog.csdn.net/c406495762/article/details/106349644