spark-shell을 이용한 "로지스틱 회귀 분석을 이용한 멀티 클래스 분류" 예이다. 


다중 클래스 분류 문제를 트레이닝하고 예측하기 위해 이진 로지스틱 회귀를 다항 로지스틱 회귀로 일반화할 수 있다. 



예를 들어 K개의 가능한 결과에 대해 결과 중 하나를 피벗으로 선택하고 다른 K-1개의 결과는 피벗 결과에 대해 개별적으로 회귀될 수 있다. 


spark.mllib에서 첫 번째 클래스 0은 피벗(pivot) 클래스로 선택된다.


다중 클래스 분류 문제의 경우 알고리즘은 첫 번째 클래스에 대해 회귀된 k-1 이진 로지스틱 회귀 모델을 포함하는 다항 로지스틱 회귀 모델을 출력한다.


 새로운 데이터 포인트가 주어지면 k-1 모델은 실행되고 가장 큰 확률을 가진 클래스가 예측 클래스로 선택된다. 


이 섹션에서는 더 빠른 수렴을 위해 L-BFGS를 사용하는 로지스틱 회귀 분석을 사용해 분류하는 예를 보여준다.





LIVSVM 포맷으로 https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass.html에서 

MNIST 데이터셋(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")



다음처럼 데이터를 트레이닝 셋(75%)과 테스트 셋(25%)으로 나눈다.



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

val training = splits(0).cache()

val test = splits(1)




트레이닝 알고리즘을 실행해 다중 클래스(이 데이터 셋의 경우는 10개이다)를 설정하여 모델을 구축한다. 



분류 정확도를 높이려면 다음처럼 Boolean true 값을 사용해 데이터셋에 인터셉트를 추가(setIntercept)한 후 유효성을 검사(setValidateData)해야 한다.


import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS

val model = new LogisticRegressionWithLBFGS()

          .setNumClasses(10)

          .setIntercept(true)

          .setValidateData(true)

          .run(training)





알고리즘이 setIntercept를 사용하여 인터셉트를 추가해야하는 경우 인터셉트를 true로 설정한다. 



모델 구축 전에 알고리즘에 트레이닝 셋으로 유효성을 검사하려면 setValidateData 함수를 사용하여 값을 true로 설정해야한다.




다음처럼 기본 임계값을 지워 기본 설정으로 트레이닝하지 않는다.


model.clearThreshold()





테스트 셋의 원본 점수를 계산해서 이전에 언급한 성능 메트릭를 사용해 다음처럼 모델을 평가할 수 있다.



val scoreAndLabels = test.map { point =>

 val score = model.predict(point.features)

 (score, point.label)

}



평가를 할 수 있도록 여러 개의 메트릭을 초기화한다.



import org.apache.spark.mllib.evaluation.MulticlassMetrics

val metrics = new MulticlassMetrics(scoreAndLabels)




혼동 행렬를 구축한다.



println(metrics.confusionMatrix)


1466.0  1.0     4.0     2.0     3.0     11.0    18.0    1.0     11.0    4.0

0.0     1709.0  11.0    3.0     2.0     6.0     1.0     5.0     15.0    4.0

10.0    17.0    1316.0  24.0    22.0    8.0     20.0    17.0    26.0    8.0

3.0     9.0     38.0    1423.0  1.0     52.0    9.0     11.0    31.0    15.0

3.0     4.0     23.0    1.0     1363.0  4.0     10.0    7.0     5.0     43.0

19.0    7.0     11.0    50.0    12.0    1170.0  23.0    6.0     32.0    11.0

6.0     2.0     15.0    3.0     10.0    19.0    1411.0  2.0     8.0     2.0

4.0     7.0     10.0    7.0     14.0    4.0     2.0     1519.0  8.0     48.0

9.0     22.0    26.0    43.0    11.0    46.0    16.0    5.0     1268.0  8.0

6.0     3.0     5.0     23.0    39.0    8.0     0.0     60.0    14.0    1327.0






혼동 행렬에서 행렬의 각 컬럼은 예측 클래스의 인스턴스를 나타내는 반면, 


각 라인은 실제 클래스의 인스턴스를 나타낸다(또는 그 반대). 


이름은 시스템이 2개의 클래스를 혼동하고 있는지 쉽게 알 수 있게 한다는 사실에서 유래한다. 


자세한 내용은 혼동 행렬(https://en.wikipedia.org/wiki/Confusion_matrix.Confusion)를 참조한다.






이제 모델의 성능을 판단하기 위해 전체 통계를 계산해보자.



val accuracy = metrics.accuracy

println("Summary Statistics")

println(s"Accuracy = $accuracy")

// 레이블 당 정확도

val labels = metrics.labels

labels.foreach { l =>

 println(s"Precision($l) = " + metrics.precision(l))

}

// 레이블 당 회수율

labels.foreach { l =>

 println(s"Recall($l) = " + metrics.recall(l))

}

// 레이블 당 거짓 긍정 비율

labels.foreach { l =>

 println(s"FPR($l) = " + metrics.falsePositiveRate(l))

}

// 레이블 당 F-측정 값

labels.foreach { l =>

 println(s"F1-Score($l) = " + metrics.fMeasure(l))

}




이전 코드 세그먼트는 정확도, 정밀도, 회수율, 참 긍정 비율, 오 탐지율 및 F1 점수와 같은 성능 메트릭을 포함하는 다음 출력을 생성한다.


Summary Statistics

----------------------

scala> // 레이블 당 정확도


scala> val labels = metrics.labels

labels: Array[Double] = Array(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)


scala> labels.foreach { l =>

     |  println(s"Precision($l) = " + metrics.precision(l))

     | }

Precision(0.0) = 0.9606815203145478

Precision(1.0) = 0.9595732734418866

Precision(2.0) = 0.9019876627827279

Precision(3.0) = 0.9012032932235592

Precision(4.0) = 0.922816519972918

Precision(5.0) = 0.8810240963855421

Precision(6.0) = 0.9344370860927153

Precision(7.0) = 0.9301898346601347

Precision(8.0) = 0.8942172073342737

Precision(9.0) = 0.9027210884353741


scala> // 레이블 당 회수율


scala> labels.foreach { l =>

     |  println(s"Recall($l) = " + metrics.recall(l))

     | }

Recall(0.0) = 0.9638395792241946

Recall(1.0) = 0.9732346241457859

Recall(2.0) = 0.896457765667575

Recall(3.0) = 0.8938442211055276

Recall(4.0) = 0.9316473000683527

Recall(5.0) = 0.87248322147651

Recall(6.0) = 0.9546684709066305

Recall(7.0) = 0.9359211337030191

Recall(8.0) = 0.8720770288858322

Recall(9.0) = 0.8936026936026936


scala> // 레이블 당 거짓 긍정 비율


scala> labels.foreach { l =>

     |  println(s"FPR($l) = " + metrics.falsePositiveRate(l))

     | }

FPR(0.0) = 0.004392386530014641

FPR(1.0) = 0.005363128491620112

FPR(2.0) = 0.010428060964048714

FPR(3.0) = 0.011479873427036574

FPR(4.0) = 0.008310249307479225

FPR(5.0) = 0.011416184971098265

FPR(6.0) = 0.0072246953221922205

FPR(7.0) = 0.00840831981118159

FPR(8.0) = 0.010927369417935456

FPR(9.0) = 0.010441004672897197


scala> // 레이블 당 F-측정 값


scala> labels.foreach { l =>

     |  println(s"F1-Score($l) = " + metrics.fMeasure(l))

     | }

F1-Score(0.0) = 0.9622579586478502

F1-Score(1.0) = 0.966355668645745

F1-Score(2.0) = 0.8992142125042706

F1-Score(3.0) = 0.8975086723431095

F1-Score(4.0) = 0.9272108843537414

F1-Score(5.0) = 0.876732858748595

F1-Score(6.0) = 0.9444444444444444

F1-Score(7.0) = 0.933046683046683

F1-Score(8.0) = 0.883008356545961

F1-Score(9.0) = 0.8981387478849409




이제 전체 통계, 즉 요약 통계를 계산하자.


println(s"Weighted precision: ${metrics.weightedPrecision}")

println(s"Weighted recall: ${metrics.weightedRecall}")

println(s"Weighted F1 score: ${metrics.weightedFMeasure}")

println(s"Weighted false positive rate: ${metrics.weightedFalsePositiveRate}") 




이전 코드는 가중치 정밀도, 회수율, F1 점수, 거짓 긍정 비율을 다음처럼 출력한다.


Weighted precision: 0.920104303076327

Weighted recall: 0.9203609775377117

Weighted F1 score: 0.9201934861645358

Weighted false positive rate: 0.008752250453215607




전체 통계에 따르면 모델의 정확도는 92%이상이다. 


그러나 랜덤 포레스트(RF)와 같은 더 좋은 알고리즘을 사용하면 성능이 향상된다.



Posted by '김용환'
,

spark-shell에서 RandomForest와 같은 알고리즘을 트레이닝할 때 메모리가 한참 부족해서 spakr이 crash나는 경우가 있다. 이럴 때는 메모리와 cpu를 넉넉히 설정하는 것이 좋다. 


예는 다음과 같다. 


spark-shell --driver-memory 16G --executor-memory 16G --executor-cores 8


Posted by '김용환'
,


우분투 버전은 동물 이름이다..



The development codename of a release takes the form "Adjective Animal". So for example: Warty Warthog (Ubuntu 4.10), Hoary Hedgehog (Ubuntu 5.04), Breezy Badger (Ubuntu 5.10), are the first three releases of Ubuntu. In general, people refer to the release using the adjective, like "warty" or "breezy". The names live on in one hidden location



MarkShuttleworth said the following with regard to where the naming scheme originally came from:

  • So, what's with the "Funky Fairy" naming system? Many sensible people have wondered why we chose this naming scheme. It came about as a joke on a ferry between Circular Quay and somewhere else, in Sydney, Australia:

    • lifeless: how long before we make a first release?
      sabdfl: it would need to be punchy. six months max.
      lifeless: six months! thats not a lot of time for polish.
      sabdfl: so we'll have to nickname it the warty warthog release.

    And voila, the name stuck. The first mailing list for the Ubuntu team was called "warthogs", and we used to hang out on #warthogs on irc.freenode.net. For subsequent releases we wanted to stick with the "hog" names, so we had Hoary Hedgehog, and Grumpy Groundhog. But "Grumpy" just didn't sound right, for a release that was looking really good, and had fantastic community participation. So we looked around and came up with "Breezy Badger". We will still use "Grumpy Groundhog", but those plans are still a surprise to be announced... For those of you who think the chosen names could be improved, you might be relieved to know that the "Breezy Badger" was originally going to be the "Bendy Badger" (I still think that rocked). There were others... For all of our sanity we are going to try to keep these names alphabetical after Breezy. We might skip a few letters, and we'll have to wrap eventually. But the naming convention is here for a while longer, at least. The possibilities are endless. Gregarious Gnu? Antsy Aardvark? Phlegmatic Pheasant? You send 'em, we'll consider 'em.

  1. lifeless is Robert Collins. sabdfl is Mark Shuttleworth.


https://wiki.ubuntu.com/DevelopmentCodeNames


https://en.wikipedia.org/wiki/Ubuntu_version_history#Ubuntu_18.04_LTS_(Bionic_Beaver)



Posted by '김용환'
,

marathon의 상태에 대한 내용이다.


- Running

- Deploying

- Suspended

- Delayed

- Waiting


(- Staging)


waiting 상태이면 메소스 자원이 없는지 메소스 슬레이브 자원 상태를 확인하거나, 추가 자원을 확보한다.


staging 상태이면 health check 실패거나 오래 걸리는 상황일 수 있다. 로그를 줄이거나 예외(exception) 또는 에러로 인해서 데몬이 실행 중인지 확인한다.  확인하는 방법은 로컬에서 진행할 수 있다. 


healthy였다고 unhealthy였다가 반복하면 docker 컨테이너에 앱이 종료되었다가, 실행되었다가 하는 것이다.항상 foreground 앱이 docker 컨테이너 안에 있어야 한다.

참조 

https://mesosphere.github.io/marathon/docs/marathon-ui.html#application-status-reference





Posted by '김용환'
,





spark-shell을 이용해 유방암 가능성을 예측하는 방법을 소개한다. 


https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data에서 데이터를 다운로드받고 로드한 다음 파싱하는 예를 소개한다.




breast-cancer-wisconsin.data를 읽고 이를 케이스 클래스로 저장하고  파싱하는 코드를 추가한다.



import org.apache.spark.rdd.RDD


case class Cancer(cancer_class: Double, thickness: Double, size: Double, shape: Double, madh: Double, epsize: Double, bnuc: Double, bchrom: Double, nNuc: Double, mit: Double)


def parseRDD(rdd: RDD[String]): RDD[Array[Double]] = {

 rdd.map(_.split(",")).filter(_(6) != "?").map(_.drop(1)).map(_.map(_.toDouble))


def parseCancer(line: Array[Double]): Cancer = {

 Cancer(if (line(9) == 4.0) 1 else 0, line(0), line(1), line(2), line(3), line(4), line(5), line(6), line(7), line(8))

}


val rdd = spark.sparkContext.textFile("data/breast-cancer-wisconsin.data")

val cancerRDD = parseRDD(rdd).map(parseCancer) 




머신러닝 파이프 라인을 위해 RDD를 데이터 프레임으로 변환한다.


import spark.sqlContext.implicits._

val cancerDF = cancerRDD.toDF().cache()

cancerDF.show() 


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

|cancer_class|thickness|size|shape|madh|epsize|bnuc|bchrom|nNuc|mit|

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

|         0.0|      5.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|

|         0.0|      5.0| 4.0|  4.0| 5.0|   7.0|10.0|   3.0| 2.0|1.0|

|         0.0|      3.0| 1.0|  1.0| 1.0|   2.0| 2.0|   3.0| 1.0|1.0|

|         0.0|      6.0| 8.0|  8.0| 1.0|   3.0| 4.0|   3.0| 7.0|1.0|

|         0.0|      4.0| 1.0|  1.0| 3.0|   2.0| 1.0|   3.0| 1.0|1.0|

|         1.0|      8.0|10.0| 10.0| 8.0|   7.0|10.0|   9.0| 7.0|1.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0|10.0|   3.0| 1.0|1.0|

|         0.0|      2.0| 1.0|  2.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|

|         0.0|      2.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|5.0|

|         0.0|      4.0| 2.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   1.0| 1.0|   3.0| 1.0|1.0|

|         0.0|      2.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|

|         1.0|      5.0| 3.0|  3.0| 3.0|   2.0| 3.0|   4.0| 4.0|1.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 3.0|   3.0| 1.0|1.0|

|         1.0|      8.0| 7.0|  5.0|10.0|   7.0| 9.0|   5.0| 5.0|4.0|

|         1.0|      7.0| 4.0|  6.0| 4.0|   6.0| 1.0|   4.0| 3.0|1.0|

|         0.0|      4.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|

|         0.0|      4.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|

|         1.0|     10.0| 7.0|  7.0| 6.0|   4.0|10.0|   4.0| 1.0|2.0|

|         0.0|      6.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|

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

only showing top 20 rows




피쳐를 추출하고 트랜스포메이션을 적용해보자.


먼저 다음처럼 피쳐 컬럼을 선택한다.


val featureCols = Array("thickness", "size", "shape", "madh", "epsize", "bnuc", "bchrom", "nNuc", "mit") 




이제 다음처럼 피쳐 컬럼(featureCols)을 피쳐 벡터(features)로 생성한다.


import org.apache.spark.ml.feature.VectorAssembler

val assembler = new VectorAssembler().setInputCols(featureCols).setOutputCol("features") 




이제 다음처럼 피쳐 벡터를 데이터 프레임으로 변환한다.



val df2 = assembler.transform(cancerDF) 

df2.show() 


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

|cancer_class|thickness|size|shape|madh|epsize|bnuc|bchrom|nNuc|mit|            features|

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

|         0.0|      5.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[5.0,1.0,1.0,1.0,...|

|         0.0|      5.0| 4.0|  4.0| 5.0|   7.0|10.0|   3.0| 2.0|1.0|[5.0,4.0,4.0,5.0,...|

|         0.0|      3.0| 1.0|  1.0| 1.0|   2.0| 2.0|   3.0| 1.0|1.0|[3.0,1.0,1.0,1.0,...|

|         0.0|      6.0| 8.0|  8.0| 1.0|   3.0| 4.0|   3.0| 7.0|1.0|[6.0,8.0,8.0,1.0,...|

|         0.0|      4.0| 1.0|  1.0| 3.0|   2.0| 1.0|   3.0| 1.0|1.0|[4.0,1.0,1.0,3.0,...|

|         1.0|      8.0|10.0| 10.0| 8.0|   7.0|10.0|   9.0| 7.0|1.0|[8.0,10.0,10.0,8....|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0|10.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|

|         0.0|      2.0| 1.0|  2.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[2.0,1.0,2.0,1.0,...|

|         0.0|      2.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|5.0|[2.0,1.0,1.0,1.0,...|

|         0.0|      4.0| 2.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[4.0,2.0,1.0,1.0,...|

|         0.0|      1.0| 1.0|  1.0| 1.0|   1.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|

|         0.0|      2.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[2.0,1.0,1.0,1.0,...|

|         1.0|      5.0| 3.0|  3.0| 3.0|   2.0| 3.0|   4.0| 4.0|1.0|[5.0,3.0,3.0,3.0,...|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 3.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|

|         1.0|      8.0| 7.0|  5.0|10.0|   7.0| 9.0|   5.0| 5.0|4.0|[8.0,7.0,5.0,10.0...|

|         1.0|      7.0| 4.0|  6.0| 4.0|   6.0| 1.0|   4.0| 3.0|1.0|[7.0,4.0,6.0,4.0,...|

|         0.0|      4.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[4.0,1.0,1.0,1.0,...|

|         0.0|      4.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[4.0,1.0,1.0,1.0,...|

|         1.0|     10.0| 7.0|  7.0| 6.0|   4.0|10.0|   4.0| 1.0|2.0|[10.0,7.0,7.0,6.0...|

|         0.0|      6.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[6.0,1.0,1.0,1.0,...|

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




이제 왼쪽 컬럼을 기반으로 계산된 피쳐가 포함된 데이터 프레임을 관찰해야 한다.




마지막으로 StringIndexer를 사용하고 다음처럼 트레이닝 데이터셋 레이블을 생성한다.


import org.apache.spark.ml.feature.StringIndexer

val labelIndexer = new StringIndexer().setInputCol("cancer_class").setOutputCol("label")

val df3 = labelIndexer.fit(df2).transform(df2)

df3.show() 


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

|cancer_class|thickness|size|shape|madh|epsize|bnuc|bchrom|nNuc|mit|            features|label|

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

|         0.0|      5.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[5.0,1.0,1.0,1.0,...|  0.0|

|         0.0|      5.0| 4.0|  4.0| 5.0|   7.0|10.0|   3.0| 2.0|1.0|[5.0,4.0,4.0,5.0,...|  0.0|

|         0.0|      3.0| 1.0|  1.0| 1.0|   2.0| 2.0|   3.0| 1.0|1.0|[3.0,1.0,1.0,1.0,...|  0.0|

|         0.0|      6.0| 8.0|  8.0| 1.0|   3.0| 4.0|   3.0| 7.0|1.0|[6.0,8.0,8.0,1.0,...|  0.0|

|         0.0|      4.0| 1.0|  1.0| 3.0|   2.0| 1.0|   3.0| 1.0|1.0|[4.0,1.0,1.0,3.0,...|  0.0|

|         1.0|      8.0|10.0| 10.0| 8.0|   7.0|10.0|   9.0| 7.0|1.0|[8.0,10.0,10.0,8....|  1.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0|10.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|

|         0.0|      2.0| 1.0|  2.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[2.0,1.0,2.0,1.0,...|  0.0|

|         0.0|      2.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|5.0|[2.0,1.0,1.0,1.0,...|  0.0|

|         0.0|      4.0| 2.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[4.0,2.0,1.0,1.0,...|  0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   1.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|

|         0.0|      2.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[2.0,1.0,1.0,1.0,...|  0.0|

|         1.0|      5.0| 3.0|  3.0| 3.0|   2.0| 3.0|   4.0| 4.0|1.0|[5.0,3.0,3.0,3.0,...|  1.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 3.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|

|         1.0|      8.0| 7.0|  5.0|10.0|   7.0| 9.0|   5.0| 5.0|4.0|[8.0,7.0,5.0,10.0...|  1.0|

|         1.0|      7.0| 4.0|  6.0| 4.0|   6.0| 1.0|   4.0| 3.0|1.0|[7.0,4.0,6.0,4.0,...|  1.0|

|         0.0|      4.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[4.0,1.0,1.0,1.0,...|  0.0|

|         0.0|      4.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[4.0,1.0,1.0,1.0,...|  0.0|

|         1.0|     10.0| 7.0|  7.0| 6.0|   4.0|10.0|   4.0| 1.0|2.0|[10.0,7.0,7.0,6.0...|  1.0|

|         0.0|      6.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[6.0,1.0,1.0,1.0,...|  0.0|

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




이제 왼쪽 컬럼을 기반으로 계산된 피쳐와 레이블이 포함된 데이터 프레임을 살펴보자.



머신 러닝 모델을 트레이닝하기 위한 피쳐와 레이블이 포함된 새로운 데이터 프레임을 생성할 것이다. 


먼저 테스트 셋과 트레이닝 셋(3:7)을 생성한다. (지도 학습이라..)



val splitSeed = 1234567

val Array(trainingData, testData) = df3.randomSplit(Array(0.7, 0.3), splitSeed)




이제 트레이닝 셋을 사용하여 에스티메이터 생성한다. elasticNetParam을 이용한 로지스틱 회귀 분석을 사용해 파이프 라인에 대한 에스티메이터를 생성하자. 다음처럼 최대 반복과 회귀 매개 변수도 지정한다.



import org.apache.spark.ml.classification.LogisticRegression

val lr = new LogisticRegression().setMaxIter(50).setRegParam(0.01).setElasticNetParam(0.01)

val model = lr.fit(trainingData)  




테스트 셋으로 원본 예측, 확률, 예측을 얻기 위해 테스트 셋을 사용해 모델을 변환한다. 



val predictions = model.transform(testData)

predictions.show() 


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

|cancer_class|thickness|size|shape|madh|epsize|bnuc|bchrom|nNuc|mit|            features|label|       rawPrediction|         probability|prediction|

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

|         0.0|      1.0| 1.0|  1.0| 1.0|   1.0| 1.0|   2.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.15956430979038...|[0.99428860556932...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   1.0| 1.0|   2.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.15956430979038...|[0.99428860556932...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   1.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.88229871718381...|[0.99247744702488...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   1.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.88229871718381...|[0.99247744702488...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.26929960916807...|[0.99487914377217...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.26929960916807...|[0.99487914377217...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.26929960916807...|[0.99487914377217...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.26929960916807...|[0.99487914377217...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.26929960916807...|[0.99487914377217...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[5.26929960916807...|[0.99487914377217...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.99203401656150...|[0.99325398211858...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.99203401656150...|[0.99325398211858...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   2.0| 3.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.74802132478210...|[0.99140567173413...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.71476842395493...|[0.99111766179519...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.71476842395493...|[0.99111766179519...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.71476842395493...|[0.99111766179519...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.71476842395493...|[0.99111766179519...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 1.0|   3.0| 2.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.59276207806523...|[0.98997663106901...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   2.0| 5.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.10129026316119...|[0.98371817931939...|       0.0|

|         0.0|      1.0| 1.0|  1.0| 1.0|   4.0| 3.0|   1.0| 1.0|1.0|[1.0,1.0,1.0,1.0,...|  0.0|[4.35023434970686...|[0.98726059831436...|       0.0|

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


이제 원본 예측과 각 로우에 대한 실제 예측이 포함된 새로운 데이터 프레임을 볼 수 있다.



다음처럼 트레이닝의 객관적인 기록 생성을 위해 각 반복마다 모델의 목표 이력을 생성하자.


val trainingSummary = model.summary

val objectiveHistory = trainingSummary.objectiveHistory

objectiveHistory.foreach(loss => println(loss))




이전 코드는 트레이닝 손실 측면에서 다음과 같은 출력을 생성한다.


   0.6562291876496595

   0.6087867761081431

   0.538972588904556

   0.4928455913405332

   0.46269258074999386

   0.3527914819973198

   0.20206901337404978

   0.16459454874996993

   0.13783437051276512

   0.11478053164710095

   0.11420433621438157

   0.11138884788059378

   0.11041889032338036

   0.10849477236373875

   0.10818880537879513

   0.10682868640074723

   0.10641395229253267

   0.10555411704574749

   0.10505186414044905

   0.10470425580130915

   0.10376219754747162

   0.10331139609033112

   0.10276173290225406

   0.10245982201904923

   0.10198833366394071

   0.10168248313103552

   0.10163242551955443

   0.10162826209311404

   0.10162119367292953

   0.10161235376791203

   0.1016114803209495

   0.10161090505556039

   0.1016107261254795

   0.10161056082112738

   0.10161050381332608

   0.10161048515341387

   0.10161043900301985

   0.10161042057436288

   0.10161040971267737

   0.10161040846923354

   0.10161040625542347

   0.10161040595207525

   0.10161040575664354

   0.10161040565870835

   0.10161040519559975

   0.10161040489834573

   0.10161040445215266

   0.1016104043469577

   0.1016104042793553

   0.1016104042606048

   0.10161040423579716 




이전 결과를 살펴보면 반복하면거 결과 값이 점차 줄어드는 것을 볼 수 있다.



이제 이진 로직 회귀 요약에서 사용한 분류자인지 확인해야 한다.


import org.apache.spark.ml.classification.BinaryLogisticRegressionSummary

val binarySummary = trainingSummary.asInstanceOf[BinaryLogisticRegressionSummary]




이제 ROC를 데이터 프레임으로 얻고 areaUnderROC로 가져온다. 근사값이 1.0이면 더 좋다.


val roc = binarySummary.roc

roc.show()


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

|FPR|                 TPR|

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

|0.0|                 0.0|

|0.0|0.017341040462427744|

|0.0| 0.03468208092485549|

|0.0| 0.05202312138728324|

|0.0| 0.06936416184971098|

|0.0| 0.08670520231213873|

|0.0| 0.10404624277456648|

|0.0| 0.12138728323699421|

|0.0| 0.13872832369942195|

|0.0| 0.15606936416184972|

|0.0| 0.17341040462427745|

|0.0|  0.1907514450867052|

|0.0| 0.20809248554913296|

|0.0|  0.2254335260115607|

|0.0| 0.24277456647398843|

|0.0| 0.26011560693641617|

|0.0|  0.2774566473988439|

|0.0|  0.2947976878612717|

|0.0| 0.31213872832369943|

|0.0| 0.32947976878612717|

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





areaUnderROC의 값을 출력한다.


println("binarySummary.areaUnderROC)

0.9960056075125305




이제 참 긍정 비율, 거짓 긍정 비율, 거짓 부정 비율, 전체 개수와 같은 메트릭과 다음처럼 제대로 예측하고 잘못 예측한 경우의 수를 계산하자. 


import org.apache.spark.sql.functions._


// 성능 메트릭을 계산한다

val lp = predictions.select("label", "prediction")

val counttotal = predictions.count()

val correct = lp.filter($"label" === $"prediction").count()

val wrong = lp.filter(not($"label" === $"prediction")).count()

val truep = lp.filter($"prediction" === 0.0).filter($"label" === $"prediction").count()

val falseN = lp.filter($"prediction" === 0.0).filter(not($"label" === $"prediction")).count()

val falseP = lp.filter($"prediction" === 1.0).filter(not($"label" === $"prediction")).count()

val ratioWrong = wrong.toDouble / counttotal.toDouble

val ratioCorrect = correct.toDouble / counttotal.toDouble


println("Total Count: " + counttotal)

println("Correctly Predicted: " + correct)

println("Wrongly Identified: " + wrong)

println("True Positive: " + truep)

println("False Negative: " + falseN)

println("False Positive: " + falseP)

println("ratioWrong: " + ratioWrong)

println("ratioCorrect: " + ratioCorrect) 



결과는 다음과 같다. 


Total Count: 209

Correctly Predicted: 202

Wrongly Identified: 7

True Positive: 140

False Negative: 4

False Positive: 3

ratioWrong: 0.03349282296650718

ratioCorrect: 0.9665071770334929





마지막으로 모델의 정확도를 판단하자. 그러나 먼저 fMeasure를 최대화하기 위해 모델 임계 값을 설정해야 한다.


val fMeasure = binarySummary.fMeasureByThreshold

val fm = fMeasure.col("F-Measure")

val maxFMeasure = fMeasure.select(max("F-Measure")).head().getDouble(0)

val bestThreshold = fMeasure.where($"F-Measure" === maxFMeasure).select("threshold").head().getDouble(0)

model.setThreshold(bestThreshold) 




이제 다음처럼 정확도를 계산하자.


import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator

val evaluator = new BinaryClassificationEvaluator().setLabelCol("label")

val accuracy = evaluator.evaluate(predictions)

println("Accuracy: " + accuracy)     



정확도는 다음처럼 거의 99.64%이다. 


Accuracy: 0.9963975418520874



Posted by '김용환'
,



https://www.openstack.org/summit/vancouver-2018/summit-schedule/global-search?t=Yuki%20Nishiwaki





Excitingly simple multi-path OpenStack networking: LAG-less, L2-less, yet fully redundant from LINE Corporation


Posted by '김용환'
,
2018년 5월에 일본 자바 유저 그룹(http://www.java-users.jp/ccc2018spring/#/) 세미나가 있었고..
라인 개발자들이 발표한 자료(http://www.java-users.jp/ccc2018spring/#/sessions)가 있었다.






이외 라인에 잼난 slideshare 자료가 있어서 공유한다.




2018.5에 올라온 라인 레디스 자료 















참고 

2015.5 월 발표 자료



Posted by '김용환'
,