모델링 2 (Python)¶

실습¶

1. 분석을 위해 필요한 라이브러리 및 데이터 불러오기¶

  • Python에서는 pandas, seaborn, matplotlib, scikit-learn, tensorflow 등을 사용합니다.
In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

import seaborn as sns
sns.set_theme(style="whitegrid", palette="muted")

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_curve, auc

import tensorflow as tf
from tensorflow import keras
  • 그래프에 한글을 출력하고자 할 때에는 한글 폰트를 지정해야 합니다.
In [2]:
!apt-get -qq install fonts-nanum
'apt-get' is not recognized as an internal or external command,
operable program or batch file.
In [3]:
from matplotlib import rc
rc('font', family='NanumGothic') # 그래프에 한글 출력시 폰트 지정해야 함
  • KNHANES(국민건강영양조사) 2019년도 데이터를 R로 불러옵니다.
  • 실습 편의상 미리 준비된 HN19_lec3.csv 파일을 불러 옵니다.
  • head()함수를 통해 불러온 데이터의 앞부분을 확인 가능하며, n = 5와 같이 표시할 행의 수를 명시할 수 있습니다.
In [4]:
# SPSS 데이터 불러오기
hn19 = pd.read_csv("HN19_lec3.csv")
hn19.head(n = 5)
Out[4]:
age HE_BMI HE_HP HE_DM_HbA1c
0 61 25.987394 3.0 3.0
1 28 16.900942 1.0 1.0
2 53 19.781829 2.0 1.0
3 50 26.631647 1.0 2.0
4 16 NaN NaN NaN

2. 변수 생성 및 결측 처리¶

  • 주어진 데이터로부터 BMI(체질량지수), 고혈압 여부, 당뇨 여부를 나타내는 범주형 변수를 생성합니다.
  • 본래 고혈압 여부는 HE_HP에 1 = 정상, 2 = 고혈압 전단계, 3 = 고혈압으로 코딩돼 있는데 이를 hp에 0 = 정상, 1 = 고혈압 전단계 또는 고혈압으로 변환하고, 당뇨 여부는 HE_DM_HbA1c에 1 = 정상, 2 = 당뇨병 전단계, 3 = 당뇨병으로 코딩돼 있는데 이를 diabetes에 0 = 정상, 1 = 당뇨병 전단계 또는 고혈압으로 코딩합니다.
  • 결측치(NA)가 포함된 행을 제거합니다.
  • 분석에 필요한 변수만 선택합니다.
  • head()와 동일한 방법으로, tail()을 이용해 데이터의 뒷부분을 확인할 수 있으며, n = 3과 같이 표시할 행의 수를 명시할 수 있습니다.
In [5]:
hn19 = hn19.assign(
    bmi=hn19['HE_BMI'],
    hp=np.where(hn19['HE_HP'].isin([2,3]), 1, 0),
    diabetes=np.where(hn19['HE_DM_HbA1c'].isin([2,3]), 1, 0)
).dropna(subset=['bmi','hp','diabetes'])[['age','bmi','hp','diabetes']]
hn19.tail(3)
Out[5]:
age bmi hp diabetes
8107 43 15.375148 0 0
8108 16 14.844970 0 0
8109 8 14.528000 0 0

3. 상관관계 분석 및 시각화¶

  • 변수 간 상관관계를 확인하면 데이터 구조와 변수 간 관계를 이해하는 데 도움이 됩니다.

  • 상관계수(correlation coefficient)는 두 변수 간 선형 관계를 -1 과 1 사이의 값으로 나타냅니다.

  • Python에서 상관관계는 수치형 설명변수끼리만 구할 수 있습니다.

  • 우선 반응변수 y와 수치형 설명변수 age, bmi, hp, diabetes의 상관계수를 구하고 이를 시각화합니다.

  • 상관계수를 구할 변수들인 y, age, bmi, hp, diabetes를 뽑아내고, corr() 함수를 이용해 상관계수 행렬을 만듭니다.

In [6]:
corr = hn19[['age','bmi','hp','diabetes']].corr()
  • 상관계수 행렬을 시각화하기 위해 seaborn패키지의 heatmap() 함수를 이용합니다.
In [7]:
sns.heatmap(corr, annot=True, cmap='coolwarm', center=0)
plt.title("변수 간 상관계수 Heatmap")
plt.show()
No description has been provided for this image

4. BMI와 당뇨병 관계 시각화¶

  • 변수 간 관계를 시각화하면 데이터의 패턴을 쉽게 확인할 수 있습니다.
  • seaborn 라이브러리의 stripplot을 이용해 반응변수와 설명변수에 대한 산점도를 그립니다.
  • 설명변수 중 BMI를 선택해 x축으로 하고, 반응변수인 diabetes를 y축으로 하는 산점도를 그립니다.
  • 반응변수인 diabetes가 0 또는 1이므로 그냥 사용하면 y=0 또는 y=1에 겹쳐지게 됩니다. 따라서 y에 임의로 노이즈를 더한 걸 y_jitter로 놓고 이를 이용하면 점들이 y=0 또는 y=1 주변으로 흩뿌려지게 합니다.
