15-1 머신러닝 모델 알아보기

머신러닝 모델이란?

머신러닝 모델 만들기 = 함수 만들기

머신러닝 모델은 함수와 비슷함

함수에 값을 입력하면 규칙에 따라 계산한 값을 출력하듯이 머신러닝 모델도 값을 입력하면 정해진 규칙에 따라 계산한 예측값을 출력

머신러닝 모델이 함수와 다른 점은 만드는 방법

함수를 만들 때는 사람이 계산 규칙을 정해 입력해야 하지만 머신러닝 모델을 만들 때는 사람이 계산 규칙을 정하지 않고 컴퓨터가 데이터에서 패턴을 찾아 스스로 규칙을 정하게 함

 

예를 들어 환자의 정보를 입력하면 당뇨병 발병 여부를 예측하는 모델을 만든다고 하자

사람이 해야 할 일은 여러 환자의 정보와 당뇨병 발병 여부 데이터를 수집해 머신러닝 알고리즘에 입력하는 것 뿐

그러면 컴퓨터는 환자의 정보와 당뇨병 발병 간의 패턴을 찾아낸 다음 환자의 정보를 입력하면 당뇨병 발병 여부를 출력하는 모델을 만듦

 

 

예측 변수와 타겟 변수

머신러닝 모델을 만들 때 두 종류의 변수를 사용

* 예측 변수: 예측하는 데 활용하는 변수 & 모델에 입력하는 값

* 타겟 변수: 예측하고자 하는 변수 & 모델이 출력하는 값

 

예를 들어 환자의 성별, 나이, 흡연 여부, 음주 여부로 당뇨병 발병을 예측하는 모델을 만든다 하자

이때 성별, 나이, 흡연 여부, 음주 여부는 예측 변수로 사용하고, 당뇨병 발병 여부는 타겟 변수로 사용

 

 

머신러닝 모델을 이용해 미래 예측하기

머신러닝 모델은 미래의 값을 예측하는 용도로 자주 사용

과거의 값을 예측 변수로 사용하고 미래의 값을 타겟 변수로 사용하면 미래를 예측하는 모델을 만들 수 있음

머신러닝 모델은 다양한 사업 영역에서 미래를 예측하는 데 활용됨

미래를 예측하면 부가 수익을 창출하고 비용을 줄일 수 있으므로 머신러닝은 여러 분야에서 주목받는 기술

 

 

 

의사결정나무 모델

의사결정나무 모델이란?

* 의사결정나무 모델: 순서대로 주어진 질문에 yes/no로 답하면 마지막에 결론을 얻는 구조로 되어 있음

질문이 나열된 모양이 가지를 뻗은 '나무'와 비슷하고, 예측값을 무엇으로 할지 '의사 결정'해 주기 때문에 의사결정나무라는 이름을 가지고 있음

 

 

의사결정나무 모델을 이용해 예측하기

의사결정나무 모델을 만들면 yes/no로 답할 수 있는 질문 목록을 갖게 됨

새 데이터가 주어졌을 때 질문 목록에 따라 순서대로 답을 하면 최종적으로 둘 중 한 가지 값을 부여받게 됨

 

* 이진 분류 모델: 둘 중 한 가지 값으로 분류하는 모델

* 다중 분류 모델: 셋 이상의 값으로 분류하는 모델(의사결정나무 모델을 이용하면 만들 수 있음)

 

 

의사결정나무 모델이 만들어지는 원리

다음과 같이 4가지 예측 변수로 당뇨병 발병 여부를 예측하는 모델을 만든다고 가정하고 모델이 만들어지는 과정을 단계별로 살펴볼 것

- 예측 변수: 흡연 여부, 음주 여부, 성별, 나이

- 타겟 변수: 당뇨병 발병 여부

 

1단계. 타겟 변수를 가장 잘 분리하는 예측 변수 선택하기

의사결정나무 모델은 어떤 순서로 어떤 질문을 할지 정하는 과정을 거쳐 만들어짐

가장 먼저 첫 번째 질문을 할 때 예측 변수 중에서 무엇을 사용할지 정함

의사결정나무 알고리즘은 '타겟 변수를 가장 잘 분리해 주는 예측 변수'를 찾아 첫 번째 질문으로 삼음

 

예측 변수가 타겟 변수를 분리해 주는 정도는 다음 절차로 알 수 있음

1. 모든 예측 변수를 yes/no로 답할 수 있는 질문으로 만듦

   ex) '흡연 여부'는 '흡연을 하십니까?', '성별'은 '남자입니까?' 또는 '여자입니까?' 등의 형태

2. 모델을 만드는데 사용할 데이터를 앞에서 만든 각각의 질문에 대입한 다음 'yes'로 답한 데이터만 추출

3. 추출한 데이터 중 발병인과 정상인의 비율을 구함

   발병인과 정상인의 비율 차이가 크면 클수록 예측 변수가 타겟 변수를 잘 분리해낸다고 볼 수 있음

 

앞에서 살펴본 변수는 범주 변수이므로 yes/no로 답할 수 있는 질문을 곧바로 만들 수 있으나,

나이와 같은 연속 변수는 질문을 하기 위해 타겟 변수를 가장 잘 분리해 주는 기준을 정해야 하며,

이를 위해 가능한 모든 경우의 수대로 질문 후보를 만들어 비교하면 됨

