페이지

2018년 5월 21일 월요일

CHAPTER 7 합성곱 신경망(CNN)

이번 장의 주제는 합성곱 신경망(convolutional neural network, CNN)입니다. CNN은 이미지 인식과 음성 인식 등 다양한 곳에서 사용되는데, 특히 이미지 인식 분야에서 딥러닝을 활용한 기법은 거의 다 CNN을 기초로 하죠. 이번 장에서는 CNN의 메커니즘을 자세히 설명하고 이를 파이썬으로 구현해보겠습니다.

7.1 전체 구조
우선 CNN의 네트워크 구조를 살펴보며 전체 틀을 이해해보기로 하죠, CNN도 지금까지 본 신경망과 같이 레고 블록처럼 계층을 조합하여 만들 수 있습니다. 다만, 합성곱 계층(convolutional layer)과 폴링 계층(pooling layer)이 새롭게 등장합니다. 합성곱 계층과 폴링 계층의 상세 내용은 잠시 후 설명하기로 하고, 이번 절에서는 어떻게 조합하여 CNN을 만드는지를 먼저 보겠습니다.
지금까지 본 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있었습니다. 이를 완전연결(fullyconnected,전결합)이라고 하며, 완전히 연결된 Affine 계층이라는 이름으로 구현했습니다. 이 Affine 계층을 사용하면, 가령 층이 5개인 완전연결 신경망은 [그림 7-1]과 같이 구현할 수 있습니다.

그림 7-1 완전연결 계층(Affine 계층)으로 이뤄진 네트워크의 예

2 -> affine -> Relu --> Affine->ReLU-->Affine->ReLU-->Affine->ReLU-->Affine-->Softmax

[그림7-1]과 같이 완전연결 신경망은 Affine 계층 뒤에 활성화 함수를 같는 ReLU계층(혹은 Sigmoid계층)이 이어집니다. 이 그림에서 Affine-ReLU조합이 4개가 쌓였고, 마지만 5번째 층은 Affine 계층에 이어 소프트맥스 계층에서 최종 결과(확률)를 출력합니다.

그림 7-2 CNN으로 이뤄진 네트워크의 예: 합성곱 계층과 폴링 계층이 새로 추가
2 ->Conv->ReLU->Pooling-->Conv->ReLU->Polling-->Conv->ReLU-->Affine->ReLU-->Affine->Sortmax-->

[그림 7-2]와 같이 CNN에서는 새로운 '합성곱 계층Conv'과 '폴링 계층Pooling'이 추가됩니다.
CNN의 계층은 'Conv-ReLU-(Pooling)'흐름으로 연결됩니다(폴링 계층는 생략하기도 합니다). 지금까지의 'Affine-ReLU'연결이 'Conv-ReLU-(Pooling)'으로 바뀌었다고 생각할 수 있겠죠.
[그림 7-2]의 CNN에서 주목할 또 다른 점은 출력에 가까운 층에서는 지금까지의 'Affine-ReLU'구성을 사용할 수 있다는 것입니다. 또, 마지막 출력 계층에서는 'Affine-Softmax'조합을 그대로 사용합니다. 이상은 일반적인 CNN에서 흔히 볼수 있는 구성입니다.

7.2 합성곱 계층
CNN에서는 패딩(padding), 스트라이드(stride)등 CNN 고유의 용어가 등장합니다. 또 각 계층 사이에는 3차원 데이터 같이 입체적인 데이터가 흐른다는 점에서 완전 연결 신경망과 다릅니다. 그래서 CNN을 처음 배울 때는 어렵게 느낄지도 모르지만 이책이 있으니 걱정은 넣어두셔도 좋습니다. 우선 이번 절에서는 CNN 에서 사용하는 합성곱 계층의 구조를 차분히 살펴보기로 하겠습니다.

7.2.1 완전연결 계층의 문제점
지금까지 본 완전연결 신경망에서는 완전연결 계층(Affine 계층)을 사용했습니다. 완전 연결 계층에서는 인접하는 계층의 뉴런이 모두 연결되고 출력의 수는 임의로 정할 수 있습니다.

완전연결 계층의 문제점은 무엇일까요? 바로 '데이터의 형상이 무시'된다는 사실입니다. 입력데이터가 이미지인 경우를 예로 들면, 이미지는 통상 세로.가로.채널(색상)으로 구성된 3차원 데이터입니다. 그러나 완전연결 계층에 입력할 때는 3차원 데이터를 평평한 1차원 데이터로 평타화해줘야 합니다. 사실 지금까지의 MNIST 데이터셋을 사용한 사례에서는 현상이 (1, 28, 28)인 이미지(1채널, 세로28픽셀, 가로 28픽셀)를 1줄로 세운 784개의 데이터를 첫 Affine 계층에 입력했습니다.

이미지는 3차원 형상이며, 이 형사에는 소중한 공간적 정보가 담겨 있죠. 예를 들어 공간적으로 가까운 픽셀은 값이 비슷하거나, RGB의 각 체널은 서로 밀접하게 관련되어 있거나, 거리가 먼 픽셀끼리는 별 연관이 없는 등, 3차원 속에서 의미를 갖는 본질적인 패턴이 숨어 있을 것입니다. 그러나 완전 연결 계층은 형상을 무시하도 모든 입력 데이터를 동등한 뉴런(같은 차원의 뉴런)으로 취급하여 형상에 담긴 정보를 살릴수 없습니다.

한편, 합성곱 계층은 형상을 유지합니다. 이미지도 3차원 데이터로 입력받으며, 마찬가지로 다음 계층에도 3차원 데이터로 전달합니다. 그래서 CNN에서는 이미지처럼 형상을 가진 데이터를 제대로 이해할 (가능성이 있는)것 입니다.

CNN에서는 합성곱 계층의 입출력 데이터를 특징 맵(feature map)이라고도 합니다. 합성곱 계층의 입력 데이터를 입력 특징 맵(input feature map), 출력 데이터를 출력 특징 맵(output feature map)이라고 하는 식이죠. 이 책에서는 '입출력 데이터'와 '특징 맵'을 같은 의미로 사용합니다.

7.2.2 합성곱 연산
합성곱 계층에서의 합성곱 연산을 처리합니다. 합성곱 연산은 이미지 처리에서 말하는 필터 연산에 해당하죠. 구체적인 예를 보며 설명하겠습니다.

그림 7-3 합성곱 연산의 예:합성곱 연산을 (*)기호로 표기

[그림 7-3]과 같이 합성곱 연산은 입력 데이터에 필터를 적용합니다. 이 예에서 입력 데이터는 세로.가로 방향의 형상을 가졌고, 필터 역시 세로.가로 방향의 차원을 갖습니다.  데이터와 필터의 형상을 (높이height, 너비width)로 표기하며, 이 예에서는 입력은 (4,4), 필터는 (3,3),출력은 (2,2)가 됩니다. 문헌에 따라 필터를 커널이라 칭하기도 합니다.

그럼[그림 7-3]의 합성곱 연산 예에서 어떤 계산이 이뤄지는지 설명하겠습니다.[그림 7-4]는 이 합성곱 연산의 계산 순서를 그려본 것입니다.

합성곱 연산은 필터의 원도우window를 일정 간격으로 이동해가며 입력 데이터에 적용합니다. 여기에서 말하는 원도우는 [그림 7-4]의 회색 3*3 부분을 가리킵니다. 이 그림에서 보듯 입력과 필터에서 대응하는 원소끼지 곱한 후 그 총합을 구합니다( 이계산을 단일 곱셈-누산fused multiply-add, FMA이라 합니다). 그리고 그 결과를 출력의 해당 장소에 저장합니다. 이 과정을 모든 장소에서 수행하면 합성곡 연산의 출력이 완성됩니다.

