Etc/Deep Learning

6장 합성곱 신경망Ⅱ(3) - 이미지 분류를 위한 신경망(VGGNet, GoogLeNet, ResNet)

z.zzz 2021. 8. 11. 16:23

6.1.3 VGGNet

등장 배경 : 합성곱층의 파라미터 개수를 줄이고, 훈련 시간 개선을 위해

VGG16 네트워크의 구조적 세부 사항

 

* 모든 합성곱 커널의 크기 : 3×3

* 모든 최대 풀링 커널 크기 : 2×2

  + 스트라이드 : 2

 

 

 

 

VGGNet16 구조 상세

층 유형 특성 맵 크기 커널 크기 스트라이드 활성화 함수
이미지 1 224×224
합성곱층 64 224×224 3×3 1 렐루(ReLU)
합성곱층 64 224×224 3×3 1 렐루(ReLU)
최대 풀링층 64 112×112 2×2 2
합성곱층 128 112×112 3×3 1 렐루(ReLU)
합성곱층 128 112×112 3×3 1 렐루(ReLU)
최대 풀링층 128 56×56 2×2 2
합성곱층 256 56×56 3×3 1 렐루(ReLU)
합성곱층 256 56×56 3×3 1 렐루(ReLU)
합성곱층 256 56×56 3×3 1 렐루(ReLU)
합성곱층 256 56×56 3×3 1 렐루(ReLU)
최대 풀링층 256 28×28 2×2 2
합성곱층 512 28×28 3×3 1 렐루(ReLU)
합성곱층 512 28×28 3×3 1 렐루(ReLU)
합성곱층 512 28×28 3×3 1 렐루(ReLU)
합성곱층 512 28×28 3×3 1 렐루(ReLU)
최대 풀링층 512 14×14 2×2 2
합성곱층 512 14×14 3×3 1 렐루(ReLU)
합성곱층 512 14×14 3×3 1 렐루(ReLU)
합성곱층 512 14×14 3×3 1 렐루(ReLU)
합성곱층 512 14×14 3×3 1 렐루(ReLU)
최대 풀링층 512 7×7 2×2 2
완전연결층 4096 렐루(ReLU)
완전연결층 4096 렐루(ReLU)
완전연결층 1000 소프트맥스(softmax)

 

실습 - VGG19 모델 생성

1. 필요한 라이브러리 호출

import cv2    //얼굴 인식, 물체 식별, 이미지 결합 등 작업이 가능한 오픈 소스 라이브러리

 

2. VGG19 네트워크 생성

Model Subclassing API 이용

class VGG19(Sequential):
    def __init__(self, input_shape):
        super().__init__()
    
        self.add(Conv2D(64, kernel_size=(3, 3), padding='same', activation='relu', input_shape=input_shape))
        self.add(Conv2D(64, kernel_size=(3, 3), padding='same', activation='relu'))
        self.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
        self.add(Conv2D(128, kernel_size=(3, 3),padding='same', activation='relu'))
        self.add(Conv2D(128, kernel_size=(3, 3), padding='same', activation='relu'))
        self.add(MaxPooling2D(pool_size=(2, 2), strides= (2, 2)))
        ...
        self.add(Flatten())
        self.add(Dense(4096, activation='relu'))
        self.add(Dropout(0.5))
        self.add(Dense(4096, activation='relu'))    
        self.add(Dropout(0.5))
        self.add(Dense(1000, activation='softmax'))
        self.compile(optimizer=tf.keras.optimizers.Adam(0.003),
                        loss='categorical_crossentropy',
                        metrics=['accuracy'])

 

3. VGG19 모델 생성

2의 클래스를 호출하여 모델 생성

model = VGG19(input_shape=(224, 224, 3))     //VGG19클래스에 전달되는 입력값 형태

 

4. 사전 훈련된 VGG19 가중치 내려받기 & 클래스 정의

VGG19모델의 파라미터 : 1억 4000만 개 → 네트워크 훈련 시간이 오래걸림

→ 여기선 사전 훈련된 모델에서 가중치를 가져와 사용 by load_weights()

#사전 훈련된 VGG19 모델의 가중치 내려받기
model.load_weights("../chap6/data/vgg19_weights_tf_dim_ordering_tf_kernels.h5")

#검증용으로 사용될 클래스 세개만 적용
classes={282: 'cat',
            664: 'notebook, notebook computer',
            970: 'alp'} 

 

5. 이미지 호출 및 예측

