SVM과 커널, SVM을 이용한 분류 모델링, 커스텀으로 커널 만들기


  • 회귀 : 연속 변수를 예측. 데이터를 지나는 추세선을 찾는다.
  • 분류 : 이산 변수, 범주형 변수를 예측. 데이터를 나누는 경계선을 찾는다.
  • 회귀와 분류는 깊게 들어가면 비슷한 부분이 있다. (로지스틱 회귀분석 - 분류에 사용)

SVM

  • 참고자료 링크
  • 참고 블로그
  • 선형 모델 : 오차를 줄이는 데 관심.
  • 정규화된 선형 모델 : 오차를 줄이고 좋은 형태도 찾음.
  • SVM : 좋은 형태를 찾고 더불어 오차도 줄이자. = SVM은 Ridge 선형 모델과 수학적으로 비슷하다.

문제의 비선형성

  • 현실의 많은 문제들은 비선형적(non-linearity)이다. (예, XOR 문제)
  • 비선형 문제는 비선형 모델로 풀 수 있지만 적합(fitting) 시키기가 매우 어렵다.
  • 해결책 : 비선형 모델을 만드는 것이 아니라 데이터를 비선형으로 변환하여 “선형 분리”가 가능하게 만든다.

Kernel Trick

  • 실제로는 비선형 변환도 쉽지 않기 때문에 비선형 변환을 한 것처럼 트릭을 사용한다.
  • 데이터 상호간 거리를 재정의(비슷한 것은 가까이 있다)
  • 데이터가 흔히 보는 자료 형태가 아닐 때 (예, 집합) 커널을 이용해서 일반적인 데이터인 것처럼 다룰 수 있다.

커널의 종류

  • 선형 커널(linear kernel)
  • RBF 커널(radio basis function)
  • 다항 커널(polynomial kernel)
  • 시그모이드 커널(sigmoid kernel)
  • 커널을 결합해도 커널이 된다.

SVM을 사용하여 자동차의 특성에 따라 door 수를 예측

import pandas
from sklearn.svm import SVC
from sklearn import metrics
# 데이터 준비
cars = pandas.read_csv('data/automobile.csv')
variables = ['bore', 'city_mpg', 'compression_ratio', 'curb_weight', 'engine_size', 'horsepower', 'peak_rpm', 'city_mpg', 'price']
X = cars[variables]   # 모델에 사용할 feature 정의.
y = cars['doors']
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)

Linear SVM

svc = SVC(kernel='linear')
svc.fit(X_train, y_train)
SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape=None, degree=3, gamma='auto', kernel='linear',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)
# 예측
y_svc = svc.predict(X_test)
# 모델 평가
metrics.confusion_matrix(y_test, y_svc)
array([[29, 10],
       [12, 13]])
metrics.accuracy_score(y_test, y_svc)
0.65625
metrics.precision_score(y_test, y_svc, pos_label='four')
0.70731707317073167
metrics.recall_score(y_test, y_svc, pos_label='four')
0.74358974358974361
metrics.f1_score(y_test, y_svc, pos_label='four')
0.72499999999999998

오차에 대한 패널티 C 조정

svc2 = SVC(kernel='linear', C=0.1)
svc2.fit(X_train, y_train)
SVC(C=0.1, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape=None, degree=3, gamma='auto', kernel='linear',
  max_iter=-1, probability=False, random_state=None, shrinking=True,
  tol=0.001, verbose=False)
y_svc2 = svc2.predict(X_test)
metrics.confusion_matrix(y_test, y_svc2)
array([[27, 12],
       [ 9, 16]])
metrics.accuracy_score(y_test, y_svc2)
0.671875
metrics.precision_score(y_test, y_svc2, pos_label='four')
0.75
metrics.recall_score(y_test, y_svc2, pos_label='four')
0.69230769230769229
metrics.f1_score(y_test, y_svc2, pos_label='four')
0.71999999999999986

모델을 만들고 평가하는 전체 과정을 함수로 생성

def run_svm_model(kernel, penalty):
    model = SVC(kernel=kernel, C=penalty)
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    
    print('confusion matrix')
    print(metrics.confusion_matrix(y_test, y_pred))
    print('accuracy : {}'.format(metrics.accuracy_score(y_test, y_pred)))
    print('precision : {}'.format(metrics.precision_score(y_test, y_pred, pos_label='four')))
    print('recall : {}'.format(metrics.recall_score(y_test, y_pred, pos_label='four')))
    print('F1 : {}'.format(metrics.f1_score(y_test, y_pred, pos_label='four')))
    return model
svc3 = run_svm_model('linear', 10)
confusion matrix
[[28 11]
 [11 14]]
accuracy : 0.65625
precision : 0.717948717948718
recall : 0.717948717948718
F1 : 0.717948717948718
svc4 = run_svm_model('linear', 0.01)
confusion matrix
[[25 14]
 [10 15]]
accuracy : 0.625
precision : 0.7142857142857143
recall : 0.6410256410256411
F1 : 0.6756756756756757

SVM + RBF kernel

