머신러닝&딥러닝/Do it ! 딥러닝 입문

로지스틱 회귀 뉴런으로 단일층 신경망 만들기

욱이명 2020. 12. 2. 21:33

사실 이전 글에서 이미 단일층 신경망을 구현했다. 로지스틱 회귀는 단일층 신경망(single layer neural network)과 동일하기 때문이다. 하지만 지금까지는 층(layer)이라는 개념을 전혀 사용하지 않았는데 이 글에서는 신경망과 관련된 개념을 정리한다.

 

일반적인 신경망의 모습

일반적으로 신경망은 다음과 같이 표현한다. 여기서 가장 왼쪽이 입력층(input layer), 가장 오른쪽이 출력층(output layer) 그리고 가운데 층들을 은닉층(hidden layer)이라고 부른다. 오른쪽에 작은 원으로 표시된 활성화 함수는 은닉층과 출력층의 한 부분으로 간주한다.

이전 글에서 설명한 로지스틱 회귀는 은닉층이 없는 신경망이라고 볼 수 있다. 이런 입력층과 출력층만 가지는 신경망을 단일층 신경망이라고 부른다. 다음은 단일층 신경망을 그림으로 나타낸 것이다.

 

단일층 신경망 구현

우리는 이미 이전 글에서 LogisticNeuron 클래스를 구현하면서 단일층 신경망을 구현하였다. 여기서는 몇 가지 유용한 기능을 추가로 구현해보겠다.

 

손실 함수의 결괏값 저장하는 기능 추가

__init__() 메서드에 손실 함수의 결괏값을 저장할 리스트 self.losses를 만든다. 그런 다음 샘플마다 손실 함수를 계산하고 그 결괏값을 모두 더한 다음 샘플 개수로 나눈 평균값을 self.losses 변수에 저장한다. 그리고 self.activation() 메서드로 계산한 a는 np.log()의 계산을 위해 한 번 더 조정한다. 왜냐하면 a가 0에 가까워지면 np.log() 함수의 값은 음의 무한대가 되고 a가 1에 가까워지면 np.log() 함수의 값은 0이 되기 때문이다. 손실값이 무한해지면 정확한 계산을 할 수 없으므로 a의 값이 -1*10^-10 ~ 1-1*10^10 사이가 되도록 np.clip() 함수로 조정해야 한다. np.clip() 함수는 주어진 범위 밖의 값을 범위 양 끝의 값으로 잘라낸다.

여러 가지 경사 하강법

지금까지 사용한 경사 하강법은 샘플 데이터 1개에 대한 그레이디언트를 계산했는데 이를 확률적 경사 하강법(stochastic gradient descent)이라고 부른다. 경사 하강법에는 확률적 경사 하강법 외에 전체 훈련 세트를 사용하여 한 번에 그레이디언트를 계산하는 방식인 배치 경사 하강법(batch gradient descent)과 배치(batch) 크기를 작게 하여(훈련 세트를 여러 번 나누어) 처리하는 방식인 미니 배치 경사 하강법(mini-batch gradient descent)이 있다.

 

확률적 경사 하강법은 샘플 데이터 1개마다 그레이디언트를 계산하여 가중치를 업데이트하므로 계산 비용은 적은 대신 가중치가 최적값에 수렴하는 과정이 불안정하다. 반면에 배치 경사 하강법은 전체 훈련 데이터 세트를 사용하여 한 번에 그레이디언트를 계산하므로 가중치가 최적값에 수렴하는 과정은 안정적이지만 그만큼 계산 비용이 많이 든다. 바로 이 둘의 장점을 절충한 것이 미니 배치 경사 하강법이다. 아래의 그래프는 확률적 경사 하강법, 배치 경사 하강법이 최적의 가중치(w1, w2)에 수렴하는 과정을 나타낸 그래프이다. 미니 배치 경사 하강법은 확률적 경사 하강법보다는 매끄럽고 배치 경사 하강법보단 덜 매끄러운 그래프가 그려진다.

확률적 경사 하강법
배치 경사 하강법

앞에서 살펴본 모든 경사 하강법들은 매 에포크마다 훈련 세트의 샘플 순서를 섞어 가중치의 최적값을 계산해야 한다. 훈련 세트의 샘플 순서를 섞으면 가중치 최적값의 탐색 과정이 다양해져 가중치 최적값을 제대로 찾을 수 있기 때문이다.

 

훈련 세트의 샘플 순서를 섞는 전형적인 방법은 넘파이 배열의 인덱스를 섞은 후 인덱스 순서대로 샘플을 뽑는 것이다. 이 방법이 훈련 세트 자체를 섞는 것보다 효율적이고 빠르다. np.random.permutation() 함수를 사용하면 이 방법을 구현 할 수 있다. 아래는 이를 구현한 코드이다.

마지막으로 정확도를 계산해 주는 score() 메서드를 추가하고 predict() 메서드도 조금 수정하겠다. score() 메서드는 정확도를 직접 계산할 때 사용했던 np.mean() 함수를 사용한다.

시그모이드 함수의 출력값은 0~1 사이의 확률값이고 양성 클래스를 판단하는 기준은 0.5 이상이다. 그런데 z가 0보다 크면 시그모이드 함수의 출력값은 0.5보다 크고 z가 0보다 작으면 시그모이드 함수의 출력값은 0.5보다 작다. 그래서 z가 0보다 큰지, 작은지만 따지면 되기 때문에 predict() 메서드에는 굳이 시그모이드 함수를 사용하지 않아도 된다. 그래서 predict() 메서드에는 로지스틱 함수를 적용하지 않고 z값의 크기만 비교하여 결과를 반환했다.

 

단일층 신경망 클래스를 완성시켰다. 이 클래스를 위스콘신 유방암 데이터 세트에 적용해 보겠다.

 

이전과 마찬가지로 SingleLayer 객체를 만들고 훈련 세트(x_train, y_train)로 이 신경망을 훈련한 다음 score() 메서드로 정확도를 출력해 보겠다.

정확도가 훨씬 좋아진것을 확인할 수 있다. LogisticNeuron과 마찬가지로 fit() 메서드의 에포크 매개변수의 기본값 100을 그대로 사용했는데도 이렇게 성능이 좋아진 이유는 에포크마다 훈련 세트를 무작위로 섞어 손실 함수의 값을 줄였기 때문이다.