개발자 교육을 끝냈을 뿐인데, 남들은 없는 ‘개발서적’이 남았다
#인공지능 

당뇨병(diabetes) 데이터셋 예시로 보는 다중 선형 회귀

2024-06-11 | 신유진

사이킷런(scikit-learn)은 파이썬에서 사용할 수 있는 머신러닝 라이브러리로, 다양한 머신러닝 도구들을 제공합니다. 이번 글에서는 사이킷런에서 제공하는 당뇨병(diabetes) 환자 데이터를 활용하여 선형 회귀 모델을 구축해 보겠습니다.

from sklearn.datasets import laod_diabetes

diabetes = load_diabetes()

사이킷런에서 당뇨병 환자 데이터를 불러오기 위해 load_diabetes()모듈을 import 하고, 당뇨병 데이터를 diabetes 변수에 로드 했습니다. load_diabetes() 함수는 return_X_y(기본값: False)와 as_frame(기본값: False)이라는 두 가지 인자를 가지고 있으며, 각각 데이터를 배열 형태로 불러올지 여부와 판다스 데이터 프레임 형태로 불러올지 여부를 결정합니다. 저는 diabetes 객체 전체를 가져오고 판다스를 사용하지 않을 것이기 때문에, 두 인자를 모두 기본값(False)으로 설정하여 데이터를 로드했습니다.
diabetes는 파이썬의 딕셔너리와 유사한 Bunch 클래스입니다. Bunch 클래스는 점 표기법을 사용하여 클래스 내부의 키에 접근할 수 있습니다. diabetes가 포함하는 키(속성)의 종류는 다음과 같습니다.

  • data : (442,10) 크기의 ndarray, 입력값
  • target: (442,) 크기의 ndarray, 타깃값
  • feature_names: data의 각 열의 특징 이름
  • frame: (442, 11) 크기의 데이터 프레임 (호출 시 인자로 as_frame=True를 설정했을 때만 접근 가능)
  • DESCR: 데이터셋 설명
  • data_filename: data의 파일 경로
  • target_filename: target의 파일 경로
  • (data, target): 입력값과 타깃 값을 튜플로 반환 (호출 시 인자로 return_X_y=True를 설정했을 때만 접근 가능)
  • ndarray: numpy의 배열 클래스

더 자세한 내용은 아래의 링크에 들어가셔서 확인 할 수 있습니다.

load_diabetes

Gallery examples: Release Highlights for scikit-learn 1.2 Gradient Boosting regression Plot individual and voting regression predictions Model Complexity Influence Model-based and sequential featur…

먼저 data와 target을 받아오겠습니다.

x_data = diabetes.data
y_data = diabetes.target

diabetes.data와 diabetes.target은 모두 ndarray이므로 shape를 이용하여 모양을 파악할 수 있습니다.

print(x_data.shape)
print(y_data.shape)

위의 설명대로, x는 (442, 10) 크기의 배열이며, 10개의 정보를 가진 입력값이 총 442개가 있는 ndarray입니다. 따라서, y도 당연히 442개의 값을 가져야 합니다. 머신러닝에서는 이러한 데이터를 특성(feature)이라고 부릅니다. 앞으로 저도 특성이라는 용어를 사용하겠습니다.
이제, 이 10개의 특성이 각각 무엇을 의미하는지 살펴보겠습니다.

for i, feture_name in enumearte(diabetes.feature_names):
	print(f'feature {i+1} : {feature_name}')

첫 번째 특성은 나이, 두 번째 특성은 성별, 세 번째 특성은 체지방 지수입니다. 이러한 방식으로 각각의 특성이 의미하는 바를 알 수 있습니다. 즉, 442개의 데이터는 442명의 당뇨병 환자들의 나이, 성별 등을 기록한 데이터입니다. 첫 번째 환자의 데이터를 살펴보겠습니다.

print('<x_data[0]> : ',x_data[0])
print()
print('<y_data[0]> : ',y_data[0])

x에는 10개의 특성이 있으며, 이 모든 특성을 고려한 결과 첫 번째 사람의 타깃 값이 151임을 확인할 수 있습니다.
하지만 숫자가 조금 이상하게 보입니다. 예를 들어, 나이가 0.03807591이라니 어떻게 된 걸까요?
사이킷런 사이트에서 확인해보면, 이는 모든 특성이 -0.2에서 0.2 사이에 분포하도록 조정되었기 때문입니다.
이제 앞에서 다뤘던 다중 선형 회귀 모델을 사용하여 이 데이터를 학습해보겠습니다.