In [8]:
y_jitter = hn19['diabetes'] + np.random.uniform(-0.05, 0.05, size=len(hn19))

sns.scatterplot(x='bmi', y=y_jitter, data=hn19, alpha=0.4)
plt.title("BMI vs diabetes")
plt.show()
No description has been provided for this image

5. 로지스틱 회귀 (2변수 모형) 및 예측 확률 시각화¶

  • 로지스틱 회귀는 종속변수가 이항형(0/1)일 때 확률을 예측하는데 사용됩니다.
  • 주어진 데이터에서 우선 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 로지스틱 회귀모형을 적합합니다.
  • R에서 로지스틱 회귀모형을 적합하는 경우 sklearn.linear_model의 LogisticRegression을 이용합니다.
In [9]:
model_logistic_2d = LogisticRegression(max_iter=1000)
model_logistic_2d.fit(hn19[['age','bmi']], hn19['diabetes'])
Out[9]:
LogisticRegression(max_iter=1000)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression(max_iter=1000)
  • 시각화를 위해, 격자 데이터를 grid에 생성합니다.
  • 격자의 각 지점별 당뇨 확률을 계산해 pred_logistic_2d에 저장합니다.
  • seaborn 라이브러리의 contourf()를 이용해 색상으로 시각화합니다.
In [10]:
# grid 생성
age_seq = np.linspace(hn19['age'].min(), hn19['age'].max(), 100)
bmi_seq = np.linspace(hn19['bmi'].min(), hn19['bmi'].max(), 100)
A, B = np.meshgrid(age_seq, bmi_seq)

grid = pd.DataFrame({'age': A.ravel(), 'bmi': B.ravel()})
pred_logistic_2d = model_logistic_2d.predict_proba(grid)[:, 1].reshape(A.shape)

contour = plt.contourf(A, B, pred_logistic_2d, levels=50, cmap='RdBu', alpha=0.7)
plt.colorbar(contour, label="P(당뇨병=1)")
plt.xlabel("나이(Age)")
plt.ylabel("BMI")
plt.title("로지스틱 회귀의 당뇨병 예측 확률")
plt.show()
No description has been provided for this image

6. 로지스틱 회귀 (다변량) 및 잔차 진단¶

  • 이번에는 설명변수를 3개 사용하여 age, bmi, hp를 설명변수로, diabetes를 반응변수로 하는 로지스틱 회귀모형을 적합합니다.
  • summary() 함수를 통해 계수 및 유의성, 모델 적합도를 확인합니다.
In [11]:
model_logistic = LogisticRegression(max_iter=1000)
model_logistic.fit(hn19[['age','bmi','hp']], hn19['diabetes'])
print("회귀계수:", model_logistic.coef_, "절편:", model_logistic.intercept_)
회귀계수: [[0.05787047 0.15102873 0.34019309]] 절편: [-6.74213772]

7. 결정 나무 (2변수와 다변량 모델 + 가지치기)¶

  • 결정나무(Decision Tree)는 변수 공간을 여러 영역으로 나누어 각 영역마다 같은 예측값을 갖도록 하는 모델입니다.

  • 해석이 쉽고 비선형 관계를 잡아낼 수 있지만, 깊게 성장하면 과적합(overfitting)이 발생할 수 있습니다.

  • 이를 방지하기 위해 가지치기(pruning) 를 수행하여 최적의 복잡도를 선택합니다

  • 주어진 데이터에서 우선 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 분류 결정나무를 적합합니다.

  • R에서 결정나무를 적합하는 경우 sklearn.tree의 DecisionTreeClassifier() 함수를 이용합니다. 함수 호출 시 전달해야 하는 인자는 다음과 같습니다.

  • plot_tree() 함수를 사용해 적합한 결정나무를 시각화합니다.

In [12]:
model_tree_2d = DecisionTreeClassifier(max_depth=2, random_state=2025)
model_tree_2d.fit(hn19[['age','bmi']], hn19['diabetes'])

plt.figure(figsize=(10,5))
plot_tree(model_tree_2d, feature_names=['age','bmi'], class_names=['0','1'], filled=True)
plt.show()
No description has been provided for this image
  • 앞에서와 마찬가지로 시각화를 합니다. 격자 데이터는 앞에서 생성한 grid를 이용합니다.
  • 격자의 각 지점별 당뇨 확률을 계산해 pred_tree_2d에 저장합니다.
  • contourf()을 이용해 색상으로 시각화합니다.
  • 결정 나무의 경우에는 경계선이 항상 축과 평행하게 나옴을 알 수 있습니다.
In [13]:
# 2. 결정나무 예측 확률 계산
pred_tree_2d = model_tree_2d.predict_proba(grid)[:, 1].reshape(A.shape)

