본문 바로가기

기계학습

인공신경망 Neural Network #2

내용 출처; http://blog.secmem.org/197


지난 포스팅과 이어집니다

링크: http://blog.kim82536.pe.kr/entry/%EC%9D%B8%EA%B3%B5%EC%8B%A0%EA%B2%BD%EB%A7%9D-Neural-Network-1



전체 소스: https://github.com/Kcrong/basic-neural-network/blob/master/sigmoid.py


1. 뉴런의 수학적 해석

지난 번에 구현한 뉴런의 입출력 동작을 수학적으로 해석해보겠습니다.
아래는 그 뉴런을 도식화한 그림입니다.

https://t1.daumcdn.net/cfile/tistory/0211353850AE367C05

편의 상 그림에는 두 개의 입력 밖에 없지만, 입력 갯수는 증가될 수 있습니다.


출력을 살펴보자면, 우선 앞 뉴런에서 입력을 받으면 (0 또는 1) 각 연결 가중치가 곱해지고, 이들의 합이 역치 값 T를 넘으면 출력이 1로, 그렇지 않으면 출력이 0으로 됩니다.


여기서 입력이 2개만 있다고 가정한다면, 출력 조건은 아래와 같습니다.


https://t1.daumcdn.net/cfile/tistory/135AAE4F50AF09E508


이 식을 x2 에 대해 정리하면,


https://t1.daumcdn.net/cfile/tistory/03510F4D50AF0ABE14


직선의 방정식이 나오게 됩니다.

이 직선을 x1와 x2 에 대한 좌표 평면에 그리면



https://t1.daumcdn.net/cfile/tistory/1951034750AF0CA734



위 그림 처럼 그려집니다.

따라서 주황색 직선 보다 위에 있는 순서쌍은 True 가 될 것이고, 아래에 있는 순서쌍은 False 를 출력하게 될 것입니다.

--> (1,1) 은 True 를 반환하게 됩니다.


즉 뉴런의 출력 여부는 가중치 w1, w2 와 역치값 T에 의해서 판가름 난다고 볼 수 있습니다.



2. Perceptron 으로 뉴런 개선하기

Perceptron 이란 뉴럴 네트워크를 이루는 뉴런들의 연결 가중치를 조정하여 목표와 최대한 근접한 해를 찾는 방법과 구조를 뜻합니다.

(결과가 다시 앞 뉴런의 입력으로 사용되는 피드백 구조를 사용하지 않아 Feed-forward Neural Network 라고도 불립니다.


예로 위에서 보인 뉴런 가중치 w1, w2 를 조절해 원하는 해집합을 구하는 것 입니다 :)


우리가 구현한 뉴런을 퍼셉트론으로 개선하기 전에, 약간 부족한 점이 있습니다.


- 역치값이 고정되어 있다.

퍼셉트론은 가중치들을 조정하여 원하는 결과를 얻지만, 역치값이 고정되어 있다면 결과 조정이 힘듭니다.


- 입력과 출력이 단조롭다.

입력값과 출력값이 0 또는 1 밖에 존재하지 않아 출력에 대한 평가 (얼마나 맞고 틀렸는지) 가 힘듭니다.

(입력값이 역치값에 얼마나 가까운지 등)


따라서 위 단점들을 극복하기 위해 뉴런 모델을 조금 수정하도록 하겠습니다.



https://t1.daumcdn.net/cfile/tistory/1219114950AF105204


모두 두 가지가 달라졌습니다.


우선 역치 값이 없어지고, 상수 Input 이 생겼습니다. 역치값이 상수항의 역할을 했지만, 이제 상수 가중치 w3 이 대신합니다.

상수 사중치 w3 은 조절가능한 값이므로 고정 역치값에 대한 문제는 해결되었습니다.


또 출력이 True, False 가 아닌 sigmoid 곡선 함수로 대체되었습니다.

sigmoid 함수는 0~1 값을 갖는 곡선 함수 입니다.

https://t1.daumcdn.net/cfile/tistory/15761F4A50AF15921A

https://t1.daumcdn.net/cfile/tistory/0171494950AF120D02



 이제 이걸 컴퓨터 코드로 작성해 보겠습니다.

def sigmoid(x):
return 1.0 / (1.0 + exp(-x))

시그모이드 연산을 위한 함수를 하나 추가해 줍니다.




