데이터 분석 초보자를 위한 Logistic Regression (with Scikit-Learn)

Gayeon Kim·2020년 10월 16일
3

데이터 분석

목록 보기
5/9

1. 0과 1, 그리고 Logistic Regression


이 세상에는 0과 1로 표현되는 문제가 너무나도 많다. 우리가 컴퓨터 속에 살고 있는 것도 아닌데 무슨 소리냐고? 합격/불합격, 정상 메일/스펨 메일, 생존/사망 등과 같이 두 개의 선택지 중 하나를 고르는 문제는 모두 0과 1로 표현할 수 있다. 예를 들면, 합격은 1로, 불합격은 0으로 표현하는 것이다. 이렇게 2개의 선택지 중에서 하나를 고르는 문제를 이진 분류라고 한다.

Logistic Regression은 이러한 이진 분류 문제를 해결할 수 있는 도구 중 하나로, Linear model에 해당한다. 그런데 여기서 한 가지 궁금증이 생긴다. Logistic Regression도 Linear model에 해당한다면 왜 Linear Regression을 쓰지 않고 굳이 Logistic Regression이라는 다른 도구를 만든 것일까? 그 답은 이진 분류에서 우리가 원하는 답은 0 또는 1이라는 데 있다.



2. Logistic Regression 살펴보기

왼쪽 그림은 일반적인 Linear Regression의 결과 얻을 수 있는 선형 함수이다. 만약 합격(=1)/불합격(=0)을 구분하는 이진 분류 문제를 이러한 Linear Regression으로 푼다고 해보자. 어학 성적, 학점, 전공, 자격증 유무 등등 여러 데이터들을 사용해서 Linear Regression을 실행하여 합격/불합격을 판단할 수 있는 모델을 만들었다. 만약 어떤 응시자의 데이터를 이 모델에 넣었더니 0이나 1이 아니라 1.2가 나왔다면 이를 어떻게 해석해야 할까? 1이 합격인데 1.2니까 합격이 아닌걸까? 우리가 원하는 답은 합격 또는 불합격, 즉 1 또는 0이다. 그런데 1.2, -1.5 이런 값들을 결과로 얻는다면 그래서 합격이라는건지 불합격이라는건지 확실하게 말할 수가 없다. 그래서 만들어진 게 바로 Logistic Regression이다.

오른쪽 그림은 Logistic Regression의 결과로 얻게 되는 함수인 Logistic function이다. Logistic function은 해당 데이터가 특정 클래스에 속할 확률 값을 출력하는 함수이다. 그림을 보면 알 수 있듯이, y값의 범위가 0에서 1로 한정되어 있다. 그렇기 때문에 0.5와 같은 특정 값을 기준으로 그 값보다 크면 1, 작으면 0으로 분류하여 이진 분류를 쉽게 수행한다. 그런데 한 가지 이상한 점이 있다. 바로 Lofistic function의 모양이 선형이 아니라는 것이다. 그런데 왜 Logistic Regression은 Linear model에 속했던 것일까? 그 이유는 Logistic function의 식에서 찾을 수 있다.

P(x)=11+e(β0+β1x1+β2x2++βnxn)P(x) = { \frac {1}{1+e^{-(\beta _{0}+\beta _{1}x_{1}+\beta _{2}x_{2}+\cdots +\beta _{n}x_{n}) } } }

위 수식을 잘 살펴보면 우리에게 친숙한 부분이 보인다. 바로 e의 지수부분이다. 아직 잘 모르겠다고? 그럼 한 번 아래와 같이 수식을 변형해보자.

y=β0+β1x1+β2x2++βnxny = \beta _{0}+\beta _{1}x_{1}+\beta _{2}x_{2}+\cdots +\beta _{n}x_{n} 라고 하면,

P(x)1P(x)=11+ey111+ey=ey\frac{P(x)}{1-P(x)} = \frac{ \frac{1}{1+e^{-y}} }{1-\frac{1}{1+e^{-y}} } = e^{y}

ln(P(x)1P(x))=yln(\frac{P(x)}{1-P(x)}) = y

ln(P(x)1P(x))=β0+β1x1+β2x2++βnxn\therefore ln(\frac{P(x)}{1-P(x)}) = \beta _{0}+\beta _{1}x_{1}+\beta _{2}x_{2}+\cdots +\beta _{n}x_{n}

