[Do it 딥러닝 입문] 04장

퍼셉트론

  • 이진 분류 문제(임의의 샘플 데이터를 True False로 구분하는 문제)에서 최적의 가중치를 학습하는 알고리즘

  • 선형함수를 통과한 값 z를 계단 함수로 보내 0보다 큰지, 작은지 검사하여 1과 -1로 분류하는 알고리즘

  • 마지막 단계에서 샘플을 이진 분류하기 위해 계단함수를 사용 → 계단 함수를 통과한 값을 다시 가중치와 절편을 업데이트 하는데 사용 ⇒ 계단 함수의 값을 학습에 사용

  • 계단 함수는 z≥0이면 1(양성 클래스)로, z<0이면 -1(음성 클래스)로 분류

    선형 함수

아달린

  • 퍼셉트론을 개선한 적응형 선형 뉴런
  • 선형 함수의 결과를 학습에 사용, 계단 함수의 결과는 예측에만 사용 → 역방향 계산이 계단 함수 출력 이전, 선형 함수 출력 이후에 진행

로지스틱 회귀

  • 아달린에 활성화 함수를 추가한 것
  • 마지막 단계에서 임계함수를 사용하여 예측을 수행; 임계 함수는 활성화 함수의 출력값을 사용

활성화 함수

  • 선형 함수를 통과시켜 얻은 z를 임계 함수에 보내기 전에 변형
  • 보통 비선형 함수를 사용

시그모이드 함수 = 로지스틱 함수

  • 로지스틱 회귀의 활성화 함수
  • 선형함수의 출력값 z를 0~1사이의 확률값으로 변환시켜줌
  • 오즈 비 > 로짓 함수 > 시그모이드 함수 의 과정을 통해 만들어짐
    • 오즈 비 : 성공 확률과 실패 확률의 비율을 나타내는 통계. p/(1-p) ; p:성공확률
    • 로짓 함수 : 오즈 비에 로그 함수를 취하여 만든 함수. p가 0.5일때 0이 되고 p가 0,1일때 각각 무한대로 음수와 양수가 됨. log(p/(1-p)) = z

로지스틱 손실 함수를 경사하강법에 적용하기

  • 로직스틱 회귀와 같은 분류의 목표 : 올바르게 분류된 샘플 데이터의 비율 자체를 높이는 것 → 올바르게 분류된 샘플 데이터의 비율은 미분가능한 함수가 아님 → 경사하강법의 손실 함수로 쓰일 수 없음 → 로지스틱 손실 함수 사용

로지스틱 손실 함수

  • 다중 분류를 위한 손실 함수인 크로스 엔트로피 손실 함수를 이진 분류 버전으로 만든 것

    a : 활성화 함수가 출력한 값
    y : 타깃

이진 분류 → 그렇다(1)과 아니다(0)으로 나뉨 → y가 1(양성 클래스)의 경우 L=-log(a), y가 -1(음성 클래스)인 경우 L=-log(1-a)


유방암 데이터 세트

  • 유방암 데이터 샘플이 악성 종양(True)인지, 양성 종양(False)인지 구분하는 이진 분류

유방암 데이터 세트 준비하기

  1. load_breast_cancer() 함수 호출하기
  • 사이킷런의 datasets 모듈의 load_breast_cancer() 함수를 호출해 Bunch 클래스의 객체 가져오기
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
  1. 입력 데이터 확인하기
  • cancer의 data와 target 확인
print(cancer.data.shape, cancer.target.shape)

=> (569,30) (569,)

특성이 30개, 산점도로 표현하기 어려움 → 박스 플롯을 이용하여 각 특성의 사분위 값을 나타내기

  1. 박스 플롯으로 특성의 사분위 관찰하기
  • 박스 플롯 : 1사분위와 3사분위의 값으로 상자를 그린 다음 그 안에 2사분위(중간값) 표시, 1사분위와 3사분위 사이의 거리(interquartile range)의 1.5배만큼 위아래 거리에서 각각 가장 큰 값과 가장 작은 값까지 수염을 그림
import matplotlib.pyplot as plt
import numpy as np

