평가 지표

평가 지표 (Evaluation Metrics)

spark.mllib는 데이터에서 학습하고 예측하는 데 쓸 수 있는 다양한 머신러닝 알고리즘을 포함해요. 이 알고리즘을 머신러닝 모델을 만들기 위해 적용할 때, 애플리케이션과 그 요구사항에 따라 정해지는 어떤 기준으로 모델의 성능을 평가할 필요가 있어요. spark.mllib는 머신러닝 모델의 성능을 평가하기 위한 지표 집합도 제공해요.

특정 머신러닝 알고리즘들은 분류, 회귀, 클러스터링 같은 더 넓은 머신러닝 애플리케이션 유형에 속해요. 각 유형에는 성능 평가를 위한 확립된 지표가 있으며, spark.mllib에서 현재 사용 가능한 그 지표들을 이 섹션에서 자세히 설명할게요.

출처: Evaluation Metrics - RDD-based API

본문

분류 모델 평가

분류 알고리즘에는 많은 유형이 있지만, 분류 모델의 평가는 모두 비슷한 원리를 공유해요. 지도 분류 문제에서는 각 데이터 포인트에 대해 진짜 출력과 모델이 생성한 예측 출력이 존재해요. 이 때문에 각 데이터 포인트의 결과는 네 가지 범주 중 하나로 할당될 수 있어요.

  • True Positive(TP) — 레이블이 양성이고 예측도 양성
  • True Negative(TN) — 레이블이 음성이고 예측도 음성
  • False Positive(FP) — 레이블이 음성이지만 예측은 양성
  • False Negative(FN) — 레이블이 양성이지만 예측은 음성

이 네 숫자는 대부분의 분류기 평가 지표의 구성 요소예요. 분류기 평가를 고려할 때 중요한 점은 순수 정확도(즉, 예측이 맞았는지 틀렸는지)가 일반적으로 좋은 지표가 아니라는 거예요. 그 이유는 데이터셋이 매우 불균형할 수 있기 때문이에요. 예를 들어 데이터 포인트의 95%가 사기가 아니고 5%가 사기인 데이터셋에서 사기를 예측하도록 설계된 모델이라면, 입력에 관계없이 사기 아님을 예측하는 순진한 분류기도 95% 정확할 거예요. 이런 이유로 보통 정밀도와 재현율(precision and recall) 같은 지표가 사용돼요. 그것들이 오류의 유형을 고려하기 때문이에요. 대부분의 애플리케이션에서 정밀도와 재현율 사이에는 어떤 원하는 균형이 있고, 이는 두 개를 F-measure라는 단일 지표로 결합해 포착할 수 있어요.

이진 분류(Binary classification)

이진 분류기는 주어진 데이터셋의 요소를 두 가능한 그룹(예: 사기 또는 사기 아님) 중 하나로 분리하는 데 쓰이며, 다중 클래스 분류의 특수한 경우예요. 대부분의 이진 분류 지표는 다중 클래스 분류 지표로 일반화될 수 있어요.

임계값 튜닝(Threshold tuning)

많은 분류 모델이 실제로 각 클래스에 대한 "점수"(흔히 확률)를 출력하며, 점수가 높을수록 가능성이 높음을 나타낸다는 것을 이해하는 것이 중요해요. 이진 경우 모델은 각 클래스에 대한 확률 $P(Y=1|X)$와 $P(Y=0|X)$를 출력할 수 있어요. 단순히 더 높은 확률을 취하는 대신, 모델이 확률이 매우 높을 때만 클래스를 예측하도록 튜닝해야 하는 경우가 있을 수 있어요(예: 모델이 >90% 확률로 사기를 예측할 때만 신용카드 거래를 차단). 따라서 모델이 출력하는 확률에 기반해 예측 클래스가 무엇이 될지 결정하는 예측 *임계값(threshold)*이 있어요.

예측 임계값을 튜닝하면 모델의 정밀도와 재현율이 바뀌며, 이는 모델 최적화의 중요한 부분이에요. 정밀도, 재현율, 기타 지표가 임계값의 함수로 어떻게 변하는지 시각화하기 위해, 임계값을 파라미터로 삼아 경쟁 지표들을 서로 대조해 플롯하는 것이 일반적인 관행이에요. P-R 곡선은 서로 다른 임계값에 대한 (정밀도, 재현율) 점을 그리고, receiver operating characteristic(ROC) 곡선은 (재현율, 위양성 비율) 점을 그려요.

사용 가능한 지표

지표 정의
Precision (Positive Predictive Value) $PPV=\frac{TP}{TP + FP}$
Recall (True Positive Rate) $TPR=\frac{TP}{P}=\frac{TP}{TP + FN}$
F-measure $F(\beta) = \left(1 + \beta^2\right) \cdot \left(\frac{PPV \cdot TPR} {\beta^2 \cdot PPV + TPR}\right)$
Receiver Operating Characteristic (ROC) $FPR(T)=\int^\infty_{T} P_0(T),dT \ TPR(T)=\int^\infty_{T} P_1(T),dT$
Area Under ROC Curve $AUROC=\int^1_{0} \frac{TP}{P} d\left(\frac{FP}{N}\right)$
Area Under Precision-Recall Curve $AUPRC=\int^1_{0} \frac{TP}{TP+FP} d\left(\frac{TP}{P}\right)$

예제

다음 코드 스니펫은 샘플 데이터셋을 로드하고, 데이터에서 이진 분류 알고리즘을 훈련하며, 여러 이진 평가 지표로 알고리즘의 성능을 평가하는 방법을 보여줘요.

API에 대한 자세한 내용은 BinaryClassificationMetrics Python 문서LogisticRegressionWithLBFGS Python 문서를 참고하세요.

from pyspark.mllib.classification import LogisticRegressionWithLBFGS
from pyspark.mllib.evaluation import BinaryClassificationMetrics
from pyspark.mllib.util import MLUtils

# Several of the methods available in scala are currently missing from pyspark
# Load training data in LIBSVM format
data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_binary_classification_data.txt")

# Split data into training (60%) and test (40%)
training, test = data.randomSplit([0.6, 0.4], seed=11)
training.cache()

# Run training algorithm to build the model
model = LogisticRegressionWithLBFGS.train(training)

# Compute raw scores on the test set
predictionAndLabels = test.map(lambda lp: (float(model.predict(lp.features)), lp.label))

# Instantiate metrics object
metrics = BinaryClassificationMetrics(predictionAndLabels)

# Area under precision-recall curve
print("Area under PR = %s" % metrics.areaUnderPR)

# Area under ROC curve
print("Area under ROC = %s" % metrics.areaUnderROC)

