728x90
반응형

로지스틱 회귀 == 단일층 신경망(single layer neural network) 하지만 층(layer) 개념은 사용하지 않음

 

 

일반적인 신경망

가장 왼쪽이 입력층(input layer), 가장 오른쪽이 출력층(output layer), 가운데 층들을 은닉층(hidden layer)

[그림1] 일반적인 신경망

 

단일층 신경망

  • 입력층과 출력 층만 가지는 신경망
  • 입력층은 입력 그 자체여서 프로그램을 구현할 때는 겉으로 드러나지 않는다.

 

단일층 신경망 구현

  • 앞의 LogisticNeuron 클래스는 이미 단일층 신경망의 역할을 할 수 있으므로 학습을 위해 단일층 신경망을 다시 만들 필요는 없다.
  • 다시 구현하는 이유는 몇 가지 유용한 기능을 추가하기 위해서이다.

 

손실 함수의 결과 (layer neural network) 하지만 층(layer) 개념은 사용하지 않음

 

 

 

손실 함수의 결괏값 조정해 저장 기능 추가하기

  • __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() 함수는 주어진 범위 밖의 값을 범위 양 끝의 값으로 잘라낸다.
class SingleLayer:
    
    def __init__(self):
        self.w = None
        self.b = None
        self.losses = []
    
    def fit(self, x, y, epochs=100):
        for i in index:      # 모든 샘플 반복
            z = self.forpass(x[i]) # 정방향 계산 
            a = self. activation(z) # 활성호 ㅏ함수 적용
            err = -(y[i] - a) # 오차 계산
            w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산
            self.w -= w_grad # 가중치 업데이트
            self.b -= b_grad # 절편 업데이트
            # 안전한 로그 계산을 위해 클리핑 후 손실 누적
            a = np.clip(a, 1e-10, 1-1e-10)
            loss += -(y[i]*np.log(a)+(1-y[i])*np.log(1-a)) # 에포크마다 손실을 저장
            
        self.losses.append(loss/len(y))
        
        
        

 

 

여러 가지 경사 하강법

 

 

확률적 경사 하강법(stochastic gradient descent)

  • 샘플 데이터 1개에 대한 그레이디언트를 계산해 가중치를 업데이트하는 방식
  • 계산 비용은 적으나 대신 가중치가 최적 값에 수렴하는 과정이 불안정하다,

[그림2] 확률적 경사 하강법

 

배치 경사 하강법(batch gradient descent)

  • 전체 훈련 세트를 사용해 한 번에 그레이디언트를 계산하는 방식
  • 가중치가 최적값에 수렴하는 과정은 안정적이나 계산 비용이 많이 든다.

[그림3] 배치 경사 하강법

 

 

미니 배치 경사 하강법(mini-batch gradient descent)

  • 배치 크기를 작게 해(훈련 세트를 여러 번 나누어) 처리하는 방식
  • 확률적 경사 하강법과배치 경사 하강법 절충안

[그림4] 미니 배치 경사 하강법

 

최적의 가중치(w1,w2)에 수렴하는 과정을 나타낸 그래프

  • 미니 배치 경사 하강법은 확률적 경사 하강법보다는 매끄럽고 배치 경사 하강법보다는 덜 매끄럽다.

[그림5] 최적의 가중치(w1,w2) 수렴하는 과정 그래프

 

 

매 에포크마다 훈련 세트의 샘플 순서를 섞어 사용한다.

  • 앞의 모든 경사 하강법은 매 에포크마다 훈련 세트의 샘플 순서를 섞어 가중치의 최적 값을 계산해야 한다.
  • 훈련 세트의 샘플 순서를 섞으면 가중치 최적 값의 탐색 과정이 다양해져 가중치 최적 값을 제대로 찾을 수 있기 때문
  • 전형적인 방법은 넘파이 배열의 인덱스를 섞은 후 인덱스 순서대로 샘플을 뽑는 방법이다.
  • 이 방법이 훈련 세트 자체를 섞는 것보다 효율적이고 빠르다. np.random.permutation() 함수를 사용하면 이 방법을 구현할 수 있다.

 

