convolution 연산에 대한 공식...
U-Net의 첫번째 컨볼루션 결과. ( 첫번째는 패딩이 없이 수행 )
U-Net의 두번째 레이어에서의 convolution 결과.
max pooling에 대한 식
1. 입력 이미지 572x572x1에 대해서 conv 3x3 , ReLU 를 한다. ( 필터는 64 개 )
64개의 필터란?
해당 레이어에서 64개의 서로 다른 특성feature를 학습하는 것을 의미.
각 필터는 입력 이미지에서 특정한 패턴을 감지하는 역할을 함.
즉, 각각 64개의 필터는 서로 다른 특징을 학습하며, 특성맵을 만든다. 결과적으로 64개의 서로다른 특성맵이 출력됨.
감지하는 패턴 : 엣지 , 모서리 , 질감 , 구조적 패턴 등등
ReLU를 사용하여 convolution연산후 음수값들을 0으로 만듬.
2. 위의 결과로 570x570x64가 나온다. 그리고 똑같은 과정을 거쳐서 568x568x64를 만든다.
3. max pooling 2x2 를 적용하여 284x284x64가 된다.
4. 다시 convolution 3x3과 ReLu 를 하여 282x282x128을 나오게 한다.
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 구하는 코드