2. 분류(Classification) > 2-8. 분류 모델

분류 모델

분류 모델

분류 모델은 크게

  • 선형회귀
  • 의사결정 트리
  • SVM
  • 최근접 알고리즘
  • 나이브 베이즈
  • 앙상블 모델

등이 있습니다.

이번 커리큘럼에서는 모든 알고리즘의 모델들을 다루지는 않습니다. 대표적인 모델들의 간단한 원리를 iris 데이터로 간단하게 실습하고, 각각의 하이퍼 파라미터에 대해 알아볼 예정입니다.

자세한 내용은 sklearn 공식 API를 확인하세요.

1. 선형회귀 기반: sklearn.linear_model

대표적인 선형 회귀 기반 분류 모델로는 LogisticRegression이 있습니다.

이름에 Regression이 들어가지만 Sigmoid 함수를 활용한 이진분류에도 해당합니다. 선형 회귀는 특정 예측 값을 반환하지만, sigmoid 함수를 활용하면 특정 값을 기점으로 0 또는 1 값을 반환하는 분류 모델이라고 할 수 있습니다.

주요 하이퍼 파라미터

  • penalty:

    • 설정 가능 값 : 'l1', 'l2', 'elasticnet', 'none'
    • 기본 값 : 'l2'
    • 1차, 2차, 1차+2차 혼합, 미규제 중에서 복잡한 모델에 대한 규제 방법을 선택
  • C : penalty에 대한 계수 설정, 기본 값은 1.0, 높을 수록 복잡한 모델에 대한 규제 강화

  • solver :

    • 설정 가능 값 : ‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’, ‘saga’
    • 기본 값 : 'lbfgs' 로지스틱 회귀는 비선형 방정식이라 근사 알고리즘으로 접근하는데, 알고리즘의 종류 선택. 특정 solver는 일부 penalty 방식을 지원하지 않을 수도 있음
  • random_state : 데이터를 고정해 실행 시마다 결과 고정

주의: 로지스틱 회귀는 종속변수가 이항분포를 따르고 그 모수 μ가 독립변수 x에 의존한다고 가정합니다.

자세한 내용은 데이터사이언스 스쿨과 이 블로그를 참고하세요.

애석하게도 iris데이터의 target은 3개입니다. 그래서 억지로 2개 값만 갖는 데이터를 설정하고 구현합니다.

from sklearn.datasets import load_iris

iris = load_iris()
features = iris.data
target = iris.target

dfX = pd.DataFrame(features, columns=iris.feature_names)
dfy = pd.DataFrame(target, columns=["species"])

# target인 species가 0, 1인 데이터만 가져오기

df = pd.concat([dfX, dfy], axis=1)
df = df[df.species.isin([1, 2])]

# target_df, feature_df 만들기
target_df = df['species']
feature_df = df.drop('species', axis=1)

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(feature_df, target_df)

from sklearn.linear_model import LogisticRegression

model = LogisticRegression(penalty='l2')
model.fit(x_train, y_train)

print("정확도:", model.score(x_train, y_train))
>>>

정확도: 0.9466666666666667

지금은 정확도만 구하지만 odds비나 sigmoid 함수, 임계값threshold 등의 개념도 있습니다. 로지스틱 휘귀는 회귀 부분에서 더 자세하게 설명해 드릴 예정입니다.

2. 의사결정 트리: sklearn.tree

의사결정 트리Decision Tree는 가장 간단하고 직관적인 분류 모델입니다. 데이터에서 추론된 규칙들을 학습해 목표 변수의 값을 예측합니다.

용어 설명

  • Root Node(루트 노드) : 깊이가 0인 꼭대기 노드

  • Leaf Node(리프 노드) : 자식 노드가 없는 마지막 노드

  • Gini Impurity(지니 불순도) : 한 노드의 모든 샘플이 같은 클래스에 속해있으면, 해당 노드는 순수(gini=0)하다고 한다.

