7.3 풀링 계층

풀링은 세로·가로 방향의 공간을 줄이는 연산이다. (패딩 : 주변 채우기 스트라이드: 도장 찍는 간격)

아래 그림을 통해 보자.

위 그림과 같이 2x2 영역을 원소 하나로 집약하여 공간 크기를 줄이는 것이 풀링이다.

위 그림은 최대 풀링 (max pooling)이고, 평균 풀링 (average pooling)도 존재한다. +(최소, 스토캐스틱, 가중치 등)

7.3.1 풀링 계층의 특징

- 학습해야 할 매개변수가 없음

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

 

- 채널 수가 변하지 않음

풀링 연산은 입력 데이터의 채널 수 그대로 출력 데이터로 내보낸다. 

위 그림처럼 채널마다 독립적으로 계산하기 때문

 

- 입력의 변화의 영향을 적게 받음 (강건함)

입력 데이터가 변해도 풀링의 결과는 잘 변하지 않는다.\

7.4 합성곱 / 풀링 계층 구현

7.4.1 4차원 배열

 앞에서 설명한대로 CNN에서 계층 사이를 흐르는 데이터는 4차원 이다.

 

이제 파이썬으로 구현해보자.

먼저 랜덤으로 데이터를 만들어보자.

x = np.random.rand(10,1,28,28)
x.shape

(10, 1, 28, 28)

여기에서 첫 번째 데이터에 접근하려면 x[0]이라고 하면된다. (두 번째 x[1]...)

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

7.4.2 im2col로 데이터 전개하기

합성곱 연산을 곧이곧대로 for을 쓰려면 너무 번거롭기 때문에, im2col을 이용하자.

 

im2col은 입력 데이터를 필터링 (가중치 계산)하기 좋게 전개해주는 함수이다. (펼쳐주는 함수)

위 그림과 같이, 3차원 입력 데이터에 im2col을 적용하면 2차원 행렬로 변하는 것을 볼 수 있다.

 

im2col은 필터링 하기 좋게 입력 데이터를 전개한다.

구체적으로는 아래 그림과 같이, 입력 데이터에서 필터를 적용하는 영역(3차원 블록)을 한 줄로 늘어놓는다.

 

이 전개를 필터를 적용하는 모든 영역에서 수행하는 것이 im2col이다.

 

이를 토대로 합성곱 연산의 필터 처리 상세과정을 그림으로 그려보면 아래와 같다.

7.4.3 합성곱 계층 구현하기

 

CNN ( convolutional neural network)은 이미지 인식과 음성 인식 등 다양한 곳에서 사용된다.

convolutional = 나선형이라는 뜻이 먼저 나오지만, 딥러닝에서 CNN은 합성곱이라는 의미로 쓰인다.

 

CNN의 핵심 키워드는 다음과 같다.

  • Convolution(합성곱)
  • 채널(Channel)
  • 필터(Filter)
  • 커널(Kernel)
  • 스트라이드(Strid)
  • 패딩(Padding)
  • 피처 맵(Feature Map)
  • 액티베이션 맵(Activation Map)
  • 풀링(Pooling) 레이어

7.1 전체 구조

CNN의 네트워크 구조를 살펴보면 크게 합성곱 계층과 풀링 계층으로 되어 있다. 지금까지 본 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있다. 이를 완전연결이라고 하며, 

 

완전 연결된 계층을 Affine 계층이라 했었다.

Affine계층을 사용한 5층 완전연결 신경망은 아래 그림과 같다.

Affine-ReLu 조합의 층이 4개, 마지막 출력층에서는 Softmax 활성화함수를 이용되는 것을 볼 수 있다.

 

다음은 CNN의 예시이다.   (일반적인 CNN구조라고한다.)

Affine-ReLu연결이    Conv-ReLu-Pooling 연결로 바뀌었다고 생각하면 편하다.

또 주의할 점은 출력에 가까운 층에는 Affine-ReLu 구성의 연결을 사용할 수 있다는 점이다!! 

 

