#!/usr/bin/env python3


Linear Support Vector Machine: Soft Margin


# x1 = Sepal Length

# x2 - Petal Width


구분 가능한 소프트 마진 SVM을 텐서플로에 구현하기 위해서는 다음과 같은 비용함수를 구현해야합니다.

$$\frac{1}{n} \sum^{n}_{i=1}{max \left(0,1-y_{i}(Ax_{i}-b) \right)}+a \parallel A \parallel ^{2}$$

$A$는 기울기 벡터, $b$는 절편, $x_{i}$는 입력벡터, $y_{i}$는 실제 분류 대상 값(-1, 1)이고 $a$는 소프트 마진 구분 정도를 가리키는 규칙화 매개변수 입니다.


그래프 세션을 시작하고, 필요한 데이터를 로드합니다. 꽃받침 길이와 꽃잎 폭에 해당하는 데이터셋의 첫번재, 네 번째 변수를 로딩합니다. setosa종일 때는 값이 1이고, 그 외의 경우에는 값이 -1이 되게 대상 값을 로드합니다.

from sklearn.datasets import load_iris

import numpy as np

import tensorflow as tf

from tensorflow.python.framework import ops


ops.reset_default_graph()


sess = tf.Session()

iris = load_iris()



# load the data

x_vals = iris.data[:, [0,3]]

print(x_vals[:6])

# [[5.1 0.2]

#  [4.9 0.2]

#  [4.7 0.2]

#  [4.6 0.2]

#  [5.  0.2]

#  [5.4 0.4]]


y_vals = np.array([1 if y==0 else -1 for y in iris.target])

print(y_vals)

# [ 1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1

#   1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1

#   1  1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1

#  -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1

#  -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1

#  -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1

#  -1 -1 -1 -1 -1 -1]



데이터셋을 학습셋과 테스트셋으로 분류합니다. 학습셋, 테스트셋 모두에 정확도를 평가할 것입니다. 선형적으로 구분이 가능한 데이터셋이기 때문에 둘 모두 100%의 정확도를 기대할 수 있습니다.

# split data

from sklearn.model_selection import train_test_split


x_train, x_test, y_train, y_test = train_test_split(x_vals, y_vals,

                                                    random_state=0, stratify=y_vals)



작업크기를 설정하고 placeholder와 model 변수를 선언합니다. SVM 알고리즘 수렴에 도움이 되게 일괄 작업 크기를 크게 설정하였습니다. 일괄 작업 크기가 작으면 최대 마진 직선이 조금씩 움직일 것을 예상할 수 있습니다. 수렴이 진행됨에 따라 학습률의 크기를 줄이는 것이 이상적입니다. 

Sepal Lentgh와 Petal Width 두가지 예상 변수가 있기 때문에 A는 2x1의 형태로 변수를 만듭니다.

# placeholder

x_data = tf.placeholder(shape=[None, 2], dtype=tf.float32)

y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)


# create Variable

A = tf.Variable(tf.random_normal(shape=[2, 1]))

b = tf.Variable(tf.random_normal(shape=[1, 1]))



모델 출력값을 선언합니다. 올바르게 분류된 경우라면 대상 값이 setosa 일 때는 1보다 크거나 같은 값이 출력되고, 대상 값이 setosa가 아닐 때는 -1보다 작거나 같은 값이 출력됩니다.

# fomula (y = ax - b)

fomula = tf.subtract(tf.matmul(x_data, A), b)



이 내용들을 조합하여 최대 마진 비용에 필요한 구성 요소들을 선언합니다.

먼저 벡터의 norm(L2)을 계산하는 함수를 선언합니다. 그런 다음에 마진 매개변수 $a$를 추가합니다.

그리고 이 두 항을 더하는 분류 비용 함수를 선언합니다.

L2_norm = tf.reduce_sum(tf.square(A))

# declare loss function

# Loss = summation(max(0, 1 - pred*actual)) + alpha * L2_norm(A)^2

alpha = tf.constant([0.01])


# Margin term in loss

left_term = tf.reduce_mean(tf.maximum(0., tf.subtract(1., tf.multiply(fomula, y_target))))


