Support Vector Machine(SVM) - 01

Support Vector Machine(SVM)

  • 데이터의 분포가 정규분포를 띈다고 보이지 않는다면 이전에 말했던 LDA나 QDA를 사용하기 힘들다. 이런 경우 클래스간 분류를 하려고 할 때 사용될 수 있는 방법 중 하나가 SVM이다. 아래 그림과 같이 클래스 집단을 분류할 수 있는 벡터를 기준으로 최대한의 마진을 주어 분류하는 방식이다.

Support Vector Machine의 배경 - 01

  • 아래 그림과 같이 두 클래스 집단간의 데이터 분포가 혼용되어있다면 어떤 방식으로 접근해야 할까? 두 클래스 집단간의 거리를 최대화하면서 혼용되어있는 데이터들에 대한 error를 적당히 허용하는 선에서 decision boundary를 결정해야 할 것이다.

Support Vector Machine의 배경 - 02

  • SVM은 regression 문제도 사용하지만 보통은 범주형 변수에 대한 classification 문제에 많이 사용한다. Support vector regression(SVR)은 최대한 많은 데이터를 margin 안에 포함하고자 하는 것이다. 이에 대해 margin 바깥에 존해하는 데이터에 대해 error를 줄어서 그 error를 최소화하는 방향으로 회귀를 진행한다. 일반적인 Regression은 해당 데이터를 설명할 수 있는 선에 대해 error를 계산하는 방식이라면, SVR은 margin 바깥에 존재하는 데이터들에 대해서만 error를 계산하는 방식이다.

Support Vector Machine의 배경 - 03

Support Vector Machine의 배경 - 04

  • 초평면에 부등호를 도입하면 다음과 같이 영역으로 데이터를 구분지을 수 있게 된다.

Support Vector Machine의 Decision boundary - 01

Support Vector Machine의 Decision boundary - 02

Support Vector Machine의 Decision boundary - 03

나그랑주 승수(Lagrange multiplier)

  • 최적화 문제(예를 들어서 극대값이나 극소값)를 푸는데 특정조건하에서 문제를 풀 수 있도록 하는 방법이다. 아래 그림에서 보면, 아무런 제한이 없었다면 x와 y의 값에 따라 $-\infty$에서 $\infty$로 움직일 수 있을 것이다. 허나, $g(x,y)=c$라는 함수 범위 내에서만 움직일 수 있다고 제한을 주면 해당 제한영역하에서의 최적화를 풀어야할 것이다.

Lagrange multiplier - 01

Lagrange multiplier - 02

Lagrange multiplier - 03

  • 위에서 언급하는 최적화 문제를 수학적으로 살펴보려면, 아래와 같이 최적화문제에 대한 설명이 필요하다.

제한조건이 있는 최적화 문제

  • 제한조건(constraint)을 가지는 최적화 문제를 풀어본다. 제한 조건은 연립방적식 또는 연립부등식이다. 연립방정식 제한조건이 있는 경우에는 라그랑주 승수법을 사용하여 새로운 최적화 문제를 풀어야 한다. 연립부등식 제한조건의 경우에는 KKT조건이라는 것을 만족하도록 하는 복잡한 과정을 거쳐야 한다.

등식 제한조건이 있는 최적화 문제

  • 현실의 최적화 문제에서는 여러가지 제한조건이 있는 최적화(constrained optimization) 문제가 많다. 가장 간단한 경우는 다음과 같이 연립방정식 제한조건이 있는 경우다. 등식(equality)제한 조건이라고도 한다.
  • 첫 번째 식만 보면 단순히 목적함수 $f(x)$를 가장 작게 하는 N차원 벡터 x값을 찾는 문제다. 하지만 마지막 식에 있는 M개의 등식 제한 조건이 있으면 M개 연립 방정식을 동시에 모두 만족시키면서 목적함수 $f(x)$를 가장 작게하는 $x$값을 찾아야 한다.
예제

목적 함수 $f$와 등식 제한조건 $g$가 다음과 같은 경우를 생각하자. 이 문제는 다음 그림 처럼 $g(x_{1}, x_{2}) = 0$으로 정의되는 직선상에서 가장 $f(x_{1},x_{2})$값이 작아지는 점 $(x_1^{\ast}, x_2^{\ast})$을 찾는 문제가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 목적함수 f(x) = x1^2 + x2^2
def f1(x1, x2):
return x1 ** 2 + x2 ** 2

x1 = np.linspace(-5, 5, 100)
x2 = np.linspace(-3, 3, 100)
X1, X2 = np.meshgrid(x1, x2)
Y = f1(X1, X2)

# 등식 제한조건 방정식 g(x) = x1 + x2 - 1 = 0
x2_g = 1 - x1

plt.contour(X1, X2, Y, colors="gray", levels=[0.5, 2, 8, 32])
plt.plot(x1, x2_g, 'g-')

plt.plot([0], [0], 'rP')
plt.plot([0.5], [0.5], 'ro', ms=10)

plt.xlim(-5, 5)
plt.ylim(-3, 3)
plt.xticks(np.linspace(-4, 4, 9))
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
plt.title("등식 제한조건이 있는 최적화 문제")
plt.show()

최적화 문제 예시

라그랑주 승수법

  • 이렇게 등식 제한조건이 있는 최적화 문제는 라그랑주 승수법(Lagrange multiplier)을 사용하여 최적화할 수 있다. 라그랑주 승수 방법에서는 목적함수를 원래의 목적함수 $f(x)$를 사용하지 않는다. 대신 제한조건 등식에 $\lambda$라는 새로운 변수를 곱해서 더한 함수를 목적함수로 간주하여 최적화한다.
  • 이때 제한조건 등식 하나마다 새로운 $\lambda_{i}$를 추가해주어야 한다. 따라서 만약 제한조건이 $M$개이면 $\lambda_{1}, \cdots, \lambda_{M}$개의 변수가 새로 생긴 것과 같다. 이렇게 확장된 목적함수 $h$는 입력변수가 더 늘어났기 때문에 그레디언트 벡터를 영벡터로 만드는 최적화 필요 조건이 다음처럼 $N\;+\;M$개가 된다.
  • 이 $N\;+\;M$개의 연립 방정식을 풀면 $N\;+\;M$개의 미지수를 구할 수 있다.
  • 구한 결과에서 찾는 최소값 $x$를 구할 수 있다. 라그랑주 승수값은 필요없다.

