본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 4. · 10 Views
로지스틱 회귀와 SVM 완벽 가이드
분류 문제를 해결하는 두 가지 핵심 알고리즘인 로지스틱 회귀와 SVM을 초급자도 쉽게 이해할 수 있도록 설명합니다. 실무에서 바로 적용 가능한 코드와 함께 핵심 원리를 배워봅시다.
목차
1. 로지스틱 회귀 개념
김개발 씨는 오늘 새로운 프로젝트를 맡았습니다. 고객이 서비스를 해지할지 예측하는 모델을 만들어야 합니다.
"이건 단순히 숫자를 예측하는 게 아니라, 해지할지 안 할지를 분류해야 하는 문제인데..." 김개발 씨는 고민에 빠졌습니다.
로지스틱 회귀는 이름에 "회귀"가 들어가지만, 사실 분류 문제를 해결하는 알고리즘입니다. 마치 문 앞에 서 있는 경비원이 출입증을 확인하고 "입장 가능" 또는 "입장 불가"를 판단하는 것과 같습니다.
이 알고리즘을 이해하면 스팸 메일 필터링, 질병 진단, 고객 이탈 예측 등 다양한 분류 문제를 해결할 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 고객 데이터: 사용 기간(월), 월 이용 금액, 불만 접수 횟수
X = np.array([[12, 50000, 0], [6, 30000, 3], [24, 80000, 1], [3, 20000, 5]])
y = np.array([0, 1, 0, 1]) # 0: 유지, 1: 해지
# 로지스틱 회귀 모델 생성 및 학습
model = LogisticRegression()
model.fit(X, y)
# 새 고객 예측: 사용 8개월, 월 40000원, 불만 2회
new_customer = [[8, 40000, 2]]
prediction = model.predict(new_customer)
probability = model.predict_proba(new_customer)
print(f"예측: {'해지' if prediction[0] else '유지'}, 해지 확률: {probability[0][1]:.2%}")
김개발 씨는 입사 6개월 차 데이터 분석가입니다. 이번 주 회의에서 팀장님이 중요한 과제를 던졌습니다.
"우리 서비스 해지율이 너무 높아요. 어떤 고객이 해지할지 미리 예측해서 관리할 수 있을까요?" 김개발 씨는 처음에 선형 회귀를 떠올렸습니다.
하지만 뭔가 이상했습니다. 선형 회귀로 예측하면 0.3이나 1.5 같은 값이 나올 텐데, 고객은 해지하거나 안 하거나 둘 중 하나잖아요?
바로 이때 선배 박시니어 씨가 다가왔습니다. "로지스틱 회귀를 써봐.
분류 문제에 딱이야." 그렇다면 로지스틱 회귀란 정확히 무엇일까요? 쉽게 비유하자면, 로지스틱 회귀는 마치 시험 합격 여부를 판단하는 채점관과 같습니다.
채점관은 답안지를 보고 점수를 매긴 뒤, 일정 기준점을 넘으면 합격, 넘지 못하면 불합격을 선언합니다. 로지스틱 회귀도 마찬가지로 입력 데이터를 분석해서 0과 1 사이의 확률값을 계산하고, 보통 0.5를 기준으로 분류 결정을 내립니다.
선형 회귀만 있던 시절에는 분류 문제가 골치 아팠습니다. 선형 회귀의 출력값은 마이너스 무한대부터 플러스 무한대까지 어떤 값이든 나올 수 있었습니다.
"해지 확률이 -20%"라는 결과가 나와도 의미가 없잖아요? 더 큰 문제는 경계선 근처의 데이터였습니다.
선형 회귀는 극단적인 값에 민감하게 반응해서, 한두 개의 이상치가 전체 모델을 망가뜨릴 수 있었습니다. 바로 이런 문제를 해결하기 위해 로지스틱 회귀가 등장했습니다.
핵심 아이디어는 간단합니다. 선형 회귀의 결과를 시그모이드 함수라는 특별한 함수에 통과시켜서, 어떤 값이 들어와도 0과 1 사이로 압축시키는 것입니다.
위의 코드를 살펴보겠습니다. 먼저 고객 데이터를 준비합니다.
각 고객의 사용 기간, 월 이용 금액, 불만 접수 횟수를 특성으로 사용합니다. LogisticRegression 모델을 생성하고 fit 메서드로 학습시킵니다.
학습이 끝나면 predict 메서드로 새 고객의 해지 여부를 예측할 수 있습니다. 더 유용한 것은 predict_proba 메서드입니다.
이 메서드는 각 클래스에 속할 확률을 알려줍니다. "이 고객이 해지할 확률은 73%입니다"처럼 구체적인 수치를 얻을 수 있어서 비즈니스 의사결정에 큰 도움이 됩니다.
실제 현업에서 로지스틱 회귀는 놀라울 정도로 다양하게 활용됩니다. 은행에서는 대출 심사에, 병원에서는 질병 진단에, 이커머스에서는 구매 예측에 사용합니다.
특히 모델의 해석이 쉽다는 장점 때문에, 의사결정의 이유를 설명해야 하는 상황에서 자주 선택됩니다. 하지만 주의할 점도 있습니다.
로지스틱 회귀는 데이터가 선형적으로 분리 가능할 때 가장 잘 작동합니다. 복잡한 패턴이 있는 데이터에서는 성능이 떨어질 수 있습니다.
이럴 때는 뒤에서 배울 SVM이나 다른 알고리즘을 고려해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
로지스틱 회귀를 적용한 결과, 해지 위험이 높은 고객을 미리 식별할 수 있게 되었습니다. 팀장님은 흡족해하며 고개를 끄덕였습니다.
"좋아, 이제 이 고객들에게 할인 쿠폰을 보내보자!"
실전 팁
💡 - predict_proba를 활용하면 단순 예측을 넘어 확률 기반 의사결정이 가능합니다
- 특성 스케일링을 적용하면 모델 수렴 속도와 성능이 향상됩니다
- class_weight='balanced' 옵션으로 불균형 데이터 문제를 완화할 수 있습니다
2. 시그모이드 함수와 확률
김개발 씨는 로지스틱 회귀가 확률을 출력한다는 것을 알게 되었습니다. 하지만 어떻게 선형 계산 결과가 0과 1 사이의 확률로 변환되는 걸까요?
"선형 회귀 결과가 100이면 확률이 100%보다 클 수도 있잖아?" 김개발 씨의 질문에 박시니어 씨가 미소를 지었습니다.
시그모이드 함수는 어떤 실수 값이든 0과 1 사이로 압축시키는 마법 같은 함수입니다. 마치 수도꼭지의 수압이 아무리 세도 물줄기가 일정 범위 내에서만 나오도록 조절하는 밸브와 같습니다.
이 함수 덕분에 로지스틱 회귀의 출력을 확률로 해석할 수 있게 됩니다.
다음 코드를 살펴봅시다.
import numpy as np
import matplotlib.pyplot as plt
# 시그모이드 함수 정의
def sigmoid(z):
return 1 / (1 + np.exp(-z))
# 선형 결합 결과값 범위
z = np.linspace(-10, 10, 100)
probabilities = sigmoid(z)
# 시그모이드 곡선 시각화
plt.figure(figsize=(10, 6))
plt.plot(z, probabilities, 'b-', linewidth=2)
plt.axhline(y=0.5, color='r', linestyle='--', label='결정 경계 (0.5)')
plt.axvline(x=0, color='gray', linestyle=':', alpha=0.5)
plt.xlabel('선형 결합 z = wx + b')
plt.ylabel('확률 P(y=1)')
plt.title('시그모이드 함수: 어떤 값도 0~1 사이로 변환')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
김개발 씨는 로지스틱 회귀의 핵심 비밀을 파헤치기로 했습니다. 도대체 어떻게 아무 숫자나 확률로 바꿀 수 있는 걸까요?
박시니어 씨가 화이트보드에 S자 곡선을 그렸습니다. "이게 바로 시그모이드 함수야.
영어로 sigmoid, S자 모양이라서 붙은 이름이지." 시그모이드 함수의 수식은 간단합니다. 1을 (1 + e의 -z승)으로 나눈 것입니다.
여기서 z는 선형 회귀의 결과값, 즉 가중치와 입력값의 곱을 모두 더한 값입니다. 이 함수가 왜 특별할까요?
마치 온도계가 아무리 뜨거운 것을 측정해도 눈금 범위 안에서만 표시되는 것처럼, 시그모이드 함수는 입력이 아무리 크거나 작아도 출력을 0과 1 사이로 제한합니다. z가 아주 큰 양수면, e의 -z승은 거의 0에 가까워지고, 전체 결과는 1에 수렴합니다.
반대로 z가 아주 작은 음수면, e의 -z승은 엄청나게 커지고, 전체 결과는 0에 수렴합니다. z가 정확히 0이면?
결과는 딱 0.5입니다. 이런 특성 덕분에 시그모이드 함수의 출력을 확률로 해석할 수 있습니다.
출력이 0.7이면 "양성 클래스에 속할 확률이 70%"라고 말할 수 있는 거죠. 위의 코드에서는 시그모이드 함수를 직접 구현하고 시각화합니다.
np.exp(-z)는 e의 -z승을 계산합니다. 결과 그래프를 보면 부드러운 S자 곡선이 나타납니다.
그래프에서 빨간 점선은 결정 경계입니다. 보통 0.5를 기준으로, 확률이 0.5보다 크면 양성 클래스, 작으면 음성 클래스로 분류합니다.
물론 이 기준점은 비즈니스 상황에 따라 조절할 수 있습니다. 실무에서는 이 기준점 조절이 중요합니다.
예를 들어 암 진단 모델에서는 놓치는 것보다 오진이 낫기 때문에 기준점을 0.3으로 낮출 수 있습니다. 반대로 스팸 필터에서는 정상 메일을 스팸으로 분류하는 것이 더 문제이므로 기준점을 0.7로 높일 수 있습니다.
시그모이드 함수에는 미분이 쉽다는 장점도 있습니다. 시그모이드 함수를 미분하면 sigmoid(z) * (1 - sigmoid(z))가 되는데, 이 간단한 형태 덕분에 경사하강법으로 학습할 때 계산이 효율적입니다.
하지만 주의할 점도 있습니다. 시그모이드 함수는 z가 너무 크거나 작으면 기울기가 거의 0에 가까워집니다.
이를 기울기 소실 문제라고 하는데, 딥러닝에서는 이 때문에 ReLU 같은 다른 활성화 함수를 더 많이 사용합니다. 김개발 씨는 고개를 끄덕였습니다.
"아, 그래서 확률처럼 해석할 수 있었군요. S자 곡선이 모든 비밀이었네요!"
실전 팁
💡 - 시그모이드 출력값은 진짜 확률이 아닌 유사 확률임을 기억하세요
- 결정 경계 0.5는 기본값일 뿐, 실무에서는 비즈니스 요구에 맞게 조절합니다
- z값이 극단적일 때 수치 안정성을 위해 np.clip 사용을 고려하세요
3. 손실 함수와 비용 함수
김개발 씨는 모델이 학습한다는 게 정확히 무슨 의미인지 궁금해졌습니다. "모델이 스스로 똑똑해진다고?
어떻게요?" 박시니어 씨가 답했습니다. "실수를 측정하고, 그 실수를 줄이는 방향으로 조금씩 조정하는 거야.
그 실수를 측정하는 게 바로 손실 함수야."
손실 함수는 모델의 예측이 얼마나 틀렸는지를 숫자로 표현합니다. 마치 양궁 선수가 과녁 중심에서 얼마나 벗어났는지 측정하는 것과 같습니다.
로지스틱 회귀에서는 로그 손실(Log Loss) 또는 이진 크로스 엔트로피라는 특별한 손실 함수를 사용합니다. 모델은 이 손실을 최소화하는 방향으로 학습합니다.
다음 코드를 살펴봅시다.
import numpy as np
# 로그 손실(이진 크로스 엔트로피) 함수
def log_loss(y_true, y_pred):
# 수치 안정성을 위해 극단값 방지
epsilon = 1e-15
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
# 손실 계산: -[y*log(p) + (1-y)*log(1-p)]
loss = -np.mean(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
return loss
# 예시: 실제값과 예측 확률
y_true = np.array([1, 0, 1, 1, 0])
y_pred_good = np.array([0.9, 0.1, 0.8, 0.95, 0.2]) # 좋은 예측
y_pred_bad = np.array([0.3, 0.7, 0.4, 0.5, 0.6]) # 나쁜 예측
print(f"좋은 예측의 손실: {log_loss(y_true, y_pred_good):.4f}")
print(f"나쁜 예측의 손실: {log_loss(y_true, y_pred_bad):.4f}")
김개발 씨는 모델 학습의 핵심 원리를 이해하고 싶었습니다. 컴퓨터가 어떻게 스스로 배우는 걸까요?
박시니어 씨가 비유를 들었습니다. "다트 게임을 생각해봐.
처음에는 과녁에서 멀리 벗어나지만, 연습하면서 점점 중심에 가까워지잖아. 모델도 마찬가지야.
얼마나 벗어났는지 측정하고, 그걸 줄이는 방향으로 조정하는 거지." 이때 "얼마나 벗어났는지"를 측정하는 도구가 바로 손실 함수입니다. 로지스틱 회귀에서는 로그 손실 또는 이진 크로스 엔트로피라고 불리는 특별한 손실 함수를 사용합니다.
왜 단순히 "맞았다/틀렸다"를 세지 않고 복잡한 로그 함수를 사용할까요? 여기에는 깊은 이유가 있습니다.
실제 정답이 1인데 모델이 0.9라고 예측했다면, 꽤 잘한 것입니다. 하지만 0.1이라고 예측했다면?
완전히 틀린 거죠. 로그 손실은 이런 "확신의 정도"까지 반영합니다.
로그 손실의 수식을 살펴보겠습니다. 실제 정답이 1일 때는 -log(예측확률)을 계산합니다.
예측이 1에 가까우면 손실이 0에 가깝고, 예측이 0에 가까우면 손실이 무한대로 커집니다. 즉, 틀렸는데 확신까지 가졌다면 엄청난 벌점을 받는 것입니다.
반대로 실제 정답이 0일 때는 -log(1-예측확률)을 계산합니다. 원리는 같습니다.
위의 코드에서 좋은 예측과 나쁜 예측의 손실을 비교해보세요. 실제로 실행하면 좋은 예측의 손실이 훨씬 작게 나옵니다.
모델은 이 손실을 최소화하는 방향으로 가중치를 조정합니다. 비용 함수는 손실 함수를 전체 데이터셋에 대해 평균 낸 것입니다.
개별 데이터 포인트의 손실을 모두 더하고 데이터 개수로 나눕니다. 모델은 이 비용 함수를 최소화하도록 경사하강법으로 학습합니다.
경사하강법은 마치 안개 낀 산에서 가장 낮은 곳을 찾아가는 것과 같습니다. 현재 위치에서 가장 가파르게 내려가는 방향을 찾아 한 걸음씩 이동합니다.
손실 함수의 기울기(미분값)가 바로 이 방향을 알려줍니다. 코드에서 epsilon을 사용한 이유는 수치 안정성 때문입니다.
예측값이 정확히 0이나 1이면 로그 계산에서 무한대나 정의되지 않는 값이 나올 수 있습니다. 아주 작은 값으로 클리핑해서 이 문제를 방지합니다.
실무에서 손실 함수를 직접 구현할 일은 드뭅니다. scikit-learn이나 TensorFlow 같은 라이브러리가 알아서 처리해줍니다.
하지만 원리를 이해하면 모델이 왜 잘 작동하는지, 또는 왜 실패하는지 파악하는 데 큰 도움이 됩니다. 김개발 씨는 이제 모델 학습의 본질을 이해했습니다.
"결국 실수를 측정하고 줄이는 과정이군요. 마치 시험 점수를 보고 공부 방법을 개선하는 것처럼요!"
실전 팁
💡 - 로그 손실이 갑자기 급증하면 학습률이 너무 크거나 데이터에 문제가 있을 수 있습니다
- 손실 곡선을 시각화하면 과적합 여부를 판단하는 데 도움이 됩니다
- sklearn의 log_loss 함수를 사용하면 간편하게 계산할 수 있습니다
4. 로지스틱 회귀 구현
이론을 충분히 배운 김개발 씨는 이제 실전에 뛰어들 준비가 되었습니다. "백 번 듣는 것보다 한 번 코딩하는 게 낫겠죠?" 박시니어 씨가 웃으며 말했습니다.
"좋아, 유방암 진단 데이터로 완전한 분류 모델을 만들어보자."
로지스틱 회귀 구현은 데이터 로드부터 모델 평가까지 전체 파이프라인을 다룹니다. 마치 요리사가 재료 준비부터 플레이팅까지 모든 과정을 거치듯, 머신러닝도 체계적인 단계를 밟아야 합니다.
이번에는 실제 의료 데이터를 사용해 완전한 분류 시스템을 구축해봅니다.
다음 코드를 살펴봅시다.
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
# 1. 데이터 로드
data = load_breast_cancer()
X, y = data.data, data.target
# 2. 데이터 분할 (학습 80%, 테스트 20%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3. 특성 스케일링 (중요!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 4. 모델 학습
model = LogisticRegression(max_iter=1000)
model.fit(X_train_scaled, y_train)
# 5. 예측 및 평가
y_pred = model.predict(X_test_scaled)
print(f"정확도: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred, target_names=['악성', '양성']))
김개발 씨는 드디어 실제 프로젝트를 시작했습니다. 이번 목표는 유방암 진단 보조 시스템입니다.
세포 검사 데이터를 입력하면 악성인지 양성인지 예측하는 모델을 만들어야 합니다. 박시니어 씨가 조언했습니다.
"머신러닝 프로젝트는 정해진 단계가 있어. 데이터 로드, 전처리, 분할, 학습, 평가.
이 흐름을 기억해." 먼저 데이터를 로드합니다. sklearn의 load_breast_cancer 함수는 유명한 위스콘신 유방암 데이터셋을 제공합니다.
569개의 샘플과 30개의 특성으로 구성되어 있습니다. 다음으로 데이터를 학습용과 테스트용으로 분할합니다.
왜 나눌까요? 모델이 암기하는 것이 아니라 진짜 패턴을 학습했는지 확인하기 위해서입니다.
시험에서 이미 본 문제만 풀면 실력을 알 수 없잖아요? test_size=0.2는 20%를 테스트용으로 남겨둔다는 의미입니다.
random_state=42는 재현 가능한 결과를 위한 시드값입니다. 특성 스케일링은 매우 중요한 단계입니다.
로지스틱 회귀는 경사하강법을 사용하는데, 특성들의 스케일이 다르면 학습이 느려지거나 불안정해집니다. StandardScaler는 각 특성의 평균을 0, 표준편차를 1로 맞춥니다.
주의할 점이 있습니다. 스케일러는 학습 데이터에만 fit 해야 합니다.
테스트 데이터에는 transform만 적용합니다. 그렇지 않으면 데이터 누수가 발생해서 평가 결과가 왜곡됩니다.
모델 학습은 간단합니다. LogisticRegression 객체를 생성하고 fit 메서드를 호출하면 됩니다.
max_iter=1000은 최대 반복 횟수를 늘려서 수렴을 보장합니다. 평가 단계에서는 여러 지표를 확인합니다.
정확도는 전체 중 맞춘 비율입니다. 하지만 정확도만으로는 부족합니다.
의료 분야에서는 재현율이 중요합니다. 실제 암 환자를 놓치면 큰 문제니까요.
classification_report는 정밀도, 재현율, F1 점수를 한눈에 보여줍니다. confusion_matrix는 어떤 유형의 오류가 발생했는지 세부적으로 파악하게 해줍니다.
실제로 이 코드를 실행하면 약 97% 이상의 정확도를 얻을 수 있습니다. 놀라운 결과죠?
하지만 박시니어 씨는 경고했습니다. "실제 의료 현장에서는 더 엄격한 검증이 필요해.
교차 검증, 다양한 데이터셋 테스트 등을 거쳐야 해." 김개발 씨는 완전한 머신러닝 파이프라인을 구축한 것에 뿌듯해했습니다. 데이터 과학의 기본기가 탄탄해진 느낌이었습니다.
실전 팁
💡 - fit_transform은 학습 데이터에만, transform은 테스트 데이터에 사용하세요
- 불균형 데이터에서는 정확도보다 F1 점수나 AUC를 확인하세요
- 수렴 경고가 나오면 max_iter를 늘리거나 solver를 변경해보세요
5. SVM 알고리즘 원리
로지스틱 회귀를 마스터한 김개발 씨에게 박시니어 씨가 새로운 도전을 던졌습니다. "로지스틱 회귀가 좋긴 한데, 더 강력한 분류기가 있어.
SVM이라고, 마진을 최대화하는 똑똑한 녀석이야." 김개발 씨의 눈이 반짝였습니다.
**SVM(Support Vector Machine)**은 클래스 간의 간격, 즉 마진을 최대화하는 결정 경계를 찾는 알고리즘입니다. 마치 두 나라 사이에 가장 넓은 비무장지대를 설정하는 것과 같습니다.
경계선에 가장 가까운 데이터 포인트들을 서포트 벡터라고 부르며, 이들이 결정 경계를 지탱합니다.
다음 코드를 살펴봅시다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_blobs
# 2차원 분류 데이터 생성
X, y = make_blobs(n_samples=100, centers=2, random_state=42, cluster_std=1.5)
# SVM 모델 학습 (선형 커널)
svm_model = SVC(kernel='linear', C=1.0)
svm_model.fit(X, y)
# 서포트 벡터 확인
print(f"서포트 벡터 개수: {len(svm_model.support_vectors_)}")
print(f"서포트 벡터 위치:\n{svm_model.support_vectors_[:3]}")
# 결정 경계 시각화를 위한 메시 그리드
xx, yy = np.meshgrid(np.linspace(X[:, 0].min()-1, X[:, 0].max()+1, 100),
np.linspace(X[:, 1].min()-1, X[:, 1].max()+1, 100))
Z = svm_model.decision_function(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
# 결정 경계와 마진 시각화
plt.contour(xx, yy, Z, levels=[-1, 0, 1], linestyles=['--', '-', '--'])
plt.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='black')
plt.scatter(svm_model.support_vectors_[:, 0], svm_model.support_vectors_[:, 1],
s=200, facecolors='none', edgecolors='green', linewidths=2, label='서포트 벡터')
plt.legend()
plt.title('SVM: 마진을 최대화하는 결정 경계')
plt.show()
김개발 씨는 새로운 알고리즘을 배울 준비가 되었습니다. SVM, 서포트 벡터 머신이라는 이름부터 뭔가 강력해 보입니다.
박시니어 씨가 화이트보드에 두 그룹의 점을 그렸습니다. "이 두 그룹을 나누는 선을 어떻게 그을래?" 김개발 씨는 대충 가운데를 지나는 선을 그었습니다.
"이 정도면 되지 않나요?" 박시니어 씨가 고개를 저었습니다. "그 선도 분류는 하지만, 최선은 아니야.
새로운 데이터가 들어왔을 때 경계선에 너무 가까우면 잘못 분류될 수 있거든." SVM의 핵심 아이디어는 마진 최대화입니다. 마진은 결정 경계와 가장 가까운 데이터 포인트 사이의 거리입니다.
SVM은 이 마진이 가장 넓어지는 결정 경계를 찾습니다. 마치 두 나라 사이에 비무장지대를 설정한다고 생각해보세요.
비무장지대가 넓을수록 충돌 가능성이 줄어듭니다. SVM도 마찬가지로, 마진이 넓을수록 새로운 데이터에 대한 일반화 성능이 좋아집니다.
결정 경계에 가장 가까이 있는 데이터 포인트들을 서포트 벡터라고 부릅니다. 이름에 "벡터"가 들어간 이유는 이 점들이 결정 경계를 지탱하기 때문입니다.
서포트 벡터만 있으면 결정 경계를 완벽하게 정의할 수 있습니다. 나머지 데이터 포인트들은 아무리 많아도 경계에 영향을 주지 않습니다.
위의 코드에서 SVC는 Support Vector Classifier의 약자입니다. kernel='linear'는 선형 결정 경계를 사용한다는 의미입니다.
C 파라미터는 마진의 유연성을 조절합니다. C가 크면 마진 내에 데이터 포인트가 들어오는 것을 엄격하게 금지합니다.
학습 데이터에는 잘 맞지만 과적합 위험이 있습니다. C가 작으면 일부 오분류를 허용하면서 더 넓은 마진을 추구합니다.
시각화 코드에서 점선은 마진의 경계를, 실선은 결정 경계를 나타냅니다. 초록색 원으로 표시된 점들이 바로 서포트 벡터입니다.
이 점들만이 결정 경계를 결정하는 데 기여합니다. 로지스틱 회귀와 비교하면 어떨까요?
로지스틱 회귀는 모든 데이터 포인트를 고려해서 확률적 경계를 학습합니다. 반면 SVM은 경계 근처의 핵심 데이터만 보고 가장 안전한 경계를 찾습니다.
김개발 씨는 두 알고리즘의 철학적 차이를 이해했습니다. "로지스틱 회귀는 민주적이고, SVM은 핵심 인물만 신경 쓰는 거군요!"
실전 팁
💡 - 데이터가 선형 분리 가능하면 선형 SVM이 매우 효과적입니다
- C 값은 교차 검증으로 최적화하세요, 기본값 1.0이 항상 최선은 아닙니다
- 대용량 데이터에서는 SGDClassifier(loss='hinge')가 더 빠를 수 있습니다
6. 커널 트릭 이해
김개발 씨는 새로운 문제에 직면했습니다. 데이터가 직선으로 나뉘지 않고, 마치 과녁처럼 동심원 형태로 분포되어 있었습니다.
"이건 아무리 선을 그어도 분류가 안 되는데요?" 박시니어 씨가 미소 지었습니다. "바로 그때 커널 트릭이 등장하는 거야."
커널 트릭은 데이터를 더 높은 차원으로 변환하여 선형 분리가 가능하게 만드는 기법입니다. 마치 종이 위에서는 분리할 수 없는 두 점을 종이를 접어서 분리하는 것과 같습니다.
놀라운 점은 실제로 고차원 계산을 하지 않고도 결과를 얻을 수 있다는 것입니다. 이것이 바로 "트릭"이라고 불리는 이유입니다.
다음 코드를 살펴봅시다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_circles
# 동심원 형태의 비선형 데이터 생성
X, y = make_circles(n_samples=200, noise=0.05, factor=0.5, random_state=42)
# 선형 SVM (실패 예상)
svm_linear = SVC(kernel='linear')
svm_linear.fit(X, y)
# RBF 커널 SVM (성공!)
svm_rbf = SVC(kernel='rbf', gamma='scale')
svm_rbf.fit(X, y)
# 비교 시각화
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
for ax, model, title in zip(axes, [svm_linear, svm_rbf], ['선형 커널', 'RBF 커널']):
xx, yy = np.meshgrid(np.linspace(-1.5, 1.5, 100), np.linspace(-1.5, 1.5, 100))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
ax.scatter(X[:, 0], X[:, 1], c=y, cmap='coolwarm', edgecolors='black')
ax.set_title(f'{title} - 정확도: {model.score(X, y):.2%}')
plt.tight_layout()
plt.show()
김개발 씨는 난감한 상황에 처했습니다. 새 프로젝트의 데이터가 이상했습니다.
한 클래스가 가운데에 모여 있고, 다른 클래스가 그 주위를 둘러싸고 있었습니다. 마치 과녁처럼요.
"아무리 직선을 그어도 이건 분리가 안 돼요." 김개발 씨가 한숨을 쉬었습니다. 박시니어 씨가 흥미로운 질문을 던졌습니다.
"2차원에서 안 되면, 3차원으로 올려보면 어떨까?" 종이 위에 그려진 동심원을 상상해보세요. 2차원에서는 아무리 선을 그어도 안과 밖을 분리할 수 없습니다.
하지만 만약 안쪽 원을 위로 들어올릴 수 있다면? 3차원에서는 수평면 하나로 깔끔하게 분리됩니다.
이것이 바로 커널 트릭의 핵심 아이디어입니다. 현재 차원에서 선형 분리가 불가능하면, 더 높은 차원으로 데이터를 변환합니다.
하지만 문제가 있습니다. 차원이 높아지면 계산량이 폭발적으로 늘어납니다.
여기서 "트릭"이 등장합니다. 커널 함수를 사용하면 실제로 고차원 변환을 하지 않고도 고차원에서의 내적 결과를 얻을 수 있습니다.
가장 많이 사용되는 커널은 RBF(Radial Basis Function) 커널입니다. 가우시안 커널이라고도 불립니다.
두 데이터 포인트 사이의 거리를 기반으로 유사도를 계산합니다. 위의 코드에서 make_circles로 동심원 형태의 데이터를 생성합니다.
선형 커널로는 약 50% 정확도밖에 나오지 않습니다. 무작위 추측과 다를 바 없죠.
하지만 RBF 커널을 사용하면 거의 100%에 가까운 정확도를 달성합니다. gamma 파라미터는 RBF 커널의 영향 범위를 조절합니다.
gamma가 크면 각 데이터 포인트의 영향 범위가 좁아져서, 결정 경계가 복잡해집니다. gamma가 작으면 영향 범위가 넓어져서, 결정 경계가 부드러워집니다.
다른 커널들도 있습니다. 다항식 커널은 degree 파라미터로 차수를 지정합니다.
시그모이드 커널은 신경망의 활성화 함수와 유사합니다. 하지만 실무에서는 RBF 커널이 가장 범용적으로 사용됩니다.
커널 선택에 정답은 없습니다. 데이터의 특성에 따라 달라집니다.
일반적으로 먼저 선형 커널을 시도하고, 성능이 부족하면 RBF 커널을 사용합니다. 김개발 씨는 감탄했습니다.
"차원을 높이면서 계산은 안 늘리다니, 정말 트릭이네요!" 박시니어 씨가 덧붙였습니다. "수학의 아름다움이지.
덕분에 복잡한 패턴도 분류할 수 있게 됐어."
실전 팁
💡 - 먼저 선형 커널로 시도하고, 안 되면 RBF로 전환하세요
- gamma와 C는 함께 튜닝해야 합니다, GridSearchCV를 활용하세요
- 특성이 많으면 선형 커널이 이미 충분히 좋을 수 있습니다
7. SVM 분류 모델 구현
모든 이론을 섭렵한 김개발 씨는 SVM으로 완전한 분류 시스템을 구축하기로 했습니다. "이번에는 숫자 이미지를 인식하는 모델을 만들어볼게요!" 박시니어 씨가 고개를 끄덕였습니다.
"좋아, 멀티클래스 분류에 SVM이 얼마나 강력한지 보여줄게."
SVM 분류 모델 구현은 실제 이미지 데이터에 SVM을 적용하는 전체 과정을 다룹니다. 마치 숙련된 요리사가 여러 재료를 조합해 완성된 요리를 만들듯, 데이터 전처리부터 하이퍼파라미터 튜닝까지 모든 단계를 체계적으로 수행합니다.
이번 예제에서는 손글씨 숫자 인식 문제를 해결합니다.
다음 코드를 살펴봅시다.
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.metrics import classification_report, confusion_matrix
import numpy as np
# 1. 손글씨 숫자 데이터 로드 (8x8 이미지, 0-9 분류)
digits = load_digits()
X, y = digits.data, digits.target
# 2. 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3. 스케일링
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 4. 하이퍼파라미터 튜닝
param_grid = {'C': [0.1, 1, 10], 'gamma': ['scale', 0.01, 0.1]}
grid_search = GridSearchCV(SVC(kernel='rbf'), param_grid, cv=5, scoring='accuracy')
grid_search.fit(X_train_scaled, y_train)
# 5. 최적 모델로 예측
best_model = grid_search.best_estimator_
y_pred = best_model.predict(X_test_scaled)
print(f"최적 파라미터: {grid_search.best_params_}")
print(f"테스트 정확도: {best_model.score(X_test_scaled, y_test):.4f}")
print(classification_report(y_test, y_pred))
김개발 씨는 드디어 최종 프로젝트에 착수했습니다. 손글씨 숫자 인식, 고전적이지만 여전히 중요한 문제입니다.
손글씨 숫자 데이터셋은 8x8 픽셀 이미지로 구성됩니다. 각 픽셀은 0부터 16까지의 밝기 값을 가집니다.
이 64개의 픽셀 값이 특성이 되고, 레이블은 0부터 9까지의 숫자입니다. "잠깐, 이건 이진 분류가 아니라 10개 클래스 분류잖아요?" 김개발 씨가 물었습니다.
박시니어 씨가 설명했습니다. "SVM은 기본적으로 이진 분류기야.
하지만 sklearn은 자동으로 일대일(one-vs-one) 또는 일대다(one-vs-rest) 전략을 적용해서 멀티클래스 분류를 처리해." 일대일 전략은 모든 클래스 쌍에 대해 분류기를 만듭니다. 10개 클래스면 45개의 분류기가 필요합니다.
일대다 전략은 각 클래스에 대해 "이 클래스인가 아닌가"를 판단하는 분류기를 만듭니다. 10개 클래스면 10개의 분류기가 필요합니다.
데이터 전처리 단계는 로지스틱 회귀와 동일합니다. 분할하고, 스케일링합니다.
SVM도 특성 스케일링에 민감하기 때문에 반드시 필요한 단계입니다. 이번 프로젝트의 핵심은 하이퍼파라미터 튜닝입니다.
C와 gamma의 조합에 따라 성능이 크게 달라집니다. GridSearchCV는 가능한 모든 조합을 시도하고, 교차 검증으로 가장 좋은 조합을 찾아줍니다.
param_grid에 테스트할 값들을 지정합니다. C는 0.1, 1, 10을, gamma는 'scale', 0.01, 0.1을 시도합니다.
cv=5는 5겹 교차 검증을 의미합니다. 총 3 x 3 x 5 = 45번의 학습이 이루어집니다.
실행 결과, 최적 파라미터와 함께 테스트 정확도가 출력됩니다. 보통 98% 이상의 정확도를 달성합니다.
classification_report는 각 숫자별 정밀도와 재현율을 보여줍니다. 어떤 숫자가 잘 인식되고, 어떤 숫자가 혼동되는지 확인해보세요.
보통 4와 9, 3과 8처럼 생김새가 비슷한 숫자들이 혼동되기 쉽습니다. 실무에서는 GridSearchCV 대신 RandomizedSearchCV를 사용하기도 합니다.
파라미터 공간이 크면 모든 조합을 시도하는 것이 비효율적이기 때문입니다. 랜덤 탐색은 더 적은 시도로 좋은 결과를 찾을 수 있습니다.
김개발 씨는 완성된 모델을 보며 뿌듯해했습니다. "98% 정확도라니, 사람보다 잘할지도 모르겠네요!" 박시니어 씨가 웃으며 덧붙였습니다.
"이제 로지스틱 회귀와 SVM, 두 가지 강력한 무기를 갖게 됐어. 상황에 맞게 잘 활용하렴."
실전 팁
💡 - GridSearchCV는 시간이 오래 걸리므로, 먼저 넓은 범위로 탐색 후 좁혀가세요
- n_jobs=-1 옵션으로 병렬 처리하면 튜닝 시간을 단축할 수 있습니다
- 최종 모델은 전체 학습 데이터로 다시 학습시키는 것이 좋습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (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의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.