반응형

 

합성곱 신경망을 사용한 컴퓨터 비전

  • 지각은 간단한 문제가 아니고 이를 이해하기 위해서는 감각기관의 작동 원리를 이해해야 함
  • 함성곱 신경망(CNN)은 1980년대부터 이미지 인식 분야에 사용
  • CNN 시각분야 뿐만 아니라 음성 인식, 자연어 처리와 같은 다른 작업에도 많이 사용됨

 

시각 피질 구조

  • 대뇌의 뉴런들이 시야의 일부 범위 안에 있는 시각 자극에만 반응
  • 고수준의 뉴런이 저수준 뉴런의 출력에 기반한다는 의미를 얻음
  • 위의 구조가 결과적으로 전체 시야 영역에 포함된 모든 종류의 복잡한 패턴을 감지할 수 있게 함

 

합성곱 층

  • CNN의 가장 중요한 구성 요소는 합성곱 층

    • 첫 번째 합성곱 층의 뉴런은 입력 이미지의 모든 픽셀에 연결되는 것이 아니라 합성곱 층 뉴런의 수용장 안에 있는 픽셀에만 연결

    • 두 번째 합성곱 층에 있는 각 뉴런은 첫 번째 층의 작은 사각 영역 안에 위치한 뉴런에 연결

위와 같은 구조는 네트워크가 첫 번째 은닉층에서는 작은 저수준 특성에 집중하고, 그다음 은닉층에서는 더 큰 고수준 특성으로 조합해나가도록 도와줌.

해당 하는 계층적 구조는 실제 이미지에서 흔히 볼 수 있으며, 이는 CNN이 이미지 인식에 잘 작동되는 이유 중 하나

 

  • 패딩
  • 위 이미지와 같이 높이와 너비를 이전 층과 같게 하기 위해 입력 주위에 0을 추가하는 것을 제로 패딩이라고 함
  • 수용장 사이에 간격을 두어 큰 입력층을 훨씬 작은 층에 연결하는 것도 가능
    • 해당 기능은 모델의 계산 복잡도를 낮춰줌
  • 한 수용장과 다음 수용장 사이 간격을 스트라이드라고 함
  • 위 그림에서 5x7 입력층(제로 패딩 적용)이 3x3 수용장과 스트라이드 2를 사용해 3x4층에 연결
  • 상위층의 i행, j열에 있는 뉴런이 이전 층의 $i * S_h$에서 $ i * S_h + f_h -1$ 까지의 행과 $ j * S_w $ 에서 $ j * S_w + f_w -1 $ 까지의 열에 위치한 뉴런과 연결
  • 여기서 $ S_h, S_w $는 스트라이드의 수직 값과 수평 값

 

필터

  • 뉴런의 가중치는 수용장 크기의 작은 이미지로 표현될 수 있음

  • 필터라 부르는 두 개의 가중치 세트를 위 그림에서 보면 첫 번째것은 가운데 흰 수직선이 있는 검은 사각형

    • 이런 가중치를 사용한 뉴런은 가운데 수직선 부분을 제외하고는 수용장에 있는 모든 것을 무시할 것
  • 두 번째 필터는 가운데 흰 수평선이 있는 검은 사각형

    • 해당 가중치를 사용한 뉴런은 가운데 수평선 부분을 제외하고는 수용장 안의 모든 것을 무시
  • 층의 전체 뉴런에 적용된 하나의 필터는 하나의 특성 맵을 생성

    • 특성맵은 필터를 가장 크게 활성화시키는 이미지의 영역을 강조
    • 수동으로 필터를 정의할 필요가 없음
    • 훈련하는 동안 합성곱 층이 자동으로 해당 문제에 가장 유용한 필터를 찾고 상위층은 이들을 연결하여 더 복잡한 패턴을 학습

 

여러 가지 특성 맵 쌓기

  • 실제 합성곱층은 여러 가지 필터를 가지고 필터마다 하나의 특성 맵을 출력하므로 3D로 표현

  • 각 특성 맵의 픽셀은 하나의 뉴런에 해당하고 하나의 특성 맵 안에서는 모든 뉴런이 같은 파라미터를 공유하지만, 다른 특성 맵에 있는 뉴런은 다른 파라미터를 사용

    • 한 수용장은 이전 층에 있는 모든 특성 맵에 걸쳐 확장 됨
    • 하나의 합성곱 층이 입력에 여러 필터를 동시에 적용하여 입력에 있는 여러 특성을 감지할 수 있음

    CNN이 한 지점에서 패턴을 인식하도록 학습되었다면 다른 어느위치에서도 해당 패턴 인지 가능

    DNN은 한 지점에 있는 패턴을 인식하도록 학습되었다면 오직 패턴이 그 위치에 있을 때만 감지가 가능

  • 입력 이미지는 컬러 채널마다 하나씩 여러 서브 층으로 구성되기도 함, 컬러 채널은 전형적으로 빨강, 파랑, 초록의 RGB 3개

    • 흑백 이미지는 하나의 채널만 가짐, 하지만 어떤 이미지는 매우 많은 채널을 가질 수 있음
      • 위성 이미지는 가시광선외에도 다른 빛의 파장이 기록됨

  • 구체적으로 보면, 합성곱 층 $l$에 있는 $k$특성 맵의 $i$행,$j$열에 위치한 뉴런은 이전 $l-1$층에 있는 모든 특성 맵의 $i * S_h$에서 $i * S_h + f_h - 1$까지의 행과 $i * S_w$에서 $i * S_w + f_w -1$까지의 열에 있는 뉴런의 출력에 연결
    • 다른 특성 맵이더라도 같은 $i$행과 $j$열에 있는 뉴런이라면 정확히 이전층에 있는 동일한 뉴런들의 출력에 연결됨
  • 합성곱 층에 있는 뉴런의 출력 계산\
    • $z_{i,j,k} = b_k + \sum_{u=0}^{f_h-1} \sum_{v=0}^{f_w-1} \sum_{k'=0}^{f_n'-1} \chi_{i',j',k'} * W_{u,v,k',k}$
    • $i' = i * S_h + u, j' = j * S_w + v$
    • 해당 수식은 합성곱 층에서 한 뉴런의 출력을 계산하는 법에 해당, 입력에 대한 가중치 합을 계산하고 편향을 더하는 것

 