자, 완전연결 신경망에서는 가중치 매개변화와 편향이 존재하는데, CNN에서는 필터의 매개변수가 그동안의 '가중치'에 해당합니다. 그리고 CNN에서 편향이 존재합니다. [그림 7-3]은 필터를 적용하는 단계까지만 보여준 것이고, 편향까지 포함하면 [그림 7-5]와 같은 흐름이 됩니다.
[그림 7-5]와 같이 편향은 필터를 적용한 후의 데이터에 더해집니다. 그리고 편향은 항상 하나(1*1)만 존재합니다. 그 하나의 값을 필터를 적용한 모든 원소에 더하는 것이죠.

7.2.3 패딩
합성곱 연산을 수행하기 전에 입력 데이터 주변을 특정 값(예컨대 0)으로 채우기도 합니다. 이를 패딩(padding)이라 하며, 합성곱 연산에서 자주 이용하는 기법입니다. 예를 들어 [그림 7-6]은 (4,4)크기의 입력 데이터에폭이 1인 패딩을 적용한 모습입니다. 폭 1짜리 패딩이라 하면 입력 데이터 사방 1픽셀을 특정 값으로 채우는 것이죠.

[그림 7-6]과 같이 처음에 크기가 (4,4)인 입력 데이터에 패딩이 추가되어 (6,6)이 됩니다. 이 입력에 (3,3) 크기의 필터를 걸면(4,4) 크기의 출력 데이터가 생성됩니다. 이 예에서는 패딩을 1로 설정했지만, 2나 3등 원하는 정수로 설정할 수 있습니다. 만약 [그림 7-5]에 패딩을 2로 설정하면 입력 데이터의 크기는 (8,8)이 되고 3으로 설정하면 (10,10)이 됩니다.

note
패딩은 주로 출력 크기를 조정할 목적으로 사용합니다. 예를 들어(4,4)입력 데이터에 (3,3)필터를 적용하면 출력은 (2,2)가 되어, 입력보다 2만큼 줄어듭니다. 이는 합성곱 연산을 몇 번이나 되풀이하는 심층 신경망에서는 문제가 될 수 있습니다. 합성곱 연산을 거칠 때마다 크기가 작아지면 어느 시점에서는 출력 크기가 1이 되어버리겠죠, 더 이상의 합성곱 연산을 적용할 수 없다는 뜻입니다. 이러한 사태를 막기위해 피딩을 사용합니다. 앞의 예에서는 패딩의 폭을 1로 설정하니 (4,4)입력에 대한 출력이 같은 크기인 (4,4)로 유지되었습니다. 한마디로 입력 데이터의 공간적 크기를 고정한 채로 다음 계층에 전달할 수 있습니다.

7.2.4 스트라이드
필터를 적용하는 위치의 간격을 스트라이드(stride)라고 합니다. 지금까지 본 예는 모두 스트라이드가 1이었지만, 예를 들어 스트라이드를 2로 하면 필터를 적용하는 원도우가 두 칸씩 이동합니다.
[그림 7-7]에서는 크기가 (7,7)인 입력 데이터에 스트라이드를 2로 설정한 필터를 적용합니다. 이처럼 스트라이드는 필터를 적용하는 간격을 지정합니다.

그런데 스트라이드를 2로 하니 출력은 (3,3)이 되는 군요, 이처럼 스트라이드를 키우면 출력 크기는 작아집니다. 한편, 패딩을 크게하면 출력 크기가 커졌죠. 이러한 관계를 수식화하면 어떻게 될까요? 이어서 패딩, 스트라이드, 출력 크기를 어떻게 계산하는지 살펴보겠습니다.

입력 크기를 (H, W), 필터 크기를 (FH, FW), 출력 크기를 (OH, OW), 패딩을 P, 스트라이드를 S라 하면, 출력 크기는 다음 식으로 계산합니다.

OH = (H + 2P -FH)/S +1
OW = (W + 2P -FW)/S +1

그러면 이 식을 사용하여 연습을 좀 해봅시다.
예1: 입력:(4,4), 패딩:1, 스트라이드:1, 필터:(3,3)

OH = (4+2 * 1-3)/1 +1 = 4
OW = (4+2 * 1-3)/1 +1 = 4

예2:입력:(7,7), 패딩:0, 스트라이드:2, 필터:(3,3)

OH = (7+2*2-3)/2 +1 =3
OW = (7+2*2-3)/2 + 1 = 3

예3: 입력(28,31), 패딩:2, 스트라이드:3, 필터:(5,5)

OH = (28+2*2-5)/3 +1 = 10
OW = (31 + 2*2-5)/3 + 1 =11

이상의 예에서처럼[식 7.1]에 단순히 값을 대입하기만 하면 출력 크기를 구할 수 있습니다.
단,[식 7.1]의 (W + 2P -FW)/S 와 (H+2P-FH)/S가 정수로 나눠떨어지는 값이어야 한다는 점에 주의하세요.
* 출력 크기가 정수가 아니면 오류를 내는 등의 대응을 해줘야겠죠. 덧붙여서, 딥러닝 프레임워크 중에는 값이 딱 나눠떨어지지 않을 때는 가장 가까운 정수로 반올림하는 등, 특별히 에러를 내지 않고 진행하도록 구현하는 경우도 있습니다.

7.2.5 3차원 데이터의 합성곱 연산
지금까지 2차원 형상을 다루는 합성곱 연산을 살펴봤습니다. 그러나 이미지만 해도 세로.가로에 대해서 채널까지 고려한 3차원 데이터입니다. 이번 절에서는 조금 전과 같은 순서로, 채널까지 고려한 3차원 데이터를 다루는 합성곱 연산을 살펴보겠습니다.

[그림 7-8]은 3차원 데이터의 합성곱 연산 예입니다. 그리고 [그림 7-9]는 계산 순서입니다. 2차원일 때(그림 7-3)와 비교하면, 길이 방향(채널 방향)으로 특징 맵이 늘어났습니다. 채널쪽으로 특정 맵이 여러 개 있다면 입력 데이터와 필터의 합성곱 연산을 채널마다 수행하고, 그 결과를 더해서 하나의 출력을 얻습니다.

그림 7-8 3차원 데이터 합성곱 연산의 예

3차원의 합성곱 연산에서 주의할 점은 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다는 것입니다. 이 뎨에서는 모두 3개로 일치합니다. 한편, 필터 자체의 크기는 원하는 값으로 설정할 수 있습니다(단, 모든 채널의 필터가 같은 크기여야 합니다). 이 예에서는 필터의 크기가 (3,3)이지만, 원한다면(2,2)나 (1,1)또는 (5,5)등으로 설정해도 되는 것이죠. 다시 말하지만, 필터의 채널 수는 입력 데이터의 채널 수와 같도록(이 예에서는 3)설정해야 합니다.

7.2.6 블록으로 생각하기
3차원의 합성곱 연산은 데이터와 필터를 직육면ㅊ 블록이라고 생각하면 쉽습니다. 블록은[그림 7-10]과 같은 3차원 직육면체입니다. 또, 3차원 데이터를 다차원 배열로 나타낼 때는 (채널 channel, 높이height, 너비width)순서로 쓰겠습니다. 예를 들어 채널 수 C, 높이H, 너비W인 데이터의 형상은(C,H,W)로 씁니다. 필터로 같은 순서로 씁니다. 예를 들어 채널 수 C, 필터 높이 FH(Filter Height),필터 너비FW(Filter Width)의 경우(C,FH, FW)로 씁니다.

그림 7-10 합성곱 연산을 직육면체 블록으로 생각한다. 블록의 형상에 주의할 것!
(C,H,W) * (C,FH,FW) --> (1, OH, OW)

자, 이 예에서 출력 데이터는 한 장의 특정 맵입니다. 한 장의 특징 맵을 다른 말로 하면 채널이 1개인 특징 맵이죠. 그럼 합성곱 연산의 출력으로 다수의 채널을 내보내면 어떻게 해야 할까요? 그 답은 필터(가중치)를 다수 사용하는 것입니다. 그림으로 [그림 7-11]처럼 됩니다.

