본문 바로가기

전공

Scikit-learn으로 로지스틱 회귀 (클래스 분류편)

Scikit-learn でロジスティック回帰(クラス分類編)의 번역본.

링크

https://qiita.com/0NE_shoT_/items/b702ab482466df6e5569

 

Scikit-learn でロジスティック回帰(クラス分類編) - Qiita

はじめに ロジスティック回帰は、説明変数の情報にもとづいて データがどのクラスに属するかを予測・分類する(例:ある顧客が商品を買うか買わないかを識別する) 注目している出来事

qiita.com

여러 수식을 표기하고 있기 때문에, 모바일 버전에서 보는 것보다, PC버전에서 보는 것이 적합합니다.

편의상 목적변수는 종속변수로, 설명변수는 독립변수로 쓰겠습니다.

 

정사상(正事象) : 유의미한 사건 / 긍정적인 사건

*2021 / 10 / 27 : オッズ(odds)를 승산, 오즈로 수정했습니다.

 


시작하며


로지스틱 회귀는 독립변수의 정보에 기초해

  1. 데이터가 어느 클래스에 속해있는가 예측・분류하기 (예: 어느 고객이 상품을 사는가 사지않는가를 식별한다)
  2.  주목하고 있는 사건이 발생하는 확률을 예측하기 (예: 어느 고객이 몇%의 확률로 상품을 사는가 예측한다)

위해서 이용되는 모델입니다.

이 게시글에서는, Scikit-learn의 라이브러리를 사용해, 로지스틱 회귀에 의해 클래스 분류를 하는 방법을 비망록으로서 써두겠습니다.

 

Scikit-learn에 대해서


Scikit-learn(사이킷런)은 Python의 기계학습 라이브러리 중 하나입니다.

 

scikit-learn: machine learning in Python — scikit-learn 0.24.1 documentation

Model selection Comparing, validating and choosing parameters and models. Applications: Improved accuracy via parameter tuning Algorithms: grid search, cross validation, metrics, and more...

scikit-learn.org

로지스틱 회귀에 대해서


로지스틱 회귀는, 분류를 위한 알고리즘입니다. (예: 전자기기를 고장내는 클래스 or 고장내지 않는 클래스로 나눈다).

단순한 알고리즘이므로 구현하기 편하지만, 특징량 공간 (독립변수가 되는 데이터가 있는 공간) 이 선형분리 가능한 경우만 높은 성능을 발휘합니다.

선형분리 가능이라는 것은, 독립변수가 2차원 평면상에 있다고 하면, 어느 클래스에 대응하는 독립변수 (의 값의 집합) 과, 다른 클래스에 대응하는 독립변수(의 값의 집합)을, 하나의 선을 그어 사이에 두는 것이 가능한 것을 말합니다.

 

로지스틱 회귀에서는, 로짓(logit)을 독립변수 \(x_i\)의 선형 합으로 표시합니다. 

예측하고 싶은 것 (정사상)의 확률을 \(p\)로 했을 때, 확률은 \(p\)\(/\)\((\)\(1\)\(-\)\(p\)\()\)라고 쓸 수 있고, 정상형의 일어나기 쉬움을 표현합니다.

오즈(odds)의 로그를 취한 것이, 로짓(logit)입니다.

 

$$\log(\frac{p}{1-p}) = w_0x_0 + w_1x_1 + \cdots +w_mx_m = \sum^m_{i=0}w_ix_i $$

 

여기서, 가중치 \(w_0\)은 \(x_0\)\(=\)\(1\)로서 절편을 표시합니다.

로지스틱 회귀는, 로짓(logit)과 복수의 독립변수의 관계를 표현하는 모델의 가중치 \(w_i\)를 학습하는 것이 목적입니다.

다만, 로지스틱 회귀를 이용할 때에 관심이 있는 것은, 독립변수의 값을 부여했을 때의 정사상*의 확률 \(p\)입니다.

여기서, 위의 식을 좌변이 \(p\)가 되도록 변형하면,

 

$$ p = \frac{1}{1+\exp(-\sum^m_{i=0}w_ix_i)} $$

 

이 됩니다. 모델의 가중치 \(w_i\)를 학습후, 이 식을 이용해 독립변수의 값이 주어졌을 때의 정사상의 확률을 구하는 것이 가능해져, 그 확률이 일정치 이상이면 정사상의 클래스에 분류할 수 있습니다.

 