예제) 위에서 제시한 예제를 라그랑주 승수법으로 풀어보자. 새로운 목적함수는 다음과 같다.

  • 라그랑주 승수법을 적용하여 그레디언트 벡터가 영벡터인 위치를 구한다.
  • 방정식의 해는 다음과 같다.

연습문제 제한조건이 $x_{1}+x_{2}\; = \; 1$일 때 목적 함수 $f(x) = - log x_{1} - log x_{2} x_{1},x_{2} > 0$ 을 최소화하는 x_{1}, x_{2}값을 라그랑주 승수법으로 계산하라.

  • 위의 문제에서 목저함수는 아래와 같다.
  • 라그랑주 승수법은 적용하여 그레디언트 벡터가 영벡터인 위치를 구한다.
  • 위 방정식을 풀면 해는 다음과 같다.
scipy를 사용하여 등식 제한조건이 있는 최적화 문제 계산하기
  • scipy의 optimize 서브패키지는 제한조건이 있는 최적화 문제를 푸는 fmin_slsqp()명령을 제공한다. 목적함수와 초기값, 그리고 제한조건 함수의 리스트를 인수로 받는다. 목적함수는 배열인 인수를 받도록 구현되어야 하고 제한조건 함수의 경우에는 항상 eqcons인수를 명시해야 한다.
1
fmin_slsqp(func_objective, x0, eqcons=[func_constraint1, func_constraint2])
  • 위에서의 두 문제를 scipy를 통해서 풀어보겠다.
1
2
3
4
5
6
7
def f1array(x):
return x[0] ** 2 + x[1] ** 2

def eq_constraint(x):
return x[0] + x[1] - 1

scipy.optimize.fmin_slsqp(f1array, np.array([1, 1]), eqcons=[eq_constraint])
결과
1
2
3
4
5
6
Optimization terminated successfully.    (Exit mode 0)
Current function value: 0.5000000000000002
Iterations: 2
Function evaluations: 8
Gradient evaluations: 2
array([0.5, 0.5])
1
2
3
4
5
6
7
def f1array(x):
return -np.log(x[0]) -np.log(x[1])

def eq_constraint(x):
return x[0] + x[1] - 1

scipy.optimize.fmin_slsqp(f1array, np.array([1, 1]), eqcons=[eq_constraint])
결과
1
2
3
4
5
6
Optimization terminated successfully.    (Exit mode 0)
Current function value: 1.3862943611198901
Iterations: 2
Function evaluations: 8
Gradient evaluations: 2
array([0.5, 0.5])

라그랑주 승수의 의미

  • 만약 최적화 문제에서 등식 제한조건 $g_{i}가 있는가 없는가에 따라 해의 값이 달라진다면 이 등식 제한조건에 대응하는 라그랑주 승수 $\lambda_{i}$는 0이 아닌 값이어야 한다. $\lambda_{i} = 0$일 때만 원래의 문제와 제한조건이 있는 문제의 최적화 조건이 같아지므로 최적화 해의 위치도 같게 나오기 때문이다.

예제

  • 목적함수가 아래와 같은 최소화 문제의 답은 $x_{1} = x_{2} = 0$이다.
  • 여기에 다음 제한 조건이 있다고 하자.
  • 라그랑주 승수법에서 새로운 목적함수는 아래와 같다.
  • 이에 따른 최적화 조건은 다음과 같다.
  • 이에 대한 해는 $x_{1} = x_{2} = \lambda = 0$으로 제한조건이 있으나 없으나 해는 동일하며, 라그랑주 승수는 0이 된다. 즉, 제한조건이 의미가 없는 경우는 라그랑주 승수가 0이된다는 의미이다.

부등식 제한조건이 있는 최적화 문제

  • 이번에는 다음과 같이 부등식(inequality) 제한조건이 있는 최적화 문제를 생각하자.
  • 만약 부등식이 $g_j(x) \geq 0$과 같다면 양변에 $-1$을 곱하여 부등호의 방향을 바꾼다. 이렇게 부등식 제한조건이 있는 최적화 문제도 라그랑주 승수 방법과 목적함수를 다음처럼 바꾸어 푼다. 이렇게 부등식 제한 조건이 있는 최적화 문제도 라그랑주 승수 방법과 목적함수를 다음처럼 바꾸어 푼다.
다만, 이 경우 최적화 해의 필요조건은 방정식 제한조건이 있는 최적화 문제와 다르게 KKT(Karush-Kuhn-Tucker)조건이라고 하며 다음처럼 3개의 조건으로 이루어진다.
  • 1) 모든 독립변수 $x_{1}, x_{2}, \ldots, \x_{N}$에 대한 미분값이 0이다.
    • 첫 번째 조건은 방정식 제한조건의 경우와 같다. 다만 변수 $x$들에 대한 미분값만 0이어야 한다. 라그랑주 승수 $\lambda$에 대한 미분은 0이 아니어도 된다.
  • 2) 모든 라그랑주 승수 $\lambda_{1}, \lambda_{2}, \ldots, \lambda_{M}$과 제한조건 부등식($\lambda$에 대한 미분값)의 곱이 0이다.
    • 두 번째 조건을 보면 확장된 목적함수를 나그랑주 승수로 미분한 값은 변수 $x$들에 대한 미분값과는 달리 반드시 0이 될 필요는 없다는 것을 알 수 있다. 이렇게 하려면 두 경우가 가능한데 등식 제한조건의 경우처럼 라그랑주 승수 $\lambda$에 대한 미분값이 0이어도 되고 아니면 라그랑주 승수 $\lambda$값 자체가 0이 되어도 된다.
  • 3) 라그랑주 승수는 음수가 아니어야 한다.
    • 마지막 조건은 KKT 조건이 실제로 부등식 제한조건이 있는 최적화 문제와 같은 문제임을 보장하는 조건이다.

