사이킷런으로 첫 머신러닝 모델 구축하기

인공지능(AI)과 머신러닝(Machine Learning)이 세상을 바꾸고 있다는 이야기는 더 이상 낯설지 않습니다. 거대 기술 기업의 전유물처럼 느껴졌던 이 기술은 이제 우리 삶 곳곳에 스며들고 있으며, 많은 개발자들이 새로운 가능성을 탐색하는 강력한 도구가 되었습니다. 하지만 어디서부터 시작해야 할지 막막하게 느껴지는 것이 사실입니다. 수많은 이론과 복잡한 수학 공식 앞에서 좌절감을 느끼기도 합니다. 이 글은 바로 그런 분들을 위해 준비되었습니다. 파이썬(Python)이라는 강력하고 직관적인 프로그래밍 언어와, 머신러닝의 '치트키'라 불리는 사이킷런(Scikit-learn) 라이브러리를 활용하여 당신의 첫 머신러닝 모델을 만들어보는 여정을 함께 떠나보겠습니다.

이 튜토리얼은 단순히 코드를 따라 치는 것을 넘어, 각 단계가 어떤 의미를 가지는지, 왜 이런 과정이 필요한지를 이해하는 데 초점을 맞춥니다. 데이터 과학의 전체 워크플로우를 경험하며, 데이터와 소통하고, 모델을 훈련시키고, 성능을 객관적으로 평가하는 방법을 배우게 될 것입니다. 오늘 우리가 다룰 '붓꽃 품종 분류' 문제는 머신러닝계의 'Hello, World!'와 같지만, 이 작은 프로젝트를 완수하고 나면 여러분은 AI 개발에 대한 막연한 두려움을 자신감으로 바꿀 수 있을 것입니다.

이 가이드에서 다루는 내용:
  • AI 개발을 위한 파이썬 개발 환경 완벽 설정 가이드 (가상환경 포함)
  • 데이터 과학의 필수 라이브러리(Pandas, NumPy, Scikit-learn) 핵심 개념 이해
  • 실전 예제: 붓꽃(Iris) 데이터셋을 이용한 분류 모델 구축 A to Z
  • 데이터 불러오기, 정제, 그리고 시각화를 통한 탐색적 데이터 분석(EDA)
  • 모델 학습과 평가를 위한 데이터 분리의 중요성 및 실습
  • 결정 트리(Decision Tree), K-최근접 이웃(KNN) 등 다양한 모델 학습 및 비교
  • 정확도(Accuracy)를 넘어 정밀도(Precision), 재현율(Recall)까지 알아보는 모델 성능 평가

AI 여정의 첫걸음, 개발 환경 설정

모든 위대한 여정은 첫걸음에서 시작됩니다. 머신러닝 모델 개발의 첫걸음은 바로 안정적이고 독립적인 개발 환경을 구축하는 것입니다. 프로젝트마다 사용하는 라이브러리의 종류와 버전이 다를 수 있기 때문에, 프로젝트별로 격리된 환경을 만들어 관리하는 것은 매우 중요합니다. 이는 '의존성 지옥(dependency hell)'이라 불리는 문제를 예방하고, 내 컴퓨터를 깔끔하게 유지하는 최고의 방법입니다.

1. 파이썬 설치

가장 먼저, 우리의 주력 언어인 파이썬을 설치해야 합니다. 이미 설치되어 있을 수도 있지만, 최신 안정화 버전을 사용하는 것을 권장합니다. 공식 Python 웹사이트에 방문하여 운영체제에 맞는 설치 파일을 다운로드하여 설치를 진행하세요. 설치 과정에서 'Add Python to PATH' 또는 이와 유사한 옵션을 반드시 체크해야 터미널(명령 프롬프트) 어디서든 `python` 명령어를 사용할 수 있습니다.

설치가 완료되었다면, 터미널을 열고 다음 명령어를 입력하여 설치된 버전을 확인해보세요.


python --version
# 또는 python3 --version

파이썬 3.8 이상의 버전이 표시된다면 성공적으로 설치된 것입니다.

2. 가상환경(Virtual Environment) 생성 및 활성화

가상환경은 특정 프로젝트를 위한 독립적인 파이썬 실행 환경을 만들어주는 마법 같은 도구입니다. `venv`는 파이썬에 기본적으로 내장된 모듈로, 별도의 설치 없이 바로 사용할 수 있습니다.

먼저, 프로젝트를 진행할 폴더(예: `ml-project`)를 하나 만드세요. 그리고 터미널에서 해당 폴더로 이동합니다.


mkdir ml-project
cd ml-project

이제 이 폴더 내부에 `venv`라는 이름의 가상환경을 생성해 보겠습니다.


# Windows
python -m venv venv

# macOS / Linux
python3 -m venv venv