로지스틱 회귀 모델


Scikit-learn으로 로지스틱 회귀를 하는 것은, linear_model의 LogisticRegression모델 (공식 문서 : https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html )를 사용합니다. 

주로 이용하는 메소드는 아래와 같습니다.

  • fit 메소드 : 로지스틱 회귀 모델의 가중치를 학습 
  • predict 메소드 : 독립변수의 값으로부터 클래스를 예측

여기서는, UCI Machine Learning Repository (http://archive.ics.uci.edu/ml/datasets/Iris ) 에서 공개되어있는, 붓꽃의 품질 데이터를

사용합니다. 

아래의 코드는, seaborn 라이브러리 부속 데이터셋을 가져옵니다. ( 이후의 코드 동작 환경은, Python 3.7.3, scikit-learn 0.20.3 입니다.)

 

import seaborn as sns
iris_df = sns.load_dataset('iris') # 데이터셋을 불러옴
iris_df = iris_df[(iris_df['species']=='versicolor') | (iris_df['species']=='virginica')] # 간단하게 2개의 품종으로 좁힘.

 

데이터를 들여다 보면, 이런 느낌입니다.

 

iris_df.head()

 

각 변수 (데이터 항목) 의 설명은 아래와 같습니다.

 

변수 설명
sepal_length 꽃받침의 길이
sepal_width 꽃받침의 너비
petal_length 꽃잎의 길이
petal_width 꽃잎의 너비
species 품종

각 변수 값의 관계를 살펴봅시다.

이하의 코드에서는, 각 변수의 관계를 가시화하는 분포도 행렬을 플로트(plot, 작도)하고 있습니다.

품종별 꽃받침과 꽃잎의 길이 및 너비의 값과 품종의 관계를 알아낼 수 있습니다.

특히, 3행 3열째의 그래프에 착안하면 꽃잎의 길이 (petal_length) 가 작은 경우의 품종은 versicolor이며, 큰 경우의 품종은 virginica로 분류가 가능해 보입니다.

 

import matplotlib.pyplot as plt
sns.pairplot(iris_df, hue='species')
plt.show()

 

 

이후에는, 꽃잎의 길이 (petal_length)를 사용해 품종을 분류하는 로지스틱 회귀 모델을 구축합니다.

 

로지스틱 회귀 모델의 구축


로지스틱 회귀 모델의 인스턴스를 작성하고, fix 메소드로 독립변수의 가중치를 학습하는 것으로, 로지스틱 회귀 모델을 구축합니다.

여기서는 품종 versicolor를 클래스 0으로, 품종 virginica를 클래스1로 하고 있으며,

scikit-learn 라이브러리의 model_selection.train_test_split 메소드로, 데이터를 모델 구축용 데이터 (학습 데이터)와 예측 정도 검증용 데이터 (검증 데이터)로 분할해, 학습 데이터를 fit 메소드의 인수로서 넘겨주어, 로지스틱 회귀 모델을 구축하고 있습니다.

 

 

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

X = iris_df[['petal_length']] # 독립변수
Y = iris_df['species'].map({'versicolor': 0, 'virginica': 1}) # versicolor를 클래스0, virginica를 클래스 1로 한다.
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.2, random_state = 0) # 80%의 데이터를 학습 데이터로, 20%를 검증 데이터로 한다.

lr = LogisticRegression() # 로지스틱 회귀 모델의 인스턴스를 생성
lr.fit(X_train, Y_train) # 로지스틱 회귀 모델의 가중치를 학습

 

 

위의 코드에서는 생략하고 있습니다만, 로지스틱 회귀의 인스턴스를 생성할 때, 몇 가지의 파라미터를 설정할 수 있습니다. (아래에 적은 것은 지정 가능한 파라미터의 일부).

 

파라미터 설명
penalty 정규화의 방법을 지정하는 파라미터.
'l1'이나 'l2'를 지정한다.
'l1'을 지정했을 때에는 L1 정규화, 'l2'를 지정했을 때에는 L2 정규화를 한다 (디폴트 값은 'l2' ).
L1 정규화는 많은 독립변수의 가중치가 0이 되도록 해, 특징선택의 수법으로도 사용된다.
L2 정규화는 독립변수의 가중치가 너무 커지는 것을 막아, 과(過)학습을 회피하기 위해서 이용된다.
C 정규화의 강도를 지정하는 파라미터.
양(+)의 값을 지정한다. (디폴트 값은 1.0)
C의 값이 작을수록 강도가 증가한다.
random_state 난수생성기의 시드를 지정하는 파라미터.
수치 (int형) 이나 RandomState 인스턴스를 지정한다.
모델의 재현성을 담보하기 위해서, 적당한 값을 지정해두는 것이 좋다.

