본문 바로가기

Study/class note

딥러닝 / 오차역전파를 이용한 2층 신경망 구현

ㅇ복습

1장. numpy와 matplotlib

2장. 퍼셉트론(가중치가 왜 갱신되어야 하는지 원리 이해)

3장. 3층 신경망 구현(저자가 만들어온 가중치를 신경망에 세팅해서 구현)

4장. 2층 신경망 구현(날코딩 -> 수치미분 이용)

5장. 2층 신경망 구현(날코딩 -> 오차 역전파 이용)

 

 

5장. 오차역전파를 이용한 2층 신경망 구현

4장에서 수치미분을 이용한 신경망을 생성해서 mnist 필기체 데이터를 학습 시켰는데 학습이 너무 느려서 우리가 끝까지 학습 시키지 않았음. 수치미분이 너무 느리므로 신경망을 학습 시킬때는 오차 역전파를 이용해서 학습 시킴.

 

수치미분으로 기울기를 구하는 함수(numerical_gradient)   vs  오차 역전파로 기울기를 구하는 함수(gradient)

 

1 오차역전파로 기울기를 구하는 함수 소개 - p.183

아래의 함수는 기울기를 구할 때 수치미분을 구하는 numerical_gradient 함수 처럼 미분을 해서 기울기를 구하는게 아니라 사람이 함수를 직접 손으로 미분해서 도함수를 이용해 기울기를 구하는 함수

미분 함수 ----변경 -----> 도함수(방정식)

 

컴퓨터나 사람이나 방정식은 미분보다는 계산하기가 쉬움

ex. f(x) = x*^2 + 3*x + 7

x가 3인 지점에서의 미분계수(기울기)를 구해라~

f(x)' = 4x + 3

x에 3 대입하면 기울기 15

 

사람이 직접 도함수를 구해서 그 도함수에 값을 대입해서 미분계수(기울기)를 구하게끔 하는 것이 gradient 함수.

4장에 수치미분을 하는 numerical_gradient는 미분을 해야했지만 5장의 gradient는 그냥 방정식

 

신경망을 학습 시킬때 필요한 함수?

1. 오차함수(크로스 엔트로피, 평균제곱오차)

2. 활성화 함수(시그모이드, 렐루)

3. 출력층 함수(소프트 맥스, 항등함수)

 

ㅇ신경망의 함수 사용 순서 

순전파 : 활성화 함수 -> 출력층 함수 -> 오차함수

역전파 : 활성화 함수 <- 출력층 함수 <- 오차함수

 def gradient(self, x, t):
        W1, W2 = self.params['W1'], self.params['W2']  # 가중치 W1 과 W2 를 가져오는 코드
        b1, b2 = self.params['b1'], self.params['b2']      # 바이어스 b1 과 b2 를 가져오는 코드
        grads = {}                                                 # 기울기를 담기위한 딕셔너리를 생성 
     
        batch_num = x.shape[0]                            # (100, 784) 에서 배치사이즈 100을 가져오는 코드 

        # forward 순전파
        a1 = np.dot(x, W1) + b1               # 순전파 1층 구현
        z1 = sigmoid(a1)                         # 순전파 1층의 sigmoid 함수 통과
        a2 = np.dot(z1, W2) + b2             # 순전파 2층(출력층)을 구현
        y = softmax(a2)                          # 소프트맥스함수를 통과해서 확률백터를 출력

        # backward 역전파                           # 오차를 역전파하는 역전파 코드 
        dy = (y - t) / batch_num             # 오차함수와 소프트맥스함수의 도함수 (예측값-정답)
        grads['W2'] = np.dot(z1.T, dy)      # 2층신경망의 가중치에 대한 역전파를 구현한 코드(기울기 생성)
        grads['b2'] = np.sum(dy, axis=0)  # 2층신경망의 바이어스에 대한 역전파 구현 코드  (기울기 생성)

        da1 = np.dot(dy, W2.T)             #  2층 신경망의 역전파를 구현한 코드(기울기로 가중치 변경)
        dz1 = sigmoid_grad(a1) * da1    # 1층 신경망의 시그모이드 함수의 도함수의 값을 출력하겠금 구현
        grads['W1'] = np.dot(x.T, dz1)    #  1층 신경망의 가중치의 역전파 (기울기 생성)
        grads['b1'] = np.sum(dz1, axis=0) # 1층 신경망의 바이어스의 역전파 (기울기 생성)
        return grads

 

