코딩 및 기타/이미지

U-Net

정지홍 2025. 1. 15. 16:18

convolution 연산에 대한 공식...
U-Net의 첫번째 컨볼루션 결과. ( 첫번째는 패딩이 없이 수행 )
U-Net의 두번째 레이어에서의 convolution 결과.


max pooling에 대한 식

  • 1. 입력 이미지 572x572x1에 대해서 conv 3x3 , ReLU를 한다. ( 필터는 64 개 )
    • 64개의 필터란?
      • 해당 레이어에서 64개의 서로 다른 특성feature를 학습하는 것을 의미.
      • 각 필터는 입력 이미지에서 특정한 패턴을 감지하는 역할을 함.
        • 즉, 각각 64개의 필터는 서로 다른 특징을 학습하며, 특성맵을 만든다.
          결과적으로 64개의 서로다른 특성맵이 출력됨.
        • 감지하는 패턴 : 엣지 , 모서리 , 질감 , 구조적 패턴 등등 
    • ReLU를 사용하여 convolution연산후 음수값들을 0으로 만듬.
      • 왜 ReLU?
        • 비선형성 추가 및 기울기 소실 문제 해결
  • 2. 위의 결과로 570x570x64가 나온다. 그리고 똑같은 과정을 거쳐서 568x568x64를 만든다.
  • 3. max pooling 2x2를 적용하여 284x284x64가 된다.
  • 4. 다시 convolution 3x3과 ReLu를 하여 282x282x128을 나오게 한다.
    • 여기서 필터는 128개를 사용.
  • 5. 다시 convolution 3x3 ReLu를 하여 280x280x128을 나오게 한다.
  • 6. 그리고 다시 max pooling을 한다. 그러면 140 x 140 x 128 이다.
  • 7. 다시 convolution 3x3과 ReLu를 하여 138x138x256을 나오게 한다. 여기서는 256개의 필터 사용.
  • 8. 다시 convolution 3x3과 ReLu를 하여 136x136x256을 나오게 한다.
  • 9. 그리고 다시 max pooling을 한다. 그러면 68 x 68 x 256 이다
  • 10. 다시 convolution 3x3과 ReLu를 하여 66 x 66 x 512을 나오게 한다. 여기서는 512개의 필터 사용.
  • 11. 다시 convolution 3x3과 ReLu를 하여 64 x 64 x 512을 나오게 한다. 
  • 12. max pool 2x2해서 32 x 32 x 512 를 나오게 한다.
  • 13. 그리고 conv 3x3 relu해서  30 x 30 x 1024만들고, 한번 더 해서 28 x 28 x 1024만든다.
  • 14. 그리고 transposed convolution울 통하여 56x56x1024로 만들고 이때 64x64x512를 copy and crop한다.
  • 15 지금까지를 반복하여 388x388x64를 만든다. 그리고 마지막에 비선형 예측을 하기 위해서 1 x 1 convolution을 수행한다.

 

 

 

U-Net

  • 인코더-디코더 기반의 모델이다.
    • 인코딩 단계
      • 입력 이미지의 특징을 포착할 수 있게, 채널수를 늘리면서 차원을 축소해나간다.
    • 디코딩 단계
      • 저차원으로 인코딩된 정보만을 이용해서, 채널수는 줄이면서 차원을 확대해나간다.
        • 하지만 이렇게 차원이 축소된 인코딩 종보만을 이용하는건, 이미지 객체에 대한 위치 정보를 잃게되며 복원 시에도 완벽하게 복원하지 못한다.
  • 위의 인코딩-디코딩의 단점을 해결하기 위해서, 인코딩 단계의 각 레이어에서 얻은 특징을 디코딩 단계의 각 레이어에 한치는 concatenation방법을 사용한다.
    • 이렇게 인코더와 디코더 레이어의 직접 연결을 skip connection이라고 한다.
  • 손실함수는 주로 dice coefficient를 사용한다.
def build_unet( inputs , ker_init , dropout ):
    
    conv1 = Conv2D( 32 , 3 , activation = 'relu' , padding = 'same' , kernel_initializer = ker_init )( inputs )
    conv1 = Conv2D( 32 , 3 , activation = 'relu' , padding = 'same' , kernel_initializer = ker_init )( conv1 )

    pool = MaxPooling2D( pool_size = ( 2 , 2 ) )( conv1 )
# -----------------------------------------------------------------------------------------------------------
    conv = Conv2D( 64 , 3 , activation = 'relu' , padding = 'same' , kernel_initializer = ker_init )( pool )
    conv = Conv2D( 64 , 3 , activation = 'relu' , padding = 'same' , kernel_initializer = ker_init )( conv )
    
    pool1 = MaxPooling2D( pool_size = ( 2 , 2 ) )( conv )
