본문 바로가기
AI

로지스틱 회귀

by 코낄2 2023. 12. 27.

1. hr 데이터셋

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
hr_df = pd.read_csv('/content/drive/MyDrive/KDT/머신러닝과 딥러닝/data/hr.csv')
hr_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 54808 entries, 0 to 54807
Data columns (total 13 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   employee_id           54808 non-null  int64  
 1   department            54808 non-null  object 
 2   region                54808 non-null  object 
 3   education             52399 non-null  object 
 4   gender                54808 non-null  object 
 5   recruitment_channel   54808 non-null  object 
 6   no_of_trainings       54808 non-null  int64  
 7   age                   54808 non-null  int64  
 8   previous_year_rating  50684 non-null  float64
 9   length_of_service     54808 non-null  int64  
 10  awards_won?           54808 non-null  int64  
 11  avg_training_score    54808 non-null  int64  
 12  is_promoted           54808 non-null  int64  
dtypes: float64(1), int64(7), object(5)
memory usage: 5.4+ MB
  • employee_id: 임의의 직원 아이디
  • department: 부서
  • region: 지역
  • education: 학력
  • gender: 성별
  • recruitment_channel: 채용 방법
  • no_of_trainings: 트레이닝 받은 횟수
  • age: 나이
  • previous_year_rating: 이전 년도 고과 점수
  • length_of_service: 근속 년수
  • awards_won: 수상 경력
  • avg_training_score: 평균 고과 점수
  • is_promoted: 승진 여부
hr_df.describe()

sns.barplot(x = 'previous_year_rating', y = 'is_promoted', data = hr_df)

sns.lineplot(x = 'previous_year_rating', y = 'is_promoted', data = hr_df)

sns.lineplot(x = 'avg_training_score', y = 'is_promoted', data = hr_df)

sns.barplot(x ='recruitment_channel', y = 'is_promoted', data = hr_df) 
✔ 심지가 길수록 오차가 클 수 있다는 의미(데이터가 적음)

hr_df['recruitment_channel'].value_counts()

other       30446
sourcing    23220
referred     1142
Name: recruitment_channel, dtype: int64
sns.barplot(x ='gender', y = 'is_promoted', data = hr_df)

hr_df['gender'].value_counts()

m    38496
f    16312
Name: gender, dtype: int64
sns.barplot(x ='department', y = 'is_promoted', data = hr_df)
plt.xticks(rotation = 45)

hr_df['department'].value_counts()

Sales & Marketing    16840
Operations           11348
Technology            7138
Procurement           7138
Analytics             5352
Finance               2536
HR                    2418
Legal                 1039
R&D                    999
Name: department, dtype: int64
plt.figure(figsize=(14,10))
sns.barplot(x ='region', y = 'is_promoted', data = hr_df)
plt.xticks(rotation = 45)

- 결측값 확인

hr_df.isna().mean()

employee_id             0.000000
department              0.000000
region                  0.000000
education               0.043953
gender                  0.000000
recruitment_channel     0.000000
no_of_trainings         0.000000
age                     0.000000
previous_year_rating    0.075244
length_of_service       0.000000
awards_won?             0.000000
avg_training_score      0.000000
is_promoted             0.000000
dtype: float64

 

hr_df['education'].value_counts()

Bachelor's          36669
Master's & above    14925
Below Secondary       805
Name: education, dtype: int64

hr_df['previous_year_rating'].value_counts()

3.0    18618
5.0    11741
4.0     9877
1.0     6223
2.0     4225
Name: previous_year_rating, dtype: int64
# NaN이 있는 행은 삭제
hr_df = hr_df.dropna()

hr_df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 48660 entries, 0 to 54807
Data columns (total 13 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   employee_id           48660 non-null  int64  
 1   department            48660 non-null  object 
 2   region                48660 non-null  object 
 3   education             48660 non-null  object 
 4   gender                48660 non-null  object 
 5   recruitment_channel   48660 non-null  object 
 6   no_of_trainings       48660 non-null  int64  
 7   age                   48660 non-null  int64  
 8   previous_year_rating  48660 non-null  float64
 9   length_of_service     48660 non-null  int64  
 10  awards_won?           48660 non-null  int64  
 11  avg_training_score    48660 non-null  int64  
 12  is_promoted           48660 non-null  int64  
dtypes: float64(1), int64(7), object(5)
memory usage: 5.2+ MB

- dtype이 object인 컬럼들 살펴보기

for i in ['department','region','education','gender','recruitment_channel']:
    print(i , hr_df[i].nunique())

department 9
region 34
education 3
gender 2
recruitment_channel 3

- region을 원 핫 인코딩 하면 컬럼수가 많이 늘어나지만(총 59개가 됨) 결과와 아예 관계가 없다고 보기 어려워서 삭제하지 않고 원 핫 인코딩 진행.

hr_df = pd.get_dummies(hr_df, columns = ['department','region','education','gender','recruitment_channel'])

 

2. 로지스틱 회귀

https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html?highlight=logistic%20regression#sklearn.linear_model.LogisticRegression

 

sklearn.linear_model.LogisticRegression

Examples using sklearn.linear_model.LogisticRegression: Release Highlights for scikit-learn 1.3 Release Highlights for scikit-learn 1.1 Release Highlights for scikit-learn 1.0 Release Highlights fo...

scikit-learn.org

로지스틱 회귀는 둘 중 하나를 결정하는 이진 분류 문제를 해결하기 위한 대표적인 알고리즘입니다. 입력 데이터와 가중치의 선형 조합으로 선형 방정식을 만들고, 그 결과를 시그모이드 함수를 사용하여 0과 1 사이의 확률값으로 변환합니다.

다중 클래스 분류

로지스틱 회귀는 이진 분류뿐만 아니라 3개 이상의 클래스(다중 클래스)에 대한 판별도 가능합니다. 이를 위해 주로 두 가지 전략을 사용합니다.

  1. OvR(One-vs-Rest): 각 클래스마다 하나의 이진 분류기를 생성하고, 해당 클래스를 기준으로 그 클래스와 나머지 모든 클래스를 구분하는 이진 분류를 수행합니다. 가장 높은 확률을 가진 클래스를 선택합니다.
  2. OvO(One-vs-One): 클래스의 개수가 N이면 N(N-1)/2개의 이진 분류기를 만들어 각각 두 클래스를 구분합니다. 입력 데이터를 각 이진 분류기에 통과시켜 가장 많이 선택된 클래스를 최종 클래스로 선택합니다.

대부분의 경우에는 OvR 전략을 선호합니다. 이는 각 클래스를 나머지 클래스와 구분짓는 단일 이진 분류기를 사용하기 때문에 계산이 효율적입니다. 그러나 클래스 간의 구분이 명확하지 않거나 데이터가 한쪽으로 치우쳐진 경우에는 OvO를 고려할 수 있습니다.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(hr_df.drop('is_promoted', axis=1), hr_df['is_promoted'], test_size = 0.2, random_state = 10)

 

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()
lr.fit(X_train, y_train)

pred = lr.predict(X_test)
from sklearn.metrics import accuracy_score, confusion_matrix
accuracy_score(y_test, pred)
// 0.9114262227702425
hr_df['is_promoted'].value_counts()
0    44428
1     4232
Name: is_promoted, dtype: int64
# 승진한 사람이 총 인원의 10%도 안됨.

✔ 승진하는 사람의 %가 매우 적기 때문에 모두 승진 못한다고 판단해도 정답률이 높게 나올 수 있음.

3. 혼돈 행렬 (confusion matrix)

혼돈 행렬(confusion matrix)은 분류 모델의 성능을 평가하기 위해 사용되는 표입니다. 이 행렬은 모델의 예측 결과와 실제 클래스 레이블을 비교하여 모델이 얼마나 잘 동작하는지를 시각화합니다. 주로 이진 분류 문제에서 사용되지만, 다중 클래스 분류에서도 확장하여 적용할 수 있습니다.

혼돈 행렬은 다음 네 가지 항목으로 구성됩니다:

  1. True Positive (TP): 모델이 양성 클래스를 정확하게 예측한 횟수입니다. 실제 값과 예측 값이 모두 양성인 경우에 해당합니다. 예) TP : 승진했는데, 승진했다고 예측
  2. True Negative (TN): 모델이 음성 클래스를 정확하게 예측한 횟수입니다. 실제 값과 예측 값이 모두 음성인 경우에 해당합니다. 예) TN : 승진하지 못했는데, 승진하지 못했다고 예측
  3. False Positive (FP): 모델이 양성 클래스로 예측했지만 실제 값은 음성인 경우입니다. 잘못된 양성 예측 횟수를 나타냅니다. 예) FP : 승진했는데, 승진하지 못했다고 예측
  4. False Negative (FN): 모델이 음성 클래스로 예측했지만 실제 값은 양성인 경우입니다. 잘못된 음성 예측 횟수를 나타냅니다. 예) FN : 승진하지 못했는데, 승진 했다고 예측

