Deep Learning

Deep Learning Study (6) / 24.09.10

JHDeveloper 2024. 9. 10. 22:32

논문 읽을 때 팁

  1. 초록 읽기
  2. 결론 읽기
  3. Figure(그림) 파악

UNet 이란?

의료 영상 분야에서 세포 이미지 등을 분할하기 위해 개발된 합성곱 신경망(CNN) 구조

기본 구조

인코더-디코더(encoder-decoder) 기반 모델

  • 대칭 구조: 인코더 (풀링을 통해 이미지 크기를 줄이며 특징을 추출) + 디코더 (업샘플링과 컨볼루션을 통해 이미지의 크기를 다시 늘림)
  • 스킵 연결 (Skip Connection): 인코더에서 추출한 특징을 디코더의 상응하는 층에 직접 연결 → 디코더에서 세밀한 정보를 보존 가능 + 이미지의 정확한 분할에 도움을 줌.
  • 기본 구조 코드
import tensorflow as tf
from tensorflow.keras import layers, models

def unet_model(input_size=(128, 128, 1)):
    # 입력층
    inputs = layers.Input(input_size)

    # 다운샘플링 (Contracting Path)
    # 1단계
    conv1 = layers.Conv2D(64, 3, activation='relu', padding='same')(inputs)
    # Conv2D(커널(채널,필터) 개수, 커널 크기, 활성화 함수, padding)
    conv1 = layers.Conv2D(64, 3, activation='relu', padding='same')(conv1)
    pool1 = layers.MaxPooling2D(pool_size=(2, 2))(conv1)

    # 2단계
    conv2 = layers.Conv2D(128, 3, activation='relu', padding='same')(pool1)
    conv2 = layers.Conv2D(128, 3, activation='relu', padding='same')(conv2)
    pool2 = layers.MaxPooling2D(pool_size=(2, 2))(conv2)

    # 3단계
    conv3 = layers.Conv2D(256, 3, activation='relu', padding='same')(pool2)
    conv3 = layers.Conv2D(256, 3, activation='relu', padding='same')(conv3)
    pool3 = layers.MaxPooling2D(pool_size=(2, 2))(conv3)

    # 4단계
    conv4 = layers.Conv2D(512, 3, activation='relu', padding='same')(pool3)
    conv4 = layers.Conv2D(512, 3, activation='relu', padding='same')(conv4)
    pool4 = layers.MaxPooling2D(pool_size=(2, 2))(conv4)

    # Bottleneck
    conv5 = layers.Conv2D(1024, 3, activation='relu', padding='same')(pool4)
    conv5 = layers.Conv2D(1024, 3, activation='relu', padding='same')(conv5)

    # 업샘플링 (Expanding Path)
    # 4단계
    up6 = layers.Conv2DTranspose(512, 2, strides=(2, 2), padding='same')(conv5)
    merge6 = layers.concatenate([conv4, up6], axis=3)
    conv6 = layers.Conv2D(512, 3, activation='relu', padding='same')(merge6)
    conv6 = layers.Conv2D(512, 3, activation='relu', padding='same')(conv6)

    # 3단계
    up7 = layers.Conv2DTranspose(256, 2, strides=(2, 2), padding='same')(conv6)
    merge7 = layers.concatenate([conv3, up7], axis=3)
    conv7 = layers.Conv2D(256, 3, activation='relu', padding='same')(merge7)
    conv7 = layers.Conv2D(256, 3, activation='relu', padding='same')(conv7)

    # 2단계
    up8 = layers.Conv2DTranspose(128, 2, strides=(2, 2), padding='same')(conv7)
    merge8 = layers.concatenate([conv2, up8], axis=3)
    conv8 = layers.Conv2D(128, 3, activation='relu', padding='same')(merge8)
    conv8 = layers.Conv2D(128, 3, activation='relu', padding='same')(conv8)

    # 1단계
    up9 = layers.Conv2DTranspose(64, 2, strides=(2, 2), padding='same')(conv8)
    merge9 = layers.concatenate([conv1, up9], axis=3)
    conv9 = layers.Conv2D(64, 3, activation='relu', padding='same')(merge9)
    conv9 = layers.Conv2D(64, 3, activation='relu', padding='same')(conv9)
    
    # 출력층
    conv10 = layers.Conv2D(1, 1, activation='sigmoid')(conv9)

    model = models.Model(inputs=inputs, outputs=conv10)

    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

    return model