개발자들이 개발을 쉽게하려고 처음에 개발 설계도(flow chart)를 생성.

신경망의 플로우 차트인 계산 그래프 그리는 방법을 5장에서 소개하고 있음.

 

 

2 계산 그래프 이용해서 신경망 순전파 구현하기 - p.148

신경망 순전파와 역전파 코드를 파이썬으로 구현할 때 계산 그래프(플로우 차트)를 이용하면 쉽게 구현할 수 있음. 

 

 

왜 계산 그래프를 이용해서 순전파 신경망에 대한 코드를 구현하려 하는가?

전체가 아무리 복잡해도 각 노드에서 수행되는 단순한 계산에만 집중해서 문제를 단순화 시키겠다.

-> 신경망이 아무리 복잡해도 / 1층 노드에만 집중하거나, 2층 노드에만 집중하거나해서 하나씩 해결하자

-> 큰 문제를 단순화 시키겠다

그림 5-2

첫번째 노드(뉴런)에서는 사과의 가격 200원이 어떻게 나왔는지에 대한 계산에만 집중하고

두번째 노드(뉴런)에서는 사과의 가격 200원에 소비세(10%)를 적용해서 220원이 어떻게 계산되었는지만 집중하겠다.

그림 5-3

그림 5-2보다는 조금 더 복잡한 계산이 이뤄지고 있지만 국소적 계산이 이뤄지고 있는 부분은 4군데로 각 뉴런이 담담하고 있는 계산에만 집중하면 되고 다른 뉴런에서 어떤 일이 벌어지는지는 신경쓰지 않아도 됨.

전체가 아무리 복잡해도 각 노드에서 수행되는 단순한 계산에만 집중하여 문제를 단순화 시킬 수 있음.

 

 

3 왜 계산 그래프로 신경망의 구현 문제를 푸는가? - p.151

신경망 전체가 아무리 복잡해도 각 노드에서 수행되는 단순한 계산에만 집중하여 문제를 단순화 시킬 수 있기 때문.

순전파는 100 -> 200 -> 220으로 값이 전파되고 있고 역전파는 다시 거꾸로 1 -> 1.1 -> 2.2 순으로 미분값이 전달되면서 역전파 되고 있음.

 

 

4 계산 그래프의 역전파 - p.153

역전파를 통해서 미분을 효율적으로 계산할 수 있는데 

신경망이 학습된다는 것은 오차가 적어지도록 가중치가 갱신된다는 것

가중치 = 가중치 - 기울기

이중에서 가중치(w1)의 기울기는 어떻게 구할 수 있는가?

신경망 학습을 통해서 최종적으로 산출하고자 하는 것 => 오차가 가장 적은 가중치

'가중치(w1)'에 변화가 생겼을 때 '오차'는 얼마나 달라지는가?

'사과값'이 아주 조금 올랐을 때 '지불금액'이 얼마나 증가되는가?

== 지불금액을 사과값으로 편미분하면 됨

ex. 사과값이 1원 오르면 지불금액이 얼마나 증가하나요? 2.2원

 

오차함수를 w1으로 편미분한 것

w1 = w1 - 기울기   <- w1을 갱신해나가면서 기울기가 0가 됐을 때 최종적으로 구해지는 w1이 우리가 원하는 가중치

 

가중치를 갱신하기 위해서는 기울기가 필요한데 역전파는 하나의 뉴런(또는 층)에 해당하는 기울기만 알면되고 다른 뉴런(또는 층)에서 발생하는 기울기는 신경쓰지 않아도 됨.

 

 