전체 예제 코드는 Spark 저장소의 "examples/src/main/python/mllib/binary_classification_metrics_example.py"에서 찾을 수 있어요.

API에 대한 자세한 내용은 LogisticRegressionWithLBFGS Scala 문서BinaryClassificationMetrics Scala 문서를 참고하세요.

import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS
import org.apache.spark.mllib.evaluation.BinaryClassificationMetrics
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils

// Load training data in LIBSVM format
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_binary_classification_data.txt")

// Split data into training (60%) and test (40%)
val Array(training, test) = data.randomSplit(Array(0.6, 0.4), seed = 11L)
training.cache()

// Run training algorithm to build the model
val model = new LogisticRegressionWithLBFGS()
  .setNumClasses(2)
  .run(training)

// Clear the prediction threshold so the model will return probabilities
model.clearThreshold()

// Compute raw scores on the test set
val predictionAndLabels = test.map { case LabeledPoint(label, features) =>
  val prediction = model.predict(features)
  (prediction, label)
}

// Instantiate metrics object
val metrics = new BinaryClassificationMetrics(predictionAndLabels)

// Precision by threshold
val precision = metrics.precisionByThreshold()
precision.collect().foreach { case (t, p) =>
  println(s"Threshold: $t, Precision: $p")
}

// Recall by threshold
val recall = metrics.recallByThreshold()
recall.collect().foreach { case (t, r) =>
  println(s"Threshold: $t, Recall: $r")
}

// Precision-Recall Curve
val PRC = metrics.pr()

// F-measure
val f1Score = metrics.fMeasureByThreshold()
f1Score.collect().foreach { case (t, f) =>
  println(s"Threshold: $t, F-score: $f, Beta = 1")
}

val beta = 0.5
val fScore = metrics.fMeasureByThreshold(beta)
fScore.collect().foreach { case (t, f) =>
  println(s"Threshold: $t, F-score: $f, Beta = 0.5")
}

// AUPRC
val auPRC = metrics.areaUnderPR()
println(s"Area under precision-recall curve = $auPRC")

// Compute thresholds used in ROC and PR curves
val thresholds = precision.map(_._1)

// ROC Curve
val roc = metrics.roc()

// AUROC
val auROC = metrics.areaUnderROC()
println(s"Area under ROC = $auROC")

전체 예제 코드는 Spark 저장소의 "examples/src/main/scala/org/apache/spark/examples/mllib/BinaryClassificationMetricsExample.scala"에서 찾을 수 있어요.

API에 대한 자세한 내용은 LogisticRegressionModel Java 문서LogisticRegressionWithLBFGS Java 문서를 참고하세요.

import scala.Tuple2;

import org.apache.spark.api.java.*;
import org.apache.spark.mllib.classification.LogisticRegressionModel;
import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS;
import org.apache.spark.mllib.evaluation.BinaryClassificationMetrics;
import org.apache.spark.mllib.regression.LabeledPoint;
import org.apache.spark.mllib.util.MLUtils;

String path = "data/mllib/sample_binary_classification_data.txt";
JavaRDD<LabeledPoint> data = MLUtils.loadLibSVMFile(sc, path).toJavaRDD();

// Split initial RDD into two... [60% training data, 40% testing data].
JavaRDD<LabeledPoint>[] splits =
  data.randomSplit(new double[]{0.6, 0.4}, 11L);
JavaRDD<LabeledPoint> training = splits[0].cache();
JavaRDD<LabeledPoint> test = splits[1];

// Run training algorithm to build the model.
LogisticRegressionModel model = new LogisticRegressionWithLBFGS()
  .setNumClasses(2)
  .run(training.rdd());

// Clear the prediction threshold so the model will return probabilities
model.clearThreshold();

// Compute raw scores on the test set.
JavaPairRDD<Object, Object> predictionAndLabels = test.mapToPair(p ->
  new Tuple2<>(model.predict(p.features()), p.label()));

// Get evaluation metrics.
BinaryClassificationMetrics metrics =
  new BinaryClassificationMetrics(predictionAndLabels.rdd());

// Precision by threshold
JavaRDD<Tuple2<Object, Object>> precision = metrics.precisionByThreshold().toJavaRDD();
System.out.println("Precision by threshold: " + precision.collect());

// Recall by threshold
JavaRDD<?> recall = metrics.recallByThreshold().toJavaRDD();
System.out.println("Recall by threshold: " + recall.collect());

// F Score by threshold
JavaRDD<?> f1Score = metrics.fMeasureByThreshold().toJavaRDD();
System.out.println("F1 Score by threshold: " + f1Score.collect());

JavaRDD<?> f2Score = metrics.fMeasureByThreshold(2.0).toJavaRDD();
System.out.println("F2 Score by threshold: " + f2Score.collect());

// Precision-recall curve
JavaRDD<?> prc = metrics.pr().toJavaRDD();
System.out.println("Precision-recall curve: " + prc.collect());

// Thresholds
JavaRDD<Double> thresholds = precision.map(t -> Double.parseDouble(t._1().toString()));

// ROC Curve
JavaRDD<?> roc = metrics.roc().toJavaRDD();
System.out.println("ROC curve: " + roc.collect());

// AUPRC
System.out.println("Area under precision-recall curve = " + metrics.areaUnderPR());

// AUROC
System.out.println("Area under ROC = " + metrics.areaUnderROC());

// Save and load model
model.save(sc, "target/tmp/LogisticRegressionModel");
LogisticRegressionModel.load(sc, "target/tmp/LogisticRegressionModel");

전체 예제 코드는 Spark 저장소의 "examples/src/main/java/org/apache/spark/examples/mllib/JavaBinaryClassificationMetricsExample.java"에서 찾을 수 있어요.

다중 클래스 분류(Multiclass classification)

다중 클래스 분류는 각 데이터 포인트에 대해 $M \gt 2$개의 가능한 레이블이 있는 분류 문제를 설명해요($M=2$인 경우가 이진 분류 문제예요). 예를 들어 손글씨 샘플을 숫자 0~9로 분류하는 것, 10개의 가능한 클래스가 있는 경우가 있어요.

다중 클래스 지표에서 양성과 음성의 개념은 약간 달라요. 예측과 레이블은 여전히 양성 또는 음성이 될 수 있지만, 특정 클래스의 맥락에서 고려돼야 해요. 각 레이블과 예측은 여러 클래스 중 하나의 값을 취하므로, 특정 클래스에 대해서는 양성이고 다른 모든 클래스에 대해서는 음성이라고 해요. 따라서 예측과 레이블이 일치할 때마다 true positive가 발생하고, 예측도 레이블도 주어진 클래스의 값을 취하지 않을 때 true negative가 발생해요. 이 규칙에 따라 주어진 데이터 샘플에 대해 여러 true negative가 있을 수 있어요. 양성·음성 레이블의 이전 정의에서 false negative와 false positive의 확장은 간단해요.

