본문 바로가기

AI tech

밑바닥부터 시작하는 딥러닝 정리 Chapter 5 - 오차역전파법(역전파)

반응형

Chapter 5 - 오차역전파법(역전파)

 

 

개요

  • 수치 미분은 구현이 간단하지만, 계산이 오래 걸림
  • 가중치 매개변수의 기울기를 효율적으로 구하는 오차역전파법에 대해 설명
  • 계산 그래프를 통한 이해

 

 

5.1 계산 그래프

  • 계산 과정을 그래프로 / 노드와 엣지로 표현

 

익숙해지기

    • 계산 과정을 노드와 화살표로 표현
    • 계산 결과를 화살표 위에 표시

    1. 슈퍼에서 100원짜리 사과 2개를 삼 + 소비세 10% 부가
    2. X2, X1.1 처럼 표현할 수도 있지만, 곱하기 자체만을 하나의 노드에 표현할 수도 있음

 


    • 결론 : 계산 그래프의 문제 풀이
      1. 계산 그래프 구성
      2. 왼쪽 → 오른쪽으로 계산 진행

 

  • 계산 그래프의 출발점 → 종착점으로의 계산이 순전파
  • 이와 반대 방향으로 계산하면 역전파가 되는 것 → 미분 계산 시 중요!

 

특징

  • 자신과 관계된 정보만으로 계산 출력 가능한 국소적 계산
  • 각 노드에서의 계산은 국소적 계산
  • 아무리 복잡해도 각 노드에서는 단순한 계산을 통해 문제 단순화 가능!

 

왜 계산 그래프로 푸는가?

  • 국소적 계산 + 또 다른 이점 : 중간 계산 결과 보관 가능
  • 결정적으로 : 역전파를 통한 효율적인 ‘미분’ 계산 가능

 

 

어캐하노

    • 사과 가격이 오를 때 최종 금액에 어떤 영향을 미치는 지를 알고 싶다면~?
    • 이 문제는 사과 가격에 대한 지불 금액의 미분 구하기 로 바뀜
    • 이 미분값 : 사과값이 ‘아주 조금’ 올랐을 때, 지불 금액이 얼마나 증가하느냐 를 나타냄
    • 사과값 뿐 아니라 소비세, 사과 개수에 대한 지불 금액의 미분도 가능

→ 중간까지 구한 미분 결과를 공유할 수 있어서 효율적인 계산 가능

결론 : 순전파/역전파를 통해 각 변수의 효율적인 미분 계산 가능~

 

 

 

연쇄법칙

역전파 + 연쇄법칙

  • 하려고 하는 것 : 각 변수의 효율적인 미분을 계산하자!

역전파

  • ‘국소적 미분’(=편미분) ? → 오른쪽 수식
  • 순전파 때의 y=f(x) 계산의 미분을 의미

 

∂y∂x\frac{\partial y}{\partial x}
image
  • 역전파 절차 : 신호 E에 노드의 국소적 미분을 곱한 후 담 노드로 전달
  • 결론 : 이렇게 하면 연쇄법칙의 원리로 목표 미분 값을 쉽게 구한다~

 

그럼 연쇄법칙은 뭔가요

  • 정의 : 합성 함수의 미분 = 그 구성 함수의 각 함수의 미분 곱으로 나타냄
  • 예시
z=(x+y)2z = (x+y)^2
z=t2z = t^2
t=x+yt=x+y
    • z는 t로 구성된 함수, t는 x와 y로 구성된 합수

→ z는? (t), (x,y)로 구성된 합성 함수

→ 그러면 z의 미분은? (t에 대한 함수 미분) * (x, y에 대한 함수 미분)

 

  • 핵심
∂z∂x=∂z∂t∂t∂x\frac{\partial z}{\partial x} = \frac{\partial z}{\partial t}\frac{\partial t}{\partial x}

 

정리 한 번

  • 역전파 → 입력신호에 각 편미분을 곱한 후 다음 노드로 전달
  • 근데? 편미분을 모두 곱하면 처음 함수의 미분이 된다~ → 연쇄법칙
  • 이렇게 우리는 가중치 매개변수의 기울기를 효율적으로 구할 수 있다!
  • 이걸로 손실함수 기울기 구해서, 경사하강법으로 함수값을 최소화 시키도록 학습하면 모델 성능 향상~!

 

예시

image
image
  • 덧셈 노드: 그대로 흘러감
  • 곱셈 노드 : 이전의 입력을 곱해주되, 위치를 바꿔서 곱( 그래서 곱셈은 입력 정보 저장 필요함!)

 

  • 사과 쇼핑 예시
