評価指標 - RDDベースAPI

spark.mllib には、データから学習して予測を行うための多くの機械学習アルゴリズムが付属しています。これらのアルゴリズムを機械学習モデルの構築に適用する場合、モデルのパフォーマンスをいくつかの基準で評価する必要があります。この基準は、アプリケーションとその要件によって異なります。spark.mllib は、機械学習モデルのパフォーマンスを評価するための指標スイートも提供しています。

特定の機械学習アルゴリズムは、分類、回帰、クラスタリングなどのより広範な機械学習アプリケーションのタイプに分類されます。これらの各タイプには、パフォーマンス評価のための確立された指標があり、現在 spark.mllib で利用可能な指標は、このセクションで詳述されています。

分類モデルの評価

分類アルゴリズムには多くの種類がありますが、分類モデルの評価はすべて同様の原則を共有しています。教師あり分類問題では、各データポイントに対して真の出力とモデルが生成した予測出力が存在します。このため、各データポイントの結果は4つのカテゴリのいずれかに割り当てることができます。

これら4つの数値は、ほとんどの分類器評価指標の構成要素です。分類器評価を考慮する上で基本的な点は、単純な精度(つまり、予測が正しかったか間違っていたか)は一般的に良い指標ではないということです。その理由は、データセットが非常に不均衡である可能性があるためです。たとえば、モデルが 95% が不正ではなく、5% が不正であるデータセットから不正を予測するように設計されている場合、入力に関係なく常に不正ではないと予測する単純な分類器は 95% の精度になります。このため、適合率と再現率のような指標が一般的に使用されます。これは、エラーの種類を考慮するためです。ほとんどのアプリケーションでは、適合率と再現率の間に何らかの望ましいバランスがあり、これは 2 つを単一の指標であるF値に結合することで捉えることができます。

二項分類

二項分類器は、指定されたデータセットの要素を 2 つの可能なグループ(例:不正または不正でない)のいずれかに分離するために使用され、多クラス分類の特殊なケースです。ほとんどの二項分類指標は、多クラス分類指標に一般化できます。

閾値チューニング

多くの分類モデルは、実際には各クラスの「スコア」(多くの場合、確率)を出力することに注意することが重要です。スコアが高いほど、可能性が高いことを示します。二項分類の場合、モデルは各クラスの確率を出力する場合があります:$P(Y=1|X)$ および $P(Y=0|X)$。単に高い方の確率を取るのではなく、モデルが非常に高い確率の場合にのみクラスを予測するように調整する必要がある場合があります(例:モデルが 90% 以上の確率で不正を予測する場合にのみクレジットカード取引をブロックする)。したがって、モデルが出力する確率に基づいて予測されるクラスを決定する予測閾値が存在します。

予測閾値を調整すると、モデルの適合率と再現率が変化し、モデルの最適化の重要な部分となります。閾値の関数として適合率、再現率、その他の指標がどのように変化するかを視覚化するために、閾値によってパラメータ化された競合する指標を互いにプロットするのが一般的です。PR曲線は、異なる閾値値での(適合率、再現率)の点をプロットし、ROC(Receiver Operating Characteristic)曲線は、(再現率、偽陽性率)の点をプロットします。

利用可能な指標

指標定義
適合率 (陽性的中率) $PPV=\frac{TP}{TP + FP}$
再現率 (真陽性率) $TPR=\frac{TP}{P}=\frac{TP}{TP + FN}$
F値 $F(\beta) = \left(1 + \beta^2\right) \cdot \left(\frac{PPV \cdot TPR} {\beta^2 \cdot PPV + TPR}\right)$
ROC (Receiver Operating Characteristic) $FPR(T)=\int^\infty_{T} P_0(T)\,dT \\ TPR(T)=\int^\infty_{T} P_1(T)\,dT$
ROC曲線下面積 $AUROC=\int^1_{0} \frac{TP}{P} d\left(\frac{FP}{N}\right)$
適合率-再現率曲線下面積 $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」にあります。

多クラス分類