7.2 합성곱 계층

CNN에서는 또한 합성곱 계층, 풀링 계층 의외  패딩 / 스트라이드의 개념이 새로 등장한다.

 

7.2.1 완전연결 (Affine) 계층의 문제점

7장 이전에 배웠던 Affine 계층에서는 인접하는 계층의 뉴런이 모두 연결되고 출력 수를 임의로 정할 수 있었다.

 

위 문장을 토대로 완전연결계층의 문제점은 무엇일까? 

바로 데이터의 형상 (차원)이 무시된다는 점이다.

 

입력 데이터가 이미지라고 한다면, 3차원 데이터일 것이다. (세로, 가로 ,채널(색))

하지만 이 데이터를 완전 계층에 입력하려면 1차원으로 평탄하게 해주어야 한다.

 

하지만 3차원 데이터는 그 자체로 중요한 정보를 갖고 있다.

예를 들면 공간적으로 가까운 픽셀은 값이 비슷하거나, RGB 각 채널은 서로 밀접하게 관련되어 있는 등 패턴이 숨겨져 있을 수 있다. 하지만 이를 1차원 데이터로 변환 시키는 순간 이 정보들을 살릴 수 없게 된다.

 

CNN에서는 합성곱 계층의 입출력 데이터를 특징맵 ( feature map ) 이라고도 한다.

따라서 합성곱 계층의 입력 데이터를 입력 특징맵 , 출력 데이터를 출력 특징맵 이라고 한다.

이 책에서는 입출력 데이터특징맵 을 같은 의미로 사용할 것이기 때문에 주의해서 해석을 하도록 한다.!!!

 

7.2.2 합성곱 연산

 합성곱 계층에서는 합성곱 연산을 처리하는데, 이는 이미지 처리에서 말하는 필터 연산에 해당한다. 

필터 연산에 대해 구체적인 예시를 통해 알아보자.

위 그림을 보면 입력 데이터에 필터(=커널, 도장) 를 적용하는 것을 볼 수 있다.

입력 데이터는 4x4, 필터는 3x3이므로 도장을 총 네번 찍어서 2x2(네 번의 도장 값)의 출력 데이터를 출력한다. 

 

자세한 곱 연산은 다음과 같다.

도장안의 각 값들은 가중치라고 볼 수 있고, 편향을 추가한다면 다음과 같은 수식을 얻을 수 있다.

7.2.3 패딩

합성곱 연산을 수행하기 전에 입력 데이터 주변 값을 특정 값 (or 0)으로 채우는 것을 패딩이라고 한다.

7.2.4 스트라이드

스트라이드 (stride)란 필터(=커널, 도장)을 적용하는 위치의 간격을 말한다.

 

간격이 2이면 도장이 2칸씩 이동하는 것을 말한다.

 

7.2.5 3차원 데이터의 합성곱 연산

위에서 알아본 합성곱 연산은 데이터가 2차원일 때의 경우이다.

3차원 데이터의 합성곱 연산에 대해 알아보자. 데이터는 이미지라고 가정한 후 (세로,가로,색상)을 다룬다고 하자.

주의사항: 입력 데이터의 채널 수와 필터의 채널 수가 같아야 한다.

                 필터 고정

7.2.6 블록으로 생각하기

3차원의 합성곱은 데이터와 필터를 직육면체 블록이라고 생각하면 쉽다.

블록은 아래 그림과 같이 3차원이다.

위 그림은 출력 데이터가 한 장의 특징 맵이다.

한 장의 특징 맵은 곧 채널이 1개인 특징 맵이라는 말과 같다.

만약 합성곱 연산의 출력으로 다수의 채널을 출력하려면 어떻게 해야 할까?

 

 

->필터를 1개보다 더 사용하는 것이다.

FN은 필터의 개수를 의미하고 이에 따라서 출력 맵 또한 FN개가 생성된다.

 


여기에 편향까지 고려한다면 최종 흐름은 아래 그림과 같다.

7.2.3 배치 처리

