붓꽃 분류 모델 실습 가이드

실습 목표

  • 첫 번째 머신러닝 모델 직접 구현
  • 사이킷런 기본 API 익히기
  • 데이터 시각화 및 분석 경험
  • 모델 성능 평가 및 해석 능력 습득

실습 환경 설정

필요한 라이브러리

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
from sklearn.preprocessing import StandardScaler

한글 폰트 설정 (선택사항)

plt.rcParams['font.family'] = 'Malgun Gothic'  # Windows
plt.rcParams['axes.unicode_minus'] = False

단계별 실습

1단계: 데이터 로드 및 탐색

# 데이터 로드
iris = load_iris()
print("데이터셋 정보:")
print(f"- 샘플 수: {iris.data.shape[0]}")
print(f"- 특성 수: {iris.data.shape[1]}")
print(f"- 클래스 수: {len(iris.target_names)}")
print(f"- 특성 이름: {iris.feature_names}")
print(f"- 클래스 이름: {iris.target_names}")
 
# 데이터프레임 생성
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = iris.target
df['species_name'] = df['species'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})
 
# 데이터 기본 정보
print("\n데이터 기본 정보:")
print(df.head())
print(f"\n데이터 형태: {df.shape}")
print(f"\n결측치 확인:\n{df.isnull().sum()}")

2단계: 탐색적 데이터 분석 (EDA)

# 기본 통계 정보
print("기본 통계 정보:")
print(df.describe())
 
# 클래스별 분포
print("\n클래스별 분포:")
print(df['species_name'].value_counts())
 
# 클래스별 평균 특성 값
print("\n클래스별 평균 특성 값:")
print(df.groupby('species_name').mean())

3단계: 데이터 시각화

# 1. 히스토그램 - 특성별 분포
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
features = iris.feature_names
for i, feature in enumerate(features):
    ax = axes[i//2, i%2]
    for species in range(3):
        data = iris.data[iris.target == species, i]
        ax.hist(data, alpha=0.6, label=iris.target_names[species])
    ax.set_title(f'{feature} 분포')
    ax.set_xlabel(feature)
    ax.set_ylabel('빈도')
    ax.legend()
plt.tight_layout()
plt.show()
 
# 2. 산점도 - 특성 간 관계
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
 
# 꽃받침 길이 vs 너비
scatter = axes[0].scatter(iris.data[:, 0], iris.data[:, 1], c=iris.target, cmap='viridis')
axes[0].set_xlabel('꽃받침 길이 (cm)')
axes[0].set_ylabel('꽃받침 너비 (cm)')
axes[0].set_title('꽃받침 길이 vs 너비')
 
# 꽃잎 길이 vs 너비
scatter = axes[1].scatter(iris.data[:, 2], iris.data[:, 3], c=iris.target, cmap='viridis')
axes[1].set_xlabel('꽃잎 길이 (cm)')
axes[1].set_ylabel('꽃잎 너비 (cm)')
axes[1].set_title('꽃잎 길이 vs 너비')
 
plt.tight_layout()
plt.show()
 
# 3. 쌍별 그래프 (Pair Plot)
sns.pairplot(df, hue='species_name', markers=["o", "s", "D"])
plt.show()
 
# 4. 상관관계 히트맵
correlation_matrix = df.iloc[:, :-2].corr()
plt.figure(figsize=(8, 6))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0)
plt.title('특성 간 상관관계')
plt.show()

4단계: 데이터 전처리

# 특성과 타겟 분리
X = iris.data
y = iris.target
 
# 데이터 분할 (80% 학습, 20% 테스트)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
 
print(f"학습 세트 크기: {X_train.shape[0]}")
print(f"테스트 세트 크기: {X_test.shape[0]}")
print(f"학습 세트 클래스 분포: {np.bincount(y_train)}")
print(f"테스트 세트 클래스 분포: {np.bincount(y_test)}")
 
# 특성 스케일링 (일부 알고리즘에 필요)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

5단계: 모델 학습 및 평가

# 여러 모델 정의
models = {
    'KNN': KNeighborsClassifier(n_neighbors=3),
    'Decision Tree': DecisionTreeClassifier(random_state=42),
    'Logistic Regression': LogisticRegression(random_state=42, max_iter=200),
    'SVM': SVC(random_state=42)
}
 
# 모델별 성능 비교
results = {}
 
for name, model in models.items():
    # 스케일링이 필요한 모델들
    if name in ['Logistic Regression', 'SVM']:
        model.fit(X_train_scaled, y_train)
        y_pred = model.predict(X_test_scaled)
    else:
        model.fit(X_train, y_train)
        y_pred = model.predict(X_test)
    
    # 성능 계산
    accuracy = accuracy_score(y_test, y_pred)
    results[name] = accuracy
    
    print(f"\n=== {name} 모델 결과 ===")
    print(f"정확도: {accuracy:.4f}")
    print("\n상세 분류 성능:")
    print(classification_report(y_test, y_pred, target_names=iris.target_names))

6단계: 최적 모델 선택 및 상세 분석

# 최고 성능 모델 선택
best_model_name = max(results, key=results.get)
best_accuracy = results[best_model_name]
 
print(f"\n최고 성능 모델: {best_model_name}")
print(f"최고 정확도: {best_accuracy:.4f}")
 
# 성능 비교 시각화
plt.figure(figsize=(10, 6))
models_names = list(results.keys())
accuracies = list(results.values())
 
bars = plt.bar(models_names, accuracies, color=['skyblue', 'lightgreen', 'lightcoral', 'lightsalmon'])
plt.title('모델별 정확도 비교')
plt.ylabel('정확도')
plt.ylim(0, 1)
 