위의 수식의 생김새에서 알 수 있듯이, Logistic Regression의 결과 얻을 수 있는 함수도 선형 모양으로 정리할 수 있다. 즉, Logistic Regression도 다른 Linear model처럼 선형 식의 회귀 계수를 찾는 과정인 것이다. 머신러닝 모델이 선형인지 아닌지를 판단하는 것은 모델을 통해 얻을 수 있는 식 자체의 모양에 의한 것이 아니라 회귀 계수에 대한 식이 선형인지 아닌지에 의해 결정된다.

그럼 이제 Sickit-Learn을 사용하여 직접 Logistic Regression 모델을 만들어보자.



3. 직접 해보는 Logistic Regression (with Scikit-Learn)


데이터 준비

# 라이브러리 및 패키지 import
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer


# 데이터 불러오기
breast_cancer = load_breast_cancer()
df = pd.DataFrame(data = breast_cancer.data, columns = breast_cancer.feature_names)
df = df.iloc[:, :10]
df["label"] = breast_cancer.target
df.columns = [ col.replace(" ", "_") for col in df.columns]

# 확인
df.head(3)

오늘 사용할 데이터셋은 Scikit-Learn을 통해 사용할 수 있는 데이터셋 중 하나인 breast cancer 데이터셋이다. 이 데이터셋은 환자들의 종양에 대해서 아래와 같은 정보를 담고 있다.

  • radius
  • texture
  • perimeter
  • area
  • smoothness
  • compactness
  • concavity
  • concave_points
  • symmetry
  • fractal_dimension

원본 데이터는 위 항목 각각에 대한 평균(mean), 표준오차(error), 큰 값 3개의 평균(worst)을 담고 있지만, 오늘은 이들 중 평균(mean)만 사용해볼 것이다. 우리는 위의 정보를 활용해서 각각의 종양이 양성인지, 악성인지 예측하는 모델을 만들 것이다. 종양의 양성/악성 여부는 label 컬럼에서 확인할 수 있다. 1은 양성(benign), 0은 악성(malignant)을 나타낸다.


[잠깐!]
이 글의 목적은 Logistic Regression에 대한 이해이므로 데이터의 전처리와 EDA에 대한 서술을 생략했다. 그러나 사람도 좋은 교재로 공부해야 좋은 성적이 나올 수 있듯이, 모든 예측 모델은 좋은 데이터를 사용해서 만들어야 좋은 성능을 기대할 수 있다. 따라서 전처리와 EDA를 통해 데이터를 정제하고, 파악하는 과정을 반드시 수행해야 한다.


Step1) 데이터셋 나누기

먼저, 주어진 데이터셋을 train, validation, test 데이터셋으로 나눠야 한다. train 데이터셋은 말 그대로 모델의 학습에 사용하는 데이터셋이다. validatioin 데이터셋과 test 데이터셋은 모델의 성능을 검증하기 위해서 사용한다. 언뜻 보면 두 데이터셋의 쓰임이 비슷해보이지만, 엄연히 차이가 있다. validation 데이터셋은 모델을 만드는 과정에서 모델의 feature 수, degree와 같은 hyperparameter를 결정하는 데 사용한다. 즉, 학습에 직접적으로 사용하는 것은 아니지만 모델의 개선에 관여를 하긴 한다. 반면, test 데이터셋은 모델의 최종 성능을 검증할 때 사용한다. 즉, 모델의 개선에 전혀 관여하지 않는다.

from sklearn.model_selection import train_test_split

# Step1) train / test 으로 나누기
train, test = train_test_split(df, test_size=0.15, random_state=1)

# Step2) train을 다시 train/validation 으로 나누기
train, val = train_test_split(train, test_size = 0.18, random_state=1)


# 확인
train.shape, val.shape, test.shape

[Out]

((396, 11), (87, 11), (86, 11))

Step2) 기준모델 만들기

기준모델은 앞으로 만들 예측 모델이 학습이 잘 된 모델인지, 의미있는 모델인지 판단하는 기준이 되어준다. 새로 만든 모델이 기준모델보다 정확도가 높다면 학습이 잘 된 모델일 것이고, 그렇지 않다면 학습이 잘 되지 않은 모델이다. 분류모델에서 사용할 수 있는 가장 기초적인 기준모델은 label을 무조건 train 데이터셋의 최빈 클래스로 예측하는 것이다.

from sklearn.metrics import accuracy_score

# 모델 만들기
majority_class = train["label"].mode()[0]