7장 이전의 신경망 처리에서는 입력 데이터를 한 덩어리로 묶어서 배치로 처리했었다.\

CNN에서도 배치 처리를 적용시켜보자.

 

배치 처리를 하기 위해 n개의 데이터를 묶고자 한다면 데이터 차원 수가 하나 더 늘어나서 총 4차원으로 바뀐다.

( 데이터 수, 채널 수, 높이, 너비)

 

26.2.3 ReLu를 사용할 때의 가중치 초기값

지난 포스팅에서 본 Xavier 초기값은 활성화 함수가 선형인 것을 전제로 이끈 결과이다.

시그모이드 함수와 tanh(하이퍼볼릭 탄젠트) 함수는 좌우 대칭이기 때문에 중앙 부근이 선형인 함수로 볼 수 있다.

 

따라서 Xavier초기값을 사용하기에 적절하다.

 

하지만 ReLu함수를 사용할 때는 ReLu에 특화된 초기값을 권장한다. (not  xavier)

He 초기값

He 초기값은 앞 계층의 노드가 n개일 때, 표준편차가  √(2 /n) 인 정규분포를 사용한다.

(Xavier 초기값: 표준편차  1 / √n ) 

키워드 : 최적화 옵티마이저 (4가지), 가중치 매개변수 초기값, 드롭아웃 등

 

6.1 매개변수 갱신

신경망 학습의 목적은 손실 함수의 값을 최소화 할 수 있는 매개변수를 찾는 것.  == 매개변수의 최적값을 찾는 것

 

정리하면 신경망 학습의 목적은 곧 최적화이다. ( optimization )

지금까지 최적화의 방법으로 매개변수의 기울기를 이용했었다. (미분)

 

매개변수의 기울기를 구해서 기울어진 방향으로 매개변수의 값을 갱신하는 일을 계속 반복해서 최적의 값을 찾아나갔다.

이를 SGD (확률적 경사 하강법)이라고 한다. 이 챕터에서는 SGD의외의 다양한 최적화 방법들을 소개한다.

 

우선 SGD를 포함한 다양한 옵티마이저를 그림으로 보고 넘어가자.

6.1.2 확률적 경사 하강법 (SGD)

SGD를 다시 복습해보자.

위 수식에서 W는 가중치, ∂L/∂WW에 대한 손실 함수의 기울기이다. n(에타)은 학습률이다. (보통 0.01이나 0.001 사용)

SGD클래스를 구현해보자.

class SGD:
	def __init__(self,lr=0.01)
    	self.lr = lr
    def update(self, params, grads):
    	for key in params.keys():
        	params[key] -= self.lr * grads[key]

SGD클래스를 이용하면 신경망 매개변수의 진행을 다음과 같이 수행할 수 있다.

network = TwoLayerNet(...)
optimizer = SGD()

for i in range(1000):
	...
    x_batch, t_batch = get_mini_batch(...) #미니배치
    grads = network.gradient(x_batch, t_batch)
    params = network.params
    optimizer.update(params, grads)
    ...

위 코드처럼 optimizer = '...'로 지정하여 SGD의외 기법도 호출하여 편하게 사용할 수 있다.

 

6.1.3 SGD의 단점

6.1.3 SGD의 단점

6.1.4 모멘텀 ( Momentum )

모멘텀이란 운동량을 뜻하는 말로, 물리와 관계가 있다. 또한 수식으로는 다음과 같다.

n(에타) = 학습, v = 속도(기울기를 따라 이동하는 속도라고 생각)

∂L/∂W = 손실함수의 기울기,   W = 가중치

av 항은 물체가 아무런 힘을 받지 않을 때 서서히 하강시키는 역할을 한다고 한다.

 

class Momentum:
	def __init__(self, lr=0.01, momentum = 0.9):
    self.lr = lr
    self.momentum = momentum
    self.v = None
   	
    def update(self, params, grads):
    	if self.v is None:
        	self.v = {}
            for key, val in params.items():
            self.v[key] = np.zeros_like(val)
        
        for key in params.keys():
        	self.v[key] = self.momentum * self.v[key] - self.lr*grads[key]
            params[key] += self.v[key]    # 인스턴스 변수 v는 물체의 속도