# 3. 시각화
plt.figure(figsize=(8, 6))
contour = plt.contourf(A, B, pred_tree_2d, levels=50, cmap='RdBu', alpha=0.7)
plt.colorbar(contour, label="P(당뇨병=1)")
plt.xlabel("나이(Age)")
plt.ylabel("BMI")
plt.title("결정 나무의 영역별 당뇨병 예측 확률")
plt.show()
No description has been provided for this image
  • 이번에는 설명변수를 3개 사용하여 age, bmi, hp를 설명변수로, diabetes를 반응변수로 하는 분류 결정나무를 적합하고 가지치기(pruning)을 합니다.
  • 실행속도 관계상 자료에서 처음 100개의 행만 골라냅니다.
  • Python 에서는 결정나무를 가지치기하는 코드가 복잡해, 여기서는 간단하게 복잡도를 조절할 수 있는 파라미터로 결정 나무를 생성합니다. ccp_alpha 파라미터를 사용해 결정나무의 복잡도를 조절합니다.
In [14]:
# 가지치기 정도를 alpha(=cp)로 지정 (값이 클수록 많이 가지치기)
model_ptree = DecisionTreeClassifier(random_state=42, ccp_alpha=0.01)
model_ptree.fit(hn19[['age','bmi', 'hp']].iloc[0:100], hn19['diabetes'].iloc[0:100])

# 시각화
plt.figure(figsize=(10, 6))
plot_tree(model_ptree, feature_names=['age','bmi','hp'], class_names=['0','1'], filled=True)
plt.title("Pruned Decision Tree (ccp_alpha=0.01)")
plt.show()
No description has been provided for this image

8. 앙상블 모형 (배깅 & 랜덤 포레스트)¶

  • 배깅(Bagging) 은 여러 부트스트랩 샘플에 대해 모델(결정나무)을 학습하고 예측을 평균/다수결로 결합합니다.

  • 랜덤 포레스트(Random Forest) 는 배깅에 변수 무작위 선택을 추가하여 상관성을 줄이고 성능을 향상시킵니다.

  • 배깅과 랜덤 포레스트는 모두 sklearn.ensemble의 RandomForestClassifier로 적합할 수 있습니다. 이는 랜덤 포레스트에서 변수의 개수를 전부 다 쓰면 그게 배깅이 되기 때문입니다.

  • 앞에서와 마찬가지로 설명변수를 3개 사용하여 age, bmi, hp를 설명변수로, diabetes를 반응변수로 하는 앙상블 모형을 생각합니다.

  • 우선 배깅 모형을 적합합니다. 이는 RandomForestClassifier 에서 사용하는 설명변수의 개수를 전체 설명변수의 개수인 3으로 지정하면 됩니다 (max_features=3).

In [15]:
model_bag = RandomForestClassifier(n_estimators=100, max_features=3, random_state=42)
model_bag.fit(hn19[['age','bmi', 'hp']], hn19['diabetes'])
Out[15]:
RandomForestClassifier(max_features=3, random_state=42)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestClassifier(max_features=3, random_state=42)
  • 다음으로 랜덤포레스트 모형을 적합합니다. 이는 RandomForestClassifier 에서 사용하는 설명변수의 개수를 전체 설명변수의 개수보다 적게 지정하면 됩니다 (max_features=3).
In [16]:
model_rf = RandomForestClassifier(n_estimators=100, max_features=2, random_state=2025)
model_rf.fit(hn19[['age','bmi', 'hp']], hn19['diabetes'])
Out[16]:
RandomForestClassifier(max_features=2, random_state=2025)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestClassifier(max_features=2, random_state=2025)

9. 신경망 모형 (Neural Network)¶

  • 신경망(Neural Network)은 입력층, 은닉층, 출력층으로 구성되며, 비선형 관계 학습에 강점이 있습니다.

  • 충분한 데이터와 계산 자원이 필요하지만, 복잡한 패턴을 학습할 수 있습니다.

  • 앞에서와 마찬가지로 설명변수를 3개 사용하여 age, bmi, hp를 설명변수로, diabetes를 반응변수로 하는, 은닉층(hidden layer)이 하나인 신경망 모형을 생각합니다.

  • 신경망 모형을 적합하는 패키지는 여러 가지 있지만, 여기서는 가장 간단하게 할 수 있는 neuralnet 패키지의 neuralnet() 함수를 소개합니다. 다만 이 패키지는 느린 편이어서, 더 복잡한 자료에서는 keras 패키지 내지는 torch 패키지를 사용하면 좀 더 빠르게 실행할 수 있습니다.

  • 실행속도 관계상 자료에서 처음 100개의 행만 골라냅니다.

  • hidden은 은닉층(hidden layer)의 뉴런 수입니다. 아래에서는 hidden = 2 를 사용했습니다.

  • plot() 함수로 신경망 구조를 시각화합니다.

