오늘은 일반적인 CNN 아키텍쳐란 무얼까 고민해볼까한다. 오늘 처음으로 CNN아키텍쳐에 따라 학습이 되고 안되고를 접하게 되었다. 대충 이론을 알다보니 그럴싸하게 혼자 레이어를 짜는 경우도 종종 있었는데 오늘 조금 고민을 하게되었다.
문제의 아키텍쳐는 아래와 같았다. 사실 아래 아키텍쳐가 문제는 아닐 수도 있겠다는 생각도 들었다. 왜냐하면 일반적인 예제에서도 학습이 안되었으니 말이다. 아시는 분께 문의를 드려보니 MaxPool size가 3,3인게 이상해보인다고 처음 말씀해주셨다. gap적용전에 shape을 1 x 1 x channel로 구성하고 싶어서 그렇게 하긴했는데, 나도 너무 고민을 안한거 같다. 보통은 Strides는데 코드 바꾸기 귀찮아서 저걸바꿨으니 말이다.
inp=tf.keras.layers.Input((100,100,3))
_inp=tf.keras.layers.Resizing(224,224)(inp)
scaling=tf.keras.layers.Rescaling(scale=1./255)(_inp)
conv1=tf.keras.layers.Conv2D(32, kernel_size=(5,5),activation='relu')(scaling)
max1=tf.keras.layers.MaxPool2D((3,3))(conv1)
conv2=tf.keras.layers.Conv2D(64, kernel_size=(5,5),activation='relu')(max1)
max2=tf.keras.layers.MaxPool2D((3,3))(conv2)
conv3=tf.keras.layers.Conv2D(128, kernel_size=(5,5),activation='relu')(max2)
max3=tf.keras.layers.MaxPool2D((2,2))(conv3)
conv4=tf.keras.layers.Conv2D(256, kernel_size=(5,5),activation='relu')(max3)
max4=tf.keras.layers.MaxPool2D((2,2))(conv4)
conv5=tf.keras.layers.Conv2D(512, kernel_size=(5,5),activation='relu')(max4)
max5=tf.keras.layers.MaxPool2D((2,2))(conv5)
gap=tf.keras.layers.GlobalAveragePooling2D()(max5)
dense1=tf.keras.layers.Dense(units=256,activation='relu')(gap)
out=tf.keras.layers.Dense(units=3,activation='softmax')(dense1)
model=tf.keras.Model(inp,out)
model.summary()
사실 해당 테스크에서 문제로 느꼈던게 Batch Norm을 적용하지 않아서 학습이 안된 것이였다. 기존에 내가하던 연구에서는 BN을 적용하기에 Batch size가 20도 안되는 테스크인 경우였기 때문에 BN을 오히려 안써왔다. 아래는 케라스의 예제인데 아래와 같이 Conv > pooling >conv >pooling > gap 혹은 flat > dense 로 엮어 왔다.
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
그러고 아는분과 BN을 적용해보자라는 얘기가 나왔다. BN을 적용할 때 순서는 아래 링크에서 얘기가 나온다. 해당글을 읽으면서 보통은 논문을 신뢰하지 않는 편인데 논문을 읽어야되겠다는 생각이 들게되었다. 아래 글이 길지는 않으니 읽어보길 바란다.

위 글의 핵심은 네트워크 연산결과가 원하는 방향의 분포대로 나오는 것이 목표이기 때문에 Conv 다음 BN을 적용하고 Activation function을 구성하는 게 올바르다고 한다. 무튼 Conv > BN > Act > Drop > Pool 순으로 구성하는게 좋아보인다가 결론이였다. 말대로 Conv >BN >Act 순으로 수정하여 학습을 해보니 학습이 되었다. 아래는 Resnet 모델인데 아키텍쳐 또한 동일하게 구성되고 있었다.

inp=tf.keras.layers.Input((100,100,3))
_inp=tf.keras.layers.Resizing(224,224)(inp)
scaling=tf.keras.layers.Rescaling(scale=1./255)(_inp)
conv1=tf.keras.layers.Conv2D(32, kernel_size=(5,5))(scaling)
batch_norm1=tf.keras.layers.BatchNormalization()(conv1)
relu1=tf.keras.layers.ReLU()(batch_norm1)
max1=tf.keras.layers.MaxPool2D((2,2))(relu1)
conv2=tf.keras.layers.Conv2D(64, kernel_size=(5,5))(max1)
batch_norm2=tf.keras.layers.BatchNormalization()(conv2)
relu2=tf.keras.layers.ReLU()(batch_norm2)
max2=tf.keras.layers.MaxPool2D((2,2))(relu2)
conv3=tf.keras.layers.Conv2D(128, kernel_size=(5,5))(max2)
batch_norm3=tf.keras.layers.BatchNormalization()(conv3)
relu3=tf.keras.layers.ReLU()(batch_norm3)
max3=tf.keras.layers.MaxPool2D((2,2))(relu3)
conv4=tf.keras.layers.Conv2D(256, kernel_size=(5,5),strides=2)(max3)
batch_norm4=tf.keras.layers.BatchNormalization()(conv4)
relu4=tf.keras.layers.ReLU()(batch_norm4)
max4=tf.keras.layers.MaxPool2D((2,2))(relu4)
conv5=tf.keras.layers.Conv2D(512, kernel_size=(5,5))(max4)
gap=tf.keras.layers.GlobalAveragePooling2D()(conv5)
# gap=tf.keras.layers.Flatten()(max4)
dense1=tf.keras.layers.Dense(units=512,activation='relu')(gap)
out=tf.keras.layers.Dense(units=3, activation='softmax')(dense1)
model=tf.keras.Model(inp,out)