🤖 Machine Learning with sklearn @ DJ,Lim

Ch02 지도학습 — KNN 실습

타이타닉 데이터셋을 활용하여 KNN 모델을 구현하고, 가장 높은 일반화 성능을 갖는 k값을 찾아봅니다.

📚
KNN 분류
🐍
Python 3.8+
📅
2022년 5월 업데이트
1
데이터 준비 라이브러리 임포트 및 타이타닉 데이터 로드
📖 학습 목표
  • titanic 데이터 셋을 활용하여 knn 모델을 구현한다.
  • 가장 높은 일반화 성능을 갖는 k의 값은 무엇인지 찾아보자.

필요한 라이브러리를 임포트합니다.

Python — 라이브러리 임포트
import pandas as pd from sklearn.model_selection import train_test_split import numpy as np

CSV 파일에서 데이터를 불러옵니다.

Python — 데이터 로드
dat = pd.read_csv("train.csv") dat
▶ 출력 결과
     PassengerId  Survived  Pclass  ...  Fare Cabin Embarked
0              1         0       3  ...  7.2500   NaN        S
1              2         1       1  ... 71.2833   C85        C
2              3         1       3  ...  7.9250   NaN        S
3              4         1       1  ... 53.1000  C123        S
4              5         0       3  ...  8.0500   NaN        S
...          ...       ...     ...  ...     ...   ...      ...
886          887         0       2  ... 13.0000   NaN        S
887          888         1       1  ... 30.0000   B42        S
888          889         0       3  ... 23.4500   NaN        S
889          890         1       1  ... 30.0000  C148        C
890          891         0       3  ...  7.7500   NaN        Q

[891 rows x 12 columns]

컬럼명과 데이터 타입을 확인합니다.

Python — 컬럼 및 정보 확인
# 컬럼명 확인 dat.columns # 데이터 정보 확인 dat.info()
▶ 출력 결과
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype='object')

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype
---  ------       --------------  -----
 0   PassengerId  891 non-null    int64
 1   Survived     891 non-null    int64
 2   Pclass       891 non-null    int64
 3   Name         891 non-null    object
 4   Sex          891 non-null    object
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64
 7   Parch        891 non-null    int64
 8   Ticket       891 non-null    object
 9   Fare         891 non-null    float64
10   Cabin        204 non-null    object
11   Embarked     889 non-null    object
dtypes: float64(2), int64(5), object(5)
📌 데이터 구성

891개의 샘플, 12개의 컬럼. 타깃 변수는 Survived(생존 여부: 0 또는 1). 결측치는 Age, Cabin, Embarked에 존재합니다.

2
첫 번째 KNN 모델 Pclass, SibSp 특성만 사용하여 기본 모델 구축
📌 데이터 분할

test_size=0.1 (10% 테스트), random_state=0으로 재현성 확보

Python — 데이터 선택 및 분할
X = dat[ ['Pclass' , 'SibSp'] ] y = dat['Survived'] # 90% 학습용, 10% 테스트용 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=0) X_train.shape, X_test.shape, y_train.shape, y_test.shape
▶ 출력 결과
((801, 2), (90, 2), (801,), (90,))
💡 scikit-learn 모델 개발 4단계
KNN 모델 개발流程
① 데이터 준비
→
② 데이터 분할
→
③ 모델 학습 .fit()
→
④ 예측·평가
Python — KNN 모델 학습 및 예측
from sklearn.neighbors import KNeighborsClassifier model = KNeighborsClassifier(n_neighbors=2) model.fit(X_train, y_train) # 예측 pred = model.predict(X_test)
Python — 정확도 평가
(pred == y_test).sum() / len(pred) # 또는 print("테스트 세트의 정확도 : {:.2f}".format(np.mean(pred == y_test)))
▶ 출력 결과
0.6
테스트 세트의 정확도 : 0.60
⚠️ 낮은 성능

2개의 특성(Pclass, SibSp)만 사용한 기본 모델의 정확도는 60%에 불과합니다. 더 많은 특성과 전처리가 필요합니다.

3
결측치 처리 및 레이블 인코딩 범주형 데이터 변환과 결측값 대체
📖 핵심 개념
  • 레이블 인코딩 — 범주형 데이터를 숫자로 변환 (male → 1, female → 2)
  • 결측치 처리 — Age 컬럼의 결측값을 평균으로 대체
📌 map 함수

Series.map() — Series를 대상으로 원하는 함수 적용 또는 값을 대체합니다. dict, Series를 인자로 사용합니다.

Python — 레이블 인코딩 (Sex → Sex_num)
mapping = { "male": 1, 'female': 2 } dat['Sex_num'] = dat['Sex'].map(mapping) dat.head()
Python — Age 결측치 평균 대체
# fillna(): 결측값을 채운다 val_mean = dat['Age'].mean() dat['Age'] = dat['Age'].fillna(val_mean) dat.info()
▶ 출력 결과
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 13 columns):
 #   Column       Non-Null Count  Dtype