- 혼돈 행렬을 통해 다양한 성능 지표를 계산

  • 정밀도 (Precision): 정밀도는 무조건 양성으로 판단해서 모델이 얼마나 제대로 맞췄는지를 측정하는 지표입니다. 이 지표는 모델이 양성으로 예측한 경우 중에서 실제로 양성인 비율을 나타냅니다. TP / (TP + FP)
  • 재현율 (Recall 또는 민감도(Sensitivity)): 재현율은 실제 양성 중에서 모델이 양성으로 정확하게 예측한 비율을 나타냅니다. 재현율은 모델이 1이라고 예측한 경우 중에서 얼마나 제대로 맞췄는지를 나타냅니다. TP / (TP + FN)
  • " F1 점수 (F1 Score)" : 정밀도와 재현율의 조화평균을 나타내는 지표. F1 점수는 두 가지 중요한 성능 지표를 하나로 종합하는 데 사용되어 모델의 품질을 더 정확하게 평가할 수 있도록 도와줍니다. 

정밀도      재현율      산술평균        조화평균
0.4         0.6         0.5             0.48
0.3         0.7         0.5             0.42
0.5         0.5         0.5             0.5

조금 더 쉽게 풀어보자면, "정밀도는 환자가 와서 암이라고 진단했을 때, 정말 암일 확률이고, 재현율은 암인 환자가 왔을 때, 정확히 진단할 확률"입니다. F1 점수 (F1 Score)는 정밀도와 재현율이 가장 높은 지점을 나타냅니다.