class MultiLinear:
  def __init__(self,learning_rate=0.001):
    self.w=None #모델의 weight 벡터 self.w=(w_1,w_2)
    self.b=None #모델의 bias
    self.lr=learning_rate #모델의 학습률
    self.losses=[] #매 에포크마다 손실을 저장하기 위한 리스트
    self.weight_history=[] #매 에포크마다 계산된 weight를 저장하기 위한 리스트
    self.bias_history=[] #매 에포크마다 계산된 bias를 저장하기 위한 리스트

  def forward(self,x):
    y_pred=np.sum(x*self.w)+self.b #np.sum함수는 인자로 받은 numpy배열의 모든 원소의 합을 return합니다.
    return y_pred

  def loss(self,x,y):
    y_pred=self.forward(x)
    return (y_pred-y)**2

  def gradient(self,x,y):
    y_pred=self.forward(x)
    w_grad=2*x*(y_pred-y)
    b_grad=2*(y_pred-y)

    return w_grad,b_grad

  def fit(self,x_data,y_data,epochs=20):
    self.w=np.ones(x_data.shape[1]) #모델의 weight들을 전부 1로 초기화
    self.b=0 #모델의 bias를 0으로 초기화
    for epoch in range(epochs):
      l=0 #계산할 손실값
      w_grad=np.zeros(x_data.shape[1]) #weight의 기울기를 누적할 numpy배열
      b_grad=0  #bias의 기울기를 누적할 변수

      for x,y in zip(x_data,y_data):
        l+=self.loss(x,y)
        w_i,b_i=self.gradient(x,y)

        w_grad+=w_i #weight누적
        b_grad+=b_i #bias누적

      self.w-=self.lr*(w_grad/len(y_data)) #weight 업데이트
      self.b-=self.lr*(b_grad/len(y_data)) #bias 업데이트
 
      print(f'epoch ({epoch+1}) ===> loss : {l/len(y_data):.5f}')
      self.losses.append(l/len(y_data)) #손실값 저장
      self.weight_history.append(self.w) #weight 배열 저장
      self.bias_history.append(self.b) #bias값 저장

fit 함수에서 가중치를 초기화할 때, 특성의 개수를 x.shape[1]을 통해 알 수 있습니다. 이를 활용하여 10개의 가중치를 1로 초기화하는 코드로 변경했습니다. 이제, 이 초기화된 가중치를 사용하여 모델을 학습해보겠습니다.

model = MultiLinear(learning_rate=0.1)
model.fit(x_data,y_data,epochs=40)

모델 객체를 생성하고, 학습률을 0.1로 설정한 뒤, 훈련 데이터에 대해 40 에포크 동안 모델을 학습시키는 과정을 코드로 작성해보겠습니다.


loss 값이 다소 크게 나오긴 하지만, 어느 정도 수렴하는 것을 확인할 수 있습니다. loss 값이 큰 이유는 상대적으로 x 값들에 비해 y 값이 매우 크기 때문입니다 (-0.2 < x < 0.2, 25 < y < 346). 따라서 현재로서는 숫자가 크다는 것에 신경 쓸 필요가 없습니다. 추후 ‘데이터 셋 – 전처리(preprocessing)’이라는 제목으로 포스팅을 할 예정이며, 이때 loss 값이 큰 이유와 이를 해결하는 방법을 정리할 것입니다.
중요한 점은 처음에는 빠르게 수렴하다가 나중에는 천천히 수렴하는 현상이 나타난다는 것입니다. 이는 우리가 옵티마이저로 경사하강법을 사용했기 때문에 발생합니다.
(loss 값이 너무 크다고 느껴진다면, y 데이터를 346으로 나눈 뒤 정규화(Normalization)하여 훈련시켜보면 숫자가 작아질 것입니다.)
사이킷런에서는 선형 회귀 모델을 제공합니다. 회귀 모델은 다음 코드와 같이 간단히 객체를 생성하여 사용할 수 있습니다. 저는 잘 사용하지 않지만, 궁금하신 분들을 위해 자세한 내용은 사이트 링크를 남겨두겠습니다.

from sklearn.linear_model import LinearRegression

#scikit-learn에서 제공하는 선형 회귀 모델
skmodel = LinearRegression()

#model을 x와 y 데이터셋을 이용하여 학습시킴
skmodel.fit(x,y)

#model이 'patient' 데이터에 대해 예측한 값을 array로 반환
skmodel.predict(patient)

#testset에 대해 model의 정확도를 판단
skmodel.score(test_x,test_y)

 

LinearRegression

Gallery examples: Principal Component Regression vs Partial Least Squares Regression Plot individual and voting regression predictions Comparing Linear Bayesian Regressors Linear Regression Example…

자세한 내용은 위 사이킷런의 선형 회귀 모델 문서에서 확인 하실 수 있습니다.