이 그림과 같이 필터를 FN개 적용하면 출력 맵도 FN개가 생성됩니다. 그리고 그 FN개의 맵을 모이면 형상이 (FN, OH, OW)인 블록이 완성됩니다. 이 완성된 블록을 다음 계층으로 넘기겠다는 것이 CNN의 처리 흐름입니다.

이상에서 보듯 합성곱 연산에서는 필터의 수도 고려해야 합니다. 그런 이유로 필터의 가중치 데이터는 4차원 데이터이며(출력 채널 수, 입력 채널 수, 높이, 너비)순으로 씁니다. 예를 들어 채널 수 3, 크기 5*5인 필터가 20개 있다면 (20, 3, 5,5)로 씁니다.

자, 합성곱 연산에도 (와전연결 계층과 마찬가지로)편향이 쓰입니다.[그림 7-12]은 [그림 7-11]에 편향을 더한 모습입니다.

[그림 7-12]에서 보듯 편향은 채널 하나에 값 하나씩으로 구성됩니다. 이 예에서는 편향의 형상은 (FN,1,1)이고, 필터의 출력 결과의 현상은(FN, OH, OW)입니다. 이를 두 블록을 더하면 편향의 각 값이 필터의 출력인(FN, OH, OW)블록의 대응 채널의 원소 모두에 더해집니다. 참고로, 현상이 다른 블록의 덧셈은 넘파이의 브로드캐스트 기능으로 쉽게 구현할 수 있습니다.

7.2.7 배치 처리
신경망 처리에서는 입력 데이터를 한 덩어리로 묶어 배치로 처리했습니다. 완전연결 신경망을 구현하면서는 이 방식을 지원하여 처리 효율을 높이고, 미니배치 방식의 학습도 지원하도록 했습니다.

합성곱 연산도 마찬가지로 배치 처리를 지원하고자 합니다. 그래서 각 계층을 흐르는 데이터의 차원을 하나 늘려 4차원 데이터로 저장합니다. 구체적으로는 데이터를 (데이터 수, 채널 수, 높이, 너비)순으로 저장합니다. 데이터가 N개일 때[그림 7-12]를 배치 처리한다면 데이터 형태가 [그림 7-13]처럼 되는 것이죠.

배치 처리 시의 데이터 흐름을 나타낸[그림 7-13]을 보면 각 데이터의 선두에 배치용 차원을 추가했습니다. 이처럼 데이터는 4차원 형상을 가진 채 각 계층을 타고 흐릅니다. 여기에서 주의할 점으로는 신경망에 4차원 데이터가하나 흐를때마다 데이터 N개에 대한 합성곱 연산이 이뤄진다는 것입니다. 즉 N회 분의 처리를 한 번에 수행하는 것이죠.

7.3 풀링 계층
풀링은 세로.가로 방향의 공간을 줄이는 연산입니다. 예를 들어 [그림 7-14]와 같이 2*2영역을 원소 하나로 집약하여 공간 크기를 줄입니다.

그림 7-14 최대 폴링의 처리 순서

[그림 7-14]는 2*2 최대 폴링(max pooling,맥스 폴링)을 스트라이드 2로 처리하는 순서입니다. 최대 폴링은 최댓값max을 구하는 연산으로, '2*2'는 대상 영역의 크기를 뜻합니다. 즉 2*2 최대 폴링은 그림과 같이 2*2크기의 영역에서 가장 큰 원소 하나를 꺼냅니다. 또, 스트라이드는 이 예에서는 2로 설정했으므로 2*2 원도우가 원소 2칸 간격으로 이동합니다. 참고로, 폴링의 원도우 크기와 스트라이드는 같은 값으로 설정하는 것이 보통입니다. 예를 들어 원도우가 3*3이면 스트라이드는 2으로, 원도우가 4*4이면 스트라이드를 4로 설정합니다.

WARING
폴링은 최대 폴링 외에도 평균 폴링(average pooling)등이 있습니다. 최대폴링은 대상 영역에서 최댓값을 취하는 연산인 반면, 평균 폴링은 대상 영역의 평균을 계산합니다. 이미지 인식 분야에서는 주로 최대 폴링을 사용합니다. 그래서 이 책에서 폴링 계층이라고 하면 최대 폴링을 말하는 것 입니다.

7.2.1 폴링 계층의 특징
폴링 계층의 특징은 무엇일까요?

학습해야 할 매개변수가 없다
풀링 계층은 합성곱 계층과 달리 학습해야 할 매개변수가 없습니다. 폴링은 대상 영역에서 최댓값이나 평균을 취하는 명확한 처리이므로 특별히 학습할 것 이 없습니다.

채널 수가 변하지 않는다
풀링 연산은 입력 데이터의 채널 수 그대로 출력 데이터로 내보냅니다. [그림 7-15]처럼 채널마다 독립적으로 계산하기 때문입니다.

입력의 변화에 영향을 적게 받는다(강건하다)
입력 데이터가 조금 변해도 폴링의 결과는 잘 변하지 않습니다. 예를 들어[그림 7-16]은 입력 데이터의 차이(데이터가 오른쪽으로 1칸씩 이동)를 폴링이 흡수해 사라지게 하는 모습을 보여줍니다.

7.4 합성곱/폴링 계층 구현하기
지금까지 합성곱 계층과 폴링 계층에 대해 자세히 설명했습니다. 이번 절에서는 이들 두 계층을 파이썬으로 구현해보겠습니다. "5장 오차역전파법"에서 설명한 것처럼 이번 절에서 구현하는 클래스에도 forward와 backward메소드를 추가하여 모듈로 이용할 수 있도록 했습니다.

합성곱 계층과 폴링 계층은 복잡해 보이지만, 사실 '트릭'을 사용하면 쉽게 구현할 수있습니다. 이번 절에서는 그 트릭을 활용해 문제를 간단히 하면서 합성곱 계층을 구현해 보겠습니다.

7.4.1 4차원 배열
앞에서 설명한 대로 CNN에서 계층 사이를 흐르는 데이터는 4차원입니다. 예를 들어 데이터의 형상이 (10, 1, 28, 28)이라면, 이는 높이 28, 너비 28, 채널1개인 데이터가 10개라는 이야기 입니다. 이를 파이썬으로 구현하면 다음과 같습니다.

>>> x = np.random.rand(10, 1, 28, 28)  #무작위로 데이터 생성
>>> x.shape
(10, 1, 28, 28)

여기에서 (10개 중) 첫번째 데이터에 접근하려면 단순히 x[0]이라고 씁니다(파이썬의 인덱스는 0부터 시작합니다). 마찬가지로 두 번째 데이터는 x[1] 위치에 있습니다.

>>>x[0].shape  #(1, 28, 28)
>>>x[1].shape  #(1, 28, 28)

또, 첫 번째 데이터의 첫 채널의 공간 데이터에 접근하려면 다음과 같이 적습니다.

>>> x[0,0] #또는 x[0][0]

이처럼 CNN은 4차원 데이터를 다룹니다. 그래서 합성곱 연산의 구현은 복잡해질 것 같지만, 다음 절에서 설명하는 im2col이라는 '트릭'이 문제를 단순하게 만들어 줍니다.

7.4.2 im2col로 데이터 전개하기
합성곱 연산을 곧이곧대로 구현하려면 for문을 겹겹이 써야겠죠. 생각만 해도 궈찮고, 또 넘파이에 for문을 사용하면 성능이 떨어진다는 단점도 있습니다(넘파이에서는 원소에 접근할 때 for문을 사용하지 않는 것이 바람직합니다). 이번 절에서는 for 문 대신 im2col이라는 편의 하수를 사용해 간단하게 구현해 보겠습니다.

im2col은 입력 데이터를 필터링(가중치 계산)하기 좋게 전개하는(펼치는)함수입니다. [그림 7-17]과 같이 3차원 입력 데이터에 im2col을 적용하면 2차원 행렬로 바뀝니다(정확히는 배치 안의 데이터 수까지 포함한 4차원 데이터를 2차원으로 변환합니다).

