본문 바로가기
Data Science/Spark MLlib

스파크 MLlib + 선형회귀법, Spark MLlib + Linear Regression - (2) Feature extraction

by 알파해커 2017. 12. 15.
반응형

앞서 (1)편에서 얘기했던, 4가지의 과정 중에서 

이번에는 Feature extraction에 대해서 얘기해볼것이다.


(가장 첫번째 단계인 데이터 수집 및 준비 단계는 이미 이루어졌다고 가정한다.)




0. Feature Extraction


Feature란, 어떤 결과의 원인에 해당하는 일이라고 설명했다.


우리가 머신러닝을 하는 대표적인 이유가 예측을 하기 위한 것이다. 그렇다면 "어떤 조건에 대한", "어떤 값에 대한" 예측을 할 것인지가 정해져야 하는데, 그 "조건"과 "값"이 원인 요소에 해당하는 것이고. 그것을 우리는 Feature라고 한다.


만약에 우리가 지도 학습을 하게 되는거라면, 다양한 이미 답이 만들어져 있는 것으로 학습을 시키게 될 것이다. 


가령, 온도와 아이스크림 판매량 데이터를 가지고, 특정 온도에서 판매량이 얼만큼 될지 예측해야하는 상황이라 해보자.


우리가 가지고 있는 데이터가 (25도, 5만원), (28도, 3만원), (30도, 1만원) 이라고 하면, 이 세개의 데이터를 이용해서 지도 학습을 시킨 후, 가지고 있지 않은 상황인 32도에 대해서도 예측 판매량을 구할 수가 있는 것이다.


이 때 각 데이터의 쌍을 (feature, label) 쌍이라고 부르는데, feature가 원인이고, label이 결과에 해당하는 것이다.


따라서, 우리가 머신 러닝을 하고자 한다면, 반드시 저 Feature 값을 잘 추출하는 것이 중요하다. 




1. 데이터 타입 


