머신러닝/딥러닝 공부

분류 (2) - 위스콘신 유방암 데이터 세트(Wisconsin breast cancer dataset) 본문

AI 공부/Machine Learning

분류 (2) - 위스콘신 유방암 데이터 세트(Wisconsin breast cancer dataset)

호사린가마데라닌 2021. 10. 21. 23:01

 

 

 

1) 위스콘신 유방암 데이터 세트 (wisconsin breast cancer dataset)

이번 포스팅에서는 로지스틱 회귀 모델을 직접 코드로 구현해보겠습니다. 학습할 데이터는 사이킷런(scikit-learn)이 제공하는 위스콘신 유방암 데이터 세트입니다. 앞에서 다루었던 당뇨병(diabetes) 환자 데이터와 똑같은 Bunch클래스입니다. 먼저 이 데이터 세트에 대해 알아보도록 하겠습니다.

 

 

from sklearn.datasets import load_breast_cancer

cancer=load_breast_cancer()

x_data=cancer.data #입력값
y_data=cancer.target #타깃

print('<wisconsin breast cancer data set>')
print('  data shape : ',x_data.shape) 
print('target shape : ',y_data.shape)


먼저 사이킷런에서 유방암 데이터 세트를 가져온 뒤, x_data에 이 유방암 세트의 data(입력값)을, y_data에 target(타깃)을 대입해주었습니다. 코드의 실행 결과는 다음과 같습니다.

 


x_data(data)는 30개의 feature를 가진 데이터 569개를 가지고 있고, y_data는 x_data의 데이터에 대해 이 샘플이 음성 유방암 세포인지(True==1), 양성 유방암 세포(False==0)인지에 대한 데이터를 가지고 있습니다.


아래의 코드를 사용하면 데이터 셋에 있는 음성 유방암 샘플과 양성 유방암 샘플의 개수를 파악할 수 있습니다.

import numpy as np

np.unique(cancer.target,return_counts=True)

 


실행 결과는 다음과 같습니다.

양성 유방암 샘플(True)는 총 212개, 음성 유방암 샘플(False)은 총 357개가 있네요.


다음은 각 30개의 feature가 무엇인지 한 번 볼까요?

for i,feature in enumerate(cancer.feature_names):
  print(f'feature{(i+1)} : ',feature)


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

 


이 데이터 세트의 feature 30개는 radius, texture, perimeter 등 10가지 feature에 대한 각각의 mean, error, worst case data입니다. 사실 이 데이터 세트의 feature들이 각각 무엇을 뜻하는지 직접 분석할 필요는 없습니다. 중요한 것은 feature의 개수가 30개이고 로지스틱 회귀는 처음 데이터를 입력받았을 때 선형 방정식으로 z를 구하기 때문에 가중치가 30개가 필요하다는 것입니다. 이제 로지스틱 모델을 직접 코드로 구현해보겠습니다.


이 데이터 세트에 대해 더 자세한 내용이 궁금하다면 밑의 링크에 들어가서 확인할 수 있습니다.

https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_breast_cancer.html

 

sklearn.datasets.load_breast_cancer

Examples using sklearn.datasets.load_breast_cancer: Post pruning decision trees with cost complexity pruning Post pruning decision trees with cost complexity pruning, Permutation Importance with Mu...

scikit-learn.org

 

 

 

2) 로지스틱 회귀(Logistic Regression) 구현


이번에도 마찬가지로 LogisticRegression 클래스를 작성하여 모델을 구현했습니다.

