Jin Dev
Jin Dev
Jin Dev
전체 방문자
오늘
어제
  • 분류 전체보기 (7)
    • 공부 (4)
      • Python (1)
      • C++ (1)
      • XE (1)
      • Git (1)
    • 팁 (3)

블로그 메뉴

  • 홈
  • About me

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jin Dev

Jin Dev

Python으로 구현한 인공지능 - Backpropagation
공부/Python

Python으로 구현한 인공지능 - Backpropagation

2019. 2. 11. 19:03
728x90
반응형

전체 소스 코드는 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은 다음 단계를 거친다.

  1. 입력 x와 가중치 v를 np.dot을 이용하여 행렬의 곱을 구함
  2. 1에서 구한 값이 활성 함수를 거침
  3. 2에서 구한 값 h_out과 가중치 w를 np.dot을 이용하여 행렬의 곱을 구함
  4. 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

[2] http://llnntms.tistory.com/31

[3] https://en.wikipedia.org/wiki/Perceptron

728x90
반응형
    Jin Dev
    Jin Dev

    티스토리툴바