레이블 기반 지표(Label based metrics)

가능한 레이블이 두 개뿐인 이진 분류와 달리 다중 클래스 분류 문제는 가능한 레이블이 많으므로 레이블 기반 지표라는 개념이 도입돼요. 정확도는 모든 레이블에 걸친 정밀도를 측정해요 — 어떤 클래스를 올바르게 예측한 횟수(true positives)를 데이터 포인트 수로 정규화한 값이에요. 레이블별 정밀도는 하나의 클래스만 고려해, 특정 레이블이 올바르게 예측된 횟수를 그 레이블이 출력에 나타나는 횟수로 정규화해 측정해요.

사용 가능한 지표

클래스(또는 레이블) 집합을 다음과 같이 정의해요.

\[L = {\ell_0, \ell_1, \ldots, \ell_{M-1} }\]

진짜 출력 벡터 $\mathbf{y}$는 $N$개의 요소로 구성돼요.

\[\mathbf{y}_0, \mathbf{y}1, \ldots, \mathbf{y}{N-1} \in L\]

다중 클래스 예측 알고리즘은 $N$개 요소의 예측 벡터 $\hat{\mathbf{y}}$를 생성해요.

\[\hat{\mathbf{y}}_0, \hat{\mathbf{y}}1, \ldots, \hat{\mathbf{y}}{N-1} \in L\]

이 섹션에서 수정된 델타 함수 $\hat{\delta}(x)$가 유용할 거예요.

\[\hat{\delta}(x) = \begin{cases}1 & \text{if $x = 0$}, \ 0 & \text{otherwise}.\end{cases}\]

지표 정의
Confusion Matrix $C_{ij} = \sum_{k=0}^{N-1} \hat{\delta}(\mathbf{y}_k-\ell_i) \cdot \hat{\delta}(\hat{\mathbf{y}}k - \ell_j)\\ \ \left( \begin{array}{ccc} \sum{k=0}^{N-1} \hat{\delta}(\mathbf{y}_k-\ell_1) \cdot \hat{\delta}(\hat{\mathbf{y}}k - \ell_1) & \ldots & \sum{k=0}^{N-1} \hat{\delta}(\mathbf{y}_k-\ell_1) \cdot \hat{\delta}(\hat{\mathbf{y}}k - \ell_N) \ \vdots & \ddots & \vdots \ \sum{k=0}^{N-1} \hat{\delta}(\mathbf{y}_k-\ell_N) \cdot \hat{\delta}(\hat{\mathbf{y}}k - \ell_1) & \ldots & \sum{k=0}^{N-1} \hat{\delta}(\mathbf{y}_k-\ell_N) \cdot \hat{\delta}(\hat{\mathbf{y}}_k - \ell_N) \end{array} \right)$
Accuracy $ACC = \frac{TP}{TP + FP} = \frac{1}{N}\sum_{i=0}^{N-1} \hat{\delta}\left(\hat{\mathbf{y}}_i - \mathbf{y}_i\right)$
Precision by label $PPV(\ell) = \frac{TP}{TP + FP} = \frac{\sum_{i=0}^{N-1} \hat{\delta}(\hat{\mathbf{y}}_i - \ell) \cdot \hat{\delta}(\mathbf{y}i - \ell)} {\sum{i=0}^{N-1} \hat{\delta}(\hat{\mathbf{y}}_i - \ell)}$
Recall by label $TPR(\ell)=\frac{TP}{P} = \frac{\sum_{i=0}^{N-1} \hat{\delta}(\hat{\mathbf{y}}_i - \ell) \cdot \hat{\delta}(\mathbf{y}i - \ell)} {\sum{i=0}^{N-1} \hat{\delta}(\mathbf{y}_i - \ell)}$
F-measure by label $F(\beta, \ell) = \left(1 + \beta^2\right) \cdot \left(\frac{PPV(\ell) \cdot TPR(\ell)} {\beta^2 \cdot PPV(\ell) + TPR(\ell)}\right)$
Weighted precision $PPV_{w}= \frac{1}{N} \sum\nolimits_{\ell \in L} PPV(\ell) \cdot \sum_{i=0}^{N-1} \hat{\delta}(\mathbf{y}_i-\ell)$
Weighted recall $TPR_{w}= \frac{1}{N} \sum\nolimits_{\ell \in L} TPR(\ell) \cdot \sum_{i=0}^{N-1} \hat{\delta}(\mathbf{y}_i-\ell)$
Weighted F-measure $F_{w}(\beta)= \frac{1}{N} \sum\nolimits_{\ell \in L} F(\beta, \ell) \cdot \sum_{i=0}^{N-1} \hat{\delta}(\mathbf{y}_i-\ell)$

예제

다음 코드 스니펫은 샘플 데이터셋을 로드하고, 데이터에서 다중 클래스 분류 알고리즘을 훈련하며, 여러 다중 클래스 분류 평가 지표로 알고리즘의 성능을 평가하는 방법을 보여줘요.

API에 대한 자세한 내용은 MulticlassMetrics Python 문서를 참고하세요.

from pyspark.mllib.classification import LogisticRegressionWithLBFGS
from pyspark.mllib.util import MLUtils
from pyspark.mllib.evaluation import MulticlassMetrics

# Load training data in LIBSVM format
data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_multiclass_classification_data.txt")

# Split data into training (60%) and test (40%)
training, test = data.randomSplit([0.6, 0.4], seed=11)
training.cache()

# Run training algorithm to build the model
model = LogisticRegressionWithLBFGS.train(training, numClasses=3)

# Compute raw scores on the test set
predictionAndLabels = test.map(lambda lp: (float(model.predict(lp.features)), lp.label))

# Instantiate metrics object
metrics = MulticlassMetrics(predictionAndLabels)

# Overall statistics
precision = metrics.precision(1.0)
recall = metrics.recall(1.0)
f1Score = metrics.fMeasure(1.0)
print("Summary Stats")
print("Precision = %s" % precision)
print("Recall = %s" % recall)
print("F1 Score = %s" % f1Score)

# Statistics by class
labels = data.map(lambda lp: lp.label).distinct().collect()
for label in sorted(labels):
    print("Class %s precision = %s" % (label, metrics.precision(label)))
    print("Class %s recall = %s" % (label, metrics.recall(label)))
    print("Class %s F1 Measure = %s" % (label, metrics.fMeasure(label, beta=1.0)))