In [17]:
X_nn = hn19[['age','bmi','hp']].iloc[0:100].values
y_nn = hn19['diabetes'].iloc[0:100].values

model_nn = keras.Sequential([
    keras.layers.Dense(2, activation='relu', input_shape=(3,)),
    keras.layers.Dense(1, activation='sigmoid')
])
model_nn.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model_nn.fit(X_nn, y_nn, epochs=50, verbose=0)
print("신경망 정확도:", model_nn.evaluate(X_nn, y_nn, verbose=0)[1])
C:\Users\rupik\anaconda3\Lib\site-packages\keras\src\layers\core\dense.py:92: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
신경망 정확도: 0.49000000953674316

10. 모델 평가 (데이터 분리, Threshold 성능 분석, ROC Curve)¶

  • 혼동행렬, 민감도, 특이도(Slide 60~64)로 모델 성능을 평가합니다.

  • threshold 변화에 따른 민감도·특이도 변화(ROC Curve)를 시각화하고, AUC를 계산해 분류기 성능을 종합적으로 판단합니다.

ㅅㄱ먀ㅜ

  • 학습용/테스트용 데이터 분리를 통해 과대적합을 방지합니다.

  • 우선 학습용/테스트용 데이터(training data/test data)를 10% 대 90%로 분리합니다. sklearn.model_selection의 train_test_split() 함수를 사용합니다.

In [18]:
train, test = train_test_split(hn19, test_size=0.9, random_state=42)
X_train, y_train = train[['age','bmi','hp']], train['diabetes']
X_test, y_test = test[['age','bmi','hp']], test['diabetes']
  • 다음으로,threshold 별 평가 지표(accuracy, sensitivity, specificity)를 한 번에 계산하는 코드를 함수로 작성합니다.
  • thresholds 에는 threshold 의 값이 벡터로 들어옵니다.
  • actual은 실제 반응변수의 값, predicted는 예측된 반응변수의 값이 들어옵니다. 단, 여기서 predicted는 확률로 값이 들어가야 합니다.
In [19]:
def calc_metrics(thresholds, actual, predicted_prob):
    results = []
    for t in thresholds:
        # 예측 확률이 threshold보다 크면 1(양성), 작으면 0(음성)
        pred_class = (predicted_prob > t).astype(int)

        # 혼동 행렬 요소 계산
        TP = np.sum((pred_class == 1) & (actual == 1))
        TN = np.sum((pred_class == 0) & (actual == 0))
        FP = np.sum((pred_class == 1) & (actual == 0))
        FN = np.sum((pred_class == 0) & (actual == 1))

        # 평가 지표 계산
        accuracy = (TP + TN) / (TP + TN + FP + FN)
        sensitivity = TP / (TP + FN) if (TP + FN) > 0 else 0
        specificity = TN / (TN + FP) if (TN + FP) > 0 else 0
        
        results.append((t, accuracy, sensitivity, specificity))
    return pd.DataFrame(results, columns=["Threshold","Accuracy","Sensitivity","Specificity"])
  • thresholds 후보를 0부터 1까지 0.01 단위의 벡터로 만듭니다.
In [20]:
thresholds = np.arange(0, 1.01, 0.01)
id_05 = np.where(thresholds == 0.5)
  • 앞과 마찬가지로 age, bmi, hp를 설명변수로, diabetes를 반응변수로 하는 모형을 생각합니다.
  • 아래 코드에서는 로지스틱 회귀모형을 생각했습니다. 학습 데이터(training data)에서 모형을 학습하고, 테스트 데이터(test data)에서 예측 확률을 산출합니다.
In [21]:
model_logistic = LogisticRegression(max_iter=1000)
model_logistic.fit(X_train, y_train)
pred_logistic = model_logistic.predict_proba(X_test)[:,1]
  • 앞에서 작성한 calc_metrics() 함수로 threshold 변화 시 accuracy, sensitivity, specificity 를 계산합니다. 특히나, accuracy 에서 threshold = 0.5에 해당하는 값이 우리가 일반적으로 생각하는 정확도입니다.
In [22]:
metrics_logistic = calc_metrics(thresholds, y_test.values, pred_logistic)
# threshold = 0.5 정확도
acc_05 = metrics_logistic[["Accuracy"]].values[id_05][0][0]
print("Logistic Accuracy: ", acc_05)
Logistic Accuracy:  0.7511227002752426
  • 위에서 계산한 값을 바탕으로 accuracy, sensitivity, specificity 를 시각화합니다.
In [23]:
metrics_long = metrics_logistic.melt(id_vars="Threshold", 
                               value_vars=["Accuracy","Sensitivity","Specificity"], 
                               var_name="Metric", value_name="Value")