# -----------------------------------------------------------------------------------------------------------
    conv2 = Conv2D( 128 , 3 , activation = 'relu' , padding = 'same' , kernel_initializer = ker_init )( pool1 )
    conv2 = Conv2D( 128 , 3 , activation = 'relu' , padding = 'same' , kernel_initializer = ker_init )( conv2 )

    pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
# -----------------------------------------------------------------------------------------------------------
    conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(pool2)
    conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(conv3)

    pool4 = MaxPooling2D(pool_size=(2, 2))(conv3)
# -----------------------------------------------------------------------------------------------------------
    conv5 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(pool4)
    conv5 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(conv5)
    
    drop5 = Dropout(dropout)(conv5)
# -----------------------------------------------------------------------------------------------------------
    up7 = Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(UpSampling2D(size = (2,2))(drop5))
    merge7 = concatenate([conv3,up7], axis = 3)
    conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(merge7)
    conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(conv7)

    up8 = Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(UpSampling2D(size = (2,2))(conv7))
    merge8 = concatenate([conv2,up8], axis = 3)
    conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(merge8)
    conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(conv8)

    up9 = Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(UpSampling2D(size = (2,2))(conv8))
    merge9 = concatenate([conv,up9], axis = 3)
    conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(merge9)
    conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(conv9)
    
    up = Conv2D(32, 2, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(UpSampling2D(size = (2,2))(conv9))
    merge = concatenate([conv1,up], axis = 3)
    conv = Conv2D(32, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(merge)
    conv = Conv2D(32, 3, activation = 'relu', padding = 'same', kernel_initializer = ker_init)(conv)
    
    conv10 = Conv2D(4, (1,1), activation = 'softmax')(conv)
    
    return Model(inputs = inputs, outputs = conv10)
input_layer = Input( (IMG_SIZE , IMG_SIZE ,  2 ) )

model = build_unet(input_layer, 'he_normal', 0.2)
model.compile(loss="categorical_crossentropy", optimizer=keras.optimizers.Adam(learning_rate=0.001), metrics = ['accuracy',tf.keras.metrics.MeanIoU(num_classes=4), dice_coef, precision, sensitivity, specificity, dice_coef_necrotic, dice_coef_edema ,dice_coef_enhancing] )



plot_model(model, 
           show_shapes = True,
           show_dtype=False,
           show_layer_names = True, 
           rankdir = 'TB', 
           expand_nested = False, 
           dpi = 70)

 

 

 

 

  • Dice 계수는 두 집합 A와 B의 유사도를 측정한다.
  • Dice 계수가 1이면, 두 집합이 완전히 일치하는 경우다.
  • Dice 계수가 0일때는 두 집합이 전혀 안겹칠때이다.
    • 0~1 사이의 두 값: 값이 클수록 두 집합이 비슷하다는 것.
  • .1 - Dice 계수를 해주면 손실 함수로 이용하는 것이 가능하다.
import keras.backend as K
# dice coefficient를 기반으로 한 손실함수를 계산하는 함수
def dice_coef( y_true , y_pred , smooth = 1.0 ): # 각각 실제값 , 모델의 예측값 , 분모가 0이되지 않게 더해주는 작은 값이다.
    class_num = 4
    for i in range( class_num ): # 각 텐서의 형태는 ( 배치사이즈 , 높이 , 폭 , class_num )이다.
        y_true_f = K.flatten( y_true[ : , : , : , i ] ) # i번째 클래스에 해당되는 실제 레이블 추출. 그리고 1차원 배열로 평탄화
        y_pred_f = K.flatten( y_pred[ : , : , : , i ] ) # i번째 클래스에 해당하는 모델의 예측값 추출. 그리고 1차원 배열로 평탄화
        intersection = K.sum( y_true_f * y_pred_f ) # 두 텐서의 요소별로 곱셈을 수행하고 모두 더해줌. 이는 예측값과 실측값이 일치하는 부분의 총합임.
        loss = ( ( 2. * intersection + smooth ) / ( K.sum( y_true_f ) + K.sum( y_pred_f ) + smooth ) ) # dice coef계산
        if i==0:
            total_loss = loss # 첫번째 클래스에 대해서는 total_loss에 loss값 할당
        else:
            total_loss = total_loss + loss # 이후에는 total_loss에 더해줌
    total_loss = total_loss/ class_num # 평균 손실을 구함 
    return total_loss

4개의 클래스에 대한 dice coef 구하는 코드