코딩 및 기타/이미지
U-Net
정지홍
2025. 1. 15. 16:18







- 1. 입력 이미지 572x572x1에 대해서 conv 3x3 , ReLU를 한다. ( 필터는 64 개 )
- 64개의 필터란?
- 해당 레이어에서 64개의 서로 다른 특성feature를 학습하는 것을 의미.
- 각 필터는 입력 이미지에서 특정한 패턴을 감지하는 역할을 함.
- 즉, 각각 64개의 필터는 서로 다른 특징을 학습하며, 특성맵을 만든다.
결과적으로 64개의 서로다른 특성맵이 출력됨. - 감지하는 패턴 : 엣지 , 모서리 , 질감 , 구조적 패턴 등등
- 즉, 각각 64개의 필터는 서로 다른 특징을 학습하며, 특성맵을 만든다.
- ReLU를 사용하여 convolution연산후 음수값들을 0으로 만듬.
- 왜 ReLU?
- 비선형성 추가 및 기울기 소실 문제 해결
- 왜 ReLU?
- 64개의 필터란?
- 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