# 기준모델의 정확도 계산을 위한 데이터 생성
y_pred = [majority_class] * len(val)

# validation 데이터셋에 대한 정확도 확인
print("최빈 클래스: ", majority_class)
print("validation 데이터셋 정확도: ", accuracy_score(val["label"], y_pred))

[Out]

최빈 클래스:  1
validation 데이터셋 정확도:  0.5287356321839081

train 데이터셋의 최빈 클래스는 1이다. 따라서 모든 종양의 클래스를 1, 즉 양성이라고 예측하는 게 우리의 기준모델이다. 그리고 기준모델의 정확도는 약 0.53이다. 즉, 새로운 종양에 대해서 종양의 크기, 위치, 질감 등 어떠한 특징도 고려하지 않고 무조건 양성이라고 예측해도 약 53%는 맞힌다는 것이다. 따라서 앞으로 우리가 만들 예측모델은 정확도가 약 0.53보다는 높아야 학습이 잘 되었고 의미가 있는 모델이라고 말 할 수 있다.


Step3) feature Matrix와 target vector 만들기

이제 feature Matrix와 target vector를 만들어야 한다.

사용할 feature 선택

feature Matirx를 만들기 위해서는 어떤 feature를 사용할지 선택해야 한다. 그 과정에는 도메인 지식이 활용될 수도 있고, SelectKBest와 같은 도구를 이용할 수도 있다. 하지만 오늘은 우선 가지고 있는 feature를 최대한 다 사용해보도록 하자.

그렇지만 feature를 최대한 다 사용한다고 해도 한 가지 고려해야 할 사항이 있다. 바로 다중공선성이다. 다중공선성이란 feature들 사이에 강한 상관관계가 존재하는 것으로, 만약 데이터셋에 다중공선성이 있다면 제대로 된 모델을 만들 수 없다. feature들 사이에 상관관계가 존재하지 않도록 주의해야 한다.

이 그림은 breast cancer 데이터셋에서 우리가 오늘 사용하는 feature들의 상관계수를 히트맵으로 정리한 것이다. 그림을 보면 알 수 있듯이, mean_radius, mean_perimeter, mean_area 사이에 강한 상관관계가 있으며, mean_compactness, mean_concave_points, mean_concavity 사이에도 강한 상관관계가 있다. 따라서 이 feature들의 경우에는 각각의 집합에서 한 개씩만 선택하여 사용해야 한다.

우선 강한 상관관계가 없는 mean_texture, mean_smoothness, mean_symmetry, mean_fractal_dimensionmean_area, mean_concave_points를 선택해서 모델을 만들어보자. 그리고 target은 종양의 양성/음성 여부인 label이다.

# feature/taraget 설정
feature = ["mean_texture", "mean_smoothness", "mean_symmetry", "mean_fractal_dimension", "mean_area", "mean_concave_points"]
target = "label"

# train 데이터셋
X_train = train[feature]
y_train = train[target]

# validation 데이터셋
X_val = val[feature]
y_val = val[target]

# test 데이터셋
X_test = test[feature]
y_test = test[target]

# 확인
print("feature Matrix: ", X_train.shape, X_val.shape, X_test.shape)
print("target vector: ", y_train.shape, y_val.shape, y_test.shape)

[Out]

feature Matrix:  (396, 6) (87, 6) (86, 6)
target vector:  (396,) (87,) (86,)

스케일 조정

다음으로 할 일은 feature들의 스케일을 통일해주는 것이다. feature들의 스케일이 다르다면 각 feature가 label의 결정에 미치는 영향을 제대로 비교할 수 없다. 따라서 모델을 잘 학습시키기 위해서는 feature들의 스케일을 맞춰줘야 한다.

feature들의 스케일을 맞추기 위한 방법은 여러가지가 있다. 오늘은 그 중 하나인 StandardScaler를 사용해보았다. StandardScaler는 각 feature의 평균을 0, 표준편차를 1로 맞춰준다.

from sklearn.preprocessing import StandardScaler

# 스케일러 생성
scaler = StandardScaler()

# 스케일 조정
X_train_sclaed = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)
X_test_scaled = scaler.transform(X_test)

# 일부만 확인
X_train_sclaed.T[0].mean(), X_train_sclaed.std()

[Out]

(1.4802973661668753e-16, 1.0)

Step4) Logistic Regression 모델 만들기

