전체 소스 코드는 github에서 확인할 수 있습니다.
Perceptron
Perceptron은 입력과 출력을 지정하고 학습하는 지도 학습 방식을 사용하는 인공신경망의 한 종류로, 입력 x에 대해 가중치 w를 곱한 값들을 전부 합한 뒤 활성함수 f에 의해 판단한다. 활성함수는 threshold에 의해 그 값을 결정해준다. 하지만 이 방식으로는 XOR 문제를 풀 수 없는 등의 단점이 있다.
Multi-layer perceptron
Perceptron의 단점을 보완한 방식인 multi-layer perceptron은 input layer와 output layer 사이에 하나 이상의 hidden layer를 배치한 계층 구조의 신경망이다. 본 문서에서 구현한 코드는 1개의 hidden layer를 이용한 방식을 사용하였다.
Backpropagation
입력 x에 대하여 실제 출력 값이 y, 신경망에 의해 예측된 값이 o라고 한다면 신경망의 Error 값은 아래와 같다.
Multi-layer perceptron에서 Error를 최소한으로 만드는 가중치 값들을 찾기 위해 신경망을 학습하여, 더 좋은 결과를 도출하게 해주는 방식이 Backpropagation이다.
데이터
한 데이터는 10개의 input, 4개의 output으로 총 14개의 정수형 값이 쉼표로 분리되어 있다. input data는 반드시 1개의 1과 9개의 0으로 이루어져 있고, output data는 1의 위치(1~10)를 4자리의 2진수로 표현되어 있다.
Input | 1의 위치 | Output |
---|---|---|
1,0,0,0,0,0,0,0,0,0 | 1 | 0,0,0,1 |
0,0,0,0,0,1,0,0,0,0 | 6 | 0,1,1,0 |
0,0,0,0,0,0,0,0,1,0 | 9 | 1,0,0,1 |
make_dataset.py
으로 데이터를 생성할 수 있다.
코드
class NN():
def __init__(self, input_size, output_size, hidden_size):
self.input_size = input_size
self.output_size = output_size
self.hidden_size = hidden_size
self.v = np.random.randn(input_size, hidden_size)
self.w = np.random.randn(hidden_size, output_size)
input_size
, output_size
, hidden_size
는 각각 input, output, hidden layer의 개수를 설정한다.
v
는 input layer와 hidden layer사이의 weight를, w
는 hidden layer와 output layer 사이의 weight를 담은 numpy 행렬이다.
# 활성 함수
def activation(self, x):
# 시그모이드 함수를 활성 함수로 사용
return 1 / (1 + np.exp(-x))
# 활성 함수의 미분
def d_activation(self, x):
# 시그모이드 함수의 미분
return x * (1 - x)
활성 함수로 시그모이드 함수를 사용한다. 시그모이드 함수는 아래와 같다.
backpropagation에서 쓰일 활성 함수의 미분 함수도 설정한다.
# Forward propagation
def forward(self, x):
self.h_in = np.dot(x, self.v)
self.h_out = self.activation(self.h_in)
self.o_in = np.dot(self.h_out, self.w)
self.o_out = self.activation(self.o_in)
return self.o_out
forward propagation은 다음 단계를 거친다.
- 입력
x
와 가중치v
를np.dot
을 이용하여 행렬의 곱을 구함 - 1에서 구한 값이 활성 함수를 거침
- 2에서 구한 값
h_out
과 가중치w
를np.dot
을 이용하여 행렬의 곱을 구함 - 3에서 구한 값이 활섬 함수를 거쳐 반환됨
이 과정을 통해 x
에 대한 예측 값인 o_out
을 구한다.
# Backward propagation
def backward(self, x, y, o):
o_error = y - o
o_delta = o_error * self.d_activation(o)
h_error = np.dot(o_delta, self.w.T)
h_delta = h_error * self.d_activation(self.h_out)
self.v += np.dot(x.T, h_delta)
self.w += np.dot(self.h_out.T, o_delta)
Backpropagation을 통해 가중치 v
, w
를 학습한다. 이 부분은 본문 하단 References의 [1], [2]에 잘 설명되어있다.
# x, y, o 데이터로 학습
def train(self, x, y, o=None):
if o is None: o = self.predict(x)
self.backward(x, y, o)
입력 값 x
와 실제 출력 값 y
, 신경망에 의해 예측된 값 o
를 통해 backpropagation으로 학습한다. o
가 인자로 넘어오지 않았을 경우 o
값을 직접 구한다.
# x 데이터로 y 값 예측
def predict(self, x):
return self.forward(x)
신경망을 통해 x
값으로 출력 값을 예측한다.
# 이진화 (threshold 값 이상 : 1, 미만 : 0 로 변환)
def binarize(x, threshold=0.5):
return (x >= threshold).astype(float)
threshold
값 기준으로 0과 1로 이진화하는 함수이다. 여기서 data는 0과 1만 사용하므로 threshold
의 기본 값을 0.5로 두었다.
# y와 y_pred의 정확도(일치도) 계산
def accuracy(y, y_pred):
return 100 * (y == y_pred).sum() / (y.shape[0] * y.shape[1])
y
와 y_pred
가 얼마나 일치하는지 백분율로 표시해준다.
# 손실 함수 계산
def loss(y, y_pred):
return np.mean(np.square(y - y_pred))
y
와 y_pred
사이의 손실 함수를 게산해준다.
# input, output, hidden 레이어의 개수
input_size, output_size, hidden_size = 10, 4, 15
# epochs(학습 반복 양)
epochs = 1000
# NN 초기화
NN = NN(input_size, output_size, hidden_size)
input_size
, output_size
, hidden_size
는 각각 input, output, hidden layer의 개수이다. 여기서는 10개의 input과 4개의 output이 있으므로 위와 같이 설정하였다. hidden_size
는 적절하게 설정하면 된다. epochs
는 학습 반복 횟수이다. 설정이 완료되면 NN
클래스의 생성자를 이용하여 인공신경망을 생성한다.
# 학습 데이터 설정
train_file = 'train_data.txt'
train_data = np.loadtxt(train_file, delimiter=',')
x_train, y_train = train_data[:, :-output_size], train_data[:, -output_size:]
# 검증 데이터 설정
test_file = 'test_data.txt'
test_data = np.loadtxt(test_file, delimiter=',')
x_test, y_test = test_data[:, :-output_size], test_data[:, -output_size:]
학습 데이터와 검증 데이터를 불러온다. x는 데이터 하나당 0
-output_size
미만, y는 -output_size
끝까지 전체 데이터를 불러온다.
# 학습
for epoch in range(epochs):
y_pred = NN.predict(x_train)
y_pred_bin = binarize(y_pred)
print("Loss : {:.10f} ({}/{})".format(loss(y_train, y_pred), epoch, epochs))
NN.train(x_train, y_train, y_pred)
y_pred
에는 x_train
데이터로 예측한 값, y_pred_bin
은 y_pred
의 이진화된 값이다. 이 때, 실제 y 값과 예측된 y 값을 이용하여 손실함수를 출력한다. 마지막에는 x_train
, y_train
, y_pred
값으로 error를 최소화 하는 신경망을 찾기 위해 학습한다.
# 검증
y_test_pred = NN.predict(x_test)
y_test_pred_bin = binarize(y_test_pred)
print("\n- 입력 값\n", x_test)
print("\n- 실제 결과\n", y_test)
print("\n- 예측 결과\n", y_test_pred)
print("\n- 이진화된 예측 결과\n", y_test_pred_bin)
print("\n- 정확도 : ", accuracy(y_test, y_test_pred_bin))
학습이 완료되었으니 검증을 한다.
결론
실행할 때마다 정확도가 달라지지만, 대부분 매우 높게 나오는 편이다. 학습 데이터는 50개 이하가 적당한 것 같다. 학습 데이터가 많아지면 정확도가 낮아진다. 아마 overfitting에 의한 문제일 것이라 추측해본다.
References
[1] https://dev.to/shamdasani/build-a-flexible-neural-network-with-backpropagation-in-python