---  ------       --------------  -----
 0   PassengerId  891 non-null    int64
 1   Survived     891 non-null    int64
 2   Pclass       891 non-null    int64
 3   Name         891 non-null    object
 4   Sex          891 non-null    object
 5   Age          891 non-null    float64
 6   SibSp        891 non-null    int64
 7   Parch        891 non-null    int64
 8   Ticket       891 non-null    object
 9   Fare         891 non-null    float64
10   Cabin        204 non-null    object
11   Embarked     889 non-null    object
12   Sex_num      891 non-null    int64
dtypes: float64(2), int64(6), object(5)
✅ 전처리 완료

Sex_num 컬럼이 추가되었고, Age 결측치가 모두 평균값으로 대체되어 891개 non-null이 되었습니다.

4
전처리 후 재학습 4개 특성으로 KNN 모델 재구축

Pclass, SibSp, Sex_num, Age 4개 특성을 사용합니다.

Python — 데이터 분할 및 학습
X = dat[ ['Pclass' , 'SibSp', 'Sex_num', 'Age'] ] y = dat['Survived'] # 90% 학습용, 10% 테스트용 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=0) X_train.shape, X_test.shape, y_train.shape, y_test.shape
▶ 출력 결과
((801, 4), (90, 4), (801,), (90,))
Python — KNN 학습 및 평가
from sklearn.neighbors import KNeighborsClassifier model = KNeighborsClassifier(n_neighbors=2) model.fit(X_train, y_train) # 예측 pred = model.predict(X_test) print("테스트 세트의 정확도 : {:.2f}".format(np.mean(pred == y_test)))
▶ 출력 결과
테스트 세트의 정확도 : 0.76
✅ 성능 향상

전처리 후 정확도가 60% → 76%로 크게 향상되었습니다. 특성 엔지니어링의 중요성을 확인할 수 있습니다.

5
k값에 따른 성능 비교 최적의 k값 탐색
📌 k값 변화에 따른 정확도 추적

k=1부터 21까지 2씩 증가시키며 학습용/테스트용 정확도를 측정합니다.

Python — k값별 정확도 비교
tr_acc = [] test_acc = [] k_nums = range(1, 22, 2) # 1, 3, 5, ..., 21 for n in k_nums: model = KNeighborsClassifier(n_neighbors=n) model.fit(X_train, y_train) acc_tr = model.score(X_train, y_train) acc_test = model.score(X_test, y_test) tr_acc.append(acc_tr) test_acc.append(acc_test) print("k :", n) print("학습용셋 정확도 {:.3f}".format(acc_tr)) print("테스트용셋 정확도 {:.3f}".format(acc_test))
▶ 출력 결과
k :  1   | 학습: 0.885 | 테스트: 0.756
k :  3   | 학습: 0.863 | 테스트: 0.800
k :  5   | 학습: 0.850 | 테스트: 0.800
k :  7   | 학습: 0.839 | 테스트: 0.733
k :  9   | 학습: 0.830 | 테스트: 0.722
k : 11   | 학습: 0.826 | 테스트: 0.744
k : 13   | 학습: 0.824 | 테스트: 0.756
k : 15   | 학습: 0.815 | 테스트: 0.744
k : 17   | 학습: 0.805 | 테스트: 0.756
k : 19   | 학습: 0.792 | 테스트: 0.711
k : 21   | 학습: 0.790 | 테스트: 0.722
📊 결과 분석
  • k=3, 5일 때 테스트 정확도 80%로 가장 높음
  • k=1은 과대적합(overfitting) — 학습 정확도는 높지만 테스트는 낮음
  • k가 커질수록 학습 정확도는 감소 (모델이 단순해짐)
  • 최적의 k는 3 또는 5

데이터프레임으로 정리하고 시각화합니다.

Python — 결과 시각화
import seaborn as sns import matplotlib.pyplot as plt # 데이터프레임 생성 dat = { "tr_acc": tr_acc, "test_acc": test_acc } data_df = pd.DataFrame(dat, index=range(1, 22, 2)) data_df # 선 그래프 sns.lineplot(data=data_df, palette="tab10") plt.show()
▶ 출력 결과 (DataFrame)
     tr_acc   test_acc
1   0.885144  0.755556
3   0.862672  0.800000
5   0.850187  0.800000
7   0.838951  0.733333
9   0.830212  0.722222
11  0.826467  0.744444
13  0.823970  0.755556
15  0.815231  0.744444
17  0.805243  0.755556
19  0.791511  0.711111
21  0.790262  0.722222
💡 그래프 해석

파란색 선(tr_acc)은 k가 증가할수록 하락 (과대적합 해소), 주황색 선(test_acc)은 k=3~5에서 정점을 찍은 후 하락합니다. 즉 k=3 또는 5가 가장 적절한 일반화 성능을 보입니다.

6
실습 과제 직접 해보기 — 완료 체크리스트
📌

핵심 정리

KNN은 단순하지만 특성 스케일링, 결측치 처리, 하이퍼파라미터 k 튜닝이 중요합니다.

🔧

과대적합 vs 과소적합

k가 작으면 과대적합, k가 크면 과소적합. 교차 검증으로 최적 k를 찾는 습관을 기르세요.

📊

EDA 습관화

모델링 전 항상 데이터 분포, 결측치, 이상치를 먼저 탐색하세요.