# Weighted stats
print("Weighted recall = %s" % metrics.weightedRecall)
print("Weighted precision = %s" % metrics.weightedPrecision)
print("Weighted F(1) Score = %s" % metrics.weightedFMeasure())
print("Weighted F(0.5) Score = %s" % metrics.weightedFMeasure(beta=0.5))
print("Weighted false positive rate = %s" % metrics.weightedFalsePositiveRate)

전체 예제 코드는 Spark 저장소의 "examples/src/main/python/mllib/multi_class_metrics_example.py"에서 찾을 수 있어요.

API에 대한 자세한 내용은 MulticlassMetrics Scala 문서를 참고하세요.

import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS
import org.apache.spark.mllib.evaluation.MulticlassMetrics
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils

// Load training data in LIBSVM format
val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_multiclass_classification_data.txt")

// Split data into training (60%) and test (40%)
val Array(training, test) = data.randomSplit(Array(0.6, 0.4), seed = 11L)
training.cache()

// Run training algorithm to build the model
val model = new LogisticRegressionWithLBFGS()
  .setNumClasses(3)
  .run(training)

// Compute raw scores on the test set
val predictionAndLabels = test.map { case LabeledPoint(label, features) =>
  val prediction = model.predict(features)
  (prediction, label)
}

// Instantiate metrics object
val metrics = new MulticlassMetrics(predictionAndLabels)

// Confusion matrix
println("Confusion matrix:")
println(metrics.confusionMatrix)

// Overall Statistics
val accuracy = metrics.accuracy
println("Summary Statistics")
println(s"Accuracy = $accuracy")

// Precision by label
val labels = metrics.labels
labels.foreach { l =>
  println(s"Precision($l) = " + metrics.precision(l))
}

// Recall by label
labels.foreach { l =>
  println(s"Recall($l) = " + metrics.recall(l))
}

// False positive rate by label
labels.foreach { l =>
  println(s"FPR($l) = " + metrics.falsePositiveRate(l))
}

// F-measure by label
labels.foreach { l =>
  println(s"F1-Score($l) = " + metrics.fMeasure(l))
}

// Weighted stats
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}")

전체 예제 코드는 Spark 저장소의 "examples/src/main/scala/org/apache/spark/examples/mllib/MulticlassMetricsExample.scala"에서 찾을 수 있어요.

API에 대한 자세한 내용은 MulticlassMetrics Java 문서를 참고하세요.

import scala.Tuple2;

import org.apache.spark.api.java.*;
import org.apache.spark.mllib.classification.LogisticRegressionModel;
import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS;
import org.apache.spark.mllib.evaluation.MulticlassMetrics;
import org.apache.spark.mllib.regression.LabeledPoint;
import org.apache.spark.mllib.util.MLUtils;
import org.apache.spark.mllib.linalg.Matrix;

String path = "data/mllib/sample_multiclass_classification_data.txt";
JavaRDD<LabeledPoint> data = MLUtils.loadLibSVMFile(sc, path).toJavaRDD();

// Split initial RDD into two... [60% training data, 40% testing data].
JavaRDD<LabeledPoint>[] splits = data.randomSplit(new double[]{0.6, 0.4}, 11L);
JavaRDD<LabeledPoint> training = splits[0].cache();
JavaRDD<LabeledPoint> test = splits[1];

// Run training algorithm to build the model.
LogisticRegressionModel model = new LogisticRegressionWithLBFGS()
  .setNumClasses(3)
  .run(training.rdd());

// Compute raw scores on the test set.
JavaPairRDD<Object, Object> predictionAndLabels = test.mapToPair(p ->
  new Tuple2<>(model.predict(p.features()), p.label()));

// Get evaluation metrics.
MulticlassMetrics metrics = new MulticlassMetrics(predictionAndLabels.rdd());

// Confusion matrix
Matrix confusion = metrics.confusionMatrix();
System.out.println("Confusion matrix: \n" + confusion);

// Overall statistics
System.out.println("Accuracy = " + metrics.accuracy());

// Stats by labels
for (int i = 0; i < metrics.labels().length; i++) {
  System.out.format("Class %f precision = %f\n", metrics.labels()[i],metrics.precision(
    metrics.labels()[i]));
  System.out.format("Class %f recall = %f\n", metrics.labels()[i], metrics.recall(
    metrics.labels()[i]));
  System.out.format("Class %f F1 score = %f\n", metrics.labels()[i], metrics.fMeasure(
    metrics.labels()[i]));
}

//Weighted stats
System.out.format("Weighted precision = %f\n", metrics.weightedPrecision());
System.out.format("Weighted recall = %f\n", metrics.weightedRecall());
System.out.format("Weighted F1 score = %f\n", metrics.weightedFMeasure());
System.out.format("Weighted false positive rate = %f\n", metrics.weightedFalsePositiveRate());

// Save and load model
model.save(sc, "target/tmp/LogisticRegressionModel");
LogisticRegressionModel sameModel = LogisticRegressionModel.load(sc,
  "target/tmp/LogisticRegressionModel");

전체 예제 코드는 Spark 저장소의 "examples/src/main/java/org/apache/spark/examples/mllib/JavaMulticlassClassificationMetricsExample.java"에서 찾을 수 있어요.

다중 레이블 분류(Multilabel classification)

다중 레이블 분류 문제는 데이터셋의 각 샘플을 클래스 레이블 집합에 매핑하는 것을 포함해요. 이 유형의 분류 문제에서 레이블은 상호 배타적이지 않아요. 예를 들어 뉴스 기사 집합을 주제로 분류할 때, 단일 기사가 과학이면서 정치일 수 있어요.

레이블이 상호 배타적이지 않기 때문에 예측과 진짜 레이블은 이제 레이블 집합의 벡터이고, 레이블 벡터가 아니에요. 따라서 다중 레이블 지표는 정밀도, 재현율 등의 기본 아이디어를 집합에 대한 연산으로 확장해요. 예를 들어 주어진 클래스의 true positive는 특정 데이터 포인트에 대해 그 클래스가 예측 집합에 있고 진짜 레이블 집합에도 있을 때 발생해요.

사용 가능한 지표

여기서 $N$개의 문서 집합 $D$를 정의해요.

\[D = \left{d_0, d_1, ..., d_{N-1}\right}\]

$L_0, L_1, …, L_{N-1}$을 레이블 집합의 패밀리로, $P_0, P_1, …, P_{N-1}$을 예측 집합의 패밀리로 정의해요. 여기서 $L_i$와 $P_i$는 각각 문서 $d_i$에 해당하는 레이블 집합과 예측 집합이에요.

모든 고유 레이블의 집합은 다음과 같아요.

\[L = \bigcup_{k=0}^{N-1} L_k\]

집합 $A$에 대한 표시 함수 $I_A(x)$의 다음 정의가 필요해요.