이처럼 모든 예측 변수로 질문을 만든 다음 타겟 변수를 가장 잘 분리해 주는 변수를 찾아 첫 번째 질문에 사용

 

 

2단계. 첫 번째 질문의 답변에 따라 데이터를 두 노드로 분할하기

* 노드 - 질문의 답변이 같아서 함께 분류된 집단

           - 의사결정나무 도식에 사각형으로 표현됨

 

 

3단계. 각 노드에서 타겟 변수를 가장 잘 분리하는 예측 변수 선택하기

각 노드에서 타겟 변수를 가장 잘 분리해주는 예측 변수를 선택

1단계 작업을 노드별로 반복하는 것

'흡연 여부'는 이미 사용했으니 제외하고 나머지 변수 중에서 타겟 변수를 가장 잘 분리해 주는 두 번째 예측 변수 찾기

 

 

4단계. 노드가 완벽하게 분리될 때까지 반복하기

노드에 한 범주만 남아 완벽하게 분리될 때까지 변수를 선택하고 노드를 분할하는 과정을 반복

노드에 발병과 정상 중 한 쪽 범주만 남으면 분할을 종료

 

 

의사결정나무 모델의 특징

노드마다 분할 횟수가 다르다

예측 변수를 선택하고 노드를 분할하는 횟수가 노드마다 다르므로 가지가 뻗어 나간 횟수도 노드마다 제각각

어떤 노드는 다섯 번 분할해야 한 범주만 남지만 어떤 노드는 두 번만에 한 범주만 남을 수 있음

따라서 모든 사람에게 같은 횟수로 질문하는 게 아니라 앞의 질문에 어떻게 답변했는지에 따라 서로 다른 횟수로 질문하게 됨

노드마다 선택되는 예측 변수가 다르다

예측 변수 선택 작업을 노드별로 따로 하기 때문에 노드마다 선택되는 변수가 다름

따라서 모든 사람에게 일괄적으로 같은 질문을 하는 게 아니라 앞의 질문에 어떻게 답변했는지에 따라 서로 다른 질문을 하게 됨

어떤 예측 변수는 모델에서 탈락한다

어떤 예측 변수는 노드를 분할하는 과정에서 한 번도 선택되지 않아 모델에서 탈락할 수 있음

데이터에 들어 있는 모든 변수를 예측에 사용하는 게 아니라 일부는 사용하고 일부는 제외하게 됨

 

 

 

 

15-2 소득 예측 모델 만들기

adult 데이터는 미국인의 성별, 인종, 직업, 학력 등 다양한 인적 정보를 담고 있는 인구 조사 데이터

adult 데이터를 이용해 인적 정보로 소득을 예측하는 의사결정나무 모델을 만들 것

 

<모델을 만드는 절차>------------------------------------------------------------------------------------------------------------------------------------

전처리    ->    모델 만들기    ->    예측 및 성능 평가

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

 

먼저 adult 데이터를 불러와 구조를 살펴볼 것

import pandas as pd
df = pd.read_csv('adult.csv')
df.info()

adult 데이터는 48842명의 정보를 담고 있으며 변수 15개로 구성됨

이 중 연소득을 나타낸 income을 타겟 변수, 나머지 14개를 예측 변수로 사용

 

소득 예측 모델을 어디에 활용할까?

   - 고가 상품 구매 프로모션

   소득 예측 모델은 고가의 상품을 구매하도록 독려하는 프로모션에 활용 가능

   소득이 높아 상품을 구매할 가능성이 높은 사람을 예측하면 프로모션 활동에 드는 시간과 비용을 절약할 수 있음

   - 저소득층 지원 대상 확대

   정부에서 저소득층 지원 사업 대상자를 찾아낼 때에도 소득 예측 모델을 활용할 수 있음

   저소득층을 선제적으로 찾아 지원하면 사람들이 스스로 지원 요건을 확인하고 신청하게 할 때보다

   복지 프로그램 대상을 확대할 수 있음

 

 

> 전처리하기

머신러닝 모델을 만들 때 가장 먼저 하는 작업은 모델을 만드는 데 적합하도록 데이터를 가공하는 것

본격적으로 모델을 만들기 전에 데이터를 처리하는 작업이므로 이를 데이터 전처리라고 함

 

1. 타겟 변수 전처리

먼저 타겟 변수 income을 검토하고 전처리

income은 조사 응답자의 연소득이 5만 달러를 초과하는지 여부를 나타냄

다음 코드의 출력 결과를 보면 연소득이 5만 달러를 초과하는 사람은 23.9%, 5만 달러 이하인 사람은 76%

df['income'].value_counts(normalize = True)

+ df.value_counts()에 normalize = True를 입력하면 범주의 비율을 구함

변수의 값에 특수 문자나 대소문자가 섞여 있으면 다루기 불편하므로 5만 달러를 초과하면 'high', 그렇지 않으면 'low'로 값을 수정

import numpy as np
df['income'] = np.where(df['income'] == '>50K', 'high', 'low')
df['income'].value_counts(normalize = True)

 

2. 불필요한 변수 제거하기

이름, 아이디, 주소 같은 변수는 대부분의 값이 고유값이어서 반복되는 패턴이 없고 타겟 변수와도 관련성 x

따라서 이런 변수는 타겟 변수를 예측하는 데 도움이 되지 않기 때문에 모델링 시간만 늘리는 역할을 하므로 제거해야 함