im2col은 필터링하기 좋게 입력 데이터를 전개합니다. 구체적으로 [그림 7-18]과 같이 입력 데이터에서 필터를 적용하는 영역(3차원 블록)을 한 줄로 늘어 놓습니다. 이 전개를 필터를 적용하는 모든 영역에서 수행하는게 im2col입니다.

[그림 7-18]에서는 보기에 좋게끔 스트라이드를 크게 잡아 필터의 적용 영역이 겹치지 않도록 했지만, 실제 상황에서는 영역이 겹치는 경우가 대부분입니다. 필터 적용 영역이 겹치게 되면 im2col로 전개한 후의 원소 수가 원래 블록의 원소 수보다 많아집니다. 그래서 im2col을 사용해 구현하면 메모리를 더 많이 소비하는 단점이 있습니다. 하지만 컴퓨터는 큰 행렬을 묶어서 계산하는 데 탁월합니다. 예를 들어 행렬 계산 라이브러리(선형 대수 라이브러리)등은 행렬 계산에 고도로 최적화되어 큰 행렬의 곱셈을 빠르게 계산할 수 있습니다. 그래서 문제를 행렬 계산으로 만드면 선형 대수 라이브러리를 활용해 효율을 높일 수 있습니다.

NOTE
im2col은 "image to column", 즉 '이미지에서 행렬로'라는 뜻입니다. 카페Caffe와 체이너Chainer등의 딥러닝 프레임워크는 im2col이라는 이름의 하수를 만들어 합성곱 계층을 구현할 때 이용하고 있습니다.

im2col로 입력 데이터를 전재한 다음에는 합성곱 계층의 필터(가중치)를 1열로 전재하고, 두 행렬의 내적을 계산하면 됩니다(그림 7-19), 이는 완전연결 계층의 Affine 계층에서 한 것과 거의 같습니다.

그림 7-19 합성곱 연산의 필터 처리 상세 과정: 필터를 세로로 1열로 전개하고, im2col이 전개한 데이터와 행렬 내적을 계산합니다. 마지막으로 출력 데이터를 변형(reshape)합니다.

[그림 7-19]와 같이 im2col방식으로 출력한 결과는 2차원 행렬입니다. CNN은 데이터를 4차원 배렬로 저장하므로 2차원 출력 데이터를 4차원으로 변형reshape합니다. 이상의 합성곱 계층의 구현 흐름입니다.

7.4.3 합성곱 계층 구현하기
이 책에서는 im2col 함수를 미리 만들어 제공합니다. 사실 그 구현은 간단한 함수 10개 정도를 묶은 것이니, 궁금한 분은 common/util.py 를 참고하세요.

자, im2col 함수의 인터페이스는 다음과 같습니다.

im2col(input_data, filter_h, filter_w, stride=1, pad=0)
-input_data - (데이터 수, 채널 수, 높이, 너비)의 4차원 배열로 이뤄진 입력 데이터
-filter_h -필터의 높이
-filter-w -필터의 너비
- stride - 스트라이드
- pad - 패딩

이 im2col은 '필터 크기', '스트라이드', '패딩'을 고려하여 입력 데이터를 2차원 배열로 전개합니다. 그러면 이 im2col을 실제로 사용해 봅시다.

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(데이터 1개), 채널은 3개, 높이.너비가 7*7의 데이터이고, 두 번째는 배치 크기만 10이고 나머지는 첫 번째와 같습니다. im2col 함수를 적용한 두 경우 모두 2번째 차원의 원소는 75개입니다. 이 값은 필터의 원소 수와 같죠(채널 3개, 5*5 데이터). 또한, 배치 크기가 1일 때는 im2col의 결과의 크기가 (9, 75)이고, 10일때는 그 10배인(90, 75)크기의 데이터가 저장됩니다.

이제 이 im2col을 사용하여 합성곱 계층을 구현해보죠. 여기에서는 합성곱 계층을 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

합성곱 계층은 필터(가중치), 편향, 스트라이드, 패딩을 인수로 받아 초기화합니다. 필터는 (FN, C, FH, FW)의 4차원 형상입니다. 여기서 FN은 필터 개수, C는 채널, FH는 필터 높이, FW는 필터 너비 입니다.

앞의 합성곱 구현 코드에서 중요한 부분을 굵게 표시했습니다. 이 부분에서 입력 데이터를 im2col로 전개하고 필터도 reshape을 사용해 2차원 배열로 전개합니다. 그리고 이렇게 전개한 두 행렬의 내적을 구합니다.

필터를 전개하는 부분(코드 중 굵은 글씨)은 [그림 7-19]에서 보듯 각 필터 블록을 1줄로 펼쳐 세웁니다. 이때 reshape의 두 번째 인수를 -1로 지정했는데, 이는 reshape의 제공하느 ㄴ편의 기능입니다. reshape에 -1을 지정 하면 다차원 배열의 원소 수가 변환 후에도 똑같이 유지되도록 적절히 묶어줍니다. 무슨 말인고 하니, 앞의 코드에서 (10, 3, 5,5) 형상을 한 다차원 배열 W의 원소 수는 총 750개죠? 이 배열 reshape(10, -1)을 호출하면 750개의 원소를 10묶음으로, 즉 형상이 (10, 75)인 배열로 만들어줍니다.

다음으로 forward 구현의 마지막에서는 출력데이터를 적절한 현상으로 바꿔줍니다. 이때 넘파이의 transpose 함수를 사용하는데, 이는 다차원 배열의 순서를 바꿔주는 함수입니다.
[그림 7-20]과 같이 인텍스(0부터 시작)를 지정하여 축의 순서를 변경합니다.

그림 7-20 넘파이의 transpose함수로 축 순서 변경하기:인텍스(번호)로 축의 순서를 변경한다.

현상    ( N, H, W, C)     ---------->        ( N, C, H, W )
인텍스   0,  1,  2,  3                             0,  3,  1,  2

이상이 합성곱 계층의 forward 구현입니다. im2col로 전개한 덕분에완전연결 계층의 Affine 계층과 거의 똑같이 구현할 수 있었습니다("5.6 Affine/Softmax 계층 구현하기" 참고).

다음은 합성곱 계층의 역전파를 구현할 차례지만, Affine 계층의 공통점이 많아 따로 설명하지 않겠습니다. 주의할 게 하나 있는데, 합성곱 계층의 연전파에서는 im2col을 역으로 처리해야 합니다. 이는 책이 제공하는 col2im 함수를 사용하면 됩니다(col2im의 구현은 common/util.py에 있습니다). col2im을 사용한다는 점을 제외하면 합성곱 계층의 역전파는 Affine 계층와 똑 같습니다. 합성곱 계층의 연전파 구현은 common/layer.py 에 있으니 궁금한 분은 참고하세요.

7.4.4 폴링 계층 구현하기
풀링 계층 구현도 합성곱 계층과 마찬기지로 im2col을 사용해 입력 데이터를 전개합니다. 단, 폴링의 경우엔 채널 쪽이 독립적이라는 점이 합성곱 계층 때와 다릅니다. 구체적으로 [그림 7-21]과 같이 폴링 적용 영역을 채널마다 독립적으로 전개 합니다.

그림 7-21 입력 데이터에 폴링 적용 영역을 전개(2*2 폴링의 예)


일단 이렇게 전개한 후, 전개한 행렬에서 행별 최댓값을 구하고 적절한 형상으로 성형하기만 하면 됩니다(그림 7-22).

그림 7-22 풀링 계층 구현의 흐름: 풀링 적용 영역에서 가장 큰 원소는 회색으로 표시

이상이 폴링 계층의 forward 처리 흐름입니다. 다음은 이를 파이썬으로 구현한 코드 입니다.


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.poll_w)/self.stride)
 
    #전개 (1)
    col = im2col(x, self.pool_h, self.pool_h, self.stride, self.pad)
    col = col.reshape(-1, self.poo_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-22]와 같이 다음의 세 단계로 진행합니다.

