다양한 계층 구현을 통한 오차역전파법 구현하기(2)

 저번에 포스팅을 이어서 하도록 하겠다. 이번 포스팅할 코드는 오차 역전파법을 이용해 2층 신경망 클래스의 구현이다. 다만 실제 학습하는 것은 생략하고 각각의 구현에 대해서 설명하도록 하겠다. 일단 먼저 구현하는 코드를 보기 전에 이 코드에 들어가는 함수들에 대해 설명하겠다.

1. sigmoid: 시그모이드 함수를 구현한 것이다.

2. softmax: 소프트맥스 함수를 구현한 것이다.

3. cross_entropy_error: 교차앤트로피 오차를 구현한 것이다.

4. numerical_gradient: 가중치 매개변수의 기울기를 구한다. 

그럼 코드를 보도록 하겠다.

import sys, os
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient

class TwoLayer:
#초깃값 초기화
def __init__(self, inputs, hiddens, outputs ,w=0.01):
#딕셔너리 선언
self.params = {}
#KEY값으로 값 설정
        self.params['W1'] = w * np.random.randn(inputs,hiddens)
self.params['b1'] = np.zeros(hiddens)
self.params['W2'] = w * np.random.randn(hiddens,outputs)
self.params['b2'] = np.zeros(outputs)

#예측 진행-(foward)
def pre(self,x):
w1
, w2 = self.params['W1'],self.params['W2']
b1
, b2 = self.params['b1'],self.params['b2']
#1층 연산 진행
        a1 = np.dot(x, w1) + b1
z1 = sigmoid(a1)
#2층 연산 진행
a2 = np.dot(z1,w2) + b2
#소프트맥스 함수로 출력
y = softmax(a2)

return y

def loss(self,x ,t):
#결과값 계산
y = self.pre(x)
#교차 엔트로피 오차를 이용한 Loss값 계산
return cross_entropy_error(y,t)

#정확도(Loss가 아니다!)
def correct(self,x, z):
#z:정답레이블
y= self.pre(x)
# y,z축 기준으로
y=np.argmax(y,axis=1)
z= np.argmax(z
,axis=1)

#x행렬의 행의 갯수
accuary = np.sum(y==z)/float(x.shape[0])
return accuary

def num_grad(self,x,t):
lossw =
lambda W:self.loss(x,t)

grad = {}
grad[
'W1'] = numerical_gradient(lossw,self.params['W1'])
grad[
'b1'] = numerical_gradient(lossw, self.params['b1'])
grad[
'W2'] = numerical_gradient(lossw, self.params['W2'])
grad[
'b2'] = numerical_gradient(lossw, self.params['b2'])

return grad

코드는 다음과 같다. 가장 먼저 설명하면 처음부분은 common이라는 폴더에 있는 함수들을 모두 가져온다는 뜻이다. 여기서는 아까 위에서 설명한 코드를 가져온다. 이제 class를 보자. 

제일 먼저 빈 딕셔너리를 params라는 이름으로 생성한다. 그리고 그 딕셔너리에 w1,w2는 key값으로 각각 입력 받은 입력층, 출력층, 은닉층의 행렬을 무작위로 생성한다. b1,b2는 0으로 가중치를 초기화 시킨다. 

그리고 pre는 예측인데 forward와 비슷한 역할을 한다고 생각하면 된다. 도트곱을 통한 계산을 한다. 1층에서는 시그모이드함수(활성함수)를 이용하고 2층에서는 소프트맥스함수(출력함수)를 사용한다.


loss값 계산은 다음과 같다. 기존에 구현했던 교차엔트로피 구현법을 사용해 위에서 구한 y의 값과 t(정답값)을 이용해 오차를 구한다.

정확도 함수는 정확도를 구한다 정확도는 어느 정도 맞았는지의 값으로 100개의 정답중 78개를 맞았으면 정확도는 78%라고 나올 수 있다. 이때 사용되는 argmax함수는 가장 큰 값을 출력하는데 현재 여기에서는 x,y,z 3가지의 축을 이용한 3차원 배열에서 판단한다. 

마지막으로 num_grad에서는 각 매개변수의 기울기를 기존에 이야기했던 오차 역전파법을 이용해 구한다.

따로 미니배치를 통한 학습을 구현하지는 않았지만 여기에 미니배치 알고리즘과 기울기 계산, 매개변수 갱신의 프로세스만 넣어주면 학습 가능 모델이 완성된다.

그럼 이제 이전 포스트에서 사용된 함수들과 TwoLayer를 이용해 오차역전파법을 적용한 신경망을 구성해 보겠다. 

import sys, os
sys.path.append(os.pardir)
from common.functions import *
from collections import OrderedDict
class TwoLayer:
#초깃값 초기화
def __init__(self, inputs, hiddens, outputs ,w=0.01):
#딕셔너리 선언
self.params = {}
#KEY값으로 값 설정
self.params['W1'] = w * np.random.randn(inputs,hiddens)
self.params['b1'] = np.zeros(hiddens)
self.params['W2'] = w * np.random.randn(hiddens,outputs)
self.params['b2'] = np.zeros(outputs)

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

#예측 진행-(foward)
def pre(self,x):
for layer in self.layers.values():
x = layer.forward(x)
return x

def loss(self,x ,t):
#결과값 계산
y = self.pre(x)
#교차 엔트로피 오차를 이용한 Loss값 계산
return self.lastLayer.forward(y,t)

#정확도(Loss가 아니다!)
def correct(self,x, z):
#z:정답레이블
y= self.pre(x)
# y,z축 기준으로
y=np.argmax(y,axis=1)
if(z.ndim != 1):
z = np.argmax(z,axis=1)

#x행렬의 행의 갯수
accuary = np.sum(y==z)/float(x.shape[0])
return accuary

def num_grad(self,x,t):
self.loss(x,t)
d = 1
d =self.lastLayer.backward(d)

layers = list(self.layers.values())
layers.reverse()
for layer in layers:
d = layer.backword(d)

grad = {}
grad['W1'] = self.layers['Affine1'].dW
grad['b1'] = self.layers['Affine1'].db
grad['W2'] = self.layers['Affine2'].dW
grad['b2'] = self.layers['Affine2'].db

return grad

크게 달라지지는 않았다만 몇 가지 설명해야 할 것들이 있다. 가장 먼저 collection 라이브러리의 OrderedDict인데 이는 순서가 있는 딕셔너리라는 것이다. 이는 역전파시 뒤집는 프로세스를 사용하기 위해 사용된 것이다. 또 달라진점은 이전 포스트의 Affine함수를 이용해 도트곱으로 표현했다는 점,소프트맥스 함수가 아닌 ReLU함수가 활성함수도 들어갔다는 점, pre함수에서 현재 계층의 forward함수를 불러 순전파를 시킨다는 것이다. 이는 뒤쪽의 역전파에도 동일하게 적용된다.  우리는 이런 방법을 통해 오차역전파법을 이용한 신경 계층을 구성할 수 있었다.


참고 코드들  

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
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))
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
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 ReLU():
def __init__(self):
memo = None

def relufo(self, x):
out = x.copy()
self.memo = (x >= 0)
return out

def reluback(self, d):
d[self.memo] = 0
return d


class Affine():
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None

def AFfor(self, x):
self.x = x
out = np.dot(x, self.W) + self.b
return out

def AFback(self, d):
d = np.dot(d, self.W.T)
self.dW = np.dot(self.x.T, d)
self.db = np.sum(d, axis=0)

return d

댓글

이 블로그의 인기 게시물

퍼셉트론(Perceptron)

[논문리뷰] 3. CNN에 대하여