정밀도와 재현율은 서로 상충 관계에 있습니다. 모델이 정밀도를 높이려고 할수록 재현율은 감소하고, 그 반대도 마찬가지입니다. 모델의 목표와 상황에 따라 어떤 지표를 중시할지 결정할 필요가 있습니다.

# 혼돈 행렬
confusion_matrix(y_test, pred)
array([[8869,    0],
       [ 862,    1]])
  TN(8869)	FP(0)
  FN(862)	TP(0)
from sklearn.metrics import precision_score, recall_score, f1_score
# 정밀도
precision_score(y_test, pred)
// 1.0

정밀도가 1이라는 것은 승진한다고 판단한 데이터는 모두 맞췄다는 의미.

# 재현율
recall_score(y_test, pred)
// 0.0011587485515643105

실제 승진한 사람중에 승진했다고 맞힌 확률

# 컬럼별 선의 기울기(종속변수 제외)
lr.coef_

array([[-5.42682567e-06, -2.11566320e-01, -1.24739314e-01,
         4.04217840e-01,  8.39462548e-02,  1.19382822e-01,
         1.24469097e-02, -4.53116409e-02, -1.59556720e-02,
        -1.69079211e-02, -1.06814883e-02,  3.10169499e-02,
         3.47912379e-03, -1.69516987e-02, -1.37996914e-02,
        -1.51941604e-02, -2.88706914e-03, -2.41993427e-03,
        -1.84270273e-02, -6.87835341e-03, -2.43612077e-03,
        -5.00401302e-03, -7.32654108e-03, -7.67662052e-03,
         7.71412885e-03, -3.21353065e-04, -6.68708228e-03,
         2.55409975e-02, -1.02529819e-02, -7.25376472e-03,
         9.23097034e-03,  9.63606751e-03, -9.39043583e-03,
         5.16728257e-03, -2.15726175e-02, -1.16101632e-02,
         9.39154969e-03, -1.82813463e-02,  1.50261133e-03,
        -1.97860883e-03, -2.28665036e-02, -1.35441186e-02,
        -5.26900930e-03, -6.03421587e-03,  3.29129457e-02,
        -1.48312957e-02, -1.18516648e-02,  2.54568502e-02,
        -2.39518611e-03, -9.66357577e-03, -2.00440936e-01,
        -1.40474208e-02,  1.14182158e-01, -1.53689321e-02,
        -8.49372671e-02, -6.62000218e-02,  4.04855793e-03,
        -3.81547353e-02]])

