본문 바로가기

2022.01./코딩 수업

01.28

[0] 오차 역전파법

1.계산그래프

1) 자료 구조 : 노드(처리장치) + 에지(선) 
최소한의 구조로 효율적으로 작성하는 능력 중요
연산자만 노드에 남기고, 데이터는 변수취급해 밖에서 입력시킨다. tensorflow에 따라 순차적으로 계산한다.
'순전파' 왼->오
'역전파' 오->왼 ☆미분 계산할 때 쓰인다.

2) 국소적 계산 pipe & filter -> 컨베이어 벨트 위에서 고정된 파이프와 필터에 들어가 척척 계산 
전체 계산 과정이 복잡하더라도, 해당 노드에서의 국소적 계산에만 초점을 맞춘다.(각각의 노드에서 분업)

3)장점
(1) 문제를 단순화
(2) 중간계산결과 보관가능
(3) 역전파로 미분 효율적 계산 가능 : 역류한다
가중치를 살짝 바꾼다면(dx 변화된 x값) 마지막에 반환된 손실치도 변한다(df x넣은 함수값)
df/dx = 변화율 (미분값 계량화)
(4)
곱셈노드는 엇갈려서 곱하고 덧셈노드는 흘려보냄.

2.연쇄법칙

합성함수

lim     f(x+h)-f(x)/h   미분값 구하는 공식.
h->0

ax^b ---미분---> a*bx^b-1


3.역전파
1) 덧셈노드의 역전파 = 입력값 그대로 흘려보냄
2) 곱셈노드의 역전파 = 입력신호들을 '서로 바꾼값'을 곱해 흘려보냄 
(=그러려면 순방향때 입력신호 변수로 저장해놔야 함)
3)


call by reference
만약 리스트를 하나 정의했다면, my_list = ['a','b','c']
my_list.appnd('n')
my_list = ['a','b','c','n']

4.단순한 계층 구현하기

클래스 작성

class Mullayer:                 # 곱셈계층 클래스
    def __init__(self):         # 공통인터페이스인 '생성자'
        self.x = None
        self.y = None           # 아직 값 없음

    def forward(self, x, y):
        self.x, self.y = x, y

        return x * y

    def backward(self,dout):    # output쪽에서 들어온 미분값을 dout라 한다.
        dx = dout * self.y      # x, y 입력신호를 서로 바꿔 미분값과 곱해준다
        dy = dout * self.x

        return dx, dy

class AddLayer:
    def __init__(self):
        pass                    # 초기화 필요 없으니 pass
    def forward(self, x, y):
        return x + y
    # Mutability 영향 안받게 안전한 개발. 변경되면 안되는 변수라는 의미.
    def backward(self, dout):
        dx = dout * 1           # 덧셈은 미분해도 1 그대로이니까 미분 입력값 그대로 흘려보내라. 그냥 그대로 써도 될걸 굳이 *1로 표기하는 이유는 immutable 위한 call by reference 
        dy = dout * 1 
        return dx, dy           #여기 리턴값 깜빡하고 안써줬더니, Exception has occurred: TypeError cannot unpack non-iterable NoneType object
    
########################  연습  ############################################# 

from layer_naive import AddLayer, Mullayer

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()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)                     # 사과 가격 = 사과 하나 가격 * 사과 개수
orange_price = mul_orange_layer.forward(orange, orange_num)                 # 오렌지 가격 = 오렌지 하나 가격 * 오렌지 개수
all_price = add_apple_orange_layer.forward(apple_price, orange_price)       # 합한 가격 = 사과 가격 + 오렌지 가격
price = mul_tax_layer.forward(all_price,tax)                                # 전체 가격 = 합한 가격 * 소비세

# backward
dprice = 1                                                                  # 기본 미분값 1 (변화 없는 본연의 모습)
dall_price, dtax = mul_tax_layer.backward(dprice)                           # 합한 가격의 미분값, 소비세의 미분값 = 전체가격 미분값으로 역전파처리
dapple_price, dorange_price = add_apple_orange_layer.backward(dall_price)   # 사과 가격의 미분값, 오렌지 가격의 미분값 = 합한가격 미분값으로 역전파처리
dorange, dorange_num = mul_orange_layer.backward(dorange_price)             # 오렌지 하나 가격의 미분값, 오렌지 개수의 미분값 = 오렌지 가격 미분값으로 역전파 처리
dapple, dapple_num = mul_apple_layer.backward(dapple_price)                 # 사과 하나 가격의 미분값, 사과 개수의 미분값 = 사과 가격 미분값으로 역전파 처리

