AI/머신러닝 - 예제

[머신러닝 - 예제] Human Resource 데이터셋 - 로지스틱 회귀

caramel-bottle 2023. 12. 27.

1. 로지스틱 회귀

로지스틱 회귀는 둘 중 하나를 결정하는 문제(이진 분류)를 풀기 위한 대표적인 알고리즘이다.


2. hr 데이터셋

hr 데이터셋은 직원정보와 승진여부에 대한 데이터이다.

hr.csv
3.58MB

 

직원 데이터를 통해 승진여부를 예측해보자.

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import datetime as dt

hr_df = pd.read_csv('/content/drive/MyDrive/KDT/머신러닝과 딥러닝/data/hr.csv')

# 요약 정보
hr_df.info()

output>>

<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

 

# 수치적 해석
hr_df.describe()

output>>

 


3. 관계 시각화

각각의 데이터들이 is_promoted와 관련이 있는지 시각적으로 알아보기 위해 seaborn과 matplotlib를 사용한다.

3-1. previous_year_rating

# 관계 시각화
sns.barplot(x='previous_year_rating', y='is_promoted', data=hr_df)

output>>

막대 상단의 검은 선은 오차 막대로 해당 막대의 신뢰구간을 의미한다.

 

신뢰구간이 크다는 것은 데이터가 적어서 오차가 크다는 의미이다.

 

# 관계 시각화
sns.lineplot(x='previous_year_rating', y='is_promoted', data=hr_df)

output>>

같은 데이터를 선 그래프로 확인한 것이다. 막대 그래프와 마찬가지로 선 뒤에 반투명한 영역이 신뢰구간을 의미한다.

 

previous_year_rating(이전 년도 고과 점수)은 승진과 어느정도 연관이 있는 것으로 보인다.


3-2. avg_training_score

# 관계 시각화
sns.lineplot(x='avg_training_score', y='is_promoted', data=hr_df)

output>>

avg_training_score(평균 고과 점수)는 지금까지 받은 트레이닝에 대한 점수를 의미하는 것 같다.

 

90점에서 100점 사이가 되면 거의 90% 이상 승진을 하는 것으로 보인다.

 

분석 결과 평균 고과 점수는 승진에 가장 직접적인 요소로 판단이 된다.


3-3. recruitment_channel

# 관계 시각화
sns.barplot(x='recruitment_channel', y='is_promoted', data=hr_df)

output>>

recruitment_channel(채용 방법)

sourcing: 조직이 인재를 발견하고 찾아내는 활동

other: 그 외

referred: 직원을 통해 추천된 지원자

 

referred의 막대 심지가 유난히 길다. 이는 데이터가 적어 오차 범위가 넓기 때문이다.

# 데이터 개수 확인
# 심지가 길면 오차가 크다
hr_df["recruitment_channel"].value_counts()

output>>

other       30446
sourcing    23220
referred     1142
Name: recruitment_channel, dtype: int64

3-4. gender

# 관계 시각화
sns.barplot(x='gender', y='is_promoted', data=hr_df)

output>>

gender(성별)가 승진여부에 연관이 있는지는 명확하게 알 수 없다.

# 데이터 개수 확인
hr_df["gender"].value_counts()

output>>

m    38496
f    16312
Name: gender, dtype: int64

3-5. department

# 관계 시각화
sns.barplot(x='department', y='is_promoted', data=hr_df)
plt.xticks(rotation=45)
plt.show()

output>>

부서별 승진 확률이다. 기술관련 부서가 가장 승진률이 높다.

# 데이터 개수
hr_df['department'].value_counts()

output>>

Sales & Marketing    16840
Operations           11348
Technology            7138
Procurement           7138
Analytics             5352
Finance               2536
HR                    2418
Legal                 1039
R&D                    999
Name: department, dtype: int64

 

부서별 인원수의 차이가 많이 나지만 승진률에 차이가 있기 때문에 독립변수로 사용해도 좋을 것 같다.


3-6. region

# 관계 시각화
plt.figure(figsize=(14, 10))
sns.barplot(x='region', y='is_promoted', data=hr_df)
plt.xticks(rotation=45)