plt.boxplot(cancer.data)
plt.xlabel('feature')
plt.ylabel('value')
plt.show()

  1. 타깃 데이터 확인하기
np.unique(cancer.target, return_counts=True)
  • 넘파이의 unique()함수를 사용해 고유한 값을 찾아 반환
  • return_counts 매개변수를 True로 지정해 고유한 값의 개수를 셀 수 있음

    ⇒ 타깃 데이터에 212개의 음성 클래스(정상 종양)과 357개의 양성 클래스(악성 종양)이 있음
  1. 훈련 데이터 세트 저장
x = cancer.data
y = cancer.target

모델이 성능 평가를 위해 훈련 세트와 테스트 세트로 나누기

  • 일반화 성능 : 훈련된 모델의 실전 성능
  • ‘과도하게 낙관적으로 일반화 성능을 추정한다 ‘ : 모델을 학습시킨 데이터로 모델의 성능을 평가하면 당연히 좋은 성능이 나옴
  • 훈련 데이터 세트를 나눌 때는 테스트 세트보다 훈련 세트가 많아야 하며, 훈련 데이터 세트를 나누기 전에 양성/음성 클래스가 어느 한 쪽에 몰리지 않도록 골고루 섞어야 함

훈련 세트와 데이터 세트로 나누기

  1. train_test_split() 함수로 훈련 데이터 세트 나누기
  • train_test_split() 함수는 훈련 데이터 세트를 훈련 세트 75%, 테스트 세트 25%의 비율로 나눔
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x,y, stratify=y, test_size=0.2, random_state=42)
  • stratify=y : 훈련 데이터를 나눌 때 stratify가 클래스 비율을 동일하게 만듬, 일부 클래스 비율이 불균형할 경우 stratify를 y로 지정해야 함
  • test_size=0.2 : 테스트 세트를 20%로 조정함
  • random_state=42 : 데이터 세트를 섞은 다음 결과가 항상 일정하도록 random_state 매개변수에 난수 초깃값 42 지정
  1. 결과 확인하기, unique()함수로 훈련 세트의 타깃 확인하기
print(x_train.shape, x_test.shape)
np.unique(y_tarin, return_counts = True)

로지스틱 회귀, 훈련하는 메서드, 에측하는 메서드 구현하기