\[I_A(x) = \begin{cases}1 & \text{if $x \in A$}, \ 0 & \text{otherwise}.\end{cases}\]

지표 정의
Precision $\frac{1}{N} \sum_{i=0}^{N-1} \frac{\left|P_i \cap L_i\right|}{\left|P_i\right|}$
Recall $\frac{1}{N} \sum_{i=0}^{N-1} \frac{\left|L_i \cap P_i\right|}{\left|L_i\right|}$
Accuracy $\frac{1}{N} \sum_{i=0}^{N - 1} \frac{\left|L_i \cap P_i \right|} {\left|L_i\right| + \left|P_i\right| - \left|L_i \cap P_i \right|}$
Precision by label $PPV(\ell)=\frac{TP}{TP + FP}= \frac{\sum_{i=0}^{N-1} I_{P_i}(\ell) \cdot I_{L_i}(\ell)} {\sum_{i=0}^{N-1} I_{P_i}(\ell)}$
Recall by label $TPR(\ell)=\frac{TP}{P}= \frac{\sum_{i=0}^{N-1} I_{P_i}(\ell) \cdot I_{L_i}(\ell)} {\sum_{i=0}^{N-1} I_{L_i}(\ell)}$
F1-measure by label $F1(\ell) = 2 \cdot \left(\frac{PPV(\ell) \cdot TPR(\ell)} {PPV(\ell) + TPR(\ell)}\right)$
Hamming Loss $\frac{1}{N \cdot \left|L\right|} \sum_{i=0}^{N - 1} \left|L_i\right| + \left|P_i\right| - 2\left|L_i \cap P_i\right|$
Subset Accuracy $\frac{1}{N} \sum_{i=0}^{N-1} I_{{L_i}}(P_i)$
F1 Measure $\frac{1}{N} \sum_{i=0}^{N-1} 2 \frac{\left|P_i \cap L_i\right|}{\left|P_i\right| \cdot \left|L_i\right|}$
Micro precision $\frac{TP}{TP + FP}=\frac{\sum_{i=0}^{N-1} \left|P_i \cap L_i\right|} {\sum_{i=0}^{N-1} \left|P_i \cap L_i\right| + \sum_{i=0}^{N-1} \left|P_i - L_i\right|}$
Micro recall $\frac{TP}{TP + FN}=\frac{\sum_{i=0}^{N-1} \left|P_i \cap L_i\right|} {\sum_{i=0}^{N-1} \left|P_i \cap L_i\right| + \sum_{i=0}^{N-1} \left|L_i - P_i\right|}$
Micro F1 Measure $2 \cdot \frac{TP}{2 \cdot TP + FP + FN}=2 \cdot \frac{\sum_{i=0}^{N-1} \left|P_i \cap L_i\right|}{2 \cdot \sum_{i=0}^{N-1} \left|P_i \cap L_i\right| + \sum_{i=0}^{N-1} \left|L_i - P_i\right| + \sum_{i=0}^{N-1} \left|P_i - L_i\right|}$

예제

다음 코드 스니펫은 다중 레이블 분류기의 성능을 평가하는 방법을 보여줘요. 예제는 아래에 보이는 다중 레이블 분류용 가짜 예측·레이블 데이터를 사용해요.

문서 예측:

  • doc 0 — predict 0, 1 — class 0, 2
  • doc 1 — predict 0, 2 — class 0, 1
  • doc 2 — predict none — class 0
  • doc 3 — predict 2 — class 2
  • doc 4 — predict 2, 0 — class 2, 0
  • doc 5 — predict 0, 1, 2 — class 0, 1
  • doc 6 — predict 1 — class 1, 2

예측 클래스:

  • class 0 — doc 0, 1, 4, 5 (총 4)
  • class 1 — doc 0, 5, 6 (총 3)
  • class 2 — doc 1, 3, 4, 5 (총 4)

진짜 클래스:

  • class 0 — doc 0, 1, 2, 4, 5 (총 5)
  • class 1 — doc 1, 5, 6 (총 3)
  • class 2 — doc 0, 3, 4, 6 (총 4)

API에 대한 자세한 내용은 MultilabelMetrics Python 문서를 참고하세요.

from pyspark.mllib.evaluation import MultilabelMetrics

scoreAndLabels = sc.parallelize([
    ([0.0, 1.0], [0.0, 2.0]),
    ([0.0, 2.0], [0.0, 1.0]),
    ([], [0.0]),
    ([2.0], [2.0]),
    ([2.0, 0.0], [2.0, 0.0]),
    ([0.0, 1.0, 2.0], [0.0, 1.0]),
    ([1.0], [1.0, 2.0])])

# Instantiate metrics object
metrics = MultilabelMetrics(scoreAndLabels)

# Summary stats
print("Recall = %s" % metrics.recall())
print("Precision = %s" % metrics.precision())
print("F1 measure = %s" % metrics.f1Measure())
print("Accuracy = %s" % metrics.accuracy)

# Individual label stats
labels = scoreAndLabels.flatMap(lambda x: x[1]).distinct().collect()
for label in labels:
    print("Class %s precision = %s" % (label, metrics.precision(label)))
    print("Class %s recall = %s" % (label, metrics.recall(label)))
    print("Class %s F1 Measure = %s" % (label, metrics.f1Measure(label)))

# Micro stats
print("Micro precision = %s" % metrics.microPrecision)
print("Micro recall = %s" % metrics.microRecall)
print("Micro F1 measure = %s" % metrics.microF1Measure)

# Hamming loss
print("Hamming loss = %s" % metrics.hammingLoss)

# Subset accuracy
print("Subset accuracy = %s" % metrics.subsetAccuracy)

전체 예제 코드는 Spark 저장소의 "examples/src/main/python/mllib/multi_label_metrics_example.py"에서 찾을 수 있어요.

API에 대한 자세한 내용은 MultilabelMetrics Scala 문서를 참고하세요.

import org.apache.spark.mllib.evaluation.MultilabelMetrics
import org.apache.spark.rdd.RDD

val scoreAndLabels: RDD[(Array[Double], Array[Double])] = sc.parallelize(
  Seq((Array(0.0, 1.0), Array(0.0, 2.0)),
    (Array(0.0, 2.0), Array(0.0, 1.0)),
    (Array.empty[Double], Array(0.0)),
    (Array(2.0), Array(2.0)),
    (Array(2.0, 0.0), Array(2.0, 0.0)),
    (Array(0.0, 1.0, 2.0), Array(0.0, 1.0)),
    (Array(1.0), Array(1.0, 2.0))), 2)

// Instantiate metrics object
val metrics = new MultilabelMetrics(scoreAndLabels)