fnlwgt 또한 타겟 변수를 예측하는 데 도움이 되지 않으므로 모델을 만들 때 사용하지 않도록 제거

df = df.drop(columns = 'fnlwgt')

 

3. 문자 타입 변수를 숫자 타입으로 바꾸기

모델을 만드는 데 사용되는 모든 변수는 숫자 타입이어야 함

df.info()의 출력 결과를 보면 Dtype이 object인 문자 타입 변수들이 있으므로 이 변수들을 모델을 만드는 데 활용하려면 숫

자 타입으로 바꾸어야 함

 

* 원핫 인코딩하기: 변수의 범주가 특정 값이면 1, 그렇지 않으면 0으로 바꾸면 문자 타입 변수를 숫자 타입을 만들 수 있음

                              이렇게 값을 1과 0으로 바꾸는 방법

df의 sex를 추출해 원핫 인코딩하는 방법을 알아볼 것

다음 코드의 출력 결과를 보면 df_tmp의 sex는 'Male' 또는 'Female'로 되어 있는 문자 타입 변수

df_tmp = df[['sex']]
df_tmp.info()
df_tmp['sex'].value_counts()

* pd.get_dummies(): 데이터 프레임을 입력하면 문자 타입 변수를 원핫 인코딩을 적용해 변환

다음 출력 결과를 보면 sex가 사라지고 그 대신 sex_Female, sex_Male이 만들어짐

# df_tmp의 문자 타입 변수에 원핫 인코딩 적용
df_tmp = pd.get_dummies(df_tmp)
df_tmp.info()

+ 원핫 인코딩으로 만들어진 변수의 타입은 unit8, unit8은 0~255의 양수를 담을 수 있는 데이터 타입

sex_Female은 sex가 Female이면 1, 그렇지 않으면 0으로 된 변수

sex_Male은 sex가 Male이면 1, 그렇지 않으면 0으로 된 변수

두 변수 중 한쪽이 1이면 다른 한쪽은 반드시 0

df_tmp[['sex_Female', 'sex_Male']].head()

원핫 인코딩을 df에 적용

타겟 변수인 income만 원래대로 유지하고, 모든 문자 타입 변수를 원핫 인코딩할 것

target = df['income']             # income 추출

df = df.drop(columns = 'income')  # income 제거
df = pd.get_dummies(df)           # 문자 타입 변수 원핫 인코딩

df['income'] = target             # df에 target 삽입
df.info()

df.info() 출력 결과에 개별 변수의 정보가 출력되지 않은 이유는 변수가 100개 이하일 때만 변수 정보를 출력하도록 설정되어 있기 때문(df.info()에 max_cols = np.inf를 입력하면 변수의 수와 관계 없이 모든 변수의 정보를 출력)

출력 결과를 보면 변수가 108개로 늘어나고, 문자 타입 변수가 전부 숫자 타입으로 바뀌었다는 것을 알 수 있음

import numpy as np
df.info(max_cols = np.inf)

 

4. 데이터 분할하기

모델을 만들 때는 가지고 있는 모든 데이터를 사용하는 게 아니라 일부만 무작위로 추출해 사용해야 함

 

모든 데이터를 사용해 모델을 만들면 성능 평가 점수를 신뢰할 수 없다

 모델을 만들고 나면 모델이 타겟 변수를 얼마나 정확하게 예측하는지 알아보기 위해 성능을 평가

 그런데 모델을 만들 때 사용한 데이터를 성능을 평가할 때 그대로 다시 사용하면 평가 점수를 신뢰할 수 없게 됨

 예측 정확도가 높게 나오더라도 모델의 성능이 좋아서인지 아니면 이미 경험한 데이터라 잘 맞춘 것인지 알 수 x

크로스 밸리데이션: 신뢰할 수 있는 성능 평가 점수를 얻는 방법

 신뢰할 수 있는 성능 평가 점수를 얻으려면 가지고 있는 데이터에서 일부만 추출해 모델을 만들 때 사용하고,

 나머지는 남겨 두었다가 성능을 평가할 때 사용 -> 데이터를 훈련용과 평가용으로 나누어 사용하는 것

 이렇게 하면 모델이 한 번도 경험한 적 없는 데이터로 성능을 평가하므로 평과 점수를 신뢰할 수 있게 됨

* 크로스 밸리데이션(교차 검증): 데이터를 분할해 일부는 모델을 만들 때 사용하고 나머지는 평가할 때 사용하는 방법

   - 트레이닝 세트: 분할한 데이터 중 모델을 만들 때 사용하는 데이터

   - 테스트 세트: 분할한 데이터 중 성능을 평가할 때 사용하는 데이터

 

# adult 데이터 분할하기

scikit-learn 패키지를 이용해 데이터를 트레이닝 세트와 테스트 세트로 분할

skylearn.model_selection의 train_test_split()을 이용하면 데이터를 트레이닝 세트와 테스트 세트로 분할 가능

train_test_split()에는 다음과 같은 파라미터를 입력

- test_size

   테스트 세트의 비율

   트레이닝 세트와 테스트 세트의 비율은 보통 7:3 또는 8:2로 정함

   정답은 없지만 데이터가 많을수록 트레이닝 세트의 비율을 늘리고 반대로 데이터가 적을수록 테스트 세트의 비율을 늘림

