출처) Apache Spark 홈페이지

Spark ML을 사용하여 간단하게 ML을 비즈니스 로직에서 활용하고 싶을 때가 있다.

 

그리고 그 비즈니스 로직에서 ML을 여러 개 사용하고 싶을 때도 물론 있다.

 

하지만 문제는 이때부터 발생한다.

 

Spark ML에는 다양한 ML 기능이 RDD 혹은 DataFrame을 이용할 수 있도록 구현되어 있는데, 이 모델을 저장하고 로딩할 때 기본 라이브러리를 이용하면 각 모델마다 지연시간이 발생한다.

 

load 함수를 호출할 때마다 Spark Task로써 SVMModel, DecisionTreeModel, GradientBoostedTreesModel 등의 모델 로딩이 수행된다.

 

하지만 이 로딩 과정이라는게 위 언급한 모델에서는 별 것 없다.

 

결과는 단순히 의사 결정 나무일 뿐이고, GBT 같은 경우는 이 트리가 여러개 있을 뿐이다.

 

따라서 이러한 모델을 한 번 로딩해 놓고 인-메모리 스토리지에 객체로 저장해두고 사용해도 무방하지만, 근본적으로 더 쉬운 해결책이 있다.

 

바로 Model에 대한 Description이다.

 

각 모델들은 toDebugString이라는 모델을 뿌려주는 함수가 존재한다.

 

예를 들면 아래와 같다.

GradientBoostedTreesModel classifier of depth 2 with 8 nodes
Tree 0:
  If (feature 0 <= 1.0)
    If (feature 1 <= 2.0)
      Predict: -0.356328434
    Else (feature 1 > 2.0)
      If (feature 1 <= 2.0)
        Predict: -0.356328434
      Else (feature 1 > 2.0)
        Predict: 0.242148432
  Else (feature 0 > 1.0)
    Predict: 0.32325233
Tree 1:
  If (feature 0 <= 1.0)
    If (feature 1 <= 2.0)
      Predict: -0.356328434
    Else (feature 1 > 2.0)
      Predict: 0.242148432
  Else (feature 0 > 1.0)
    Predict: 0.32325233

 

조금만 살펴보면 단순한 트리 구조를 가지고 있어, 이를 해석하는데 무리가 없음을 알 수 있다(?).

 

그리고 이 외의 것들도 모델 객체의 생성자 혹은 load, predict 등을 소스코드 내에서 따라가보면 금방 이해할 수 있을 것이다.

 

그리고 간단하게 구현한 프로토타입을 참고해서 이해해도 좋을 것 같다.

 

프로토타입은 간단하게 GradientBoostedTreesModel의 예시를 구현하였다.

 

TreeNode 객체는 일반적으로 의사 결정 나무를 Java에서 구현하는 예시를 참고하였다.

public class TreeNode {
	TreeNode nodeRoot;
	TreeNode nodeLeft;
	TreeNode nodeRight;
	int feature;
	double threshold;
	double predictLeft;
	double predictRight;
}

 

이 노드 구성을 토대로 트리의 각 노드 별 If, Else, Predict에 따라 의사 결정 나무를 초기화해준다.

boolean isLeftLeaf = true;
for (String[] line : model) {
	if (line[IDX_CLASS].equals("If")) {
		int feature = Integer.parseInt(line[IDX_FEATURE]);
		double threshold = Double.parseDouble(line[IDX_THRESHOLD]);
		insertFeature(isLeftLeaf, feature, threshold);
		isLeftLeaf = true;
	} else if (line[IDX_CLASS].equals("Else")) {
		if (!isLeftLeaf) moveToEmptyRoot();
		isLeftLeaf = false;
	} else if (line[IDX_CLASS].equals("Predict")) {
		double predict = Double.parseDouble(line[IDX_PREDICT]);
		insertPredict(isLeftLeaf, predict);
	}
}

 

이렇게 구성한 의사 결정 나무에 predict 함수를 구현하여 feature 수에 따라 double[]로 Decision 결과를 반환하면 완성이다.

 

Decision 결과를 반환 시에 유의할 점은 GBT에 있다.

 

Spark ML 소스코드를 참고해보면 SUM과 VOTE 중 기본 계산 방식인 SUM에서 각 트리 마다의 가중치가 존재함을 확인할 수 있다.

 

사실 결과값에 각 트리 가중치만 반영해주면 그만이지만 Spark ML 코드 내에서는 BLAS.dot을 이용하여 벡터 내적을 수행한다.

BLAS.dot(new DenseVector(predictVec), new DenseVector(treeWeights));

 

이를 참고하여 Spark ML을 적용하면 모델 로딩에 따른 시간적 피해를 최소화할 수 있다.

블로그 이미지

나뷜나뷜

,