스파크에서의 Feature와 그 자세한 활용 및 적용 방법에 대해 알아보기 전에, Feature와 관련한 데이터타입에 대해서 알아두면 이해하는데 도움이 된다. 

  • 벡터(Vector) : 수학적인 의미의 벡터를 의미한다. Feature는 보통 한가지의 요소만으로는 표현이 잘안되고, 복잡적인 요인이 반영되야 하는 상황이 많다. 따라서 Feature를 벡터로 나타낸다. 
  • 자바나 스칼라에서는 MLlib의 Vector클래스는 주로 데이터를 표현하기 위해서 사용된다. 사용자 API에서 더하기나 빼기 같은 사칙연산을 제공하지 않는다. (파이썬에서는 고밀도 벡터에 NumPy를 써서 수학적 연산을 거친 후 MLlib에 넘길 수 있다). 이는 MLlib 자체를 복잡하지 않게 만들려는 것이 주된 의도인데, 전체적인 선형대수 라이브러리를 구현하게 되면 이는 프로젝트 자체의 범위를 넘어서기 때문.


    하지만 프로그램 내에서 벡터에 수학적연산을 하고 싶다면 스칼라의 브리즈(Breeze, http://bit.ly/1li2qav)나 자바의 MTJ(http://bit.ly/1I78OdC) 같은 서드파티 라이브러리를 써서 데이터를 MLlib 벡터로 변환할 수 있다.

  • 고밀도 벡터(Dense vector), 저밀도 벡터(Sparse vector) : MLlib는 모든 값이 저장되는 고밀도 벡터(dense vector)와 공간을 절약하기 위해 0이 아닌 값만 저장하는 저밀도 벡터(sparse vector)를 모두 지원한다. 고밀도 벡터는 모든 데이터를 부동 소수의 배열에 저장한다. 예를 들어, 사이즈 100의 벡터는 100개의 double 값을 저장한다. 반면에 저밀도 벡터는 오직 0이 아닌 값과 그것들의 인덱스를 저장한다. 0이 아닌 값이 10퍼센트 이하라면 저밀도 벡터가 보통 더 선호되는 편이다 (메모리 사용량이나 속도 측면에서 모두). 특성 판별을 위한 많은 기술이 매우 저밀도의 벡터를 결과로 내놓는 편이므로 이 방식을 사용한 것은 종종 키 최적화를 가져온다.
  • Label : 자료에 따라 Target이라고 부르기도 한다. 앞서 설명한 것 처럼, 입력에 대한 올바른 출력 값을 알고 있는 데이터셋을 가지고 입력과 그에 따른 출력값을 함께 학습한 뒤, 아직 답이 알려지지 않은 새로운 입력값에 대한 출력값을 찾게하는 방법이 있는데 이를 지도학습 방식(supervised learning)이라고도 한다. 이때 입력값에 대한 올바른 출력 값을 레이블(label)이라고 한다.
  • LabeledPoint : 이것은 앞서 말한 입력값(feature)과 그에 따른 올바른 출력값(label)의 쌍을 만들어주는, 스파크가 제공하는 데이터 타입이다. 그러니까 (입력값, 올바른 출력값) 쌍으로 이루어져 있는 데이터 타입이며, 이 (feature, label) 쌍을 LabeledPoint 라고 한다.


아래의 코드는 스칼라 언어와 스파크를 이용하여 벡터 변수를 만드는 예제이다.



import org.apache.spark.mllib.linalg.Vectors


// 고밀도 벡터 <1.0, 2.0, 3.0>를 만든다. Vectors.dense는 값들이나 배열을 받는다.

val denseVec1 = Vectors.dense(1.0, 2.0, 3.0)

vla denseVec2 = Vectors.dense(Array(1.0, 2.0, 3.0))


// 저밀도 벡터 <1.0, 0.0, 2.0, 0.0>를 만든다. 

// Vectors.sparse는 벡터의 크기와 (여기서는 4), 0이 아닌 값들의 위치를 받는다.

val sparseVec1 = Vectors.sparse(4, Array(0, 2), Array(1.0, 2.0))



아래의 코드는 LabeledPoint 자료형을 이용하여 스파크에서 Linear Regression (선형회귀법)을 구현하는 예제이다.



 var data = sc.textFile(eachFile)

     var operationData = data.filter(line => line.contains("operations"))

     var parsedData = operationData.map { line =>

       var parts = line.split(' ')

       LabeledPoint(parts(0).toDouble, Vectors.dense(_00h.take(24).map(_.toDouble)))

     }


     // Build linear regression model

     var regression = new LinearRegressionWithSGD().setIntercept(false)

     regression.optimizer.setNumIterations(200)

     regression.optimizer.setStepSize(1) //0.0, 0.20999999999999963, 0.03999999999999999

     var model00 = regression.run(parsedData)

var predictedValue00 = model00.predict(Vectors.dense(_00h.take(24).map(_.toDouble)))

 


위의 예제 코드는 필자가 직접 작성한 코드로,

1. 우선 log 파일에 있는 내용 중, "operations"라는 키워드가 있는 라인만 뽑아낸다.

2. 그 라인을 공백(' ')으로 나눈 후, 나누어진 그 배열의 0번째 요소를 Label로 둔다.

3. 미리 정해둔 _00h라는 배열의 24개의 값을 취한 후, 각 요소를 Double 형으로 만든다.


이때 미리 정해둔 _00h 이라는 배열은 필자가 정한 feature 벡터이다. 벡터를 배열로 표현해서 나타냈으며, feature는 필자가 생각하기에 적절한 형태로 만들었다.


부가 설명을 하자면, 각 시간대 별로 특징을 알아보는 간단한 실험이었는데, 0시부터 23시까지의 각 시간을 배열로 나타냈다. 이 때, 시간이라는 그 숫자 자체가 주는 영향을 상쇄 시키기 위해, 각 시간은 0과 1로 구성된, 24개의 숫자 값으로 표현했다.


가령, 아래와 같은 방식이었다.


00시 : 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .... 0 

01시 : 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 .... 0

02시 : 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 .... 0

.

.

.

23시 : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .... 1


어쨌든 지도학습에서 원인과 결과에 해당하는 feature, label쌍을 LabeledPoint 자료형을 이용해서 만든 후, 알고리즘에 적용하면, 위의 코드의 predict 함수를 통해 나타낸 것 처럼, 어떤 새로운 feature 값(Vectors.dense(_00h.take(24).map(_.toDouble)), 이때 형태는 처음 LabeledPoint의 feature값으로 넣을 때 처럼 Vector형이어야 한다.)을 입력했을때 예측값을 얻을 수 있다. 




2. Feature on Spark


스파크에서 제공하는 mllib.feature 패키지는 일반적인 feature 트랜스포메이션(transformation)들을 위한 여러 클래스를 갖고 있다. 여기에는 문자열 혹은 다른 토큰들로부터 feature vector를 구축하는 알고리즘과 feature들을 정규화하고 정량화 하는 방법들을 포함한다. 

  • TF-IDF
단어 빈도-역문서 빈도(TF-IDF, Term Frequency0-Inverse Document Frequency)는 텍스트 문서에서 feature vector를 생성하는 간단한 방식으로써, 가장 많이 언급되는 방식이다. 이는 각 문서의 각 단어에 대해 두 가지의 통계를 계산하는데, 하나의 문서에서 단어가 등장하는 빈도인 TF와 전체 문서군에서 얼마나 (덜) 자주 단어가 나타나는지를 계산한 역문서 빈도 IDF이다. 이 값들의 곡ㅂ인 TF x IDF는 특정 문서가 얼마나 그 단어와 연관이 있는지를 보여준다. 

예를 들어, 특정 단어가 하나의 문서에만 많이 등장하고, 전체 문서군에는 적게 나올 경우 그런 연관성을 파악할 수 있을 것이다.
  • 정량화
앞서 필자가 feature vector를 뽑아내는 방법을 이야기하면서 잠깐 언급했듯이, 대부분의 머신 러닝 알고리즘은 feature vector에서 각 요소의 척도를 고려하므로, feature들이 정량화 (scaling)되었을 때에 잘 동작하며, 그를 위해 요소들이 동일하게 정량화되어야 한다 (즉, 모든 feature들이 평균 0의 값과 표준편차 1을 가지도록). 

한 번 feature vector가 만들어지면 MLlib의 StandardScaler 클래스를 써서 평균과 표준 편차 양쪽에 대해 이런 정량화 작업을 할 수 있다. 먼저 StandardScaler를 만들고, StandardScalerModel(예: 평균과 각 칼람의 분산 계산)을 얻기 위해 데이터세트에 fit()을 호출한 후, 모델에서 데이터세트를 정량화하기 위해 transform()을 호출할 수 있다.

아래의 파이썬으로 만들어진 코드는 그 과정을 보여준다.


from pyspark.mllib.feature import StandardScaler


vectors = [Vectors.dense([-2.0, 5.0, 1.0]), Vectors.dense([2.0, 0.0, 1.0])]

dataset = sc.parallelize(vectors)

scaler = StandardScaler(withMean = True, widStd = True)

model = scaler.fit(dataset)



  • 정규화

  • Word2Vec






3. References


[1] https://www.slideshare.net/JunKim22/spark-zeppelin

[2] https://www.cs.princeton.edu/courses/archive/fall08/cos436/Duda/PR_model/f_vecs.htm

[3] http://hyunje.com/data%20analysis/2015/09/09/advanced-analytics-with-spark-ch4/

[4] http://thinkingdino.tistory.com/46

[5] 러닝 스파크 (책)




반응형

댓글