종종 자바 오브젝트가 딱 데이터만큼 메모리를 소비한다고 생각하는 개발자들이 있어서 공유한다. 메타데이터 데이터가 자바 오브젝트에 있다. 사실 String 이나 Java collection 코드를 보면 생각보다 데이터를 많이 갖고 있음을 알면 좋다. 




자바 오브젝트는 접근하기에 충분히 빠르지만 원시 필드의 실제(원시) 데이터보다 2~5배 더 많은 공간을 차지한다. 



예를 들어 각 개별 자바 오브젝트에는 오브젝트 헤더를 갖고 있는데 16바이트를 포함한다. 



또한 자바 문자열의 경우 원시 문자열 대비 추가로 거의 40바이트가 추가된다. 




또한 Set, List, Queue, ArrayList, Vector, LinkedList, PriorityQueue, HashSet, LinkedHashSet, TreeSet 등과 같은 자바 컬렉션 클래스도 사용된다. 



반면에 연결 데이터 구조(예, LinkedList)는 너무 복잡해서 데이터 구조의 각 항목에 대한 래퍼(wrapper) 오브젝트가 있기 때문에 너무 많은 공간을 차지한다. 



한편 원시 타입의 컬렉션은 java.lang.Double과 java.lang.Integer와 같은 박스형 오브젝트이기 때문에 메모리에 저장한다.



Posted by '김용환'
,


머신 러닝의 지도학습을 공부하다 보면 회귀와 분류에 대한 내용이 나오는데.. 둘 다 비슷한 확률적인 공식을 기반으로 동작한다고 보면 된다.





1. 분류라 하는 영역은 이산 값을 포함하는 유한 집합에서 레이블을 예측하는 것이다. 이것과 저것을 분류하는 것이다. binary/multiclass classification 이 이런 종류이다. 예를 들어 예/아니오 등과 같은 boolean을 얻는 형태이다.



2. 회귀는 연속된 값을 예측하는 것이다. 차원 감소, SVG와  spark을 보면 predict함수가 존재한다. 예를 들어 앞으로 직원들의 성과를 기반으로 하는 연봉 추이를 얻을 수 있는 내용을 얻을 수 있다. 







아래 참고자료가 명확히 이해할 수 있는 내용이라 참조한다. 


참조 : https://www.slideshare.net/ssuser163469/ndc-2016-61452271



분류는 미리 정의된, 가능성이 있는 여러 클래스 레이블 중 하나를 예측하는 것이다. 앞 장에서 붓꽃의 품종을 예측하는 것은 분류에 속한다. 분류는 두 개로 분류하는 이진 분류(binary classification)과 셋 이상으로 분류하는 다중 분류(multiclass classification)으로 나누어 진다. 이진 분류는 예 / 아니요만 나올 수 있다고 보면 된다. 남자, 여자로 나눌 수도 있지만, 남자인가? 라는 질문에는 예와 아니요로 바꿀 수 있기 때문에 결국 예 / 아니요라고 볼 수 있다.

붓꽃 예제의 경우 3개의 클래스를 가지고 있기 때문에 다중 분류이다.


회귀는 연속적인 숫자(실수)를 예측하는 것이다. 어떤 사람의 교육 수준, 나이 등을 이용해 연봉을 예측하는 것도 회귀 문제의 예이고, 몸무게를 이용해 키를 예측하는 것도 회귀 문제라고 볼 수 있다.


출력 값에 연속성이 있다면 회귀 문제라고 볼 수 있다. 연봉을 예상할 때 1억이든 1억 1만원이든 큰 문제가 되지 않는다. 하지만 분류 문제에서는 중간은 없다. 예를 들어 스팸메일을 분류한다면 스팸 메일이거나 아니거나 두 가지로 나누어지는 것이지 중간인 메일은 없다.

Posted by '김용환'
,


spark-shell을 이용한 PCA 예이다. 



스파크에서는 선형 회귀 알고리즘의 RDD 기반 구현을 제공한다.


https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/mllib/regression/LinearRegression.scala




SGD(stochastic gradient descent)를 사용해 정규화가 필요없는 선형 회귀 모델을 학습시킬 수 있다. 이는 다음과 같은 최소 제곱 회귀(least squares regression) 공식을 풀 수 있다.



f(가중치) = 1/n ||A 가중치-y||^2  

