
저차원 뿐만 아니라 고차원 정보도 이용하여 이미지의 특징을 추출함과 동시에 정확한 위치 파악도 가능하게 하자 → Skip Connection : 인코딩 단계의 각 레이어에서 얻은 특징을 디코딩 단계의 각 레이어야 합치는(concatenation) 방법
U-Net스타일 구조의 유일한 취약점이라고 하면 깊은 모델에서의 중간 단계의 레이어에서 학습이 느려질 수 있기 때문에, 대략적인 Feature에 대한 정보가 유실될 수 있다는 점

신경망 구조를 스킵 연결을 평행하게 두고 가운데를 기준으로 좌우가 대칭이되도록 레이어를 배치하여 U 형태 → U-Net



""" Conv Block """
class ConvBlock(tf.keras.layers.Layer):
def __init__(self, n_filters):
super(ConvBlock, self).__init__()
self.conv1 = Conv2D(n_filters, 3, padding='same')
self.conv2 = Conv2D(n_filters, 3, padding='same')
self.bn1 = BatchNormalization()
self.bn2 = BatchNormalization()
self.activation = Activation('relu')
def call(self, inputs):
x = self.conv1(inputs)
x = self.bn1(x)
x = self.activation(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.activation(x)
return x

""" Encoder Block """
class EncoderBlock(tf.keras.layers.Layer):
def __init__(self, n_filters):
super(EncoderBlock, self).__init__()
self.conv_blk = ConvBlock(n_filters)
self.pool = MaxPooling2D((2,2))
def call(self, inputs):
x = self.conv_blk(inputs)
p = self.pool(x)
return x, p

위 그림에서 단계별 한 뭉탱이 의미

""" Decoder Block """
class DecoderBlock(tf.keras.layers.Layer):
def __init__(self, n_filters):
super(DecoderBlock, self).__init__()
self.up = Conv2DTranspose(n_filters, (2,2), strides=2, padding='same')
self.conv_blk = ConvBlock(n_filters)
def call(self, inputs, skip):
x = self.up(inputs)
x = Concatenate()([x, skip])
x = self.conv_blk(x)
return x
Keras를 사용하여 U-Net 구조 구현하는 예시
입력 이미지 256*256 / 1개 채널(→ 그레이스케일)
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Dropout, UpSampling2D, concatenate
def create_model(img_height, img_width, num_channels):
inputs = Input((img_height, img_width, num_channels))
conv1 = Conv2D(64, (3, 3), activation='relu', padding='same')(inputs)
conv1 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = Conv2D(128, (3, 3), activation='relu', padding='same')(pool1)
conv2 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = Conv2D(256, (3, 3), activation='relu', padding='same')(pool2)
conv3 = Conv2D(256, (3, 3), activation='relu', padding='same')(conv3)
up4 = concatenate([UpSampling2D(size=(2, 2))(conv3), conv2], axis=3)
conv4 = Conv2D(128, (3, 3), activation='relu', padding='same')(up4)
conv4 = Conv2D(128, (3, 3), activation='relu', padding='same')(conv4)
up5 = concatenate([UpSampling2D(size=(2, 2))(conv4), conv1], axis=3)
conv5 = Conv2D(64, (3, 3), activation='relu', padding='same')(up5)
conv5 = Conv2D(64, (3, 3), activation='relu', padding='same')(conv5)
conv6 = Conv2D(1, (1, 1), activation='sigmoid')(conv5)
model = Model(inputs=[inputs], outputs=[conv6])
return model
model = create_model(256, 256, 1)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model.summary()
""" U-Net Model """
class UNET(tf.keras.Model):
def __init__(self, n_classes):
super(UNET, self).__init__()
# Encoder
self.e1 = EncoderBlock(64)
self.e2 = EncoderBlock(128)
self.e3 = EncoderBlock(256)
self.e4 = EncoderBlock(512)
# Bridge
self.b = ConvBlock(1024)
# Decoder
self.d1 = DecoderBlock(512)
self.d2 = DecoderBlock(256)
self.d3 = DecoderBlock(128)
self.d4 = DecoderBlock(64)
# Outputs
if n_classes == 1:
activation = 'sigmoid'
else:
activation = 'softmax'
self.outputs = Conv2D(n_classes, 1, padding='same', activation=activation)
def call(self, inputs):
s1, p1 = self.e1(inputs)
s2, p2 = self.e2(p1)
s3, p3 = self.e3(p2)
s4, p4 = self.e4(p3)
b = self.b(p4)
d1 = self.d1(b, s4)
d2 = self.d2(d1, s3)
d3 = self.d3(d2, s2)
d4 = self.d4(d3, s1)
outputs = self.outputs(d4)
return outputs
# U-Net model
# coded by st.watermelon
import tensorflow as tf
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Conv2DTranspose
from tensorflow.keras.layers import Activation, BatchNormalization, Concatenate
""" Conv Block """
class ConvBlock(tf.keras.layers.Layer):
def __init__(self, n_filters):
super(ConvBlock, self).__init__()
self.conv1 = Conv2D(n_filters, 3, padding='same')
self.conv2 = Conv2D(n_filters, 3, padding='same')
self.bn1 = BatchNormalization()
self.bn2 = BatchNormalization()
self.activation = Activation('relu')
def call(self, inputs):
x = self.conv1(inputs)
x = self.bn1(x)
x = self.activation(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.activation(x)
return x
""" Encoder Block """
class EncoderBlock(tf.keras.layers.Layer):
def __init__(self, n_filters):
super(EncoderBlock, self).__init__()
self.conv_blk = ConvBlock(n_filters)
self.pool = MaxPooling2D((2,2))
def call(self, inputs):
x = self.conv_blk(inputs)
p = self.pool(x)
return x, p
""" Decoder Block """
class DecoderBlock(tf.keras.layers.Layer):
def __init__(self, n_filters):
super(DecoderBlock, self).__init__()
self.up = Conv2DTranspose(n_filters, (2,2), strides=2, padding='same')
self.conv_blk = ConvBlock(n_filters)
def call(self, inputs, skip):
x = self.up(inputs)
x = Concatenate()([x, skip])
x = self.conv_blk(x)
return x
""" U-Net Model """
class UNET(tf.keras.Model):
def __init__(self, n_classes):
super(UNET, self).__init__()
# Encoder
self.e1 = EncoderBlock(64)
self.e2 = EncoderBlock(128)
self.e3 = EncoderBlock(256)
self.e4 = EncoderBlock(512)
# Bridge
self.b = ConvBlock(1024)
# Decoder
self.d1 = DecoderBlock(512)
self.d2 = DecoderBlock(256)
self.d3 = DecoderBlock(128)
self.d4 = DecoderBlock(64)
# Outputs
if n_classes == 1:
activation = 'sigmoid'
else:
activation = 'softmax'
self.outputs = Conv2D(n_classes, 1, padding='same', activation=activation)
def call(self, inputs):
s1, p1 = self.e1(inputs)
s2, p2 = self.e2(p1)
s3, p3 = self.e3(p2)
s4, p4 = self.e4(p3)
b = self.b(p4)
d1 = self.d1(b, s4)
d2 = self.d2(d1, s3)
d3 = self.d3(d2, s2)
d4 = self.d4(d3, s1)
outputs = self.outputs(d4)
return outputs