print(dapple_num, dapple, dorange_num, dorange, dapple_price, dorange_price, dall_price, dtax)

5. 활성화 함수 계층 구현하기

1) ReLU 계층

x>0일때, 그대로 반환
x<=0일때  0으로 반환

=> mask라는 인스턴스를 통해 구현해보기 

class Relu:
    def __init__(self):
        self.mask = None        # 마스크는 True False 변환시켜주는 변수다.

    def forward(self, x):       # 앞의 ch05 함수명과 동일하게 만들어줘야한다.
        self.mask = (x <= 0)    # x가 0보다 작거나 같으면 True
        out = x.copy()          # x의 복사본 mutability 방지위해 만들고, out으로 변수.
        out[self.mask] = 0      # self.mask인 경우만 추출해서 0으로 변환. p41
        return out

    def backward(self, dout):
        dx = dout.copy()        # 미분값을 복사해 dx로 변수저장. 원본 안건드릴거임.
        dx[self.mask] = 0       # 미분값이 self.mask(True)인 경우만 추출해서 0으로 변환.
        return dx

################ 마스크가 어떤 기능을 하냐면. #####################
import numpy as np

from layers import Relu

input = np.array([2, -1, 0])
# 경계값 테스트
rl = Relu()
print(rl.forward(input)) # o보다 작거나 같으면 그 위치 0처리하는 mask 적용. 이제부터 1,2번째 자리는 마스크적용될거임.

mask = (input <= 0)
#print(mask)
clone = input.copy()
clone[mask] = 0     #mask 조건 충족하는 것들을 0으로 바꿔라. (인덱스 필터 기능) #이제부터 아무데나 마스크 씌우면 1,2번째 자리는 0으로 변환된다.
#print(clone)
diff = np.array([2.4,-0.5,22])
print(rl.backward(diff))    # 위에서 고정시켜놓은 self.mask 적용시켜라. 1,2번째 자리 mask 적용 되는거 볼 수 있음.



dx = diff.copy()

dx[mask] = 0        #input에서 0으로 변환되었던 [1], [2] 자리 위치를 0으로 바꾸라는 말임!!!!!!!

#print(diff)         #마스크 적용 안된상태
#print(dx)           #마스크 적용 된 상태. 위에서 input 1번자리 2번자리 0으로 변환되었으니 여기서도 1,2번째 자리 변환된다.

2) Sigmoid 계층

y = 1/(1+e^-x)를 미분해라 x = (1+e^-x) 로 치환
y = 1/x 이것을 미분해라. (훨씬 쉬워짐)
-1/x^2 = 미분의 결과.(-y^2과 동일)

6. Affine 계층
class Affine:
    def __init__(self, W, b):
        self.W = W
        self.b = b
        
        self.x = None
        self.original_x_shape = None
        # 가중치와 편향 매개변수의 미분
        self.dW = None
        self.db = None

    def forward(self, x):
        # 텐서 대응
        self.original_x_shape = x.shape
        x = x.reshape(x.shape[0], -1) # x행렬 행 형태로 행 잡고, 열은 데이터 개수에 따라 알아서 따라와라.
        self.x = x

        out = np.dot(self.x, self.W) + self.b

        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T) #transpose해서 곱 가능하게 모양 바꿔준 다음. 미분치와 가중치곱
        self.dW = np.dot(self.x.T, dout) # 바뀐 x와 미분치 곱
        self.db = np.sum(dout, axis=0) # 미분치에 전부 편향 미분치 더해주기.
        
        dx = dx.reshape(*self.original_x_shape)  # 입력 데이터 모양 변경(텐서 대응) 원래 모양으로 바꿔줌
        return 

'2022.01. > 코딩 수업' 카테고리의 다른 글

02.07  (0) 2022.02.08
02.04 옵티마이저  (0) 2022.02.07
01.26 softmax(확률), 추론(배치처리), 손실함수  (0) 2022.01.26
01.25 퍼셉트론, 신경망  (0) 2022.01.25
01.14  (0) 2022.01.16