세부 구조

  • 인코더(Down-sampling path):
    • 합성곱 층과 최대 풀링 층으로 구성, 점진적으로 입력 이미지의 공간 해상도를 줄이면서 중요한 특징 추출
    • 합성곱 층 : 3x3 필터, ReLU 활성화 함수 적용
    • 풀링 층 : 2x2 최대 풀링(MaxPooling)을 사용해 해상도를 절반으로 줄임 → 다운샘플링
    *2x2 최대 풀링이 다른 크기의 풀링에 비해 제일 적절
  • 디코더(Up-sampling path):
    • 인코더에서 축소된 이미지의 해상도 복원하는 역할, 이때 중요한 것은 인코더에서 추출된 세부 정보를 보존하면서 복원하는 것입니다.
    • 디코더에서 **업샘플링(Up-sampling)**을 수행하고, 그 다음에 합성곱 층을 통해 이미지를 재구성합니다.
    • 각 업샘플링 단계에서 인코더의 대응 층에서 나온 특징 맵을 **스킵 연결(skip connection)**을 통해 디코더로 전달하여, 원래 이미지의 세부 정보가 보존되도록 합니다.
  • 스킵 연결(Skip Connections):
    • 인코더의 각 단계에서 추출한 특징을 디코더의 대응하는 단계로 연결하여, 인코더가 추출한 세부 정보가 디코더에서 다시 활용될 수 있도록 합니다. 이를 통해 디코더는 더 높은 해상도의 세부 정보를 잘 복원할 수 있습니다.

input 이미지 : (1, 572**572) 사이즈 이미지*

*함수이름 옆에 적인 건 input 값 / Conv (축/커널/채널 수, 가로**세로) → 축/커널/채널 수는 내가 정한 대로

*이 과정은 코드가 아닌, 그냥 그림 과정 이해를 위한 것일 뿐! + Conv에서 padding 값을 same이 아닌 valid로 해두었기 때문에 가로, 세로 각각 2씩 줄어들게 됨.

  • Conv 함수 역할 : (보통 2배로 늘림 + 보통 각 단계에서 2번 사용하는 게 보편적) 다운샘플링에서 원하는 채널 개수만큼 생성해서(특성 맵에 반영하고 싶은 개수 만큼) 특성 추출하기 → 특성 맵 생성 (padding = ‘same’ 또는 ‘valid’로 설정 ← 특성 맵 크기 결정)
    • same으로 하는 경우 : 입력 특성맵과 크기가 같아짐
    • → output_size = input_size
    • valid로 하는 경우 : 입력 특성맵보다 크기가 작아짐
    • → output_size = input_size - kernel_size + 1
  • MaxPooling 함수 역할 : 채널이 늘어났기 때문에 연산 복잡성을 줄이고 계산의 편리함을 위해 특성 맵의 가로, 세로 사이즈 줄여줌.
    • 손상된 정보 : 이미지의 중앙부보다는 가장자리에 있는 세부적인 공간 정보위치 정보를 주로 손실 ← 업샘플링과 skip connection을 통해 복구됨.
  • ConvTranspose 함수 역할 : 다운샘플링에서 maxpooling 으로 인해 줄어들었던 가로, 세로를 늘려줌.
    • same으로 하는 경우 : 입력 특성맵의 정수배 만큼 크기를 키워줌
    • → output_size = input_size * stride
    • valid로 하는 경우 : 입력 특성맵보다 크기가 더 커짐.
    • → output_size = (input_size - 1) * stride + kernel_size
  • concatenate 함수 역할 : 다운샘플링에서 얻은 특징 맵과 결합하여 세부적인 정보를 복구 (채널 수 crop) → skip connection 을 도와줌.
    • 다운샘플링 단계에서 Conv 층을 거쳐 채널 수가 2배로 증가했던 거를 다시 crop 할 필요가 있음. (crop의 대상 = 작은 특성 맵의 크기에 맞춰 가장자리 부분 제거)

[다운샘플링]

1단계 : (1, 572x572) 입력 → Conv → (64, 570x570) 출력 → Conv → (64, 568x568) 출력 → maxPooling → (64, 284x284) 출력 및 2단계 Conv 입력