Logistic Regression은 선형 모델이기 때문에 각 feature에 대한 계수가 있으며, 이 계수는 해당 feature의 중요도를 나타냅니다. 각 feature의 계수는 해당 feature가 출력에 어떤 영향을 미치는지를 나타냅니다. 양수인 경우 해당 feature가 증가하면 출력 값이 증가하고, 음수인 경우 해당 feature가 증가하면 출력 값이 감소합니다.

 

# 독립변수
TempX = hr_df[['avg_training_score', 'previous_year_rating']]
# 종속변수
tempY = hr_df['is_promoted']

temp_lr = LogisticRegression()
# 학습시키기
temp_lr.fit(TempX, tempY)

# 새로운 데이터 만들기
temp_df = pd.DataFrame({'avg_training_score' : [60 , 80, 100],
                        'previous_year_rating' : [5.0, 4.5, 5.0]})
temp_df

# 새로 만든 데이터 예측시키기
pred = temp_lr.predict(temp_df)
pred // array([0, 0, 0])

모두 승진하지 못한다고(0) 예측했습니다.

temp_lr.coef_
// array([[0.04565839, 0.51245263]])

temp_lr.intercept_
// array([-7.28583474])

모델이 y = ax + b 형태로 표현될 때, temp_lr.coef_의 결과 값은 각 계수의 값을 나타내며, temp_lr.intercept_는 상수 를 나타냅니다. avg_training_score의 계수 (0.04565839), previous_year_rating의 계수 (0.51245263). temp_lr.intercept_ 모든 feature의 값이 0일 때의 예측값에 더해지는 상수(-7.28583474)입니다.

proba = temp_lr.predict_proba(temp_df)
proba
	# [0일 확률, 1일 확률]
array([[0.87911419, 0.12088581],
       [0.790365  , 0.209635  ],
       [0.53935167, 0.46064833]])

- 세번째 데이터는 아깝게 승진을 못했습니다.(1일 확률 0.46)

# 임계값 변경
threshold = 0.4
pred = (proba > threshold).astype(int)
# proba가 threshold(0.4)보다 크면(True)를 int로 변경

pred // array([0, 0, 1])

임계값을 0.4로 낮춰서 세번째 데이터는 승진하는것으로 예측되었습니다.

낮은 임계값을 사용하면, 더 많은 샘플이 양성 클래스로 분류되어 재현율이 증가하고 정밀도가 감소함.

4. 교차 검증(Cross Validation)