// Summary stats
println(s"Recall = ${metrics.recall}")
println(s"Precision = ${metrics.precision}")
println(s"F1 measure = ${metrics.f1Measure}")
println(s"Accuracy = ${metrics.accuracy}")

// Individual label stats
metrics.labels.foreach(label =>
  println(s"Class $label precision = ${metrics.precision(label)}"))
metrics.labels.foreach(label => println(s"Class $label recall = ${metrics.recall(label)}"))
metrics.labels.foreach(label => println(s"Class $label F1-score = ${metrics.f1Measure(label)}"))

// Micro stats
println(s"Micro recall = ${metrics.microRecall}")
println(s"Micro precision = ${metrics.microPrecision}")
println(s"Micro F1 measure = ${metrics.microF1Measure}")

// Hamming loss
println(s"Hamming loss = ${metrics.hammingLoss}")

// Subset accuracy
println(s"Subset accuracy = ${metrics.subsetAccuracy}")

전체 예제 코드는 Spark 저장소의 "examples/src/main/scala/org/apache/spark/examples/mllib/MultiLabelMetricsExample.scala"에서 찾을 수 있어요.

API에 대한 자세한 내용은 MultilabelMetrics Java 문서를 참고하세요.

import java.util.Arrays;
import java.util.List;

import scala.Tuple2;

import org.apache.spark.api.java.*;
import org.apache.spark.mllib.evaluation.MultilabelMetrics;
import org.apache.spark.SparkConf;

List<Tuple2<double[], double[]>> data = Arrays.asList(
  new Tuple2<>(new double[]{0.0, 1.0}, new double[]{0.0, 2.0}),
  new Tuple2<>(new double[]{0.0, 2.0}, new double[]{0.0, 1.0}),
  new Tuple2<>(new double[]{}, new double[]{0.0}),
  new Tuple2<>(new double[]{2.0}, new double[]{2.0}),
  new Tuple2<>(new double[]{2.0, 0.0}, new double[]{2.0, 0.0}),
  new Tuple2<>(new double[]{0.0, 1.0, 2.0}, new double[]{0.0, 1.0}),
  new Tuple2<>(new double[]{1.0}, new double[]{1.0, 2.0})
);
JavaRDD<Tuple2<double[], double[]>> scoreAndLabels = sc.parallelize(data);

// Instantiate metrics object
MultilabelMetrics metrics = new MultilabelMetrics(scoreAndLabels.rdd());

// Summary stats
System.out.format("Recall = %f\n", metrics.recall());
System.out.format("Precision = %f\n", metrics.precision());
System.out.format("F1 measure = %f\n", metrics.f1Measure());
System.out.format("Accuracy = %f\n", metrics.accuracy());

// Stats by labels
for (int i = 0; i < metrics.labels().length - 1; i++) {
  System.out.format("Class %1.1f precision = %f\n", metrics.labels()[i], metrics.precision(
    metrics.labels()[i]));
  System.out.format("Class %1.1f recall = %f\n", metrics.labels()[i], metrics.recall(
    metrics.labels()[i]));
  System.out.format("Class %1.1f F1 score = %f\n", metrics.labels()[i], metrics.f1Measure(
    metrics.labels()[i]));
}

// Micro stats
System.out.format("Micro recall = %f\n", metrics.microRecall());
System.out.format("Micro precision = %f\n", metrics.microPrecision());
System.out.format("Micro F1 measure = %f\n", metrics.microF1Measure());

// Hamming loss
System.out.format("Hamming loss = %f\n", metrics.hammingLoss());

// Subset accuracy
System.out.format("Subset accuracy = %f\n", metrics.subsetAccuracy());

전체 예제 코드는 Spark 저장소의 "examples/src/main/java/org/apache/spark/examples/mllib/JavaMultiLabelClassificationMetricsExample.java"에서 찾을 수 있어요.

랭킹 시스템(Ranking systems)

랭킹 알고리즘의 역할(흔히 추천 시스템이라고 생각할 수 있어요)은 어떤 훈련 데이터를 기반으로 사용자에게 관련 아이템 또는 문서 집합을 반환하는 것이에요. 관련성의 정의는 다양하며 보통 애플리케이션별로 달라요. 랭킹 시스템 지표는 다양한 맥락에서 이 랭킹 또는 추천의 효과를 정량화하는 것을 목표로 해요. 어떤 지표는 추천된 문서 집합을 관련 문서의 진실(ground truth) 집합과 비교하고, 다른 지표는 수치 평점을 명시적으로 통합할 수 있어요.

사용 가능한 지표

랭킹 시스템은 보통 $M$명의 사용자 집합을 다뤄요.

\[U = \left{u_0, u_1, ..., u_{M-1}\right}\]

각 사용자($u_i$)는 $N_i$개의 진짜 관련 문서 집합을 가져요.

\[D_i = \left{d_0, d_1, ..., d_{N_i-1}\right}\]

그리고 관련성 감소 순서로 된 $Q_i$개의 추천 문서 목록이 있어요.

\[R_i = \left[r_0, r_1, ..., r_{Q_i-1}\right]\]

랭킹 시스템의 목표는 각 사용자에게 가장 관련성 높은 문서 집합을 만드는 것이에요. 집합의 관련성과 알고리즘의 효과는 아래 나열된 지표로 측정할 수 있어요.

추천 문서와 진짜 관련 문서 집합이 주어지면 추천 문서에 대한 관련성 점수를 반환하는 함수를 정의할 필요가 있어요.

\[rel_D(r) = \begin{cases}1 & \text{if $r \in D$}, \ 0 & \text{otherwise}.\end{cases}\]

지표 정의 참고
Precision at k $p(k)=\frac{1}{M} \sum_{i=0}^{M-1} {\frac{1}{k} \sum_{j=0}^{\text{min}(Q_i, k) - 1} rel_{D_i}(R_i(j))}$ Precision at k는 처음 k개의 추천 문서 중 몇 개가 진짜 관련 문서 집합에 있는지를 모든 사용자에 걸쳐 평균한 측정이에요. 이 지표에서는 추천의 순서가 고려되지 않아요.
Mean Average Precision $MAP=\frac{1}{M} \sum_{i=0}^{M-1} {\frac{1}{N_i} \sum_{j=0}^{Q_i-1} \frac{rel_{D_i}(R_i(j))}{j + 1}}$ MAP는 추천 문서 중 몇 개가 진짜 관련 문서 집합에 있는지를 측정하는데, 여기서는 추천 순서가 고려돼요(즉, 높은 관련성 문서가 더 앞에 있으면 더 좋음).
Normalized Discounted Cumulative Gain $NDCG(k)=\frac{1}{M} \sum_{i=0}^{M-1} {\frac{1}{IDCG(D_i, k)}\sum_{j=0}^{n-1} \frac{rel_{D_i}(R_i(j))}{\text{log}(j+2)}} \ \text{Where} \ \hspace{5 mm} n = \text{min}\left(\text{max}\left(Q_i, N_i\right),k\right) \ \hspace{5 mm} IDCG(D, k) = \sum_{j=0}^{\text{min}(\left|D\right|, k) - 1} \frac{1}{\text{log}(j+2)}$ NDCG at k는 처음 k개의 추천 문서 중 몇 개가 진짜 관련 문서 집합에 있는지를 모든 사용자에 걸쳐 평균한 측정이에요. precision at k와 달리 이 지표는 추천의 순서를 고려해요(문서가 관련성 감소 순서로 있다고 가정).