output>>


4. 결측치

# na 확인
hr_df.isna().mean()

output>>

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

 

education, previous_year_rating에 NaN 존재.

4-1. education

# 데이터 개수
hr_df['education'].value_counts(dropna=False)

output>>

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

 

education의 종류는 Bachelor's (학사), Master's & above (석사 및 그 이상), Below Secondary (중졸 미만), NaN

 

NaN을 대체할만한 데이터가 없는 경우에는 삭제하는 것도 좋은 방법이다.


4-2. previous_year_rating

# 데이터 개수
hr_df['previous_year_rating'].value_counts(dropna=False)

output>>

3.0    18618
5.0    11741
4.0     9877
1.0     6223
2.0     4225
NaN     4124
Name: previous_year_rating, dtype: int64

 

previous_year_rating(이전 년도 고과 점수)가 NaN인 데이터는 평균으로 대체하거나 0.0으로 만드는 것도 방법이지만 데이터 품질에 악영향을 줄 수도 있기 때문에 지운다.

 

# 결측치 제거
hr_df = hr_df.dropna()

4-3. One Hot Encoding

# 데이터 확인
hr_df.info()

output>>

<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

 

문자열 형식의 데이터들은 머신러닝 학습에 사용할 수 없으니 원 핫 인코딩을 해야한다.

 

그 전에 각 column의 value 집합의 크기를 확인한다.

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

output>>

department 9
region 34
education 3
gender 2
recruitment_channel 3

 

region이 조금 많기는 하지만 이정도는 원 핫 인코딩해도 괜찮다.

 

하지만 column의 증가는 성능 저하에 영향을 주기 때문에 잘 고려해야한다.

# 원 핫 인코딩
hr_df = pd.get_dummies(hr_df, columns=['department', 'region', 'education', 'gender', 'recruitment_channel'])

5. 로지스틱 회귀( Logistic Regression )

  • 둘 중 하나를 결정하는 문제(이진 분류)를 풀기 위한 대표적인 알고리즘
  • 입력 데이터와 가중치의 선형 조합으로 선형 방적식을 만듦 -> 선형 방적식의 결과를 0과 1사이의 확률값으로 변환(시그모이드 함수)
  • 3개 이상의 클래스에 대한 판별을 할 수 있음
    • OvR(One-vs-Rest): 각 클래스마다 하나의 이진 분류기를 만들고, 해당 클래스를 기준으로 그 클래스와 나머지 모든 클래스를 구분하는 이진 분류를 수행 -> 가장 높은 확률을 가진 클래스를 선택
    • OvO(One-vs-One): 클래스의 개수가 N인 경우 N(N-1)/2개의 이진 분류기를 만듦 -> 각 이진 분류기는 두 개의 클래스만 구분하는데, 해당 클래스와 나머지 클래스 간에 이진 분류를 수행 -> 입력 데이터를 각 이진 분류기에 넣어 가장 많이 선택된 클래스를 최종 선택

대부분 OvR 전략을 선호하지만, 클래스 간의 구분이 명확하지 않거나 데이터가 한쪽으로 치우친 경우 OvO를 고려한다.

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix

lr = LogisticRegression()

lr.fit(X_train, y_train)

pred = lr.predict(X_test)
# 점수
accuracy_score(y_test, pred)

output>>

0.9114262227702425

 


6. 분류 성능 평가 지표 & 분류 성능 평가

2023.12.27 - [분류 전체보기] - [머신러닝] Confusion Matrix

 

[머신러닝] Confusion Matrix

혼돈행렬 선형 모델의 성능은 MSE, MAE, RMSE등으로 평가된다. 분류 모델은 Confusion Matrix(혼돈 행렬)를 사용하여 Perfomance Test를 한다. 혼돈 행렬을 사용하여 정밀도(Precision), 재현률(Recall), 정확도(Accur

caramelbottle.tistory.com

 

# 정밀도, 재현율(민감도), 조화평균
from sklearn.metrics import precision_score, recall_score, f1_score

6-1. precisioon_score

# 정밀도
precision_score(y_test, pred)

output>>

1.0

6-2. recall_score

# 재현율(민감도)
recall_score(y_test, pred)

output>>

0.0011587485515643105

6-3. f1_score

# 조화평균
f1_score(y_test, pred)

output>>

0.0023148148148148147

6-4. 기울기(coef_)

# 기울기
lr.coef_

output>>

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]])