plt.figure(figsize=(8,6))
sns.lineplot(data=metrics_long, x="Threshold", y="Value", hue="Metric", linewidth=1.5)
plt.xlabel("Threshold")
plt.ylabel("Metric Value")
plt.title("Accuracy / Sensitivity / Specificity")
plt.legend(title="Metric")
plt.show()
No description has been provided for this image
  • ROC curve 를 계산 및 시각화하고 AUC 를 계산합니다. sklearn.metrics의 roc_curve() 함수를 이용하면 됩니다.
In [24]:
fpr, tpr, _ = roc_curve(y_test, pred_logistic)
roc_auc = auc(fpr, tpr)

plt.figure(figsize=(6,6))
plt.plot(fpr, tpr, label=f"AUC = {roc_auc:.3f}")
plt.plot([0,1],[0,1],'k--')
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve")
plt.legend()
plt.show()
No description has been provided for this image

연습 문제¶

  • 똑같은 데이터를 사용합니다.

1. 로지스틱 회귀 모형¶

  • 설명변수를 1개 사용하여 bmi를 설명변수로, diabetes를 반응변수로 하는 로지스틱 회귀모형을 적합합니다.
  • 4단계에서 그린 BMI와 당뇨병 관계 시각화에 더해서 BMI 값 별로 로지스틱 회귀모형의 당뇨병 예측 확률을 곡선으로 그립니다. sns.lineplot(x=hn19["diabetes"], y=pred_logistic)과 같은 형태로 할 수 있습니다.

풀이¶

  • bmi를 설명변수로, diabetes를 반응변수로 하는 로지스틱 회귀모형을 적합합니다.
In [25]:
model_logistic_bmi = LogisticRegression(max_iter=1000)
model_logistic_bmi.fit(hn19[['bmi']], hn19['diabetes'])
Out[25]:
LogisticRegression(max_iter=1000)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression(max_iter=1000)
  • bmi에 해당하는 격자를 grid_bmi로 생성합니다.
  • 격자의 각 지점별 당뇨 확률을 계산해 pred_logistic에 저장합니다.
In [26]:
grid_bmi = pd.DataFrame({'bmi': np.linspace(hn19['bmi'].min(), hn19['bmi'].max(), 100)})
pred_logistic = model_logistic_bmi.predict_proba(grid_bmi)[:, 1]
  • 위의 BMI와 당뇨병 관계를 시각화는 코드를 가져오고, 여기에 회귀곡선을 시각화합니다. grid_bmi의 bmi에 격자가, pred_logistic에 당뇨 확률이 계산돼 있으므로 sns.lineplot(x=grid_bmi['bmi'], y=pred_logistic) 같은 식으로 코드를 작성하면 됩니다.
In [27]:
y_jitter = hn19['diabetes'] + np.random.uniform(-0.05, 0.05, size=len(hn19))

sns.scatterplot(x='bmi', y=y_jitter, data=hn19, alpha=0.4)
sns.lineplot(x=grid_bmi['bmi'], y=pred_logistic)
plt.title("BMI vs diabetes")
plt.show()
No description has been provided for this image

2. 결정나무¶

  • 설명변수를 1개 사용하여 bmi를 설명변수로, diabetes를 반응변수로 하는 분류 결정나무를 적합합니다. 가지치기는 따로 안해도 됩니다.
  • 4단계에서 그린 BMI와 당뇨병 관계 시각화에 더해서 BMI 값 별로 분류 결정나무의 당뇨병 예측 확률을 곡선으로 그립니다. sns.lineplot(x=hn19["diabetes"], y=pred_logistic)과 같은 형태로 할 수 있습니다.

풀이¶

  • bmi를 설명변수로, diabetes를 반응변수로 하는 분류 결정나무를 적합합니다.
In [28]:
model_tree_bmi = DecisionTreeClassifier(max_depth=2, random_state=2025)
model_tree_bmi.fit(hn19[['bmi']], hn19['diabetes'])
Out[28]:
DecisionTreeClassifier(max_depth=2, random_state=2025)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
DecisionTreeClassifier(max_depth=2, random_state=2025)
  • 앞 문제의 grid_bmi를 bmi에 해당하는 격자로 활용합니다.
  • 격자의 각 지점별 당뇨 확률을 계산해 pred_tree에 저장합니다.
In [29]:
pred_tree = model_tree_bmi.predict_proba(grid_bmi)[:, 1]
  • 위의 BMI와 당뇨병 관계를 시각화는 코드를 가져오고, 여기에 회귀곡선을 시각화합니다. grid_bmi의 bmi에 격자가, pred_tree에 당뇨 확률이 계산돼 있으므로 sns.lineplot(x=grid_bmi['bmi'], y=pred_tree) 같은 식으로 코드를 작성하면 됩니다.
In [30]:
y_jitter = hn19['diabetes'] + np.random.uniform(-0.05, 0.05, size=len(hn19))

sns.scatterplot(x='bmi', y=y_jitter, data=hn19, alpha=0.4)
sns.lineplot(x=grid_bmi['bmi'], y=pred_tree)
plt.title("BMI vs diabetes")
plt.show()
No description has been provided for this image

