#!/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 |
---|