#!/usr/bin/env python # coding: utf-8 # # 밑바닥부터 시작하는 딥러닝 # # # Deep Learning from Scratch # # ## Github # # https://github.com/WegraLee/deep-learning-from-scratch # # ## 목차 # # http://nbviewer.jupyter.org/github/SDRLurker/deep-learning/blob/master/%EB%AA%A9%EC%B0%A8.ipynb # # 5 오차역전파법 # # 오차역전파법(backpropagation): 가중치 매개변수의 기울기를 효율적으로 계산 # # 오차를 역(반대 방향)으로 전파하는 방법(backward propagation of errors) # # 안드레 카패시(Andrej Karpathy)의 블로그 # # * 참고주소 : http://karpathy.github.io/neuralnets # # * 오차역전파법을 계산 그래프로 설명 # # 페이페이 리(Fei-Fei Li) 교수가 진행한 스탠퍼드 대학교 딥러닝 수업 CS231n 참고 # # * 참고주소 : http://cs231n.github.io # ## 5.1 계산 그래프 # # 계산 그래프(computational graph): 계산 과정을 그래프로 나타낸 것 # # 복수의 노드(node)와 에지(edge)로 표현됨. # # 에지: 노드 사이의 직선 # ### 5.1.1 계산 그래프로 풀다 # # 계산 그래프를 이용한 문제풀이는 다음 흐름으로 진행 # # * 계산 그래프를 구성한다. # * 그래프에서 계산을 왼쪽에서 오른쪽으로 진행한다. # # 순전파: 계산을 왼쪽에서 오른쪽으로 진행. 계산 그래프의 출발점부터 종착점으로의 전파. # ### 5.1.2 국소적 계산 # # 국소적: 자신과 직접 관련된 작은 범위 # # 국소적 계산: 자신과 관계된 정보만으로 다음 결과를 출력할 수 있음 # # 각 노드는 자신과 관련된 계산 외에는 아무 것도 신경 쓸게 없음 # # 복잡한 계산을 '단순하고 국소적 계산'으로 분할하고 계산 결과를 다음 노드로 전달 # # 복잡한 계산도 분해하면 단순한 계산으로 구성됨 # ### 5.1.3 왜 계산 그래프로 푸는가? # # 역전파를 통해 '미분'을 효율적으로 계산할 수 있음 # # 중간까지 구한 미분 결과를 공유할 수 있어 다수의 미분을 효율적으로 계산할 수 있음 # ## 5.2 연쇄법칙 # # '국소적 미분'을 전달하는 원리는 연쇄 법칙(chain rule)에 따른 것 # ### 5.2.1 계산 그래프의 역전파 # # 계산 그래프의 역전파: 순방향과는 반대 방향으로 국소적 미분을 곱한다. # # 역전파의 계산 절차는 신호 E에 노드의 국소적 미분을 곱한 후 다음 노드로 전달 # # 역전파의 계산 순에 따르면 목표로 하는 미분 값을 효율적으로 구할 수 있음 # ### 5.2.2 연쇄법칙이란? # # 합성 함수: 여러 함수로 구성된 함수 # # #### 식 5.1 # # \begin{equation*} # z = t^{2} # \end{equation*} # # \begin{equation*} # t = x + y # \end{equation*} # # 연쇄법칙은 함성 함수의 미분에 대한 성질 # # 합성 함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다. # # #### 식 5.2 # # \begin{equation*} # \frac{\partial z}{\partial x} = \frac{\partial z}{\partial t} \frac{\partial t}{\partial x} # \end{equation*} # # x에 대한 z의 미분은 t에 대한 z의 미분과 x에 대한 t의 미분의 곱으로 나타낼 수 있음 # # ∂t를 서로 지울 수 있음. # # \begin{equation*} # \frac{\partial z}{\partial x} = \frac{\partial z}{} \frac{}{\partial x} # \end{equation*} # # #### 식 5.3 # # 식 5.1에 대한 국소적 미분(편미분)을 구함 # # \begin{equation*} # \frac{\partial z}{\partial t} = 2t # \end{equation*} # # \begin{equation*} # \frac{\partial t}{\partial x} = 1 # \end{equation*} # # 최종적으로 구하고 싶은 x에 대한 z의 미분은 다음 두 미분을 곱해 계산 # # #### 식 5.4 # # \begin{equation*} # \frac{\partial z}{\partial x} = \frac{\partial z}{\partial t} \frac{\partial t}{\partial x} = 2t · 1 = 2(x+y) # \end{equation*} # ### 5.2.3 연쇄법칙과 계산 그래프 # # 계산 그래프의 역전파는 오른쪽에서 왼쪽으로 신호를 전파 # # 노드로 들어온 입력신호에 그 노드의 국소적 미분(편미분)을 곱한 후 다음 노드로 전달 # # 역전파가 하는 일은 연쇄 법칙의 원리와 같음. # ## 5.3 역전파 # ### 5.3.1 덧셈 노드의 역전파 # # z = x + y 의 미분. 다음은 해석적으로 계산 # # #### 식 5.5 # # \begin{equation*} # \frac{\partial z}{\partial x} = 1 # \end{equation*} # # \begin{equation*} # \frac{\partial z}{\partial y} = 1 # \end{equation*} # # 덧셈 노드의 역전파는 1을 곱하기만 할 뿐 입력된 값을 그대로 다음 노드로 보내게 됨. # ### 5.3.2 곱셈 노드의 역전파 # # z = xy 의 미분 # # #### 식 5.6 # # \begin{equation*} # \frac{\partial z}{\partial x} = y # \end{equation*} # # \begin{equation*} # \frac{\partial z}{\partial y} = x # \end{equation*} # # 곱셈 노드의 역전파는 상류의 값에 순전파 때의 입력 신호들을 '서로 바꾼 값'을 곱해서 하류로 보냄 # # 순전파 때 x 였다면 역전파에서는 y. 순전파 때 y 였다면 역전파에서는 x로 바꿈 # ### 5.3.3 사과 쇼핑의 예 # ## 5.4 단순한 계층 구현하기 # # 계산 그래프의 곱셈 노드를 'MultiLayer', 덧셈 노드를 'AddLayer'로 구현 # ### 5.4.1 곱셈 계층 # # 모든 계층은 forward() 순전파, backward() 역전파 라는 공통의 메서드(인터페이스)를 갖도록 구현 # # 곱셈 계층을 MultiLayer 클래스로 다음처럼 구현 # In[1]: # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/ch05/layer_naive.py 소스 참고 class MulLayer: def __init__(self): self.x = None self.y = None def forward(self, x, y): self.x = x self.y = y out = x * y return out def backward(self, dout): dx = dout * self.y # x와 y를 바꾼다. dy = dout * self.x return dx, dy # \__init\__() : 인스턴스 변수인 x와 y를 초기화. 순전파 시 입력 값을 유지하기 위해 사용. # # forward() : x와 y를 인수로 받고 두 값을 곱해 반환 # # backward() : 상류에서 넘어온 미분(dout)에 순전파 때 값을 '서로 바꿔' 곱한 후 하류로 흘림. # # MultiLayer를 사용하여 순전파 구현 # In[2]: # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/ch05/buy_apple.py 소스 참고 apple = 100 apple_num = 2 tax = 1.1 # 계층들 mul_apple_layer = MulLayer() mul_tax_layer = MulLayer() # 순전파 apple_price = mul_apple_layer.forward(apple, apple_num) price = mul_tax_layer.forward(apple_price, tax) print(price) # 220 # 각 변수에 대한 미분은 backward()로 구할 수 있음 # In[3]: dprice = 1 dapple_price, dtax = mul_tax_layer.backward(dprice) dapple, dapple_num = mul_apple_layer.backward(dapple_price) print(dapple, dapple_num, dtax) # 2.2 110 200 # backward() 호출 순서는 forward() 때와 반대 # # backward()가 받는 인수는 '순전파의 출력에 대한 미분' # ### 5.4.2 덧셈 계층 # # 모든 계층은 forward() 순전파, backward() 역전파 라는 공통의 메서드(인터페이스)를 갖도록 구현 # # 덧셈 계층을 MultiLayer 클래스 # In[4]: class AddLayer: def __init__(self): pass def forward(self, x, y): out = x + y return out def backward(self, dout): dx = dout * 1 dy = dout * 1 return dx, dy # \__init\__() : pass를 통해 아무 일도 하지 않음 # # forward() : x와 y를 인수로 받고 두 값을 더해 반환 # # backward() : 상류에서 넘어온 미분(dout)을 그대로 하류로 흘림 # # 그림 5-17의 계산 그래프 파이썬 구현 # In[5]: # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/ch05/buy_apple.py 소스 참고 apple = 100 apple_num = 2 orange = 150 orange_num = 3 tax = 1.1 # 계층들 mul_apple_layer = MulLayer() mul_orange_layer = MulLayer() add_apple_orange_layer = AddLayer() mul_tax_layer = MulLayer() # 순전파 apple_price = mul_apple_layer.forward(apple, apple_num) # (1) orange_price = mul_orange_layer.forward(orange, orange_num) # (2) all_price = add_apple_orange_layer.forward(apple_price, orange_price) # (3) price = mul_tax_layer.forward(all_price, tax) # (4) # 역전파 dprice = 1 dall_price, dtax = mul_tax_layer.backward(dprice) # (4) dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price) # (3) dorange, dorange_num = mul_orange_layer.backward(dorange_price) # (2) dapple, dapple_num = mul_apple_layer.backward(dapple_price) # (1) print("price:", int(price)) # 715 print("dApple:", dapple) # 2.2 print("dApple_num:", int(dapple_num)) # 110 print("dOrange:", dorange) # 3.3 print("dOrange_num:", int(dorange_num)) # 165 print("dTax:", dtax) # 650 # ## 5.5 활성화 함수 계층 구현하기 # # 활성화 함수인 ReLU와 Sigmoid 계층을 구현 # ### 5.5.1 ReLU 계층 # # #### 식 5.7 ReLU 식 # # \begin{equation*} # y = x ( x > 0 ) # \end{equation*} # # \begin{equation*} # y = 0 ( x <= 0 ) # \end{equation*} # # #### 식 5.8 ReLU x에 대한 y 미분 식 # # \begin{equation*} # \frac{\partial y}{\partial x} = 1 ( x > 0 ) # \end{equation*} # # \begin{equation*} # \frac{\partial y}{\partial x} = 0 ( x <= 0 ) # \end{equation*} # # 순전파 때 입력인 x가 0보다 크면 역전파는 상류의 값을 그대로 하류로 흘림 # # 순전파 때 x가 0 이하면 역전파 때는 하류로 신호를 보내지 않음 # # ReLU 계층을 구현한 코드 # In[6]: # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/common/layers.py 소스 참고 class Relu: def __init__(self): self.mask = None def forward(self, x): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout return dx # Relu 클래스는 mask 인스턴스 변수를 가짐 # # mask는 순전파의 입력인 x의 원소 값이 0 이하인 인덱스는 True, 그 외(0보다 큰 원소)는 False로 유지 # In[7]: import numpy as np x = np.array([[1.0, 0.5], [-2.0, 3.0]]) print(x) # In[8]: mask = (x <= 0) print(mask) # In[9]: out = x.copy() out[mask] = 0 out # ReLU 계층은 전기 회로의 '스위치'에 비유 # # 순전파 때 전류가 흐르고 있으면 스위치를 ON, 흐르지 않으면 OFF # # 역전파 때 스위치가 ON이라면 전류가 그대로 흐르고, OFF면 더 이상 흐르지 않음 # ### 5.5.2 Sigmoid 계층 # # #### 식 5.9 시그모이드 함수 # # \begin{equation*} # y = \frac{1}{1 + exp(-x)} # \end{equation*} # # **1단계** '/' 노드, y = 1 / x를 미분하면 다음식이 됨 # # #### 식 5.10 # # \begin{equation*} # \frac{\partial y}{\partial x} = -\frac{1}{x^{2}} # \end{equation*} # # \begin{equation*} # = - y^{2} # \end{equation*} # # 역전파 때는 상류의 예측값에 -y\**2 을 곱해서 하류로 전달 # # **2단계** 상류의 값을 여과 없이 하류로 보냄 # # **3단계** y = exp(x) 연산을 수행 # # #### 식 5.11 # # \begin{equation*} # \frac{\partial y}{\partial x} = exp(x) # \end{equation*} # # 계산 그래프에서는 상류의 순전파 때의 출력(exp(-x))을 곱해 하류로 전파 # # **4단계** y = exp(x) 연산을 수행 # # 'X' 노드, 순전파 때의 값을 서로 바꿔 곱함. 이 예에서는 -1을 곱함 # # 시그모이드 간소화버전 # # 노드를 그룹화하여 Sigmoid 계층의 세세한 내용을 노출하지 않고 입력과 출력에만 집중 # # \begin{equation*} # \frac{\partial L}{\partial y} y^{2} exp(-x) = \frac{\partial L}{\partial y} \frac{1} { (1+exp(-x))^{2}} exp(-x) # \end{equation*} # # \begin{equation*} # = \frac{\partial L}{\partial y} \frac{1} { 1+exp(-x)} \frac{exp(-x)} {1+exp(-x)} # \end{equation*} # # \begin{equation*} # = \frac{\partial L}{\partial y} y (1-y) # \end{equation*} # # Sigmoid 계층의 계산 그래프: 순전파의 출력 y만으로 역전파를 계산 # # Sigmoid 계층을 파이썬으로 구현 # In[10]: class Sigmoid: def __init__(self): self.out = None def forward(self, x): out = 1 / (1 + np.exp(-x)) self.out = out return out def backward(self, dout): dx = dout * (1.0 - self.out) * self.out return dx # ## 5.6 Affine/Softmax 계층 구현하기 # # ### 5.6.1 Affine 계층 # # 신경망의 순전파에서는 가중치 신호의 총합을 계산하기 위해 행렬의 내적(np.dot())을 사용 # In[11]: X = np.random.rand(2) # 입력 W = np.random.rand(2,3) # 가중치 B = np.random.rand(3) # 편향 print(X.shape) # (2,) print(W.shape) # (2, 3) print(B.shape) # (3,) Y = np.dot(X, W) + B # X와 W의 내적은 대응하는 차원의 원소 수를 일치 시켜야 함 # # 어파인 변환(affine transformation): 신경망의 순전파 때 수행하는 행렬의 내적. 기하학 용어 # # 이 계산 그래프는 '행렬'이 흐름 # # #### 식 5.13 행렬을 사용한 역전파 전개식 # # \begin{equation*} # \frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y} W^{T} # \end{equation*} # # \begin{equation*} # \frac{\partial L}{\partial W} = X^{T} \frac{\partial L}{\partial Y} # \end{equation*} # # 전치행렬 : W의 (i,j) 위치의 원소를 (j,i) 위치로 변경 # # #### 식 5.14 전치행렬 수식 # # \begin{equation*} # W = \begin{vmatrix} # w_{11} w_{21} w_{31}\\ # w_{12} w_{22} w_{32}\ # \end{vmatrix} # \end{equation*} # # \begin{equation*} # W^{T} = \begin{vmatrix} # w_{11} w_{12}\\ # w_{21} w_{22}\\ # w_{31} w_{32}\ # \end{vmatrix} # \end{equation*} # # W의 형상이 (2,3) 이면 W.T의 형상은 (3,2) # # #### 그림 5.25 Affine 계층의 역전파: 역전파에서의 변수 형상은 해당 변수명 옆에 표기 # # \begin{equation*} # \frac{\partial L}{\partial X}(2,) = \frac{\partial L}{\partial Y}(3,) W^{T} (3,2) # \end{equation*} # # \begin{equation*} # X(2,) 와 \frac{\partial L}{\partial X}(2,) 은 같은 형상 # \end{equation*} # # \begin{equation*} # \frac{\partial L}{\partial W}(2,3) = X^{T}(2,1) \frac{\partial L}{\partial Y} (1,3) # \end{equation*} # # \begin{equation*} # W(2,3) 와 \frac{\partial L}{\partial W}(2,3) 은 같은 형상 # \end{equation*} # ### 5.6.2 배치용 Affine 계층 # # #### 그림 5-27 배치용 Affine 계층의 계산 그래프 # # \begin{equation*} # \frac{\partial L}{\partial X}(N,2) = \frac{\partial L}{\partial Y}(N,3) W^{T} (3,2) # \end{equation*} # # \begin{equation*} # \frac{\partial L}{\partial W}(2,3) = X^{T}(2,N) \frac{\partial L}{\partial Y} (N,3) # \end{equation*} # # \begin{equation*} # \frac{\partial L}{\partial B}(3) = \frac{\partial L}{\partial Y} (N,3) 의 첫 번째(제 0축, 열방향)의 합 # \end{equation*} # # 기존과 다른 부분은 입력인 X의 형상이 (N,2)가 됨 # # 예를 들어 N=2(데이터가 2개)로 한 경우, 편향은 그 두 데이터 각각에 더해집니다. # In[12]: X_dot_W = np.array([[0, 0, 0], [10, 10, 10]]) B = np.array([1, 2, 3]) X_dot_W # In[13]: X_dot_W + B # 순전파의 편향 덧셈은 각각의 데이터(1번째 데이터, 2번째 데이터)에 더해짐 # # 역전파 때는 각 데이터의 역전파 값이 편향의 원소에 모여야 함 # In[14]: dY = np.array([[1, 2, 3], [4, 5, 6]]) dY # In[15]: dB = np.sum(dY, axis=0) dB # np.sum()에서 0번째 축(데이터를 단위로 한 축)에 대해서 (axis=0)의 총합을 구함 # # Affine 구현 # # common/layer.py 파일의 Affine 구현은 입력 데이터가 텐서(4차원 데이터)인 경우도 고려. 다음 구현과 약간 차이가 있음. # In[16]: class Affine: def __init__(self, W, b): self.W = W self.b = b self.x = None self.dW = None self.db = None def forward(self, x): self.x = x out = np.dot(x, self.W) + self.b return out def backward(self, dout): dx = np.dot(dout, self.W.T) self.dW = np.dot(self.x.T, dout) self.db = np.sum(dout, axis=0) return dx # ### 5.6.3 Softmax-with-Loss 계층 # # 소프트맥스 함수는 입력 값을 정규화하여 출력 # # 추론할 때는 일반적으로 Softmax 계층을 사용하지 않음 # # 점수(score): Softmax 앞의 Affine 계층의 출력 # # 신경망을 학습할 때는 Softmax 계층이 필요 # # 소프트맥스 계층 구현: 손실 함수인 교차 엔트로피 오차도 포함하여 'Softmax-with-Loss 계층'이라는 이름으로 구현 # # Softmax 계층: 입력 (a1, a2, a3)를 정규화하여 (y1, y2, y3)를 출력 # # Cross Entropy 계층: Softmax의 출력(y1, y2, y3)과 정답 레이블(t1, t2, t3)를 받고, 손실 L을 출력 # # Softmax 계층의 역전파는 (y1-t1, y2-t2, y3-t3)로 말끔한 결과임 # # Softmax 계층의 출력과 정답 레이블의 차분. # # 신경망의 역전파에서는 이 차이인 오차가 앞 계층에 전해지는 것 # # 소프트맥스 함수의 손실 함수로 교차 엔트로피 오차를 사용하니 역전파가 (y1-t1, y2-t2, y3-t3)로 말끔히 떨어짐 # # => 교차 엔트로피 함수가 그렇게 설계되었기 때문 # # 항등 함수의 손실 함수로 '평균 제곱 오차'를 사용하면 역전파의 결과가 말끔히 떨어짐 # # 구체적인 예 # # 정답 레이블 (0, 1, 0), 소프트맥스 계층이 (0.3, 0.2, 0.5)를 출력 # # => 소프트맥스 계층의 역전파는 (0.3, -0.8, 0.5)라는 커다란 오차를 전파 # # 정답 레이블 (0, 1, 0), 소프트맥스 계층이 (0.01, 0.99, 0)을 출력 # # => 소프트맥스 계층의 역전파가 보내는 오차는 (0.01, -0.01, 0)이 됨. 학습하는 정도가 작아짐 # # Softmax-with-Loss 계층을 구현한 코드 # In[17]: # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/common/functions.py 소스 참고 # 3.5.2 소프트맥스 함수 구현시 주의점 참고 def sigmoid(x): return 1 / (1 + np.exp(-x)) # 4.2.2. 교차 엔트로피 오차 참고 def cross_entropy_error(y, t): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1, y.size) # 훈련 데이터가 원-핫 벡터라면 정답 레이블의 인덱스로 반환 if t.size == y.size: t = t.argmax(axis=1) batch_size = y.shape[0] return -np.sum(np.log(y[np.arange(batch_size), t])) / batch_size 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] dx = (self.y - self.t) / batch_size return dx # 주의. 역전파 때는 전파하는 값을 배치의 수(batch_size)로 나눠 데이터 1개당 오차를 앞 계층으로 전파함 # ## 5.7 오차역전파법 구현하기 # ### 5.7.1 신경망 학습의 전체 그림 # # **전제** # # 학습: 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정 # # **1단계 - 미니배치** # # 미니배치: 훈련 데이터 중 일부를 무작위로 가져옴 # # 목표: 미니배치의 손실 함수 값을 줄이기 # # **2단계 - 기울기 산출** # # 가중치 매개변수의 기울기를 구함. 기울기는 손실 함수의 값을 가장 작게하는 방향을 제시 # # **3단계 - 매개변수 갱신** # # 가중치 매개변수를 기울기 방향으로 아주 조금 갱신 # # **4단계 - 반복** # # 1~3 단계를 반복 # # 오차역전법이 등장하는 단계는 두 번째인 '기울기 산출' # # 느린 수치 미분과 달리 기울기를 효율적이고 빠르게 구할 수 있음 # ### 5.7.2 오차역전파법을 적용한 신경망 구현하기 # # 계층을 사용함으로써 # # 인식 결과를 얻는 처리(predict())와 기울기를 구하는 처리(gradient()) 계층의 전파만으로 동작이 이루어짐. # In[18]: # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/ch05/two_layer_net.py 참고 # coding: utf-8 #import sys, os #sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정 import numpy as np #from common.layers import * #from common.gradient import numerical_gradient from collections import OrderedDict # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/common/functions.py def softmax(x): if x.ndim == 2: x = x.T x = x - np.max(x, axis=0) y = np.exp(x) / np.sum(np.exp(x), axis=0) return y.T x = x - np.max(x) # 오버플로 대책 return np.exp(x) / np.sum(np.exp(x)) # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/common/gradient.py 참고 def numerical_gradient(f, x): h = 1e-4 # 0.0001 grad = np.zeros_like(x) it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) while not it.finished: idx = it.multi_index tmp_val = x[idx] x[idx] = float(tmp_val) + h fxh1 = f(x) # f(x+h) x[idx] = tmp_val - h fxh2 = f(x) # f(x-h) grad[idx] = (fxh1 - fxh2) / (2*h) x[idx] = tmp_val # 값 복원 it.iternext() return grad 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) # 계층 생성 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 # \### 으로 중요코드 표시. 집중해서 살펴보세요. # # OrderedDict: 딕셔너리에 추가한 순서를 기억하는 (순서가 있는) 딕셔너리 # # 순전파 때는 추가한 순서대로 각 계층의 forward() 메서드를 호출 # # 역전파 때는 계층을 반대 순서로 호출 # # 신경망의 구성 요소를 '계층'으로 구현한 덕분에 신경망을 쉽게 구축 # # => 레고 블록을 조립하듯 필요한 만큼 계층을 더 추가하면 됨 # ### 5.7.3 오차역전파법으로 구한 기울기 검증하기 # # 수치미분은 느립니다. # In[19]: from dataset.mnist import load_mnist (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] # In[20]: get_ipython().run_line_magic('timeit', 'network.numerical_gradient(x_batch, t_batch)') # In[21]: get_ipython().run_line_magic('timeit', 'network.gradient(x_batch, t_batch)') # 수치미분(numerical_gradient) 속도: 9.95초, 14.1초 # # 오차역전법(gradient) 속도: 248 µs(0.000248초), 470 µs(0.000470초) # # 약 42,000배 속도차이가 남 # 수치 미분을 오차역전파법을 정확히 구현했는지 확인하기 위해 필요. # # 수치 미분의 이점은 구현하기 쉬움 # # 기울기 확인(gradient check): 수치 미분의 결과와 오차역전파법의 결과를 비교하여 오차역전파법을 제대로 구현했는지 검증함. # In[22]: # https://github.com/WegraLee/deep-learning-from-scratch/blob/master/ch05/gradient_check.py 참고 # coding: utf-8 #import sys, os #sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정 import numpy as np from dataset.mnist import load_mnist # 데이터 읽기 (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)) # 이 결과는 수치 미분과 오차역전파법으로 구한 기울기의 차이가 매우 작다고 말해줌 # # 오차역전법이 실수 없이 구현했다는 믿음이 커짐 # # 수치 미분과 오차역전파법의 결과 오차가 0이 되는 일은 드뭄 # # 올바르게 구현했다면 0에 아주 가까운 작은 값이 됨 # ### 5.7.4 오차역전파법을 사용한 학습 구현하기 # In[23]: # coding: utf-8 #import sys, os #sys.path.append(os.pardir) import numpy as np from dataset.mnist import load_mnist #from 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) # ## 5.8 정리 # # 계산 그래프를 이용하여 신경망의 동작과 오차역전파법을 설명 # # 모든 계층에서 forward와 backward 메서드를 구현 # # forward는 데이터를 순방향으로 backward는 역방향으로 전파함 # # 가중치 매개변수의 기울기를 효율적으로 구할 수 있음 # **이번 장에서 배운 것** # # 계산그래프를 이용하면 계산 과정을 시각적으로 파악 가능 # # 계산그래프 노드는 국소적 계산으로 구성. 국소적 계산을 조합해 전체 계산을 구성 # # 순전파는 통상의 계산을 수행. 역전파는 노드의 미분을 구함 # # 오차역전파법: 신경망의 구성 요소를 계층으로 구현하여 기울기를 효율적으로 계산 # # 기울기 확인: 수치 미분과 오차역전파법의 결과를 비교하면 오차역전파법 구현에 잘못이 없는지 확인가능