Home 5 심장 질환 예측하기
Post
Cancel

5 심장 질환 예측하기

머신러닝을 사용해 심장질환을 예측해달라는 병원의 요청을 받았다고 가정한다. 의사와 간화사가 환자의 건강을 돌보기 위해 관심을 두어야 할 중요한 두 세개의 feature를 예측하는 모델을 만드는 것이 목표이다.

결정 트리 분류기를 사용하고 하이퍼 파라미터 튜닝을 해보자. 모델을 만든 후 심장 질환을 예측하는 데 가장 중요한 특성을 가진 feature_importance_ 를 사용해 결과를 해석할 것이다.

5.1 심장 질환 데이터셋


1
2
df_heart = pd.read_csv('./heart_disease.csv')
df_heart.head()

target=1은 심장 질환이 있는 것이고 0은 그렇지 안다는 것이다.

다음은 각 feature의 의미이다.

  • age: 나이
  • sex: 성별
  • cp: 가슴 통증(chest pain) (1=전형적인 협심증, 2=비전형적인 협심증, 3=협심증이 아닌 통증, 4=무증상)
  • trestbps: 안정혈압 (입원시 mmHg)
  • chol: 혈중 콜레스테롤(serum cholesterol) (mg/dl)
  • fbs: 공복 혈당 > 120 mg/dl ? 1 : 0
  • restecg: 심전도 결과 (0: 정상, 1: ST-T파 이상(T파 반전 및/또는 0.05 mV이상의 ST 상승 또는 감소), 2: Estes 기준에 의해 좌심실 비대증 가능성 또는 유력)
  • thalach: 최대 심장 박동 수
  • exang: 운동으로 인한 협심증 (1: yes, 0: no)
  • oldpeak: 휴식 대비 운동으로 인한 ST감소
  • slope: 최대 운동 ST 세그먼트 기울기 (1: 상승 기울기, 2: 수평, 3: 하강 기울기)
  • ca: 형광 투시로 착색된 주요 혈관 수 (0 ~ 3)
  • thal: 탈륨 스트레스 테스트, (3: 정상, 6: 고정 결함, 7: 가역적 결함)

머신러닝 작업을 위해 훈련 세트와 테스트 세트로 나눈다.

1
2
3
X = df_heart.iloc[:, :-1]
y = df_heart.iloc[:, -1]
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=2)

5.2 결정 트리 분류기


하이퍼 파라미터 튜닝 하기 전에 비교를 위해 기준이 될만한 모델을 만든다.

다음처럼 DecisionTreeClassfier 클래스와 cross_val_score() 함수를 사용한다.

1
2
3
4
model = DecisionTreeClassifier(random_state=2)
scores = cross_val_score(model, X, y, cv=5)
print(f'\n... accuracy:{np.round(scores, 2)} ...\n')
print(f'\n... accuracy mean:{scores.mean():.2f} ...\n')

… accuracy:[0.75 0.85 0.75 0.7 0.72] …

… accuracy mean:0.76 …

초기 모델의 정확도는 76%이다. 하이퍼 파라미터 튜닝으로 더 나은 성능을 얻을 수 있을 지 확인해보자.

RandomizedSearchedCV

탐색할 하이퍼 파라미터가 많을 때 GridSearchCV로 하이퍼 파라미터 튜닝을 하면 너무 오랜 시간이 걸릴 수 있다. 이때 RandomziedSearchCV 는 GridSearchCV와 동일한 방식으로 동작하지만 모든 하이퍼파라미터 조합을 테스트하는 대신 랜덤한 조합을 테스트한다. 즉 모든 값을 테스트하지 않으며 제한된 시간 안에 최상의 조합을 찾는다.

RandomizedSearchCV를 사용해서 점수를 출력하고 최상의 모델을 반환하는 함수를 만든다. 이 함수의 매개변수는 params, runs(시도할 조합의 횟수), DecisionTreeClassfier 객체이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sklearn.model_selection import RandomizedSearchCV

def randomized_search_clf(params, runs=20, clf=DecisionTreeClassifier(
    random_state=2
)):
  rand_clf = RandomizedSearchCV(clf, params, n_iter=runs,
                                cv=5, n_jobs=-1, random_state=2)
  rand_clf.fit(X_train, y_train)


  best_model = rand_clf.best_estimator_
  best_score = rand_clf.best_score_
  print(f'\n... train score: {best_score:.3f} ...\n')

  y_pred = rand_clf.best_estimator_.predict(X_test)
  accuracy = accuracy_score(y_test, y_pred)
  print(f'\n... test score: {accuracy:.3f} ...\n')

  return best_model

5.3 하이퍼 파라미터 튜닝


하이퍼파라미터를 고르는 하나의 완벽한 방법은 존재하지 않는다. 다음은 randomized_search_clf() 함수에 넣은 초기 매개변수 리스트이다. 분산을 줄이고 넓은 범위를 탐색하기 위해 수치를 선택했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
%%time
params = {
    'criterion': ['entropy', 'gini'],
    'splitter': ['random', 'best'],
    'min_samples_split': [2, 3, 4, 5, 6, 8, 10],
    'min_samples_leaf': [1, .01, .02, .03, .04],
    'min_impurity_decrease': [0, .0005, .005, .05, .1, .15, .2],
    'max_leaf_nodes': [10, 15, 20, 25, 30, 35, 40, 45, 50, None],
    'max_features': ['auto', .95, .9, .85, .8, .75, .7],
    'max_depth': [None, 2, 4, 6, 8],
    'min_weight_fraction_leaf': [0, .0025, .005, .0075, .01, .05]
}
randomized_search_clf(params)