예제

  • 부등식 제한조건을 가지는 최적화의 예를 풀어보자. 목적함수는 아래와 같다.
  • 이 예제에서 두 가지 제한 조건을 고려해 볼 텐데 하나는 다음 그림 중 왼쪽 그림처럼 부등식 제한조건이 아래와 같은 경우이다.
  • 다른 하나의 제한조건은 아래와 같고 이에 대한 그림은 오른쪽에 해당한다.
  • 아래 그림에서 제한조건을 만족하는 영역을 어둡게 표시했다. 최적점의 위치는 점으로 표시했다. 첫 번째 제한조건의 경우에는 부등식 제한조건이 있기는 하지만 원래의 최적화 문제의 해가 부등식 제한조건이 제시하는 영역 안에 있기 때문에 최적점의 위치가 달라지지 않는다. 두 번째 제한조건의 경우에는 원래의 최적화 문제의 해가 부등식 제한조건이 제시하는 영역 바깥에 있기 때문에 최적점의 위치가 달라졌다. 하지만 최적점의 위치가 영역의 경계선(boundary line)에 있다는 점에 주의하라.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
plt.figure(figsize=(13, 7))
ax1 = plt.subplot(121)
plt.contour(X1, X2, Y, colors="gray", levels=[0.5, 2, 8])
plt.plot(x1, x2_g, 'g-')
ax1.fill_between(x1, -20, x2_g, alpha=0.5)
plt.plot([0], [0], 'ro', ms=10)
plt.xlim(-3, 3)
plt.ylim(-5, 5)
plt.xticks(np.linspace(-4, 4, 9))
plt.yticks(np.linspace(-5, 5, 11))
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
plt.title("최적해가 부등식과 관계없는 경우")
ax2 = plt.subplot(122)
plt.contour(X1, X2, Y, colors="gray", levels=[0.5, 2, 8])
plt.plot(x1, x2_g, 'g-')
ax2.fill_between(x1, 20, x2_g, alpha=0.5)
plt.plot([0.5], [0.5], 'ro', ms=10)
plt.xlabel("x_1")
plt.xlim(-3, 3)
plt.ylim(-5, 5)
plt.xticks(np.linspace(-4, 4, 9))
plt.yticks(np.linspace(-5, 5, 11))
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
plt.title("최적해가 부등식에 의해 결정되는 경우")
plt.suptitle("부등식 제한조건이 있는 최적화 문제")
plt.show()

부등식 제한조건이 있는 최적화 문제

  • 그림에서 보듯이 부등식 제한조건이 있는 최적화 문제를 풀면 그 제한조건은 다음 두 가지 경우의 하나가 되어 버린다.

    • 최적화 결과에 전혀 영향을 주지 않는 쓸모없는 제한조건
    • 최적화 결과에 영향을 주는 등식(equality)인 제한조건
  • 어느 경우이든 부등식 제한조건 문제로 시작했지만 결과는 제한조건이 없거나 등식 제한조건 문제를 푸는 것과 같아진다. KKT조건 중 두 번째 조건이 뜻하는 바는 다음과 같다. 다음 식에서 $x^{\ast}, \lambda^{\ast}$는 KKT 조건을 풀어서 구한 최적해의 값이다.

  • 만약 $g_{i} = 0$이면 이 조건은 부등식 제한조건이 아닌 등식 제한조건이 된다. 그리고 등식 제한조건에서 말한 바와 같이 (이 제한조건이 있으나 없으나 해가 바뀌지 않는 특수한 경우를 제외하면) 라그랑주 승수는 0이 아닌값을 가진다.
  • 반대로 $g_i \neq 0 \; (g_i < 0)$이면 해가 $g_{i}$가 표현하는 곡선으로부터 떨어져 있기 때문에 부등식 제한조건이 아무런 의미가 없어진다. 즉, 제한조건이 있을 때와 없을 때의 해가 같다. 따라서 목적함수 $h(x,\lambda)$는 $\lambda_{i}g_{i}(g_{i} \neq 0)$항이 있으나 없으나 상관없이 같은 해를 가진다. 따라서 $\lambda_{i} = 0$이 된다.
  • 따라서 부등식 제한조건이 있는 최적화 문제는 각 제한조건에 대해 위의 두 가지 경우를 가정하여 각각 풀어보면서 최적의 답을 찾는다.

예제) 다음은 복수의 부등식 제한조건이 있는 또다른 2차원 최적화 문제의 예이다.

  • 위의 4가지 제한조건은 다음과 같은 하나의 부등식으로 나타낼 수도 있다.
  • 아래 예제에서 최적해가 $x_{1}\;=\;1, x_{2}\; = \;0$이라는 사실을 이용하여 라그랑주 승수 $\lambda_{1}, \lambda_{2}, \lambda_{3}, \lambda_{4}$ 중 어느 값이 0이 되는지 말해보자.

    • 이에 대한 답은 $\lambda_{2} = \lambda_{3} = 0$이 될 것이다. 해를 찾는데 아무런 영향을 미치지 않기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def f2plt(x1, x2):
return np.sqrt((x1 - 4) ** 2 + (x2 - 2) ** 2)


x1 = np.linspace(-2, 5, 100)
x2 = np.linspace(-1.5, 3, 100)
X1, X2 = np.meshgrid(x1, x2)
Y = f2plt(X1, X2)

plt.contour(X1, X2, Y, colors="gray",
levels=np.arange(0.5, 5, 0.5) * np.sqrt(2))

# 제한 조건의 상수
k = 1
ax = plt.gca()
x12 = np.linspace(-k, 0, 10)
x13 = np.linspace(0, k, 10)
ax.fill_between(x12, x12 + k, -k - x12, color='g', alpha=0.5)
ax.fill_between(x13, x13 - k, k - x13, color='g', alpha=0.5)

# 최적점 위치
x1_sol = 1
x2_sol = 0
plt.plot(x1_sol, x2_sol, 'ro', ms=20)

plt.xlim(-2, 5)
plt.ylim(-1.5, 3)
plt.xticks(np.linspace(-2, 5, 8))
plt.yticks(np.linspace(-1, 3, 5))
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")
plt.title("$|x_1| + |x_2| \leq {}$ 제한조건을 가지는 최적화 문제".format(k))
plt.show()

