본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 6. · 12 Views
Logistic Regression 및 분류 기초 완벽 가이드
머신러닝의 핵심인 로지스틱 회귀와 분류 알고리즘을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실무에서 바로 활용할 수 있는 코드 예제와 함께 분류 문제의 A to Z를 다룹니다.
목차
- 분류_문제란_무엇인가
- 시그모이드_함수의_마법
- 로지스틱_회귀_완전_정복
- 다중_클래스_분류
- 결정_경계_시각화
- 모델_평가_지표
- ROC_곡선과_AUC
- 과적합과_규제
- 교차_검증으로_신뢰성_높이기
- 실전_프로젝트_적용
1. 분류 문제란 무엇인가
어느 날 김개발 씨는 은행 시스템 개발팀에 합류하게 되었습니다. 첫 번째 과제는 "고객이 대출을 상환할 것인지 예측하는 모델"을 만드는 것이었습니다.
숫자를 예측하는 것도 아니고, 단순히 "상환할 것이다" 또는 "연체할 것이다" 둘 중 하나를 맞춰야 한다니, 어떻게 접근해야 할까요?
**분류(Classification)**는 주어진 데이터를 미리 정해진 범주 중 하나로 구분하는 것입니다. 마치 우체국 직원이 편지를 보고 "서울행", "부산행", "대구행" 상자에 분류하는 것과 같습니다.
이것을 이해하면 스팸 메일 필터링, 질병 진단, 고객 이탈 예측 등 다양한 실무 문제를 해결할 수 있습니다.
다음 코드를 살펴봅시다.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
# 붓꽃 데이터 로드 - 꽃잎/꽃받침 크기로 품종 분류
iris = load_iris()
X, y = iris.data, iris.target
# 학습용 80%, 테스트용 20%로 분리
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 분류 모델 생성 및 학습
model = LogisticRegression(max_iter=200)
model.fit(X_train, y_train)
# 새로운 데이터의 품종 예측
prediction = model.predict(X_test[:1])
print(f"예측된 품종: {iris.target_names[prediction][0]}")
김개발 씨는 입사 첫 주에 데이터 사이언스팀과 미팅을 가졌습니다. 팀장님이 화이트보드에 큰 글씨로 "분류 vs 회귀"라고 적었습니다.
"개발자분들이 가장 먼저 이해해야 할 개념이에요." 그렇다면 분류란 정확히 무엇일까요? 쉽게 비유하자면, 분류는 마치 과일 선별장에서 일하는 것과 같습니다.
컨베이어 벨트 위로 사과, 배, 귤이 섞여서 지나갑니다. 여러분의 임무는 각 과일을 보고 "이건 사과", "이건 배"라고 올바른 상자에 넣는 것입니다.
과일의 색상, 크기, 모양이라는 **특성(Feature)**을 보고 어떤 **범주(Category)**에 속하는지 판단하는 것이죠. 분류가 없던 시절에는 어땠을까요?
개발자들은 수많은 if-else 조건문을 직접 작성해야 했습니다. "나이가 30 이상이고, 연소득이 5천만 원 이상이고, 기존 대출이 없으면 승인" 같은 규칙을 일일이 만들어야 했습니다.
문제는 이런 규칙이 수백 개, 수천 개로 늘어난다는 것이었습니다. 더 큰 문제는 어떤 규칙이 정말 중요한지, 얼마나 중요한지를 사람이 직접 판단해야 했다는 점입니다.
바로 이런 문제를 해결하기 위해 머신러닝 분류 알고리즘이 등장했습니다. 컴퓨터가 과거 데이터를 학습하여 스스로 규칙을 찾아내는 것입니다.
수천 건의 대출 기록을 보여주면, 어떤 고객이 상환하고 어떤 고객이 연체했는지 패턴을 파악합니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 **load_iris()**로 붓꽃 데이터를 불러옵니다. 이 데이터에는 150송이의 붓꽃 정보가 담겨 있습니다.
각 꽃의 꽃잎 길이, 꽃잎 너비, 꽃받침 길이, 꽃받침 너비 네 가지 수치가 있고, 이 꽃이 세 가지 품종 중 어디에 속하는지 정답이 적혀 있습니다. 다음으로 **train_test_split()**이 데이터를 학습용과 테스트용으로 나눕니다.
왜 나눌까요? 시험 문제를 미리 알려주고 시험을 보면 진짜 실력을 알 수 없는 것처럼, 모델도 처음 보는 데이터에서 얼마나 잘 맞추는지 검증해야 하기 때문입니다.
**LogisticRegression()**으로 분류 모델을 만들고, fit() 메서드로 학습시킵니다. 이 과정에서 모델은 "꽃잎이 길고 넓으면 이 품종일 확률이 높다"는 식의 패턴을 스스로 학습합니다.
실제 현업에서는 어떻게 활용할까요? 이커머스 회사에서 "이 고객이 다음 달에 이탈할 것인가"를 예측한다고 가정해봅시다.
지난 3개월간 구매 횟수, 방문 빈도, 장바구니 포기율 등의 데이터를 모아 분류 모델을 학습시킵니다. 그러면 새로운 고객이 들어왔을 때 "이탈 위험 고객"인지 "충성 고객"인지 자동으로 분류할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 불균형 데이터를 간과하는 것입니다.
예를 들어 사기 거래 탐지에서 정상 거래가 99%, 사기가 1%라면, 모델이 그냥 "모두 정상"이라고 예측해도 정확도가 99%가 됩니다. 따라서 데이터의 균형을 확인하고 적절한 평가 지표를 사용해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 팀장님의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.
"아, 분류는 정해진 선택지 중 하나를 고르는 거군요!" 분류 문제를 제대로 이해하면 세상의 수많은 "예/아니오" 문제를 데이터로 해결할 수 있습니다. 여러분도 주변에서 분류로 풀 수 있는 문제를 찾아보세요.
실전 팁
💡 - 분류 문제인지 회귀 문제인지 먼저 판단하세요. 예측값이 범주면 분류, 연속적인 숫자면 회귀입니다.
- 데이터를 학습용과 테스트용으로 반드시 분리하여 모델의 일반화 성능을 검증하세요.
2. 시그모이드 함수의 마법
김개발 씨가 분류 모델을 공부하던 중, 한 가지 의문이 생겼습니다. "컴퓨터가 계산한 결과는 -3.5나 7.2 같은 아무 숫자인데, 이걸 어떻게 0과 1 사이의 확률로 바꾸지?" 박시니어 씨가 웃으며 대답했습니다.
"그래서 시그모이드 함수가 필요한 거야."
시그모이드 함수는 어떤 숫자든 0과 1 사이의 값으로 변환해주는 마법 같은 함수입니다. 마치 온도계의 수은주가 아무리 온도가 높거나 낮아도 눈금 안에서만 움직이는 것과 같습니다.
이 함수 덕분에 모델의 출력을 "확률"로 해석할 수 있게 됩니다.
다음 코드를 살펴봅시다.
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(z):
"""시그모이드 함수: 모든 실수를 0~1 사이로 변환"""
return 1 / (1 + np.exp(-z))
# -10부터 10까지의 값에 시그모이드 적용
z_values = np.linspace(-10, 10, 100)
probabilities = sigmoid(z_values)
# 결과 확인
print(f"z = -5 일 때 확률: {sigmoid(-5):.4f}") # 0에 가까움
print(f"z = 0 일 때 확률: {sigmoid(0):.4f}") # 정확히 0.5
print(f"z = 5 일 때 확률: {sigmoid(5):.4f}") # 1에 가까움
# 시그모이드의 핵심: 음수는 0으로, 양수는 1로 수렴
print(f"z = 100 일 때도: {sigmoid(100):.4f}") # 거의 1
김개발 씨는 로지스틱 회귀의 핵심 원리를 파헤치기 시작했습니다. 선형 회귀는 이미 알고 있었습니다.
y = wx + b 형태로 직선을 그리는 것이죠. 하지만 이 결과값은 마이너스 무한대부터 플러스 무한대까지 어떤 값이든 될 수 있습니다.
그런데 분류 문제에서는 "이 고객이 상환할 확률이 73%입니다" 같은 답이 필요합니다. -235%나 340% 같은 확률은 말이 안 되죠.
어떻게 아무 숫자나 받아서 0과 1 사이로 깔끔하게 바꿀 수 있을까요? 바로 여기서 시그모이드 함수가 등장합니다.
시그모이드 함수는 마치 양쪽 끝이 막힌 파이프와 같습니다. 아무리 큰 물을 부어도 파이프 안에서만 흐르는 것처럼, 아무리 크거나 작은 숫자를 넣어도 결과는 항상 0과 1 사이에 머무릅니다.
수학 공식으로는 **1 / (1 + e^(-z))**로 표현됩니다. 이 함수의 모양을 상상해보면 알파벳 S를 옆으로 눕힌 것 같습니다.
왼쪽 끝은 0에 바짝 붙어 있고, 오른쪽 끝은 1에 바짝 붙어 있으며, 가운데 z=0 지점에서 정확히 0.5를 통과합니다. 위의 코드에서 핵심을 살펴보겠습니다.
**np.exp(-z)**는 자연상수 e의 -z승을 계산합니다. z가 큰 양수면 e^(-z)는 0에 가까워져서 전체 결과가 1에 수렴합니다.
반대로 z가 큰 음수면 e^(-z)가 엄청 커져서 분모가 무한대로 가고, 결과는 0에 수렴합니다. 실무에서 이것이 왜 중요할까요?
은행 대출 심사 시스템을 예로 들어봅시다. 모델이 내부적으로 계산한 점수가 3.2라고 합시다.
이 숫자만으로는 고객에게 설명하기 어렵습니다. 하지만 시그모이드를 통과시키면 sigmoid(3.2) = 0.961이 됩니다.
"상환 확률 96%"라고 말할 수 있게 되는 것입니다. 하지만 주의할 점도 있습니다.
시그모이드 함수는 양 끝에서 기울기가 거의 0이 됩니다. 이것을 기울기 소실(Vanishing Gradient) 문제라고 합니다.
딥러닝에서 층이 깊어지면 학습이 잘 안 되는 원인이 되기도 합니다. 그래서 딥러닝에서는 ReLU 같은 다른 활성화 함수를 주로 사용하지만, 이진 분류의 마지막 출력층에서는 여전히 시그모이드가 표준입니다.
김개발 씨는 감탄했습니다. "S자 곡선 하나가 이렇게 중요한 역할을 하는군요!" 시그모이드 함수는 단순해 보이지만, 분류 문제를 확률적으로 해석할 수 있게 해주는 핵심 도구입니다.
실전 팁
💡 - z 값이 0이면 확률은 정확히 0.5입니다. 이 점을 기준으로 분류 결정이 이루어집니다.
- 시그모이드의 미분값은 sigmoid(z) * (1 - sigmoid(z))로, 역전파 계산에 활용됩니다.
3. 로지스틱 회귀 완전 정복
김개발 씨가 드디어 본론에 들어왔습니다. "분류도 알겠고, 시그모이드도 알겠는데, 그래서 로지스틱 회귀가 정확히 뭐예요?" 박시니어 씨가 노트북을 펼쳤습니다.
"자, 이제 모든 조각을 맞춰보자."
**로지스틱 회귀(Logistic Regression)**는 선형 회귀의 결과를 시그모이드 함수에 통과시켜 분류를 수행하는 알고리즘입니다. 마치 심사위원들의 점수를 합산한 뒤, "합격/불합격" 판정으로 바꾸는 것과 같습니다.
이름에 "회귀"가 들어있지만 실제로는 분류 알고리즘입니다.
다음 코드를 살펴봅시다.
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
import numpy as np
# 예시: 공부 시간과 수면 시간으로 시험 합격 여부 예측
X = np.array([[2, 8], [3, 7], [4, 6], [5, 6], [6, 5],
[7, 5], [8, 4], [3, 8], [5, 7], [7, 6]])
y = np.array([0, 0, 0, 1, 1, 1, 1, 0, 1, 1]) # 0: 불합격, 1: 합격
# 특성 스케일링 - 로지스틱 회귀 성능 향상
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 로지스틱 회귀 모델 학습
model = LogisticRegression()
model.fit(X_scaled, y)
# 새로운 학생: 공부 6시간, 수면 6시간
new_student = scaler.transform([[6, 6]])
prob = model.predict_proba(new_student)[0]
print(f"불합격 확률: {prob[0]:.1%}, 합격 확률: {prob[1]:.1%}")
김개발 씨는 화이트보드 앞에 섰습니다. 지금까지 배운 것을 정리해보기로 했습니다.
선형 회귀는 z = w1x1 + w2x2 + ... + b 형태로 숫자를 예측합니다.
그리고 시그모이드 함수는 이 숫자를 0과 1 사이로 변환합니다. 로지스틱 회귀는 이 두 가지를 결합한 것입니다.
먼저 입력 데이터에 가중치를 곱하고 더해서 하나의 숫자를 만듭니다. 그 다음 시그모이드 함수를 통과시켜 확률을 얻습니다.
마지막으로 확률이 0.5보다 크면 클래스 1, 작으면 클래스 0으로 분류합니다. 왜 이름이 "회귀"일까요?
역사적인 이유입니다. 수학적으로 보면 로지스틱 회귀는 "확률"을 예측하는 것이고, 확률은 0과 1 사이의 연속적인 숫자이기 때문에 회귀라는 이름이 붙었습니다.
하지만 최종 목적은 분류이므로, 요즘은 분류 알고리즘으로 분류됩니다. 헷갈리지 않도록 주의하세요.
위의 코드를 분석해보겠습니다. 학생들의 공부 시간과 수면 시간 데이터가 있습니다.
이 두 가지 특성으로 시험 합격 여부를 예측하려 합니다. 먼저 **StandardScaler()**로 데이터를 정규화합니다.
공부 시간이 28시간, 수면 시간이 48시간으로 범위가 다른데, 이를 비슷한 스케일로 맞춰주는 것입니다. **LogisticRegression()**으로 모델을 만들고 **fit()**으로 학습시킵니다.
이 과정에서 모델은 "공부 시간에는 얼마의 가중치를, 수면 시간에는 얼마의 가중치를 줘야 합격/불합격을 잘 구분할 수 있을까"를 찾아냅니다. predict_proba() 메서드는 각 클래스에 속할 확률을 반환합니다.
단순히 0 또는 1로 예측하는 것이 아니라, "불합격 확률 23%, 합격 확률 77%"처럼 확률값을 제공합니다. 이것이 로지스틱 회귀의 큰 장점입니다.
단순 예측이 아닌 확신의 정도를 알 수 있기 때문입니다. 실무에서 이 확률값은 매우 유용합니다.
예를 들어 암 진단 모델에서 "양성 확률 51%"와 "양성 확률 99%"는 같은 "양성" 예측이지만, 의사가 취해야 할 조치는 완전히 다를 수 있습니다. 확률값 덕분에 이런 미묘한 차이를 반영할 수 있습니다.
주의할 점이 있습니다. 로지스틱 회귀는 선형 결정 경계를 만듭니다.
즉, 두 클래스를 직선(또는 고차원에서는 평면)으로 나눕니다. 만약 데이터가 복잡하게 섞여 있어서 직선으로 나눌 수 없다면, 로지스틱 회귀는 좋은 성능을 내기 어렵습니다.
이런 경우에는 결정 트리나 신경망 같은 비선형 모델이 필요합니다. 김개발 씨는 끄덕였습니다.
"로지스틱 회귀는 간단하지만, 그래서 해석하기 쉽고 빠르다는 장점이 있군요!" 복잡한 문제에는 복잡한 모델이 필요할 수 있지만, 로지스틱 회귀로 충분한 문제도 많습니다.
실전 팁
💡 - 로지스틱 회귀를 사용하기 전에 StandardScaler로 특성을 정규화하면 수렴이 빨라지고 성능이 향상됩니다.
- 이름에 "회귀"가 있지만 분류 알고리즘입니다. 혼동하지 마세요.
- 모델의 가중치(coef_)를 확인하면 어떤 특성이 분류에 중요한지 알 수 있습니다.
4. 다중 클래스 분류
김개발 씨가 새로운 문제에 부딪혔습니다. "지금까지는 합격/불합격, 스팸/정상 같은 이진 분류만 했는데, 만약 클래스가 3개 이상이면 어떡하죠?" 박시니어 씨가 미소 지었습니다.
"좋은 질문이야. 실무에서는 다중 클래스가 더 흔하거든."
**다중 클래스 분류(Multi-class Classification)**는 3개 이상의 범주 중 하나를 선택하는 문제입니다. 마치 식당에서 메뉴를 고르는 것처럼, "한식/중식/일식/양식" 중 하나를 예측하는 것입니다.
로지스틱 회귀도 약간의 변형으로 다중 클래스를 처리할 수 있습니다.
다음 코드를 살펴봅시다.
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# 붓꽃 데이터: 3가지 품종 분류
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.2, random_state=42
)
# multinomial: 소프트맥스를 사용한 다중 클래스 분류
model = LogisticRegression(multi_class='multinomial', max_iter=200)
model.fit(X_train, y_train)
# 테스트 샘플 하나에 대한 각 클래스별 확률
sample = X_test[0:1]
probs = model.predict_proba(sample)[0]
print("각 품종별 확률:")
for name, prob in zip(iris.target_names, probs):
print(f" {name}: {prob:.1%}")
# 가장 확률이 높은 클래스로 예측
prediction = model.predict(sample)
print(f"\n최종 예측: {iris.target_names[prediction][0]}")
김개발 씨는 이커머스 회사의 고객 분류 프로젝트를 맡게 되었습니다. 고객을 "VIP", "일반", "휴면", "이탈" 네 가지 등급으로 분류해야 합니다.
이진 분류처럼 0과 1로만 나누면 안 되고, 네 가지 중 하나를 골라야 합니다. 다중 클래스 분류는 이런 상황을 위한 것입니다.
핵심 아이디어는 두 가지 방식이 있습니다. 첫 번째는 One-vs-Rest(OvR) 방식입니다.
클래스가 4개라면, "VIP vs 나머지", "일반 vs 나머지", "휴면 vs 나머지", "이탈 vs 나머지" 이렇게 4개의 이진 분류기를 만듭니다. 그리고 가장 높은 확률을 반환한 분류기의 클래스를 선택합니다.
두 번째는 Softmax(Multinomial) 방식입니다. 이 방식은 모든 클래스에 대한 점수를 동시에 계산하고, 소프트맥스 함수를 통해 확률로 변환합니다.
소프트맥스 함수는 시그모이드의 다중 클래스 버전이라고 생각하면 됩니다. 모든 클래스의 확률 합이 정확히 1이 되도록 만들어줍니다.
위 코드에서 **multi_class='multinomial'**이 바로 소프트맥스 방식을 사용하겠다는 설정입니다. Scikit-learn의 LogisticRegression은 기본적으로 이진 분류용이지만, 이 옵션으로 다중 클래스를 자연스럽게 처리합니다.
predict_proba() 결과를 보면 세 품종에 대한 확률이 출력됩니다. 예를 들어 [0.02, 0.95, 0.03] 같은 결과가 나오면, 두 번째 품종일 확률이 95%로 가장 높다는 뜻입니다.
세 확률을 더하면 정확히 1(100%)이 됩니다. 실무에서 다중 클래스 분류는 정말 다양하게 사용됩니다.
뉴스 기사를 "정치/경제/스포츠/문화/IT" 카테고리로 분류하기, 제품 리뷰의 감정을 "매우 불만/불만/보통/만족/매우 만족" 다섯 단계로 분류하기, 의료 영상에서 질병의 종류를 분류하기 등이 있습니다. 주의할 점은 클래스 불균형입니다.
만약 VIP 고객이 5%, 일반이 60%, 휴면이 25%, 이탈이 10%라면, 모델이 그냥 "일반"만 예측해도 60%의 정확도를 얻습니다. 이런 경우 class_weight='balanced' 옵션을 사용하거나, 적은 클래스의 데이터를 오버샘플링하는 방법을 고려해야 합니다.
김개발 씨는 고객 분류 모델을 성공적으로 구축했습니다. "이제 고객 등급별로 맞춤 마케팅을 할 수 있겠어요!" 다중 클래스 분류는 현실 세계의 복잡한 분류 문제를 해결하는 핵심 도구입니다.
실전 팁
💡 - Scikit-learn의 LogisticRegression은 다중 클래스를 자동으로 감지하고 처리합니다.
- 클래스가 불균형할 때는 class_weight='balanced' 옵션을 사용하세요.
- 소프트맥스 출력의 모든 확률 합은 항상 1입니다.
5. 결정 경계 시각화
김개발 씨가 모델을 만들고 나니, 선배가 물었습니다. "이 모델이 어떤 기준으로 분류하는지 설명할 수 있어?" 김개발 씨는 당황했습니다.
모델 내부에서 무슨 일이 일어나는지 어떻게 보여줄 수 있을까요?
**결정 경계(Decision Boundary)**는 모델이 한 클래스와 다른 클래스를 구분하는 선입니다. 마치 국경선처럼, 이 선을 기준으로 "여기는 클래스 A 영역", "저기는 클래스 B 영역"을 나눕니다.
결정 경계를 시각화하면 모델이 어떻게 판단하는지 직관적으로 이해할 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
# 2차원 분류 데이터 생성 (시각화를 위해 특성 2개만)
X, y = make_classification(n_samples=100, n_features=2,
n_redundant=0, n_clusters_per_class=1,
random_state=42)
# 로지스틱 회귀 학습
model = LogisticRegression()
model.fit(X, y)
# 결정 경계 계산 - wx1 + wx2 + b = 0 인 지점
w = model.coef_[0]
b = model.intercept_[0]
x1_line = np.linspace(X[:, 0].min() - 1, X[:, 0].max() + 1, 100)
x2_line = -(w[0] * x1_line + b) / w[1] # 결정 경계 직선
# 결정 경계 시각화
plt.scatter(X[y==0, 0], X[y==0, 1], label='Class 0', alpha=0.7)
plt.scatter(X[y==1, 0], X[y==1, 1], label='Class 1', alpha=0.7)
plt.plot(x1_line, x2_line, 'k--', label='Decision Boundary')
plt.legend()
plt.savefig('decision_boundary.png')
박시니어 씨가 화이트보드에 점들을 그렸습니다. 빨간 점과 파란 점이 섞여 있습니다.
"자, 이 점들을 두 그룹으로 나누는 선을 그려봐." 김개발 씨가 직선 하나를 그렸습니다. "이게 바로 결정 경계야." 결정 경계는 모델이 "여기까지는 클래스 0, 여기부터는 클래스 1"이라고 판단하는 경계선입니다.
로지스틱 회귀에서는 이 경계가 직선(2차원일 때) 또는 평면(3차원 이상일 때)입니다. 시그모이드 함수의 출력이 정확히 0.5가 되는 지점들을 연결한 것이 결정 경계입니다.
위 코드에서 핵심 수학을 살펴보겠습니다. 로지스틱 회귀의 예측은 w1x1 + w2x2 + b를 시그모이드에 통과시킨 것입니다.
시그모이드(z) = 0.5가 되려면 z = 0이어야 합니다. 따라서 결정 경계는 w1x1 + w2x2 + b = 0을 만족하는 점들의 집합입니다.
이 방정식을 x2에 대해 정리하면 x2 = -(w1*x1 + b) / w2가 됩니다. 이것이 바로 직선의 방정식입니다.
기울기는 -w1/w2이고, y절편은 -b/w2입니다. 시각화가 왜 중요할까요?
첫째, 모델 검증에 도움이 됩니다. 결정 경계가 데이터를 잘 나누는지 눈으로 확인할 수 있습니다.
둘째, 이상 감지가 가능합니다. 만약 경계가 이상한 방향으로 그어졌다면, 데이터에 문제가 있거나 특성 선택이 잘못되었을 수 있습니다.
셋째, 비기술 이해관계자에게 설명할 때 유용합니다. 그래프 하나로 "이 기준을 넘으면 합격"이라고 보여줄 수 있습니다.
하지만 현실의 데이터는 보통 특성이 10개, 100개, 심지어 수천 개입니다. 2차원이나 3차원을 넘어가면 결정 경계를 시각화하기 어렵습니다.
이런 경우에는 **PCA(주성분 분석)**나 t-SNE 같은 차원 축소 기법으로 데이터를 2차원으로 줄인 뒤 시각화합니다. 물론 차원을 줄이면 정보 손실이 있으므로, 참고용으로만 사용해야 합니다.
김개발 씨는 자신의 모델 결정 경계를 시각화해보았습니다. 직선이 두 클래스를 깔끔하게 나누는 것을 보니, 모델이 잘 학습되었다는 확신이 들었습니다.
"시각화는 단순해 보여도, 정말 강력한 도구네요!"
실전 팁
💡 - 결정 경계가 직선이라면 로지스틱 회귀, 곡선이라면 비선형 모델(SVM with RBF, 신경망 등)입니다.
- 고차원 데이터는 PCA로 2차원으로 축소한 뒤 시각화하세요.
6. 모델 평가 지표
김개발 씨가 모델을 완성하고 정확도 95%라고 보고했습니다. 그런데 팀장님이 고개를 저었습니다.
"정확도만 보면 안 돼요. 이 모델이 사기 탐지용인데, 정밀도와 재현율은 얼마죠?" 김개발 씨는 처음 듣는 용어에 당황했습니다.
분류 모델 평가에는 정확도 외에도 정밀도(Precision), 재현율(Recall), F1 점수 등 다양한 지표가 있습니다. 마치 의사를 평가할 때 "진단 횟수"뿐 아니라 "정확한 진단 비율", "놓친 환자 비율"도 봐야 하는 것과 같습니다.
상황에 따라 중요한 지표가 달라집니다.
다음 코드를 살펴봅시다.
from sklearn.metrics import (accuracy_score, precision_score,
recall_score, f1_score,
confusion_matrix, classification_report)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
# 불균형 데이터 생성 (클래스 1이 10%만 존재)
X, y = make_classification(n_samples=1000, weights=[0.9, 0.1],
random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
# 다양한 평가 지표
print(f"정확도(Accuracy): {accuracy_score(y_test, y_pred):.3f}")
print(f"정밀도(Precision): {precision_score(y_test, y_pred):.3f}")
print(f"재현율(Recall): {recall_score(y_test, y_pred):.3f}")
print(f"F1 점수: {f1_score(y_test, y_pred):.3f}")
print("\n분류 리포트:")
print(classification_report(y_test, y_pred))
팀장님이 화이트보드에 2x2 표를 그렸습니다. "이걸 **혼동 행렬(Confusion Matrix)**이라고 해." 가로축은 예측값(0 또는 1), 세로축은 실제값(0 또는 1)입니다.
네 칸에는 True Negative, False Positive, False Negative, True Positive가 들어갑니다. **True Positive(TP)**는 실제로 양성인데 양성이라고 맞게 예측한 것입니다.
**False Positive(FP)**는 실제로 음성인데 양성이라고 잘못 예측한 것입니다. 이것을 "1종 오류" 또는 "거짓 경보"라고도 합니다.
**False Negative(FN)**는 실제로 양성인데 음성이라고 놓친 것입니다. 이것을 "2종 오류"라고 합니다.
**True Negative(TN)**는 실제로 음성인데 음성이라고 맞게 예측한 것입니다. **정확도(Accuracy)**는 전체 중에 맞게 예측한 비율입니다.
(TP + TN) / 전체. 하지만 데이터가 불균형할 때 정확도는 함정이 됩니다.
사기 거래가 1%인 데이터에서 "전부 정상"이라고 예측해도 정확도 99%가 나옵니다. **정밀도(Precision)**는 양성이라고 예측한 것 중 실제로 양성인 비율입니다.
TP / (TP + FP). 스팸 필터에서 중요합니다.
정상 메일을 스팸으로 잘못 분류하면 사용자가 중요한 메일을 놓칠 수 있기 때문입니다. **재현율(Recall)**은 실제 양성 중에서 양성이라고 예측한 비율입니다.
TP / (TP + FN). 암 진단에서 중요합니다.
실제 암 환자를 놓치면 치료 시기를 놓칠 수 있기 때문입니다. F1 점수는 정밀도와 재현율의 조화 평균입니다.
둘 다 중요할 때 사용합니다. 한쪽이 극단적으로 낮으면 F1 점수도 낮아집니다.
위 코드에서 **classification_report()**는 이 모든 지표를 한 번에 보여줍니다. 클래스별로 정밀도, 재현율, F1 점수를 확인할 수 있어 매우 유용합니다.
실무에서 어떤 지표를 중시해야 할까요? 사기 탐지에서는 재현율이 중요합니다.
사기꾼을 놓치는 것(FN)이 정상 거래를 잠시 막는 것(FP)보다 비용이 크기 때문입니다. 반면 추천 시스템에서는 정밀도가 더 중요할 수 있습니다.
엉뚱한 추천(FP)이 사용자 경험을 해치기 때문입니다. 김개발 씨는 이제 정확도 95%의 함정을 이해했습니다.
"불균형 데이터에서는 정밀도와 재현율을 반드시 확인해야 하는군요!" 올바른 지표를 선택하는 것이 좋은 모델을 만드는 첫걸음입니다.
실전 팁
💡 - 불균형 데이터에서는 정확도 대신 F1 점수나 AUC-ROC를 사용하세요.
- 비즈니스 요구사항에 따라 정밀도와 재현율 중 더 중요한 것을 결정하세요.
- confusion_matrix()로 TP, FP, FN, TN을 직접 확인할 수 있습니다.
7. ROC 곡선과 AUC
김개발 씨가 모델 두 개를 만들었는데, 하나는 정밀도가 높고 하나는 재현율이 높았습니다. "도대체 어떤 모델이 더 좋은 거죠?" 박시니어 씨가 말했습니다.
"그럴 땐 ROC 곡선을 그려보면 돼."
**ROC 곡선(Receiver Operating Characteristic Curve)**은 분류 임계값을 변화시키면서 True Positive Rate와 False Positive Rate의 관계를 그린 그래프입니다. 마치 볼륨을 조절하면서 음질과 잡음의 균형을 찾는 것과 같습니다.
**AUC(Area Under Curve)**는 이 곡선 아래 면적으로, 모델의 전반적인 성능을 하나의 숫자로 요약합니다.
다음 코드를 살펴봅시다.
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt
# 데이터 준비
X, y = make_classification(n_samples=500, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
model = LogisticRegression()
model.fit(X_train, y_train)
# 확률 예측 (ROC는 확률값이 필요)
y_prob = model.predict_proba(X_test)[:, 1]
# ROC 곡선 계산
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
auc = roc_auc_score(y_test, y_prob)
# ROC 곡선 시각화
plt.plot(fpr, tpr, label=f'ROC Curve (AUC = {auc:.3f})')
plt.plot([0, 1], [0, 1], 'k--', label='Random Classifier')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.legend()
plt.savefig('roc_curve.png')
print(f"AUC 점수: {auc:.3f}")
박시니어 씨가 설명을 시작했습니다. "로지스틱 회귀는 확률을 출력하잖아?
그 확률이 얼마 이상이면 클래스 1로 예측할지, 그 기준을 **임계값(threshold)**이라고 해." 기본 임계값은 0.5입니다. 확률이 0.5 이상이면 클래스 1, 미만이면 클래스 0으로 예측합니다.
하지만 이 임계값을 바꿀 수 있습니다. 임계값을 0.3으로 낮추면 더 많은 데이터를 클래스 1로 예측하게 됩니다.
재현율은 올라가지만 정밀도는 떨어집니다. 반대로 0.7로 높이면 확실한 것만 클래스 1로 예측합니다.
정밀도는 올라가지만 재현율은 떨어집니다. ROC 곡선은 임계값을 0부터 1까지 변화시키면서 **True Positive Rate(TPR, 재현율)**와 **False Positive Rate(FPR)**을 그래프로 그린 것입니다.
TPR은 실제 양성 중 맞게 예측한 비율, FPR은 실제 음성 중 잘못 양성으로 예측한 비율입니다. 이상적인 모델은 왼쪽 상단 모서리에 찍힙니다.
FPR이 0(잘못된 경보 없음)이고 TPR이 1(모든 양성을 찾음)인 상태입니다. 반면 완전히 랜덤한 모델은 대각선을 따라갑니다.
동전 던지기와 같은 수준이라는 뜻입니다. **AUC(Area Under Curve)**는 ROC 곡선 아래 면적입니다.
0.5면 랜덤, 1이면 완벽한 모델입니다. 일반적으로 0.7 이상이면 괜찮고, 0.8 이상이면 좋고, 0.9 이상이면 아주 좋은 모델로 봅니다.
AUC의 큰 장점은 임계값에 독립적이라는 것입니다. 정밀도나 재현율은 임계값에 따라 달라지지만, AUC는 "이 모델이 전반적으로 얼마나 양성과 음성을 잘 구분하는가"를 측정합니다.
따라서 모델을 비교할 때 매우 유용합니다. 또한 AUC는 불균형 데이터에도 강건합니다.
정확도는 불균형에 속기 쉽지만, AUC는 양성과 음성 각각에서의 성능을 반영하기 때문에 더 신뢰할 수 있습니다. 김개발 씨는 두 모델의 ROC 곡선을 그려보았습니다.
하나는 AUC 0.82, 다른 하나는 AUC 0.78이었습니다. "전반적인 성능은 첫 번째 모델이 더 좋군요.
하지만 비즈니스 요구사항에 따라 임계값을 조정할 수 있겠어요."
실전 팁
💡 - AUC가 0.5에 가까우면 모델이 거의 랜덤 수준이므로 특성 엔지니어링이나 다른 알고리즘을 고려하세요.
- 여러 모델을 비교할 때 ROC 곡선을 겹쳐 그리면 직관적으로 성능 차이를 볼 수 있습니다.
8. 과적합과 규제
김개발 씨가 자랑스럽게 말했습니다. "학습 데이터에서 정확도 99%가 나왔어요!" 하지만 테스트 데이터에서는 75%밖에 안 나왔습니다.
박시니어 씨가 한숨을 쉬었습니다. "과적합이 일어났네.
규제를 적용해봐야겠어."
**과적합(Overfitting)**은 모델이 학습 데이터에 너무 맞춰져서 새로운 데이터에서 성능이 떨어지는 현상입니다. 마치 시험 문제를 외워서 만점을 받았지만, 조금만 다른 문제가 나오면 못 푸는 것과 같습니다.
**규제(Regularization)**는 모델을 일부러 제약해서 일반화 성능을 높이는 기법입니다.
다음 코드를 살펴봅시다.
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
# 특성이 많은 복잡한 데이터
X, y = make_classification(n_samples=200, n_features=50,
n_informative=5, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
# 규제 없는 모델 (C가 클수록 규제가 약함)
model_no_reg = LogisticRegression(C=1000, max_iter=1000)
model_no_reg.fit(X_train, y_train)
# L2 규제 적용 모델 (기본값, C=1.0)
model_l2 = LogisticRegression(C=1.0, max_iter=1000)
model_l2.fit(X_train, y_train)
# L1 규제 적용 모델 (특성 선택 효과)
model_l1 = LogisticRegression(penalty='l1', solver='saga', C=1.0, max_iter=1000)
model_l1.fit(X_train, y_train)
print(f"규제 없음 - 학습: {model_no_reg.score(X_train, y_train):.3f}, "
f"테스트: {model_no_reg.score(X_test, y_test):.3f}")
print(f"L2 규제 - 학습: {model_l2.score(X_train, y_train):.3f}, "
f"테스트: {model_l2.score(X_test, y_test):.3f}")
print(f"L1 규제 - 학습: {model_l1.score(X_train, y_train):.3f}, "
f"테스트: {model_l1.score(X_test, y_test):.3f}")
박시니어 씨가 비유를 들었습니다. "학생이 수학 공식의 원리를 이해하지 않고, 기출문제 답만 외운다고 생각해봐.
똑같은 문제는 맞추지만, 숫자만 바뀌어도 틀려." 이것이 바로 과적합입니다. 과적합은 왜 발생할까요?
첫째, 모델이 너무 복잡할 때입니다. 특성이 200개인데 데이터가 100개뿐이라면, 모델이 각 데이터 포인트를 개별적으로 외울 수 있습니다.
둘째, 학습 데이터가 너무 적을 때입니다. 셋째, 노이즈까지 학습할 때입니다.
실제 패턴이 아닌 우연한 변동까지 모델이 기억해버립니다. 규제는 모델의 복잡도를 제한하는 기법입니다.
로지스틱 회귀에서는 가중치(w)가 너무 커지는 것을 막습니다. 가중치가 크면 모델이 특정 특성에 과도하게 의존하게 되어 과적합이 발생하기 쉽습니다.
**L2 규제(Ridge)**는 가중치의 제곱합에 페널티를 부여합니다. 가중치를 0에 가깝게 만들지만, 완전히 0으로 만들지는 않습니다.
모든 특성을 조금씩 사용하되 과도하게 의존하지 않게 합니다. **L1 규제(Lasso)**는 가중치의 절대값 합에 페널티를 부여합니다.
일부 가중치를 완전히 0으로 만듭니다. 이는 특성 선택 효과가 있습니다.
중요하지 않은 특성을 자동으로 제거하는 것입니다. 위 코드에서 C 파라미터는 규제의 강도를 조절합니다.
C가 클수록 규제가 약하고, 작을수록 규제가 강합니다. 직관과 반대라서 헷갈릴 수 있지만, C는 "규제의 역수"라고 생각하면 됩니다.
결과를 보면, 규제 없는 모델은 학습 정확도는 높지만 테스트 정확도가 낮습니다. 과적합입니다.
L2나 L1 규제를 적용하면 학습 정확도는 약간 떨어지지만 테스트 정확도는 올라갑니다. 이것이 일반화 성능 향상입니다.
김개발 씨는 규제를 적용한 후 테스트 정확도가 85%로 올랐습니다. "학습 데이터에서 만점 받는 것보다, 새로운 데이터에서 잘 맞추는 게 진짜 중요하군요!"
실전 팁
💡 - 기본적으로 L2 규제(penalty='l2')를 사용하세요. 대부분의 경우 잘 작동합니다.
- 특성이 너무 많다면 L1 규제로 자동 특성 선택 효과를 얻을 수 있습니다.
- C 값은 cross-validation으로 최적값을 찾으세요.
9. 교차 검증으로 신뢰성 높이기
김개발 씨가 모델 성능을 보고하려는데, 박시니어 씨가 물었습니다. "테스트 세트가 우연히 쉬운 데이터였을 수도 있잖아?
결과가 신뢰할 만한지 어떻게 알아?" 김개발 씨는 다시 생각에 빠졌습니다.
**교차 검증(Cross-Validation)**은 데이터를 여러 번 나누어 학습과 검증을 반복하는 기법입니다. 마치 시험을 여러 번 보고 평균 점수를 내는 것처럼, 한 번의 테스트 결과에 의존하지 않고 안정적인 성능 추정치를 얻을 수 있습니다.
다음 코드를 살펴봅시다.
from sklearn.model_selection import cross_val_score, KFold
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
import numpy as np
# 데이터 준비
X, y = make_classification(n_samples=500, random_state=42)
model = LogisticRegression(max_iter=1000)
# 5-Fold 교차 검증
cv = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')
print("각 폴드별 정확도:")
for i, score in enumerate(scores, 1):
print(f" Fold {i}: {score:.3f}")
print(f"\n평균 정확도: {scores.mean():.3f}")
print(f"표준편차: {scores.std():.3f}")
print(f"95% 신뢰구간: {scores.mean():.3f} +/- {1.96 * scores.std():.3f}")
# 다양한 지표로 교차 검증
from sklearn.model_selection import cross_validate
results = cross_validate(model, X, y, cv=5,
scoring=['accuracy', 'precision', 'recall', 'f1'])
print(f"\nF1 평균: {results['test_f1'].mean():.3f}")
박시니어 씨가 카드 한 벌을 가져왔습니다. "데이터가 이 카드들이라고 생각해봐.
한 번만 섞어서 나누면, 우연히 쉬운 카드가 테스트에 들어갈 수도 있어. 하지만 여러 번 다르게 나눠서 테스트하면, 평균적인 난이도를 알 수 있지." K-Fold 교차 검증은 가장 흔한 방식입니다.
데이터를 K개의 동일한 크기로 나눕니다. 첫 번째 라운드에서는 첫 번째 조각을 테스트로, 나머지를 학습으로 사용합니다.
두 번째 라운드에서는 두 번째 조각을 테스트로, 나머지를 학습으로 사용합니다. 이렇게 K번 반복하고 평균을 냅니다.
왜 5-Fold를 많이 사용할까요? K가 작으면(예: 2) 학습 데이터가 너무 적어집니다.
K가 크면(예: 20) 각 테스트 세트가 너무 작아서 불안정합니다. 5나 10이 적당한 균형점입니다.
위 코드에서 **cross_val_score()**가 핵심입니다. 내부적으로 데이터를 나누고, 모델을 학습시키고, 평가하는 과정을 K번 반복합니다.
결과로 K개의 점수 배열을 반환합니다. 표준편차도 중요합니다.
평균이 85%인데 표준편차가 15%라면, 어떤 분할에서는 70%, 어떤 분할에서는 100%가 나온다는 뜻입니다. 모델이 불안정한 것입니다.
반면 표준편차가 2%라면 어떤 분할에서든 비슷한 성능이 나옵니다. 교차 검증은 하이퍼파라미터 튜닝에도 필수입니다.
규제 강도 C를 0.01, 0.1, 1, 10 중에서 고를 때, 각 값으로 교차 검증을 수행하고 평균 점수가 가장 높은 값을 선택합니다. 이렇게 하면 특정 테스트 세트에서만 좋은 값을 고르는 실수를 피할 수 있습니다.
하지만 주의할 점이 있습니다. 교차 검증은 시간이 K배 더 걸립니다.
데이터가 크거나 모델이 복잡하면 부담이 됩니다. 또한 시계열 데이터에서는 일반 K-Fold를 사용하면 안 됩니다.
미래 데이터로 과거를 예측하는 "데이터 누출"이 발생하기 때문입니다. 시계열에는 TimeSeriesSplit을 사용해야 합니다.
김개발 씨는 교차 검증을 적용했습니다. "평균 85%, 표준편차 3%네요.
이 정도면 새로운 데이터에서도 82~88% 정도 성능이 나올 것 같아요!"
실전 팁
💡 - 기본적으로 5-Fold 교차 검증을 사용하세요. 데이터가 적으면 10-Fold도 고려하세요.
- shuffle=True로 데이터를 섞어서 분할하세요. 순서가 있는 데이터가 아니라면 항상 섞는 것이 좋습니다.
- 시계열 데이터에서는 TimeSeriesSplit을 사용하세요.
10. 실전 프로젝트 적용
드디어 김개발 씨가 실전 프로젝트에 로지스틱 회귀를 적용할 시간입니다. 은행 대출 상환 예측 모델을 처음부터 끝까지 만들어봅니다.
지금까지 배운 모든 것을 총동원하는 시간입니다.
실전에서는 데이터 전처리부터 모델 저장까지 전체 파이프라인을 구축해야 합니다. 마치 요리사가 재료 손질부터 플레이팅까지 모든 과정을 숙달해야 하는 것처럼, 개발자도 전체 머신러닝 워크플로우를 이해해야 합니다.
다음 코드를 살펴봅시다.
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score
from sklearn.pipeline import Pipeline
import joblib
# 가상의 대출 데이터 생성
np.random.seed(42)
n_samples = 1000
data = pd.DataFrame({
'income': np.random.normal(50000, 20000, n_samples),
'debt_ratio': np.random.uniform(0, 0.5, n_samples),
'credit_score': np.random.randint(300, 850, n_samples),
'loan_amount': np.random.uniform(10000, 100000, n_samples),
})
# 상환 여부 (소득 높고, 부채비율 낮고, 신용점수 높으면 상환 확률 높음)
prob = 1 / (1 + np.exp(-(data['income']/50000 + data['credit_score']/500
- data['debt_ratio']*5 - data['loan_amount']/50000)))
data['repaid'] = (np.random.random(n_samples) < prob).astype(int)
# 특성과 타겟 분리
X = data.drop('repaid', axis=1)
y = data['repaid']
# 학습/테스트 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 파이프라인 구성: 스케일링 + 로지스틱 회귀
pipeline = Pipeline([
('scaler', StandardScaler()),
('classifier', LogisticRegression(C=1.0, max_iter=1000))
])
# 교차 검증으로 성능 확인
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring='roc_auc')
print(f"CV AUC 평균: {cv_scores.mean():.3f} (+/- {cv_scores.std()*2:.3f})")
# 최종 모델 학습
pipeline.fit(X_train, y_train)
# 테스트 세트 평가
y_pred = pipeline.predict(X_test)
y_prob = pipeline.predict_proba(X_test)[:, 1]
print(f"\n테스트 AUC: {roc_auc_score(y_test, y_prob):.3f}")
print(classification_report(y_test, y_pred))
# 모델 저장
joblib.dump(pipeline, 'loan_model.pkl')
print("모델이 저장되었습니다.")
김개발 씨는 드디어 실전입니다. 은행 대출 데이터를 받았습니다.
고객의 소득, 부채비율, 신용점수, 대출금액 정보가 있고, 실제로 상환했는지 여부가 기록되어 있습니다. 첫 단계는 데이터 이해입니다.
특성이 몇 개인지, 결측치는 없는지, 각 특성의 분포는 어떤지 확인합니다. 실무에서는 이 단계에 시간의 70% 이상이 소요되기도 합니다.
위 코드에서는 가상 데이터를 생성했지만, 실제로는 df.info(), df.describe(), df.isnull().sum() 등으로 데이터를 탐색합니다. 두 번째 단계는 전처리입니다.
소득은 수만 단위이고, 부채비율은 0~0.5 사이입니다. 스케일이 너무 다릅니다.
StandardScaler로 모든 특성을 평균 0, 표준편차 1로 정규화합니다. 세 번째는 파이프라인 구성입니다.
Pipeline은 여러 처리 단계를 하나로 묶어주는 도구입니다. 왜 중요할까요?
첫째, 코드가 깔끔해집니다. 둘째, 교차 검증에서 데이터 누출을 방지합니다.
스케일러를 전체 데이터로 fit하고 그 다음에 분할하면, 테스트 데이터의 정보가 스케일러에 누출됩니다. 파이프라인은 각 fold에서 스케일러를 새로 fit합니다.
네 번째는 교차 검증입니다. 교차 검증으로 모델 성능을 검증하고, 하이퍼파라미터를 튜닝합니다.
여기서는 C=1.0을 사용했지만, 실무에서는 GridSearchCV로 최적값을 찾습니다. 다섯 번째는 최종 학습과 평가입니다.
교차 검증에서 성능이 확인되면, 전체 학습 데이터로 최종 모델을 학습합니다. 테스트 세트는 이때 처음 사용합니다.
테스트 세트로 여러 번 평가하면서 모델을 수정하면, 테스트 세트에 과적합되는 문제가 생깁니다. 마지막은 모델 저장입니다.
**joblib.dump()**로 파이프라인 전체를 저장합니다. 나중에 새 고객 데이터가 들어오면 joblib.load()로 불러와서 바로 예측할 수 있습니다.
스케일러의 평균/표준편차도 함께 저장되므로, 전처리 과정도 동일하게 적용됩니다. 김개발 씨는 프로젝트를 성공적으로 완료했습니다.
"전체 흐름을 이해하니, 이제 어떤 분류 문제든 자신 있게 접근할 수 있을 것 같아요!"
실전 팁
💡 - 항상 Pipeline을 사용하세요. 전처리와 모델을 묶으면 실수를 방지하고 배포가 쉬워집니다.
- 테스트 세트는 최종 평가에만 딱 한 번 사용하세요. 개발 중에는 교차 검증을 활용합니다.
- 모델과 함께 전처리기(스케일러 등)도 반드시 저장하세요.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.