In [181]:
# 붓꽃 데이터 로드
from sklearn.datasets import load_iris
import pandas as pd
iris = load_iris()
iris_pd = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_pd.head()| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | |
|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 |
Plain text view
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) 0 5.1 3.5 1.4 0.2 1 4.9 3.0 1.4 0.2 2 4.7 3.2 1.3 0.2 3 4.6 3.1 1.5 0.2 4 5.0 3.6 1.4 0.2
In [182]:
# 정답 컬럼 추가
iris_pd["species"] = iris.target
iris_pd.head()| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | species | |
|---|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 | 0 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 | 0 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 | 0 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 | 0 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 | 0 |
Plain text view
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) \ 0 5.1 3.5 1.4 0.2 1 4.9 3.0 1.4 0.2 2 4.7 3.2 1.3 0.2 3 4.6 3.1 1.5 0.2 4 5.0 3.6 1.4 0.2 species 0 0 1 0 2 0 3 0 4 0
데이터 시각화
첫번째로 꽃받침 (sepal)의 길이와 너비를 살펴본다. 우리는 boxplot 을 이용해 데이터를 시각화한다.
Boxplot을 사용하면 다음과 같은 장점이 있다.
- 데이터 분포 확인: 중앙값, 사분위수, 최솟값/최댓값 파악
- 이상치(outlier) 탐지
- 품종별 비교
In [183]:
# 꽃받침 길이 (sepal length)
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
sns.boxplot(
x="sepal length (cm)",
y="species",
hue="species", # 품종을 기준으로 색깔 구분
data=iris_pd,
orient="h"
)
plt.tight_layout()
plt.show()꽃받침 길이로는 Setosa(0)가 짧아 다른 품종과 구분됨을 알 수 있다. 다만 Versicolor(1)와 Verginica(2)는 꽃받침 길이만으로는 완벽한 분류가 불가능하다.
In [184]:
# 꽃받침 폭 (sepal width)
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(12,6))
sns.boxplot(
x="sepal width (cm)",
y="species",
hue="species",
data=iris_pd,
orient="h"
)
plt.tight_layout()
plt.show()꽃받침 너비로는 품종간 구분이 어렵다. 다음으로 꽃잎(petal) 길이와 너비를 살펴보자. 이번에는 scatter plot 을 사용해본다.
scatter plot 을 사용하면 다음과 같은 것들이 가능하다.
- 상관관계 파악
- 데이터 군집 확인
- 분류 가능성 탐색
In [185]:
# 데이터 시각화 scatter plot
plt.figure(figsize=(12, 6))
sns.scatterplot(
x="petal length (cm)",
y="petal width (cm)",
hue="species",
data=iris_pd,
)
plt.show()꽃잎길이(petal length)를 사용하면 3가지 종을 나눌 수 있는 가능성을 발견했다. 다음으로 petal length 와 Decision boundary 를 사용하여 각각을 분류해보자.
In [186]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
X = iris_pd[["petal length (cm)", "petal width (cm)"]].values
y = iris_pd["species"].values
# 데이터를 훈련 세트와 테스트 세트로 분할
# X: 특성 데이터 (petal length, petal width)
# y: 타겟 데이터 (species)
# test_size=0.2: 전체 데이터의 20%를 테스트 세트로 사용 (80%는 훈련 세트)
# random_state=42: 랜덤 시드 고정으로 재현 가능한 결과 보장
# stratify=y: 클래스 비율을 유지하며 분할 (각 클래스가 훈련/테스트 세트에 균등하게 분배됨)
X_train, X_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2,
random_state=42,
stratify=y
)
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(X_train, y_train)
y_pred_train = clf.predict(X_train)
y_pred_test = clf.predict(X_test)
train_accuracy = accuracy_score(y_train, y_pred_train)
test_accuracy = accuracy_score(y_test, y_pred_test)
print(f"Train Accuracy: {train_accuracy:.2f}")
print(f"Test Accuracy: {test_accuracy:.2f}")
Train Accuracy: 0.97 Test Accuracy: 0.93
결정 경계를 시각화 해보자
In [187]:
# 결정 경계 시각화를 위한 x축, y축 범위 설정
# X[:, 0]: 첫 번째 특성(petal length)의 최솟값과 최댓값
# X[:, 1]: 두 번째 특성(petal width)의 최솟값과 최댓값
# 각각 -1, +1을 해서 여백을 추가하여 경계가 잘리지 않도록 함
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
# 결정 경계를 그리기 위해 전체 영역에 대한 그리드 생성
# meshgrid: x축과 y축 범위에 대해 0.01 간격으로 격자 좌표 생성
# xx, yy는 각각 2D 배열로, 모든 (x, y) 좌표 조합을 포함
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01),
np.arange(y_min, y_max, 0.01))
# 각 그리드 포인트에 대해 모델의 예측 수행
# ravel(): 2D 배열을 1D로 평탄화 (예: [[1,2],[3,4]] -> [1,2,3,4])
# np.c_: 두 1D 배열을 열(column)로 결합하여 (N, 2) 형태의 배열 생성
# predict: 각 (x, y) 좌표에 대해 어떤 클래스로 분류되는지 예측
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
# 예측 결과를 다시 원래 그리드 형태(xx.shape)로 변환하여 contourf로 시각화 가능하게 함
Z = Z.reshape(xx.shape)
# 그래프 크기 설정 (가로 12, 세로 6)
plt.figure(figsize=(12, 6))
# contourf: 결정 경계 영역을 채워서 그리기
# xx, yy: 그리드 좌표, Z: 각 좌표의 예측 클래스, alpha: 투명도 (0.3 = 30% 불투명)
plt.contourf(xx, yy, Z, alpha=0.3)
# scatter: 실제 데이터 포인트를 산점도로 표시
# X[:, 0], X[:, 1]: petal length와 petal width 값
# c=y: 색상은 실제 클래스(y)에 따라 구분, edgecolor='k': 점 테두리는 검은색
plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train, edgecolor='k')
plt.title("Decision Tree Boundary")
plt.xlabel("Petal Length (cm)")
plt.ylabel("Petal Width (cm)")
plt.show()
Decision tree 의 깊이를 2로 했기 때문에 2줄이 그어졌다. 만약 test / train set 으로 나누지 않고 decision tree depth 를 None 으로 한다면 다음과 같은 과적합(overfitting) 모델이 만들어진다.
In [188]:
# 과적합 예시
clf_overfit = DecisionTreeClassifier(max_depth=None, random_state=42)
clf_overfit.fit(X, y)
Z = clf_overfit.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.figure(figsize=(12, 6))
plt.contourf(xx, yy, Z, alpha=0.3)
plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train, edgecolor='k')
plt.title("Decision Tree Boundary (Overfit)")
plt.xlabel("Petal Length (cm)")
plt.ylabel("Petal Width (cm)")
plt.show()모델이 과적합이 되면 해당 모델은 일반화 할 수 있는 가능성을 스스로 낮추게 된다. 따라서 과적합은 항상 지양하여야 한다.
이제 오분류 데이터를 시각화해보자.
In [189]:
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
wrong_pred = y_pred_test != y_test
# 결정 경계 (배경)
plt.figure(figsize=(12, 6))
plt.contourf(xx, yy, Z, alpha=0.25, cmap="Set2")
# train 데이터
plt.scatter(
X_train[:, 0],
X_train[:, 1],
c=y_train,
alpha=1,
cmap="Set2",
label="Train",
)
# test 데이터
plt.scatter(
X_test[:, 0],
X_test[:, 1],
c=y_test,
marker='^',
cmap="Set2",
s=80,
edgecolor='k',
label="Test"
)
# missclassified
plt.scatter(
X_test[wrong_pred, 0],
X_test[wrong_pred, 1],
facecolors="none",
edgecolors="red",
s=160,
linewidth=2,
label="Missclassified",
)
plt.title("Decision Tree Boundary + Missclassified")
plt.legend()
plt.show()