From 2ee821ad936c38d630e166b793f93945399937d7 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 19 Mar 2019 00:50:16 +0800 Subject: [PATCH 01/39] add JNI stub --- .../dmlc/xgboost4j/scala/spark/XGBoost.scala | 3 +- .../scala/spark/DistributedEvalError.scala | 49 +++++++++++++++++++ .../scala/spark/XGBoostGeneralSuite.scala | 16 ++++-- .../java/ml/dmlc/xgboost4j/java/Booster.java | 4 ++ .../ml/dmlc/xgboost4j/java/IEvaluation.java | 9 ++-- .../java/IEvaluationForDistributed.java | 14 ++++++ .../java/ml/dmlc/xgboost4j/java/XGBoost.java | 6 ++- .../ml/dmlc/xgboost4j/java/XGBoostJNI.java | 4 +- .../main/resources/xgboost-tracker.properties | 1 + .../xgboost4j/src/native/xgboost4j.cpp | 11 +++++ jvm-packages/xgboost4j/src/native/xgboost4j.h | 8 +++ 11 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala create mode 100644 jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java create mode 100644 jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties diff --git a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala index 65f0ef30fe2a..8c0bd55efcdc 100644 --- a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala +++ b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala @@ -24,7 +24,8 @@ import scala.collection.mutable.ListBuffer import scala.collection.{AbstractIterator, mutable} import scala.util.Random -import ml.dmlc.xgboost4j.java.{IRabitTracker, Rabit, XGBoostError, RabitTracker => PyRabitTracker} +import ml.dmlc.xgboost4j.java.{IEvaluationForDistributed, IRabitTracker, Rabit, XGBoostError, RabitTracker => PyRabitTracker} +import ml.dmlc.xgboost4j.java.XGBoostJNI import ml.dmlc.xgboost4j.scala.rabit.RabitTracker import ml.dmlc.xgboost4j.scala.{XGBoost => SXGBoost, _} import ml.dmlc.xgboost4j.{LabeledPoint => XGBLabeledPoint} diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala new file mode 100644 index 000000000000..b696a142dcb0 --- /dev/null +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala @@ -0,0 +1,49 @@ +/* + Copyright (c) 2014 by Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package ml.dmlc.xgboost4j.scala.spark + +import ml.dmlc.xgboost4j.java.IEvaluationForDistributed +import ml.dmlc.xgboost4j.scala.{DMatrix, EvalTrait} + +class DistributedEvalError extends EvalTrait with IEvaluationForDistributed { + + /** + * get evaluate metric + * + * @return evalMetric + */ + override def getMetric: String = "distributed_error" + + /** + * evaluate with predicts and data + * + * @param predicts predictions as array + * @param dmat data matrix to evaluate + * @return result of the metric + */ + override def eval(predicts: Array[Array[Float]], dmat: DMatrix): Float = 0.0f + + /** + * calculate the metrics for a single row given its label and prediction + */ + override def evalRow(label: Float, pred: Float): Float = 0.0f + + /** + * perform transformation with the sum of error and weights to get the final evaluation metrics + */ + override def getFinal(errorSum: Float, weightSum: Float): Float = 0.0f +} diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala index 1affe1474f2a..c069e08a2de4 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala @@ -37,7 +37,7 @@ import org.apache.spark.ml.feature.VectorAssembler class XGBoostGeneralSuite extends FunSuite with PerTest { - test("test Rabit allreduce to validate Scala-implemented Rabit tracker") { + ignore("test Rabit allreduce to validate Scala-implemented Rabit tracker") { val vectorLength = 100 val rdd = sc.parallelize( (1 to numWorkers * vectorLength).toArray.map { _ => Random.nextFloat() }, numWorkers).cache() @@ -85,8 +85,18 @@ class XGBoostGeneralSuite extends FunSuite with PerTest { List("eta" -> "1", "max_depth" -> "6", "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, "custom_eval" -> null, "custom_obj" -> null, "use_external_memory" -> false, - "missing" -> Float.NaN).toMap, - hasGroup = false) + "missing" -> Float.NaN).toMap) + assert(booster != null) + } + + test("distributed training with customized evaluation metrics") { + val trainingRDD = sc.parallelize(Classification.train) + val (booster, metrics) = XGBoost.trainDistributed( + trainingRDD, + List("eta" -> "1", "max_depth" -> "6", + "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, + "custom_eval" -> new DistributedEvalError, "custom_obj" -> null, + "use_external_memory" -> false, "missing" -> Float.NaN).toMap) assert(booster != null) } diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java index 0d89d6a230ac..e72a4a908bd2 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java @@ -277,6 +277,10 @@ public String evalSet(DMatrix[] evalMatrixs, String[] evalNames, IEvaluation eva return evalInfo; } + public long getHandle() { + return handle; + } + /** * Advanced predict function with all the options. * diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluation.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluation.java index 7f8abece4dc0..00da19737bd8 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluation.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluation.java @@ -19,20 +19,19 @@ /** * interface for customized evaluation - * - * @author hzx */ public interface IEvaluation extends Serializable { + /** - * get evaluate metric - * - * @return evalMetric + * get metrics' name */ String getMetric(); /** * evaluate with predicts and data * + * this method is used only for evaluation in single-host mode + * * @param predicts predictions as array * @param dmat data matrix to evaluate * @return result of the metric diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java new file mode 100644 index 000000000000..076c3c3fc60a --- /dev/null +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java @@ -0,0 +1,14 @@ +package ml.dmlc.xgboost4j.java; + +public interface IEvaluationForDistributed { + + /** + * calculate the metrics for a single row given its label and prediction + */ + float evalRow(float label, float pred); + + /** + * perform transformation with the sum of error and weights to get the final evaluation metrics + */ + float getFinal(float errorSum, float weightSum); +} diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index b6a173bd6bd1..08415eb19e64 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -196,9 +196,13 @@ public static Booster train( if (evalMats.length > 0) { float[] metricsOut = new float[evalMats.length]; String evalInfo; - if (eval != null) { + if (eval != null && !(eval instanceof IEvaluationForDistributed)) { evalInfo = booster.evalSet(evalMats, evalNames, eval, metricsOut); } else { + if (eval instanceof IEvaluationForDistributed) { + // TODO + XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric()); + } evalInfo = booster.evalSet(evalMats, evalNames, iter, metricsOut); } for (int i = 0; i < metricsOut.length; i++) { diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java index e797d67aa3a2..2c9430d6ae64 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java @@ -23,8 +23,6 @@ /** * xgboost JNI functions * change 2015-7-6: *use a long[] (length=1) as container of handle to get the output DMatrix or Booster - * - * @author hzx */ class XGBoostJNI { private static final Log logger = LogFactory.getLog(DMatrix.class); @@ -100,6 +98,8 @@ public final static native int XGBoosterEvalOneIter(long handle, int iter, long[ public final static native int XGBoosterPredict(long handle, long dmat, int option_mask, int ntree_limit, float[][] predicts); + public final static native int XGBoosterAddNewMetrics(long handle, String metricsName); + public final static native int XGBoosterLoadModel(long handle, String fname); public final static native int XGBoosterSaveModel(long handle, String fname); diff --git a/jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties b/jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties new file mode 100644 index 000000000000..ecbf2c0d94d6 --- /dev/null +++ b/jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties @@ -0,0 +1 @@ +host-ip=0.0.0.0 \ No newline at end of file diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 2857938791a7..6b1be45886a5 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -769,6 +769,17 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr return ret; } +/* + * Class: ml_dmlc_xgboost4j_java_XGBoostJNI + * Method: XGBoosterAddNewMetrics + * Signature: (JLjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics + (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name) { + std::cout << "adding new metrics\n"; + return 0; +} + /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBoosterLoadRabitCheckpoint diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.h b/jvm-packages/xgboost4j/src/native/xgboost4j.h index 96eaa97b27cc..62f41d6e87dd 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.h +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.h @@ -255,6 +255,14 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterGetAttr JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr (JNIEnv *, jclass, jlong, jstring, jstring); +/* + * Class: ml_dmlc_xgboost4j_java_XGBoostJNI + * Method: XGBoosterAddNewMetrics + * Signature: (JLjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics + (JNIEnv *, jclass, jlong, jstring); + /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBoosterLoadRabitCheckpoint From 6e7e28aa42761309e136878038dd0311af4ddf80 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 19 Mar 2019 14:46:10 +0800 Subject: [PATCH 02/39] temp --- include/xgboost/build_config.h | 1 - include/xgboost/metric.h | 3 +- .../java/ml/dmlc/xgboost4j/java/XGBoost.java | 3 +- .../ml/dmlc/xgboost4j/java/XGBoostJNI.java | 3 +- .../xgboost4j/src/native/xgboost4j.cpp | 47 ++++++++++++++++++- jvm-packages/xgboost4j/src/native/xgboost4j.h | 2 +- src/metric/elementwise_metric.cu | 14 ++++-- src/metric/metric.cc | 1 + 8 files changed, 63 insertions(+), 11 deletions(-) diff --git a/include/xgboost/build_config.h b/include/xgboost/build_config.h index 6d364a6ff081..4d9b9f959397 100644 --- a/include/xgboost/build_config.h +++ b/include/xgboost/build_config.h @@ -15,7 +15,6 @@ #elif defined(__GNUC__) // Enable __builtin_prefetch for GCC #define XGBOOST_BUILTIN_PREFETCH_PRESENT -#endif // GUARDS #endif // !defined(XGBOOST_MM_PREFETCH_PRESENT) && !defined() diff --git a/include/xgboost/metric.h b/include/xgboost/metric.h index 56ecebfbf8df..9543bbfb2a7f 100644 --- a/include/xgboost/metric.h +++ b/include/xgboost/metric.h @@ -1,8 +1,7 @@ /*! - * Copyright 2014 by Contributors + * Copyright 2019 by Contributors * \file metric.h * \brief interface of evaluation metric function supported in xgboost. - * \author Tianqi Chen, Kailong Chen */ #ifndef XGBOOST_METRIC_H_ #define XGBOOST_METRIC_H_ diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index 08415eb19e64..5e00f59cd9cc 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -201,7 +201,8 @@ public static Booster train( } else { if (eval instanceof IEvaluationForDistributed) { // TODO - XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric()); + int numClasses = 2; + XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses); } evalInfo = booster.evalSet(evalMats, evalNames, iter, metricsOut); } diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java index 2c9430d6ae64..c629525ca722 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java @@ -98,7 +98,8 @@ public final static native int XGBoosterEvalOneIter(long handle, int iter, long[ public final static native int XGBoosterPredict(long handle, long dmat, int option_mask, int ntree_limit, float[][] predicts); - public final static native int XGBoosterAddNewMetrics(long handle, String metricsName); + public final static native int XGBoosterAddNewMetrics(long handle, String metricsName, + int numClasses); public final static native int XGBoosterLoadModel(long handle, String fname); diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 6b1be45886a5..9ba96ca60e6e 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "./xgboost4j.h" #include #include @@ -148,6 +149,46 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( } } +/*! \brief handle to a data iterator */ +typedef void *CustomEvalHandle; // NOLINT(*) + +// bridge classes for customized metrics +class CustomEvalElementWise { + + CustomEvalElementWise(std::string& name, CustomEvalHandle handle): + metrics_name(name), custom_eval_handle(handle) { + int jni_status = global_jvm->GetEnv((void **)&jenv, JNI_VERSION_1_6); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } + } + + XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, + xgboost::bst_float pred) const { + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + + jmethodID evalRow = jenv->GetMethodID(eval_interface, + "evalRow", "(FF)F;"); + return 0.0f; + } + + xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + + jmethodID getFinal = jenv->GetMethodID(eval_interface, + "getFinal", "(FF)F;"); + return 0.0f; + } + +private: + std::string metrics_name; + + CustomEvalHandle custom_eval_handle; + JNIEnv* jenv; +}; + /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBGetLastError @@ -775,8 +816,10 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr * Signature: (JLjava/lang/String;)I */ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics - (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name) { - std::cout << "adding new metrics\n"; + (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name, + jint num_classes) { + #include "../../../../src/metric/elementwise_metric.cu" + xgboost::metric::ElementWiseMetricsRegister::registerCustomMetrics(metrics_name); return 0; } diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.h b/jvm-packages/xgboost4j/src/native/xgboost4j.h index 62f41d6e87dd..4f886b2dce66 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.h +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.h @@ -261,7 +261,7 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr * Signature: (JLjava/lang/String;)I */ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics - (JNIEnv *, jclass, jlong, jstring); + (JNIEnv *, jclass, jlong, jstring, jint); /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI diff --git a/src/metric/elementwise_metric.cu b/src/metric/elementwise_metric.cu index a9221be849bf..cace252832a3 100644 --- a/src/metric/elementwise_metric.cu +++ b/src/metric/elementwise_metric.cu @@ -1,9 +1,7 @@ /*! * Copyright 2015-2019 by Contributors - * \file elementwise_metric.cc - * \brief evaluation metrics for elementwise binary or regression. - * \author Kailong Chen, Tianqi Chen */ + #include #include #include @@ -352,6 +350,16 @@ struct EvalEWiseBase : public Metric { ElementWiseMetricsReduction reducer_; }; +class ElementWiseMetricsRegister { + + template + static void RegisterCustomMetrics(std::string metrics_name) { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name) + .describe("customized metrics") + .set_body([](const char* param) { return new EvalEWiseBase(); }); + } +}; + XGBOOST_REGISTER_METRIC(RMSE, "rmse") .describe("Rooted mean square error.") .set_body([](const char* param) { return new EvalEWiseBase(); }); diff --git a/src/metric/metric.cc b/src/metric/metric.cc index 8d3d9d9280cc..ad54a3cf5b95 100644 --- a/src/metric/metric.cc +++ b/src/metric/metric.cc @@ -4,6 +4,7 @@ * \brief Registry of objective functions. */ #include + #include #include "metric_common.h" From 06d48bba9fde795e74b2c3aa4d373a68595686d3 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Wed, 20 Mar 2019 09:43:27 +0800 Subject: [PATCH 03/39] temp --- include/xgboost/elementwise_metric.h | 324 ++++++++++++++++++ include/xgboost/metric_param.h | 31 ++ .../java/ml/dmlc/xgboost4j/java/XGBoost.java | 3 +- .../ml/dmlc/xgboost4j/java/XGBoostJNI.java | 3 +- .../xgboost4j/src/native/xgboost4j.cpp | 36 +- jvm-packages/xgboost4j/src/native/xgboost4j.h | 2 +- src/metric/elementwise_metric.cu | 174 +--------- src/metric/metric.cc | 1 + 8 files changed, 391 insertions(+), 183 deletions(-) create mode 100644 include/xgboost/elementwise_metric.h create mode 100644 include/xgboost/metric_param.h diff --git a/include/xgboost/elementwise_metric.h b/include/xgboost/elementwise_metric.h new file mode 100644 index 000000000000..b22dac74abd4 --- /dev/null +++ b/include/xgboost/elementwise_metric.h @@ -0,0 +1,324 @@ +/* + * Copyright 2015-2019 by Contributors + */ + +#ifndef XGBOOST_ELEMENTWISE_METRIC_H +#define XGBOOST_ELEMENTWISE_METRIC_H + +#include +#include + +#include "../src/common/common.h" + +#if defined(XGBOOST_USE_CUDA) +#include +#include +#include +#include // thrust::plus<> + +#include "../common/device_helpers.cuh" +#endif // XGBOOST_USE_CUDA + +/*! + * \brief base class of element-wise evaluation + * \tparam Derived the name of subclass + */ +namespace xgboost { +namespace metric { + template + class ElementWiseMetricsReduction { + public: + explicit ElementWiseMetricsReduction(EvalRow policy) : + policy_(std::move(policy)) {} + + PackedReduceResult CpuReduceMetrics( + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds) const { + size_t ndata = labels.Size(); + + const auto& h_labels = labels.HostVector(); + const auto& h_weights = weights.HostVector(); + const auto& h_preds = preds.HostVector(); + + bst_float residue_sum = 0; + bst_float weights_sum = 0; + +#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) + for (omp_ulong i = 0; i < ndata; ++i) { + const bst_float wt = h_weights.size() > 0 ? h_weights[i] : 1.0f; + residue_sum += policy_.EvalRow(h_labels[i], h_preds[i]) * wt; + weights_sum += wt; + } + PackedReduceResult res { residue_sum, weights_sum }; + return res; + } + +#if defined(XGBOOST_USE_CUDA) + + PackedReduceResult DeviceReduceMetrics( + GPUSet::GpuIdType device_id, + size_t device_index, + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds) { + size_t n_data = preds.DeviceSize(device_id); + + thrust::counting_iterator begin(0); + thrust::counting_iterator end = begin + n_data; + + auto s_label = labels.DeviceSpan(device_id); + auto s_preds = preds.DeviceSpan(device_id); + auto s_weights = weights.DeviceSpan(device_id); + + bool const is_null_weight = weights.Size() == 0; + + auto d_policy = policy_; + + PackedReduceResult result = thrust::transform_reduce( + thrust::cuda::par(allocators_.at(device_index)), + begin, end, + [=] XGBOOST_DEVICE(size_t idx) { + bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; + + bst_float residue = d_policy.EvalRow(s_label[idx], s_preds[idx]); + residue *= weight; + return PackedReduceResult{ residue, weight }; + }, + PackedReduceResult(), + thrust::plus()); + + return result; + } + +#endif // XGBOOST_USE_CUDA + + PackedReduceResult Reduce( + GPUSet devices, + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds) { + PackedReduceResult result; + + if (devices.IsEmpty()) { + result = CpuReduceMetrics(weights, labels, preds); + } +#if defined(XGBOOST_USE_CUDA) + else { // NOLINT + if (allocators_.size() != devices.Size()) { + allocators_.clear(); + allocators_.resize(devices.Size()); + } + preds.Reshard(devices); + labels.Reshard(devices); + weights.Reshard(devices); + std::vector res_per_device(devices.Size()); + +#pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) + for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { + dh::safe_cuda(cudaSetDevice(id)); + size_t index = devices.Index(id); + res_per_device.at(index) = + DeviceReduceMetrics(id, index, weights, labels, preds); + } + + for (auto const& res : res_per_device) { + result += res; + } + } +#endif // defined(XGBOOST_USE_CUDA) + return result; + } + + private: + EvalRow policy_; +#if defined(XGBOOST_USE_CUDA) + std::vector allocators_; +#endif // defined(XGBOOST_USE_CUDA) + }; + + template + class MetricsReduction { + public: + class PackedReduceResult { + double residue_sum_; + double weights_sum_; + friend MetricsReduction; + + public: + XGBOOST_DEVICE PackedReduceResult() : residue_sum_{0}, weights_sum_{0} {} + + XGBOOST_DEVICE PackedReduceResult(double residue, double weight) : + residue_sum_{residue}, weights_sum_{weight} {} + + XGBOOST_DEVICE + PackedReduceResult operator+(PackedReduceResult const &other) const { + return PackedReduceResult{residue_sum_ + other.residue_sum_, + weights_sum_ + other.weights_sum_}; + } + + double Residue() const { return residue_sum_; } + + double Weights() const { return weights_sum_; } + }; + + public: + explicit MetricsReduction(EvalRow policy) : + policy_(std::move(policy)) {} + + PackedReduceResult CpuReduceMetrics( + const HostDeviceVector &weights, + const HostDeviceVector &labels, + const HostDeviceVector &preds) const { + size_t ndata = labels.Size(); + + const auto &h_labels = labels.HostVector(); + const auto &h_weights = weights.HostVector(); + const auto &h_preds = preds.HostVector(); + + bst_float residue_sum = 0; + bst_float weights_sum = 0; + +#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) + for (omp_ulong i = 0; i < ndata; ++i) { + const bst_float wt = h_weights.size() > 0 ? h_weights[i] : 1.0f; + residue_sum += policy_.EvalRow(h_labels[i], h_preds[i]) * wt; + weights_sum += wt; + } + PackedReduceResult res{residue_sum, weights_sum}; + return res; + } + +#if defined(XGBOOST_USE_CUDA) + + PackedReduceResult DeviceReduceMetrics( + GPUSet::GpuIdType device_id, + size_t device_index, + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds) { +size_t n_data = preds.DeviceSize(device_id); + +thrust::counting_iterator begin(0); +thrust::counting_iterator end = begin + n_data; + +auto s_label = labels.DeviceSpan(device_id); +auto s_preds = preds.DeviceSpan(device_id); +auto s_weights = weights.DeviceSpan(device_id); + +bool const is_null_weight = weights.Size() == 0; + +auto d_policy = policy_; + +PackedReduceResult result = thrust::transform_reduce( + thrust::cuda::par(allocators_.at(device_index)), + begin, end, + [=] XGBOOST_DEVICE(size_t idx) { + bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; + + bst_float residue = d_policy.EvalRow(s_label[idx], s_preds[idx]); + residue *= weight; + return PackedReduceResult{ residue, weight }; + }, + PackedReduceResult(), + thrust::plus()); + +return result; +} + +#endif // XGBOOST_USE_CUDA + + PackedReduceResult Reduce( + GPUSet devices, + const HostDeviceVector &weights, + const HostDeviceVector &labels, + const HostDeviceVector &preds) { + PackedReduceResult result; + + if (devices.IsEmpty()) { + result = CpuReduceMetrics(weights, labels, preds); + } +#if defined(XGBOOST_USE_CUDA) + else { // NOLINT + if (allocators_.size() != devices.Size()) { + allocators_.clear(); + allocators_.resize(devices.Size()); + } + preds.Reshard(devices); + labels.Reshard(devices); + weights.Reshard(devices); + std::vector res_per_device(devices.Size()); + +#pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) + for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { + dh::safe_cuda(cudaSetDevice(id)); + size_t index = devices.Index(id); + res_per_device.at(index) = + DeviceReduceMetrics(id, index, weights, labels, preds); + } + + for (size_t i = 0; i < devices.Size(); ++i) { + result.residue_sum_ += res_per_device[i].residue_sum_; + result.weights_sum_ += res_per_device[i].weights_sum_; + } +} +#endif // defined(XGBOOST_USE_CUDA) + return result; + } + + private: + EvalRow policy_; +#if defined(XGBOOST_USE_CUDA) + std::vector allocators_; +#endif // defined(XGBOOST_USE_CUDA) + }; + + template + struct EvalEWiseBase : public Metric { + EvalEWiseBase() : policy_{}, reducer_{policy_} {} + + EvalEWiseBase(Policy& policy) : policy_{policy}, reducer_{policy_} {} + + explicit EvalEWiseBase(char const *policy_param) : + policy_{policy_param}, reducer_{policy_} {} + + void Configure( + const std::vector> &args) override { + param_.InitAllowUnknown(args); + } + + bst_float Eval(const HostDeviceVector &preds, + const MetaInfo &info, + bool distributed) override { + CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; + CHECK_EQ(preds.Size(), info.labels_.Size()) + << "label and prediction size not match, " + << "hint: use merror or mlogloss for multi-class classification"; + const auto ndata = static_cast(info.labels_.Size()); + // Dealing with ndata < n_gpus. + GPUSet devices = GPUSet::All(param_.gpu_id, param_.n_gpus, ndata); + + auto result = + reducer_.Reduce(devices, info.weights_, info.labels_, preds); + + double dat[2]{result.Residue(), result.Weights()}; + if (distributed) { + rabit::Allreduce(dat, 2); + } + return Policy::GetFinal(dat[0], dat[1]); + } + + const char *Name() const override { + return policy_.Name(); + } + + private: + Policy policy_; + + MetricParam param_; + + MetricsReduction reducer_; + }; + } +} +#endif //XGBOOST_ELEMENTWISE_METRIC_H diff --git a/include/xgboost/metric_param.h b/include/xgboost/metric_param.h new file mode 100644 index 000000000000..bc2a687572d0 --- /dev/null +++ b/include/xgboost/metric_param.h @@ -0,0 +1,31 @@ +/*! + * Copyright 2018 by Contributors + * \file metric_param.cc + */ +#ifndef XGBOOST_METRIC_METRIC_PARAM_H_ +#define XGBOOST_METRIC_METRIC_PARAM_H_ + +#include +#include "../../src/common/common.h" + +namespace xgboost { +namespace metric { + +// Created exclusively for GPU. +struct MetricParam : public dmlc::Parameter { + int n_gpus; + int gpu_id; + DMLC_DECLARE_PARAMETER(MetricParam) { + DMLC_DECLARE_FIELD(n_gpus).set_default(1).set_lower_bound(GPUSet::kAll) + .describe("Number of GPUs to use for multi-gpu algorithms."); + DMLC_DECLARE_FIELD(gpu_id) + .set_lower_bound(0) + .set_default(0) + .describe("gpu to use for objective function evaluation"); + }; +}; + +} // namespace metric +} // namespace xgboost + +#endif // XGBOOST_METRIC_METRIC_PARAM_H_ diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index 5e00f59cd9cc..b6fea917f4b8 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -202,7 +202,8 @@ public static Booster train( if (eval instanceof IEvaluationForDistributed) { // TODO int numClasses = 2; - XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses); + XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses, + (IEvaluationForDistributed) eval); } evalInfo = booster.evalSet(evalMats, evalNames, iter, metricsOut); } diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java index c629525ca722..aeb965f0b50a 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java @@ -99,7 +99,8 @@ public final static native int XGBoosterPredict(long handle, long dmat, int opti int ntree_limit, float[][] predicts); public final static native int XGBoosterAddNewMetrics(long handle, String metricsName, - int numClasses); + int numClasses, + IEvaluationForDistributed eval); public final static native int XGBoosterLoadModel(long handle, String fname); diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 9ba96ca60e6e..d50e5ccd7f66 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "./xgboost4j.h" #include #include @@ -154,14 +155,16 @@ typedef void *CustomEvalHandle; // NOLINT(*) // bridge classes for customized metrics class CustomEvalElementWise { - +public: CustomEvalElementWise(std::string& name, CustomEvalHandle handle): metrics_name(name), custom_eval_handle(handle) { - int jni_status = global_jvm->GetEnv((void **)&jenv, JNI_VERSION_1_6); - if (jni_status == JNI_EDETACHED) { - global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); - } else { - CHECK(jni_status == JNI_OK); + if (jenv == nullptr) { + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } } } @@ -174,7 +177,7 @@ class CustomEvalElementWise { return 0.0f; } - xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { + static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); jmethodID getFinal = jenv->GetMethodID(eval_interface, @@ -182,13 +185,20 @@ class CustomEvalElementWise { return 0.0f; } + const char *Name() const { + return metrics_name.data(); + } + private: std::string metrics_name; CustomEvalHandle custom_eval_handle; - JNIEnv* jenv; + + static JNIEnv* jenv; }; +JNIEnv* CustomEvalElementWise::jenv = nullptr; + /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBGetLastError @@ -817,9 +827,13 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr */ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name, - jint num_classes) { - #include "../../../../src/metric/elementwise_metric.cu" - xgboost::metric::ElementWiseMetricsRegister::registerCustomMetrics(metrics_name); + jint num_classes, jobject custom_eval) { + std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics") + .set_body([&metrics_name_in_str, &custom_eval](const char* param) { + return new xgboost::metric::EvalEWiseBase( + *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); }); return 0; } diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.h b/jvm-packages/xgboost4j/src/native/xgboost4j.h index 4f886b2dce66..db40ad2462e0 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.h +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.h @@ -261,7 +261,7 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr * Signature: (JLjava/lang/String;)I */ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics - (JNIEnv *, jclass, jlong, jstring, jint); + (JNIEnv *, jclass, jlong, jstring, jint, jobject); /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI diff --git a/src/metric/elementwise_metric.cu b/src/metric/elementwise_metric.cu index cace252832a3..851f1d419aa5 100644 --- a/src/metric/elementwise_metric.cu +++ b/src/metric/elementwise_metric.cu @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -20,123 +22,14 @@ #include "../common/device_helpers.cuh" #endif // XGBOOST_USE_CUDA +#include "../common/math.h" +#include "../common/common.h" + namespace xgboost { namespace metric { // tag the this file, used by force static link later. DMLC_REGISTRY_FILE_TAG(elementwise_metric); -template -class ElementWiseMetricsReduction { - public: - explicit ElementWiseMetricsReduction(EvalRow policy) : - policy_(std::move(policy)) {} - - PackedReduceResult CpuReduceMetrics( - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) const { - size_t ndata = labels.Size(); - - const auto& h_labels = labels.HostVector(); - const auto& h_weights = weights.HostVector(); - const auto& h_preds = preds.HostVector(); - - bst_float residue_sum = 0; - bst_float weights_sum = 0; - -#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) - for (omp_ulong i = 0; i < ndata; ++i) { - const bst_float wt = h_weights.size() > 0 ? h_weights[i] : 1.0f; - residue_sum += policy_.EvalRow(h_labels[i], h_preds[i]) * wt; - weights_sum += wt; - } - PackedReduceResult res { residue_sum, weights_sum }; - return res; - } - -#if defined(XGBOOST_USE_CUDA) - - PackedReduceResult DeviceReduceMetrics( - GPUSet::GpuIdType device_id, - size_t device_index, - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) { - size_t n_data = preds.DeviceSize(device_id); - - thrust::counting_iterator begin(0); - thrust::counting_iterator end = begin + n_data; - - auto s_label = labels.DeviceSpan(device_id); - auto s_preds = preds.DeviceSpan(device_id); - auto s_weights = weights.DeviceSpan(device_id); - - bool const is_null_weight = weights.Size() == 0; - - auto d_policy = policy_; - - PackedReduceResult result = thrust::transform_reduce( - thrust::cuda::par(allocators_.at(device_index)), - begin, end, - [=] XGBOOST_DEVICE(size_t idx) { - bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; - - bst_float residue = d_policy.EvalRow(s_label[idx], s_preds[idx]); - residue *= weight; - return PackedReduceResult{ residue, weight }; - }, - PackedReduceResult(), - thrust::plus()); - - return result; - } - -#endif // XGBOOST_USE_CUDA - - PackedReduceResult Reduce( - GPUSet devices, - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) { - PackedReduceResult result; - - if (devices.IsEmpty()) { - result = CpuReduceMetrics(weights, labels, preds); - } -#if defined(XGBOOST_USE_CUDA) - else { // NOLINT - if (allocators_.size() != devices.Size()) { - allocators_.clear(); - allocators_.resize(devices.Size()); - } - preds.Reshard(devices); - labels.Reshard(devices); - weights.Reshard(devices); - std::vector res_per_device(devices.Size()); - -#pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) - for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { - dh::safe_cuda(cudaSetDevice(id)); - size_t index = devices.Index(id); - res_per_device.at(index) = - DeviceReduceMetrics(id, index, weights, labels, preds); - } - - for (auto const& res : res_per_device) { - result += res; - } - } -#endif // defined(XGBOOST_USE_CUDA) - return result; - } - - private: - EvalRow policy_; -#if defined(XGBOOST_USE_CUDA) - std::vector allocators_; -#endif // defined(XGBOOST_USE_CUDA) -}; - struct EvalRowRMSE { char const *Name() const { return "rmse"; @@ -302,63 +195,6 @@ struct EvalTweedieNLogLik { protected: bst_float rho_; }; -/*! - * \brief base class of element-wise evaluation - * \tparam Derived the name of subclass - */ -template -struct EvalEWiseBase : public Metric { - EvalEWiseBase() : policy_{}, reducer_{policy_} {} - explicit EvalEWiseBase(char const* policy_param) : - policy_{policy_param}, reducer_{policy_} {} - - void Configure( - const std::vector >& args) override { - param_.InitAllowUnknown(args); - } - - bst_float Eval(const HostDeviceVector& preds, - const MetaInfo& info, - bool distributed) override { - CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; - CHECK_EQ(preds.Size(), info.labels_.Size()) - << "label and prediction size not match, " - << "hint: use merror or mlogloss for multi-class classification"; - const auto ndata = static_cast(info.labels_.Size()); - // Dealing with ndata < n_gpus. - GPUSet devices = GPUSet::All(param_.gpu_id, param_.n_gpus, ndata); - - auto result = - reducer_.Reduce(devices, info.weights_, info.labels_, preds); - - double dat[2] { result.Residue(), result.Weights() }; - if (distributed) { - rabit::Allreduce(dat, 2); - } - return Policy::GetFinal(dat[0], dat[1]); - } - - const char* Name() const override { - return policy_.Name(); - } - - private: - Policy policy_; - - MetricParam param_; - - ElementWiseMetricsReduction reducer_; -}; - -class ElementWiseMetricsRegister { - - template - static void RegisterCustomMetrics(std::string metrics_name) { - XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name) - .describe("customized metrics") - .set_body([](const char* param) { return new EvalEWiseBase(); }); - } -}; XGBOOST_REGISTER_METRIC(RMSE, "rmse") .describe("Rooted mean square error.") diff --git a/src/metric/metric.cc b/src/metric/metric.cc index ad54a3cf5b95..f4c3fc34d045 100644 --- a/src/metric/metric.cc +++ b/src/metric/metric.cc @@ -4,6 +4,7 @@ * \brief Registry of objective functions. */ #include +#include #include From 5a302772fd256cad3a1fc32b3029f9c270ea7c98 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Wed, 20 Mar 2019 14:25:01 +0800 Subject: [PATCH 04/39] basic framework --- .../scala/spark/DistributedEvalError.scala | 6 ++- .../scala/spark/XGBoostGeneralSuite.scala | 2 +- .../java/ml/dmlc/xgboost4j/java/XGBoost.java | 45 +++++++++++++------ .../xgboost4j/src/native/xgboost4j.cpp | 36 +++++++++++---- 4 files changed, 63 insertions(+), 26 deletions(-) diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala index b696a142dcb0..7a2f62381837 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala @@ -40,10 +40,12 @@ class DistributedEvalError extends EvalTrait with IEvaluationForDistributed { /** * calculate the metrics for a single row given its label and prediction */ - override def evalRow(label: Float, pred: Float): Float = 0.0f + override def evalRow(label: Float, pred: Float): Float = 1.0f /** * perform transformation with the sum of error and weights to get the final evaluation metrics */ - override def getFinal(errorSum: Float, weightSum: Float): Float = 0.0f + override def getFinal(errorSum: Float, weightSum: Float): Float = { + errorSum / weightSum + } } diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala index c069e08a2de4..a145e0c7c6f3 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala @@ -91,7 +91,7 @@ class XGBoostGeneralSuite extends FunSuite with PerTest { test("distributed training with customized evaluation metrics") { val trainingRDD = sc.parallelize(Classification.train) - val (booster, metrics) = XGBoost.trainDistributed( + val (booster, _) = XGBoost.trainDistributed( trainingRDD, List("eta" -> "1", "max_depth" -> "6", "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index b6fea917f4b8..41f4904aa594 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -24,11 +24,10 @@ /** * trainer for xgboost - * - * @author hzx */ public class XGBoost { private static final Log logger = LogFactory.getLog(XGBoost.class); + private static boolean initializedEval = false; /** * load model from modelPath @@ -108,6 +107,35 @@ public static Booster train( return train(dtrain, params, round, watches, metrics, obj, eval, earlyStoppingRound, null); } + private static synchronized void registerNewCustomEvalForDistributed( + Booster booster, IEvaluation eval) { + if (!initializedEval) { + int numClasses = 2; + XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses, + (IEvaluationForDistributed) eval); + initializedEval = true; + } + } + + private static String performEvaluation( + Booster booster, + IEvaluation eval, + String[] evalNames, + DMatrix[] evalMats, + int iter, + float[] metricsOut) throws XGBoostError { + String evalInfo; + if (eval != null && !(eval instanceof IEvaluationForDistributed)) { + evalInfo = booster.evalSet(evalMats, evalNames, eval, metricsOut); + } else { + if (eval != null) { + registerNewCustomEvalForDistributed(booster, eval); + } + evalInfo = booster.evalSet(evalMats, evalNames, iter, metricsOut); + } + return evalInfo; + } + /** * Train a booster given parameters. * @@ -195,18 +223,7 @@ public static Booster train( //evaluation if (evalMats.length > 0) { float[] metricsOut = new float[evalMats.length]; - String evalInfo; - if (eval != null && !(eval instanceof IEvaluationForDistributed)) { - evalInfo = booster.evalSet(evalMats, evalNames, eval, metricsOut); - } else { - if (eval instanceof IEvaluationForDistributed) { - // TODO - int numClasses = 2; - XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses, - (IEvaluationForDistributed) eval); - } - evalInfo = booster.evalSet(evalMats, evalNames, iter, metricsOut); - } + String evalInfo = performEvaluation(booster, eval, evalNames, evalMats, iter, metricsOut); for (int i = 0; i < metricsOut.length; i++) { metrics[i][iter] = metricsOut[i]; } diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index d50e5ccd7f66..c7e93a03b11b 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -157,7 +157,7 @@ typedef void *CustomEvalHandle; // NOLINT(*) class CustomEvalElementWise { public: CustomEvalElementWise(std::string& name, CustomEvalHandle handle): - metrics_name(name), custom_eval_handle(handle) { + metrics_name(name) { if (jenv == nullptr) { int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -165,6 +165,7 @@ class CustomEvalElementWise { } else { CHECK(jni_status == JNI_OK); } + custom_eval_handle = handle; } } @@ -174,7 +175,8 @@ class CustomEvalElementWise { jmethodID evalRow = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F;"); - return 0.0f; + jobject jeval = static_cast(custom_eval_handle); + return jenv->CallFloatMethod(jeval, evalRow, label, pred); } static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { @@ -182,7 +184,8 @@ class CustomEvalElementWise { jmethodID getFinal = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F;"); - return 0.0f; + jobject jeval = static_cast(custom_eval_handle); + return jenv->CallFloatMethod(jeval, getFinal, esum, wsum); } const char *Name() const { @@ -192,12 +195,13 @@ class CustomEvalElementWise { private: std::string metrics_name; - CustomEvalHandle custom_eval_handle; + static CustomEvalHandle custom_eval_handle; static JNIEnv* jenv; }; JNIEnv* CustomEvalElementWise::jenv = nullptr; +CustomEvalHandle CustomEvalElementWise::custom_eval_handle = nullptr; /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI @@ -829,11 +833,25 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name, jint num_classes, jobject custom_eval) { std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); - XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) - .describe("customized metrics") - .set_body([&metrics_name_in_str, &custom_eval](const char* param) { - return new xgboost::metric::EvalEWiseBase( - *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); }); + /** + * num_classes < 0 => ranking + * num_classes == 0 => regression + * num_classes == 2 => binary classification + * num_classes > 2 => multi_classes classification + * else => invalid + */ + if (num_classes == 2 || num_classes == 0) { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics") + .set_body([&metrics_name_in_str, &custom_eval](const char *param) { + return new xgboost::metric::EvalEWiseBase( + *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); + }); + } else if (num_classes > 2) { + + } else { + // ranking + } return 0; } From 4d9361498f3107a1e028f49a41b1e8d8fe5c7cd6 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Fri, 22 Mar 2019 01:48:52 +0800 Subject: [PATCH 05/39] fixing metrics adding logic --- include/xgboost/c_api.h | 5 ++ include/xgboost/learner.h | 5 +- .../dmlc/xgboost4j/scala/spark/XGBoost.scala | 7 ++- .../xgboost4j/java/DistributedEvalError.java | 25 +++++++++ .../scala/spark/CustomizedEvalSuite.scala | 34 +++++++++++++ .../scala/spark/DistributedEvalError.scala | 51 ------------------- .../scala/spark/XGBoostGeneralSuite.scala | 11 ---- .../java/ml/dmlc/xgboost4j/java/Booster.java | 5 +- .../ml/dmlc/xgboost4j/scala/XGBoost.scala | 4 +- .../xgboost4j/src/native/xgboost4j.cpp | 42 +++++++-------- src/c_api/c_api.cc | 10 ++++ 11 files changed, 108 insertions(+), 91 deletions(-) create mode 100644 jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java create mode 100644 jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala delete mode 100644 jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala diff --git a/include/xgboost/c_api.h b/include/xgboost/c_api.h index 3328aba88316..e0995c80ffd4 100644 --- a/include/xgboost/c_api.h +++ b/include/xgboost/c_api.h @@ -11,6 +11,7 @@ #define XGB_EXTERN_C extern "C" #include #include +#include #else #define XGB_EXTERN_C #include @@ -565,4 +566,8 @@ XGB_DLL int XGBoosterLoadRabitCheckpoint( */ XGB_DLL int XGBoosterSaveRabitCheckpoint(BoosterHandle handle); +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string metrics_name); + +XGB_DLL int XGBoosterGetMetricsCount(BoosterHandle handle); + #endif // XGBOOST_C_API_H_ diff --git a/include/xgboost/learner.h b/include/xgboost/learner.h index 187d27a2d6ab..8ddec35ccf87 100644 --- a/include/xgboost/learner.h +++ b/include/xgboost/learner.h @@ -186,6 +186,9 @@ class Learner : public rabit::Serializable { */ virtual const std::map& GetConfigurationArguments() const = 0; + /*! \brief The evaluation metrics used to evaluate the model. */ + std::vector > metrics_; + protected: /*! \brief internal base score of the model */ bst_float base_score_; @@ -193,8 +196,6 @@ class Learner : public rabit::Serializable { std::unique_ptr obj_; /*! \brief The gradient booster used by the model*/ std::unique_ptr gbm_; - /*! \brief The evaluation metrics used to evaluate the model. */ - std::vector > metrics_; }; // implementation of inline functions. diff --git a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala index 8c0bd55efcdc..137629ab9948 100644 --- a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala +++ b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala @@ -24,8 +24,7 @@ import scala.collection.mutable.ListBuffer import scala.collection.{AbstractIterator, mutable} import scala.util.Random -import ml.dmlc.xgboost4j.java.{IEvaluationForDistributed, IRabitTracker, Rabit, XGBoostError, RabitTracker => PyRabitTracker} -import ml.dmlc.xgboost4j.java.XGBoostJNI +import ml.dmlc.xgboost4j.java.{IEvaluation, IEvaluationForDistributed, IRabitTracker, Rabit, XGBoostError, XGBoostJNI, RabitTracker => PyRabitTracker} import ml.dmlc.xgboost4j.scala.rabit.RabitTracker import ml.dmlc.xgboost4j.scala.{XGBoost => SXGBoost, _} import ml.dmlc.xgboost4j.{LabeledPoint => XGBLabeledPoint} @@ -140,7 +139,7 @@ object XGBoost extends Serializable { rabitEnv: java.util.Map[String, String], round: Int, obj: ObjectiveTrait, - eval: EvalTrait, + eval: IEvaluation, prevBooster: Booster): Iterator[(Booster, Map[String, Array[Float]])] = { // to workaround the empty partitions in training dataset, @@ -284,7 +283,7 @@ object XGBoost extends Serializable { val round = params("num_round").asInstanceOf[Int] val useExternalMemory = params("use_external_memory").asInstanceOf[Boolean] val obj = params.getOrElse("custom_obj", null).asInstanceOf[ObjectiveTrait] - val eval = params.getOrElse("custom_eval", null).asInstanceOf[EvalTrait] + val eval = params.getOrElse("custom_eval", null).asInstanceOf[IEvaluation] val missing = params.getOrElse("missing", Float.NaN).asInstanceOf[Float] validateSparkSslConf(sparkContext) diff --git a/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java b/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java new file mode 100644 index 000000000000..c187036884ce --- /dev/null +++ b/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java @@ -0,0 +1,25 @@ +package ml.dmlc.xgboost4j.java; + +public class DistributedEvalError implements IEvaluation, IEvaluationForDistributed { + + @Override + public float evalRow(float label, float pred) { + System.out.println("aaa"); + return 1.0f; + } + + @Override + public float getFinal(float errorSum, float weightSum) { + return errorSum/weightSum; + } + + @Override + public String getMetric() { + return "distributed_error"; + } + + @Override + public float eval(float[][] predicts, DMatrix dmat) { + return 0; + } +} diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala new file mode 100644 index 000000000000..115b716f0fd8 --- /dev/null +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala @@ -0,0 +1,34 @@ +/* + Copyright (c) 2014 by Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package ml.dmlc.xgboost4j.scala.spark + +import ml.dmlc.xgboost4j.java.DistributedEvalError +import org.scalatest.FunSuite + +class CustomizedEvalSuite extends FunSuite with PerTest { + + private val paramMap = List("eta" -> "1", "max_depth" -> "6", + "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> 1, + "custom_eval" -> new DistributedEvalError).toMap + + test("distributed training with customized evaluation metrics") { + val trainingDF = buildDataFrame(Classification.train, 1) + val xgbModel = new XGBoostClassifier(paramMap).fit(trainingDF) + + println(xgbModel.summary.toString()) + } +} diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala deleted file mode 100644 index 7a2f62381837..000000000000 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright (c) 2014 by Contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -package ml.dmlc.xgboost4j.scala.spark - -import ml.dmlc.xgboost4j.java.IEvaluationForDistributed -import ml.dmlc.xgboost4j.scala.{DMatrix, EvalTrait} - -class DistributedEvalError extends EvalTrait with IEvaluationForDistributed { - - /** - * get evaluate metric - * - * @return evalMetric - */ - override def getMetric: String = "distributed_error" - - /** - * evaluate with predicts and data - * - * @param predicts predictions as array - * @param dmat data matrix to evaluate - * @return result of the metric - */ - override def eval(predicts: Array[Array[Float]], dmat: DMatrix): Float = 0.0f - - /** - * calculate the metrics for a single row given its label and prediction - */ - override def evalRow(label: Float, pred: Float): Float = 1.0f - - /** - * perform transformation with the sum of error and weights to get the final evaluation metrics - */ - override def getFinal(errorSum: Float, weightSum: Float): Float = { - errorSum / weightSum - } -} diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala index a145e0c7c6f3..f39f9d9e7d5e 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/XGBoostGeneralSuite.scala @@ -89,17 +89,6 @@ class XGBoostGeneralSuite extends FunSuite with PerTest { assert(booster != null) } - test("distributed training with customized evaluation metrics") { - val trainingRDD = sc.parallelize(Classification.train) - val (booster, _) = XGBoost.trainDistributed( - trainingRDD, - List("eta" -> "1", "max_depth" -> "6", - "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, - "custom_eval" -> new DistributedEvalError, "custom_obj" -> null, - "use_external_memory" -> false, "missing" -> Float.NaN).toMap) - assert(booster != null) - } - test("training with external memory cache") { val eval = new EvalError() val training = buildDataFrame(Classification.train) diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java index e72a4a908bd2..a4e35d0db10a 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java @@ -223,6 +223,7 @@ public void boost(DMatrix dtrain, float[] grad, float[] hess) throws XGBoostErro public String evalSet(DMatrix[] evalMatrixs, String[] evalNames, int iter) throws XGBoostError { long[] handles = dmatrixsToHandles(evalMatrixs); String[] evalInfo = new String[1]; + System.out.println("evaluating iteration " + iter); XGBoostJNI.checkCall(XGBoostJNI.XGBoosterEvalOneIter(handle, iter, handles, evalNames, evalInfo)); return evalInfo[0]; @@ -243,7 +244,9 @@ public String evalSet(DMatrix[] evalMatrixs, String[] evalNames, int iter, float String stringFormat = evalSet(evalMatrixs, evalNames, iter); String[] metricPairs = stringFormat.split("\t"); for (int i = 1; i < metricPairs.length; i++) { - metricsOut[i - 1] = Float.valueOf(metricPairs[i].split(":")[1]); + // TODO: remove min + metricsOut[Math.min(metricsOut.length - 1, i - 1)] = + Float.valueOf(metricPairs[i].split(":")[1]); } return stringFormat; } diff --git a/jvm-packages/xgboost4j/src/main/scala/ml/dmlc/xgboost4j/scala/XGBoost.scala b/jvm-packages/xgboost4j/src/main/scala/ml/dmlc/xgboost4j/scala/XGBoost.scala index 609d7b2cde8c..138c6e871e75 100644 --- a/jvm-packages/xgboost4j/src/main/scala/ml/dmlc/xgboost4j/scala/XGBoost.scala +++ b/jvm-packages/xgboost4j/src/main/scala/ml/dmlc/xgboost4j/scala/XGBoost.scala @@ -18,7 +18,7 @@ package ml.dmlc.xgboost4j.scala import java.io.InputStream -import ml.dmlc.xgboost4j.java.{Booster => JBooster, XGBoost => JXGBoost, XGBoostError} +import ml.dmlc.xgboost4j.java.{IEvaluation, XGBoostError, Booster => JBooster, XGBoost => JXGBoost} import scala.collection.JavaConverters._ /** @@ -52,7 +52,7 @@ object XGBoost { watches: Map[String, DMatrix] = Map(), metrics: Array[Array[Float]] = null, obj: ObjectiveTrait = null, - eval: EvalTrait = null, + eval: IEvaluation = null, earlyStoppingRound: Int = 0, booster: Booster = null): Booster = { val jWatches = watches.mapValues(_.jDMatrix).asJava diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index c7e93a03b11b..a34a71c6aa69 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -150,13 +150,10 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( } } -/*! \brief handle to a data iterator */ -typedef void *CustomEvalHandle; // NOLINT(*) - // bridge classes for customized metrics class CustomEvalElementWise { public: - CustomEvalElementWise(std::string& name, CustomEvalHandle handle): + CustomEvalElementWise(std::string& name, jobject handle): metrics_name(name) { if (jenv == nullptr) { int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); @@ -166,26 +163,22 @@ class CustomEvalElementWise { CHECK(jni_status == JNI_OK); } custom_eval_handle = handle; + eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + + eval_row_func = jenv->GetMethodID(eval_interface, + "evalRow", "(FF)F"); + get_final_func = jenv->GetMethodID(eval_interface, + "getFinal", "(FF)F"); } } XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, xgboost::bst_float pred) const { - jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); - - jmethodID evalRow = jenv->GetMethodID(eval_interface, - "evalRow", "(FF)F;"); - jobject jeval = static_cast(custom_eval_handle); - return jenv->CallFloatMethod(jeval, evalRow, label, pred); + return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); } static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { - jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); - - jmethodID getFinal = jenv->GetMethodID(eval_interface, - "getFinal", "(FF)F;"); - jobject jeval = static_cast(custom_eval_handle); - return jenv->CallFloatMethod(jeval, getFinal, esum, wsum); + return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); } const char *Name() const { @@ -195,14 +188,20 @@ class CustomEvalElementWise { private: std::string metrics_name; - static CustomEvalHandle custom_eval_handle; - static JNIEnv* jenv; + + static jobject custom_eval_handle; + static jclass eval_interface; + static jmethodID eval_row_func; + static jmethodID get_final_func; + }; JNIEnv* CustomEvalElementWise::jenv = nullptr; -CustomEvalHandle CustomEvalElementWise::custom_eval_handle = nullptr; - +jobject CustomEvalElementWise::custom_eval_handle = nullptr; +jclass CustomEvalElementWise::eval_interface = nullptr; +jmethodID CustomEvalElementWise::eval_row_func = nullptr; +jmethodID CustomEvalElementWise::get_final_func = nullptr; /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBGetLastError @@ -841,12 +840,15 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet * else => invalid */ if (num_classes == 2 || num_classes == 0) { + std::cout << "bst metrics length (before): " << XGBoosterGetMetricsCount((BoosterHandle) jhandle) << "\n"; XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) .describe("customized metrics") .set_body([&metrics_name_in_str, &custom_eval](const char *param) { return new xgboost::metric::EvalEWiseBase( *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); }); + XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); + std::cout << "bst metrics length: " << XGBoosterGetMetricsCount((BoosterHandle) jhandle) << "\n"; } else if (num_classes > 2) { } else { diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index ac9c35c4c7de..33abf9a6e1c0 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1157,5 +1157,15 @@ QueryBoosterConfigurationArguments(BoosterHandle handle) { return bst->learner()->GetConfigurationArguments(); } +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string metrics_name) { + auto* bst = static_cast(handle); + bst->learner()->metrics_.emplace_back(Metric::Create(metrics_name)); +} + +XGB_DLL int XGBoosterGetMetricsCount(BoosterHandle handle) { + auto* bst = static_cast(handle); + return bst->learner()->metrics_.size(); +} + // force link rabit static DMLC_ATTRIBUTE_UNUSED int XGBOOST_LINK_RABIT_C_API_ = RabitLinkTag(); From fa37eeff08c161a3ad033751f9856dbd0595227b Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Fri, 22 Mar 2019 16:18:29 +0800 Subject: [PATCH 06/39] investigation --- .../xgboost4j/java/DistributedEvalError.java | 20 +++++++-- .../java/ml/dmlc/xgboost4j/java/Booster.java | 1 - .../java/IEvaluationForDistributed.java | 6 +++ .../xgboost4j/src/native/xgboost4j.cpp | 45 ++++++++++++++----- src/c_api/c_api.cc | 2 + 5 files changed, 58 insertions(+), 16 deletions(-) diff --git a/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java b/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java index c187036884ce..ce0aeb820f4d 100644 --- a/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java +++ b/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java @@ -1,16 +1,30 @@ package ml.dmlc.xgboost4j.java; -public class DistributedEvalError implements IEvaluation, IEvaluationForDistributed { +public class DistributedEvalError implements IEvaluationForDistributed, IEvaluation { @Override public float evalRow(float label, float pred) { - System.out.println("aaa"); return 1.0f; } @Override public float getFinal(float errorSum, float weightSum) { - return errorSum/weightSum; + return 1.0f; + } + + @Override + public float constant() { + return 0; + } + + @Override + public float constant1(float e) { + return e; + } + + @Override + public boolean hasNext() { + return false; } @Override diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java index a4e35d0db10a..7a84233c748e 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java @@ -223,7 +223,6 @@ public void boost(DMatrix dtrain, float[] grad, float[] hess) throws XGBoostErro public String evalSet(DMatrix[] evalMatrixs, String[] evalNames, int iter) throws XGBoostError { long[] handles = dmatrixsToHandles(evalMatrixs); String[] evalInfo = new String[1]; - System.out.println("evaluating iteration " + iter); XGBoostJNI.checkCall(XGBoostJNI.XGBoosterEvalOneIter(handle, iter, handles, evalNames, evalInfo)); return evalInfo[0]; diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java index 076c3c3fc60a..32ead4a68091 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java @@ -11,4 +11,10 @@ public interface IEvaluationForDistributed { * perform transformation with the sum of error and weights to get the final evaluation metrics */ float getFinal(float errorSum, float weightSum); + + float constant(); + + float constant1(float e); + + boolean hasNext(); } diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index a34a71c6aa69..4a284749b4a5 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -35,13 +35,15 @@ void setHandle(JNIEnv *jenv, jlongArray jhandle, void* handle) { jenv->SetLongArrayRegion(jhandle, 0, 1, &out); } +typedef void *CustomEvalHandle; // NOLINT(*) + // global JVM static JavaVM* global_jvm = nullptr; // overrides JNI on load jint JNI_OnLoad(JavaVM *vm, void *reserved) { global_jvm = vm; - return JNI_VERSION_1_6; + return JNI_VERSION_1_8; } XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( @@ -50,7 +52,7 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( DataHolderHandle set_function_handle) { jobject jiter = static_cast(data_handle); JNIEnv* jenv; - int jni_status = global_jvm->GetEnv((void **)&jenv, JNI_VERSION_1_6); + int jni_status = global_jvm->GetEnv((void **)&jenv, JNI_VERSION_1_8); if (jni_status == JNI_EDETACHED) { global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); } else { @@ -62,6 +64,7 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( "hasNext", "()Z"); jmethodID next = jenv->GetMethodID(iterClass, "next", "()Ljava/lang/Object;"); + int ret_value; if (jenv->CallBooleanMethod(jiter, hasNext)) { ret_value = 1; @@ -153,32 +156,48 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( // bridge classes for customized metrics class CustomEvalElementWise { public: - CustomEvalElementWise(std::string& name, jobject handle): + CustomEvalElementWise(std::string& name, CustomEvalHandle handle): metrics_name(name) { if (jenv == nullptr) { - int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_8); if (jni_status == JNI_EDETACHED) { global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); } else { CHECK(jni_status == JNI_OK); } - custom_eval_handle = handle; + custom_eval_handle = static_cast(handle); eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); - eval_row_func = jenv->GetMethodID(eval_interface, - "evalRow", "(FF)F"); - get_final_func = jenv->GetMethodID(eval_interface, - "getFinal", "(FF)F"); + eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); + get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); + func1 = jenv->GetMethodID(eval_interface, "hasNext", "()Z"); + jenv->CallBooleanMethod(custom_eval_handle, func1); } } XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, xgboost::bst_float pred) const { - return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); + // return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); + return 1.0f; } static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { - return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); + // jmethodID c2 = jenv->GetMethodID(eval_interface, "constant1", "(F)F"); + JNIEnv* jenv; + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_8); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } + jclass eval_interface_local = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + jmethodID x = jenv->GetMethodID(eval_interface_local, "constant", "()F"); + jobject custom_eval_handle_local = custom_eval_handle; + // float a1 = jenv->CallFloatMethod(custom_eval_handle_local, x); + // float a2 = jenv->CallFloatMethod(custom_eval_handle, get_final_func, 1.0f); + return 1.0f; + // return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); + // return 1.0f; } const char *Name() const { @@ -194,7 +213,7 @@ class CustomEvalElementWise { static jclass eval_interface; static jmethodID eval_row_func; static jmethodID get_final_func; - + static jmethodID func1; }; JNIEnv* CustomEvalElementWise::jenv = nullptr; @@ -202,6 +221,8 @@ jobject CustomEvalElementWise::custom_eval_handle = nullptr; jclass CustomEvalElementWise::eval_interface = nullptr; jmethodID CustomEvalElementWise::eval_row_func = nullptr; jmethodID CustomEvalElementWise::get_final_func = nullptr; +jmethodID CustomEvalElementWise::func1 = nullptr; + /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBGetLastError diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index 33abf9a6e1c0..8e914c6370f4 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1160,6 +1160,8 @@ QueryBoosterConfigurationArguments(BoosterHandle handle) { XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string metrics_name) { auto* bst = static_cast(handle); bst->learner()->metrics_.emplace_back(Metric::Create(metrics_name)); + bst->learner()->metrics_.back()->Configure(bst->learner()->GetConfigurationArguments().begin(), + bst->learner()->GetConfigurationArguments().end()); } XGB_DLL int XGBoosterGetMetricsCount(BoosterHandle handle) { From 640dd19c7c7e08d4013718895d11b25eee4ae4c9 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Fri, 22 Mar 2019 16:53:42 +0800 Subject: [PATCH 07/39] fix refernce problem --- .../xgboost4j/src/native/xgboost4j.cpp | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 4a284749b4a5..78d425095d10 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -165,39 +165,21 @@ class CustomEvalElementWise { } else { CHECK(jni_status == JNI_OK); } - custom_eval_handle = static_cast(handle); - eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); - - eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); - get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); - func1 = jenv->GetMethodID(eval_interface, "hasNext", "()Z"); - jenv->CallBooleanMethod(custom_eval_handle, func1); + custom_eval_handle = jenv->NewGlobalRef(static_cast(handle)); } } XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, xgboost::bst_float pred) const { - // return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); - return 1.0f; + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); + return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); } static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { - // jmethodID c2 = jenv->GetMethodID(eval_interface, "constant1", "(F)F"); - JNIEnv* jenv; - int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_8); - if (jni_status == JNI_EDETACHED) { - global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); - } else { - CHECK(jni_status == JNI_OK); - } - jclass eval_interface_local = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); - jmethodID x = jenv->GetMethodID(eval_interface_local, "constant", "()F"); - jobject custom_eval_handle_local = custom_eval_handle; - // float a1 = jenv->CallFloatMethod(custom_eval_handle_local, x); - // float a2 = jenv->CallFloatMethod(custom_eval_handle, get_final_func, 1.0f); - return 1.0f; - // return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); - // return 1.0f; + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + jmethodID get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); + return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); } const char *Name() const { @@ -206,7 +188,6 @@ class CustomEvalElementWise { private: std::string metrics_name; - static JNIEnv* jenv; static jobject custom_eval_handle; From 642a8fc8545d454ef6b779973f5235f28d7b97e7 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Sat, 23 Mar 2019 23:08:39 +0800 Subject: [PATCH 08/39] fix race condition when registering new customized functions --- .../scala/spark/CustomizedEvalSuite.scala | 4 +- .../java/ml/dmlc/xgboost4j/java/XGBoost.java | 11 ++---- .../xgboost4j/src/native/xgboost4j.cpp | 39 +++++++++---------- src/c_api/c_api.cc | 3 ++ 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala index 115b716f0fd8..34ab03b44468 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala @@ -22,11 +22,11 @@ import org.scalatest.FunSuite class CustomizedEvalSuite extends FunSuite with PerTest { private val paramMap = List("eta" -> "1", "max_depth" -> "6", - "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> 1, + "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, "custom_eval" -> new DistributedEvalError).toMap test("distributed training with customized evaluation metrics") { - val trainingDF = buildDataFrame(Classification.train, 1) + val trainingDF = buildDataFrame(Classification.train, numWorkers) val xgbModel = new XGBoostClassifier(paramMap).fit(trainingDF) println(xgbModel.summary.toString()) diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index 41f4904aa594..80eb82b5b84b 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -107,14 +107,11 @@ public static Booster train( return train(dtrain, params, round, watches, metrics, obj, eval, earlyStoppingRound, null); } - private static synchronized void registerNewCustomEvalForDistributed( + private static void registerNewCustomEvalForDistributed( Booster booster, IEvaluation eval) { - if (!initializedEval) { - int numClasses = 2; - XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses, - (IEvaluationForDistributed) eval); - initializedEval = true; - } + int numClasses = 2; + XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses, + (IEvaluationForDistributed) eval); } private static String performEvaluation( diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 78d425095d10..9a0bd4077873 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -43,7 +43,7 @@ static JavaVM* global_jvm = nullptr; // overrides JNI on load jint JNI_OnLoad(JavaVM *vm, void *reserved) { global_jvm = vm; - return JNI_VERSION_1_8; + return JNI_VERSION_1_6; } XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( @@ -52,7 +52,7 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( DataHolderHandle set_function_handle) { jobject jiter = static_cast(data_handle); JNIEnv* jenv; - int jni_status = global_jvm->GetEnv((void **)&jenv, JNI_VERSION_1_8); + int jni_status = global_jvm->GetEnv((void **)&jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); } else { @@ -158,25 +158,31 @@ class CustomEvalElementWise { public: CustomEvalElementWise(std::string& name, CustomEvalHandle handle): metrics_name(name) { - if (jenv == nullptr) { - int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_8); - if (jni_status == JNI_EDETACHED) { - global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); - } else { - CHECK(jni_status == JNI_OK); - } + JNIEnv* jenv; + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } + std::lock_guard guard(eval_handle_mutex); + if (custom_eval_handle == nullptr) { custom_eval_handle = jenv->NewGlobalRef(static_cast(handle)); } } XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, xgboost::bst_float pred) const { + JNIEnv* jenv; + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); } static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { + JNIEnv* jenv; + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); jmethodID get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); @@ -188,21 +194,14 @@ class CustomEvalElementWise { private: std::string metrics_name; - static JNIEnv* jenv; static jobject custom_eval_handle; - static jclass eval_interface; - static jmethodID eval_row_func; - static jmethodID get_final_func; - static jmethodID func1; + /*! \brief lock guarding the registering*/ + static std::mutex eval_handle_mutex; }; -JNIEnv* CustomEvalElementWise::jenv = nullptr; +std::mutex CustomEvalElementWise::eval_handle_mutex; jobject CustomEvalElementWise::custom_eval_handle = nullptr; -jclass CustomEvalElementWise::eval_interface = nullptr; -jmethodID CustomEvalElementWise::eval_row_func = nullptr; -jmethodID CustomEvalElementWise::get_final_func = nullptr; -jmethodID CustomEvalElementWise::func1 = nullptr; /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI @@ -842,7 +841,6 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet * else => invalid */ if (num_classes == 2 || num_classes == 0) { - std::cout << "bst metrics length (before): " << XGBoosterGetMetricsCount((BoosterHandle) jhandle) << "\n"; XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) .describe("customized metrics") .set_body([&metrics_name_in_str, &custom_eval](const char *param) { @@ -850,7 +848,6 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); }); XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); - std::cout << "bst metrics length: " << XGBoosterGetMetricsCount((BoosterHandle) jhandle) << "\n"; } else if (num_classes > 2) { } else { diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index 8e914c6370f4..1ddaf04b37cf 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1159,6 +1159,9 @@ QueryBoosterConfigurationArguments(BoosterHandle handle) { XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string metrics_name) { auto* bst = static_cast(handle); + // note: this function is only called by jvm packages for now which does not support multiple + // evaluation metrics, as a result, we clear all registered metrics and add the new customized one + bst->learner()->metrics_.clear(); bst->learner()->metrics_.emplace_back(Metric::Create(metrics_name)); bst->learner()->metrics_.back()->Configure(bst->learner()->GetConfigurationArguments().begin(), bst->learner()->GetConfigurationArguments().end()); From 1cabe7049f3a5af8b2ca1612419c6e6281b2da6b Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Sun, 24 Mar 2019 09:19:47 +0800 Subject: [PATCH 09/39] temporarily to private dmlc-core --- .gitmodules | 2 +- dmlc-core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index dbf7ee1a4496..baef331c4f73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "dmlc-core"] path = dmlc-core - url = https://github.com/dmlc/dmlc-core + url = https://github.com/CodingCat/dmlc-core [submodule "rabit"] path = rabit url = https://github.com/dmlc/rabit diff --git a/dmlc-core b/dmlc-core index ac983092ee3b..e490ac40957f 160000 --- a/dmlc-core +++ b/dmlc-core @@ -1 +1 @@ -Subproject commit ac983092ee3b339f76a2d7e7c3b846570218200d +Subproject commit e490ac40957fe69bb6d74030c492113b9f5a26c9 From c3540baccdd31b65186eda4e83cc64406f6b0519 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Sun, 24 Mar 2019 21:33:51 +0800 Subject: [PATCH 10/39] update dmlc-core --- .gitmodules | 2 +- dmlc-core | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index baef331c4f73..dbf7ee1a4496 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "dmlc-core"] path = dmlc-core - url = https://github.com/CodingCat/dmlc-core + url = https://github.com/dmlc/dmlc-core [submodule "rabit"] path = rabit url = https://github.com/dmlc/rabit diff --git a/dmlc-core b/dmlc-core index e490ac40957f..3ffea8694adf 160000 --- a/dmlc-core +++ b/dmlc-core @@ -1 +1 @@ -Subproject commit e490ac40957fe69bb6d74030c492113b9f5a26c9 +Subproject commit 3ffea8694adf9c0363f9abbf162dc0e4a45b22c5 From b6dafee37464e9c332dffebd0f7eb1411fc9b430 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Sun, 24 Mar 2019 23:45:40 +0800 Subject: [PATCH 11/39] support multi_classes --- include/xgboost/multiclass_metric.h | 89 +++++++++++++++++++ .../dmlc/xgboost4j/scala/spark/XGBoost.scala | 16 ++-- .../xgboost4j/java/DistributedEvalError.java | 39 -------- .../scala/spark/CustomizedEvalSuite.scala | 26 ++++-- .../scala/spark/DistributedEvalError.scala | 54 +++++++++++ jvm-packages/xgboost4j/pom.xml | 9 ++ .../java/ml/dmlc/xgboost4j/java/Booster.java | 4 +- ....java => IEvalElementWiseDistributed.java} | 11 ++- .../java/IEvalMultiClassesDistributed.java | 22 +++++ .../java/ml/dmlc/xgboost4j/java/XGBoost.java | 18 ++-- .../ml/dmlc/xgboost4j/java/XGBoostJNI.java | 4 +- .../xgboost4j/src/native/xgboost4j.cpp | 79 ++++++++++++---- jvm-packages/xgboost4j/src/native/xgboost4j.h | 2 +- src/metric/multiclass_metric.cc | 7 ++ src/metric/multiclass_metric.cu | 58 ------------ 15 files changed, 293 insertions(+), 145 deletions(-) create mode 100644 include/xgboost/multiclass_metric.h delete mode 100644 jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java create mode 100644 jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala rename jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/{IEvaluationForDistributed.java => IEvalElementWiseDistributed.java} (58%) create mode 100644 jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalMultiClassesDistributed.java diff --git a/include/xgboost/multiclass_metric.h b/include/xgboost/multiclass_metric.h new file mode 100644 index 000000000000..86b05775a57c --- /dev/null +++ b/include/xgboost/multiclass_metric.h @@ -0,0 +1,89 @@ +/*! + * Copyright 2019 by Contributors + * \file multiclass_metric.cc + * \brief evaluation metrics for multiclass classification. + */ + +#ifndef XGBOOST_MULTICLASS_METRIC_H +#define XGBOOST_MULTICLASS_METRIC_H + +#include +namespace xgboost { + namespace metric { +/*! + * \brief base class of multi-class evaluation + * \tparam Derived the name of subclass + */ + template + struct EvalMClassBase : public Metric { + bst_float Eval(const HostDeviceVector &preds, + const MetaInfo &info, + bool distributed) override { + CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; + CHECK(preds.Size() % info.labels_.Size() == 0) + << "label and prediction size not match"; + const size_t nclass = preds.Size() / info.labels_.Size(); + CHECK_GE(nclass, 1U) + << "mlogloss and merror are only used for multi-class classification," + << " use logloss for binary classification"; + const auto ndata = static_cast(info.labels_.Size()); + double sum = 0.0, wsum = 0.0; + int label_error = 0; + + const auto &labels = info.labels_.HostVector(); + const auto &weights = info.weights_.HostVector(); + const std::vector &h_preds = preds.HostVector(); + +#pragma omp parallel for reduction(+: sum, wsum) schedule(static) + for (bst_omp_uint i = 0; i < ndata; ++i) { + const bst_float wt = weights.size() > 0 ? weights[i] : 1.0f; + auto label = static_cast(labels[i]); + if (label >= 0 && label < static_cast(nclass)) { + sum += Derived::EvalRow(label, + h_preds.data() + i * nclass, + nclass) * wt; + wsum += wt; + } else { + label_error = label; + } + } + CHECK(label_error >= 0 && label_error < static_cast(nclass)) + << "MultiClassEvaluation: label must be in [0, num_class)," + << " num_class=" << nclass << " but found " << label_error << " in label"; + + double dat[2]; + dat[0] = sum, dat[1] = wsum; + if (distributed) { + rabit::Allreduce(dat, 2); + } + return Derived::GetFinal(dat[0], dat[1]); + } + + /*! + * \brief to be implemented by subclass, + * get evaluation result from one row + * \param label label of current instance + * \param pred prediction value of current instance + * \param nclass number of class in the prediction + */ + inline static bst_float EvalRow(int label, + const bst_float *pred, + size_t nclass); + + /*! + * \brief to be overridden by subclass, final transformation + * \param esum the sum statistics returned by EvalRow + * \param wsum sum of weight + */ + inline static bst_float GetFinal(bst_float esum, bst_float wsum) { + return esum / wsum; + } + + private: + // used to store error message + const char *error_msg_; + }; + }; +} + +#endif //XGBOOST_MULTICLASS_METRIC_H diff --git a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala index 137629ab9948..921ecd03428f 100644 --- a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala +++ b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala @@ -18,13 +18,11 @@ package ml.dmlc.xgboost4j.scala.spark import java.io.File import java.nio.file.Files -import java.util.Properties -import scala.collection.mutable.ListBuffer import scala.collection.{AbstractIterator, mutable} import scala.util.Random -import ml.dmlc.xgboost4j.java.{IEvaluation, IEvaluationForDistributed, IRabitTracker, Rabit, XGBoostError, XGBoostJNI, RabitTracker => PyRabitTracker} +import ml.dmlc.xgboost4j.java.{IEvalElementWiseDistributed, IEvalMultiClassesDistributed, IEvaluation, IRabitTracker, Rabit, XGBoostError, RabitTracker => PyRabitTracker} import ml.dmlc.xgboost4j.scala.rabit.RabitTracker import ml.dmlc.xgboost4j.scala.{XGBoost => SXGBoost, _} import ml.dmlc.xgboost4j.{LabeledPoint => XGBLabeledPoint} @@ -173,7 +171,7 @@ object XGBoost extends Serializable { } } - private def overrideParamsAccordingToTaskCPUs( + private def overrideParams( params: Map[String, Any], sc: SparkContext): Map[String, Any] = { val coresPerTask = sc.getConf.getInt("spark.task.cpus", 1) @@ -186,6 +184,14 @@ object XGBoost extends Serializable { } else { overridedParams = params + ("nthread" -> coresPerTask) } + if (params("custom_eval") != null) { + params("custom_eval") match { + case _: IEvalElementWiseDistributed => + overridedParams = overridedParams + ("custom_eval_type" -> "regression/binary") + case _: IEvalMultiClassesDistributed => + overridedParams = overridedParams + ("custom_eval_type" -> "multi_classes") + } + } overridedParams } @@ -426,7 +432,7 @@ object XGBoost extends Serializable { checkpointRound: Int => val tracker = startTracker(nWorkers, trackerConf) try { - val overriddenParams = overrideParamsAccordingToTaskCPUs(params, sc) + val overriddenParams = overrideParams(params, sc) val parallelismTracker = new SparkParallelismTracker(sc, timeoutRequestWorkers, nWorkers) val rabitEnv = tracker.getWorkerEnvs diff --git a/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java b/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java deleted file mode 100644 index ce0aeb820f4d..000000000000 --- a/jvm-packages/xgboost4j-spark/src/test/java/ml/dmlc/xgboost4j/java/DistributedEvalError.java +++ /dev/null @@ -1,39 +0,0 @@ -package ml.dmlc.xgboost4j.java; - -public class DistributedEvalError implements IEvaluationForDistributed, IEvaluation { - - @Override - public float evalRow(float label, float pred) { - return 1.0f; - } - - @Override - public float getFinal(float errorSum, float weightSum) { - return 1.0f; - } - - @Override - public float constant() { - return 0; - } - - @Override - public float constant1(float e) { - return e; - } - - @Override - public boolean hasNext() { - return false; - } - - @Override - public String getMetric() { - return "distributed_error"; - } - - @Override - public float eval(float[][] predicts, DMatrix dmat) { - return 0; - } -} diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala index 34ab03b44468..833f04279d1c 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala @@ -16,19 +16,31 @@ package ml.dmlc.xgboost4j.scala.spark -import ml.dmlc.xgboost4j.java.DistributedEvalError import org.scalatest.FunSuite class CustomizedEvalSuite extends FunSuite with PerTest { - private val paramMap = List("eta" -> "1", "max_depth" -> "6", - "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, - "custom_eval" -> new DistributedEvalError).toMap - - test("distributed training with customized evaluation metrics") { + test("(binary classification) distributed training with customized evaluation metrics") { + val paramMap = List("eta" -> "1", "max_depth" -> "6", + "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, + "custom_eval" -> new DistributedEvalErrorElementWise).toMap val trainingDF = buildDataFrame(Classification.train, numWorkers) + val trainingCount = trainingDF.count() val xgbModel = new XGBoostClassifier(paramMap).fit(trainingDF) + // DistributedEvalError returns 1.0f in evalRow and sum of error in getFinal + xgbModel.summary.trainObjectiveHistory.foreach(_ === trainingCount) + } - println(xgbModel.summary.toString()) + test("(multi classes classification) distributed training with" + + " customized evaluation metrics") { + val paramMap = List("eta" -> "1", "max_depth" -> "6", + "objective" -> "multi:softmax", "num_class" -> 6, + "num_round" -> 5, "num_workers" -> numWorkers, + "custom_eval" -> new DistributedEvalErrorMultiClasses).toMap + val trainingDF = buildDataFrame(MultiClassification.train, numWorkers) + val trainingCount = trainingDF.count() + val xgbModel = new XGBoostClassifier(paramMap).fit(trainingDF) + // DistributedEvalError returns 1.0f + num_classes in evalRow and sum of error in getFinal + xgbModel.summary.trainObjectiveHistory.foreach(_ === trainingCount * (1 + 6)) } } diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala new file mode 100644 index 000000000000..cdd0c748d952 --- /dev/null +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala @@ -0,0 +1,54 @@ +/* + Copyright (c) 2014 by Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +package ml.dmlc.xgboost4j.scala.spark + +import ml.dmlc.xgboost4j.java.{IEvalElementWiseDistributed, IEvalMultiClassesDistributed} + +class DistributedEvalErrorElementWise extends IEvalElementWiseDistributed { + + /** + * calculate the metrics for a single row given its label and prediction + */ + def evalRow(label: Float, pred: Float): Float = 1.0f + + /** + * perform transformation with the sum of error and weights to get the final evaluation metrics + */ + def getFinal(errorSum: Float, weightSum: Float): Float = errorSum + + /** + * get metrics' name + */ + override def getMetric: String = "distributed_error_element_wise" +} + +class DistributedEvalErrorMultiClasses extends IEvalMultiClassesDistributed { + + /** + * calculate the metrics for a single row given its label and prediction + */ + override def evalRow(label: Int, pred: Float, numClasses: Int): Float = { + 1.0f + numClasses + } + + override def getFinal(errorSum: Float, weightSum: Float): Float = errorSum + + /** + * get metrics' name + */ + override def getMetric: String = "distributed_error_multi_classes" +} diff --git a/jvm-packages/xgboost4j/pom.xml b/jvm-packages/xgboost4j/pom.xml index 83372a88ca7a..d85264f10e0b 100644 --- a/jvm-packages/xgboost4j/pom.xml +++ b/jvm-packages/xgboost4j/pom.xml @@ -83,6 +83,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 8 + 8 + + diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java index 7a84233c748e..e72a4a908bd2 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/Booster.java @@ -243,9 +243,7 @@ public String evalSet(DMatrix[] evalMatrixs, String[] evalNames, int iter, float String stringFormat = evalSet(evalMatrixs, evalNames, iter); String[] metricPairs = stringFormat.split("\t"); for (int i = 1; i < metricPairs.length; i++) { - // TODO: remove min - metricsOut[Math.min(metricsOut.length - 1, i - 1)] = - Float.valueOf(metricPairs[i].split(":")[1]); + metricsOut[i - 1] = Float.valueOf(metricPairs[i].split(":")[1]); } return stringFormat; } diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed.java similarity index 58% rename from jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java rename to jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed.java index 32ead4a68091..b67c95979aa7 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvaluationForDistributed.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed.java @@ -1,6 +1,6 @@ package ml.dmlc.xgboost4j.java; -public interface IEvaluationForDistributed { +public interface IEvalElementWiseDistributed extends IEvaluation { /** * calculate the metrics for a single row given its label and prediction @@ -12,9 +12,8 @@ public interface IEvaluationForDistributed { */ float getFinal(float errorSum, float weightSum); - float constant(); - - float constant1(float e); - - boolean hasNext(); + @Override + default float eval(float[][] predicts, DMatrix dmat) { + throw new RuntimeException("IEvalElementWiseDistributed does not support eval method"); + } } diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalMultiClassesDistributed.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalMultiClassesDistributed.java new file mode 100644 index 000000000000..3c531f8658a6 --- /dev/null +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalMultiClassesDistributed.java @@ -0,0 +1,22 @@ +package ml.dmlc.xgboost4j.java; + +public interface IEvalMultiClassesDistributed extends IEvaluation { + + /** + * calculate the metrics for a single row given its label and prediction + */ + float evalRow(int label, float pred, int numClasses); + + /** + * perform transformation with the sum of error and weights to get the final evaluation metrics + */ + default float getFinal(float errorSum, float weightSum) { + return errorSum / weightSum; + } + + @Override + default float eval(float[][] predicts, DMatrix dmat) { + throw new RuntimeException("IEvalMultiClassesDistributed does not support eval method"); + } + +} diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index 80eb82b5b84b..da901b36537e 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -108,10 +108,8 @@ public static Booster train( } private static void registerNewCustomEvalForDistributed( - Booster booster, IEvaluation eval) { - int numClasses = 2; - XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), numClasses, - (IEvaluationForDistributed) eval); + Booster booster, IEvaluation eval, String evalType) { + XGBoostJNI.XGBoosterAddNewMetrics(booster.getHandle(), eval.getMetric(), evalType, eval); } private static String performEvaluation( @@ -120,13 +118,16 @@ private static String performEvaluation( String[] evalNames, DMatrix[] evalMats, int iter, - float[] metricsOut) throws XGBoostError { + float[] metricsOut, + String evalType) throws XGBoostError { String evalInfo; - if (eval != null && !(eval instanceof IEvaluationForDistributed)) { + if (eval != null && + !(eval instanceof IEvalElementWiseDistributed) && + !(eval instanceof IEvalMultiClassesDistributed)) { evalInfo = booster.evalSet(evalMats, evalNames, eval, metricsOut); } else { if (eval != null) { - registerNewCustomEvalForDistributed(booster, eval); + registerNewCustomEvalForDistributed(booster, eval, evalType); } evalInfo = booster.evalSet(evalMats, evalNames, iter, metricsOut); } @@ -220,7 +221,8 @@ public static Booster train( //evaluation if (evalMats.length > 0) { float[] metricsOut = new float[evalMats.length]; - String evalInfo = performEvaluation(booster, eval, evalNames, evalMats, iter, metricsOut); + String evalInfo = performEvaluation(booster, eval, evalNames, evalMats, iter, + metricsOut, (String) params.get("custom_eval_type")); for (int i = 0; i < metricsOut.length; i++) { metrics[i][iter] = metricsOut[i]; } diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java index aeb965f0b50a..3eb020814b09 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoostJNI.java @@ -99,8 +99,8 @@ public final static native int XGBoosterPredict(long handle, long dmat, int opti int ntree_limit, float[][] predicts); public final static native int XGBoosterAddNewMetrics(long handle, String metricsName, - int numClasses, - IEvaluationForDistributed eval); + String evalType, + IEvaluation eval); public final static native int XGBoosterLoadModel(long handle, String fname); diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 9a0bd4077873..89a579f7229f 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "./xgboost4j.h" #include #include @@ -153,7 +154,55 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( } } -// bridge classes for customized metrics +class CustomEvalMultiClasses : public xgboost::metric::EvalMClassBase { +public: + CustomEvalMultiClasses(std::string& name, CustomEvalHandle handle): metrics_name(name) { + JNIEnv* jenv; + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } + std::lock_guard guard(eval_handle_mutex); + if (custom_eval_handle == nullptr) { + custom_eval_handle = jenv->NewGlobalRef(static_cast(handle)); + } + } + + inline static xgboost::bst_float EvalRow(int label, + const xgboost::bst_float *pred, + int nclass) { + JNIEnv* jenv; + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalMultiClassesDistributed"); + jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(IFI)F"); + return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, *pred, nclass); + } + + static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { + JNIEnv* jenv; + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalMultiClassesDistributed"); + jmethodID get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); + return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); + } + + const char *Name() const { + return metrics_name.data(); + } + +private: + std::string metrics_name; + + static jobject custom_eval_handle; + /*! \brief lock guarding the registering*/ + static std::mutex eval_handle_mutex; +}; + +std::mutex CustomEvalMultiClasses::eval_handle_mutex; +jobject CustomEvalMultiClasses::custom_eval_handle = nullptr; + class CustomEvalElementWise { public: CustomEvalElementWise(std::string& name, CustomEvalHandle handle): @@ -175,7 +224,7 @@ class CustomEvalElementWise { xgboost::bst_float pred) const { JNIEnv* jenv; global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); - jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); } @@ -183,7 +232,7 @@ class CustomEvalElementWise { static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { JNIEnv* jenv; global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); - jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvaluationForDistributed"); + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); } @@ -831,28 +880,26 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr */ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name, - jint num_classes, jobject custom_eval) { + jstring eval_type, jobject custom_eval) { std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); - /** - * num_classes < 0 => ranking - * num_classes == 0 => regression - * num_classes == 2 => binary classification - * num_classes > 2 => multi_classes classification - * else => invalid - */ - if (num_classes == 2 || num_classes == 0) { + std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); + if (eval_type_in_str == "regression/binary") { XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) - .describe("customized metrics") + .describe("customized metrics for binary/regression") .set_body([&metrics_name_in_str, &custom_eval](const char *param) { return new xgboost::metric::EvalEWiseBase( *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); }); - XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); - } else if (num_classes > 2) { - + } else if (eval_type_in_str == "multi_classes") { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics for multi_classes") + .set_body([&metrics_name_in_str, &custom_eval](const char *param) { + return new CustomEvalMultiClasses(metrics_name_in_str, custom_eval); + }); } else { // ranking } + XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); return 0; } diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.h b/jvm-packages/xgboost4j/src/native/xgboost4j.h index db40ad2462e0..e1b3330b2e40 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.h +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.h @@ -261,7 +261,7 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr * Signature: (JLjava/lang/String;)I */ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics - (JNIEnv *, jclass, jlong, jstring, jint, jobject); + (JNIEnv *, jclass, jlong, jstring, jstring, jobject); /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI diff --git a/src/metric/multiclass_metric.cc b/src/metric/multiclass_metric.cc index 7733a334f5c0..39b754ad5d70 100644 --- a/src/metric/multiclass_metric.cc +++ b/src/metric/multiclass_metric.cc @@ -3,6 +3,13 @@ */ // Dummy file to keep the CUDA conditional compile trick. + +#include +#include +#include +#include "../common/math.h" + #if !defined(XGBOOST_USE_CUDA) #include "multiclass_metric.cu" #endif // !defined(XGBOOST_USE_CUDA) + diff --git a/src/metric/multiclass_metric.cu b/src/metric/multiclass_metric.cu index 88af0014ed5a..8f84379afef2 100644 --- a/src/metric/multiclass_metric.cu +++ b/src/metric/multiclass_metric.cu @@ -162,64 +162,6 @@ class MultiClassMetricsReduction { #endif // defined(XGBOOST_USE_CUDA) }; -/*! - * \brief base class of multi-class evaluation - * \tparam Derived the name of subclass - */ -template -struct EvalMClassBase : public Metric { - void Configure( - const std::vector >& args) override { - param_.InitAllowUnknown(args); - } - - bst_float Eval(const HostDeviceVector &preds, - const MetaInfo &info, - bool distributed) override { - CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; - CHECK(preds.Size() % info.labels_.Size() == 0) - << "label and prediction size not match"; - const size_t nclass = preds.Size() / info.labels_.Size(); - CHECK_GE(nclass, 1U) - << "mlogloss and merror are only used for multi-class classification," - << " use logloss for binary classification"; - const auto ndata = static_cast(info.labels_.Size()); - - GPUSet devices = GPUSet::All(param_.gpu_id, param_.n_gpus, ndata); - auto result = reducer_.Reduce(devices, nclass, info.weights_, info.labels_, preds); - double dat[2] { result.Residue(), result.Weights() }; - - if (distributed) { - rabit::Allreduce(dat, 2); - } - return Derived::GetFinal(dat[0], dat[1]); - } - /*! - * \brief to be implemented by subclass, - * get evaluation result from one row - * \param label label of current instance - * \param pred prediction value of current instance - * \param nclass number of class in the prediction - */ - XGBOOST_DEVICE static bst_float EvalRow(int label, - const bst_float *pred, - size_t nclass); - /*! - * \brief to be overridden by subclass, final transformation - * \param esum the sum statistics returned by EvalRow - * \param wsum sum of weight - */ - inline static bst_float GetFinal(bst_float esum, bst_float wsum) { - return esum / wsum; - } - - private: - MultiClassMetricsReduction reducer_; - MetricParam param_; - // used to store error message - const char *error_msg_; -}; - /*! \brief match error */ struct EvalMatchError : public EvalMClassBase { const char* Name() const override { From 2f415040cc253886fb16bc42d9df21a3012941d7 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 00:22:59 +0800 Subject: [PATCH 12/39] try to fix double free --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 89a579f7229f..a2f59d76dc8e 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -223,7 +223,12 @@ class CustomEvalElementWise { XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, xgboost::bst_float pred) const { JNIEnv* jenv; - global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); @@ -231,7 +236,12 @@ class CustomEvalElementWise { static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { JNIEnv* jenv; - global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); From 72a51b339bbfda8f182df21f0ca1c46f358ff70b Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 01:06:34 +0800 Subject: [PATCH 13/39] investigate --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index a2f59d76dc8e..9778ec9ff778 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -207,6 +207,7 @@ class CustomEvalElementWise { public: CustomEvalElementWise(std::string& name, CustomEvalHandle handle): metrics_name(name) { + /* JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -217,11 +218,12 @@ class CustomEvalElementWise { std::lock_guard guard(eval_handle_mutex); if (custom_eval_handle == nullptr) { custom_eval_handle = jenv->NewGlobalRef(static_cast(handle)); - } + }*/ } XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, xgboost::bst_float pred) const { + /* JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -232,9 +234,12 @@ class CustomEvalElementWise { jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); + */ + return 1.0f; } static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { + /* JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -245,6 +250,8 @@ class CustomEvalElementWise { jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); + */ + return 1.0f; } const char *Name() const { From d842ccf48f92e4c22dad45c268a95db3e204e71c Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 01:54:32 +0800 Subject: [PATCH 14/39] fix compilation --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 9778ec9ff778..f64f0ae661a4 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -898,8 +898,9 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name, jstring eval_type, jobject custom_eval) { - std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); - std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); + jboolean is_copy = true; + std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, &is_copy); + std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, &is_copy); if (eval_type_in_str == "regression/binary") { XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) .describe("customized metrics for binary/regression") From c304e552abd37a94413e9d27d633950e64148d7e Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 01:57:57 +0800 Subject: [PATCH 15/39] test --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index f64f0ae661a4..b449cca1487a 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -898,9 +898,10 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name, jstring eval_type, jobject custom_eval) { - jboolean is_copy = true; - std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, &is_copy); - std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, &is_copy); + // std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); + // std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); + std::string metrics_name_in_str = "metrics"; + std::string eval_type_in_str = "regression/binary"; if (eval_type_in_str == "regression/binary") { XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) .describe("customized metrics for binary/regression") From 63c5cd81e4e7fa19c1e6ca886b3b281dd6ce4ff1 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 08:27:56 +0800 Subject: [PATCH 16/39] thread safe registering --- .../xgboost4j/src/native/xgboost4j.cpp | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index b449cca1487a..96c49ab23f83 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -890,6 +890,8 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr return ret; } +std::mutex registering_mutex; +bool registered; /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBoosterAddNewMetrics @@ -900,24 +902,29 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet jstring eval_type, jobject custom_eval) { // std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); // std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); - std::string metrics_name_in_str = "metrics"; - std::string eval_type_in_str = "regression/binary"; - if (eval_type_in_str == "regression/binary") { - XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) - .describe("customized metrics for binary/regression") - .set_body([&metrics_name_in_str, &custom_eval](const char *param) { - return new xgboost::metric::EvalEWiseBase( - *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); - }); - } else if (eval_type_in_str == "multi_classes") { - XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) - .describe("customized metrics for multi_classes") - .set_body([&metrics_name_in_str, &custom_eval](const char *param) { - return new CustomEvalMultiClasses(metrics_name_in_str, custom_eval); - }); - } else { - // ranking + registering_mutex.lock(); + if (!registered) { + std::string metrics_name_in_str = "metrics"; + std::string eval_type_in_str = "regression/binary"; + if (eval_type_in_str == "regression/binary") { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics for binary/regression") + .set_body([&metrics_name_in_str, &custom_eval](const char *param) { + return new xgboost::metric::EvalEWiseBase( + *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); + }); + } else if (eval_type_in_str == "multi_classes") { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics for multi_classes") + .set_body([&metrics_name_in_str, &custom_eval](const char *param) { + return new CustomEvalMultiClasses(metrics_name_in_str, custom_eval); + }); + } else { + // ranking + } + registered = true; } + registering_mutex.unlock(); XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); return 0; } From d9d130d7a801119f495856d2d24d60dc480f7df0 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 08:31:02 +0800 Subject: [PATCH 17/39] up --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 96c49ab23f83..c9fc93ee818c 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -902,10 +902,10 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet jstring eval_type, jobject custom_eval) { // std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); // std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); + std::string metrics_name_in_str = "metrics"; + std::string eval_type_in_str = "regression/binary"; registering_mutex.lock(); if (!registered) { - std::string metrics_name_in_str = "metrics"; - std::string eval_type_in_str = "regression/binary"; if (eval_type_in_str == "regression/binary") { XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) .describe("customized metrics for binary/regression") From 1a1f462f139ca47cbe2a8ac26d739eb94122d944 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 10:34:12 +0800 Subject: [PATCH 18/39] copy constructor --- include/xgboost/c_api.h | 2 +- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 10 ++++------ src/c_api/c_api.cc | 7 ++++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/include/xgboost/c_api.h b/include/xgboost/c_api.h index e0995c80ffd4..542c72ced2c8 100644 --- a/include/xgboost/c_api.h +++ b/include/xgboost/c_api.h @@ -566,7 +566,7 @@ XGB_DLL int XGBoosterLoadRabitCheckpoint( */ XGB_DLL int XGBoosterSaveRabitCheckpoint(BoosterHandle handle); -XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string metrics_name); +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string& metrics_name); XGB_DLL int XGBoosterGetMetricsCount(BoosterHandle handle); diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index c9fc93ee818c..d6ee67fd65f3 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -156,7 +156,7 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( class CustomEvalMultiClasses : public xgboost::metric::EvalMClassBase { public: - CustomEvalMultiClasses(std::string& name, CustomEvalHandle handle): metrics_name(name) { + CustomEvalMultiClasses(std::string name, CustomEvalHandle handle): metrics_name(name) { JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -205,7 +205,7 @@ jobject CustomEvalMultiClasses::custom_eval_handle = nullptr; class CustomEvalElementWise { public: - CustomEvalElementWise(std::string& name, CustomEvalHandle handle): + CustomEvalElementWise(std::string name, CustomEvalHandle handle): metrics_name(name) { /* JNIEnv* jenv; @@ -900,10 +900,8 @@ bool registered; JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMetrics (JNIEnv *jenv, jclass jcls, jlong jhandle, jstring metrics_name, jstring eval_type, jobject custom_eval) { - // std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); - // std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); - std::string metrics_name_in_str = "metrics"; - std::string eval_type_in_str = "regression/binary"; + std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); + std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); registering_mutex.lock(); if (!registered) { if (eval_type_in_str == "regression/binary") { diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index 1ddaf04b37cf..c8a82a4f9987 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1157,10 +1157,11 @@ QueryBoosterConfigurationArguments(BoosterHandle handle) { return bst->learner()->GetConfigurationArguments(); } -XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string metrics_name) { +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string& metrics_name) { auto* bst = static_cast(handle); - // note: this function is only called by jvm packages for now which does not support multiple - // evaluation metrics, as a result, we clear all registered metrics and add the new customized one + // note: this function is only called by jvm packages which does not support multiple + // evaluation metrics for now, + // as a result, we clear all registered metrics and add the new customized one bst->learner()->metrics_.clear(); bst->learner()->metrics_.emplace_back(Metric::Create(metrics_name)); bst->learner()->metrics_.back()->Configure(bst->learner()->GetConfigurationArguments().begin(), From 3f6769263cdd4d0b1e78e4730577b07e7e7f3c53 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 11:58:23 +0800 Subject: [PATCH 19/39] fix lambda syntax --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index d6ee67fd65f3..08083b79ab56 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -205,9 +205,8 @@ jobject CustomEvalMultiClasses::custom_eval_handle = nullptr; class CustomEvalElementWise { public: - CustomEvalElementWise(std::string name, CustomEvalHandle handle): + CustomEvalElementWise(std::string& name, CustomEvalHandle handle): metrics_name(name) { - /* JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -218,12 +217,11 @@ class CustomEvalElementWise { std::lock_guard guard(eval_handle_mutex); if (custom_eval_handle == nullptr) { custom_eval_handle = jenv->NewGlobalRef(static_cast(handle)); - }*/ + } } XGBOOST_DEVICE xgboost::bst_float EvalRow(xgboost::bst_float label, xgboost::bst_float pred) const { - /* JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -234,12 +232,9 @@ class CustomEvalElementWise { jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalRow", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, pred); - */ - return 1.0f; } static xgboost::bst_float GetFinal(xgboost::bst_float esum, xgboost::bst_float wsum) { - /* JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); if (jni_status == JNI_EDETACHED) { @@ -250,8 +245,6 @@ class CustomEvalElementWise { jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalElementWiseDistributed"); jmethodID get_final_func = jenv->GetMethodID(eval_interface, "getFinal", "(FF)F"); return jenv->CallFloatMethod(custom_eval_handle, get_final_func, esum, wsum); - */ - return 1.0f; } const char *Name() const { @@ -907,7 +900,7 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet if (eval_type_in_str == "regression/binary") { XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) .describe("customized metrics for binary/regression") - .set_body([&metrics_name_in_str, &custom_eval](const char *param) { + .set_body([=](const char *param) { return new xgboost::metric::EvalEWiseBase( *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); }); From b517ff0894cc5a0ffc4f834e03adacb75b084563 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 12:12:47 +0800 Subject: [PATCH 20/39] recover some changes --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 08083b79ab56..69daab821d42 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -205,7 +205,7 @@ jobject CustomEvalMultiClasses::custom_eval_handle = nullptr; class CustomEvalElementWise { public: - CustomEvalElementWise(std::string& name, CustomEvalHandle handle): + CustomEvalElementWise(std::string name, CustomEvalHandle handle): metrics_name(name) { JNIEnv* jenv; int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); @@ -907,7 +907,7 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet } else if (eval_type_in_str == "multi_classes") { XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) .describe("customized metrics for multi_classes") - .set_body([&metrics_name_in_str, &custom_eval](const char *param) { + .set_body([=](const char *param) { return new CustomEvalMultiClasses(metrics_name_in_str, custom_eval); }); } else { From fdbeffd539a559b5e8145353dc484c11e99832da Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 12:25:07 +0800 Subject: [PATCH 21/39] fix race condition --- .../main/resources/xgboost-tracker.properties | 1 - .../xgboost4j/src/native/xgboost4j.cpp | 34 ++++++++----------- 2 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties diff --git a/jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties b/jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties deleted file mode 100644 index ecbf2c0d94d6..000000000000 --- a/jvm-packages/xgboost4j/src/main/resources/xgboost-tracker.properties +++ /dev/null @@ -1 +0,0 @@ -host-ip=0.0.0.0 \ No newline at end of file diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 69daab821d42..048a5f4cd447 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -884,7 +884,6 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterSetAttr } std::mutex registering_mutex; -bool registered; /* * Class: ml_dmlc_xgboost4j_java_XGBoostJNI * Method: XGBoosterAddNewMetrics @@ -896,24 +895,21 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet std::string metrics_name_in_str = jenv->GetStringUTFChars(metrics_name, 0); std::string eval_type_in_str = jenv->GetStringUTFChars(eval_type, 0); registering_mutex.lock(); - if (!registered) { - if (eval_type_in_str == "regression/binary") { - XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) - .describe("customized metrics for binary/regression") - .set_body([=](const char *param) { - return new xgboost::metric::EvalEWiseBase( - *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); - }); - } else if (eval_type_in_str == "multi_classes") { - XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) - .describe("customized metrics for multi_classes") - .set_body([=](const char *param) { - return new CustomEvalMultiClasses(metrics_name_in_str, custom_eval); - }); - } else { - // ranking - } - registered = true; + if (eval_type_in_str == "regression/binary") { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics for binary/regression") + .set_body([=](const char *param) { + return new xgboost::metric::EvalEWiseBase( + *(new CustomEvalElementWise(metrics_name_in_str, custom_eval))); + }); + } else if (eval_type_in_str == "multi_classes") { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics for multi_classes") + .set_body([=](const char *param) { + return new CustomEvalMultiClasses(metrics_name_in_str, custom_eval); + }); + } else { + // ranking } registering_mutex.unlock(); XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); From 6df2c9c40c31ab5d9849c4bd93267ae052875927 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 13:38:26 +0800 Subject: [PATCH 22/39] fix cuda build --- include/xgboost/elementwise_metric.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xgboost/elementwise_metric.h b/include/xgboost/elementwise_metric.h index b22dac74abd4..965117e17bfc 100644 --- a/include/xgboost/elementwise_metric.h +++ b/include/xgboost/elementwise_metric.h @@ -16,7 +16,7 @@ #include #include // thrust::plus<> -#include "../common/device_helpers.cuh" +#include "../src/common/device_helpers.cuh" #endif // XGBOOST_USE_CUDA /*! From df1d89fd47c17ac6b2c84020e2c4dd2b22ac446a Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 15:11:05 +0800 Subject: [PATCH 23/39] add ranking --- include/xgboost/ranking_metric.h | 105 ++++++++++++++++++ .../dmlc/xgboost4j/scala/spark/XGBoost.scala | 4 +- .../scala/spark/CustomizedEvalSuite.scala | 11 ++ .../scala/spark/DistributedEvalError.scala | 16 ++- .../java/IEvalRankListDistributed.java | 11 ++ .../java/ml/dmlc/xgboost4j/java/XGBoost.java | 3 +- .../xgboost4j/src/native/xgboost4j.cpp | 51 ++++++++- src/metric/rank_metric.cc | 81 +------------- 8 files changed, 197 insertions(+), 85 deletions(-) create mode 100644 include/xgboost/ranking_metric.h create mode 100644 jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalRankListDistributed.java diff --git a/include/xgboost/ranking_metric.h b/include/xgboost/ranking_metric.h new file mode 100644 index 000000000000..844a1988794a --- /dev/null +++ b/include/xgboost/ranking_metric.h @@ -0,0 +1,105 @@ +/* + Copyright (c) 2019 by Contributors + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include + +#ifndef XGBOOST_RANKING_METRIC_H +#define XGBOOST_RANKING_METRIC_H + +namespace xgboost { + namespace metric { +/*! \brief Evaluate rank list */ + struct EvalRankList : public Metric { + public: + bst_float Eval(const HostDeviceVector &preds, + const MetaInfo &info, + bool distributed) override { + CHECK_EQ(preds.Size(), info.labels_.Size()) + << "label size predict size not match"; + // quick consistency when group is not available + std::vector tgptr(2, 0); + tgptr[1] = static_cast(preds.Size()); + const std::vector &gptr = info.group_ptr_.size() == 0 ? tgptr : info.group_ptr_; + CHECK_NE(gptr.size(), 0U) << "must specify group when constructing rank file"; + CHECK_EQ(gptr.back(), preds.Size()) + << "EvalRanklist: group structure must match number of prediction"; + const auto ngroup = static_cast(gptr.size() - 1); + // sum statistics + double sum_metric = 0.0f; + const auto &labels = info.labels_.HostVector(); + + const std::vector &h_preds = preds.HostVector(); +#pragma omp parallel reduction(+:sum_metric) + { + // each thread takes a local rec + std::vector > rec; +#pragma omp for schedule(static) + for (bst_omp_uint k = 0; k < ngroup; ++k) { + rec.clear(); + for (unsigned j = gptr[k]; j < gptr[k + 1]; ++j) { + rec.emplace_back(h_preds[j], static_cast(labels[j])); + } + sum_metric += this->EvalMetric(rec); + } + } + if (distributed) { + bst_float dat[2]; + dat[0] = static_cast(sum_metric); + dat[1] = static_cast(ngroup); + // approximately estimate the metric using mean + rabit::Allreduce(dat, 2); + return dat[0] / dat[1]; + } else { + return static_cast(sum_metric) / ngroup; + } + } + + const char *Name() const override { + return name_.c_str(); + } + + protected: + explicit EvalRankList(const char *name, const char *param) { + using namespace std; // NOLINT(*) + minus_ = false; + if (param != nullptr) { + std::ostringstream os; + os << name << '@' << param; + name_ = os.str(); + if (sscanf(param, "%u[-]?", &topn_) != 1) { + topn_ = std::numeric_limits::max(); + } + if (param[strlen(param) - 1] == '-') { + minus_ = true; + } + } else { + name_ = name; + topn_ = std::numeric_limits::max(); + } + } + + /*! \return evaluation metric, given the pair_sort record, (pred,label) */ + virtual bst_float + EvalMetric(std::vector > &pair_sort) const = 0; // NOLINT(*) + + protected: + unsigned topn_; + std::string name_; + bool minus_; + }; + } +} + +#endif //XGBOOST_RANKING_METRIC_H diff --git a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala index 921ecd03428f..002fdd52a3a3 100644 --- a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala +++ b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala @@ -22,7 +22,7 @@ import java.nio.file.Files import scala.collection.{AbstractIterator, mutable} import scala.util.Random -import ml.dmlc.xgboost4j.java.{IEvalElementWiseDistributed, IEvalMultiClassesDistributed, IEvaluation, IRabitTracker, Rabit, XGBoostError, RabitTracker => PyRabitTracker} +import ml.dmlc.xgboost4j.java.{IEvalElementWiseDistributed, IEvalMultiClassesDistributed, IEvalRankListDistributed, IEvaluation, IRabitTracker, Rabit, XGBoostError, RabitTracker => PyRabitTracker} import ml.dmlc.xgboost4j.scala.rabit.RabitTracker import ml.dmlc.xgboost4j.scala.{XGBoost => SXGBoost, _} import ml.dmlc.xgboost4j.{LabeledPoint => XGBLabeledPoint} @@ -190,6 +190,8 @@ object XGBoost extends Serializable { overridedParams = overridedParams + ("custom_eval_type" -> "regression/binary") case _: IEvalMultiClassesDistributed => overridedParams = overridedParams + ("custom_eval_type" -> "multi_classes") + case _: IEvalRankListDistributed => + overridedParams = overridedParams + ("custom_eval_type" -> "ranking") } } overridedParams diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala index 833f04279d1c..67b38127078b 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala @@ -43,4 +43,15 @@ class CustomizedEvalSuite extends FunSuite with PerTest { // DistributedEvalError returns 1.0f + num_classes in evalRow and sum of error in getFinal xgbModel.summary.trainObjectiveHistory.foreach(_ === trainingCount * (1 + 6)) } + + test("(ranking) distributed training with" + + " customized evaluation metrics") { + val paramMap = List("eta" -> "1", "max_depth" -> "6", + "objective" -> "rank:pairwise", "num_round" -> 5, "num_workers" -> numWorkers, + "custom_eval" -> new DistributedEvalErrorRankList).toMap + val trainingDF = buildDataFrame(Ranking.train, numWorkers) + val trainingCount = trainingDF.count() + val xgbModel = new XGBoostRegressor(paramMap).fit(trainingDF) + xgbModel.summary.trainObjectiveHistory.foreach(_ === trainingCount * (1 + 6)) + } } diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala index cdd0c748d952..2446ef33cae5 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala @@ -16,7 +16,7 @@ package ml.dmlc.xgboost4j.scala.spark -import ml.dmlc.xgboost4j.java.{IEvalElementWiseDistributed, IEvalMultiClassesDistributed} +import ml.dmlc.xgboost4j.java.{IEvalElementWiseDistributed, IEvalMultiClassesDistributed, IEvalRankListDistributed} class DistributedEvalErrorElementWise extends IEvalElementWiseDistributed { @@ -52,3 +52,17 @@ class DistributedEvalErrorMultiClasses extends IEvalMultiClassesDistributed { */ override def getMetric: String = "distributed_error_multi_classes" } + +class DistributedEvalErrorRankList extends IEvalRankListDistributed { + + override def evalMetric(preds: Array[Float], labels: Array[Int]): Float = { + println(s"preds:${preds.mkString(",")}") + println(s"labels:${preds.mkString(",")}") + labels.sum + } + + /** + * get metrics' name + */ + override def getMetric: String = "distributed_error_ranking" +} diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalRankListDistributed.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalRankListDistributed.java new file mode 100644 index 000000000000..464d5f4ac811 --- /dev/null +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/IEvalRankListDistributed.java @@ -0,0 +1,11 @@ +package ml.dmlc.xgboost4j.java; + +public interface IEvalRankListDistributed extends IEvaluation { + + float evalMetric(float[] preds, int[] labels); + + @Override + default float eval(float[][] predicts, DMatrix dmat) { + throw new RuntimeException("IEvalRankingDistributed does not support eval method"); + } +} diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index da901b36537e..8c0f1d6715c9 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -123,7 +123,8 @@ private static String performEvaluation( String evalInfo; if (eval != null && !(eval instanceof IEvalElementWiseDistributed) && - !(eval instanceof IEvalMultiClassesDistributed)) { + !(eval instanceof IEvalMultiClassesDistributed) && + !(eval instanceof IEvalRankListDistributed)) { evalInfo = booster.evalSet(evalMats, evalNames, eval, metricsOut); } else { if (eval != null) { diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 048a5f4cd447..060bacea52ee 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2014 by Contributors + Copyright (c) 2019 by Contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ #include #include #include +#include // helper functions // set handle @@ -154,6 +155,46 @@ XGB_EXTERN_C int XGBoost4jCallbackDataIterNext( } } +class CustomEvalRanking : public xgboost::metric::EvalRankList { +public: + explicit CustomEvalRanking(std::string name, CustomEvalHandle handle): + EvalRankList(name.data(), name.data()), metrics_name(name) { + JNIEnv* jenv; + int jni_status = global_jvm->GetEnv((void **) &jenv, JNI_VERSION_1_6); + if (jni_status == JNI_EDETACHED) { + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + } else { + CHECK(jni_status == JNI_OK); + } + std::lock_guard guard(eval_handle_mutex); + if (custom_eval_handle == nullptr) { + custom_eval_handle = jenv->NewGlobalRef(static_cast(handle)); + } + } + +protected: + + xgboost::bst_float EvalMetric(std::vector< std::pair > &rec) + const override { + JNIEnv* jenv; + global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); + jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalRankListDistributed"); + jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalMetric", "([F[I)F"); + // return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, *pred, nclass); + return 1.0f; + } + +private: + std::string metrics_name; + + static jobject custom_eval_handle; + /*! \brief lock guarding the registering*/ + static std::mutex eval_handle_mutex; +}; + +std::mutex CustomEvalRanking::eval_handle_mutex; +jobject CustomEvalRanking::custom_eval_handle = nullptr; + class CustomEvalMultiClasses : public xgboost::metric::EvalMClassBase { public: CustomEvalMultiClasses(std::string name, CustomEvalHandle handle): metrics_name(name) { @@ -908,8 +949,12 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet .set_body([=](const char *param) { return new CustomEvalMultiClasses(metrics_name_in_str, custom_eval); }); - } else { - // ranking + } else if (eval_type_in_str == "ranking") { + XGBOOST_REGISTER_METRIC(CUSTOM_METRICS, metrics_name_in_str) + .describe("customized metrics for ranking") + .set_body([=](const char *param) { + return new CustomEvalRanking(metrics_name_in_str, custom_eval); + }); } registering_mutex.unlock(); XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); diff --git a/src/metric/rank_metric.cc b/src/metric/rank_metric.cc index 43a5a2333924..f6b6fb7c1988 100644 --- a/src/metric/rank_metric.cc +++ b/src/metric/rank_metric.cc @@ -1,11 +1,11 @@ /*! - * Copyright 2015 by Contributors + * Copyright 2019 by Contributors * \file rank_metric.cc * \brief prediction rank based metrics. - * \author Kailong Chen, Tianqi Chen */ #include #include +#include #include #include @@ -159,83 +159,6 @@ struct EvalAuc : public Metric { } }; -/*! \brief Evaluate rank list */ -struct EvalRankList : public Metric { - public: - bst_float Eval(const HostDeviceVector &preds, - const MetaInfo &info, - bool distributed) override { - CHECK_EQ(preds.Size(), info.labels_.Size()) - << "label size predict size not match"; - // quick consistency when group is not available - std::vector tgptr(2, 0); - tgptr[1] = static_cast(preds.Size()); - const std::vector &gptr = info.group_ptr_.size() == 0 ? tgptr : info.group_ptr_; - CHECK_NE(gptr.size(), 0U) << "must specify group when constructing rank file"; - CHECK_EQ(gptr.back(), preds.Size()) - << "EvalRanklist: group structure must match number of prediction"; - const auto ngroup = static_cast(gptr.size() - 1); - // sum statistics - double sum_metric = 0.0f; - const auto& labels = info.labels_.HostVector(); - - const std::vector& h_preds = preds.HostVector(); -#pragma omp parallel reduction(+:sum_metric) - { - // each thread takes a local rec - std::vector< std::pair > rec; - #pragma omp for schedule(static) - for (bst_omp_uint k = 0; k < ngroup; ++k) { - rec.clear(); - for (unsigned j = gptr[k]; j < gptr[k + 1]; ++j) { - rec.emplace_back(h_preds[j], static_cast(labels[j])); - } - sum_metric += this->EvalMetric(rec); - } - } - if (distributed) { - bst_float dat[2]; - dat[0] = static_cast(sum_metric); - dat[1] = static_cast(ngroup); - // approximately estimate the metric using mean - rabit::Allreduce(dat, 2); - return dat[0] / dat[1]; - } else { - return static_cast(sum_metric) / ngroup; - } - } - const char* Name() const override { - return name_.c_str(); - } - - protected: - explicit EvalRankList(const char* name, const char* param) { - using namespace std; // NOLINT(*) - minus_ = false; - if (param != nullptr) { - std::ostringstream os; - os << name << '@' << param; - name_ = os.str(); - if (sscanf(param, "%u[-]?", &topn_) != 1) { - topn_ = std::numeric_limits::max(); - } - if (param[strlen(param) - 1] == '-') { - minus_ = true; - } - } else { - name_ = name; - topn_ = std::numeric_limits::max(); - } - } - /*! \return evaluation metric, given the pair_sort record, (pred,label) */ - virtual bst_float EvalMetric(std::vector > &pair_sort) const = 0; // NOLINT(*) - - protected: - unsigned topn_; - std::string name_; - bool minus_; -}; - /*! \brief Precision at N, for both classification and rank */ struct EvalPrecision : public EvalRankList{ public: From e4fa239c919a5363dc5ad021cd3ba5c37a12c984 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Mon, 25 Mar 2019 15:55:45 +0800 Subject: [PATCH 24/39] add unit test --- .../scala/spark/CustomizedEvalSuite.scala | 21 ++++++++++++++++--- .../scala/spark/DistributedEvalError.scala | 4 +--- .../xgboost4j/src/native/xgboost4j.cpp | 15 ++++++++++--- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala index 67b38127078b..49205bcc49a6 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/CustomizedEvalSuite.scala @@ -20,6 +20,18 @@ import org.scalatest.FunSuite class CustomizedEvalSuite extends FunSuite with PerTest { + test("(regression) distributed training with customized evaluation metrics") { + val paramMap = List("eta" -> "1", "max_depth" -> "6", + "objective" -> "reg:squarederror", "num_round" -> 5, "num_workers" -> numWorkers, + "custom_eval" -> new DistributedEvalErrorElementWise).toMap + val trainingDF = buildDataFrame(Regression.train, numWorkers) + val trainingCount = trainingDF.count() + val xgbModel = new XGBoostRegressor(paramMap).fit(trainingDF) + // DistributedEvalError returns 1.0f in evalRow and sum of error in getFinal + xgbModel.summary.trainObjectiveHistory.foreach(metricsInRound => + assert(metricsInRound === trainingCount)) + } + test("(binary classification) distributed training with customized evaluation metrics") { val paramMap = List("eta" -> "1", "max_depth" -> "6", "objective" -> "binary:logistic", "num_round" -> 5, "num_workers" -> numWorkers, @@ -28,7 +40,8 @@ class CustomizedEvalSuite extends FunSuite with PerTest { val trainingCount = trainingDF.count() val xgbModel = new XGBoostClassifier(paramMap).fit(trainingDF) // DistributedEvalError returns 1.0f in evalRow and sum of error in getFinal - xgbModel.summary.trainObjectiveHistory.foreach(_ === trainingCount) + xgbModel.summary.trainObjectiveHistory.foreach(metricsInRound => + assert(metricsInRound === trainingCount)) } test("(multi classes classification) distributed training with" + @@ -41,7 +54,8 @@ class CustomizedEvalSuite extends FunSuite with PerTest { val trainingCount = trainingDF.count() val xgbModel = new XGBoostClassifier(paramMap).fit(trainingDF) // DistributedEvalError returns 1.0f + num_classes in evalRow and sum of error in getFinal - xgbModel.summary.trainObjectiveHistory.foreach(_ === trainingCount * (1 + 6)) + xgbModel.summary.trainObjectiveHistory.foreach(metricsInRound => + assert(metricsInRound === trainingCount * (1 + 6))) } test("(ranking) distributed training with" + @@ -52,6 +66,7 @@ class CustomizedEvalSuite extends FunSuite with PerTest { val trainingDF = buildDataFrame(Ranking.train, numWorkers) val trainingCount = trainingDF.count() val xgbModel = new XGBoostRegressor(paramMap).fit(trainingDF) - xgbModel.summary.trainObjectiveHistory.foreach(_ === trainingCount * (1 + 6)) + xgbModel.summary.trainObjectiveHistory.foreach(metricsInRound => + assert(metricsInRound === 0.0f)) } } diff --git a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala index 2446ef33cae5..55eddd787ff7 100644 --- a/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala +++ b/jvm-packages/xgboost4j-spark/src/test/scala/ml/dmlc/xgboost4j/scala/spark/DistributedEvalError.scala @@ -56,9 +56,7 @@ class DistributedEvalErrorMultiClasses extends IEvalMultiClassesDistributed { class DistributedEvalErrorRankList extends IEvalRankListDistributed { override def evalMetric(preds: Array[Float], labels: Array[Int]): Float = { - println(s"preds:${preds.mkString(",")}") - println(s"labels:${preds.mkString(",")}") - labels.sum + 0.0f } /** diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 060bacea52ee..037b4c9d91db 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -179,9 +179,18 @@ class CustomEvalRanking : public xgboost::metric::EvalRankList { JNIEnv* jenv; global_jvm->AttachCurrentThread(reinterpret_cast(&jenv), nullptr); jclass eval_interface = jenv->FindClass("ml/dmlc/xgboost4j/java/IEvalRankListDistributed"); - jmethodID eval_row_func = jenv->GetMethodID(eval_interface, "evalMetric", "([F[I)F"); - // return jenv->CallFloatMethod(custom_eval_handle, eval_row_func, label, *pred, nclass); - return 1.0f; + jmethodID eval_metrics_func = jenv->GetMethodID(eval_interface, "evalMetric", "([F[I)F"); + jfloatArray preds = jenv->NewFloatArray(rec.size()); + jintArray labels = jenv->NewIntArray(rec.size()); + jfloat fill_float[rec.size()]; + jint fill_int[rec.size()]; + for (int i = 0; i < rec.size(); i++) { + fill_float[i] = rec[i].first; + fill_int[i] = rec[i].second; + } + jenv->SetFloatArrayRegion(preds, 0, rec.size(), fill_float); + jenv->SetIntArrayRegion(labels, 0, rec.size(), fill_int); + return jenv->CallFloatMethod(custom_eval_handle, eval_metrics_func, preds, labels); } private: From e135e3ec4a44cf322932d3cf4c1dce0e47787e19 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 26 Mar 2019 14:03:11 +0800 Subject: [PATCH 25/39] move files --- include/xgboost/c_api.h | 10 +- include/xgboost/elementwise_metric.h | 4 +- include/xgboost/learner.h | 2 +- include/xgboost/metric/elementwise_metric.h | 218 ++++++++++++++++++ include/xgboost/{ => metric}/metric.h | 6 +- include/xgboost/{ => metric}/metric_param.h | 8 +- include/xgboost/metric/multiclass_metric.h | 93 ++++++++ include/xgboost/metric/ranking_metric.h | 109 +++++++++ include/xgboost/multiclass_metric.h | 89 ------- include/xgboost/ranking_metric.h | 105 --------- .../xgboost4j/src/native/xgboost4j.cpp | 8 +- src/c_api/c_api.cc | 7 +- src/metric/elementwise_metric.cu | 6 +- src/metric/metric.cc | 4 +- src/metric/multiclass_metric.cc | 2 +- src/metric/rank_metric.cc | 4 +- tests/cpp/helpers.h | 2 +- tests/cpp/metric/test_elementwise_metric.cc | 2 +- tests/cpp/metric/test_metric.cc | 2 +- tests/cpp/metric/test_multiclass_metric.cc | 2 +- tests/cpp/metric/test_rank_metric.cc | 2 +- 21 files changed, 454 insertions(+), 231 deletions(-) create mode 100644 include/xgboost/metric/elementwise_metric.h rename include/xgboost/{ => metric}/metric.h (96%) rename include/xgboost/{ => metric}/metric_param.h (80%) create mode 100644 include/xgboost/metric/multiclass_metric.h create mode 100644 include/xgboost/metric/ranking_metric.h delete mode 100644 include/xgboost/multiclass_metric.h delete mode 100644 include/xgboost/ranking_metric.h diff --git a/include/xgboost/c_api.h b/include/xgboost/c_api.h index 542c72ced2c8..70d6e66fcaaa 100644 --- a/include/xgboost/c_api.h +++ b/include/xgboost/c_api.h @@ -11,13 +11,17 @@ #define XGB_EXTERN_C extern "C" #include #include -#include #else #define XGB_EXTERN_C #include #include #endif // __cplusplus +// XGBoost C API will include APIs in Rabit C API +#include + +#include + #if defined(_MSC_VER) || defined(_WIN32) #define XGB_DLL XGB_EXTERN_C __declspec(dllexport) #else @@ -566,8 +570,6 @@ XGB_DLL int XGBoosterLoadRabitCheckpoint( */ XGB_DLL int XGBoosterSaveRabitCheckpoint(BoosterHandle handle); -XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string& metrics_name); - -XGB_DLL int XGBoosterGetMetricsCount(BoosterHandle handle); +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, const std::string& metrics_name); #endif // XGBOOST_C_API_H_ diff --git a/include/xgboost/elementwise_metric.h b/include/xgboost/elementwise_metric.h index 965117e17bfc..bef7e21c29b0 100644 --- a/include/xgboost/elementwise_metric.h +++ b/include/xgboost/elementwise_metric.h @@ -5,8 +5,8 @@ #ifndef XGBOOST_ELEMENTWISE_METRIC_H #define XGBOOST_ELEMENTWISE_METRIC_H -#include -#include +#include +#include #include "../src/common/common.h" diff --git a/include/xgboost/learner.h b/include/xgboost/learner.h index 8ddec35ccf87..15b8123cc148 100644 --- a/include/xgboost/learner.h +++ b/include/xgboost/learner.h @@ -16,7 +16,7 @@ #include #include "./base.h" #include "./gbm.h" -#include "./metric.h" +#include "xgboost/metric/metric.h" #include "./objective.h" namespace xgboost { diff --git a/include/xgboost/metric/elementwise_metric.h b/include/xgboost/metric/elementwise_metric.h new file mode 100644 index 000000000000..d22639fcac74 --- /dev/null +++ b/include/xgboost/metric/elementwise_metric.h @@ -0,0 +1,218 @@ +/* + * Copyright 2015-2019 by Contributors + */ + +#ifndef XGBOOST_ELEMENTWISE_METRIC_H_ +#define XGBOOST_ELEMENTWISE_METRIC_H_ + +#include +#include + +#include +#include +#include +#include + +#include "../../../src/common/common.h" + +#if defined(XGBOOST_USE_CUDA) +#include +#include +#include +#include // thrust::plus<> + +#include "../src/common/device_helpers.cuh" +#endif // XGBOOST_USE_CUDA + +/*! + * \brief base class of element-wise evaluation + * \tparam Derived the name of subclass + */ +namespace xgboost { +namespace metric { + +template +class MetricsReduction { + public: + class PackedReduceResult { + double residue_sum_; + double weights_sum_; + friend MetricsReduction; + + public: + XGBOOST_DEVICE PackedReduceResult() : residue_sum_{0}, weights_sum_{0} {} + + XGBOOST_DEVICE PackedReduceResult(double residue, double weight) : + residue_sum_{residue}, weights_sum_{weight} {} + + XGBOOST_DEVICE + PackedReduceResult operator+(PackedReduceResult const &other) const { + return PackedReduceResult{residue_sum_ + other.residue_sum_, + weights_sum_ + other.weights_sum_}; + } + + double Residue() const { return residue_sum_; } + + double Weights() const { return weights_sum_; } + }; + + public: + explicit MetricsReduction(EvalRow policy) : + policy_(std::move(policy)) {} + + PackedReduceResult CpuReduceMetrics( + const HostDeviceVector &weights, + const HostDeviceVector &labels, + const HostDeviceVector &preds) const { + size_t ndata = labels.Size(); + + const auto &h_labels = labels.HostVector(); + const auto &h_weights = weights.HostVector(); + const auto &h_preds = preds.HostVector(); + + bst_float residue_sum = 0; + bst_float weights_sum = 0; + +#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) + for (omp_ulong i = 0; i < ndata; ++i) { + const bst_float wt = h_weights.size() > 0 ? h_weights[i] : 1.0f; + residue_sum += policy_.EvalRow(h_labels[i], h_preds[i]) * wt; + weights_sum += wt; + } + PackedReduceResult res{residue_sum, weights_sum}; + return res; + } + +#if defined(XGBOOST_USE_CUDA) + + PackedReduceResult DeviceReduceMetrics( + GPUSet::GpuIdType device_id, + size_t device_index, + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds) { + size_t n_data = preds.DeviceSize(device_id); + + thrust::counting_iterator begin(0); + thrust::counting_iterator end = begin + n_data; + + auto s_label = labels.DeviceSpan(device_id); + auto s_preds = preds.DeviceSpan(device_id); + auto s_weights = weights.DeviceSpan(device_id); + + bool const is_null_weight = weights.Size() == 0; + + auto d_policy = policy_; + + PackedReduceResult result = thrust::transform_reduce( + thrust::cuda::par(allocators_.at(device_index)), + begin, end, + [=] XGBOOST_DEVICE(size_t idx) { + bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; + + bst_float residue = d_policy.EvalRow(s_label[idx], s_preds[idx]); + residue *= weight; + return PackedReduceResult{ residue, weight }; + }, + PackedReduceResult(), + thrust::plus()); + + return result; + } + +#endif // XGBOOST_USE_CUDA + + PackedReduceResult Reduce( + GPUSet devices, + const HostDeviceVector &weights, + const HostDeviceVector &labels, + const HostDeviceVector &preds) { + PackedReduceResult result; + + if (devices.IsEmpty()) { + result = CpuReduceMetrics(weights, labels, preds); + } +#if defined(XGBOOST_USE_CUDA) + else { // NOLINT + if (allocators_.size() != devices.Size()) { + allocators_.clear(); + allocators_.resize(devices.Size()); + } + preds.Reshard(devices); + labels.Reshard(devices); + weights.Reshard(devices); + std::vector res_per_device(devices.Size()); + +#pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) + for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { + dh::safe_cuda(cudaSetDevice(id)); + size_t index = devices.Index(id); + res_per_device.at(index) = DeviceReduceMetrics(id, index, weights, labels, preds); + } + + for (size_t i = 0; i < devices.Size(); ++i) { + result.residue_sum_ += res_per_device[i].residue_sum_; + result.weights_sum_ += res_per_device[i].weights_sum_; + } + } +#endif // defined(XGBOOST_USE_CUDA) + return result; + } + + private: + EvalRow policy_; +#if defined(XGBOOST_USE_CUDA) + std::vector allocators_; +#endif // defined(XGBOOST_USE_CUDA) +}; + +template +struct EvalEWiseBase : public Metric { + EvalEWiseBase() : policy_{}, reducer_{policy_} {} + + explicit EvalEWiseBase(Policy &policy) : policy_{policy}, reducer_{policy_} {} + + explicit EvalEWiseBase(char const *policy_param) : + policy_{policy_param}, reducer_{policy_} {} + + void Configure( + const std::vector> &args) override { + param_.InitAllowUnknown(args); + } + + bst_float Eval(const HostDeviceVector &preds, + const MetaInfo &info, + bool distributed) override { + CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; + CHECK_EQ(preds.Size(), info.labels_.Size()) + << "label and prediction size not match, " + << "hint: use merror or mlogloss for multi-class classification"; + const auto ndata = static_cast(info.labels_.Size()); + // Dealing with ndata < n_gpus. + GPUSet devices = GPUSet::All(param_.gpu_id, param_.n_gpus, ndata); + + auto result = + reducer_.Reduce(devices, info.weights_, info.labels_, preds); + + double dat[2]{result.Residue(), result.Weights()}; + if (distributed) { + rabit::Allreduce(dat, 2); + } + return Policy::GetFinal(dat[0], dat[1]); + } + + const char *Name() const override { + return policy_.Name(); + } + + private: + Policy policy_; + + MetricParam param_; + + MetricsReduction reducer_; +}; + +} // namespace metric +} // namespace xgboost +#endif // XGBOOST_ELEMENTWISE_METRIC_H_ diff --git a/include/xgboost/metric.h b/include/xgboost/metric/metric.h similarity index 96% rename from include/xgboost/metric.h rename to include/xgboost/metric/metric.h index 9543bbfb2a7f..9e0554e355ed 100644 --- a/include/xgboost/metric.h +++ b/include/xgboost/metric/metric.h @@ -12,9 +12,9 @@ #include #include -#include "./data.h" -#include "./base.h" -#include "../../src/common/host_device_vector.h" +#include "../data.h" +#include "../base.h" +#include "../../../src/common/host_device_vector.h" namespace xgboost { /*! diff --git a/include/xgboost/metric_param.h b/include/xgboost/metric/metric_param.h similarity index 80% rename from include/xgboost/metric_param.h rename to include/xgboost/metric/metric_param.h index bc2a687572d0..b6a639d219f9 100644 --- a/include/xgboost/metric_param.h +++ b/include/xgboost/metric/metric_param.h @@ -2,11 +2,11 @@ * Copyright 2018 by Contributors * \file metric_param.cc */ -#ifndef XGBOOST_METRIC_METRIC_PARAM_H_ -#define XGBOOST_METRIC_METRIC_PARAM_H_ +#ifndef XGBOOST_METRIC_PARAM_H_ +#define XGBOOST_METRIC_PARAM_H_ #include -#include "../../src/common/common.h" +#include "../../../src/common/common.h" namespace xgboost { namespace metric { @@ -28,4 +28,4 @@ struct MetricParam : public dmlc::Parameter { } // namespace metric } // namespace xgboost -#endif // XGBOOST_METRIC_METRIC_PARAM_H_ +#endif // XGBOOST_METRIC_PARAM_H_ diff --git a/include/xgboost/metric/multiclass_metric.h b/include/xgboost/metric/multiclass_metric.h new file mode 100644 index 000000000000..c0719a3148ba --- /dev/null +++ b/include/xgboost/metric/multiclass_metric.h @@ -0,0 +1,93 @@ +/*! + * Copyright 2019 by Contributors + * \file multiclass_metric.cc + * \brief evaluation metrics for multiclass classification. + */ + +#ifndef XGBOOST_MULTICLASS_METRIC_H_ +#define XGBOOST_MULTICLASS_METRIC_H_ + +#include + +#include + +namespace xgboost { +namespace metric { +/*! +* \brief base class of multi-class evaluation +* \tparam Derived the name of subclass +*/ +template +struct EvalMClassBase : public Metric { + bst_float Eval(const HostDeviceVector &preds, + const MetaInfo &info, + bool distributed) override { + CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; + CHECK(preds.Size() % info.labels_.Size() == 0) + << "label and prediction size not match"; + const size_t nclass = preds.Size() / info.labels_.Size(); + CHECK_GE(nclass, 1U) + << "mlogloss and merror are only used for multi-class classification," + << " use logloss for binary classification"; + const auto ndata = static_cast(info.labels_.Size()); + double sum = 0.0, wsum = 0.0; + int label_error = 0; + + const auto &labels = info.labels_.HostVector(); + const auto &weights = info.weights_.HostVector(); + const std::vector &h_preds = preds.HostVector(); + +#pragma omp parallel for reduction(+: sum, wsum) schedule(static) + for (bst_omp_uint i = 0; i < ndata; ++i) { + const bst_float wt = weights.size() > 0 ? weights[i] : 1.0f; + auto label = static_cast(labels[i]); + if (label >= 0 && label < static_cast(nclass)) { + sum += Derived::EvalRow(label, + h_preds.data() + i * nclass, + nclass) * wt; + wsum += wt; + } else { + label_error = label; + } + } + CHECK(label_error >= 0 && label_error < static_cast(nclass)) + << "MultiClassEvaluation: label must be in [0, num_class)," + << " num_class=" << nclass << " but found " << label_error << " in label"; + + double dat[2]; + dat[0] = sum, dat[1] = wsum; + if (distributed) { + rabit::Allreduce(dat, 2); + } + return Derived::GetFinal(dat[0], dat[1]); + } + + /*! + * \brief to be implemented by subclass, + * get evaluation result from one row + * \param label label of current instance + * \param pred prediction value of current instance + * \param nclass number of class in the prediction + */ + inline static bst_float EvalRow(int label, + const bst_float *pred, + size_t nclass); + + /*! + * \brief to be overridden by subclass, final transformation + * \param esum the sum statistics returned by EvalRow + * \param wsum sum of weight + */ + inline static bst_float GetFinal(bst_float esum, bst_float wsum) { + return esum / wsum; + } + + private: + // used to store error message + const char *error_msg_; +}; + +} // namespace metric +} // namespace xgboost + +#endif // XGBOOST_MULTICLASS_METRIC_H_ diff --git a/include/xgboost/metric/ranking_metric.h b/include/xgboost/metric/ranking_metric.h new file mode 100644 index 000000000000..6dab6e661d5b --- /dev/null +++ b/include/xgboost/metric/ranking_metric.h @@ -0,0 +1,109 @@ +/* + Copyright (c) 2019 by Contributors + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef XGBOOST_RANKING_METRIC_H_ +#define XGBOOST_RANKING_METRIC_H_ + +#include +#include + +#include +#include + +namespace xgboost { +namespace metric { +/*! \brief Evaluate rank list */ +struct EvalRankList : public Metric { + public: + bst_float Eval(const HostDeviceVector &preds, + const MetaInfo &info, + bool distributed) override { + CHECK_EQ(preds.Size(), info.labels_.Size()) + << "label size predict size not match"; + // quick consistency when group is not available + std::vector tgptr(2, 0); + tgptr[1] = static_cast(preds.Size()); + const std::vector &gptr = info.group_ptr_.size() == 0 ? tgptr : info.group_ptr_; + CHECK_NE(gptr.size(), 0U) << "must specify group when constructing rank file"; + CHECK_EQ(gptr.back(), preds.Size()) + << "EvalRanklist: group structure must match number of prediction"; + const auto ngroup = static_cast(gptr.size() - 1); + // sum statistics + double sum_metric = 0.0f; + const auto &labels = info.labels_.HostVector(); + + const std::vector &h_preds = preds.HostVector(); + #pragma omp parallel reduction(+:sum_metric) + { + // each thread takes a local rec + std::vector > rec; + #pragma omp for schedule(static) + for (bst_omp_uint k = 0; k < ngroup; ++k) { + rec.clear(); + for (unsigned j = gptr[k]; j < gptr[k + 1]; ++j) { + rec.emplace_back(h_preds[j], static_cast(labels[j])); + } + sum_metric += this->EvalMetric(rec); + } + } + if (distributed) { + bst_float dat[2]; + dat[0] = static_cast(sum_metric); + dat[1] = static_cast(ngroup); + // approximately estimate the metric using mean + rabit::Allreduce(dat, 2); + return dat[0] / dat[1]; + } else { + return static_cast(sum_metric) / ngroup; + } + } + + const char *Name() const override { + return name_.c_str(); + } + + protected: + explicit EvalRankList(const char *name, const char *param) { + using namespace std; // NOLINT(*) + minus_ = false; + if (param != nullptr) { + std::ostringstream os; + os << name << '@' << param; + name_ = os.str(); + if (sscanf(param, "%u[-]?", &topn_) != 1) { + topn_ = std::numeric_limits::max(); + } + if (param[strlen(param) - 1] == '-') { + minus_ = true; + } + } else { + name_ = name; + topn_ = std::numeric_limits::max(); + } + } + + /*! \return evaluation metric, given the pair_sort record, (pred,label) */ + virtual bst_float + EvalMetric(std::vector > &pair_sort) const = 0; // NOLINT(*) + + protected: + unsigned topn_; + std::string name_; + bool minus_; +}; + +} // namespace metric +} // namespace xgboost + +#endif // XGBOOST_RANKING_METRIC_H_ diff --git a/include/xgboost/multiclass_metric.h b/include/xgboost/multiclass_metric.h deleted file mode 100644 index 86b05775a57c..000000000000 --- a/include/xgboost/multiclass_metric.h +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright 2019 by Contributors - * \file multiclass_metric.cc - * \brief evaluation metrics for multiclass classification. - */ - -#ifndef XGBOOST_MULTICLASS_METRIC_H -#define XGBOOST_MULTICLASS_METRIC_H - -#include -namespace xgboost { - namespace metric { -/*! - * \brief base class of multi-class evaluation - * \tparam Derived the name of subclass - */ - template - struct EvalMClassBase : public Metric { - bst_float Eval(const HostDeviceVector &preds, - const MetaInfo &info, - bool distributed) override { - CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; - CHECK(preds.Size() % info.labels_.Size() == 0) - << "label and prediction size not match"; - const size_t nclass = preds.Size() / info.labels_.Size(); - CHECK_GE(nclass, 1U) - << "mlogloss and merror are only used for multi-class classification," - << " use logloss for binary classification"; - const auto ndata = static_cast(info.labels_.Size()); - double sum = 0.0, wsum = 0.0; - int label_error = 0; - - const auto &labels = info.labels_.HostVector(); - const auto &weights = info.weights_.HostVector(); - const std::vector &h_preds = preds.HostVector(); - -#pragma omp parallel for reduction(+: sum, wsum) schedule(static) - for (bst_omp_uint i = 0; i < ndata; ++i) { - const bst_float wt = weights.size() > 0 ? weights[i] : 1.0f; - auto label = static_cast(labels[i]); - if (label >= 0 && label < static_cast(nclass)) { - sum += Derived::EvalRow(label, - h_preds.data() + i * nclass, - nclass) * wt; - wsum += wt; - } else { - label_error = label; - } - } - CHECK(label_error >= 0 && label_error < static_cast(nclass)) - << "MultiClassEvaluation: label must be in [0, num_class)," - << " num_class=" << nclass << " but found " << label_error << " in label"; - - double dat[2]; - dat[0] = sum, dat[1] = wsum; - if (distributed) { - rabit::Allreduce(dat, 2); - } - return Derived::GetFinal(dat[0], dat[1]); - } - - /*! - * \brief to be implemented by subclass, - * get evaluation result from one row - * \param label label of current instance - * \param pred prediction value of current instance - * \param nclass number of class in the prediction - */ - inline static bst_float EvalRow(int label, - const bst_float *pred, - size_t nclass); - - /*! - * \brief to be overridden by subclass, final transformation - * \param esum the sum statistics returned by EvalRow - * \param wsum sum of weight - */ - inline static bst_float GetFinal(bst_float esum, bst_float wsum) { - return esum / wsum; - } - - private: - // used to store error message - const char *error_msg_; - }; - }; -} - -#endif //XGBOOST_MULTICLASS_METRIC_H diff --git a/include/xgboost/ranking_metric.h b/include/xgboost/ranking_metric.h deleted file mode 100644 index 844a1988794a..000000000000 --- a/include/xgboost/ranking_metric.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - Copyright (c) 2019 by Contributors - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -#include -#include - -#ifndef XGBOOST_RANKING_METRIC_H -#define XGBOOST_RANKING_METRIC_H - -namespace xgboost { - namespace metric { -/*! \brief Evaluate rank list */ - struct EvalRankList : public Metric { - public: - bst_float Eval(const HostDeviceVector &preds, - const MetaInfo &info, - bool distributed) override { - CHECK_EQ(preds.Size(), info.labels_.Size()) - << "label size predict size not match"; - // quick consistency when group is not available - std::vector tgptr(2, 0); - tgptr[1] = static_cast(preds.Size()); - const std::vector &gptr = info.group_ptr_.size() == 0 ? tgptr : info.group_ptr_; - CHECK_NE(gptr.size(), 0U) << "must specify group when constructing rank file"; - CHECK_EQ(gptr.back(), preds.Size()) - << "EvalRanklist: group structure must match number of prediction"; - const auto ngroup = static_cast(gptr.size() - 1); - // sum statistics - double sum_metric = 0.0f; - const auto &labels = info.labels_.HostVector(); - - const std::vector &h_preds = preds.HostVector(); -#pragma omp parallel reduction(+:sum_metric) - { - // each thread takes a local rec - std::vector > rec; -#pragma omp for schedule(static) - for (bst_omp_uint k = 0; k < ngroup; ++k) { - rec.clear(); - for (unsigned j = gptr[k]; j < gptr[k + 1]; ++j) { - rec.emplace_back(h_preds[j], static_cast(labels[j])); - } - sum_metric += this->EvalMetric(rec); - } - } - if (distributed) { - bst_float dat[2]; - dat[0] = static_cast(sum_metric); - dat[1] = static_cast(ngroup); - // approximately estimate the metric using mean - rabit::Allreduce(dat, 2); - return dat[0] / dat[1]; - } else { - return static_cast(sum_metric) / ngroup; - } - } - - const char *Name() const override { - return name_.c_str(); - } - - protected: - explicit EvalRankList(const char *name, const char *param) { - using namespace std; // NOLINT(*) - minus_ = false; - if (param != nullptr) { - std::ostringstream os; - os << name << '@' << param; - name_ = os.str(); - if (sscanf(param, "%u[-]?", &topn_) != 1) { - topn_ = std::numeric_limits::max(); - } - if (param[strlen(param) - 1] == '-') { - minus_ = true; - } - } else { - name_ = name; - topn_ = std::numeric_limits::max(); - } - } - - /*! \return evaluation metric, given the pair_sort record, (pred,label) */ - virtual bst_float - EvalMetric(std::vector > &pair_sort) const = 0; // NOLINT(*) - - protected: - unsigned topn_; - std::string name_; - bool minus_; - }; - } -} - -#endif //XGBOOST_RANKING_METRIC_H diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 037b4c9d91db..8878f374fc85 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -17,14 +17,14 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include "./xgboost4j.h" #include #include #include -#include +#include // helper functions // set handle diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index c8a82a4f9987..e0a7abbff10a 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1157,7 +1157,7 @@ QueryBoosterConfigurationArguments(BoosterHandle handle) { return bst->learner()->GetConfigurationArguments(); } -XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string& metrics_name) { +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, const std::string& metrics_name) { auto* bst = static_cast(handle); // note: this function is only called by jvm packages which does not support multiple // evaluation metrics for now, @@ -1168,10 +1168,5 @@ XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, std::string& metr bst->learner()->GetConfigurationArguments().end()); } -XGB_DLL int XGBoosterGetMetricsCount(BoosterHandle handle) { - auto* bst = static_cast(handle); - return bst->learner()->metrics_.size(); -} - // force link rabit static DMLC_ATTRIBUTE_UNUSED int XGBOOST_LINK_RABIT_C_API_ = RabitLinkTag(); diff --git a/src/metric/elementwise_metric.cu b/src/metric/elementwise_metric.cu index 851f1d419aa5..5afc185de850 100644 --- a/src/metric/elementwise_metric.cu +++ b/src/metric/elementwise_metric.cu @@ -3,9 +3,9 @@ */ #include -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/metric/metric.cc b/src/metric/metric.cc index f4c3fc34d045..83174a3c1d47 100644 --- a/src/metric/metric.cc +++ b/src/metric/metric.cc @@ -3,8 +3,8 @@ * \file metric_registry.cc * \brief Registry of objective functions. */ -#include -#include +#include +#include #include diff --git a/src/metric/multiclass_metric.cc b/src/metric/multiclass_metric.cc index 39b754ad5d70..fb697389a7e2 100644 --- a/src/metric/multiclass_metric.cc +++ b/src/metric/multiclass_metric.cc @@ -5,7 +5,7 @@ #include -#include +#include #include #include "../common/math.h" diff --git a/src/metric/rank_metric.cc b/src/metric/rank_metric.cc index f6b6fb7c1988..518816bd9960 100644 --- a/src/metric/rank_metric.cc +++ b/src/metric/rank_metric.cc @@ -4,8 +4,8 @@ * \brief prediction rank based metrics. */ #include -#include -#include +#include +#include #include #include diff --git a/tests/cpp/helpers.h b/tests/cpp/helpers.h index 33dd072f2c5e..718f124e08ad 100644 --- a/tests/cpp/helpers.h +++ b/tests/cpp/helpers.h @@ -16,8 +16,8 @@ #include #include -#include #include +#include #if defined(__CUDACC__) #define DeclareUnifiedTest(name) GPU ## name diff --git a/tests/cpp/metric/test_elementwise_metric.cc b/tests/cpp/metric/test_elementwise_metric.cc index f4e3f413719b..62133ffecb5f 100644 --- a/tests/cpp/metric/test_elementwise_metric.cc +++ b/tests/cpp/metric/test_elementwise_metric.cc @@ -1,7 +1,7 @@ /*! * Copyright 2018 XGBoost contributors */ -#include +#include #include #include "../helpers.h" diff --git a/tests/cpp/metric/test_metric.cc b/tests/cpp/metric/test_metric.cc index 8311663b0b8d..46a1a6559d70 100644 --- a/tests/cpp/metric/test_metric.cc +++ b/tests/cpp/metric/test_metric.cc @@ -1,5 +1,5 @@ // Copyright by Contributors -#include +#include #include "../helpers.h" diff --git a/tests/cpp/metric/test_multiclass_metric.cc b/tests/cpp/metric/test_multiclass_metric.cc index 79954784593a..59f16128b066 100644 --- a/tests/cpp/metric/test_multiclass_metric.cc +++ b/tests/cpp/metric/test_multiclass_metric.cc @@ -1,5 +1,5 @@ // Copyright by Contributors -#include +#include #include #include "../helpers.h" diff --git a/tests/cpp/metric/test_rank_metric.cc b/tests/cpp/metric/test_rank_metric.cc index e8082fc67b68..2d0e1f74560b 100644 --- a/tests/cpp/metric/test_rank_metric.cc +++ b/tests/cpp/metric/test_rank_metric.cc @@ -1,5 +1,5 @@ // Copyright by Contributors -#include +#include #include "../helpers.h" From e374d826e43f42e69d86d104e2e6bf51018b130b Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 26 Mar 2019 14:15:07 +0800 Subject: [PATCH 26/39] make lint happy --- include/xgboost/metric/elementwise_metric.h | 6 +++--- include/xgboost/metric/metric.h | 6 +++--- include/xgboost/metric/metric_param.h | 6 +++--- include/xgboost/metric/multiclass_metric.h | 6 +++--- include/xgboost/metric/ranking_metric.h | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/include/xgboost/metric/elementwise_metric.h b/include/xgboost/metric/elementwise_metric.h index d22639fcac74..8123b550c16c 100644 --- a/include/xgboost/metric/elementwise_metric.h +++ b/include/xgboost/metric/elementwise_metric.h @@ -2,8 +2,8 @@ * Copyright 2015-2019 by Contributors */ -#ifndef XGBOOST_ELEMENTWISE_METRIC_H_ -#define XGBOOST_ELEMENTWISE_METRIC_H_ +#ifndef XGBOOST_METRIC_ELEMENTWISE_METRIC_H_ +#define XGBOOST_METRIC_ELEMENTWISE_METRIC_H_ #include #include @@ -215,4 +215,4 @@ struct EvalEWiseBase : public Metric { } // namespace metric } // namespace xgboost -#endif // XGBOOST_ELEMENTWISE_METRIC_H_ +#endif // XGBOOST_METRIC_ELEMENTWISE_METRIC_H_ diff --git a/include/xgboost/metric/metric.h b/include/xgboost/metric/metric.h index 9e0554e355ed..3fa0402dee75 100644 --- a/include/xgboost/metric/metric.h +++ b/include/xgboost/metric/metric.h @@ -3,8 +3,8 @@ * \file metric.h * \brief interface of evaluation metric function supported in xgboost. */ -#ifndef XGBOOST_METRIC_H_ -#define XGBOOST_METRIC_H_ +#ifndef XGBOOST_METRIC_METRIC_H_ +#define XGBOOST_METRIC_METRIC_H_ #include #include @@ -92,4 +92,4 @@ struct MetricReg ::xgboost::MetricReg& __make_ ## MetricReg ## _ ## UniqueId ## __ = \ ::dmlc::Registry< ::xgboost::MetricReg>::Get()->__REGISTER__(Name) } // namespace xgboost -#endif // XGBOOST_METRIC_H_ +#endif // XGBOOST_METRIC_METRIC_H_ diff --git a/include/xgboost/metric/metric_param.h b/include/xgboost/metric/metric_param.h index b6a639d219f9..44ed52239a49 100644 --- a/include/xgboost/metric/metric_param.h +++ b/include/xgboost/metric/metric_param.h @@ -2,8 +2,8 @@ * Copyright 2018 by Contributors * \file metric_param.cc */ -#ifndef XGBOOST_METRIC_PARAM_H_ -#define XGBOOST_METRIC_PARAM_H_ +#ifndef XGBOOST_METRIC_METRIC_PARAM_H_ +#define XGBOOST_METRIC_METRIC_PARAM_H_ #include #include "../../../src/common/common.h" @@ -28,4 +28,4 @@ struct MetricParam : public dmlc::Parameter { } // namespace metric } // namespace xgboost -#endif // XGBOOST_METRIC_PARAM_H_ +#endif // XGBOOST_METRIC_METRIC_PARAM_H_ diff --git a/include/xgboost/metric/multiclass_metric.h b/include/xgboost/metric/multiclass_metric.h index c0719a3148ba..690184f83626 100644 --- a/include/xgboost/metric/multiclass_metric.h +++ b/include/xgboost/metric/multiclass_metric.h @@ -4,8 +4,8 @@ * \brief evaluation metrics for multiclass classification. */ -#ifndef XGBOOST_MULTICLASS_METRIC_H_ -#define XGBOOST_MULTICLASS_METRIC_H_ +#ifndef XGBOOST_METRIC_MULTICLASS_METRIC_H_ +#define XGBOOST_METRIC_MULTICLASS_METRIC_H_ #include @@ -90,4 +90,4 @@ struct EvalMClassBase : public Metric { } // namespace metric } // namespace xgboost -#endif // XGBOOST_MULTICLASS_METRIC_H_ +#endif // XGBOOST_METRIC_MULTICLASS_METRIC_H_ diff --git a/include/xgboost/metric/ranking_metric.h b/include/xgboost/metric/ranking_metric.h index 6dab6e661d5b..f92ab18b94d2 100644 --- a/include/xgboost/metric/ranking_metric.h +++ b/include/xgboost/metric/ranking_metric.h @@ -12,8 +12,8 @@ limitations under the License. */ -#ifndef XGBOOST_RANKING_METRIC_H_ -#define XGBOOST_RANKING_METRIC_H_ +#ifndef XGBOOST_METRIC_RANKING_METRIC_H_ +#define XGBOOST_METRIC_RANKING_METRIC_H_ #include #include @@ -106,4 +106,4 @@ struct EvalRankList : public Metric { } // namespace metric } // namespace xgboost -#endif // XGBOOST_RANKING_METRIC_H_ +#endif // XGBOOST_METRIC_RANKING_METRIC_H_ From 28bf3a3c8b285be9758e718b100e8d07128d11ae Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 26 Mar 2019 14:22:45 +0800 Subject: [PATCH 27/39] make lint further happy --- include/xgboost/metric/metric_param.h | 3 +-- include/xgboost/metric/multiclass_metric.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/include/xgboost/metric/metric_param.h b/include/xgboost/metric/metric_param.h index 44ed52239a49..2c0804150ecf 100644 --- a/include/xgboost/metric/metric_param.h +++ b/include/xgboost/metric/metric_param.h @@ -1,6 +1,5 @@ /*! - * Copyright 2018 by Contributors - * \file metric_param.cc + * Copyright 2019 by Contributors */ #ifndef XGBOOST_METRIC_METRIC_PARAM_H_ #define XGBOOST_METRIC_METRIC_PARAM_H_ diff --git a/include/xgboost/metric/multiclass_metric.h b/include/xgboost/metric/multiclass_metric.h index 690184f83626..573c0f8132a3 100644 --- a/include/xgboost/metric/multiclass_metric.h +++ b/include/xgboost/metric/multiclass_metric.h @@ -1,7 +1,5 @@ /*! * Copyright 2019 by Contributors - * \file multiclass_metric.cc - * \brief evaluation metrics for multiclass classification. */ #ifndef XGBOOST_METRIC_MULTICLASS_METRIC_H_ From 333f2513a668d42576ad19efa895e9aa0a8362e7 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 26 Mar 2019 17:13:12 +0800 Subject: [PATCH 28/39] fix windows build --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 8878f374fc85..99c67de3636e 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -182,14 +182,16 @@ class CustomEvalRanking : public xgboost::metric::EvalRankList { jmethodID eval_metrics_func = jenv->GetMethodID(eval_interface, "evalMetric", "([F[I)F"); jfloatArray preds = jenv->NewFloatArray(rec.size()); jintArray labels = jenv->NewIntArray(rec.size()); - jfloat fill_float[rec.size()]; - jint fill_int[rec.size()]; + std::vector fill_float; + std::vector fill_int; for (int i = 0; i < rec.size(); i++) { - fill_float[i] = rec[i].first; - fill_int[i] = rec[i].second; + fill_float.push_back(rec[i].first); + fill_int.push_back(rec[i].second); } - jenv->SetFloatArrayRegion(preds, 0, rec.size(), fill_float); - jenv->SetIntArrayRegion(labels, 0, rec.size(), fill_int); + float *f1 = &fill_float[0]; + int *f2 = &fill_int[0]; + jenv->SetFloatArrayRegion(preds, 0, rec.size(), f1); + jenv->SetIntArrayRegion(labels, 0, rec.size(), f2); return jenv->CallFloatMethod(custom_eval_handle, eval_metrics_func, preds, labels); } From 4d7298de2deffdde021e8f698993a44e96e85878 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 26 Mar 2019 20:32:15 +0800 Subject: [PATCH 29/39] windows build --- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 99c67de3636e..186119b0a3e6 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -188,8 +188,8 @@ class CustomEvalRanking : public xgboost::metric::EvalRankList { fill_float.push_back(rec[i].first); fill_int.push_back(rec[i].second); } - float *f1 = &fill_float[0]; - int *f2 = &fill_int[0]; + jfloat *f1 = &fill_float[0]; + jint *f2 = &fill_int[0]; jenv->SetFloatArrayRegion(preds, 0, rec.size(), f1); jenv->SetIntArrayRegion(labels, 0, rec.size(), f2); return jenv->CallFloatMethod(custom_eval_handle, eval_metrics_func, preds, labels); From ee89febc5b7c12762f3c1409c50d2ce191b6d380 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 28 Mar 2019 10:40:54 +0800 Subject: [PATCH 30/39] add docs --- doc/jvm/xgboost4j_spark_tutorial.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/jvm/xgboost4j_spark_tutorial.rst b/doc/jvm/xgboost4j_spark_tutorial.rst index fd106be7c3d1..8e0be9d0b2c3 100644 --- a/doc/jvm/xgboost4j_spark_tutorial.rst +++ b/doc/jvm/xgboost4j_spark_tutorial.rst @@ -205,6 +205,18 @@ Training with Evaluation Sets You can also monitor the performance of the model during training with multiple evaluation datasets. By specifying ``eval_sets`` or call ``setEvalSets`` over a XGBoostClassifier or XGBoostRegressor, you can pass in multiple evaluation datasets typed as a Map from String to DataFrame. +Training with Custom Evaluation Metrics +---------------- +With XGBoost4j (including XGBoost4J-Spark), users are able to implement their own custom evaluation metrics and synchronize the values in the distributed training setting. To implement a custom evaluation metric, users should implement the interface ``ml.dmlc.xgboost4j.java.IEvalElementWiseDistributed`` (for binary classification and regression), ``ml.dmlc.xgboost4j.java.IEvalMultiClassesDistributed`` (for multi classification) and ``ml.dmlc.xgboost4j.java.IEvalRankListDistributed`` (for ranking). + +* ``ml.dmlc.xgboost4j.java.IEvalElementWiseDistributed``: users are supposed to implement ``float evalRow(float label, float pred);`` which calculates the metric for a single sample given the prediction and label, as well as ``float getFinal(float errorSum, float weightSum);`` which performs the final transformation over the sum of error and weights of samples. + +* ``ml.dmlc.xgboost4j.java.IEvalMultiClassesDistributed``: the methods to be implemented by the users are similar to ``ml.dmlc.xgboost4j.java.IEvalElementWiseDistributed`` except that the single row metric calculating method is ``float evalRow(int label, float pred, int numClasses);`` + +* ``ml.dmlc.xgboost4j.java.IEvalRankListDistributed``: users are to implement ``float evalMetric(float[] preds, int[] labels);`` which gives the predictions and labels for instances in the same group; + +By default, these interfaces do not support being used in single machine evaluation, users can change this by re-implement ``float eval(float[][] predicts, DMatrix dmat)`` method. + Prediction ========== From df90c9b5a60e98feefc6c90b39b22823adcd3ed4 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 28 Mar 2019 10:45:41 +0800 Subject: [PATCH 31/39] support both 4j and 4j-spark --- .../ml/dmlc/xgboost4j/scala/spark/XGBoost.scala | 10 ---------- .../main/java/ml/dmlc/xgboost4j/java/XGBoost.java | 13 ++++++++++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala index 002fdd52a3a3..de8abaa0a90f 100644 --- a/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala +++ b/jvm-packages/xgboost4j-spark/src/main/scala/ml/dmlc/xgboost4j/scala/spark/XGBoost.scala @@ -184,16 +184,6 @@ object XGBoost extends Serializable { } else { overridedParams = params + ("nthread" -> coresPerTask) } - if (params("custom_eval") != null) { - params("custom_eval") match { - case _: IEvalElementWiseDistributed => - overridedParams = overridedParams + ("custom_eval_type" -> "regression/binary") - case _: IEvalMultiClassesDistributed => - overridedParams = overridedParams + ("custom_eval_type" -> "multi_classes") - case _: IEvalRankListDistributed => - overridedParams = overridedParams + ("custom_eval_type" -> "ranking") - } - } overridedParams } diff --git a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java index 8c0f1d6715c9..9d5c4e212ff0 100644 --- a/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java +++ b/jvm-packages/xgboost4j/src/main/java/ml/dmlc/xgboost4j/java/XGBoost.java @@ -118,8 +118,7 @@ private static String performEvaluation( String[] evalNames, DMatrix[] evalMats, int iter, - float[] metricsOut, - String evalType) throws XGBoostError { + float[] metricsOut) throws XGBoostError { String evalInfo; if (eval != null && !(eval instanceof IEvalElementWiseDistributed) && @@ -128,6 +127,14 @@ private static String performEvaluation( evalInfo = booster.evalSet(evalMats, evalNames, eval, metricsOut); } else { if (eval != null) { + String evalType; + if (eval instanceof IEvalElementWiseDistributed) { + evalType = "regression/binary"; + } else if (eval instanceof IEvalMultiClassesDistributed) { + evalType = "multi_classes"; + } else { + evalType = "ranking"; + } registerNewCustomEvalForDistributed(booster, eval, evalType); } evalInfo = booster.evalSet(evalMats, evalNames, iter, metricsOut); @@ -223,7 +230,7 @@ public static Booster train( if (evalMats.length > 0) { float[] metricsOut = new float[evalMats.length]; String evalInfo = performEvaluation(booster, eval, evalNames, evalMats, iter, - metricsOut, (String) params.get("custom_eval_type")); + metricsOut); for (int i = 0; i < metricsOut.length; i++) { metrics[i][iter] = metricsOut[i]; } From 708bf69673aa42890ccd7a6059a66754e0b4075b Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 28 Mar 2019 10:47:31 +0800 Subject: [PATCH 32/39] recover header --- include/xgboost/build_config.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xgboost/build_config.h b/include/xgboost/build_config.h index 4d9b9f959397..195d92905162 100644 --- a/include/xgboost/build_config.h +++ b/include/xgboost/build_config.h @@ -7,6 +7,7 @@ // These check are for Makefile. #if !defined(XGBOOST_MM_PREFETCH_PRESENT) && !defined(XGBOOST_BUILTIN_PREFETCH_PRESENT) + /* default logic for software pre-fetching */ #if (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))) || defined(__INTEL_COMPILER) // Enable _mm_prefetch for Intel compiler and MSVC+x86 @@ -15,6 +16,7 @@ #elif defined(__GNUC__) // Enable __builtin_prefetch for GCC #define XGBOOST_BUILTIN_PREFETCH_PRESENT +#endif // GUARDS #endif // !defined(XGBOOST_MM_PREFETCH_PRESENT) && !defined() From 3802324a21d0a9fc348c1486a3ca512635c7114a Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Tue, 2 Apr 2019 10:15:57 +0800 Subject: [PATCH 33/39] address comments --- include/xgboost/c_api.h | 4 +--- include/xgboost/metric/metric.h | 6 +++--- jvm-packages/xgboost4j/src/native/xgboost4j.cpp | 2 +- src/c_api/c_api.cc | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/include/xgboost/c_api.h b/include/xgboost/c_api.h index 70d6e66fcaaa..03e6d3ee19b6 100644 --- a/include/xgboost/c_api.h +++ b/include/xgboost/c_api.h @@ -20,8 +20,6 @@ // XGBoost C API will include APIs in Rabit C API #include -#include - #if defined(_MSC_VER) || defined(_WIN32) #define XGB_DLL XGB_EXTERN_C __declspec(dllexport) #else @@ -570,6 +568,6 @@ XGB_DLL int XGBoosterLoadRabitCheckpoint( */ XGB_DLL int XGBoosterSaveRabitCheckpoint(BoosterHandle handle); -XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, const std::string& metrics_name); +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, const char* metrics_name); #endif // XGBOOST_C_API_H_ diff --git a/include/xgboost/metric/metric.h b/include/xgboost/metric/metric.h index 3fa0402dee75..d05ad7dd0c8d 100644 --- a/include/xgboost/metric/metric.h +++ b/include/xgboost/metric/metric.h @@ -1,5 +1,5 @@ /*! - * Copyright 2019 by Contributors + * Copyright 2014-2019 by Contributors * \file metric.h * \brief interface of evaluation metric function supported in xgboost. */ @@ -12,8 +12,8 @@ #include #include -#include "../data.h" -#include "../base.h" +#include +#include #include "../../../src/common/host_device_vector.h" namespace xgboost { diff --git a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp index 186119b0a3e6..7124cc64bbe6 100644 --- a/jvm-packages/xgboost4j/src/native/xgboost4j.cpp +++ b/jvm-packages/xgboost4j/src/native/xgboost4j.cpp @@ -968,7 +968,7 @@ JNIEXPORT jint JNICALL Java_ml_dmlc_xgboost4j_java_XGBoostJNI_XGBoosterAddNewMet }); } registering_mutex.unlock(); - XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str); + XGBoosterRegisterNewMetrics((BoosterHandle) jhandle, metrics_name_in_str.data()); return 0; } diff --git a/src/c_api/c_api.cc b/src/c_api/c_api.cc index e0a7abbff10a..706f9fd0e9a3 100644 --- a/src/c_api/c_api.cc +++ b/src/c_api/c_api.cc @@ -1157,7 +1157,7 @@ QueryBoosterConfigurationArguments(BoosterHandle handle) { return bst->learner()->GetConfigurationArguments(); } -XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, const std::string& metrics_name) { +XGB_DLL void XGBoosterRegisterNewMetrics(BoosterHandle handle, const char* metrics_name) { auto* bst = static_cast(handle); // note: this function is only called by jvm packages which does not support multiple // evaluation metrics for now, From d8ced76a61195d2fd638211a6462d47a04fd1b20 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 18 Apr 2019 13:46:15 -0700 Subject: [PATCH 34/39] update file names --- include/xgboost/elementwise_metric.h | 2 +- include/xgboost/metric/elementwise_metric.h | 2 +- .../xgboost}/metric/metric_common.h | 15 +++++----- include/xgboost/metric/metric_param.h | 30 ------------------- include/xgboost/metric/ranking_metric.h | 2 +- src/metric/elementwise_metric.cu | 3 +- src/metric/metric.cc | 4 +-- src/metric/multiclass_metric.cu | 4 +-- 8 files changed, 15 insertions(+), 47 deletions(-) rename {src => include/xgboost}/metric/metric_common.h (82%) delete mode 100644 include/xgboost/metric/metric_param.h diff --git a/include/xgboost/elementwise_metric.h b/include/xgboost/elementwise_metric.h index bef7e21c29b0..d78ca9511d0c 100644 --- a/include/xgboost/elementwise_metric.h +++ b/include/xgboost/elementwise_metric.h @@ -6,7 +6,7 @@ #define XGBOOST_ELEMENTWISE_METRIC_H #include -#include +#include #include "../src/common/common.h" diff --git a/include/xgboost/metric/elementwise_metric.h b/include/xgboost/metric/elementwise_metric.h index 8123b550c16c..590d60a307b7 100644 --- a/include/xgboost/metric/elementwise_metric.h +++ b/include/xgboost/metric/elementwise_metric.h @@ -6,7 +6,7 @@ #define XGBOOST_METRIC_ELEMENTWISE_METRIC_H_ #include -#include +#include #include #include diff --git a/src/metric/metric_common.h b/include/xgboost/metric/metric_common.h similarity index 82% rename from src/metric/metric_common.h rename to include/xgboost/metric/metric_common.h index 293d0a235926..7a0f06eebe6e 100644 --- a/src/metric/metric_common.h +++ b/include/xgboost/metric/metric_common.h @@ -1,12 +1,11 @@ /*! - * Copyright 2018-2019 by Contributors - * \file metric_param.cc + * Copyright 2019 by Contributors */ -#ifndef XGBOOST_METRIC_METRIC_COMMON_H_ -#define XGBOOST_METRIC_METRIC_COMMON_H_ +#ifndef XGBOOST_METRIC_METRIC_PARAM_H_ +#define XGBOOST_METRIC_METRIC_PARAM_H_ #include -#include "../common/common.h" +#include "../../../src/common/common.h" namespace xgboost { namespace metric { @@ -16,6 +15,7 @@ struct MetricParam : public dmlc::Parameter { int n_gpus; int gpu_id; DMLC_DECLARE_PARAMETER(MetricParam) { + DMLC_DECLARE_FIELD(n_gpus).set_default(1).set_lower_bound(GPUSet::kAll) .describe("Number of GPUs to use for multi-gpu algorithms."); DMLC_DECLARE_FIELD(gpu_id) @@ -37,8 +37,9 @@ class PackedReduceResult { XGBOOST_DEVICE PackedReduceResult operator+(PackedReduceResult const &other) const { return PackedReduceResult{residue_sum_ + other.residue_sum_, - weights_sum_ + other.weights_sum_}; + weights_sum_ + other.weights_sum_}; } + PackedReduceResult &operator+=(PackedReduceResult const &other) { this->residue_sum_ += other.residue_sum_; this->weights_sum_ += other.weights_sum_; @@ -51,4 +52,4 @@ class PackedReduceResult { } // namespace metric } // namespace xgboost -#endif // XGBOOST_METRIC_METRIC_COMMON_H_ +#endif // XGBOOST_METRIC_METRIC_PARAM_H_ diff --git a/include/xgboost/metric/metric_param.h b/include/xgboost/metric/metric_param.h deleted file mode 100644 index 2c0804150ecf..000000000000 --- a/include/xgboost/metric/metric_param.h +++ /dev/null @@ -1,30 +0,0 @@ -/*! - * Copyright 2019 by Contributors - */ -#ifndef XGBOOST_METRIC_METRIC_PARAM_H_ -#define XGBOOST_METRIC_METRIC_PARAM_H_ - -#include -#include "../../../src/common/common.h" - -namespace xgboost { -namespace metric { - -// Created exclusively for GPU. -struct MetricParam : public dmlc::Parameter { - int n_gpus; - int gpu_id; - DMLC_DECLARE_PARAMETER(MetricParam) { - DMLC_DECLARE_FIELD(n_gpus).set_default(1).set_lower_bound(GPUSet::kAll) - .describe("Number of GPUs to use for multi-gpu algorithms."); - DMLC_DECLARE_FIELD(gpu_id) - .set_lower_bound(0) - .set_default(0) - .describe("gpu to use for objective function evaluation"); - }; -}; - -} // namespace metric -} // namespace xgboost - -#endif // XGBOOST_METRIC_METRIC_PARAM_H_ diff --git a/include/xgboost/metric/ranking_metric.h b/include/xgboost/metric/ranking_metric.h index f92ab18b94d2..bc77cb264da6 100644 --- a/include/xgboost/metric/ranking_metric.h +++ b/include/xgboost/metric/ranking_metric.h @@ -16,7 +16,7 @@ #define XGBOOST_METRIC_RANKING_METRIC_H_ #include -#include +#include #include #include diff --git a/src/metric/elementwise_metric.cu b/src/metric/elementwise_metric.cu index 5afc185de850..e220f714187f 100644 --- a/src/metric/elementwise_metric.cu +++ b/src/metric/elementwise_metric.cu @@ -4,12 +4,11 @@ #include #include -#include +#include #include #include #include -#include "metric_common.h" #include "../common/math.h" #include "../common/common.h" diff --git a/src/metric/metric.cc b/src/metric/metric.cc index 83174a3c1d47..8074b6f07701 100644 --- a/src/metric/metric.cc +++ b/src/metric/metric.cc @@ -4,12 +4,10 @@ * \brief Registry of objective functions. */ #include -#include +#include #include -#include "metric_common.h" - namespace dmlc { DMLC_REGISTRY_ENABLE(::xgboost::MetricReg); } diff --git a/src/metric/multiclass_metric.cu b/src/metric/multiclass_metric.cu index 8f84379afef2..a550ca707b09 100644 --- a/src/metric/multiclass_metric.cu +++ b/src/metric/multiclass_metric.cu @@ -5,10 +5,10 @@ * \author Kailong Chen, Tianqi Chen */ #include -#include +#include +#include #include -#include "metric_common.h" #include "../common/math.h" #include "../common/common.h" From 137d01481ae047a5f7c9c1d8df2af48de071f969 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 18 Apr 2019 14:19:53 -0700 Subject: [PATCH 35/39] sync with upstream --- include/xgboost/metric/elementwise_metric.h | 34 +--- include/xgboost/metric/metric_common.h | 2 +- include/xgboost/metric/multiclass_metric.h | 188 ++++++++++++++++---- src/metric/multiclass_metric.cu | 136 -------------- 4 files changed, 161 insertions(+), 199 deletions(-) diff --git a/include/xgboost/metric/elementwise_metric.h b/include/xgboost/metric/elementwise_metric.h index 590d60a307b7..4a2eb0022d7c 100644 --- a/include/xgboost/metric/elementwise_metric.h +++ b/include/xgboost/metric/elementwise_metric.h @@ -32,32 +32,9 @@ namespace xgboost { namespace metric { template -class MetricsReduction { +class ElementWiseMetricsReduction { public: - class PackedReduceResult { - double residue_sum_; - double weights_sum_; - friend MetricsReduction; - - public: - XGBOOST_DEVICE PackedReduceResult() : residue_sum_{0}, weights_sum_{0} {} - - XGBOOST_DEVICE PackedReduceResult(double residue, double weight) : - residue_sum_{residue}, weights_sum_{weight} {} - - XGBOOST_DEVICE - PackedReduceResult operator+(PackedReduceResult const &other) const { - return PackedReduceResult{residue_sum_ + other.residue_sum_, - weights_sum_ + other.weights_sum_}; - } - - double Residue() const { return residue_sum_; } - - double Weights() const { return weights_sum_; } - }; - - public: - explicit MetricsReduction(EvalRow policy) : + explicit ElementWiseMetricsReduction(EvalRow policy) : policy_(std::move(policy)) {} PackedReduceResult CpuReduceMetrics( @@ -150,9 +127,8 @@ class MetricsReduction { res_per_device.at(index) = DeviceReduceMetrics(id, index, weights, labels, preds); } - for (size_t i = 0; i < devices.Size(); ++i) { - result.residue_sum_ += res_per_device[i].residue_sum_; - result.weights_sum_ += res_per_device[i].weights_sum_; + for (auto const& res : res_per_device) { + result += res; } } #endif // defined(XGBOOST_USE_CUDA) @@ -210,7 +186,7 @@ struct EvalEWiseBase : public Metric { MetricParam param_; - MetricsReduction reducer_; + ElementWiseMetricsReduction reducer_; }; } // namespace metric diff --git a/include/xgboost/metric/metric_common.h b/include/xgboost/metric/metric_common.h index 7a0f06eebe6e..da74a6340891 100644 --- a/include/xgboost/metric/metric_common.h +++ b/include/xgboost/metric/metric_common.h @@ -37,7 +37,7 @@ class PackedReduceResult { XGBOOST_DEVICE PackedReduceResult operator+(PackedReduceResult const &other) const { return PackedReduceResult{residue_sum_ + other.residue_sum_, - weights_sum_ + other.weights_sum_}; + weights_sum_ + other.weights_sum_}; } PackedReduceResult &operator+=(PackedReduceResult const &other) { diff --git a/include/xgboost/metric/multiclass_metric.h b/include/xgboost/metric/multiclass_metric.h index 573c0f8132a3..c321a1fe1a76 100644 --- a/include/xgboost/metric/multiclass_metric.h +++ b/include/xgboost/metric/multiclass_metric.h @@ -6,60 +6,181 @@ #define XGBOOST_METRIC_MULTICLASS_METRIC_H_ #include - +#include #include namespace xgboost { namespace metric { + +template +class MultiClassMetricsReduction { + void CheckLabelError(int32_t label_error, size_t n_class) const { + CHECK(label_error >= 0 && label_error < static_cast(n_class)) + << "MultiClassEvaluation: label must be in [0, num_class)," + << " num_class=" << n_class << " but found " << label_error << " in label"; + } + + public: + MultiClassMetricsReduction() = default; + + PackedReduceResult CpuReduceMetrics( + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds, + const size_t n_class) const { + size_t ndata = labels.Size(); + + const auto& h_labels = labels.HostVector(); + const auto& h_weights = weights.HostVector(); + const auto& h_preds = preds.HostVector(); + + bst_float residue_sum = 0; + bst_float weights_sum = 0; + int label_error = 0; + bool const is_null_weight = weights.Size() == 0; + +#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) + for (omp_ulong idx = 0; idx < ndata; ++idx) { + bst_float weight = is_null_weight ? 1.0f : h_weights[idx]; + auto label = static_cast(h_labels[idx]); + if (label >= 0 && label < static_cast(n_class)) { + residue_sum += EvalRowPolicy::EvalRow( + label, h_preds.data() + idx * n_class, n_class) * weight; + weights_sum += weight; + } else { + label_error = label; + } + } + CheckLabelError(label_error, n_class); + PackedReduceResult res { residue_sum, weights_sum }; + + return res; + } + +#if defined(XGBOOST_USE_CUDA) + + PackedReduceResult DeviceReduceMetrics( + GPUSet::GpuIdType device_id, + size_t device_index, + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds, + const size_t n_class) { + size_t n_data = labels.DeviceSize(device_id); + + thrust::counting_iterator begin(0); + thrust::counting_iterator end = begin + n_data; + + auto s_labels = labels.DeviceSpan(device_id); + auto s_preds = preds.DeviceSpan(device_id); + auto s_weights = weights.DeviceSpan(device_id); + + bool const is_null_weight = weights.Size() == 0; + auto s_label_error = label_error_.GetSpan(1); + s_label_error[0] = 0; + + PackedReduceResult result = thrust::transform_reduce( + thrust::cuda::par(allocators_.at(device_index)), + begin, end, + [=] XGBOOST_DEVICE(size_t idx) { + bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; + bst_float residue = 0; + auto label = static_cast(s_labels[idx]); + if (label >= 0 && label < static_cast(n_class)) { + residue = EvalRowPolicy::EvalRow( + label, &s_preds[idx * n_class], n_class) * weight; + } else { + s_label_error[0] = label; + } + return PackedReduceResult{ residue, weight }; + }, + PackedReduceResult(), + thrust::plus()); + CheckLabelError(s_label_error[0], n_class); + + return result; + } + +#endif // XGBOOST_USE_CUDA + + PackedReduceResult Reduce( + GPUSet devices, + size_t n_class, + const HostDeviceVector& weights, + const HostDeviceVector& labels, + const HostDeviceVector& preds) { + PackedReduceResult result; + + if (devices.IsEmpty()) { + result = CpuReduceMetrics(weights, labels, preds, n_class); + } + #if defined(XGBOOST_USE_CUDA) + else { // NOLINT + if (allocators_.size() != devices.Size()) { + allocators_.clear(); + allocators_.resize(devices.Size()); + } + preds.Reshard(GPUDistribution::Granular(devices, n_class)); + labels.Reshard(devices); + weights.Reshard(devices); + std::vector res_per_device(devices.Size()); + + #pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) + for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { + dh::safe_cuda(cudaSetDevice(id)); + size_t index = devices.Index(id); + res_per_device.at(index) = + DeviceReduceMetrics(id, index, weights, labels, preds, n_class); + } + + for (auto const& res : res_per_device) { + result += res; + } + } + #endif // defined(XGBOOST_USE_CUDA) + return result; + } + + private: + #if defined(XGBOOST_USE_CUDA) + dh::PinnedMemory label_error_; + std::vector allocators_; + #endif // defined(XGBOOST_USE_CUDA) +}; + + /*! * \brief base class of multi-class evaluation * \tparam Derived the name of subclass */ template struct EvalMClassBase : public Metric { + void Configure( + const std::vector >& args) override { + param_.InitAllowUnknown(args); + } + bst_float Eval(const HostDeviceVector &preds, const MetaInfo &info, bool distributed) override { CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; CHECK(preds.Size() % info.labels_.Size() == 0) - << "label and prediction size not match"; + << "label and prediction size not match"; const size_t nclass = preds.Size() / info.labels_.Size(); CHECK_GE(nclass, 1U) - << "mlogloss and merror are only used for multi-class classification," - << " use logloss for binary classification"; + << "mlogloss and merror are only used for multi-class classification," + << " use logloss for binary classification"; const auto ndata = static_cast(info.labels_.Size()); - double sum = 0.0, wsum = 0.0; - int label_error = 0; - const auto &labels = info.labels_.HostVector(); - const auto &weights = info.weights_.HostVector(); - const std::vector &h_preds = preds.HostVector(); - -#pragma omp parallel for reduction(+: sum, wsum) schedule(static) - for (bst_omp_uint i = 0; i < ndata; ++i) { - const bst_float wt = weights.size() > 0 ? weights[i] : 1.0f; - auto label = static_cast(labels[i]); - if (label >= 0 && label < static_cast(nclass)) { - sum += Derived::EvalRow(label, - h_preds.data() + i * nclass, - nclass) * wt; - wsum += wt; - } else { - label_error = label; - } - } - CHECK(label_error >= 0 && label_error < static_cast(nclass)) - << "MultiClassEvaluation: label must be in [0, num_class)," - << " num_class=" << nclass << " but found " << label_error << " in label"; + GPUSet devices = GPUSet::All(param_.gpu_id, param_.n_gpus, ndata); + auto result = reducer_.Reduce(devices, nclass, info.weights_, info.labels_, preds); + double dat[2] { result.Residue(), result.Weights() }; - double dat[2]; - dat[0] = sum, dat[1] = wsum; if (distributed) { rabit::Allreduce(dat, 2); } return Derived::GetFinal(dat[0], dat[1]); } - /*! * \brief to be implemented by subclass, * get evaluation result from one row @@ -67,10 +188,9 @@ struct EvalMClassBase : public Metric { * \param pred prediction value of current instance * \param nclass number of class in the prediction */ - inline static bst_float EvalRow(int label, - const bst_float *pred, - size_t nclass); - + XGBOOST_DEVICE static bst_float EvalRow(int label, + const bst_float *pred, + size_t nclass); /*! * \brief to be overridden by subclass, final transformation * \param esum the sum statistics returned by EvalRow @@ -81,6 +201,8 @@ struct EvalMClassBase : public Metric { } private: + MultiClassMetricsReduction reducer_; + MetricParam param_; // used to store error message const char *error_msg_; }; diff --git a/src/metric/multiclass_metric.cu b/src/metric/multiclass_metric.cu index a550ca707b09..33617b03b59f 100644 --- a/src/metric/multiclass_metric.cu +++ b/src/metric/multiclass_metric.cu @@ -26,142 +26,6 @@ namespace metric { // tag the this file, used by force static link later. DMLC_REGISTRY_FILE_TAG(multiclass_metric); -template -class MultiClassMetricsReduction { - void CheckLabelError(int32_t label_error, size_t n_class) const { - CHECK(label_error >= 0 && label_error < static_cast(n_class)) - << "MultiClassEvaluation: label must be in [0, num_class)," - << " num_class=" << n_class << " but found " << label_error << " in label"; - } - - public: - MultiClassMetricsReduction() = default; - - PackedReduceResult CpuReduceMetrics( - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds, - const size_t n_class) const { - size_t ndata = labels.Size(); - - const auto& h_labels = labels.HostVector(); - const auto& h_weights = weights.HostVector(); - const auto& h_preds = preds.HostVector(); - - bst_float residue_sum = 0; - bst_float weights_sum = 0; - int label_error = 0; - bool const is_null_weight = weights.Size() == 0; - -#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) - for (omp_ulong idx = 0; idx < ndata; ++idx) { - bst_float weight = is_null_weight ? 1.0f : h_weights[idx]; - auto label = static_cast(h_labels[idx]); - if (label >= 0 && label < static_cast(n_class)) { - residue_sum += EvalRowPolicy::EvalRow( - label, h_preds.data() + idx * n_class, n_class) * weight; - weights_sum += weight; - } else { - label_error = label; - } - } - CheckLabelError(label_error, n_class); - PackedReduceResult res { residue_sum, weights_sum }; - - return res; - } - -#if defined(XGBOOST_USE_CUDA) - - PackedReduceResult DeviceReduceMetrics( - GPUSet::GpuIdType device_id, - size_t device_index, - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds, - const size_t n_class) { - size_t n_data = labels.DeviceSize(device_id); - - thrust::counting_iterator begin(0); - thrust::counting_iterator end = begin + n_data; - - auto s_labels = labels.DeviceSpan(device_id); - auto s_preds = preds.DeviceSpan(device_id); - auto s_weights = weights.DeviceSpan(device_id); - - bool const is_null_weight = weights.Size() == 0; - auto s_label_error = label_error_.GetSpan(1); - s_label_error[0] = 0; - - PackedReduceResult result = thrust::transform_reduce( - thrust::cuda::par(allocators_.at(device_index)), - begin, end, - [=] XGBOOST_DEVICE(size_t idx) { - bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; - bst_float residue = 0; - auto label = static_cast(s_labels[idx]); - if (label >= 0 && label < static_cast(n_class)) { - residue = EvalRowPolicy::EvalRow( - label, &s_preds[idx * n_class], n_class) * weight; - } else { - s_label_error[0] = label; - } - return PackedReduceResult{ residue, weight }; - }, - PackedReduceResult(), - thrust::plus()); - CheckLabelError(s_label_error[0], n_class); - - return result; - } - -#endif // XGBOOST_USE_CUDA - - PackedReduceResult Reduce( - GPUSet devices, - size_t n_class, - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) { - PackedReduceResult result; - - if (devices.IsEmpty()) { - result = CpuReduceMetrics(weights, labels, preds, n_class); - } -#if defined(XGBOOST_USE_CUDA) - else { // NOLINT - if (allocators_.size() != devices.Size()) { - allocators_.clear(); - allocators_.resize(devices.Size()); - } - preds.Reshard(GPUDistribution::Granular(devices, n_class)); - labels.Reshard(devices); - weights.Reshard(devices); - std::vector res_per_device(devices.Size()); - -#pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) - for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { - dh::safe_cuda(cudaSetDevice(id)); - size_t index = devices.Index(id); - res_per_device.at(index) = - DeviceReduceMetrics(id, index, weights, labels, preds, n_class); - } - - for (auto const& res : res_per_device) { - result += res; - } - } -#endif // defined(XGBOOST_USE_CUDA) - return result; - } - - private: -#if defined(XGBOOST_USE_CUDA) - dh::PinnedMemory label_error_; - std::vector allocators_; -#endif // defined(XGBOOST_USE_CUDA) -}; - /*! \brief match error */ struct EvalMatchError : public EvalMClassBase { const char* Name() const override { From 17785c59500b34d72ceda2bf5d09b0f278a8d0f4 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 18 Apr 2019 20:15:40 -0700 Subject: [PATCH 36/39] fix cuda buiod --- include/xgboost/elementwise_metric.h | 324 --------------------- include/xgboost/metric/metric.h | 5 +- include/xgboost/metric/metric_common.h | 7 +- include/xgboost/metric/multiclass_metric.h | 15 +- src/metric/multiclass_metric.cu | 10 +- 5 files changed, 21 insertions(+), 340 deletions(-) delete mode 100644 include/xgboost/elementwise_metric.h diff --git a/include/xgboost/elementwise_metric.h b/include/xgboost/elementwise_metric.h deleted file mode 100644 index d78ca9511d0c..000000000000 --- a/include/xgboost/elementwise_metric.h +++ /dev/null @@ -1,324 +0,0 @@ -/* - * Copyright 2015-2019 by Contributors - */ - -#ifndef XGBOOST_ELEMENTWISE_METRIC_H -#define XGBOOST_ELEMENTWISE_METRIC_H - -#include -#include - -#include "../src/common/common.h" - -#if defined(XGBOOST_USE_CUDA) -#include -#include -#include -#include // thrust::plus<> - -#include "../src/common/device_helpers.cuh" -#endif // XGBOOST_USE_CUDA - -/*! - * \brief base class of element-wise evaluation - * \tparam Derived the name of subclass - */ -namespace xgboost { -namespace metric { - template - class ElementWiseMetricsReduction { - public: - explicit ElementWiseMetricsReduction(EvalRow policy) : - policy_(std::move(policy)) {} - - PackedReduceResult CpuReduceMetrics( - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) const { - size_t ndata = labels.Size(); - - const auto& h_labels = labels.HostVector(); - const auto& h_weights = weights.HostVector(); - const auto& h_preds = preds.HostVector(); - - bst_float residue_sum = 0; - bst_float weights_sum = 0; - -#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) - for (omp_ulong i = 0; i < ndata; ++i) { - const bst_float wt = h_weights.size() > 0 ? h_weights[i] : 1.0f; - residue_sum += policy_.EvalRow(h_labels[i], h_preds[i]) * wt; - weights_sum += wt; - } - PackedReduceResult res { residue_sum, weights_sum }; - return res; - } - -#if defined(XGBOOST_USE_CUDA) - - PackedReduceResult DeviceReduceMetrics( - GPUSet::GpuIdType device_id, - size_t device_index, - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) { - size_t n_data = preds.DeviceSize(device_id); - - thrust::counting_iterator begin(0); - thrust::counting_iterator end = begin + n_data; - - auto s_label = labels.DeviceSpan(device_id); - auto s_preds = preds.DeviceSpan(device_id); - auto s_weights = weights.DeviceSpan(device_id); - - bool const is_null_weight = weights.Size() == 0; - - auto d_policy = policy_; - - PackedReduceResult result = thrust::transform_reduce( - thrust::cuda::par(allocators_.at(device_index)), - begin, end, - [=] XGBOOST_DEVICE(size_t idx) { - bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; - - bst_float residue = d_policy.EvalRow(s_label[idx], s_preds[idx]); - residue *= weight; - return PackedReduceResult{ residue, weight }; - }, - PackedReduceResult(), - thrust::plus()); - - return result; - } - -#endif // XGBOOST_USE_CUDA - - PackedReduceResult Reduce( - GPUSet devices, - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) { - PackedReduceResult result; - - if (devices.IsEmpty()) { - result = CpuReduceMetrics(weights, labels, preds); - } -#if defined(XGBOOST_USE_CUDA) - else { // NOLINT - if (allocators_.size() != devices.Size()) { - allocators_.clear(); - allocators_.resize(devices.Size()); - } - preds.Reshard(devices); - labels.Reshard(devices); - weights.Reshard(devices); - std::vector res_per_device(devices.Size()); - -#pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) - for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { - dh::safe_cuda(cudaSetDevice(id)); - size_t index = devices.Index(id); - res_per_device.at(index) = - DeviceReduceMetrics(id, index, weights, labels, preds); - } - - for (auto const& res : res_per_device) { - result += res; - } - } -#endif // defined(XGBOOST_USE_CUDA) - return result; - } - - private: - EvalRow policy_; -#if defined(XGBOOST_USE_CUDA) - std::vector allocators_; -#endif // defined(XGBOOST_USE_CUDA) - }; - - template - class MetricsReduction { - public: - class PackedReduceResult { - double residue_sum_; - double weights_sum_; - friend MetricsReduction; - - public: - XGBOOST_DEVICE PackedReduceResult() : residue_sum_{0}, weights_sum_{0} {} - - XGBOOST_DEVICE PackedReduceResult(double residue, double weight) : - residue_sum_{residue}, weights_sum_{weight} {} - - XGBOOST_DEVICE - PackedReduceResult operator+(PackedReduceResult const &other) const { - return PackedReduceResult{residue_sum_ + other.residue_sum_, - weights_sum_ + other.weights_sum_}; - } - - double Residue() const { return residue_sum_; } - - double Weights() const { return weights_sum_; } - }; - - public: - explicit MetricsReduction(EvalRow policy) : - policy_(std::move(policy)) {} - - PackedReduceResult CpuReduceMetrics( - const HostDeviceVector &weights, - const HostDeviceVector &labels, - const HostDeviceVector &preds) const { - size_t ndata = labels.Size(); - - const auto &h_labels = labels.HostVector(); - const auto &h_weights = weights.HostVector(); - const auto &h_preds = preds.HostVector(); - - bst_float residue_sum = 0; - bst_float weights_sum = 0; - -#pragma omp parallel for reduction(+: residue_sum, weights_sum) schedule(static) - for (omp_ulong i = 0; i < ndata; ++i) { - const bst_float wt = h_weights.size() > 0 ? h_weights[i] : 1.0f; - residue_sum += policy_.EvalRow(h_labels[i], h_preds[i]) * wt; - weights_sum += wt; - } - PackedReduceResult res{residue_sum, weights_sum}; - return res; - } - -#if defined(XGBOOST_USE_CUDA) - - PackedReduceResult DeviceReduceMetrics( - GPUSet::GpuIdType device_id, - size_t device_index, - const HostDeviceVector& weights, - const HostDeviceVector& labels, - const HostDeviceVector& preds) { -size_t n_data = preds.DeviceSize(device_id); - -thrust::counting_iterator begin(0); -thrust::counting_iterator end = begin + n_data; - -auto s_label = labels.DeviceSpan(device_id); -auto s_preds = preds.DeviceSpan(device_id); -auto s_weights = weights.DeviceSpan(device_id); - -bool const is_null_weight = weights.Size() == 0; - -auto d_policy = policy_; - -PackedReduceResult result = thrust::transform_reduce( - thrust::cuda::par(allocators_.at(device_index)), - begin, end, - [=] XGBOOST_DEVICE(size_t idx) { - bst_float weight = is_null_weight ? 1.0f : s_weights[idx]; - - bst_float residue = d_policy.EvalRow(s_label[idx], s_preds[idx]); - residue *= weight; - return PackedReduceResult{ residue, weight }; - }, - PackedReduceResult(), - thrust::plus()); - -return result; -} - -#endif // XGBOOST_USE_CUDA - - PackedReduceResult Reduce( - GPUSet devices, - const HostDeviceVector &weights, - const HostDeviceVector &labels, - const HostDeviceVector &preds) { - PackedReduceResult result; - - if (devices.IsEmpty()) { - result = CpuReduceMetrics(weights, labels, preds); - } -#if defined(XGBOOST_USE_CUDA) - else { // NOLINT - if (allocators_.size() != devices.Size()) { - allocators_.clear(); - allocators_.resize(devices.Size()); - } - preds.Reshard(devices); - labels.Reshard(devices); - weights.Reshard(devices); - std::vector res_per_device(devices.Size()); - -#pragma omp parallel for schedule(static, 1) if (devices.Size() > 1) - for (GPUSet::GpuIdType id = *devices.begin(); id < *devices.end(); ++id) { - dh::safe_cuda(cudaSetDevice(id)); - size_t index = devices.Index(id); - res_per_device.at(index) = - DeviceReduceMetrics(id, index, weights, labels, preds); - } - - for (size_t i = 0; i < devices.Size(); ++i) { - result.residue_sum_ += res_per_device[i].residue_sum_; - result.weights_sum_ += res_per_device[i].weights_sum_; - } -} -#endif // defined(XGBOOST_USE_CUDA) - return result; - } - - private: - EvalRow policy_; -#if defined(XGBOOST_USE_CUDA) - std::vector allocators_; -#endif // defined(XGBOOST_USE_CUDA) - }; - - template - struct EvalEWiseBase : public Metric { - EvalEWiseBase() : policy_{}, reducer_{policy_} {} - - EvalEWiseBase(Policy& policy) : policy_{policy}, reducer_{policy_} {} - - explicit EvalEWiseBase(char const *policy_param) : - policy_{policy_param}, reducer_{policy_} {} - - void Configure( - const std::vector> &args) override { - param_.InitAllowUnknown(args); - } - - bst_float Eval(const HostDeviceVector &preds, - const MetaInfo &info, - bool distributed) override { - CHECK_NE(info.labels_.Size(), 0U) << "label set cannot be empty"; - CHECK_EQ(preds.Size(), info.labels_.Size()) - << "label and prediction size not match, " - << "hint: use merror or mlogloss for multi-class classification"; - const auto ndata = static_cast(info.labels_.Size()); - // Dealing with ndata < n_gpus. - GPUSet devices = GPUSet::All(param_.gpu_id, param_.n_gpus, ndata); - - auto result = - reducer_.Reduce(devices, info.weights_, info.labels_, preds); - - double dat[2]{result.Residue(), result.Weights()}; - if (distributed) { - rabit::Allreduce(dat, 2); - } - return Policy::GetFinal(dat[0], dat[1]); - } - - const char *Name() const override { - return policy_.Name(); - } - - private: - Policy policy_; - - MetricParam param_; - - MetricsReduction reducer_; - }; - } -} -#endif //XGBOOST_ELEMENTWISE_METRIC_H diff --git a/include/xgboost/metric/metric.h b/include/xgboost/metric/metric.h index d05ad7dd0c8d..79ef4d026f03 100644 --- a/include/xgboost/metric/metric.h +++ b/include/xgboost/metric/metric.h @@ -7,13 +7,14 @@ #define XGBOOST_METRIC_METRIC_H_ #include +#include +#include + #include #include #include #include -#include -#include #include "../../../src/common/host_device_vector.h" namespace xgboost { diff --git a/include/xgboost/metric/metric_common.h b/include/xgboost/metric/metric_common.h index da74a6340891..756fb4d8941e 100644 --- a/include/xgboost/metric/metric_common.h +++ b/include/xgboost/metric/metric_common.h @@ -1,8 +1,8 @@ /*! * Copyright 2019 by Contributors */ -#ifndef XGBOOST_METRIC_METRIC_PARAM_H_ -#define XGBOOST_METRIC_METRIC_PARAM_H_ +#ifndef XGBOOST_METRIC_METRIC_COMMON_H_ +#define XGBOOST_METRIC_METRIC_COMMON_H_ #include #include "../../../src/common/common.h" @@ -15,7 +15,6 @@ struct MetricParam : public dmlc::Parameter { int n_gpus; int gpu_id; DMLC_DECLARE_PARAMETER(MetricParam) { - DMLC_DECLARE_FIELD(n_gpus).set_default(1).set_lower_bound(GPUSet::kAll) .describe("Number of GPUs to use for multi-gpu algorithms."); DMLC_DECLARE_FIELD(gpu_id) @@ -52,4 +51,4 @@ class PackedReduceResult { } // namespace metric } // namespace xgboost -#endif // XGBOOST_METRIC_METRIC_PARAM_H_ +#endif // XGBOOST_METRIC_METRIC_COMMON_H_ diff --git a/include/xgboost/metric/multiclass_metric.h b/include/xgboost/metric/multiclass_metric.h index c321a1fe1a76..cabcf6707092 100644 --- a/include/xgboost/metric/multiclass_metric.h +++ b/include/xgboost/metric/multiclass_metric.h @@ -7,7 +7,20 @@ #include #include + +#include +#include #include +#include + +#if defined(XGBOOST_USE_CUDA) +#include // thrust::cuda::par +#include // thrust::plus<> +#include +#include + +#include "../common/device_helpers.cuh" +#endif // XGBOOST_USE_CUDA namespace xgboost { namespace metric { @@ -141,7 +154,7 @@ class MultiClassMetricsReduction { return result; } - private: + private: #if defined(XGBOOST_USE_CUDA) dh::PinnedMemory label_error_; std::vector allocators_; diff --git a/src/metric/multiclass_metric.cu b/src/metric/multiclass_metric.cu index 33617b03b59f..54d879923aea 100644 --- a/src/metric/multiclass_metric.cu +++ b/src/metric/multiclass_metric.cu @@ -7,20 +7,12 @@ #include #include #include +#include #include #include "../common/math.h" #include "../common/common.h" -#if defined(XGBOOST_USE_CUDA) -#include // thrust::cuda::par -#include // thrust::plus<> -#include -#include - -#include "../common/device_helpers.cuh" -#endif // XGBOOST_USE_CUDA - namespace xgboost { namespace metric { // tag the this file, used by force static link later. From 82e0ebbf0d4224adbd616b4feb3de1ade25dfb15 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 18 Apr 2019 20:32:20 -0700 Subject: [PATCH 37/39] change include path --- include/xgboost/metric/multiclass_metric.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xgboost/metric/multiclass_metric.h b/include/xgboost/metric/multiclass_metric.h index cabcf6707092..b847e56ff277 100644 --- a/include/xgboost/metric/multiclass_metric.h +++ b/include/xgboost/metric/multiclass_metric.h @@ -19,7 +19,7 @@ #include #include -#include "../common/device_helpers.cuh" +#include "../../../src/common/device_helpers.cuh" #endif // XGBOOST_USE_CUDA namespace xgboost { From 416d3af3f49a6509b917fe85f4a237cf71cf72c1 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 18 Apr 2019 20:43:16 -0700 Subject: [PATCH 38/39] change include path --- include/xgboost/metric/elementwise_metric.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xgboost/metric/elementwise_metric.h b/include/xgboost/metric/elementwise_metric.h index 4a2eb0022d7c..5f67e8db1794 100644 --- a/include/xgboost/metric/elementwise_metric.h +++ b/include/xgboost/metric/elementwise_metric.h @@ -21,7 +21,7 @@ #include #include // thrust::plus<> -#include "../src/common/device_helpers.cuh" +#include "../../../src/common/device_helpers.cuh" #endif // XGBOOST_USE_CUDA /*! From 69421425142a3e682df56886bc5ba0d4881ba4e6 Mon Sep 17 00:00:00 2001 From: Nan Zhu Date: Thu, 18 Apr 2019 21:30:14 -0700 Subject: [PATCH 39/39] fix mc_metrics.cc --- src/metric/multiclass_metric.cc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/metric/multiclass_metric.cc b/src/metric/multiclass_metric.cc index fb697389a7e2..6e721e3b68fd 100644 --- a/src/metric/multiclass_metric.cc +++ b/src/metric/multiclass_metric.cc @@ -3,12 +3,6 @@ */ // Dummy file to keep the CUDA conditional compile trick. - -#include -#include -#include -#include "../common/math.h" - #if !defined(XGBOOST_USE_CUDA) #include "multiclass_metric.cu" #endif // !defined(XGBOOST_USE_CUDA)