- 참고 : 핸즈온 머신러닝 2판
합성곱 신경망을 사용한 컴퓨터 비전
- 지각은 간단한 문제가 아니고 이를 이해하기 위해서는 감각기관의 작동 원리를 이해해야 함
- 함성곱 신경망(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에서 몇 개 층마다 최대 풀링 층을 추가하면 전체적으로 일정 수준의 이동 불변성을 얻을 수 있음 또한 최대 풀링은 회전과 확대, 축소에 대해 약간의 불변성을 제공함
- 이와 같은 불변성은 분류 작업처럼 예측을 수행할때 작은 부분에서 영향을 받지 않는 경우 유용
- 이미지 b와 c는 이미지 a와 동일하지만 각각 오른쪽으로 한 픽셀과 두 픽셀 이동한 것
- 위와 같은 a,b,c 2x2 커널, 스트라이드 2인 최대 풀링을 통과
최대 풀링의 단점
- 최대 풀링은 매우 파괴적
- 작은 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가지 규제 기법을 사용
- 훈련하는 동안 F9과 F10의 출력에 드롭아웃을 50% 비율로 적용
- 훈련 이미지를 랜덤하게 여러 간격으로 이동하거나 수평으로 뒤집고 조명을 바꾸는 식으로 데이터 증식 수행
- ReLU 단계 이후 LRN이라 부르는 경쟁적인 정규화 단계를 사용, 가장 강하게 활성화된 뉴런이 다른 특성 맵에 있는 같은 위치의 뉴런을 억제
- 해당 수행은 특성 맵을 각기 특별하게 다른 것과 구분되게 하고, 더 넓은 시각에서 특징을 탐색하도록 만들어 결국 일반화 성능을 향상
- AlexNet의 하이퍼파라미터는 $r=2, a=0.00002, B = 0.75, k=1$
- $r : 깊이 반경, a : 뉴런 활성화 값, B : 뉴런의 정규화된 출력, k : 편향$
- tf.nn.local_response_normalization() 연산을 사용해 구현 가능
- 과대적합을 줄이기 위해 2가지 규제 기법을 사용
GoogLeNet
네트워크가 이전 CNN보다 훨씬 더 깊어짐, 인셉션 모듈이라는 서브 네트워크를 가지고 있어서 이전의 구조보다 훨씬 효과적으로 파라미터를 사용
AlexNet보다 10배나 적은 파라미터를 가짐
설명
- '3x3+1(S)' : 3x3커널, 스트라이드 1, padding='same' 사용한다는 의미
- 처음에 입력 신호가 복사되어 네 개의 다른 층에 전달, 모든 합성곱 층은 ReLU 활성화 함수를 사용
- 두 번째 합성곱 층은 각기 다른 커널크기(1x1, 3x3, 5x5)를 사용해 다른 크기의 패턴을 잡음, 모든 층은 스트라이드 1과 'same' 패딩을 사용하므로 출력의 높이와 너비가 모두 입력과 같음
- 모든 출력을 깊이 연결 층에 깊이방향으로 연결할 수 있음
- 해당 연결 층은 텐서플로의 axis=3 (3은 깊이방향 축), 매개변수로 tf.concat() 연산을 사용해 구현
인셉션 모듈이 1x1 커널의 합성곱 층을 가지는 이유
- 공간상의 패턴을 잡을 수는 없지만 깊이 차원을 따라 놓인 패턴을 잡음
- 입력보다 더 작은 특성 맵을 출력하므로 차원을 줄인다는 의미인 병목 층의 역할을 수행, 연산 비용과 파라미터 개수를 줄여 훈련 속도를 높이고 일반화 성능을 향상
- 합성곱 층의 쌍 ([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
- CNN에 또 다른 꽃이 정말 이미지에 존재하는지 확률을 추정하기 위해 존재여부를 출력을 추가, 시그모이드 활성화 함수를 사용하고 binary crossentropy 사용해 훈련, 이후 임곗값 이하인 bbox 모두 삭제
- 존재여부가 높은 bbox 찾고 해당 박스와 많이 중첩된 다른 bbox 모두 제거
- 더이상 제거할 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의 높이와 너비에 적용
- 각 격자 셀과 각 앵커 박스마다 네트워크는 수직, 수평 방향 스케일 조정 비율의 로그값을 예측
- 다른 스케일을 가진 이미지를 사용해 훈련, 훈련하는 동안 배치마다 랜덤하게 새로운 이미지 차원 선택
- 네트워크가 다른 스케일의 객체를 감지하는 방법을 학습
- 각 격자 셀마다 5개 bbox 출력 (4개 좌표 + 존재여부 점수)
시맨틱 분할
- 각 픽셀은 픽셀이 속한 객체의 클래스로 분류
- 클래스가 같은 물체는 구별되지 않음
- 보통의 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$개의 특성 맵을 출력
- keras.layers.Conv1D
- 이중 선형 보간법
정확도 개선을 위해 아래쪽 층부터 스킵 연결을 추가
- 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 |