a 多クラス分類は、各データポイントに $M \gt 2$ の可能なラベルが存在する分類問題を記述します($M=2$ の場合は二項分類問題)。たとえば、手書きサンプルを 0 から 9 までの数字に分類する場合、10 の可能なクラスがあります。

多クラス指標では、陽性および陰性の概念が少し異なります。予測とラベルは依然として陽性または陰性になる可能性がありますが、特定のクラスのコンテキストで考慮する必要があります。各ラベルと予測は複数のクラスのいずれかの値を取り、したがってそれらは特定のクラスに対して陽性であり、他のすべてのクラスに対して陰性であると言われます。したがって、真陽性は、予測とラベルが一致する場合に発生し、真陰性は、予測とラベルのいずれも指定されたクラスの値を取らない場合に発生します。この規約によれば、特定のデータサンプルに対して複数の真陰性が存在する可能性があります。陽性および陰性ラベルの以前の定義からの偽陰性および偽陽性の拡張は簡単です。

ラベルベースの指標

2 つの可能なラベルしかない二項分類とは対照的に、多クラス分類問題には多くの可能なラベルがあるため、ラベルベースの指標の概念が導入されます。精度は、すべてのラベルにわたる適合率を測定します。つまり、任意のクラスが正しく予測された回数(真陽性)をデータポイントの数で正規化したものです。ラベルごとの適合率は、1 つのクラスのみを考慮し、特定のラベルが正しく予測された回数を、そのラベルが出力に現れた回数で正規化したものを測定します。

利用可能な指標

クラスまたはラベルのセットを以下のように定義します。

\[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}\]
指標定義
混同行列 $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)$
精度 $ACC = \frac{TP}{TP + FP} = \frac{1}{N}\sum_{i=0}^{N-1} \hat{\delta}\left(\hat{\mathbf{y}}_i - \mathbf{y}_i\right)$
ラベルごとの適合率 $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)}$
ラベルごとの再現率 $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値 $F(\beta, \ell) = \left(1 + \beta^2\right) \cdot \left(\frac{PPV(\ell) \cdot TPR(\ell)} {\beta^2 \cdot PPV(\ell) + TPR(\ell)}\right)$
重み付き適合率 $PPV_{w}= \frac{1}{N} \sum\nolimits_{\ell \in L} PPV(\ell) \cdot \sum_{i=0}^{N-1} \hat{\delta}(\mathbf{y}_i-\ell)$
重み付き再現率 $TPR_{w}= \frac{1}{N} \sum\nolimits_{\ell \in L} TPR(\ell) \cdot \sum_{i=0}^{N-1} \hat{\delta}(\mathbf{y}_i-\ell)$
重み付きF値 $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」にあります。

マルチラベル分類

a マルチラベル分類問題は、データセットの各サンプルをクラスラベルのセットにマッピングすることを含みます。このタイプの分類問題では、ラベルは相互に排他的ではありません。たとえば、一連のニュース記事をトピックに分類する場合、単一の記事は科学と政治の両方である可能性があります。

ラベルは相互に排他的ではないため、予測と真のラベルは、ラベルのセットのベクトルになります。ラベルのベクトルではありません。したがって、マルチラベル指標は、適合率、再現率などの基本的なアイデアをセットに対する操作に拡張します。たとえば、特定のクラスの真陽性は、そのクラスが予測セットに存在し、特定のデータポイントの真ラベルセットに存在する場合に発生します。

利用可能な指標

ここで、$N$ 個のドキュメントのセット $D$ を定義します。

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

ラベルセット $L_i$ と予測セット $P_i$ を、それぞれドキュメント $d_i$ に対応するラベルセットと予測セットとすると、ラベルのファミリー $L_0, L_1, …, L_{N-1}$ と予測セットのファミリー $P_0, P_1, …, P_{N-1}$ を定義します。

すべてのユニークなラベルのセットは次のように与えられます。

