본문 바로가기
Deep Learning/2023 DL 기초 이론 공부

[밑바닥부터 시작하는 딥러닝 1] chap7(합성곱 신경망)

by a._muj 2023. 7. 8.
728x90
반응형

 

 

  • CNN(합성곱 신경망- convolutional neural network)
  • 이미지 인식 + 음성 인식 등 다양한 곳에서 사용됨.
7-1) 전체 구조
  • 기존 : 완전연결계층(=Affine 계층)
  • CNN : Conv계층, Pooling 계층이 더해져서 'Afiine - Relu' -> 'Conv -> Relu -> (pooling)'으로 바뀜.
  •  
7-2) 합성곱 계층
  • 입체적인 데이터가 흐른다는 차이점이 있음
7-2-1) 완전연결 계층의 문제점
  • 데이터의 형상이 무시됨
  • ex) 기존: 3차원 데이터를 1차원 데이터로 바꿔서 계산햇었음
  • 이미지의 경우, 3차원이기에 1차원으로 바꿔버리면 그에 담긴 정보들이 사라져버림
  • CNN의 입출력 데이터: Feature Map(특징맵)이라고 함 - 입력 특징 맵/ 출력 특징 맵
7-2-2) 합성곱 연산
  • 입력 데이터 - 필터(커널) - 출력
  • 윈도우: 일정 간격으로 이동해가며 입력 데이터에 적용 (여기서는 윈도우가 3x3)
  • 입력과 필터에서 대응하는 원소들끼리 곱해서 총합 구함 ⇒ 단일 곱셈 누산
  • 편향 : 필터 적용한 후 출력값(2x2)에 편향을 각각 더해줌 ex) 편향:3, [18, 19, 9, 18]
7-2-3) 패딩
  • 패딩: 출력 크기를 조정할 목적으로 사용
  • 왜 사용?: 기존 (4,4)에 (3,3) 필터를 출력하면 (2,2)로 줄어듬. ⇒ 이런식으로 합성곱 연산을 계속 하면 줄어들어서 출력 크기가 1이 되어버림 → 더 이상 연산이 안됨.
  • 따라서, 패딩을 활용해 입력 데이터의 크기를 유지해서 다음 계층에 그대로 전달을 할 수 있음
7-2-4) 스트라이드

스트라이드: 필터를 적용하는 위치의 간격

  • 스트라이드가 커지면 출력 크기는 작아짐 ( 그만큼 이동하는게 커지므로 칸 수도 작아짐
  • 패딩을 크게 하면 출력 크기가 커짐
  • 입력 크기(H,W), 필터 크기(FH,FW), 출력 크기(OH, OW), 패딩(P), 스트라이드(S)
7-2-5) 3차원 데이터의 합성곱 연산
  • 입력 데이터와 필터의 합성곱 연산은 채널마다 수행 → 결과를 더해 하나의 출력을 얻음
  • 채널: 여기선 3개 ( 필터의 채널 수와 입력 데이터의 채널 수는 같게 설정)
7-2-6) 블록으로 생각하기
  • 직육면체로 생각하면 됨
  • 3차원 데이터를 배열로 나타낼 때는 채널, 높이, 너비 순서 (C, H, W) / 필터 (C, FH, FW)
  • 필터가 여러개 → 출력도 여러개
  • 따라서 필터의 수 도 고려해야 함. ⇒ 필터의 가중치 데이터는 4차원 데이터 (출력 채널의 수, 입력 채널의 수, 높이 , 너비)로 구성됨 (C: 뒤로 몇개, 출력: FN개)
7-2-7) 배치 처리
  • 신경망에서 배치 처리 해줬음 ⇒ 마찬가지로 CNN에서도 미니배치 학습 지원해줌
  • 기존 3차원에서 4차원으로 데이터를 저장하게 됨.
  • (데이터 수, 채널 수, 높이, 너비)
  • ** 4차원 데이터가 하나 흐를 때마다 데이터 N개에 대한 합성곱 연산이 이뤄짐.
  • 즉, 기존 신경망 미니배치와 같은 말 ⇒ N회 분에 대한 처리를 한 번에 수행해줌. ( 미니배치 10, 전체 100⇒ 10회 분에 대한 처리를 한번에 다 돌려버림)
7-3) 풀링 계층
  • 풀링: 가로 세로 방향의 공간을 줄이는 연산

→ 이 경우, 해당 max 값을 추출함 ( 최대 풀링)

  • 풀링의 윈도우 크기와 스트라이드는 같은 값으로 설정하는 것이 보통 (윈도우: 2x2, 스트라이드(보폭: 2))
풀링 계층의 특징
  1. 학습해야 할 매개변수가 없다 (ex) 그냥 최댓값만 구하면 되므로)
  1. 채널 수의 변화가 없음
    • 독립적으로 계산하기 때문에
  1. 입력의 변화에 영향을 적게 받는다
    • 데이터가 오른쪽으로 이동한다 한 들 변화는 없음
7-4) 합성곱/풀링 계층 구현
7-4-1) 4차원 배열
  • 데이터의 형상이 (10,1,28,28) 이라면 ⇒ 데이터가 10개, 채널 1개, 28*28
  • x[0].shape ⇒ (1,28,28) ⇒ 첫 번째 데이터에 접근
  • ⇒ im2col이라는 트릭이 문제를 단순하게 만들어줌.