교차 검증(Cross Validation)은 머신러닝 모델의 성능을 평가하고 일반화 성능을 높이기 위한 효과적인 방법 중 하나입니다. train_test_split에서 데이터의 섞임에 따라 성능이 좌우되는 문제를 해결하기 위해 사용합니다. 데이터 분할, 훈련, 평가의 과정을 여러 번 반복하면서 다양한 훈련 및 평가 세트를 사용하는 것이 교차 검증의 핵심입니다. 대표적으로 k-겹 교차 검증(k-fold cross-validation)이 널리 사용됩니다. 크로스벨리데이션을 사용하는 이유는 결과를 좋게 하기 위함이 아니라 믿을 만한 검증을 하기 위함입니다.

from sklearn.model_selection import KFold
kf = KFold(n_splits=5)
kf // KFold(n_splits=5, random_state=None, shuffle=False)
for train_index, test_index in kf.split(range(len(hr_df))):
    print(train_index, test_index)
    print(len(train_index), len(test_index))
    
[ 9732  9733  9734 ... 48657 48658 48659] [   0    1    2 ... 9729 9730 9731]
38928 9732
[    0     1     2 ... 48657 48658 48659] [ 9732  9733  9734 ... 19461 19462 19463]
38928 9732
[    0     1     2 ... 48657 48658 48659] [19464 19465 19466 ... 29193 29194 29195]
38928 9732
[    0     1     2 ... 48657 48658 48659] [29196 29197 29198 ... 38925 38926 38927]
38928 9732
[    0     1     2 ... 38925 38926 38927] [38928 38929 38930 ... 48657 48658 48659]
38928 9732

위에 결과값을 확인해보면  KFold(n_splits=5)를 그냥 쓰면 섞이지 않고 그대로 쪼개기만 한다는 것을 알 수 있습니다.

kf = KFold(n_splits=5, random_state=2023, shuffle=True)
kf // KFold(n_splits=5, random_state=2023, shuffle=True)
for train_index, test_index in kf.split(range(len(hr_df))):
    print(train_index, test_index)
    print(len(train_index), len(test_index))

[    1     2     3 ... 48656 48658 48659] [    0     7    13 ... 48634 48645 48657]
38928 9732
[    0     1     3 ... 48656 48657 48659] [    2    12    16 ... 48642 48644 48658]
38928 9732
[    0     1     2 ... 48657 48658 48659] [    5     6    17 ... 48652 48653 48655]
38928 9732
[    0     1     2 ... 48657 48658 48659] [    4     8    10 ... 48650 48654 48656]
38928 9732
[    0     2     4 ... 48656 48657 48658] [    1     3     9 ... 48648 48649 48659]
38928 9732

( random_state=2023, shuffle=True ) 파라미터를 통해 데이터가 랜덤으로 섞였다는 것을 확인 할 수 있습니다.

acc_list = []

for train_index, test_index in kf.split(range(len(hr_df))):
    X = hr_df.drop('is_promoted', axis = 1)
    y = hr_df['is_promoted']
    X_train = X.iloc[train_index]
    X_test = X.iloc[test_index]
    y_train = y.iloc[train_index]
    y_test = y.iloc[test_index]

    lr = LogisticRegression()
    lr.fit(X_train, y_train)
    pred = lr.predict(X_test)
    acc_list.append(accuracy_score(y_test, pred))
acc_list
//
[0.9169749280723387,
 0.9110152075626798,
 0.9126592683929305,
 0.913481298808056,
 0.9110152075626798]
# 결과의 평균
np.array(acc_list).mean()
//0.9130291820797372

 

'AI' 카테고리의 다른 글

랜덤 포레스트(데이터 전처리)  (0) 2023.12.28
서포트 벡터 머신  (0) 2023.12.28
의사 결정 나무(자전거 대여 예제)  (0) 2023.12.26
선형 회귀(랜트비 예측)  (1) 2023.12.26
타이타닉 데이터셋  (0) 2023.12.26