과정

  1. root node(꼭대기)에서 시작
  2. petal width가 0.8과 같거나 작은지 검사(조건에 따라 좌우 분기)
  3. 만약 False라면 오른쪽으로 이동해 root node에서 했던 조건 검사를 실시하여 반복하여 분기
  4. 마지막에 leaf node(끝)에 도달했을 때, 추가적인 조건 검사 없이 가장 많은 클래스의 비중을 차지하고 있는 곳으로 클래스 예측

장점

  1. 시각화 할 수 있어 직관적이다.
  2. 데이터 전처리를 크게 신경쓰지 않아도 작동한다.
  3. 수치, 범주형 데이터를 모두 처리할 수 있다.
  4. white box 모델이다: black box와 다르게 내부에서 어떤 논리적 절차를 거치는지 확인할 수 있습니다.

단점

  1. 과적합: 전처리를 하지 않은 경우 특히 괴상한 조합을 통해 높은 accuracy에 도달하기도 합니다.
  2. 데이터에 휘둘리는 경향: 데이터가 바뀌면 완전히 다른 tree를 만들 수 있습니다.
from sklearn import tree
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score, confusion_matrix, precision_recall_fscore_support
from sklearn.model_selection import train_test_split

# iris 데이터 불러오기
iris = load_iris()

iris_df = pd.DataFrame(data = iris.data, columns = iris.feature_names)

iris_df['target'] = iris.target
iris_df

X = iris_df.drop('target',1)
Y = iris_df.target

# train_test_split
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.3, random_state = 0)

# decision tree 모델 학습 및 예측
dt = tree.DecisionTreeClassifier().fit(x_train,y_train)

dt.predict(x_train)

y_pred = dt.predict(x_test)

# tree 시각화
tree.plot_tree(dt)

tree1

# 더 예쁘게 시각화
!pip install export_graphviz

import graphviz
dot_data = tree.export_graphviz(dt, out_file=None,

feature_names=iris.feature_names,

class_names=iris.target_names,filled=True, rounded=True,special_characters=True)

graph = graphviz.Source(dot_data)

graph

tree2

3. SVM서포트 벡터 머신: sklearn.svm

서포트 벡터 머신SVM은 2d, 3d 등 공간 상에서 데이터를 나누는 최적의 선(결정경계decision boundary)을 정의하는 모델입니다. 2차원이면 선분, 3차원이면 면이 결정 경계가 됩니다. 그 이상의 차원도 가능하지만 3차원 이상은 사고할 수 없으니 그냥 있다고만 생각하셔도 좋습니다.

용어 정리

  • 결정 경계decision boundary: 데이터를 분류하는 기준선

  • 서포트 벡터: 결정 경계와 가장 가까운 데이터. 결정 경계를 '지지support'하기 때문에 support vector입니다.

  • 마진margin: 마진은 아래 그림처럼 결정 경계와 서포트 벡터 사이에 생기는 거리

    • 하드 마진: 결정 경계와 가장 가가운 서포트 벡터를 기준으로 마진 설정. 이상치를 허용하지 않아 오버피팅(과적합) 가능

    • 소프트 마진: 결정경계보다 군집에 더 가깝게 마진 설정. 이상치를 허용하여 언더피팅(과소적합) 가능

support vector

svm_img

주요 하이퍼 파라미터

  • C(float, default=1.0): 오류 허용율

e.g.)

# 기본값=1: 오류 허용 안함
svm = SVC(C=1)