예제

다음 코드 스니펫은 샘플 데이터셋을 로드하고, 데이터에서 교대 최소 제곱(ALS) 추천 모델을 훈련하며, 여러 랭킹 지표로 추천기의 성능을 평가하는 방법을 보여줘요. 방법론의 간단한 요약은 아래에 제공해요.

MovieLens 평점은 1~5 척도예요.

  • 5: 꼭 봐야 함
  • 4: 즐길 것
  • 3: 괜찮음
  • 2: 꽤 나쁨
  • 1: 끔찍함

따라서 예측 평점이 3 미만이면 영화를 추천하지 말아야 해요. 평점을 신뢰도 점수로 매핑하려면 다음을 사용해요.

  • 5 → 2.5
  • 4 → 1.5
  • 3 → 0.5
  • 2 → -0.5
  • 1 → -1.5

이 매핑은 관찰되지 않은 항목이 일반적으로 괜찮음과 꽤 나쁨 사이임을 의미해요. 이 확장된 비양성 가중치 세계에서 0의 의미는 "전혀 상호작용한 적이 없는 것과 같다"예요.

API에 대한 자세한 내용은 RegressionMetrics Python 문서RankingMetrics Python 문서를 참고하세요.

from pyspark.mllib.recommendation import ALS, Rating
from pyspark.mllib.evaluation import RegressionMetrics

# Read in the ratings data
lines = sc.textFile("data/mllib/sample_movielens_data.txt")

def parseLine(line):
    fields = line.split("::")
    return Rating(int(fields[0]), int(fields[1]), float(fields[2]) - 2.5)
ratings = lines.map(lambda r: parseLine(r))

# Train a model on to predict user-product ratings
model = ALS.train(ratings, 10, 10, 0.01)

# Get predicted ratings on all existing user-product pairs
testData = ratings.map(lambda p: (p.user, p.product))
predictions = model.predictAll(testData).map(lambda r: ((r.user, r.product), r.rating))

ratingsTuple = ratings.map(lambda r: ((r.user, r.product), r.rating))
scoreAndLabels = predictions.join(ratingsTuple).map(lambda tup: tup[1])

# Instantiate regression metrics to compare predicted and actual ratings
metrics = RegressionMetrics(scoreAndLabels)

# Root mean squared error
print("RMSE = %s" % metrics.rootMeanSquaredError)

# R-squared
print("R-squared = %s" % metrics.r2)

전체 예제 코드는 Spark 저장소의 "examples/src/main/python/mllib/ranking_metrics_example.py"에서 찾을 수 있어요.

API에 대한 자세한 내용은 RegressionMetrics Scala 문서RankingMetrics Scala 문서를 참고하세요.

import org.apache.spark.mllib.evaluation.{RankingMetrics, RegressionMetrics}
import org.apache.spark.mllib.recommendation.{ALS, Rating}

// Read in the ratings data
val ratings = spark.read.textFile("data/mllib/sample_movielens_data.txt").rdd.map { line =>
  val fields = line.split("::")
  Rating(fields(0).toInt, fields(1).toInt, fields(2).toDouble - 2.5)
}.cache()

// Map ratings to 1 or 0, 1 indicating a movie that should be recommended
val binarizedRatings = ratings.map(r => Rating(r.user, r.product,
  if (r.rating > 0) 1.0 else 0.0)).cache()

// Summarize ratings
val numRatings = ratings.count()
val numUsers = ratings.map(_.user).distinct().count()
val numMovies = ratings.map(_.product).distinct().count()
println(s"Got $numRatings ratings from $numUsers users on $numMovies movies.")

// Build the model
val numIterations = 10
val rank = 10
val lambda = 0.01
val model = ALS.train(ratings, rank, numIterations, lambda)

// Define a function to scale ratings from 0 to 1
def scaledRating(r: Rating): Rating = {
  val scaledRating = math.max(math.min(r.rating, 1.0), 0.0)
  Rating(r.user, r.product, scaledRating)
}

// Get sorted top ten predictions for each user and then scale from [0, 1]
val userRecommended = model.recommendProductsForUsers(10).map { case (user, recs) =>
  (user, recs.map(scaledRating))
}

// Assume that any movie a user rated 3 or higher (which maps to a 1) is a relevant document
// Compare with top ten most relevant documents
val userMovies = binarizedRatings.groupBy(_.user)
val relevantDocuments = userMovies.join(userRecommended).map { case (user, (actual,
predictions)) =>
  (predictions.map(_.product), actual.filter(_.rating > 0.0).map(_.product).toArray)
}

// Instantiate metrics object
val metrics = new RankingMetrics(relevantDocuments)

// Precision at K
Array(1, 3, 5).foreach { k =>
  println(s"Precision at $k = ${metrics.precisionAt(k)}")
}

// Mean average precision
println(s"Mean average precision = ${metrics.meanAveragePrecision}")

// Mean average precision at k
println(s"Mean average precision at 2 = ${metrics.meanAveragePrecisionAt(2)}")

// Normalized discounted cumulative gain
Array(1, 3, 5).foreach { k =>
  println(s"NDCG at $k = ${metrics.ndcgAt(k)}")
}

// Recall at K
Array(1, 3, 5).foreach { k =>
  println(s"Recall at $k = ${metrics.recallAt(k)}")
}

// Get predictions for each data point
val allPredictions = model.predict(ratings.map(r => (r.user, r.product))).map(r => ((r.user,
  r.product), r.rating))
val allRatings = ratings.map(r => ((r.user, r.product), r.rating))
val predictionsAndLabels = allPredictions.join(allRatings).map { case ((user, product),
(predicted, actual)) =>
  (predicted, actual)
}

// Get the RMSE using regression metrics
val regressionMetrics = new RegressionMetrics(predictionsAndLabels)
println(s"RMSE = ${regressionMetrics.rootMeanSquaredError}")

// R-squared
println(s"R-squared = ${regressionMetrics.r2}")