모멘텀을 생각할 때는 공이 경사면을 따라 굴러간다고 생각하면 이해하기 쉽다.

또한 SGD와 비교했을 때 위아래 흔들림이 적다.

6.1.5 Adagrad

신경망 학습에서는 학습률 값이 중요하다. 너무 크거나 작으면 학습이 효과적으로 이루어지지 않는다.

이 학습률을 올바르게 설정하기 위한 몇 가지 기술 中, 학습률 감소가 있다.

학습률 감소란 처음에는 크게 학습하다가 진행하면서 조금씩 학습률을 점차 줄여나가는 방법이다.

 

학습률 감소에 대한 구체적인 설명은 다음 블로그를 참고하자.

https://velog.io/@good159897/Learning-rate-Decay%EC%9D%98-%EC%A2%85%EB%A5%98

 

Learning rate Decay의 종류

Michigan 대학의 CS231n 강의를 보고 Learning rate Decay에 대해 정리를 했습니다.

velog.io

학습률을 서서히 낮추는 가장 간단한 방법은 "전체"의 학습률을 값을 일괄적으로 낮추는 것이다.

이를 더욱 발전시킨 것이 AdaGrad이다.

 

Adagrad는 전체에서 발전해 "각각"의 매개변수에 맞게 갱신해준다.

'적응적 학슙를'을 기반으로 한 옵티마이저이다.

 

Adagrad, RMSprop = 적응적 학습률 기반

momentum이라는 개념을 이용하여 gradient를 조절

 

어떻게 개별 매개변수마다 갱신을 해줄까? 자세히 알아보자.

위 수식에서 h는 기존 기울기값을 제곱하여 계속 더해준다. 

그리고 매개변수를 갱신할 때 1/ √h를 곱해서 학습률을 조정한다.!!

이 뜻은 매개변수 원소 중에서 많이 움직이는 원소는 학습률이 낮아진다는 뜻이라고 볼 수 있다.

 

(다시 말해 학습률 감소가 매개변수 원소마다 다르게 작용됨.)

 

손코딩

class AdaGrad:
    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, params, grads):
        if self.h in None:
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
        
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            parmas[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

주의) 마지막 줄에서 1e-7를 더해주는 부분. 1e-7은 self.h[key]에 0이 담겨 있어도 0으로 나누는 것을 막아줌.

Adagrad의 갱신경로를 그림으로 보자.

최소값을 향해 효율적인 움직임을 보여준다.!!

 

6.1.6 아담 ( Adam )

모멘텀은 공을 구르는 듯한 움직임을 보여준다. Adagrad는 원소 개별적으로 갱신해주었다.

이 둘을 합치면 Adam이라고 할 수 있다.

그림을 먼저 보자.

모멘텀과 비슷한 패턴이지만, 좌우 흔들림이 적은 것을 볼 수 있다.

이는 학습의 갱신 강도를 적응적으로 조정해서 얻는 benefit이라고 한다.

 

마지막으로 정리해서 봐보자.

무엇이 제일 좋다!! 라는 것은 데이터, 목적, 그 밖의 하이퍼 파라미터에 따라 다르기 때문에 정해진 것이 없다.

하지만 통상적으로 SGD보다 momentum,adam,adagrad 3개가 더 좋고, 일반적으로 Adam을 많이 쓴다고 한다.

 

6.2 가중치의 초기값

신경망 학습에서 가중치 초기값이 매우 중요하다고 한다.

2절에서는 권장 초기값을 배우고 실제로 학습이 잘 이루어지는지 확인해보자.

 

6.2.1 초기값을 0으로 하면?

이제부터 오버피팅을 억제해 범용 성능을 높이는 테크닉인 가중치 감소 기법을 소개해주겠다.