이미지를 모델에 적용한 후 정확한 분류로 예측이 되었는지 검증

image1 = plt.imread("../chap6/data/labtop.jpg")
#image1 = cv2.imread("../chap6/data/starrynight.jpeg")
#image1 = cv2.imread("../chap6/data/cat.jpg")

image1 = cv2.resize(image1, (224, 224))
plt.figure()
plt.imshow(image1)
image1 = image1[np.newaxis, :]     #차원 확장(행을 추가)

predicted_value = model.predict_classes(image1)  ---- ①
plt.title(classes[predicted_value[0]])     #출력에 대한 제목 지정

① predict() VS predict_classes

 - predict() 반환값 : 숫자 값, 샘플이 각 범주에 속할 확률을 나타냄

 - predict_classes() 반환 값 : 샘플이 속한 카테고리 레이블

  예) predict()의 반환값은 [[0.23522541] [0.9731412]], predict_classes()의 반환값은 [[0] [1]] 형태

 

🚫오류
  • error: (-215:Assertion failed) !ssize.empty() in function 'cv::resize'
  • Image data of dtype object cannot be converted to float

   ⇒ cv2.imread -> plt.imread로 해결 

 

+ 또 다른 이미지로 테스트를 진행하고 싶다면 https://unsplash.com/ 웹 사이트에서 이미지를 내려받아 진행

 


6.1.4 GoogLeNet

GoogLeNet이란?

주어진 하드웨어 자원을 최대한 효율적으로 이용, 학습 능력은 극대화함

 

인셉션 모듈의 추가

인셉션 모듈 : 특징을 효율적으로 추출하기 위해 1×1, 3×3, 5×5의 합성곱 연산을 각각 수행 

    //3×3 최대 풀링은 입력과 출력의 높이와 너비가 같아야하므로, 패딩 연산 추가 必(풀링 연산에선 드문 케이스)

⇒ 결과적으로 GoogLeNet에 적용된 해결 방법 : 희소 연결

   //희소 연결 : CNN은 합성곱, 풀링, 완전연결층이 서로 밀집하게 연결되어 있는데,

                         빽빽하게 연결된 신경망 대신 '관련성'이 높은 노드끼리만 연결하는 방법(효과 : 연산량↓, 과적합 해결 가능)

 

GoogLeNet의 인셉션 모듈

GoogLeNet의 인셉션 모듈

인셉션 모듈의 네 가지 연산

 1×1 합성곱

 1×1 합성곱 + 3×3 합성곱

 1×1 합성곱 + 5×5 합성곱

 3×3 최대 풀링 + 1×1 합성곱

 

 

 

합성곱 신경망에서 자주 나타나는 문제

심층 신경망의 계층이 넓고(뉴런이 많고), 깊으면(계층이 많으면)

→ 장점 : 인식률은 좋아짐

→ 단점 : 과적합, 기울기 소멸 문제, 학습 시간 지연, 연산 속도 등의 문제 발생

GoogLeNet(인셉션)으로 해결 가능

 


6.1.5 ResNet

ResNet 핵심

깊어진 신경망을 효과적으로 학습하기 위한 방법으로 레지듀얼(residual; 잔여) 개념을 고안한 것

 

신경망 깊이가 깊어질 수록

  • 딥러닝 성능은 좋아짐 (X) 
  • 성능이 좋아지다가 일정한 단계에 다다르면 성능이 나빠짐 (O)

 

네트워크 깊이에 따른 성능 비교

훈련 에러 / 검증 에러

네트워크 56층이 20층보다 더 나쁜 성능을 보임

⇒ 깊이가 깊다고 해서 무조건 성능이 좋아지는 것 X

→ ResNet : 문제 해결을 위해 레지듀얼 블록(residual block) 도입

 

 

레지듀얼 블록 : 기울기가 잘 전파될 수 있도록 일종의 숏컷(shortcut, skip connection; 비선형 처리 계층을 건너뛰고 신경 네트워크의 서로 다른 계층에 있는 노드 간의 추가 연결)을 만들어줌

  - ResNet은 층이 총 152개(GoogLeNet은 22개) → 기울기 소멸 문제가 발생할 수 있으므로 숏컷을 두어 기울기 소멸 문제를 방지함

ResNet 구조

 

기존 신경망 

 목적 : 입력 값 x를 출력 값 y로 매핑하는 함수 H(x)를 얻는 것