부등식 최적화 문제

Scipy를 사용하여 부등식 제한조건이 있는 최적화 문제 계산하기

  • fmin_slsqp()명령은 이렇게 부등식 제한조건이 있는 경우에도 사용할 수 있다. 제한조건 인수의 이름이 ieqcons로 달라졌다. 단, ieqcons 인수에 들어가는 부등호는 우리가 지금까지 사용한 방식과 달리 0 또는 양수이어야 한다.
1
fmin_slsqp(func_objective, x0, ieqcons=[func_constraint1, func_constraint2])
  • 이렇듯, fmin_slsqp() 명령은 등식 제한조건과 부등식 제한조건을 동시에 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
def f2(x):
return np.sqrt((x[0] - 4) ** 2 + (x[1] - 2) ** 2)

# 제한 조건 상수
k = 1
def ieq_constraint(x):
return np.atleast_1d(k - np.sum(np.abs(x)))


sp.optimize.fmin_slsqp(f2, np.array([0, 0]), ieqcons=[ieq_constraint])
결과
1
2
3
4
5
6
7
Optimization terminated successfully.    (Exit mode 0)
Current function value: 3.6055512804550336
Iterations: 11
Function evaluations: 77
Gradient evaluations: 11

array([9.99999982e-01, 1.79954011e-08])

서포트 벡터 머신

  • 변수가 1개있다면, $x^T$는 N차원을 갖는 헹벡터라면, $\beta$도 같은 차원을 갖는 열벡터로서 내적을 통해 그에 따른 벡터의 영역이 초평면(hyperplane)을 이루게 될 것이다.

SVM 정의

  • 퍼셉트론은 가장 단순하고 빠른 판별 함수 기반 분류 모형이지만 판별 경계선(decision hyperplane)이 유니크하게 존재하지 않는다는 특징이 있다. 서포트 벡터 머신(SVM: Support vector machine)은 퍼셉트론 기반의 모형에 가장 안정적인 판별 경계선을 찾기 위한 제한 조건을 추가한 모형이라고 볼 수 있다.

SVM의 decision boundary

서포트와 마진

  • 다음과 같이 $N$개의 학습용 데이터가 있다고 하자.
  • 판별함수 모형에서 $y$는 $+1,\; -1$ 두 개의 값을 가진다.
  • $x$ 데이터 중에서 $y$값이 $+1$인 데이터를 $x_{+}$, $y$값이 $-1$인 데이터를 $x_{-}$라고 하자. 판별함수 모형에서 직선인 판별 함수 $f(x)$는 다음과 같은 수식으로 나타낼 수 있다.
  • 혹시라도 판별함수의 직선의 방정식이 어떻게 나온건지 이해가 안가시는 분들에게 설명을 드리고자 잠깐 선형대수의 직선의 방정식에 관한 설명을 하도록 하겠다.

직선의 방정식

  • 어떤 벡터 $w$가 있을 때

    • 원점에서 출발한 벡터 $w$가 가리키는 점을 지나면서
    • 벡터 $w$에 수직인
  • 직선의 방정식을 구해보자.

  • 위 두 조건을 만족하는 직선상의 임의의 점을 가리키는 벡터를 $x$라고 하면, 벡터 $x$가 가리키는 점과 벡터 $w$가 가리키는 점을 이은 벡터 $x - w$는 조건에 따라 벡터 $w$와 직교해야 한다. 따라서 다음 식이 성립한다.
  • 정리하면 다음과 같아진다.
  • 이 직선과 원점 사이의 거리는 벡터 $w$의 norm $|w|$이다.

연습문제) 만약 $v$가 원점을 지나는 직선의 방향을 나타내는 단위벡터라고 하자. 이때 그 직선 위에 있지 않는 어떤 점 $x$와 그 직선과의 거리의 제곱이 다음과 같음을 증명하라.

  • $ x \; - \; v \perp v$이기 때문에

  • $a^{\Vert b} = | x | cos \theta = \frac{| v | | x | cos \theta}{| v |} = \frac{x^{T} v}{| v |}$

  • 벡터 $v$는 단위벡터이므로 $a^{\Vert b} = x^{T}v$가 된다. 여기서 피타고라스 정리를 사용하면 우리가 증명해야 하는 식을 구할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v = np.array([2, 1]) / np.sqrt(5)
x = np.array([1, 3])
plt.plot(0, 0, 'kP', ms=10)
plt.annotate('', xy=v, xytext=(0, 0), arrowprops=black)
plt.plot([-2, 8], [-1, 4], 'b--', lw=2)
plt.plot([1, 2], [3, 1], 'g:', lw=2)
plt.plot(x[0], x[1], 'ro', ms=10)
plt.text(0.1, 0.5, "$v$")
plt.text(0.6, 3.2, "$x$")
plt.xticks(np.arange(-3, 15))
plt.yticks(np.arange(-1, 5))
plt.xlim(-3, 7)
plt.ylim(-1, 5)
plt.show()

벡터 v 의 스칼라배한 직선위에 존재하지 않는 점과의 거리

  • 예를 들어 아래와 같을 때
  • 이 방정식은 벡터 $w$가 가리키는 점 (1,2)를 지나면서 벡터 $w$에 수직인 직선을 뜻한다. 이 직선과 원점 사이의 거리는 $ |w|=\sqrt{5} $이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
w = np.array([1, 2])
x1 = np.array([3, 1])
x2 = np.array([-1, 3])
plt.annotate('', xy=w, xytext=(0, 0), arrowprops=black)
plt.annotate('', xy=x1, xytext=(0, 0), arrowprops=green)
plt.annotate('', xy=x2, xytext=(0, 0), arrowprops=green)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(w[0], w[1], 'ro', ms=10)
plt.plot(x1[0], x1[1], 'ro', ms=10)
plt.plot(x2[0], x2[1], 'ro', ms=10)
plt.plot([-3, 5], [4, 0], 'r-', lw=5)
plt.text(-0.2, 1.5, "벡터 $w$")
plt.text(1.55, 0.25, "$x_1$")
plt.text(-0.9, 1.40, "$x_2$")
plt.text(1.8, 1.8, "$x_1 - w$")
plt.text(-0.2, 2.8, "$x_2 - w$")
plt.text(3.6, 0.8, "직선 $x$")
plt.xticks(np.arange(-2, 5))
plt.yticks(np.arange(-1, 5))
plt.xlim(-2, 5)
plt.ylim(-0.6, 3.6)
plt.show()