1. 입력 데이터를 전개한다.
2. 행별 최댔값을 구한다.
3. 적절한 모양으로 성형한다.

앞의 코드에서와 같이 각 단계는 한두줄 정도로 간단히 구현됩니다.

NOTE
최댓값 계산에는 넘파이의 np.max 메서드를 사용할 수 있습니다. np.max는 인수로 축(axis)을 지정할 수 있는데, 이 인수로 지정한 축마다 최댓값을 구할 수 있습니다. 가령 np.max(x.axis=1)과 같이 쓰면 입력 x의 1번째 차원의 축마다 최댓값을 구합니다.

이상이 풀링 계층의 forwad처리입니다. 이 절에서 선택한 전략을 따라 입력 데이터를 풀링하기 쉬운 형태로 전개해버리면 그 후의 구현은 간단합니다.

풀링 계층의 backward 처리는 관련 사항을 이미 설명했으니 여기에서는 설명을 생략합니다.
ReLU 계층을 구현할 때 사용한 max의 역전파를 참고하세요("5.5.1 ReLU계층"). 풀링 계층의 전체 구현은 common/layer.py에 있으니 궁금하신 분은 한번 살펴보세요.

7.5 CNN 구현하기

합성곱 계층과 풀링 계층을 구현했으니, 이들 계층을 조합하여 손글씨 숫자를 인식하는 CNN을 조립해 보겠습니다. 여기에서는 [그림 7-23]과 같은 CNN을 구현합니다.

그림 7-23

2-> Conv->ReLU->Pooling --> Affine->ReLu--> Affine->Sorfmax-->

이 그림의 CNN 네트워크는 "Convolution-ReLU-Pooling-Affine-ReLU-Affine-Softmx" 순으로 흐릅니다. 이를 SimpleConvNet이라는 이름의 클래스로 구현하겠습니다.

우선 SimpleConvNet의 초기화(__init__)를 살펴봅시다. 초기화 때는 다음 인수들을 받습니다.

초기화 때 받는 인수
- input_dim - 입력 데이터(채널 수, 높이, 너비)의 차원
- conv_param - 합성곱 계층의 하이퍼파라미터(딕셔너리). 딕셔너리의 키는 다음과 같다.
   -- filter_num - 필터수
   -- filter_size - 필터 크기
   -- stride - 스트라이드
   -- pad - 패딩

- hidden_size - 은닌층(완전연결)의 뉴런수
- output_size - 출력층(완전연결)의 뉴런수
- weight_init_std - 초기화 때의 가중치 표준편차

여기에서 합성곱 계층의 하이퍼파라미터는 딕셔너리 형태로 주어집니다(conv_param).
이것은 필요한 하이퍼파라미터의 값이 예컨대 {'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1}처럼 저장된다는 뜻입니다.

자, SimpleConvNet의 초기화는 코드가 좀 길어지므로 세 부분으로 나눠 설명하겠습니다. 다음은 그 중 첫번재 입니다.

여기에서는 초기화 인수로 주어진 합성곱 계층의 하이퍼파라미터를 딕셔너리에서 꺼냅니다(나중에 쓰기 쉽도록). 그리고 합성곱 계층의 출력 크기를 계산합니다. 이어서 다음 코드는 가중치 매개변수를 초기화하는 부분입니다.

학습에 필요한 매개변수는 1번째 층의 합성곱 계층과 나머지 두 완전연결 계층의 가중치와 편향입니다. 이들 매개변수를 인스턴스 변수 params 딕셔너리에 저장합니다. 1번째 층의 합성곱 계층의 가중츠를 W1, 편향을 b1 이라는 키로 저장합니다. 마찬가지로 2번째 층의 완전연결 계층의 가중치와 편향을 W2와 b2, 마지막 3번째 층의 완전연결 계층의 가중치와 편향을 W3와 b3라는 키로 각각 저장합니다.

마지막으로 CNN을 구성하는 계층들을 생성합니다.



순서가 있는 딕셔너리(OrderedDict)인 layers에 계층들을 차례로 추가합니다. 마지막 SoftmaxWithLoss 계층만큼은 last_layer라는 별도 변수에 저장해둡니다.

이상이 SimpleConvNet의 초기화입니다. 이렇게 초기화를 마친 다음에는 추론을 수행하는 predict메소드와 손실함수의 값을 구하는 loss메서드를 다음과 같이 구현할 수 있습니다.

이 코드에서 인수 x는 입력 데이터, t는 정답 레이블입니다. 추론을 수행하는 predict 메소드는 초기화 때 layers에 추가한 계층을 맨 앞에서부터 차례로 forward 메소드를 호출하며 그 결과를 다음 계층에 전달합니다. 손실 함수를 구하는 loss 메소드는 predict메소드의 결과를 인수로 마지막 층의 forward메소드를 호출합니다. 즉, 첫 계층부터 마지막 계층까지 forward를 처리합니다.

이어서 오차역전파법으로 기울기를 구하는 구현은 다음과 같습니다.












2018년 4월 18일 수요일

import numpy as np

# 7.1 전체 구조
"""
합성곱 신경망convolutional neural network, CNN
(합성곱 : 컨볼루션. 두 함수 중 하나를 반전, 이동시켜가며 나머지 함수와의 곱을 연이어 적분하는 것)
지금까지 본 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있었다. 이를 완전연결fully-connected
라고 하며, 이를 Affine 계층이라는 이름으로 구현했다.
CNN에는 여기에 합성곱 계층과 풀링 계층이 추가된다.
Affine-ReLu가 Conv-ReLU-(Pooling)으로 바뀌며 출력에 가까운 층에서는 Affine-ReLU 구성을
사용할 수 있다. 출력 계층에서는 Affine-Softmax 조합을 사용한다.
"""

# 7.2 합성곱 계층
# 7.2.1 완전연결 계층의 문제점
"""
완전연결Affine 계층 : 인접하는 계층의 뉴런이 모두 연결되고 출력의 수는 임의로 정할 수 있다.
단점은 데이터의 형상이 무시된다.(가로, 세로, 채널로 구성된 3차원 데이터인 이미지를 1차원 데이터로
평탄화해줘야 한다.)
합성곱 계층은 형상을 유지하기 때문에 형상을 가진 데이터의 패턴을 이해할 수 있다.
CNN에서는 입출력 데이터를 특징 맵feature map이라고도 한다.
"""

# 7.2.2 합성곱 연산
"""
이미지 처리에서 말하는 필터 연산에 해당한다.
입력데이터  * 필터  →  출력 데이터
(4, 4)     (3, 3)    (2, 2)
1 2 3 0    2 0 1     15 16
0 1 2 3    0 1 2      6 15
3 0 1 2    1 0 2
2 3 0 1
필터의 윈도우를 일정 간격으로 이동해가며 입력 데이터에 적용한다. 입력과 필터의 원소를 곱한 후
총합을 구한다(단일 곱셈-누산fused multiply-add, FMA) 결과를 출력에 저장한다.
이 과정을 모든 장소에서 수행한다.
(합성곱은 교차상관과 비슷한 연산이다. 플리핑을 하면 합성곱, 아니면 교차상관. 딥러닝에서는 이를
잘 구분하지 않으며, 딥러닝 라이브러리에서는 플리핑하지 않거나 플리핑 여부를 인수로 받는다.)

CNN에서는 필터의 매개변수가 가중치에 해당하며, 편향 역시 존재한다.
입력데이터  * 필터              + 편향  → 출력데이터
(4, 4)     (3, 3)    (2, 2)           (2, 2)
1 2 3 0    2 0 1     15 16     3      18 19
0 1 2 3    0 1 2      6 15             9 18
3 0 1 2    1 0 2
2 3 0 1
"""

# 7.2.3 패딩
"""
패딩 : 합성곱을 수행 전 입력 데이터 주변을 특정 값(0 등)으로 채우는 것.
주로 출력 크기 조정을 목적으로 사용한다. 출력 크기가 커지기 때문에 입력 데이터의 공간적 크기를
유지한 채로 다음 계층에 전달할 수 있다.
"""

# 7.2.4 스트라이드
"""
스트라이드stride : 필터를 적용하는 위치의 간격. 일반적으로는 1이지만
2로 하면 필터를 적용하는 윈도우가 두 칸씩 이동한다. 스트라이드를 키우면 출력 크기가 작아진다.

이 값들에는 아래의 관계가 있다.
OH = (H + 2P + FH)/S + 1
OW = (W + 2P + FW)/S + 1
입력 크기 : (H, W)
필터 크기 : (FH, FW)
출력 크기 : (OH, OW)
패딩 : P, 스트라이드 : S
최종 결과는 정수로 나와야 한다.
"""

# 7.2.5 3차원 데이터의 합성곱 연산
"""
이미지는 가로 세로 채널이 있는 3차원 데이터이다. 채널의 수 만큼 필터가 필요하다.
모든 채널의 필터는 같은 크기여야 한다.
"""

# 7.2.6 블록으로 생각하기
"""
데이터와 필터를 직육면체 블록이라고 생각하면 쉽다. 이를 (채널, 높이, 너비) 순서로 나타낸다.
입력 데이터 * 필터    →    출력 데이터
(C, H, W)  (C, FH, FW) (1, OH, OW)

출력으로 다수의 채널을 내보내려면 필터를 여러 개(FN개) 사용하면 된다.
따라서 필터의 가중치 데이터는 4차원이 된다. 편향은 채널 하나에 값 하나씩으로 구성된다.(FN, 1, 1)
입력 데이터 *    필터         + 편향  →    출력 데이터
(C, H, W)  (FN, C, FH, FW)  (FN, 1, 1) (FN, OH, OW)
"""

# 7.2.7 배치 처리
"""
데이터 N개를 배치 처리한다면 데이터 형태는 다음과 같다.

입력 데이터(N개) * 필터(FN개)  +  편향   →   출력 데이터(N개)
(N, C, H, W) (FN, C, FH, FW) (FN, 1, 1) (N, FN, OH, OW)
"""

# 7.3 풀링 계층
"""
풀링 : 가로 세로 방향의 공간을 줄이는 연산. 여러 영역을 원소 하나로 집약하여 공간 크기를 줄인다.
2*2 최대 풀링max pooling을 스트라이드 2로 처리하는 예
(2*2 영역에서 가장 큰 원소 하나를 꺼낸다.)
1 2 3 0    2  3
0 1 2 3
3 0 1 2    4  2
2 3 0 1

일반적으로 윈도우 크기와 스트라이드는 같은 값으로 설정한다.
최대 풀링 외에도 평균 풀링 등이 있다. 이미지 인식 분야에서는 주로 최대 풀링을 사용한다.
"""

# 7.3.1 풀링 계층의 특징
"""
학습해야 할 매개변수가 없다 : 최댓값이나 평균을 취하는 명확한 연산이다.
채널 수가 변하지 않는다 : 채널 독립적으로 계산한다.
입력의 변화에 영향을 적게 받는다(강건하다) : 입력데이터가 조금 변해도 풀링의 결과는 잘 변하지 않는다.
"""

# 7.4 합성곱/풀링 계층 구현하기
# 7.4.1 4차원 배열
"""
CNN의 데이터는 4차원이다.
"""
x = np.random.rand(10, 1, 28, 28)  # 무작위 데이터 생성
print(x.shape)

# 첫 번째 데이터, 두 번째 데이터
print(x[0].shape)  # (1, 28, 28)
print(x[1].shape)  # (1, 28, 28)

# 첫 번째 데이터의 첫 채널의 공간 데이터
print(x[0, 0])  # or x[0][0]. 28 * 28의 벡터

# 7.4.2 im2cal로 데이터 전개하기
"""
합성곱을 구현하려면 다중 for문을 사용한다. 이는 복잡하고 numpy에서는 for문을 사용하면
성능이 떨어지기 때문에 im2col()이라는 함수를 사용해 구현한다.
im2col(image to column)은 필터를 적용하는 영역(3차원 블록)을 한 줄로 늘어놓는다.
4차원 데이터가 2차원이 된다. 여러 딥러닝 프레임워크에서 해당 함수를 구현해 사용하고 있다.
메모리를 더 많이 소비하는 단점이 있지만 행렬 계산 라이브러리는 큰 행렬의 계산에 장점이 있으므로
효율이 높아진다.

입력데이터를 2차원으로, 필터를 세로 1열로 전개해 내적을 취한 뒤 이를 4차원으로 reshape한다.
"""


# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from simple_convnet import SimpleConvNet
from matplotlib.image import imread
from common.layers import Convolution

def filter_show(filters, nx=4, show_num=16):
    """
    c.f. https://gist.github.com/aidiary/07d530d5e08011832b12#file-draw_weight-py
    """
    FN, C, FH, FW = filters.shape
    ny = int(np.ceil(show_num / nx))

    fig = plt.figure()
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)

    for i in range(show_num):
        ax = fig.add_subplot(4, 4, i+1, xticks=[], yticks=[])
        ax.imshow(filters[i, 0], cmap=plt.cm.gray_r, interpolation='nearest')


