주제

1. 선형 SVM
2. 비선형 SVM
3. SVR
4. Decision Tree
5. SVM과 Decision Tree를 이용한 실습

 

선형 SVM

  • 두 데이터를 나누는 직선(고차원의 경우 초평면)을 찾는 것
  • 이때 직선과 평행한 두 개의 직선을 만들 수 있고, 이 두 직선 사이의 거리를 마진(margin)이라고 함
  • 서포트 벡터(support vector) : 마진을 구성하는 데이터(마진 선에 걸친 데이터들)

선형 SVM

  • SVM은 마진을 최대화 하는 최적 직선(최대 마진 초평면)을 만드는 것이 목적

최대 마진 초평면의 식

  • w : 최대 마진 초평면의 법선 벡터
  • b : 편향값

1.1 서포트 벡터(support vector)

  • 평행한 두 직선 위에 서포트 벡터가 존재함
  • 서포트 벡터는 아래의 식을 만족함

서포트 벡터

  • w^T + b ≥ 1 인 영역에서는 y = 1을, w^T + b ≤ 1인 영역에서는 y = -1을 만족한다
  • 마진은 점과 직선 사이의 거리 공식을 이용하여 다음과 같이 표현이 가능하다

마진 표현식
서포트 벡터

  • 최적화 목표 : 마진을 키우면서 모든 데이터를 알맞게 분류

최적화 식

 

 

1.2 하드 마진 SVM & 소프트 마진 SVM

  • 하드 마진 SVM : 어떠한 오분류도 허용하지 않고 완벽한 선형 모델로 분리가 가능한 것
  • 소프트 마진 SVM : 어느 정도의 오분류를 허용하면서 오차 발생에 따른 패널티를 비용 함수에 부과하여 일반화 성능을 올릴 수 있는 것

1.2.1 슬랙 변수(Slack variable)

  • 소프트 마진 SVM에서 사용하는 개념으로, 각 데이터 포인트(i) 당 하나의 슬랙 변수 ξ(크사이)가 할당된다
  • 슬랙 변수 : 해당 데이터 포인트가 마진을 얼마나 위반하는지 수치적으로 나타내는 변수
  • 마진을 위반하지 않은 데이터 : ξi = 0
    • 서포트 벡터, 서포트 벡터보다 멀리 있는 데이터
  • 마진을 위반한 데이터 :
    • 마진 경계 ~ 결정 경계 : 0 < ξi ≤ 1
    • 결정 경계 이후 : 1 < ξi  (올바르지 못한 클래스로 분류됨)

위치에 따른 슬랙 변수의 값

 

1.3 소프트 마진 SVM의 최적화 함수

  • 소프트 마진 SVM은 하드 마진 SVM 최적화 과정에 규제 패널티(ξi)를 도입하여 일반화한 식을 사용
  • 식 아래는 조건이다

소프트 마진 SV의 최적화 함수

  • 목적 : 마진의 크기를 최대화 & 마진 위반을 최소화
  • C : 일반화를 위한 하이퍼파라미터로 마진 크기와 규제 사이의 중요도 변수. 마진 크기와 규제 사이를 밸런싱
  • 1 - ξi : 규제가 적용될 데이터 포인트에 대해, 결정 경계에서 ξi의 거리만틈 벗어날 수 있음을 허용하는 과정
  • ξi ≥ 0 : ξi이 음수를 가질 수 없음을 조건으로 제시

 

실습

  • 선형 SVM을 위한 데이터 생성
# 데이터 생성
X1, y1 = make_classification(n_samples=100, # 생성할 데이터 수
                             n_features=2, # 사용할 특성의 수
                             n_redundant=0, # 중복 특성(다른 특성으로부터 파생된 특성) 수, 상관관계
                             n_clusters_per_class=1, # 클래스 당 클러스터의 수
                             flip_y=0, #  클래스 레이블이 뒤바뀔 확률, 노이즈에 해당
                             class_sep=2, # 클래스 간 분리도를 조절, 높을수록 분리가 잘 됨을 의미
                             random_state=5)

생성된 데이터 시각화

  • 하드 마진 SVM
  • 커널은 선형(linear), C는 1000으로 지정하여 학습