def fit(self, x, y, epochs=100):
        self.w = np.ones(x.shape[1]) # 가중치 1로 초기화
        self.b = 0 # 절편 초기화
        for i in range(epochs):
            loss = 0
            indexes = np.random.permutation(np.arange(len(x))) # 인덱스 섞기
            for i in indexes:      # 모든 샘플 반복
                z = self.forpass(x[i]) # 정방향 계산 
                a = self. activation(z) # 활성호 ㅏ함수 적용
                err = -(y[i] - a) # 오차 계산
                w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산
                self.w -= w_grad # 가중치 업데이트
                self.b -= b_grad # 절편 업데이트
                # 안전한 로그 계산을 위해 클리핑 후 손실 누적
                a = np.clip(a, 1e-10, 1-1e-10)
                loss += -(y[i]*np.log(a)+(1-y[i])*np.log(1-a)) # 에포크마다 손실을 저장

        self.losses.append(loss/len(y))
        

 

score() 메서드

  • 정확도를 계산해주는 메서드
  def predict(self, x):
        z = [self.forpass(x_i) for x_i in x]
        return np.arrau(z) > 0 # 계단 함수 적용
    
    def score(self, x, y):
        return np.mean(self.predict(x) == y)

 

시그모이드 함수의 출력값은 0~1 사이의 확률 값 양성 클래스를 판단하는 기준은 0.5 이상 

그런데 z가 0보다 크면 시그모이드 함수의 출력 값은 출력 값은 0.5보다 크고 z가 0보다 작으면 시그모이드 함수의 출력 값은 0.5보다 작다.

그래서 predit() 메서드에서는 시그모이드 함수를 사용하지 않아도 괜찮다.

z가 0보다 큰지 작은지 비교만

 

전체 코드

import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_breast_cancer



class SingleLayer:
    
    def __init__(self):
        self.w = None
        self.b = None
        self.losses = []
    
    def forpass(self, x):
        z = np.sum(x * self.w) + self.b # 직선 방정식 계산
        return z
    
    def backprop(self, x, err):
        w_grad = x * err # 가중치에 대한 그레이디언트 계산
        b_grad = 1 * err # 절편에 대한 그레이디언트 계산
        return w_grad, b_grad
     
    def activation(self, z):
        z = np.clip(z, -100, None) # 안전한 np.exp()계산 위해서
        a = 1/(1+np.exp(-z)) # 시그모이드 계산
        return a
    
    def fit(self, x, y, epochs=100):
        self.w = np.ones(x.shape[1]) # 가중치 1로 초기화
        self.b = 0 # 절편 초기화
        for i in range(epochs):
            loss = 0
            indexes = np.random.permutation(np.arange(len(x))) # 인덱스 섞기
            for i in indexes:      # 모든 샘플 반복
                z = self.forpass(x[i]) # 정방향 계산 
                a = self. activation(z) # 활성호 ㅏ함수 적용
                err = -(y[i] - a) # 오차 계산
                w_grad, b_grad = self.backprop(x[i], err) # 역방향 계산
                self.w -= w_grad # 가중치 업데이트
                self.b -= b_grad # 절편 업데이트
                # 안전한 로그 계산을 위해 클리핑 후 손실 누적
                a = np.clip(a, 1e-10, 1-1e-10)
                loss += -(y[i]*np.log(a)+(1-y[i])*np.log(1-a)) # 에포크마다 손실을 저장

            self.losses.append(loss/len(y))
    
    def predict(self, x):
        z = [self.forpass(x_i) for x_i in x]     # 정방향 계산
        return np.array(z) > 0                   # 스텝 함수 적용
    
    def score(self, x, y):
        return np.mean(self.predict(x) == y)
        

 

 

단일층 신경망 훈련하기

 

1. 단일층 신경망 훈련하고 정확도 출력하기

  • SingleLayer 객체를 만들고 훈련 세트(x_train, y_train)로 이 신경망을 훈련한 다음 score() 메서드로 정확도를 출력
cancer = load_breast_cancer()
x = cancer.data
y = cancer.target
x_train, x_test, y_train, y_test = train_test_split(x, y ,stratify=y, test_size=0.2, random_state=42)




layer = SingleLayer()
layer.fit(x_train, y_train)
layer.score(x_test, y_test)

[그림6] 정확도 출력

 

 

2. 손실 함수누적값 확인하기

plt.plot(layer.losses)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

[그림7] 손실 함수 누적값 확인하기

은닉층을 사용하지 않기 대문에 단일층 신경망은 로지스틱 회귀나 퍼셉트론 알고리즘과 매우 비슷하다.

728x90
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기