원점에서 출발하는 벡터가 가리키는 점을 지나는 수직인 직선과의 거리

  • 이번에는 벡터 $w$가 가리키는 점을 지나야 한다는 조건을 없애고 단순히
    • 벡터 $w$에 수직인
  • 직선 $x$의 방정식을 구하면 이때는 직선이 $w$가 아니라 $w$와 방향이 같고 길이가 다른 벡터 $w’=cw$을 지날 것이다. c는 양의 실수이다. 위에서 했던 방법으로 다시 직선의 방정식을 다음과 같다.
  • 여기에서 $c | w |^2$는 임의의 수가 될 수 있으므로 단순히 벡터 $w$에 수직인 직선의 방정식은 다음과 같이 나타낼 수 있다.
  • 이 직선과 원점 사이의 거리는 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
w = np.array([1, 2])
plt.annotate('', xy=w, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=0.5 * w, xytext=(0, 0), arrowprops=black)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(0.5 * w[0], 0.5 * w[1], 'ro', ms=10)
plt.plot([-2, 5], [2.25, -1.25], 'r-', lw=5)
plt.text(-0.7, 0.8, "벡터 $cw$")
plt.text(-0.1, 1.6, "벡터 $w$")
plt.text(1, 1, "직선 $x$")
plt.xticks(np.arange(-2, 5))
plt.yticks(np.arange(-1, 5))
plt.xlim(-2, 5)
plt.ylim(-0.6, 3.6)
plt.show()

벡터 w에 수직인 직선

  • 예를 들어 $c=0.5$이면 벡터 $w=[1, 2]^T$에 수직이고 원점으로부터의 거리가 $\frac{\sqrt{5}}{2}$인 직선이 된다.

직선과 점의 거리

  • 이번에는 직선 $w^Tx - |w|^2 = 0$과 이 직선 위에 있지 않은 점 $x’$ 사이의 거리를 구해볼 것이다. 벡터 $w$에 대한 벡터 $x’$의 투영성분 $x’^{\Vert w}$의 길이는 다음과 같다.
  • 직선과 점 $x’$ 사이의 거리는 이 길이에서 원점에서 직선까지의 거리 $|w|$를 뺀 값의 절대값이다.
  • 직선의 방정식이 $w^Tx - w_0 = 0$이면 직선과 점의 거리는 다음과 같다.
  • 이 공식은 아래 내용중 SVM의 판별함수의 직선과 서포트벡터간의 거리를 계산하는데에서 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
w = np.array([1, 2])
x1 = np.array([4, 3])
x2 = np.array([1, 2]) * 2
plt.annotate('', xy=x1, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=x2, xytext=(0, 0), arrowprops=gray)
plt.annotate('', xy=w, xytext=(0, 0), arrowprops=red)
plt.plot(0, 0, 'kP', ms=10)
plt.plot(w[0], w[1], 'ro', ms=10)
plt.plot(x1[0], x1[1], 'ro', ms=10)
plt.plot([-3, 7], [4, -1], 'r-', lw=5)
plt.plot([2, 4], [4, 3], 'k:', lw=2)
plt.plot([3, 4], [1, 3], 'k:', lw=2)
plt.text(0.1, 0.9, "$w$")
plt.text(4.2, 3.1, "$x'$")
plt.text(1.5, 2.4, "$x'^{\Vert w}$")
plt.xticks(np.arange(-3, 15))
plt.yticks(np.arange(-1, 5))
plt.xlim(-3, 7)
plt.ylim(-1, 5)
plt.show()

점과 직선사이의 거리

  • 다시 SVM 판별함수를 살펴보면 정의에 따라 $y$값이 $+1$인 그 데이터 $x_{+}$에 대한 판별함수 값은 양수가 된다.
  • 반대로 y값이 -1인 그 데이터 $x_{-}$에 대한 판별함수 값은 음수가 된다.
  • $y$ 값이 $+1$인 데이터 중에서 판별 함수의 값이 가장 작은 데이터를 $x^{+}$라고 하고 $y$값이 $-1$인 데이터 중에서 판별함수의 값이 가장 큰 데이터를 $x^{-}$라고 하자. 이 데이터들은 각각의 클래스에 속한 데이터 중에서 가장 경계선에 가까이 붙어있는 최전방(most front)의 데이터들이다. 이러한 데이터를 서포트(support) 혹은 서포트 벡터(support vector)라고 한다. 물론 이 서포트에 대해서도 부호 조건은 만족되어야 한다.
  • 서포트에 대한 판별 함수의 값 $f(x^{+}), f(x^{-})$값은 부호 조건만 지키면 어떤 값이 되어도 괜찮다, 따라서 다음과 같은 조건을 만족하도록 판별 함수를 구한다.