svm_hard_margin = SVC(kernel='linear', C=1000)
svm_hard_margin.fit(X1, y1)
  • 결과 시각화
plt.scatter(X1[:, 0], X1[:, 1], c=y1, cmap=plt.cm.Paired)

plt.xlabel('feature1')
plt.ylabel('featuer2')

plt.margins(0.2)

# 현재 그래프의 x축과 y축 범위를 가져옴
xlim = plt.gca().get_xlim()
ylim = plt.gca().get_ylim()

# 그래프의 x & y축 범위를 바탕으로 모든 x,y 조합의 좌표 그리드를 생성
xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], 50),
                     np.linspace(ylim[0], ylim[1], 50))

# 그리드 포인트를 이용해 각 포인트에서의 Desicion 결과값을 출력
Z = svm_hard_margin.decision_function(
    np.column_stack(
        (xx.ravel(), # xx matrix를 1차원 행렬로 flatten
        yy.ravel())  # yy matrix를 1차원 행렬로 flatten
     ) # 1 차원 행렬을 열 방향으로 묶어줌
) # 입력된 각 (x, y) 포인트에 대해 결정 경계로부터의 거리를 계산
Z = Z.reshape(xx.shape)

# 결정 경계와 마진 직선을 그리는 함수
plt.contour(xx, yy, # xx와 yy 공간 안에
            Z, # Z를 그릴건데
            levels=[-1, 0, 1], # Z가 -1, 0, 1 인 부분만 그릴것!
            colors=['orange', 'blue', 'orange'],
            alpha=0.5,
            linestyles=['--', '-', '--'] # -1, 0, 1 의 직선 개형을 표시
)

잘 분류된 것을 볼 수 있다

 

  • 소프트 마진 SVM
  • 노이즈가 섞인 데이터 생성
# 데이터 생성
X2, y2 = make_classification(n_samples=100, # 생성할 데이터 수
                             n_features=2, # 사용할 특성의 수
                             n_redundant=0, # 중복 특성(다른 특성으로부터 파생된 특성) 수
                             n_clusters_per_class=1, # 클래스 당 클러스터의 수
                             flip_y=0.06, #  클래스 레이블이 뒤바뀔 확률, 노이즈에 해당
                             class_sep=1.5, # 클래스 간 분리도를 조절, 높을수록 분리가 잘 됨을 의미
                             random_state=5)

소프트 마진 데이터

  • 커널은 선형으로 동일, 단 c의 값을 0.1로 부여
svm_soft_margin = SVC(kernel='linear', C=0.1)
svm_soft_margin.fit(X2, y2)
  • 결과를 위한 시각화 진행
plt.scatter(X2[:, 0], X2[:, 1], c=y2, cmap=plt.cm.Paired)

plt.xlabel('feature1')
plt.ylabel('featuer2')

plt.margins(0.2)

# 현재 그래프의 x축과 y축 범위를 가져옴
xlim = plt.gca().get_xlim()
ylim = plt.gca().get_ylim()

# 그래프의 x & y축 범위를 바탕으로 모든 x,y 조합의 좌표 그리드를 생성
xx, yy = np.meshgrid(np.linspace(xlim[0], xlim[1], 50),
                     np.linspace(ylim[0], ylim[1], 50))

# 그리드 포인트를 이용해 각 포인트에서의 Desicion 결과값을 출력
Z = svm_soft_margin.decision_function(
    np.column_stack(
        (xx.ravel(), # xx matrix를 1차원 행렬로 flatten
        yy.ravel())  # yy matrix를 1차원 행렬로 flatten
     ) # 1 차원 행렬을 열 방향으로 묶어줌
) # 입력된 각 (x, y) 포인트에 대해 결정 경계로부터의 거리를 계산
Z = Z.reshape(xx.shape)

# 결정 경계와 마진 직선을 그리는 함수
plt.contour(xx, yy, # xx와 yy 공간 안에
            Z, # Z를 그릴건데
            levels=[-1, 0, 1], # Z가 -1, 0, 1 인 부분만 그릴것!
            colors=['orange', 'blue', 'orange'],
            alpha=0.5,
            linestyles=['--', '-', '--'] # -1, 0, 1 의 직선 개형을 표시
)