3. 배깅¶

  • 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 배깅 모형을 적합합니다.
  • 5단계 마지막에서 했던 것처럼 age 및 BMI 값 별로 배깅 모형의 당뇨병 예측 확률을 색칠해 나타냅니다.

풀이¶

  • 우선 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 배깅 모형을 적합합니다.
In [31]:
model_bag_2d = RandomForestClassifier(n_estimators=100, max_features=2, random_state=2025)
model_bag_2d.fit(hn19[['age','bmi']], hn19['diabetes'])
Out[31]:
RandomForestClassifier(max_features=2, random_state=2025)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestClassifier(max_features=2, random_state=2025)
  • 그러고서는 5단계 마지막에 했던 시각화를 반복합니다.
  • 우선 격자 데이터를 grid에 생성합니다.
  • 격자의 각 지점별 당뇨 확률을 계산해 pred_bag_2d에 저장합니다.
  • seaborn 라이브러리의 contourf()를 이용해 색상으로 시각화합니다.
In [32]:
# grid 생성
age_seq = np.linspace(hn19['age'].min(), hn19['age'].max(), 100)
bmi_seq = np.linspace(hn19['bmi'].min(), hn19['bmi'].max(), 100)
A, B = np.meshgrid(age_seq, bmi_seq)

grid = pd.DataFrame({'age': A.ravel(), 'bmi': B.ravel()})
pred_bag_2d = model_bag_2d.predict_proba(grid)[:, 1].reshape(A.shape)

contour = plt.contourf(A, B, pred_bag_2d, levels=50, cmap='RdBu', alpha=0.7)
plt.colorbar(contour, label="P(당뇨병=1)")
plt.xlabel("나이(Age)")
plt.ylabel("BMI")
plt.title("배깅의 당뇨병 예측 확률")
plt.show()
No description has been provided for this image

4. 랜덤 포레스트¶

  • 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 랜덤 포레스트 모형을 적합합니다.
  • 5단계 마지막에서 했던 것처럼 age 및 BMI 값 별로 랜덤 포레스트 모형의 당뇨병 예측 확률을 색칠해 나타냅니다.

풀이¶

  • 우선 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 랜덤 포레스트 모형을 적합합니다.
In [33]:
model_rf_2d = RandomForestClassifier(n_estimators=100, max_features=1, random_state=2025)
model_rf_2d.fit(hn19[['age','bmi']], hn19['diabetes'])
Out[33]:
RandomForestClassifier(max_features=1, random_state=2025)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestClassifier(max_features=1, random_state=2025)
  • 그러고서는 5단계 마지막에 했던 시각화를 반복합니다.
  • 우선 격자 데이터를 grid에 생성합니다.
  • 격자의 각 지점별 당뇨 확률을 계산해 pred_rf_2d에 저장합니다.
  • seaborn 라이브러리의 contourf()를 이용해 색상으로 시각화합니다.
In [34]:
# grid 생성
age_seq = np.linspace(hn19['age'].min(), hn19['age'].max(), 100)
bmi_seq = np.linspace(hn19['bmi'].min(), hn19['bmi'].max(), 100)
A, B = np.meshgrid(age_seq, bmi_seq)

grid = pd.DataFrame({'age': A.ravel(), 'bmi': B.ravel()})
pred_rf_2d = model_rf_2d.predict_proba(grid)[:, 1].reshape(A.shape)

contour = plt.contourf(A, B, pred_rf_2d, levels=50, cmap='RdBu', alpha=0.7)
plt.colorbar(contour, label="P(당뇨병=1)")
plt.xlabel("나이(Age)")
plt.ylabel("BMI")
plt.title("랜덤 포레스트의 당뇨병 예측 확률")
plt.show()
No description has been provided for this image

5. 인공 신경망¶

  • 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 인공 신경망을 적합합니다.
  • 5단계 마지막에서 했던 것처럼 age 및 BMI 값 별로 인공 신경망 모형의 당뇨병 예측 확률을 색칠해 나타냅니다.

풀이¶

  • 우선 설명변수를 2개 사용하여 age, bmi를 설명변수로, diabetes를 반응변수로 하는 인공 신경망을 적합합니다.
In [35]:
X_nn = hn19[['age','bmi']].iloc[0:100].values
y_nn = hn19['diabetes'].iloc[0:100].values

model_nn_2d = keras.Sequential([
    keras.layers.Dense(2, activation='relu', input_shape=(2,)),
    keras.layers.Dense(1, activation='sigmoid')
])
model_nn_2d.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model_nn_2d.fit(X_nn, y_nn, epochs=50, verbose=0)
print("신경망 정확도:", model_nn_2d.evaluate(X_nn, y_nn, verbose=0)[1])
C:\Users\rupik\anaconda3\Lib\site-packages\keras\src\layers\core\dense.py:92: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
신경망 정확도: 0.6499999761581421
  • 그러고서는 5단계 마지막에 했던 시각화를 반복합니다.
  • 우선 격자 데이터를 grid에 생성합니다.
  • 격자의 각 지점별 당뇨 확률을 계산해 pred_nn_2d에 저장합니다.
  • seaborn 라이브러리의 contourf()를 이용해 색상으로 시각화합니다.
