AI tech
밑바닥부터 시작하는 딥러닝 정리 Chapter 7 - 합성곱 신경망(CNN)
도리컴
2024. 2. 7. 02:18
반응형
Chapter 7 - 합성곱 신경망(CNN)
개요
- CNN 메커니즘 자세히 설명하고 파이썬으로 구현할 예정
- 사용 : 이미지/음성 인식
7.1 전체 구조
- CNN도 지금까지 본 신경망과 같이 레고처럼 계층을 조합해서 만들 수 있음
- 새로운 등장! → 합성곱 계층, 풀링 계층
- 인접 계층의 모든 뉴런과 연결된 경우 → fully connected → 완전 연결된 계층을 Affine 계층이라는 이름으로 구현했음
- 기존 Affine 계층
- 그럼 CNN 구조는 어떻게 다르지?
- 합성곱 계층과 풀링 계층이 추가됨
- Conv - ReLU - (Pooling) 순으로 연결(풀링 계층은 생략하기도 함)
- 또 다른 특징 : Affine-ReLU를 출력에 가까운 층에서 사용 가능 + 마지막에는 Affine-Softmax 사용 (일반적인 CNN에서 흔히 볼 수 있는 구성)
7.2 합성곱 계층
- padding, stride 등장(CNN 고유의 용어)
- 합성곱 계층의 구조를 살펴볼 예정
완전연결 계층(Affine)의 문제점
- 데이터의 형상이 무시된다는 문제가 있음
- 이미지는 통상 가로, 세로, 채널(색상)로 구성된 3차원 데이터임 → 이걸 Affine 계층에 입력 시 1차원 데이터로 평탄화를 해야 함
- 3차원 형상에는 공간적 정보가 담겨 있는데(가까운 픽셀은 비슷하다는 등, 3차원 속에서 의미를 갖는 패턴들) → 모든 입력을 동등한 뉴런으로 취급하면 이 정보를 못살림
합성곱 계층은?
- 형상을 유지한다~ → 이미지도 3차원 데이터로 입력 받고, 다음 계층에도 3차원으로 전달
- 형상을 가진 데이터(like 이미지) 를 제대로 이해할 가능성이 있다!
- 합성곱 계층의 입출력 데이터 → feature map이라고 부름 / ex: input feature map
합성곱 연산
- 이미지 처리에서 말하는 그 유명한 필터 연산에 해당함!
- 입력은? (4, 4) / 출력은? (2, 2) / 필터는? (3, ,3) → 필터를 ‘커널’이라고도 부름
- 필터의 ‘윈도우’를 일정 간격으로 이동해가며 입력 데이터에 적용
- 윈도우 무엇? - 그림에서 회색 부분 → 필터 크기이고, 한 칸씩 이동하면서 계산을 진행
- 어떤 계산? → 원소끼리 곱한 후 총합(fused mutiply-add → FMA) → 그 결과를 해당 장소에 저장
- 이렇게 모든 장소에서 수행 → 합성곱 연산의 출력 완성~
- 핵심 : 요 필터의 매개변수가 CNN에서의 가중치에 해당하는 겁니다.
- 편향도 존재(항상 하나만 존재, 그림 참고)
패딩
- 입력값 주변을 0으로 채움(보통 폭 단위로 해결)
- 왜? 왜 채워? - 출력 크기를 조정하기 위한 목적이 있음
- 합성곱 연산을 몇 번이나 되풀이하는 심층 신경망? → 크기가 작아지면 문제가 됨
- 즉, 공간적 크기를 고정한 채로 다음 계층에 전달할 수 있다~
- (4, 4) 입력 데이터에 패딩 1 추가해서 (6, 6)이 됨(패딩 원하는 값으로 설정 가능)
스트라이드
- 정의 : 필터를 적용하는 위치의 간격(필터를 얼만 큼 띄엄띄엄 할 건지 → 지금까지 1이었음)
- 2로 하면, 윈도우가 두 칸 씩 이동
패딩, 스트라이드, 출력 크기 계산?
- 수식을 외운다기 보다 직관적으로 이해를 하면 좋겠음주의 : 각 값이 정수로 나누어 떨어져야 함
3차원 데이터의 합성곱 연산
- 핵심 : 필터가 3차원이 되었다~ / 채널 방향으로 feature map이 늘어남
- 주의 : 입력과 필터의 채널 수가 같아야 한다~
- 필터 자체의 크기는 원하는 값으로 설정 가능하나, 모든 채널의 필터는 같은 크기여야 함
블록으로 생각하기
- 여기서, 합성곱 연산의 출력으로 다수의 채널을 내보내려면? → 필터(가중치)를 여러 개 쓰자!
- 필터를 a 쓰면, 출력 맵도 a개 생성
- 편향도 2차원과 마찬가지로 쓰임~
- 합성곱 연산에서는 필터의 수도 고려해야 하기에, 필터의 가중치 데이터는 4차원 데이터임
- 무슨 말? → (출력 채널 수, 입력 채널 수, 높이, 너비) 순으로 나타냄
배치 처리
- 합성곱 연산도 신경망 처리 처럼 배치 처리를 지원하고자 함
- 각 계층의 데이터 차원을 하나 늘려서 4차원으로 저장 → (데이터 수, 채널 수, 높이, 너비) 순주의 : 신경망에 4차원 데이터 하나 흐를 때마다 데이터 N개에 대한 합성곱 연산이 이루어짐(N회 분의 처리를 한 번에 수행)
7.3 풀링 계층(Pooling)
- 풀링? - 세로, 가로 방향의 공간을 줄이는 연산
- 최대 풀링, 평균 풀링 등이 있음
- 최대 풀링 : 대상 영역의 최댓값을 구함 (평균 풀링은 평균)
- 풀링의 윈도우 크기와 스트라이드는 같은 값으로 설정하는 것이 보통임(겹치지 않게 하려고)
풀링 계층의 특징
- 학습해야 할 매개변수가 없음
- 영역의 최댓값/평균 구하는 것이므로, 학습할 게 없음
- 채널 수 불변
- 채널마다 독립적으로 계산하므로, 채널 수 변화 x
- 입력의 변화에 영향이 적다
- 입력 데이터가 바뀌어도 풀링의 결과는 잘 안변함
7.4 합성곱/풀링 계층 구현
4차원 데이터 → 배열로 생성
x = np.random.rand(10, 1, 28 ,28) # 무작위로 데이터 생성
x.shape
# (10, 1, 28, 28)
x[0].shape # (1, 28, 28) # x[0], x[1] 이렇게 데이터 접근
x[0, 0]
im2col로 전개하기(image to column)
- 이게 뭔데 : 합성곱 연산의 문제를 단순하게 만들어주는 함수
- → 입력 데이터를 필터링(가중치 계산)하기 좋게 펼치는 함수
- 펼치면 뭐해? → 그냥 행렬곱 연산으로 3차원 데이터 합성곱 연산 계산할 수 있는거
- for문 쓰면 복잡해지므로 im2col 등장
- 실제로는 필터 적용 영역이 겹치게 되는 경우가 대부분임 / 하지만, 컴퓨터는 큰 행렬곱에 속도가 탁월하기 때문에 빠르게 계산 가능 → 효율 상승
- 입력데이터 전개 끝나면? → 필터(가중치)도 1열로 전개하고, 두 행렬곱 계산하면 됨 → 완전 연결 계층의 Affine 계층에서 한 것과 거의 동일
- 위 그림의 출력데이터는 2차원 행렬인데, CNN은 데이터를 4차원 배열로 저장함 → reshape까지 해주면, 합성곱 게층 구현 흐름 끝!
합성곱 계층 구현하기
- 코드로 구현 / im2col 함수를 책에서 만들어서 제공해줌→ 필터 크기, 스트라이드, 패딩을 고려하여 입력 데이터를 2차원 배열로 전개
import sys, os sys.path.append(os.pardir) from common.util import im2col x1 = np.random.rand(1, 3, 7, 7) # (데이터 수, 채널 수, 높이, 너비) col1 = im2col(x1, 5, 5, stride=1, pad=0) print(col1.shape) # (9, 75) x2 = np.random.rand(10, 3, 7, 7) # 데이터 10개 col2 = im2col(x2, 5, 5, stride=1, pad=0) print(col2.shape) # (90, 75)
- 첫번째 : 배치 1 / 두번째 : 배치 10
- 합성곱 계층 클래스
class Convolution: def __init__(self, W, b, stride=1, pad=0): self.W = W self.b = b self.stride = stride self.pad = pad def forward(self, x): FN, C, FH, FW = self.W.shape N, C, H, W = x.shape out_h = int(1 + (H + 2*self.pad - FH) / self.stride) out_w = int(1 + (W = 2*self.pad - FW) / self.srtride) col = im2col(x, FH, FW, self.stride, self.pad) col_W = self.W.reshape(FN, -1).T # 필터 전개 / reshape에서 -1은 원소 수가 똑같이 유지되도록 자동설정 out = np.dot(col, col_W) + self.b out = out.reshape(N, out_h, out_w, -1).transpose(0, 1, 3, 2) # 적절한 형상으로 바꿈 return out
- 초기화 시 인수 : 필터(가중치), 편향, 스트라이드, 패딩
- 하는 짓 : 입력 데이터를 im2col로 전개 + 필터 전개(reshape사용) → 두 개 행렬곱 구함
- transpose로 형상 변경하기 - 아래 그림 참조
- 합성곱 계층의 역전파는 어떻게?
- Affine계층과 똑
같다 - 다만, im2col을 역으로 처리하려면 col2im을 쓰기(책 코드 찾아보면 나옴)
- Affine계층과 똑
풀링 계층 구현하기
- 어떻게? - 합성곱 계층처럼 im2col 써서 입력 데이터 전개
- 단, 합성곱 계층과 달리, 채널 쪽이 독립적이다!
- 각 부분을 행으로 나열 후, 행 별 최댓값을 적절한 형상으로 변환(성형?)하면 됨(그림)
- 코드
- 3 단계 알면 끝
- 입력데이터 전개
- 행 별 최댓값 구하기
- 적절한 모양으로 성형하기
- 3 단계 알면 끝
class Pooling: def __init__(self, pool_h, pool_w, stride=1, pad=0): self.pool_h = pool_h self.pool_w = pool_w self.stride = stride self.pad = pad def forward(self, x): N, C, H, W = x.shape out_h = int(1 + (H - self.pool_h) / self.stride) out_w = int(1 + (W - self.pool_w) / self.stride) # 전개 (1단계) col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad) col = col.reshape(-1, self.pool_h * self.pool_w) # 최댓값 (2단계) out = np.max(col, axix=1) # axis=1 : 입력 x의 1번째 차원의 축마다 최댓값 구함 # 성형 (3단계) out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2) return out
7.5 CNN 구현하기
- 손글씨 숫자 인식 CNN 조립할 예정(SimpleConvNet)
- hparams가 딕셔너리로 주어짐을 알아야 함
- 코드(초기화 부터)
class SimpleConvNet: def __init__(self, input_dim=(1, 28, 28), conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1}, hidden_size=100, output_size=10, weight_init_std=0.01): # 1. hparams 꺼내서 합성곱 계층의 출력 크기 계산 filter_num = conv_param['filter_num'] filter_size = conv_parmam['filter_size'] filter_pad = conv_parmam['pad'] filter_stride = conv_parmam['stride'] input_size = input_dim[1] conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1 pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))
# 2. 가중치 매개변수 초기화 - 각 완전연결 계층의 가중치와 편향 저장
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(filter_num, input_dim[0],
filter_size, filter_size)
self.params['b1'] = np.zeros(filter_num)
self.params['W2'] = weight_init_std * np.random.randn(pool_outout_size, hidden_size)
self.params['b2'] = np.zeros(hidden_size)
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b3'] = np.zeros(output_size)
# 3. CNN 구성 계층 생성
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'],
self.params['b1'],
conv_param['stride'],
conv_param['pad'])
self.layers['Relu1'] = Relu()
self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = Affine(self.params['W2'],
self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3'],
self.params['b3'])
self.last_layer = SoftmaxWithLoss()
```
- 추론 및 손실 함수 값 구하기(predict, loss)
def predict(self, x): for layer in self.layers.values(): x = layer.forward() # layers의 계층을 맨 앞에서부터 차례로 forward 후 결과 전달 return x def loss(self, x, t): y = self.predict(x) return self.last_layer.forward(y, t) # 마지막 층의 forward 호출해서 처리
- x는 입력 데이터, t는 정답 레이블
- 기울기 구하기(오차역전파법)
- 순전파와 역전파를 반복함 - 적절한 순서로 호출만 해주면 됨
- grads(딕셔너리 변수)에 각 가중치 매개변수 기울기 저장~ → 끝
def gradient(self, x, t): # 순전파 self.loss(x, t) # 역전파 dout = 1 dout = self.last_layer.backward(dout) layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) # 결과 저장 grads = {} grads['W1'] = self.layers['Conv1'].dW grads['b1'] = self.layers['Conv1'].db grads['W2'] = self.layers['Affine1'].dW grads['b2'] = self.layers['Affine1'].db grads['W3'] = self.layers['Affine2'].dW grads['b3'] = self.layers['Affine2'].db return grads
- SimpleConvNet으로 MNIST 데이터셋 학습해보면? (train_convnet.py 코드 참조)
- 정확도는 - 훈련데이터 99.82%, 시험데이터 98.96%
- 작은 네트워크로서 훌륭한 성능이라 할 수 있다~
- 다음 장에서 계층을 더 깊게 해볼 예정(정확도 향상)
결론
- 합성곱 계층, 풀링 계층은 이미지 인식에 필수적인 모듈이다~
- 이미지라는 공간적인 형상에 담긴 특징을 CNN이 잘 파악해서 높은 정확도를 보여준다~
7.6 CNN 시각화하기
- CNN은 이미지 데이터에서 ‘무엇을 보고 있는’ 것일까?
1번째 층의 가중치 시각화
- 합성곱 계층 필터를 이미지로 나타내보자!
- 학습 전과 후 가중치 비교(아래 그림 참조)
- 학습 전 : 무작위 초기화 - 규칙성 없음
- 학습 후 : 뭔가 덩어리(블롭)가 진 것(?) 같은 규칙성이 생겼다~
- 오른쪽 ‘학습 후’ 처럼 규칙성 있는 필터는 ‘무엇을 보고 있는 걸까?
- → 바로 에지(색상이 바뀐 경계선)와 블롭(국소적으로 덩어리진 영역)을 봄
- 결론 : 합성곱 계층의 필터는 에지나 블롭 등의 원시적 정보를 추출할 수 있다~
- → 이런 원시적인 정보가 CNN의 뒷단 계층에 전달됨
층 깊이에 따른 추출 정보 변화
- 1번째 합성곱 계층에선 에지나 블롭 등의 저수준 정보가 추출됨
- 겹겹이 쌓인 CNN 각 계층에선 어떤 정보가 추출될까?
- 더 깊어질수록, 추출 정보(강하게 반응하는 뉴런)는 더 추상화 된다고 함
- AlexNet에 대해 - 일반 사물 인식(자동차나 개)을 수행한 8층 CNN
- 합성곱 계층, 풀링 계층을 여러 겹 쌓고, 마지막에 완전연결 계층 거쳐 결과 출력
- 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 ‘고급’정보로 변화함
- → 즉, 사물의 ‘의미’를 이해하도록 변화하는 것!
- 합성곱 계층, 풀링 계층을 여러 겹 쌓고, 마지막에 완전연결 계층 거쳐 결과 출력
7.7 대표적인 CNN
- 다양한 CNN 네트워크 중 특히 중요한 네트워크 두 개
- CNN의 원조인 LeNet
- 딥러닝이 주목받도록 이끈 AlexNet
LeNet
- 손글씨 숫자 인식하는 네트워크(1998년 제안)
- 단순히 합성곱 계층과 풀링 계층을 반복, 마지막엔 완전연결 계층 거쳐 결과 출력
- 현재의 CNN과 비교하면?
- 활성화 함수 - LeNet은 시그모이드, 현재는 주로 ReLU
- LeNet은 서브샘플링으로 중간 데이터 크기를 줄이지만, 현재는 최대 풀링이 주류임
- LeNet이 20년 전 꺼인데, 현재의 CNN과 큰 차이가 없다는 게 놀랍다고 말함!
AlexNet
- 딥러닝 열풍을 일으키는 데 큰 역할(2012년 발표)
- 구성은 기본적으로 LeNet과 비슷
- LeNet에서 다음과 같은 변화를 줌
- 활성화 함수로 ReLU
- LRN(Local Response Normalization)이라는 국소적 정규화를 실시하는 계층 이용
- 드롭아웃 사용
- 네트워크 구성 면에서는 LeNet과 AlexNet에 큰 차이는 없음
- → 이를 둘러싼 환경과 컴퓨팅 기술이 큰 진보를 이룬 것!(병렬 GPU, 빅데이터 등)
7.8 정리
- CNN - 기본 구성 모듈 : ‘합성곱 계층’, ‘풀링 계층’
- 이 두 계층을 구현 수준으로 이해할 수 있도록 설명함
- CNN은 이미지를 다루는 분야에서는 거의 예외 없이 쓰인다~
반응형