Support vector의 판별함수 값

  • 이렇게 되면 모든 Support vector $(x_{+}, x_{-})$ 데이터들에 대한 판별함수의 값의 절대값이 1보다 커지므로 다음 부등식이 성립한다.
  • 판별 경계선 $w^{T}x - w_{0} = 0$과 점 $x^{+}, x^{-}$ 사이의 거리는 다음과 같이 계산할 수 있다.
  • 이 거리의 합을 마진(margin)이라고 하며 마진값이 클 수록 더 경계선이 안정적이라고 볼 수 있다. 그런데 위에서 정한 스케일링에 의해 마진은 다음과 같이 정리된다.
  • 마진 값이 최대가 되는 경우는 $| w |$ 즉, $| w |^{2}$가 최소가 되는 경우와 같다. 다음과 같은 목적함수를 최소화하면 된다.
  • 또한 모든 표본 데이터에 대해 분류는 제대로 되어야 하므로 모든 데이터 $x_{i}, y_{i} (i = 1,\ldots, N)$에 대해 다음 조건을 만족해야 한다. 위에서 스케일링을 사용하여 모든 데이터에 대해 $f(x_i) = w^Tx_i - w_o$가 $1$보다 크거나 $-1$보다 작게 만들었다는 점을 이용한다.
  • 라그랑주 승수법을 사용하면 최소화 목적함수를 다음과 같이 고치면 된다. 즉, 위의 조건을 만족하는 w의 최소화 문제를 푸는 것과 같게 된다. $a_{i}$은 각각의 부등식에 대한 라그랑주 승수이다. 이 최적화 문제를 풀어 $w, w_{0}, a$를 구하면 판별함수를 얻을 수 있다.
  • KKT(Karush-Kuhn-Tucker) 조건에 따르면 부등식 제한 조건이 있는 경우에는 등식 제한조건을 가지는 라그랑주 승수 방법과 비슷하지만 $i$번째 부등식이 있으나 없으나 답이 같은 경우에는 해당 라그랑주 승수의 값이 $a_{i}=0$이 된다. 이 경우는 판별함수의 값 $w^Tx_i - w_o$이 $-1$보다 작거나 1보다 큰 경우이다.
    즉, 마진안에 포함되지 않고 바깥에 있는 데이터들 같은 경우는 해당 조건식이 해를 찾는데 영향을 주지 않아 등식이 있는 최적화 문제를 푸는 것과 같다는 의미이다.
  • 학습 데이터 중에서 최전방 데이터인 서포트 벡터가 아닌 모든 데이터들에 대해서는 이 조건이 만족되므로 서포트 벡터가 아닌 데이터는 라그랑지 승수가 $0$이라는 것을 알 수 있다.

듀얼 형식

  • 최적화 조건은 목적함수 $L$을 $w, w_{0}$로 미분한 값이 0이 되어야 하는 것이다.
  • 이 식을 풀어서 정리하면 다음과 같아진다.
  • 정리해보면, 다음과 같다.
  • 이 두 수식을 원래의 목적함수에 대입하여 $w, w_{0}$을 없애면 다음과 같다.
  • 이 떄 $a$는 다음 조건을 만족한다.
  • 이 문제는 $w$를 구하는 문제가 아니라 $a$만을 구하는 문제로 바뀌었으므로 듀얼형식(dual form)이라고 한다. 듀얼형식으로 바꾸면 수치적으로 박스(Box)제한 조건이 있는 이차프로그래밍(QP: Quadratic programming)문제가 되므로 원래의 문제보다는 효율적으로 풀 수 있다.

선형계획법 문제와 이차계획법 문제

  • 듀얼 형식 문제를 풀어 함수 $L$을 최소화하는 $a$를 구하면 예측 모형을 다음과 같이 쓸 수 있다.
  • $w_{0}$는 아래와 같이 구한다.
  • 라그랑주 승수 값이 0 즉, $a_{i} = 0$이면 해당 데이터는 예측 모형, 즉 $w$ 계산에 아무런 기여를 하지 않으므로 위의 식은 실제로는 다음과 같다.
  • 여기에서 $x^{T}x^{+}$는 $x$와 $x^{+}$ 사이의 코사인 유사도, $x^{T}x^{-}$는 $x$와 $x^{-}$ 사이의 코사인 유사도이므로 결국 두 서포트 벡터와의 유사도를 측정해서 값이 큰쪽으로 판별하게 된다.

Scikit-Learn의 서포트 벡터 머신

  • Scikit-Learn의 svm 서브패키지는 서포트 벡터 머신 모형인 SVC(Support Vector Classifier) 클래스를 제공한다. 이와 동시에 SVR(Support Vector Regressor)도 제공을 하지만 SVR은 추후에 설명하고 먼저, SVC에 대해 다루어 볼 것이다.
1
2
3
4
5
6
7
8
9
10
11
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=50, centers=2, cluster_std=0.5, random_state=4)
y = 2 * y - 1

plt.scatter(X[y == -1, 0], X[y == -1, 1], marker='o', label="-1 클래스")
plt.scatter(X[y == +1, 0], X[y == +1, 1], marker='x', label="+1 클래스")
plt.xlabel("x1")
plt.ylabel("x2")
plt.legend()
plt.title("학습용 데이터")
plt.show()

Support Vector Machine train data set

  • SVC 클래스는 커널(Kernel)을 선택하는 인수 kernel과 슬랙변수 가중치(slack variable weight)를 선택하는 인수 C를 받는데 지금까지 공부한 서포트 벡터 머신을 사용하려면 인수를 다음처럼 넣어준다.
1
2
from sklearn.svm import SVC
model = SVC(kernel='linear', C=1e10).fit(X, y)
  • SVC를 사용하여 모형을 구하면 다음과 같은 속성값을 가진다.
    • n_support : 각 클래스의 서포트 벡터의 개수
    • support : 각 클래스의 서포트 벡터의 인덱스
    • support_vectors_ : 각 클래스의 서포트의 $x$값.$(x^{T}, x^{-})$
    • coef : $w$벡터
    • intercept : $- w_{0}$
    • dual_coef : 각 원소가 $a_{i} \dot y_{i}$로 이루어진 벡터
1
model.n_support_
결과
1
array([1, 1], dtype=int32)
1
model.support_
결과
1
array([42,  1], dtype=int32)
1
model.support_vectors_
결과
1
2
array([[9.03715314, 1.71813465],
[9.17124955, 3.52485535]])
1
y[model.support_]
결과
1
array([-1,  1])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
xmin = X[:, 0].min()
xmax = X[:, 0].max()
ymin = X[:, 1].min()
ymax = X[:, 1].max()
xx = np.linspace(xmin, xmax, 10)
yy = np.linspace(ymin, ymax, 10)
X1, X2 = np.meshgrid(xx, yy)

Z = np.empty(X1.shape)
for (i, j), val in np.ndenumerate(X1):
x1 = val
x2 = X2[i, j]
p = model.decision_function([[x1, x2]])
Z[i, j] = p[0]
levels = [-1, 0, 1]
linestyles = ['dashed', 'solid', 'dashed']
plt.scatter(X[y == -1, 0], X[y == -1, 1], marker='o', label="-1 클래스")
plt.scatter(X[y == +1, 0], X[y == +1, 1], marker='x', label="+1 클래스")
plt.contour(X1, X2, Z, levels, colors='k', linestyles=linestyles)
plt.scatter(model.support_vectors_[:, 0], model.support_vectors_[:, 1], s=300, alpha=0.3)