- stratify

   범주별 비율을 통일할 변수

   stratify에 타겟 변수를 입력하면 트레이닝 세트와 테스트 세트에 타겟 변수의 범주별 비율을 비슷하게 맞춰줌

   타겟 변수를 입력하지 않으면 타겟 변수의 범주별 비율이 데이터 세트마다 달라지므로 평가 결과를 신뢰하기 어려움

- random_state

   난수 초깃값

   train_test_split()은 난수를 이용해 데이터를 무작위로 추출하므로 함수를 실행할 때마다 추출되는 데이터가 달라짐

   난수를 고정하면 코드를 반복 실행해도 항상 같은 데이터가 추출됨

 

다음 코드는 함수의 출력 결과를 df_train, df_test 두 변수에 할당하는 형태로 되어 있음

train_test_split()은 트레이닝 세트와 테스트 세트를 함께 출력

트레이닝 세트는 앞에 입력한 변수, 테스트 세트는 뒤에 입력한 변수에 할당

from sklearn.model_selection import train_test_split
df_train, df_test = train_test_split(df,
                                     test_size = 0.3,         # 테스트 세트 비율
                                     stratify = df['income'], # 타겟 변수 비율 유지
                                     random_state = 1234)     # 난수 고정

df.shape로 두 데이터 세트를 살펴보면 변수가 108개로 같고, 행의 수는 다름

# train
df_train.shape
# test
df_test.shape

타겟 변수의 범주별 비율은 두 데이터 세트 모두 비슷함

train_test_split()의 stratify에 타겟 변수를 지정했기 때문

# train
df_train['income'].value_counts(normalize = True)
#test
df_train['income'].value_counts(normalize = True)

 

 

 

> 의사결정나무 모델 만들기

전처리를 완료했으니 모델을 만들 것

모델을 만들 때는 df_train을 사용, df_test는 마지막에 모델을 평가할 때 사용

 

모델 설정하기

* sklearn의 tree.DecisionTreeClassifier(): 의사결정나무 모델을 만들 수 있음

먼저 모델을 만드는 데 사용할 clf를 만들 것

tree.DecisionTreeClassiFier()에는 다음과 같은 파라미터를 입력

- random_state

   난수 초깃값

   변수 선택 과정에서 난수를 이용하기 때문에 코드를 실행할 때마다 결과가 달라짐

   난수를 고정하면 코드를 여러 번 실행해도 결과가 항상 같음

- max_depth

   나무의 깊이

   노드를 최대 몇 번까지 분할할지 정함

   숫자가 클수록 노드를 여러 번 분할해 복잡한 모델을 만듦

   값을 지정하지 않으면 노드를 최대한 많이 분할

 

여기서는 단순한 모델을 만들도록 max_depth에 3을 입력

from sklearn import tree
clf = tree.DecisionTreeClassifier(random_state = 1234,  # 난수 고정
                                 max_depth = 3)         # 나무 깊이

+ clf는 classifier의 줄임말, 고소득/저소득처럼 데이터를 몇 개 중 하나로 분류하는 모델을 분류 모델 또는 분류기라고 함

 

 

모델 만들기

앞에서 만든 clf()를 이용해 모델을 만들 것

먼저 df_train에서 예측 변수와 타겟 변수를 각각 추출해 데이터 프레임을 만듦

그런 다음 clf.fit()의 X에는 예측 변수, y에는 타겟 변수를 입력

train_x = df_train.drop(columns = 'income') # 예측 변수 추출
train_y = df_train['income']                # 타겟 변수 추출

model = clf.fit(X = train_x, y = train_y)   # 모델 만들기

+ 모델을 만들 때 난수를 고정하는 이유

   의사결정나무 알고리즘은 타겟 변수를 가장 잘 분리해 주는 예측 변수를 선택해 노드를 분할

   그런데 여러 예측 변수가 똑같이 타겟 변수를 잘 분리해내는 경우가 있음

   이럴 때 난수를 이용해 무작위로 예측 변수를 선택하기 때문에 코드를 실행할 때마다 결과가 조금씩 달라짐

   코드를 여러 번 실행해도 항상 같은 결과가 나오게 하려면 난수를 고정해야 함

 

 

 

> 모델 구조 살펴보기

완성된 모델을 그래프로 만들어 구조를 살펴볼 것

* tree.plot_tree(): 모델을 시각화

먼저 그래프를 크고 선명하게 표현하도록 설정한 다음 그래프를 출력할 것

import matplotlib.pyplot as plt
plt.rcParams.update({'figure.dpi' : '100',          # 해상도 설정
                     'figure.figsize' : [12, 8]})   # 그래프 크기 설정

tree.plot_tree(model);                              # 그래프 출력

+ 그래프 출력 코드 뒤에 ;를 입력하면 메시지를 제외하고 그래프만 출력

tree.plot_tree()에 몇 가지 파라미터를 추가해 그래프를 보기 좋게 수정

tree.plot_tree(model,
              feature_names = train_x.columns,  # 예측 변수명
              class_names = ['high', 'low'],    # 타겟 변수 클래스, 알파벳순
              proportion = True,                # 비율 표기
              filled = True,                    # 색칠
              rounded = True,                   # 둥근 테두리
              impurity = False,                 # 불순도 표시
              label = 'root',                   # label 표시 위치
              fontsize = 10);                   # 글자 크기

 