예제. 아래의 기울기(미분계수)를 구하시오.

 

 

5 합성함수 미분 - p.153

신경망에는 활성화 함수가 여러개 이므로 합성함수 미분을 알아야 신경망 오차 역전파를 이해할 수 있습니다.

 

z = t^2

t = x+y

 

 

6 연쇄 법칙과 계산 그래프 - p154

z를 z로 미분하면 1. 따라서 끝에서는 무조건 1이 오게 되어 있음.

 

 

7 덧셈 노드의 역전파 - p.156

덧셈 노드의 역전파 값은 상류에서 전해진 값이 그대로 흘러감.

 

 

8 곱셈 노드의 역전파 - p.157

z = xy

곱셈 노드의 역전파 값은 상류에서 흘러왔던 값에 상대편 쪽의 값을 곱해주는 것.

 

 

문제95. 아래의 덧셈 노드의 역전파 값을 쓰시오.  답 : 3

 

문제96. 아래의 곱셈 노드의 역전파 값을 쓰시오.  답 : 21, 15

 

문제97. 책 161에 나오는 곱셈계층 클래스(설계도)를 파이썬으로 구현하시오.

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
        dy = dout * self.x
        return dx, dy

 

문제98. 위에서 만든 곱셈 클래스(설계도)를 객체화(제품) 시켜서 아래의 사과 가격을 구하시오.

apple = 100
apple_num = 2

mul_apple_layer = MulLayer()  # 곱셈계층 객체 생성
apple_price = mul_apple_layer.forward(apple,apple_num)
apple_price  #200

 

문제99. 덧셈 계층 클래스를 파이썬으로 구현하시오. - p.163

class AddLayer:
    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 * 1
        dy = dout * 1
        return dx, dy

 

문제100. 곱셈 클래스를 이용해서 사과 가격을 출력하시오.

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.00000000000003

 

 

문제101. 역전파 그림을 먼저 그리시오

문제102. 역전파 그림을 코드로 구현하시오.

순전파로 전파를 흘려 보내고 나서 역전파가 되어야 합니다.

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.00000000000003

#역전파
dprice = 1
dall_price, dtax = mul_tax_layer.backward(dprice)
dapple_price, dapple_num = mul_apple_layer.backward(dall_price)
print(dapple_price)  #2.2
print(dapple_num)  #110.00000000000001

 

 

문제103. 역전파 되는 부분의 네모에 값을 직접 넣으시오.

덧셈의 역전파는 상류에서 흘러온 값을 그대로 흘려보내는 것이고 곱셈의 역전파는 상류에서 흘러온 값에 상대편 쪽 순전파 값을 곱해서 흘려보내는 것

 

 

문제104. 위에서 만든 곱셈 클래스와 덧셈 클래스를 이용해서 과일 가격만 출력하시오.

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)
orange_price = mul_orange_layer.forward(orange, orange_num)
price = add_apple_orange_layer.forward(apple_price, orange_price)
tax_price = mul_tax_layer.forward(price, tax)
print(tax_price)  #715.0000000000001

 

 

위에서는 덧셈계층과 곱셈계층을 만들었는데 신경망을 위해서 진짜 만들어야 하는 계층은?

1. 시그모이드 함수

   - 순전파 함수, 역전파 함수

2. 렐루 함수

   - 순전파 함수, 역전파 함수

3. Affine(입력값과 가중치의 내적을 계산) 계층

   - 순전파 함수, 역전파 함수

4. 출력층 함수(순전파/역전파) 5. 오차함수(순전파/ 역전파)  <- 부록 참고

 

9 렐루함수의 계산 그래프 - p.165

계산 그래프를 그리는 이유가 계산 그래프를 보고 파이썬 코드 구현을 하려고 그리는 것.

 

렐루함수: 0보다 큰 값이 입력이 되면 그 값을 그대로 출력하고 0보다 작은 값이 입력되면 0을 출력하는 함수