network = SimpleConvNet(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)

# 학습된 가중치
network.load_params("params.pkl")

filter_show(network.params['W1'], 16)

img = imread('../dataset/lena_gray.png')
img = img.reshape(1, 1, *img.shape)

fig = plt.figure()

w_idx = 1

for i in range(16):
    w = network.params['W1'][i]
    b = 0  # network.params['b1'][i]

    w = w.reshape(1, *w.shape)
    #b = b.reshape(1, *b.shape)
    conv_layer = Convolution(w, b)
    out = conv_layer.forward(img)
    out = out.reshape(out.shape[2], out.shape[3])
   
    ax = fig.add_subplot(4, 4, i+1, xticks=[], yticks=[])
    ax.imshow(out, cmap=plt.cm.gray_r, interpolation='nearest')

plt.show()










# coding: utf-8
import numpy as np


def smooth_curve(x):
    """손실 함수의 그래프를 매끄럽게 하기 위해 사용
   
    참고:http://glowingpython.blogspot.jp/2012/02/convolution-with-numpy.html
    """
    window_len = 11
    s = np.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]]
    w = np.kaiser(window_len, 2)
    y = np.convolve(w/w.sum(), s, mode='valid')
    return y[5:len(y)-5]


def shuffle_dataset(x, t):
    """데이터셋을 뒤섞는다.

    Parameters
    ----------
    x : 훈련 데이터
    t : 정답 레이블
   
    Returns
    -------
    x, t : 뒤섞은 훈련 데이터와 정답 레이블
    """
    permutation = np.random.permutation(x.shape[0])
    x = x[permutation, :] if x.ndim == 2 else x[permutation, :, :, :]
    t = t[permutation]

    return x, t

def conv_output_size(input_size, filter_size, stride=1, pad=0):
    return (input_size + 2*pad - filter_size) / stride + 1


def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    """다수의 이미지를 입력받아 2차원 배열로 변환한다(평탄화).
   
    Parameters
    ----------
    input_data : 4차원 배열 형태의 입력 데이터(이미지 수, 채널 수, 높이, 너비)
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩
   
    Returns
    -------
    col : 2차원 배열
    """
    N, C, H, W = input_data.shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1

    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))

    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]

    col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    return col


def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
    """(im2col과 반대) 2차원 배열을 입력받아 다수의 이미지 묶음으로 변환한다.
   
    Parameters
    ----------
    col : 2차원 배열(입력 데이터)
    input_shape : 원래 이미지 데이터의 형상(예:(10, 1, 28, 28))
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩
   
    Returns
    -------
    img : 변환된 이미지들
    """
    N, C, H, W = input_shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1
    col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)

    img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]

    return img[:, :, pad:H + pad, pad:W + pad]