7-4-2) im2col(image to column) 데이터 전개하기
  • 합성곱 연산을 구현하기 위해선 for문 겹겹이 써야함 ⇒ 단점.
  • ⇒ im2col( 입력 데이터를 가중치 계산하기 좋게 펼치는 함수
  • ex) 3차원 입력 데이터에 im2col을 적용하면 2차원 행렬로.
  • 실제 상황에서는 필터 적용 영역이 겹치는 경우가 대부분
  • 필터 적용 영역이 겹치게 되면 im2col로 전개한 후의 원소 수가 원래보다 많아짐 (메모리 더 많이 소비)
  • 컴퓨터는 큰 행렬을 만들어 계산하는 데 탁월해 효율 높일 수 있음
  • im2col 입력 데이터 전개 후 합성곱계층 필터 1열전개하고 행렬곱 계산

 

  • 이미지를 열로 붙임
7-4-3) 합성곱 계층 구현
# im2col 사용 구현   import sys, os sys.path.append('/deep-learning-from-scratch') 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)   x2 = np.random.rand(10, 3, 7, 7) # 데이터 10개 col2 = im2col(x2, 5, 5, stride=1, pad=0) print(col2.shape)   >>> (9, 75) (90, 75)
  • 두 가지 경우 모두 2번째 차원의 원소는 75개
    • 필터의 원소 수와 같음 (채널3, 5*5 데이터)
  • 배치크기가 1일 때는 (9, 75) 10일 때는 10배인 (90, 75)
# 합성곱 계층 구현 - Convolution 클래스   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.stride)       col = im2col(x, FH, FW, self.stride, self.pad) # 입력데이터 전개     col_W = self.W.reshape(FN, -1).T # 필터 전개     out = np.dot(col, col_W) + self.b       out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)       return out
  • eshape 두 번째 인수 -1로 지정하면 다차원 배열의 원소 수가 변환 후에도 똑같이 유지되도록 묶어줌
  • transpose함수를 이용해 출력데이터를 적절한 형상으로 바꾸어 줌
    • 인덱스를 지정하여 축의 순서 변경
  • 역전파에서는 im2col 대신 col2im 함수 사용
7-4-4) 풀링 계층 구현
  • 풀링의 경우에는 채널이 독립적이라는 점이 합성곱계층과 다른 점
  • 합성곱 계층의 경우, 각 필터와 데이터를 곱해서 한 칸에 다 작성 but, 풀링의 경우 이어붙임?!
  • 전개 후 최댓값 구하고 적절한 형상으로 바꾸어줌
    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, axis=1) #최댓값 여기서는 축 기준으로의 최댓값                  #적절한 모양으로 성형 (3) 				out=out.reshape(n, out_h, out_w, c).transpose(0, 3, 1, 2)                  return out

 

7-5) CNN 구현하기
  • CNN 네트워크는 Convolution-ReLU-Pooling-Affine-ReLU-Affine-Softmax  순으로  흐름
  • 하이퍼 파리미터 설정
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):         filter_num = conv_param['filter_num']         filter_size = conv_param['filter_size']         filter_pad = conv_param['pad']         filter_stride = conv_param['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))
  • Parameters

    input_size : 입력 크기(MNIST의 경우엔 784) hidden_size_list : 각 은닉층의 뉴런 수를 담은 리스트(e.g. [100, 100, 100]) output_size : 출력 크기(MNIST의 경우엔 10) activation : 활성화 함수 - 'relu' 혹은 'sigmoid' weight_init_std : 가중치의 표준편차 지정(e.g. 0.01) 'relu'나 'he'로 지정하면 'He 초깃값'으로 설정 'sigmoid'나 'xavier'로 지정하면 'Xavier 초깃값'으로 설정

  • pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/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_output_size, hidden_size)         self.params['b2'] = np.zeros(hidden_size)         self.params['W3'] = weight_init_std * \                             np.random.randn(hidden_size, output_size)         self.params['b3'] = np.zeros(output_size)    
  • 계층 생
# 계층 생성         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(x)          return x  def loss(self, x, t):         """손실 함수를 구한다.         Parameters         ----------         x : 입력 데이터         t : 정답 레이블         """         y = self.predict(x)         return self.last_layer.forward(y, t)
  • 여기까지가 순전파 코드
  • 역전파 기울기 구하는 구현
def gradient(self, x, t):         """기울기를 구한다(오차역전파법).         Parameters         ----------         x : 입력 데이터         t : 정답 레이블         Returns         -------         각 층의 기울기를 담은 사전(dictionary) 변수             grads['W1']、grads['W2']、... 각 층의 가중치             grads['b1']、grads['b2']、... 각 층의 편향         """         # forward         self.loss(x, t)          # backward         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'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db         grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db         grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db          return grads
7-6) CNN 시각화
  • 학습 전의 필터는 무작위로 초기화 됨 ⇒ 규칙성이 없음
  • 학습 후의 필터는 규칙있는 이미지가 됨
  • 흰색부분과 검정색 부분으로 뚜렷하게 나뉜 것 → ex) 왼쪽이 흰색, 오른쪽이 검정 → 세로 방향의 에지에 반응하는 필터임.
  • ex)

→ 층을 여러겹 쌓게 되면 더 복잡해지고 추상화된 정보가 추출됨 → 사물에 대한 의미 파악 정보를 더 많이 알 수 있게 됨. ex) 처음에는 에지만 반응 , 깊어질수록 더 구체화된 그림에

7-7) 대표적 CNN
1) LeNet
  • 합성곱 계층과 풀링 꼐층을 반복 후, 완전연결 계층을 거침
  • 손글씨 숫자 인식 네트워크
  • LeNet과 현 CNN의 차이점
    1. sigmoid vs ReLU
    1. 서브 샘플링(중간 데이터 크기를 줄임) vs 최대 풀링 사용
2) AlexNet
  • LeNet과의 차이점
    • 활성화 함수로 ReLU 사용
    • LRN이라는 국소적 정규화 실시하는 계층 이용
    • 드롭 아웃 사용
728x90
반응형