In [36]:
# grid 생성
age_seq = np.linspace(hn19['age'].min(), hn19['age'].max(), 100)
bmi_seq = np.linspace(hn19['bmi'].min(), hn19['bmi'].max(), 100)
A, B = np.meshgrid(age_seq, bmi_seq)

grid = pd.DataFrame({'age': A.ravel(), 'bmi': B.ravel()})
pred_nn_2d = model_nn_2d.predict(grid).reshape(A.shape)

contour = plt.contourf(A, B, pred_nn_2d, levels=50, cmap='RdBu', alpha=0.7)
plt.colorbar(contour, label="P(당뇨병=1)")
plt.xlabel("나이(Age)")
plt.ylabel("BMI")
plt.title("인공 신경망의 당뇨병 예측 확률")
plt.show()
313/313 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
No description has been provided for this image

6. 모델 평가¶

  • 설명변수를 3개 사용하여 age, bmi, hp를 설명변수로, diabetes를 반응변수로 하는 모형을 생각합니다.
  • 위 실습에서는 로지스틱 회귀모형에 대해서 accuracy, sensitivity, specificity 의 곡선을 그리고 roc curve 를 그렸습니다. 이를 결정나무, 배깅, 랜덤 포레스트, 인공신경망에 대해 반복합니다.

풀이¶

  • 앞에서처럼 학습용/테스트용 데이터(training data/test data)를 10% 대 90%로 분리합니다. sklearn.model_selection의 train_test_split() 함수를 사용합니다.
In [37]:
train, test = train_test_split(hn19, test_size=0.9, random_state=2025)
X_train, y_train = train[['age','bmi','hp']], train['diabetes']
X_test, y_test = test[['age','bmi','hp']], test['diabetes']
  • 앞에서 작성했던 threshold 별 평가 지표(accuracy, sensitivity, specificity)를 한 번에 계산하는 calc_metrics 함수를 가져옵니다.
In [38]:
def calc_metrics(thresholds, actual, predicted_prob):
    results = []
    for t in thresholds:
        # 예측 확률이 threshold보다 크면 1(양성), 작으면 0(음성)
        pred_class = (predicted_prob > t).astype(int)

        # 혼동 행렬 요소 계산
        TP = np.sum((pred_class == 1) & (actual == 1))
        TN = np.sum((pred_class == 0) & (actual == 0))
        FP = np.sum((pred_class == 1) & (actual == 0))
        FN = np.sum((pred_class == 0) & (actual == 1))

        # 평가 지표 계산
        accuracy = (TP + TN) / (TP + TN + FP + FN)
        sensitivity = TP / (TP + FN) if (TP + FN) > 0 else 0
        specificity = TN / (TN + FP) if (TN + FP) > 0 else 0
        
        results.append((t, accuracy, sensitivity, specificity))
    return pd.DataFrame(results, columns=["Threshold","Accuracy","Sensitivity","Specificity"])
  • 여기에 더해, accuracy, sensitivity, specificity 를 시각화하는 함수, roc curve 를 그리는 함수를 각각 만듭니다.
In [39]:
def plot_metrics(thresholds, actual, predicted):
    metric = calc_metrics(thresholds, actual, predicted)

    metrics_long = metric.melt(id_vars="Threshold", 
                               value_vars=["Accuracy","Sensitivity","Specificity"], 
                               var_name="Metric", value_name="Value")

    plt.figure(figsize=(8,6))
    sns.lineplot(data=metrics_long, x="Threshold", y="Value", hue="Metric", linewidth=1.5)
    plt.xlabel("Threshold")
    plt.ylabel("Metric Value")
    plt.title("Accuracy / Sensitivity / Specificity")
    plt.legend(title="Metric")
    plt.show()

def plot_roc(actual, predicted):
    fpr, tpr, _ = roc_curve(actual, predicted)
    roc_auc = auc(fpr, tpr)
    
    plt.figure(figsize=(6,6))
    plt.plot(fpr, tpr, label=f"AUC = {roc_auc:.3f}")
    plt.plot([0,1],[0,1],'k--')
    plt.xlabel("False Positive Rate")
    plt.ylabel("True Positive Rate")
    plt.title("ROC Curve")
    plt.legend()
    plt.show()
  • 앞에서처럼 thresholds 후보를 0부터 1까지 0.01 단위의 벡터로 만듭니다.