가중치 감소가중치 매개변수의 값이 작아지도록 학습하는 방법이다.

 

가중치 값을 작게 해서 오버피팅을 일어나지 않도록 하게 하는 것이다.

가중치 값을 작게 만들고 싶으면 초기값도 최대한 작은 값에서 시작하는 것이 정공법이기 때문에,,!

사실 지금까지 가중치 초기값은 0.01 * np.random.randn(10,100)처럼 정규분포에 생성된 값에 0.01배를 한 작은값을 사용

했었다.

 

그렇다면 가중치 초기값을 모두 0으로 설정하면 어떨까??

결론부터 말하자면, 아주 안좋은 생각이다.

실제로 가중치 초기값을 0으로 하면 학습이 올바로 이루어지지 않는다.

오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문이다.

 

예를 들어 2층 신경망에서 첫 번째, 두 번째 층의 가중치가 0이라고 가정해보자.

그럼 순전파 때는 입력층의 가중치가 0이기 때문에 두 번째 층의 뉴런에 모두 같은 값이 전달된다.

두 번째 층의 모든 뉴런에 같은 값이 입력된다는 것은 역전파  두 번 층의 가중치가 모두 똑같이 갱신된다는 말과 같다.

(곱셈 노드의 역전파를 기억해보자.)

그래서 가중치들을 같은 초기값에서 시작하고 갱신을 거쳐도 여전히 같은 값을 유지하는 것이다.

이는 가중치를 여러 개 갖는 의미를 사라지게 한다.

 

이 '가중치가 고르게 되어버리는 상황'을 막으려면 초기값을 무작위로 설정해야 한다.

 

6.2.2 은닉층의 활성화값 분포

은닉층의 활성화값(활성화 함수의 출력 데이터)의 분포를 관찰하면 중요한 정보를 얻을 수 있었다.

 

이번 절에서는 가중치의 초기값에 따라 은닉층 활성화 값들이 어떤 분포를 갖는지 시각화 해보려한다.

예시로 5층의 신경망을 가정, 시그모이드 함수 사용, 입력데이터 무작위로 선정

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-1))
    
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100                 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5          # 은닉층이 5개
activations = {}               # 이 곳에 활성화 결과(활성화값)를 저장

fro i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]
        
    w = np.random.randn(node_num, node_num) * 1
    
    a = np.dot(x, w)
    z = sigmoid(a)
    activations[i] = z

층은 5개, 각 층의 뉴런은 100개씩이다.

활성화 값들의 분포를 그려보자.

# 히스토그램 그리기
for i, a in activations.items():
    plt.subplot(1, len(activations), i+1)
    plt.title(str(i+1) + '-layer')
    plt.hist(a.flatten(), 30, range=(0,1))
plt.show()

각 층의 활성화 값들이 0과 1에 치우쳐져 있다.

시그모이드 함수는 출력이 0또는 1에 가까워지면 미분값은 0에 수렴한다.

따라서 데이터가 0과 1에 치우쳐 분포하게 되면 역전파 기울기 값이 점점 작아지다가 사라진다.

이를 기울기 소실 현상 ( gradient vanishing )이라고 한다.

 

이제 표준편차를 0.01로 바꿔서 다시 그려보면 다음과 같은 그래프를 얻을 수 있다.

아까와 같은 기울기 소실 문제는 보이지 않지만, 모두 중간값에 몰빵되있는 것을 볼 수 있다.

이는 표현력 관점에서 문제라고 볼 수 있다.

 

Xavier 초기값

사비에르 초기값이란 사비에르가 작성한 논문에서 발췌한 것으로, 현재까지 딥러닝 프레임워크에서 많이 사용한다.

초기값 = 표준편차를 1 /√n 로 설정!   ( n = 노드 개수)

 

사비에르 초기값을 사용해서 분포를 보면 다음과 같다.

 

5.6 Affine / Softmax 계층 구현하기

5.6.1 Affine 계층 

Affine 변환 = 순전파에서 행렬,   

Affine 계층 = Affine 변환을 수행하는 처리