소프트 마진의 결과

  • 다양한 c값에 따른 결과 확인
  • C값이 높을수록 더 적절한 직선을 찾는 것을 확인할 수 있다

 

 

 

2. 비선형 SVM

  • 데이터의 복잡성이 증가하면 선형 결정 경계로 데이터를 분류할 수 없는 경우가 있음(데이터가 휘어진 상태)
  • 따라서 비선형 SVM을 사용

비선형 데이터 분포

 

2.1 고차원 데이터

  • 선형적으로 분리할 수 없는 데이터를 고차원으로 변형하면 고차원의 초평면으로 분리할 수 있는 형태가 나옴
  • 어떻게?
    • 차원이 증가하면 데이터 포인트 간의 상대 거리 증가
    • 차원에서 데이터끼리 차지하는 공간 확징
    • 비슷한 특성을 공유하는 데이터들은 특정한 축 혹은 방향으로 군집될 가능성 증가
  • 고차원의 데이터일수록 선형으로 분류할 수 있는 가능성이 높다
  • 저차원의 데이터를 고차원의 데이터로 옮기는 과정의 함수 : Mapping function(ф)
  • 단점 :
    • 고차원의 데이터는 계산량이 늘어남
    • 확장한 고차원이 원본 데이터의 차원보다 훨씬 크다면 모델의 복잡도 ↑, 효율성 ↓
    • 이를 해결하기 위해 커널 트릭이 제시

 

2.1.1 커널 트릭(Kernel Trick)

  • 데이터를 선형 분류하기 위해서는 내적 계산이 필요 → 고차원에서도 필요
  • 내적 : 두 임의의 vector 사이의 유사도를 측정 & 선형 경계를 생성하는데 사용
  • 고차원에서의 내적 계산식

고차원 내적 계산식

  • 하지만, 계산량이 너무 많다는 단점이 있음
  • 커널 트릭 : 고차원의 내적 연산의 결과와 똑같은 결과를 보여주는 저차원 vector끼리의 연산 함수

커널 트릭 함수

  • 고차원으로 변형하지 않고, 저차원 데이터만으로 고차원 데이터의 내적 연산의 효과를 가짐
  • 커널의 종류 :
    • 다항 커널(Polynomial Kernel) : 다양한 차수 설정으로 여러 식을 근사할 수 있음. 과적합의 위험 존재 
    • RBF 커널(Radial Basis Function Kernel) 혹은 가우시안 커널(Gaussian Kernel) : 다양한 데이터에 적용하면서도 유연성이 높아 범용성이 높음
    • 시그모이드 커널(Sigmoid Kernel) : 이진 분류에 최적화되며 RBF 커널에 비해 성능이 떨어짐

다항 커널

 

RBF 커널

 

시그모이드 커널

 

2.2 비선형 SVM의 최적화

  • 소프트 마진과 비슷하지만 저차원이 아니라 고차원의 데이터 분류가 가능하도록 하는 조건이 들어감

비선형 SVM의 최적화 함수

  • 목적 : 마진의 크기를 최대화 & 마진 위반을 최소화
  • 아래 조건은 고차원 데이터에서 선형 분류가 가능함을 표시하는 조건

고차원 데이터에 대한 조건

 

실습

  • make_moons를 이용한 비선형 데이터 생성
from sklearn.datasets import make_moons

# 데이터 생성
X_moons, y_moons = make_moons(n_samples=100, noise=0.15, random_state=0)

# 시각화
plt.figure(figsize=(6, 5))
plt.scatter(X_moons[:, 0], X_moons[:, 1], c=y_moons, cmap=plt.cm.Paired)
plt.title("Moon Shaped Data")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.show()

비선형 더미 데이터 생성

 

  • 비선형 SVM으로 분류하고 다양한 커널을 시도
# 비선형 SVM 으로 분류

# 선형 커널
Linear_SVM = SVC(kernel='linear')
# 다항 커널
Poly_SVM = SVC(kernel='poly', degree=3)
# RBF 커널
RBF_SVM = SVC(kernel='rbf')
# Sigmoid 커널
sigmoid_SVM = SVC(kernel='sigmoid')

  • RBF 커널이 가장 잘 분류한 것을 확인할 수 있다

 

 