def work(self, input_data):
# return sigmoid(sum([self.input[i] * self.input_weight[i] for i in range(len(self.input))]))

output_sum = 0

for idx in range(len(input_data)):
output_sum += self.weight[idx] * input_data[idx]

output_sum += self.weight[-1]

return sigmoid(output_sum)

이제 역치값 비교를 이용한 True, False 을 반환하는 대신 Sigmoid 함수 값을 반환합니다.




3. 퍼셉트론


이제 본격적으로 뉴런을 학습 시켜보겠습니다. --> 퍼셉트론을 만드는 것과 동일합니다.


실제로 사람의 뉴런에서는 축삭으로 신호 전달이 발생하면 그에 해당하는 가지돌기의 시냅스 연결 강도를 더 강하게 해주어 다음 번에는 신호를 더 잘 받아 들이게 된다고 합니다. 지금 구현하는 퍼셉트론도 비슷한 원리로 작동하게 됩니다.


원하는 출력이 나오지 않을 경우, 오차를 줄이기 위해 각 연결 가중치를 변경하는 것이 핵심입니다.


E(오차) = RO(실제 출력) - WO(기대 출력)  이라 할 때, E 가 양수면 출력을 줄이고 음수면 출력을 키워야 합니다.

예를 들어 E가 양수로 나와 출력을 줄이려면 양수를 입력 받은 시냅스의 가중치를 줄이고, 음수를 입력 받은 시냅스의 가중치를 높이면 됩니다.


시냅스 i 의 가중치를 아래와 같이 수정할 수 있습니다.



https://t1.daumcdn.net/cfile/tistory/14488C4D50AF963613



위 식에서 알파는 민감도 상수입니다. 이 값이 작을 수록 적은 양의 수정만 이루어 집니다.

이런 식으로 각 시냅스의 가중치를 조금씩 수정합니다.


여기서 조금 더 나아가자면, 출력 함수가 곡선을 띄기 때문에, 수정 강도도 출력에 따라 달라져야 합니다.

(출력 함수가 직선이면 고려 하지 않아도 되는 부분입니다.)


조금 풀어 말하면, 출력이 급격하게 바뀌는 구간이라면 아주 조금만 수정해도 E가 조정되지만, 완만한 구간에서는 더 많이 수정해야 합니다.

이를 반영하기 위해서는 수정 크기에 출력 위치의 기울기를 곱해주면 됩니다.


다행히 출력 함수로 이용하고 있는 sigmoid 함수의 도함수는

f'(x) = f(x)(1-f(x))

와 같이 편리한 성질을 띄고 있기 떄문에, 출력 값으로 부터 바로 기울기를 구할 수 있습니다.


위 성질을 이용해 식을 약간 수정하면,



https://t1.daumcdn.net/cfile/tistory/1857294950AF96840C


o 는 뉴런의 출력 값으로, 기존 수정량에 출력 기울기를 곱하였습니다.




[+] 위 식은 가중치를 바로 수정하는 것 처럼 작성하였지만, 실제 가중치 조절은 학습과 동시에 가중치를 수정하는 것 보다, 한 세트의 학습이 끝났을 때 수정하는 것이 더 효과적입니다.



이제 코드로 옮기는 일만 남았습니다.



class Neuron:
def __init__(self):
self.alpha = 0.1 # 민감도 상수
self.weight_error = [0 for _ in range(INPUT_CNT+1)] # 가중치 오류를 저장할 리스트
self.weight = [uniform(-1, 1) for _ in range(INPUT_CNT+1)] # 가중치 리스트


민감도 상수와 에러 누적 리스트가 추가되었습니다.

( alpha, error )


그리고 정답 데이터를 입력받아 학습하는 learn 함수와 누적된 에러를 이용해 가중치를 수정하는 fix 함수를 추가해보겠습니다.


def learn(self, input_data, answer):
output = self.work(input_data) # 결과값 저장
output_error = output - answer # 결과값에 대한 오류값 저장

for idx in range(len(input_data)):
self.weight_error[idx] += output_error * input_data[idx] * output * (1 - output)
# W_i <- W_i = Alpha ( E * X_i * O(1-O) )

self.weight_error[-1] += output_error * 1.0 * output * (1 - output) # For Alpha

위에서 구한 식 (가중치 수정) 을 이용해 weight_error 에 오류값을 저장합니다.




그리고 저장된 오류 값을 바탕으로