학습으로 얻을 수 있었던, 로지스틱 회귀 모델의 절편 \(w_0\)은 intercept_ 속성에, 독립변수의 계수 \(w_1\)은 coef_속성에 격납됩니다.

학습결과를 확인하면, 계수는 약 0.74, 절편은 약 -3.39인 것을 알 수 있습니다.

 

print("coefficient = ", lr.coef_)
print("intercept = ", lr.intercept_)

coefficient =  [[0.73642001]]
intercept =  [-3.39483004]

 

이 모델에 대해, 검증 데이터의 독립변수의 값으로서 predict 메소드를 실행하면, 각각의 검증 데이터가 속한 클래스를 예측할 수 있습니다.

 

Y_pred = lr.predict(X_test)
print(Y_pred)

[1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 0 1 0 1 0]

 

로지스틱 회귀 모델의 성능평가


클래스 분류의 성능평가에는, 주로 아래와 같은 지표가 사용됩니다.

아래의 코드처럼, scikit-learn.metrics 라이브러리의 각종 메소드로 이들 지표의 값을 산출할 수 있습니다.

 

  • 혼동행렬 (confusion matrix)
  • 정확도 (accuracy)
  • 적합률 (precision)
  • 재현률 (recall)
  • F1 스코어 (F1-score)
  • AUC (곡선하면적; 曲線下面積)

혼동행렬이란, 실제 클래스가 0,1인 데이터에 대해 클래스 0,1로 분류된 데이터의 개수를 요소로 하는 행렬입니다.

 

출력결과의 

  1. 1행 1열째의 요소는, 실제로 클래스0이며, 알맞게 클래스0으로 분류된 데이터 수 (진음성 (TN: True Negative))
  2. 1행 2열째의 요소는, 실제로는 클래스 0이지만, 실수로 클래스1로 분류된 데이터 수 (위양성 (FP: False Positive))
  3. 2행 1열째의 요소는, 실제로는 클래스 1이지만, 실수로 클래스0으로 분류된 데이터 수 (위음성 (FN: False Negative))
  4. 2행 2열째의 요소는, 실제로 클래스1이며, 알맞게 클래스 1로 분류된 데이터 수 (진양성 (True Positive))

로 나타냅니다.

진양성·위음성·위양성·진음성의 「진·위」는 「분류가 맞는가」를 나타내고, 「음성·양성」 은 「클래스1·클래스2」를 나타냅니다.

예를 들면, 위음성은, 예측된 클래스는 음성이지만 분류가 잘못되었다는 걸 나타냅니다.

 

정확도란, 분류한 데이터의 총수 중, 알맞게 분류된 데이터의 비율입니다.

식으로 쓰면 (TP+TN)/(TP+FN+FP+TN) 입니다.

 

적합률이란, 클래스1에 분류된 데이터 중, 실제로 클래스1인 데이터 수의 비율입니다.

식으로 쓰면, TP/(TP+FP) 입니다.

 

재현률이란, 클래스1에 분류된 데이터 중, 클래스1에 분류된 데이터 수의 비율입니다.

식으로 쓰면, TP/(TP+FN) 입니다.

 

F1 스코어는, 적합률과 재현률의 조화평균으로 정의됩니다.

일반적으로, 적합률과 재현률은 트레이드오프의 관계에 있기 때문에, F1 스코어는 이들의 밸런스를 평가하는 지표로 볼 수 있습니다.

 

from sklearn.metrics import confusion_matrix, accuracy_score, precision_score, recall_score, f1_score

print('confusion matrix = \n', confusion_matrix(y_true=Y_test, y_pred=Y_pred))
print('accuracy = ', accuracy_score(y_true=Y_test, y_pred=Y_pred))
print('precision = ', precision_score(y_true=Y_test, y_pred=Y_pred))
print('recall = ', recall_score(y_true=Y_test, y_pred=Y_pred))
print('f1 score = ', f1_score(y_true=Y_test, y_pred=Y_pred))