텐서플로 구현

  • 텐서플로에서 각 입력 이미지는 보통 [높이, 너비, 채널] 형태의 3D 텐서로 표현

  • 하나의 미니배치는 [미니배치 크기, 높이, 너비, 채널] 형태의 4D 텐서로 표현

  • 합성곱 층의 가중치는 [$f_h, f_w, f_n', f_n$] 형태의 4D 텐서로 표현, 합성곱 층의 편향은 간단하게 [$f_n$] 형태의 1D 텐서로 나타냄

  • 예제

    import numpy as np
    from sklearn.datasets import load_sample_image
    
    # 샘플 이미지를 로드합니다.
    china = load_sample_image("china.jpg") / 255
    flower = load_sample_image("flower.jpg") / 255
    images = np.array([china, flower])
    batch_size, height, width, channels = images.shape
    
    # 2개의 필터를 만듭니다.
    filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
    filters[:, 3, :, 0] = 1  # 수직선
    filters[3, :, :, 1] = 1  # 수평선
    
    outputs = tf.nn.conv2d(images, filters, strides=1, padding="SAME")
    
    plt.imshow(outputs[0, :, :, 1], cmap="gray") # 첫 번째 이미지의 두 번째 특성맵을 그립니다.
    plt.axis("off") # 책에는 없습니다.
    plt.show()

  • tf.nn.conv2d()

    • image는 입력의 미니배치(4D)
    • filters는 적용될 일련의 필터(4D)
    • strides는 1이나 4개의 원소를 갖는 1D 배열로 지정할 수 있음, 1D 배열의 가운데 두 개의 원소는 수직, 수평, 스트라이드이고 현재는 첫 번째와 마지막 원소가 1이어야 함
    • padding은 'valid'와 'same' 중 하나를 지정
      • 'valid'로 지정하면 합성곱 층에 제로 패딩을 사용하지 않음
  • 해당 예제에서는 필터를 직접 지정, 하지만 CNN을 사용할때는 보통 훈련 가능한 변수로 필터를 정의 하므로 앞서 설명한 것처럼 신경망이 가장 잘 맞는 필터를 학습할 수 있음

    conv = keras.layers.Conv2D(filters=32, kernel_size=3, strides=1, 
                               padding='same', activation='relu')
    • 해당 코드는 3x3 크기의 32개의 필터, 수평 수직 스트라이드 1, 'same' 패딩을 사용하는 conv2d 층을 만들고 출력을 위해 ReLU 활성화 함수를 적용
    • 합성곱 층을 위해 꽤 많은 하이퍼파마리터가 필요 <- 필터의 수, 필터의 높이와 너비, 스트라이드, 패딩 종류

 

메모리 요구 사항

  • CNN에 관련된 또 하나의 문제는 합성곱 층이 많은 양의 RAM을 필요로 함
    • 특히 훈련하는 동안에 역전파 알고리즘이 역방향 계산을 할 때 정방향에서 계산했던 모든 중간 값을 필요로 하기 때문
    • 예시로 5 x 5 필터로 스트라이드 1과 'same' 패딩을 상요해 150 x 100 크기의 특성 맵 200개를 만드는 합성곱을 예시로 한다면
      • 입력이 150 x 100 RGB 이미지, 파라미터 수는 ((5x5x3+1)x200=15,200) (+1은 편향) 해당수는 완전연결 층보다는 적지만 200개의 특성 맵마다 150x100개의 뉴런을 포함하고, 각 뉴런이 5x5x3개의 입력에 대한 가중치 합을 계산
      • 추론을 수행할 때는 하나의 층이 점유하고 있는 RAM은 다음 층의 계산이 완료되자마자 해제가 가능
        • 연속된 두 개의 층에서 필요로 하는 만큼의 RAM만 가지고 있으면 가능함

 

풀링 층

  • 풀링 층의 목적은 계산량과 메모리 사용량, 결과적으로 과대적합을 줄여주는 파라미터 수를 줄이기 위해 입력 이미지의 부표본을 만드는 것

  • 풀링 층의 각 뉴런은 이전 층의 작은 사각 영역의 수용장 안에 있는 뉴런의 출력과 연결되어 있음

    • 이전과 동일하게 크기, 스트라이드, 패딩 유형을 지정해야 함
  • 풀링층은 가중치 없고, 최대나 평균 같은 합산 합수를 사용해 입력값을 더하는 연산

    • 최대 풀링 층 예시

      • 2 x 2 풀링 커널 과 스트라이드 2를 사용하며 패딩은 없음
      • 1,5,3,2 중 최댓값인 5가 다음 층으로 전달, 스트라이드가 2이므로 높이/2, 너비/2
  • 계산량, 메모리, 파라미터 수를 감소하는 것 외에도 최대 풀링은 작은 변화에도 일정 수준의 불변성을 생성

    • 위와 같은 a,b,c 2x2 커널, 스트라이드 2인 최대 풀링을 통과
      • 이미지 b와 c는 이미지 a와 동일하지만 각각 오른쪽으로 한 픽셀과 두 픽셀 이동한 것
        • 이미지 a,b의 최대 풀링층의 출력은 동일함, 이를 이동 불변성
      • 이미지 c의 출력은 조금 다른데, 오른쪽으로 한픽셀 이동함
        • CNN에서 몇 개 층마다 최대 풀링 층을 추가하면 전체적으로 일정 수준의 이동 불변성을 얻을 수 있음 또한 최대 풀링은 회전과 확대, 축소에 대해 약간의 불변성을 제공함
        • 이와 같은 불변성은 분류 작업처럼 예측을 수행할때 작은 부분에서 영향을 받지 않는 경우 유용
  • 최대 풀링의 단점

    • 최대 풀링은 매우 파괴적
      • 작은 2x2 커널과 스트라이드 2를 사용하더라도 출력은 양방향으로 절반이 줄어들어 입력값의 75% 손실
    • 동변성
      • 입력 이미지가 오른쪽으로 한 픽셀 이동했다면 출력도 오른쪽으로 한 픽셀 이동해야 함

 

텐서플로 구현

  • 텐서플로에서 최대 풀링 층을 구현하는 것은 아주 쉬움

  • 2x2 커널을 사용해 최대 풀링 예제

    max_pool = keras.layers.MaxPool2D(pool_size=2)
  • 평균 풀링 층을 만들때는 MaxPool2D 대신 AvgPool2D를 사용

    • 해당 층은 최댓값이 아닌 평균을 계산하는 것만 빼고 최대 풀링 층과 동일하게 작동
    • 일반적으로 평균 풀링 < 최대 풀링
  • 깊이방향 풀링 층은 케라스에서 지원 X, 텐서플로 지원 tf.nn.max_pool()

    • 커널 크기와 스트라이드를 4개의 원소를 가진 튜플로 지정
    • 첫 번째 세 값은 1, 이는 배치 ,높이 ,너비 차원을 따라 커널 크기와 스트라이드가 1이라는 의미
    • 예를 들어 3과 같이 깊이 차원을 따라 원하는 커널 사이즈와 스트라이드를 마지막 값에 지정
    output =  tf.nn.max_pool(inputs,
                             ksize=(1, 1, 1, 3),
                             strides=(1, 1, 1, 3),
                             padding="valid")
    
    # keras 층으로 사용할 경우 lambda
    depth_pool = keras.layers.Lambda(lambda X: tf.nn.max_pool(
        X, ksize=(1, 1, 1, 3), strides=(1, 1, 1, 3), padding="VALID"))
  • 최근 신경망 구조에서는 마지막 출링 층의 종류는 전역 평균 풀링 층 사용

    • 해당 층의 작동방법은 각 특성 맵의 평균을 계산 하는 방식
    • 각 샘플의 특성 맵마다 하나의 숫자를 출력한다는 의미, 물론 매우 파괴적인 연산 이미지만 출력층에는 유용할 수 있음
    global_avg_pool = keras.layers.GlobalAvgPool2D()
    
    # 높이 너비를 따라 평균을 계산하는 Lambda 층과 동일
    output_global_avg2 = keras.layers.Lambda(lambda X: tf.reduce_mean(X, axis=[1, 2]))

 

CNN 구조

  • 전형적인 CNN 구조는 합성곱 층을 몇 개 쌓고 그다음에 풀링층을 쌓고, 그 다음에 또 합성곱을 몇 개 더 쌓고 그 다음에 다시 풀링 층을 쌓는 방식으로 수행

  • 네트워크를 통과하여 진행할수록 이미지는 점점 작아지지만 합성곱 때문에 일반적으로 점점 더 깊어짐

  • 맨 위층에는 몇 개의 완전 연결 층으로 구성된 일반적인 피드포워드 신경망이 추가되고 마지막 층에서 예측을 출력

  • 패션 MNIST 데이터셋 CNN 예제

    from functools import partial
    
    DefaultConv2D = partial(keras.layers.Conv2D,
                            kernel_size=3, activation='relu', padding="SAME")
    
    model = keras.models.Sequential([
        DefaultConv2D(filters=64, kernel_size=7, input_shape=[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'),
    ])
    • 첫 Layer 64 필터 7x7, 스트라이드 1
    • 풀링 크기 2인 최대 풀링 추가, 공간 방향 차원을 절반으로 축소
    • CNN이 출력층에 갈수록 필터 개수가 늘어남, 저수준 특성의 개수는 적지만 이를 연결하여 고수준 특성을 만들 수 있는 방법이 많기 때문에 해당 구조 사용
    • 풀링층 다음으로는 필터 개수를 두 배로 늘리는 것이 일반적인 방법, 풀링 층이 공간 방향 차원을 절반으로 줄이기 때문에 이어지는 층에서 파라미터 개수, 메모리 사용량, 계산 비용을 크게 늘리지 않고 특성 맵 개수를 두 배로 늘림
    • 두 개의 은닉층과 하나의 출력층으로 구성된 완전 연결 네트워크, 1d 연결을 위해 Flatten 사용, 밀집 층 사이에 과대적합을 줄이기 위해 0.5 드랍아웃 비율을 가진 드롭아웃 층 추가

 

발전한 CNN 구조 설명

 

LeNet-5

  • LeNet-5 구조는 가장 널리 알려진 CNN구조

  • 구조

    • 28 x 28 인 input을 Layer 적용전에 zero-padding 수행해 32 x 32 변경 및 정규화
    • 네트워크 다른 부분에서는 zero-padding 사용하지 않음, 그래서 이미지 네트워크를 따라 진행하면서 크기 축소
    • 평균 풀링층이 복잡, 각 뉴런의 입력의 평균 계산한 다음 해당 값에 학습되는 계숫값을 곱함 그 후 학습되는 값인 편향을 더하는 방식으로 적용
    • 출력층에서 입력과 가중치 벡터를 곱셈하는 방식 대신 각 뉴런에서 입력 벡터와 가중치 벡터 사이의 유클리드 거리를 출력, 각 출력은 이미지가 얼마나 특정 숫자 클래스에 속하는지 측정함
      • 최근에는 Cross_Entropy 사용

 

AlexNet

  • 크기가 더 깊어졌을 뿐 LeNet-5와 비슷하고 처음으로 합성곱 층 위에 풀링 층을 쌓지 않고 바로 합성곱 층끼리 적용하는 구조

  • 구조

    • 과대적합을 줄이기 위해 2가지 규제 기법을 사용
      1. 훈련하는 동안 F9과 F10의 출력에 드롭아웃을 50% 비율로 적용
      2. 훈련 이미지를 랜덤하게 여러 간격으로 이동하거나 수평으로 뒤집고 조명을 바꾸는 식으로 데이터 증식 수행
    • ReLU 단계 이후 LRN이라 부르는 경쟁적인 정규화 단계를 사용, 가장 강하게 활성화된 뉴런이 다른 특성 맵에 있는 같은 위치의 뉴런을 억제
      • 해당 수행은 특성 맵을 각기 특별하게 다른 것과 구분되게 하고, 더 넓은 시각에서 특징을 탐색하도록 만들어 결국 일반화 성능을 향상
    • AlexNet의 하이퍼파라미터는 $r=2, a=0.00002, B = 0.75, k=1$
      • $r : 깊이 반경, a : 뉴런 활성화 값, B : 뉴런의 정규화된 출력, k : 편향$
      • tf.nn.local_response_normalization() 연산을 사용해 구현 가능

 

GoogLeNet

  • 네트워크가 이전 CNN보다 훨씬 더 깊어짐, 인셉션 모듈이라는 서브 네트워크를 가지고 있어서 이전의 구조보다 훨씬 효과적으로 파라미터를 사용

  • AlexNet보다 10배나 적은 파라미터를 가짐

  • 설명

    • '3x3+1(S)' : 3x3커널, 스트라이드 1, padding='same' 사용한다는 의미
    • 처음에 입력 신호가 복사되어 네 개의 다른 층에 전달, 모든 합성곱 층은 ReLU 활성화 함수를 사용
    • 두 번째 합성곱 층은 각기 다른 커널크기(1x1, 3x3, 5x5)를 사용해 다른 크기의 패턴을 잡음, 모든 층은 스트라이드 1과 'same' 패딩을 사용하므로 출력의 높이와 너비가 모두 입력과 같음
      • 모든 출력을 깊이 연결 층에 깊이방향으로 연결할 수 있음
      • 해당 연결 층은 텐서플로의 axis=3 (3은 깊이방향 축), 매개변수로 tf.concat() 연산을 사용해 구현
  • 인셉션 모듈이 1x1 커널의 합성곱 층을 가지는 이유

    1. 공간상의 패턴을 잡을 수는 없지만 깊이 차원을 따라 놓인 패턴을 잡음
    2. 입력보다 더 작은 특성 맵을 출력하므로 차원을 줄인다는 의미인 병목 층의 역할을 수행, 연산 비용과 파라미터 개수를 줄여 훈련 속도를 높이고 일반화 성능을 향상
    3. 합성곱 층의 쌍 ([1x1, 3x3]과 [1x1, 5x5])이 더 복잡한 패턴을 감지할 수 있는 한 개의 강력한 합성곱 층처럼 작동, 실제로 합성곱 층의 쌍은 단순한 선형 분류기 하나로 이미지를 훑는 것이 아닌 두 개의 층을 가진 신경망으로 이미지를 훑음

전체 인셉션 모듈을 여러 크기의 복잡한 패턴이 담긴 특성 맵을 출력하는 합성곱 층

 

  • 구조

    • 합성곱 층과 풀링 층에서 출력되는 특성 맵의 수는 커널 크기 앞에 표시 되어 있음
    • 해당 네트워크는 매우 깊어서 세 개의 열로 나타냄
    • GoogLeNet은 실제로 네트워크를 하나로 길게 쌓은 구조이고 9개의 인셉션 모듈을 포함하고 있음
      • 인셉션 모듈에 있는 6개의 숫자는 모듈 안에 있는 합성곱 층에서 출력하는 특성 맵의 수를 나타냄
      • 모든 합성곱 층은 ReLU 활성화 함수를 사용
  • 네트워크

    • 처음의 두 층은 계산양을 줄이기 위해 이미지의 높이와 너비를 4배로 줄임
    • 많은 정보를 유지하기 위해 첫 번째 층은 큰 크기의 커널을 사용
    • LRN 층은 이전 층이 다양한 특성을 학습하도록 만듬, 이어지는 두 개의 합성곱 층 중에서 첫 번째 층이 앞서 설명했듯이 병목 층처럼 작동, 이 합성곱 쌍을 하나의 합성곱 층으로 생각 할수 있음
    • 다시 LRN층이 이전 층의 다양한 패턴을 학습하도록 만듬
    • 최대 풀링 층이 계산 속도를 높이기 위해 이미지의 높이와 너비를 2배로 줄임
    • 9개의 인셉션 모듈이 이어지면서 차원 감소와 속도 향상을 위해 몇 개의 최대 풀링 층을 넣음
    • 전역 평균 풀링 층이 각 특성 맵의 평균을 출력, 여기서 공간 방향 정보를 모두 잃음 이 지점에서는 남아 있는 공간 정보가 많지 않기 때문에 괜찮음
      • 실제 GoogleNet는 입력이 224x224, 5번의 최대 풀링 층에서 매번 높이와 너비가 절반으로 줄어들면 특성 맵의 크기는 7x7
      • 위치 추정이 아니라 분류 작업이므로 물체가 어디 있는지는 중요하지 않음.
      • 해당 층에서 수행된 차원 축소에 의해 CNN 위에 몇 개의 완전 연결 층을 둘 필요가 없음, 파라미터의 수를 크게 감소시키고 과대적합의 위험도 줄어줌
    • 마지막 층은 규제를 위한 드롭아웃 층 다음에 1000개 유닛과 소프트맥스 활성화 함수를 적용한 완전 연결 층으로 클래스 확률 추정 값을 출력

 

VGGNet

  • 매우 단순하고 고전적인 구조, 2개 또는 3개의 합성곱층 뒤에 풀링 층이 나오고 다시 2개 또는 3개의 합성곱 층과 풀링 층이 등장하는 방식(VGGNet 종류에 따라 총 16개 또는 19개의 합성곱 층이 존재)
  • 마지막 밀집 네트워크는 2개의 은닉층과 출력층으로 이루어짐
  • VGGNet은 3x3필터만 사용

 

ResNet

  • 잔차 네트워크를 사용해 성능 향상

  • 더 적은 파라미터를 사용해 점점 더 깊은 네트워크 모델을 구성하는 일반적인 트렌드를 만들어냄

  • 이런 깊은 네트워크를 훈련시킬 수 있는 핵심 요소는 스킵 연결

    • 어떤 층에 주입되는 신호가 상위 층의 출력에도 더해지는 것
  • 신경망을 훈련시킬 때는 목적 함수 $h(x)$ 대신 $f(x) = h(x)-x$를 학습하게 됨, 이를 잔차 학습

      • 일반적인 신경망을 초기화 할때 가중치는 0에 가깝기 때문에 네트워크도 0에 가까운 값을 출력
      • 스킵 연결을 추가하면 이 네트워크는 입력과 같은 값을 출력
        • 즉, 초기에는 항등 함수를 모델링, 목적 함수가 항등 함수에 매우 가깝다면 훈련 속도가 빨라짐
      • 스킵 연결을 많이 추가하면 일부 층이 학습되지 않았더라도 네트워크는 훈련을 시작 할 수 있음
        • 스킵 연결 덕분에 입력 신호가 전체 네트워크에 손십게 영향을 미치게 됨
        • 일반적인 심층 신경망과 심층 잔차 네트워크 이미지
        • 심층 잔차 네트워크는 스킵 연결을 가진 작은 신경망인 잔차 유닛을 쌓은 형태
  • ResNet 구조

    • 실제 구조는 단순함

    • GoogLeNet과 똑같이 시작하고 종료, 중간에 단순한 잔차 유닛을 매우 깊게 쌓은 형태

    • 각 잔차 유닛은 배치 정규화와 ReLU, 3x3 커널을 사용하고 공간 정보를 유지하는 stride = 1, padding='same'의 두 개의 합성곱 층으로 구성

    • 특성 맵의 수는 몇 개의 잔차 유닛마다 두 배로 늘어나고 높이와 너비는 절반이 됨

    • 이러한 경우 입력과 출력의 크기가 다르기 때문에 입력이 잔차 유닛의 출력에 바로 더해질 수 없음

      해당 문제를 해결하기 위해 stride=2, 출력 특성맵의 수가 같은 1x1 합성곱 층으로 입력을 통과

  • 특성 맵의 크기와 깊이가 바뀔 때 스킵 연결

    • ResNet-34는 34개 층으로 이루어진 ResNet으로 64개의 특성 맵을 출력하는 3개 RU, 128개 맵의 4개 RU, 256개 맵의 6개 RU, 512개 맵의 3개 RU를 포함
    • 깊은 형태의 ResNet-152 같은 형태는 조금 다른 잔차 유닛 사용
      • 256개의 특성 맵으로 된 두 개의 3x3 합성곱 층 대신 세 개의 합성곱 층을 사용
      • 병목 층처럼 작동하는 64개 특성 맵의 1x1 합성곱 층, 다음에 64개 특성 맵의 3x3 층, 마지막으로 원본 깊이를 복원하는 256개 특성 맵의 1x1 합성곱 층
    • ResNet-152는 256개 맵을 출력하는 3개의 RU, 512개 맵의 8개 RU, 1024개 맵의 36개 RU 그리고 마지막으로 2048개 맵의 3개 RU 포함

 

Xception

  • googLeNet 구조의 다른 변종

  • 대규모 비전 문제에서 inception-v3보다 좋은 성능

  • inception-v4와 같이 GoogLeNet과 ResNet의 아이디어를 합친 것, 인셉션 모듈은 깊이별 분리 합성곱 층이라는 특별한 층으로 대체

  • 깊이별 분리 합성곱 층 구조

    • 분리 합성곱 층은 입력 채널마다 하나의 공간 필터만 가지기 때문에 입력층과 같이 채널이 너무 적은 층 다음에 사용하는 것을 피해야 함
    • Xception 구조는 2개의 일반 합성곱 층으로 시작, 이 구조의 나머지는 분리 합성곱만 사용
      • 여기에 최대 풀링, 전역 평균 풀링, 밀집 출력 층 사용

 

SENet

  • 인셉션 네트워크와 ResNet과 같은 구조를 확장하여 성능향상
  • SENet은 SE-Inception 또는 SE-ResNet 이라고 불림
  • SENet 구조
    • 모든 유닛에 SE 블록이라는 작은 신경망을 추가해 성능 향상
    • SE 블록이 추가된 부분의 유닛의 출력을 깊이 차원에 초점을 맞추어 분석
      • 어떤 특성이 일반적으로 동시에 가장 크게 활성화되는지 학습 그 후에 특성 맵을 보정
      • 눈,코,입을 동시에 볼수있어서 관련 없는 특성 맵의 값을 줄이는 역할
      • SE 블록은 전역 평균 풀링, ReLU 사용하는 밀집층과 시그모이드 활성화 함수 사용하는 밀집층으로 구성

 

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

  • 케라스를 사용해 ResNet-34 모델 구현예제

    • ResidualUnit층

      DefaultConv2D = partial(keras.layers.Conv2D, kernel_size=3, strides=1,
                              padding="SAME", use_bias=False)
      
      class ResidualUnit(keras.layers.Layer):
          def __init__(self, filters, strides=1, activation="relu", **kwargs):
              super().__init__(**kwargs)
              self.activation = keras.activations.get(activation)
              self.main_layers = [
                  DefaultConv2D(filters, strides=strides),
                  keras.layers.BatchNormalization(),
                  self.activation,
                  DefaultConv2D(filters),
                  keras.layers.BatchNormalization()]
              self.skip_layers = []
              if strides > 1:
                  self.skip_layers = [
                      DefaultConv2D(filters, kernel_size=1, strides=strides),
                      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)
      • 생성자에서 필요한 층을 모두 만듬
      • skip_layers는 stride>1 경우에만 사용
      • call 메서드에서 입력을 main_layers와 skip_layers에 통과시킨 후 두 출력을 더하여 활성화 함수에 적용
    • Sequential 클래스를 사용해 ResNet-34 생성

      model = keras.models.Sequential()
      model.add(DefaultConv2D(64, kernel_size=7, strides=2,
                              input_shape=[224, 224, 3]))
      model.add(keras.layers.BatchNormalization())
      model.add(keras.layers.Activation("relu"))
      model.add(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(keras.layers.GlobalAvgPool2D())
      model.add(keras.layers.Flatten())
      model.add(keras.layers.Dense(10, activation="softmax"))
      • 처음 3개 RU는 64개의 필터를 가지고 그다음 4개 RU는 128개의 필터를 가지는 형태
      • 필터 개수가 이전 RU와 동일할 경우 stride=1 아니면 stride=2
        • [64, 64, 64, 128, 128, 128, 128, 256, 256, 256, 256, 256, 256, 512, 512, 512]
      • ResidualUnit을 더하고 마지막에 prev_filters를 업데이트

     

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

  • keras.applications 패키지에 있는 사전훈련된 모델을 load가능

    model = keras.applications.resnet50.ResNet50(weights="imagenet")
  • 이미지가 적절한 크기인지 확인해야 함

    • ResNet-50 모델은 224x224 픽셀 크기의 이미지를 입력 기대

    • 텐서플로의 tf.image.resize() 함수로 적재한 이미지의 크기를 변경

      images_resized = tf.image.resize(images, [224, 224])
      • tf.image.resize()는 가로세로 비율 유지하지 않음, tf.image.crop_and_resize() 함수 사용시 유지
  • 모델마다 이미지를 전처리해주는 preprocess_input() 함수를 제공

    • 해당 함수는 픽셀값이 0~255 사이라고 가정
    inputs = keras.applications.resnet50.preprocess_input(images_resized * 255)
    # 예측
    Y_proba = model.predict(inputs)
    • 최상위 k개의 예측을 클래스 이름과 예측 클래스의 추정 확률을 출력하려면 decode_predictions() 함수 사용
      • 각 이미지에 대해 top k 예측을 담은 리스트 반환
    top_K = keras.applications.resnet50.decode_predictions(Y_proba, top=3)
    for image_index in range(len(images)):
        print("Image #{}".format(image_index))
        for class_id, name, y_proba in top_K[image_index]:
            print("  {} - {:12s} {:.2f}%".format(class_id, name, y_proba * 100))
        print()
    
    >> Image #0
          n03877845 - palace       43.39%
          n02825657 - bell_cote    43.07%
          n03781244 - monastery    11.70%
    
        Image #1
          n04522168 - vase         53.96%
          n07930864 - cup          9.52%
          n11939491 - daisy        4.97%

 

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

  • 충분하지 않은 데이터로 이미지 분류기를 훈련하려면 11장에서 언급한 것처럼 사전훈련된 모델의 하위층을 사용하는 것이 좋음

  • 사전훈련된 Xception 모델을 사용해 꽃 이미지를 분류하는 모델 훈련 예제

    import tensorflow_datasets as tfds
    
    dataset, info = tfds.load("tf_flowers", as_supervised=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 = keras.applications.xception.preprocess_input(resized_image)
        return final_image, label
    
    batch_size = 32
    train_set = train_set_raw.shuffle(1000).repeat()
    train_set = train_set.map(partial(preprocess, randomize=True)).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 = keras.applications.xception.Xception(weights="imagenet",
                                                      include_top=False)
    avg = keras.layers.GlobalAveragePooling2D()(base_model.output)
    output = keras.layers.Dense(n_classes, activation="softmax")(avg)
    model = keras.models.Model(inputs=base_model.input, outputs=output)
    • 사전훈련된 Xception 모델 로드, include_top=False로 지정해 네트워크 최상층에 해당하는 전역 평균 풀링 층과 밀집 출력 층을 제외시킴
    • 기반 모델의 출력을 바탕으로 새로운 전역 평균 풀링 층을 추가하고 그 뒤에 softmax 활성화 함수 적용
    for layer in base_model.layers:
        layer.trainable = False
    
    optimizer = keras.optimizers.SGD(learning_rate=0.2, momentum=0.9, decay=0.01)
    model.compile(loss="sparse_categorical_crossentropy", optimizer=optimizer,
                  metrics=["accuracy"])
    history = model.fit(train_set,
                        steps_per_epoch=int(0.75 * dataset_size / batch_size),
                        validation_data=valid_set,
                        validation_steps=int(0.15 * dataset_size / batch_size),
                        epochs=5)
    • 훈련 초기에는 사전훈련된 층의 가중치를 동결
    • 몇 번의 epoch동안 검증 정확도가 75~80에 도달하고 더 나아지지 않으면 새로 추가된 층이 잘 훈련됨을 나타냄
    • 모든 층의 동결을 해제하고 훈련을 계속수행
    for layer in base_model.layers:
        layer.trainable = True
    
    optimizer = keras.optimizers.SGD(learning_rate=0.01, momentum=0.9,
                                     nesterov=True, decay=0.001)
    model.compile(loss="sparse_categorical_crossentropy", optimizer=optimizer,
                  metrics=["accuracy"])
    history = model.fit(train_set,
                        steps_per_epoch=int(0.75 * dataset_size / batch_size),
                        validation_data=valid_set,
                        validation_steps=int(0.15 * dataset_size / batch_size),
                        epochs=40)

 

분류와 위치 추정

  • 사진에서 물체의 위치를 추정하는 것은 회귀 작업으로 나타낼 수 있음

  • 물체 주위의 바운딩 박스를 예측하는 일반적인 방법은 물체 중심의 수평, 수직 좌표와 높이, 너비를 예측하는 것

    • 4개의 숫자를 예측해야 함
    • 모델의 큰 변동 없이 가능하고 네 개의 유닛을 가진 두 번째 밀집 출력 층을 추가하고 MSE 손실을 사용해 훈련
  • 예제

    base_model = keras.applications.xception.Xception(weights="imagenet",
                                                      include_top=False)
    avg = keras.layers.GlobalAveragePooling2D()(base_model.output)
    class_output = keras.layers.Dense(n_classes, activation="softmax")(avg)
    loc_output = keras.layers.Dense(4)(avg)
    model = keras.models.Model(inputs=base_model.input,
                               outputs=[class_output, loc_output])
    model.compile(loss=["sparse_categorical_crossentropy", "mse"],
                  loss_weights=[0.8, 0.2], # 어떤 것을 중요하게 생각하느냐에 따라
                  optimizer=optimizer, metrics=["accuracy"])
    • 사용하는 데이터셋은 bbox를 가지고 있지 않음, 따라서 직접 만들어서 추가해야 함

    • 모든 데이터셋에 bbox가 준비되어 있다고 생각하면

      • 클래스 레이블, bbox와 함께 전처리된 이미지의 배치가 하나의 원소인 데이터셋을 만들어야 함
      • 각 원소는 (images, (class_labels, bounding_boxes)) 형태의 튜플로 생성

      bbox의 높이와 너비는 물론 수평과 수직 좌표의 범위를 0~1 사이로 정규화 해야 함

      높이와 너비를 예측하지않고 높이와 너비의 제곱근을 예측

    • bbox 예측의 좋은 지표는 IoU, 예측한 bbox와 target bbox 사이에 중첩되는 영역을 전체 영역으로 나눈 것

      • tf.keras 에서 tf.keras.metrics.MeanIoU

 

객체 탐지

  • 하나의 이미지에서 여러 물체를 분류하고 위치를 추정하는 작업
  • 조금씩 다른 위치에서 동일한 물체를 여러 번 감지하는 경우 bbox를 제거하기 위해 사후 처리가 필요 -> NMS
    1. CNN에 또 다른 꽃이 정말 이미지에 존재하는지 확률을 추정하기 위해 존재여부를 출력을 추가, 시그모이드 활성화 함수를 사용하고 binary crossentropy 사용해 훈련, 이후 임곗값 이하인 bbox 모두 삭제
    2. 존재여부가 높은 bbox 찾고 해당 박스와 많이 중첩된 다른 bbox 모두 제거
    3. 더이상 제거할 bbox 없을 때까지 2단계 반복
  • FCN(완전 합성곱 신경망)을 사용하면 CNN을 훨씬 빠르게 이미지에 슬라이딩 할수 있음

 

완전 합성곱 신경망

  • CNN 맨 위의 밀집 층을 합성곱 층으로 바꿀수 있음
    • 밀집 층을 합성곱 층으로 변경하려면 합성곱 층의 필터 개수와 밀집 층의 유닛 개수가 동일해야 하고 필터의 크기가 입력 특성 맵의 크기와 같아야 함, padding= 'valid' 사용해야 하고 stride >=1
  • 밀집 층은 특정 입력 크기를 받아야 하지만, 합성곱 층은 어떤 크기의 이미지도 처리 가능
  • FCN은 합성곱 층만 가지므로 어떤 크기의 이미지에서도 훈련하고 실행할 수 있음
  • FCN 방식은 이미지를 딱 한 번만 처리하기 때문에 효율적

 

YOLO

  • 빠르고 정확한 객체 탐지 도구
  • YOLOV3 구조 설명
    • 각 격자 셀마다 5개 bbox 출력 (4개 좌표 + 존재여부 점수)
      • class 수가 20일 경우 4개 좌표를 가진 5개의 bbox, 5개 존재여부 점수, 20개의 클래스 확률
    • bbox 중심의 절대 좌표를 예측하는 대신 격자 셀에 대한 상대 좌표를 예측
      • (0,0) : 왼쪽위, (1,1) 오른쪽 아래
    • 각 결자 셀에 대해 bbox 중심이 격자 셀 안에 놓인 것만 예측하도록 훈련
    • 로지스틱 활성화 함수 적용해 bbox 좌표가 0~1 사이로 예측
    • 신경망 훈련전에 Yolov3는 앵커 박스라 부르는 5개의 대표 bbox 크기를 찾음
      • 이를 위해 k-mean 알고리즘을 훈련 세트 bbox의 높이와 너비에 적용
      • 각 격자 셀과 각 앵커 박스마다 네트워크는 수직, 수평 방향 스케일 조정 비율의 로그값을 예측
    • 다른 스케일을 가진 이미지를 사용해 훈련, 훈련하는 동안 배치마다 랜덤하게 새로운 이미지 차원 선택
      • 네트워크가 다른 스케일의 객체를 감지하는 방법을 학습

 

시맨틱 분할

  • 각 픽셀은 픽셀이 속한 객체의 클래스로 분류
  • 클래스가 같은 물체는 구별되지 않음
  • 보통의 CNN은 이미지의 왼쪽 아래 어딘가에 사람이 있다고 알 수 있지만 그보다 더 정확히 알지 못함
  • 사전훈련된 CNN -> FCN 변경, 해당 cnn에서 사용한 stride 합이 32이고 이는 입력 이미지 보다 32배 작은 특성 맵을 출력한다는 뜻
    • 따라서 해상도를 32배로 늘리는 업샘플링 층을 추가함
  • 여러가지 업샘플링 방법
    • 이중 선형 보간법
      • 4배 또는 8배로 는리는데 적합
    • 전치 합성곱 층
      • 이미지에 빈 행과 열을 삽입하여 늘린 다음 일반적인 합성곱을 수행
      • 선형 보간에 가까운 작업을 수행하도록 초기화 할수 있지만 훈련되는 층이기 때문에 훈련 과정에서 더 잘 학습됨
        • tf.Conv2DTranspose 층 사용, 해당 stride는 필터의 스탭 크기가 아니라 입력이 얼마나 늘어나는지로 정의 따라서 stride가 클수록 출력이 커짐
      • tensorflow의 다양한 합성곱
        • keras.layers.Conv1D
          • 1D 입력에 대한 합성곱 층 생성, 시계열이나 텍스트
        • keras.layers.Conv3D
          • 3D PET 스캔 같은 3D 입력을 위한 합성곱 층
        • dilation_rate
          • tf.keras의 합성곱 층에 있는 dilation_rate 매개변수를 2 이상으로 지정하면 아트루스 합성곱 층이 됨, 이는 0으로 된 행과 열을 추가하여 늘린 필터로 보통의 합성곱 층을 사용하는 것과 동일
        • tf.nn.depthwise.conv2d()
          • 깊이방향 합성곱 층 생성, 모든 필터를 개개의 입력 채널에 독립적으로 적용 따라서 $fn$개의 필터와 $f'n$개의 채널이 있다면 $fn * f'n$개의 특성 맵을 출력

 

  • 정확도 개선을 위해 아래쪽 층부터 스킵 연결을 추가

    • 32배가 아닌 2배로 출력 이미지를 업샘플링하고 아래쪽 층의 출력을 더하여 해상도를 두 배로 키움
    • 그다음 해당 결과를 16배로 늘려서 업샘플링하여 최종적으로 32배의 업샘플링
      • 해당 방식은 풀링 층에서 잃은 일부 공간 정보를 복원
    • 두 번째 스킵 연결을 사용해 더 낮은 층의 세부정보를 복원하여 최상의 구조를 생성
  • 인스턴스 분할 : 시맨틱 분할과 비슷하지만 동일한 클래스 물체를 하나의 덩어리로 합치는 것이 아니라 각 물체를 구분하여 표시

반응형

'Study > Self Education' 카테고리의 다른 글

Pytorch 기본기 - 2  (0) 2024.07.02
Pytorch 기본기 - 1  (0) 2024.07.02
핸즈온 머신러닝 - 13  (0) 2024.06.19
핸즈온 머신러닝 - 12  (0) 2024.06.19
핸즈온 머신러닝 - 11  (0) 2024.06.19