본문 바로가기
정보/IT 지식 정보

scikit-learn으로 퍼셉트론과 로지스틱 회귀 구현하기 [4편]

by 안다니. 2026. 3. 26.
반응형
로지스틱스 회귀

 

 

직접 만든 코드를 라이브러리로 대체하고, 시그모이드 함수와 확률 기반 분류를 이해합니다

시리즈: 신경망 밑바닥부터 구현하기
1편: 퍼셉트론 · 2편: ADALINE · 3편: SGD · 4편: scikit-learn 퍼셉트론 & 로지스틱 회귀 · 5편: SVM · 6편: 결정 트리 & 랜덤 포레스트

왜 scikit-learn을 쓰는가?

1~3편에서 퍼셉트론과 ADALINE을 밑바닥부터 구현했습니다. 원리를 이해하는 데는 좋지만, 실무에서 매번 이렇게 구현할 수는 없습니다. scikit-learn은 머신러닝 알고리즘을 표준화된 인터페이스로 제공하는 파이썬 라이브러리로, fit()predict() 패턴만 익히면 거의 모든 알고리즘을 같은 방식으로 사용할 수 있습니다.

이번 편부터는 scikit-learn을 활용하되, 각 알고리즘이 내부적으로 어떻게 동작하는지 설명하는 데 집중합니다.

데이터 준비 — 훈련/테스트 분할과 표준화

이번에는 붓꽃 3종류(setosa, versicolor, virginica) 전부를 분류합니다. 1~3편에서는 2종류만 썼지만, scikit-learn은 다중 클래스 분류도 자연스럽게 처리합니다.

데이터 로드와 분할
from sklearn import datasets
from sklearn.model_selection import train_test_split

iris = datasets.load_iris()
X = iris.data[:, [2, 3]]  # 꽃잎 길이, 꽃잎 너비
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=1, stratify=y)

stratify=y가 핵심입니다. 이걸 지정하면 훈련 데이터와 테스트 데이터에 각 클래스의 비율이 동일하게 유지됩니다. 원본 데이터가 각 50개(1:1:1)이므로, 훈련 세트에 각 35개, 테스트 세트에 각 15개가 깔끔하게 배분됩니다.

표준화
from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
sc.fit(X_train)                    # 훈련 데이터의 평균/표준편차 계산
X_train_std = sc.transform(X_train) # 훈련 데이터 변환
X_test_std = sc.transform(X_test)   # 테스트 데이터도 같은 기준으로 변환
왜 fit은 훈련 데이터에만 하나요?
테스트 데이터는 "아직 보지 못한 새로운 데이터"를 시뮬레이션하는 것입니다. 테스트 데이터의 정보(평균, 표준편차)를 사전에 사용하면 정보 누출(data leakage)이 발생합니다. 항상 훈련 데이터에서 계산한 기준으로 테스트 데이터를 변환해야 합니다.

scikit-learn 퍼셉트론으로 3클래스 분류

from sklearn.linear_model import Perceptron

ppn = Perceptron(eta0=0.1, random_state=1)
ppn.fit(X_train_std, y_train)

y_pred = ppn.predict(X_test_std)
print('잘못 분류된 샘플 개수:', (y_test != y_pred).sum())
실행 결과
잘못 분류된 샘플 개수: 1
정확도: 0.978 (97.8%)
실제 반복 횟수(에포크): 11

1~3편에서 수십 줄에 걸쳐 구현했던 퍼셉트론이 단 3줄로 완성되었습니다. 45개 테스트 샘플 중 1개만 오분류했으니 97.8%의 정확도입니다. scikit-learn의 퍼셉트론은 내부적으로 SGD 방식을 사용하며, 11번의 에포크 만에 수렴했습니다.

 

반응형

로지스틱 회귀란? — 시그모이드 함수의 등장

퍼셉트론은 "이 꽃이 A인가 B인가"만 판단합니다. 하지만 실무에서는 "A일 확률이 85%, B일 확률이 15%"처럼 확률값이 필요한 경우가 훨씬 많습니다. 이것을 가능하게 하는 것이 로지스틱 회귀(Logistic Regression)입니다.

이름에 "회귀"가 들어가지만 분류 알고리즘입니다. 핵심은 시그모이드(sigmoid) 함수입니다.

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

시그모이드 함수는 어떤 값이든 0과 1 사이로 변환합니다. 가중합이 크면 1에 가깝고, 작으면 0에 가깝고, 0이면 정확히 0.5를 출력합니다. 이 출력값을 "클래스 1에 속할 확률"로 해석할 수 있습니다.