노드의 값

그래프에서 가장 위에 있는 첫 번째 노드를 이용해 그래프를 해석하는 방법을 알아볼 것

다른 노드도 같은 순서로 값이 표현되어 있음

1) 전체 데이터의 몇 퍼센트가 해당 노드로 분류됐는지 나타냄

   첫 번째 노드는 아직 한 번도 나뉘지 않았으므로 데이터의 100%가 이 노드에 속함

   다음 단계의 노드로 내려가면 데이터가 여러 노드로 배분되므로 비율이 줄어듦

2) 타겟 변수의 클래스별 비율을 알파벳순으로 나타냄

   따라서 타겟 변수 income의 'high'와 'low' 순으로 비율이 표시되며

   값을 보면 전체 데이터 중 'high'가 23.9% 'low'가 76.1%라는 것을 알 수 있음

   + 타겟 변수의 범주를 클래스라고 함

3) 0.5를 기준으로 타겟 변수의 두 클래스 중 어느 쪽이 더 많은지 나타냄

   그림에선 'high'가 23.9%, 'low'가 76.1%로 'low'가 표시

4) 노드를 분리할 때 사용할 기준을 나타냄

   이 기준을 충족하는 데이터는 왼쪽, 충족하지 않는 데이터는 오른쪽 노드로 할당

   marital_status_Married-civ-spouse는 원핫 인코딩으로 만들어진 변수로 기혼이면 1, 비혼이면 0으로 되어 있음

   따라서 비혼이면 변수의 값이 0이므로 'martial_stats_Married-civ-spouse <= 0.5(비혼)' 조건을 충족해

   왼쪽 노드로 내려가고, 기혼이면 조건을 충족하지 않으므로 오른쪽 노드로 내려감

   즉 비혼은 왼쪽 노드, 기혼은 오른쪽 노드로 내려감

   다른 노드도 분리 기준이 표시되지만 마지막 단계의 '끝 노드'는 더 이상 나뉘지 않으므로 분리 기준이 표시 x

+ 첫 번째 단계의 노드를 뿌리 노드, 마지막 단계의 노드를 끝 노드라 함

   어떤 노드의 윗 단계에 있는 노드를 부모 노드, 아랫 단계에 있는 노드를 자식 노드라 함

 

- 왼쪽 노드

이번에는 첫 번째 노드의 조건에 따라 나뉘어진 두 번째 단계의 노드를 해석할 것

먼저 왼쪽 노드를 살펴보자

   첫 번째 노드의 조건 'marital_status_Married-civ-spouse <= 0.5(비혼)'을 충족한 비혼자가 이 노드에 할당됨

   비혼자는 전체의 554.2%

   비혼자의 income은 'high' 6.4%, 'low' 93.6%로 'low'가 더 많음

   다음으로 노드를 나누는 기준은 'captial_gain <= 7073.5'

- 오른쪽 노드

   첫 번째 노드의 조건 'marital_status_Married-civ-spouse <= 0.5(비혼)'을 충족하지 않은 기혼자가 이 노드에 할당됨

   기혼자는 전체의 45.8%

   기혼자의 income은 'high' 44.7%, 'low' 55.3%로 'low'가 더 많음

   다음으로 노드를 나누는 기준은 'education_num <= 12.5'

 

노드의 색

- 노드의 색깔

   노드의 색깔은 우세한 타겟 변수의 클래스에 따라 정해짐

   그래프를 보면 'high'의 비율이 높은 노드는 주황색, 'low'의 비율이 높은 노드는 파란색 계열로 표현

- 노드의 색농도

   노드의 색농도는 '한 클래스의 구성 비율이 우세한 정도'를 나타냄   

   한 클래스의 비율이 다른 클래스보다 높을수록 진하고, 두 클래스의 비율이 비슷할수록 농도가 연함

   두 번째 단계의 노드를 보면 왼쪽 노드는 'high'보다 'low'의 비율이 월등히 높아 농도가 진한 반면,

   오른쪽 노드는 두 클래스의 비율이 비슷하므로 농도가 연함

   + 순도: 한 클래스의 비율이 우세한 정도

 

 

 

> 모델을 이용해 예측하기

앞에서 만든 모델을 활용해 새 데이터의 타겟 변수를 예측하는 방법을 알아볼 것

먼저 모델을 만들 때 사용하지 않은 df_test에서 예측 변수와 타겟 변수를 각각 추출

test_x = df_test.drop(columns = 'income')  # 예측 변수 추출
test_y = df_test['income']                 # 타겟 변수 추출

* model.predict(): 모델을 이용해 새 데이터의 타겟 변수 값을 예측할 수 있음

 model.predict()에 test_x를 입력해 타겟 변수 예측값을 구한 다음 df_test['pred']에 할당할 것

df_test를 출력하면 가장 오른쪽에 pred가 만들어진 것을 확인 가능

pred는 모델이 train_x에 들어 있는 예측 변수만 이용해서 구한 값

# 예측값 구하기
df_test['pred'] = model.predict(test_x)
df_test

income과 pred의 값을 보면 모델의 예측이 맞았는지 알 수 있음

두 변수의 값이 같으면 예측이 맞은 것이고, 두 변수의 값이 다르면 예측이 틀린 것

 

 

 

> 성능 평가하기