class LogisticRegression:
  def __init__(self,learning_rate=0.01):
    self.w=None #가중치
    self.b=None #바이어스
    self.lr=learning_rate #학습률
    self.losses=[] #매 에포크마다 손실을 저장할 배열
    self.weight_history=[] #매 에포크마다 갱신된 가중치를 저장할 배열
    self.bias_history=[] #매 에포크마다 갱신된 바이어스를 저장할 배열

  def forward(self,x):
    z=np.sum(self.w*x)+self.b
    z=np.clip(z,-50,None) # <z가 -50이하인 경우 -50으로 잘라줌>
    # exp함수에 너무 작은 수가 들어가면 NaN(Not a Number)이 될 가능성을 배제하기 위해 사용
    return z
  
  def loss(self,y,a):
    return -(y*np.log(a)+(1-y)*np.log(1-a)) #binary cross-entropy 

  def activation(self,z): #활성화 함수 
    a=1/(1+np.exp(-z)) #시그모이드 함수
    a=np.clip(a,1e-10,1-(1e-10)) 
    #로그 계산에서 a의 값이 0이 되거나 1이 되는 경우를 배제하기 위해 사용
    return a

  def gradient(self,x,y): #gradient 메소드
    z=self.forward(x)
    a=self.activation(z)

    w_grad=x*(-(y-a))
    b_grad=-(y-a)

    return w_grad,b_grad 
  
  def fit(self,x_data,y_data,epochs=30):
    self.w=np.ones(x_data.shape[1]) #모델의 가중치들을 모두 1로 초기화
    self.b=0 #모델의 바이어스를 0으로 초기화
    for epoch in range(epochs):
      l=0 #매 에포크마다 손실을 누적할 변수
      w_grad=np.zeros(x_data.shape[1]) #손실함수에 대한 가중치의 기울기
      b_grad=0 #손실함수에 대한 바이어스의 기울기

      for x,y in zip(x_data,y_data):
        z=self.forward(x) 
        a=self.activation(z)
       
        l+=self.loss(y,a) #손실값 누적

        w_i,b_i=self.gradient(x,y) #가중치와 바이어스의 기울기 계산

        w_grad+=w_i #가중치의 기울기 누적
        b_grad+=b_i #바이어스의 기울기 누적 

      self.w-=self.lr*(w_grad/len(y_data)) #가중치 업데이트
      self.b-=self.lr*(b_grad/len(y_data)) #바이어스 업데이트

      print(f'epoch{(epoch+1)} ===> loss : {l/len(y_data):.4f} | weight : {self.w[0]:.4f} | bias : {self.b:.4f}')
      self.losses.append(l/len(y_data)) #이번 에포크에서의 손실값 저장
      self.weight_history.append(self.w) #이번 에포크에서의 가중치값 저장
      self.bias_history.append(self.b) #이번 에포크에서의 바이어스값 저장

 

 

선형 회귀 모델의 코드와 아주 유사합니다. 다만 로지스틱 회귀의 경우 선형 방정식을 통과한 데이터가 시그모이드 함수를 통하여 a값으로 바뀌고, 그 a값과 타깃(y)으로 손실을 계산했었습니다. 또한 선형 회귀 모델의 경우 손실 함수로 MSE를 사용했으나 로지스틱 회귀의 경우 binary cross-entorpy를 사용했습니다.

 


따라서 LogisticRegression 모델의 loss 메소드를 보면 MSE대신 binary cross-entropy함수가 작성되어 있는 것을 볼 수 있고, fit() 메소드에서 forward()와 activation() 메소드를 통해 입력으로 받은 data를 a로 바꿔주는 것을 확인할 수 있습니다.


모델의 코드 중간중간에 지수함수(exp)을 계산할 때나 로그함수(log)를 계산할 때 numpy의 clip함수를 사용합니다. np.clip() 함수는 입력으로 받은 인자의 값을 너무 커지거나 작아지지 않도록 잘라줍니다. activation() 메소드에서는 z의 값이 너무 작아지면 exp의 값이 매우 커져 NAN이 되는 것을 방지하고자 사용하였고, fit() 메소드에서는 a의 값이 0이 되거나 1이 되어 log함수의 계산에서 exception 경고가 나오는 것을 방지하고자 사용하였습니다. 그 외의 코드는 전부 앞에서 정리한 내용과 똑같습니다.


이제 모델을 생성하여 위스콘신 유방암 데이터 세트에 대하여 학습을 시켜보겠습니다. 학습률은 0.0005로, 60에포크 동안 학습을 시켰습니다.

model=LogisticRegression(learning_rate=0.0005)
model.fit(x_data,y_data,epochs=60)

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


숫자가 들쭉날쭉하긴 하지만 손실 값이 점점 작아지는 것이 보이네요. 그래프로 그려보면 다음과 같습니다.

import matplotlib.pyplot as plt

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



여기까지 로지스틱 회귀(Logistic Regression)에 대한 정리였습니다. 로지스틱 회귀는 머신러닝의 분류 모델 중 이진 분류 모델의 가장 대표적인 알고리즘입니다. 다음 포스팅에서는 다중 분류 모델(Multi Classification Model)에 대하여 정리를 해보도록 하겠습니다. 또한 다중 분류 모델을 코드로 구현하여 의류 이미지를 분류하는 것을 정리해 볼 생각입니다. 그다음에 이 함수를 확장시켜 활성화 함수, 데이터 세트를 테스트 세트, 검증 세트, 트레이닝 세트로 나누는 것, 과대적합과 과소적합, L1과 L2규제, 그리고 다층 신경망에 대해 정리한 뒤, 딥러닝 정리로 넘어가 볼 생각입니다.


원래는 머신러닝에서 딥러닝에 기본인 되는 것들만 정리하고 넘어가려고 했었는데 생각보다 내용이 많아졌네요. 빨리 딥러닝으로 넘어가고 싶습니다.


Comments