이제 드디어 Logistic Regression 모델을 생성할 차례이다. 머신러닝을 위한 파이썬 패키지인 Scikit-Learn를 사용하면 Logistic Regression 모델을 쉽게 생성할 수 있다.

from sklearn.linear_model import LogisticRegression

# 모델 생성 및 학습 시키기
logistic = LogisticRegression()
logistic.fit(X_train_sclaed, y_train)

# 결과 확인
print("validation 데이터셋 정확도")
logistic.score(X_val_scaled, y_val)

[Out]

validation 데이터셋 정확도
0.9425287356321839

정확도가 약 0.94로 굉장히 높게 나왔다. 기준모델의 정확도인 0.53과 비교해보면 위 모델은 학습이 잘 된 모델이라고 할 수 있다.

add_featureaccuracy
0mean_radius, mean_compactness0.931034
1mean_radius, mean_concave_points0.942529
2mean_radius, mean_concavity0.91954
3mean_perimeter, mean_compactness0.931034
4mean_perimeter, mean_concave_points0.942529
5mean_perimeter, mean_concavity0.91954
6mean_area, mean_compactness0.931034
7mean_area, mean_concave_points0.942529
8mean_area, mean_concavity0.91954

위의 표는 mean_texture, mean_smoothness, mean_symmetry, mean_fractal_dimension는 고정한 채 mean_radius, mean_perimeter, mean_areamean_compactness, mean_concave_points, mean_concavity 에서 각각 한 개씩 선택해서 만든 모델의 정확도를 정리한 것이다. 총 사용한 feature의 수, 사용한 스케일러의 종류 등 다른 조건은 모두 동일하게 유지한 채 사용한 feature의 조합만 변경해서 모델을 생성하였는데, 정확도는 모두 달랐다. 이를 통해서 어떤 feature를 선택하는지가 모델의 성능에 영향을 미친다는 것을 알 수 있다.


Step5) 최종 모델 만들기

마지막 작업은 만들었던 모델 중에서 가장 성능이 좋은 모델을 선택하여 최종 모델을 만드는 것이다. 이때는 validation 데이터셋도 학습에 사용한다. 그리고 최종 모델의 성능은 test 데이터셋을 사용하여 평가한다.

# train 데이터셋과 validation 데이터셋 합치기
X_total = pd.concat([X_train, X_test])
y_total = pd.concat([y_train, y_test])

# feature/taraget 설정
feature = ["mean_texture", "mean_smoothness", "mean_symmetry", "mean_fractal_dimension", "mean_radius", "mean_concave_points"]
target = "label"

# 스케일러 생성
scaler = StandardScaler()

# 스케일 조정
X_total_sclaed = scaler.fit_transform(X_total)
X_test_scaled = scaler.transform(X_test)

# 모델 생성 및 학습 시키기
model = LogisticRegression()
model.fit(X_total_sclaed, y_total)

# 결과 확인
print("test 데이터셋 정확도")
model.score(X_test_scaled, y_test)

[Out]

test 데이터셋 정확도
0.9651162790697675

우리가 만든 모델의 정확도는 약 0.97이다. 기준모델과 비교했을 때 이 모델은 학습이 잘 된 모델이며, 의미있는 모델이다. 이 모델의 회귀계수는 아래와 같다.

model_coef = pd.Series(model.coef_[0], feature).sort_values(ascending=False)
model_coef

[Out]

mean_fractal_dimension    0.404885
mean_symmetry            -0.412741
mean_smoothness          -0.676046
mean_texture             -1.359872
mean_radius              -2.334211
mean_concave_points      -2.580463
dtype: float64

회귀계수의 절댓값이 클수록 label의 결정에 큰 영향을 주는 것이며, 절댓값이 작을수록 label 결정에 영향을 작게 준다. 이 모델에서 종양이 양성인지 악성인지를 결정하는데 가장 큰 영향을 주는 feature는 mean_concave_points(오목한 점의 수의 평균)이다. 그리고 가장 영향을 작게 주는 것은 mean_fractal_dimension(평균 프랙탈 차원)이다.


지금까지 Logistic Regression에 대해서 알아보았다. Logistic Regression은 기준모델로 써도 될 정도로 간단한 모델이니 익혀두면 이진 분류 문제를 해결할 때 도움이 될 것이다. 그러나 누누히 말하지만, 세상에 완벽한 방법은 없다. 그러므로 문제에 맞는 적절한 방법을 선택할 수 있도록 모델들에 대해서도 알아보도록 하자.



0개의 댓글