예측값을 실제값과 대조해 예측이 얼마나 잘 맞았는지 모델의 성능을 평가할 것

성능 평가 지표는 종류가 다양하고 특징이 서로 달라서 어떤 지표가 높더라도 다른 지표는 낮을 수 있음

그러므로 모델을 사용하는 목적에 맞게 평가 기준으로 삼을 지표를 선택해야 함

 

confusion matrix 만들기

* 컨퓨전 매트릭스 - 모델이 예측한 값 중 맞은 경우와 틀린 경우의 빈도를 나타낸 것

                              - sklearn.metrics의 confusion_matrix()를 이용하면 컨퓨전 매트릭스를 만들 수 있음

confusion_matrix()에는 다음 파라미터를 입력

- y_true: 타겟 변수

- y_pred: 예측 변수

- labels: 클래스 배치 순서

from sklearn.metrics import confusion_matrix
conf_mat = confusion_matrix(y_true = df_test['income'],  # 실제값
                            y_pred = df_test['pred'],    # 예측값
                            labels = ['high', 'low'])    # 클래스 배치 순서
conf_mat

* skylearn.metrics의 ConfusionMatrixDisplay(): 컨퓨전 매트릭스로 히트맵 만들기

* 히트맵: 격자 위에 값을 표시하고 값이 클수록 셀의 색농도를 진하게 표현한 그래프

plt.rcParams.update(plt.rcParamsDefault)                     # 그래프 설정 되돌리기

from sklearn.metrics import ConfusionMatrixDisplay
p = ConfusionMatrixDisplay(confusion_matrix = conf_mat,      # 컨퓨전 매트릭스
                           display_labels = ('high', 'low')) # 타겟 변수 클래스명

p.plot(cmap = 'Blues')                                       # 컬러맵 적용해 출력

confusion matrix 해석하기

 컨퓨전 매트릭스의 행은 실제 빈도를 의미

   첫 번째 행은 income이 실제로 high인 사람, 두 번째 행은 실제로 low인 사람을 나타냄

 컨퓨전 매트릭스의 열은 예측한 빈도를 의미

   첫 번째 열은 모델이 high로 예측한 사람, 두 번째 열은 low로 예측한 사람을 나타냄

 

컨퓨전 매트릭스를 보면 예측이 맞은 빈도와 틀린 빈도를 알 수 있음

행과 열의 레이블이 같은 왼쪽 대각선의 두 셀은 예측이 맞은 빈도를 나타냄

반대로 레이블이 서로 다른 오른쪽 대각선의 두 셀은 예측이 틀린 빈도를 나타냄

 

                              

컨퓨전 매트릭스의 셀 이름

컨퓨전 매트릭스의 각 셀은 다음과 같은 단어로 표현

- 정답 여부(True/False): 모델의 예측값이 실제값과 일치하면 True, 일치하지 않으면 False

예측 클래스(Positive/Negative): 타겟 변수의 클래스 중 모델이 예측하고자 하는 관심 클래스는 Positive, 그 반대는 Negative(여기서는 모델의 목적이 고소득자를 찾아내는 것이므로 income이 high이면 Positive, low면 Negative)

-> 컨퓨전 매트릭스의 셀 이름은 True/False와 Positive/Negative의 첫 글자를 따서 TP, TN, FP, FN으로 줄여서 표현할 때가 많음

 

 

성능 평가 지표 구하기

성능 평가 지표를 구하면 모델의 예측이 얼마나 정확한지 알 수 있음

 

Accuracy

  accuracy(정확도) - 모델이 '예측해서 맞춘 비율'을 의미

                                - 컨퓨전 매트리스 전체 셀 합계에서 왼쪽 대각선 셀의 합계가 차지하는 비율

                                - 모델의 성능을 평가할 때 기본적으로 accuracy를 가장 먼저 구함

  accuracy는 앞에서 만든 conf_mat을 이용해 직접 계산할 수도 있지만,

  skylearn.metrics의 accuracy_score()을 이용하면 구할 수 있음

다음 코드의 출력 결과를 보면 accuracy가 약 84.3%라는 것을 알 수 있음

import sklearn.metrics as metrics
metrics.accuracy_score(y_true = df_test['income'],   # 실제값
                       y_pred = df_test['pred'])     # 예측값

accuracy는 타겟 변수의 클래스별 비율이 불균형하면 신뢰하기 어렵다는 제한점이 있음

이 장에서 사용한 adult 데이터는 연소득이 low에 해당하는 사람이 76%로 매우 많음

그러므로 어떤 데이터를 입력하든 항상 low로 예측하는 이상한 모델을 만들더라도 accuracy는 최소 76%가 됨

따라서 accuracy만 봐서는 점수가 높더라도 모델의 성능이 좋아서인지 아니면 자료가 불균형해서인지 판단할 수 x

 

Precision

  precision(정밀도) - 관심 클래스를 예측해서 맞춘 비율

                                - 컨퓨전 매트릭스 첫 번째 열의 셀 합계에서 위쪽 셀이 차지하는 비율을 구하면 precision이 됨

고소득자 예측 모델에서는 모델이 income을 high로 예측한 사람 중에서 실제로 high인 사람의 비율이 precision

다른 예로 당뇨병 예측 모델이라면 모델이 발병으로 예측한 사람 중에서 실제 발병한 사람의 비율이 precision

                                - metrics.precision_score()을 이용하면 precision을 구할 수 있음