이 명령을 실행하면 현재 폴더(`ml-project`) 안에 `venv`라는 하위 폴더가 생성됩니다. 이 폴더에는 독립적인 파이썬 인터프리터와 라이브러리들이 설치될 공간이 마련됩니다. 이제 이 가상환경을 '활성화'시켜야 합니다.


# Windows
.\venv\Scripts\activate

# macOS / Linux
source venv/bin/activate

활성화에 성공하면 터미널 프롬프트 맨 앞에 `(venv)`라는 표시가 붙게 됩니다. 이는 현재 터미널 세션이 시스템의 전역 파이썬이 아닌, 우리가 방금 만든 `venv` 가상환경의 파이썬을 사용하고 있음을 의미합니다. 이제부터 설치하는 모든 라이브러리는 이 가상환경 안에만 설치됩니다.

가상환경 사용은 선택이 아닌 필수입니다. 협업 시 다른 개발자와 동일한 환경을 쉽게 구성할 수 있게 해주며, 다양한 프로젝트 간의 라이브러리 충돌을 원천적으로 방지합니다. 가상환경에서 나가고 싶을 때는 터미널에 `deactivate`라고 입력하면 됩니다.

3. 필수 라이브러리 설치

가상환경이 활성화된 상태에서, 파이썬의 패키지 관리자인 `pip`을 사용하여 데이터 과학 및 머신러닝에 필요한 핵심 라이브러리들을 설치하겠습니다.


pip install numpy pandas scikit-learn matplotlib seaborn jupyterlab

설치가 진행되는 동안, 각 라이브러리가 어떤 역할을 하는지 간단히 살펴보겠습니다.

  • NumPy (넘파이): Numerical Python의 약자로, 파이썬에서 과학 계산을 위한 핵심 라이브러리입니다. 강력한 다차원 배열 객체(ndarray)와 이를 다루는 다양한 함수를 제공하여, 벡터 및 행렬 연산을 매우 빠르게 처리할 수 있게 해줍니다. 머신러닝 알고리즘의 내부 연산은 대부분 NumPy를 기반으로 합니다.
  • Pandas (판다스): 테이블 형태의 데이터(CSV, 엑셀 파일 등)를 쉽고 직관적으로 다룰 수 있게 해주는 라이브러리입니다. DataFrame이라는 강력한 데이터 구조를 제공하여 데이터 정제, 변환, 분석, 시각화 등 데이터 전처리 과정 전반에 걸쳐 필수적으로 사용됩니다.
  • Scikit-learn (사이킷런): 이 튜토리얼의 주인공입니다. 분류, 회귀, 군집화, 차원 축소 등 다양한 머신러닝 알고리즘과 함께 모델 선택, 평가, 데이터 전처리를 위한 유용한 도구들을 포함하고 있는, 파이썬 머신러닝 생태계의 핵심 라이브러리입니다.
  • Matplotlib (맷플롯립): 파이썬의 대표적인 데이터 시각화 라이브러리입니다. 데이터를 그래프나 플롯 형태로 시각화하여 데이터의 패턴이나 인사이트를 직관적으로 파악할 수 있도록 도와줍니다.
  • Seaborn (시본): Matplotlib을 기반으로 더 아름답고 통계적으로 의미 있는 그래프를 쉽게 그릴 수 있도록 도와주는 라이브러리입니다. 복잡한 시각화를 단 몇 줄의 코드로 구현할 수 있게 해줍니다.
  • JupyterLab (주피터랩): 웹 브라우저에서 대화형으로 코드를 작성하고 실행하며, 시각화 결과와 마크다운 문서를 함께 볼 수 있는 강력한 개발 환경입니다. 데이터 분석 및 머신러닝 모델링처럼 실험과 탐색이 중요한 작업에 최적화되어 있습니다.

4. 주피터랩 실행

모든 설치가 완료되었다면, 터미널에서 다음 명령어를 입력하여 주피터랩을 실행합니다.


jupyter lab

자동으로 웹 브라우저 새 탭이 열리며 주피터랩 인터페이스가 나타납니다. 왼쪽 탐색기에서 'Notebook'을 클릭하여 새로운 노트북 파일(.ipynb)을 생성하면, 이제 본격적인 AI 모델 개발을 시작할 준비가 모두 끝났습니다. 이 노트북 파일이 우리의 실험실이자 연구 노트가 될 것입니다.

핵심 라이브러리 파헤치기: 데이터 과학의 삼총사

모델을 만들기 전에, 우리가 사용할 도구들과 친숙해지는 시간을 갖겠습니다. 특히 Pandas, NumPy, 그리고 Scikit-learn은 데이터 과학 프로젝트의 거의 모든 단계에서 사용되므로, 이들의 기본 철학과 사용법을 이해하는 것은 매우 중요합니다. 이들은 서로 긴밀하게 연결되어 작동하며, 함께 사용될 때 엄청난 시너지를 발휘합니다.