전체 예제 코드는 Spark 저장소의 "examples/src/main/scala/org/apache/spark/examples/mllib/RankingMetricsExample.scala"에서 찾을 수 있어요.

API에 대한 자세한 내용은 RegressionMetrics Java 문서RankingMetrics Java 문서를 참고하세요.

import java.util.*;

import scala.Tuple2;

import org.apache.spark.api.java.*;
import org.apache.spark.mllib.evaluation.RegressionMetrics;
import org.apache.spark.mllib.evaluation.RankingMetrics;
import org.apache.spark.mllib.recommendation.ALS;
import org.apache.spark.mllib.recommendation.MatrixFactorizationModel;
import org.apache.spark.mllib.recommendation.Rating;

String path = "data/mllib/sample_movielens_data.txt";
JavaRDD<String> data = sc.textFile(path);
JavaRDD<Rating> ratings = data.map(line -> {
    String[] parts = line.split("::");
    return new Rating(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Double
        .parseDouble(parts[2]) - 2.5);
  });
ratings.cache();

// Train an ALS model
MatrixFactorizationModel model = ALS.train(JavaRDD.toRDD(ratings), 10, 10, 0.01);

// Get top 10 recommendations for every user and scale ratings from 0 to 1
JavaRDD<Tuple2<Object, Rating[]>> userRecs = model.recommendProductsForUsers(10).toJavaRDD();
JavaRDD<Tuple2<Object, Rating[]>> userRecsScaled = userRecs.map(t -> {
    Rating[] scaledRatings = new Rating[t._2().length];
    for (int i = 0; i < scaledRatings.length; i++) {
      double newRating = Math.max(Math.min(t._2()[i].rating(), 1.0), 0.0);
      scaledRatings[i] = new Rating(t._2()[i].user(), t._2()[i].product(), newRating);
    }
    return new Tuple2<>(t._1(), scaledRatings);
  });
JavaPairRDD<Object, Rating[]> userRecommended = JavaPairRDD.fromJavaRDD(userRecsScaled);

// Map ratings to 1 or 0, 1 indicating a movie that should be recommended
JavaRDD<Rating> binarizedRatings = ratings.map(r -> {
    double binaryRating;
    if (r.rating() > 0.0) {
      binaryRating = 1.0;
    } else {
      binaryRating = 0.0;
    }
    return new Rating(r.user(), r.product(), binaryRating);
  });

// Group ratings by common user
JavaPairRDD<Object, Iterable<Rating>> userMovies = binarizedRatings.groupBy(Rating::user);

// Get true relevant documents from all user ratings
JavaPairRDD<Object, List<Integer>> userMoviesList = userMovies.mapValues(docs -> {
    List<Integer> products = new ArrayList<>();
    for (Rating r : docs) {
      if (r.rating() > 0.0) {
        products.add(r.product());
      }
    }
    return products;
  });

// Extract the product id from each recommendation
JavaPairRDD<Object, List<Integer>> userRecommendedList = userRecommended.mapValues(docs -> {
    List<Integer> products = new ArrayList<>();
    for (Rating r : docs) {
      products.add(r.product());
    }
    return products;
  });
JavaRDD<Tuple2<List<Integer>, List<Integer>>> relevantDocs = userMoviesList.join(
  userRecommendedList).values();

// Instantiate the metrics object
RankingMetrics<Integer> metrics = RankingMetrics.of(relevantDocs);

// Precision, NDCG and Recall at k
Integer[] kVector = {1, 3, 5};
for (Integer k : kVector) {
  System.out.format("Precision at %d = %f\n", k, metrics.precisionAt(k));
  System.out.format("NDCG at %d = %f\n", k, metrics.ndcgAt(k));
  System.out.format("Recall at %d = %f\n", k, metrics.recallAt(k));
}

// Mean average precision
System.out.format("Mean average precision = %f\n", metrics.meanAveragePrecision());

//Mean average precision at k
System.out.format("Mean average precision at 2 = %f\n", metrics.meanAveragePrecisionAt(2));

// Evaluate the model using numerical ratings and regression metrics
JavaRDD<Tuple2<Object, Object>> userProducts =
    ratings.map(r -> new Tuple2<>(r.user(), r.product()));

JavaPairRDD<Tuple2<Integer, Integer>, Object> predictions = JavaPairRDD.fromJavaRDD(
  model.predict(JavaRDD.toRDD(userProducts)).toJavaRDD().map(r ->
    new Tuple2<>(new Tuple2<>(r.user(), r.product()), r.rating())));
JavaRDD<Tuple2<Object, Object>> ratesAndPreds =
  JavaPairRDD.fromJavaRDD(ratings.map(r ->
    new Tuple2<Tuple2<Integer, Integer>, Object>(
      new Tuple2<>(r.user(), r.product()),
      r.rating())
  )).join(predictions).values();

// Create regression metrics object
RegressionMetrics regressionMetrics = new RegressionMetrics(ratesAndPreds.rdd());

// Root mean squared error
System.out.format("RMSE = %f\n", regressionMetrics.rootMeanSquaredError());

// R-squared
System.out.format("R-squared = %f\n", regressionMetrics.r2());

전체 예제 코드는 Spark 저장소의 "examples/src/main/java/org/apache/spark/examples/mllib/JavaRankingMetricsExample.java"에서 찾을 수 있어요.

회귀 모델 평가(Regression model evaluation)

회귀 분석은 여러 독립 변수에서 연속 출력 변수를 예측할 때 사용돼요.

사용 가능한 지표

지표 정의
Mean Squared Error (MSE) $MSE = \frac{\sum_{i=0}^{N-1} (\mathbf{y}_i - \hat{\mathbf{y}}_i)^2}{N}$
Root Mean Squared Error (RMSE) $RMSE = \sqrt{\frac{\sum_{i=0}^{N-1} (\mathbf{y}_i - \hat{\mathbf{y}}_i)^2}{N}}$
Mean Absolute Error (MAE) $MAE=\frac{1}{N}\sum_{i=0}^{N-1} \left|\mathbf{y}_i - \hat{\mathbf{y}}_i\right|$
Coefficient of Determination $(R^2)$ $R^2=1 - \frac{MSE}{\text{VAR}(\mathbf{y}) \cdot (N-1)}=1-\frac{\sum_{i=0}^{N-1} (\mathbf{y}_i - \hat{\mathbf{y}}i)^2}{\sum{i=0}^{N-1}(\mathbf{y}_i-\bar{\mathbf{y}})^2}$
Explained Variance $1 - \frac{\text{VAR}(\mathbf{y} - \mathbf{\hat{y}})}{\text{VAR}(\mathbf{y})}$

더 알아보기 (Learn more)