# Put terms together

loss = tf.add(left_term, tf.multiply(alpha, L2_norm))


학습셋과 테스트셋을 대상으로 정확도를 측정하기 위해 예측 함수와 정확도 함수를 선언합니다.

# prediction function

prediction = tf.sign(fomula)

accuracy = tf.reduce_mean(tf.cast(tf.equal(prediction, y_target), tf.float32))



최적화 함수를 선언하고 모델 변수를 초기화합니다.

train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)


# initialize variable

init = tf.global_variables_initializer()

sess.run(init)



학습 루프를 실행합니다. 비용함수 값과 더불어 학습셋과 테스트셋 양쪽의 정확도를 기록합니다.

test_acc = []

train_acc = []

loss_vec = []

batch_size = 112

for i in np.arange(1000):

    rand_idx = np.random.choice(len(x_train), size=batch_size)

    rand_x = x_train[rand_idx]

    rand_y = y_train[rand_idx].reshape(-1, 1)


    sess.run(train_step, feed_dict={x_data:rand_x, y_target:rand_y})


    temp_loss = sess.run(loss, feed_dict={x_data:rand_x, y_target:rand_y})

    loss_vec.append(temp_loss)


    temp_train_acc = sess.run(accuracy, feed_dict={x_data:x_train, y_target:y_train.reshape(-1, 1)})

    train_acc.append(temp_train_acc)


    temp_test_acc = sess.run(accuracy, feed_dict={x_data:x_test, y_target:y_test.reshape(-1, 1)})

    test_acc.append(temp_test_acc)


    if (i+1) % 150 == 0:

        print('step {}:\n  A={}, b={}\nloss={}\n'.format(i+1, sess.run(A), sess.run(b), temp_loss))

        # step 150:

        # A=[[-0.1508689 ]

        #    [-0.02083003]], b=[[0.03109207]]

        # loss=[0.6171418]


        # step 300:

        # A=[[-0.06546283]

        #    [-0.4349473 ]], b=[[-0.04024722]]

        # loss=[0.39113477]


        # step 450:

        # A=[[ 0.01723341]

        #    [-0.8050836 ]], b=[[-0.12105083]]

        # loss=[0.41022006]


        # step 600:

        # A=[[ 0.07058643]

        #    [-1.1637722 ]], b=[[-0.19953299]]

        # loss=[0.2737451]


        # step 750:

        # A=[[ 0.14719042]

        #    [-1.503774  ]], b=[[-0.28408656]]

        # loss=[0.2209843]


        # step 900:

        # A=[[ 0.19714044]

        #    [-1.8002963 ]], b=[[-0.35890797]]

        # loss=[0.17502114]



결과를 그림으로 나타내려면 다음과 같이 계수를 추출하고, setosa 종 여부에 따라 x 값을 분리해야 합니다.

# visualization

import matplotlib.pyplot as plt

[[a1], [a2]] = sess.run(A)

[[b]] = sess.run(b)


slope = -a2/a1

icept = b


best_fit = []

for i in x_vals[:, 1]:

    temp = i * slope + icept

    best_fit.append(temp)


setosa_x = [setosa[1] for i, setosa in enumerate(x_vals) if y_vals[i] == 1]

setosa_y = [setosa[0] for i, setosa in enumerate(x_vals) if y_vals[i] == 1]

not_setosa_x = [setosa[1] for i, setosa in enumerate(x_vals) if y_vals[i] == -1]

not_setosa_y = [setosa[0] for i, setosa in enumerate(x_vals) if y_vals[i] == -1]



다음 코드를 이용하여 선형 구분자 및 정확도, 비용 함수를 그림으로 표현할 수 있습니다.

_, axe = plt.subplots(1, 3)


axe[0].scatter(setosa_x, setosa_y, marker='o', label='setosa')

axe[0].scatter(not_setosa_x, not_setosa_y, marker='o', label='non-setosa')

axe[0].plot(x_vals[:, 1], best_fit, c='red', label='Linear Separator')

axe[0].set_ylim([0, 10])

axe[0].legend(loc='best')

axe[0].set_title('Sepal Length vs Petal Width')

