-
resnet-36, resnet-50 구현 tensorflowpytorch & tensorflow 2021. 9. 29. 17:49
목표
basemodel로 널리 사용되고 있는 resnet에 대하여 간단하게 알아보고 블럭 구현및 테스트를 진행 해보자!
resnet은 residual path --> skip connection이라고도 표현되는 구조를 고안했다.
이미지 처리를 딥러닝으로 하다 보면 문제점이 발생하는데 그것은 layer의 깊이와 관련되 있을 것이다.
일반적으로는 layer가 많고 깊에 쌓을 수록 좋은 성능을 보여준다.
하지만 위의 그림처럼 무조건 깊게만 하면 성능은 오히려 떨어지게 되는데...
깊어짐에 따라 overfitting문제가 발생할 수 있고 gradient vanishing 문제가 발생한다.
그렇기 때문에 무작정 층계만 많이 쌓으면 오히려 성능이 많이 저하되는 것을 볼 수 있다.
그것을 해결하기 위해 residual path를 생각해 냈다.
아이디어와 구성은 매우 간단하다.
X가 입력이고 F(x)는 layer와 활성화 함수 relu를 통과한 것을 의미한다.
블럭은 위의 그림처럼 구성되어 있다.
VGGNet와 유사하지만 identity x가 새로 등장하는데 간단히 말해서 원본이라고 생각하면 쉽다.
layer를 통과하고 나온 F(x)에 x를 더해 주어 다음 블록의 입력으로 사용한다.
블럭을 지날때 마다 gradient가 점점 희미해져서 손실이 나지만 그것을 방지하기 위해 layer을 통과하기전 값을 더해주어 보존을 할 수 있게 된다.
결과적으로는 논문에서 152층 까지 block를 무리없이 쌓을 수 있었다.
현재도 base model로 resnet을 많이 사용한다. resnet역시 구조가 매우 단순하며 좋은 성능을 나타내고 있기 때문이다.
그럼 바로 resnet에 구현에 대해 알아보겠다.
resnet 구조
resnet 18 - 152까지 논문에서는 제시하고있으며 알아볼 것은 36과 152이다. 원리는 똑같기 떄문에 하나만 한다면 다른것은 쉽게 만들어 낼 수 있을 것이다.
우선 34를 예시로 보면 블럭은 총 4개가 사용되었다.
블럭마다 사용되는 conv필터의 개수는 각각 다르다.
VGG처럼 모든 conv필터 사이즈는 3x3으로 사용하고 있으며 사진에는 포함되지 않았지만 conv를 하나 지나면 Batchnormalization을 사용한다.
또한 한 블럭이 끝나면 maxpooling을 사용하고 마지막 block가 끝나면 average pooling을 사용한다.
resnet block
# is_50 : True --> resnet_50 # is_plain :True --> no skip connection def build_resnet_block(input_layer, num_cnn=3, channel=64, block_num=1,is_50 = False,is_plain = False): # 입력 레이어 x = input_layer if not is_50: # CNN 레이어 for cnn_num in range(num_cnn): identity = x x = keras.layers.Conv2D( filters=channel, kernel_size=(3,3), activation='relu', kernel_initializer='he_normal', padding='same', name=f'block{block_num}_conv{cnn_num}' )(x) x = keras.layers.BatchNormalization()(x) x = keras.layers.Conv2D( filters=channel, kernel_size=(3,3), activation='relu', kernel_initializer='he_normal', padding='same', name=f'block{block_num}_1_conv{cnn_num}' )(x) if not is_plain: identity_channel = identity.shape.as_list()[-1] if identity_channel != channel: identity = keras.layers.Conv2D(channel, kernel_size=(1, 1), strides=(1, 1), padding="same")(identity) # skip connection x = keras.layers.Add()([x,identity]) else: pass else : identity = x x = keras.layers.Conv2D( filters=channel, kernel_size=(1,1), activation='relu', kernel_initializer='he_normal', padding='same', name=f'block{block_num}_conv{cnn_num}' )(x) x = keras.layers.BatchNormalization()(x) x = keras.layers.Conv2D( filters=channel, kernel_size=(3,3), activation='relu', kernel_initializer='he_normal', padding='same', name=f'block{block_num}_1_conv{cnn_num}' )(x) x = keras.layers.Conv2D( filters=channel * 4, kernel_size=(1,1), activation='relu', kernel_initializer='he_normal', padding='same', name=f'block{block_num}_2_conv{cnn_num}' )(x) if not is_plain: identity_channel = identity.shape.as_list()[-1] if identity_channel != channel: identity = keras.layers.Conv2D(channel, kernel_size=(1, 1), strides=(1, 1), padding="same")(identity) # skip connection x = keras.layers.Add()([x,identity]) else: pass # Max Pooling 레이어 # 마지막 블록 뒤에는 pooling을 하지 않음 if identity.shape[1] != 1: x = keras.layers.MaxPooling2D( pool_size=(2, 2), strides=2, name=f'block{block_num}_pooling' )(x) return x
성능을 테스트 하기 위해 복잡하게 만들었지만 사용하는 방법은 매우 간단하다.
기본 값은 resnet-34로 되어있고 두가지 옵션이 있는데 이는 각각 이것을 의미한다.
is_50 : resnet 50을 사용할지 안할지를 정하는 bool 변수
is_plain : skip connection을 사용할지 안할지를 정하는 bool 변수이다.
resnet builder
def build_resnet(input_shape=(32,32,3), num_cnn_list=[3,4,6,3], channel_list=[64,128,256,512], num_classes=10,is_50 = False, is_plain = False): assert len(num_cnn_list) == len(channel_list) #모델을 만들기 전에 config list들이 같은 길이인지 확인합니다. if is_50: num_cnn_list = [3,4,6,3] channel_list = [64,128,256,512] num_classes = 10 input_layer = keras.layers.Input(shape=input_shape) # input layer를 만들어둡니다. output = input_layer #conv1층 output = keras.layers.Conv2D(filters=64, kernel_size = (2,2), strides = 2, padding = 'valid')(output) output = keras.layers.BatchNormalization()(output) #conv2_x pooling output = keras.layers.MaxPooling2D(pool_size = (2,2), strides = 2,)(output) # config list들의 길이만큼 반복해서 블록을 생성합니다. for i, (num_cnn, channel) in enumerate(zip(num_cnn_list, channel_list)): output = build_resnet_block( output, num_cnn=num_cnn, channel=channel, block_num=i ) output = keras.layers.AveragePooling2D(padding = 'same')(output) output = keras.layers.Flatten(name='flatten')(output) output = keras.layers.Dense(512, activation='relu', name='fc1')(output) output = keras.layers.Dense(num_classes, activation='softmax', name='predictions')(output) model = keras.Model( inputs=input_layer, outputs=output ) return model
위의 구조에서 볼수 있던것처럼 블록에 넣기전 7x7 conv를 거치게 된다.
하지만 cifar-10의 사이즈는 32x32x3이므로 7x7을 사용하면 마지막블록까지 형태가 제대로 유지되지 않기 떄문에
input layer의 conv크기를 바꿔주어 형태를 유지했다.
블록을 지난다음에는 averagepooling을 거치고 dense를 통해 분류를 한다.
resnet_34 = build_resnet(is_50 = False) resnet_50 = build_resnet(is_50 = True) plain_resnet_34 = build_resnet(is_50 = False, is_plain = True) plain_resnet_50 = build_resnet(is_50 = True, is_plain = True)
이런식으로 모델을 load해주면 바로 사용이 가능하다.
resnet_34.compile( loss='sparse_categorical_crossentropy', optimizer=tf.keras.optimizers.SGD(lr=0.01, clipnorm=1.), metrics=['accuracy'], ) histoty_34 = resnet_34.fit( ds_train, steps_per_epoch=int(ds_info.splits['train'].num_examples/BATCH_SIZE), validation_steps=int(ds_info.splits['test'].num_examples/BATCH_SIZE), epochs=EPOCH, validation_data=ds_test, verbose=1, use_multiprocessing=True, )
학습을 하고 history에 loss와 acc를 저장하며 그래프로 나타내 보자.
결과
20에폭까지 진행한 결과 학습이 정상적으로 진행되는 것을 확인 할 수 있었다.
에폭수를 늘려준다면 둘의 차이를 더 정확하게 볼 수 있을것이라고 생각된다.
'pytorch & tensorflow' 카테고리의 다른 글
SRGAN Tensor flow 코드 구현 및 테스트 (4) 2021.10.07 VGG-16, VGG-19 Tensorflow 구현 (0) 2021.09.29 Pytorch mobile (0) 2021.06.07 Cycle gan webcam (0) 2021.06.07 전이학습 Transfer learning (0) 2021.06.07