Pandas: 데이터 조작의 마에스트로

판다스는 정형 데이터를 다루는 데 있어 파이썬의 표준과도 같은 라이브러리입니다. 핵심 데이터 구조는 DataFrameSeries입니다.

  • Series (시리즈): 1차원 배열과 같은 구조로, 하나의 데이터 타입으로 구성된 값들과 각 값에 대응하는 인덱스(index)를 가집니다.
  • DataFrame (데이터프레임): 2차원 테이블(표) 형태의 데이터 구조로, 여러 개의 시리즈가 모여 만들어진 것으로 생각할 수 있습니다. 각 열(column)은 서로 다른 데이터 타입을 가질 수 있습니다. 우리가 흔히 보는 엑셀 시트나 데이터베이스 테이블과 매우 유사합니다.

주피터 노트북의 셀에 다음 코드를 입력하고 실행해보세요. (Shift + Enter)


import pandas as pd
import numpy as np

# 딕셔너리를 사용하여 데이터프레임 생성
data = {
    '이름': ['김철수', '이영희', '박지성', '손흥민'],
    '나이': [25, 31, 42, 31],
    '도시': ['서울', '부산', '런던', '런던'],
    '점수': [85.5, 92.0, np.nan, 88.8] # np.nan은 결측치를 의미
}

df = pd.DataFrame(data)

# 데이터프레임 출력
print("--- 전체 데이터프레임 ---")
print(df)

위 코드는 간단한 데이터를 담은 데이터프레임을 생성합니다. 판다스는 데이터를 탐색하는 데 매우 유용한 여러 메서드를 제공합니다.


# 데이터의 처음 2개 행을 확인
print("\n--- head(2) ---")
print(df.head(2))

# 데이터의 기술 통계량 요약
print("\n--- describe() ---")
print(df.describe())

# 데이터프레임의 전체적인 정보 (컬럼별 데이터 타입, null 값 개수 등)
print("\n--- info() ---")
df.info()

.head()는 데이터를 빠르게 훑어볼 때, .describe()는 수치형 데이터의 분포(평균, 표준편차, 사분위수 등)를 파악할 때, .info()는 데이터의 전체적인 구조와 결측치(missing value) 존재 여부를 확인할 때 매우 유용합니다. 실제 데이터 분석 프로젝트의 첫 단계는 항상 이렇게 데이터를 불러오고, 이 세 가지 메서드를 통해 데이터의 '건강 상태'를 확인하는 것입니다.

NumPy: 수치 연산의 심장

넘파이는 파이썬 리스트보다 훨씬 빠르고 효율적인 다차원 배열 `ndarray`를 제공합니다. 판다스의 데이터프레임 내부에서도 수치 데이터는 대부분 넘파이 배열 형태로 저장됩니다. 사이킷런의 머신러닝 알고리즘들은 입력값으로 넘파이 배열을 기대하는 경우가 많습니다.

넘파이의 강력함은 '벡터화(Vectorization)' 연산에서 드러납니다. 반복문 없이 배열의 모든 요소에 대해 한 번에 연산을 적용할 수 있어 코드가 간결해지고 실행 속도가 비약적으로 향상됩니다.


# 파이썬 리스트
list1 = [1, 2, 3, 4, 5]
# 각 요소에 2를 곱하려면 반복문이 필요
list2 = [x * 2 for x in list1] 
print(f"파이썬 리스트 연산: {list2}")

# 넘파이 배열
arr1 = np.array([1, 2, 3, 4, 5])
# 벡터화 연산: 반복문 없이 바로 연산 가능
arr2 = arr1 * 2
print(f"넘파이 배열 연산: {arr2}")

데이터 분석과 머신러닝에서는 대규모 행렬 연산이 빈번하게 발생하는데, 이 때 넘파이의 성능은 프로젝트의 속도를 좌우하는 핵심 요소가 됩니다.

Scikit-learn: 머신러닝을 위한 스위스 아미 나이프

사이킷런은 일관성 있고 쉬운 API 설계를 자랑합니다. 수많은 머신러닝 알고리즘이 서로 다른 원리로 동작하지만, 사이킷런에서는 이들을 거의 동일한 방식으로 사용할 수 있습니다. 이 일관성이 바로 사이킷런의 가장 큰 장점입니다.

사이킷런 API의 핵심 패턴은 다음과 같습니다.

  1. 모델 클래스 선택 및 인스턴스화: 사용하고자 하는 알고리즘에 해당하는 클래스를 `import`하고, 하이퍼파라미터(모델의 동작을 제어하는 설정값)를 지정하여 모델 객체(인스턴스)를 생성합니다.
  2. `fit()` 메서드로 모델 학습: 준비된 훈련 데이터(특성 데이터 `X`와 정답 레이블 `y`)를 `fit()` 메서드에 전달하여 모델을 학습시킵니다. 이 과정에서 모델은 데이터의 패턴을 학습하게 됩니다.
  3. `predict()` 메서드로 예측: 학습된 모델을 사용하여 새로운 데이터(테스트 데이터)에 대한 예측을 수행합니다.
  4. `score()` 또는 다른 평가 함수로 성능 평가: 모델의 예측이 얼마나 정확한지 평가합니다.