2단계 : (64, 284x284) 입력 → Conv → (128, 282x282) 출력 → Conv → (128, 280x280) 출력 → maxPooling → (128, 140x140) 출력 및 3단계 Conv 입력

3단계 : (128, 140x140) 입력 → Conv → (256, 138x138) 출력 → Conv → (256, 136x136) 출력 → maxPooling → (256, 68x68) 출력 및 4단계 Conv 입력

4단계 : (256, 68x68) 입력 → Conv → (512, 66x66) 출력 → Conv → (512, 64x64) 출력 → maxPooling → (512, 32x32) 출력

*Conv 층에서 padding은 valid로 설정, 채널 수는 2배로 증가

 

[Bottleneck]

(512, 32x32) 입력 → Conv → (1024, 30x30) 출력 → Conv → (1024, 28x28) 출력 및 업샘플링에 입력

*Bottleneck에서 MaxPooling을 하지 않는 이유 : 이미지 줄이는 작업 끝나고 추상적인 특징 학습하는 단계(여기에서 만들어진 특징 맵은 이미 액기스만 모아둔 거임) 에서 굳이 크기를 줄이는 연산인 MaxPooling이 필요없음.

 

[업샘플링]

4단계 : (1024, 28x28) 입력 -> ConvTranspose -> (1024, 56x56) 출력 → concatenate (다운샘플링 4단계에서의 Conv의 적용 최종 결과물인 (512, 64x64)참고) -> (512, 56x56) 출력 -> Conv -> (512, 54x54) -> Conv → 결과물 (512, 52x52) 출력 및 다음 3단계 ConvTranspose 입력

3단계 : (512, 52x52) 입력 → ConvTranspose → (512, 104x104) 출력 → concatenate → (256, 104x104) 출력 → Conv → (256, 102x102) 출력 → Conv → (256, 100x100) 출력 및 다음 2단계 ConvTranspose 입력

2단계 : (256, 100x100) 입력 → ConvTranspose → (256, 200x200) 출력 → concatenate → (128, 200x200) 출력 → Conv → (128, 198x198) 출력 → Conv → (128, 196x196) 출력 및 다음 1단계 ConvTranspose 입력

1단계 : (128, 196x196) 입력 → ConvTranspose → (128, 392x392) 출력 → concatenate → (64, 392x392) 출력 → Conv → (64, 390x390) 출력 → Conv → (64, 388x388) 출력 → Conv (11) → 최종값 feature map인 (2, 388x388) 출력*

 

*ConvTranspose 층에서 padding은 same으로 설정, stride는 2

*concatenate 에서 참고하는 [다운샘플링]에서의 결과물 = [다운샘플링]의 해당 단계에서의 MaxPooling함수에 들어가는 입력 특성 맵 (2번의 Conv 작업이 끝나고 난 후의 결과물)

 

정보 출처

https://velog.io/@lighthouse97/UNet의-이해

 

UNet의 이해

참고1참고2이미지 세그멘테이션(image segmentation)은 이미지의 모든 픽셀이 어떤 카테고리(예를 들면 자동차, 사람, 도로 등)에 속하는지 분류하는 것을 말한다.이미지 전체에 대해 단일 카테고리를

velog.io

 

 


 

GAN 이란?

실제에 가까운 이미지나 사람이 쓴 것과 같은 글 등 여러 가짜 데이터들을 생성하는 모델

GAN과의 연관성

UNet 구조는 GAN의 일부로 사용될 수 있음. UNet은 주로 이미지 분할 작업에 사용되며, GAN 내에서 생성자로 활용될 수 있습니다. UNet과 GAN이 결합되면, UNet의 정교한 이미지 변환 능력과 GAN의 강력한 생성 능력이 결합하여 보다 정확하고 현실적인 이미지 생성이 가능

U-Net 구조GAN의 **생성자(Generator)**로 사용된다면, U-Net 구조의 **최종 출력(feature map)**이 바로 생성자가 만들어낸 이미지로 간주 → 출력된 feature map은 생성된 가짜 데이터(예: 이미지)가 되며, 이 결과는 Discriminator로 입력되어 진짜와 가짜를 구분하는 작업에 사용