\[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}\]
指標定義
適合率$\frac{1}{N} \sum_{i=0}^{N-1} \frac{\left|P_i \cap L_i\right|}{\left|P_i\right|}$
再現率$\frac{1}{N} \sum_{i=0}^{N-1} \frac{\left|L_i \cap P_i\right|}{\left|L_i\right|}$
精度 $\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|}$
ラベルごとの適合率$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)}$
ラベルごとの再現率$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値$F1(\ell) = 2 \cdot \left(\frac{PPV(\ell) \cdot TPR(\ell)} {PPV(\ell) + TPR(\ell)}\right)$
ハム距離損失 $\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|$
サブセット精度 $\frac{1}{N} \sum_{i=0}^{N-1} I_{\{L_i\}}(P_i)$
F1値 $\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|}$
マイクロ適合率 $\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|}$
マイクロ再現率 $\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|}$
マイクロF1値 $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|}$

以下のコードスニペットは、マルチラベル分類器のパフォーマンスを評価する方法を示しています。例では、以下に示すマルチラベル分類用の偽の予測およびラベルデータを使用しています。

ドキュメントの予測

予測されたクラス

真のクラス

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」にあります。

ランキングシステム

ランキングアルゴリズム(レコメンダーシステムとも考えられる)の役割は、トレーニングデータに基づいて、関連するアイテムまたはドキュメントのセットをユーザーに返すことです。関連性の定義は異なる場合があり、通常はアプリケーション固有です。ランキングシステム指標は、さまざまなコンテキストでこれらのランキングまたは推奨の有効性を定量化することを目指しています。一部の指標は、推奨ドキュメントのセットを関連ドキュメントの真のセットと比較し、他の指標は数値評価を明示的に組み込む場合があります。

利用可能な指標

$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}\]
指標定義注記
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))}$ k における適合率は、最初の k 個の推奨ドキュメントのうち、真の関連ドキュメントのセットに含まれているものの割合を、すべてのユーザーにわたって平均したものです。この指標では、推奨の順序は考慮されません。
平均適合率 $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 は、推奨ドキュメントのうち、真の関連ドキュメントのセットに含まれているものの割合を測定します。この場合、推奨の順序が考慮されます(つまり、非常に関連性の高いドキュメントに対するペナルティが高くなります)。
正規化割引累積ゲイン $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{ここで} \\ \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)}$ k における NDCG は、最初の k 個の推奨ドキュメントのうち、真の関連ドキュメントのセットに含まれているものの割合を、すべてのユーザーにわたって平均したものです。k における適合率とは対照的に、この指標は推奨の順序を考慮します(ドキュメントは関連性の高い順に並んでいると想定されます)。

以下のコードスニペットは、サンプルデータセットをロードし、データで交代最小二乗法レコメンデーションモデルをトレーニングし、いくつかのランキング指標でレコメンダーのパフォーマンスを評価する方法を示しています。方法論の簡単な概要を以下に示します。

MovieLens の評価は 1~5 のスケールです。

したがって、予測評価が 3 未満の場合は映画を推奨すべきではありません。評価を信頼度スコアにマッピングするには、次を使用します。

このマッピングは、観測されていないエントリが一般的に「まあまあ」と「かなりひどい」の間にあることを意味します。非正の重みのこの拡張された世界における 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」にあります。

回帰モデルの評価

回帰分析は、多数の独立変数から連続的な出力変数を予測する場合に使用されます。

利用可能な指標

指標定義
平均二乗誤差 (MSE) $MSE = \frac{\sum_{i=0}^{N-1} (\mathbf{y}_i - \hat{\mathbf{y}}_i)^2}{N}$
二乗平均平方根誤差 (RMSE) $RMSE = \sqrt{\frac{\sum_{i=0}^{N-1} (\mathbf{y}_i - \hat{\mathbf{y}}_i)^2}{N}}$
平均絶対誤差 (MAE) $MAE=\frac{1}{N}\sum_{i=0}^{N-1} \left|\mathbf{y}_i - \hat{\mathbf{y}}_i\right|$
決定係数 $(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}$
説明分散 $1 - \frac{\text{VAR}(\mathbf{y} - \mathbf{\hat{y}})}{\text{VAR}(\mathbf{y})}$