x_new = [10, 2]
plt.scatter(x_new[0], x_new[1], marker='^', s=100)
plt.text(x_new[0] + 0.03, x_new[1] + 0.08, "테스트 데이터")

plt.xlabel("x1")
plt.ylabel("x2")
plt.legend()
plt.title("SVM 예측 결과")

plt.show()

SVM 모델 test data 예측 결과

1
2
x_new = [10, 2]
model.decision_function([x_new])
결과
1
array([-0.61101582])
1
model.coef_.dot(x_new) + model.intercept_
결과
1
array([-0.61101582])
1
2
# dual_coef_ = a_i * y_i
model.dual_coef_
결과
1
array([[-0.60934379,  0.60934379]])
1
2
3
model.dual_coef_[0][0] * model.support_vectors_[0].dot(x_new) + \
model.dual_coef_[0][1] * model.support_vectors_[1].dot(x_new) + \
model.intercept_
결과
1
array([-0.61101582])

iris 문제를 서포트 벡터 머신으로 풀어보자. 다음과 같은 데이터만 사용한 이진 분류 문제로 바꾸어 풀어본다. 위의 예제와 마찬가지로 커널 인수 kernel과 슬랙변수 가중치 인수 C는 각각 linear, 1e10으로 한다.

- 특징 변수를 꽃받침의 길이와 폭만 사용한다.
- 붓꽆 종을 Setosa와 Versicolour만 대상으로 한다.
1
2
3
4
5
6
7
8
9
10
11
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.datasets import load_iris
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split

iris = load_iris()
X_data = iris.data[(iris.target == 0) | (iris.target == 1), :2]
y = iris.target[(iris.target == 0) | (iris.target == 1)]
X_train, X_test, y_train, y_test = train_test_split(X_data, y, test_size=0.3)
svm = SVC(kernel="linear", C=1e10)
svm.fit(X_train, y_train)
결과
1
2
3
4
SVC(C=10000000000.0, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
kernel='linear', max_iter=-1, probability=False, random_state=None,
shrinking=True, tol=0.001, verbose=False)
1
2
pred_y = svm.predict(X_test)
confusion_matrix(pred_y, y_test)
결과
1
2
array([[14,  0],
[ 0, 16]])
  • 위의 조건에서 kernel="linear"로 유지한채 C값만 [0.01, 0.1, 1, 10, 100]으로 변화를 주며 결과를 살펴보니 C값이 높아지면 Slack 변수로 줄 수 있는 값이 줄어들어 서포트 벡터의 수가 줄어든다. 반대로 C값을 낮추어 줄수록 Slack 변수가 갖는 값이 크게 되어 서포트 벡터는 많아지며 마진이 줄어든다.

슬랙변수

  • 만약 데이터가 직선인 판별 경계선으로 나누어지지 않는 즉, 선형분이(linear seperable)가 불가능한 경우에는 다음과 같이 슬랙변수(slack variable)를 사용하여 개별적인 오차를 허용할 수 있다.

  • 원래 판별 함수의 값은 클래스 $x^{T}$ 영역의 샘플 $x_{+}$에 대해선 첫번째 수식과 같고, 클래스 -1 영역의 샘플 $x_{-}$에 대해서는 두 번째 수식과 같아야한다.

  • 양수인 슬랙변수 $\xi \geq 0$를 사용하면 이 조건을 다음과 같이 완화할 수 있다.
  • 모든 슬랙변수는 0보다 같거나 크다.
  • 위의 부등식 조건을 모두 고려한 최적화 목적함수는 다음과 같아진다. 아래 식에서 $C \sum_{i=1}^N \xi_i$ 항은 슬랙변수의 합이 너무 커지지 않도록 제한하는 역할을 한다.

슬랙변수의 C값에 따른 오차 허용의 차이

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
np.random.seed(0)
X = np.r_[np.random.randn(20, 2) - [2, 2], np.random.randn(20, 2) + [2, 2]]
Y = [-1] * 20 + [1] * 20

plotnum = 1
for name, penalty in (('C=10', 10), ('C=0.1', 0.1)):
clf = SVC(kernel='linear', C=penalty).fit(X, Y)
xx = np.linspace(-5, 5)

x_jin = -5
x_jax = 5
y_jin = -9
y_jax = 9
XX, YY = np.mgrid[x_jin:x_jax:200j, y_jin:y_jax:200j]

levels = [-1, 0, 1]
linestyles = ['dashed', 'solid', 'dashed']
Z = clf.decision_function(np.c_[XX.ravel(), YY.ravel()])
Z = Z.reshape(XX.shape)

plt.subplot(1, 2, plotnum)
plt.contour(XX, YY, Z, levels, colors='k', linestyles=linestyles)
plt.scatter(clf.support_vectors_[:, 0], clf.support_vectors_[:, 1], s=120, linewidth=4)
plt.scatter(X[:, 0], X[:, 1], c=Y, s=60, linewidth=1, cmap=plt.cm.Paired)
plt.xlim(x_jin, x_jax)
plt.ylim(y_jin, y_jax)
plt.title(name)

plotnum += 1

plt.suptitle("슬랙변수 가중치 C의 영향")
plt.tight_layout()
plt.show()

슬랙변수 가중치 C의 영향

  • 다시 한번 정리하자면, 아래 그림에서 살펴보면 직선의 방정식 $ x^{T} \beta + \beta_{0} $와 각 서포트 벡터 $ x(x^{+}, x^{-}) $와의 거리가 $ \frac{1}{\beta}$ 이므로 결굴 마진을 크게 하는 것은 $ \beta $를 작게 하는 것과 동일한 의미이다. 그러한 측면에서도 위에서 자세히 언급했듯이 Cost function(목적함수, 비용함수)가 아래와 같이 나온다는 것을 알 수 있다. 여기서 동시에 error를 최소화하고 싶으므로 라그랑주 승수 $ C $를 크게가져가면서 error를 허용하는 slack변수를 최소화하는 동시에 $ \beta $ 의 값도 최소화하는 optimization 문제를 풀 수 있다.

