People-Analytics201

다항 로지스틱 회귀분석(Multinomial Logistic Regression)으로 분류 모델 만들어보기 본문

Programing/Python

다항 로지스틱 회귀분석(Multinomial Logistic Regression)으로 분류 모델 만들어보기

2024. 2. 4. 16:02

1. 다항 로지스틱 회귀분석 (Multinomial Logistic Regression)

이항 로지스틱 회귀분석은 종속변수가 두 가지(Binary)일 때 사용하는 분류 방법이라면,
다항 로지스틱 회귀분석은 3가지 이상(Multinomial)의 클래스로 분류/매핑합니다.

 

https://michael-fuchs-python.netlify.app/2019/11/15/multinomial-logistic-regression/

 

다항 로지스틱 회귀분석(Multinomial Logistic Regression)은 소프트맥스 회귀(Softmax Regression)로도 불리는데요.
확률값을 시그모이드 함수(Sigmoid Function)를 통해 구하는 이항 로지스틱 회귀분석과는 달리,
다항 로지스틱 회귀분석에서는 소프트맥스 함수(Softmax Function)를 활용하여 확률값을 구하기 때문입니다.


2. Multinomial Logistic Regression 실습 (Python)

본 포스팅에서는 다항 로지스틱 회귀함수 모델을 활용해,
아래 핸드북의 예시 파일인 (health_insurance) 데이터의 예측 모델을 파이썬을 통해 구현해봅니다.
https://peopleanalytics-regression-book.org/multinomial-logistic-regression-for-nominal-category-outcomes.html

 

6 Multinomial Logistic Regression for Nominal Category Outcomes | Handbook of Regression Modeling in People Analytics: With Exam

In the previous chapter we looked at how to model a binary or dichotomous outcome using a logistic function. In this chapter we look at how to extend this to the case when the outcome has a number...

peopleanalytics-regression-book.org

health_insurance 데이터에는 다음과 같은 직원 인사 정보가 있으며,
종속변수는 해당 직원들이 선택한 보험 상품(product)입니다.

product : 선택한 보험 상품 (A/B/C) ► 종속변수 
age : 나이
household : 가족
position_level : 직장 내 직위
gender : 성별
absent : 일년 중 결근한 일수

 

본 포스팅에서는 [직원의 인사 정보가 주어졌을 때, 과연 어떤 보험 상품을 선택할 것인가?] 하는 예측을 진행해볼텐데요.
다항 로지스틱 회귀분석 모델링을 해보고, 정확도가 얼마나 되는지 확인해보도록 하겠습니다.

# 필요한 라이브러리 호출
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
# 데이터 불러오기
df = pd.read_csv("health_insurance.csv")
df.head()

# 데이터 정보 확인
df.info()

총 1453개의 데이터가 있음

# 데이터 결측치 확인
df.isnull().sum()

결측치는 없는 것을 확인할 수 있네요.

◼︎ 종속변수 product가 A/B/C의 값으로 되어 있기 때문에, 0~2로 레이블 인코딩을 해줍니다.

# 레이블 인코딩
label_encoder = LabelEncoder()
df['product'] = label_encoder.fit_transform(df['product'])

# A는 0으로, B는 1으로, C는 2로 인코딩
df['product']

◼︎ 성별이 Male, Female 두 가지이기 때문에, One-hot Encoding을 통해 전처리 해줍니다.

# One-Hot Encoding (더미 변수 생성)
df = pd.get_dummies(df, columns=['gender'])

# Female과 Male 분리
df.head()

어느 정도 전처리가 되어 있는 데이터인 것으로 보이기 때문에,
전처리는 여기까지 하고 데이터셋의 분포를 확인하기 위한 시각화를 진행합니다.

# 데이터셋의 특성 분포를 확인하는 코드

import matplotlib.pyplot as plt
import seaborn as sns

# 데이터셋에서 필요한 열만 선택
selected_columns = ['age', 'household', 'position_level', 'gender_Female', 'gender_Male', 'absent', 'product']
selected_data = df[selected_columns]

# 히스토그램 및 박스플롯
plt.figure(figsize=(12, 8))
for i, column in enumerate(selected_columns[:-1]):
    plt.subplot(2, 3, i + 1)
    sns.histplot(selected_data, x=column, hue='product', multiple='stack', kde=True)
    plt.title(f'Distribution of {column}')

plt.tight_layout()
plt.show()

1. 나이대별 분포를 보면 20~40대 까지는 product A의 선호도가 높은 반면, 40대 이후에는 A를 선택한 사람이 거의 없음
2. 가족 수가 2명 이상인 경우, product B를 선택한 사람이 거의 없음
3. 포지션 레벨 별로는 고른 분포를 보이고 있음
4. 여성의 경우 고른 분포를 보이고 있으며, 남성의 경우 A와 C의 선호도가 높음
5. 결근 일수별로는 대체로 고른 분포를 보이고 있음을 확인할 수 있음