def fix(self):
for idx in range(INPUT_CNT+1):
self.weight[idx] -= self.alpha * self.weight_error[idx] # 민감도 상수를 이용해 가중치 교정
self.weight_error[idx] = 0 # 오류 저장 리스트 초기화

가중치를 교정하고, 오류 저장 리스트는 0으로 초기화



이제 뉴런 1개로 동작하는 퍼셉트론이 완성되었습니다. 이제 이 뉴런에게 AND 연산을 학습 시켜 보겠습니다.


if __name__ == '__main__':
all_input = [
[0, 0],
[0, 1],
[1, 0],
[1, 1]
]

correct_input = [0, 0, 0, 1]

neuron = Neuron()

for i in range(40000):
for j in range(4):
neuron.learn(all_input[j], correct_input[j])
neuron.fix()

if (i + 1) % 1000 == 0:
print("-----------Learn %d times-----------" % (i+1))
for j in range(4):
print("%d %d : %f" % (all_input[j][0], all_input[j][1], neuron.work(all_input[j])))


총 30000번의 학습을 진행하면서 1000번째 마다 교정된 가중치를 이용해 결과 값을 확인합니다.


실행 결과는 아래와 같습니다.


-----------Learn 0 times-----------
0 0 : 0.409563
0 1 : 0.495010
1 0 : 0.352537
1 1 : 0.434849
-----------Learn 2000 times-----------
0 0 : 0.004692
0 1 : 0.134906
1 0 : 0.134899
1 1 : 0.837623
-----------Learn 4000 times-----------
0 0 : 0.001282
0 1 : 0.092323
1 0 : 0.092323
1 1 : 0.889603
-----------Learn 6000 times-----------
0 0 : 0.000610
0 1 : 0.073723
1 0 : 0.073723
1 1 : 0.912060
-----------Learn 8000 times-----------
0 0 : 0.000365
0 1 : 0.062885
1 0 : 0.062885
1 1 : 0.925089
-----------Learn 10000 times-----------
0 0 : 0.000246
0 1 : 0.055626
1 0 : 0.055626
1 1 : 0.933793
-----------Learn 12000 times-----------
0 0 : 0.000179
0 1 : 0.050350
1 0 : 0.050350
1 1 : 0.940110
-----------Learn 14000 times-----------
0 0 : 0.000137
0 1 : 0.046300
1 0 : 0.046300
1 1 : 0.944953
-----------Learn 16000 times-----------
0 0 : 0.000109
0 1 : 0.043069
1 0 : 0.043069
1 1 : 0.948813
-----------Learn 18000 times-----------
0 0 : 0.000089
0 1 : 0.040416
1 0 : 0.040416
1 1 : 0.951980
-----------Learn 20000 times-----------
0 0 : 0.000075
0 1 : 0.038189
1 0 : 0.038189
1 1 : 0.954637
-----------Learn 22000 times-----------
0 0 : 0.000064
0 1 : 0.036285
1 0 : 0.036285
1 1 : 0.956907
-----------Learn 24000 times-----------
0 0 : 0.000055
0 1 : 0.034635
1 0 : 0.034635
1 1 : 0.958875
-----------Learn 26000 times-----------
0 0 : 0.000048
0 1 : 0.033186
1 0 : 0.033186
1 1 : 0.960601
-----------Learn 28000 times-----------
0
0 : 0.000043

0 1 : 0.031902
1 0 : 0.031902
1 1 : 0.962131
-----------Learn 30000 times-----------
0 0 : 0.000038
0 1 : 0.030753
1 0 : 0.030753
1 1 : 0.963499


AND 연산

0 0 : 0

0 1 : 0

1 0 : 0

1 1 : 1


처음에는 전혀 상관 없는 값이 나오다가

점점 AND 연산과 비슷한 결과 값이 나오는 것을 볼 수 있습니다.



4. 끝으로


 이번 게시물을 통해 한 개의 뉴런으로도 간단하게나마 퍼셉트론을 구현해 볼 수 있었습니다. 그러나 하나의 뉴런 만으로는 아직 제약이 많습니다 (XOR 등) 하지만 이와 같은 뉴런들이 여러 개가 모이면 그 가능성은 매우 크고 넓습니다.


따라서 여러 개의 뉴런으로 퍼셉트론을 만드는 것이 일반적이며, 이를 멀티레이어 퍼셉트론 (Multi-Layer Perceptron) 이라 합니다.


감사합니다.