SVM 계산 - 01

  • 위에서의 조건하에 최적화를 하는 것이므로 라그랑주 승수를 도입하여 부등식이 있는 최적화 문제를 풀게 된다.

SVM 계산 - 02

SVM 계산 - 03

  • KKT조건을 만족함으로서, global minimum을 보장받을 수 있다.

SVM 계산 - 04

  • 즉, KKT의 2번째 조건에 의해서 서포트벡터인 경우는 조건식이 의미가 있기 때문에 라그랑지 승수 $\alpha_{i} \neq 0$이 된다는 의미이다.

SVM 계산 - 05

얼굴 이미지 인식

  • 총 40명이 각각 10장의 조금씩 다른 표정이나 모습으로 찍은 이미지 데이터이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces()

N = 2
M = 5
np.random.seed(0)
fig = plt.figure(figsize=(9, 5))
plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)
klist = np.random.choice(range(len(faces.data)), N * M)
for i in range(N):
for j in range(M):
k = klist[i * M + j]
ax = fig.add_subplot(N, M, i * M + j + 1)
ax.imshow(faces.images[k], cmap=plt.cm.bone)
ax.grid(False)
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
plt.title(faces.target[k])
plt.tight_layout()
plt.show()

랜덤하게 뽑은 이미지

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(faces.data, faces.target, test_size=0.4, random_state=0)

from sklearn.svm import SVC
svc = SVC(kernel='linear').fit(X_train, y_train)

N = 2
M = 5
np.random.seed(4)
fig = plt.figure(figsize=(9, 5))
plt.subplots_adjust(top=1, bottom=0, hspace=0, wspace=0.05)
klist = np.random.choice(range(len(y_test)), N * M)
for i in range(N):
for j in range(M):
k = klist[i * M + j]
ax = fig.add_subplot(N, M, i * M + j + 1)
ax.imshow(X_test[k:(k + 1), :].reshape(64, 64), cmap=plt.cm.bone)
ax.grid(False)
ax.xaxis.set_ticks([])
ax.yaxis.set_ticks([])
plt.title("%d => %d" %
(y_test[k], svc.predict(X_test[k:(k + 1), :])[0]))
plt.tight_layout()
plt.show()

랜덤하게 뽑은 이미지의 예측

1
2
3
4
from sklearn.metrics import classification_report, accuracy_score

y_pred_train = svc.predict(X_train)
y_pred_test = svc.predict(X_test)
1
print(classification_report(y_train, y_pred_train))
결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
              precision    recall  f1-score   support

0 1.00 1.00 1.00 4
1 1.00 1.00 1.00 5
2 1.00 1.00 1.00 6
3 1.00 1.00 1.00 8
4 1.00 1.00 1.00 8
5 1.00 1.00 1.00 5
6 1.00 1.00 1.00 4
7 1.00 1.00 1.00 7
8 1.00 1.00 1.00 8
9 1.00 1.00 1.00 7
10 1.00 1.00 1.00 4
11 1.00 1.00 1.00 6
12 1.00 1.00 1.00 6
13 1.00 1.00 1.00 6
14 1.00 1.00 1.00 4
15 1.00 1.00 1.00 4
16 1.00 1.00 1.00 8
17 1.00 1.00 1.00 4
18 1.00 1.00 1.00 9
19 1.00 1.00 1.00 4
20 1.00 1.00 1.00 9
21 1.00 1.00 1.00 6
22 1.00 1.00 1.00 7
23 1.00 1.00 1.00 5
24 1.00 1.00 1.00 6
25 1.00 1.00 1.00 5
26 1.00 1.00 1.00 5
27 1.00 1.00 1.00 8
28 1.00 1.00 1.00 6
29 1.00 1.00 1.00 4
30 1.00 1.00 1.00 6
31 1.00 1.00 1.00 5
32 1.00 1.00 1.00 6
33 1.00 1.00 1.00 7
34 1.00 1.00 1.00 4
35 1.00 1.00 1.00 7
36 1.00 1.00 1.00 6
37 1.00 1.00 1.00 6
38 1.00 1.00 1.00 9
39 1.00 1.00 1.00 6

accuracy 1.00 240
macro avg 1.00 1.00 1.00 240
weighted avg 1.00 1.00 1.00 240
1
print(classification_report(y_test, y_pred_test))
결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
              precision    recall  f1-score   support

0 0.86 1.00 0.92 6
1 1.00 1.00 1.00 5
2 1.00 1.00 1.00 4
3 0.50 1.00 0.67 2
4 1.00 0.50 0.67 2
5 1.00 1.00 1.00 5
6 0.83 0.83 0.83 6
7 1.00 0.67 0.80 3
8 0.67 1.00 0.80 2
9 1.00 1.00 1.00 3
10 1.00 1.00 1.00 6
11 1.00 1.00 1.00 4
12 0.67 1.00 0.80 4
13 1.00 1.00 1.00 4
14 1.00 1.00 1.00 6
15 1.00 0.33 0.50 6
16 0.67 1.00 0.80 2
17 1.00 1.00 1.00 6
18 1.00 1.00 1.00 1
19 1.00 1.00 1.00 6
20 1.00 1.00 1.00 1
21 1.00 0.75 0.86 4
22 1.00 1.00 1.00 3
23 0.71 1.00 0.83 5
24 1.00 1.00 1.00 4
25 1.00 1.00 1.00 5
26 1.00 1.00 1.00 5
27 1.00 1.00 1.00 2
28 1.00 1.00 1.00 4
29 1.00 1.00 1.00 6
30 1.00 1.00 1.00 4
31 1.00 1.00 1.00 5
32 1.00 1.00 1.00 4
33 1.00 1.00 1.00 3
34 1.00 0.83 0.91 6
35 1.00 0.67 0.80 3
36 1.00 1.00 1.00 4
37 1.00 1.00 1.00 4
38 0.50 1.00 0.67 1
39 0.67 0.50 0.57 4

accuracy 0.93 160
macro avg 0.93 0.93 0.91 160
weighted avg 0.95 0.93 0.92 160