def run_svc_model(kernel, penalty, gamma='auto'):
    model = SVC(kernel=kernel, C=penalty, gamma=gamma)
    model.fit(X_train, y_train)
    y_pred = model.predict(X_test)
    
    print('confusion matrix')
    print(metrics.confusion_matrix(y_test, y_pred))
    print('accuracy : {}'.format(metrics.accuracy_score(y_test, y_pred)))
    print('precision : {}'.format(metrics.precision_score(y_test, y_pred, pos_label='four')))
    print('recall : {}'.format(metrics.recall_score(y_test, y_pred, pos_label='four')))
    print('F1 : {}'.format(metrics.f1_score(y_test, y_pred, pos_label='four')))
    return model
  • gamma : how far the influence of a single training example reaches, with low values meaning ‘far’ and high values meaning ‘close’.
  • gamma가 너무 작으면 모델이 너무 제한적이 되어서 데이터의 복잡성이나 모양을 잘 캡쳐할 수 없다.
svc_rbf1 = run_svc_model('rbf', 1)
confusion matrix
[[39  0]
 [25  0]]
accuracy : 0.609375
precision : 0.609375
recall : 1.0
F1 : 0.7572815533980582
svc_rbf2 = run_svc_model('rbf', 0.01, gamma=0.001)
confusion matrix
[[39  0]
 [25  0]]
accuracy : 0.609375
precision : 0.609375
recall : 1.0
F1 : 0.7572815533980582

Custom Kernel

# 독립변수 중에서 범주형 변수만 골라낸다.
cate_var = cars.columns[cars.dtypes == 'object']
cate_var
Index(['maker', 'fuel', 'aspiration', 'doors', 'body', 'wheels',
       'engine_location', 'engine_type', 'cylinders', 'fuel_system'],
      dtype='object')
data = cars[cate_var]
data.head()
maker fuel aspiration doors body wheels engine_location engine_type cylinders fuel_system
0 audi gas std four sedan fwd front ohc four mpfi
1 audi gas std four sedan 4wd front ohc five mpfi
2 audi gas std four sedan fwd front ohc five mpfi
3 audi gas turbo four sedan fwd front ohc five mpfi
4 bmw gas std two sedan rwd front ohc four mpfi
X = cars[cate_var.difference(['doors'])]   # doors 변수를 제외한 나머지를 feature로 사용
y = cars['doors']
# 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)
row_train, _ = X_train.shape   # 훈련 데이터 갯수
row_train
95
row_test, _ = X_test.shape
row_test
64
# custom kernel 만들기
X_train.iloc[0:2,:]
aspiration body cylinders engine_location engine_type fuel fuel_system maker wheels
140 std sedan four front ohc diesel idi volkswagen fwd
33 std hatchback four front ohc gas 2bbl mazda fwd
'''2개의 데이터 포인트에서 9가지 변수 중에 같은 변수의 갯수를 센다'''
same = 0
for a, b in zip(X_train.iloc[0,:], X_train.iloc[1,:]):
    if a == b:
        same = same + 1
same
5
def hand_made_kernel(d1, d2):
    same = 0
    for a, b in zip(d1, d2):
        if a == b:
            same = same + 1
    return same
# custom kernel 적용
import numpy
P_train = numpy.zeros((row_train, row_train))   # array 생성 및 초기화
P_train
array([[ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       ..., 
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.]])
for i in range(row_train):
    for j in range(row_train):
        P_train[i, j] = hand_made_kernel(X_train.iloc[i,:], X_train.iloc[j,:])
P_train
array([[ 9.,  5.,  4., ...,  4.,  6.,  5.],
       [ 5.,  9.,  6., ...,  6.,  7.,  7.],
       [ 4.,  6.,  9., ...,  7.,  5.,  5.],
       ..., 
       [ 4.,  6.,  7., ...,  9.,  5.,  5.],
       [ 6.,  7.,  5., ...,  5.,  9.,  8.],
       [ 5.,  7.,  5., ...,  5.,  8.,  9.]])
# modeling & training
model = SVC(kernel='precomputed', C=1)
model.fit(P_train, y_train)
SVC(C=1, cache_size=200, class_weight=None, coef0=0.0,
  decision_function_shape=None, degree=3, gamma='auto',
  kernel='precomputed', max_iter=-1, probability=False, random_state=None,
  shrinking=True, tol=0.001, verbose=False)
# prediction

P_test = numpy.zeros((row_test, row_train))
for i in range(row_test):
    for j in range(row_train):
        P_test[i, j] = hand_made_kernel(X_test.iloc[i,:], X_train.iloc[j,:])
P_test
array([[ 3.,  5.,  5., ...,  4.,  4.,  4.],
       [ 5.,  6.,  4., ...,  4.,  7.,  6.],
       [ 6.,  6.,  5., ...,  5.,  7.,  6.],
       ..., 
       [ 4.,  7.,  5., ...,  5.,  6.,  6.],
       [ 4.,  5.,  5., ...,  4.,  5.,  6.],
       [ 5.,  7.,  5., ...,  5.,  8.,  8.]])
y_c_pred = model.predict(P_test)
print('confusion matrix')
print(metrics.confusion_matrix(y_test, y_c_pred))
print('accuracy : {}'.format(metrics.accuracy_score(y_test, y_c_pred)))
print('precision : {}'.format(metrics.precision_score(y_test, y_c_pred, pos_label='four')))
print('recall : {}'.format(metrics.recall_score(y_test, y_c_pred, pos_label='four')))
print('F1 : {}'.format(metrics.f1_score(y_test, y_c_pred, pos_label='four')))

# 정확도가 80 % 이상으로 높아졌다.
confusion matrix
[[42  4]
 [ 5 13]]
accuracy : 0.859375
precision : 0.8936170212765957
recall : 0.9130434782608695
F1 : 0.9032258064516129