HyeM

[Do it] 로지스틱 뉴런 단일층 신경망 구현, 사이킷런 로지스틱 회귀 수행 본문

Study/AI&DeepLearning

[Do it] 로지스틱 뉴런 단일층 신경망 구현, 사이킷런 로지스틱 회귀 수행

Hailey_HyeM207 2021. 5. 5. 09:29

04_4 분류용 데이터 세트 준비

유방암 데이터 세트 준비

1. load_breast_cancer() 함수 호출

from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

 

 

 

2. 입력데이터 확인하기

입력데이터 data와 target 살펴보기

print(cancer.data.shape, cancer.target.shape)

569개 샘플과 30개 특성이 있음

 

 

특성이 30개 이므로, 산점도로는 표현하기 어려우니, 박스 플롯으로 각 특성의 사분위를 나타내보기

 

 

3. 박스 플롯으로 특성의 사분위 관찰하기

박스플롯

4, 14, 24번째 특성이 다른 값에 비해 분포가 더 큼

 

 

4. 눈에 띄는 특성 살펴보기

 

5. 타깃 데이터 확인하기

 

타깃데이터는 음성 샘플인지 양성 샘플인지 구분하는 0,1로 구성됨

unique : 고유한 값 찾아 반환 (return_counts를 true로 하면 고유한 값이 등장하는 횟수까지 세어 반환함)

0이 212개, 1이 357개 이다. 즉 212개가 음성 클래스(정상 종양), 357개가 양성 클래스(악성종양)이다.

 

 

6. 훈련 데이터 세트 저장하기

 

 

 

 

 

04_5. 로지스틱 회귀를 위한 뉴런 만들기

모델 성능 평가를 위해 훈련세트와 테스트 세트 분류

 

훈련 데이터 세트를 훈련 세트와 테스트 세트로 나누는 규칙

  • 훈련 데이터 세트를 나눌 때 테스트 세트보다 훈련 세트가 더 많아야 됨
  • 훈련 데이터 세트를 나누기 전에 양성, 음성 클래스가 훈련 세트나 테스트 세트의 어느 한쪽에 몰리지 않도록 골고루 섞기

훈련 세트와 테스트 세트로 나누기

훈련세트와 테스트 세트로 나눌 때 양성, 음성 클래스가 훈련 세트와 테스트 세트에 고르게 분포되어야 함

 

1. train_test_split() 함수로 훈련 데이터 세트 나누기

train_test_split() : 기본 훈련 세트 75%, 테스트 세트 25%

  • sratify : 훈련 데이터를 나눌 때 클래스 비율 동일하게 함. 만약 클래스 비율이 불균형할 경우 stratify를 y로 지정함
  • test_size : 기본 훈련 데이터세트를 75:25 비율로 나누는데, 이 비율을 조정할 수 있음. 위 예제는 입력된 데이터 세트의 20%를 테스트 세트로 나누기 위해 test_size에 0.2 전달
  • random_state : train_test_split()는 무작위로 데이터 세트를 섞은 다음 나누는데, 나눈 결과가 항상 일정하도록 난수를 42로 초기화 함

2. 결과 확인하기

약 4:1 비율

 

3. unique()함수로 훈련 세트의 타깃 확인하기

훈련 세트의 타깃의 클래수 개수는 전체 훈련 데이터 세트의 클래스 비율과 거의 비슷

 

 

로지스틱 회귀 구현

class LogisticNeuron:
    
    def __init__(self):
        self.w = None
        self.b = None

    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 fit(self, x, y, epochs=100):
        self.w = np.ones(x.shape[1])      # 가중치를 초기화합니다.
        self.b = 0                        # 절편을 초기화합니다.
        for i in range(epochs):           # epochs만큼 반복합니다
            for x_i, y_i in zip(x, y):    # 모든 샘플에 대해 반복합니다
                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          # 절편 업데이트
    
    def activation(self, z):
        z = np.clip(z, -100, None) # 안전한 np.exp() 계산을 위해
        a = 1 / (1 + np.exp(-z))  # 시그모이드 계산
        return a
    
    def predict(self, x):
        z = [self.forpass(x_i) for x_i in x]    # 선형함수(정방향 계산)
        a = self.activation(np.array(z))        # 활성화 함수 적용
        return a > 0.5   # 계단 함수 적용

init : 데이터 특성이 많아 가중치와 절편을 미리 초기화 하지 않음

forpass : 가중치와 입력 특성 곱을 모두 더하기 위해 np.sum() 함수 이용함

fit : 가중치를 1로 초기화 하고, 절편으로 0으로 초기화 함. 활성화 함수 activation를 적용시키고, 역방향 계산에서 로지스틱 손실 함수의 도함수를 적용함