예를 들어, 어떤 분류 모델을 사용하든 기본적인 코드는 아래와 같은 형태를 띠게 됩니다.


# 1. 모델 클래스 선택 및 인스턴스화
# from sklearn.some_module import SomeModel
# model = SomeModel(hyperparameter1='value')

# 2. 모델 학습
# model.fit(X_train, y_train)

# 3. 예측
# predictions = model.predict(X_test)

# 4. 평가
# accuracy = model.score(X_test, y_test)

이러한 일관성 덕분에 우리는 다양한 모델을 쉽게 교체하며 실험할 수 있습니다. 이제 이 도구들을 가지고 실제 문제를 해결해볼 시간입니다.

실전 프로젝트: 붓꽃(Iris) 품종 분류 모델 만들기

이론만으로는 지루할 수 있습니다. 이제 실제 데이터를 가지고 머신러닝 프로젝트의 전 과정을 직접 경험해보겠습니다. 우리의 목표는 붓꽃의 꽃받침(sepal)과 꽃잎(petal)의 길이와 너비 정보를 바탕으로, 해당 붓꽃이 세 가지 품종('setosa', 'versicolor', 'virginica') 중 어디에 속하는지 자동으로 분류하는 모델을 만드는 것입니다. 이것은 전형적인 지도 학습(Supervised Learning)분류(Classification) 문제에 해당합니다.

1단계: 문제 정의 및 데이터 준비

사이킷런은 학습용으로 사용할 수 있는 여러 데이터셋을 기본적으로 제공합니다. 그 중 하나가 바로 붓꽃(Iris) 데이터셋입니다. 먼저 필요한 라이브러리들을 모두 불러온 후, 데이터를 로드하겠습니다.


import pandas as pd
import numpy as np
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.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# Matplotlib 한글 폰트 설정 (Windows 예시, 환경에 맞게 수정 필요)
# from matplotlib import font_manager, rc
# font_path = "c:/Windows/Fonts/malgun.ttf"
# font = font_manager.FontProperties(fname=font_path).get_name()
# rc('font', family=font)

# 데이터 로드
iris = load_iris()

# 데이터의 구조 확인
print(iris.keys())
# 출력: dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module'])

load_iris()가 반환하는 객체는 딕셔너리와 유사한 Bunch 객체입니다. 주요 키의 내용은 다음과 같습니다.

  • data: 특성 데이터(feature data). 꽃받침/꽃잎의 길이/너비가 담긴 NumPy 배열입니다.
  • target: 레이블 데이터(label data). 각 데이터가 어떤 품종에 속하는지를 0, 1, 2로 나타낸 NumPy 배열입니다.
  • feature_names: 각 특성의 이름. ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
  • target_names: 각 레이블의 이름. ['setosa', 'versicolor', 'virginica']

이 데이터는 매우 깔끔하게 정제되어 있지만, 실제 프로젝트에서는 보통 CSV 파일 등으로 데이터를 받게 됩니다. 따라서 분석과 조작이 용이한 판다스 데이터프레임으로 변환하는 것이 일반적입니다.


# 판다스 데이터프레임으로 변환
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label'] = iris.target

# 변환된 데이터프레임 확인
iris_df.head()

이렇게 데이터프레임으로 만들면 데이터를 한눈에 파악하고 다루기가 훨씬 수월해집니다. `label` 컬럼의 0은 'setosa', 1은 'versicolor', 2는 'virginica'를 의미합니다.

2단계: 탐색적 데이터 분석 (EDA - Exploratory Data Analysis)

모델을 만들기 전에 데이터를 깊이 있게 이해하는 과정은 필수적입니다. 데이터에 숨겨진 패턴, 이상치, 변수 간의 관계 등을 시각화를 통해 파악하는 이 과정을 EDA라고 합니다. EDA를 통해 모델링 전략에 대한 중요한 힌트를 얻을 수 있습니다.

먼저, 데이터의 기본적인 통계 정보를 확인합니다.


# 기술 통계량 확인
iris_df.describe()

describe()의 결과를 보면 각 특성의 평균, 표준편차, 최솟값, 최댓값 등을 알 수 있습니다. 예를 들어, 'petal length (cm)'의 평균은 3.75cm이고, 최솟값은 1.0cm, 최댓값은 6.9cm인 것을 확인할 수 있습니다. 데이터의 스케일이 크게 차이나지 않는다는 점도 엿볼 수 있습니다.

결측치가 있는지도 확인해봅니다.