.신경망의 순전파에서는 가중치 총합의 신호를 계산하기 위해 행렬 곱을 사용했다 ( np.dot ) 

 

그리고 행렬 곱의 핵심은 원소 개수 (차원 개수)를 일치시키는 것이다.

 

예시를 통해 그림으로 이해해보자. (계산 그래프)

X,W,B가 있다고 하자. 각각 입력, 가중치, 편향이고  shape은 각각(2,) , (2,3), (3,)이다.

X와 W가 들어와 np.dot을 통해 행렬 곱 수행 -> x * W   -> ( x * W ) +B  = Y

 

이제 역전파를 알아보자.

아래 식이 도출되는것을 확인하자.

  WT는 T의 전치행렬을 말함. 

(전치행렬 = Wij 를 Wji로 바꾼것. 아래 그림 참조)

위 식을 통해서 계산 그래프의 역전파를 구해보자.

위 그림의 네모 박스 1,2를 유심히 살펴보자. (덧셈노드는 그대로 흐르고, 곱셈 노드는 교차하므로 !!)

X,W,B의 shape은 각각(2,) , (2,3), (3,)이다.

∂L/∂Y는 (3,) 인데 W인 (2,3)과 차원이 달라 곱셈이 되지 않는다. 

따라서 WT를 통해 (3,2)로 바꿔 곱셈을 수행할 수 있도록 변환 (+X도 마찬가지)

 

5.6.2 배치용 Affine 계층

지금까지 예시로 든것은 X (입력 데이터)가 하나일 때만 고려한 상황이였다.

하지만 현실에서는 하나일 확률이 매우 적으므로 실제 Affine 계층을 생각해보자.

 

입력 데이터가 N개일 경우이다.

5.6.3 Softmax-with-Loss 계층

마지막으로 배울 것은 출력층에서 사용하는 소프트맥스 함수이다.

  • 딥러닝에서는 학습과 추론 두 가지가 있다.
  • 일반적으로 추론일 때는 Softmax 계층(layer)을 사용하지 않는다. Softmax 계층 앞의 Affine 계층의 출력을 점수(score)라고 하는데, 딥러닝의 추론에서는 답을 하나만 예측하는 경우에는 가장 높은 점수만 알면 되므로 Softmax 계층이 필요없다. 반면, 딥러닝을 학습할 때는 Softmax 계층이 필요하다.

이제 소프트맥스 계층을 구현할텐데, 손실 함수인 엔트로피 오차도 포함하여 계산 그래프를 살펴보자.

너무 복잡하다. 간소화 해보자.

여기서는 3클래스 분류를 가정하고 이전 계층에서 3개의 입력(점수)를 받는다.

그림과 같이 Softmax 계층은 입력 a1,a2,a3를 정규화하여 y1,y2,y3를 출력함

Cross-entropy계층은 y1,y2,y3와 t1,t2,t3(정답)을 넘겨받고 손실 L를 출력함.

 

위 그림에서 역전파의 결과인 (y1-t1),(y2-t2),(y3-t3)를 보면 softmax계층의 출력 정답레이블의 차분임.

따라서 신경망에서의 역전파는 '출력 - 정답', 즉 오차를 출력한다고 확인할 수 있다.

파이썬으로Softmax-with-Loss를 구현해보자.

# Softmax-with-Loss 계층 구현
# common / layer.py
 
class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None # 손실
        self.y = None # softmax의 출력
        self.t = None # 정답레이블 (원-핫 벡터)
 
    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss
 
    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        if self.t.size == self.y.size: # 정답레이블이 원-핫 벡터일 경우
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size
        
        return dx

5.7 오차역전파법 구현하기

5.7.1 신경망 학습의 전체 그림

 

이제 구현해보자!!

5.7.2 오차역전파를 사용한 신경망 구현