2018년 4월 16일 월요일

chapter 6 학습관련 기술들

http://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html


http://umbum.tistory.com/217?category=751025


2018년 4월 1일 일요일

CHAPTER 14 가중치의 진짜 업데이트

아직 우리는 인공신경망에서 가중치를 어떻게 업데이트해야 하는가의 핵심적인 질문에 답하지 않았습니다. 지금가지의 고정은 이에 대한 답을 하기 위한 과정이었으며 이제 거의 다 왔습니다. 이 비밀을 풀기 위해 반드시 이해해야 할 핵심 아이디어 한 가지만 더 이해하면 됩니다!

지금까지는 네트워크의 각 계층에 걸쳐 역전파되는 오차를 구해봤습니다. 이처럼 오차를 구하는 이유는 인공 신경망이 보다 나은 답을 출력하게 하기 위해 가중치를 조정해가는 데 지침 역할 을 하는 것이 오차이기 때문입니다. 이러한 과정은 이 책의 앞 부분에 나왔던 선형 분류자 예제에서부터 우리가 보아왔던 것입니다.
 하지만 신경망에서 노드는 단순한 선형 분류자가 아닙니다. 노드는 입력되는 신호에 가중치를 적용한 후 이의 합을 구하고 다시 여기에 시그모이드 활성화 함수를 적용하는 식으로 좀 더 복잡한 구조를 가집니다. 그렇다면 이처럼 정교한 노드 사이를 연결하는 연결 노드의 가중치를 어떻게 업데이트해야 할까요? 우리는 왜 어떤 끝내주는 대수학공식을 이용해 가중치를 단번에 구해낼 수 없는 것일까요?

- 가중치 계산
신경망에는 너무나도 많은 가중치의 조합이 존재합니다. 또한 신호가 여러 개의 계층을 타고 전파되어나갈 때 한 계층을 거칠 때마다 직전 계층의 출력 값이 다음 계층의 입력값이 되므로 함수의 함수,ㅡ 함수의 함수의 함수...같은 식으로 수많은 함수의 조합이 필요하게 됩니다. 따라서 수학 연산의 과정이 너무 복잡하게 되므로 가중치를 한 방에 풀어주는 대수학을 활용할 수 없는 것입니다.

CHAPTER 13 행렬곱을 이용한 오차의 연전파

우리는 앞 장에서 실제 숫자를 대입해 역전파를 이해하고자 했습니다. 하지만 실제 역전파는 매우 복잡하겠죠? 그렇다면 우리는 역전파에도 행렬곱을 이용할 수 있을까요? 기억하겠지만 우리는 이미 9장에서 앞쪽으로 전파법에 대해 행렬곱으로 계산한 바 있습니다.

이번에는 오차의 역전파를 행렬곱으로 간결하게 만들 수 있는지 보기 위해서 단계별 기호를 이용해 적어보겠습니다. 이렇게 행렬곱으로 표현하는 방식을 벡터화한다(vectorize)라고 합니다.

수많은 연산을 행렬의 형태로 표현하게 되면 우리가 손으로 쓰기에도 편하지만 무엇보다도 컴퓨터가 반복적인 유사한 연산을 순식간에 처리할 수 있게 되므로 훨씬 더 효율적으로 작업을 수행할 수 있다는 장점을 가지게 됩니다.

최종 출력 계층으로 부터 나오는 오차로부터 시작해보겠습니다. 출력 계층에 2개의 노등가 있는 예제를 보고 있었으니 여기에서의 오차는 다음과 같이 표현할 수 있습니다.

error(output) = ( e1, e2)

다음으로는 은닉계층의 오차를 행렬로 구성해보겠습니다. 어렵게 느껴질 수 있으니 단계별로 차근차근 보겠습니다. 우선 은닉 계층의 첫 번째 노드부터 보겠습니다. 앞장의 그림을 다시 보면 은닉 계층의 첫 번째 노드의 오차에 영향을 주는 것은 출력 계층으로 부터의 2개의 경로임을 확인할 수 있습니다. 이 2개의 경로를 통해 전달되는 오차 신호는 각각 e1 * w11 /(w11 + w21)과 e2 * w12/(w12 + w22)입니다. 은닉 계층의 두 번째 노드는 마찬가지로 각각 e1 * w21 /(w21 + w11)과 e2 * w22 /(w22 + w12)가 됩니다. 만약 이 식이 혼동이 되는 분은 반드시 12장을 다시 한번 학습하고 돌아오기 바랍니다.

그렇다면 이제 은닉 계층의 오류를 다음과 같이 행렬로 표현할 수 있습니다.

이 행렬을 보다 간결하게 행렬곱으로 다시 쓸 수 있다면 좋을 것 같ㅅ브니다. 마치 전파법에서 봤던 것 처럼 말입니다.

하지만 안타깝게도 전파법에서 봤던 수준으로 간결하게 이 행렬을 다시 쓸 수는 없습니다. 위의 복잡한 수식을 풀어내기가 쉽지는 않기 때문입니다. 그렇다면 어떻게 해야 할까요? 정말 간결하게 쓰고 싶기는 한데 말입니다.

위의 수식을 다시 봅시다. 이 수식에서 핵심은 출력 오차인 en과 가중치 wij의 곱셈 연산입니다. 가중치가 크면 클수록 더 많은 출력 오차가 은닉계층으로 돌아오게 됩니다. 이것이 핵심입니다. 분수에서 분모 부분은 일정의 정규화 인자(nomalizing factor)라고 할 수 있습니다. 만약 정규화 인자를 무시한다고 해도 우리는 되돌아오는 오창의 일정 비율 정도를 잃을 뿐입니다. 따라서 e1 * w11/(w11+w21)을 간단하게 e1 * w11로 표현해도 되는 것입니다.

가중치 행렬은 우리가 8장에서 만들었던 행렬과 유사해 보이지만 자세히 보면 대각선 방향으로 원소가 뒤바뀐 것을 알 수 있습니다. 즉 우측 상단의 가중치와 좌측 하단의가중치가 바뀌어 있습니다. 이처럼 핻렬릐 행과 열을 바꾸는 것을 전치(transpose)한다고 부르며, 행렬 w의 전치 행렬을 기호로는 wt라고 표기합니다.

다음 그림에서 전치 행렬의 두 가지 예를 보겠습니다. 전치 행렬은 행과 열의 개수가 다른 경우에도 가능함을 이해하기 바랍니다.
이제 다음과 같이 오차의 역전파를 행렬로 간결하게 표현할 수 있게 되었습니다.

error(hidden) = wt(hidden_output) * error(output)

