4 분 소요

본 글은 [핸즈온 머신러닝 2판 (Hands-On Machine Learning with Scikit-Learn, keras & TensorFlow)] - (박해선 옮김) 책을 개인공부하기 위해 요약, 정리한 내용입니다.
전체 코드는 https://github.com/ingu627/handson-ml2에 기재했습니다.(원본 코드 fork)
뿐만 아니라 책에서 설명이 부족하거나 이해가 안 되는 것들은 외부 자료들을 토대로 메꿔 놓았습니다.
오타나 오류는 알려주시길 바라며, 도움이 되길 바랍니다.

more info

1. 이미지 자료 : formal.hknu.ac.kr
2. 딥러닝을 위한 콘볼루션 계산 가이드





14.4 CNN 구조

  • 전형적인 CNN 구조 : 합성곱 층을 몇 개 쌓고(각각 ReLU 층을 그 뒤에 놓고), 그다음에 풀링층을 쌓고, 그다음에 또 합성곱 층(+ReLU)을 몇 개 더 쌓고, 그다음에 다시 풀링 층을 쌓는 식.


# 다음은 패션 MNIST 데이터셋에 대한 CNN

model = keras.models.Sequential([
    DefaultConv2D(filters=64, kernel_size=7, input_shape=[28, 28, 1]), # filter:7x7인 64개를 의미, stride:1, image:28x28, 하나의 컬러채널:1 => [28,28,1] 
    keras.layers.MaxPooling2D(pool_size=2), # 공간 방향 차원을 절반으로 줄임
    DefaultConv2D(filters=128), # 풀링 층 다음에 필터 개수를 두 배로 늘리는 것이 일반적인 방법
    DefaultConv2D(filters=128),
    keras.layers.MaxPooling2D(pool_size=2),
    DefaultConv2D(filters=256),
    DefaultConv2D(filters=256),
    keras.layers.MaxPooling2D(pool_size=2),
    keras.layers.Flatten(),
    keras.layers.Dense(units=128, activation='relu'),
    keras.layers.Dropout(0.5), # 밀집 층 사이에 과대적합을 줄이기위해 드롭아웃 사용
    keras.layers.Dense(units=64, activation='relu'),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(units=10, activation='softmax'),
])



14.4.1 LeNet-5



14.4.2 AlexNets

image

  • LeNet과 비슷하지만, AlexNet은
    1. max pooling, ReLU 비선형을 썼다.
    2. 데이터가 더 많고 더 큰 모델이다.
    3. GPU로 구현했다.
    4. dropout 규제를 활용했다.



이미지출처 1


  • 두 가지 규제 기법 사용 (과대적합을 줄이기 위해)
    1. 훈련하는 동안 F9과 F10의 출력에 드롭아웃을 50% 비율로 적용
    2. 데이터 증식(Data Augmentation) 사용

  • C1과 C3 층의 ReLU 단계 후에 바로 LRN(local response normalization) 정규화 사용
    • 뉴런의 출력값을 보다 경쟁적으로 만드는 정규화 기법
    • 어떤 특성 지도에 속한 하나의 뉴런의 활성화 함수의 반환값이 클 경우 주변 특성지도의 동일한 위치에 뉴런의 활성화 함수값을 크게 만들어줌
  • 식 : $b_i=a_i(k+\alpha\sum_{j=j_{low}}^{j_{high}}a_j^2)^{-\beta}$
    • 여기서 $j_{high}=min(i+\frac{r}{2},f_n-1)$
    • $j_{low}=max(0,i-\frac{r}{2})$
    • $b_i$ = $i$ 특성 맵, $u$행, $v$열에 위치한 뉴런의 정규화된 출력
    • $a_i$ = ReLU 단계를 지나고 정규화 단계는 거치기 전인 뉴런의 활성화 값
    • $k, \alpha, \beta, r$ : 하이퍼파라미터, $k$=편향, $r$=깊이 반경
    • $f_n$ = 특성 맵의 수



14.4.3 GoogLeNet

  • 인셉션 모듈(inception module) 서브 네트워크 사용
    • 수용적 필드 크기와 연산이 다른 병렬(parallel) 경로는 feature map의 stack에서 희소(sparse) 상관 관계 패턴을 캡처하는 것을 의미한다.

    image

    • ex. 3x3+1(S) : 3x3 커널, stride 1, ‘same’ 패딩
    • 모든 합성곱 층은 ReLU 활성화 함수를 사용한다.
    • 둘째 층은 다양한 패턴을 다양한 스케일로 파악하기 위한 용도


image

  • 하지만 보다시피 파라미터 수가 엄청 많아져 엄청난 계산이 요구된다.
  • 또한 pooling 레이어는 feature depth를 보존하는데 concatenation 후 총 depth는 모든 레이어에서만 증가할 수 있다는 의미이다.
  • 따라서 1x1 커널 사용 층을 사용하는데, 깊이별 패턴을 확인하며, 다른 합성곱 층과 연계하는 역할 수행한다.
    • 값비싼 컨볼루션에 들어가기 전에 치수(dimension) 감소를 위해 1x1 컨볼루션을 사용한다.