(평균 제곱 오차(MSE, mean squared error)이다) 





여기서 데이터 행렬은 n 개의 로우를 가지며 입력 RDD는 A의 로우 셋을 보유하고 각각은 해당 오른쪽 사이드 레이블 y를 가진다. 



데이터셋을 로드하고 RDD를 생성한다.


LIBSVM 포맷으로 MNIST 데이터셋을 로딩하기 위해 여기에 스파크 MLlib의 MLUtils라는 내장 API를 사용했다. 예에서 사용되는 mnist.bz2의 주소는 https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/mnist.bz2이다. 




import org.apache.spark.mllib.util.MLUtils

val data = MLUtils.loadLibSVMFile(spark.sparkContext, "data/mnist.bz2") 




차원 축소를 쉽게 진행하기 위해 피쳐 개수를 계산한다. 결과는 780이 나온다.


val featureSize = data.first().features.size

println("Feature Size: " + featureSize)






이제 데이터셋에는 780개의 컬럼이 있다. 피쳐는 고차원 피쳐로 간주될 수 있다. 


이제 다음처럼 트레이닝 셋과 테스트 셋을 준비한다.


LinearRegressionwithSGD 모델을 트레이닝시킬 것이다. 먼저 피쳐의 원래 차원을 가진 일반 데이터셋을 사용하고 두 번째로 피쳐의 절반을 사용한다. 원래 데이터 셋을 이용해 트레이닝 셋과 테스트 셋을 준비한다.


75%는 트레이닝 셋, 나머지 25%는 테스트 셋이다. 



val splits = data.randomSplit(Array(0.75, 0.25), seed = 12345L)

val (training, test) = (splits(0), splits(1))




이제 PCA를 통해 축소된 피쳐를 트레이닝한다.


import org.apache.spark.mllib.feature.PCA

val pca = new PCA(featureSize/2).fit(data.map(_.features))

val training_pca = training.map(p => p.copy(features = pca.transform(p.features)))

val test_pca = test.map(p => p.copy(features = pca.transform(p.features))) 






선형 회귀 모형을 트레이닝한다.

다음처럼 LinearRegressionWithSGD를 20번 반복하고 일반 피쳐와 축소된 피쳐를 각각 트레이닝한다.


import org.apache.spark.mllib.regression.LinearRegressionWithSGD

val numIterations = 20

val stepSize = 0.0001

val model = LinearRegressionWithSGD.train(training, numIterations)

val model_pca = LinearRegressionWithSGD.train(training_pca, numIterations)




* LinearRegressionWithSGD는 NaN을 리턴할 수 있다. 이유는 다음과 같다.


1. stepSize가 큰 경우이다. 이 경우 0.0001, 0.001, 0.01, 0.03, 0.1, 0.3, 1.0 등과 같이 작은 값을 사용해야 한다.

2. 트레이닝 데이터에 NaN이 있어서 모델을 트레이닝하기 전에 null 값을 제거해야 한다.




두 모델을 평가한다.



분류 모델을 평가하기 전에, 먼저 원래 예측에 대한 차원 축소에 미치는 영향을 살펴보기 위해 일반적인 MSE를 계산한다.  모델 정확도를 정량화하고 잠재적으로 정밀도를 높여 오버 피팅을 피하는 공식적인 방법을 원한다면 분명하다. 



그럼에도 불구하고 잔차 분석(residual analysis)을 통해 수행할 수 있다. 또한 모델 구축과 평가에 사용될 트레이닝 셋과 테스트 셋의 선택을 분석하는 것은 가치가 있다. 모델 예측과 PCA 예측 코드를 작성한다.



val valuesAndPreds = test.map { point =>

                     val score = model.predict(point.features)

                     (score, point.label)

                    }


val valuesAndPreds_pca = test_pca.map { point =>

                        val score = model_pca.predict(point.features)

                        (score, point.label)

                      }




이제 MSE를 계산하고 다음처럼 각각의 경우를 출력한다.


val MSE = valuesAndPreds.map { case (v, p) => math.pow(v - p, 2) }.mean()

val MSE_pca = valuesAndPreds_pca.map { case (v, p) => math.pow(v - p, 2) }.mean()


println("Mean Squared Error = " + MSE)

println("PCA Mean Squared Error = " + MSE_pca)