# 결측치 확인
iris_df.info()

출력 결과를 보면 'Non-Null Count'가 모든 컬럼에서 150으로, 전체 데이터 개수와 동일합니다. 즉, 이 데이터셋에는 결측치가 없다는 것을 의미합니다. 만약 결측치가 있다면, 특정 값으로 채우거나(Imputation) 해당 행을 제거하는 등의 처리가 필요합니다.

이제 시각화를 통해 데이터의 관계를 살펴보겠습니다. Seaborn의 `pairplot`은 데이터프레임에 있는 모든 특성들의 쌍에 대해 관계를 시각화해주어 매우 강력합니다.


# pairplot으로 전체 특성 간의 관계 시각화
# hue='label'을 통해 품종별로 색상을 다르게 표시
sns.pairplot(iris_df, hue='label', palette='bright')
plt.show()

이 플롯을 통해 많은 정보를 얻을 수 있습니다. 예를 들어, 대각선에 위치한 히스토그램을 보면 각 특성의 품종별 분포를 알 수 있습니다. 'petal length (cm)'와 'petal width (cm)'를 보면, label 0(setosa)이 다른 두 품종과 명확하게 구분되는 것을 볼 수 있습니다. 반면 label 1(versicolor)과 label 2(virginica)는 다소 겹치는 영역이 존재합니다. 이는 setosa 품종은 분류하기 매우 쉽지만, 나머지 두 품종을 분류하는 데는 조금 더 정교한 모델이 필요할 수 있음을 시사합니다. 이러한 인사이트가 바로 EDA의 힘입니다.

EDA의 중요성: "Garbage in, garbage out" (쓰레기를 넣으면, 쓰레기가 나온다)는 데이터 과학의 유명한 격언입니다. 아무리 뛰어난 머신러닝 알고리즘이라도, 데이터 자체의 품질이 낮거나 데이터에 대한 이해가 부족하면 좋은 성능을 낼 수 없습니다. EDA는 우리가 다루는 데이터의 본질을 이해하고, 더 나은 모델을 만들기 위한 첫 단추입니다.

모델링: 데이터 분리부터 학습까지

데이터에 대한 이해를 마쳤으니, 이제 본격적으로 머신러닝 모델을 만들어보겠습니다. 모델링 과정은 크게 '데이터 분리', '모델 선택', '모델 학습'의 세 단계로 나눌 수 있습니다.

3단계: 훈련 데이터와 테스트 데이터 분리

머신러닝 모델의 성능을 객관적으로 평가하기 위해 가장 중요한 원칙 중 하나는, 모델을 학습시킨 데이터로 모델을 평가해서는 안 된다는 것입니다. 이는 마치 학생에게 연습문제를 풀게 한 뒤, 똑같은 문제로 시험을 보는 것과 같습니다. 학생이 정답을 단순히 외웠을 뿐, 문제 해결 능력을 갖췄는지는 알 수 없습니다.

따라서 우리는 가지고 있는 전체 데이터를 두 그룹으로 나눕니다.

  • 훈련 데이터 (Training Data): 모델을 학습시키는 데 사용되는 데이터. 모델은 이 데이터의 패턴을 보고 학습합니다. (연습문제)
  • 테스트 데이터 (Test Data): 학습이 완료된 모델의 성능을 평가하는 데 사용되는 데이터. 모델은 이 데이터를 학습 과정에서 본 적이 없어야 합니다. (시험문제)

사이킷런의 `train_test_split` 함수를 사용하면 이 과정을 매우 쉽게 수행할 수 있습니다.


# 특성 데이터(X)와 레이블 데이터(y) 분리
X_data = iris_df.drop('label', axis=1) # 'label' 컬럼을 제외한 나머지 컬럼
y_data = iris_df['label']               # 'label' 컬럼

# 훈련 데이터와 테스트 데이터 분리 (80% 훈련, 20% 테스트)
X_train, X_test, y_train, y_test = train_test_split(
    X_data, 
    y_data, 
    test_size=0.2, 
    random_state=42,
    stratify=y_data
)

# 분리된 데이터의 크기 확인
print("X_train shape:", X_train.shape) # (120, 4)
print("X_test shape:", X_test.shape)   # (30, 4)
print("y_train shape:", y_train.shape) # (120,)
print("y_test shape:", y_test.shape)   # (30,)