image



14.4.4 VGGNet

image
이미지출처 2

  • (2~3개의 합성곱 층+풀링층)의 단순 반복
  • VGGNet 종류에 따라 총 16개 또는 19개의 합성곱 층이 있다.
  • 밀집 네트워크는 2개의 은닉층과 출력층으로 이루어진다.
  • 3x3 필터만 사용
    • 왜 그럴까?
    • Answer : 3개의 3x3 컨볼루션 레이어의 스택은 하나의 7x7 컨볼루션 레이어와 동일한 효과적인 수용 필드(receptive field)를 갖는다.
    • 깊어질수록, 더 많은 비선형 함수가 더 많이 들어가서 decision function이 더 잘 학습된다. 3
    • 그리고 7x7 컨볼루션보다 더 적은 파라미터로 학습할 수 있다.



14.4.5 ResNet

  • 잔차 네트워크(Residual Network) 사용
  • 우승한 네트워크는 152개 층으로 구성된 극도로 깊은 CNN 사용(ResNet-152)
  • 이런 깊은 네트워크를 훈련시킬 수 있는 핵심 요소는 스킵 연결(skip connection)
    • 어떤 층에 주입되는 신호가 상위 층의 출력에도 더해진다.
  • 잔차 학습(Residual Learning) : 신경망을 훈련시킬 때는 목적 함수 h(x)를 모델링하는 것이 목표인데, 입력 x를 네트워크의 출력에 더한다면(=스킵 연결 추가) 네트워크는 h(x)대신 f(x)=h(x)-x를 학습
  • 일반적인 신경망을 초기화할 때는 가중치가 0에 가깝기 때문에 네트워크도 0에 가까운 값을 출력한다.
  • 스킵 연결을 추가하면 이 네트워크는 입력과 같은 값을 출력한다.

  • 잔차 유닛(residual unit) : 스킵 연결을 가진 작은 신경망

image


  • RU를 활용한 잔차학습으로 스킵 연결로 인한 보다 수월한 학습 가능
  • image


  • ResNet 구조

image

  • GoogLeNet과 똑같이 시작하고 종료하지만 중간에 단순한 잔차 유닛을 매우 깊게 쌓았다.


image

  • 특성 맵의 수는 몇 개의 잔차 유닛마다 두 배로 늘어남
  • 반면 높이와 너비는 절반이 된다.
    • 이러한 경우 입력과 출력의 크기가 다르기 때문에 입력이 잔차 유닛의 출력에 바로 더해질 수 없다.
    • 이 문제를 해결하기 위해 stride 2이고 출력 틍성 맵의 수가 같은 1x1 합성곱 층으로 입력을 통과시킴 (위의 그림)


  • Inception-v4 : GooLeNet + ResNet 모델의 합성



14.4.6 Xception

  • extreme inception을 의미
  • GooLeNet + ResNet But 인셉션 모듈 대신 깊이별 분리 합성곱 층(depthwise separable convolution layer) 사용
    • 공간별 패턴인식 합성곱 층과 깊이별 패턴인식 합성곱 층을 분리하여 연속적으로 활용
    • 공간별 패턴인식 : 형태 인식(입력 특성지도마다 한개만 탐색)
    • 깊이별 패턴인식 : 채널 사이의 패턴 인식(1x1 필터 사용)
  • 분리 합성곱 층은 입력 채널마다 하나의 공간 필터만 가지기 때문에 입력층에 많은 채널(특성맵)가 존재할 경우에만 활용

image


  • Xception 구조

image



14.4.7 SENet

  • GoogLeNet + ResNet
  • GoogLeNet의 인셉션 모듈 또는 ResNet의 잔차유닛에 SE block을 추가하여 성능 향상

image


  • SE 블록이 추가된 부분의 유닛의 출력을 깊이 차원에 초점을 맞추어 분석한다.
  • SE block은 입력된 특성 지도를 대상으로 깊이별 패턴 특성 분석한다.
    • 패턴 특성들을 파악한 후 출력값 보정한다.
    • ex. 입과 코 특성 맵이 강하게 활성되고 눈 특성 맵만 크게 활성화되지 않았을 경우 눈 특성 맵의 출력 강화한다.

image


  • SE block 구조

image

  • 첫째 밀집 층 : 뉴런 수를 1/16로 줄임 -> 특성맵들 사이의 연관성 학습 강요
  • 둘째 밀집 층 : 뉴런 수를 정상화시킴 -> 학습된 연관성을 이용하여 입력 특성지도를 보정할 가중치 출력



14.5 케라스를 사용해 ResNet-34 CNN 구현하기

  • 먼저 ResidualUnit 층 만들기 (그림 참고)

image

import tensorflow as tf