ResNet

 목적 : F(x)+x를 최소화하는 것

      //F(x) : 레지듀얼 함수, 두 개의 합성곱층 사이에 위치

       - 출력(H(x))입력 x에 대한 차(F(x)=H(x)-x)로 표현 가능

       - x는 현시점에서 변할 수 없는 값 → F(x)를 0에 가깝게 만드는 것이 목적

       - F(x)가 0이 되면 출력입력 모두 x로 같아지게 됨

       - 즉, F(x)=H(x)-x이므로 F(x)를 최소로 한다는 것은 H(x)-x를 최소로 한다는 것과 의미가 동일

       - H(x)-x를 레지듀얼이라 함

📈ResNet 기울기 소멸 문제 해결 과정 정리
1. 입력(x)과 레이블(y) 관계를 설명하는 함수 H(x) = x가 되도록 학습시킴
2. F(x)가 0이 되도록 학습시킴
3. F(x) + x = H(x) = x가 되도록 학습시키면, F(x)+x의 미분 값은 F′(x)+1로, 최소 1 이상의 값이 도출됨
4. 모든 계층에서 기울기가 F′(x)+1 ⇒ (오차가 0에 가깝게 수렴하여 발생하는) 기울기 소멸 문제가 해결

 

ResNet 구조

= 아이덴티티 블록(숏컷으로 만들어진 블록) + 합성곱 블록(합성곱층으로 구성됨)

아이덴티티 블록

 네트워크의 매핑함수 F(x)에 입력 x를 그대로 더함

 

 

합성곱 블록

입력 x가 1×1 합성곱층을 거친 후 F(x)에 더해 줌

 

 

 

ResNet 구조

두 블록을 왼쪽 그림처럼 쌓아서 구성

 

 

🛠ResNet 구조
 VGG19 구조를 뼈대 + 합성곱층을 추가해 깊게 만듦 + 숏컷 추가

 

실습 - 아이덴티티 블록, 합성곱 블록

아이덴티티 블록

def res_identity(x, filters):
    x_skip = x    //입력값
    f1, f2 = filters

    x = Conv2D(f1, kernel_size=(1, 1), strides=(1, 1), padding='valid', kernel_regularizer=I2(0.001))(x) --- ①
    x = BatchNormalization()(x) --- ②
    x = Activation(activations.relu)(x)   #첫번째 블록

    x = Conv2D(f1, kernel_size=(3, 3), strides=(1, 1), padding='same', kernel_regularizer=I2(0.001))(x)
    x = BatchNormalization()(x)
    x = Activation(activations.relu)(x)    #두번째 블록

    x = Conv2D(f2, kernel_size=(1, 1), strides=(1, 1), padding='valid', kernel_regularizer=I2(0.001))(x)
    x = BatchNormalization()(x)    #세번째 블록

    x = Add()([x, x_skip])    #숏컷
    x = Activation(activations.relu)(x)
    return x

① kernel_regularizier : Regularizer to apply a penalty on the layer's kernel(These penalties are summed into the loss function that the network optimizes)

BatchNormalization : 데이터의 평균을 0으로, 표준편차를 1로 분포시킴

     각 계층에서 입력 데이터의 분포앞 계층에서 업데이트된 가중치에 따라 변함.

     → 각 계층마다 변화되는 분포는 학습 속도를 늦춤 + 학습을 어렵게 함

 

합성곱 블록

def res_conv(x, s, filters):    #입력, 스트라이드 크기, 필터 개수 배열
    x_skip = x
    f1, f2 = filters

    x = Conv2D(f1, kernel_size=(1, 1), strides=(s, s), padding='valid', kernel_regularizer=l2(0.001))(x)
    x = BatchNormalization()(x)
    x = Activation(activations.relu)(x)     #첫번째 블록

    x = Conv2D(f1, kernel_size=(3, 3), strides=(1, 1), padding='same', kernel_regularizer=l2(0.001))(x)
    x = BatchNormalization()(x)
    x = Activation(activations.relu)(x)     #두번째 블록

    x = Conv2D(f2, kernel_size=(1, 1), strides=(1, 1), padding='valid', kernel_regularizer=l2(0.001))(x)
    x = BatchNormalization()(x)     #세번째 블록

    x_skip = Conv2D(f2, kernel_size=(1, 1), strides=(s, s), padding='valid', kernel_regularizer=l2(0.001))(x_skip)
    x_skip = BatchNormalization()(x_skip)    #숏컷

    x = Add()([x, x_skip])
    x = Activation(activations.relu)(x)
    return x