train_test_split 함수의 주요 인자들을 살펴보겠습니다.

  • X_data, y_data: 분할할 특성 데이터와 레이블 데이터입니다.
  • test_size=0.2: 전체 데이터 중 20%를 테스트 데이터로 할당하겠다는 의미입니다. (따라서 훈련 데이터는 80%가 됩니다.)
  • random_state=42: 데이터를 무작위로 섞을 때 사용되는 시드(seed) 값입니다. 이 값을 고정하면 코드를 여러 번 실행해도 항상 동일한 방식으로 데이터가 분할됩니다. 이는 실험 결과를 재현하는 데 매우 중요합니다. 아무 숫자나 상관없지만, 보통 42가 관례적으로 많이 쓰입니다.
  • stratify=y_data: 이 옵션이 매우 중요합니다. 원본 데이터의 레이블 분포를 훈련 데이터와 테스트 데이터에서도 동일하게 유지하도록 만듭니다. 예를 들어, 원본 데이터에 A, B, C 클래스가 3:4:3 비율로 있었다면, 분할된 훈련/테스트 데이터에서도 이 비율이 유지됩니다. 분류 문제에서는 특정 클래스의 데이터가 한쪽으로 쏠리는 것을 방지하기 위해 이 옵션을 반드시 사용하는 것이 좋습니다.

4G단계: 모델 선택 및 학습

이제 훈련 데이터를 사용하여 모델을 학습시킬 차례입니다. 우리는 초보자가 이해하기 쉬우면서도 강력한 두 가지 분류 모델, 결정 트리(Decision Tree)K-최근접 이웃(K-Nearest Neighbors, KNN)을 사용해보겠습니다.

모델 1: 결정 트리 (Decision Tree)

결정 트리는 '스무고개'와 유사한 방식으로 작동합니다. 데이터의 특성(feature)에 대한 질문을 연속적으로 던져가며 데이터를 분류해나가는 모델입니다. "꽃잎의 길이가 5cm보다 작은가?" (예/아니오) 와 같은 질문들을 통해 데이터를 점점 더 순수한(하나의 품종만 있는) 그룹으로 나누어 갑니다. 모델의 학습 과정이 직관적으로 이해하기 쉽다는 큰 장점이 있습니다.


# 결정 트리 모델 생성 및 학습
# random_state를 지정하여 실행할 때마다 동일한 결과가 나오도록 함
dt_model = DecisionTreeClassifier(random_state=42)

# 훈련 데이터로 모델 학습
dt_model.fit(X_train, y_train)

print("결정 트리 모델 학습 완료!")

단 두 줄의 코드로 모델 학습이 끝났습니다. `fit` 메서드가 호출되는 순간, 결정 트리 모델은 X_train 데이터의 특성과 y_train 레이블 간의 관계를 가장 잘 설명하는 질문(분류 규칙)들의 집합을 스스로 찾아냅니다. 이것이 바로 '학습' 과정입니다.

모델 2: K-최근접 이웃 (KNN)

KNN은 '유유상종'이라는 말과 가장 잘 어울리는 알고리즘입니다. 새로운 데이터가 들어왔을 때, 기존 훈련 데이터 중에서 가장 가까운 'K'개의 이웃을 찾습니다. 그리고 그 이웃들이 가장 많이 속해 있는 클래스로 새로운 데이터를 분류합니다. 예를 들어 K=5일 때, 가장 가까운 이웃 5개 중 3개가 'versicolor'이고 2개가 'virginica'라면, 새로운 데이터는 'versicolor'로 예측됩니다. 매우 단순하지만 많은 경우에 좋은 성능을 보입니다.


# K-최근접 이웃 모델 생성 및 학습
# 이웃의 개수(n_neighbors)는 보통 홀수로 지정
knn_model = KNeighborsClassifier(n_neighbors=5)

# 훈련 데이터로 모델 학습
knn_model.fit(X_train, y_train)

print("KNN 모델 학습 완료!")

역시 `fit` 메서드를 통해 간단하게 학습이 완료되었습니다. KNN의 `fit`은 사실 결정 트리처럼 복잡한 규칙을 찾는 것이 아니라, 훈련 데이터를 메모리에 저장하는 과정에 가깝습니다. 실제 분류는 `predict`가 호출될 때 이루어집니다.

평가와 개선: 모델 성능의 진실

모델을 만들었으니, 이제 이 모델들이 얼마나 잘 작동하는지 평가해볼 시간입니다. 평가는 반드시 학습에 사용되지 않은 '테스트 데이터'로 수행해야 합니다. 모델이 처음 보는 문제에 대해 얼마나 잘 일반화하여 예측하는지를 측정하는 것입니다.

5단계: 예측 및 성능 평가

학습된 모델의 `predict` 메서드에 테스트 데이터의 특성(X_test)을 입력하여 예측을 수행합니다.


# 결정 트리 모델로 테스트 데이터 예측
dt_pred = dt_model.predict(X_test)

# KNN 모델로 테스트 데이터 예측
knn_pred = knn_model.predict(X_test)

# 예측 결과 확인 (앞 5개)
print("결정 트리 예측:", dt_pred[:5])
print("KNN 예측:    ", knn_pred[:5])
print("실제 정답:    ", y_test.values[:5])

이제 이 예측 결과(dt_pred, knn_pred)가 실제 정답(y_test)과 얼마나 일치하는지 확인해야 합니다. 가장 직관적인 평가 지표는 정확도(Accuracy)입니다.

