딥러닝 파이토치 강좌, U-Net

딥러닝 모델 중 하나인 U-Net은 주로 의료 이미지 분할에서 널리 사용되는 모델입니다. U-Net 모델은 이미지를 픽셀 단위로 분할해야 하는 작업에 특히 효과적입니다. 이 블로그 포스트에서는 U-Net의 개념, 구조, 그리고 파이토치를 이용한 구현 방법에 대해 상세히 알아보겠습니다.

1. U-Net의 역사

U-Net은 2015년 Olaf Ronneberger, Philipp Fischer, Thomas Becker에 의해 제안된 모델로, 의료 이미징 대회인 ISBI에서 우수한 성능을 보였습니다. U-Net은 일반적인 Convolutional Neural Network (CNN)의 아키텍처에서 출발하여, 특징 추출 및 세그멘테이션 작업을 동시에 수행할 수 있도록 설계되었습니다. 이러한 이유로 U-Net은 특수한 세그멘테이션 작업에서 높은 성능을 발휘합니다.

2. U-Net의 구조

U-Net의 구조는 크게 두 부분으로 나뉩니다: 다운샘플링(클리닝) 경로와 업샘플링(확장) 경로입니다. 다운샘플링 경로는 이미지를 점차적으로 줄이면서 특징을 추출하고, 업샘플링 경로는 점차적으로 이미지를 복원하면서 세그멘테이션 맵을 생성합니다.

2.1 다운샘플링 경로

다운샘플링 경로는 여러 개의 Convolutional 블록으로 이루어져 있습니다. 각 블록은 Convolutional 레이어와 활성화 함수, 풀링 레이어로 구성됩니다. 이렇게 데이터를 처리하면서 이미지의 크기가 줄어들고, 특징이 더욱 강조됩니다.

2.2 업샘플링 경로

업샘플링 경로에서는 업샘플링 레이어를 통해 이미지를 원래 크기로 복원하는 과정이 진행됩니다. 이때, 다운샘플링 경로에서 추출된 특징들과 병합하여 세분화된 정보를 제공합니다. 이를 통해 각 픽셀에 대한 예측 정확도를 높입니다.

2.3 Skip Connections

U-Net은 ‘Skip Connections’를 사용하여 다운샘플링 경로와 업샘플링 경로에서의 데이터를 연결합니다. 이를 통해 정보 손실을 최소화하고, 더욱 정교한 세그멘테이션 결과를 얻을 수 있습니다.

3. U-Net 구현하기 (PyTorch)

이제 PyTorch를 사용하여 U-Net 모델을 구현하겠습니다. 먼저 필요한 패키지를 설치하고 데이터를 준비합니다.

    
    # 필요한 패키지 임포트
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    from torchvision import transforms
    from torchvision import datasets
    from torch.utils.data import DataLoader
    
    

3.1 U-Net 모델 정의

아래는 U-Net 모델의 기본 구조를 정의한 코드입니다.

    
    class UNet(nn.Module):
        def __init__(self, in_channels, out_channels):
            super(UNet, self).__init__()

            self.encoder1 = self.conv_block(in_channels, 64)
            self.encoder2 = self.conv_block(64, 128)
            self.encoder3 = self.conv_block(128, 256)
            self.encoder4 = self.conv_block(256, 512)

            self.bottom = self.conv_block(512, 1024)

            self.decoder4 = self.upconv_block(1024, 512)
            self.decoder3 = self.upconv_block(512, 256)
            self.decoder2 = self.upconv_block(256, 128)
            self.decoder1 = self.upconv_block(128, 64)

            self.final_conv = nn.Conv2d(64, out_channels, kernel_size=1)

        def conv_block(self, in_channels, out_channels):
            return nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
                nn.ReLU(inplace=True),
                nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
                nn.ReLU(inplace=True)
            )

        def upconv_block(self, in_channels, out_channels):
            return nn.Sequential(
                nn.ConvTranspose2d(in_channels, out_channels, kernel_size=2, stride=2),
                nn.ReLU(inplace=True)
            )

        def forward(self, x):
            enc1 = self.encoder1(x)
            enc2 = self.encoder2(F.max_pool2d(enc1, kernel_size=2))
            enc3 = self.encoder3(F.max_pool2d(enc2, kernel_size=2))
            enc4 = self.encoder4(F.max_pool2d(enc3, kernel_size=2))

            bottleneck = self.bottom(F.max_pool2d(enc4, kernel_size=2))

            dec4 = self.decoder4(bottleneck)
            dec4 = torch.cat((dec4, enc4), dim=1)
            dec4 = self.conv_block(dec4.size(1), dec4.size(1))(dec4)

            dec3 = self.decoder3(dec4)
            dec3 = torch.cat((dec3, enc3), dim=1)
            dec3 = self.conv_block(dec3.size(1), dec3.size(1))(dec3)

            dec2 = self.decoder2(dec3)
            dec2 = torch.cat((dec2, enc2), dim=1)
            dec2 = self.conv_block(dec2.size(1), dec2.size(1))(dec2)

            dec1 = self.decoder1(dec2)
            dec1 = torch.cat((dec1, enc1), dim=1)
            dec1 = self.conv_block(dec1.size(1), dec1.size(1))(dec1)

            return self.final_conv(dec1)
    
    

3.2 모델 학습

이제 U-Net 모델을 학습할 준비가 되었습니다. 손실 함수와 최적화 알고리즘을 지정하고, 학습 데이터를 준비합니다.

    
    # 하이퍼파라미터 정의
    num_epochs = 25
    learning_rate = 0.001

    # 모델 생성
    model = UNet(in_channels=3, out_channels=1).cuda()
    criterion = nn.BCEWithLogitsLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    # 데이터 로드 및 전처리
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Resize((128, 128)),
    ])

    train_dataset = datasets.ImageFolder(root='your_dataset_path/train', transform=transform)
    train_loader = DataLoader(dataset=train_dataset, batch_size=16, shuffle=True)

    # 모델 학습
    for epoch in range(num_epochs):
        for images, masks in train_loader:
            images = images.cuda()
            masks = masks.cuda()

            # 순전파
            outputs = model(images)
            loss = criterion(outputs, masks)

            # 역전파 및 최적화
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
    
    

4. U-Net의 활용 사례

U-Net은 주로 의료 이미지 분야에서 사용되지만, 그 외에도 다양한 분야에서 활용될 수 있습니다. 예를 들어:

  • 의료 이미지 분석: CT 스캔, MRI 이미지 분할 등에서 조직, 종양 등을 정확히 식별.
  • 위성 이미지 분석: 지형 분할, 도시 계획 등.
  • 자율주행차: 도로, 장애물 검출 등.
  • 비디오 처리: 특정 객체 추적, 행동 인식 등.

5. 결론

U-Net은 그 구조 덕분에 다양한 이미지 분할 작업에서 뛰어난 성능을 보여줍니다. 이번 포스트에서는 U-Net의 기초부터 구현에 이르기까지 다루어 보았습니다. U-Net은 특히 의료 영상 분야에서 널리 사용되지만, 그 응용은 그 범위를 훨씬 뛰어넘습니다. 현재의 딥러닝 기술이 더욱 발전함에 따라, U-Net의 다양한 변형 및 같은 네트워크 구조를 활용한 새로운 접근 방식이 기대됩니다.

참고 자료

  • Ronneberger, Olaf, et al. “U-Net: Convolutional Networks for Biomedical Image Segmentation.” Medical Image Computing and Computer-Assisted Intervention. 2015.
  • Pytorch Documentation: https://pytorch.org/docs/stable/index.html