간결하게 표현해서 좋기는 하지만 정규화 인자를 생략했다는 점이 마음에 걸립니다. 이렇게 생각해 버려도 괜찮은 것일까요? 결론부터 말하면 이처럼 오차 신호의 피드백을 단순화하더라도 정규화 인자가 있을 때와 마찬가지로 잘 동작한다는 사실이 밝혀져 있습니다. 오차를 역전파하는 몇 가지 방ㅂ버을 비굑한 결과를 블로그에 올렸으니 관심 있는 분들은 참고하기 바랍니다(http://bit.ly/2m2z2YY). 간결한 방식이 문제 없이 작동한다면. 더 어려운 방법을 쓸 필요는 없겠죠!

이점에 대해 조금 더 말하면, 설령 다소 크거나 작은 오차가 역전파되었다고 하더라도 우리의 인공 신경망은 다음 학습 단계에서 이를 스스로 바로 잡아갑니다.
오차에 대한 책임을 분산시키는 데 지침이 되는 것은 가중치의 크기이므로 역전파되는 오차가 연결 노드의 가중치의 크기를 중시한다는 점이 핵심입니다.

지금까지 정말 많은 것을 학습하느라 고생 많으셨습니다. 이론과 관련한 마지막 장이 될 다음 장으로 넘어가기 전에 잠깐 쉬어도 좋을 것 같습니다. 다음 장은 정말 멋지지만 살짝 골치 아픈 내용을 다루기 때문입니다.

핵심 정리
- 오차의 역전파를 행렬곱으로 표현할 수 있습니다.
- 오차의 역전파를 행렬곱으로 표현함으로써 우리는 네트워크의 크기에 상관없이 이를 간결하게 표현할 수 있으며, 컴퓨터를 통해 보다 효율적이고 빠르게 업무를 처리하게 할 수 있습니다.
- 이제 우리는 전파법과 역전파 모두 행렬곱을 통해 효율적으로 처리할 수 있다는 사실을 알게 되었습니다.

2018년 3월 27일 화요일

laradock on mac


mysql bash -> hostname -I

.env DB_HOST SETTING

php artisan make:auth



php artisan migrate

CHAPTER12 다중 계층에서의 오차의 역전파

다음은 3개 계층(입력 계층, 은닉 계층, 출력 계층)으로 구성된 간단한 신경망입니다.


 입력          입력계층               은닉계층            출력계층          출력


--->             (1)  --->               (1)     ------>         (1)     ---->


오른쪽 끝의 최종 출력 계층부터 보겠습니다. 출력 계층의 오차를 이용해 출력 계층의 연결된 노드들의 가중치를 업데이트하게 됩니다. 이를 조금 더 일반화해보면 출력 계층의 오차는 e(output)으로, 여기에서는 가중치 who로 표현할 수 있습니다. 물론 e(output)은 가중치 비율에 따라 나뉘어 전달될 것입니다.
이렇게 시각화해봄으로써우리는 새로 추가된 계층에 대해 어떻게 하면 좋을지 감을 잡을 수 있을 것 같습니다. 즉 은닉 계층의 노드으 ㅣ출력 값과 관련된 오차를 e(hidden)이라고 하고 이 오차를 입력 계층과 은닉계층을 연결하는 가중치 wih에 대해 그 비율에 맞게 나누어 주면 되는 것입니다. 다음 그림에 이를 표현햅습니다.

만약 계층이 더 많이 있다면 이처럼 최종 출력 꼐층으로부터 역방향으로 동일한 과정을 반복하면 될 것입니다. 이처럼 오차 관련 정보가 뒤쪽으로 계속해서 전파되므로 역전파라고 부르는 것입니다.

출력 계층의 경우에는 출력 계층의 노드의 결과 값의 오차인 e(output)을 사용했지만, 은닉 계층의 노드에서 e(hidden)은 어떤 오차 값으 ㄹ사용하면 될까요? 만약 이런 의문이 들었다면 지금까지의 내용을 제대로 이해하고 있는 것이라고 보룻 었을 것 같습니다. 중간에 위치하는 은닉 계층은 사실 명백한 오차를 가지고 있지 않기 때문입니다. 앞에서 배웠던 내요을 다시 상기해볼까요? 은닉 계층의 각각의 노드는 직전 계층으로부터 입력을 받아 적절한 출력값을 다음 계층으로 전달하게 됩니다. 이때 이 출력 값이란 직전 계층의 입력 값에 가중치를 적용한 다음이를 모두 더한 값에 활성화 함수를 적용한 값입니다. 그럼 도대체 어떻게 오차를 구해낼 수 있는 것일까요?

은닉 노드에 대해서는 목표로 하는 출력 값이 없습니다. 오직 최종 출려 ㄱ계층의 노드만이 학습데이터로부터 얻은 실제값, 즉 목표로 하는 출력 값을 가지고 있다는 것이죠. 앞으로 그림을 보면 다시 한번 생각해보기 바랍니다. 은닉 계층의 첫 번째 노드는 2개의 연결 노드를 가지는데 이들은 출력 계층의 2개의 노드로 각각 연결됩니다. 그리고 우리는 출력 계층의 노드에서의 오차를 이 연결 노드에 나누어줄 수 있다는 점을 알고 있습니다. 이 말은 결국 중간의 은닉 계층의 노드로부터 오는 2개의 연결 노드 각각에 대해 어떤 오차가 존재한다느 말입니다. 우리는 이러한 2개의 연결노드의 오차를 재조합함으로써 은닉 계층의 노드의 에러로 사용할 수 있습니다. 즉 중간 계층의 노드는 목표로 하는 출력 값을 가지지 않기 때문에 이처럼 차선의 방법을 사용하는 것입니다. 이와 같은 아이디어를 다음 그림에 표현해봤습니다.

그림을 보면 좀 더 명확해질 것입니다. 우리에게 필요한 것은 은닉 계층의 노드들에 대한 오차입니다. 이 오차를 이용해 직전 계층과의 연결 노드에 존재하는 가중치를 업데이트할 것이기 때문입니다. 은닉 계층의 노드의 오차를 e(hidden)이라고 하겠습니다. 아직까지 우리는 실제 이 오차의 값이 얼마인지에 대한 답을 가지고 있지 않습니다. 그 이유는 (반복해서 말하지만) 학습 데이터로부터 얻을 수 있는 목표 값은 (으닉 계층이 아니라) 오직 최종 출력 계층의 노드에 대한 것이므로, 으닉 계층에서의 오차를 목표 값과 실제 값 간의 차이라고 정의할 수 없기 때문입니다.

학습 데이터들은 최종 노드의 결과 값이 가져야 할 목표 값에 대해서만 말해줍니다. 이 외의 노드들의 결과 값에 대해서는 아무것도 말해주지 않습니다. 이것이 이 퍼즐의 핵심입니다.

우리는 앞에서 본 대로 오직의 역전파를 이용해 연결 노드에 대해 나뉜 오차를 재조합해야 합니다. 따라서 은닉계층의 첫 번째 노드의 오차는 이 은닉계층에서 다음 계층으로 연결되는 모든 연결 노드에 있는 나뉜 오차들의 합이 됩니다. 앞의 그림에서 보면 e(output.1)은 w11과 연결되어 있으면 e(output2)는 w12와 연결되어 있습니다. 이를 수식으로 써보면 다음과 같습니다.

e(hidden1) = 연결 노드 w11, w12로 나뉘어 전달되는 오차의 합
               = e(output1) * w11 /(w11 + w21) + e(output2) * w12/(w12 + w22)

수식으로만 보면 이해가 어려울 수 있으니 실제 숫자를 대입해서 보겠습니다. 다음 그림은 오차가 역전파하는 과정을 실제 숫자와 함께 표현한 것입니다.

앞의 그림에서 출력 계층의 첫 번째 노드의 오차 0.8은 이와 연결된 2개의 가중치 2.0과 3.0에 비례해 각각 0.32와 0.48로 나뉘어 전달된다는 것을 확인할 수 있습니다. 출력 계층의 두 번째 노드의 오차 0.5는 연결된 2개의 가중치 1.0과 4.0에 비례해 각각 0.1과 0.4로 나뉩니다. 따라서 은닉 계층의 첫 번째 노드의 오차는 0.32와 0.1의 합인 0.42가 되고 두번 째 노드의 오차는 0.48과 0.4의 합인 0.88이 되는 것입니다.
다음 그림은 한 단계 더 나아가 은닉 계층의 오차 값을 이용해 입력 계층이 오차를 구하는 것을 보여줍니다.

핵심정리
- 인공 신경망에서 학습이란 연결 노드의 가중치를 업데이트하는 과정을 의미합니다. 가중치의 업데이트는 오차에 의해 주도되는데, 오차는 학습데이터로부터 주어진 정답과 출력값 간의 차이를 의미합니다.
- 출력 노드의 오차는 실제 값(정답)과 출력 값 사이의 차이를 의미합니다.
- 중간 계층의 존재하는 노드들의 오차는 명백하지 않습니다. 한 가지 접근 방법은 출력 계층의 노드들의 오차를 이와 연결된 가중치의 크기에 비례해 누눠서 역전파하고 이를 재조합하는 방법입니다.