다음 코드의 출력 결과를 보면 모델의 precision이 75.5%

따라서 모델이 income을 high로 예측한 사람 중 75.5%가 실제로 high이고, 나머지 24.5%는 실제로는 low인데 high로 잘못 분류한 것

metrics.precision_score(y_true = df_test['income'],  # 실제값
                        y_pred = df_test['pred'],    # 예측값
                        pos_label = 'high')          # 관심 클래스

 

Recall

  recall(재현율) - 모델이 실제 데이터에서 관심 클래스를 찾아낸 비율

                         컨퓨전 매트릭스 첫 번째 행의 셀 합계에서 왼쪽 셀이 차지하는 비율을 구하면 recall

고소득자 예측 모델에서는 income이 실제로 high인 사람 중에서 모델이 high로 예측해서 찾아낸 사람의 비율이 recall

다른 예로 당뇨병 예측 모델이라면 실제로 당뇨병 발병자 중에서 모델이 발병으로 예측해서 찾아낸 비율이 recall

(recall과 precision은 계산할 때 분모에 놓는 값이 다름

precision은 모델이 '관심 클래스로 예측한 빈도'를 분모에 놓고 구하는 반면, recall은 '실제 관심 클래스의 빈도'를 분모에 놓고 구함)

+ sensitivity(민감도)도 recall과 같은 뜻으로 쓰임

                        - metrics.recall_score()을 이용하면 recall을 구할 수 있음

다음 코드의 출력 결과를 보면 모델의 recall이 51.3%

따라서 실제로 income이 high인 사람 중에서 51.3%를 모델이 high로 맞게 예측해서 찾아냈고, 나머지 48.7%는 low로 잘못 예측해서 놓친 것

metrics.recall_score(y_true = df_test['income'],   # 실제값
                     y_pred = df_test['pred'],     # 예측값
                     pos_label = 'high')           # 관심 클래스

 

F1 score

  - recall과 precision이 모두 중요할 때 recall과 precision의 크기를 함께 반영한 것

  - F1 score는 recall과 precision의 조화 평균(0~1 사이의 값을 지니며 성능이 높을수록 1에 가까운 값이 됨)

  - recall과 precision을 곱해서 구하기 때문에 둘 중 하나라도 0이면 0이 됨

F1 score은 accuracy와 달리 타겟 변수의 클래스가 불균형해도 모델의 성능을 잘 표현함

예를 들어 Negative 클래스가 훨씬 많은 불균형 데이터를 예측할 때 어떤 모델은 데이터를 대부분 Negative로만 예측하고 Positive로는 거의 예측하지 않을 수 있음

이런 모델은 Positive 클래스를 거의 맞추지 못해 recall과 precision이 낮은데도 accuracy를 구하면 매우 높게 나옴

반면 F1 score는 recall과 precision의 조화평균이기 때문에 매우 낮게 나옴

F1 score는 recall과 precision을 고루 반영하고 클래스가 불균형해도 모델의 성능을 잘 나타내므로 여러 모델의 성능을 한가지 지표로 비교해야 할 때 특히 자주 사용

  - metrics.f1_score()을 이용하면 F1 score을 구할 수 있음

다음 코드의 출력 결과를 보면 모델의 F1 score가 0.61

metrics.f1_score(y_true = df_test['income'],       # 실제값
                 y_pred = df_test['pred'],         # 예측값
                 pos_label = 'high')               # 관심 클래스

 

어떤 성능 평가 지표를 사용해야 할까?

성능 평가 지표는 특징이 서로 달라서 어떤 지표가 높더라도 다른 지표는 낮을 수 있음

그러므로 모델을 사용하는 목적에 맞게 평가 기준으로 삼을 지표를 선택해야 함

accuracy는 모델의 일반적인 성능을 나타내므로 항상 살펴봐야 하고, 이에 더해 목적에 따라 precision 또는 recall 중 한 가지 이상을 함께 살펴봐야 함

 

precision: 관심 클래스가 분명할 때

모델을 사용하는 목적이 타겟 변수의 클래스 중에서 관심을 두는 한쪽 클래스를 정확하게 예측하는 것이라면 precision 기준으로 성능을 평가해야 함

예를 들어 고소득자를 예측해 고가의 제품을 홍보한다면 모델이 고소득자로 예측했을 때 얼마나 잘 맞는지 살펴봐야 하므로 precision을 기준으로 평가해야 함

이처럼 타겟 변수의 한쪽 클래스에 분명한 관심이 있을 때 precision을 사용

recall: 관심 클래스를 최대한 많이 찾아내야 할 때

모델을 사용하는 목적이 관심 클래스를 최대한 많이 찾아내는 것이라면 recall을 기준으로 성능을 평가해야 함

예를 들어 전염병에 감염된 사람을 최대한 많이 찾아내 격리해야 한다면, 실제로 전염병에 감염된 사람 중에서 몇 퍼센트를 감연된 것으로 예측하는지 살펴봐야 하므로 recall을 기준으로 평가 기준으로 사용하면 됨

관심 클래스로 예측해서 틀릴 때 손실 VS 관심 클래스를 놓칠 때 손실

평가 기준으로 삼을 지표를 결정하는 또 다른 방법은 데이터를 '관심 클래스로 예측해서 틀릴 때'의 손실과 '관심 클래스를 놓칠 때'의 손실 중 무엇이 더 큰지를 놓고 판단하는 것