confusion matrix = 
 [[ 5  5]
 [ 0 10]]
accuracy =  0.75
precision =  0.6666666666666666
recall =  1.0
f1 score =  0.8

 

AUC는, ROC (Receiver Operating Characteristic) 곡선의 아래쪽의 면적으로, 이 값이 크면 클수록 분류성능이 좋다는 것을 표현하는 지표입니다 (최대치는 1).

 

ROC 곡선이란, x축으로 위양성률 (FPR: False Positive Rate) 을, y축으로 진양성률 (TPR: True Positive Rate) 을 취한, 모델의 분류성능을 표현한 곡선입니다.

클래스 분류에서는, 각 데이터가 클래스1에 속하는 확률 (스코어) 을 산출해, 스코어가 역치(閾値)를 상회하고 있다면, 데이터를 클래스 1에 분류합니다.

따라서, 그 역치를 변화시키면, 위양성률 (실제로는 클래스 0 이지만, 실수로 클래스 1에 분류된 데이터의 비율) 과 진양성률 (실제로 클래스 1이고 알맞게 클래스 1에 분류된 확률)이 변화합니다.

역치가 극단적으로 클 때는 어떤 데이터도 클래스 1에 분류되지 않기 때문에 ROC 곡선은 점(0, 0)을 지나고, 역치가 극단적으로 작을 때에는 모든 데이터가 클래스 1에 분류되기 때문에 ROC 곡선은 (1, 1)을 지납니다.

역치를 큰 값부터 작은 값으로 변화시켜 나가면, 스코어가 큰 데이터부터 차례대로 클래스 1로 분류됩니다.

실제로 클래스 1의 데이터의 스코어가 큰 경향이 있으면, 진양성률이 위양성률보다도 빨리 커져, ROC 곡선은 점(0, 0)부터 그래프의 왼쪽 위를 지나 점(1, 1)까지 그려집니다.

그 결과, ROC 곡선의 아래쪽의 면적인 AUC의 값이 커집니다.

따라서, AUC는 실제로 클래스 1의 데이터에 대한 스코어가 크게 예측되어, 실제로 클래스 0의 데이터에 대한 스코어는 낮게 예측되는가를 평가하는 지표라고 말할 수 있습니다.

예를 들면, 아래와 같은 코드로 ROC 곡선을 묘화해, AUC를 산출할 수 있습니다. 

 

from sklearn.metrics import roc_curve, auc

Y_score = lr.predict_proba(X_test)[:, 1] # 검증 데이터가 클래스 1에 속하는지 확인
fpr, tpr, thresholds = roc_curve(y_true=Y_test, y_score=Y_score)

plt.plot(fpr, tpr, label='roc curve (area = %0.3f)' % auc(fpr, tpr))
plt.plot([0, 1], [0, 1], linestyle='--', label='random')
plt.plot([0, 0, 1], [0, 1, 1], linestyle='--', label='ideal')
plt.legend()
plt.xlabel('false positive rate')
plt.ylabel('true positive rate')
plt.show()

 

 

 

또한, 아래의 코드에서는, ROC 곡선을 생략해 AUC의 값만을 취득하고 있습니다.

 

from sklearn.metrics import roc_auc_score
print('auc = ', roc_auc_score(y_true=Y_test, y_score=Y_score))

auc =  0.975

 

실제로 어떤 지표를 이용해 클래스 분류의 성능평가를 해야하는가는, 클래스 분류에 의해 무엇을 달성하고 싶은가 · 무엇을 검증하고 싶은가 에 대한 과제설정에 의존합니다.

예를 들면, 실제로 클래스 1에 속한 데이터가 몇 퍼센트 밖에 없는 상황에서 클래스 1에 속한 데이터를 맞추고 싶을 (예: 고장나는 기기의 특정 · CV(컨버젼; 목표)하는 고객의 특정 등) 때, 정확도를 지표로 이용하는 것은 적당하지 않습니다.

그 이유는, 어떤 데이터에 대해 클래스 0으로 예측해버리면, 99%에 가까운 정확도를 얻기 때문입니다.

 

끝으로


이 게시글에서는, scikit-learn 라이브러리로 로지스틱 회귀 모델을 구축해, 클래스 분류의 성능평가를 하는 방법에 대해서 간단하게 다뤄보았습니다.

 

참고