ADALINE과의 차이
ADALINE: 가중합 → 항등 함수(그대로 통과) → 연속값 출력
로지스틱 회귀: 가중합 → 시그모이드 함수 → 0~1 확률값 출력

활성화 함수만 바뀌었을 뿐, 구조는 거의 동일합니다!

로지스틱 회귀 직접 구현하기

ADALINE 코드에서 두 가지만 바꾸면 로지스틱 회귀가 됩니다.

변경점 1: 활성화 함수 항등 함수 → 시그모이드 함수
return 1. / (1. + np.exp(-z))
변경점 2: 비용 함수 오차 제곱합 → 로그 손실(cross-entropy)
-y·log(출력) - (1-y)·log(1-출력)
핵심 코드 — 비용 함수 변경
# ADALINE의 비용 함수 (오차 제곱합)
# cost = (errors ** 2).sum() / 2.0

# 로지스틱 회귀의 비용 함수 (로그 손실)
cost = -y.dot(np.log(output)) - ((1 - y).dot(np.log(1 - output)))

왜 비용 함수를 바꿔야 할까요? 시그모이드 함수의 출력은 S자 곡선이라서, 오차 제곱합을 쓰면 비용 함수가 울퉁불퉁해져서 경사 하강법이 지역 최솟값에 빠질 수 있습니다. 로그 손실은 이 문제가 없어서 안정적으로 수렴합니다.

직접 구현한 로지스틱 회귀 결과
학습률 0.5, 1000 에포크로 학습 후 setosa와 versicolor를 깔끔하게 분류하는 결정 경계가 만들어집니다.

scikit-learn 로지스틱 회귀 — 확률과 규제

모델 학습
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression(C=100.0, random_state=1)
lr.fit(X_train_std, y_train)

scikit-learn의 로지스틱 회귀는 3클래스 분류도 자동으로 처리합니다. 내부적으로 OvR(One-vs-Rest) 방식을 사용하여, 각 클래스를 나머지와 구분하는 분류기를 3개 만듭니다.

확률값 확인 — predict_proba
lr.predict_proba(X_test_std[:3, :])
실행 결과
[[0.000, 0.000, 1.000], → virginica (99.96%)
 [0.994, 0.006, 0.000], → setosa (99.36%)
 [0.999, 0.001, 0.000]] → setosa (99.87%)

각 행의 합은 항상 1.0입니다. 가장 높은 확률의 클래스가 예측 결과가 됩니다.

이것이 퍼셉트론과의 결정적 차이입니다. 퍼셉트론은 "이건 setosa다"만 말하지만, 로지스틱 회귀는 "이건 setosa일 확률이 99.36%, versicolor일 확률이 0.64%다"라고 말합니다.

L2 규제와 파라미터 C

scikit-learn의 로지스틱 회귀에는 C라는 파라미터가 있습니다. 이것은 규제(regularization)의 강도를 조절합니다.

규제란? 모델이 훈련 데이터에 너무 맞춰지는 것(과적합)을 방지하는 기법입니다.

C 값이 크면 → 규제가 약함 → 훈련 데이터에 더 맞추려 함
C 값이 작으면 → 규제가 강함 → 가중치를 작게 유지하려 함

C를 10⁻⁵부터 10⁵까지 바꿔가며 가중치 변화를 관찰하면, C가 작을수록 가중치가 0에 가까워지고, C가 클수록 가중치가 자유롭게 커지는 것을 확인할 수 있습니다.

실무에서의 팁
C의 최적값은 데이터마다 다릅니다. 보통 교차 검증(cross-validation)으로 C=0.001, 0.01, 0.1, 1, 10, 100 등을 시험해보며 최적값을 찾습니다.

정리와 다음 단계

4편 핵심 정리

scikit-learn을 사용하면 복잡한 알고리즘도 fit/predict 패턴으로 간단하게 구현할 수 있습니다.

데이터 분할 시 stratify로 클래스 비율을 유지하고, 표준화는 훈련 데이터 기준으로만 합니다.

로지스틱 회귀는 시그모이드 함수로 확률값을 출력하며, ADALINE에서 활성화 함수와 비용 함수만 바꾼 것입니다.

규제(C)는 과적합을 방지하는 핵심 도구이며, 교차 검증으로 최적값을 찾습니다.

다음 글 예고
👉 5편: SVM이란? 선형 SVM부터 커널 트릭까지 완전 정리
직선으로 분류할 수 없는 XOR 문제를 커널 SVM이 어떻게 해결하는지 확인합니다.

신경망 밑바닥부터 구현하기 시리즈 [4편]

반응형

댓글