2020년 7월 11일 토요일

데이터 분류에 편리한 머신러닝 기술 SVM(Support Vector Machine) 개념 및 사용하기

이 글은 머신러닝 기법 중 데이터 분류에 편리한 SVM(Support Vector Machine) 개념, 계산 및 사용 방법을 간략히 정리한다. SVM은 데이터 분류에 굳이 번거로운 딥러닝을 사용하고 싶지 않을 때, 간단히 여러 개발도구에 있는 SVM 함수를 호출해 분류 기능을 구현하고 싶을 때 사용한다.

SVM개념
SVM은 데이터를 구분할 수 있는 경계를 계산하는 알고리즘이다. 이 경계는 수학적 모델로 정의할 수 있으며, 회귀모델을 개발할 때와 비슷한 수치해석 방법을 사용한다. 경계 모델은 직선, 평면, 곡면 등이 될 수 있다. 이 경계는 다음 그림과 같이 두 데이터 군과의 거리차이가 가장 크게 차이가 나는(margin이 많은) 분류 기준이 된다.
경계 예시

SVM은 Boser, Guyon, Vapnic가 1992년 제안한 일종의 지도학습으로, 예를 들어, 두개의 데이터 그룹이 섞여 있을 경우, 이를 구분할 수 있는 경계를 찾는 알고리즘이다. 현재에도 많은 학계와 업계 사용하고 있다.

SVM 계산 기본원리
SVM 분류기는 Machine Learning에 포함된 간단한 이진 분류기이다. 이 장에서는 SVM이해를 위해 그라디언트 하강 최적화 접근 방식을 사용해, 데이터를 두 클래스로 분리하고 두 클래스 간 거리를 최대화 할 수 있는 선형 탐색 알고리즘을 설명한다. 이 장은 SVM Simple Example(medium, russsun, 2019.4)를 참고하였음을 밝힌다.

직선 공식은 다음과 같다.
w * x + b = 0
라벨 1의 경우는 w * x + b ≥ 1
라벨 -1의 경우는 w * x + b ≤ -1
두 클래스 사이의 간격 거리 다음과 같다.
2 / | w |
이 거리를 최대화한다.

두 클래스를 완벽하게 분리하는 라인을 탐색하는 SVM 비용 함수를 마진 비용 함수라고 한다. 예를 들어, 비용 함수를 사용할 때 Hinge loss함수를 사용할 수 있다.
max(0, 1 - yi (dot(w, xi) - b))
이를 구현한 알고리즘 소스코드는 다음과 같다. 참고로, main()함수에 구현된 것은 나이, 소득에 따라 집 구매 여부를 구분하는 SVM 모델 생성의 예이다.
double getHingeLoss(double &x1, double &x2, int &y, double &w1, double &w2, double &b)
{
  double loss = 0;
  if(y==1)
    loss = 1-(w1*x1 + w2*x2 + b);
  else
    loss = 1+(w1*x1 + w2*x2 + b);

  if(loss < 0) loss = 0;
    return loss;
}

// slope:a, intercept:b, derivative of w: dw, derivative of intercept: db
double getSVMCost(vector<double> &x1, vector<double> &x2, vector<int> &y, double w1, double w2, double b, double &dw1, double &dw2, double &db)
{
  int n = static_cast<int>(y.size());
  // hinge loss
  double cost=0;
  dw1 = 0;
  dw2 = 0;
  db = 0;
  for(int i = 0; i<n;i++)
  {
    double loss = getHingeLoss(x1[i], x2[i], y[i], w1, w2, b);
    cost += loss;
    // when loss = 0, all derivatives are 0
    if(loss > 0)
    {
      dw1 += (-x1[i]*y[i]);
      dw2 += (-x2[i]*y[i]);
      db += (-y[i]);
    }
  }
  cost /= n;
  dw1 /= n;
  dw2 /= n;
  db /=n;
  return cost;
}

void trainSVM(vector<double> &x1, vector<double> &x2, vector<int> &y)
{
  double lrate = 0.0005;
  double threshold = 0.001;
  double w1 = 1; double w2 = 1;
  double b = 0;  
  double dw1 = 0; double dw2 = 0;
  double db = 0;
  int iter = 0;
  while(true)
  {
    double cost = getSVMCost(x1, x2, y, w1, w2, b, dw1, dw2, db);
    if(iter%1000==0)
    {
      cout<<”Iter: “<<iter<< “ cost = “<<cost<< “ dw1 = “ << dw1 << “ dw2 = “ << dw2 << “ db = “<<db<< endl;
    }
    iter++;
    if(abs(dw1) < threshold && abs(dw2) < threshold && abs(db) < threshold)
    {
      cout<<”y = “<<w1<<” * x1 + “<<w2<<” * x2 + “<<b<<endl;
      break;
    }
    w1 -= lrate* dw1;
    w2 -= lrate* dw2;
    b -= lrate * db;
  }
}

// 나이, 소득에 따라 집 구매 여부를 구분하는 SVM 모델 생성의 예
int main() {
  // age
  vector<double> X1 = {35,27,19,25,26,45,46,48,47,29,27,28,27,30,28,23,27,18};
  // income in K
  vector<double> X2 = {20,57,76,33,52,26,28,29,49,43,137,44,90,49,84,20,54,44};
  // buy a house
  vector<int> Y = {-1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1 };
  trainSVM(X1, X2, Y);
  return 0;
}


파이썬 기반 SVM 함수 사용방법
SVM을 사용하기 위해 보통 다음과 같이 기존 개발된 라이브러리를 사용한다.
아래는 Scikit-learn을 사용한 SVM예제이다.
from sklearn import svm
X = [[0, 0], [1, 1]]
y = [0, 1]
clf = svm.SVC()
clf.fit(X, y)
clf.predict([[2., 2.]])
clf.support_vectors_
# get indices of support vectors
clf.support_
# get number of support vectors for each class
clf.n_support_

다음은 개, 고양이 이미지에서 히스토그램 등 특징을 추출해, SVM으로 학습하는 방법을 설명한 내용이다. 단, 이미지 같은 비정형 데이터의 경우, 딥러닝 보다는 정확도가 높지 않다.

마무리
SVM은 이미 많은 곳에 사용되고 있는 검증된 머신러닝 기반 분류 기술로, 파이썬 등 다양한 언어에 라이브러리로 포함되어 있어 사용이 매우 편리하다. 분류에 사용되는 수치해석도 성능이 좋아 실시간에 필요한 분류 기능일 경우에는 좋은 대안이 될 수 있다. 앞에서 언급한 정도의 분류 문제라면 굳이 많은 노력이 필요한 딥러닝 대신 유용하게 활용할 수 있을 것이다.

댓글 없음:

댓글 쓰기