activation : 자연 상수의 지수 함수를 계산하는 np.exp()로 시그모이드 함수를 구현함

predict : 예측값은 선형함수, 활성화 함수, 임계 함수 순서로 통과시킨다. z는 리스트 내포 문법을 사용하여 x의 행을 하나씩 꺼내어 forpass()에 적용하고 그 결과를 이용해 새리크스 z로 만드는 것이다.

  • np.ones() : 입력된 매개변수와 동일한 크기의 배열을 만들고 값을 모두 1로 채움. cf) np.zeros, np.full
  • np.exp() : 자연 상수의 지수 함수를 계산함

 

로지스틱 회귀 모델 훈련시키기

준비된 데이터 세트로 로지스틱 회귀 모델을 훈련하고, 정확도를 측정해봄

정확도는 위의 결과와 같음(82% 정확도)

 

 

 

 

 

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

일반적인 신경망 모습

일반적인 신경망은 입력층, 은닉층, 출력층으로 구성되어 있음. 단일층 신경망은 입력층과 출력층만 가지는 단일층 신경망임.

여러 가지 경사법

  • 확률적 경사 하강법 : 샘플 데이터 1개에 대한 그레이디언트 계산
  • 배치 경사 하강법 : 전체 룬련 세트를 사용하여 한 번에 그레이디언트 계산
  • 미니 배치 경사 하강법 : batch 크기를 작게 하여 처리함

확률적 경사 하강법은 계산 비용이 적은 대신 가중치가 최적값에 수렴하는 과정이 불안정함. 배치 경사 하강법은 전체 훈련 데이터를 사용하여 계산 비용이 많이 듦. 미니 배치 경사법은 둘 방법의 절충안으로 확률적 경사 하강보다는 그래프가 매끄럽고, 배치 경사 하강법보다는 덜 매끄러움.

단일층 신경망 구현

이미 앞에서 구현한 LogisticNeuron 클래스는 이미 단일층 신경망 역할임. 여기에 몇 가지 유용한 기능을 추가해본다.

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])               # 가중치를 초기화합니다.
        self.b = 0                                 # 절편을 초기화합니다.
        for i in range(epochs):                    # 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)

init : 손실 함수의 결과값을 저장할 리스트를 만듦

fit :

  1. np.random.permutation 사용하여,인덱스를 섞어 훈련시킨다.(매 에포크마다 훈련 세트의 샘플 순서를 섞어 사용)
  2. 샘플마다 손실 함수를 계산하고 그 결과값을 다 더하여 샘플 개수로 나눈다. 이때 activation으로 계산한 a를 np.log에 적용시키기 위해 조정을 하고, 안전한 로그 계산을 위해 clip 시켜준 후 계산한다. (손실 함수의 결과값 저장 기능 추가)

score : 시그모이드 함수의 출력값은 0~1사이의 확률값이고, 양성 클래스를 판단하는 기준은 0.5 이상이다. predict()에는 굳이 시그모이드 함수를 사용자 않아도 되므로, predict에서 로지스틱 함수를 적용하지 않고 z 값의 크기만 비교하여 결과물에 반환함 (score 메서드 추가)

 

 

단일층 신경망 훈련하기

정확도는 88%로 에포크마다 훈련 세트를 무작위로 섞어 손실 함수의 값을 줄였기 때문에 성능이 좋아짐

손실 함수의 누적값 확인함 로지스틱 솔실 함수의 값이 에포크가 진행됨에 따라 감소하고 있음을 확인함

 

 

 

 

 

 

04_7. 사이킷런으로 로지스틱 회귀 수행

로지스틱 회귀 문제 외에도 여러 가지 문제에 경사 하강법 적용할 수 있음. SGDClassifier를 사용하여 로지스틱 회귀 문제를 간단히 해결해 보자.

사이킷런으로 경사 하강법 적용

1.로지스틱 손실 함수 지정

  • loss=log : 손실 함수로 log 지정
  • max_iter=100 : 반복 횟수 100
  • tol=1e-3 : 반복할 때 마다 로지스틱 손실 함수의 값이 tol값 만큼 감소되지 않으면 반복 중단. 만약 tol 설정 하지 않으면 max_iter의 값을 늘리라는 경고 발생
  • random_state=42 : 반복 시 난수 초기값 42

2.사이킷런으로 훈련하고 평가하기

  • fit으로 훈련하고 score로 정확도 계산

3.사이킷런으로 예측

  • 사이킷런은 입력데이터로 2차원 배열만 받아드림. 배열의 슬라이싱으로 테스트 세트에서 10개의 샘플만 뽑아 예측함.
Comments