# 1%까지 오류 허용(99%)
svm = SVC(C=0.01)

  • kernel(default=’rbf'): 분류 모델

  • linear: 선형SVM. 직선을 그어 나눕니다. 그렇기 때문에 이상치를 처리하지 못하는 경우가 많습니다. C값을 통해 오류를 어디까지 허용할지 정해 정확도를 높일 수 있습니다.

  • rbf: 디폴트. Radius Basis Function 또는 Gaussian Kernel. 고차원 공간에 사상한 후 2차원 공간에 매핑해 분류합니다.

rbf

아래 그림처럼 직선과 C값 만으로 구별할 수 없는 경우가 많습니다.

svm_이상치

뿐만 아니라 아래 그림처럼 선으로 분류가 불가능할 수도 있습니다.

![svm_rbf_이상치](../img(2-8-3_5_svm_rbf.png)

rbf모델은 이러한 데이터도 분류할 수 있습니다.

  • degree(int, default=3): 몇 개의 polynomial kernel로 나눌지

  • gamma({‘scale’, ‘auto’} or float, default=’scale’): poly, rbf, sigmoid에 적용할 수 있습니다.

  • scale: 디폴트. 값을 지정하지 않으면 1로 사용.

gamma가 클수록 한 데이터 포인터들이 영향력을 행사하는 거리가 짧아지고, gamma가 낮을수록 커집니다.

  • auto: 자동으로 감마를 설정합니다.

svm_gamma

gamma 값이 10에서 100, 1000으로 갈 수록 하나의 벡터가 주위의 반지름이 작아집니다. 즉, 영향력이 작아져 더 세분화된 분류가 가능합니다. 하지만 C와 마찬가지로 과대적합, 과소적합의 문제가 생길 수 있습니다.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.preprocessing import FunctionTransformer
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 데이터 셋
iris =load_iris()

#['sepal length (cm)','sepal width (cm)','petal length (cm)','petal width (cm)'] 타겟 외 컬럼

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=0)

# 스케일링 & 정규화
sc = StandardScaler()
sc.fit(X_train)

X_train_std=sc.transform(X_train)
X_test_std=sc.transform(X_test)

X_combined_std=np.vstack((X_train_std,X_test_std))
Y_combined_std=np.hstack((Y_train,Y_test))

# 모델링
## 선형 SVM
model1=SVC(kernel='linear').fit(X_test_std,Y_test)

## 다항 커널SVM
model2=SVC(kernel='poly', random_state=0,gamma=10,C=1).fit(X_test_std,Y_test)

## 가우시안 커널SVM
model3=SVC(kernel='rbf',random_state=0,gamma=1,C=1).fit(X_test_std,Y_test)

  

## 시각화 함수
def plot_iris(X,Y,model,title,xmin=-2.5,xmax=2.5, ymin=-2.5,ymax=2.5):

    XX,YY=np.meshgrid(
    np.arange(xmin,xmax,(xmax-xmin)/1000),
    np.arange(ymin,ymax,(ymax-ymin)/1000))

    ZZ=np.reshape(model.predict(np.array([XX.ravel(),YY.ravel()]).T),XX.shape)
        plt.contourf(XX,YY,ZZ,cmap=plt.cm.Paired_r,alpha=0.5)

    plt.scatter(X[Y == 0, 0], X[Y == 0, 1], c='r', marker='^', label='0', s=100)

    plt.scatter(X[Y == 1, 0], X[Y == 1, 1], c='g', marker='o', label='1', s=100)

    plt.scatter(X[Y == 2, 0], X[Y == 2, 1], c='b', marker='s', label='2', s=100)

    plt.xlim(xmin, xmax)
    plt.ylim(ymin, ymax)

    plt.xlabel("sepal length")
    plt.ylabel("sepal width")

    plt.title(title)

plt.figure(figsize=(8, 12))

plt.subplot(311)
plot_iris(X_test_std, Y_test, model1, "Linear SVC")

plt.subplot(312)
plot_iris(X_test_std, Y_test, model2, "Poly kernel SVC")

plt.subplot(313)
plot_iris(X_test_std, Y_test, model3, "RBF kernel SVM")

plt.tight_layout()

plt.show()

svm_result

공식 API, 블로그1블로그2, 블로그3, 블로그4에서 코드와 이미지를 참고했습니다. 원리를 더 파악하고 싶다면 블로그들을 참고하세요!

모델은 다 구현되어 있으니, 하이퍼 파라미터 중 C나 gamma 값을 변경하면서 변화를 살펴보세요!

4. 최근접 알고리즘: sklearn.neighbors

최근접 알고리즘의 대표적인 모델은 K Nearest Neighbors(KNN) 모델입니다.

KNN은 유사한 특징을 가진 데이터끼리의 거리가 가깝다는 기본적인 생각에서 시작합니다. 즉 기하학적으로 가장 가까운 데이터들을 하나의 클래스로 분류합니다.

knn_img

위 그림은 2개의 피처feature를 가졌기 때문에 2차원 그래프에 사상되었습니다. 모델은 반지름 k의 크기를 조절하면서 원 안이 Class A인지 Class B인지 판단합니다.

  • k = 3: 초록 세모가 2개, 별이 1개이므로 이 경우 Class B로 분류합니다.

  • k = 7: 별이 4개, 세모가 3개이므로 Class A로 분류합니다.

당연히 k가 너무 작으면 과적합overfitting, 너무 크면 과소적합underfitting이 일어날 수 있습니다.

  • 참고: 현실의 데이터는 이상치가 매우 클 수 있어요. KNN은 거리(크기)에 민감하기 때문에 가능하면 scaling을 통해 편차를 줄여주거나 삭제하는 조치가 모델 성능을 높일 수 있습니다.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import FunctionTransformer
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 데이터 셋

iris =load_iris()

#['sepal length (cm)','sepal width (cm)','petal length (cm)','petal width (cm)'] 타겟 외 컬럼
X=iris.data[:,:2]

# 타겟 컬럼
Y=iris.target

x_train,x_test,y_train,y_test = train_test_split(X,Y,test_size=0.3,random_state=30)

# 모델링
train_acc = []
test_acc = []

for k in range(1, 15):
    knn = KNeighborsClassifier(n_jobs=-1, n_neighbors = k)
    knn.fit(x_train, y_train)

    prediction = knn.predict(x_test)

    train_acc.append(knn.score(x_train, y_train))

    test_acc.append((prediction==y_test).mean())

# 시각화
plt.figure(figsize=(12, 9))
plt.plot(range(1, 15), train_acc, label='TRAIN set')
plt.plot(range(1, 15), test_acc, label='TEST set')

plt.xlabel("n_neighbors")
plt.ylabel("accuracy")

plt.xticks(np.arange(0, 16, step=1))

plt.legend()

knn result

k = 6일 때 test set의 성능이 가장 좋군요! 스케일링이나 다른 하이퍼 파라미터를 조정하면 달라질 수도 있습니다. 한번 조정해보면서 최고의 accuracy를 갖는 모델과 그때의 k값을 찾아보세요!

더 자세한 내용은 sklearn.neighbors 공식 API를 참고하세요. 코드와 설명은 블로그1블로그2를 참고했습니다.

5. 나이브 베이즈: sklearn.naive_bayes

나이브 베이즈Naive Bayes 분류는 베이지안 통계에 기반합니다. 그럼 베이지안 통계는 뭐냐구요? 사건과 관련된 여러 확률을 통해 아직 일어나지 않은 일에 대한 확률을 추정하는 확률론입니다.

나이브 베이즈를 설명하기 앞서 우리는 조건부 확률결합 확률을 알아야합니다.

  • 조건부 확률Likelihood

$$P(A|B) = \frac{P({A}\cap{B})}{P(B)}$$

하나씩 떼어서 살펴보면

$P(A)$: 사건 A가 일어날 확률

$P(B)$: 사건 B가 일어날 확률. $P(A|B)$에서 사전확률Prior

$P(A|B)$: 사건 B가 일어나고, 사건 A가 발생할 확률. 조건부 확률Likelihood

$P(B|A)$: 사건 A가 발생했을 때 사건 B가 먼저 일어났을 확률. 사후확률Posteriror

입니다. $P(A|B)$에서는 $P(B)$가 사전 확률, $P(A)$가 우리가 알고 싶은 사후 확률이 되겠군요.

물론 반대로 $P(A)$가 사전 확률이고, $P(B)$가 사후 확률인 $P(B|A)$도 존재할 수 있습니다.

  • 결합 확률

결합 확률은 두 가지 이상의 사건이 동시에 발생하는 확률입니다.

$P(X,Y) = P(X|Y)\cdot{P(Y)}$

사건 X, Y가 독립이라면

$$P(X,Y) = P(X|Y)\cdot{P(Y)} = \frac{P({X}\cap{Y})}{P(Y)}\cdot{P(Y)} = P({X}\cap{Y} = P(X) \cdot P(Y)$$

가 성립합니다.

  • 베이즈 정리

$$P(B|A) = \frac{{P(A|B)}\cdot{P(A)}}{P(A)}$$

이때 각각을 떼어서 정리하면 다음과 같습니다.

$P(B|A)$: 사후 확률Posterior

$P(A|B)$: 조건부확률Likelihood

$P(B)$: 사전확률Prior

$P(A)$: 이때 $P(A|B)$는 증거Evidence

입니다.

  • 베이즈 정리 활용 예시

그렇다면 베이즈 정리는 왜 사용할까요? 예시를 통해 알아봅시다. 아래는 가정입니다.

코로나 19 발생 초기, 감염된 30%가 마스크를 착용하지 않은 사람이었다. 즉, 나머지 70%는 마스크를 착용하고도 걸렸다! 혹시 마스크는 무의미하거나 오히려 감염을 부추기는 요인이 아닐까?

물론 이때 마스크를 착용한 사람과 그렇지 않은 사람의 비율을 알아야 합니다.

이 상황을 베이즈 정리로 표현해 봅시다.

$P(A)$: covid19에 걸릴 확률

$P(\sim{A})$: covid19에 걸리지 않을 확률

$P(B)$: 마스크 착용자 비율

$P(\sim{B})$: 마스크 미착용자 비율

앞선 정보로 추출할 수 있는 정보는

코로나에 걸린 사람 중 마스크를 쓰지 않은 사람 비율 = $P(\sim{B}|A)$ = 0.3

코로나에 걸린 사람 중 마스크를 쓴 사람 비율 = $P(B|A)$ = 0.7

우리가 알고 싶은 정보는

마스크를 착용한 사람 중 코로나에 걸린 사람의 비율 = $P(A|B)$

마스크를 착용하지 않은 사람 중 코로나에 걸린 사람의 비율 = $P(A|~B)$

우리는 $P(A)$, $P(B)$만 빼면 정답의 조건을 모두 알고 있네요!

각각을 0.01, 0.9라고 가정하면

마스크를 쓰고 코로나에 걸릴 확률 =

$$P(A|B) = \frac{{P(B|A)}\cdot{P(A)}}{P(B)} = \frac{0.7 \cdot 0.01}{0.9} \approx 0.0078$$

마스크를 쓰지 않고 코로나에 걸릴 확률 =

$$P(A|\sim{B}) = \frac{{P(\sim{B}|A)}\cdot{P(A)}}{P(\sim{B})} = \frac{0.3 \cdot 0.01}{0.1} \approx 0.03$$

즉, 마스크를 쓸 때보다 쓰지 않을 때 코로나19에 걸릴 가능성이$0.03 \div 0.0078 ≈ 3.8$배 높네요!

  • 나이브 베이즈 분류Naive Bayes Classification

베이즈 분류까지 알아 보았으니 머신러닝에서 베이즈 정리가 어떻게 나이브 베이즈 분류가 되는지 알아봐야겠죠?

나이브 베이즈 분류란

  • 피처feature의 조건부 확률을 통해 타겟target/label에 속할 가능성을 측정

  • 피처feature가 모두 상호 독립적이라는 가정 하에 확률을 단순화 -> 그래서 Naive하다고 합니다.

앞에서 언급한 결합확률과 베이즈 정리를 함께 생각해 보겠습니다. 피처가 모두 독립이므로 결합확률이 두 확률의 곱이겠군요.

X, Y를 피처 $P(X,Y)$를 하나의 확률로 취급하고 A를 타겟target/label로 가정합시다.

  • X, Y: feature

  • A: target/label

$$P(A|X,Y) = \frac{P({X,Y}\cap{A})}{P(A)}\cdot{P(X,Y)}$$

이때 발생되는 값들의 의미는 다음과 같습니다.

  • $P(A)$: 데이터가 target/label A에 속할 확률

  • $P(X,Y|A)$: 타겟값이 A인 데이터가 X, Y 피처 속성을 모두 가질 확률

  • $P(X,Y)$: 데이터가 X, Y 피처를 모두 가질 확률

  • $P(A|X,Y)$: 피처 X, Y를 모두 갖는 데이터가 타겟 A에 속할 확률

앞서 사건 A에 대해 X, Y의 독립을 가정했으므로 $P(X,Y) = P(X) \cdot P(Y)$입니다.

그러면

$$P(X,Y|A) = P(X|A)\cdot{P(Y|A)}$$

가 됩니다.

나이브 베이즈 수식으로 돌아오면 다음과 같군요.

$$P(A|X,Y) = \frac{P({X|A})\cdot{P(Y|A)}}{P(X)\cdot{P(Y)}}$$

  • 예시 코드

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import accuracy_score

# 아래와 같이 X, y데이터를 나눌 수 있습니다. 사실 X는 피처 집합이기 때문에 대문자,Y는 하나의 열만 갖는 Series이기 때문에 보통소문자로 표현합니다

X,y = load_iris(return_X_y=True)

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

# 가우시안 나이브 베이즈
nb = GaussianNB()
pred = nb.fit(X_train, y_train).predict(X_test)

print('Naive Bayes 예측 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))

>>>
Naive Bayes 예측 정확도: 0.9333

0.93이면 아무 조치도 하지 않았는데 굉장히 높은 수치네요!

통계에 대한 더 자세한 설명은 블로그를 참고하세요.

6. 앙상블: sklearn.ensemble

  • 앙상블Ensemble의 개념

앙상블Ensemble은 프랑스어로 함께, 동시에라는 의미를 가진 단어입니다. 그리고 사회과학 또는 철학에서도 중요한 개념입니다. 마르크스의 포이어바흐 테제 6번에서도

"인간적이라는 것은 (중략) 사회적 관계들의 총체Ensemble이다."

라고 언급되는 개념이니까요.

머신러닝에서 앙상블은 약한 분류기Weak Classifier 여러 개를 생성하고, 그 모델을 조합한 강한 분류기Strong Classifier로 더 나은 예측을 하는 기법입니다. 예컨대 Decision Tree 모델을 여러 개 조합한다면 한번의 tree 모델보다 더 나은 예측을 할 수 있을 겁니다.

앙상블Ensemble 기법은 크게 두 가지로 나뉩니다.

  1. 배깅Bagging:
    • Boostrap AGGregatING의 약자
    • 데이터의 샘플을 여러 번 나누어Bootstrap, 각 모델을 학습시킨 결과물을 모으는Aggregation 방법
    • 같은 유형의 알고리즘 기반 분류기 사용
    • 데이터 중복 허용
    • 이산/범주형 데이터: 다수결 투표 방식
    • 연속/실수형 데이터: 평균값 집계aggregation
    • 대표적 Bagging 모델: Random Forest

Bagging

  1. 부스팅Boosting:
    • 여러 분류기가 순차적으로 학습할 때 틀린 데이터에 가중치weight를 적용해 약한 분류기를 강한 분류기로 만드는 방법.

    • 가중치weight를 주는 것이 boost하는 것 같아서 이름 붙여짐

    • 장점: 성능

    • 단점: 느린 속도와 과적합 문제

    • 대표적 Boosting 모델: GBM, XGBoost, LightGBM, Adaboost

머신러닝 완벽가이드와 블로그1블로그2를 참고했습니다.

마지막으로 업데이트 된 날짜:
2022년 10월 31일