MLP, CNN, VGG16은 생략
핵심 Inception Module을 이어서 만든 모델
Vanishing Gradient(기울기 소실)을 방지하기 위해 고안 된 방법
Auxilary module(가짜 분류기)를 넣어줘서 방지함
아이디어 : 여러 다양한 커널을 통해 다양한 패턴을 추출하고 깊어질수록 기울기가 소실 되는 문제를 방지하기 위해 고안
# Naïve Inception 블록을 만들기 위한 함수
def my_naive_inception(input_layer, conv1_filter, conv3_filter, conv5_filter, dense_filter):
conv1 = keras.layers.Conv2D(conv1_filter, (1,1), padding='same', activation='relu')(input_layer)
conv3 = keras.layers.Conv2D(conv3_filter, (3,3), padding='same', activation='relu')(conv1)
conv5 = keras.layers.Conv2D(conv5_filter, (5,5), padding='same', activation='relu')(conv3)
max_pool = keras.layers.MaxPooling2D((3,3), strides=(1,1), padding='same')(input_layer)
# 위에서 언급한 4개의 layer 통해서 나온 feature map들을 모두 concatenation 한다.
out_layer = keras.layers.Concatenate()([conv1, conv3, conv5, max_pool])
return out_layer
VGG의 철학을 이어 받음 (핵심 Risidual Module, skip connection)
모든 Convolution 연산이 3×3 필터로 구성되어 있는 모습을 볼 수 있습니다
Layer의 깊이가 깊어질수록 Pooling을 통해 Feature Map의 사이즈는 절반으로 줄어듭니다. 그 대신 Channel 사이즈를 두배로 늘려주어 Layer별 연산량을 유지해 주는 모습을 볼 수 있습니다
연산량을 위해 BottleNeck 구조를 도입할 수 있다
Vanishing Gradient(기울기 소실)을 방지하기 위해 고안 된 방법
아이디어 : 모델이 깊어질수록 기울기가 전달 되지 못하는 현상을 방지하기 위해 Risidual(잔차, 나머지)를 통해 직접 기울기를 전달하여 나머지 부분만 학습할 수 있도록 지름길을 만들어 주는 것 (skip connection, Risidual)
def residual_module(input_layer, n_filters):
merge_input = input_layer
# if문에서는 채널 사이즈가 동일한지 확인하고, 만일 동일하지 않다면 1x1 convolution을 통해서 채널 사이즈를 맞춰 준다.
if input_layer.shape[-1] != n_filters:
merge_input = keras.layers.Conv2D(n_filters, (1,1), padding='same', activation='relu')(input_layer) # n_filter로 채널 사이즈를 맞춰 준다.
# Conv2D layer
conv1 = keras.layers.Conv2D(n_filters, (3,3), padding='same', activation='relu')(input_layer)
# Conv2D layer
conv2 = keras.layers.Conv2D(n_filters, (3,3), padding='same', activation='linear')(conv1)
# Add를 통해서 skip connection을 구현하는 부분
out_layer = keras.layers.Add()([conv2, merge_input])
out_layer = keras.layers.Activation('relu')(out_layer)
return out_layer
# input layer 정의
input = keras.layers.Input(shape=(256, 256, 3))
residual_out = residual_module(input, 64) # input layer와 필터(채널)수를 arg로 받음
# 모델 확인
model = keras.models.Model(inputs=input, outputs=residual_out)
model.summary()
# 모델 구조 시각화
plot_model(model, show_shapes=True, to_file='residual_module.png')
핵심 Transpose, Skip Connection(crop, Concatenation)
Encoder(Contracting path), Decoder(Expanding path)
아이디어 : 저차원(low-resolution) 뿐만 아니라 고차원(high-resolution) 정보도 이용하여 이미지의 특징을 추출함과 동시에 정확한 위치 파악도 가능하게 하자는 것
Encoder 과정에서 3x3 Conv 2개의 레이어로 사이즈를 줄이고 2배로 채널 수를 늘려가며 층을 쌓아가고, 다양한 패턴을 추출하면서 압축 시킨다
Decoder 과정에서 Transpose로 사이즈를 늘리고 채널을 줄여가며 이미지를 복원 시킨다. Transpose와 cropping 된 레이어를 concatenation 하면서 Encoder 과정 중 소실된 정보를 보완한다.
Tip. 초반부 Over-Segmentation 시에는 학습률을 작게하는 것이 좋다~
!pip install graphviz
!pip install pydot
import tensorflow.keras.layers as layers
import tensorflow as tf
# U-Net 레이어 구조 설정 (Functional API)
inputs = layers.Input(shape=(572, 572, 1))
# Contracting path 시작
# [1]
conv0 = layers.Conv2D(64, activation='relu', kernel_size = 3)(inputs)
conv1 = layers.Conv2D(64, activation='relu', kernel_size=3)(conv0) # Skip connection으로 Expanding path로 이어질 예정
conv2 = layers.MaxPool2D(pool_size=(2, 2), strides=(2, 2))(conv1)
# Q.위 이미지를 보고 [2]번 블럭을 구현해 봅시다. (filter 수를 주의하세요!)
conv3 = layers.Conv2D(128, activation='relu', kernel_size=3)(conv2)
conv4 = layers.Conv2D(128, activation='relu', kernel_size=3)(conv3)
conv5 = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(conv4)
# Q.위 이미지를 보고 [3]번 블럭을 구현해 봅시다. (filter 수를 주의하세요!)
conv6 = layers.Conv2D(256, activation='relu', kernel_size=3)(conv5)
conv7 = layers.Conv2D(256, activation='relu', kernel_size=3)(conv6)
conv8 = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(conv7)
# Q.위 이미지를 보고 [4]번 블럭을 구현해 봅시다. (filter 수를 주의하세요!)
conv9 = layers.Conv2D(512, activation='relu', kernel_size=3)(conv8)
conv10 = layers.Conv2D(512, activation='relu', kernel_size=3)(conv9)
conv11 = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(conv10)
# [5]
conv12 = layers.Conv2D(1024, activation='relu', kernel_size=3)(conv11)
conv13 = layers.Conv2D(1024, activation='relu', kernel_size=3)(conv12)
# Contracting path 끝
# Expanding path 시작
# [6]
trans01 = layers.Conv2DTranspose(512, kernel_size=2, strides=(2, 2), activation='relu')(conv13)
crop01 = layers.Cropping2D(cropping=(4, 4))(conv10)
concat01 = layers.concatenate([trans01, crop01], axis=-1)
# [7]
conv14 = layers.Conv2D(512, activation='relu', kernel_size=3)(concat01)
conv15 = layers.Conv2D(512, activation='relu', kernel_size=3)(conv14)
trans02 = layers.Conv2DTranspose(256, kernel_size=2, strides=(2, 2), activation='relu')(conv15)
# [8]
crop02 = layers.Cropping2D(cropping=(16, 16))(conv7)
concat02 = layers.concatenate([trans02, crop02], axis=-1) # axis 채널 순서 맞추기
# Q.위 이미지를 보고 [9]번 블럭을 구현해 봅시다. (filter 수를 주의하세요!)
conv16 = layers.Conv2D(256, activation='relu', kernel_size=3)(concat02)
conv17 = layers.Conv2D(256, activation='relu', kernel_size=3)(conv16)
trans03 = layers.Conv2DTranspose(128, kernel_size=2, strides=(2, 2), activation='relu')(conv17)
# Q.위 이미지를 보고 [10]번 블럭을 구현해 봅시다. (cropping=(40, 40))
crop03 = layers.Cropping2D(cropping=(40,40))(conv4)
concat03 = layers.concatenate([trans03, crop03],axis=-1)
# Q.위 이미지를 보고 [11]번 블럭을 구현해 봅시다. (filter 수를 주의하세요!)
conv18 = layers.Conv2D(128, activation='relu', kernel_size=3)(concat03)
conv19 = layers.Conv2D(128, activation='relu', kernel_size=3)(conv18)
trans04 = layers.Conv2DTranspose(64, kernel_size=2, strides=(2, 2), activation='relu')(conv19)
# Q.위 이미지를 보고 [12]번 블럭을 구현해 봅시다. (cropping=(88, 88))
crop04 = layers.Cropping2D(cropping=(88,88))(conv1)
concat04 = layers.concatenate([trans04, crop04],axis=-1)
# [13]
conv20 = layers.Conv2D(64, activation='relu', kernel_size=3)(concat04)
conv21 = layers.Conv2D(64, activation='relu', kernel_size=3)(conv20)
# Expanding path 끝
outputs = layers.Conv2D(2, kernel_size=1)(conv21)
model = tf.keras.Model(inputs=inputs, outputs=outputs, name="u-netmodel")