3. SVR(Support Vector Regression)

  • 회귀 문제에 적용되는 SVM : SVR
  • 주어진 데이터에서 가능한 많은 데이터 포인트를 포함하는 마진 구역을 설정
  • 마진 구역은 사용자가 선언한 허용 오차(ε) 내부의 구역
  • 그 구연 안에서 회귀선(초평면)을 찾는 것이 목표

SVR의 마진구역과 회귀선

 

3.1 SVR의 최적화

  • 커널 함수를 적용한 SVR의 최적화 함수는 다음과 같다

SVR의 최적화 함수

  • 정답과 예측값 사이의 차이가 ε 이라는 변수 안에서 허용될 수 있음을 내포함
  • 양쪽 방향의 슬랙 변수를 도입해 많은 데이터 포인트를 포괄하도록 강제

실습

  • 실습을 위한 데이터 생성
# 데이터 생성 변수
w0 = 2.3
w1 = 3.5
num_data = 100
noise = np.random.normal(0, 6, num_data)

# 데이터 생성
x = np.linspace(0, 10, num_data)
y = w0 + w1 * x + noise

생성된 데이터

  • epsilon을 5로 지정한 SVR 분류 실행(시각화 코드는 생략)
from sklearn.svm import SVR

epsilon = 5

# SVR 객체 생성
svr_linear = SVR(kernel='linear', epsilon=epsilon)
svr_poly = SVR(kernel='poly', degree=3, epsilon=epsilon)
svr_rbf = SVR(kernel='rbf', epsilon=epsilon)
svr_sigmoid = SVR(kernel='sigmoid', epsilon=epsilon)

# SVR 모델 학습
svr_linear.fit(x.reshape(-1, 1), y)
svr_poly.fit(x.reshape(-1, 1), y)
svr_rbf.fit(x.reshape(-1, 1), y)
svr_sigmoid.fit(x.reshape(-1, 1), y)

다양한 SVR 커널 학습 결과

  • 생성된 데이터가 선형 관계였기 때문에 선형 SVR이 가장 적합한 모델임을 알 수 있다.

 

 

4. Decision Tree

  • 데이터를 분할하는 기준을 결정하는데 사용되는 방법론
  • 트리의 각 단계에서 최적의 분할을 찾기 위해 사용
  • 트리의 깊이와 복잡성을 관리할 수 있음
  • 좋은 결정 기준은 트리를 간결하고 효율적으로 만들며 과적합을 방지하고 일반화 성능을 향상시킴
  • 용어 :
    • 노드(Node) : 데이터에 대한 특정 질문이나 조건. 데이터를 분류하는 과정에서 사용
    • 엣지(Edge) : 노드와 노드를 연결하는 선. 상위 노드의 특정 질문에 대한 가능한 답변
    • 루트 노드(Root node) : 트리의 가장 상단에 위치한 노드. 분류 또는 예측을 시작한는 지점
    • 분할 노드(=결정 노드) : 데이터를 더 작은 하위 집합으로 나누는 데 사용되는 중간 노드
    • 리프 노드(=터미널 노드) : 트리의 말단에 위치한 노드. 더 이상의 분기가 없고 자식 노드를 갖지 않음

결정 트리의 예시

  • 분류에서 사용되는 결정 기준 :
    • 정보 이득
    • 지니 불순도
  • 회귀에서 사용되는 결정 기준 :
    • MSE 최소화

 

4.1 엔트로피(Entropy)

  • 엔트로피 : 어떤 상황이나 현상이 품고 있는 불확실성을 의미, 포함하는 정보의 양과 반비례
  • 엔트로피 ↑, 불확실성 ↑, 정보량 ↓
  • 엔트로피의 수식은 다음과 같다

엔트로피의 식

  • P(xi) : 각 사건의 발생 확률. 불확실성이 높은 상황일수록 다양한 사건의 발생 확률이 균일
  • log2P(xi) : 이진 로그를 사용해 비트 단위의 정보량을 표현
  • -∑ : 발생할 각 사건을 모두 더한 뒤, 로그값을 양수로 만들기 위해 -를 취함

 

4.2 정보 이득(Information Gain)

  • 부모 노드와 자식 노드들의 엔트로피를 계산해 엔트로피가 낮아지는 방향으로 결정 경계를 선정하는 것
  • 정보 이득을 최대화하는 방향 = 엔트로피가 낮아지는 방향