# 정확도 값 표시
for bar, accuracy in zip(bars, accuracies):
    plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, 
             f'{accuracy:.3f}', ha='center', va='bottom')
 
plt.tight_layout()
plt.show()

7단계: 혼동 행렬 (Confusion Matrix) 분석

# 최고 성능 모델로 혼동 행렬 생성
if best_model_name in ['Logistic Regression', 'SVM']:
    best_model = models[best_model_name]
    best_model.fit(X_train_scaled, y_train)
    y_pred_best = best_model.predict(X_test_scaled)
else:
    best_model = models[best_model_name]
    best_model.fit(X_train, y_train)
    y_pred_best = best_model.predict(X_test)
 
# 혼동 행렬 시각화
cm = confusion_matrix(y_test, y_pred_best)
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
            xticklabels=iris.target_names, 
            yticklabels=iris.target_names)
plt.title(f'{best_model_name} 모델의 혼동 행렬')
plt.xlabel('예측 클래스')
plt.ylabel('실제 클래스')
plt.show()
 
# 분류 오류 분석
print("\n분류 오류 분석:")
incorrect_indices = np.where(y_test != y_pred_best)[0]
if len(incorrect_indices) > 0:
    print(f"총 {len(incorrect_indices)}개 오분류")
    for idx in incorrect_indices:
        actual = iris.target_names[y_test[idx]]
        predicted = iris.target_names[y_pred_best[idx]]
        print(f"실제: {actual}, 예측: {predicted}")
else:
    print("완벽한 분류 - 오분류 없음!")

8단계: 새로운 데이터 예측

# 새로운 샘플 예측 예제
new_samples = np.array([
    [5.1, 3.5, 1.4, 0.2],  # Setosa 같은 특성
    [6.2, 2.9, 4.3, 1.3],  # Versicolor 같은 특성
    [7.3, 2.9, 6.3, 1.8]   # Virginica 같은 특성
])
 
print("\n새로운 샘플 예측:")
for i, sample in enumerate(new_samples):
    if best_model_name in ['Logistic Regression', 'SVM']:
        sample_scaled = scaler.transform([sample])
        prediction = best_model.predict(sample_scaled)[0]
        probabilities = best_model.predict_proba(sample_scaled)[0] if hasattr(best_model, 'predict_proba') else None
    else:
        prediction = best_model.predict([sample])[0]
        probabilities = best_model.predict_proba([sample])[0] if hasattr(best_model, 'predict_proba') else None
    
    print(f"샘플 {i+1}: {sample}")
    print(f"  예측: {iris.target_names[prediction]}")
    if probabilities is not None:
        print(f"  확률: {dict(zip(iris.target_names, probabilities))}")
    print()

9단계: 모델 저장 및 불러오기

import joblib
 
# 모델 저장
model_filename = f'iris_best_model_{best_model_name.replace(" ", "_").lower()}.pkl'
joblib.dump(best_model, model_filename)
print(f"모델이 '{model_filename}'에 저장되었습니다.")
 
# 스케일러도 저장 (필요한 경우)
if best_model_name in ['Logistic Regression', 'SVM']:
    scaler_filename = 'iris_scaler.pkl'
    joblib.dump(scaler, scaler_filename)
    print(f"스케일러가 '{scaler_filename}'에 저장되었습니다.")
 
# 모델 불러오기 예제
loaded_model = joblib.load(model_filename)
print(f"모델이 '{model_filename}'에서 불러와졌습니다.")

실습 체크리스트

데이터 탐색 ✓

  • 데이터셋 기본 정보 확인
  • 클래스 분포 확인
  • 특성별 통계 분석
  • 시각화를 통한 패턴 파악

모델 개발 ✓

  • 데이터 전처리 (분할, 스케일링)
  • 여러 모델 비교 실험
  • 최적 모델 선택
  • 성능 평가 및 해석

결과 분석 ✓

  • 혼동 행렬 해석
  • 분류 오류 분석
  • 새로운 데이터 예측
  • 모델 저장/불러오기

확장 실습 과제

1. 하이퍼파라미터 튜닝

from sklearn.model_selection import GridSearchCV
 
# KNN 하이퍼파라미터 튜닝
param_grid = {'n_neighbors': [3, 5, 7, 9, 11]}
grid_search = GridSearchCV(KNeighborsClassifier(), param_grid, cv=5)
grid_search.fit(X_train, y_train)
print(f"최적 k 값: {grid_search.best_params_}")

2. 교차 검증

from sklearn.model_selection import cross_val_score
 
# 5-fold 교차 검증
cv_scores = cross_val_score(best_model, X_train, y_train, cv=5)
print(f"교차 검증 점수: {cv_scores}")
print(f"평균 점수: {cv_scores.mean():.4f} (+/- {cv_scores.std() * 2:.4f})")

3. 특성 중요도 분석

# 결정 트리의 특성 중요도
if best_model_name == 'Decision Tree':
    feature_importance = best_model.feature_importances_
    feature_names = iris.feature_names
    
    plt.figure(figsize=(10, 6))
    plt.barh(feature_names, feature_importance)
    plt.title('특성 중요도')
    plt.xlabel('중요도')
    plt.show()

학습 성과 요약

이 실습을 통해 다음을 경험했습니다:

  1. 데이터 이해: 붓꽃 데이터셋의 구조와 특성
  2. 시각화: 다양한 그래프를 통한 데이터 탐색
  3. 모델 비교: 여러 알고리즘의 성능 비교
  4. 성능 평가: 정확도, 혼동 행렬 등 평가 지표
  5. 실무 적용: 새로운 데이터 예측 및 모델 저장

이 실습은 머신러닝의 전체 워크플로우를 경험하는 첫 번째 실습입니다. 각 단계를 차근차근 따라하며 개념을 익혀보세요.