image
  • 결론 : 소비세와 사과 가격이 같은 양만큼 오르면, 최종 금액에는 소비세가 200의 크기로, 사과 가격이 2.2 크기로 영향을 준다!

 

 

 

구현

노드

    • 곱셈 노드
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
 
    • 덧셈 노드
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
 
    • 예제 해보자
image
# 변수 설정
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)
all_price = add_apple_orange_layer.forward(apple_price, orange_price)
price = mul_tax_layer.forward(all_price, tax)

# 역전파
dprice = 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(price) # 715
print(dapple_num, dapple, dorange, dorange_num, dtax)
# 110 2.2 3.3 165 650
 

 

새로운 노드 - 시그모이드에 활용

    • ‘/’ 노드 → y= 1/x 미분하면
∂y∂x=−1/x2=−y2\frac{\partial y}{\partial x} = -1/x^2 = -y^2

결론 : 흘러들어온 값에 -y제곱을 곱해서 전달(순전파의 출력을 사용)

 

    • ‘exp’ 노드 → y = exp(x) 미분하면
∂y∂x=exp(x)\frac{\partial y}{\partial x} = exp(x)

결론 : 흘러들어온 값에 순전파의 출력 그대로 곱해서 전달(시그모이드에서는 exp(-x)임)

 

활성화 함수 계층

    • ReLU
image
class Relu:
	def __init__(self):
		self_mask = None
			
	def forward(self, x):
		self.mask = (x <= 0) # mask : True/False
		out = x.copy()
		out[self.mask] = 0
		
		return out
	
	def backward(self, dout):
		dout[self.mask] = 0
		dx = dout
		
		return dx
 

 

  • Sigmoid
    • ‘exp’, ‘/’ 노드 등장
    • 한번 봅시다
image

 

image
image

→ 간소화 가능 (y^2exp(-x) = y(1-y) 이므로 변환)

image
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
 

특징 : Sigmoid 의 역전파는 순전파의 출력(y)만으로 계산 가능

 

    • Affine/Softmax
      • 어파인 변환 : 신경망의 순전파 때 수행하는 행렬 곱
image
      • X, W, B가 행렬(다차원 배열)이라는 점에 주의! → 여기선 스칼라가 아닌 행렬이 흐름

→ 이건 뭘 뜻하냐 : 차원의 원소 수를 일치시켜야 함

    • 역전파 시 미분 값 → 곱과 비슷하게 바꿔서 보내주되, W^T를 앞뒤로 곱해줌
∂L∂X=∂L∂Y.WT\frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y} . W^T
∂L∂W=WT.∂L∂Y\frac{\partial L}{\partial W} = W^T.\frac{\partial L}{\partial Y}
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, xelf.W) + self.b
		
		return out
		
	def backward(self, dout):
		dx = np.dot(dout, self.W.T)
		self.db = np.sum(dout, axix=0)
		
		return dx
 

 

 

    • Softmax-with-Loss
image
    1. 3클래스 분류 예시 / Softmax, Cross Entropy Error 계층 간소화
    2. 역전파 결과에 주목 : y는 출력, t는 정답 레이블 (역전파가 이렇게 나오도록 CEE가 설계되어 있음)
    3. 오차에 따라 학습하는 정도가 달라지도록 한다!
    4. 그래서 결론? → 신경망의 역전파에서는 이 오차가 앞 계층에 전해진다~
class SoftmaxWithloss:
	def __init__(self):
		self.loss = None # 손실
		self.y = None # softmax의 출력
		self.t = None # 정답 레이블(원-핫 벡터)
		
	def forward(self, x, y):
		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
 

 

 

오차역전파법 구현하기

    • 앞에서 구현한 계층 조합 → 레고 조립하듯 신경망 구축
image

 

  • 오차역전파법은 2단계 - 기울기 산출에서 사용!

 

신경망 구현

  • 코드 확인

 

기울기 확인

  • 수치 미분을 써서 구한 것과 비교함! (오차역전파법 결과 검증)
  • 오차가 0이 되는 일은 드물지만, 올바르게 구현했다면 0에 아주 가까운 값이 됨

 

학습 구현하기

  • 코드 확인

 

 

정리

  • 계산 그래프에 대해
  • 신경망 동작 + 오차역전파법 설명 / 계층 단위 구현
  • 모든 계층에서 forward, backward 메서드 구현
반응형