정확도 = (올바르게 예측한 데이터의 수) / (전체 데이터의 수)


# 정확도 평가
dt_accuracy = accuracy_score(y_test, dt_pred)
knn_accuracy = accuracy_score(y_test, knn_pred)

print(f"결정 트리 정확도: {dt_accuracy:.4f}") # 약 1.0000
print(f"KNN 정확도: {knn_accuracy:.4f}") # 약 1.0000

두 모델 모두 1.0, 즉 100%의 정확도를 보였습니다! 붓꽃 데이터셋이 비교적 분류하기 쉬운 문제이기 때문에 가능한 결과입니다. 하지만 실제 문제에서는 정확도만으로 모델의 성능을 판단하기에는 부족한 경우가 많습니다.

예를 들어, 암 환자 100명 중 1명이 실제 암이고 99명이 정상인 데이터를 예측하는 모델이 있다고 가정해봅시다. 만약 모델이 모든 환자를 '정상'이라고만 예측해도 정확도는 99%가 됩니다. 하지만 이 모델은 단 한 명의 암 환자도 찾아내지 못했으므로 실제로는 아무 쓸모가 없습니다. 이처럼 데이터의 클래스 분포가 불균형할 때 정확도는 성능을 왜곡할 수 있습니다.

이럴 때 사용하는 것이 혼동 행렬(Confusion Matrix)정밀도(Precision), 재현율(Recall)입니다.

  • 혼동 행렬: 모델의 예측이 정답과 비교하여 얼마나 헷갈렸는지(confused)를 행렬 형태로 보여줍니다. 행은 실제 클래스, 열은 예측된 클래스를 나타냅니다. 대각선에 위치한 값들이 올바르게 예측한 경우입니다.
  • 정밀도: 모델이 '양성(Positive)'이라고 예측한 것들 중에서, 실제로 양성이었던 것의 비율입니다. (모델의 예측이 얼마나 정밀한가?)
  • 재현율: 실제 양성이었던 것들 중에서, 모델이 '양성'이라고 예측해낸 것의 비율입니다. (모델이 실제 양성을 얼마나 잘 재현해내는가?)

사이킷런의 `classification_report`는 이 모든 지표를 한 번에 보여줍니다.


# 결정 트리 모델의 분류 리포트
print("--- Decision Tree Classification Report ---")
print(classification_report(y_test, dt_pred, target_names=iris.target_names))

# KNN 모델의 분류 리포트
print("\n--- K-Nearest Neighbors Classification Report ---")
print(classification_report(y_test, knn_pred, target_names=iris.target_names))

리포트를 보면 각 클래스(setosa, versicolor, virginica) 별로 precision, recall, f1-score(정밀도와 재현율의 조화 평균)가 계산되어 있습니다. 두 모델 모두 모든 지표에서 1.0을 기록하며 완벽한 성능을 보였음을 알 수 있습니다.

혼동 행렬을 시각화하면 모델이 어떤 클래스를 헷갈려 하는지 더 직관적으로 파악할 수 있습니다.


# 결정 트리 모델의 혼동 행렬
cm_dt = confusion_matrix(y_test, dt_pred)

plt.figure(figsize=(6, 4))
sns.heatmap(cm_dt, annot=True, cmap='Blues', 
            xticklabels=iris.target_names, yticklabels=iris.target_names)
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Decision Tree Confusion Matrix')
plt.show()

결과로 나온 히트맵을 보면 모든 값이 대각선에만 존재하고, 다른 칸은 모두 0입니다. 이는 단 하나의 오분류도 없었음을 의미합니다.

6단계: 더 나아가기 (하이퍼파라미터 튜닝)

지금은 완벽한 결과가 나왔지만, 실제 프로젝트에서는 모델의 성능을 더 끌어올리기 위한 노력이 필요합니다. 모델을 생성할 때 우리가 지정해주는 설정값들을 하이퍼파라미터(Hyperparameter)라고 합니다. 예를 들어 결정 트리에서는 '트리의 최대 깊이(max_depth)'가, KNN에서는 '이웃의 수(n_neighbors)'가 하이퍼파라미터입니다.

최적의 하이퍼파라미터 조합을 찾는 과정을 하이퍼파라미터 튜닝이라고 합니다. 사이킷런은 이 과정을 자동화해주는 `GridSearchCV`라는 강력한 도구를 제공합니다. `GridSearchCV`는 우리가 지정한 하이퍼파라미터 후보들을 모든 조합에 대해 테스트하고, 교차 검증(Cross-Validation)을 통해 가장 좋은 성능을 내는 조합을 찾아줍니다.