In [40]:
thresholds = np.arange(0, 1.01, 0.01)
  • 이제 결정나무에 대해 accuracy, sensitivity, specificity 의 곡선을 그리고 roc curve 를 그립니다.
  • 학습 데이터(training data)에서 모형을 학습하고, 테스트 데이터(test data)에서 예측 확률을 산출합니다.
In [41]:
model_tree = DecisionTreeClassifier(max_depth=2, random_state=2025)
model_tree.fit(X_train, y_train)
pred_tree = model_tree.predict_proba(X_test)[:, 1]
  • 앞에서 작성한 plot_metrics() 함수로 threshold 변화 시 accuracy, sensitivity, specificity 를 시각화합니다.
In [42]:
plot_metrics(thresholds, y_test, pred_tree)
No description has been provided for this image
  • ROC curve 를 계산 및 시각화하고 AUC 를 계산합니다.
In [43]:
plot_roc(y_test, pred_tree)
No description has been provided for this image
  • 이제 배깅에 대해 accuracy, sensitivity, specificity 의 곡선을 그리고 roc curve 를 그립니다.
  • 학습 데이터(training data)에서 모형을 학습하고, 테스트 데이터(test data)에서 예측 확률을 산출합니다.
In [44]:
model_bag = RandomForestClassifier(n_estimators=100, max_features=3, random_state=2025)
model_bag.fit(X_train, y_train)
pred_bag = model_bag.predict_proba(X_test)[:, 1]
  • 앞에서 작성한 plot_metrics() 함수로 threshold 변화 시 accuracy, sensitivity, specificity 를 시각화합니다.
In [45]:
plot_metrics(thresholds, y_test, pred_bag)
No description has been provided for this image
  • ROC curve 를 계산 및 시각화하고 AUC 를 계산합니다.
In [46]:
plot_roc(y_test, pred_bag)
No description has been provided for this image
  • 이제 랜덤포레스트에 대해 accuracy, sensitivity, specificity 의 곡선을 그리고 roc curve 를 그립니다.
  • 학습 데이터(training data)에서 모형을 학습하고, 테스트 데이터(test data)에서 예측 확률을 산출합니다.
In [47]:
model_rf = RandomForestClassifier(n_estimators=100, max_features=2, random_state=2025)
model_rf.fit(X_train, y_train)
pred_rf = model_rf.predict_proba(X_test)[:, 1]
  • 앞에서 작성한 plot_metrics() 함수로 threshold 변화 시 accuracy, sensitivity, specificity 를 시각화합니다.
In [48]:
plot_metrics(thresholds, y_test, pred_rf)
No description has been provided for this image
  • ROC curve 를 계산 및 시각화하고 AUC 를 계산합니다.
In [49]:
plot_roc(y_test, pred_rf)
No description has been provided for this image
  • 이제 배깅에 대해 accuracy, sensitivity, specificity 의 곡선을 그리고 roc curve 를 그립니다.
  • 학습 데이터(training data)에서 모형을 학습하고, 테스트 데이터(test data)에서 예측 확률을 산출합니다.
In [50]:
model_bag = RandomForestClassifier(n_estimators=100, max_features=3, random_state=2025)
model_bag.fit(X_train, y_train)
pred_bag = model_bag.predict_proba(X_test)[:, 1]
  • 앞에서 작성한 plot_metrics() 함수로 threshold 변화 시 accuracy, sensitivity, specificity 를 시각화합니다.
In [51]:
plot_metrics(thresholds, y_test, pred_bag)
No description has been provided for this image
  • ROC curve 를 계산 및 시각화하고 AUC 를 계산합니다.
In [52]:
plot_roc(y_test, pred_bag)
No description has been provided for this image
  • 마지막으로 인공신경망에 대해 accuracy, sensitivity, specificity 의 곡선을 그리고 roc curve 를 그립니다.
  • 학습 데이터(training data)에서 모형을 학습하고, 테스트 데이터(test data)에서 예측 확률을 산출합니다.
In [53]:
model_nn = keras.Sequential([
    keras.layers.Dense(2, activation='relu', input_shape=(3,)),
    keras.layers.Dense(1, activation='sigmoid')
])
model_nn.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model_nn.fit(X_train, y_train, epochs=50, verbose=0)
pred_nn = model_nn.predict(X_test)[:, 0]
C:\Users\rupik\anaconda3\Lib\site-packages\keras\src\layers\core\dense.py:92: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.
  super().__init__(activity_regularizer=activity_regularizer, **kwargs)
216/216 ━━━━━━━━━━━━━━━━━━━━ 2s 6ms/step
  • 앞에서 작성한 plot_metrics() 함수로 threshold 변화 시 accuracy, sensitivity, specificity 를 시각화합니다.
In [54]:
plot_metrics(thresholds, y_test, pred_nn)
No description has been provided for this image
  • ROC curve 를 계산 및 시각화하고 AUC 를 계산합니다.
In [55]:
plot_roc(y_test, pred_nn)
No description has been provided for this image