데이터를 관심 클래스로 예측해서 틀릴 때 손실이 더 크면 precision, 반대로 관심 클래스를 놓칠 때 손실이 더 크면 recall을 평가 기준으로 사용하면 됨

F1 score: recall과 precision이 모두 중요할 때

데이터를 관심 클래스로 예측해서 틀릴 때의 손실과 관심 클래스로 놓칠 때의 손실이 둘 다 중요하다면 recall과 precision을 모두 판단 기준으로 삼아야 함

그런데 여러 모델을 만들어 성능을 비교할 때 평가 기준 지표가 여러 개면 지표에 따라 성능이 좋다고 판단되는 모델이 다를 수 있음                         -> 이럴 때는 F1 score를 평가 기준으로 삼으면 됨

F1 score을 이용하면 여러 모델 중에 어떤 모델이 우수한지 한 가지 기준으로 비교할 수 있음

 

모델의 성능 지표가 얼마면 될까?

모델의 성능 지표는 얼마를 넘겨야 모델을 사용할 수 있다는 절대적인 기준은 x

모델이 쓸만한지는 기존에 해오던 방식으로 예측했을 때와 모델을 이용해 예측했을 때의 성능 지표를 비교해야 판단할 수 있음

예측 방식을 선택할 때는 예측에 드는 시간과 비용도 고려해야 함

예측 성능이 아무리 좋더라도 예측하는 데 시간이 너무 오래 걸리고 비용이 많이 든다면 사용하기 어려움

따라서 모델을 이용해 예측할 때와 기존 방식으로 예측할 때 드는 시간과 비용을 비교해 모델을 사용할지 여부를 결정

 

 

 

<<정리하기>>

## 1. 전처리

# 데이터 불러오기
import pandas as pd
df = pd.read_csv('adult.csv')

# 1. 타겟 변수 전처리
import numpy as np
df['income'] = np.where(df['income'] == '>50K', 'high', 'low')

# 2. 불필요한 변수 제거하기
df = df.drop(columns = 'fnlwgt')

# 3. 문자 타입 변수를 숫자 타입으로 바꾸기
target = df['income']                # income 추출
df = df.drop(columns = 'income')     # income 제거
df = pd.get_dummies(df)              # 원핫 인코딩으로 변환
df['income'] = target                # df에 target 삽입

# 4. 데이터 분할하기
from sklearn.model_selection import train_test_split
df_train, df_test = train_test_split(df,
                                     test_size = 0.3,           # 테스트 세트 비율
                                     stratify = df['income'],   # 타겟 변수 비율 유지
                                     random_stat = 1234)        # 난수 유지


## 2. 의사결정나무 모델 만들기

# 모델 설정하기
from sklearn import tree
clf = tree.DecisionTreeClassifier(random_state = 1234,  # 난수 고정
                                  max_depth = 3)        # 나무 깊이

# 모델 만들기
train_x = df_train.drop(columns = 'income')   # 예측 변수 추출
train_y = df_train['income']                  # 타겟 변수 추출
model = clf.fit(X = train_x, y = train_y)     # 모델 만들기

# 모델 구조 살펴보기
import matplotlib.pyplot as ply
tree.plot_tree(model,
               feature_names = train_x.columns,   # 예측 변수명
               class_names = ['high', 'low'],     # 타겟 변수 클래스, 알파벳 순
               proportion = True,                 # 비율 표기
               filled = True,                     # 색칠
               rounded = True,                    # 둥근 테두리
               impurity = False,                  # 불순도 표시
               label = 'root',                    # label 표시 위치
               fontsize = 12)                     # 글자 크기


## 3. 모델을 이용해 예측하기

# 예측하기
test_x = df_test.drop(columns = 'income')  # 예측 변수 추출
test_y = df_test['income']                 # 타겟 변수 추출
df_test['pred'] = model.predict(test_x)    # 예측값 구하기


## 4. 성능 평가하기

# confusion matrix 만들기
from sklearn import metrics
conf_mat = confusion_matrix(y_true = df_test['income'],   # 실제값
                            y_pred = df_test['pred'],     # 예측값
                            labels = ['high', 'low'])     # 클래스 배치 순서

# confusion matrix 시각화
from sklearn.matrics import ConfusionMatrixDisplay
p = ConfusionMatrixDisplay(confusion_matrix = conf_mat,       # 컨퓨전 매트릭스
                           display_labels = ('high', 'low'))  # 타겟 변수 클래스명
p.plot(cmap = 'Blues')                                        # 컬러맵 적용해 출력

# accuracy
metrics.accuracy_score(y_true = df_test['income'],  # 실제값
                       y_pred = df_test['pred'])    # 예측값

# precision
metrics.precision_score(y_true = df_test['income'],  # 실제값
                        y_pred = df_test['pred'],    # 예측값
                        pos_label = 'high')          # 관심 클래스

# recall
petrics.recall_score(y_true = df_test['income'],     # 실제값
                     y_pred = df_test['pred'],       # 예측값
                     pos_label = 'high')             # 관심 클래스

# F1 score
metrics.f1_score(y_true = df_test['income'],         # 실제값
                 y_pred = df_test['pred'],           # 예측값
                 pos_label = 'high')                 # 관심 클래스