… train score: 0.798 …

… test score: 0.855 …

… DecisionTreeClassifier(criterion=’entropy’, max_depth=8, max_features=0.8, max_leaf_nodes=45, min_impurity_decrease=0, min_samples_leaf=0.04, min_samples_split=10, min_weight_fraction_leaf=0.05, random_state=2) …

CPU times: user 211 ms, sys: 5.54 ms, total: 217 ms

Wall time: 749 ms

CPU times: user 211 ms, sys: 5.54 ms, total: 217 ms Wall time: 749 ms 확실히 향상되었고 테스트 세트에 잘 일반화 되었다. 범위를 좁혀서 더 나은 결과를 얻을 수 있는지 알아보자.

5.4 탐색 범위 좁히기


매개변수 범위를 좁히는 것이 성능을 향상시킬 수 있는 한 가지 방법이다.

예를 들어 최상의 모델에서 얻은 max_depth=8을 기준으로 탐색 범위를 7~9로 좁힐 수 있다.

또 다른 전략은 기본값이 잘 동하는 매개변수를 탐색에서 제외시키는 것이다. 예를 들어 ‘entropy’는 차이가 크지 않기 때문에 ‘gini’ 대신에 추천하지 않는다. min_impurity_decrease도 기본값 그대로 둘 수 있다.

새로운 매개변수 범위에서 100번으로 탐색 횟수를 증가시켜보자.

1
2
3
4
5
6
7
8
9
10
11
%%time
params = {
    'max_depth': [None, 6, 7],
    'max_features': ['auto', .78],
    'max_leaf_nodes': [45, None],
    'min_samples_leaf': [1, .035, .04, .045, .05],
    'min_samples_split': [2, 9, 10],
    'min_weight_fraction_leaf': [0, .05, .06, .07]
}
model = randomized_search_clf(params, runs=100)
print(f'\n... {model} ...\n')

… train score: 0.802 …

… test score: 0.868 …

… DecisionTreeClassifier(max_depth=7, max_features=0.78, max_leaf_nodes=45, min_samples_leaf=0.045, min_samples_split=9, min_weight_fraction_leaf=0.06, random_state=2) …

CPU times: user 713 ms, sys: 35.2 ms, total: 748 ms Wall time: 5.13 s

이 모델의 훈련 점수와 테스트 점수는 더욱 높아졌다.

반환된 최상의 모델을 전체 데이터 셋에서 교차 검증 함수를 적용해 기본 모델과 비교해본다.

1
2
3
scores = cross_val_score(best_model, X, y, cv=5)
print(f'\n... accuracy:{np.round(scores, 2)} ...\n')
print(f'\n... accuracy mean:{scores.mean():.2f} ...\n')

… accuracy:[0.82 0.9 0.8 0.8 0.78] …

… accuracy mean:0.82 …

무려 6% 이득!

5.5 특성 중요도 (feature importance)


마지막으로 이 모델에서 가장 중요한 feature를 확인해보자. 결정 트리는 이런 값을 제공해주는 feature_importances_ 속성을 제공한다.

먼저 앞서 만든 모델을 전체 데이터셋에서 훈련하자.

모델을 훈련할 때 훈련세트와 테스트 세트를 섞지 않는 것이 중요하다. 하지만 최종 모델을 선택한 후에는 전체 데이터셋을 사용해 모델을 훈련하는 것이 정확도를 더 높일 수 있기 때문에 도움이 된다.

1
2
best_model.fit(X, y)
best_model.feature_importances_

array([0.04826754, 0.04081653, 0.48409586, 0.00568635, 0. ,
0. , 0. , 0.00859483, 0. , 0.02690379,
0. , 0.18069065, 0.20494446])

결과를 해석하기 난해하다. feature 이름과 feature importances를 딕셔너리로 만든 다음 특성 중요도의 내림차순으로 정렬해서 보자.

1
2
3
feature_dict = dict(zip(X.columns, best_model.feature_importances_))
import operator
sorted(feature_dict.items(), key=operator.itemgetter(1), reverse=True)[0:3]

[(‘cp’, 0.4840958610240171),
(‘thal’, 0.20494445570568706),
(‘ca’, 0.18069065321397942)]

이 값 들은 노드 분할에 사용된 feature별 감소된 불순도 량을 더한 후 전체 값이 1이 되도록 정규화한 것이다.

가장 중요한 세 개의 특성은 다음과 같다.

  • cp: 가슴 통증(chest pain) (1=전형적인 협심증, 2=비전형적인 협심증, 3=협심증이 아닌 통증, 4=무증상)
  • thalach: 최대 심장 박동 수
  • ca: 형광 투시로 착색된 주요 혈관 수 (0 ~ 3)

이제 가장 중요한 세 개의 특성인 가슴통증, 최대 심장 박동수, 형광 투시로 착색된 주요 혈관 수로 환자가 심장 질환을 가졌는지 82% 정확도로 예측할 수 있다고 의사와 간호사에게 말 할 수 있다.

This post is licensed under CC BY 4.0 by the author.

4 결정 트리 하이퍼파라미터 튜닝

1 배깅 앙상블 (Baggin ensemble)