다음과 같은 결과가 나온다. 거의 동일하다.


Mean Squared Error = 4.809249884658854E238


PCA Mean Squared Error = 4.807999600599884E238



MSE는 실제로 다음 공식을 사용해 계산되었다.





다음처럼 모델 계수를 계산한다.



println("Model coefficients:"+ model.toString())

println("Model with PCA coefficients:"+ model_pca.toString())



Model coefficients: intercept = 0.0, numFeatures = 780

Model with PCA coefficients: intercept = 0.0, numFeatures = 390



Posted by '김용환'
,


spark-shell에서 PCA를 공부한다. (차원 축소)


차원 축소는 고려하는 변수의 수를 줄이는 과정이다. 원본과 잡음이 많은 피쳐에서 잠재 피쳐를 추출하거나 구조를 유지하면서 데이터를 압축하는 데 사용할 수 있다. 스파크 MLlib는 RowMatrix 클래스의 차원 축소를 지원한다. 데이터의 차원을 줄이기 위해 가장 일반적으로 사용되는 알고리즘은 PCA와 SVD이다. 


PCA를 살펴본다.



PCA는 가능한 상관 변수의 관찰 셋을 주성분(Principal component)라고 부르는 선형 무상관 변수 집합으로 변환하기 위해 직교(orthogonal) 트랜스포메이션을 사용하는 통계적 과정이다.



PCA 알고리즘은 PCA를 사용해 저차원 공간에 벡터를 투영하는 데 사용할 수 있다. 



그런 다음, 감소 된 피쳐 벡터에 기초해 ML 모델을 트레이닝할 수 있다. 


다음 예는 6차원 피쳐 벡터를 4 차원 주성분에 투영하는 방법을 보여준다. 다음과 같은 피쳐 벡터가 있다고 가정한다.


scala>
import org.apache.spark.ml.linalg.Vectors


val data = Array(

Vectors.dense(3.5, 2.0, 5.0, 6.3, 5.60, 2.4),

Vectors.dense(4.40, 0.10, 3.0, 9.0, 7.0, 8.75),

Vectors.dense(3.20, 2.40, 0.0, 6.0, 7.4, 3.34))





* 여기서 주의할 점은 Vectors의 패키지를 잘 확인해야 한다.


import org.apache.spark.mllib.linalg.Vectors이 아니라 import org.apache.spark.ml.linalg.Vectors을 사용해야 한다.




이제 데이터 프레임을 생성하자.


scala>


val df = spark.createDataFrame(data.map(Tuple1.apply)).toDF("features")

df.show(false)


+--------------------------+

|features                  |

+--------------------------+

|[3.5,2.0,5.0,6.3,5.6,2.4] |

|[4.4,0.1,3.0,9.0,7.0,8.75]|

|[3.2,2.4,0.0,6.0,7.4,3.34]|

+--------------------------+




이제 PCA에 대한 6차원 피쳐 벡터를 갖는 피쳐 데이터 프레임을 생성한다.


이제 다음과 같은 매개 변수를 설정하여 PCA 모델을 초기화한다.



import org.apache.spark.ml.feature.PCA


val pca = new PCA()

.setInputCol("features")

.setOutputCol("pcaFeatures")

.setK(4)

.fit(df)






차이를 주기 위해 setOutputCol 메소드를 사용하여 출력 컬럼을 pcaFeatures로 설정했다. 

그리고 PCA의 차원을 설정했다. 

마지막으로 트랜스포메이션을 생성하기 위해 데이터 프레임을 피팅했다. 



PCA 모델에는 explain variance(분산도)이 포함된다. 



val result = pca.transform(df).select("pcaFeatures")

result.show(false)


+-------------------------------------------------------------------------------+

|pcaFeatures                                                                    |

+-------------------------------------------------------------------------------+

|[-5.149253129088708,3.2157431427730376,-5.390271710168745,-1.0528214606325355] |

|[-12.372614091904456,0.8041966678176822,-5.390271710168748,-1.0528214606325337]|

|[-5.649682494292663,-2.1891778048858255,-5.390271710168744,-1.0528214606325337]|

+-------------------------------------------------------------------------------+



이전 코드는 PCA를 사용해 주성분으로서 4차원의 피쳐 벡터를 갖는 피쳐 데이터 프레임을 생성한다. 



Posted by '김용환'
,