import sys, os
sys.path.append('/deep-learning-from-scratch')
import numpy as np
from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict
 
 
class TwoLayerNet:
 
    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        # 가중치 초기화
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) 
        self.params['b2'] = np.zeros(output_size)
 
        # 계층 생성
        # OrderedDict = 순서가 있는 딕셔너리, 순서 기억
        # 순전파 때는 계층을 추가한 순서대로 / 역전파 때는 계층 반대 순서로 호출
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])
 
        self.lastLayer = SoftmaxWithLoss()
        
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        
        return x
        
    # x: 입력데이터, t : 정답레이블
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
    
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        
    # x: 입력데이터, t : 정답레이블
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        
        return grads
        
    def gradient(self, x, t):
        # forward, 순전파
        self.loss(x, t)
 
        # backward, 역전파
        dout = 1
        dout = self.lastLayer.backward(dout)
        
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)
 
        # 결과 저장
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
 
        return grads

아래 표를 참고해서 위 코드를 이해합시다.

또한 위 코드에서 OrderDict주의 ( 순서가 있는 딕셔너리 ) 이를 통해 순전파, 역전파를 쉽게 사

5.7.3 오차역전파법으로 구한 기울기 검증하기

  • 느린 수치 미분보다 오차역전파법을 사용한 해석적 방법이 효율적 계산 가능
  • 수치 미분은 오차역전파법을 정확히 구현했는지 확인위해 필요
  • 수치 미분은 구현하기 쉬우나, 오차역전파법은 구현 복잡해 실수가 있을 수 있음
  • 기울기 확인(gradient check) : 두 방식으로 구한 기울기가 일치(거의 같음)함을 확인 하는 작업
# 기울기 확인 (gradient check)
import sys, os
sys.path.append('/deep-learning-from-scratch')
import numpy as np
from dataset.mnist import load_mnist
from ch05.two_layer_net import TwoLayerNet
 
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
 
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
 
x_batch = x_train[:3]
t_batch = t_train[:3]
 
grad_numerical = network.numerical_gradient(x_batch, t_batch) # 수치미분법
grad_backprop = network.gradient(x_batch, t_batch) # 오차역전파법
 
# 각 가중치 차이의 절댓값을 구한 후, 절댓값들의 평균 구함
for key in grad_numerical.keys():
    diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
    print(key + ":" + str(diff))
    
>>>
W1:4.1821647831167817e-10
b1:2.534937764494963e-09
W2:5.183343681548899e-09

5.7.4 오차역전파법을 사용한 학습 구현하기

import sys, os
sys.path.append('/content/drive/MyDrive/deep-learning-from-scratch')
 
import numpy as np
from dataset.mnist import load_mnist
from ch05.two_layer_net import TwoLayerNet
 
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
 
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
 
iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
 
train_loss_list = []
train_acc_list = []
test_acc_list = []
 
iter_per_epoch = max(train_size / batch_size, 1)
 
for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    # 오차역전파법으로 기울기 구함
    #grad = network.numerical_gradient(x_batch, t_batch) # 수치미분법
    grad = network.gradient(x_batch, t_batch)
    
    # 갱신
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(train_acc, test_acc)
        
>>>
0.16111666666666666 0.1669
0.9040833333333333 0.9045
0.9236666666666666 0.9269
0.93625 0.9373
0.94525 0.944
0.9503833333333334 0.948
0.9547166666666667 0.951
0.9602166666666667 0.9569
0.9626333333333333 0.9588
0.9652166666666666 0.9598
0.9688 0.9619
0.9709833333333333 0.9641
0.9729 0.9653
0.9746166666666667 0.9667
0.97505 0.9663
0.97645 0.967
0.9784833333333334 0.9692

'밑바닥 딥러닝' 카테고리의 다른 글

Ch 6-2 학습 관련 기술들  (0) 2023.05.31
Ch 6-1 학습 관련 기술들  (0) 2023.05.29
Ch 5-1 오차 역전파 ( Error Backpropagation )  (0) 2023.05.27
Ch4-2 신경망 학습  (0) 2023.05.26
Ch4-1 신경망 학습  (0) 2023.05.17

+ Recent posts