이상치 확인을 위한 Boxplot도 한번 그려봅니다.

# 이상치 확인을 위한 상자 수염 그림(Boxplot)
plt.figure(figsize=(12, 8))
for i, column in enumerate(selected_columns[:-1]):
    plt.subplot(2, 3, i + 1)
    sns.boxplot(x='product', y=column, data=selected_data)
    plt.title(f'Boxplot of {column}')

plt.tight_layout()
plt.show()

위의 데이터셋 특성 분포와 대체로 비슷한 분포를 보이고 있으며, 튀는 outlier 값은 없는 것으로 보입니다.
대체로 잘 정제된 데이터로 보여지기 때문에 추가적인 전처리 작업을 거치지 않고 바로 분석을 진행해봅니다.

# 독립변수와 종속변수 분리
X = df[['age', 'household', 'position_level', 'gender_Female', 'gender_Male', 'absent']]
y = df['product']
# 데이터 표준화
scaler = StandardScaler()
X_standardized = scaler.fit_transform(X)

 

총 1453개 데이터 중 90%는 모델을 훈련하는데 사용하고, 나머지 10% 데이터를 통해 예측을 얼마나 정확하게 하는지 검증합니다.
데이터 수가 많지 않아 훈련하는 데 데이터의 90%를 사용하였으나,
Overfitting(과적합) 우려가 있으므로 통상적으로는 훈련 데이터의 비중이 90%보다 더 낮은 0.7~0.8 정도로 설정합니다. 

# 데이터를 훈련 세트와 테스트 세트로 분할
X_train, X_test, y_train, y_test = train_test_split(X_standardized, y, test_size=0.1, random_state = 42)

 

https://wikidocs.net/27534

사이킷런(sklearn)에서는 로지스틱 회귀 중 다항 로지스틱 회귀분석에 대한 기능을 지원하는데요.
세부 하이퍼파라미터는 위와 같으며, 기본적인 모델을 적용해봅니다.

model = LogisticRegression(multi_class='multinomial', solver='saga', max_iter=1000)
model.fit(X_train, y_train)
# 테스트 세트에 대한 예측
y_pred = model.predict(X_test)
# 정확도 평가
accuracy = metrics.accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy:.2f}')

78%의 정확도를 보여줍니다.
훈련 데이터의 비중을 조정해봐도 크게 차이는 나지 않았음을 확인할 수 있었습니다.

# 혼동 행렬 출력
confusion_matrix = metrics.confusion_matrix(y_test, y_pred)
print('Confusion Matrix:')
print(confusion_matrix)

위 혼동행렬에 대한 해석은 다음과 같습니다.

  - 실제 A -> A로 분류 38, B로 분류 5, C로 분류 5
  - 실제 B -> A로 분류 4, B로 분류 33, C로 분류 4
  - 실제 C -> A로 분류 5, B로 분류 9, C로 분류 43

정확도를 조금 더 높여보기 위한 작업인 교차검증(Cross Validation)을 진행해봅니다.

 

# Cross-validation 수행
k = 10  # k-fold의 수를 설정
cv_scores = cross_val_score(model, X_standardized, y, cv=k, scoring='accuracy')
# Cross-validation 결과 출력
print(f'Cross-Validation Scores: {cv_scores}')
print(f'Mean Accuracy: {np.mean(cv_scores):.2f}')

 

교차검증을 실시해본 결과, 73%에서 최대 82%까지 정확도가 분포되어 있음을 확인할 수 있었고, 평균 정확도는 77%이네요.
마지막으로 테스트 세트에 대한 예측 및 정확도 평가를 진행해봅니다.

# 테스트 세트에 대한 예측 및 정확도 평가
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = metrics.accuracy_score(y_test, y_pred)
print(f'Test Set Accuracy: {accuracy:.2f}')

 

테스트 셋에 대한 정확도는 78%가 나왔습니다.

데이터 수가 제한되어 있고, 정제된 데이터이기 때문에 데이터 전처리를 거의 하지 않았음을 감안하면
추가적인 전처리가 있다면 정확도를 조금 더 높일 수 있지 않을까 하는 생각을 해봅니다.

코드 상에 문제가 있거나, 분석 상에 틀린 부분이 있다면 언제든 댓글 부탁드립니다!

<Reference>
1. https://peopleanalytics-regression-book.org/multinomial-logistic-regression-for-nominal-category-outcomes.html

Comments