7. predict_proba

이진 분류의 경우 시그모이드 함수의 한 종류인 로지스틱 함수를 사용하여 각 클래스에 속할 확률을 계산하고 반환한다.

 

다중 클래스 문제의 경우 소프트맥스 함수를 사용하여 각 클래스에 속할 확률을 계산하고 반환한다

 

승진하냐 못하냐는 이진 분류이므로 로지스틱 함수를 사용한다.

7-1. awards_won?

# 관계 시각화
sns.barplot(x='awards_won?', y='is_promoted', data=hr_df)

output>>

승진 여부에 영향을 주는 또다른 column이 있어 추가하도록 한다.


7-2. predict

# 독립변수
TempX = hr_df[['avg_training_score', 'previous_year_rating', 'awards_won?']]

# 종속변수
tempY = hr_df['is_promoted']

temp_lr = LogisticRegression()

temp_lr.fit(TempX, tempY)

# input data
temp_df = pd.DataFrame({'avg_training_score' : [60, 80, 100],
                        'previous_year_rating': [5.0, 3.5, 5.0],
                        'awards_won?':[0, 1, 1]})
                        
# predict
pred = temp_lr.predict(temp_df)

# 예측 결과 출력
print(pred)

 

output>>

[0 1 1]

 

로지스틱 회귀 모델은 

$$ y=a_{1}x + a_{2}x + ... + a_{n}x + b$$

형태이다.


7-3. coef_

coef_를 사용하여 기울기를 확인할 수 있다.

# 기울기
temp_lr.coef_

output>>

array([[0.04309755, 0.51112537, 2.09583168]])

7-4. intercept_

intercept_를 사용하여 절편을 확인할 수 있다.

# 절편
temp_lr.intercept_

output>>

array([-7.20658921])

7-5. predict_proba

predict_proba를 사용하여 이진 분류 항목에 속할 확률을 확인할 수 있다.

# 확률 예측
proba = temp_lr.predict_proba(temp_df)

output>>

array([[0.88746545, 0.11253455],
       [0.46854527, 0.53145473],
       [0.14746488, 0.85253512]])

 

각 리스트의 0번 인덱스는 0에 속할 확률, 1번 인덱스는 1에 속할 확률이다.

 

승진할 확률만 보고 싶다면

# 승진할 확률
proba = temp_lr.predict_proba(temp_df)[:, 1]

output>>

array([0.11253455, 0.53145473, 0.85253512])

7-6. Threshold 변경

임계값(Treshold)를 변경하여 더 많은 샘플이 양성 클래스로 분류되도록 할 수 있다.

 

임계값을 낮춰서 더 많은 샘플이 양성 클래스로 분류된다면 재현률(Recall)이 증가하고 정밀도(Precision)이 감소한다.

 

# 임계값 변경
threshold = 0.4
pred = (proba > threshold).astype(int)

output>>

array([0, 1, 1])

 

이번의 경우 임계값을 0.4로 낮춰도 결과에 변화는 없다.


끝내며

로지스틱 회귀는 이름은 회귀지만 분류에 사용된다. 

이진 분류 모델에는 로지스틱 회귀, SVM, Decision Tree, 랜덤 포레스트 등이 사용될 수 있다.

간단한 문제의 경우 로지스틱 회귀 기법이 가장 효과적이지 않나?

 

로지스틱 회귀는 이진 분류 외에도 3개 이상의 클래스를 분류할 수 있음(OvR)

proba는 각 클래스에 속할 확률을 반환함

다중 선형 회귀 기법과 다중 클래스 분류는 다른 개념임

 

 

 

 

 

 

 

댓글