모델 주요 하이퍼파라미터 설명 튜닝의 효과
Decision Tree max_depth 트리가 얼마나 깊어질 수 있는지를 제한합니다. 너무 깊어지면 훈련 데이터에 과적합(overfitting)될 수 있습니다. 모델의 복잡도를 제어하여 과적합을 방지하고 일반화 성능을 높입니다.
Decision Tree min_samples_leaf 하나의 리프 노드(가장 마지막 노드)가 가져야 하는 최소한의 샘플 수를 지정합니다. 노드가 너무 세부적으로 나뉘는 것을 막아 모델을 단순화하고 안정성을 높입니다.
KNN n_neighbors 참조할 이웃의 개수(K)를 정합니다. K가 너무 작으면 노이즈에 민감하고, 너무 크면 결정 경계가 단순해집니다. 모델의 편향-분산 트레이드오프(Bias-Variance Tradeoff)를 조절하여 최적의 균형점을 찾습니다.
KNN weights 이웃들의 투표 가중치를 정합니다. 'uniform'은 모든 이웃이 동일한 가중치를, 'distance'는 가까운 이웃에게 더 큰 가중치를 줍니다. 가까운 이웃의 영향력을 더 크게 만들어 분류 정확도를 높일 수 있습니다.

이러한 튜닝 과정을 통해 모델의 잠재력을 최대한으로 이끌어낼 수 있습니다. 지금은 100% 성능이 나왔으므로 추가 튜닝이 무의미하지만, 대부분의 실제 문제에서는 이 과정이 모델의 성패를 가르는 중요한 단계가 됩니다.

이제 당신은 AI 개발자입니다: 다음 단계는?

축하합니다! 당신은 방금 파이썬과 사이킷런을 사용하여 데이터 분석부터 모델 학습, 평가에 이르는 머신러닝 프로젝트의 전체 과정을 완수했습니다. 비록 간단한 예제였지만, 이 과정에 담긴 원리와 철학은 훨씬 더 복잡한 AI 모델 개발 프로젝트에서도 동일하게 적용됩니다. 당신은 이제 데이터로 문제를 해결하는 AI 개발자의 첫발을 내디딘 것입니다.

여기서 멈추지 마세요. 이번 프로젝트는 여러분의 끝없는 가능성을 열어줄 시작점일 뿐입니다. 앞으로 나아갈 수 있는 몇 가지 방향을 제시해 드립니다.

  1. 다른 데이터셋에 도전하기: 사이킷런은 유방암 진단, 보스턴 집값 예측 등 다양한 예제 데이터셋을 제공합니다. 오늘 배운 프로세스를 다른 데이터셋에 적용해보세요. 특히 집값 예측과 같은 회귀(Regression) 문제에 도전해보는 것을 추천합니다. Scikit-learn 예제 데이터셋 목록에서 새로운 도전 과제를 찾아보세요.
  2. 더 다양한 모델 탐험하기: 결정 트리와 KNN 외에도 사이킷런은 로지스틱 회귀(Logistic Regression), 서포트 벡터 머신(SVM), 그리고 앙상블(Ensemble) 기법인 랜덤 포레스트(Random Forest), 그라디언트 부스팅(Gradient Boosting) 등 훨씬 강력한 모델들을 제공합니다. 이 모델들의 원리를 간단히 학습하고, 붓꽃 데이터에 적용하여 성능을 비교해보세요.
  3. 데이터 경진대회 참여하기: 캐글(Kaggle)과 같은 데이터 과학 경진대회 플랫폼에 참여하여 전 세계의 데이터 과학자들과 실력을 겨뤄보세요. '타이타닉 생존자 예측'과 같은 입문용 대회부터 시작하여 실제 기업의 데이터를 다루는 대회까지, 여러분의 실력을 한 단계 끌어올릴 최고의 훈련장이 될 것입니다.
  4. 딥러닝으로의 확장: 머신러닝의 한 분야인 딥러닝(Deep Learning)은 이미지 인식, 자연어 처리 등 비정형 데이터 처리에서 엄청난 성공을 거두고 있습니다. 사이킷런으로 머신러닝의 기초를 탄탄히 다졌다면, 텐서플로우(TensorFlow)파이토치(PyTorch) 같은 딥러닝 프레임워크를 학습하여 AI의 더 깊은 세계로 나아갈 수 있습니다.

머신러닝의 핵심은 화려한 알고리즘이 아니라, 문제를 정의하고, 데이터를 이해하며, 결과를 비판적으로 해석하는 능력입니다. 오늘 여러분이 배운 것은 단순한 코딩 기술이 아니라, 데이터 기반의 문제 해결 방법론 그 자체입니다.

한 데이터 과학자

이 글이 여러분의 AI 개발 여정에 훌륭한 나침반이 되었기를 바랍니다. 끊임없이 질문하고, 실험하고, 공유하세요. 데이터 속에서 새로운 가치를 찾아내는 멋진 여정을 응원합니다!

OlderNewest

Post a Comment