순전파일 때 어떤 값을 흘려보냈는지가 기억되어야 역전파 일 때 값을 역전파로 흘려보낼지 말지를 결정할 수 있음.

값이 기억되게 파이썬 코드를 구현해야함.

 

1. copy모듈 사용법

2. x[x<=0]의 의미

 

ㅇcopy모듈 사용법

a = [1,2,3]
b = a  # a가 가리키고 있는 곳을 b도 가리키겠다
print(b)  #[1, 2, 3]

a[1] = 6
print(a)  #[1, 6, 3]
print(b)  #[1, 6, 3]

b는 a와 같은 곳을 바라보고 있는 것이므로 똑같이 [1,6,3]이 출력됨

 

from copy import copy

a = [1,2,3]
b = copy(a)  #copy했기 때문에 b는 별도의 객체가 됨
b = a.copy()

a[1] = 6
print(a)  #[1, 6, 3]
print(b)  #[1, 2, 3]

copy는 별도의 객체가 되므로 a[1]의 요소값을 바꿔도 b 리스트 값이 변하지 않음.

순전파일때 입력값을 기억하기 위해서 copy가 필요함.

 

2. x[x<=0] 의 의미? 렐루는 순전파때 신호를 보냈다는 유무를 기억하고 있어야하기 때문에 이 문법이 필요함.

import numpy as np

x = np.array([[1.0,-0.5], [-2.0, 3.0]])
print(x)

mask = (x<=0)
print(mask)  # 입력값이 0보다 작거나 같으면 True, 나머지 False

out = x.copy()
out[mask] = 0  # True인 위치에 0을 할당해라

print(out)

 

문제105. 책 166페이지에 나오는 Relu 클래스를 생성하시오.

class Relu:
    def __init__(self):
        self.mask = None
        
    def forward(self, x):  # 순전파를 구현하는 코드
        self.mask = (x<=0)  # 순전파일때 어느 자리에 값이 흘러갔는지 유무 저장
        out = x.copy()  
        out[self.mask] = 0  # x<=0 이면 전부 0이 할당됨
        return out
    
    def backward(self, dout):  # 역전파 코드
        dout[self.mask] = 0
        dx = dout
        return dx

순전파일 때의 데이터 행렬 shape과 역전파일때의 데이터 행렬 shape가 같음.

 

 

문제106. 위의 relu클래스를 객체화 시켜서 아래의 x데이터를 흘려보내고 순전파 결과를 출력하시오.

x = np.array([[1.0,-0.5],[-2.0,3.0]])

relu = Relu()
print(relu.forward(x))
# [[1. 0.]
#  [0. 3.]]

 

 

문제107. 위의 Relu 클래스를 객체화 시켜서 위의 x 입력값을 흘려보내고 아래의 dout행렬의 역전파 값을 출력하시오.

x = np.array([[1.0,-0.5],[-2.0,3.0]])

relu = Relu()
print(relu.forward(x))

# 순전파를 먼저 흘려보내야 역전파를 실행할 수 있음
dout = np.array([35, 73, 92, 83]).reshape(2,2)

print(relu.backward(dout))
#[[35  0]
# [ 0 83]]

dout 역전파 데이터가 backward 함수에서 실행되려면 먼저 순전파가 수행되어서 순전파때 데이터가 흘러갔는지 안 흘러갔는지에 대한 기억이 있어야 함.

 

 

10 시그모이드 계층 구현하기 - p.167

시그모이드 함수는 입력값을 받아서 0 ~ 1사이의 실수를 출력하는 함수

 

 

문제108. 시그모이드 함수 계층을 위한 시그모이드 클래스를 구현하고 아래의 입력값의 순전파값을 출력하시오 - p.170

# 시그모이드 계층
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

# 입력값
x = np.array([24,32,5])

# 순전파
sigmoid = Sigmoid()
print(sigmoid.forward(x))  #[1.         1.         0.99330715]

 

 

반응형