axe[0].set_xlabel('Petal Width')

axe[0].set_ylabel('Sepal Length')


axe[1].plot(train_acc, c='k', linestyle='-', label='Training Accuracy')

axe[1].plot(test_acc, c='r', linestyle='--', label='Test Accuracy')

axe[1].set_title('Train and Test Accuracy')

axe[1].set_xlabel('Genearation')

axe[1].set_ylabel('Accuracy')

axe[1].legend(loc='best')


axe[2].plot(loss_vec, c='k')

axe[2].set_title('Loss per Generation')

axe[2].set_xlabel('Generation')

axe[2].set_ylabel('loss')


plt.show()

학습 진행중인 모델의 최종 결과 그래프와 테스트셋과 학습셋의 정확도, 두 분류가 선형적으로 구분 가능하기 때문에 정확도가 100%에 도달




참고 자료: 

[1]TensorFlow Machine Learning Cookbook, Nick McClure

[2]https://github.com/nfmcclure/tensorflow_cookbook


'Tensorflow > Support Vector Machine' 카테고리의 다른 글

SVM intro  (0) 2018.05.01

Introduction


Support Vector Machine는 이진 분류 기법입니다. 기본 Idea는 두 분류 사이에 존재하는 선형 구분선hyperplane을 찾는 것입니다.

분류대상의 값을 -1, 1이라 가정합니다. 두 분류를 구분하는 직선은 무수히 많지만, 두 분류에서 가장 멀리 떨어진 구분선을 최적 선형 구분자라할 수 있습니다.


구분 가능한 두 분류 o, x가 주어졌을 때 두 분류를 구분하는 선형 구분 방정식을 찾고자 합니다.

왼쪽 그림을 보면 두 분류를 구분하는 여러 직선이 존재한다는 것을 알 수 있습니다. 오른쪽 그림은 분류 사이이의 magin을 최대로 하는 유일한 직선을 나타낸 것입니다.

마진의 폭은 $\frac{2}{llAll}$ 입니다.


초평면은 다음 방정식으로 표현할 수 있습니다.$$Ax-b=0$$

A는 기울기를 나태는 벡터고, $x$는 입력 벡터입니다. 최대 margin의 폭은 A L2 norm 값으로 나눈 값이 됩니다. 기하학적으로 2차원상의 한점에서 직선까지의 거리를 구하는 상황입니다.


선형적으로 구분 가능한 이진 분류 데이터의 경우 margin을 최대화 하려면 A의 L2 norm값을 최소화해야 합니다. 이 최솟값은 다음 제약도 만족해야 합니다.

$$y_{i}(Ax_{i}-b)\ge 1\forall i$$

이 제한을 만족해야만 한 분류에 속하는 모든 점이 구분선의 같은 편에 족재하게 됩니다.


모든 데이터셋을 선형적으로 구분할 수 있는 것은 아니기 때문에 선을 넘어가는 점을 대상으로 한 cost function을 도입할 수 있습니다. n개의 데이터 지점에 대해 soft margin cost function은 다음과 같이 정의합니다.

$$\frac{1}{n}\sum^{n}_{i=1}max \left(0,1 - y_{i}(Ax_{i}-b) \right) +a\parallel A \parallel ^{2}$$

데이터 포인트가 올바른 자리에 있으면 $y_{i}(Ax_{i}-b)$의 값은 항상 1보다 클 것입니다. 그러면 cost function의 좌측항은 0이 되어 cont function에 영향을 미치는 부분은

$a\parallel A\parallel ^{2}$인 margin의 크기만 남게 됩니다.


이 cost function을 사용하면 데이터 포인트가 margin line 을 넘어가는 경우를 허용하는 선형 구분 직선을 찾을 수 있습니다.

$a$ 값에 따라 허용하는 정도가 느슨할 수 있고, 엄격할 수 있습니다. 

$a$ 값이 클수록 margin의 폭이 늘어나고 작을 수록 엄격한 margin을 적용하게 됩니다.


'Tensorflow > Support Vector Machine' 카테고리의 다른 글

Linear Support Vector Machine: Soft Margin  (0) 2018.05.01

+ Recent posts