[딥러닝] 오차역전파법

Chris Kim·2024년 11월 20일

딥러닝

목록 보기
4/5

0. 서론

수치 미분을 통한 기울기 산출은 단순하지만, 계산 시간이 오래 걸린다. 하지만 오차역전파법(backpropagation) 을 통해 기울기를 효율적으로 계산할 수 있다.

1. 연쇄법칙

오차역전파법을 이해하기 위해서는 연쇄법칙을 이해해야한다. 먼저 합성함수의 미분에 대한 중요한 성질 하나를 짚고 넘어가도록 하자.

합성함수의 미분은 합성 함수를 구성하는 각 함수의 미분의 곱으로 나타낼 수 있다.

[예시1]

z=t2t=x+yz = t^2\\ t = x+y
zx=zttx\frac{\partial z}{\partial x} = \frac{\partial z}{\partial t}\frac{\partial t}{\partial x}

2. 오차역전파

2.1 덧셈 노드의 역전파

t=x+yt = x+y 를 대상으로 그 역전파를 살펴보도록 하자. 해당 식의 미분은 tx=1\frac{\partial t}{\partial x} =1ty=1\frac{\partial t}{\partial y}=1 과 같이 해석적으로 계산될 수 있다. 이는 상류에서 전해진 미분에 1을 곱하여 그대로 하류로 내보낸다는 뜻이다.

2.2 곱셈 노드의 역전파

z=xyz=xy의 미분은 다음과 같다.

zx=y\frac{\partial z}{\partial x} = y
zy=x\frac{\partial z}{\partial y} = x

즉 곱셈 노드의 역전파는 상류의 값에 순전파 입력 신호를 서로 바꾼 값을 곱해서 하류로 보낸다.

3. 구현

3.1 곱셈 계층

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

3.2 덧셈 계층

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

4. 활성화 함수 계층 구현

4.1 ReLU 계층

ReLU는 수식으로 다음과 같이 살펴볼 수 있다.

y={x(x>0)0(x0)y = \begin{cases} x\,(x>0)\\ 0\,(x\leq 0) \end{cases}
yx={1(x>0)0(x0)\frac{\partial y}{\partial x} = \begin{cases} 1\,(x>0)\\ 0\,(x\leq 0) \end{cases}

파이썬으로 구현한 예시는 다음과 같다.

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

4.2 sigmoid 계층

sigmoid 수식은 다음과 같다.

y=11+exp(x)y = \frac{1}{1+exp(-x)}

이를 미분해보도록 하자

y=1tyt=1t2=y2y = \frac{1}{t}\\ \frac{\partial y}{\partial t} = -\frac{1}{t^2} = -y^2

1+exp(x)1+exp(x)는 덧셈이므로 바로exp(x)exp(x)를 미분해보자

t=exp(x)tx=exp(x)t =exp(x)\\ \frac{\partial t}{\partial x} = exp(x)

그리고 xx에 -1을 곱하므로 역전파를 전할 때 -1을 곱하면 된다.

상류에서 온 값을 Ly\frac{\partial L}{\partial y}라 할 때 sigmoid 계층의 역전파는 다음과 같이 정리가 가능하다.

Ly(y2)exp(x)(1)=Lyy2exp(x)=Ly1(1+exp(x))2exp(x)\frac{\partial L}{\partial y} *(-y^2)*exp(-x)*(-1) =\frac{\partial L}{\partial y}y^2exp(-x) = \frac{\partial L}{\partial y}\frac{1}{(1+exp(-x))^2}exp(-x)
=Ly1(1+exp(x))exp(x)1+exp(x)=Lyy(1y)=\frac{\partial L}{\partial y}\frac{1}{(1+exp(-x))}\frac{exp(-x)}{1+exp(-x)} = \frac{\partial L}{\partial y}y(1-y)

파이썬으로는 다음과 같이 구현 가능하다.

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 - self.out) * self.out
       
       return dx
   

5. Affine/Softmax 계층 구현

5.1 Affine 계층

Y=XWY = XW 라 할 때, 역전파 수식은 다음과 같이 나타낼 수 있다.

LX=LYWT\frac{\partial L}{\partial X} = \frac{\partial L}{\partial Y}W^T
LW=XTLY\frac{\partial L}{\partial W} = X^T\frac{\partial L}{\partial Y}

LW\frac{\partial L}{\partial W}WW, 그리고 LX\frac{\partial L}{\partial X}XX의 형상은 서로 같음에 주의해야 한다.

편향 덧셈은 XWXW에 대한 편향이 각 데이터에 더해진다. 그러므로 각 데이터의 역전파 값은 편향의 원소에 모여야한다.

파이썬 구현은 다음과 같다.

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.2 Softmax-with-Loss 계층

여기에서는 소프트맥스 계층 구현시, 손실 함수인 교차 엔트로피 오차도 포함하여 구현한다. 역전파를 구하는 과정은 매우 복잡하여 여기서는 생략하고, 추후에 다뤄보도록 하겠다. (y1,y2,y3,...,yn)(y_1,y_2,y_3,...,y_n)에 대한 역전파는 (y1t1,y2t2,y3t3,...,yntn)(y_1-t_1, y_2-t_2, y_3-t_3, ..., y_n-t_n)으로 비교적 깔끔하게 결과가 나온다.
이렇게 깔끔한 결과가 나오는 것은 설계부터가 이를 목적으로 했기 때문이다. 항등 함수의 손실 함수로 오차제곱합을 이용하는 이유도 이와 같다. 이 때에도 역전파의 결과가 같다.

Softmax-with-Loss 계층을 구현한 코드는 다음과 같다.

class SoftmaxWithLoss:
	def __init__(self):
   	self.loss = None
       self.y = None
       self.t = None
   
   def forward(self, x, t):
   	self.t = t
       self.y = softmax(x)
       self.loss = cross_entropy_error(self.y, t)
       return self.loss
   
   def backward(self, dout = 1)
   	batch_size = self.t.shape[0]
       dx = (self.y - self.t)/batch_size

6. 오차역전파법 구현

상세한 구현 코드는 여기를 참고할 것.
핵심은 TwoLayerNet 클래스 정의시, layers 인스턴스를 OrderedDict, 순서가 있는 딕셔너리로 구현한다는 것이다. 그리고 역전파를 구할 때, 이 인스턴스를 reverse()하여 backward(dout) 메서드를 반복적으로 수행한다.
이렇게 구한 기울기는 반드시 수치 미분으로 구현한 기울기와 일치하는지 검증해야한다.

profile
회계+IT=???

0개의 댓글