class ResidualUnit(tf.keras.layers.Layer):
    def __init__(self, filters, strides=1, activation='relu', **kwargs):
        super().__init__(**kwargs)
        self.activation = tf.keras.activations.get(activation)
        self.main_layers = [
            tf.keras.layers.Conv2D(filters, 3, strides=strides,
                                   padding='same', use_bias=False),
            tf.keras.layers.BatchNormalization(),
            self.activation,
            tf.keras.layers.Conv2D(filters, 3, strides=1,
                                   padding='same', use_bias=False),
            tf.keras.layers.BatchNormalization()
            ]
        self.skip_layers = []
        if strides > 1:
            self.skip_layers = [
                tf.keras.layers.Conv2D(filters, 1, strides=strides,
                                       padding='same', use_bias=False),
                tf.keras.layers.BatchNormalization()
            ]
    
    def call(self, inputs):
        Z = inputs
        for layer in self.main_layers:
            Z = layer(Z)
        skip_Z = inputs
        for layer in self.skip_layers:
            skip_Z = layer(skip_Z)
        return self.activation(Z + skip_Z)


  • 이 네트워크는 연속되어 길게 연결된 층이기 때문에 Sequential 클래스를 사용해 ResNet-34 모델을 만들 수 있다.


model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Conv2D(64, 7, strides=2, input_shape=[224, 224, 3],
                                 padding='same', use_bias=False))
model.add(tf.keras.layers.BatchNormalization())
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same'))
prev_filters = 64
for filters in [64] * 3 + [128] * 4 + [256] * 6 +[512] * 3:
    strides = 1 if filters == prev_filters else 2
    model.add(ResidualUnit(filters, strides=strides))
    prev_filters = filters
model.add(tf.keras.layers.GlobalAvgPool2D())
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(10, activation='softmax'))
  • model.summary()



14.6 케라스에서 제공하는 사전훈련된 모델 사용하기

  • keras.applications 패키지에서 사전훈련된 모델을 가져올 수 있다.
  • ex)
model= tf.keras.applications.resnet50.ResNet50(weights='imagenet')
# ResNet-50 모델을 만들고 이미지넷 데이터셋에서 사전훈련된 가중치를 다운로드


images_resized = tf.image.resize(images, [224, 224])
# 적재한 이미지의 크기를 바꿈

inputs = tf.keras.applications.resnet50.preprocess_input(images_resized * 255)
# preprocess_input() : 모델마다 이미지를 전처리 해줌

Y_proba = model.predict(inputs)
# 예측



14.7 사전훈련된 모델을 사용한 전이 학습

import tensorflow_datasets as tfds

dataset, info = tfds.load('tf_flowers', as_supervised=True, with_info=True)
# with_info=True : 데이터셋에 대한 정보 얻기
dataset_size = info.splits['train'].num_examples
class_names = info.features['label'].names
n_classes = info.features['label'].num_classes


test_set_raw, valid_set_raw, train_set_raw = tfds.load(
    "tf_flowers",
    split=["train[:10%]", "train[10%:25%]", "train[25%:]"],
    as_supervised=True)
# 모델 나누기


def preprocess(image, label):
    resized_image = tf.image.resize(image, [224, 224]) # 크기 조정
    final_image = tf.keras.applications.xception.preprocess_input(resized_image) # 이미지 전처리
    return final_image, label


batch_size = 32
train_set_raw = train_set_raw.shuffle(1000)

# 배치 크기 정함 & 프리패츠 적용
train_set = train_set_raw.map(preprocess).batch(batch_size).prefetch(1)
valid_set = valid_set_raw.map(preprocess).batch(batch_size).prefetch(1)
test_set = test_set_raw.map(preprocess).batch(batch_size).prefetch(1)


base_model = tf.keras.applications.xception.Xception(weights='imagenet',
                                                     include_top=False)
# include_top=False : 네트워크의 최상층에 해당하는 전역 평균 풀링 층과 밀집 출력 층을 제외                                                    
avg = tf.keras.layers.GlobalAveragePooling2D()(base_model.output)
output = tf.keras.layers.Dense(n_classes, activation='softmax')(avg)
model = tf.keras.Model(inputs=base_model.input, outputs=output)


# 사전훈련된 층의 가중치 동결
for layer in base_model.layers:
    layer.trainable = False


# 모델을 컴파일하고 훈련 시작
optimizer = tf.keras.optimizers.SGD(lr=0.2, momentum=0.9, decay=0.01)
model.compile(loss='sparse_categorical_crossentropy', optimizer=optimizer,
              metrics=['accuracy'])
history = model.fit(train_set, epochs=5, validation_data=valid_set)


# 모델을 몇 번의 에포크 동안 훈련하는 것은 새로 추가한 최상위 층을 훈련했다는 의미.add()# 다시 모든 층의 동결을 해제하고 훈련을 계속한다.

for layer in base_model.layers:
    layer.trainable = True

optimizer = tf.keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=0.001)
model.compile(loss='sparse_categorical_crossentropy', optimizer=optimizer,
              metrics=['accuracy'])
history = model.fit(train_set, epochs=10, validation_data=valid_set)





References

댓글남기기