정보 이득의 식

  • 각 노드에 포함되는 데이터의 순도에 따라 엔트로피가 계산됨(엔트로피가 클수록 고르게 분포, 순도가 낮음)
  • 노드 안에 서로 다른 클래스의 데이터가 많이 섞여 있으면 순도 ↓
  • 같은 클래스의 데이터가 모여있다면 순도 ↑

 

4.3 지니 불순도(Gini Impurity)

  • 데이터 안에 존재하는 클래스 분포의 불균형을 평가하는 방법(집합의 순도를 측정)

지니 불순도

  • Pi : 데이터 집합 안에 존재하는 i 번째 클래스가 나타나는 확률
  • 0 이상 1 미만의 값을 가짐
    • 0 : 모든 데이터가 하나의 클래스에 속함. 제일 순도가 높은 상태
    • 1에 가까운 값 : 모든 클래스의 데이터가 고루 섞인 상태로 불순도가 제일 높음

 

4.4 회귀 문제를 위한 Decision Tree

 

4.4.1 MSE 최소화 방식

  • 각 노드에서 실제 정답과 예측값 사이의 평균 제곱 오차의 평균을 계산하고 이 값을 최소화하는 노드를 찾아가는 방식

 

4.5 Tree 사용의 주의사항

  • 트리 구조의 장점 :
    • 해성 용이성 & 사용 편리
    • 적당한 성능
    • 스케일링에 둔감함
  • 주의점 :
    • 트리 기반 모델은 축에 수직인 방향으로 데이터가 분할됨
      • 경계면이 회전된 데이터는 경계면이 구불해짐
      • 일반화가 어려워짐
      • 필요 시 주성분 분석을 사용
    • 데이터 노이즈에 굉장히 민감
      • 특정 데이터의 추가가 전체 모델 결과에 큰 변화를 줄 수 있음
    • Depth가 깊다면 강한 overfit의 위험이 큼

 

 

5. SVM과 Decision Tree를 이용한 실습

  • 어제 사용한 비행 경헙 만족도 데이터를 그대로 사용
  • 전처리는 동일하게 진행

SVM 학습

  • 가장 좋은 성능을 보였던 RBF 커널을 이용해 학습
  • SVM 학습의 학습 시간은 약 30분 정도 소요
from sklearn.svm import SVC

svm = SVC(kernel='rbf', C=0.1)
svm.fit(X_train, y_train)
# 예측 수행
y_train_pred_svm = svm.predict(X_train)
y_test_pred_svm = svm.predict(X_test)

# 평가 지표 계산: 정확도 (맞은수/전체)
acc_train = accuracy_score(y_train, y_train_pred_svm)
acc_test = accuracy_score(y_test, y_test_pred_svm)

print(f'학습 데이터를 이용한 SVM Acc 값 : {acc_train*100:.1f}%')
print(f'평가 데이터를 이용한 SVM Acc 값 : {acc_test*100:.1f}%')

SVM을 이용한 정확도

  • 어제 실행한 로지스틱 회귀를 이용한 정확도는 각각 82.8%와 82.9%로 SVM이 더 높은 정확도를 보인다.
  • 분류 평가 척도 :
정밀도(precision) 재현율(recall) F1 점수
예측한 양성 결과가 실제로 얼마나 진짜 양성인지를 계산 실제 양성 중 얼마나 양성을 잘 찾아냈는지를 계산 정밀도와 재현율의 조화 평균
모델이 양성 결과를 잘 찾아내야 하는 상황에서 중요 정답을 잘 찾아내는 과정에서 중요 조화 평균을 사용해 낮은 점수에 대한 패널티를 늘림.
정밀도와 재현율이 전반적으로 좋아야 좋은 F1 값을 가질 수 있음
# 정밀도, 재현율, F1 값 비교
from sklearn.metrics import precision_score, recall_score, f1_score

logistic_precision = precision_score(y_test, y_test_pred_logis)
logistic_recall = recall_score(y_test, y_test_pred_logis)
logistic_f1 = f1_score(y_test, y_test_pred_logis)
print(f'Logistic의 P,R,F1 : {logistic_precision:.2f} / {logistic_recall:.2f} / {logistic_f1:.2f}')