class LogisticNeuron:
  def __init__(self):
    #1.__init__() 메서드 작성
    self.w = None   
    self.b = None #초기화하지 않음; 입력 데이터의 특성이 많아 #5에서 입력 데이털르 보고 특성 개수에 맞게 결정

  #2.정방향 계산
  def forpass(self,x):
    z = np.sum(x*self.w)+self.b   #직선 방정식 계산, np.sum() : 함수의 인자로 전달하면 각 요소를 모두 더한 값을 반환
    return z

  #3.역방향 계산
  def backprop(self,x,err):
    w_grad = x*err    #가중치에 대한 그레디언트 계산
    b_grad = 1*err    #절편에 대한 그레디언트 계산 
    return w_grad, b_grad
    
  #4.activation()메서드 구현;시그모이드 계산 
  def activation(self,z):
    a = 1/(1+np.exp(-z))
    return a
      
  #5.훈련을 위한 fit()메서드 구현
  def fit(self, x, y, epochs=100):
    self.w=np.ones(x.shape[1])    #가중치 초기화 
    self.b=0    #절편 초기화
    for i in range(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    #절편 업데이트
    
  #6.에측하는 메서드 구현
  def perdict(self,x):
    z = [self.forpass(x_i) for x_i in x]   #선형 함수 적용
    a = self.activation(np.array(z))    #활성화 함수 적용 
    return a > 0.5     #계단함수 적용

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

neuron = LogisticNeuron()
neuron.fit(x_train, y_train)

테스트 세트 사용해 모델의 정확도 평가하기

np.mean(neuron.predict(x_test)==y_test)

0.8245614035087719


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

단일층 신경망

  • 입력층과 출력층만 가지는 신경망. 은닉층이 없음 (ex>로지스틱 회귀)

경사 하강법의 종류

  • 확률적 경사 하강법 : 샘플 데이터 1개마다 그레디언트를 계산하여 가중치를 업데이트

    • 1개 샘플을 중복되지 않도록 무작위로 선택 → 그레디언트 계산
    • 계산 비용은 적으나 가중치가 최적값에 수렴하는 과정이 불안정
  • 배치 경사 하강법 : 전체 훈련 세트를 사용하여 한 번에 그레디언트를 계산

    • 전체 샘플을 모두 선택 → 여러 에포크를 반복해 그레디언트 계산
    • 가중치가 최적값에 수렴하는 과정은 안정적, 그러나 계산 비용이 많이 듬
  • 미니 배치 경사 하강법 : 배치 크기를 작게 하여(훈련 세트를 여러번 나눠) 처리

    • 전체 샘플 중 몇 개의 샘플을 중복되지 않도록 무작위로 선택 → 그레디언트 계산
    • 확률적 경사 하강법과 배치 경사 하강법의 장점을 절충한 것
  • 위의 경사 하강법들은 매 에포크마다 훈련 세트의 샘플 순서를 섞어 가중치의 최적값을 계산해야 함 → 훈련 세트의 샘플을 섞으면 가중치 최적값의 탐색 과정이 다양해져 가중치 최적값을 제대로 찾을 수 있음

    • 넘파이 배열의 인덱스를 섞은 후 인덱스 순서대로 샘플을 뽑는 방법으로 훈련 세트의 샘플 순서를 섞을 수 있음 ⇒ np.random.permutation()함수 사용
class SingleLayer:
  def __init__(self):
    #1.__init__() 메서드 작성
    self.w = None   
    self.b = None 
    self.losses=[]    #손실함수의 결과값을 저장할 리스트. 샘플마다 손실 함수를 계산하고 그 결괏값을 모두 더해 샘플 개수로 나눈 평균값을 저장 

  #2.정방향 계산
  def forpass(self,x):
    z = np.sum(x*self.w)+self.b   
    return z

  #3.역방향 계산
  def backprop(self,x,err):
    w_grad = x*err   
    b_grad = 1*err    
    return w_grad, b_grad
  
  def add_bias(self,x):
    return np.c_[np.ones((x.shpae[0],1)),x]   #행렬의 맨 앞에 1로 채워진 열 벡터를 추가
    
  #5.activation()메서드 구현;시그모이드 계산 
  def activation(self,z):
    a = 1/(1+np.exp(-z))
    return a
      
  #6.훈련을 위한 fit()메서드 구현
  def fit(self, x, y, epochs=100):
    self.w = np.ones(x.shape[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)    #np.clip() : 주어진 범위 밖의 값을 범위 양 끝의 값으로 잘라냄. 
        loss +=-(y[i]*np.log(a)+(1-y[i])*np.log(1-a))
      self.losses.append(loss/len(y))   #에포크마다 평균 손실 저장

  #7.에측하는 메서드 구현
  def predict(self,x):
    z = [self.forpass(x_i) for x_i in x]   
    return np.array(z) > 0  

  #8.정확도 계산을 위한 score()메서드 구현
  def score(self,x,y):
    return np.mean(self.predict(x)==y)

단일층 신경망 훈련하기

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

사이킷런으로 로지스틱 회귀 수행하기

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

from sklearn.linear_model import SGDClassifier
sgd = SGDClassifier(loss='log', max_iter=100, tol=1e-3, random_state=42)
   #loss 매개변수에 손실함수로 log 지정, max_iter로 반복회수 100으로 지정, 난수 초깃값 42로 설정, 반복할 때 마다 로지스틱 손실함수의 값이 tol에 저장된 값만큼 감소하지 않으면 반복을 중단하도록 설정 
sgd.fit(x_train,y_train)    #사이킷런의 fit()메서드로 훈련
sgd.score(x_test,y_test)    #사이킷런의 score()메서드로 정확도 계산
sgd.predict(x_test[0:10])   #사이킷런의 predict()메서드로 예측

좋은 웹페이지 즐겨찾기