svm_precision = precision_score(y_test, y_test_pred_svm)
svm_recall = recall_score(y_test, y_test_pred_svm)
svm_f1 = f1_score(y_test, y_test_pred_svm)
print(f'SVM의 P,R,F1 : {svm_precision:.2f} / {svm_recall:.2f} / {svm_f1:.2f}')

로지스틱 회귀와 SVM의 평가 점수

 

 

Decision Tree 학습

  • 엔트로피 결정 경계를 사용하는 최대 깊이 5의 트리를 생성
  • Decision Tree는 SVM에 비해 학습 시간이 짧음
from sklearn.tree import DecisionTreeClassifier

dt = DecisionTreeClassifier(criterion='entropy',
                            max_depth=5,
                            min_samples_split=5)
dt.fit(X_train, y_train)
  • 트리가 잘 생성이 되었는지 시각화로 확인
from sklearn.tree import plot_tree

plt.figure(figsize=(100, 20))
plot_tree(dt, filled=True,
          feature_names=ext_ordinal_columns + ext_category_columns,
          class_names=['satisfied', 'unsatisfied'])
plt.title("Plot of Decision Tree")
plt.show()

결정 트리가 잘 생성된 모습

 

최고의 모델 찾기

  • 머신 러닝 모델은 크기가 커지고 복잡도가 증가할수록 성능은 올라간다
  • 하지만 과적합 문제가 발생하면 성능이 하락한다
  • 따라서 평가 데이터에 대한 성능이 낮아지기 시작하는 지점의 세팅을 이용해 최적의 모델을 선택해야 한다
  • max depth에 따른 성능 비교(시각화 부분은 편의상 생략)
# max depth에 따른 학습 결과 경향성 파악
max_depths = range(3, 30)

train_accuracies = []
test_accuracies = []

for depth in max_depths:
    model = DecisionTreeClassifier(criterion='entropy',
                                   max_depth=depth,
                                   min_samples_split=5)
    model.fit(X_train, y_train)

    # 학습 데이터에 대한 정확도
    y_train_pred = model.predict(X_train)
    train_acc = accuracy_score(y_train, y_train_pred)
    train_accuracies.append(train_acc)

    # 평가 데이터에 대한 정확도
    y_test_pred = model.predict(X_test)
    test_acc = accuracy_score(y_test, y_test_pred)
    test_accuracies.append(test_acc)

depth가 커질수록 모델의 정확도 확인

  • depth가 커질수록 테스트 성능은 떨어지는 것을 확인할 수 있다
  • 더 높은 정확도의 모델을 만들기 위해서는 최대 정확도를 만드는 depth를 이용하면 된다
# 최대 정확도를 달성하는 max_depth를 찾고 해당 depth로 최적 모델 학습
max_acc = max(test_accuracies)
best_depth = max_depths[test_accuracies.index(max_acc)]
print('최대 정확도의 depth :', best_depth)

dt = DecisionTreeClassifier(criterion='entropy',
                            max_depth=best_depth,
                            min_samples_split=5)
  • 이 데이터의 경우에는 최적의 depth는 16이므로 max_depth를 16으로 바꿔서 다시 학습해보면 정확도는 다음과 같이 상승한다
# 예측 수행
y_train_pred_dt = dt.predict(X_train)
y_test_pred_dt = dt.predict(X_test)

# 평가 지표 계산: 정확도 (맞은수/전체)
acc_train = accuracy_score(y_train, y_train_pred_dt)
acc_test = accuracy_score(y_test, y_test_pred_dt)

print(f'학습 데이터를 이용한 DT Acc 값 : {acc_train*100:.1f}%')
print(f'평가 데이터를 이용한 DT Acc 값 : {acc_test*100:.1f}%')

최적의 depth로 학습한 모델의 정확도

  • 다른 평가 척도도 확인

세가지 방법의 평가 점수

  • 이에 따르면 최적의 depth로 수정한 Decision Tree 모델의 성능이 무려 93.0%까지 상승한 것을 볼 수있다.
  • 마찬가지로 정밀도, 재현율, F1 점수도 전부 올랐다.
  • 이런 과정을 통하여 최고의 모델을 찾는 것이다.

+ Recent posts